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