Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/sandstorm/neosacl
https://github.com/sandstorm/neosacl
dynamic-roles neos neoscms restrictions user-management
Last synced: about 2 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/sandstorm/neosacl
- Owner: sandstorm
- Created: 2019-03-23T10:10:51.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2024-05-17T10:34:29.000Z (8 months ago)
- Last Synced: 2024-09-19T09:41:23.112Z (4 months ago)
- Topics: dynamic-roles, neos, neoscms, restrictions, user-management
- Language: JavaScript
- Homepage:
- Size: 4.78 MB
- Stars: 14
- Watchers: 12
- Forks: 8
- Open Issues: 8
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Sandstorm Neos ACL
This package implements dynamic Access Control Lists for Neos Roles.
The development of this package was sponsored by [ujamii](https://www.ujamii.com/) and [queo](https://www.queo.de).
Main features:
- Switch `RestrictedEditor` to an allowlist-only permission approach. By installing this package, the `RestrictedEditor` is
no 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.![listing](./Documentation/listing.png)
![edit](./Documentation/edit.png)
## Installation
1. Install the package:
```
composer require sandstorm/neosacl
```2. Run the migrations
```
./flow doctrine:migrate
```3. Log in with an admin account and visit the new menu entry 'Dynamic Roles'
## Development
**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-editor
yarn
yarn dev
```Then, log into the backend of Neos, and visit the module "Dynamic Roles".
### Internal Implementation Details
#### Implementing Dynamic Node Privileges and MethodPrivileges
The basic idea was the following: Hook into `PolicyService::emitConfigurationLoaded`, and modify the `$configuration` array (introduce new roles
and privilegeTargets). This basically works **at runtime** - however there is a problem with dynamic MethodPrivilege enforcement, which is
explained below and by the following diagram:![Concept](./Documentation/DynamicMethodPrivileges.svg)
#### How do Method Privileges work
- Background: An implementation of `PointcutFilterInterface` can - during compile time of Flow - decide which classes
and methods match for a certain aspect.
- This is used in `PolicyEnforcementAspect` (which is the central point for enforcing **MethodPrivileges**).
- There, the `MethodPrivilegePointcutFilter` is referenced.
- The `MethodPrivilegePointcutFilter` asks the `PolicyService` for all configured `MethodPrivilege`s - and ensures
AOP proxies are built for these methods.
- **Side Effect**: Now, during building up the pointcut filters, the `MethodPrivilegePointcutFilter` **additionally** builds up
a data structure `methodPermissions` - which remembers which `MethodPrivileges` are registered for which method.
- This data structure is stored **persistently in the `Flow_Security_Authorization_Privilege_Method` cache**.
- At runtime, for a class which is intercepted by `PolicyEnforcementAspect`, all configured `MethodPrivilege`s are
invoked - and they have to quickly decide if they match **this particular call-site**.
- This is done using the `methodPermissions` data structure from the `Flow_Security_Authorization_Privilege_Method` cache.#### What's the problem with dynamically added MethodPrivileges
- If a `MethodPrivilege` is defined dynamically at runtime, then the `methodPermissions` data structure is missing
the information that this new privilege should be invoked for certain methods.
- NOTE: You can only dynamically add `MethodPrivileges` for call-sites **which are already instrumented by AOP**;
because otherwise the code will never get invoked (because of missing proxies).We are mostly working with `EditNodePrivilege` etc. - so why does this apply there?
- `EditNodePrivilege` has an internal `MethodPrivilege` **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, the `Policy.yaml` of this package defines a catch-all `Sandstorm.NeosAcl:EditAllNodes`
PrivilegeTarget - so AOP will instrument the corresponding methods of `NodeInterface`. This catch-all makes sense
in any case, because this switches the security framework [to an allowlist-only approach](https://docs.neos.io/guide/manual/backend-permissions/real-world-examples#user-rights-for-part-of-a-page-tree)
- making it easier to grasp.#### The goal
In order to make the dynamic policy enforcement work, we need to add custom stuff to the `methodPermissions` - for
the dynamically added roles.#### Implementation
The post-processing of the `methodPermissions` is done using a custom cache frontend (`SecurityAuthorizationPrivilegeMethodCacheFrontend`).
#### Implementing dynamic AOP Runtime Expressions
Method privileges internally can use dynamic AOP Runtime Expressions (in case you check for method parameters). Especially
the `MethodPrivilege` - which is attached to the `RemoveNodePrivilege` - uses the following expression code:```php
return 'within(' . NodeInterface::class . ') && method(.*->setRemoved(removed == true))';
```The `removed == true` part is a so-called *AOP Runtime Expression*.
This is internally implemented using the `Flow_Aop_RuntimeExpressions` "cache", which is pre-filled again during the compile
time (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.