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)1557.7k—2.7%9[5 issues](https://github.com/sandstorm/NeosAcl/issues)[1 PRs](https://github.com/sandstorm/NeosAcl/pulls)JavaScript

Since Mar 16Pushed 7mo ago10 watchersCompare

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

READMEChangelog (10)Dependencies (2)Versions (22)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

44

—

FairBetter than 92% of packages

Maintenance38

Infrequent updates — may be unmaintained

Popularity40

Moderate usage in the ecosystem

Community20

Small or concentrated contributor base

Maturity65

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

732d 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

[gesdinet/jwt-refresh-token-bundle

Implements a refresh token system over Json Web Tokens in Symfony

70516.4M35](/packages/gesdinet-jwt-refresh-token-bundle)[illuminate/auth

The Illuminate Auth package.

9327.3M1.0k](/packages/illuminate-auth)[beatswitch/lock

A flexible, driver based Acl package for PHP 5.4+

870304.7k2](/packages/beatswitch-lock)[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.

116989.0k674](/packages/neos-neos)[amocrm/amocrm-api-library

amoCRM API Client

182728.5k6](/packages/amocrm-amocrm-api-library)[visanduma/nova-two-factor

Nova Two Factor Authentication

56621.3k](/packages/visanduma-nova-two-factor)

PHPackages © 2026

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