Skip to content

Commit 74cde74

Browse files
feat(share): transport share-review permissions as opaque self-describing entries
Replace the permissions bitmask on ShareReviewEntry with a list of ShareReviewPermission objects carrying an opaque namespaced identifier and localized display metadata, so apps can expose app-specific permissions without squeezing them into OCP\Constants bits. Assisted-by: Claude Code:claude-fable-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
1 parent 4951d16 commit 74cde74

6 files changed

Lines changed: 140 additions & 12 deletions

File tree

lib/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,7 @@
895895
'OCP\\Share\\ShareReview\\IShareReviewSource' => $baseDir . '/lib/public/Share/ShareReview/IShareReviewSource.php',
896896
'OCP\\Share\\ShareReview\\RegisterShareReviewSourceEvent' => $baseDir . '/lib/public/Share/ShareReview/RegisterShareReviewSourceEvent.php',
897897
'OCP\\Share\\ShareReview\\ShareReviewEntry' => $baseDir . '/lib/public/Share/ShareReview/ShareReviewEntry.php',
898+
'OCP\\Share\\ShareReview\\ShareReviewPermission' => $baseDir . '/lib/public/Share/ShareReview/ShareReviewPermission.php',
898899
'OCP\\Snowflake\\ISnowflakeDecoder' => $baseDir . '/lib/public/Snowflake/ISnowflakeDecoder.php',
899900
'OCP\\Snowflake\\ISnowflakeGenerator' => $baseDir . '/lib/public/Snowflake/ISnowflakeGenerator.php',
900901
'OCP\\Snowflake\\Snowflake' => $baseDir . '/lib/public/Snowflake/Snowflake.php',

lib/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
936936
'OCP\\Share\\ShareReview\\IShareReviewSource' => __DIR__ . '/../../..' . '/lib/public/Share/ShareReview/IShareReviewSource.php',
937937
'OCP\\Share\\ShareReview\\RegisterShareReviewSourceEvent' => __DIR__ . '/../../..' . '/lib/public/Share/ShareReview/RegisterShareReviewSourceEvent.php',
938938
'OCP\\Share\\ShareReview\\ShareReviewEntry' => __DIR__ . '/../../..' . '/lib/public/Share/ShareReview/ShareReviewEntry.php',
939+
'OCP\\Share\\ShareReview\\ShareReviewPermission' => __DIR__ . '/../../..' . '/lib/public/Share/ShareReview/ShareReviewPermission.php',
939940
'OCP\\Snowflake\\ISnowflakeDecoder' => __DIR__ . '/../../..' . '/lib/public/Snowflake/ISnowflakeDecoder.php',
940941
'OCP\\Snowflake\\ISnowflakeGenerator' => __DIR__ . '/../../..' . '/lib/public/Snowflake/ISnowflakeGenerator.php',
941942
'OCP\\Snowflake\\Snowflake' => __DIR__ . '/../../..' . '/lib/public/Snowflake/Snowflake.php',

lib/public/Share/ShareReview/ShareReviewEntry.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
namespace OCP\Share\ShareReview;
1111

1212
use OCP\AppFramework\Attribute\Consumable;
13-
use OCP\Constants;
1413
use OCP\Share\IShare;
1514

1615
/**
@@ -34,13 +33,14 @@ final class ShareReviewEntry {
3433
* later; used for sorting and for the
3534
* new-since-last-review filter. Pass 0
3635
* if the app tracks neither.
37-
* @param int-mask-of<Constants::PERMISSION_*> $permissions Permissions level of the share.
36+
* @param list<ShareReviewPermission> $permissions Permissions granted by
37+
* the share. An empty list
38+
* means the share grants
39+
* nothing beyond existing.
3840
* @param string $action Optional deletion identifier override. An empty
3941
* string means $id is used.
4042
* @param bool $hasPassword Whether the share is password protected. Never
4143
* the password itself.
42-
* @param bool $canManage Whether the recipient can administer the shared
43-
* object and its sharing.
4444
* @param int|null $expirationTimestamp Optional expiration Unix timestamp
4545
* of the share.
4646
* @param string|null $parent Optional identifier of the parent share.
@@ -54,10 +54,9 @@ public function __construct(
5454
public readonly int $type,
5555
public readonly string $recipient,
5656
public readonly int $lastModifiedTimestamp,
57-
public readonly int $permissions = 1,
57+
public readonly array $permissions = [],
5858
public readonly string $action = '',
5959
public readonly bool $hasPassword = false,
60-
public readonly bool $canManage = false,
6160
public readonly ?int $expirationTimestamp = null,
6261
public readonly ?string $parent = null,
6362
) {
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCP\Share\ShareReview;
11+
12+
use OCP\AppFramework\Attribute\Consumable;
13+
14+
/**
15+
* A single permission granted by an app-managed share, as exposed to a
16+
* share-review app through {@see ShareReviewEntry::$permissions}.
17+
*
18+
* Permissions are identified by an opaque, namespaced string owned by the
19+
* emitting app and carry their own localized display metadata, so a
20+
* share-review app can render any app-specific permission without
21+
* interpreting it.
22+
*
23+
* @since 34.0.2
24+
*/
25+
#[Consumable(since: '34.0.2')]
26+
final class ShareReviewPermission {
27+
/**
28+
* Identifiers for the permissions of files/folder shares, owned by the
29+
* files app. Other apps MUST NOT emit these — every app uses its own
30+
* namespace, even for permissions with the same name (e.g. "deck:edit").
31+
*
32+
* @since 34.0.2
33+
*/
34+
public const FILES_READ = 'files:read';
35+
36+
/**
37+
* @since 34.0.2
38+
*/
39+
public const FILES_UPDATE = 'files:update';
40+
41+
/**
42+
* @since 34.0.2
43+
*/
44+
public const FILES_CREATE = 'files:create';
45+
46+
/**
47+
* @since 34.0.2
48+
*/
49+
public const FILES_DELETE = 'files:delete';
50+
51+
/**
52+
* @since 34.0.2
53+
*/
54+
public const FILES_RESHARE = 'files:reshare';
55+
56+
/**
57+
* @param string $id Opaque, stable identifier owned by the emitting app,
58+
* prefixed with the emitting app's ID
59+
* ("<appId>:<permission>", e.g. "deck:manage"). Apps
60+
* never share identifiers, even for permissions with
61+
* the same name. Consumers must not parse or interpret
62+
* the identifier beyond equality checks (e.g. for icon
63+
* or translation lookup).
64+
* @param string $displayName Localized, human readable label.
65+
* @param string|null $hint Optional localized description.
66+
* @param int $priority 1-100, higher is listed first.
67+
*
68+
* @since 34.0.2
69+
*/
70+
public function __construct(
71+
public readonly string $id,
72+
public readonly string $displayName,
73+
public readonly ?string $hint = null,
74+
public readonly int $priority = 50,
75+
) {
76+
}
77+
}

tests/lib/Share20/ShareReview/ShareReviewEntryTest.php

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,27 @@
1111

1212
use OCP\Share\IShare;
1313
use OCP\Share\ShareReview\ShareReviewEntry;
14+
use OCP\Share\ShareReview\ShareReviewPermission;
1415
use PHPUnit\Framework\TestCase;
1516

1617
final class ShareReviewEntryTest extends TestCase {
1718

1819
public function testHoldsAllFields(): void {
20+
$permissions = [
21+
new ShareReviewPermission('deck:read', 'Read', priority: 80),
22+
new ShareReviewPermission('deck:manage', 'Manage board', priority: 30),
23+
];
24+
1925
$entry = new ShareReviewEntry(
2026
id: '42',
2127
object: 'Board "Roadmap"',
2228
initiator: 'alice',
2329
type: IShare::TYPE_USER,
2430
recipient: 'bob',
2531
lastModifiedTimestamp: 1783764000,
26-
permissions: 31,
32+
permissions: $permissions,
2733
action: 'board-share-42',
2834
hasPassword: true,
29-
canManage: true,
3035
expirationTimestamp: 1785837600,
3136
parent: '23',
3237
);
@@ -37,10 +42,9 @@ public function testHoldsAllFields(): void {
3742
$this->assertSame(IShare::TYPE_USER, $entry->type);
3843
$this->assertSame('bob', $entry->recipient);
3944
$this->assertSame(1783764000, $entry->lastModifiedTimestamp);
40-
$this->assertSame(31, $entry->permissions);
45+
$this->assertSame($permissions, $entry->permissions);
4146
$this->assertSame('board-share-42', $entry->action);
4247
$this->assertTrue($entry->hasPassword);
43-
$this->assertTrue($entry->canManage);
4448
$this->assertSame(1785837600, $entry->expirationTimestamp);
4549
$this->assertSame('23', $entry->parent);
4650
}
@@ -56,10 +60,9 @@ public function testDefaults(): void {
5660
);
5761

5862
$this->assertSame(0, $entry->lastModifiedTimestamp);
59-
$this->assertSame(1, $entry->permissions);
63+
$this->assertSame([], $entry->permissions);
6064
$this->assertSame('', $entry->action);
6165
$this->assertFalse($entry->hasPassword);
62-
$this->assertFalse($entry->canManage);
6366
$this->assertNull($entry->expirationTimestamp);
6467
$this->assertNull($entry->parent);
6568
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace lib\Share20\ShareReview;
11+
12+
use OCP\Share\ShareReview\ShareReviewPermission;
13+
use PHPUnit\Framework\TestCase;
14+
15+
final class ShareReviewPermissionTest extends TestCase {
16+
17+
public function testHoldsAllFields(): void {
18+
$permission = new ShareReviewPermission(
19+
id: 'deck:manage',
20+
displayName: 'Manage board',
21+
hint: 'Administer participants and board settings',
22+
priority: 30,
23+
);
24+
25+
$this->assertSame('deck:manage', $permission->id);
26+
$this->assertSame('Manage board', $permission->displayName);
27+
$this->assertSame('Administer participants and board settings', $permission->hint);
28+
$this->assertSame(30, $permission->priority);
29+
}
30+
31+
public function testDefaults(): void {
32+
$permission = new ShareReviewPermission(ShareReviewPermission::FILES_READ, 'Read');
33+
34+
$this->assertSame('files:read', $permission->id);
35+
$this->assertSame('Read', $permission->displayName);
36+
$this->assertNull($permission->hint);
37+
$this->assertSame(50, $permission->priority);
38+
}
39+
40+
public function testFilesIdentifiers(): void {
41+
$this->assertSame('files:read', ShareReviewPermission::FILES_READ);
42+
$this->assertSame('files:update', ShareReviewPermission::FILES_UPDATE);
43+
$this->assertSame('files:create', ShareReviewPermission::FILES_CREATE);
44+
$this->assertSame('files:delete', ShareReviewPermission::FILES_DELETE);
45+
$this->assertSame('files:reshare', ShareReviewPermission::FILES_RESHARE);
46+
}
47+
}

0 commit comments

Comments
 (0)