-
-
Notifications
You must be signed in to change notification settings - Fork 224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: FEATURE: Content Repository Privileges #4185
Conversation
$privileges = $this->privilegeProvider->getPrivileges($visibilityConstraints); | ||
if (!$privileges->isContentStreamAllowed($contentStreamId)) { | ||
throw new \RuntimeException(sprintf('No access to content stream "%s"', $contentStreamId->value), 1681306937); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does it make sense to check the privileges here already?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say yes (if we can, like it is done here). Ofc people could work around this if they manually instanciated ContentSubgraph; but we could never prevent this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rename to sth like $this->privilegeProvider->isFetchingOfNodesAllowedInSubgraph($contentStreamId, $dimensionSpacePoint)
(consider using result object for more context)
/** | ||
* @api | ||
*/ | ||
final class Privileges |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is missing the "restriction edge attributes" still
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(see #4557 -> but they will be called node tags as discussed)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename to sth like ReadPrivileges
if (!$privilegeProviderFactory instanceof PrivilegeProviderFactoryInterface) { | ||
throw InvalidConfigurationException::fromMessage('privilegeProvider.factoryObjectName for content repository "%s" is not an instance of %s but %s.', $contentRepositoryId->value, PrivilegeProviderFactoryInterface::class, get_debug_type($privilegeProviderFactory)); | ||
} | ||
return $privilegeProviderFactory->build($contentRepositoryId, $contentRepositorySettings['userIdProvider']['options'] ?? [], $userIdProvider, $contentRepositoryRegistry); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PrivilegeProvider
currently has a dependency to...
- the
UserIdProvider
to load policies of the authenticated user - the
ContentRepositoryRegistry
to retrieve ContentStreams for the user (viaWorkspaceFinder
)..
This is nasty
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UserIdProvider is fine IMHO, ContentRepositoryRegistry should go away. What about making the $contentRepository available to the provider at runtime? would IMHO be easier and more consistent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@skurfuerst can you clarify what you mean by "available [...] at runtime"? A parameter of the PrivilegeProvider::getPrivileges()
method? But than the ContentGraph would need to pass it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, that's what I thought - similar to what we did in the command handlers. But you are right, the projection does not know it yet...
/** | ||
* @internal | ||
*/ | ||
final class FakePrivilegeProvider implements PrivilegeProviderInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really a FakePrivilegeProvider
but a first implementation to test content stream access
|
||
$privileges = Privileges::create(); | ||
|
||
$userWorkspace = $contentRepository->getWorkspaceFinder()->findOneByWorkspaceOwner($userId->value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This won't necessarily return the actual user workspace, but any shared workspace with the same owner.
Also this won't include base workspaces currently
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dangerous code - people without user workspace will automatically be admins. let's discuss :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hehe, good catch. That can't stay like this ofc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
really really cool ❤️ ❤️ ❤️ ❤️
$privileges = $this->privilegeProvider->getPrivileges($visibilityConstraints); | ||
if (!$privileges->isContentStreamAllowed($contentStreamId)) { | ||
throw new \RuntimeException(sprintf('No access to content stream "%s"', $contentStreamId->value), 1681306937); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say yes (if we can, like it is done here). Ofc people could work around this if they manually instanciated ContentSubgraph; but we could never prevent this.
{ | ||
private function __construct( | ||
public readonly ?ContentStreamIds $allowedContentStreamIds, | ||
public readonly ?ContentStreamIds $disallowedContentStreamIds, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is the semantics of having both of these lists? :) I don't fully understand this yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Me neither :)
But I thought, that might need both. an allow list and a deny list..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at this after a break, I think the terms "allowed" and "disallowed" are not helpful.
i.e. what does $privileges->isContentStreamAllowed($contentStreamId)
exactly mean?
Also we should re-evaluate whether we really need allow- and deny list
if ($this->contentStreamPrivilege === null) { | ||
return true; | ||
} | ||
return $this->contentStreamPrivilege->allowedContentStreamIds->contain($contentStreamId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, I am not so sure about this one - IMHO this must be done lazily (otherwise we could under some scenarios have REALLY long lists of contentStreamIds; where only a single one will be relevant here)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't it just the currently active Content Streams of the user workspace (and its base workspace(s))?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rename to sth. that makes clearer that this is about filtering nodes
if (!$privilegeProviderFactory instanceof PrivilegeProviderFactoryInterface) { | ||
throw InvalidConfigurationException::fromMessage('privilegeProvider.factoryObjectName for content repository "%s" is not an instance of %s but %s.', $contentRepositoryId->value, PrivilegeProviderFactoryInterface::class, get_debug_type($privilegeProviderFactory)); | ||
} | ||
return $privilegeProviderFactory->build($contentRepositoryId, $contentRepositorySettings['userIdProvider']['options'] ?? [], $userIdProvider, $contentRepositoryRegistry); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UserIdProvider is fine IMHO, ContentRepositoryRegistry should go away. What about making the $contentRepository available to the provider at runtime? would IMHO be easier and more consistent.
|
||
$privileges = Privileges::create(); | ||
|
||
$userWorkspace = $contentRepository->getWorkspaceFinder()->findOneByWorkspaceOwner($userId->value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dangerous code - people without user workspace will automatically be admins. let's discuss :)
A couple of ideas @skurfuerst and I just came up with: We should change the interface PrivilegeProviderInterface {
public function isFetchingOfNodesAllowedInSubgraph(ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): PrivilegeResult;
public function isCommandAllowed(CommandInterface $command): PrivilegeResult;
} (potentially split into two individual interfaces) This should make it possible to deal with all 4 security related categories we came up with: 1. Prevent certain commands to be handledThis could be enforced with a single call to the Examples
1a. Determine whether a certain command is allowedThis would not be part of the core itself – Instead the surrounding framework (e.g. Neos) could call their implementation of the Examples
2. Hide certain nodes depending on the contextThis will be enforced via "Subtree Tags" (#4550). Examples
3. Hide UI elements depending on the contextLike above this would not be a concern of the CR Examples
|
Closing in favor of #5298 |
Related: #3732