feat: add new editor roles (#3637)

* feat: add new editor roles

* add tests for additional roles

* fix: disable new roles in config

* adjust tests

* fix listGrantsShareRole test

---------

Co-authored-by: v.scharf <v.scharf@opencloud.eu>
This commit is contained in:
Michael Barzandv.scharf authored and GitHub committed 2026-10-05 14:07:30 +02:00
1 parent 4dec19089d
commit b955c7c4f0
18 files changed
+2745 -32

No files matched your search

+2
View File
@@ -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
+6
View File
@@ -232,6 +232,12 @@ config = {
],
"skip": False,
},
"sharingNgAdditionalShareRole": {
"suites": [
"apiSharingNgAdditionalShareRole",
],
"skip": False,
},
"notification": {
"suites": [
"apiNotification",
@@ -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,
@@ -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."
@@ -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:
+21 -17
View File
@@ -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
+115
View File
@@ -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()
@@ -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,
},
@@ -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';
+105 -8
View File
@@ -2921,15 +2921,9 @@ class GraphContext implements Context {
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);
}
/**
@@ -3490,4 +3484,107 @@ class GraphContext implements Context {
$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)
);
}
}
@@ -123,6 +123,36 @@ class OcConfigContext implements Context {
$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
@@ -26,6 +26,7 @@ use GuzzleHttp\Exception\GuzzleException;
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 @@ class SharingNgContext implements Context {
?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" && $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);
$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 @@ class SharingNgContext implements Context {
);
}
/**
* @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,30 @@ class SharingNgContext implements Context {
$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),
null,
30
);
$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
+36 -1
View File
@@ -307,6 +307,38 @@ class SpacesContext implements Context {
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 @@ class SpacesContext implements Context {
* @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 @@ class SpacesContext implements Context {
* @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 @@ class SpacesContext implements Context {
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) {
+12
View File
@@ -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:
@@ -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"
@@ -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"