Skip to content

Commit

Permalink
Merge pull request #1597 from ArthurHoaro/feature/share-private-bookmark
Browse files Browse the repository at this point in the history
Feature: Share private bookmarks using a URL containing a private key
  • Loading branch information
ArthurHoaro committed Oct 27, 2020
2 parents e6215a2 + 9c04921 commit 977db7e
Show file tree
Hide file tree
Showing 10 changed files with 268 additions and 23 deletions.
7 changes: 5 additions & 2 deletions application/bookmark/BookmarkFileService.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,15 @@ public function __construct(ConfigManager $conf, History $history, Mutex $mutex,
/**
* @inheritDoc
*/
public function findByHash(string $hash): Bookmark
public function findByHash(string $hash, string $privateKey = null): Bookmark
{
$bookmark = $this->bookmarkFilter->filter(BookmarkFilter::$FILTER_HASH, $hash);
// PHP 7.3 introduced array_key_first() to avoid this hack
$first = reset($bookmark);
if (! $this->isLoggedIn && $first->isPrivate()) {
if (!$this->isLoggedIn
&& $first->isPrivate()
&& (empty($privateKey) || $privateKey !== $first->getAdditionalContentEntry('private_key'))
) {
throw new Exception('Not authorized');
}

Expand Down
5 changes: 3 additions & 2 deletions application/bookmark/BookmarkServiceInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ interface BookmarkServiceInterface
/**
* Find a bookmark by hash
*
* @param string $hash
* @param string $hash Bookmark's hash
* @param string|null $privateKey Optional key used to access private links while logged out
*
* @return Bookmark
*
* @throws \Exception
*/
public function findByHash(string $hash): Bookmark;
public function findByHash(string $hash, string $privateKey = null);

/**
* @param $url
Expand Down
26 changes: 26 additions & 0 deletions application/front/controller/admin/ManageShaareController.php
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,32 @@ public function pinBookmark(Request $request, Response $response, array $args):
return $this->redirectFromReferer($request, $response, ['/pin'], ['pin']);
}

/**
* GET /admin/shaare/private/{hash} - Attach a private key to given bookmark, then redirect to the sharing URL.
*/
public function sharePrivate(Request $request, Response $response, array $args): Response
{
$this->checkToken($request);

$hash = $args['hash'] ?? '';
$bookmark = $this->container->bookmarkService->findByHash($hash);

if ($bookmark->isPrivate() !== true) {
return $this->redirect($response, '/shaare/' . $hash);
}

if (empty($bookmark->getAdditionalContentEntry('private_key'))) {
$privateKey = bin2hex(random_bytes(16));
$bookmark->addAdditionalContentEntry('private_key', $privateKey);
$this->container->bookmarkService->set($bookmark);
}

return $this->redirect(
$response,
'/shaare/' . $hash . '?key=' . $bookmark->getAdditionalContentEntry('private_key')
);
}

/**
* Helper function used to display the shaare form whether it's a new or existing bookmark.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,10 @@ public function index(Request $request, Response $response): Response
*/
public function permalink(Request $request, Response $response, array $args): Response
{
$privateKey = $request->getParam('key');

try {
$bookmark = $this->container->bookmarkService->findByHash($args['hash']);
$bookmark = $this->container->bookmarkService->findByHash($args['hash'], $privateKey);
} catch (BookmarkNotFoundException $e) {
$this->assignView('error_message', $e->getMessage());

Expand Down
40 changes: 22 additions & 18 deletions inc/languages/fr/LC_MESSAGES/shaarli.po
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
msgid ""
msgstr ""
"Project-Id-Version: Shaarli\n"
"POT-Creation-Date: 2020-10-21 15:00+0200\n"
"PO-Revision-Date: 2020-10-21 15:06+0200\n"
"POT-Creation-Date: 2020-10-27 19:32+0100\n"
"PO-Revision-Date: 2020-10-27 19:32+0100\n"
"Last-Translator: \n"
"Language-Team: Shaarli\n"
"Language: fr_FR\n"
Expand Down Expand Up @@ -123,38 +123,38 @@ msgstr ""
"l'extension php-gd doit être chargée pour utiliser les miniatures. Les "
"miniatures sont désormais désactivées. Rechargez la page."

#: application/Utils.php:383
#: application/Utils.php:385
msgid "Setting not set"
msgstr "Paramètre non défini"

#: application/Utils.php:390
#: application/Utils.php:392
msgid "Unlimited"
msgstr "Illimité"

#: application/Utils.php:393
#: application/Utils.php:395
msgid "B"
msgstr "o"

#: application/Utils.php:393
#: application/Utils.php:395
msgid "kiB"
msgstr "ko"

#: application/Utils.php:393
#: application/Utils.php:395
msgid "MiB"
msgstr "Mo"

#: application/Utils.php:393
#: application/Utils.php:395
msgid "GiB"
msgstr "Go"

#: application/bookmark/BookmarkFileService.php:180
#: application/bookmark/BookmarkFileService.php:202
#: application/bookmark/BookmarkFileService.php:224
#: application/bookmark/BookmarkFileService.php:238
#: application/bookmark/BookmarkFileService.php:183
#: application/bookmark/BookmarkFileService.php:205
#: application/bookmark/BookmarkFileService.php:227
#: application/bookmark/BookmarkFileService.php:241
msgid "You're not authorized to alter the datastore"
msgstr "Vous n'êtes pas autorisé à modifier les données"

#: application/bookmark/BookmarkFileService.php:205
#: application/bookmark/BookmarkFileService.php:208
msgid "This bookmarks already exists"
msgstr "Ce marque-page existe déjà."

Expand Down Expand Up @@ -439,12 +439,12 @@ msgstr "ID du lien non valide."
msgid "Invalid visibility provided."
msgstr "Visibilité du lien non valide."

#: application/front/controller/admin/ManageShaareController.php:352
#: application/front/controller/admin/ManageShaareController.php:378
#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:171
msgid "Edit"
msgstr "Modifier"

#: application/front/controller/admin/ManageShaareController.php:355
#: application/front/controller/admin/ManageShaareController.php:381
#: tmp/page.header.b91ef64efc3688266305ea9b42e5017e.rtpl.php:28
#: tmp/page.header.cedf684561d925457130839629000a81.rtpl.php:28
msgid "Shaare"
Expand Down Expand Up @@ -551,7 +551,7 @@ msgstr "Hier"
msgid "Daily"
msgstr "Quotidien"

#: application/front/controller/visitor/ErrorController.php:36
#: application/front/controller/visitor/ErrorController.php:33
msgid "An unexpected error occurred."
msgstr "Une erreur inattendue s'est produite."

Expand Down Expand Up @@ -604,7 +604,7 @@ msgstr "Permissions insuffisantes :"
msgid "Login"
msgstr "Connexion"

#: application/front/controller/visitor/LoginController.php:78
#: application/front/controller/visitor/LoginController.php:77
msgid "Wrong login/password."
msgstr "Nom d'utilisateur ou mot de passe incorrect(s)."

Expand Down Expand Up @@ -738,7 +738,7 @@ msgstr "Impossible de purger %s : le répertoire n'existe pas"
msgid "An error occurred while running the update "
msgstr "Une erreur s'est produite lors de l'exécution de la mise à jour "

#: index.php:65
#: index.php:80
msgid "Shared bookmarks on "
msgstr "Liens partagés sur "

Expand Down Expand Up @@ -1376,6 +1376,10 @@ msgstr "Changer statut épinglé"
msgid "Sticky"
msgstr "Épinglé"

#: tmp/linklist.b91ef64efc3688266305ea9b42e5017e.rtpl.php:189
msgid "Share a private link"
msgstr "Partager un lien privé"

#: tmp/linklist.paging.b91ef64efc3688266305ea9b42e5017e.rtpl.php:5
#: tmp/linklist.paging.cedf684561d925457130839629000a81.rtpl.php:5
msgid "Filters"
Expand Down
1 change: 1 addition & 0 deletions index.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
$this->get('/add-shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:addShaare');
$this->get('/shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:displayCreateForm');
$this->get('/shaare/{id:[0-9]+}', '\Shaarli\Front\Controller\Admin\ManageShaareController:displayEditForm');
$this->get('/shaare/private/{hash}', '\Shaarli\Front\Controller\Admin\ManageShaareController:sharePrivate');
$this->post('/shaare', '\Shaarli\Front\Controller\Admin\ManageShaareController:save');
$this->get('/shaare/delete', '\Shaarli\Front\Controller\Admin\ManageShaareController:deleteBookmark');
$this->get('/shaare/visibility', '\Shaarli\Front\Controller\Admin\ManageShaareController:changeVisibility');
Expand Down
31 changes: 31 additions & 0 deletions tests/bookmark/BookmarkFileServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,37 @@ public function testFilterHashInValid()
$this->publicLinkDB->findByHash('');
}

/**
* Test filterHash() on a private bookmark while logged out.
*/
public function testFilterHashPrivateWhileLoggedOut()
{
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Not authorized');

$hash = smallHash('20141125_084734' . 6);

$this->publicLinkDB->findByHash($hash);
}

/**
* Test filterHash() with private key.
*/
public function testFilterHashWithPrivateKey()
{
$hash = smallHash('20141125_084734' . 6);
$privateKey = 'this is usually auto generated';

$bookmark = $this->privateLinkDB->findByHash($hash);
$bookmark->addAdditionalContentEntry('private_key', $privateKey);
$this->privateLinkDB->save();

$this->privateLinkDB = new BookmarkFileService($this->conf, $this->history, $this->mutex, false);
$bookmark = $this->privateLinkDB->findByHash($hash, $privateKey);

static::assertSame(6, $bookmark->getId());
}

/**
* Test linksCountPerTag all tags without filter.
* Equal occurrences should be sorted alphabetically.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php

declare(strict_types=1);

namespace Shaarli\Front\Controller\Admin\ManageShaareControllerTest;

use Shaarli\Bookmark\Bookmark;
use Shaarli\Front\Controller\Admin\FrontAdminControllerMockHelper;
use Shaarli\Front\Controller\Admin\ManageShaareController;
use Shaarli\Http\HttpAccess;
use Shaarli\TestCase;
use Slim\Http\Request;
use Slim\Http\Response;

/**
* Test GET /admin/shaare/private/{hash}
*/
class SharePrivateTest extends TestCase
{
use FrontAdminControllerMockHelper;

/** @var ManageShaareController */
protected $controller;

public function setUp(): void
{
$this->createContainer();

$this->container->httpAccess = $this->createMock(HttpAccess::class);
$this->controller = new ManageShaareController($this->container);
}

/**
* Test shaare private with a private bookmark which does not have a key yet.
*/
public function testSharePrivateWithNewPrivateBookmark(): void
{
$hash = 'abcdcef';
$request = $this->createMock(Request::class);
$response = new Response();

$bookmark = (new Bookmark())
->setId(123)
->setUrl('http://domain.tld')
->setTitle('Title 123')
->setPrivate(true)
;

$this->container->bookmarkService
->expects(static::once())
->method('findByHash')
->with($hash)
->willReturn($bookmark)
;
$this->container->bookmarkService
->expects(static::once())
->method('set')
->with($bookmark, true)
->willReturnCallback(function (Bookmark $bookmark): Bookmark {
static::assertSame(32, strlen($bookmark->getAdditionalContentEntry('private_key')));

return $bookmark;
})
;

$result = $this->controller->sharePrivate($request, $response, ['hash' => $hash]);

static::assertSame(302, $result->getStatusCode());
static::assertRegExp('#/subfolder/shaare/' . $hash . '\?key=\w{32}#', $result->getHeaderLine('Location'));
}

/**
* Test shaare private with a private bookmark which does already have a key.
*/
public function testSharePrivateWithExistingPrivateBookmark(): void
{
$hash = 'abcdcef';
$existingKey = 'this is a private key';
$request = $this->createMock(Request::class);
$response = new Response();

$bookmark = (new Bookmark())
->setId(123)
->setUrl('http://domain.tld')
->setTitle('Title 123')
->setPrivate(true)
->addAdditionalContentEntry('private_key', $existingKey)
;

$this->container->bookmarkService
->expects(static::once())
->method('findByHash')
->with($hash)
->willReturn($bookmark)
;
$this->container->bookmarkService
->expects(static::never())
->method('set')
;

$result = $this->controller->sharePrivate($request, $response, ['hash' => $hash]);

static::assertSame(302, $result->getStatusCode());
static::assertSame('/subfolder/shaare/' . $hash . '?key=' . $existingKey, $result->getHeaderLine('Location'));
}

/**
* Test shaare private with a public bookmark.
*/
public function testSharePrivateWithPublicBookmark(): void
{
$hash = 'abcdcef';
$request = $this->createMock(Request::class);
$response = new Response();

$bookmark = (new Bookmark())
->setId(123)
->setUrl('http://domain.tld')
->setTitle('Title 123')
->setPrivate(false)
;

$this->container->bookmarkService
->expects(static::once())
->method('findByHash')
->with($hash)
->willReturn($bookmark)
;
$this->container->bookmarkService
->expects(static::never())
->method('set')
;

$result = $this->controller->sharePrivate($request, $response, ['hash' => $hash]);

static::assertSame(302, $result->getStatusCode());
static::assertSame('/subfolder/shaare/' . $hash, $result->getHeaderLine('Location'));
}
}

0 comments on commit 977db7e

Please sign in to comment.