- Notifications
You must be signed in to change notification settings - Fork8
sandstorm/NeosAcl
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
This package implements dynamic Access Control Lists for Neos Roles.
The development of this package was sponsored byujamii andqueo.
Main features:
- Switch
RestrictedEditor
to an allowlist-only permission approach. By installing this package, theRestrictedEditor
isno longer allowed to change any content. - Configure dynamic roles through a Neos backend module.
- Permissions on the node tree, workspaces and dimensions possible.
- Permissions work predictably with sane defaults and purely additive logic.
- Install the package:
composer require sandstorm/neosacl
- Run the migrations
./flow doctrine:migrate
- Log in with an admin account and visit the new menu entry 'Dynamic Roles'
Initial (Package) Setup
- Clone this package as
Sandstorm.NeosAcl
in the DistributionPackages of a Neos 4.3 or later installation - Add it to
composer.json
as"sandstorm/neosacl": "*"
- Run
composer update
Initial React Setup
cd Resources/Private/react-acl-editoryarnyarn dev
Then, log into the backend of Neos, and visit the module "Dynamic Roles".
The basic idea was the following: Hook intoPolicyService::emitConfigurationLoaded
, and modify the$configuration
array (introduce new rolesand privilegeTargets). This basically worksat runtime - however there is a problem with dynamic MethodPrivilege enforcement, which isexplained below and by the following diagram:
- Background: An implementation of
PointcutFilterInterface
can - during compile time of Flow - decide which classesand methods match for a certain aspect.- This is used in
PolicyEnforcementAspect
(which is the central point for enforcingMethodPrivileges). - There, the
MethodPrivilegePointcutFilter
is referenced. - The
MethodPrivilegePointcutFilter
asks thePolicyService
for all configuredMethodPrivilege
s - and ensuresAOP proxies are built for these methods.
- This is used in
- Side Effect: Now, during building up the pointcut filters, the
MethodPrivilegePointcutFilter
additionally builds upa data structuremethodPermissions
- which remembers whichMethodPrivileges
are registered for which method.- This data structure is storedpersistently in the
Flow_Security_Authorization_Privilege_Method
cache. - At runtime, for a class which is intercepted by
PolicyEnforcementAspect
, all configuredMethodPrivilege
s areinvoked - and they have to quickly decide if they matchthis particular call-site. - This is done using the
methodPermissions
data structure from theFlow_Security_Authorization_Privilege_Method
cache.
- This data structure is storedpersistently in the
- If a
MethodPrivilege
is defined dynamically at runtime, then themethodPermissions
data structure is missingthe information that this new privilege should be invoked for certain methods. - NOTE: You can only dynamically add
MethodPrivileges
for call-siteswhich are already instrumented by AOP;because otherwise the code will never get invoked (because of missing proxies).
We are mostly working withEditNodePrivilege
etc. - so why does this apply there?
EditNodePrivilege
has an internalMethodPrivilege
which takes care of the method call enforcement part;i.e. preventing you to call e.g.NodeInterface::setProperty()
if you do not have the permission to do so.
Furthermore, to make this idea work, thePolicy.yaml
of this package defines a catch-allSandstorm.NeosAcl:EditAllNodes
PrivilegeTarget - so AOP will instrument the corresponding methods ofNodeInterface
. This catch-all makes sensein any case, because this switches the security frameworkto an allowlist-only approach
- making it easier to grasp.
In order to make the dynamic policy enforcement work, we need to add custom stuff to themethodPermissions
- forthe dynamically added roles.
The post-processing of themethodPermissions
is done using a custom cache frontend (SecurityAuthorizationPrivilegeMethodCacheFrontend
).
Method privileges internally can use dynamic AOP Runtime Expressions (in case you check for method parameters). EspeciallytheMethodPrivilege
- which is attached to theRemoveNodePrivilege
- uses the following expression code:
return'within(' . NodeInterface::class .') && method(.*->setRemoved(removed == true))';
Theremoved == true
part is a so-calledAOP Runtime Expression.
This is internally implemented using theFlow_Aop_RuntimeExpressions
"cache", which is pre-filled again during the compiletime (which is a nasty side-effect).
Thus, in our case we need to again implement a custom cache frontend (AopRuntimeExpressionsCacheFrontend
),using the runtime expressions of the base configuration, which exists properly.