]>
Commit | Line | Data |
---|---|---|
18e67967 | 1 | <?php |
18e67967 A |
2 | namespace Shaarli\Api; |
3 | ||
4 | use Shaarli\Api\Exceptions\ApiAuthorizationException; | |
dea72c71 | 5 | use Shaarli\Http\Base64Url; |
18e67967 A |
6 | |
7 | /** | |
7a9daac5 | 8 | * REST API utilities |
18e67967 A |
9 | */ |
10 | class ApiUtils | |
11 | { | |
12 | /** | |
13 | * Validates a JWT token authenticity. | |
14 | * | |
00af48d9 | 15 | * @param string $token JWT token extracted from the headers. |
18e67967 A |
16 | * @param string $secret API secret set in the settings. |
17 | * | |
def39d0d A |
18 | * @return bool true on success |
19 | * | |
18e67967 A |
20 | * @throws ApiAuthorizationException the token is not valid. |
21 | */ | |
22 | public static function validateJwtToken($token, $secret) | |
23 | { | |
24 | $parts = explode('.', $token); | |
25 | if (count($parts) != 3 || strlen($parts[0]) == 0 || strlen($parts[1]) == 0) { | |
26 | throw new ApiAuthorizationException('Malformed JWT token'); | |
27 | } | |
28 | ||
7a9daac5 | 29 | $genSign = Base64Url::encode(hash_hmac('sha512', $parts[0] .'.'. $parts[1], $secret, true)); |
18e67967 A |
30 | if ($parts[2] != $genSign) { |
31 | throw new ApiAuthorizationException('Invalid JWT signature'); | |
32 | } | |
33 | ||
7a9daac5 | 34 | $header = json_decode(Base64Url::decode($parts[0])); |
18e67967 A |
35 | if ($header === null) { |
36 | throw new ApiAuthorizationException('Invalid JWT header'); | |
37 | } | |
38 | ||
7a9daac5 | 39 | $payload = json_decode(Base64Url::decode($parts[1])); |
18e67967 A |
40 | if ($payload === null) { |
41 | throw new ApiAuthorizationException('Invalid JWT payload'); | |
42 | } | |
43 | ||
44 | if (empty($payload->iat) | |
45 | || $payload->iat > time() | |
46 | || time() - $payload->iat > ApiMiddleware::$TOKEN_DURATION | |
47 | ) { | |
48 | throw new ApiAuthorizationException('Invalid JWT issued time'); | |
49 | } | |
def39d0d A |
50 | |
51 | return true; | |
18e67967 | 52 | } |
c3b00963 A |
53 | |
54 | /** | |
55 | * Format a Link for the REST API. | |
56 | * | |
00af48d9 | 57 | * @param array $link Link data read from the datastore. |
c3b00963 A |
58 | * @param string $indexUrl Shaarli's index URL (used for relative URL). |
59 | * | |
60 | * @return array Link data formatted for the REST API. | |
61 | */ | |
62 | public static function formatLink($link, $indexUrl) | |
63 | { | |
64 | $out['id'] = $link['id']; | |
65 | // Not an internal link | |
a8e7da01 | 66 | if (! is_note($link['url'])) { |
c3b00963 A |
67 | $out['url'] = $link['url']; |
68 | } else { | |
69 | $out['url'] = $indexUrl . $link['url']; | |
70 | } | |
71 | $out['shorturl'] = $link['shorturl']; | |
72 | $out['title'] = $link['title']; | |
73 | $out['description'] = $link['description']; | |
74 | $out['tags'] = preg_split('/\s+/', $link['tags'], -1, PREG_SPLIT_NO_EMPTY); | |
75 | $out['private'] = $link['private'] == true; | |
76 | $out['created'] = $link['created']->format(\DateTime::ATOM); | |
77 | if (! empty($link['updated'])) { | |
78 | $out['updated'] = $link['updated']->format(\DateTime::ATOM); | |
79 | } else { | |
80 | $out['updated'] = ''; | |
81 | } | |
82 | return $out; | |
83 | } | |
68016e37 A |
84 | |
85 | /** | |
86 | * Convert a link given through a request, to a valid link for LinkDB. | |
87 | * | |
88 | * If no URL is provided, it will generate a local note URL. | |
89 | * If no title is provided, it will use the URL as title. | |
90 | * | |
91 | * @param array $input Request Link. | |
92 | * @param bool $defaultPrivate Request Link. | |
93 | * | |
94 | * @return array Formatted link. | |
95 | */ | |
96 | public static function buildLinkFromRequest($input, $defaultPrivate) | |
97 | { | |
98 | $input['url'] = ! empty($input['url']) ? cleanup_url($input['url']) : ''; | |
99 | if (isset($input['private'])) { | |
100 | $private = filter_var($input['private'], FILTER_VALIDATE_BOOLEAN); | |
101 | } else { | |
102 | $private = $defaultPrivate; | |
103 | } | |
104 | ||
105 | $link = [ | |
106 | 'title' => ! empty($input['title']) ? $input['title'] : $input['url'], | |
107 | 'url' => $input['url'], | |
108 | 'description' => ! empty($input['description']) ? $input['description'] : '', | |
109 | 'tags' => ! empty($input['tags']) ? implode(' ', $input['tags']) : '', | |
110 | 'private' => $private, | |
111 | 'created' => new \DateTime(), | |
112 | ]; | |
113 | return $link; | |
114 | } | |
cf9181dd A |
115 | |
116 | /** | |
117 | * Update link fields using an updated link object. | |
118 | * | |
119 | * @param array $oldLink data | |
120 | * @param array $newLink data | |
121 | * | |
122 | * @return array $oldLink updated with $newLink values | |
123 | */ | |
124 | public static function updateLink($oldLink, $newLink) | |
125 | { | |
126 | foreach (['title', 'url', 'description', 'tags', 'private'] as $field) { | |
127 | $oldLink[$field] = $newLink[$field]; | |
128 | } | |
129 | $oldLink['updated'] = new \DateTime(); | |
130 | ||
131 | if (empty($oldLink['url'])) { | |
132 | $oldLink['url'] = '?' . $oldLink['shorturl']; | |
133 | } | |
134 | ||
135 | if (empty($oldLink['title'])) { | |
136 | $oldLink['title'] = $oldLink['url']; | |
137 | } | |
138 | ||
139 | return $oldLink; | |
140 | } | |
d3f42ca4 A |
141 | |
142 | /** | |
143 | * Format a Tag for the REST API. | |
144 | * | |
145 | * @param string $tag Tag name | |
146 | * @param int $occurrences Number of links using this tag | |
147 | * | |
148 | * @return array Link data formatted for the REST API. | |
149 | */ | |
150 | public static function formatTag($tag, $occurences) | |
151 | { | |
152 | return [ | |
153 | 'name' => $tag, | |
154 | 'occurrences' => $occurences, | |
155 | ]; | |
156 | } | |
18e67967 | 157 | } |