diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Exception/WorkspaceDoesNotExist.php b/Neos.ContentRepository.Core/Classes/SharedModel/Exception/WorkspaceDoesNotExist.php
index 3def569da87..26004ba393b 100644
--- a/Neos.ContentRepository.Core/Classes/SharedModel/Exception/WorkspaceDoesNotExist.php
+++ b/Neos.ContentRepository.Core/Classes/SharedModel/Exception/WorkspaceDoesNotExist.php
@@ -14,18 +14,28 @@
namespace Neos\ContentRepository\Core\SharedModel\Exception;
+use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;
/**
- * @api because exception is thrown during invariant checks on command execution
+ * @api because exception is thrown during invariant checks on command execution or when attempting to query a non-existing workspace
*/
final class WorkspaceDoesNotExist extends \DomainException
{
public static function butWasSupposedTo(WorkspaceName $name): self
{
return new self(sprintf(
- 'The source workspace %s does not exist',
+ 'The workspace "%s" does not exist',
$name->value
), 1513924741);
}
+
+ public static function butWasSupposedToInContentRepository(WorkspaceName $name, ContentRepositoryId $contentRepositoryId): self
+ {
+ return new self(sprintf(
+ 'The workspace "%s" does not exist in content repository "%s"',
+ $name->value,
+ $contentRepositoryId->value
+ ), 1733737361);
+ }
}
diff --git a/Neos.ContentRepositoryRegistry/Classes/Service/EventMigrationService.php b/Neos.ContentRepositoryRegistry/Classes/Service/EventMigrationService.php
index 80d849bb17c..3348f1b73b3 100644
--- a/Neos.ContentRepositoryRegistry/Classes/Service/EventMigrationService.php
+++ b/Neos.ContentRepositoryRegistry/Classes/Service/EventMigrationService.php
@@ -716,7 +716,7 @@ public function migrateWorkspaceMetadataToWorkspaceService(\Closure $outputFn):
];
$roleAssignments[] = [
'subject_type' => WorkspaceRoleSubjectType::GROUP->value,
- 'subject' => 'Neos.Neos:Everybody',
+ 'subject' => 'Neos.Flow:Everybody',
'role' => WorkspaceRole::VIEWER->value,
];
} elseif ($isInternalWorkspace) {
diff --git a/Neos.Neos/Classes/Command/WorkspaceCommandController.php b/Neos.Neos/Classes/Command/WorkspaceCommandController.php
index 876e5b8d199..ac290f02685 100644
--- a/Neos.Neos/Classes/Command/WorkspaceCommandController.php
+++ b/Neos.Neos/Classes/Command/WorkspaceCommandController.php
@@ -15,7 +15,6 @@
namespace Neos\Neos\Command;
use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Exception\WorkspaceAlreadyExists;
-use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\DeleteWorkspace;
use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Dto\RebaseErrorHandlingStrategy;
use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception\WorkspaceRebaseFailed;
use Neos\ContentRepository\Core\Service\WorkspaceMaintenanceServiceFactory;
@@ -31,6 +30,7 @@
use Neos\Neos\Domain\Model\WorkspaceDescription;
use Neos\Neos\Domain\Model\WorkspaceRole;
use Neos\Neos\Domain\Model\WorkspaceRoleAssignment;
+use Neos\Neos\Domain\Model\WorkspaceRoleAssignments;
use Neos\Neos\Domain\Model\WorkspaceRoleSubject;
use Neos\Neos\Domain\Model\WorkspaceRoleSubjectType;
use Neos\Neos\Domain\Model\WorkspaceTitle;
@@ -148,7 +148,8 @@ public function createRootCommand(string $name, string $contentRepository = 'def
$contentRepositoryId,
$workspaceName,
WorkspaceTitle::fromString($title ?? $name),
- WorkspaceDescription::fromString($description ?? '')
+ WorkspaceDescription::fromString($description ?? ''),
+ WorkspaceRoleAssignments::createEmpty()
);
$this->outputLine('Created root workspace "%s" in content repository "%s"', [$workspaceName->value, $contentRepositoryId->value]);
}
@@ -206,6 +207,7 @@ public function createSharedCommand(string $workspace, string $baseWorkspace = '
WorkspaceTitle::fromString($title ?? $workspaceName->value),
WorkspaceDescription::fromString($description ?? ''),
WorkspaceName::fromString($baseWorkspace),
+ WorkspaceRoleAssignments::createEmpty()
);
$this->outputLine('Created shared workspace "%s"', [$workspaceName->value]);
}
@@ -400,11 +402,7 @@ public function deleteCommand(string $workspace, bool $force = false, string $co
$this->workspacePublishingService->discardAllWorkspaceChanges($contentRepositoryId, $workspaceName);
}
- $contentRepositoryInstance->handle(
- DeleteWorkspace::create(
- $workspaceName
- )
- );
+ $this->workspaceService->deleteWorkspace($contentRepositoryId, $workspaceName);
$this->outputLine('Deleted workspace "%s"', [$workspaceName->value]);
}
diff --git a/Neos.Neos/Classes/Domain/Import/LiveWorkspaceCreationProcessor.php b/Neos.Neos/Classes/Domain/Import/LiveWorkspaceCreationProcessor.php
index 788dad93734..3e0b582d438 100644
--- a/Neos.Neos/Classes/Domain/Import/LiveWorkspaceCreationProcessor.php
+++ b/Neos.Neos/Classes/Domain/Import/LiveWorkspaceCreationProcessor.php
@@ -20,8 +20,7 @@
use Neos\ContentRepository\Export\ProcessorInterface;
use Neos\ContentRepository\Export\Severity;
use Neos\Neos\Domain\Model\WorkspaceDescription;
-use Neos\Neos\Domain\Model\WorkspaceRole;
-use Neos\Neos\Domain\Model\WorkspaceRoleAssignment;
+use Neos\Neos\Domain\Model\WorkspaceRoleAssignments;
use Neos\Neos\Domain\Model\WorkspaceTitle;
use Neos\Neos\Domain\Service\WorkspaceService;
@@ -44,7 +43,12 @@ public function run(ProcessingContext $context): void
$context->dispatch(Severity::NOTICE, 'Workspace already exists, skipping');
return;
}
- $this->workspaceService->createRootWorkspace($this->contentRepository->id, WorkspaceName::forLive(), WorkspaceTitle::fromString('Live workspace'), WorkspaceDescription::fromString(''));
- $this->workspaceService->assignWorkspaceRole($this->contentRepository->id, WorkspaceName::forLive(), WorkspaceRoleAssignment::createForGroup('Neos.Neos:LivePublisher', WorkspaceRole::COLLABORATOR));
+ $this->workspaceService->createRootWorkspace(
+ $this->contentRepository->id,
+ WorkspaceName::forLive(),
+ WorkspaceTitle::fromString('Public live workspace'),
+ WorkspaceDescription::empty(),
+ WorkspaceRoleAssignments::createForLiveWorkspace()
+ );
}
}
diff --git a/Neos.Neos/Classes/Domain/Model/WorkspaceRoleAssignment.php b/Neos.Neos/Classes/Domain/Model/WorkspaceRoleAssignment.php
index f8206c8d2ce..418e7134e22 100644
--- a/Neos.Neos/Classes/Domain/Model/WorkspaceRoleAssignment.php
+++ b/Neos.Neos/Classes/Domain/Model/WorkspaceRoleAssignment.php
@@ -42,4 +42,10 @@ public static function createForGroup(string $flowRoleIdentifier, WorkspaceRole
$role
);
}
+
+ public function equals(WorkspaceRoleAssignment $other): bool
+ {
+ return $this->subject->equals($other->subject)
+ && $this->role === $other->role;
+ }
}
diff --git a/Neos.Neos/Classes/Domain/Model/WorkspaceRoleAssignments.php b/Neos.Neos/Classes/Domain/Model/WorkspaceRoleAssignments.php
index a63eb23b899..87b33bc0d3b 100644
--- a/Neos.Neos/Classes/Domain/Model/WorkspaceRoleAssignments.php
+++ b/Neos.Neos/Classes/Domain/Model/WorkspaceRoleAssignments.php
@@ -5,6 +5,7 @@
namespace Neos\Neos\Domain\Model;
use Neos\Flow\Annotations as Flow;
+use Neos\Neos\Domain\Service\WorkspaceService;
/**
* A set of {@see WorkspaceRoleAssignment} instances
@@ -25,6 +26,16 @@ private function __construct(WorkspaceRoleAssignment ...$assignments)
$this->assignments = $assignments;
}
+ public static function createEmpty(): self
+ {
+ return new self();
+ }
+
+ public static function create(WorkspaceRoleAssignment ...$assignments): self
+ {
+ return new self(...$assignments);
+ }
+
/**
* @param array $assignments
*/
@@ -33,6 +44,44 @@ public static function fromArray(array $assignments): self
return new self(...$assignments);
}
+ /**
+ * Default role assignment to be specified at creation via {@see WorkspaceService::createRootWorkspace()}
+ *
+ * Users with the role "Neos.Neos:LivePublisher" are collaborators and everybody can read.
+ */
+ public static function createForLiveWorkspace(): self
+ {
+ return new self(
+ WorkspaceRoleAssignment::createForGroup(
+ 'Neos.Neos:LivePublisher',
+ WorkspaceRole::COLLABORATOR
+ ),
+ WorkspaceRoleAssignment::createForGroup(
+ 'Neos.Flow:Everybody',
+ WorkspaceRole::VIEWER
+ )
+ );
+ }
+
+ /**
+ * Default role assignment to be specified at creation via {@see WorkspaceService::createSharedWorkspace()}
+ *
+ * Users with the role "Neos.Neos:AbstractEditor" are collaborators and the specified user is manager
+ */
+ public static function createForSharedWorkspace(UserId $userId): self
+ {
+ return new self(
+ WorkspaceRoleAssignment::createForUser(
+ $userId,
+ WorkspaceRole::MANAGER,
+ ),
+ WorkspaceRoleAssignment::createForGroup(
+ 'Neos.Neos:AbstractEditor',
+ WorkspaceRole::COLLABORATOR,
+ )
+ );
+ }
+
public function isEmpty(): bool
{
return $this->assignments === [];
@@ -47,4 +96,19 @@ public function count(): int
{
return count($this->assignments);
}
+
+ public function contains(WorkspaceRoleAssignment $assignment): bool
+ {
+ foreach ($this->assignments as $existingAssignment) {
+ if ($existingAssignment->equals($assignment)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public function withAssignment(WorkspaceRoleAssignment $assignment): self
+ {
+ return new self(...[...$this->assignments, $assignment]);
+ }
}
diff --git a/Neos.Neos/Classes/Domain/Repository/WorkspaceMetadataAndRoleRepository.php b/Neos.Neos/Classes/Domain/Repository/WorkspaceMetadataAndRoleRepository.php
index cf320af71f5..c5ce393682a 100644
--- a/Neos.Neos/Classes/Domain/Repository/WorkspaceMetadataAndRoleRepository.php
+++ b/Neos.Neos/Classes/Domain/Repository/WorkspaceMetadataAndRoleRepository.php
@@ -178,6 +178,40 @@ public function getMostPrivilegedWorkspaceRoleForSubjects(ContentRepositoryId $c
return WorkspaceRole::from($role);
}
+ public function deleteWorkspaceMetadata(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName): void
+ {
+ try {
+ $this->dbal->delete(self::TABLE_NAME_WORKSPACE_METADATA, [
+ 'content_repository_id' => $contentRepositoryId->value,
+ 'workspace_name' => $workspaceName->value,
+ ]);
+ } catch (DbalException $e) {
+ throw new \RuntimeException(sprintf(
+ 'Failed to delete metadata for workspace "%s" (Content Repository "%s"): %s',
+ $workspaceName->value,
+ $contentRepositoryId->value,
+ $e->getMessage()
+ ), 1726821159, $e);
+ }
+ }
+
+ public function deleteWorkspaceRoleAssignments(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName): void
+ {
+ try {
+ $this->dbal->delete(self::TABLE_NAME_WORKSPACE_ROLE, [
+ 'content_repository_id' => $contentRepositoryId->value,
+ 'workspace_name' => $workspaceName->value,
+ ]);
+ } catch (DbalException $e) {
+ throw new \RuntimeException(sprintf(
+ 'Failed to delete role assignments for workspace "%s" (Content Repository "%s"): %s',
+ $workspaceName->value,
+ $contentRepositoryId->value,
+ $e->getMessage()
+ ), 1726821159, $e);
+ }
+ }
+
/**
* Removes all workspace metadata records for the specified content repository id
*/
@@ -306,7 +340,7 @@ public function addWorkspaceMetadata(ContentRepositoryId $contentRepositoryId, W
}
}
- public function findPrimaryWorkspaceNameForUser(ContentRepositoryId $contentRepositoryId, UserId $userId): ?WorkspaceName
+ public function findWorkspaceNameByUser(ContentRepositoryId $contentRepositoryId, UserId $userId): ?WorkspaceName
{
$tableMetadata = self::TABLE_NAME_WORKSPACE_METADATA;
$query = <<dbal->transactional($fn);
+ }
}
diff --git a/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php b/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php
index 5842e6366db..09d64fefd46 100644
--- a/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php
+++ b/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php
@@ -33,6 +33,9 @@
use Neos\Neos\Domain\Exception\SiteNodeTypeIsInvalid;
use Neos\Neos\Domain\Model\Site;
use Neos\Neos\Domain\Model\SiteNodeName;
+use Neos\Neos\Domain\Model\WorkspaceDescription;
+use Neos\Neos\Domain\Model\WorkspaceRoleAssignments;
+use Neos\Neos\Domain\Model\WorkspaceTitle;
/**
* @internal FIXME refactor and incorporate into SiteService
@@ -89,7 +92,16 @@ public function removeSiteNode(SiteNodeName $siteNodeName): void
public function createSiteNodeIfNotExists(Site $site, string $nodeTypeName): void
{
- $this->workspaceService->createLiveWorkspaceIfMissing($this->contentRepository->id);
+ $liveWorkspace = $this->contentRepository->findWorkspaceByName(WorkspaceName::forLive());
+ if ($liveWorkspace === null) {
+ $this->workspaceService->createRootWorkspace(
+ $this->contentRepository->id,
+ WorkspaceName::forLive(),
+ WorkspaceTitle::fromString('Public live workspace'),
+ WorkspaceDescription::empty(),
+ WorkspaceRoleAssignments::createForLiveWorkspace()
+ );
+ }
$sitesNodeIdentifier = $this->getOrCreateRootNodeAggregate();
$siteNodeType = $this->nodeTypeManager->getNodeType($nodeTypeName);
diff --git a/Neos.Neos/Classes/Domain/Service/WorkspaceService.php b/Neos.Neos/Classes/Domain/Service/WorkspaceService.php
index b4021fae155..00ac32de62c 100644
--- a/Neos.Neos/Classes/Domain/Service/WorkspaceService.php
+++ b/Neos.Neos/Classes/Domain/Service/WorkspaceService.php
@@ -18,7 +18,9 @@
use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Command\CreateRootWorkspace;
use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Command\CreateWorkspace;
use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Exception\WorkspaceAlreadyExists;
+use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\DeleteWorkspace;
use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId;
+use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist;
use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId;
use Neos\ContentRepository\Core\SharedModel\Workspace\Workspace;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;
@@ -95,13 +97,11 @@ public function setWorkspaceDescription(ContentRepositoryId $contentRepositoryId
}
/**
- * Retrieve the currently active personal workspace for the specified $userId
- *
- * NOTE: Currently there can only ever be a single personal workspace per user. But this API already prepares support for multiple personal workspaces per user
+ * Retrieve the personal workspace for the specified user, if no workspace exist an exception is thrown.
*/
public function getPersonalWorkspaceForUser(ContentRepositoryId $contentRepositoryId, UserId $userId): Workspace
{
- $workspaceName = $this->metadataAndRoleRepository->findPrimaryWorkspaceNameForUser($contentRepositoryId, $userId);
+ $workspaceName = $this->metadataAndRoleRepository->findWorkspaceNameByUser($contentRepositoryId, $userId);
if ($workspaceName === null) {
throw new \RuntimeException(sprintf('No workspace is assigned to the user with id "%s")', $userId->value), 1718293801);
}
@@ -111,9 +111,19 @@ public function getPersonalWorkspaceForUser(ContentRepositoryId $contentReposito
/**
* Create a new root (aka base) workspace with the specified metadata
*
+ * To ensure that editors can publish to the live workspace and to allow everybody to view it an assignment like {@see WorkspaceRoleAssignments::createForLiveWorkspace} needs to be specified:
+ *
+ * $this->workspaceService->createRootWorkspace(
+ * $contentRepositoryId,
+ * WorkspaceName::forLive(),
+ * WorkspaceTitle::fromString('Public live workspace'),
+ * WorkspaceDescription::empty(),
+ * WorkspaceRoleAssignments::createForLiveWorkspace()
+ * );
+ *
* @throws WorkspaceAlreadyExists
*/
- public function createRootWorkspace(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName, WorkspaceTitle $title, WorkspaceDescription $description): void
+ public function createRootWorkspace(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName, WorkspaceTitle $title, WorkspaceDescription $description, WorkspaceRoleAssignments $assignments): void
{
$contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId);
$contentRepository->handle(
@@ -122,48 +132,74 @@ public function createRootWorkspace(ContentRepositoryId $contentRepositoryId, Wo
ContentStreamId::create()
)
);
- $this->metadataAndRoleRepository->addWorkspaceMetadata($contentRepositoryId, $workspaceName, $title, $description, WorkspaceClassification::ROOT, null);
- }
- /**
- * Create the "live" root workspace with the default role assignment (users with the role "Neos.Neos:LivePublisher" are collaborators)
- */
- public function createLiveWorkspaceIfMissing(ContentRepositoryId $contentRepositoryId): void
- {
- $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId);
- $workspaceName = WorkspaceName::forLive();
- $liveWorkspace = $contentRepository->findWorkspaceByName($workspaceName);
- if ($liveWorkspace !== null) {
- // live workspace already exists
- return;
- }
- $this->createRootWorkspace($contentRepositoryId, $workspaceName, WorkspaceTitle::fromString('Public live workspace'), WorkspaceDescription::empty());
- $this->metadataAndRoleRepository->assignWorkspaceRole($contentRepositoryId, $workspaceName, WorkspaceRoleAssignment::createForGroup('Neos.Neos:LivePublisher', WorkspaceRole::COLLABORATOR));
+ $this->metadataAndRoleRepository->transactional(function () use ($contentRepositoryId, $workspaceName, $title, $description, $assignments) {
+ $this->metadataAndRoleRepository->addWorkspaceMetadata($contentRepositoryId, $workspaceName, $title, $description, WorkspaceClassification::ROOT, ownerUserId: null);
+ foreach ($assignments as $assignment) {
+ $this->metadataAndRoleRepository->assignWorkspaceRole($contentRepositoryId, $workspaceName, $assignment);
+ }
+ });
}
/**
- * Create a new, personal, workspace for the specified user
+ * Create a new, personal, workspace for the specified user (fails if the user already owns a workspace)
*/
public function createPersonalWorkspace(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName, WorkspaceTitle $title, WorkspaceDescription $description, WorkspaceName $baseWorkspaceName, UserId $ownerId): void
{
- $this->createWorkspace($contentRepositoryId, $workspaceName, $title, $description, $baseWorkspaceName, $ownerId, WorkspaceClassification::PERSONAL);
+ $existingUserWorkspace = $this->metadataAndRoleRepository->findWorkspaceNameByUser($contentRepositoryId, $ownerId);
+ if ($existingUserWorkspace !== null) {
+ throw new \RuntimeException(sprintf('Failed to create personal workspace "%s" for user with id "%s", because the workspace "%s" is already assigned to the user', $workspaceName->value, $ownerId->value, $existingUserWorkspace->value), 1733754904);
+ }
+ $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId);
+ $contentRepository->handle(
+ CreateWorkspace::create(
+ $workspaceName,
+ $baseWorkspaceName,
+ ContentStreamId::create()
+ )
+ );
+ $this->metadataAndRoleRepository->addWorkspaceMetadata($contentRepositoryId, $workspaceName, $title, $description, WorkspaceClassification::PERSONAL, $ownerId);
}
/**
* Create a new, potentially shared, workspace
+ *
+ * To ensure that the user can manage the shared workspace and to enable collaborates an assignment like {@see WorkspaceRoleAssignments::createForSharedWorkspace} needs to be specified:
+ *
+ * $this->workspaceService->createWorkspace(
+ * ...,
+ * assignments: WorkspaceRoleAssignments::createForSharedWorkspace(
+ * $currentUser->getId()
+ * )
+ * );
+ *
+ * NOTE: By default - if no role assignments are specified - only administrators can manage workspaces without role assignments.
*/
- public function createSharedWorkspace(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName, WorkspaceTitle $title, WorkspaceDescription $description, WorkspaceName $baseWorkspaceName): void
+ public function createSharedWorkspace(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName, WorkspaceTitle $title, WorkspaceDescription $description, WorkspaceName $baseWorkspaceName, WorkspaceRoleAssignments $assignments): void
{
- $this->createWorkspace($contentRepositoryId, $workspaceName, $title, $description, $baseWorkspaceName, null, WorkspaceClassification::SHARED);
+ $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId);
+ $contentRepository->handle(
+ CreateWorkspace::create(
+ $workspaceName,
+ $baseWorkspaceName,
+ ContentStreamId::create()
+ )
+ );
+
+ $this->metadataAndRoleRepository->transactional(function () use ($contentRepositoryId, $workspaceName, $title, $description, $assignments) {
+ $this->metadataAndRoleRepository->addWorkspaceMetadata($contentRepositoryId, $workspaceName, $title, $description, WorkspaceClassification::SHARED, ownerUserId: null);
+ foreach ($assignments as $assignment) {
+ $this->metadataAndRoleRepository->assignWorkspaceRole($contentRepositoryId, $workspaceName, $assignment);
+ }
+ });
}
/**
* Create a new, personal, workspace for the specified user if none exists yet
- * @internal experimental api, until actually used by the Neos.Ui
*/
public function createPersonalWorkspaceForUserIfMissing(ContentRepositoryId $contentRepositoryId, User $user): void
{
- $existingWorkspaceName = $this->metadataAndRoleRepository->findPrimaryWorkspaceNameForUser($contentRepositoryId, $user->getId());
+ $existingWorkspaceName = $this->metadataAndRoleRepository->findWorkspaceNameByUser($contentRepositoryId, $user->getId());
if ($existingWorkspaceName !== null) {
$this->requireWorkspace($contentRepositoryId, $existingWorkspaceName);
return;
@@ -204,6 +240,24 @@ public function unassignWorkspaceRole(ContentRepositoryId $contentRepositoryId,
$this->metadataAndRoleRepository->unassignWorkspaceRole($contentRepositoryId, $workspaceName, $subject);
}
+ /**
+ * Deletes a content repository workspace and also all role assignments and metadata
+ */
+ public function deleteWorkspace(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName): void
+ {
+ $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId);
+ $this->requireWorkspace($contentRepositoryId, $workspaceName);
+
+ $contentRepository->handle(
+ DeleteWorkspace::create(
+ $workspaceName
+ )
+ );
+
+ $this->metadataAndRoleRepository->deleteWorkspaceMetadata($contentRepositoryId, $workspaceName);
+ $this->metadataAndRoleRepository->deleteWorkspaceRoleAssignments($contentRepositoryId, $workspaceName);
+ }
+
/**
* Get all role assignments for the specified workspace
*
@@ -211,6 +265,7 @@ public function unassignWorkspaceRole(ContentRepositoryId $contentRepositoryId,
*/
public function getWorkspaceRoleAssignments(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName): WorkspaceRoleAssignments
{
+ $this->requireWorkspace($contentRepositoryId, $workspaceName);
return $this->metadataAndRoleRepository->getWorkspaceRoleAssignments($contentRepositoryId, $workspaceName);
}
@@ -244,26 +299,16 @@ public function getUniqueWorkspaceName(ContentRepositoryId $contentRepositoryId,
// ------------------
- private function createWorkspace(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName, WorkspaceTitle $title, WorkspaceDescription $description, WorkspaceName $baseWorkspaceName, UserId|null $ownerId, WorkspaceClassification $classification): void
- {
- $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId);
- $contentRepository->handle(
- CreateWorkspace::create(
- $workspaceName,
- $baseWorkspaceName,
- ContentStreamId::create()
- )
- );
- $this->metadataAndRoleRepository->addWorkspaceMetadata($contentRepositoryId, $workspaceName, $title, $description, $classification, $ownerId);
- }
-
+ /**
+ * @throws WorkspaceDoesNotExist if the workspace does not exist
+ */
private function requireWorkspace(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName): Workspace
{
$workspace = $this->contentRepositoryRegistry
->get($contentRepositoryId)
->findWorkspaceByName($workspaceName);
if ($workspace === null) {
- throw new \RuntimeException(sprintf('Failed to find workspace with name "%s" for content repository "%s"', $workspaceName->value, $contentRepositoryId->value), 1718379722);
+ throw WorkspaceDoesNotExist::butWasSupposedToInContentRepository($workspaceName, $contentRepositoryId);
}
return $workspace;
}
diff --git a/Neos.Neos/Classes/Security/Authorization/ContentRepositoryAuthorizationService.php b/Neos.Neos/Classes/Security/Authorization/ContentRepositoryAuthorizationService.php
index 6de599cbd7c..b187e63f7a6 100644
--- a/Neos.Neos/Classes/Security/Authorization/ContentRepositoryAuthorizationService.php
+++ b/Neos.Neos/Classes/Security/Authorization/ContentRepositoryAuthorizationService.php
@@ -59,6 +59,10 @@ public function getWorkspacePermissions(ContentRepositoryId $contentRepositoryId
if ($userId !== null) {
$subjects[] = WorkspaceRoleSubject::createForUser($userId);
}
+ /**
+ * We hardcode the check against administrators to always grant manage permissions. This is done to allow administrators to fix permissions of all workspaces.
+ * We don't allow all rights like read and write. Admins should be able to grant themselves permissions to write to other personal workspaces, but they should not have this permission automagically.
+ */
$userIsAdministrator = in_array(self::ROLE_NEOS_ADMINISTRATOR, $roleIdentifiers, true);
$userWorkspaceRole = $this->metadataAndRoleRepository->getMostPrivilegedWorkspaceRoleForSubjects($contentRepositoryId, $workspaceName, WorkspaceRoleSubjects::fromArray($subjects));
if ($userWorkspaceRole === null) {
diff --git a/Neos.Neos/Classes/Security/ContentRepositoryAuthProvider/ContentRepositoryAuthProvider.php b/Neos.Neos/Classes/Security/ContentRepositoryAuthProvider/ContentRepositoryAuthProvider.php
index 74bfd788aa4..73c4bb6bf3f 100644
--- a/Neos.Neos/Classes/Security/ContentRepositoryAuthProvider/ContentRepositoryAuthProvider.php
+++ b/Neos.Neos/Classes/Security/ContentRepositoryAuthProvider/ContentRepositoryAuthProvider.php
@@ -26,6 +26,7 @@
use Neos\ContentRepository\Core\Feature\SubtreeTagging\Command\UntagSubtree;
use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Command\CreateRootWorkspace;
use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Command\CreateWorkspace;
+use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Exception\BaseWorkspaceDoesNotExist;
use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\ChangeBaseWorkspace;
use Neos\ContentRepository\Core\Feature\WorkspaceModification\Command\DeleteWorkspace;
use Neos\ContentRepository\Core\Feature\WorkspacePublication\Command\DiscardIndividualNodesFromWorkspace;
@@ -36,6 +37,8 @@
use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphReadModelInterface;
use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints;
use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId;
+use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist;
+use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceHasNoBaseWorkspaceName;
use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;
use Neos\Flow\Security\Context as SecurityContext;
@@ -116,6 +119,13 @@ public function canExecuteCommand(CommandInterface $command): Privilege
if ($command instanceof CreateRootWorkspace) {
return Privilege::denied('Creation of root workspaces is currently only allowed with disabled authorization checks');
}
+ if ($command instanceof CreateWorkspace) {
+ $baseWorkspacePermissions = $this->getWorkspacePermissionsForCurrentUser($command->baseWorkspaceName);
+ if (!$baseWorkspacePermissions->read) {
+ return Privilege::denied(sprintf('Missing "read" permissions for base workspace "%s": %s', $command->baseWorkspaceName->value, $baseWorkspacePermissions->getReason()));
+ }
+ return Privilege::granted(sprintf('User has "read" permissions for base workspace "%s"', $command->baseWorkspaceName->value));
+ }
if ($command instanceof ChangeBaseWorkspace) {
$workspacePermissions = $this->getWorkspacePermissionsForCurrentUser($command->workspaceName);
if (!$workspacePermissions->manage) {
@@ -127,6 +137,28 @@ public function canExecuteCommand(CommandInterface $command): Privilege
}
return Privilege::granted(sprintf('User has "manage" permissions for workspace "%s" and "read" permissions for base workspace "%s"', $command->workspaceName->value, $command->baseWorkspaceName->value));
}
+ if ($command instanceof PublishWorkspace || $command instanceof PublishIndividualNodesFromWorkspace) {
+ $workspacePermissions = $this->getWorkspacePermissionsForCurrentUser($command->workspaceName);
+ if (!$workspacePermissions->write) {
+ return Privilege::denied(sprintf('Missing "write" permissions for workspace "%s": %s', $command->workspaceName->value, $workspacePermissions->getReason()));
+ }
+ $workspace = $this->contentGraphReadModel->findWorkspaceByName($command->workspaceName);
+ if ($workspace === null) {
+ throw WorkspaceDoesNotExist::butWasSupposedTo($command->workspaceName);
+ }
+ if ($workspace->baseWorkspaceName === null) {
+ throw WorkspaceHasNoBaseWorkspaceName::butWasSupposedTo($workspace->workspaceName);
+ }
+ $baseWorkspace = $this->contentGraphReadModel->findWorkspaceByName($workspace->baseWorkspaceName);
+ if ($baseWorkspace === null) {
+ throw BaseWorkspaceDoesNotExist::butWasSupposedTo($workspace->workspaceName);
+ }
+ $baseWorkspacePermissions = $this->getWorkspacePermissionsForCurrentUser($baseWorkspace->workspaceName);
+ if (!$baseWorkspacePermissions->write) {
+ return Privilege::denied(sprintf('Missing "write" permissions for base workspace "%s": %s', $baseWorkspace->workspaceName->value, $baseWorkspacePermissions->getReason()));
+ }
+ return Privilege::granted(sprintf('User has "manage" permissions for workspace "%s" and "write" permissions for base workspace "%s"', $command->workspaceName->value, $baseWorkspace->workspaceName->value));
+ }
return match ($command::class) {
AddDimensionShineThrough::class,
ChangeNodeAggregateName::class,
@@ -136,10 +168,7 @@ public function canExecuteCommand(CommandInterface $command): Privilege
UpdateRootNodeAggregateDimensions::class,
DiscardWorkspace::class,
DiscardIndividualNodesFromWorkspace::class,
- PublishWorkspace::class,
- PublishIndividualNodesFromWorkspace::class,
RebaseWorkspace::class => $this->requireWorkspaceWritePermission($command->workspaceName),
- CreateWorkspace::class => $this->requireWorkspaceWritePermission($command->baseWorkspaceName),
DeleteWorkspace::class => $this->requireWorkspaceManagePermission($command->workspaceName),
default => Privilege::granted('Command not restricted'),
};
diff --git a/Neos.Neos/Configuration/Policy.yaml b/Neos.Neos/Configuration/Policy.yaml
index c441d522255..c5f3beb3ca3 100644
--- a/Neos.Neos/Configuration/Policy.yaml
+++ b/Neos.Neos/Configuration/Policy.yaml
@@ -56,10 +56,6 @@ privilegeTargets:
label: General access to content editing
matcher: 'method(Neos\Neos\Service\Controller\NodeController->(show|getPrimaryChildNode|getChildNodesForTree|filterChildNodesForTree|getChildNodes|getChildNodesFromParent|create|createAndRender|createNodeForTheTree|move|moveBefore|moveAfter|moveInto|moveAndRender|copy|copyBefore|copyAfter|copyInto|copyAndRender|update|updateAndRender|delete|searchPage|error)Action()) || method(Neos\Neos\Controller\Backend\ContentController->(uploadAsset|assetsWithMetadata|imageWithMetadata|createImageVariant|error)Action()) || method(Neos\Neos\Controller\Service\AssetProxiesController->(index|show|import|error)Action()) || method(Neos\Neos\Controller\Service\AssetsController->(index|show|error)Action()) || method(Neos\Neos\Controller\Service\NodesController->(index|show|create|error)Action())'
- 'Neos.Neos:Backend.PublishToLiveWorkspace':
- label: Allowed to publish to the live workspace
- matcher: 'method(Neos\ContentRepository\Domain\Model\Workspace->(publish|publishNode|publishNodes)(targetWorkspace.name === "live"))'
-
'Neos.Neos:Backend.PublishOwnWorkspaceContent':
label: Allowed to publish own personal workspace
matcher: 'method(Neos\Neos\Service\Controller\WorkspaceController->(publishNode|publishNodes|error)Action()) || method(Neos\Neos\Service\Controller\WorkspaceController->publishAllAction(workspaceName = current.userInformation.personalWorkspaceName)) || method(Neos\Neos\Service\Controller\WorkspaceController->getWorkspaceWideUnpublishedNodesAction(workspace.name = current.userInformation.personalWorkspaceName))'
@@ -170,14 +166,12 @@ roles:
permission: GRANT
'Neos.Neos:LivePublisher':
+ # This group is assigned conventionally as collaborator for the live workspace. See WorkspaceService::assignWorkspaceRole
label: Live publisher
description: The role allows to publish to the live workspace
- privileges:
- -
- privilegeTarget: 'Neos.Neos:Backend.PublishToLiveWorkspace'
- permission: GRANT
'Neos.Neos:AbstractEditor':
+ # This group is assigned conventionally for new shared workspaces as collaborator. See WorkspaceService::assignWorkspaceRole
abstract: true
parentRoles: ['Neos.ContentRepository:Administrator']
privileges:
diff --git a/Neos.Neos/Migrations/Mysql/Version20240425223900.php b/Neos.Neos/Migrations/Mysql/Version20240425223900.php
index 69bd0305bcd..aca3a3a172a 100644
--- a/Neos.Neos/Migrations/Mysql/Version20240425223900.php
+++ b/Neos.Neos/Migrations/Mysql/Version20240425223900.php
@@ -29,7 +29,7 @@ public function up(Schema $schema): void
$tableWorkspaceMetadata->addColumn('classification', 'string', ['length' => 255]);
$tableWorkspaceMetadata->addColumn('owner_user_id', 'string', ['length' => 255, 'notnull' => false]);
$tableWorkspaceMetadata->setPrimaryKey(['content_repository_id', 'workspace_name']);
- $tableWorkspaceMetadata->addIndex(['owner_user_id']);
+ $tableWorkspaceMetadata->addIndex(['owner_user_id'], 'IDX_D6197E562B18554A');
$tableWorkspaceRole = $schema->createTable('neos_neos_workspace_role');
$tableWorkspaceRole->addColumn('content_repository_id', 'string', ['length' => 16]);
diff --git a/Neos.Neos/Migrations/Mysql/Version20241212000000.php b/Neos.Neos/Migrations/Mysql/Version20241212000000.php
new file mode 100644
index 00000000000..260398ed844
--- /dev/null
+++ b/Neos.Neos/Migrations/Mysql/Version20241212000000.php
@@ -0,0 +1,40 @@
+abortIf(
+ !$this->connection->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\MariaDBPlatform,
+ "Migration can only be executed safely on '\Doctrine\DBAL\Platforms\MariaDBPlatform'."
+ );
+
+ $tableWorkspaceMetadata = $schema->getTable('neos_neos_workspace_metadata');
+ $tableWorkspaceMetadata->addUniqueIndex(['content_repository_id', 'owner_user_id'], 'owner');
+ $tableWorkspaceMetadata->dropIndex('IDX_D6197E562B18554A');
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->abortIf(
+ !$this->connection->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\MariaDBPlatform,
+ "Migration can only be executed safely on '\Doctrine\DBAL\Platforms\MariaDBPlatform'."
+ );
+
+ $tableWorkspaceMetadata = $schema->getTable('neos_neos_workspace_metadata');
+ $tableWorkspaceMetadata->addIndex(['owner_user_id'], 'IDX_D6197E562B18554A');
+ $tableWorkspaceMetadata->dropIndex('owner');
+ }
+}
diff --git a/Neos.Neos/Migrations/Postgresql/Version20240425223901.php b/Neos.Neos/Migrations/Postgresql/Version20240425223901.php
index b370bd7b9d1..93cb4813b24 100644
--- a/Neos.Neos/Migrations/Postgresql/Version20240425223901.php
+++ b/Neos.Neos/Migrations/Postgresql/Version20240425223901.php
@@ -26,7 +26,7 @@ public function up(Schema $schema): void
$tableWorkspaceMetadata->addColumn('classification', 'string', ['length' => 255]);
$tableWorkspaceMetadata->addColumn('owner_user_id', 'string', ['length' => 255, 'notnull' => false]);
$tableWorkspaceMetadata->setPrimaryKey(['content_repository_id', 'workspace_name']);
- $tableWorkspaceMetadata->addIndex(['owner_user_id']);
+ $tableWorkspaceMetadata->addIndex(['owner_user_id'], 'IDX_D6197E562B18554A');
$tableWorkspaceRole = $schema->createTable('neos_neos_workspace_role');
$tableWorkspaceRole->addColumn('content_repository_id', 'string', ['length' => 16]);
diff --git a/Neos.Neos/Migrations/Postgresql/Version20241212000001.php b/Neos.Neos/Migrations/Postgresql/Version20241212000001.php
new file mode 100644
index 00000000000..85e574c2969
--- /dev/null
+++ b/Neos.Neos/Migrations/Postgresql/Version20241212000001.php
@@ -0,0 +1,40 @@
+abortIf(
+ !$this->connection->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\PostgreSQLPlatform,
+ "Migration can only be executed safely on '\Doctrine\DBAL\Platforms\PostgreSQLPlatform'."
+ );
+
+ $tableWorkspaceMetadata = $schema->getTable('neos_neos_workspace_metadata');
+ $tableWorkspaceMetadata->addUniqueIndex(['content_repository_id', 'owner_user_id'], 'owner');
+ $tableWorkspaceMetadata->dropIndex('IDX_D6197E562B18554A');
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->abortIf(
+ !$this->connection->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\PostgreSQLPlatform,
+ "Migration can only be executed safely on '\Doctrine\DBAL\Platforms\PostgreSQLPlatform'."
+ );
+
+ $tableWorkspaceMetadata = $schema->getTable('neos_neos_workspace_metadata');
+ $tableWorkspaceMetadata->addIndex(['owner_user_id'], 'IDX_D6197E562B18554A');
+ $tableWorkspaceMetadata->dropIndex('owner');
+ }
+}
diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/FlowSecurityTrait.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/FlowSecurityTrait.php
index 5f4915dfcf1..23c6b9e347d 100644
--- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/FlowSecurityTrait.php
+++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/FlowSecurityTrait.php
@@ -83,6 +83,7 @@ final protected function enableFlowSecurity(): void
$this->flowSecurity_testingProvider = $tokenAndProviderFactory->getProviders()['TestingProvider'];
$securityContext = $this->getObject(SecurityContext::class);
+ $securityContext->clearContext(); // enable authorizationChecks
$httpRequest = $this->getObject(ServerRequestFactoryInterface::class)->createServerRequest('GET', 'http://localhost/');
$this->flowSecurity_mockActionRequest = ActionRequest::fromHttpRequest($httpRequest);
$securityContext->setRequest($this->flowSecurity_mockActionRequest);
@@ -132,4 +133,16 @@ public function getConfiguration(string $configurationType, string $configuratio
}
});
}
+
+ /**
+ * @When I am not authenticated
+ */
+ final public function iAmNotAuthenticated(): void
+ {
+ $this->flowSecurity_testingProvider->reset();
+ $securityContext = $this->getObject(SecurityContext::class);
+ $securityContext->clearContext();
+ $securityContext->setRequest($this->flowSecurity_mockActionRequest);
+ $this->getObject(AuthenticationProviderManager::class)->authenticate();
+ }
}
diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/UserServiceTrait.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/UserServiceTrait.php
index da251d624fb..7585fdf492f 100644
--- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/UserServiceTrait.php
+++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/UserServiceTrait.php
@@ -16,10 +16,8 @@
use Neos\Flow\Persistence\PersistenceManagerInterface;
use Neos\Flow\Security\AccountFactory;
use Neos\Flow\Security\Cryptography\HashService;
-use Neos\Flow\Security\Policy\PolicyService;
use Neos\Neos\Domain\Model\User;
use Neos\Neos\Domain\Service\UserService;
-use Neos\Neos\Security\Authorization\Privilege\ReadNodePrivilege;
use Neos\Party\Domain\Model\PersonName;
use Neos\Utility\ObjectAccess;
@@ -61,11 +59,14 @@ public function theNeosUserExists(string $username, string $id = null, string $f
public function theFollowingNeosUsersExist(TableNode $usersTable): void
{
foreach ($usersTable->getHash() as $userData) {
+ if (empty($userData['Roles'])) {
+ throw new \InvalidArgumentException('Please specify explicit roles for the Neos user, to avoid using any fallbacks.');
+ }
$this->createUser(
username: $userData['Username'],
firstName: $userData['First name'] ?? null,
lastName: $userData['Last name'] ?? null,
- roleIdentifiers: !empty($userData['Roles']) ? explode(',', $userData['Roles']) : null,
+ roleIdentifiers: explode(',', $userData['Roles']),
id: $userData['Id'] ?? null,
);
}
@@ -81,6 +82,7 @@ private function createUser(string $username, string $firstName = null, string $
$accountFactory = $this->getObject(AccountFactory::class);
+ // todo either hack the global hash service or fix flow to avoid having to inline this code
// NOTE: We replace the original {@see HashService} by a "mock" for performance reasons (the default hashing strategy usually takes a while to create passwords)
/** @var HashService $originalHashService */
diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/WorkspaceServiceTrait.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/WorkspaceServiceTrait.php
index af046a39443..938ce3f31a8 100644
--- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/WorkspaceServiceTrait.php
+++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/WorkspaceServiceTrait.php
@@ -15,15 +15,16 @@
use Behat\Gherkin\Node\TableNode;
use Neos\ContentRepository\BehavioralTests\TestSuite\Behavior\CRBehavioralTestsSubjectProvider;
use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Command\CreateRootWorkspace;
-use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Command\CreateWorkspace;
+use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist;
use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;
-use Neos\Flow\Security\Context as SecurityContext;
use Neos\Neos\Domain\Model\UserId;
use Neos\Neos\Domain\Model\WorkspaceDescription;
use Neos\Neos\Domain\Model\WorkspaceRole;
use Neos\Neos\Domain\Model\WorkspaceRoleAssignment;
+use Neos\Neos\Domain\Model\WorkspaceRoleAssignments;
use Neos\Neos\Domain\Model\WorkspaceRoleSubject;
+use Neos\Neos\Domain\Model\WorkspaceRoleSubjectType;
use Neos\Neos\Domain\Model\WorkspaceTitle;
use Neos\Neos\Domain\Service\UserService;
use Neos\Neos\Domain\Service\WorkspaceService;
@@ -59,9 +60,35 @@ public function theRootWorkspaceIsCreated(string $workspaceName, string $title =
WorkspaceName::fromString($workspaceName),
WorkspaceTitle::fromString($title ?? $workspaceName),
WorkspaceDescription::fromString($description ?? ''),
+ WorkspaceRoleAssignments::createEmpty()
));
}
+ /**
+ * @When the workspace :workspaceName is deleted
+ */
+ public function theWorkspaceIsDeleted(string $workspaceName): void
+ {
+ $this->tryCatchingExceptions(fn () => $this->getObject(WorkspaceService::class)->deleteWorkspace(
+ $this->currentContentRepository->id,
+ WorkspaceName::fromString($workspaceName),
+ ));
+ }
+
+ /**
+ * @Given the live workspace exists
+ */
+ public function theLiveWorkspaceExists(): void
+ {
+ $this->getObject(WorkspaceService::class)->createRootWorkspace(
+ $this->currentContentRepository->id,
+ WorkspaceName::forLive(),
+ WorkspaceTitle::fromString('Public live workspace'),
+ WorkspaceDescription::empty(),
+ WorkspaceRoleAssignments::createForLiveWorkspace()
+ );
+ }
+
/**
* @When the personal workspace :workspaceName is created with the target workspace :targetWorkspace for user :username
*/
@@ -90,17 +117,58 @@ public function aPersonalWorkspaceForUserIsCreated(string $username): void
));
}
+ /**
+ * @Then the personal workspace for user :username is :workspaceName
+ */
+ public function thePersonalWorkspaceForUserIs(string $username, string $workspaceName): void
+ {
+ $ownerUserId = $this->userIdForUsername($username);
+ $actualWorkspace = $this->getObject(WorkspaceService::class)->getPersonalWorkspaceForUser($this->currentContentRepository->id, $ownerUserId);
+ Assert::assertNotNull($actualWorkspace);
+ Assert::assertSame($workspaceName, $actualWorkspace->workspaceName->value);
+ }
+
+ /**
+ * @Then the user :username does not have a personal workspace
+ */
+ public function theUserDoesNotHaveAPersonalWorkspace(string $username): void
+ {
+ $ownerUserId = $this->userIdForUsername($username);
+ try {
+ $this->getObject(WorkspaceService::class)->getPersonalWorkspaceForUser($this->currentContentRepository->id, $ownerUserId);
+ } catch (\Throwable $e) {
+ // todo throw WorkspaceDoesNotExist instead??
+ Assert::assertInstanceOf(\RuntimeException::class, $e, $e->getMessage());
+ Assert::assertSame(1718293801, $e->getCode());
+ return;
+ }
+ Assert::fail('Did not throw');
+ }
+
/**
* @When the shared workspace :workspaceName is created with the target workspace :targetWorkspace
+ * @When the shared workspace :workspaceName is created with the target workspace :targetWorkspace and role assignments:
*/
- public function theSharedWorkspaceIsCreatedWithTheTargetWorkspace(string $workspaceName, string $targetWorkspace): void
+ public function theSharedWorkspaceIsCreatedWithTheTargetWorkspace(string $workspaceName, string $targetWorkspace, ?TableNode $rawRoleAssignments = null): void
{
+ $workspaceRoleAssignments = WorkspaceRoleAssignments::createEmpty();
+ foreach ($rawRoleAssignments?->getHash() ?? [] as $row) {
+ $workspaceRoleAssignments = $workspaceRoleAssignments->withAssignment(WorkspaceRoleAssignment::create(
+ WorkspaceRoleSubject::create(
+ WorkspaceRoleSubjectType::from($row['Type']),
+ $row['Value']
+ ),
+ WorkspaceRole::from($row['Role'])
+ ));
+ }
+
$this->tryCatchingExceptions(fn () => $this->getObject(WorkspaceService::class)->createSharedWorkspace(
$this->currentContentRepository->id,
WorkspaceName::fromString($workspaceName),
WorkspaceTitle::fromString($workspaceName),
WorkspaceDescription::fromString(''),
WorkspaceName::fromString($targetWorkspace),
+ $workspaceRoleAssignments
));
}
@@ -115,18 +183,6 @@ public function aRootWorkspaceExistsWithoutMetadata(string $workspaceName): void
));
}
- /**
- * @When a workspace :workspaceName with base workspace :baseWorkspaceName exists without metadata
- */
- public function aWorkspaceWithBaseWorkspaceExistsWithoutMetadata(string $workspaceName, string $baseWorkspaceName): void
- {
- $this->currentContentRepository->handle(CreateWorkspace::create(
- WorkspaceName::fromString($workspaceName),
- WorkspaceName::fromString($baseWorkspaceName),
- ContentStreamId::create(),
- ));
- }
-
/**
* @When the title of workspace :workspaceName is set to :newTitle
*/
@@ -165,6 +221,42 @@ public function theWorkspaceShouldHaveTheFollowingMetadata($workspaceName, Table
]);
}
+ /**
+ * @Then the metadata for workspace :workspaceName does not exist
+ */
+ public function theWorkspaceMetadataFails($workspaceName): void
+ {
+ $metaData = $this->getObject(\Neos\Neos\Domain\Repository\WorkspaceMetadataAndRoleRepository::class)->loadWorkspaceMetadata($this->currentContentRepository->id, WorkspaceName::fromString($workspaceName));
+ Assert::assertNull($metaData);
+
+ // asking the API FAILS!
+ try {
+ $this->getObject(WorkspaceService::class)->getWorkspaceMetadata($this->currentContentRepository->id, WorkspaceName::fromString($workspaceName));
+ } catch (\Throwable $e) {
+ Assert::assertInstanceOf(WorkspaceDoesNotExist::class, $e, $e->getMessage());
+ return;
+ }
+ Assert::fail('Did not throw');
+ }
+
+ /**
+ * @Then the roles for workspace :workspaceName does not exist
+ */
+ public function theWorkspaceRolesFails($workspaceName): void
+ {
+ $roles = $this->getObject(\Neos\Neos\Domain\Repository\WorkspaceMetadataAndRoleRepository::class)->getWorkspaceRoleAssignments($this->currentContentRepository->id, WorkspaceName::fromString($workspaceName));
+ Assert::assertTrue($roles->isEmpty());
+
+ // asking the API FAILS!
+ try {
+ $this->getObject(WorkspaceService::class)->getWorkspaceRoleAssignments($this->currentContentRepository->id, WorkspaceName::fromString($workspaceName));
+ } catch (\Throwable $e) {
+ Assert::assertInstanceOf(WorkspaceDoesNotExist::class, $e, $e->getMessage());
+ return;
+ }
+ Assert::fail('Did not throw');
+ }
+
/**
* @When the role :role is assigned to workspace :workspaceName for group :groupName
* @When the role :role is assigned to workspace :workspaceName for user :username
diff --git a/Neos.Neos/Tests/Behavior/Features/ContentRepository/Security/EditNodePrivilege.feature b/Neos.Neos/Tests/Behavior/Features/ContentRepository/Security/EditNodePrivilege.feature
index d279d035287..2355a2f72f4 100644
--- a/Neos.Neos/Tests/Behavior/Features/ContentRepository/Security/EditNodePrivilege.feature
+++ b/Neos.Neos/Tests/Behavior/Features/ContentRepository/Security/EditNodePrivilege.feature
@@ -29,10 +29,7 @@ Feature: EditNodePrivilege related features
"""
And using identifier "default", I define a content repository
And I am in content repository "default"
- And the command CreateRootWorkspace is executed with payload:
- | Key | Value |
- | workspaceName | "live" |
- | newContentStreamId | "cs-identifier" |
+ And the live workspace exists
And I am in workspace "live" and dimension space point {}
And the command CreateRootNodeAggregateWithNode is executed with payload:
| Key | Value |
diff --git a/Neos.Neos/Tests/Behavior/Features/ContentRepository/Security/ReadNodePrivilege.feature b/Neos.Neos/Tests/Behavior/Features/ContentRepository/Security/ReadNodePrivilege.feature
index 2545f9c7bac..02cfd9b7e80 100644
--- a/Neos.Neos/Tests/Behavior/Features/ContentRepository/Security/ReadNodePrivilege.feature
+++ b/Neos.Neos/Tests/Behavior/Features/ContentRepository/Security/ReadNodePrivilege.feature
@@ -29,10 +29,7 @@ Feature: ReadNodePrivilege related features
"""
And using identifier "default", I define a content repository
And I am in content repository "default"
- And the command CreateRootWorkspace is executed with payload:
- | Key | Value |
- | workspaceName | "live" |
- | newContentStreamId | "cs-identifier" |
+ And the live workspace exists
And I am in workspace "live" and dimension space point {}
And the command CreateRootNodeAggregateWithNode is executed with payload:
| Key | Value |
@@ -64,7 +61,6 @@ Feature: ReadNodePrivilege related features
| nodeAggregateId | "a" |
| nodeVariantSelectionStrategy | "allSpecializations" |
| tag | "subtree_a" |
- And the role VIEWER is assigned to workspace "live" for group "Neos.Flow:Everybody"
When a personal workspace for user "editor" is created
And content repository security is enabled
diff --git a/Neos.Neos/Tests/Behavior/Features/ContentRepository/Security/WorkspacePermissions.feature b/Neos.Neos/Tests/Behavior/Features/ContentRepository/Security/WorkspacePermissions.feature
index c039224874b..f9532c61317 100644
--- a/Neos.Neos/Tests/Behavior/Features/ContentRepository/Security/WorkspacePermissions.feature
+++ b/Neos.Neos/Tests/Behavior/Features/ContentRepository/Security/WorkspacePermissions.feature
@@ -20,11 +20,8 @@ Feature: Workspace permission related features
"""
And using identifier "default", I define a content repository
And I am in content repository "default"
- And the command CreateRootWorkspace is executed with payload:
- | Key | Value |
- | workspaceName | "live" |
- | newContentStreamId | "cs-identifier" |
- And I am in workspace "live" and dimension space point {}
+ And the live workspace exists
+ And I am in workspace "live"
And the command CreateRootNodeAggregateWithNode is executed with payload:
| Key | Value |
| nodeAggregateId | "root" |
@@ -45,32 +42,24 @@ Feature: Workspace permission related features
And the following Neos users exist:
| Username | Roles |
| admin | Neos.Neos:Administrator |
- | editor | Neos.Neos:Editor |
- | restricted_editor | Neos.Neos:RestrictedEditor |
+ # all editors are Neos.Neos:LivePublisher
| owner | Neos.Neos:Editor |
| manager | Neos.Neos:Editor |
| collaborator | Neos.Neos:Editor |
- | uninvolved | Neos.Neos:Editor |
- And I am in workspace "live"
- And I am in dimension space point {"language":"de"}
- And the command TagSubtree is executed with payload:
- | Key | Value |
- | nodeAggregateId | "a" |
- | nodeVariantSelectionStrategy | "allSpecializations" |
- | tag | "subtree_a" |
- And the command DisableNodeAggregate is executed with payload:
- | Key | Value |
- | nodeAggregateId | "a1a1a" |
- | nodeVariantSelectionStrategy | "allVariants" |
+ | restricted_editor | Neos.Neos:RestrictedEditor |
+ | uninvolved_editor | Neos.Neos:Editor |
+ # neos user with out any editing roles
+ | simple_user | Neos.Neos:UserManager |
+
+ When content repository security is enabled
+ And the shared workspace "shared-workspace" is created with the target workspace "live" and role assignments:
+ | Role | Type | Value |
+ | COLLABORATOR | GROUP | Neos.Neos:AbstractEditor |
+
+ Given I am authenticated as owner
And the personal workspace "workspace" is created with the target workspace "live" for user "owner"
- And I am in workspace "workspace"
And the role MANAGER is assigned to workspace "workspace" for user "manager"
And the role COLLABORATOR is assigned to workspace "workspace" for user "collaborator"
- # The following step was added in order to make the `AddDimensionShineThrough` command viable
- And I change the content dimensions in content repository "default" to:
- | Identifier | Values | Generalizations |
- | language | mul, de, ch | ch->de->mul |
- And content repository security is enabled
Scenario Outline: Creating a root workspace
Given I am authenticated as
@@ -80,13 +69,13 @@ Feature: Workspace permission related features
Examples:
| user |
| admin |
- | editor |
| restricted_editor |
| owner |
| collaborator |
- | uninvolved |
+ | uninvolved_editor |
+ | simple_user |
- Scenario Outline: Creating a base workspace without WRITE permissions
+ Scenario Outline: Creating a nested workspace without READ 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
@@ -97,11 +86,11 @@ Feature: Workspace permission related features
Examples:
| user |
| admin |
- | editor |
| restricted_editor |
- | uninvolved |
+ | uninvolved_editor |
+ | simple_user |
- Scenario Outline: Creating a base workspace with WRITE permissions
+ Scenario Outline: Creating a nested workspace with READ permissions
Given I am authenticated as
And the shared workspace "some-shared-workspace" is created with the target workspace "workspace"
@@ -110,6 +99,52 @@ Feature: Workspace permission related features
Examples:
| user |
| collaborator |
+ # the "owner" user already owns a workspace
+
+ Scenario: Creating a workspace without Neos User but READ permissions on live
+ Given I am not authenticated
+ And the shared workspace "some-shared-workspace" is created with the target workspace "live"
+
+ Scenario Outline: Creating a workspace with READ permissions (on live)
+ Given I am authenticated as
+ And the shared workspace "some-shared-workspace" is created with the target workspace "live"
+
+ And the personal workspace "some-other-personal-workspace" is created with the target workspace "live" for user
+
+ Examples:
+ | user |
+ | admin |
+ | collaborator |
+ | uninvolved_editor |
+ | restricted_editor |
+ | simple_user |
+ # the "owner" user already owns a workspace
+
+ Scenario Outline: Changing a base workspace without MANAGE permissions or READ permissions on the base workspace
+ Given I am authenticated as
+ When the command ChangeBaseWorkspace is executed with payload and exceptions are caught:
+ | Key | Value |
+ | workspaceName | "workspace" |
+ | baseWorkspaceName | "shared-workspace" |
+ Then the last command should have thrown an exception of type "AccessDenied" with code 1729086686
+
+ Examples:
+ | user |
+ | restricted_editor |
+ | collaborator |
+ | uninvolved_editor |
+
+ Scenario Outline: Changing a base workspace with MANAGE permissions or READ permissions on the base workspace
+ Given I am authenticated as
+ When the command ChangeBaseWorkspace is executed with payload:
+ | Key | Value |
+ | workspaceName | "workspace" |
+ | baseWorkspaceName | "shared-workspace" |
+
+ Examples:
+ | user |
+ | admin |
+ | manager |
| owner |
Scenario Outline: Deleting a workspace without MANAGE permissions
@@ -118,9 +153,10 @@ Feature: Workspace permission related features
Then the last command should have thrown an exception of type "AccessDenied" with code 1729086686
Examples:
- | user |
- | collaborator |
- | uninvolved |
+ | user |
+ | collaborator |
+ | uninvolved_editor |
+ | simple_user |
Scenario Outline: Deleting a workspace with MANAGE permissions
Given I am authenticated as
@@ -147,9 +183,9 @@ Feature: Workspace permission related features
Then an exception of type "AccessDenied" should be thrown with code 1731654519
Examples:
- | user |
- | collaborator |
- | uninvolved |
+ | user |
+ | collaborator |
+ | uninvolved_editor |
Scenario Outline: Managing metadata and roles of a workspace with MANAGE permissions
Given I am authenticated as
@@ -165,11 +201,33 @@ Feature: Workspace permission related features
| owner |
Scenario Outline: Handling commands that require WRITE permissions on the workspace
- When I am authenticated as "uninvolved"
+ # Prepare the content repository so all commands are applicable
+ When I am authenticated as "owner"
+ And I am in workspace "live" and dimension space point {"language":"de"}
+ And the command TagSubtree is executed with payload:
+ | Key | Value |
+ | nodeAggregateId | "a" |
+ | nodeVariantSelectionStrategy | "allSpecializations" |
+ | tag | "subtree_a" |
+ And the command DisableNodeAggregate is executed with payload:
+ | Key | Value |
+ | nodeAggregateId | "a1a1a" |
+ | nodeVariantSelectionStrategy | "allVariants" |
+ # The following step was added in order to make the `AddDimensionShineThrough` command viable
+ And I change the content dimensions in content repository "default" to:
+ | Identifier | Values | Generalizations |
+ | language | mul, de, ch | ch->de->mul |
+ And the command RebaseWorkspace is executed with payload:
+ | Key | Value |
+ | workspaceName | "workspace" |
+
+ And I am in workspace "workspace"
+
+ Given I am not authenticated
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"
+ When I am authenticated as "uninvolved_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
@@ -209,8 +267,58 @@ Feature: Workspace permission related features
| UpdateRootNodeAggregateDimensions | {"nodeAggregateId":"root"} |
| DiscardWorkspace | {} |
| DiscardIndividualNodesFromWorkspace | {"nodesToDiscard":["a1"]} |
- | PublishWorkspace | {} |
- | PublishIndividualNodesFromWorkspace | {"nodesToPublish":["a1"]} |
| RebaseWorkspace | {} |
+ # note, creating a core workspace will not grant permissions to it to the current user: Missing "read" permissions for base workspace "new-workspace"
| CreateWorkspace | {"workspaceName":"new-workspace","baseWorkspaceName":"workspace","newContentStreamId":"any"} |
+ Scenario Outline: Publishing a workspace without WRITE permissions to live
+ # make changes as owner
+ Given I am authenticated as owner
+
+ And the following CreateNodeAggregateWithNode commands are executed:
+ | nodeAggregateId | nodeTypeName | parentNodeAggregateId | workspaceName | originDimensionSpacePoint |
+ | shernode-homes | Neos.Neos:Document | a | workspace | {"language":"de"} |
+ | other-node | Neos.Neos:Document | a | workspace | {"language":"de"} |
+
+ # someone else attempts to publish
+ Given I am authenticated as
+
+ And the command PublishIndividualNodesFromWorkspace is executed with payload and exceptions are caught:
+ | Key | Value |
+ | workspaceName | "workspace" |
+ | nodesToPublish | ["shernode-homes"] |
+ Then the last command should have thrown an exception of type "AccessDenied" with code 1729086686
+
+ And the command PublishWorkspace is executed with payload and exceptions are caught:
+ | Key | Value |
+ | workspaceName | "workspace" |
+ Then the last command should have thrown an exception of type "AccessDenied" with code 1729086686
+
+ Examples:
+ | user |
+ | restricted_editor |
+ | simple_user |
+ | uninvolved_editor |
+ | admin |
+
+ Scenario Outline: Publishing a workspace with WRITE permissions to live
+ Given I am authenticated as
+
+ And the following CreateNodeAggregateWithNode commands are executed:
+ | nodeAggregateId | nodeTypeName | parentNodeAggregateId | workspaceName | originDimensionSpacePoint |
+ | shernode-homes | Neos.Neos:Document | a | workspace | {"language":"de"} |
+ | other-node | Neos.Neos:Document | a | workspace | {"language":"de"} |
+
+ And the command PublishIndividualNodesFromWorkspace is executed with payload:
+ | Key | Value |
+ | workspaceName | "workspace" |
+ | nodesToPublish | ["shernode-homes"] |
+
+ And the command PublishWorkspace is executed with payload:
+ | Key | Value |
+ | workspaceName | "workspace" |
+
+ Examples:
+ | user |
+ | owner |
+ | collaborator |
diff --git a/Neos.Neos/Tests/Behavior/Features/ContentRepository/WorkspaceService.feature b/Neos.Neos/Tests/Behavior/Features/ContentRepository/WorkspaceService.feature
index 1bdac437c30..c91c1a3f3b6 100644
--- a/Neos.Neos/Tests/Behavior/Features/ContentRepository/WorkspaceService.feature
+++ b/Neos.Neos/Tests/Behavior/Features/ContentRepository/WorkspaceService.feature
@@ -87,14 +87,39 @@ Feature: Neos WorkspaceService related features
| Title | Description | Classification | Owner user id |
| some-root-workspace | Some new workspace description | ROOT | |
-
Scenario: Create a single personal workspace
When the root workspace "some-root-workspace" is created
+ Then the user "jane.doe" does not have a personal workspace
And the personal workspace "some-user-workspace" is created with the target workspace "some-root-workspace" for user "jane.doe"
+ Then the personal workspace for user "jane.doe" is "some-user-workspace"
Then the workspace "some-user-workspace" should have the following metadata:
| Title | Description | Classification | Owner user id |
| some-user-workspace | | PERSONAL | janedoe |
+ Scenario: Personal workspace names are unique https://github.com/neos/neos-development-collection/issues/2850
+ And the following Neos users exist:
+ | Id | Username | First name | Last name | Roles |
+ | 123 | test-user | Test | User | Neos.Neos:Administrator |
+ | 456 | test.user | Test | User | Neos.Neos:Administrator |
+
+ When the root workspace "live" is created
+
+ When a personal workspace for user "test-user" is created
+ Then the personal workspace for user "test-user" is "test-user"
+
+ When a personal workspace for user "test.user" is created
+ Then the personal workspace for user "test.user" is "test-user-1"
+
+ Scenario: A user cannot have multiple personal workspaces
+ When the root workspace "some-root-workspace" is created
+ And the personal workspace "a-user-workspace" is created with the target workspace "some-root-workspace" for user "jane.doe"
+ And the personal workspace "b-user-workspace" is created with the target workspace "some-root-workspace" for user "jane.doe"
+ Then an exception of type "RuntimeException" should be thrown with message:
+ """
+ Failed to create personal workspace "b-user-workspace" for user with id "janedoe", because the workspace "a-user-workspace" is already assigned to the user
+ """
+ Then the personal workspace for user "jane.doe" is "a-user-workspace"
+
Scenario: Create a single shared workspace
When the root workspace "some-root-workspace" is created
And the shared workspace "some-shared-workspace" is created with the target workspace "some-root-workspace"
@@ -102,18 +127,35 @@ Feature: Neos WorkspaceService related features
| Title | Description | Classification | Owner user id |
| some-shared-workspace | | SHARED | |
- Scenario: Get metadata of non-existing sub workspace
+ Scenario: Get metadata of a sub workspace which is directly created via the content repository
Given the root workspace "some-root-workspace" is created
- When a workspace "some-workspace" with base workspace "some-root-workspace" exists without metadata
+ # dont use the workspace service here:
+ When the command CreateWorkspace is executed with payload:
+ | Key | Value |
+ | workspaceName | "some-workspace" |
+ | baseWorkspaceName | "some-root-workspace" |
+ | newContentStreamId | "any-cs" |
+
Then the workspace "some-workspace" should have the following metadata:
| Title | Description | Classification | Owner user id |
| some-workspace | | UNKNOWN | |
+ Scenario: Get metadata or roles if the workspace does not exist
+ Then the metadata for workspace "non-existing-workspace" does not exist
+ Then the roles for workspace "non-existing-workspace" does not exist
+
+ When the root workspace "some-root-workspace" with title "Some root workspace" and description "Some description" is created
+ And the role COLLABORATOR is assigned to workspace "some-root-workspace" for group "Neos.Neos:AbstractEditor"
+ Given the workspace "some-root-workspace" is deleted
+
+ Then the metadata for workspace "some-root-workspace" does not exist
+ Then the roles for workspace "some-root-workspace" does not exist
+
Scenario: Assign role to non-existing workspace
When the role COLLABORATOR is assigned to workspace "some-workspace" for group "Neos.Neos:AbstractEditor"
- Then an exception of type "RuntimeException" should be thrown with message:
+ Then an exception of type "WorkspaceDoesNotExist" should be thrown with message:
"""
- Failed to find workspace with name "some-workspace" for content repository "default"
+ The workspace "some-workspace" does not exist in content repository "default"
"""
Scenario: Assign group role to root workspace
@@ -150,9 +192,9 @@ Feature: Neos WorkspaceService related features
Scenario: Unassign role from non-existing workspace
When the role for group "Neos.Neos:AbstractEditor" is unassigned from workspace "some-workspace"
- Then an exception of type "RuntimeException" should be thrown with message:
+ Then an exception of type "WorkspaceDoesNotExist" should be thrown with message:
"""
- Failed to find workspace with name "some-workspace" for content repository "default"
+ The workspace "some-workspace" does not exist in content repository "default"
"""
Scenario: Unassign role from workspace that has not been assigned before
@@ -179,6 +221,7 @@ Feature: Neos WorkspaceService related features
Scenario: Workspace permissions for personal workspace for admin user
Given the root workspace "live" is created
When a personal workspace for user "jane.doe" is created
+ Then the personal workspace for user "jane.doe" is "jane-doe"
Then the workspace "jane-doe" should have the following metadata:
| Title | Description | Classification | Owner user id |
| Jane Doe | | PERSONAL | janedoe |
diff --git a/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php b/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php
index 8718f28aedb..0bdd6dc234f 100644
--- a/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php
+++ b/Neos.Workspace.Ui/Classes/Controller/WorkspaceController.php
@@ -54,6 +54,7 @@
use Neos\Neos\Domain\Model\WorkspaceDescription;
use Neos\Neos\Domain\Model\WorkspaceRole;
use Neos\Neos\Domain\Model\WorkspaceRoleAssignment;
+use Neos\Neos\Domain\Model\WorkspaceRoleAssignments;
use Neos\Neos\Domain\Model\WorkspaceTitle;
use Neos\Neos\Domain\Repository\SiteRepository;
use Neos\Neos\Domain\Service\NodeTypeNameFactory;
@@ -221,6 +222,9 @@ public function createAction(
$title,
$description,
$baseWorkspace,
+ WorkspaceRoleAssignments::createForSharedWorkspace(
+ $currentUser->getId()
+ )
);
} catch (WorkspaceAlreadyExists $exception) {
$this->addFlashMessage(
@@ -230,22 +234,6 @@ public function createAction(
);
$this->redirect('new');
}
- $this->workspaceService->assignWorkspaceRole(
- $contentRepositoryId,
- $workspaceName,
- WorkspaceRoleAssignment::createForUser(
- $currentUser->getId(),
- WorkspaceRole::MANAGER,
- )
- );
- $this->workspaceService->assignWorkspaceRole(
- $contentRepositoryId,
- $workspaceName,
- WorkspaceRoleAssignment::createForGroup(
- 'Neos.Neos:AbstractEditor',
- WorkspaceRole::COLLABORATOR,
- )
- );
$this->addFlashMessage($this->getModuleLabel('workspaces.workspaceHasBeenCreated', [$title->value]));
$this->redirect('index');
}
@@ -1017,7 +1005,7 @@ protected function prepareBaseWorkspaceOptions(
continue;
}
$permissions = $this->contentRepositoryAuthorizationService->getWorkspacePermissions($contentRepository->id, $workspace->workspaceName, $this->securityContext->getRoles(), $currentUser?->getId());
- if (!$permissions->manage) {
+ if (!$permissions->read) {
continue;
}
$baseWorkspaceOptions[$workspace->workspaceName->value] = $workspaceMetadata->title->value;