From b4b1b0c567aa6d7695a975e13f0c413299c4b365 Mon Sep 17 00:00:00 2001 From: micbar Date: Wed, 30 Sep 2026 17:11:51 +0200 Subject: [PATCH 1/5] feat: add new editor roles --- .bingo/go-xgettext.mod | 2 + .../pkg/l10n/locale/de/LC_MESSAGES/graph.po | 25 ++++ services/graph/pkg/unifiedrole/conversion.go | 8 ++ services/graph/pkg/unifiedrole/export_test.go | 38 +++--- services/graph/pkg/unifiedrole/roles.go | 115 ++++++++++++++++++ services/graph/pkg/unifiedrole/roles_test.go | 4 + 6 files changed, 175 insertions(+), 17 deletions(-) diff --git a/.bingo/go-xgettext.mod b/.bingo/go-xgettext.mod index 8e3c6210b5..b14946a0b8 100644 --- a/.bingo/go-xgettext.mod +++ b/.bingo/go-xgettext.mod @@ -3,3 +3,5 @@ module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT go 1.23.4 require github.com/gosexy/gettext v0.0.0-20160830220431-74466a0a0c4a // go-xgettext + +require github.com/jessevdk/go-flags v1.6.1 // indirect diff --git a/services/graph/pkg/l10n/locale/de/LC_MESSAGES/graph.po b/services/graph/pkg/l10n/locale/de/LC_MESSAGES/graph.po index ad7453067e..957b2cfc1b 100644 --- a/services/graph/pkg/l10n/locale/de/LC_MESSAGES/graph.po +++ b/services/graph/pkg/l10n/locale/de/LC_MESSAGES/graph.po @@ -162,3 +162,28 @@ msgid "View, download, upload, edit, add, delete including the history." msgstr "" "Ansehen, herunterladen, hochladen, bearbeiten, hinzufügen, löschen - " "inklusive des Verlaufs." + +#. UnifiedRole SpaceEditorWithoutTrashbin, Role DisplayName (resolves +#. directly) +#: pkg/unifiedrole/roles.go:183 +msgid "Can edit with versions" +msgstr "Kann bearbeiten mit Historie" + +#. UnifiedRole EditorListGrantsWithVersions, Role Description (resolves +#. directly) +#: pkg/unifiedrole/roles.go:162 +msgid "" +"View, download, upload, edit, delete, show all versions and all invited " +"people." +msgstr "" +"Ansehen, herunterladen, hochladen, bearbeiten, löschen und anzeigen aller " +"Versionen und eingeladenen Personen." + +#. UnifiedRole FileEditorListGrantsWithVersions, Role Description (resolves +#. directly) +#: pkg/unifiedrole/roles.go:201 +msgid "" +"View, download, upload, edit, show all versions and all invited people." +msgstr "" +"Ansehen, herunterladen, hochladen, bearbeiten und anzeigen aller Versionen " +"und eingeladenen Personen." diff --git a/services/graph/pkg/unifiedrole/conversion.go b/services/graph/pkg/unifiedrole/conversion.go index a554ff2b9a..3e85e8a18b 100644 --- a/services/graph/pkg/unifiedrole/conversion.go +++ b/services/graph/pkg/unifiedrole/conversion.go @@ -219,16 +219,24 @@ func cs3RoleToDisplayName(role *conversions.Role) string { return _editorWithVersionsUnifiedRoleDisplayName case conversions.RoleEditorListGrants: return _editorListGrantsUnifiedRoleDisplayName + case conversions.RoleEditorListGrantsWithVersions: + return _editorListGrantsWithVersionsUnifiedRoleDisplayName case conversions.RoleSpaceEditor: return _spaceEditorUnifiedRoleDisplayName case conversions.RoleSpaceEditorWithoutVersions: return _spaceEditorWithoutVersionsUnifiedRoleDisplayName + case conversions.RoleSpaceEditorWithoutTrashbin: + return _spaceEditorWithoutTrashbinUnifiedRoleDisplayName + case conversions.RoleSpaceEditorWithoutVersionsWithoutTrashbin: + return _spaceEditorWithoutVersionsWithoutTrashbinUnifiedRoleDisplayName case conversions.RoleFileEditor: return _fileEditorUnifiedRoleDisplayName case conversions.RoleFileEditorWithVersions: return _fileEditorWithVersionsUnifiedRoleDisplayName case conversions.RoleFileEditorListGrants: return _fileEditorListGrantsUnifiedRoleDisplayName + case conversions.RoleFileEditorListGrantsWithVersions: + return _fileEditorListGrantsWithVersionsUnifiedRoleDisplayName case conversions.RoleEditorLite: return _editorLiteUnifiedRoleDisplayName case conversions.RoleManager: diff --git a/services/graph/pkg/unifiedrole/export_test.go b/services/graph/pkg/unifiedrole/export_test.go index c47ab0ea31..ffafccc3ec 100644 --- a/services/graph/pkg/unifiedrole/export_test.go +++ b/services/graph/pkg/unifiedrole/export_test.go @@ -1,23 +1,27 @@ package unifiedrole var ( - RoleViewer = roleViewer - RoleViewerWithVersions = roleViewerWithVersions - RoleViewerListGrants = roleViewerListGrants - RoleSpaceViewer = roleSpaceViewer - RoleSpaceViewerWithVersions = roleSpaceViewerWithVersions - RoleEditor = roleEditor - RoleEditorWithVersions = roleEditorWithVersions - RoleEditorListGrants = roleEditorListGrants - RoleSpaceEditor = roleSpaceEditor - RoleSpaceEditorWithVersions = roleSpaceEditorWithVersions - RoleFileEditor = roleFileEditor - RoleFileEditorWithVersions = roleFileEditorWithVersions - RoleFileEditorListGrants = roleFileEditorListGrants - RoleEditorLite = roleEditorLite - RoleManager = roleManager - RoleSecureViewer = roleSecureViewer - RoleDenied = roleDenied + RoleViewer = roleViewer + RoleViewerWithVersions = roleViewerWithVersions + RoleViewerListGrants = roleViewerListGrants + RoleSpaceViewer = roleSpaceViewer + RoleSpaceViewerWithVersions = roleSpaceViewerWithVersions + RoleEditor = roleEditor + RoleEditorWithVersions = roleEditorWithVersions + RoleEditorListGrants = roleEditorListGrants + RoleEditorListGrantsWithVersions = roleEditorListGrantsWithVersions + RoleSpaceEditor = roleSpaceEditor + RoleSpaceEditorWithVersions = roleSpaceEditorWithVersions + RoleSpaceEditorWithoutTrashbin = roleSpaceEditorWithoutTrashbin + RoleSpaceEditorWithoutVersionsWithoutTrashbin = roleSpaceEditorWithoutVersionsWithoutTrashbin + RoleFileEditor = roleFileEditor + RoleFileEditorWithVersions = roleFileEditorWithVersions + RoleFileEditorListGrants = roleFileEditorListGrants + RoleFileEditorListGrantsWithVersions = roleFileEditorListGrantsWithVersions + RoleEditorLite = roleEditorLite + RoleManager = roleManager + RoleSecureViewer = roleSecureViewer + RoleDenied = roleDenied BuildInRoles = buildInRoles diff --git a/services/graph/pkg/unifiedrole/roles.go b/services/graph/pkg/unifiedrole/roles.go index 52aeb0d2ab..e67bbdbcee 100644 --- a/services/graph/pkg/unifiedrole/roles.go +++ b/services/graph/pkg/unifiedrole/roles.go @@ -32,16 +32,25 @@ const ( UnifiedRoleEditorWithVersionsID = "b8c6e1c9-5d2a-4f0e-9c3b-1a2b3c4d5e6f" // UnifiedRoleEditorListGrantsID Unified role editor id. UnifiedRoleEditorListGrantsID = "e8ea8b21-abd4-45d2-b893-8d1546378e9e" + // UnifiedRoleEditorListGrantsWithVersionsID Unified role editor with list grants and versions id. + UnifiedRoleEditorListGrantsWithVersionsID = "0911d62b-1e3f-4778-8b1b-903b7e4e8476" // UnifiedRoleSpaceEditorID Unified role space editor id. UnifiedRoleSpaceEditorID = "58c63c02-1d89-4572-916a-870abc5a1b7d" // UnifiedRoleSpaceEditorWithoutVersionsID Unified role space editor without list/restore versions id. UnifiedRoleSpaceEditorWithoutVersionsID = "3284f2d5-0070-4ad8-ac40-c247f7c1fb27" + // UnifiedRoleSpaceEditorWithoutTrashbinID Unified role space editor without list/restore resources in trashbin id. + UnifiedRoleSpaceEditorWithoutTrashbinID = "8f4701d9-c68f-4109-a482-88e22ee32805" + // UnifiedRoleSpaceEditorWithoutVersionsWithoutTrashbinID Unified role space editor without list/restore + // versions and without list/restore resources in trashbin id. + UnifiedRoleSpaceEditorWithoutVersionsWithoutTrashbinID = "a5f73816-4d4b-452d-8973-3b61c3d0bed4" // UnifiedRoleFileEditorID Unified role file editor id. UnifiedRoleFileEditorID = "2d00ce52-1fc2-4dbc-8b95-a73b73395f5a" // UnifiedRoleFileEditorWithVersionsID Unified role file editor id. UnifiedRoleFileEditorWithVersionsID = "3d00ce52-1fc2-4dbc-8b95-a73b73395f5a" // UnifiedRoleFileEditorListGrantsID Unified role file editor id. UnifiedRoleFileEditorListGrantsID = "c1235aea-d106-42db-8458-7d5610fb0a67" + // UnifiedRoleFileEditorListGrantsWithVersionsID Unified role file editor with list grants and versions id. + UnifiedRoleFileEditorListGrantsWithVersionsID = "b173329d-cf2e-42f0-a595-ee410645d840" // UnifiedRoleEditorLiteID Unified role editor-lite id. UnifiedRoleEditorLiteID = "1c996275-f1c9-4e71-abdf-a42f6495e960" // UnifiedRoleManagerID Unified role manager id. @@ -149,6 +158,12 @@ var ( // UnifiedRole EditorListGrants, Role DisplayName (resolves directly) _editorListGrantsUnifiedRoleDisplayName = l10n.Template("Can edit") + // UnifiedRole EditorListGrantsWithVersions, Role Description (resolves directly) + _editorListGrantsWithVersionsUnifiedRoleDescription = l10n.Template("View, download, upload, edit, delete, show all versions and all invited people.") + + // UnifiedRole EditorListGrantsWithVersions, Role DisplayName (resolves directly) + _editorListGrantsWithVersionsUnifiedRoleDisplayName = l10n.Template("Can edit") + // UnifiedRole SpaseEditor, Role Description (resolves directly) _spaceEditorUnifiedRoleDescription = l10n.Template("View, download, upload, edit, add, delete including the history.") @@ -161,6 +176,18 @@ var ( // UnifiedRole SpaseEditorWithoutVersions, Role DisplayName (resolves directly) _spaceEditorWithoutVersionsUnifiedRoleDisplayName = l10n.Template("Can edit without versions") + // UnifiedRole SpaceEditorWithoutTrashbin, Role Description (resolves directly) + _spaceEditorWithoutTrashbinUnifiedRoleDescription = l10n.Template("View, download, upload, edit, add, delete including the history.") + + // UnifiedRole SpaceEditorWithoutTrashbin, Role DisplayName (resolves directly) + _spaceEditorWithoutTrashbinUnifiedRoleDisplayName = l10n.Template("Can edit with versions") + + // UnifiedRole SpaceEditorWithoutVersionsWithoutTrashbin, Role Description (resolves directly) + _spaceEditorWithoutVersionsWithoutTrashbinUnifiedRoleDescription = l10n.Template("View, download, upload, edit, add and delete.") + + // UnifiedRole SpaceEditorWithoutVersionsWithoutTrashbin, Role DisplayName (resolves directly) + _spaceEditorWithoutVersionsWithoutTrashbinUnifiedRoleDisplayName = l10n.Template("Can edit") + // UnifiedRole FileEditor, Role Description (resolves directly) _fileEditorUnifiedRoleDescription = l10n.Template("View, download and edit.") @@ -170,6 +197,12 @@ var ( // UnifiedRole FileEditorListGrants, Role Description (resolves directly) _fileEditorListGrantsUnifiedRoleDescription = l10n.Template("View, download, edit and show all invited people.") + // UnifiedRole FileEditorListGrantsWithVersions, Role Description (resolves directly) + _fileEditorListGrantsWithVersionsUnifiedRoleDescription = l10n.Template("View, download, upload, edit, show all versions and all invited people.") + + // UnifiedRole FileEditorListGrantsWithVersions, Role DisplayName (resolves directly) + _fileEditorListGrantsWithVersionsUnifiedRoleDisplayName = l10n.Template("Can edit") + // UnifiedRole FileEditorWithVersions, Role DisplayName (resolves directly) _fileEditorWithVersionsUnifiedRoleDisplayName = l10n.Template("Can edit") @@ -227,12 +260,16 @@ var ( roleSpaceViewerWithVersions, roleEditor, roleEditorListGrants, + roleEditorListGrantsWithVersions, roleEditorWithVersions, roleSpaceEditorWithVersions, roleSpaceEditor, + roleSpaceEditorWithoutTrashbin, + roleSpaceEditorWithoutVersionsWithoutTrashbin, roleFileEditor, roleFileEditorWithVersions, roleFileEditorListGrants, + roleFileEditorListGrantsWithVersions, roleEditorLite, roleManager, roleSecureViewer, @@ -432,6 +469,27 @@ var ( } }() + // roleEditorListGrantsWithVersions creates an editor role that can also list versions. + roleEditorListGrantsWithVersions = func() *libregraph.UnifiedRoleDefinition { + r := conversions.NewEditorListGrantsWithVersionsRole() + return &libregraph.UnifiedRoleDefinition{ + Id: proto.String(UnifiedRoleEditorListGrantsWithVersionsID), + Description: proto.String(_editorListGrantsWithVersionsUnifiedRoleDescription), + DisplayName: proto.String(cs3RoleToDisplayName(r)), + RolePermissions: []libregraph.UnifiedRolePermission{ + { + AllowedResourceActions: CS3ResourcePermissionsToLibregraphActions(r.CS3ResourcePermissions()), + Condition: proto.String(UnifiedRoleConditionFolder), + }, + { + AllowedResourceActions: CS3ResourcePermissionsToLibregraphActions(r.CS3ResourcePermissions()), + Condition: proto.String(UnifiedRoleConditionFolderFederatedUser), + }, + }, + LibreGraphWeight: proto.Int32(72), + } + }() + roleEditorWithVersions = func() *libregraph.UnifiedRoleDefinition { r := conversions.NewEditorWithVersionsRole() return &libregraph.UnifiedRoleDefinition{ @@ -448,6 +506,42 @@ var ( } }() + // roleSpaceEditorWithoutVersionsWithoutTrashbin creates a space editor role without + // list/restore versions and without list/restore resources in the trashbin. + roleSpaceEditorWithoutVersionsWithoutTrashbin = func() *libregraph.UnifiedRoleDefinition { + r := conversions.NewSpaceEditorWithoutVersionsWithoutTrashbinRole() + return &libregraph.UnifiedRoleDefinition{ + Id: proto.String(UnifiedRoleSpaceEditorWithoutVersionsWithoutTrashbinID), + Description: proto.String(_spaceEditorWithoutVersionsWithoutTrashbinUnifiedRoleDescription), + DisplayName: proto.String(cs3RoleToDisplayName(r)), + RolePermissions: []libregraph.UnifiedRolePermission{ + { + AllowedResourceActions: CS3ResourcePermissionsToLibregraphActions(r.CS3ResourcePermissions()), + Condition: proto.String(UnifiedRoleConditionDrive), + }, + }, + LibreGraphWeight: proto.Int32(78), + } + }() + + // roleSpaceEditorWithoutTrashbin creates a space editor role without list/restore + // resources in the trashbin. + roleSpaceEditorWithoutTrashbin = func() *libregraph.UnifiedRoleDefinition { + r := conversions.NewSpaceEditorWithoutTrashbinRole() + return &libregraph.UnifiedRoleDefinition{ + Id: proto.String(UnifiedRoleSpaceEditorWithoutTrashbinID), + Description: proto.String(_spaceEditorWithoutTrashbinUnifiedRoleDescription), + DisplayName: proto.String(cs3RoleToDisplayName(r)), + RolePermissions: []libregraph.UnifiedRolePermission{ + { + AllowedResourceActions: CS3ResourcePermissionsToLibregraphActions(r.CS3ResourcePermissions()), + Condition: proto.String(UnifiedRoleConditionDrive), + }, + }, + LibreGraphWeight: proto.Int32(88), + } + }() + // roleSpaceEditor creates an editor without versions role roleSpaceEditor = func() *libregraph.UnifiedRoleDefinition { r := conversions.NewSpaceEditorWithoutVersionsRole() @@ -541,6 +635,27 @@ var ( } }() + // roleFileEditorListGrantsWithVersions creates a file-editor role that can also list versions. + roleFileEditorListGrantsWithVersions = func() *libregraph.UnifiedRoleDefinition { + r := conversions.NewFileEditorListGrantsWithVersionsRole() + return &libregraph.UnifiedRoleDefinition{ + Id: proto.String(UnifiedRoleFileEditorListGrantsWithVersionsID), + Description: proto.String(_fileEditorListGrantsWithVersionsUnifiedRoleDescription), + DisplayName: proto.String(cs3RoleToDisplayName(r)), + RolePermissions: []libregraph.UnifiedRolePermission{ + { + AllowedResourceActions: CS3ResourcePermissionsToLibregraphActions(r.CS3ResourcePermissions()), + Condition: proto.String(UnifiedRoleConditionFile), + }, + { + AllowedResourceActions: CS3ResourcePermissionsToLibregraphActions(r.CS3ResourcePermissions()), + Condition: proto.String(UnifiedRoleConditionFileFederatedUser), + }, + }, + LibreGraphWeight: proto.Int32(111), + } + }() + // roleManager creates a manager role roleManager = func() *libregraph.UnifiedRoleDefinition { r := conversions.NewManagerRole() diff --git a/services/graph/pkg/unifiedrole/roles_test.go b/services/graph/pkg/unifiedrole/roles_test.go index 986163e2cd..d6d910f008 100644 --- a/services/graph/pkg/unifiedrole/roles_test.go +++ b/services/graph/pkg/unifiedrole/roles_test.go @@ -185,6 +185,7 @@ func TestGetRolesByPermissions(t *testing.T) { unifiedrole.RoleFileEditor, unifiedrole.RoleFileEditorWithVersions, unifiedrole.RoleFileEditorListGrants, + unifiedrole.RoleFileEditorListGrantsWithVersions, }, }, "BuildInRoles | folder": { @@ -199,6 +200,7 @@ func TestGetRolesByPermissions(t *testing.T) { unifiedrole.RoleEditor, unifiedrole.RoleEditorListGrants, unifiedrole.RoleEditorWithVersions, + unifiedrole.RoleEditorListGrantsWithVersions, unifiedrole.RoleDenied, }, }, @@ -208,7 +210,9 @@ func TestGetRolesByPermissions(t *testing.T) { unifiedRoleDefinition: []*libregraph.UnifiedRoleDefinition{ unifiedrole.RoleSpaceViewer, unifiedrole.RoleSpaceViewerWithVersions, + unifiedrole.RoleSpaceEditorWithoutVersionsWithoutTrashbin, unifiedrole.RoleSpaceEditor, + unifiedrole.RoleSpaceEditorWithoutTrashbin, unifiedrole.RoleSpaceEditorWithVersions, unifiedrole.RoleManager, }, From fe33ebc18ee1a6f1bb08803d1134d37739c2e32d Mon Sep 17 00:00:00 2001 From: "v.scharf" Date: Fri, 2 Oct 2026 18:08:08 +0200 Subject: [PATCH 2/5] add tests for additional roles --- .woodpecker.star | 6 + tests/acceptance/TestHelpers/GraphHelper.php | 4 + tests/acceptance/bootstrap/GraphContext.php | 103 ++ .../acceptance/bootstrap/OcConfigContext.php | 30 + .../acceptance/bootstrap/SharingNgContext.php | 94 +- tests/acceptance/bootstrap/SpacesContext.php | 37 +- tests/acceptance/config/behat.yml | 12 + .../editorListGrantsWithVersions.feature | 1067 ++++++++++++++ .../listGrantsShareRole.feature | 1281 +++++++++++++++++ .../spaceEditorWithoutTrashbin.feature | 28 + ...itorWithoutVersionsWithoutTrashbin.feature | 33 + 11 files changed, 2688 insertions(+), 7 deletions(-) create mode 100644 tests/acceptance/features/apiSharingNgAdditionalShareRole/editorListGrantsWithVersions.feature create mode 100644 tests/acceptance/features/apiSharingNgAdditionalShareRole/listGrantsShareRole.feature create mode 100644 tests/acceptance/features/apiSharingNgAdditionalShareRole/spaceEditorWithoutTrashbin.feature create mode 100644 tests/acceptance/features/apiSharingNgAdditionalShareRole/spaceEditorWithoutVersionsWithoutTrashbin.feature diff --git a/.woodpecker.star b/.woodpecker.star index c25a2610ca..a0d7ad8870 100644 --- a/.woodpecker.star +++ b/.woodpecker.star @@ -232,6 +232,12 @@ config = { ], "skip": False, }, + "sharingNgAdditionalShareRole": { + "suites": [ + "apiSharingNgAdditionalShareRole", + ], + "skip": False, + }, "notification": { "suites": [ "apiNotification", diff --git a/tests/acceptance/TestHelpers/GraphHelper.php b/tests/acceptance/TestHelpers/GraphHelper.php index 8fda5ccc58..4a1a6e7fa5 100644 --- a/tests/acceptance/TestHelpers/GraphHelper.php +++ b/tests/acceptance/TestHelpers/GraphHelper.php @@ -49,6 +49,10 @@ class GraphHelper { 'File Editor With Versions' => '3d00ce52-1fc2-4dbc-8b95-a73b73395f5a', 'File Editor List Grants' => 'c1235aea-d106-42db-8458-7d5610fb0a67', 'Denied' => '63e64e19-8d43-42ec-a738-2b6af2610efa', + 'Editor List Grants With Versions' => '0911d62b-1e3f-4778-8b1b-903b7e4e8476', + 'Space Editor Without Trashbin' => '8f4701d9-c68f-4109-a482-88e22ee32805', + 'Space Editor Without Versions Without Trashbin' => 'a5f73816-4d4b-452d-8973-3b61c3d0bed4', + 'File Editor List Grants With Versions' => 'b173329d-cf2e-42f0-a595-ee410645d840', ]; public const SHARES_SPACE_ID = 'a0ca6a90-a365-4782-871e-d44447bbc668$a0ca6a90-a365-4782-871e-d44447bbc668'; diff --git a/tests/acceptance/bootstrap/GraphContext.php b/tests/acceptance/bootstrap/GraphContext.php index 8129bb35f0..59b043d52c 100644 --- a/tests/acceptance/bootstrap/GraphContext.php +++ b/tests/acceptance/bootstrap/GraphContext.php @@ -3490,4 +3490,107 @@ public function userGetsDriveItemWithColonPathOfPersonalSpaceOf( $url = "/graph/$apiVersion/drives/$driveId/root:/$encoded"; $this->sendGraphRequestAndCaptureResponse($user, "GET", $url); } + + /** + * @param string $user + * @param string $resource + * @param string $spaceName + * + * @return ResponseInterface + * @throws GuzzleException + */ + public function getActivities( + string $user, + string $resource, + string $spaceName + ): ResponseInterface { + if ($spaceName === "Shares") { + $resourceId = $this->spacesContext->getSharesRemoteItemId($user, $resource); + } else { + $resourceId = $this->spacesContext->getResourceId($user, $spaceName, $resource); + } + return GraphHelper::getActivities( + $this->featureContext->getBaseUrl(), + $this->featureContext->getStepLineRef(), + $user, + $this->featureContext->getPasswordForUser($user), + $resourceId + ); + } + + /** + * @param string $user + * @param string $resource + * @param string $spaceName + * @param TableNode $table + * + * @return void + * @throws GuzzleException + */ + #[Then('/^for user "([^"]*)" (?:folder|file) "([^"]*)" of the space "([^"]*)" should have the following activities:$/')] + public function forUserFolderOrFileOfTheSpaceShouldHaveTheseActivities( + string $user, + string $resource, + string $spaceName, + TableNode $table + ): void { + $expectedMessages = \array_map(fn ($row) => $row[0], $table->getRows()); + + // Activities are recorded asynchronously from events, so poll until every + // expected activity shows up (or the wait times out). + $actualMessages = []; + WaitHelper::waitUntil( + function () use ($user, $resource, $spaceName, &$actualMessages) { + $activities = $this->featureContext->getJsonDecodedResponse( + $this->getActivities($user, $resource, $spaceName) + ); + $actualMessages = \array_map( + fn ($activity) => $activity['template']['message'], + $activities['value'] ?? [] + ); + }, + function () use ($expectedMessages, &$actualMessages) { + foreach ($expectedMessages as $message) { + if (!\in_array($message, $actualMessages, true)) { + return false; + } + } + return true; + } + ); + + $errors = []; + foreach ($expectedMessages as $message) { + if (!\in_array($message, $actualMessages, true)) { + $errors[] = "Expected activity '$message' was not found in the response. "; + } + } + if (!empty($errors)) { + Assert::fail(implode("\n", $errors)); + } + } + + /** + * @param string $user + * @param string $resource + * @param string $spaceName + * + * @return void + * @throws GuzzleException + */ + #[Then('/^for user "([^"]*)" (?:folder|file) "([^"]*)" of the space "([^"]*)" should not have any activity$/')] + public function forUserFileOfTheSpaceShouldNotHaveAnyActivity( + string $user, + string $resource, + string $spaceName + ): void { + $response = $this->getActivities($user, $resource, $spaceName); + $responseBody = $response->getBody()->getContents(); + Assert::assertEmpty( + $responseBody, + __METHOD__ + . "\nExpected no activity of resource '$resource' for user '$user', but some activities were found\n" + . print_r(json_decode($responseBody, true), true) + ); + } } diff --git a/tests/acceptance/bootstrap/OcConfigContext.php b/tests/acceptance/bootstrap/OcConfigContext.php index b4efb0cf5c..1e1bb4fd8e 100644 --- a/tests/acceptance/bootstrap/OcConfigContext.php +++ b/tests/acceptance/bootstrap/OcConfigContext.php @@ -123,6 +123,36 @@ public function theAdministratorHasEnabledTheRole(string $role): void { $this->setEnabledPermissionsRoles($defaultRoles); } + /** + * + * @param TableNode $table + * + * @return void + */ + #[Given('the administrator has enabled the following share permissions roles:')] + public function theAdministratorHasEnabledTheFollowingSharePermissionsRoles(TableNode $table): void { + $defaultRoles = array_values(GraphHelper::DEFAULT_PERMISSIONS_ROLES); + $roles = []; + foreach ($table->getHash() as $row) { + $roles[] = $row['permissions-role']; + $roleId = GraphHelper::getPermissionsRoleIdByName($row['permissions-role']); + if (!\in_array($roleId, $defaultRoles)) { + $defaultRoles[] = $roleId; + } + } + $envs = [ + "GRAPH_AVAILABLE_ROLES" => implode(',', $defaultRoles), + ]; + $response = OcConfigHelper::reConfigureOc($envs); + Assert::assertEquals( + 200, + $response->getStatusCode(), + "Failed to enable roles: " . implode(', ', $roles) + . ". Response: " . $response->getBody()->getContents() + ); + $this->setEnabledPermissionsRoles($defaultRoles); + } + /** * * @param string $role diff --git a/tests/acceptance/bootstrap/SharingNgContext.php b/tests/acceptance/bootstrap/SharingNgContext.php index ffc7b31450..311743f808 100644 --- a/tests/acceptance/bootstrap/SharingNgContext.php +++ b/tests/acceptance/bootstrap/SharingNgContext.php @@ -26,6 +26,7 @@ use PHPUnit\Framework\Assert; use Psr\Http\Message\ResponseInterface; use TestHelpers\GraphHelper; +use TestHelpers\WaitHelper; use TestHelpers\WebDavHelper; use TestHelpers\HttpRequestHelper; use TestHelpers\BehatHelper; @@ -161,12 +162,18 @@ public function getPermissionsList( ?string $resource = '', ?string $query = null ): ResponseInterface { - $spaceId = ($this->spacesContext->getSpaceByName($user, $space))["id"]; - - if ($fileOrFolder === 'folder') { - $itemId = $this->spacesContext->getResourceId($user, $space, $resource); + if ($space === "Shares") { + // a shared resource lives in the owner's space; its permissions are + // listed via the share's remote item id and its parent drive id + $spaceId = $this->spacesContext->getSharesRemoteItemParentDriveId($user, $resource); + $itemId = $this->spacesContext->getSharesRemoteItemId($user, $resource); } else { - $itemId = $this->spacesContext->getFileId($user, $space, $resource); + $spaceId = ($this->spacesContext->getSpaceByName($user, $space))["id"]; + if ($fileOrFolder === 'folder') { + $itemId = $this->spacesContext->getResourceId($user, $space, $resource); + } else { + $itemId = $this->spacesContext->getFileId($user, $space, $resource); + } } return GraphHelper::getPermissionsList( @@ -233,6 +240,63 @@ public function userGetsPermissionsListForResourceOfTheSpaceUsingTheGraphAPI( ); } + /** + * @param string $user + * @param string $fileOrFolder (file|folder) + * @param string $resource + * @param TableNode $table + * + * @return void + * @throws GuzzleException + */ + #[Then('/^for user "([^"]*)" (file|folder) "([^"]*)" should have the following shares:$/')] + public function userGetsAllTheSharesOfTheResource( + string $user, + string $fileOrFolder, + string $resource, + TableNode $table + ): void { + $permission = $this->getPermissionsList($user, $fileOrFolder, "Shares", $resource); + $jsonBody = $this->featureContext->getJsonDecodedResponseBodyContent($permission); + + $errors = []; + foreach ($table->getHash() as $row) { + $expectedRoleId = GraphHelper::getPermissionsRoleIdByName($row['permissionsRole']); + if ($row['shareType'] === 'user') { + $expectedSharee = $this->featureContext->getDisplayNameForUser($row['sharee']); + } else { + $expectedSharee = $row['sharee']; + } + $found = false; + $actualSharee = ''; + foreach ($jsonBody->value as $share) { + if ($row['shareType'] === 'user') { + if (isset($share->grantedToV2->user->displayName)) { + $actualSharee = $share->grantedToV2->user->displayName; + } + } else { + if (isset($share->grantedToV2->group->displayName)) { + $actualSharee = $share->grantedToV2->group->displayName; + } + } + if ($actualSharee === $expectedSharee) { + $found = true; + if ($share->roles[0] !== $expectedRoleId) { + $errors[] = "Expected user $actualSharee share role id to be '$expectedRoleId'" + . " but found '{$share->roles[0]}'"; + } + break; + } + } + if (!$found) { + $errors[] = "Expected sharee '$expectedSharee' to be present but found '$actualSharee'"; + } + } + if (!empty($errors)) { + Assert::fail(implode("\n", $errors)); + } + } + /** * * @param string $user @@ -458,10 +522,28 @@ public function userHasSentTheFollowingResourceShareInvitation(string $user, Tab $rows, "'resource' should be provided in the data-table while sharing a resource" ); - $response = $this->sendShareInvitation($user, $rows); + $response = WaitHelper::waitUntil( + fn () => $this->sendShareInvitation($user, $rows), + fn ($response) => !self::isShareManagerMigrating($response) + ); $this->featureContext->theHTTPStatusCodeShouldBe(200, "", $response); } + /** + * @param ResponseInterface $response + * + * @return bool + */ + private static function isShareManagerMigrating(ResponseInterface $response): bool { + if ($response->getStatusCode() !== 500) { + return false; + } + return \str_contains( + (string)$response->getBody(), + "share manager is currently migrating" + ); + } + /** * * @param string $user diff --git a/tests/acceptance/bootstrap/SpacesContext.php b/tests/acceptance/bootstrap/SpacesContext.php index 2d20b3ce66..89fc9870c6 100644 --- a/tests/acceptance/bootstrap/SpacesContext.php +++ b/tests/acceptance/bootstrap/SpacesContext.php @@ -307,6 +307,38 @@ public function getSharesRemoteItemId(string $user, string $share): string { throw new Exception("Cannot find share: $share"); } + /** + * @param string $user + * @param string $share + * + * @return string + * + * @throws Exception|GuzzleException + */ + public function getSharesRemoteItemParentDriveId(string $user, string $share): string { + $credentials = $this->featureContext->graphContext->getAdminOrUserCredentials($user); + $response = GraphHelper::getSharesSharedWithMe( + $this->featureContext->getBaseUrl(), + $this->featureContext->getStepLineRef(), + $credentials['username'], + $credentials['password'] + ); + + $jsonBody = $this->featureContext->getJsonDecodedResponseBodyContent($response); + + // Search parent driveId of a given share's remoteItem + foreach ($jsonBody->value as $item) { + if (isset($item->name) && $item->name === $share) { + if (isset($item->remoteItem->parentReference->driveId)) { + return $item->remoteItem->parentReference->driveId; + } + throw new Exception("Failed to find remoteItem parent driveId for share: $share"); + } + } + + throw new Exception("Cannot find share: $share"); + } + /** * @param string $user * @param string $share @@ -3145,6 +3177,7 @@ public function listAllDeletedFilesFromTrash( * @throws GuzzleException */ #[When('user :user lists all deleted files in the trash bin of the space :spaceName')] + #[When('user :user tries to list all deleted files in the trash bin of the space :spaceName')] public function userListAllDeletedFilesInTrash( string $user, string $spaceName @@ -3264,6 +3297,7 @@ public function checkExistenceOfObjectsInTrashbin( * @throws Exception */ #[When('/^user "([^"]*)" restores the (?:file|folder) "([^"]*)" from the trash of the space "([^"]*)" to "([^"]*)"$/')] + #[When('/^user "([^"]*)" tries to restore the (?:file|folder) "([^"]*)" from the trash of the space "([^"]*)" to "([^"]*)"$/')] public function userRestoresSpaceObjectsFromTrashRequest( string $user, string $object, @@ -3271,9 +3305,10 @@ public function userRestoresSpaceObjectsFromTrashRequest( string $destination ): void { $space = $this->getSpaceByName($user, $spaceName); + $spaceOwner = $this->getSpaceCreator($spaceName); // find object in trash - $objectsInTrash = $this->getObjectsInTrashbin($user, $spaceName); + $objectsInTrash = $this->getObjectsInTrashbin($spaceOwner, $spaceName); $pathToDeletedObject = ""; foreach ($objectsInTrash as $objectInTrash) { if ($objectInTrash["name"] === $object) { diff --git a/tests/acceptance/config/behat.yml b/tests/acceptance/config/behat.yml index 39d04b02cf..7b45b0cf21 100644 --- a/tests/acceptance/config/behat.yml +++ b/tests/acceptance/config/behat.yml @@ -383,6 +383,18 @@ default: - SharingNgContext: - PublicWebDavContext: - OcConfigContext: + + apiSharingNgAdditionalShareRole: + paths: + - "%paths.base%/../features/apiSharingNgAdditionalShareRole" + context: *common_ldap_suite_context + contexts: + - FeatureContext: *common_feature_context_params + - SpacesContext: + - GraphContext: + - SharingNgContext: + - FilesVersionsContext: + - OcConfigContext: apiOcm: paths: diff --git a/tests/acceptance/features/apiSharingNgAdditionalShareRole/editorListGrantsWithVersions.feature b/tests/acceptance/features/apiSharingNgAdditionalShareRole/editorListGrantsWithVersions.feature new file mode 100644 index 0000000000..1f2536979d --- /dev/null +++ b/tests/acceptance/features/apiSharingNgAdditionalShareRole/editorListGrantsWithVersions.feature @@ -0,0 +1,1067 @@ +@env-config +Feature: an user shares resources + As a user + I want to share resources with Editor List Grants With Versions role + So that users can edit the resource and see the versions + + Background: + Given these users have been created with default attributes: + | username | + | Alice | + | Brian | + And the administrator has enabled the following share permissions roles: + | permissions-role | + | File Editor List Grants With Versions | + | Editor List Grants With Versions | + + + Scenario: sharee checks version of a file shared with FileEditorListGrantsWithVersions role + Given user "Alice" has uploaded file with content "to share" to "textfile.txt" + And we save it into "FILEID" + When user "Alice" sends the following resource share invitation using the Graph API: + | resource | textfile.txt | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | File Editor List Grants With Versions | + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "array", + "maxItems": 1, + "minItems": 1, + "items": { + "type": "object", + "required": ["createdDateTime", "id", "roles", "grantedToV2", "invitation"], + "properties": { + "createdDateTime": { "format": "date-time" }, + "id": { "pattern": "^%permissions_id_pattern%$" }, + "roles": { + "type": "array", + "maxItems": 1, + "minItems": 1, + "items": { "const": "b173329d-cf2e-42f0-a595-ee410645d840" } + }, + "invitation": { + "type": "object", + "required": ["invitedBy"], + "properties": { + "invitedBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id", "@libre.graph.userType"], + "properties": { + "displayName": { "const": "Alice Hansen" }, + "id": { "pattern": "^%user_id_pattern%$" }, + "@libre.graph.userType": { "const": "Member" } + } + } + } + } + } + }, + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["id", "displayName", "@libre.graph.userType"], + "properties": { + "id": { "pattern": "^%user_id_pattern%$" }, + "displayName": { "const": "Brian Murphy" }, + "@libre.graph.userType": { "const": "Member" } + } + } + } + } + } + } + } + } + } + """ + And user "Brian" has uploaded file with content "updated content" to "Shares/textfile.txt" + When user "Brian" gets the number of versions of file "textfile.txt" using file-id "<>" + Then the HTTP status code should be "207" + And the number of versions should be "1" + + + Scenario: sharee checks version of a file inside a folder shared with EditorListGrantsWithVersions role + Given user "Alice" has created folder "folderToShare" + And user "Alice" has uploaded file with content "to share" to "folderToShare/textfile.txt" + And we save it into "FILEID" + When user "Alice" sends the following resource share invitation using the Graph API: + | resource | folderToShare | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | Editor List Grants With Versions | + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "array", + "maxItems": 1, + "minItems": 1, + "items": { + "type": "object", + "required": ["createdDateTime", "id", "roles", "grantedToV2", "invitation"], + "properties": { + "createdDateTime": { "format": "date-time" }, + "id": { "pattern": "^%permissions_id_pattern%$" }, + "roles": { + "type": "array", + "maxItems": 1, + "minItems": 1, + "items": { "const": "0911d62b-1e3f-4778-8b1b-903b7e4e8476" } + }, + "invitation": { + "type": "object", + "required": ["invitedBy"], + "properties": { + "invitedBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id", "@libre.graph.userType"], + "properties": { + "displayName": { "const": "Alice Hansen" }, + "id": { "pattern": "^%user_id_pattern%$" }, + "@libre.graph.userType": { "const": "Member" } + } + } + } + } + } + }, + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["id", "displayName", "@libre.graph.userType"], + "properties": { + "id": { "pattern": "^%user_id_pattern%$" }, + "displayName": { "const": "Brian Murphy" }, + "@libre.graph.userType": { "const": "Member" } + } + } + } + } + } + } + } + } + } + """ + And user "Brian" has uploaded file with content "updated content" to "Shares/folderToShare/textfile.txt" + When user "Brian" gets the number of versions of file "textfile.txt" using file-id "<>" + Then the HTTP status code should be "207" + And the number of versions should be "1" + + + Scenario: user lists permissions of a file in personal space after enabling FileEditorListGrantsWithVersions role + Given user "Alice" has uploaded file with content "hello world" to "textfile0.txt" + When user "Alice" gets permissions list for file "textfile0.txt" of the space "Personal" using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "@libre.graph.permissions.actions.allowedValues", + "@libre.graph.permissions.roles.allowedValues" + ], + "properties": { + "@libre.graph.permissions.actions.allowedValues": { + "type": "array", + "minItems": 19, + "maxItems": 19, + "uniqueItems": true + }, + "@libre.graph.permissions.roles.allowedValues": { + "type": "array", + "minItems": 3, + "maxItems": 3, + "uniqueItems": true, + "items": { + "oneOf": [ + { + "type": "object", + "required": ["@libre.graph.weight", "description", "displayName", "id"], + "properties": { + "@libre.graph.weight": { "const": 10 }, + "description": { "const": "View and download." }, + "displayName": { "const": "Can view" }, + "id": { "const": "b1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5" } + } + }, + { + "type": "object", + "required": ["@libre.graph.weight", "description", "displayName", "id"], + "properties": { + "@libre.graph.weight": { "const": 100 }, + "description": { "const": "View, download and edit." }, + "displayName": { "const": "Can edit" }, + "id": { "const": "2d00ce52-1fc2-4dbc-8b95-a73b73395f5a" } + } + }, + { + "type": "object", + "required": ["@libre.graph.weight", "description", "displayName", "id"], + "properties": { + "@libre.graph.weight": { "const": 111 }, + "description": { "const": "View, download, upload, edit, show all versions and all invited people." }, + "displayName": { "const": "Can edit" }, + "id": { "const": "b173329d-cf2e-42f0-a595-ee410645d840" } + } + } + ] + } + } + } + } + """ + + + Scenario: user lists permissions of a file in project space after enabling FileEditorListGrantsWithVersions role + Given using spaces DAV path + And the administrator has assigned the role "Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "new-space" with the default quota using the Graph API + And user "Alice" has uploaded a file inside space "new-space" with content "hello world" to "textfile0.txt" + When user "Alice" gets permissions list for file "textfile0.txt" of the space "new-space" using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "@libre.graph.permissions.actions.allowedValues", + "@libre.graph.permissions.roles.allowedValues" + ], + "properties": { + "@libre.graph.permissions.actions.allowedValues": { + "type": "array", + "minItems": 19, + "maxItems": 19, + "uniqueItems": true + }, + "@libre.graph.permissions.roles.allowedValues": { + "type": "array", + "minItems": 3, + "maxItems": 3, + "uniqueItems": true, + "items": { + "oneOf": [ + { + "type": "object", + "required": ["@libre.graph.weight", "description", "displayName", "id"], + "properties": { + "@libre.graph.weight": { "const": 10 }, + "description": { "const": "View and download." }, + "displayName": { "const": "Can view" }, + "id": { "const": "b1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5" } + } + }, + { + "type": "object", + "required": ["@libre.graph.weight", "description", "displayName", "id"], + "properties": { + "@libre.graph.weight": { "const": 100 }, + "description": { "const": "View, download and edit." }, + "displayName": { "const": "Can edit" }, + "id": { "const": "2d00ce52-1fc2-4dbc-8b95-a73b73395f5a" } + } + }, + { + "type": "object", + "required": ["@libre.graph.weight", "description", "displayName", "id"], + "properties": { + "@libre.graph.weight": { "const": 111 }, + "description": { "const": "View, download, upload, edit, show all versions and all invited people." }, + "displayName": { "const": "Can edit" }, + "id": { "const": "b173329d-cf2e-42f0-a595-ee410645d840" } + } + } + ] + } + } + } + } + """ + + + Scenario: user lists permissions of a folder in personal space after enabling EditorListGrantsWithVersions role + Given user "Alice" has created folder "folderToShare" + When user "Alice" gets permissions list for folder "folderToShare" of the space "Personal" using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "@libre.graph.permissions.actions.allowedValues", + "@libre.graph.permissions.roles.allowedValues" + ], + "properties": { + "@libre.graph.permissions.actions.allowedValues": { + "type": "array", + "minItems": 19, + "maxItems": 19, + "uniqueItems": true + }, + "@libre.graph.permissions.roles.allowedValues": { + "type": "array", + "minItems": 4, + "maxItems": 4, + "uniqueItems": true, + "items": { + "oneOf": [ + { + "type": "object", + "required": ["@libre.graph.weight", "description", "displayName", "id"], + "properties": { + "@libre.graph.weight": { "const": 10 }, + "description": { "const": "View and download." }, + "displayName": { "const": "Can view" }, + "id": { "const": "b1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5" } + } + }, + { + "type": "object", + "required": ["@libre.graph.weight", "description", "displayName", "id"], + "properties": { + "@libre.graph.weight": { "const": 50 }, + "description": { "const": "View, download and upload." }, + "displayName": { "const": "Can upload" }, + "id": { "const": "1c996275-f1c9-4e71-abdf-a42f6495e960" } + } + }, + { + "type": "object", + "required": ["@libre.graph.weight", "description", "displayName", "id"], + "properties": { + "@libre.graph.weight": { "const": 60 }, + "description": { "const": "View, download, upload, edit, add and delete." }, + "displayName": { "const": "Can edit" }, + "id": { "const": "fb6c3e19-e378-47e5-b277-9732f9de6e21" } + } + }, + { + "type": "object", + "required": ["@libre.graph.weight", "description", "displayName", "id"], + "properties": { + "@libre.graph.weight": { "const": 72 }, + "description": { "const": "View, download, upload, edit, delete, show all versions and all invited people." }, + "displayName": { "const": "Can edit" }, + "id": { "const": "0911d62b-1e3f-4778-8b1b-903b7e4e8476" } + } + } + ] + } + } + } + } + """ + + + Scenario: user lists permissions of a folder in project space after enabling EditorListGrantsWithVersions role + Given using spaces DAV path + And the administrator has assigned the role "Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "new-space" with the default quota using the Graph API + And user "Alice" has created a folder "folder" in space "new-space" + When user "Alice" gets permissions list for folder "folder" of the space "new-space" using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "@libre.graph.permissions.actions.allowedValues", + "@libre.graph.permissions.roles.allowedValues" + ], + "properties": { + "@libre.graph.permissions.actions.allowedValues": { + "type": "array", + "minItems": 19, + "maxItems": 19, + "uniqueItems": true + }, + "@libre.graph.permissions.roles.allowedValues": { + "type": "array", + "minItems": 4, + "maxItems": 4, + "uniqueItems": true, + "items": { + "oneOf": [ + { + "type": "object", + "required": ["@libre.graph.weight", "description", "displayName", "id"], + "properties": { + "@libre.graph.weight": { "const": 10 }, + "description": { "const": "View and download." }, + "displayName": { "const": "Can view" }, + "id": { "const": "b1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5" } + } + }, + { + "type": "object", + "required": ["@libre.graph.weight", "description", "displayName", "id"], + "properties": { + "@libre.graph.weight": { "const": 50 }, + "description": { "const": "View, download and upload." }, + "displayName": { "const": "Can upload" }, + "id": { "const": "1c996275-f1c9-4e71-abdf-a42f6495e960" } + } + }, + { + "type": "object", + "required": ["@libre.graph.weight", "description", "displayName", "id"], + "properties": { + "@libre.graph.weight": { "const": 60 }, + "description": { "const": "View, download, upload, edit, add and delete." }, + "displayName": { "const": "Can edit" }, + "id": { "const": "fb6c3e19-e378-47e5-b277-9732f9de6e21" } + } + }, + { + "type": "object", + "required": ["@libre.graph.weight", "description", "displayName", "id"], + "properties": { + "@libre.graph.weight": { "const": 72 }, + "description": { "const": "View, download, upload, edit, delete, show all versions and all invited people." }, + "displayName": { "const": "Can edit" }, + "id": { "const": "0911d62b-1e3f-4778-8b1b-903b7e4e8476" } + } + } + ] + } + } + } + } + """ + + + Scenario: sharee lists the received shares (Personal Space) + Given user "Alice" has uploaded file with content "hello world" to "textfile.txt" + And user "Alice" has created folder "folder" + And user "Alice" has sent the following resource share invitation: + | resource | textfile.txt | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | File Editor List Grants With Versions | + And user "Alice" has sent the following resource share invitation: + | resource | folder | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | Editor List Grants With Versions | + When user "Brian" lists the shares shared with him using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should contain resource "textfile.txt" with the following data: + """ + { + "type": "object", + "required": ["@UI.Hidden","@client.synchronize","createdBy","eTag","file","id", + "lastModifiedDateTime","name","parentReference","remoteItem","size"], + "properties": { + "@UI.Hidden": { "const": false }, + "@client.synchronize": { "const": true }, + "eTag": { "pattern": "%etag_pattern%" }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { "mimeType": { "const": "text/plain" } } + }, + "id": { "pattern": "^%share_id_pattern%$" }, + "name": { "const": "textfile.txt" }, + "remoteItem": { + "type": "object", + "required": ["createdBy","eTag","file","id","lastModifiedDateTime","name", + "parentReference","permissions","size","spaceId"], + "properties": { + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["id", "displayName"], + "properties": { + "id": { "pattern": "^%user_id_pattern%$" }, + "displayName": { "const": "Alice Hansen" } + } + } + } + }, + "eTag": { "pattern": "%etag_pattern%" }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { "const": "text/plain" } + } + }, + "id": { "pattern": "^%file_id_pattern%$" }, + "name": { "const": "textfile.txt" }, + "parentReference": { + "type": "object", + "required": ["driveId", "driveType"], + "properties": { + "driveId": { "pattern": "^%file_id_pattern%$" }, + "driveType": { "const": "personal" } + } + }, + "spaceId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "permissions": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "type": "object", + "required": ["grantedToV2", "id", "invitation", "roles"], + "properties": { + "id": { "pattern": "^%permissions_id_pattern%$" }, + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { "const": "Brian Murphy" }, + "id": { "pattern": "^%user_id_pattern%$" } + } + } + } + }, + "invitation": { + "type": "object", + "properties": { + "invitedBy": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "displayName": { "const": "Alice Hansen" }, + "id": { "pattern": "^%user_id_pattern%$" } + }, + "required": ["displayName", "id"] + } + }, + "required": ["user"] + } + }, + "required": ["invitedBy"] + }, + "roles": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { "const": "b173329d-cf2e-42f0-a595-ee410645d840" } + } + } + } + } + } + }, + "size": { "const": 11 } + } + } + """ + And the JSON data of the response should contain resource "folder" with the following data: + """ + { + "type": "object", + "required": ["@UI.Hidden","@client.synchronize","createdBy","eTag","folder","id", + "lastModifiedDateTime","name","parentReference","remoteItem"], + "properties": { + "@UI.Hidden": { "const": false }, + "@client.synchronize": { "const": true }, + "eTag": { "pattern": "%etag_pattern%" }, + "id": { "pattern": "^%share_id_pattern%$" }, + "name": { "const": "folder" }, + "remoteItem": { + "type": "object", + "required": ["createdBy","eTag","folder","id","lastModifiedDateTime", + "name","parentReference","permissions","spaceId"], + "properties": { + "createdBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["id", "displayName"], + "properties": { + "id": { "pattern": "^%user_id_pattern%$" }, + "displayName": { "const": "Alice Hansen" } + } + } + } + }, + "eTag": { "pattern": "%etag_pattern%" }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { "const": "text/plain" } + } + }, + "id": { "pattern": "^%file_id_pattern%$" }, + "name": { "const": "folder" }, + "parentReference": { + "type": "object", + "required": ["driveId", "driveType"], + "properties": { + "driveId": { "pattern": "^%file_id_pattern%$" }, + "driveType": { "const": "personal" } + } + }, + "spaceId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "permissions": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "type": "object", + "required": ["grantedToV2", "id", "invitation", "roles"], + "properties": { + "id": { "pattern": "^%permissions_id_pattern%$" }, + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "properties": { + "displayName": { "const": "Brian Murphy" }, + "id": { "pattern": "^%user_id_pattern%$" } + }, + "required": ["displayName", "id"] + } + } + }, + "invitation": { + "type": "object", + "properties": { + "invitedBy": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "displayName": { "const": "Alice Hansen" }, + "id": { "pattern": "^%user_id_pattern%$" } + }, + "required": ["displayName", "id"] + } + }, + "required": ["user"] + } + }, + "required": ["invitedBy"] + }, + "roles": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { "const": "0911d62b-1e3f-4778-8b1b-903b7e4e8476" } + } + } + } + } + } + } + } + } + """ + + + Scenario: sharee lists the received shares (Project Space) + Given using spaces DAV path + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "new-space" with the default quota using the Graph API + And user "Alice" has uploaded a file inside space "new-space" with content "some content" to "testfile.txt" + And user "Alice" has created a folder "folder" in space "new-space" + And user "Alice" has sent the following resource share invitation: + | resource | testfile.txt | + | space | new-space | + | sharee | Brian | + | shareType | user | + | permissionsRole | File Editor List Grants With Versions | + And user "Alice" has sent the following resource share invitation: + | resource | folder | + | space | new-space | + | sharee | Brian | + | shareType | user | + | permissionsRole | Editor List Grants With Versions | + When user "Brian" lists the shares shared with him using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should contain resource "testfile.txt" with the following data: + """ + { + "type": "object", + "required": ["@UI.Hidden","@client.synchronize","eTag","file","id", + "lastModifiedDateTime","name","parentReference","remoteItem","size"], + "properties": { + "@UI.Hidden": { "const": false }, + "@client.synchronize": { "const": true }, + "eTag": { "pattern": "%etag_pattern%" }, + "id": { "pattern": "^%share_id_pattern%$" }, + "name": { "const": "testfile.txt" }, + "remoteItem": { + "type": "object", + "required": ["eTag","file","id","lastModifiedDateTime", + "name","parentReference","permissions","size","spaceId"], + "properties": { + "eTag": { "pattern": "%etag_pattern%" }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { "const": "text/plain" } + } + }, + "id": { "pattern": "^%file_id_pattern%$" }, + "name": { "const": "testfile.txt" }, + "parentReference": { + "type": "object", + "required": ["driveId", "driveType"], + "properties": { + "driveId": { "pattern": "^%file_id_pattern%$" }, + "driveType": { "const": "project" } + } + }, + "spaceId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "permissions": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "type": "object", + "required": ["grantedToV2", "id", "invitation", "roles"], + "properties": { + "id": { "pattern": "^%permissions_id_pattern%$" }, + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { "const": "Brian Murphy" }, + "id": { "pattern": "^%user_id_pattern%$" } + } + } + } + }, + "invitation": { + "type": "object", + "properties": { + "invitedBy": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "displayName": { "const": "Alice Hansen" }, + "id": { "pattern": "^%user_id_pattern%$" } + }, + "required": ["displayName", "id"] + } + }, + "required": ["user"] + } + }, + "required": ["invitedBy"] + }, + "roles": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { "const": "b173329d-cf2e-42f0-a595-ee410645d840" } + } + } + } + } + } + }, + "size": { "const": 12 } + } + } + """ + And the JSON data of the response should contain resource "folder" with the following data: + """ + { + "type": "object", + "required": ["@UI.Hidden","@client.synchronize","eTag","folder","id", + "lastModifiedDateTime","name","parentReference","remoteItem"], + "properties": { + "@UI.Hidden": { "const": false }, + "@client.synchronize": { "const": true }, + "eTag": { "pattern": "%etag_pattern%" }, + "id": { "pattern": "^%share_id_pattern%$" }, + "name": { "const": "folder" }, + "remoteItem": { + "type": "object", + "required": ["eTag","folder","id","lastModifiedDateTime", + "name","parentReference","permissions","spaceId"], + "properties": { + "eTag": { "pattern": "%etag_pattern%" }, + "file": { + "type": "object", + "required": ["mimeType"], + "properties": { + "mimeType": { "const": "text/plain" } + } + }, + "id": { "pattern": "^%file_id_pattern%$" }, + "name": { "const": "folder" }, + "parentReference": { + "type": "object", + "required": ["driveId", "driveType"], + "properties": { + "driveId": { "pattern": "^%file_id_pattern%$" }, + "driveType": { "const": "project" } + } + }, + "spaceId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + }, + "permissions": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "type": "object", + "required": ["grantedToV2", "id", "invitation", "roles"], + "properties": { + "id": { "pattern": "^%permissions_id_pattern%$" }, + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "properties": { + "displayName": { "const": "Brian Murphy" }, + "id": { "pattern": "^%user_id_pattern%$" } + }, + "required": ["displayName", "id"] + } + } + }, + "invitation": { + "type": "object", + "properties": { + "invitedBy": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "displayName": { "const": "Alice Hansen" }, + "id": { "pattern": "^%user_id_pattern%$" } + }, + "required": ["displayName", "id"] + } + }, + "required": ["user"] + } + }, + "required": ["invitedBy"] + }, + "roles": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { "const": "0911d62b-1e3f-4778-8b1b-903b7e4e8476" } + } + } + } + } + } + } + } + } + """ + + + Scenario: sharee checks file versions after updating the permission roles to with-versions roles (Personal Space) + Given using spaces DAV path + And user "Alice" has created folder "folderToShare" + And user "Alice" has uploaded file with content "to share" to "folderToShare/lorem.txt" + And user "Alice" has uploaded file with content "to share" to "textfile.txt" + And user "Alice" has sent the following resource share invitation: + | resource | textfile.txt | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | File Editor | + And user "Brian" has uploaded file with content "updated content" to "Shares/textfile.txt" + And user "Alice" has sent the following resource share invitation: + | resource | folderToShare | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | Editor | + And user "Brian" has uploaded file with content "updated content" to "Shares/folderToShare/lorem.txt" + And user "Alice" has updated the following resource share: + | permissionsRole | File Editor List Grants With Versions | + | space | Personal | + | resource | textfile.txt | + | sharee | Brian | + And user "Alice" has updated the following resource share: + | permissionsRole | Editor List Grants With Versions | + | space | Personal | + | resource | folderToShare | + | sharee | Brian | + When user "Brian" gets the number of versions of file "Shares/textfile.txt" + Then the HTTP status code should be "207" + And the number of versions should be "1" + When user "Brian" gets the number of versions of file "Shares/folderToShare/lorem.txt" + Then the HTTP status code should be "207" + And the number of versions should be "1" + + + Scenario: sharee tries to check file versions after updating the with-versions roles to other roles (Personal Space) + Given using spaces DAV path + And user "Alice" has created folder "folderToShare" + And user "Alice" has uploaded file with content "to share" to "folderToShare/lorem.txt" + And user "Alice" has uploaded file with content "to share" to "textfile.txt" + And user "Alice" has sent the following resource share invitation: + | resource | textfile.txt | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | File Editor List Grants With Versions | + And user "Brian" has uploaded file with content "updated content" to "Shares/textfile.txt" + And user "Alice" has sent the following resource share invitation: + | resource | folderToShare | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | Editor List Grants With Versions | + And user "Brian" has uploaded file with content "updated content" to "Shares/folderToShare/lorem.txt" + And user "Alice" has updated the following resource share: + | permissionsRole | File Editor | + | space | Personal | + | resource | textfile.txt | + | sharee | Brian | + And user "Alice" has updated the following resource share: + | permissionsRole | Editor | + | space | Personal | + | resource | folderToShare | + | sharee | Brian | + When user "Brian" tries to get versions of file "textfile.txt" from "Alice" + Then the HTTP status code should be "403" + When user "Brian" tries to get versions of file "folderToShare/lorem.txt" from "Alice" + Then the HTTP status code should be "403" + + + Scenario: sharee checks file versions after updating the permission role to with-versions roles (Project Space) + Given using spaces DAV path + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "new-space" with the default quota using the Graph API + And user "Alice" has created a folder "folderToShare" in space "new-space" + And user "Alice" has uploaded a file inside space "new-space" with content "to share" to "folderToShare/lorem.txt" + And user "Alice" has uploaded a file inside space "new-space" with content "to share" to "textfile.txt" + And user "Alice" has sent the following resource share invitation: + | resource | textfile.txt | + | space | new-space | + | sharee | Brian | + | shareType | user | + | permissionsRole | File Editor | + And user "Brian" has uploaded file with content "updated content" to "Shares/textfile.txt" + And user "Alice" has sent the following resource share invitation: + | resource | folderToShare | + | space | new-space | + | sharee | Brian | + | shareType | user | + | permissionsRole | Editor | + And user "Brian" has uploaded file with content "updated content" to "Shares/folderToShare/lorem.txt" + And user "Alice" has updated the following resource share: + | permissionsRole | File Editor List Grants With Versions | + | space | new-space | + | resource | textfile.txt | + | sharee | Brian | + And user "Alice" has updated the following resource share: + | permissionsRole | Editor List Grants With Versions | + | space | new-space | + | resource | folderToShare | + | sharee | Brian | + When user "Brian" gets the number of versions of file "Shares/textfile.txt" + Then the HTTP status code should be "207" + And the number of versions should be "1" + When user "Brian" gets the number of versions of file "Shares/folderToShare/lorem.txt" + Then the HTTP status code should be "207" + And the number of versions should be "1" + + + Scenario: sharee tries to check file versions after updating the with-versions roles to other roles (Project Space) + Given using spaces DAV path + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "new-space" with the default quota using the Graph API + And user "Alice" has created a folder "folderToShare" in space "new-space" + And user "Alice" has uploaded a file inside space "new-space" with content "to share" to "folderToShare/lorem.txt" + And user "Alice" has uploaded a file inside space "new-space" with content "to share" to "textfile.txt" + And we save it into "FILEID" + And user "Alice" has sent the following resource share invitation: + | resource | textfile.txt | + | space | new-space | + | sharee | Brian | + | shareType | user | + | permissionsRole | File Editor List Grants With Versions | + And user "Brian" has uploaded file with content "updated content" to "Shares/textfile.txt" + And user "Alice" has sent the following resource share invitation: + | resource | folderToShare | + | space | new-space | + | sharee | Brian | + | shareType | user | + | permissionsRole | Editor List Grants With Versions | + And user "Brian" has uploaded file with content "updated content" to "Shares/folderToShare/lorem.txt" + And user "Alice" has updated the following resource share: + | permissionsRole | File Editor | + | space | new-space | + | resource | textfile.txt | + | sharee | Brian | + And user "Alice" has updated the following resource share: + | permissionsRole | Editor | + | space | new-space | + | resource | folderToShare | + | sharee | Brian | + When user "Brian" tries to get versions of the file "textfile.txt" from the space "Shares" using the WebDAV API + Then the HTTP status code should be "403" + When user "Brian" tries to get versions of the file "folderToShare/lorem.txt" from the space "Shares" using the WebDAV API + Then the HTTP status code should be "403" \ No newline at end of file diff --git a/tests/acceptance/features/apiSharingNgAdditionalShareRole/listGrantsShareRole.feature b/tests/acceptance/features/apiSharingNgAdditionalShareRole/listGrantsShareRole.feature new file mode 100644 index 0000000000..0cd2afff32 --- /dev/null +++ b/tests/acceptance/features/apiSharingNgAdditionalShareRole/listGrantsShareRole.feature @@ -0,0 +1,1281 @@ +@env-config +Feature: ListGrants role + As a user + I want to share resources with listGrants role + So that sharee can view activities and grants list of shared resources + + Background: + Given these users have been created with default attributes: + | username | + | Alice | + | Brian | + And the administrator has enabled the following share permissions roles: + | permissions-role | + | Viewer List Grants | + | File Editor List Grants | + | Editor List Grants | + + + Scenario: user shares personal resources with ListGrants role + Given user "Alice" has created folder "FolderToShare" + And user "Alice" has uploaded file with content "to share" to "textfile1.txt" + When user "Alice" sends the following resource share invitation using the Graph API: + | resource | textfile1.txt | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | File Editor List Grants | + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "array", + "maxItems": 1, + "minItems": 1, + "items": { + "type": "object", + "required": ["createdDateTime","id","roles","grantedToV2"], + "properties": { + "createdDateTime": { "format": "date-time" }, + "id": {"pattern": "^%permissions_id_pattern%$"}, + "roles": { + "type": "array", + "maxItems": 1, + "minItems": 1, + "items": {"pattern": "^%role_id_pattern%$"} + }, + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["id","displayName"], + "properties": { + "id": {"pattern": "^%user_id_pattern%$"}, + "displayName": {"const": "Brian Murphy"} + } + } + } + } + } + } + } + } + } + """ + And for user "Brian" file "textfile1.txt" should have the following shares: + | sharee | shareType | permissionsRole | + | Brian | user | File Editor List Grants | + And for user "Brian" file "textfile1.txt" of the space "Shares" should have the following activities: + | {user} added {resource} to {folder} | + | {user} shared {resource} with {sharee} | + When user "Alice" sends the following resource share invitation using the Graph API: + | resource | FolderToShare | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | Editor List Grants | + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "array", + "maxItems": 1, + "minItems": 1, + "items": { + "type": "object", + "required": ["createdDateTime","id","roles","grantedToV2"], + "properties": { + "createdDateTime": { "format": "date-time" }, + "id": {"pattern": "^%permissions_id_pattern%$"}, + "roles": { + "type": "array", + "maxItems": 1, + "minItems": 1, + "items": {"pattern": "^%role_id_pattern%$"} + }, + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["id","displayName"], + "properties": { + "id": {"pattern": "^%user_id_pattern%$"}, + "displayName": {"const": "Brian Murphy"} + } + } + } + } + } + } + } + } + } + """ + And for user "Brian" folder "FolderToShare" should have the following shares: + | sharee | shareType | permissionsRole | + | Brian | user | Editor List Grants | + And for user "Brian" folder "FolderToShare" of the space "Shares" should have the following activities: + | {user} added {resource} to {folder} | + | {user} shared {resource} with {sharee} | + + + Scenario: user shares project resources with ListGrants role + Given the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And using spaces DAV path + And user "Alice" has created a space "NewSpace" with the default quota using the Graph API + And user "Alice" has created a folder "FolderToShare" in space "NewSpace" + And user "Alice" has uploaded a file inside space "NewSpace" with content "share space items" to "textfile1.txt" + When user "Alice" sends the following resource share invitation using the Graph API: + | resource | textfile1.txt | + | space | NewSpace | + | sharee | Brian | + | shareType | user | + | permissionsRole | File Editor List Grants | + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "array", + "maxItems": 1, + "minItems": 1, + "items": { + "type": "object", + "required": ["createdDateTime","id","roles","grantedToV2"], + "properties": { + "createdDateTime": { "format": "date-time" }, + "id": {"pattern": "^%permissions_id_pattern%$"}, + "roles": { + "type": "array", + "maxItems": 1, + "minItems": 1, + "items": {"pattern": "^%role_id_pattern%$"} + }, + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["id","displayName"], + "properties": { + "id": {"pattern": "^%user_id_pattern%$"}, + "displayName": {"const": "Brian Murphy"} + } + } + } + } + } + } + } + } + } + """ + And for user "Brian" file "textfile1.txt" should have the following shares: + | sharee | shareType | permissionsRole | + | Brian | user | File Editor List Grants | + And for user "Brian" file "textfile1.txt" of the space "Shares" should have the following activities: + | {user} added {resource} to {folder} | + | {user} shared {resource} with {sharee} | + When user "Alice" sends the following resource share invitation using the Graph API: + | resource | FolderToShare | + | space | NewSpace | + | sharee | Brian | + | shareType | user | + | permissionsRole | Editor List Grants | + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "array", + "maxItems": 1, + "minItems": 1, + "items": { + "type": "object", + "required": ["createdDateTime","id","roles","grantedToV2"], + "properties": { + "createdDateTime": { "format": "date-time" }, + "id": {"pattern": "^%permissions_id_pattern%$"}, + "roles": { + "type": "array", + "maxItems": 1, + "minItems": 1, + "items": {"pattern": "^%role_id_pattern%$"} + }, + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["id","displayName"], + "properties": { + "id": {"pattern": "^%user_id_pattern%$"}, + "displayName": {"const": "Brian Murphy"} + } + } + } + } + } + } + } + } + } + """ + And for user "Brian" folder "FolderToShare" should have the following shares: + | sharee | shareType | permissionsRole | + | Brian | user | Editor List Grants | + And for user "Brian" folder "FolderToShare" of the space "Shares" should have the following activities: + | {user} added {resource} to {folder} | + | {user} shared {resource} with {sharee} | + + + Scenario Outline: sharer updates shared file roles to ListGrants roles (Personal space) + Given the administrator has enabled the permissions role "" + And user "Alice" has uploaded file with content "to share" to "textfile1.txt" + And user "Alice" has sent the following resource share invitation: + | resource | textfile1.txt | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | | + When user "Alice" updates the last resource share with the following properties using the Graph API: + | permissionsRole | | + | space | Personal | + | resource | textfile1.txt | + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["grantedToV2","id","roles"], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties":{ + "user": { + "type": "object", + "required": ["displayName","id"], + "properties": { + "displayName": {"const": "Brian Murphy"}, + "id": {"pattern": "^%user_id_pattern%$"} + } + } + } + }, + "id": {"pattern": "^%permissions_id_pattern%$"}, + "roles": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": {"pattern": "^%role_id_pattern%$"} + } + } + } + """ + And for user "Brian" file "textfile1.txt" should have the following shares: + | sharee | shareType | permissionsRole | + | Brian | user | | + And for user "Brian" file "textfile1.txt" of the space "Shares" should have the following activities: + | {user} added {resource} to {folder} | + | {user} shared {resource} with {sharee} | + Examples: + | permissions-role | new-permissions-role | + | Viewer | File Editor List Grants | + | File Editor | Viewer List Grants | + + + Scenario Outline: sharer updates shared folder roles to ListGrants roles (Personal space) + Given the administrator has enabled the permissions role "" + And user "Alice" has created folder "FolderToShare" + And user "Alice" has sent the following resource share invitation: + | resource | FolderToShare | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | | + When user "Alice" updates the last resource share with the following properties using the Graph API: + | permissionsRole | | + | space | Personal | + | resource | FolderToShare | + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["grantedToV2","id","roles"], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties":{ + "user": { + "type": "object", + "required": ["displayName","id"], + "properties": { + "displayName": {"const": "Brian Murphy"}, + "id": {"pattern": "^%user_id_pattern%$"} + } + } + } + }, + "id": {"pattern": "^%permissions_id_pattern%$"}, + "roles": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": {"pattern": "^%role_id_pattern%$"} + } + } + } + """ + And for user "Brian" folder "FolderToShare" should have the following shares: + | sharee | shareType | permissionsRole | + | Brian | user | | + And for user "Brian" folder "FolderToShare" of the space "Shares" should have the following activities: + | {user} added {resource} to {folder} | + | {user} shared {resource} with {sharee} | + Examples: + | permissions-role | new-permissions-role | + | Viewer | Viewer List Grants | + | Editor | Editor List Grants | + + + Scenario Outline: sharer updates shared file roles to ListGrants roles (Project space) + Given the administrator has enabled the permissions role "" + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And using spaces DAV path + And user "Alice" has created a space "NewSpace" with the default quota using the Graph API + And user "Alice" has uploaded a file inside space "NewSpace" with content "share space items" to "textfile1.txt" + And user "Alice" has sent the following resource share invitation: + | resource | textfile1.txt | + | space | NewSpace | + | sharee | Brian | + | shareType | user | + | permissionsRole | | + When user "Alice" updates the last resource share with the following properties using the Graph API: + | permissionsRole | | + | space | NewSpace | + | resource | textfile1.txt | + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["grantedToV2","id","roles"], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties":{ + "user": { + "type": "object", + "required": ["displayName","id"], + "properties": { + "displayName": {"const": "Brian Murphy"}, + "id": {"pattern": "^%user_id_pattern%$"} + } + } + } + }, + "id": {"pattern": "^%permissions_id_pattern%$"}, + "roles": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": {"pattern": "^%role_id_pattern%$"} + } + } + } + """ + And for user "Brian" file "textfile1.txt" should have the following shares: + | sharee | shareType | permissionsRole | + | Brian | user | | + And for user "Brian" file "textfile1.txt" of the space "Shares" should have the following activities: + | {user} added {resource} to {folder} | + | {user} shared {resource} with {sharee} | + | {user} updated {field} for the {resource} | + Examples: + | permissions-role | new-permissions-role | + | Viewer | Viewer List Grants | + | File Editor | File Editor List Grants | + + + Scenario Outline: sharer updates shared folder roles to ListGrants roles (Project space) + Given the administrator has enabled the permissions role "" + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And using spaces DAV path + And user "Alice" has created a space "NewSpace" with the default quota using the Graph API + And user "Alice" has created a folder "FolderToShare" in space "NewSpace" + And user "Alice" has sent the following resource share invitation: + | resource | FolderToShare | + | space | NewSpace | + | sharee | Brian | + | shareType | user | + | permissionsRole | | + When user "Alice" updates the last resource share with the following properties using the Graph API: + | permissionsRole | | + | space | NewSpace | + | resource | FolderToShare | + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["grantedToV2","id","roles"], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties":{ + "user": { + "type": "object", + "required": ["displayName","id"], + "properties": { + "displayName": {"const": "Brian Murphy"}, + "id": {"pattern": "^%user_id_pattern%$"} + } + } + } + }, + "id": {"pattern": "^%permissions_id_pattern%$"}, + "roles": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": {"pattern": "^%role_id_pattern%$"} + } + } + } + """ + And for user "Brian" folder "FolderToShare" should have the following shares: + | sharee | shareType | permissionsRole | + | Brian | user | | + And for user "Brian" folder "FolderToShare" of the space "Shares" should have the following activities: + | {user} added {resource} to {folder} | + | {user} shared {resource} with {sharee} | + | {user} updated {field} for the {resource} | + Examples: + | permissions-role | new-permissions-role | + | Editor | Viewer List Grants | + | Viewer | Editor List Grants | + + + Scenario Outline: sharer updates shared file roles from ListGrants roles to other roles (Personal space) + Given the administrator has enabled the permissions role "" + And user "Alice" has uploaded file with content "to share" to "textfile1.txt" + And user "Alice" has sent the following resource share invitation: + | resource | textfile1.txt | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | | + When user "Alice" updates the last resource share with the following properties using the Graph API: + | permissionsRole | | + | space | Personal | + | resource | textfile1.txt | + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["grantedToV2","id","roles"], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties":{ + "user": { + "type": "object", + "required": ["displayName","id"], + "properties": { + "displayName": {"const": "Brian Murphy"}, + "id": {"pattern": "^%user_id_pattern%$"} + } + } + } + }, + "id": {"pattern": "^%permissions_id_pattern%$"}, + "roles": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": {"pattern": "^%role_id_pattern%$"} + } + } + } + """ + And for user "Brian" file "textfile1.txt" should have the following shares: + | sharee | shareType | permissionsRole | + | Brian | user | | + And for user "Brian" file "textfile1.txt" of the space "Shares" should not have any activity + Examples: + | permissions-role | new-permissions-role | + | Viewer List Grants | Viewer | + | File Editor List Grants | File Editor | + + + Scenario Outline: sharer updates shared folder roles from ListGrants roles to other roles (Personal space) + Given the administrator has enabled the permissions role "" + And user "Alice" has created folder "FolderToShare" + And user "Alice" has sent the following resource share invitation: + | resource | FolderToShare | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | | + When user "Alice" updates the last resource share with the following properties using the Graph API: + | permissionsRole | | + | space | Personal | + | resource | FolderToShare | + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["grantedToV2","id","roles"], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties":{ + "user": { + "type": "object", + "required": ["displayName","id"], + "properties": { + "displayName": {"const": "Brian Murphy"}, + "id": {"pattern": "^%user_id_pattern%$"} + } + } + } + }, + "id": {"pattern": "^%permissions_id_pattern%$"}, + "roles": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": {"pattern": "^%role_id_pattern%$"} + } + } + } + """ + And for user "Brian" folder "FolderToShare" should have the following shares: + | sharee | shareType | permissionsRole | + | Brian | user | | + And for user "Brian" folder "FolderToShare" of the space "Shares" should not have any activity + Examples: + | permissions-role | new-permissions-role | + | Viewer List Grants | Editor | + | Editor List Grants | Viewer | + + + Scenario Outline: sharer updates shared file roles from ListGrants roles to other roles (Project space) + Given the administrator has enabled the permissions role "" + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And using spaces DAV path + And user "Alice" has created a space "NewSpace" with the default quota using the Graph API + And user "Alice" has uploaded a file inside space "NewSpace" with content "share space items" to "textfile1.txt" + And user "Alice" has sent the following resource share invitation: + | resource | textfile1.txt | + | space | NewSpace | + | sharee | Brian | + | shareType | user | + | permissionsRole | | + When user "Alice" updates the last resource share with the following properties using the Graph API: + | permissionsRole | | + | space | NewSpace | + | resource | textfile1.txt | + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["grantedToV2","id","roles"], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties":{ + "user": { + "type": "object", + "required": ["displayName","id"], + "properties": { + "displayName": {"const": "Brian Murphy"}, + "id": {"pattern": "^%user_id_pattern%$"} + } + } + } + }, + "id": {"pattern": "^%permissions_id_pattern%$"}, + "roles": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": {"pattern": "^%role_id_pattern%$"} + } + } + } + """ + And for user "Brian" file "textfile1.txt" should have the following shares: + | sharee | shareType | permissionsRole | + | Brian | user | | + And for user "Brian" file "textfile1.txt" of the space "Shares" should have the following activities: + | {user} added {resource} to {folder} | + | {user} shared {resource} with {sharee} | + | {user} updated {field} for the {resource} | + Examples: + | new-permissions-role | permissions-role | + | Viewer List Grants | File Editor | + | File Editor List Grants | Viewer | + + + Scenario Outline: sharer updates shared folder roles to ListGrants roles to other roles (Project space) + Given the administrator has enabled the permissions role "" + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And using spaces DAV path + And user "Alice" has created a space "NewSpace" with the default quota using the Graph API + And user "Alice" has created a folder "FolderToShare" in space "NewSpace" + And user "Alice" has sent the following resource share invitation: + | resource | FolderToShare | + | space | NewSpace | + | sharee | Brian | + | shareType | user | + | permissionsRole | | + When user "Alice" updates the last resource share with the following properties using the Graph API: + | permissionsRole | | + | space | NewSpace | + | resource | FolderToShare | + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["grantedToV2","id","roles"], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties":{ + "user": { + "type": "object", + "required": ["displayName","id"], + "properties": { + "displayName": {"const": "Brian Murphy"}, + "id": {"pattern": "^%user_id_pattern%$"} + } + } + } + }, + "id": {"pattern": "^%permissions_id_pattern%$"}, + "roles": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": {"pattern": "^%role_id_pattern%$"} + } + } + } + """ + And for user "Brian" folder "FolderToShare" should have the following shares: + | sharee | shareType | permissionsRole | + | Brian | user | | + And for user "Brian" folder "FolderToShare" of the space "Shares" should have the following activities: + | {user} added {resource} to {folder} | + | {user} shared {resource} with {sharee} | + | {user} updated {field} for the {resource} | + Examples: + | new-permissions-role | permissions-role | + | Viewer List Grants | Viewer | + | Editor List Grants | Uploader | + + + Scenario: sharer lists shared-by-me (Personal space) + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API + And user "Alice" has created folder "folder" + And user "Alice" has uploaded file with content "to share" to "textfile.txt" + And user "Alice" has created a group "grp1" using the Graph API + And user "Brian" has been added to group "grp1" + And user "Alice" has sent the following resource share invitation: + | resource | folder | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | Editor List Grants | + And user "Alice" has sent the following resource share invitation: + | resource | textfile.txt | + | space | Personal | + | sharee | grp1 | + | shareType | group | + | permissionsRole | File Editor List Grants | + When user "Alice" lists the shares shared by her using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should contain resource "folder" with the following data: + """ + { + "type": "object", + "required": ["parentReference","permissions","name"], + "properties": { + "parentReference": { + "type": "object", + "required": ["driveId","driveType","path","name","id"], + "properties": { + "driveId": {"pattern": "^%space_id_pattern%$"}, + "driveType": {"const": "personal"}, + "path": {"const": "/"}, + "name": {"const": "/"}, + "id": {"pattern": "^%file_id_pattern%$"} + } + }, + "permissions": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "type": "object", + "required": ["grantedToV2","id","roles"], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName","id"], + "properties": { + "id": {"pattern": "^%user_id_pattern%$"}, + "displayName": {"const": "Brian Murphy"} + } + } + } + }, + "id": {"pattern": "^%permissions_id_pattern%$"}, + "roles": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": {"pattern": "^%role_id_pattern%$"} + } + } + } + }, + "name": {"const": "folder"} + } + } + """ + And the JSON data of the response should contain resource "textfile.txt" with the following data: + """ + { + "type": "object", + "required": ["parentReference","permissions","name","size"], + "properties": { + "name": {"const": "textfile.txt"}, + "size": {"const": 8}, + "parentReference": { + "type": "object", + "required": ["driveId","driveType","path","name","id"], + "properties": { + "driveId": {"pattern": "^%space_id_pattern%$"}, + "driveType": {"const": "personal"}, + "path": {"const": "/"}, + "name": {"const": "/"}, + "id": {"pattern": "^%file_id_pattern%$"} + } + }, + "permissions": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "type": "object", + "required": ["createdDateTime","grantedToV2","id","roles","invitation"], + "properties": { + "createdDateTime": { "format": "date-time" }, + "id": {"pattern": "^%permissions_id_pattern%$"}, + "roles": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": {"pattern": "^%role_id_pattern%$"} + }, + "invitation": { + "type": "object", + "required": ["invitedBy"], + "properties": { + "invitedBy": { + "type": "object", + "required": ["user"], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id", "@libre.graph.userType"], + "properties": { + "displayName": { "const": "Alice Hansen" }, + "id": { "pattern": "^%user_id_pattern%$" }, + "@libre.graph.userType": { "const": "Member" } + } + } + } + } + } + }, + "grantedToV2": { + "type": "object", + "required": ["group"], + "properties": { + "group": { + "type": "object", + "required": ["displayName","id"], + "properties": { + "id": {"pattern": "^%group_id_pattern%$"}, + "displayName": {"const": "grp1"} + } + } + } + } + } + } + } + } + } + """ + + + Scenario: sharee list shared-with-me (Personal space) + Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API + And user "Alice" has created folder "folder" + And user "Alice" has created a group "grp1" using the Graph API + And user "Brian" has been added to group "grp1" + And user "Alice" has uploaded file with content "to share" to "textfile.txt" + And user "Alice" has sent the following resource share invitation: + | resource | textfile.txt | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | Viewer List Grants | + And user "Alice" has sent the following resource share invitation: + | resource | folder | + | space | Personal | + | sharee | grp1 | + | shareType | group | + | permissionsRole | Editor List Grants | + When user "Brian" lists the shares shared with him using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should contain resource "textfile.txt" with the following data: + """ + { + "type": "object", + "required": ["@UI.Hidden","@client.synchronize","createdBy","eTag","file", + "id","lastModifiedDateTime","name","parentReference","remoteItem","size"], + "properties": { + "lastModifiedDateTime": { "format": "date-time" }, + "eTag": {"pattern": "%etag_pattern%"}, + "id": {"pattern": "^%share_id_pattern%$"}, + "name": {"const": "textfile.txt"}, + "remoteItem": { + "type": "object", + "required": ["createdBy","eTag","file","id","lastModifiedDateTime","name","parentReference","permissions","size", "spaceId"], + "properties": { + "eTag": {"pattern": "%etag_pattern%"}, + "id": {"pattern": "^%file_id_pattern%$"}, + "name": {"const": "textfile.txt"}, + "permissions": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "uniqueItems": true, + "items": { + "type": "object", + "required": ["grantedToV2","id","invitation","roles"], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["user"], + "properties":{ + "user": { + "type": "object", + "required": ["displayName","id"], + "properties": { + "displayName": {"const": "Brian Murphy"}, + "id": {"pattern": "^%user_id_pattern%$"} + } + } + } + }, + "id": {"pattern": "^%permissions_id_pattern%$"}, + "invitation": { + "type": "object", + "required": ["invitedBy"], + "properties": { + "user":{ + "type": "object", + "required": ["displayName","id"], + "properties": { + "displayName": {"const": "Alice Hansen"}, + "id": {"pattern": "^%user_id_pattern%$"} + } + } + } + }, + "roles": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": {"pattern": "^%role_id_pattern%$"} + } + } + } + }, + "spaceId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + } + } + } + } + } + """ + And the JSON data of the response should contain resource "folder" with the following data: + """ + { + "type": "object", + "required": ["@UI.Hidden","@client.synchronize","createdBy","eTag","folder", + "id","lastModifiedDateTime","name","parentReference","remoteItem"], + "properties": { + "lastModifiedDateTime": { "format": "date-time" }, + "eTag": {"pattern": "%etag_pattern%"}, + "id": {"pattern": "^%share_id_pattern%$"}, + "name": {"const": "folder"}, + "remoteItem": { + "type": "object", + "required": ["createdBy","eTag","folder","id","lastModifiedDateTime","name","parentReference","permissions", "spaceId"], + "properties": { + "eTag": {"pattern": "%etag_pattern%"}, + "file": {}, + "id": {"pattern": "^%file_id_pattern%$"}, + "name": {"const": "folder"}, + "permissions": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "uniqueItems": true, + "items": { + "type": "object", + "required": ["grantedToV2","id","invitation","roles"], + "properties": { + "grantedToV2": { + "type": "object", + "required": ["group"], + "properties":{ + "group": { + "type": "object", + "required": ["displayName","id"], + "properties": { + "displayName": {"const": "grp1"}, + "id": {"pattern": "^%user_id_pattern%$"} + } + } + } + }, + "id": {"pattern": "^%permissions_id_pattern%$"}, + "invitation": { + "type": "object", + "required": ["invitedBy"], + "properties": { + "user":{ + "type": "object", + "required": ["displayName","id"], + "properties": { + "displayName": {"const": "Alice Hansen"}, + "id": {"pattern": "^%user_id_pattern%$"} + } + } + } + }, + "roles": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": {"pattern": "^%role_id_pattern%$"} + } + } + } + }, + "spaceId": { + "type": "string", + "pattern": "^%space_id_pattern%$" + } + } + } + } + } + """ + + + Scenario Outline: list activities of folder shared with listGrant roles (Personal space) + Given the administrator has enabled the permissions role "" + And using spaces DAV path + And using SharingNG + And user "Alice" has created folder "folder" + And user "Alice" has sent the following resource share invitation: + | resource | folder | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | | + And user "Brian" has a share "folder" synced + When user "Brian" lists the activities of folder "folder" from space "Shares" using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "uniqueItems": true, + "items": { + "oneOf": [ + { + "type": "object", + "required": ["id","template","times"], + "properties": { + "id": {"pattern": "^%user_id_pattern%$"}, + "template": { + "type": "object", + "required": ["message","variables"], + "properties": { + "message": {"const": "{user} added {resource} to {folder}"}, + "variables":{ + "type": "object", + "required": ["folder","resource","user"], + "properties": { + "folder": { + "type": "object", + "required": ["id","name"], + "properties":{ + "id": {"const": ""}, + "name": {"const": "shared-with-me"} + } + }, + "resource": { + "type": "object", + "required": ["id","name"], + "properties": {"name": {"const": "folder"}} + }, + "user": { + "type": "object", + "required": ["id","displayName"], + "properties":{"displayName": {"const": "Alice Hansen"}} + } + } + } + } + } + } + }, + { + "type": "object", + "required": ["id","template","times"], + "properties": { + "id": {"pattern": "^%user_id_pattern%$"}, + "template": { + "type": "object", + "required": ["message","variables"], + "properties": { + "message": {"const": "{user} shared {resource} with {sharee}"}, + "variables": { + "type": "object", + "required": ["folder","resource","sharee","user"], + "properties": { + "resource": { + "type": "object", + "required": ["id","name"], + "properties": {"name": {"const": "folder"}} + }, + "sharee": { + "type": "object", + "required": ["id","displayName"], + "properties": {"displayName": {"const": "Brian"}} + }, + "user": { + "type": "object", + "required": ["id","displayName"], + "properties": {"displayName": {"const": "Alice Hansen"}} + } + } + } + } + }, + "times": { + "type": "object", + "required": ["recordedTime"], + "properties": { + "recordedTime": { "format": "date-time" } + } + } + } + } + ] + } + } + } + } + """ + Examples: + | permissions-role | + | Viewer List Grants | + | Editor List Grants | + + + Scenario: user lists permissions of a folder after enabling 'Viewer List Grants' role (Personal space) + Given the administrator has enabled the permissions role "Viewer List Grants" + And user "Alice" has created folder "folder" + When user "Alice" gets permissions list for folder "folder" of the space "Personal" using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "@libre.graph.permissions.actions.allowedValues", + "@libre.graph.permissions.roles.allowedValues" + ], + "properties": { + "@libre.graph.permissions.roles.allowedValues": { + "type": "array", + "minItems": 4, + "maxItems": 4, + "uniqueItems": true, + "items": { + "oneOf": [ + { + "type": "object", + "required": ["@libre.graph.weight", "description", "displayName", "id"], + "properties": { + "@libre.graph.weight": { "const": 10 }, + "description": { "const": "View and download." }, + "displayName": { "const": "Can view" }, + "id": { "const": "b1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5" } + } + }, + { + "type": "object", + "required": ["@libre.graph.weight", "description", "displayName", "id"], + "properties": { + "@libre.graph.weight": { "const": 30 }, + "description": { "const": "View, download and show all invited people." }, + "displayName": { "const": "Can view" }, + "id": { "const": "d5041006-ebb3-4b4a-b6a4-7c180ecfb17d" } + } + }, + { + "type": "object", + "required": ["@libre.graph.weight", "description", "displayName", "id"], + "properties": { + "@libre.graph.weight": { "const": 50 }, + "description": { "const": "View, download and upload." }, + "displayName": { "const": "Can upload" }, + "id": { "const": "1c996275-f1c9-4e71-abdf-a42f6495e960" } + } + }, + { + "type": "object", + "required": ["@libre.graph.weight", "description", "displayName", "id"], + "properties": { + "@libre.graph.weight": { "const": 60 }, + "description": { "const": "View, download, upload, edit, add and delete." }, + "displayName": { "const": "Can edit" }, + "id": { "const": "fb6c3e19-e378-47e5-b277-9732f9de6e21" } + } + } + ] + } + } + } + } + """ + + + Scenario: user lists permissions of a file after enabling 'File Editor List Grants' role (Project space) + Given the administrator has enabled the permissions role "File Editor List Grants" + And using spaces DAV path + And the administrator has assigned the role "Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "new-space" with the default quota using the Graph API + And user "Alice" has uploaded a file inside space "new-space" with content "hello world" to "textfile0.txt" + When user "Alice" gets permissions list for file "textfile0.txt" of the space "new-space" using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "@libre.graph.permissions.actions.allowedValues", + "@libre.graph.permissions.roles.allowedValues" + ], + "properties": { + "@libre.graph.permissions.roles.allowedValues": { + "type": "array", + "minItems": 3, + "maxItems": 3, + "uniqueItems": true, + "items": { + "oneOf": [ + { + "type": "object", + "required": ["@libre.graph.weight", "description", "displayName", "id"], + "properties": { + "@libre.graph.weight": { "const": 10 }, + "description": { "const": "View and download." }, + "displayName": { "const": "Can view" }, + "id": { "const": "b1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5" } + } + }, + { + "type": "object", + "required": ["@libre.graph.weight", "description", "displayName", "id"], + "properties": { + "@libre.graph.weight": { "const": 100 }, + "description": { "const": "View, download and edit." }, + "displayName": { "const": "Can edit" }, + "id": { "const": "2d00ce52-1fc2-4dbc-8b95-a73b73395f5a" } + } + }, + { + "type": "object", + "required": ["@libre.graph.weight", "description", "displayName", "id"], + "properties": { + "@libre.graph.weight": { "const": 110 }, + "description": { "const": "View, download, edit and show all invited people." }, + "displayName": { "const": "Can edit" }, + "id": { "const": "c1235aea-d106-42db-8458-7d5610fb0a67" } + } + } + ] + } + } + } + } + """ \ No newline at end of file diff --git a/tests/acceptance/features/apiSharingNgAdditionalShareRole/spaceEditorWithoutTrashbin.feature b/tests/acceptance/features/apiSharingNgAdditionalShareRole/spaceEditorWithoutTrashbin.feature new file mode 100644 index 0000000000..d33e3568a2 --- /dev/null +++ b/tests/acceptance/features/apiSharingNgAdditionalShareRole/spaceEditorWithoutTrashbin.feature @@ -0,0 +1,28 @@ +@env-config +Feature: an user shares resources + As a user + I don't want space editor to access deleted files + So that they can't restore them + + + Scenario: sharee checks trashbin after file is deleted + Given these users have been created with default attributes: + | username | + | Alice | + | Brian | + And using spaces DAV path + And the administrator has enabled the permissions role "Space Editor Without Trashbin" + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "new-space" with the default quota using the Graph API + And user "Alice" has uploaded a file inside space "new-space" with content "hello world" to "textfile.txt" + And user "Alice" has sent the following space share invitation: + | space | new-space | + | sharee | Brian | + | shareType | user | + | permissionsRole | Space Editor Without Trashbin | + And user "Brian" has removed the file "textfile.txt" from space "new-space" + When user "Brian" tries to list all deleted files in the trash bin of the space "new-space" + Then the HTTP status code should be "403" + When user "Brian" tries to restore the file "textfile.txt" from the trash of the space "new-space" to "/textfile.txt" + Then the HTTP status code should be "403" + And as "Alice" file "textfile.txt" should exist in the trashbin of the space "new-space" diff --git a/tests/acceptance/features/apiSharingNgAdditionalShareRole/spaceEditorWithoutVersionsWithoutTrashbin.feature b/tests/acceptance/features/apiSharingNgAdditionalShareRole/spaceEditorWithoutVersionsWithoutTrashbin.feature new file mode 100644 index 0000000000..bb7266552b --- /dev/null +++ b/tests/acceptance/features/apiSharingNgAdditionalShareRole/spaceEditorWithoutVersionsWithoutTrashbin.feature @@ -0,0 +1,33 @@ +@env-config +Feature: an user shares resources + As a user + I don't want space editor to access file versions or the trash bin + So that they can't see the versions or restore deleted files + + + Scenario: space editor without versions without trash bin permissions cannot access versions or restore deleted files + Given these users have been created with default attributes: + | username | + | Alice | + | Brian | + And using spaces DAV path + And the administrator has enabled the permissions role "Space Editor Without Versions Without Trashbin" + And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API + And user "Alice" has created a space "new-space" with the default quota using the Graph API + And user "Alice" has uploaded a file inside space "new-space" with content "new content" to "textfile.txt" + And user "Alice" has uploaded a file inside space "new-space" with content "newest content" to "textfile.txt" + And user "Alice" has sent the following space share invitation: + | space | new-space | + | sharee | Brian | + | shareType | user | + | permissionsRole | Space Editor Without Versions Without Trashbin | + When user "Brian" tries to get versions of the file "textfile.txt" from the space "new-space" using the WebDAV API + Then the HTTP status code should be "403" + When user "Brian" tries to download version of the file "textfile.txt" with the index "1" of the space "new-space" using the WebDAV API + Then the HTTP status code should be "403" + When user "Brian" removes the file "textfile.txt" from space "new-space" + And user "Brian" tries to list all deleted files in the trash bin of the space "new-space" + Then the HTTP status code should be "403" + When user "Brian" tries to restore the file "textfile.txt" from the trash of the space "new-space" to "/textfile.txt" + Then the HTTP status code should be "403" + And as "Alice" file "textfile.txt" should exist in the trashbin of the space "new-space" \ No newline at end of file From c41e7753e0af5c57298e6613988bd5aabd606bf4 Mon Sep 17 00:00:00 2001 From: micbar Date: Sun, 4 Oct 2026 11:49:15 +0200 Subject: [PATCH 3/5] fix: disable new roles in config --- services/graph/pkg/config/defaults/defaultconfig.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/graph/pkg/config/defaults/defaultconfig.go b/services/graph/pkg/config/defaults/defaultconfig.go index c2c669469d..598e4a099e 100644 --- a/services/graph/pkg/config/defaults/defaultconfig.go +++ b/services/graph/pkg/config/defaults/defaultconfig.go @@ -17,9 +17,13 @@ var ( unifiedrole.UnifiedRoleSecureViewerID, unifiedrole.UnifiedRoleSpaceViewerWithVersionsID, unifiedrole.UnifiedRoleSpaceEditorWithoutVersionsID, + unifiedrole.UnifiedRoleSpaceEditorWithoutTrashbinID, + unifiedrole.UnifiedRoleSpaceEditorWithoutVersionsWithoutTrashbinID, unifiedrole.UnifiedRoleViewerListGrantsID, unifiedrole.UnifiedRoleEditorListGrantsID, + unifiedrole.UnifiedRoleEditorListGrantsWithVersionsID, unifiedrole.UnifiedRoleFileEditorListGrantsID, + unifiedrole.UnifiedRoleFileEditorListGrantsWithVersionsID, unifiedrole.UnifiedRoleViewerWithVersionsID, unifiedrole.UnifiedRoleEditorWithVersionsID, unifiedrole.UnifiedRoleFileEditorWithVersionsID, From 0efe09f6350edba439af70fdf793e0b23b9c221f Mon Sep 17 00:00:00 2001 From: "v.scharf" Date: Mon, 5 Oct 2026 08:30:27 +0200 Subject: [PATCH 4/5] adjust tests --- tests/acceptance/bootstrap/GraphContext.php | 10 +- .../acceptance/bootstrap/SharingNgContext.php | 4 +- .../editorListGrantsWithVersions.feature | 84 +++++-------- .../listGrantsShareRole.feature | 116 +----------------- 4 files changed, 42 insertions(+), 172 deletions(-) diff --git a/tests/acceptance/bootstrap/GraphContext.php b/tests/acceptance/bootstrap/GraphContext.php index 59b043d52c..3cce71e6ee 100644 --- a/tests/acceptance/bootstrap/GraphContext.php +++ b/tests/acceptance/bootstrap/GraphContext.php @@ -2921,15 +2921,9 @@ public function userListsTheActivitiesForResourceOfSpaceUsingTheGraphAPI( string $resource, string $spaceName ): void { - $resourceId = $this->featureContext->spacesContext->getResourceId($user, $spaceName, $resource); - $response = GraphHelper::getActivities( - $this->featureContext->getBaseUrl(), - $this->featureContext->getStepLineRef(), - $user, - $this->featureContext->getPasswordForUser($user), - $resourceId + $this->featureContext->setResponse( + $this->getActivities($user, $resource, $spaceName) ); - $this->featureContext->setResponse($response); } /** diff --git a/tests/acceptance/bootstrap/SharingNgContext.php b/tests/acceptance/bootstrap/SharingNgContext.php index 311743f808..9efe16f777 100644 --- a/tests/acceptance/bootstrap/SharingNgContext.php +++ b/tests/acceptance/bootstrap/SharingNgContext.php @@ -524,7 +524,9 @@ public function userHasSentTheFollowingResourceShareInvitation(string $user, Tab ); $response = WaitHelper::waitUntil( fn () => $this->sendShareInvitation($user, $rows), - fn ($response) => !self::isShareManagerMigrating($response) + fn ($response) => !self::isShareManagerMigrating($response), + null, + 30 ); $this->featureContext->theHTTPStatusCodeShouldBe(200, "", $response); } diff --git a/tests/acceptance/features/apiSharingNgAdditionalShareRole/editorListGrantsWithVersions.feature b/tests/acceptance/features/apiSharingNgAdditionalShareRole/editorListGrantsWithVersions.feature index 1f2536979d..47ba000c71 100644 --- a/tests/acceptance/features/apiSharingNgAdditionalShareRole/editorListGrantsWithVersions.feature +++ b/tests/acceptance/features/apiSharingNgAdditionalShareRole/editorListGrantsWithVersions.feature @@ -193,7 +193,8 @@ Feature: an user shares resources "type": "array", "minItems": 19, "maxItems": 19, - "uniqueItems": true + "uniqueItems": true, + "items": { "type": "string" } }, "@libre.graph.permissions.roles.allowedValues": { "type": "array", @@ -260,7 +261,8 @@ Feature: an user shares resources "type": "array", "minItems": 19, "maxItems": 19, - "uniqueItems": true + "uniqueItems": true, + "items": { "type": "string" } }, "@libre.graph.permissions.roles.allowedValues": { "type": "array", @@ -324,7 +326,8 @@ Feature: an user shares resources "type": "array", "minItems": 19, "maxItems": 19, - "uniqueItems": true + "uniqueItems": true, + "items": { "type": "string" } }, "@libre.graph.permissions.roles.allowedValues": { "type": "array", @@ -401,7 +404,8 @@ Feature: an user shares resources "type": "array", "minItems": 19, "maxItems": 19, - "uniqueItems": true + "uniqueItems": true, + "items": { "type": "string" } }, "@libre.graph.permissions.roles.allowedValues": { "type": "array", @@ -495,7 +499,7 @@ Feature: an user shares resources "remoteItem": { "type": "object", "required": ["createdBy","eTag","file","id","lastModifiedDateTime","name", - "parentReference","permissions","size","spaceId"], + "parentReference","permissions","size"], "properties": { "createdBy": { "type": "object", @@ -529,10 +533,6 @@ Feature: an user shares resources "driveType": { "const": "personal" } } }, - "spaceId": { - "type": "string", - "pattern": "^%space_id_pattern%$" - }, "permissions": { "type": "array", "minItems": 1, @@ -606,7 +606,7 @@ Feature: an user shares resources "remoteItem": { "type": "object", "required": ["createdBy","eTag","folder","id","lastModifiedDateTime", - "name","parentReference","permissions","spaceId"], + "name","parentReference","permissions"], "properties": { "createdBy": { "type": "object", @@ -640,10 +640,6 @@ Feature: an user shares resources "driveType": { "const": "personal" } } }, - "spaceId": { - "type": "string", - "pattern": "^%space_id_pattern%$" - }, "permissions": { "type": "array", "minItems": 1, @@ -738,7 +734,7 @@ Feature: an user shares resources "remoteItem": { "type": "object", "required": ["eTag","file","id","lastModifiedDateTime", - "name","parentReference","permissions","size","spaceId"], + "name","parentReference","permissions","size"], "properties": { "eTag": { "pattern": "%etag_pattern%" }, "file": { @@ -758,10 +754,6 @@ Feature: an user shares resources "driveType": { "const": "project" } } }, - "spaceId": { - "type": "string", - "pattern": "^%space_id_pattern%$" - }, "permissions": { "type": "array", "minItems": 1, @@ -835,7 +827,7 @@ Feature: an user shares resources "remoteItem": { "type": "object", "required": ["eTag","folder","id","lastModifiedDateTime", - "name","parentReference","permissions","spaceId"], + "name","parentReference","permissions"], "properties": { "eTag": { "pattern": "%etag_pattern%" }, "file": { @@ -855,10 +847,6 @@ Feature: an user shares resources "driveType": { "const": "project" } } }, - "spaceId": { - "type": "string", - "pattern": "^%space_id_pattern%$" - }, "permissions": { "type": "array", "minItems": 1, @@ -930,6 +918,10 @@ Feature: an user shares resources | shareType | user | | permissionsRole | File Editor | And user "Brian" has uploaded file with content "updated content" to "Shares/textfile.txt" + And user "Alice" has updated the last resource share with the following properties: + | permissionsRole | File Editor List Grants With Versions | + | space | Personal | + | resource | textfile.txt | And user "Alice" has sent the following resource share invitation: | resource | folderToShare | | space | Personal | @@ -937,16 +929,10 @@ Feature: an user shares resources | shareType | user | | permissionsRole | Editor | And user "Brian" has uploaded file with content "updated content" to "Shares/folderToShare/lorem.txt" - And user "Alice" has updated the following resource share: - | permissionsRole | File Editor List Grants With Versions | - | space | Personal | - | resource | textfile.txt | - | sharee | Brian | - And user "Alice" has updated the following resource share: + And user "Alice" has updated the last resource share with the following properties: | permissionsRole | Editor List Grants With Versions | | space | Personal | | resource | folderToShare | - | sharee | Brian | When user "Brian" gets the number of versions of file "Shares/textfile.txt" Then the HTTP status code should be "207" And the number of versions should be "1" @@ -967,6 +953,10 @@ Feature: an user shares resources | shareType | user | | permissionsRole | File Editor List Grants With Versions | And user "Brian" has uploaded file with content "updated content" to "Shares/textfile.txt" + And user "Alice" has updated the last resource share with the following properties: + | permissionsRole | File Editor | + | space | Personal | + | resource | textfile.txt | And user "Alice" has sent the following resource share invitation: | resource | folderToShare | | space | Personal | @@ -974,16 +964,10 @@ Feature: an user shares resources | shareType | user | | permissionsRole | Editor List Grants With Versions | And user "Brian" has uploaded file with content "updated content" to "Shares/folderToShare/lorem.txt" - And user "Alice" has updated the following resource share: - | permissionsRole | File Editor | - | space | Personal | - | resource | textfile.txt | - | sharee | Brian | - And user "Alice" has updated the following resource share: + And user "Alice" has updated the last resource share with the following properties: | permissionsRole | Editor | | space | Personal | | resource | folderToShare | - | sharee | Brian | When user "Brian" tries to get versions of file "textfile.txt" from "Alice" Then the HTTP status code should be "403" When user "Brian" tries to get versions of file "folderToShare/lorem.txt" from "Alice" @@ -1004,6 +988,10 @@ Feature: an user shares resources | shareType | user | | permissionsRole | File Editor | And user "Brian" has uploaded file with content "updated content" to "Shares/textfile.txt" + And user "Alice" has updated the last resource share with the following properties: + | permissionsRole | File Editor List Grants With Versions | + | space | new-space | + | resource | textfile.txt | And user "Alice" has sent the following resource share invitation: | resource | folderToShare | | space | new-space | @@ -1011,16 +999,10 @@ Feature: an user shares resources | shareType | user | | permissionsRole | Editor | And user "Brian" has uploaded file with content "updated content" to "Shares/folderToShare/lorem.txt" - And user "Alice" has updated the following resource share: - | permissionsRole | File Editor List Grants With Versions | - | space | new-space | - | resource | textfile.txt | - | sharee | Brian | - And user "Alice" has updated the following resource share: + And user "Alice" has updated the last resource share with the following properties: | permissionsRole | Editor List Grants With Versions | | space | new-space | | resource | folderToShare | - | sharee | Brian | When user "Brian" gets the number of versions of file "Shares/textfile.txt" Then the HTTP status code should be "207" And the number of versions should be "1" @@ -1044,6 +1026,10 @@ Feature: an user shares resources | shareType | user | | permissionsRole | File Editor List Grants With Versions | And user "Brian" has uploaded file with content "updated content" to "Shares/textfile.txt" + And user "Alice" has updated the last resource share with the following properties: + | permissionsRole | File Editor | + | space | new-space | + | resource | textfile.txt | And user "Alice" has sent the following resource share invitation: | resource | folderToShare | | space | new-space | @@ -1051,16 +1037,10 @@ Feature: an user shares resources | shareType | user | | permissionsRole | Editor List Grants With Versions | And user "Brian" has uploaded file with content "updated content" to "Shares/folderToShare/lorem.txt" - And user "Alice" has updated the following resource share: - | permissionsRole | File Editor | - | space | new-space | - | resource | textfile.txt | - | sharee | Brian | - And user "Alice" has updated the following resource share: + And user "Alice" has updated the last resource share with the following properties: | permissionsRole | Editor | | space | new-space | | resource | folderToShare | - | sharee | Brian | When user "Brian" tries to get versions of the file "textfile.txt" from the space "Shares" using the WebDAV API Then the HTTP status code should be "403" When user "Brian" tries to get versions of the file "folderToShare/lorem.txt" from the space "Shares" using the WebDAV API diff --git a/tests/acceptance/features/apiSharingNgAdditionalShareRole/listGrantsShareRole.feature b/tests/acceptance/features/apiSharingNgAdditionalShareRole/listGrantsShareRole.feature index 0cd2afff32..0b9e790ffa 100644 --- a/tests/acceptance/features/apiSharingNgAdditionalShareRole/listGrantsShareRole.feature +++ b/tests/acceptance/features/apiSharingNgAdditionalShareRole/listGrantsShareRole.feature @@ -893,7 +893,7 @@ Feature: ListGrants role "name": {"const": "textfile.txt"}, "remoteItem": { "type": "object", - "required": ["createdBy","eTag","file","id","lastModifiedDateTime","name","parentReference","permissions","size", "spaceId"], + "required": ["createdBy","eTag","file","id","lastModifiedDateTime","name","parentReference","permissions","size"], "properties": { "eTag": {"pattern": "%etag_pattern%"}, "id": {"pattern": "^%file_id_pattern%$"}, @@ -944,10 +944,6 @@ Feature: ListGrants role } } } - }, - "spaceId": { - "type": "string", - "pattern": "^%space_id_pattern%$" } } } @@ -967,7 +963,7 @@ Feature: ListGrants role "name": {"const": "folder"}, "remoteItem": { "type": "object", - "required": ["createdBy","eTag","folder","id","lastModifiedDateTime","name","parentReference","permissions", "spaceId"], + "required": ["createdBy","eTag","folder","id","lastModifiedDateTime","name","parentReference","permissions"], "properties": { "eTag": {"pattern": "%etag_pattern%"}, "file": {}, @@ -1019,10 +1015,6 @@ Feature: ListGrants role } } } - }, - "spaceId": { - "type": "string", - "pattern": "^%space_id_pattern%$" } } } @@ -1043,107 +1035,9 @@ Feature: ListGrants role | shareType | user | | permissionsRole | | And user "Brian" has a share "folder" synced - When user "Brian" lists the activities of folder "folder" from space "Shares" using the Graph API - Then the HTTP status code should be "200" - And the JSON data of the response should match - """ - { - "type": "object", - "required": ["value"], - "properties": { - "value": { - "type": "array", - "minItems": 2, - "maxItems": 2, - "uniqueItems": true, - "items": { - "oneOf": [ - { - "type": "object", - "required": ["id","template","times"], - "properties": { - "id": {"pattern": "^%user_id_pattern%$"}, - "template": { - "type": "object", - "required": ["message","variables"], - "properties": { - "message": {"const": "{user} added {resource} to {folder}"}, - "variables":{ - "type": "object", - "required": ["folder","resource","user"], - "properties": { - "folder": { - "type": "object", - "required": ["id","name"], - "properties":{ - "id": {"const": ""}, - "name": {"const": "shared-with-me"} - } - }, - "resource": { - "type": "object", - "required": ["id","name"], - "properties": {"name": {"const": "folder"}} - }, - "user": { - "type": "object", - "required": ["id","displayName"], - "properties":{"displayName": {"const": "Alice Hansen"}} - } - } - } - } - } - } - }, - { - "type": "object", - "required": ["id","template","times"], - "properties": { - "id": {"pattern": "^%user_id_pattern%$"}, - "template": { - "type": "object", - "required": ["message","variables"], - "properties": { - "message": {"const": "{user} shared {resource} with {sharee}"}, - "variables": { - "type": "object", - "required": ["folder","resource","sharee","user"], - "properties": { - "resource": { - "type": "object", - "required": ["id","name"], - "properties": {"name": {"const": "folder"}} - }, - "sharee": { - "type": "object", - "required": ["id","displayName"], - "properties": {"displayName": {"const": "Brian"}} - }, - "user": { - "type": "object", - "required": ["id","displayName"], - "properties": {"displayName": {"const": "Alice Hansen"}} - } - } - } - } - }, - "times": { - "type": "object", - "required": ["recordedTime"], - "properties": { - "recordedTime": { "format": "date-time" } - } - } - } - } - ] - } - } - } - } - """ + Then for user "Brian" folder "folder" of the space "Shares" should have the following activities: + | {user} added {resource} to {folder} | + | {user} shared {resource} with {sharee} | Examples: | permissions-role | | Viewer List Grants | From da4d31b73dd07f90a6bc2a8a80c585e0d1c6f046 Mon Sep 17 00:00:00 2001 From: "v.scharf" Date: Mon, 5 Oct 2026 13:26:56 +0200 Subject: [PATCH 5/5] fix listGrantsShareRole test --- tests/acceptance/bootstrap/SharingNgContext.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/acceptance/bootstrap/SharingNgContext.php b/tests/acceptance/bootstrap/SharingNgContext.php index 9efe16f777..4aa1d82d4c 100644 --- a/tests/acceptance/bootstrap/SharingNgContext.php +++ b/tests/acceptance/bootstrap/SharingNgContext.php @@ -162,7 +162,7 @@ public function getPermissionsList( ?string $resource = '', ?string $query = null ): ResponseInterface { - if ($space === "Shares") { + if ($space === "Shares" && $resource !== '') { // a shared resource lives in the owner's space; its permissions are // listed via the share's remote item id and its parent drive id $spaceId = $this->spacesContext->getSharesRemoteItemParentDriveId($user, $resource);