diff options
author | ArthurHoaro <arthur@hoa.ro> | 2020-11-12 13:02:36 +0100 |
---|---|---|
committer | ArthurHoaro <arthur@hoa.ro> | 2020-11-12 13:02:36 +0100 |
commit | 1409f1c89a7ca01456ae2dcd6357d296e2b99f5a (patch) | |
tree | ffa30a9358e82d27be75d8fc5e57f3c8820dc6d3 /application/api | |
parent | 054e03f37fa29da8066f1a637919f13c7e7dc5d2 (diff) | |
parent | a6935feb22df8d9634189ee87d257da9f03eedbd (diff) | |
download | Shaarli-1409f1c89a7ca01456ae2dcd6357d296e2b99f5a.tar.gz Shaarli-1409f1c89a7ca01456ae2dcd6357d296e2b99f5a.tar.zst Shaarli-1409f1c89a7ca01456ae2dcd6357d296e2b99f5a.zip |
Diffstat (limited to 'application/api')
-rw-r--r-- | application/api/ApiMiddleware.php | 6 | ||||
-rw-r--r-- | application/api/ApiUtils.php | 21 | ||||
-rw-r--r-- | application/api/controllers/ApiController.php | 3 | ||||
-rw-r--r-- | application/api/controllers/HistoryController.php | 1 | ||||
-rw-r--r-- | application/api/controllers/Info.php | 4 | ||||
-rw-r--r-- | application/api/controllers/Links.php | 31 | ||||
-rw-r--r-- | application/api/exceptions/ApiAuthorizationException.php | 2 | ||||
-rw-r--r-- | application/api/exceptions/ApiException.php | 2 |
8 files changed, 45 insertions, 25 deletions
diff --git a/application/api/ApiMiddleware.php b/application/api/ApiMiddleware.php index f5b53b01..9fb88358 100644 --- a/application/api/ApiMiddleware.php +++ b/application/api/ApiMiddleware.php | |||
@@ -1,6 +1,8 @@ | |||
1 | <?php | 1 | <?php |
2 | |||
2 | namespace Shaarli\Api; | 3 | namespace Shaarli\Api; |
3 | 4 | ||
5 | use malkusch\lock\mutex\FlockMutex; | ||
4 | use Shaarli\Api\Exceptions\ApiAuthorizationException; | 6 | use Shaarli\Api\Exceptions\ApiAuthorizationException; |
5 | use Shaarli\Api\Exceptions\ApiException; | 7 | use Shaarli\Api\Exceptions\ApiException; |
6 | use Shaarli\Bookmark\BookmarkFileService; | 8 | use Shaarli\Bookmark\BookmarkFileService; |
@@ -107,7 +109,8 @@ class ApiMiddleware | |||
107 | */ | 109 | */ |
108 | protected function checkToken($request) | 110 | protected function checkToken($request) |
109 | { | 111 | { |
110 | if (!$request->hasHeader('Authorization') | 112 | if ( |
113 | !$request->hasHeader('Authorization') | ||
111 | && !isset($this->container->environment['REDIRECT_HTTP_AUTHORIZATION']) | 114 | && !isset($this->container->environment['REDIRECT_HTTP_AUTHORIZATION']) |
112 | ) { | 115 | ) { |
113 | throw new ApiAuthorizationException('JWT token not provided'); | 116 | throw new ApiAuthorizationException('JWT token not provided'); |
@@ -143,6 +146,7 @@ class ApiMiddleware | |||
143 | $linkDb = new BookmarkFileService( | 146 | $linkDb = new BookmarkFileService( |
144 | $conf, | 147 | $conf, |
145 | $this->container->get('history'), | 148 | $this->container->get('history'), |
149 | new FlockMutex(fopen(SHAARLI_MUTEX_FILE, 'r'), 2), | ||
146 | true | 150 | true |
147 | ); | 151 | ); |
148 | $this->container['db'] = $linkDb; | 152 | $this->container['db'] = $linkDb; |
diff --git a/application/api/ApiUtils.php b/application/api/ApiUtils.php index faebb8f5..05a2840a 100644 --- a/application/api/ApiUtils.php +++ b/application/api/ApiUtils.php | |||
@@ -1,4 +1,5 @@ | |||
1 | <?php | 1 | <?php |
2 | |||
2 | namespace Shaarli\Api; | 3 | namespace Shaarli\Api; |
3 | 4 | ||
4 | use Shaarli\Api\Exceptions\ApiAuthorizationException; | 5 | use Shaarli\Api\Exceptions\ApiAuthorizationException; |
@@ -27,7 +28,7 @@ class ApiUtils | |||
27 | throw new ApiAuthorizationException('Malformed JWT token'); | 28 | throw new ApiAuthorizationException('Malformed JWT token'); |
28 | } | 29 | } |
29 | 30 | ||
30 | $genSign = Base64Url::encode(hash_hmac('sha512', $parts[0] .'.'. $parts[1], $secret, true)); | 31 | $genSign = Base64Url::encode(hash_hmac('sha512', $parts[0] . '.' . $parts[1], $secret, true)); |
31 | if ($parts[2] != $genSign) { | 32 | if ($parts[2] != $genSign) { |
32 | throw new ApiAuthorizationException('Invalid JWT signature'); | 33 | throw new ApiAuthorizationException('Invalid JWT signature'); |
33 | } | 34 | } |
@@ -42,7 +43,8 @@ class ApiUtils | |||
42 | throw new ApiAuthorizationException('Invalid JWT payload'); | 43 | throw new ApiAuthorizationException('Invalid JWT payload'); |
43 | } | 44 | } |
44 | 45 | ||
45 | if (empty($payload->iat) | 46 | if ( |
47 | empty($payload->iat) | ||
46 | || $payload->iat > time() | 48 | || $payload->iat > time() |
47 | || time() - $payload->iat > ApiMiddleware::$TOKEN_DURATION | 49 | || time() - $payload->iat > ApiMiddleware::$TOKEN_DURATION |
48 | ) { | 50 | ) { |
@@ -89,12 +91,12 @@ class ApiUtils | |||
89 | * If no URL is provided, it will generate a local note URL. | 91 | * 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. | 92 | * If no title is provided, it will use the URL as title. |
91 | * | 93 | * |
92 | * @param array $input Request Link. | 94 | * @param array|null $input Request Link. |
93 | * @param bool $defaultPrivate Request Link. | 95 | * @param bool $defaultPrivate Setting defined if a bookmark is private by default. |
94 | * | 96 | * |
95 | * @return Bookmark instance. | 97 | * @return Bookmark instance. |
96 | */ | 98 | */ |
97 | public static function buildLinkFromRequest($input, $defaultPrivate) | 99 | public static function buildBookmarkFromRequest(?array $input, bool $defaultPrivate): Bookmark |
98 | { | 100 | { |
99 | $bookmark = new Bookmark(); | 101 | $bookmark = new Bookmark(); |
100 | $url = ! empty($input['url']) ? cleanup_url($input['url']) : ''; | 102 | $url = ! empty($input['url']) ? cleanup_url($input['url']) : ''; |
@@ -110,6 +112,15 @@ class ApiUtils | |||
110 | $bookmark->setTags(! empty($input['tags']) ? $input['tags'] : []); | 112 | $bookmark->setTags(! empty($input['tags']) ? $input['tags'] : []); |
111 | $bookmark->setPrivate($private); | 113 | $bookmark->setPrivate($private); |
112 | 114 | ||
115 | $created = \DateTime::createFromFormat(\DateTime::ATOM, $input['created'] ?? ''); | ||
116 | if ($created instanceof \DateTimeInterface) { | ||
117 | $bookmark->setCreated($created); | ||
118 | } | ||
119 | $updated = \DateTime::createFromFormat(\DateTime::ATOM, $input['updated'] ?? ''); | ||
120 | if ($updated instanceof \DateTimeInterface) { | ||
121 | $bookmark->setUpdated($updated); | ||
122 | } | ||
123 | |||
113 | return $bookmark; | 124 | return $bookmark; |
114 | } | 125 | } |
115 | 126 | ||
diff --git a/application/api/controllers/ApiController.php b/application/api/controllers/ApiController.php index c4b3d0c3..88a845eb 100644 --- a/application/api/controllers/ApiController.php +++ b/application/api/controllers/ApiController.php | |||
@@ -4,6 +4,7 @@ namespace Shaarli\Api\Controllers; | |||
4 | 4 | ||
5 | use Shaarli\Bookmark\BookmarkServiceInterface; | 5 | use Shaarli\Bookmark\BookmarkServiceInterface; |
6 | use Shaarli\Config\ConfigManager; | 6 | use Shaarli\Config\ConfigManager; |
7 | use Shaarli\History; | ||
7 | use Slim\Container; | 8 | use Slim\Container; |
8 | 9 | ||
9 | /** | 10 | /** |
@@ -31,7 +32,7 @@ abstract class ApiController | |||
31 | protected $bookmarkService; | 32 | protected $bookmarkService; |
32 | 33 | ||
33 | /** | 34 | /** |
34 | * @var HistoryController | 35 | * @var History |
35 | */ | 36 | */ |
36 | protected $history; | 37 | protected $history; |
37 | 38 | ||
diff --git a/application/api/controllers/HistoryController.php b/application/api/controllers/HistoryController.php index 505647a9..d83a3a25 100644 --- a/application/api/controllers/HistoryController.php +++ b/application/api/controllers/HistoryController.php | |||
@@ -1,6 +1,5 @@ | |||
1 | <?php | 1 | <?php |
2 | 2 | ||
3 | |||
4 | namespace Shaarli\Api\Controllers; | 3 | namespace Shaarli\Api\Controllers; |
5 | 4 | ||
6 | use Shaarli\Api\Exceptions\ApiBadParametersException; | 5 | use Shaarli\Api\Exceptions\ApiBadParametersException; |
diff --git a/application/api/controllers/Info.php b/application/api/controllers/Info.php index 12f6b2f0..ae7db93e 100644 --- a/application/api/controllers/Info.php +++ b/application/api/controllers/Info.php | |||
@@ -29,13 +29,13 @@ class Info extends ApiController | |||
29 | $info = [ | 29 | $info = [ |
30 | 'global_counter' => $this->bookmarkService->count(), | 30 | 'global_counter' => $this->bookmarkService->count(), |
31 | 'private_counter' => $this->bookmarkService->count(BookmarkFilter::$PRIVATE), | 31 | 'private_counter' => $this->bookmarkService->count(BookmarkFilter::$PRIVATE), |
32 | 'settings' => array( | 32 | 'settings' => [ |
33 | 'title' => $this->conf->get('general.title', 'Shaarli'), | 33 | 'title' => $this->conf->get('general.title', 'Shaarli'), |
34 | 'header_link' => $this->conf->get('general.header_link', '?'), | 34 | 'header_link' => $this->conf->get('general.header_link', '?'), |
35 | 'timezone' => $this->conf->get('general.timezone', 'UTC'), | 35 | 'timezone' => $this->conf->get('general.timezone', 'UTC'), |
36 | 'enabled_plugins' => $this->conf->get('general.enabled_plugins', []), | 36 | 'enabled_plugins' => $this->conf->get('general.enabled_plugins', []), |
37 | 'default_private_links' => $this->conf->get('privacy.default_private_links', false), | 37 | 'default_private_links' => $this->conf->get('privacy.default_private_links', false), |
38 | ), | 38 | ], |
39 | ]; | 39 | ]; |
40 | 40 | ||
41 | return $response->withJson($info, 200, $this->jsonStyle); | 41 | return $response->withJson($info, 200, $this->jsonStyle); |
diff --git a/application/api/controllers/Links.php b/application/api/controllers/Links.php index 29247950..c379b962 100644 --- a/application/api/controllers/Links.php +++ b/application/api/controllers/Links.php | |||
@@ -96,11 +96,12 @@ class Links extends ApiController | |||
96 | */ | 96 | */ |
97 | public function getLink($request, $response, $args) | 97 | public function getLink($request, $response, $args) |
98 | { | 98 | { |
99 | if (!$this->bookmarkService->exists($args['id'])) { | 99 | $id = is_integer_mixed($args['id']) ? (int) $args['id'] : null; |
100 | if ($id === null || ! $this->bookmarkService->exists($id)) { | ||
100 | throw new ApiLinkNotFoundException(); | 101 | throw new ApiLinkNotFoundException(); |
101 | } | 102 | } |
102 | $index = index_url($this->ci['environment']); | 103 | $index = index_url($this->ci['environment']); |
103 | $out = ApiUtils::formatLink($this->bookmarkService->get($args['id']), $index); | 104 | $out = ApiUtils::formatLink($this->bookmarkService->get($id), $index); |
104 | 105 | ||
105 | return $response->withJson($out, 200, $this->jsonStyle); | 106 | return $response->withJson($out, 200, $this->jsonStyle); |
106 | } | 107 | } |
@@ -115,10 +116,11 @@ class Links extends ApiController | |||
115 | */ | 116 | */ |
116 | public function postLink($request, $response) | 117 | public function postLink($request, $response) |
117 | { | 118 | { |
118 | $data = $request->getParsedBody(); | 119 | $data = (array) ($request->getParsedBody() ?? []); |
119 | $bookmark = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links')); | 120 | $bookmark = ApiUtils::buildBookmarkFromRequest($data, $this->conf->get('privacy.default_private_links')); |
120 | // duplicate by URL, return 409 Conflict | 121 | // duplicate by URL, return 409 Conflict |
121 | if (! empty($bookmark->getUrl()) | 122 | if ( |
123 | ! empty($bookmark->getUrl()) | ||
122 | && ! empty($dup = $this->bookmarkService->findByUrl($bookmark->getUrl())) | 124 | && ! empty($dup = $this->bookmarkService->findByUrl($bookmark->getUrl())) |
123 | ) { | 125 | ) { |
124 | return $response->withJson( | 126 | return $response->withJson( |
@@ -130,7 +132,7 @@ class Links extends ApiController | |||
130 | 132 | ||
131 | $this->bookmarkService->add($bookmark); | 133 | $this->bookmarkService->add($bookmark); |
132 | $out = ApiUtils::formatLink($bookmark, index_url($this->ci['environment'])); | 134 | $out = ApiUtils::formatLink($bookmark, index_url($this->ci['environment'])); |
133 | $redirect = $this->ci->router->relativePathFor('getLink', ['id' => $bookmark->getId()]); | 135 | $redirect = $this->ci->router->pathFor('getLink', ['id' => $bookmark->getId()]); |
134 | return $response->withAddedHeader('Location', $redirect) | 136 | return $response->withAddedHeader('Location', $redirect) |
135 | ->withJson($out, 201, $this->jsonStyle); | 137 | ->withJson($out, 201, $this->jsonStyle); |
136 | } | 138 | } |
@@ -148,18 +150,20 @@ class Links extends ApiController | |||
148 | */ | 150 | */ |
149 | public function putLink($request, $response, $args) | 151 | public function putLink($request, $response, $args) |
150 | { | 152 | { |
151 | if (! $this->bookmarkService->exists($args['id'])) { | 153 | $id = is_integer_mixed($args['id']) ? (int) $args['id'] : null; |
154 | if ($id === null || !$this->bookmarkService->exists($id)) { | ||
152 | throw new ApiLinkNotFoundException(); | 155 | throw new ApiLinkNotFoundException(); |
153 | } | 156 | } |
154 | 157 | ||
155 | $index = index_url($this->ci['environment']); | 158 | $index = index_url($this->ci['environment']); |
156 | $data = $request->getParsedBody(); | 159 | $data = $request->getParsedBody(); |
157 | 160 | ||
158 | $requestBookmark = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links')); | 161 | $requestBookmark = ApiUtils::buildBookmarkFromRequest($data, $this->conf->get('privacy.default_private_links')); |
159 | // duplicate URL on a different link, return 409 Conflict | 162 | // duplicate URL on a different link, return 409 Conflict |
160 | if (! empty($requestBookmark->getUrl()) | 163 | if ( |
164 | ! empty($requestBookmark->getUrl()) | ||
161 | && ! empty($dup = $this->bookmarkService->findByUrl($requestBookmark->getUrl())) | 165 | && ! empty($dup = $this->bookmarkService->findByUrl($requestBookmark->getUrl())) |
162 | && $dup->getId() != $args['id'] | 166 | && $dup->getId() != $id |
163 | ) { | 167 | ) { |
164 | return $response->withJson( | 168 | return $response->withJson( |
165 | ApiUtils::formatLink($dup, $index), | 169 | ApiUtils::formatLink($dup, $index), |
@@ -168,7 +172,7 @@ class Links extends ApiController | |||
168 | ); | 172 | ); |
169 | } | 173 | } |
170 | 174 | ||
171 | $responseBookmark = $this->bookmarkService->get($args['id']); | 175 | $responseBookmark = $this->bookmarkService->get($id); |
172 | $responseBookmark = ApiUtils::updateLink($responseBookmark, $requestBookmark); | 176 | $responseBookmark = ApiUtils::updateLink($responseBookmark, $requestBookmark); |
173 | $this->bookmarkService->set($responseBookmark); | 177 | $this->bookmarkService->set($responseBookmark); |
174 | 178 | ||
@@ -189,10 +193,11 @@ class Links extends ApiController | |||
189 | */ | 193 | */ |
190 | public function deleteLink($request, $response, $args) | 194 | public function deleteLink($request, $response, $args) |
191 | { | 195 | { |
192 | if (! $this->bookmarkService->exists($args['id'])) { | 196 | $id = is_integer_mixed($args['id']) ? (int) $args['id'] : null; |
197 | if ($id === null || !$this->bookmarkService->exists($id)) { | ||
193 | throw new ApiLinkNotFoundException(); | 198 | throw new ApiLinkNotFoundException(); |
194 | } | 199 | } |
195 | $bookmark = $this->bookmarkService->get($args['id']); | 200 | $bookmark = $this->bookmarkService->get($id); |
196 | $this->bookmarkService->remove($bookmark); | 201 | $this->bookmarkService->remove($bookmark); |
197 | 202 | ||
198 | return $response->withStatus(204); | 203 | return $response->withStatus(204); |
diff --git a/application/api/exceptions/ApiAuthorizationException.php b/application/api/exceptions/ApiAuthorizationException.php index 0e3f4776..c77e9eea 100644 --- a/application/api/exceptions/ApiAuthorizationException.php +++ b/application/api/exceptions/ApiAuthorizationException.php | |||
@@ -28,7 +28,7 @@ class ApiAuthorizationException extends ApiException | |||
28 | */ | 28 | */ |
29 | public function setMessage($message) | 29 | public function setMessage($message) |
30 | { | 30 | { |
31 | $original = $this->debug === true ? ': '. $this->getMessage() : ''; | 31 | $original = $this->debug === true ? ': ' . $this->getMessage() : ''; |
32 | $this->message = $message . $original; | 32 | $this->message = $message . $original; |
33 | } | 33 | } |
34 | } | 34 | } |
diff --git a/application/api/exceptions/ApiException.php b/application/api/exceptions/ApiException.php index d6b66323..7deafb96 100644 --- a/application/api/exceptions/ApiException.php +++ b/application/api/exceptions/ApiException.php | |||
@@ -44,7 +44,7 @@ abstract class ApiException extends \Exception | |||
44 | } | 44 | } |
45 | return [ | 45 | return [ |
46 | 'message' => $this->getMessage(), | 46 | 'message' => $this->getMessage(), |
47 | 'stacktrace' => get_class($this) .': '. $this->getTraceAsString() | 47 | 'stacktrace' => get_class($this) . ': ' . $this->getTraceAsString() |
48 | ]; | 48 | ]; |
49 | } | 49 | } |
50 | 50 | ||