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