aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--application/bookmark/BookmarkFileService.php7
-rw-r--r--application/bookmark/BookmarkServiceInterface.php5
-rw-r--r--application/front/controller/admin/ManageShaareController.php26
-rw-r--r--application/front/controller/visitor/BookmarkListController.php4
-rw-r--r--inc/languages/fr/LC_MESSAGES/shaarli.po40
-rw-r--r--index.php1
-rw-r--r--tests/bookmark/BookmarkFileServiceTest.php31
-rw-r--r--tests/front/controller/admin/ManageShaareControllerTest/SharePrivateTest.php139
-rw-r--r--tests/front/controller/visitor/BookmarkListControllerTest.php31
-rw-r--r--tpl/default/linklist.html7
10 files changed, 268 insertions, 23 deletions
diff --git a/application/bookmark/BookmarkFileService.php b/application/bookmark/BookmarkFileService.php
index eb7899bf..14b3d620 100644
--- a/application/bookmark/BookmarkFileService.php
+++ b/application/bookmark/BookmarkFileService.php
@@ -97,12 +97,15 @@ class BookmarkFileService implements BookmarkServiceInterface
97 /** 97 /**
98 * @inheritDoc 98 * @inheritDoc
99 */ 99 */
100 public function findByHash(string $hash): Bookmark 100 public function findByHash(string $hash, string $privateKey = null): Bookmark
101 { 101 {
102 $bookmark = $this->bookmarkFilter->filter(BookmarkFilter::$FILTER_HASH, $hash); 102 $bookmark = $this->bookmarkFilter->filter(BookmarkFilter::$FILTER_HASH, $hash);
103 // PHP 7.3 introduced array_key_first() to avoid this hack 103 // PHP 7.3 introduced array_key_first() to avoid this hack
104 $first = reset($bookmark); 104 $first = reset($bookmark);
105 if (! $this->isLoggedIn && $first->isPrivate()) { 105 if (!$this->isLoggedIn
106 && $first->isPrivate()
107 && (empty($privateKey) || $privateKey !== $first->getAdditionalContentEntry('private_key'))
108 ) {
106 throw new Exception('Not authorized'); 109 throw new Exception('Not authorized');
107 } 110 }
108 111
diff --git a/application/bookmark/BookmarkServiceInterface.php b/application/bookmark/BookmarkServiceInterface.php
index 37a54d03..9fa61533 100644
--- a/application/bookmark/BookmarkServiceInterface.php
+++ b/application/bookmark/BookmarkServiceInterface.php
@@ -20,13 +20,14 @@ interface BookmarkServiceInterface
20 /** 20 /**
21 * Find a bookmark by hash 21 * Find a bookmark by hash
22 * 22 *
23 * @param string $hash 23 * @param string $hash Bookmark's hash
24 * @param string|null $privateKey Optional key used to access private links while logged out
24 * 25 *
25 * @return Bookmark 26 * @return Bookmark
26 * 27 *
27 * @throws \Exception 28 * @throws \Exception
28 */ 29 */
29 public function findByHash(string $hash): Bookmark; 30 public function findByHash(string $hash, string $privateKey = null);
30 31
31 /** 32 /**
32 * @param $url 33 * @param $url
diff --git a/application/front/controller/admin/ManageShaareController.php b/application/front/controller/admin/ManageShaareController.php
index 908ebae3..e490f85a 100644
--- a/application/front/controller/admin/ManageShaareController.php
+++ b/application/front/controller/admin/ManageShaareController.php
@@ -321,6 +321,32 @@ class ManageShaareController extends ShaarliAdminController
321 } 321 }
322 322
323 /** 323 /**
324 * GET /admin/shaare/private/{hash} - Attach a private key to given bookmark, then redirect to the sharing URL.
325 */
326 public function sharePrivate(Request $request, Response $response, array $args): Response
327 {
328 $this->checkToken($request);
329
330 $hash = $args['hash'] ?? '';
331 $bookmark = $this->container->bookmarkService->findByHash($hash);
332
333 if ($bookmark->isPrivate() !== true) {
334 return $this->redirect($response, '/shaare/' . $hash);
335 }
336
337 if (empty($bookmark->getAdditionalContentEntry('private_key'))) {
338 $privateKey = bin2hex(random_bytes(16));
339 $bookmark->addAdditionalContentEntry('private_key', $privateKey);
340 $this->container->bookmarkService->set($bookmark);
341 }
342
343 return $this->redirect(
344 $response,
345 '/shaare/' . $hash . '?key=' . $bookmark->getAdditionalContentEntry('private_key')
346 );
347 }
348
349 /**
324 * Helper function used to display the shaare form whether it's a new or existing bookmark. 350 * Helper function used to display the shaare form whether it's a new or existing bookmark.
325 * 351 *
326 * @param array $link data used in template, either from parameters or from the data store 352 * @param array $link data used in template, either from parameters or from the data store
diff --git a/application/front/controller/visitor/BookmarkListController.php b/application/front/controller/visitor/BookmarkListController.php
index 5267c8f5..78c474c9 100644
--- a/application/front/controller/visitor/BookmarkListController.php
+++ b/application/front/controller/visitor/BookmarkListController.php
@@ -137,8 +137,10 @@ class BookmarkListController extends ShaarliVisitorController
137 */ 137 */
138 public function permalink(Request $request, Response $response, array $args): Response 138 public function permalink(Request $request, Response $response, array $args): Response
139 { 139 {
140 $privateKey = $request->getParam('key');
141
140 try { 142 try {
141 $bookmark = $this->container->bookmarkService->findByHash($args['hash']); 143 $bookmark = $this->container->bookmarkService->findByHash($args['hash'], $privateKey);
142 } catch (BookmarkNotFoundException $e) { 144 } catch (BookmarkNotFoundException $e) {
143 $this->assignView('error_message', $e->getMessage()); 145 $this->assignView('error_message', $e->getMessage());
144 146
diff --git a/inc/languages/fr/LC_MESSAGES/shaarli.po b/inc/languages/fr/LC_MESSAGES/shaarli.po
index db6bfa3e..3f14d22c 100644
--- a/inc/languages/fr/LC_MESSAGES/shaarli.po
+++ b/inc/languages/fr/LC_MESSAGES/shaarli.po
@@ -1,8 +1,8 @@
1msgid "" 1msgid ""
2msgstr "" 2msgstr ""
3"Project-Id-Version: Shaarli\n" 3"Project-Id-Version: Shaarli\n"
4"POT-Creation-Date: 2020-10-21 15:00+0200\n" 4"POT-Creation-Date: 2020-10-27 19:32+0100\n"
5"PO-Revision-Date: 2020-10-21 15:06+0200\n" 5"PO-Revision-Date: 2020-10-27 19:32+0100\n"
6"Last-Translator: \n" 6"Last-Translator: \n"
7"Language-Team: Shaarli\n" 7"Language-Team: Shaarli\n"
8"Language: fr_FR\n" 8"Language: fr_FR\n"
@@ -123,38 +123,38 @@ msgstr ""
123"l'extension php-gd doit être chargée pour utiliser les miniatures. Les " 123"l'extension php-gd doit être chargée pour utiliser les miniatures. Les "
124"miniatures sont désormais désactivées. Rechargez la page." 124"miniatures sont désormais désactivées. Rechargez la page."
125 125
126#: application/Utils.php:383 126#: application/Utils.php:385
127msgid "Setting not set" 127msgid "Setting not set"
128msgstr "Paramètre non défini" 128msgstr "Paramètre non défini"
129 129
130#: application/Utils.php:390 130#: application/Utils.php:392
131msgid "Unlimited" 131msgid "Unlimited"
132msgstr "Illimité" 132msgstr "Illimité"
133 133
134#: application/Utils.php:393 134#: application/Utils.php:395
135msgid "B" 135msgid "B"
136msgstr "o" 136msgstr "o"
137 137
138#: application/Utils.php:393 138#: application/Utils.php:395
139msgid "kiB" 139msgid "kiB"
140msgstr "ko" 140msgstr "ko"
141 141
142#: application/Utils.php:393 142#: application/Utils.php:395
143msgid "MiB" 143msgid "MiB"
144msgstr "Mo" 144msgstr "Mo"
145 145
146#: application/Utils.php:393 146#: application/Utils.php:395
147msgid "GiB" 147msgid "GiB"
148msgstr "Go" 148msgstr "Go"
149 149
150#: application/bookmark/BookmarkFileService.php:180 150#: application/bookmark/BookmarkFileService.php:183
151#: application/bookmark/BookmarkFileService.php:202 151#: application/bookmark/BookmarkFileService.php:205
152#: application/bookmark/BookmarkFileService.php:224 152#: application/bookmark/BookmarkFileService.php:227
153#: application/bookmark/BookmarkFileService.php:238 153#: application/bookmark/BookmarkFileService.php:241
154msgid "You're not authorized to alter the datastore" 154msgid "You're not authorized to alter the datastore"
155msgstr "Vous n'êtes pas autorisé à modifier les données" 155msgstr "Vous n'êtes pas autorisé à modifier les données"
156 156
157#: application/bookmark/BookmarkFileService.php:205 157#: application/bookmark/BookmarkFileService.php:208
158msgid "This bookmarks already exists" 158msgid "This bookmarks already exists"
159msgstr "Ce marque-page existe déjà." 159msgstr "Ce marque-page existe déjà."
160 160
@@ -439,12 +439,12 @@ msgstr "ID du lien non valide."
439msgid "Invalid visibility provided." 439msgid "Invalid visibility provided."
440msgstr "Visibilité du lien non valide." 440msgstr "Visibilité du lien non valide."
441 441
442#: application/front/controller/admin/ManageShaareController.php:352 442#: application/front/controller/admin/ManageShaareController.php:378
443#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171 443#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171
444msgid "Edit" 444msgid "Edit"
445msgstr "Modifier" 445msgstr "Modifier"
446 446
447#: application/front/controller/admin/ManageShaareController.php:355 447#: application/front/controller/admin/ManageShaareController.php:381
448#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28 448#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
449#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:28 449#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:28
450msgid "Shaare" 450msgid "Shaare"
@@ -551,7 +551,7 @@ msgstr "Hier"
551msgid "Daily" 551msgid "Daily"
552msgstr "Quotidien" 552msgstr "Quotidien"
553 553
554#: application/front/controller/visitor/ErrorController.php:36 554#: application/front/controller/visitor/ErrorController.php:33
555msgid "An unexpected error occurred." 555msgid "An unexpected error occurred."
556msgstr "Une erreur inattendue s'est produite." 556msgstr "Une erreur inattendue s'est produite."
557 557
@@ -604,7 +604,7 @@ msgstr "Permissions insuffisantes :"
604msgid "Login" 604msgid "Login"
605msgstr "Connexion" 605msgstr "Connexion"
606 606
607#: application/front/controller/visitor/LoginController.php:78 607#: application/front/controller/visitor/LoginController.php:77
608msgid "Wrong login/password." 608msgid "Wrong login/password."
609msgstr "Nom d'utilisateur ou mot de passe incorrect(s)." 609msgstr "Nom d'utilisateur ou mot de passe incorrect(s)."
610 610
@@ -738,7 +738,7 @@ msgstr "Impossible de purger %s : le répertoire n'existe pas"
738msgid "An error occurred while running the update " 738msgid "An error occurred while running the update "
739msgstr "Une erreur s'est produite lors de l'exécution de la mise à jour " 739msgstr "Une erreur s'est produite lors de l'exécution de la mise à jour "
740 740
741#: index.php:65 741#: index.php:80
742msgid "Shared bookmarks on " 742msgid "Shared bookmarks on "
743msgstr "Liens partagés sur " 743msgstr "Liens partagés sur "
744 744
@@ -1376,6 +1376,10 @@ msgstr "Changer statut épinglé"
1376msgid "Sticky" 1376msgid "Sticky"
1377msgstr "Épinglé" 1377msgstr "Épinglé"
1378 1378
1379#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:189
1380msgid "Share a private link"
1381msgstr "Partager un lien privé"
1382
1379#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:5 1383#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:5
1380#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:5 1384#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:5
1381msgid "Filters" 1385msgid "Filters"
diff --git a/index.php b/index.php
index a46e32c9..0ed52bad 100644
--- a/index.php
+++ b/index.php
@@ -128,6 +128,7 @@ $app->group('/admin', function () {
128 $this->get('/add-shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:addShaare'); 128 $this->get('/add-shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:addShaare');
129 $this->get('/shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:displayCreateForm'); 129 $this->get('/shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:displayCreateForm');
130 $this->get('/shaare/{id:[0-9]+}', '\Shaarli\Front\Controller\Admin\ManageShaareController:displayEditForm'); 130 $this->get('/shaare/{id:[0-9]+}', '\Shaarli\Front\Controller\Admin\ManageShaareController:displayEditForm');
131 $this->get('/shaare/private/{hash}', '\Shaarli\Front\Controller\Admin\ManageShaareController:sharePrivate');
131 $this->post('/shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:save'); 132 $this->post('/shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:save');
132 $this->get('/shaare/delete', '\Shaarli\Front\Controller\Admin\ManageShaareController:deleteBookmark'); 133 $this->get('/shaare/delete', '\Shaarli\Front\Controller\Admin\ManageShaareController:deleteBookmark');
133 $this->get('/shaare/visibility', '\Shaarli\Front\Controller\Admin\ManageShaareController:changeVisibility'); 134 $this->get('/shaare/visibility', '\Shaarli\Front\Controller\Admin\ManageShaareController:changeVisibility');
diff --git a/tests/bookmark/BookmarkFileServiceTest.php b/tests/bookmark/BookmarkFileServiceTest.php
index daafd250..47970117 100644
--- a/tests/bookmark/BookmarkFileServiceTest.php
+++ b/tests/bookmark/BookmarkFileServiceTest.php
@@ -898,6 +898,37 @@ class BookmarkFileServiceTest extends TestCase
898 } 898 }
899 899
900 /** 900 /**
901 * Test filterHash() on a private bookmark while logged out.
902 */
903 public function testFilterHashPrivateWhileLoggedOut()
904 {
905 $this->expectException(\Exception::class);
906 $this->expectExceptionMessage('Not authorized');
907
908 $hash = smallHash('20141125_084734' . 6);
909
910 $this->publicLinkDB->findByHash($hash);
911 }
912
913 /**
914 * Test filterHash() with private key.
915 */
916 public function testFilterHashWithPrivateKey()
917 {
918 $hash = smallHash('20141125_084734' . 6);
919 $privateKey = 'this is usually auto generated';
920
921 $bookmark = $this->privateLinkDB->findByHash($hash);
922 $bookmark->addAdditionalContentEntry('private_key', $privateKey);
923 $this->privateLinkDB->save();
924
925 $this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, false);
926 $bookmark = $this->privateLinkDB->findByHash($hash, $privateKey);
927
928 static::assertSame(6, $bookmark->getId());
929 }
930
931 /**
901 * Test linksCountPerTag all tags without filter. 932 * Test linksCountPerTag all tags without filter.
902 * Equal occurrences should be sorted alphabetically. 933 * Equal occurrences should be sorted alphabetically.
903 */ 934 */
diff --git a/tests/front/controller/admin/ManageShaareControllerTest/SharePrivateTest.php b/tests/front/controller/admin/ManageShaareControllerTest/SharePrivateTest.php
new file mode 100644
index 00000000..1e7877c7
--- /dev/null
+++ b/tests/front/controller/admin/ManageShaareControllerTest/SharePrivateTest.php
@@ -0,0 +1,139 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest;
6
7use Shaarli\Bookmark\Bookmark;
8use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper;
9use Shaarli\Front\Controller\Admin\ManageShaareController;
10use Shaarli\Http\HttpAccess;
11use Shaarli\TestCase;
12use Slim\Http\Request;
13use Slim\Http\Response;
14
15/**
16 * Test GET /admin/shaare/private/{hash}
17 */
18class SharePrivateTest extends TestCase
19{
20 use FrontAdminControllerMockHelper;
21
22 /** @var ManageShaareController */
23 protected $controller;
24
25 public function setUp(): void
26 {
27 $this->createContainer();
28
29 $this->container->httpAccess = $this->createMock(HttpAccess::class);
30 $this->controller = new ManageShaareController($this->container);
31 }
32
33 /**
34 * Test shaare private with a private bookmark which does not have a key yet.
35 */
36 public function testSharePrivateWithNewPrivateBookmark(): void
37 {
38 $hash = 'abcdcef';
39 $request = $this->createMock(Request::class);
40 $response = new Response();
41
42 $bookmark = (new Bookmark())
43 ->setId(123)
44 ->setUrl('http://domain.tld')
45 ->setTitle('Title 123')
46 ->setPrivate(true)
47 ;
48
49 $this->container->bookmarkService
50 ->expects(static::once())
51 ->method('findByHash')
52 ->with($hash)
53 ->willReturn($bookmark)
54 ;
55 $this->container->bookmarkService
56 ->expects(static::once())
57 ->method('set')
58 ->with($bookmark, true)
59 ->willReturnCallback(function (Bookmark $bookmark): Bookmark {
60 static::assertSame(32, strlen($bookmark->getAdditionalContentEntry('private_key')));
61
62 return $bookmark;
63 })
64 ;
65
66 $result = $this->controller->sharePrivate($request, $response, ['hash' => $hash]);
67
68 static::assertSame(302, $result->getStatusCode());
69 static::assertRegExp('#/subfolder/shaare/' . $hash . '\?key=\w{32}#', $result->getHeaderLine('Location'));
70 }
71
72 /**
73 * Test shaare private with a private bookmark which does already have a key.
74 */
75 public function testSharePrivateWithExistingPrivateBookmark(): void
76 {
77 $hash = 'abcdcef';
78 $existingKey = 'this is a private key';
79 $request = $this->createMock(Request::class);
80 $response = new Response();
81
82 $bookmark = (new Bookmark())
83 ->setId(123)
84 ->setUrl('http://domain.tld')
85 ->setTitle('Title 123')
86 ->setPrivate(true)
87 ->addAdditionalContentEntry('private_key', $existingKey)
88 ;
89
90 $this->container->bookmarkService
91 ->expects(static::once())
92 ->method('findByHash')
93 ->with($hash)
94 ->willReturn($bookmark)
95 ;
96 $this->container->bookmarkService
97 ->expects(static::never())
98 ->method('set')
99 ;
100
101 $result = $this->controller->sharePrivate($request, $response, ['hash' => $hash]);
102
103 static::assertSame(302, $result->getStatusCode());
104 static::assertSame('/subfolder/shaare/' . $hash . '?key=' . $existingKey, $result->getHeaderLine('Location'));
105 }
106
107 /**
108 * Test shaare private with a public bookmark.
109 */
110 public function testSharePrivateWithPublicBookmark(): void
111 {
112 $hash = 'abcdcef';
113 $request = $this->createMock(Request::class);
114 $response = new Response();
115
116 $bookmark = (new Bookmark())
117 ->setId(123)
118 ->setUrl('http://domain.tld')
119 ->setTitle('Title 123')
120 ->setPrivate(false)
121 ;
122
123 $this->container->bookmarkService
124 ->expects(static::once())
125 ->method('findByHash')
126 ->with($hash)
127 ->willReturn($bookmark)
128 ;
129 $this->container->bookmarkService
130 ->expects(static::never())
131 ->method('set')
132 ;
133
134 $result = $this->controller->sharePrivate($request, $response, ['hash' => $hash]);
135
136 static::assertSame(302, $result->getStatusCode());
137 static::assertSame('/subfolder/shaare/' . $hash, $result->getHeaderLine('Location'));
138 }
139}
diff --git a/tests/front/controller/visitor/BookmarkListControllerTest.php b/tests/front/controller/visitor/BookmarkListControllerTest.php
index 5ca92507..5cbc8c73 100644
--- a/tests/front/controller/visitor/BookmarkListControllerTest.php
+++ b/tests/front/controller/visitor/BookmarkListControllerTest.php
@@ -292,6 +292,37 @@ class BookmarkListControllerTest extends TestCase
292 } 292 }
293 293
294 /** 294 /**
295 * Test GET /shaare/{hash}?key={key} - Find a link by hash using a private link.
296 */
297 public function testPermalinkWithPrivateKey(): void
298 {
299 $hash = 'abcdef';
300 $privateKey = 'this is a private key';
301
302 $assignedVariables = [];
303 $this->assignTemplateVars($assignedVariables);
304
305 $request = $this->createMock(Request::class);
306 $request->method('getParam')->willReturnCallback(function (string $key, $default = null) use ($privateKey) {
307 return $key === 'key' ? $privateKey : $default;
308 });
309 $response = new Response();
310
311 $this->container->bookmarkService
312 ->expects(static::once())
313 ->method('findByHash')
314 ->with($hash, $privateKey)
315 ->willReturn((new Bookmark())->setId(123)->setTitle('Title 1')->setUrl('http://url1.tld'))
316 ;
317
318 $result = $this->controller->permalink($request, $response, ['hash' => $hash]);
319
320 static::assertSame(200, $result->getStatusCode());
321 static::assertSame('linklist', (string) $result->getBody());
322 static::assertCount(1, $assignedVariables['links']);
323 }
324
325 /**
295 * Test getting link list with thumbnail updates. 326 * Test getting link list with thumbnail updates.
296 * -> 2 thumbnails update, only 1 datastore write 327 * -> 2 thumbnails update, only 1 datastore write
297 */ 328 */
diff --git a/tpl/default/linklist.html b/tpl/default/linklist.html
index 48cd9aad..e1115d49 100644
--- a/tpl/default/linklist.html
+++ b/tpl/default/linklist.html
@@ -129,6 +129,7 @@
129 {$strAddTag=t('Add tag')} 129 {$strAddTag=t('Add tag')}
130 {$strToggleSticky=t('Toggle sticky')} 130 {$strToggleSticky=t('Toggle sticky')}
131 {$strSticky=t('Sticky')} 131 {$strSticky=t('Sticky')}
132 {$strShaarePrivate=t('Share a private link')}
132 {ignore}End of translations{/ignore} 133 {ignore}End of translations{/ignore}
133 {loop="links"} 134 {loop="links"}
134 <div class="anchor" id="{$value.shorturl}"></div> 135 <div class="anchor" id="{$value.shorturl}"></div>
@@ -241,6 +242,12 @@
241 {$strPermalinkLc} 242 {$strPermalinkLc}
242 </a> 243 </a>
243 244
245 {if="$is_logged_in && $value.private"}
246 <a href="{$base_path}/admin/shaare/private/{$value.shorturl}?token={$token}" title="{$strShaarePrivate}">
247 <i class="fa fa-share-alt"></i>
248 </a>
249 {/if}
250
244 <div class="pure-u-0 pure-u-lg-visible"> 251 <div class="pure-u-0 pure-u-lg-visible">
245 {if="isset($value.link_plugin)"} 252 {if="isset($value.link_plugin)"}
246 &middot; 253 &middot;