mirror of https://github.com/nextcloud/bookmarks
Implement locking
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
This commit is contained in:
parent
291615ad26
commit
c9a2f0141f
|
@ -49,6 +49,8 @@ return [
|
|||
['name' => 'internal_bookmark#get_bookmark_image', 'url' => '/bookmark/{id}/image', 'verb' => 'GET'],
|
||||
['name' => 'internal_bookmark#get_bookmark_favicon', 'url' => '/bookmark/{id}/favicon', 'verb' => 'GET'],
|
||||
['name' => 'internal_bookmark#count_bookmarks', 'url' => '/folder/{folder}/count', 'verb' => 'GET'],
|
||||
['name' => 'internal_bookmark#acquire_lock', 'url' => '/lock', 'verb' => 'POST'],
|
||||
['name' => 'internal_bookmark#release_lock', 'url' => '/lock', 'verb' => 'DELETE'],
|
||||
['name' => 'internal_tags#full_tags', 'url' => '/tag', 'verb' => 'GET'],
|
||||
['name' => 'internal_tags#rename_tag', 'url' => '/tag', 'verb' => 'POST'],
|
||||
['name' => 'internal_tags#delete_tag', 'url' => '/tag', 'verb' => 'DELETE'],
|
||||
|
@ -89,6 +91,8 @@ return [
|
|||
['name' => 'bookmark#get_bookmark_image', 'url' => '/public/rest/v2/bookmark/{id}/image', 'verb' => 'GET'],
|
||||
['name' => 'bookmark#get_bookmark_favicon', 'url' => '/public/rest/v2/bookmark/{id}/favicon', 'verb' => 'GET'],
|
||||
['name' => 'bookmark#count_bookmarks', 'url' => '/public/rest/v2/folder/{folder}/count', 'verb' => 'GET'],
|
||||
['name' => 'bookmark#acquire_lock', 'url' => '/public/rest/v2/lock', 'verb' => 'POST'],
|
||||
['name' => 'bookmark#release_lock', 'url' => '/public/rest/v2/lock', 'verb' => 'DELETE'],
|
||||
['name' => 'tags#full_tags', 'url' => '/public/rest/v2/tag', 'verb' => 'GET'],
|
||||
['name' => 'tags#rename_tag', 'url' => '/public/rest/v2/tag', 'verb' => 'POST'],
|
||||
['name' => 'tags#delete_tag', 'url' => '/public/rest/v2/tag', 'verb' => 'DELETE'],
|
||||
|
|
|
@ -119,9 +119,13 @@ class BookmarkController extends ApiController {
|
|||
* @var IRootFolder
|
||||
*/
|
||||
private $rootFolder;
|
||||
/**
|
||||
* @var \OCA\Bookmarks\Service\LockManager
|
||||
*/
|
||||
private $lockManager;
|
||||
|
||||
public function __construct(
|
||||
$appName, $request, IL10N $l10n, BookmarkMapper $bookmarkMapper, TagMapper $tagMapper, FolderMapper $folderMapper, TreeMapper $treeMapper, PublicFolderMapper $publicFolderMapper, ITimeFactory $timeFactory, LoggerInterface $logger, IURLGenerator $url, HtmlExporter $htmlExporter, Authorizer $authorizer, BookmarkService $bookmarks, FolderService $folders, IRootFolder $rootFolder
|
||||
$appName, $request, IL10N $l10n, BookmarkMapper $bookmarkMapper, TagMapper $tagMapper, FolderMapper $folderMapper, TreeMapper $treeMapper, PublicFolderMapper $publicFolderMapper, ITimeFactory $timeFactory, LoggerInterface $logger, IURLGenerator $url, HtmlExporter $htmlExporter, Authorizer $authorizer, BookmarkService $bookmarks, FolderService $folders, IRootFolder $rootFolder, \OCA\Bookmarks\Service\LockManager $lockManager
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->request = $request;
|
||||
|
@ -139,6 +143,7 @@ class BookmarkController extends ApiController {
|
|||
$this->bookmarks = $bookmarks;
|
||||
$this->folders = $folders;
|
||||
$this->rootFolder = $rootFolder;
|
||||
$this->lockManager = $lockManager;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -767,4 +772,54 @@ class BookmarkController extends ApiController {
|
|||
$count = $this->bookmarkMapper->countArchived($this->authorizer->getUserId());
|
||||
return new JSONResponse(['status' => 'success', 'item' => $count]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JSONResponse
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
* @CORS
|
||||
* @PublicPage
|
||||
*/
|
||||
public function acquireLock(): JSONResponse {
|
||||
if (!Authorizer::hasPermission(Authorizer::PERM_EDIT, $this->authorizer->getPermissionsForFolder(-1, $this->request))) {
|
||||
return new JSONResponse(['status' => 'error', 'data' => ['Insufficient permissions']], Http::STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
try {
|
||||
if ($this->lockManager->getLock($this->authorizer->getUserId()) === true) {
|
||||
return new JSONResponse(['status' => 'error', 'data' => 'Resource is already locked'], Http::STATUS_LOCKED);
|
||||
}
|
||||
|
||||
$this->lockManager->setLock($this->authorizer->getUserId(), true);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
return new JSONResponse(['status' => 'error'], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
return new JSONResponse(['status' => 'success']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JSONResponse
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
* @CORS
|
||||
* @PublicPage
|
||||
*/
|
||||
public function releaseLock(): JSONResponse {
|
||||
if (!Authorizer::hasPermission(Authorizer::PERM_EDIT, $this->authorizer->getPermissionsForFolder(-1, $this->request))) {
|
||||
return new JSONResponse(['status' => 'error', 'data' => ['Insufficient permissions']], Http::STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
try {
|
||||
if ($this->lockManager->getLock($this->authorizer->getUserId()) === false) {
|
||||
return new JSONResponse(['status' => 'success']);
|
||||
}
|
||||
|
||||
$this->lockManager->setLock($this->authorizer->getUserId(), false);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
return new JSONResponse(['status' => 'error'], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
return new JSONResponse(['status' => 'success']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -231,4 +231,22 @@ class InternalBookmarkController extends ApiController {
|
|||
public function countArchived(): JSONResponse {
|
||||
return $this->publicController->countArchived();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JSONResponse
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function acquireLock(): JSONResponse {
|
||||
return $this->publicController->acquireLock();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JSONResponse
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function releaseLock(): JSONResponse {
|
||||
return $this->publicController->releaseLock();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
/*
|
||||
* Copyright (c) 2020. The Nextcloud Bookmarks contributors.
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or later. See the COPYING file.
|
||||
*/
|
||||
|
||||
namespace OCA\Bookmarks\Migration;
|
||||
|
||||
use Closure;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use OCA\Bookmarks\Db\Types;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
|
||||
/**
|
||||
* Auto-generated migration step: Please modify to your needs!
|
||||
*/
|
||||
class Version004005000Date20210918124721 extends SimpleMigrationStep {
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param array $options
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param array $options
|
||||
*
|
||||
* @return ISchemaWrapper
|
||||
*
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
if ($schema->hasTable('bookmarks_root_folders')) {
|
||||
$table = $schema->getTable('bookmarks_root_folders');
|
||||
$table->addColumn('locked', Types::BOOLEAN, ['notnull' => false]);
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param array $options
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
/*
|
||||
* Copyright (c) 2020. The Nextcloud Bookmarks contributors.
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or later. See the COPYING file.
|
||||
*/
|
||||
|
||||
namespace OCA\Bookmarks\Service;
|
||||
|
||||
use OCA\Bookmarks\Db\FolderMapper;
|
||||
use OCA\Bookmarks\Db\Types;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
class LockManager {
|
||||
|
||||
/**
|
||||
* @var IDBConnection
|
||||
*/
|
||||
private $db;
|
||||
/**
|
||||
* @var FolderMapper
|
||||
*/
|
||||
private $folderMapper;
|
||||
|
||||
public function __construct(IDBConnection $db, FolderMapper $folderMapper) {
|
||||
$this->db = $db;
|
||||
$this->folderMapper = $folderMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $userId
|
||||
* @param bool $locked
|
||||
*/
|
||||
public function setLock(string $userId, bool $locked): void {
|
||||
$this->folderMapper->findRootFolder($userId);
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->update('bookmarks_root_folders')
|
||||
->set('locked', $qb->createNamedParameter($locked, Types::BOOLEAN))
|
||||
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)))
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $userId
|
||||
* @return bool
|
||||
*/
|
||||
public function getLock(string $userId): bool {
|
||||
$this->folderMapper->findRootFolder($userId);
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$locked = $qb->select('locked')
|
||||
->from('bookmarks_root_folders')
|
||||
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)))
|
||||
->execute()
|
||||
->fetch(\PDO::FETCH_COLUMN);
|
||||
return (bool) $locked;
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ use OCA\Bookmarks\Service\Authorizer;
|
|||
use OCA\Bookmarks\Service\BookmarkService;
|
||||
use OCA\Bookmarks\Service\FolderService;
|
||||
use OCA\Bookmarks\Service\HtmlExporter;
|
||||
use OCA\Bookmarks\Service\LockManager;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
use OCP\AppFramework\Http;
|
||||
|
@ -146,6 +147,10 @@ class BookmarkControllerTest extends TestCase {
|
|||
* @var FolderService
|
||||
*/
|
||||
private $folders;
|
||||
/**
|
||||
* @var LockManager
|
||||
*/
|
||||
private $lockManager;
|
||||
|
||||
/**
|
||||
* @throws \OCP\AppFramework\QueryException
|
||||
|
@ -188,14 +193,15 @@ class BookmarkControllerTest extends TestCase {
|
|||
$htmlExporter = OC::$server->get(HtmlExporter::class);
|
||||
$this->authorizer = OC::$server->get(Authorizer::class);
|
||||
$this->folders = OC::$server->get(FolderService::class);
|
||||
$this->lockManager = OC::$server->get(LockManager::class);
|
||||
|
||||
/** @var IRootFolder $rootFolder */
|
||||
$rootFolder = OC::$server->get(IRootFolder::class);
|
||||
|
||||
$this->controller = new BookmarkController('bookmarks', $this->request, $l, $this->bookmarkMapper, $this->tagMapper, $this->folderMapper, $this->treeMapper, $this->publicFolderMapper, $timeFactory, $logger, $urlGenerator, $htmlExporter, $this->authorizer, $this->bookmarks, $this->folders, $rootFolder);
|
||||
$this->otherController = new BookmarkController('bookmarks', $this->request, $l, $this->bookmarkMapper, $this->tagMapper, $this->folderMapper, $this->treeMapper, $this->publicFolderMapper, $timeFactory, $logger, $urlGenerator, $htmlExporter, $this->authorizer, $this->bookmarks, $this->folders, $rootFolder);
|
||||
$this->controller = new BookmarkController('bookmarks', $this->request, $l, $this->bookmarkMapper, $this->tagMapper, $this->folderMapper, $this->treeMapper, $this->publicFolderMapper, $timeFactory, $logger, $urlGenerator, $htmlExporter, $this->authorizer, $this->bookmarks, $this->folders, $rootFolder, $this->lockManager);
|
||||
$this->otherController = new BookmarkController('bookmarks', $this->request, $l, $this->bookmarkMapper, $this->tagMapper, $this->folderMapper, $this->treeMapper, $this->publicFolderMapper, $timeFactory, $logger, $urlGenerator, $htmlExporter, $this->authorizer, $this->bookmarks, $this->folders, $rootFolder, $this->lockManager);
|
||||
|
||||
$this->publicController = new BookmarkController('bookmarks', $this->publicRequest, $l, $this->bookmarkMapper, $this->tagMapper, $this->folderMapper, $this->treeMapper, $this->publicFolderMapper, $timeFactory, $logger, $urlGenerator, $htmlExporter, $this->authorizer, $this->bookmarks, $this->folders, $rootFolder);
|
||||
$this->publicController = new BookmarkController('bookmarks', $this->publicRequest, $l, $this->bookmarkMapper, $this->tagMapper, $this->folderMapper, $this->treeMapper, $this->publicFolderMapper, $timeFactory, $logger, $urlGenerator, $htmlExporter, $this->authorizer, $this->bookmarks, $this->folders, $rootFolder, $this->lockManager);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -717,4 +723,39 @@ class BookmarkControllerTest extends TestCase {
|
|||
$r = $this->otherController->clickBookmark('https://www.golem.de');
|
||||
$this->assertNotSame(Http::STATUS_OK, $r->getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws AlreadyExistsError
|
||||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
* @throws UrlParseError
|
||||
* @throws UserLimitExceededError
|
||||
* @throws UnsupportedOperation
|
||||
*/
|
||||
public function testLock(): void {
|
||||
$this->cleanUp();
|
||||
$this->setupBookmarksWithSharedFolder();
|
||||
$this->authorizer->setUserId($this->userId);
|
||||
|
||||
// First come, gets the lock
|
||||
$output = $this->controller->acquireLock();
|
||||
$this->assertEquals(Http::STATUS_OK, $output->getStatus());
|
||||
|
||||
// Second gets an error
|
||||
$output = $this->controller->acquireLock();
|
||||
$this->assertEquals(Http::STATUS_LOCKED, $output->getStatus());
|
||||
|
||||
// Releasing...
|
||||
$output = $this->controller->releaseLock();
|
||||
$this->assertEquals(Http::STATUS_OK, $output->getStatus());
|
||||
|
||||
// Then we get the lock again
|
||||
$output = $this->controller->acquireLock();
|
||||
$this->assertEquals(Http::STATUS_OK, $output->getStatus());
|
||||
|
||||
// other users can lock independently
|
||||
$this->authorizer->setUserId($this->otherUserId);
|
||||
$output = $this->otherController->acquireLock();
|
||||
$this->assertEquals(Http::STATUS_OK, $output->getStatus());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue