Skip to content

Commit

Permalink
FEATURE: Content Repository Privileges
Browse files Browse the repository at this point in the history
Related: #3732
  • Loading branch information
bwaidelich committed Oct 16, 2024
1 parent 2a8b490 commit 49aa8bb
Show file tree
Hide file tree
Showing 30 changed files with 426 additions and 196 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public function build(
),
$tableNames,
$dimensionSpacePointsRepository,
new ContentRepositoryReadModel($contentRepositoryReadModelAdapter)
new ContentRepositoryReadModel($contentRepositoryReadModelAdapter),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<string,mixed> $options
*/
public function build(ContentRepositoryId $contentRepositoryId, array $options): UserIdProviderInterface
public function build(ContentRepositoryId $contentRepositoryId, array $options): AuthProviderInterface
{
return new FakeUserIdProvider();
return new FakeAuthProvider();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

/**
Expand Down
19 changes: 15 additions & 4 deletions Neos.ContentRepository.Core/Classes/ContentRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
}

/**
Expand All @@ -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.
Expand Down Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -35,7 +38,7 @@
final class ContentRepositoryReadModel implements ProjectionStateInterface
{
public function __construct(
private readonly ContentRepositoryReadModelAdapterInterface $adapter
private readonly ContentRepositoryReadModelAdapterInterface $adapter,
) {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -70,7 +70,8 @@ public function __construct(
$contentDimensionSource,
$contentDimensionZookeeper,
$interDimensionalVariationGraph,
new PropertyConverter($propertySerializer)
new PropertyConverter($propertySerializer),
$this->authProvider,
);
$this->projectionsAndCatchUpHooks = $projectionsAndCatchUpHooksFactory->build($this->projectionFactoryDependencies);
}
Expand Down Expand Up @@ -99,7 +100,7 @@ public function getOrBuild(): ContentRepository
$this->projectionFactoryDependencies->nodeTypeManager,
$this->projectionFactoryDependencies->interDimensionalVariationGraph,
$this->projectionFactoryDependencies->contentDimensionSource,
$this->userIdProvider,
$this->authProvider,
$this->clock,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -37,6 +38,7 @@ public function __construct(
public ContentDimensionZookeeper $contentDimensionZookeeper,
public InterDimensionalVariationGraph $interDimensionalVariationGraph,
public PropertyConverter $propertyConverter,
public AuthProviderInterface $authProvider,
) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Neos\ContentRepository\Core\SharedModel\Auth;

use Neos\ContentRepository\Core\CommandHandler\CommandInterface;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;

/**
* @internal except for CR factory implementations
*/
interface AuthProviderInterface
{
public function getUserId(): UserId;

public function getWorkspacePrivilege(WorkspaceName $workspaceName, WorkspacePrivilegeType $privilegeType): Privilege;

public function getCommandPrivilege(CommandInterface $command): Privilege;
}
38 changes: 38 additions & 0 deletions Neos.ContentRepository.Core/Classes/SharedModel/Auth/Privilege.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

/*
* This file is part of the Neos.ContentRepository package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Neos\ContentRepository\Core\SharedModel\Auth;

/**
* A privilege that is returned by the {@see AuthProviderInterface}
* @api
*/
final readonly class Privilege
{
private function __construct(
public bool $granted,
public ?string $message,
) {
}

public static function granted(): self
{
return new self(true, null);
}

public static function denied(string $message): self
{
return new self(false, $message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Neos\ContentRepository\Core\SharedModel\Auth;

use Neos\ContentRepository\Core\CommandHandler\CommandInterface;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;

/**
* A simple auth provider that just statically returns the same user id that it was given upon construction time and grants all privileges
*
* @api
*/
final class StaticAuthProvider implements AuthProviderInterface
{
public function __construct(
private readonly UserId $userId,
) {
}

public function getUserId(): UserId
{
return $this->userId;
}

public function getWorkspacePrivilege(WorkspaceName $workspaceName, WorkspacePrivilegeType $privilegeType): Privilege
{
return Privilege::granted();
}

public function getCommandPrivilege(CommandInterface $command): Privilege
{
return Privilege::granted();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

/*
* This file is part of the Neos.ContentRepository package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Neos\ContentRepository\Core\SharedModel\Auth;

/**
* @api
*/
enum WorkspacePrivilegeType
{
case READ_NODES;
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@
use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints;
use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId;
use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId;
use Neos\ContentRepository\Core\SharedModel\User\UserId;
use Neos\ContentRepository\Core\SharedModel\Auth\UserId;
use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;
use Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap\Helpers\FakeClock;
use Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap\Helpers\FakeUserIdProvider;
use Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap\Helpers\FakeAuthProvider;

/**
* The node creation trait for behavioral tests
Expand Down Expand Up @@ -74,7 +74,7 @@ abstract protected function getContentRepository(ContentRepositoryId $id): Conte
*/
public function iAmUserIdentifiedBy(string $userId): void
{
FakeUserIdProvider::setUserId(UserId::fromString($userId));
FakeAuthProvider::setUserId(UserId::fromString($userId));
}

/**
Expand Down
Loading

0 comments on commit 49aa8bb

Please sign in to comment.