Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add API parameters to specify the lock type #199

Merged
merged 1 commit into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,30 @@ 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

If locking is available the app will expose itself through the capabilties endpoint under the files key:

Make sure to validate that also the key exists in the capabilities response, not just check the value as on older versions the entry might be missing completely.

```
curl http://admin:[email protected]/ocs/v1.php/cloud/capabilities\?format\=json \
-H 'OCS-APIRequest: true' \
| jq .ocs.data.capabilities.files
{
...
"locking": "1.0",
"api-feature-lock-type" => true,
...
}
```

- `locking`: The version of the locking API
- `api-feature-lock-type`: Feature flag, whether the server supports the `lockType` parameter for the OCS API or `X-User-Lock-Type` header for WebDAV requests.

### WebDAV: Fetching lock details

WebDAV returns the following additional properties if requests through a `PROPFIND`:
Expand Down Expand Up @@ -88,6 +96,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.
Expand Down Expand Up @@ -123,6 +136,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
Expand Down Expand Up @@ -155,6 +173,13 @@ curl -X UNLOCK \
curl -X PUT 'http://admin:[email protected]/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
```
<?xml version="1.0"?>
Expand Down Expand Up @@ -193,6 +218,13 @@ curl -X PUT 'http://admin:[email protected]/ocs/v2.php/apps/files_lock/lock/
curl -X DELETE 'http://admin:[email protected]/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
```
<?xml version="1.0"?>
Expand Down
1 change: 1 addition & 0 deletions lib/Capability.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public function getCapabilities() {
return [
'files' => [
'locking' => '1.0',
'api-feature-lock-type' => true,
]
];
}
Expand Down
6 changes: 3 additions & 3 deletions lib/Controller/LockController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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();
Expand Down
6 changes: 4 additions & 2 deletions lib/DAV/LockPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -216,14 +217,15 @@ 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());

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(
Expand Down
25 changes: 10 additions & 15 deletions lib/Service/LockService.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -61,14 +57,14 @@ class LockService {
use TLogger;


private ?string $userId;
private IUserManager $userManager;
private IL10N $l10n;
private LocksRequest $locksRequest;
private FileService $fileService;
private ConfigService $configService;
private IAppManager $appManager;
private IEventDispatcher $eventDispatcher;
private IUserSession $userSession;


private array $locks = [];
Expand All @@ -79,23 +75,23 @@ 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;
$this->fileService = $fileService;
$this->configService = $configService;
$this->appManager = $appManager;
$this->eventDispatcher = $eventDispatcher;
$this->userSession = $userSession;

$this->setup('app', 'files_lock');
}
Expand Down Expand Up @@ -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();
Expand All @@ -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;
Expand All @@ -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);
Expand Down
Loading