aboutsummaryrefslogtreecommitdiffhomepage
path: root/application/api
diff options
context:
space:
mode:
authorArthurHoaro <arthur@hoa.ro>2020-11-12 13:02:36 +0100
committerArthurHoaro <arthur@hoa.ro>2020-11-12 13:02:36 +0100
commit1409f1c89a7ca01456ae2dcd6357d296e2b99f5a (patch)
treeffa30a9358e82d27be75d8fc5e57f3c8820dc6d3 /application/api
parent054e03f37fa29da8066f1a637919f13c7e7dc5d2 (diff)
parenta6935feb22df8d9634189ee87d257da9f03eedbd (diff)
downloadShaarli-1409f1c89a7ca01456ae2dcd6357d296e2b99f5a.tar.gz
Shaarli-1409f1c89a7ca01456ae2dcd6357d296e2b99f5a.tar.zst
Shaarli-1409f1c89a7ca01456ae2dcd6357d296e2b99f5a.zip
Merge branch 'master' into v0.12v0.12.1v0.12
Diffstat (limited to 'application/api')
-rw-r--r--application/api/ApiMiddleware.php6
-rw-r--r--application/api/ApiUtils.php21
-rw-r--r--application/api/controllers/ApiController.php3
-rw-r--r--application/api/controllers/HistoryController.php1
-rw-r--r--application/api/controllers/Info.php4
-rw-r--r--application/api/controllers/Links.php31
-rw-r--r--application/api/exceptions/ApiAuthorizationException.php2
-rw-r--r--application/api/exceptions/ApiException.php2
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
2namespace Shaarli\Api; 3namespace Shaarli\Api;
3 4
5use malkusch\lock\mutex\FlockMutex;
4use Shaarli\Api\Exceptions\ApiAuthorizationException; 6use Shaarli\Api\Exceptions\ApiAuthorizationException;
5use Shaarli\Api\Exceptions\ApiException; 7use Shaarli\Api\Exceptions\ApiException;
6use Shaarli\Bookmark\BookmarkFileService; 8use 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
2namespace Shaarli\Api; 3namespace Shaarli\Api;
3 4
4use Shaarli\Api\Exceptions\ApiAuthorizationException; 5use 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
5use Shaarli\Bookmark\BookmarkServiceInterface; 5use Shaarli\Bookmark\BookmarkServiceInterface;
6use Shaarli\Config\ConfigManager; 6use Shaarli\Config\ConfigManager;
7use Shaarli\History;
7use Slim\Container; 8use 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
4namespace Shaarli\Api\Controllers; 3namespace Shaarli\Api\Controllers;
5 4
6use Shaarli\Api\Exceptions\ApiBadParametersException; 5use 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