diff options
author | ArthurHoaro <arthur@hoa.ro> | 2020-10-16 20:17:08 +0200 |
---|---|---|
committer | ArthurHoaro <arthur@hoa.ro> | 2020-10-27 19:32:57 +0100 |
commit | 9c04921a8c28c18ef757f2d43ba35e7e2a7f1a4b (patch) | |
tree | bcc2cb0dbad3ea27c38e676a20f3a377b50e9066 | |
parent | e6215a2ad97182efcf88ef532ec6bd65ae35fd19 (diff) | |
download | Shaarli-9c04921a8c28c18ef757f2d43ba35e7e2a7f1a4b.tar.gz Shaarli-9c04921a8c28c18ef757f2d43ba35e7e2a7f1a4b.tar.zst Shaarli-9c04921a8c28c18ef757f2d43ba35e7e2a7f1a4b.zip |
Feature: Share private bookmarks using a URL containing a private key
- Add a share link next to « Permalink » in linklist (using share icon
from fork awesome)
- This link generates a private key associated to the bookmark
- Accessing the bookmark while logged out with the proper key will
display it
Fixes #475
-rw-r--r-- | application/bookmark/BookmarkFileService.php | 7 | ||||
-rw-r--r-- | application/bookmark/BookmarkServiceInterface.php | 5 | ||||
-rw-r--r-- | application/front/controller/admin/ManageShaareController.php | 26 | ||||
-rw-r--r-- | application/front/controller/visitor/BookmarkListController.php | 4 | ||||
-rw-r--r-- | inc/languages/fr/LC_MESSAGES/shaarli.po | 40 | ||||
-rw-r--r-- | index.php | 1 | ||||
-rw-r--r-- | tests/bookmark/BookmarkFileServiceTest.php | 31 | ||||
-rw-r--r-- | tests/front/controller/admin/ManageShaareControllerTest/SharePrivateTest.php | 139 | ||||
-rw-r--r-- | tests/front/controller/visitor/BookmarkListControllerTest.php | 31 | ||||
-rw-r--r-- | tpl/default/linklist.html | 7 |
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 @@ | |||
1 | msgid "" | 1 | msgid "" |
2 | msgstr "" | 2 | msgstr "" |
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 |
127 | msgid "Setting not set" | 127 | msgid "Setting not set" |
128 | msgstr "Paramètre non défini" | 128 | msgstr "Paramètre non défini" |
129 | 129 | ||
130 | #: application/Utils.php:390 | 130 | #: application/Utils.php:392 |
131 | msgid "Unlimited" | 131 | msgid "Unlimited" |
132 | msgstr "Illimité" | 132 | msgstr "Illimité" |
133 | 133 | ||
134 | #: application/Utils.php:393 | 134 | #: application/Utils.php:395 |
135 | msgid "B" | 135 | msgid "B" |
136 | msgstr "o" | 136 | msgstr "o" |
137 | 137 | ||
138 | #: application/Utils.php:393 | 138 | #: application/Utils.php:395 |
139 | msgid "kiB" | 139 | msgid "kiB" |
140 | msgstr "ko" | 140 | msgstr "ko" |
141 | 141 | ||
142 | #: application/Utils.php:393 | 142 | #: application/Utils.php:395 |
143 | msgid "MiB" | 143 | msgid "MiB" |
144 | msgstr "Mo" | 144 | msgstr "Mo" |
145 | 145 | ||
146 | #: application/Utils.php:393 | 146 | #: application/Utils.php:395 |
147 | msgid "GiB" | 147 | msgid "GiB" |
148 | msgstr "Go" | 148 | msgstr "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 |
154 | msgid "You're not authorized to alter the datastore" | 154 | msgid "You're not authorized to alter the datastore" |
155 | msgstr "Vous n'êtes pas autorisé à modifier les données" | 155 | msgstr "Vous n'êtes pas autorisé à modifier les données" |
156 | 156 | ||
157 | #: application/bookmark/BookmarkFileService.php:205 | 157 | #: application/bookmark/BookmarkFileService.php:208 |
158 | msgid "This bookmarks already exists" | 158 | msgid "This bookmarks already exists" |
159 | msgstr "Ce marque-page existe déjà." | 159 | msgstr "Ce marque-page existe déjà." |
160 | 160 | ||
@@ -439,12 +439,12 @@ msgstr "ID du lien non valide." | |||
439 | msgid "Invalid visibility provided." | 439 | msgid "Invalid visibility provided." |
440 | msgstr "Visibilité du lien non valide." | 440 | msgstr "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 |
444 | msgid "Edit" | 444 | msgid "Edit" |
445 | msgstr "Modifier" | 445 | msgstr "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 |
450 | msgid "Shaare" | 450 | msgid "Shaare" |
@@ -551,7 +551,7 @@ msgstr "Hier" | |||
551 | msgid "Daily" | 551 | msgid "Daily" |
552 | msgstr "Quotidien" | 552 | msgstr "Quotidien" |
553 | 553 | ||
554 | #: application/front/controller/visitor/ErrorController.php:36 | 554 | #: application/front/controller/visitor/ErrorController.php:33 |
555 | msgid "An unexpected error occurred." | 555 | msgid "An unexpected error occurred." |
556 | msgstr "Une erreur inattendue s'est produite." | 556 | msgstr "Une erreur inattendue s'est produite." |
557 | 557 | ||
@@ -604,7 +604,7 @@ msgstr "Permissions insuffisantes :" | |||
604 | msgid "Login" | 604 | msgid "Login" |
605 | msgstr "Connexion" | 605 | msgstr "Connexion" |
606 | 606 | ||
607 | #: application/front/controller/visitor/LoginController.php:78 | 607 | #: application/front/controller/visitor/LoginController.php:77 |
608 | msgid "Wrong login/password." | 608 | msgid "Wrong login/password." |
609 | msgstr "Nom d'utilisateur ou mot de passe incorrect(s)." | 609 | msgstr "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" | |||
738 | msgid "An error occurred while running the update " | 738 | msgid "An error occurred while running the update " |
739 | msgstr "Une erreur s'est produite lors de l'exécution de la mise à jour " | 739 | msgstr "Une erreur s'est produite lors de l'exécution de la mise à jour " |
740 | 740 | ||
741 | #: index.php:65 | 741 | #: index.php:80 |
742 | msgid "Shared bookmarks on " | 742 | msgid "Shared bookmarks on " |
743 | msgstr "Liens partagés sur " | 743 | msgstr "Liens partagés sur " |
744 | 744 | ||
@@ -1376,6 +1376,10 @@ msgstr "Changer statut épinglé" | |||
1376 | msgid "Sticky" | 1376 | msgid "Sticky" |
1377 | msgstr "Épinglé" | 1377 | msgstr "Épinglé" |
1378 | 1378 | ||
1379 | #: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:189 | ||
1380 | msgid "Share a private link" | ||
1381 | msgstr "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 |
1381 | msgid "Filters" | 1385 | msgid "Filters" |
@@ -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 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest; | ||
6 | |||
7 | use Shaarli\Bookmark\Bookmark; | ||
8 | use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper; | ||
9 | use Shaarli\Front\Controller\Admin\ManageShaareController; | ||
10 | use Shaarli\Http\HttpAccess; | ||
11 | use Shaarli\TestCase; | ||
12 | use Slim\Http\Request; | ||
13 | use Slim\Http\Response; | ||
14 | |||
15 | /** | ||
16 | * Test GET /admin/shaare/private/{hash} | ||
17 | */ | ||
18 | class 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 | · | 253 | · |