From 9357689c27b0c49e665746324df63620774739e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Tue, 19 Dec 2023 16:07:49 +0100 Subject: [PATCH] feat: Add API parameters to specify the lock type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl tmp: bump max version Signed-off-by: Julius Härtl --- README.md | 27 ++++++++++++++++++++++++++- appinfo/info.xml | 2 +- lib/Capability.php | 1 + lib/Controller/LockController.php | 6 +++--- lib/DAV/LockPlugin.php | 6 ++++-- lib/Service/LockService.php | 25 ++++++++++--------------- 6 files changed, 45 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 2581bfcc..e114b14b 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,8 @@ Locks are separated into three different types: This lock type is initiated by a user manually through the WebUI or Clients and will limit editing capabilities on the file to the lock owning user. - **1 App owned lock:** This lock type is created by collaborative apps like Text or Office to avoid outside changes through WevDAV or other apps. -- **2 Token owned lock:** (not implemented yet) This lock type will bind the ownership to the provided lock token. Any request that aims to modify the file will be required to sent the token, the user itself is not able to write to files without the token. This will allow to limit the locking to an individual client. +- **2 Token owned lock:** This lock type will bind the ownership to the provided lock token. Any request that aims to modify the file will be required to sent the token, the user itself is not able to write to files without the token. This will allow to limit the locking to an individual client. + - This is mostly used for automatic client locking, e.g. when a file is opened in a client or with WebDAV clients that support native WebDAV locking. The lock token can be skipped on follow up requests using the OCS API or the `X-User-Lock` header for WebDAV requests, but in that case the server will not be able to validate the lock ownership when unlocking the file from the client. ### Capability @@ -88,6 +89,11 @@ curl -X LOCK \ --header 'X-User-Lock: 1' ``` +#### Headers + +- `X-User-Lock`: Indicate that locking shall ignore token requirements that the WebDAV standard required. +- `X-User-Lock-Type`: The type of the lock (see description above) + #### Response The response will give back the updated properties after obtaining the lock with a `200 Success` status code or the existing lock details in case of a `423 Locked` status. @@ -123,6 +129,11 @@ curl -X UNLOCK \ --header 'X-User-Lock: 1' ``` +#### Headers + +- `X-User-Lock`: Indicate that locking shall ignore token requirements that the WebDAV standard required. +- `X-User-Lock-Type`: The type of the lock (see description above) + #### Response ```xml @@ -155,6 +166,13 @@ curl -X UNLOCK \ curl -X PUT 'http://admin:admin@nextcloud.local/ocs/v2.php/apps/files_lock/lock/123' -H 'OCS-APIREQUEST: true'` ``` +#### Parameters + +- `lockType` (optional): The type of the lock. Defaults to `0` (user owned manual lock). Possible values are: + - `0`: User owned manual lock + - `1`: App owned lock + - `2`: Token owned lock + #### Success ``` @@ -193,6 +211,13 @@ curl -X PUT 'http://admin:admin@nextcloud.local/ocs/v2.php/apps/files_lock/lock/ curl -X DELETE 'http://admin:admin@nextcloud.local/ocs/v2.php/apps/files_lock/lock/123' -H 'OCS-APIREQUEST: true' ``` +#### Parameters + +- `lockType` (optional): The type of the lock. Defaults to `0` (user owned manual lock). Possible values are: + - `0`: User owned manual lock + - `1`: App owned lock + - `2`: Token owned lock + #### Success ``` diff --git a/appinfo/info.xml b/appinfo/info.xml index 16b8526f..36656b96 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -43,6 +43,6 @@ Allow your users to temporary lock their files to avoid conflicts while working - + diff --git a/lib/Capability.php b/lib/Capability.php index 0db85b0b..6e3b11c6 100644 --- a/lib/Capability.php +++ b/lib/Capability.php @@ -9,6 +9,7 @@ public function getCapabilities() { return [ 'files' => [ 'locking' => '1.0', + 'api-feature-lock-type' => true, ] ]; } diff --git a/lib/Controller/LockController.php b/lib/Controller/LockController.php index 7eb7970f..1636ec66 100644 --- a/lib/Controller/LockController.php +++ b/lib/Controller/LockController.php @@ -90,13 +90,13 @@ public function __construct( * * @return DataResponse */ - public function locking(string $fileId): DataResponse { + public function locking(string $fileId, int $lockType = ILock::TYPE_USER): DataResponse { try { $user = $this->userSession->getUser(); $file = $this->fileService->getFileFromId($user->getUID(), (int)$fileId); $lock = $this->lockService->lock(new LockContext( - $file, ILock::TYPE_USER, $user->getUID() + $file, $lockType, $user->getUID() )); return new DataResponse($lock, Http::STATUS_OK); @@ -115,7 +115,7 @@ public function locking(string $fileId): DataResponse { * * @return DataResponse */ - public function unlocking(string $fileId): DataResponse { + public function unlocking(string $fileId, int $lockType = ILock::TYPE_USER): DataResponse { try { $user = $this->userSession->getUser(); $this->lockService->enableUserOverride(); diff --git a/lib/DAV/LockPlugin.php b/lib/DAV/LockPlugin.php index 37caeac9..d0591501 100644 --- a/lib/DAV/LockPlugin.php +++ b/lib/DAV/LockPlugin.php @@ -183,13 +183,14 @@ public function customProperties(PropFind $propFind, INode $node) { public function httpLock(RequestInterface $request, ResponseInterface $response) { if ($request->getHeader('X-User-Lock')) { + $lockType = (int)($request->getHeader('X-User-Lock-Type') ?? ILock::TYPE_USER); $response->setHeader('Content-Type', 'application/xml; charset=utf-8'); $file = $this->fileService->getFileFromAbsoluteUri($this->server->getRequestUri()); try { $lockInfo = $this->lockService->lock(new LockContext( - $file, ILock::TYPE_USER, $this->userSession->getUser()->getUID() + $file, $lockType, $this->userSession->getUser()->getUID() )); $response->setStatus(200); $response->setBody( @@ -216,6 +217,7 @@ public function httpLock(RequestInterface $request, ResponseInterface $response) public function httpUnlock(RequestInterface $request, ResponseInterface $response) { if ($request->getHeader('X-User-Lock')) { + $lockType = (int)($request->getHeader('X-User-Lock-Type') ?? ILock::TYPE_USER); $response->setHeader('Content-Type', 'application/xml; charset=utf-8'); $file = $this->fileService->getFileFromAbsoluteUri($this->server->getRequestUri()); @@ -223,7 +225,7 @@ public function httpUnlock(RequestInterface $request, ResponseInterface $respons try { $this->lockService->enableUserOverride(); $this->lockService->unlock(new LockContext( - $file, ILock::TYPE_USER, $this->userSession->getUser()->getUID() + $file, $lockType, $this->userSession->getUser()->getUID() )); $response->setStatus(200); $response->setBody( diff --git a/lib/Service/LockService.php b/lib/Service/LockService.php index 6540e24a..37f6cee4 100644 --- a/lib/Service/LockService.php +++ b/lib/Service/LockService.php @@ -47,12 +47,8 @@ use OCP\Files\NotFoundException; use OCP\IL10N; use OCP\IUserManager; +use OCP\IUserSession; -/** - * Class LockService - * - * @package OCA\FilesLock\Service - */ class LockService { public const PREFIX = 'files_lock'; @@ -61,7 +57,6 @@ class LockService { use TLogger; - private ?string $userId; private IUserManager $userManager; private IL10N $l10n; private LocksRequest $locksRequest; @@ -69,6 +64,7 @@ class LockService { private ConfigService $configService; private IAppManager $appManager; private IEventDispatcher $eventDispatcher; + private IUserSession $userSession; private array $locks = []; @@ -79,16 +75,15 @@ class LockService { public function __construct( - $userId, IL10N $l10n, IUserManager $userManager, LocksRequest $locksRequest, FileService $fileService, ConfigService $configService, IAppManager $appManager, - IEventDispatcher $eventDispatcher + IEventDispatcher $eventDispatcher, + IUserSession $userSession, ) { - $this->userId = $userId; $this->l10n = $l10n; $this->userManager = $userManager; $this->locksRequest = $locksRequest; @@ -96,6 +91,7 @@ public function __construct( $this->configService = $configService; $this->appManager = $appManager; $this->eventDispatcher = $eventDispatcher; + $this->userSession = $userSession; $this->setup('app', 'files_lock'); } @@ -229,7 +225,7 @@ public function enableUserOverride(): void { } public function canUnlock(LockContext $request, FileLock $current): void { - $isSameUser = $current->getOwner() === $this->userId; + $isSameUser = $current->getOwner() === $this->userSession->getUser()?->getUID(); $isSameToken = $request->getOwner() === $current->getToken(); $isSameOwner = $request->getOwner() === $current->getOwner(); $isSameType = $request->getType() === $current->getType(); @@ -256,7 +252,7 @@ public function canUnlock(LockContext $request, FileLock $current): void { 'OCA\Files_External\Config\ConfigAdapter' ]; if ($request->getType() === ILock::TYPE_USER - && $request->getNode()->getOwner()->getUID() === $this->userId + && $request->getNode()->getOwner()->getUID() === $this->userSession->getUser()?->getUID() && !in_array($request->getNode()->getMountPoint()->getMountProvider(), $ignoreFileOwnership) ) { return; @@ -274,22 +270,21 @@ public function canUnlock(LockContext $request, FileLock $current): void { * @throws NotFoundException * @throws UnauthorizedUnlockException */ - public function unlockFile(int $fileId, string $userId, bool $force = false): FileLock { + public function unlockFile(int $fileId, string $userId, bool $force = false, int $lockType = ILock::TYPE_USER): FileLock { $lock = $this->getLockForNodeId($fileId); if (!$lock) { throw new LockNotFoundException(); } - $type = ILock::TYPE_USER; if ($force) { $userId = $lock->getOwner(); - $type = $lock->getType(); + $lockType = $lock->getType(); } $node = $this->fileService->getFileFromId($userId, $fileId); $lock = new LockContext( $node, - $type, + $lockType, $userId, ); $this->propagateEtag($lock);