diff --git a/Neos.Neos/Classes/Domain/Service/WorkspaceService.php b/Neos.Neos/Classes/Domain/Service/WorkspaceService.php index 95be477088..b4021fae15 100644 --- a/Neos.Neos/Classes/Domain/Service/WorkspaceService.php +++ b/Neos.Neos/Classes/Domain/Service/WorkspaceService.php @@ -280,7 +280,7 @@ private function requireManagementWorkspacePermission(ContentRepositoryId $conte $this->userService->getCurrentUser()?->getId() ); if (!$workspacePermissions->manage) { - throw new AccessDenied(sprintf('The current user does not have manage permissions for workspace "%s" in content repository "%s"', $workspaceName->value, $contentRepositoryId->value), 1731654519); + throw new AccessDenied(sprintf('Managing workspace "%s" in "%s" was denied: %s', $workspaceName->value, $contentRepositoryId->value, $workspacePermissions->getReason()), 1731654519); } } } diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/ExceptionsTrait.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/ExceptionsTrait.php index 5a87e10724..b2257a5c4d 100644 --- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/ExceptionsTrait.php +++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/ExceptionsTrait.php @@ -38,10 +38,11 @@ private function tryCatchingExceptions(\Closure $callback): mixed } /** + * @Then an exception of type :expectedShortExceptionName should be thrown with code :code * @Then an exception of type :expectedShortExceptionName should be thrown with message: * @Then an exception of type :expectedShortExceptionName should be thrown */ - public function anExceptionShouldBeThrown(string $expectedShortExceptionName, PyStringNode $expectedExceptionMessage = null): void + public function anExceptionShouldBeThrown(string $expectedShortExceptionName, ?int $code = null, PyStringNode $expectedExceptionMessage = null): void { Assert::assertNotNull($this->lastCaughtException, 'Expected an exception but none was thrown'); $lastCaughtExceptionShortName = (new \ReflectionClass($this->lastCaughtException))->getShortName(); @@ -49,6 +50,9 @@ public function anExceptionShouldBeThrown(string $expectedShortExceptionName, Py if ($expectedExceptionMessage !== null) { Assert::assertSame($expectedExceptionMessage->getRaw(), $this->lastCaughtException->getMessage()); } + if ($code !== null) { + Assert::assertSame($code, $this->lastCaughtException->getCode()); + } $this->lastCaughtException = null; } diff --git a/Neos.Neos/Tests/Behavior/Features/ContentRepository/Security/WorkspacePermissions.feature b/Neos.Neos/Tests/Behavior/Features/ContentRepository/Security/WorkspacePermissions.feature index b7ba9bfb5c..7b02cca647 100644 --- a/Neos.Neos/Tests/Behavior/Features/ContentRepository/Security/WorkspacePermissions.feature +++ b/Neos.Neos/Tests/Behavior/Features/ContentRepository/Security/WorkspacePermissions.feature @@ -82,6 +82,35 @@ Feature: Workspace permission related features | admin | | editor | | restricted_editor | + | owner | + | collaborator | + | uninvolved | + + Scenario Outline: Creating a base workspace without WRITE permissions + Given I am authenticated as + And the shared workspace "some-shared-workspace" is created with the target workspace "workspace" + Then an exception of type "AccessDenied" should be thrown with code 1729086686 + + And the personal workspace "some-other-personal-workspace" is created with the target workspace "workspace" for user + Then an exception of type "AccessDenied" should be thrown with code 1729086686 + + Examples: + | user | + | admin | + | editor | + | restricted_editor | + | uninvolved | + + Scenario Outline: Creating a base workspace with WRITE permissions + Given I am authenticated as + And the shared workspace "some-shared-workspace" is created with the target workspace "workspace" + + And the personal workspace "some-other-personal-workspace" is created with the target workspace "workspace" for user + + Examples: + | user | + | collaborator | + | owner | Scenario Outline: Deleting a workspace without MANAGE permissions Given I am authenticated as @@ -103,7 +132,43 @@ Feature: Workspace permission related features | manager | | owner | + Scenario Outline: Managing metadata and roles of a workspace without MANAGE permissions + Given I am authenticated as + And the title of workspace "workspace" is set to "Some new workspace title" + Then an exception of type "AccessDenied" should be thrown with code 1731654519 + + And the description of workspace "workspace" is set to "Some new workspace description" + Then an exception of type "AccessDenied" should be thrown with code 1731654519 + + When the role COLLABORATOR is assigned to workspace "workspace" for group "Neos.Neos:AbstractEditor" + Then an exception of type "AccessDenied" should be thrown with code 1731654519 + + When the role for group "Neos.Neos:AbstractEditor" is unassigned from workspace "workspace" + Then an exception of type "AccessDenied" should be thrown with code 1731654519 + + Examples: + | user | + | collaborator | + | uninvolved | + + Scenario Outline: Managing metadata and roles of a workspace with MANAGE permissions + Given I am authenticated as + And the title of workspace "workspace" is set to "Some new workspace title" + And the description of workspace "workspace" is set to "Some new workspace description" + When the role COLLABORATOR is assigned to workspace "workspace" for group "Neos.Neos:AbstractEditor" + When the role for group "Neos.Neos:AbstractEditor" is unassigned from workspace "workspace" + + Examples: + | user | + | admin | + | manager | + | owner | + Scenario Outline: Handling commands that require WRITE permissions on the workspace + When I am authenticated as "uninvolved" + And the command is executed with payload '' and exceptions are caught + Then the last command should have thrown an exception of type "AccessDenied" with code 1729086686 + When I am authenticated as "editor" And the command is executed with payload '' and exceptions are caught Then the last command should have thrown an exception of type "AccessDenied" with code 1729086686 @@ -119,6 +184,10 @@ Feature: Workspace permission related features When I am authenticated as "owner" And the command is executed with payload '' + # todo test also collaborator, but cannot commands twice here: + # When I am authenticated as "collaborator" + # And the command is executed with payload '' and exceptions are caught + Examples: | command | command payload | | CreateNodeAggregateWithNode | {"nodeAggregateId":"a1b1","parentNodeAggregateId":"a1b","nodeTypeName":"Neos.Neos:Document"} |