From 49aa8bbaada53ec737548f8d3bd89c9b2b51d1c8 Mon Sep 17 00:00:00 2001 From: Bastian Waidelich Date: Tue, 15 Oct 2024 18:23:44 +0200 Subject: [PATCH] FEATURE: Content Repository Privileges Related: #3732 --- ...trineDbalContentGraphProjectionFactory.php | 2 +- ...actory.php => FakeAuthProviderFactory.php} | 12 +- .../Settings.ContentRepositoryRegistry.yaml | 4 +- .../Classes/CommandHandlingDependencies.php | 8 +- .../Classes/ContentRepository.php | 19 ++- .../Classes/ContentRepositoryReadModel.php | 5 +- .../Factory/ContentRepositoryFactory.php | 9 +- .../Factory/ProjectionFactoryDependencies.php | 2 + .../Auth/AuthProviderInterface.php | 20 +++ .../Classes/SharedModel/Auth/Privilege.php | 38 +++++ .../SharedModel/Auth/StaticAuthProvider.php | 36 +++++ .../SharedModel/{User => Auth}/UserId.php | 2 +- .../Auth/WorkspacePrivilegeType.php | 23 +++ .../SharedModel/User/StaticUserIdProvider.php | 23 --- .../User/UserIdProviderInterface.php | 13 -- .../Bootstrap/CRTestSuiteRuntimeVariables.php | 6 +- .../Bootstrap/Helpers/FakeAuthProvider.php | 37 +++++ .../Bootstrap/Helpers/FakeUserIdProvider.php | 23 --- .../Classes/ContentRepositoryRegistry.php | 18 +-- .../AuthProviderFactoryInterface.php} | 10 +- .../StaticAuthProviderFactory.php | 20 +++ .../StaticUserIdProviderFactory.php | 20 --- .../Configuration/Settings.yaml | 4 +- .../ContentRepositoryAuthProvider.php | 147 ++++++++++++++++++ .../ContentRepositoryAuthProviderFactory.php | 35 +++++ .../Domain/Service/WorkspaceService.php | 9 +- .../Controller/AbstractServiceController.php | 10 -- .../Classes/UserIdProvider/UserIdProvider.php | 29 ---- .../UserIdProvider/UserIdProviderFactory.php | 34 ---- .../Settings.ContentRepositoryRegistry.yaml | 4 +- 30 files changed, 426 insertions(+), 196 deletions(-) rename Neos.ContentRepository.BehavioralTests/Classes/TestSuite/Behavior/{FakeUserIdProviderFactory.php => FakeAuthProviderFactory.php} (52%) create mode 100644 Neos.ContentRepository.Core/Classes/SharedModel/Auth/AuthProviderInterface.php create mode 100644 Neos.ContentRepository.Core/Classes/SharedModel/Auth/Privilege.php create mode 100644 Neos.ContentRepository.Core/Classes/SharedModel/Auth/StaticAuthProvider.php rename Neos.ContentRepository.Core/Classes/SharedModel/{User => Auth}/UserId.php (96%) create mode 100644 Neos.ContentRepository.Core/Classes/SharedModel/Auth/WorkspacePrivilegeType.php delete mode 100644 Neos.ContentRepository.Core/Classes/SharedModel/User/StaticUserIdProvider.php delete mode 100644 Neos.ContentRepository.Core/Classes/SharedModel/User/UserIdProviderInterface.php create mode 100644 Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Helpers/FakeAuthProvider.php delete mode 100644 Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Helpers/FakeUserIdProvider.php rename Neos.ContentRepositoryRegistry/Classes/Factory/{UserIdProvider/UserIdProviderFactoryInterface.php => AuthProvider/AuthProviderFactoryInterface.php} (52%) create mode 100644 Neos.ContentRepositoryRegistry/Classes/Factory/AuthProvider/StaticAuthProviderFactory.php delete mode 100644 Neos.ContentRepositoryRegistry/Classes/Factory/UserIdProvider/StaticUserIdProviderFactory.php create mode 100644 Neos.Neos/Classes/ContentRepositoryAuthProvider/ContentRepositoryAuthProvider.php create mode 100644 Neos.Neos/Classes/ContentRepositoryAuthProvider/ContentRepositoryAuthProviderFactory.php delete mode 100644 Neos.Neos/Classes/UserIdProvider/UserIdProvider.php delete mode 100644 Neos.Neos/Classes/UserIdProvider/UserIdProviderFactory.php diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php index 1b750c114a4..6a0644842a9 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php @@ -58,7 +58,7 @@ public function build( ), $tableNames, $dimensionSpacePointsRepository, - new ContentRepositoryReadModel($contentRepositoryReadModelAdapter) + new ContentRepositoryReadModel($contentRepositoryReadModelAdapter), ); } } diff --git a/Neos.ContentRepository.BehavioralTests/Classes/TestSuite/Behavior/FakeUserIdProviderFactory.php b/Neos.ContentRepository.BehavioralTests/Classes/TestSuite/Behavior/FakeAuthProviderFactory.php similarity index 52% rename from Neos.ContentRepository.BehavioralTests/Classes/TestSuite/Behavior/FakeUserIdProviderFactory.php rename to Neos.ContentRepository.BehavioralTests/Classes/TestSuite/Behavior/FakeAuthProviderFactory.php index 6a0a5c7a408..df59e7f80c8 100644 --- a/Neos.ContentRepository.BehavioralTests/Classes/TestSuite/Behavior/FakeUserIdProviderFactory.php +++ b/Neos.ContentRepository.BehavioralTests/Classes/TestSuite/Behavior/FakeAuthProviderFactory.php @@ -5,17 +5,17 @@ namespace Neos\ContentRepository\BehavioralTests\TestSuite\Behavior; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; -use Neos\ContentRepository\Core\SharedModel\User\UserIdProviderInterface; -use Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap\Helpers\FakeUserIdProvider; -use Neos\ContentRepositoryRegistry\Factory\UserIdProvider\UserIdProviderFactoryInterface; +use Neos\ContentRepository\Core\SharedModel\Auth\AuthProviderInterface; +use Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap\Helpers\FakeAuthProvider; +use Neos\ContentRepositoryRegistry\Factory\AuthProvider\AuthProviderFactoryInterface; -final class FakeUserIdProviderFactory implements UserIdProviderFactoryInterface +final class FakeAuthProviderFactory implements AuthProviderFactoryInterface { /** * @param array $options */ - public function build(ContentRepositoryId $contentRepositoryId, array $options): UserIdProviderInterface + public function build(ContentRepositoryId $contentRepositoryId, array $options): AuthProviderInterface { - return new FakeUserIdProvider(); + return new FakeAuthProvider(); } } diff --git a/Neos.ContentRepository.BehavioralTests/Configuration/Testing/Behat/Settings.ContentRepositoryRegistry.yaml b/Neos.ContentRepository.BehavioralTests/Configuration/Testing/Behat/Settings.ContentRepositoryRegistry.yaml index 53eafcc4977..74387df65ff 100644 --- a/Neos.ContentRepository.BehavioralTests/Configuration/Testing/Behat/Settings.ContentRepositoryRegistry.yaml +++ b/Neos.ContentRepository.BehavioralTests/Configuration/Testing/Behat/Settings.ContentRepositoryRegistry.yaml @@ -2,8 +2,8 @@ Neos: ContentRepositoryRegistry: presets: default: - userIdProvider: - factoryObjectName: 'Neos\ContentRepository\BehavioralTests\TestSuite\Behavior\FakeUserIdProviderFactory' + authProvider: + factoryObjectName: 'Neos\ContentRepository\BehavioralTests\TestSuite\Behavior\FakeAuthProviderFactory' clock: factoryObjectName: 'Neos\ContentRepository\BehavioralTests\TestSuite\Behavior\FakeClockFactory' nodeTypeManager: diff --git a/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php index 1c778159fcd..12d7b67eabe 100644 --- a/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php +++ b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php @@ -38,8 +38,10 @@ final class CommandHandlingDependencies */ private array $overriddenContentGraphInstances = []; - public function __construct(private readonly ContentRepository $contentRepository) - { + public function __construct( + private readonly ContentRepository $contentRepository, + private readonly ContentRepositoryReadModel $contentRepositoryReadModel, + ) { } public function handle(CommandInterface $command): CommandResult @@ -84,7 +86,7 @@ public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInter return $this->overriddenContentGraphInstances[$workspaceName->value]; } - return $this->contentRepository->getContentGraph($workspaceName); + return $this->contentRepositoryReadModel->getContentGraphByWorkspaceName($workspaceName); } /** diff --git a/Neos.ContentRepository.Core/Classes/ContentRepository.php b/Neos.ContentRepository.Core/Classes/ContentRepository.php index 54e81fb63a3..f2e50762a18 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepository.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepository.php @@ -35,10 +35,11 @@ use Neos\ContentRepository\Core\Projection\ProjectionStateInterface; use Neos\ContentRepository\Core\Projection\ProjectionStatuses; use Neos\ContentRepository\Core\Projection\WithMarkStaleInterface; +use Neos\ContentRepository\Core\SharedModel\Auth\WorkspacePrivilegeType; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryStatus; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; -use Neos\ContentRepository\Core\SharedModel\User\UserIdProviderInterface; +use Neos\ContentRepository\Core\SharedModel\Auth\AuthProviderInterface; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStream; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreams; @@ -85,10 +86,10 @@ public function __construct( private readonly NodeTypeManager $nodeTypeManager, private readonly InterDimensionalVariationGraph $variationGraph, private readonly ContentDimensionSourceInterface $contentDimensionSource, - private readonly UserIdProviderInterface $userIdProvider, + private readonly AuthProviderInterface $authProvider, private readonly ClockInterface $clock, ) { - $this->commandHandlingDependencies = new CommandHandlingDependencies($this); + $this->commandHandlingDependencies = new CommandHandlingDependencies($this, $this->getContentRepositoryReadModel()); } /** @@ -99,12 +100,16 @@ public function __construct( */ public function handle(CommandInterface $command): CommandResult { + $privilege = $this->authProvider->getCommandPrivilege($command); + if (!$privilege->granted) { + throw new \RuntimeException(sprintf('Command "%s" was denied: %s', $command::class, $privilege->message), 1729086686); + } // the commands only calculate which events they want to have published, but do not do the // publishing themselves $eventsToPublish = $this->commandBus->handle($command, $this->commandHandlingDependencies); // TODO meaningful exception message - $initiatingUserId = $this->userIdProvider->getUserId(); + $initiatingUserId = $this->authProvider->getUserId(); $initiatingTimestamp = $this->clock->now()->format(\DateTimeInterface::ATOM); // Add "initiatingUserId" and "initiatingTimestamp" metadata to all events. @@ -269,6 +274,12 @@ public function findContentStreams(): ContentStreams */ public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface { + $privilege = $this->authProvider->getWorkspacePrivilege($workspaceName, WorkspacePrivilegeType::READ_NODES); + if (!$privilege->granted) { + throw new \RuntimeException(sprintf('Read access denied for workspace "%s": %s', $workspaceName->value, $privilege->message ?? ''), 1729014760); + // TODO more specific exception + //throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); + } return $this->getContentRepositoryReadModel()->getContentGraphByWorkspaceName($workspaceName); } diff --git a/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php b/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php index 95584a07f76..a9fd7d0eeef 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepositoryReadModel.php @@ -16,6 +16,9 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ProjectionStateInterface; +use Neos\ContentRepository\Core\Projection\WithMarkStaleInterface; +use Neos\ContentRepository\Core\SharedModel\Auth\AuthProviderInterface; +use Neos\ContentRepository\Core\SharedModel\Auth\WorkspacePrivilegeType; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStream; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; @@ -35,7 +38,7 @@ final class ContentRepositoryReadModel implements ProjectionStateInterface { public function __construct( - private readonly ContentRepositoryReadModelAdapterInterface $adapter + private readonly ContentRepositoryReadModelAdapterInterface $adapter, ) { } diff --git a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php index bba7d784579..8dc6e517c88 100644 --- a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php +++ b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php @@ -31,7 +31,7 @@ use Neos\ContentRepository\Core\Projection\ProjectionCatchUpTriggerInterface; use Neos\ContentRepository\Core\Projection\ProjectionsAndCatchUpHooks; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; -use Neos\ContentRepository\Core\SharedModel\User\UserIdProviderInterface; +use Neos\ContentRepository\Core\SharedModel\Auth\AuthProviderInterface; use Neos\EventStore\EventStoreInterface; use Psr\Clock\ClockInterface; use Symfony\Component\Serializer\Serializer; @@ -54,7 +54,7 @@ public function __construct( Serializer $propertySerializer, ProjectionsAndCatchUpHooksFactory $projectionsAndCatchUpHooksFactory, private readonly ProjectionCatchUpTriggerInterface $projectionCatchUpTrigger, - private readonly UserIdProviderInterface $userIdProvider, + private readonly AuthProviderInterface $authProvider, private readonly ClockInterface $clock, ) { $contentDimensionZookeeper = new ContentDimensionZookeeper($contentDimensionSource); @@ -70,7 +70,8 @@ public function __construct( $contentDimensionSource, $contentDimensionZookeeper, $interDimensionalVariationGraph, - new PropertyConverter($propertySerializer) + new PropertyConverter($propertySerializer), + $this->authProvider, ); $this->projectionsAndCatchUpHooks = $projectionsAndCatchUpHooksFactory->build($this->projectionFactoryDependencies); } @@ -99,7 +100,7 @@ public function getOrBuild(): ContentRepository $this->projectionFactoryDependencies->nodeTypeManager, $this->projectionFactoryDependencies->interDimensionalVariationGraph, $this->projectionFactoryDependencies->contentDimensionSource, - $this->userIdProvider, + $this->authProvider, $this->clock, ); } diff --git a/Neos.ContentRepository.Core/Classes/Factory/ProjectionFactoryDependencies.php b/Neos.ContentRepository.Core/Classes/Factory/ProjectionFactoryDependencies.php index 9bb2f0cc31f..36b614ba623 100644 --- a/Neos.ContentRepository.Core/Classes/Factory/ProjectionFactoryDependencies.php +++ b/Neos.ContentRepository.Core/Classes/Factory/ProjectionFactoryDependencies.php @@ -20,6 +20,7 @@ use Neos\ContentRepository\Core\EventStore\EventNormalizer; use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; +use Neos\ContentRepository\Core\SharedModel\Auth\AuthProviderInterface; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\EventStore\EventStoreInterface; @@ -37,6 +38,7 @@ public function __construct( public ContentDimensionZookeeper $contentDimensionZookeeper, public InterDimensionalVariationGraph $interDimensionalVariationGraph, public PropertyConverter $propertyConverter, + public AuthProviderInterface $authProvider, ) { } } diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Auth/AuthProviderInterface.php b/Neos.ContentRepository.Core/Classes/SharedModel/Auth/AuthProviderInterface.php new file mode 100644 index 00000000000..b2d03733050 --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Auth/AuthProviderInterface.php @@ -0,0 +1,20 @@ +userId; + } + + public function getWorkspacePrivilege(WorkspaceName $workspaceName, WorkspacePrivilegeType $privilegeType): Privilege + { + return Privilege::granted(); + } + + public function getCommandPrivilege(CommandInterface $command): Privilege + { + return Privilege::granted(); + } +} diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/User/UserId.php b/Neos.ContentRepository.Core/Classes/SharedModel/Auth/UserId.php similarity index 96% rename from Neos.ContentRepository.Core/Classes/SharedModel/User/UserId.php rename to Neos.ContentRepository.Core/Classes/SharedModel/Auth/UserId.php index 86f78e31a21..80228a031d0 100644 --- a/Neos.ContentRepository.Core/Classes/SharedModel/User/UserId.php +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Auth/UserId.php @@ -12,7 +12,7 @@ declare(strict_types=1); -namespace Neos\ContentRepository\Core\SharedModel\User; +namespace Neos\ContentRepository\Core\SharedModel\Auth; use Neos\ContentRepository\Core\SharedModel\Id\UuidFactory; diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Auth/WorkspacePrivilegeType.php b/Neos.ContentRepository.Core/Classes/SharedModel/Auth/WorkspacePrivilegeType.php new file mode 100644 index 00000000000..c08d6aa4d46 --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Auth/WorkspacePrivilegeType.php @@ -0,0 +1,23 @@ +userId; - } -} diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/User/UserIdProviderInterface.php b/Neos.ContentRepository.Core/Classes/SharedModel/User/UserIdProviderInterface.php deleted file mode 100644 index 8530a34e30d..00000000000 --- a/Neos.ContentRepository.Core/Classes/SharedModel/User/UserIdProviderInterface.php +++ /dev/null @@ -1,13 +0,0 @@ -buildPropertySerializer($contentRepositoryId, $contentRepositorySettings), $this->buildProjectionsFactory($contentRepositoryId, $contentRepositorySettings), $this->buildProjectionCatchUpTrigger($contentRepositoryId, $contentRepositorySettings), - $this->buildUserIdProvider($contentRepositoryId, $contentRepositorySettings), + $this->buildAuthProvider($contentRepositoryId, $contentRepositorySettings), $clock ); } catch (\Exception $exception) { @@ -275,14 +275,14 @@ private function buildProjectionCatchUpTrigger(ContentRepositoryId $contentRepos } /** @param array $contentRepositorySettings */ - private function buildUserIdProvider(ContentRepositoryId $contentRepositoryId, array $contentRepositorySettings): UserIdProviderInterface + private function buildAuthProvider(ContentRepositoryId $contentRepositoryId, array $contentRepositorySettings): AuthProviderInterface { - isset($contentRepositorySettings['userIdProvider']['factoryObjectName']) || throw InvalidConfigurationException::fromMessage('Content repository "%s" does not have userIdProvider.factoryObjectName configured.', $contentRepositoryId->value); - $userIdProviderFactory = $this->objectManager->get($contentRepositorySettings['userIdProvider']['factoryObjectName']); - if (!$userIdProviderFactory instanceof UserIdProviderFactoryInterface) { - throw InvalidConfigurationException::fromMessage('userIdProvider.factoryObjectName for content repository "%s" is not an instance of %s but %s.', $contentRepositoryId->value, UserIdProviderFactoryInterface::class, get_debug_type($userIdProviderFactory)); + isset($contentRepositorySettings['authProvider']['factoryObjectName']) || throw InvalidConfigurationException::fromMessage('Content repository "%s" does not have authProvider.factoryObjectName configured.', $contentRepositoryId->value); + $authProviderFactory = $this->objectManager->get($contentRepositorySettings['authProvider']['factoryObjectName']); + if (!$authProviderFactory instanceof AuthProviderFactoryInterface) { + throw InvalidConfigurationException::fromMessage('authProvider.factoryObjectName for content repository "%s" is not an instance of %s but %s.', $contentRepositoryId->value, AuthProviderFactoryInterface::class, get_debug_type($authProviderFactory)); } - return $userIdProviderFactory->build($contentRepositoryId, $contentRepositorySettings['userIdProvider']['options'] ?? []); + return $authProviderFactory->build($contentRepositoryId, $contentRepositorySettings['authProvider']['options'] ?? []); } /** @param array $contentRepositorySettings */ diff --git a/Neos.ContentRepositoryRegistry/Classes/Factory/UserIdProvider/UserIdProviderFactoryInterface.php b/Neos.ContentRepositoryRegistry/Classes/Factory/AuthProvider/AuthProviderFactoryInterface.php similarity index 52% rename from Neos.ContentRepositoryRegistry/Classes/Factory/UserIdProvider/UserIdProviderFactoryInterface.php rename to Neos.ContentRepositoryRegistry/Classes/Factory/AuthProvider/AuthProviderFactoryInterface.php index a6145c7e8dc..9aebd57e688 100644 --- a/Neos.ContentRepositoryRegistry/Classes/Factory/UserIdProvider/UserIdProviderFactoryInterface.php +++ b/Neos.ContentRepositoryRegistry/Classes/Factory/AuthProvider/AuthProviderFactoryInterface.php @@ -1,15 +1,17 @@ $options */ - public function build(ContentRepositoryId $contentRepositoryId, array $options): UserIdProviderInterface; + public function build(ContentRepositoryId $contentRepositoryId, array $options): AuthProviderInterface; } diff --git a/Neos.ContentRepositoryRegistry/Classes/Factory/AuthProvider/StaticAuthProviderFactory.php b/Neos.ContentRepositoryRegistry/Classes/Factory/AuthProvider/StaticAuthProviderFactory.php new file mode 100644 index 00000000000..4cc8b9a7ebd --- /dev/null +++ b/Neos.ContentRepositoryRegistry/Classes/Factory/AuthProvider/StaticAuthProviderFactory.php @@ -0,0 +1,20 @@ + $options */ + public function build(ContentRepositoryId $contentRepositoryId, array $options): AuthProviderInterface + { + return new StaticAuthProvider(UserId::forSystemUser()); + } +} diff --git a/Neos.ContentRepositoryRegistry/Classes/Factory/UserIdProvider/StaticUserIdProviderFactory.php b/Neos.ContentRepositoryRegistry/Classes/Factory/UserIdProvider/StaticUserIdProviderFactory.php deleted file mode 100644 index 563bc6b19a9..00000000000 --- a/Neos.ContentRepositoryRegistry/Classes/Factory/UserIdProvider/StaticUserIdProviderFactory.php +++ /dev/null @@ -1,20 +0,0 @@ - $options */ - public function build(ContentRepositoryId $contentRepositoryId, array $options): UserIdProviderInterface - { - return new StaticUserIdProvider(UserId::forSystemUser()); - } -} diff --git a/Neos.ContentRepositoryRegistry/Configuration/Settings.yaml b/Neos.ContentRepositoryRegistry/Configuration/Settings.yaml index 44e28699641..460706caaf8 100644 --- a/Neos.ContentRepositoryRegistry/Configuration/Settings.yaml +++ b/Neos.ContentRepositoryRegistry/Configuration/Settings.yaml @@ -34,8 +34,8 @@ Neos: projectionCatchUpTrigger: factoryObjectName: Neos\ContentRepositoryRegistry\Factory\ProjectionCatchUpTrigger\SubprocessProjectionCatchUpTriggerFactory - userIdProvider: - factoryObjectName: Neos\ContentRepositoryRegistry\Factory\UserIdProvider\StaticUserIdProviderFactory + authProvider: + factoryObjectName: Neos\ContentRepositoryRegistry\Factory\AuthProvider\StaticAuthProviderFactory clock: factoryObjectName: Neos\ContentRepositoryRegistry\Factory\Clock\SystemClockFactory diff --git a/Neos.Neos/Classes/ContentRepositoryAuthProvider/ContentRepositoryAuthProvider.php b/Neos.Neos/Classes/ContentRepositoryAuthProvider/ContentRepositoryAuthProvider.php new file mode 100644 index 00000000000..b154f482211 --- /dev/null +++ b/Neos.Neos/Classes/ContentRepositoryAuthProvider/ContentRepositoryAuthProvider.php @@ -0,0 +1,147 @@ +userService->getCurrentUser(); + if ($user === null) { + return UserId::forSystemUser(); + } + return UserId::fromString($user->getId()->value); + } + + public function getWorkspacePrivilege(WorkspaceName $workspaceName, WorkspacePrivilegeType $privilegeType): Privilege + { + if ($this->securityContext->areAuthorizationChecksDisabled()) { + return Privilege::granted(); + } + $workspacePermissions = $this->getWorkspacePermissionsForAuthenticatedUser($workspaceName); + if ($workspacePermissions === null) { + return Privilege::denied('No user is authenticated'); + } + return match ($privilegeType) { + WorkspacePrivilegeType::READ_NODES => $workspacePermissions->read ? Privilege::granted() : Privilege::denied(sprintf('User has no read permission for workspace "%s"', $workspaceName->value)), + }; + } + + public function getCommandPrivilege(CommandInterface $command): Privilege + { + if ($this->securityContext->areAuthorizationChecksDisabled()) { + return Privilege::granted(); + } + if ($command instanceof CreateWorkspace) { + $baseWorkspacePermissions = $this->getWorkspacePermissionsForAuthenticatedUser($command->baseWorkspaceName); + if ($baseWorkspacePermissions === null || !$baseWorkspacePermissions->write) { + return Privilege::denied(sprintf('no write permissions on base workspace "%s"', $command->baseWorkspaceName->value)); + } + return Privilege::granted(); + } + list($privilege, $workspaceName) = match ($command::class) { + AddDimensionShineThrough::class, + ChangeNodeAggregateName::class, + ChangeNodeAggregateType::class, + CopyNodesRecursively::class, + CreateNodeAggregateWithNode::class, + CreateNodeAggregateWithNodeAndSerializedProperties::class, + CreateNodeVariant::class, + CreateRootNodeAggregateWithNode::class, + DisableNodeAggregate::class, + DiscardIndividualNodesFromWorkspace::class, + DiscardWorkspace::class, + EnableNodeAggregate::class, + MoveDimensionSpacePoint::class, + MoveNodeAggregate::class, + PublishIndividualNodesFromWorkspace::class, + PublishWorkspace::class, + RebaseWorkspace::class, + RemoveNodeAggregate::class, + SetNodeProperties::class, + SetNodeReferences::class, + SetSerializedNodeProperties::class, + SetSerializedNodeReferences::class, + TagSubtree::class, + UntagSubtree::class, + UpdateRootNodeAggregateDimensions::class => ['write', $command->workspaceName], + ChangeBaseWorkspace::class, + CreateRootWorkspace::class, + CreateWorkspace::class, + DeleteWorkspace::class => ['manage', $command->workspaceName], + default => [null, null], + }; + if ($privilege === null) { + return Privilege::granted(); + } + $workspacePermissions = $this->getWorkspacePermissionsForAuthenticatedUser($workspaceName); + if ($workspacePermissions === null) { + return Privilege::denied(sprintf('No user is authenticated to %s workspace "%s" because no user is authenticated', $privilege, $workspaceName->value)); + } + $privilegeGranted = $privilege === 'write' ? $workspacePermissions->write : $workspacePermissions->manage; + return $privilegeGranted ? Privilege::granted() : Privilege::denied(sprintf('User has no %s permission for workspace "%s"', $privilege, $workspaceName->value)); + } + + private function getWorkspacePermissionsForAuthenticatedUser(WorkspaceName $workspaceName): ?WorkspacePermissions + { + $user = $this->userService->getCurrentUser(); + if ($user === null) { + return null; + } + return $this->workspaceService->getWorkspacePermissionsForUser($this->contentRepositoryId, $workspaceName, $user); + } +} diff --git a/Neos.Neos/Classes/ContentRepositoryAuthProvider/ContentRepositoryAuthProviderFactory.php b/Neos.Neos/Classes/ContentRepositoryAuthProvider/ContentRepositoryAuthProviderFactory.php new file mode 100644 index 00000000000..63f84ae439e --- /dev/null +++ b/Neos.Neos/Classes/ContentRepositoryAuthProvider/ContentRepositoryAuthProviderFactory.php @@ -0,0 +1,35 @@ + $options + */ + public function build(ContentRepositoryId $contentRepositoryId, array $options): ContentRepositoryAuthProvider + { + return new ContentRepositoryAuthProvider($contentRepositoryId, $this->userService, $this->workspaceService); + } +} diff --git a/Neos.Neos/Classes/Domain/Service/WorkspaceService.php b/Neos.Neos/Classes/Domain/Service/WorkspaceService.php index 0ada7877998..e64c5acba84 100644 --- a/Neos.Neos/Classes/Domain/Service/WorkspaceService.php +++ b/Neos.Neos/Classes/Domain/Service/WorkspaceService.php @@ -27,6 +27,7 @@ use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; +use Neos\Flow\Security\Context as SecurityContext; use Neos\Flow\Security\Exception\NoSuchRoleException; use Neos\Neos\Domain\Model\User; use Neos\Neos\Domain\Model\UserId; @@ -44,6 +45,8 @@ /** * Central authority to interact with Content Repository Workspaces within Neos * + * TODO evaluate permissions for workspace changes + * * @api */ #[Flow\Scope('singleton')] @@ -56,6 +59,7 @@ public function __construct( private readonly ContentRepositoryRegistry $contentRepositoryRegistry, private readonly UserService $userService, private readonly Connection $dbal, + private readonly SecurityContext $securityContext, ) { } @@ -83,6 +87,7 @@ public function getWorkspaceMetadata(ContentRepositoryId $contentRepositoryId, W */ public function setWorkspaceTitle(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName, WorkspaceTitle $newWorkspaceTitle): void { + // TODO check workspace permissions -> $this->getWorkspacePermissionsForUser($contentRepositoryId, $workspaceName, $this->userService->getCurrentUser()); $this->updateWorkspaceMetadata($contentRepositoryId, $workspaceName, [ 'title' => $newWorkspaceTitle->value, ]); @@ -173,14 +178,14 @@ public function createPersonalWorkspaceForUserIfMissing(ContentRepositoryId $con return; } $workspaceName = $this->getUniqueWorkspaceName($contentRepositoryId, $user->getLabel()); - $this->createPersonalWorkspace( + $this->securityContext->withoutAuthorizationChecks(fn () => $this->createPersonalWorkspace( $contentRepositoryId, $workspaceName, WorkspaceTitle::fromString($user->getLabel()), WorkspaceDescription::empty(), WorkspaceName::forLive(), $user->getId(), - ); + )); } /** diff --git a/Neos.Neos/Classes/Service/Controller/AbstractServiceController.php b/Neos.Neos/Classes/Service/Controller/AbstractServiceController.php index a212f1ac725..aca0d7a9327 100644 --- a/Neos.Neos/Classes/Service/Controller/AbstractServiceController.php +++ b/Neos.Neos/Classes/Service/Controller/AbstractServiceController.php @@ -15,7 +15,6 @@ namespace Neos\Neos\Service\Controller; use GuzzleHttp\Psr7\Response; -use Neos\ContentRepository\Core\SharedModel\User\UserId; use Neos\Flow\Annotations as Flow; use Neos\Flow\Exception as FlowException; use Neos\Flow\Log\ThrowableStorageInterface; @@ -157,13 +156,4 @@ protected function convertException(\Throwable $exception): array } return $exceptionData; } - - protected function getCurrentUserIdentifier(): ?UserId - { - $user = $this->domainUserService->getCurrentUser(); - - return $user - ? UserId::fromString($this->persistenceManager->getIdentifierByObject($user)) - : null; - } } diff --git a/Neos.Neos/Classes/UserIdProvider/UserIdProvider.php b/Neos.Neos/Classes/UserIdProvider/UserIdProvider.php deleted file mode 100644 index b81c97e8c5a..00000000000 --- a/Neos.Neos/Classes/UserIdProvider/UserIdProvider.php +++ /dev/null @@ -1,29 +0,0 @@ -userService->getCurrentUser(); - if ($user === null) { - return UserId::forSystemUser(); - } - return UserId::fromString($user->getId()->value); - } -} diff --git a/Neos.Neos/Classes/UserIdProvider/UserIdProviderFactory.php b/Neos.Neos/Classes/UserIdProvider/UserIdProviderFactory.php deleted file mode 100644 index 388dc6f19f3..00000000000 --- a/Neos.Neos/Classes/UserIdProvider/UserIdProviderFactory.php +++ /dev/null @@ -1,34 +0,0 @@ - $options - */ - public function build(ContentRepositoryId $contentRepositoryId, array $options): UserIdProviderInterface - { - return new UserIdProvider($this->userService); - } -} diff --git a/Neos.Neos/Configuration/Settings.ContentRepositoryRegistry.yaml b/Neos.Neos/Configuration/Settings.ContentRepositoryRegistry.yaml index a3ae6ab53c0..c088a067cbe 100644 --- a/Neos.Neos/Configuration/Settings.ContentRepositoryRegistry.yaml +++ b/Neos.Neos/Configuration/Settings.ContentRepositoryRegistry.yaml @@ -3,8 +3,8 @@ Neos: presets: 'default': - userIdProvider: - factoryObjectName: Neos\Neos\UserIdProvider\UserIdProviderFactory + authProvider: + factoryObjectName: Neos\Neos\ContentRepositoryAuthProvider\ContentRepositoryAuthProviderFactory projections: 'Neos.Neos:DocumentUriPathProjection':