Skip to content

Commit 17a6953

Browse files
committed
feat: request federated locks using webdav propfind
Signed-off-by: Benjamin Frueh <[email protected]>
1 parent e5d3a5a commit 17a6953

File tree

8 files changed

+91
-144
lines changed

8 files changed

+91
-144
lines changed

appinfo/routes.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,5 @@
1111
'ocs' => [
1212
['name' => 'Lock#locking', 'url' => '/lock/{fileId}', 'verb' => 'PUT'],
1313
['name' => 'Lock#unlocking', 'url' => '/lock/{fileId}', 'verb' => 'DELETE'],
14-
['name' => 'Lock#getLockByToken', 'url' => '/lock/token/{token}', 'verb' => 'GET'],
1514
]
1615
];

lib/AppInfo/Application.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use OCA\Files\Event\LoadAdditionalScriptsEvent;
1414
use OCA\FilesLock\Capability;
1515
use OCA\FilesLock\Listeners\LoadAdditionalScripts;
16+
use OCA\FilesLock\Listeners\PropfindPropertiesListener;
1617
use OCA\FilesLock\LockProvider;
1718
use OCA\FilesLock\Service\FileService;
1819
use OCA\FilesLock\Service\LockService;
@@ -21,6 +22,7 @@
2122
use OCP\AppFramework\Bootstrap\IBootContext;
2223
use OCP\AppFramework\Bootstrap\IBootstrap;
2324
use OCP\AppFramework\Bootstrap\IRegistrationContext;
25+
use OCP\Files\Events\BeforePropfindEvent;
2426
use OCP\Files\Lock\ILockManager;
2527
use OCP\IUserSession;
2628
use OCP\Server;
@@ -48,6 +50,10 @@ public function register(IRegistrationContext $context): void {
4850
LoadAdditionalScriptsEvent::class,
4951
LoadAdditionalScripts::class
5052
);
53+
$context->registerEventListener(
54+
BeforePropfindEvent::class,
55+
PropfindPropertiesListener::class
56+
);
5157
}
5258

5359
public function boot(IBootContext $context): void {

lib/Controller/LockController.php

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,14 @@
1717
use OCA\FilesLock\Service\FileService;
1818
use OCA\FilesLock\Service\LockService;
1919
use OCP\AppFramework\Http;
20-
use OCP\AppFramework\Http\Attribute\BruteForceProtection;
21-
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
22-
use OCP\AppFramework\Http\Attribute\PublicPage;
2320
use OCP\AppFramework\Http\DataResponse;
2421
use OCP\AppFramework\OCSController;
25-
use OCP\Files\Folder;
2622
use OCP\Files\Lock\ILock;
2723
use OCP\Files\Lock\LockContext;
2824
use OCP\Files\Lock\OwnerLockedException;
29-
use OCP\Files\NotFoundException;
3025
use OCP\IL10N;
3126
use OCP\IRequest;
3227
use OCP\IUserSession;
33-
use OCP\Share\Exceptions\ShareNotFound;
34-
use OCP\Share\IManager;
3528
use Psr\Log\LoggerInterface;
3629

3730
/**
@@ -51,7 +44,6 @@ public function __construct(
5144
private FileService $fileService,
5245
private LockService $lockService,
5346
private IL10N $l10n,
54-
private IManager $shareManager,
5547
) {
5648
parent::__construct(Application::APP_ID, $request);
5749

@@ -187,40 +179,4 @@ protected function fail(
187179

188180
return new DataResponse($data, $status);
189181
}
190-
191-
#[PublicPage]
192-
#[NoCSRFRequired]
193-
#[BruteForceProtection(action: 'files_lock_token')]
194-
public function getLockByToken(string $token, string $path = ''): DataResponse {
195-
try {
196-
$share = $this->shareManager->getShareByToken($token);
197-
$node = $share->getNode();
198-
199-
if ($path !== '' && $node instanceof Folder) {
200-
try {
201-
$node = $node->get(ltrim($path, '/'));
202-
} catch (NotFoundException) {
203-
return new DataResponse([], Http::STATUS_NOT_FOUND);
204-
}
205-
}
206-
207-
$fileId = $node->getId();
208-
209-
try {
210-
$lock = $this->lockService->getLockFromFileId($fileId);
211-
$this->lockService->injectMetadata($lock);
212-
213-
return new DataResponse([
214-
'locked' => true,
215-
'lock' => $lock->jsonSerialize()
216-
]);
217-
} catch (LockNotFoundException) {
218-
return new DataResponse(['locked' => false]);
219-
}
220-
} catch (ShareNotFound) {
221-
$response = new DataResponse([], Http::STATUS_NOT_FOUND);
222-
$response->throttle(['token' => $token]);
223-
return $response;
224-
}
225-
}
226182
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\FilesLock\Listeners;
11+
12+
use OCA\FilesLock\AppInfo\Application;
13+
use OCP\EventDispatcher\Event;
14+
use OCP\EventDispatcher\IEventListener;
15+
use OCP\Files\Events\BeforePropfindEvent;
16+
17+
/**
18+
* @template-implements IEventListener<BeforePropfindEvent>
19+
*/
20+
class PropfindPropertiesListener implements IEventListener {
21+
public function handle(Event $event): void {
22+
if (!($event instanceof BeforePropfindEvent)) {
23+
return;
24+
}
25+
26+
$event->addProperties([
27+
Application::DAV_PROPERTY_LOCK,
28+
Application::DAV_PROPERTY_LOCK_OWNER,
29+
Application::DAV_PROPERTY_LOCK_OWNER_DISPLAYNAME,
30+
Application::DAV_PROPERTY_LOCK_OWNER_TYPE,
31+
Application::DAV_PROPERTY_LOCK_EDITOR,
32+
Application::DAV_PROPERTY_LOCK_TIME,
33+
Application::DAV_PROPERTY_LOCK_TIMEOUT,
34+
Application::DAV_PROPERTY_LOCK_TOKEN,
35+
]);
36+
}
37+
}

lib/LockProvider.php

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,13 @@ public function __construct(LockService $lockService) {
2020
}
2121

2222
public function getLocks(int $fileId): array {
23-
try {
24-
$lock = $this->lockService->getLockFromFileId($fileId);
25-
$this->lockService->injectMetadata($lock);
26-
} catch (Exceptions\LockNotFoundException $e) {
23+
$lock = $this->lockService->getLockForNodeId($fileId);
24+
if (!$lock) {
2725
return [];
2826
}
2927

30-
if ($lock) {
31-
return [$lock];
32-
}
33-
return [];
28+
$this->lockService->injectMetadata($lock);
29+
return [$lock];
3430
}
3531

3632
/**

lib/Model/FileLock.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,6 @@ public function import(array $data) {
312312
$this->setDisplayName($this->get('owner', $data));
313313
}
314314

315-
316315
/**
317316
* @return array
318317
*/

lib/Service/LockService.php

Lines changed: 29 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
namespace OCA\FilesLock\Service;
1111

1212
use Exception;
13-
use OCA\Files_Sharing\External\Storage as ExternalStorage;
13+
use OC\Files\Storage\DAV;
14+
use OCA\FilesLock\AppInfo\Application;
1415
use OCA\FilesLock\Db\LocksRequest;
1516
use OCA\FilesLock\Exceptions\LockNotFoundException;
1617
use OCA\FilesLock\Exceptions\UnauthorizedUnlockException;
@@ -25,7 +26,6 @@
2526
use OCP\Files\Lock\LockContext;
2627
use OCP\Files\Lock\OwnerLockedException;
2728
use OCP\Files\NotFoundException;
28-
use OCP\Http\Client\IClientService;
2929
use OCP\IL10N;
3030
use OCP\IRequest;
3131
use OCP\IUserManager;
@@ -68,7 +68,6 @@ public function __construct(
6868
IUserSession $userSession,
6969
IRequest $request,
7070
LoggerInterface $logger,
71-
private IClientService $clientService,
7271
private IRootFolder $rootFolder,
7372
) {
7473
$this->l10n = $l10n;
@@ -96,8 +95,8 @@ public function getLockForNodeId(int $nodeId) {
9695
try {
9796
$this->lockCache[$nodeId] = $this->getLockFromFileId($nodeId);
9897
} catch (LockNotFoundException) {
99-
$remoteLock = $this->getRemoteLockForFileId($nodeId);
100-
$this->lockCache[$nodeId] = $remoteLock ?? false;
98+
$remoteLock = $this->getRemoteLockFromDav($nodeId);
99+
$this->lockCache[$nodeId] = $remoteLock ?: false;
101100
}
102101

103102
return $this->lockCache[$nodeId];
@@ -168,13 +167,7 @@ public function lock(LockContext $lockScope): FileLock {
168167

169168
$this->injectMetadata($known);
170169
throw new OwnerLockedException($known);
171-
} catch (LockNotFoundException) {
172-
$remoteLock = $this->getRemoteLockForFileId($lockScope->getNode()->getId());
173-
if ($remoteLock !== null) {
174-
$this->injectMetadata($remoteLock);
175-
throw new OwnerLockedException($remoteLock);
176-
}
177-
170+
} catch (LockNotFoundException $e) {
178171
$lock = FileLock::fromLockScope($lockScope, $this->configService->getTimeoutSeconds());
179172
$this->generateToken($lock);
180173
$lock->setCreation(time());
@@ -353,9 +346,6 @@ public function injectMetadata(FileLock $lock): FileLock {
353346
}
354347

355348
if ($displayName) {
356-
if ($lock->getRemoteHost()) {
357-
$displayName .= '@' . $lock->getRemoteHost();
358-
}
359349
$lock->setDisplayName($displayName);
360350
}
361351
return $lock;
@@ -408,87 +398,60 @@ function (FileLock $lock) {
408398
$this->locksRequest->removeIds($ids);
409399
}
410400

411-
private function propagateEtag(LockContext $lockContext): void {
412-
$node = $lockContext->getNode();
413-
$node->getStorage()->getCache()->update($node->getId(), [
414-
'etag' => uniqid(),
415-
]);
416-
$node->getStorage()->getUpdater()->propagate($node->getInternalPath(), $node->getMTime());
417-
}
418-
419-
public function getRemoteLockForFileId(int $fileId): ?FileLock {
401+
public function getRemoteLockFromDav(int $nodeId): ?FileLock {
420402
try {
421403
$user = $this->userSession->getUser();
422404
if (!$user) {
423405
return null;
424406
}
425407

426408
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
427-
$nodes = $userFolder->getById($fileId);
409+
$nodes = $userFolder->getById($nodeId);
428410
if (empty($nodes)) {
429411
return null;
430412
}
431413

432414
$node = $nodes[0];
433415
$storage = $node->getStorage();
434-
if (!$storage->instanceOfStorage(ExternalStorage::class)) {
416+
417+
if (!$storage->instanceOfStorage(DAV::class)) {
435418
return null;
436419
}
437420

438-
$internalPath = $node->getInternalPath();
421+
$path = $node->getInternalPath();
422+
$storage->getMetaData($path);
439423

440-
/** @var ExternalStorage $storage */
441-
$remoteLockData = $this->getRemoteLock(
442-
$storage->getRemote(),
443-
$storage->getToken(),
444-
$internalPath
445-
);
446-
447-
if (!$remoteLockData) {
424+
$isLocked = $storage->getPropfindPropertyValue($path, Application::DAV_PROPERTY_LOCK);
425+
if (!$isLocked) {
448426
return null;
449427
}
450428

451429
$fileLock = new FileLock();
452-
$fileLock->import($remoteLockData);
430+
$fileLock->import([
431+
'fileId' => $nodeId,
432+
'owner' => (string)($storage->getPropfindPropertyValue($path, Application::DAV_PROPERTY_LOCK_OWNER_DISPLAYNAME) ?? ''),
433+
'type' => (int)($storage->getPropfindPropertyValue($path, Application::DAV_PROPERTY_LOCK_OWNER_TYPE) ?? 0),
434+
'creation' => (int)($storage->getPropfindPropertyValue($path, Application::DAV_PROPERTY_LOCK_TIME) ?? 0),
435+
'ttl' => (int)($storage->getPropfindPropertyValue($path, Application::DAV_PROPERTY_LOCK_TIMEOUT) ?? 0),
436+
'token' => (string)($storage->getPropfindPropertyValue($path, Application::DAV_PROPERTY_LOCK_TOKEN) ?? ''),
437+
]);
453438

454439
$remoteHost = parse_url($storage->getRemote(), PHP_URL_HOST);
455440
$fileLock->setRemoteHost($remoteHost);
456-
457-
$this->injectMetadata($fileLock);
458-
459-
$this->lockCache[$fileId] = $fileLock;
441+
$fileLock->setDisplayName($fileLock->getDisplayName() . '@' . $remoteHost);
460442

461443
return $fileLock;
462444
} catch (\Exception $e) {
463-
$this->logger->debug('Failed to get remote lock: ' . $e->getMessage());
445+
$this->logger->debug('Failed to get remote lock from DAV: ' . $e->getMessage());
464446
return null;
465447
}
466448
}
467449

468-
public function getRemoteLock(string $remoteUrl, string $shareToken, string $path = ''): ?array {
469-
$url = rtrim($remoteUrl, '/') . "/ocs/v2.php/apps/files_lock/lock/token/{$shareToken}";
470-
471-
if ($path !== '') {
472-
$url .= '?path=' . urlencode($path);
473-
}
474-
475-
try {
476-
$response = $this->clientService->newClient()->get($url, [
477-
'headers' => [
478-
'OCS-APIRequest' => 'true',
479-
'Accept' => 'application/json'
480-
],
481-
'timeout' => 5,
482-
]);
483-
484-
$data = json_decode($response->getBody(), true);
485-
if (!isset($data['ocs']['data']['locked']) || !$data['ocs']['data']['locked']) {
486-
return null;
487-
}
488-
489-
return $data['ocs']['data']['lock'];
490-
} catch (\Exception) {
491-
return null;
492-
}
450+
private function propagateEtag(LockContext $lockContext): void {
451+
$node = $lockContext->getNode();
452+
$node->getStorage()->getCache()->update($node->getId(), [
453+
'etag' => uniqid(),
454+
]);
455+
$node->getStorage()->getUpdater()->propagate($node->getInternalPath(), $node->getMTime());
493456
}
494457
}

0 commit comments

Comments
 (0)