PHPackages                             sandstorm/neosacl - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. sandstorm/neosacl

ActiveNeos-package[Authentication &amp; Authorization](/categories/authentication)

sandstorm/neosacl
=================

2.3.1(2y ago)1463.4k↓31.8%9[9 issues](https://github.com/sandstorm/NeosAcl/issues)[1 PRs](https://github.com/sandstorm/NeosAcl/pulls)JavaScript

Since Mar 16Pushed 1mo ago10 watchersCompare

[ Source](https://github.com/sandstorm/NeosAcl)[ Packagist](https://packagist.org/packages/sandstorm/neosacl)[ RSS](/packages/sandstorm-neosacl/feed)WikiDiscussions master Synced 2d ago

READMEChangelog (10)Dependencies (2)Versions (25)Used By (0)

Sandstorm Neos ACL
==================

[](#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)](./Documentation/listing.png)

[![edit](./Documentation/edit.png)](./Documentation/edit.png)

Installation
------------

[](#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
-----------

[](#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

[](#internal-implementation-details)

#### Implementing Dynamic Node Privileges and MethodPrivileges

[](#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)](./Documentation/DynamicMethodPrivileges.svg)

#### How do Method Privileges work

[](#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

[](#whats-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

[](#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

[](#implementation)

The post-processing of the `methodPermissions` is done using a custom cache frontend (`SecurityAuthorizationPrivilegeMethodCacheFrontend`).

#### Implementing dynamic AOP Runtime Expressions

[](#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:

```
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.

###  Health Score

47

—

FairBetter than 93% of packages

Maintenance48

Moderate activity, may be stable

Popularity40

Moderate usage in the ecosystem

Community20

Small or concentrated contributor base

Maturity66

Established project with proven stability

 Bus Factor1

Top contributor holds 79.2% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~108 days

Recently: every ~193 days

Total

15

Last Release

778d ago

Major Versions

1.0.4 → 2.0.02021-01-06

1.0.5 → 2.0.22021-02-09

### Community

Maintainers

![](https://www.gravatar.com/avatar/2ced0d63cfdae881c32128c7f66451a013d3e24d9eed210d6a846b6d8e95fa3b?d=identicon)[sandstorm](/maintainers/sandstorm)

---

Top Contributors

[![skurfuerst](https://avatars.githubusercontent.com/u/190777?v=4)](https://github.com/skurfuerst "skurfuerst (42 commits)")[![lorenzulrich](https://avatars.githubusercontent.com/u/1816023?v=4)](https://github.com/lorenzulrich "lorenzulrich (6 commits)")[![Pingu501](https://avatars.githubusercontent.com/u/12086990?v=4)](https://github.com/Pingu501 "Pingu501 (2 commits)")[![Benjamin-K](https://avatars.githubusercontent.com/u/3098031?v=4)](https://github.com/Benjamin-K "Benjamin-K (1 commits)")[![DrillSergeant](https://avatars.githubusercontent.com/u/883552?v=4)](https://github.com/DrillSergeant "DrillSergeant (1 commits)")[![mberhorst](https://avatars.githubusercontent.com/u/2861236?v=4)](https://github.com/mberhorst "mberhorst (1 commits)")

---

Tags

dynamic-rolesneosneoscmsrestrictionsuser-management

### Embed Badge

![Health badge](/badges/sandstorm-neosacl/health.svg)

```
[![Health](https://phpackages.com/badges/sandstorm-neosacl/health.svg)](https://phpackages.com/packages/sandstorm-neosacl)
```

###  Alternatives

[neos/neos

An open source Content Application Platform based on Flow. A set of core Content Management features is resting within a larger context that allows you to build a perfectly customized experience for your users.

1151.0M777](/packages/neos-neos)[sandstorm/neostwofactorauthentication

1327.0k](/packages/sandstorm-neostwofactorauthentication)[neos/fusion-form

Fusion Form

19776.4k47](/packages/neos-fusion-form)[neos/media

The Media package

101.2M53](/packages/neos-media)[sandstorm/usermanagement

Neos and Flow package for user management, login/logout, password reset and user activation

3928.9k](/packages/sandstorm-usermanagement)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
