Permissions & Access Management UI

Hi Neos Community,

we currently have a Client who wants to have control over Permission-Management for their Users.
Basically they want to control everything which is possible with the policy.yaml in a User Interface.

Questions:

  1. Are there any plans around making Permissions editable by the Backend-User?
  2. Is it possible right now to implement something like this by writing a custom Backend Module?
  3. Would you accept funding to implement this into Neos Core?

Thank you for your help :slight_smile:
Torben

1 Like

Hey Torben,

welcome here in the Neos community :heart:

I’ve recently thought about that concept, and I think this is doable. I’ve created a WIP to test out the concept at https://github.com/sandstorm/NeosAcl - but I guess this won’t make sense to anybody except me as it is very rough. In my idea though, you would not be able to make all policy permissions editable in the Neos UI, but a “sensible subset” - mostly dealing with node permissions. Could you elaborate some more on the use cases?

I personally am quite busy right now; but I’ve discussed the concept with @markusguenther at the last sprint :slight_smile: - so maybe he wants to implement something? :slight_smile: I’d be happy to provide my thoughts and guidance though!

All the best,
Sebastian

1 Like

Hi Torben and congratulations to your first post here :slight_smile:

Basically they want to control everything which is possible with the policy.yaml in a User Interface.

I totally agree that we are missing some UI features for permission management and visualization. But personally I’d be very strongly against some kind of fully-fledged Policy Editor for productive environments because that could be quite dangerous and error prone.

We used to have the Roles defined in the database but changed this a long time ago because we now consider a Role something that should not be specific to one instance of neos but rather a concept of the whole application.
The same thing holds true for privilegeTargets IMO.

Here’s my vision of some measures we could take to improve the situation:

  1. Improve the UI so that it is much easier to visualize the permissions of each individual user
  2. Create some ACL Editor (maybe based on the one Sebastian shared) but not meant to be used within a productive site (IMO) but more as a kickstarter that can export Policy.yaml files
  3. Introduce the notion of a User Group

The last part is IMO the most profound and it would require a change in the Flow Core.
The idea is basically that you can assign a Backend User to some Role (like today) but at the same time be able to specify some parameters. For example: User X has the role NewsEditor for category xyz

It is described here. Unfortunately we never got around finishing that because there was no urgent need. But as it comes up from time to time I’d love to tackle this one at some point…

The question is, whether this would solve your specific use case. So, like Sebastian, I’d like to know more about that :slight_smile:

1 Like

Hi :sunny:

first of all thank you both, for the kind replies.
It already sounds quite promising :blush:

I will be in a meeting today at 15:30, in which we will discuss the specific needs of our client(s).
After that I will post the results here. Maybe then we can discuss possible implementation-details a little better.

:heart:, Torben

1 Like

Hey :blush:

you can find our clients needs listed below, ordered from highest to lowest priority.

  1. New Roles can be created by the end-user (in Production)

  2. Permissions can be granted per Page (Neos.Neos:Document)
    2.1 Permissions are „Create“, „Read“, „Update“, „Delete“ (I thought about something like this for the UI:)


    2.2 Sub-Pages inherit permissions from the next higher specified permissions
    2.3 NodeTypes placed on a Page inherit the permissions from the Page

  3. Roles do have „global“ Permissions
    3.1 May see the Backend
    3.2 May see the „Media“-Module
    3.3 May see „User Management“-Module
    3.4 … (could be a list of all Backend-Modules)

  4. New Role: „Permission / Role Admin“
    4.1 May create new Roles
    4.2 May change Permissions on Pages

  5. New View: „Which users are in which role“

  6. Lock user from logging in
    6.1 Lock with checkbox
    6.2 Lock after specific Date

  7. New Role „Everyone“ or „Anonymous“ (every user who is not logged in)
    7.1 This may come in handy, when showing content only to users who are logged in. But this is probably already possible in some other way, right?

  8. Password Guidelines
    8.1 minimum x characters
    8.2 minimum x numbers
    8.3 minimum x special characters

  9. A user may only create users with the same or „lower“ roles
    This would imply some sort of role-hierarchy and is probably too hard to implement. But for the sake of completion, I wrote it down anyway :smile:

Thank you for your time and effort :heart:,
Torben

1 Like

Thanks for putting this together. Could you elaborate on a higher level on what they are trying to achieve. Especially the “add new roles in production” part.
As I wrote above I’m convinced that this is a dangerous path to follow and one that wouldn’t easily scale to multiple (testing) stages.

Hey Bastian :blush:,

the Problem is, that the Website is for a City.
In most cases cities don’t have their own editors. They give an Account to for example the press officer of the local fire station. He then has to only have permissions to edit (and not publish) one specific page of the website.

They can’t provide us all the possible Accounts with their permissions in advance, so they have to be created in production.
Also sometimes a new account has to be created relatively fast so we cant always add them for them.

:heart:,
Torben

That’s exactly one use case that would be possible with the suggested groups feature described above.

You would define a role with the privilege to edit certain node types and worlspaces like today. And when they assign that role to an account in the backend, they would have to specify the required parameters (in this case for example the root node for which this privilege applies).

I don’t know 100% how the UI for this should look like, but I have some rough ideas and could provide a mockup when I’m back from holidays

You are absolutely right. I didn’t really get it until now. :blush:

What do you think has to be done to move this forward?
I would love to help implement something like this. But I am more a Frontend-Dev, so I could probably only help with the UI…or would need some help in the Backend :smile:

Right now we are planning for the website to go online next year in summer.
Do you think, we could have something at least usable by then?

That would be awesome :+1:
I could probably also get a Designer from my agency to provide some tips, if there is a need for that :slightly_smiling_face:

But for now: Happy holidays :sunny:

1 Like

Hi :blush:

I did take a look at the issue on github and now I have a few questions. I could be wrong at any time, because I don’t 100% understand the underlying construct but i thought i would share them and maybe move the issue a bit forward (hopefully) :smile: or at least get a better understanding of what needs to be done.

  1. Would we have to create a new Database-Table including the accountIdentifier, role and parameters or can this be represented / saved another way?

  2. Regarding backwards compatibility. Would it be a good idea, to keep the parameters in the Policy.yaml and just make it possible “override” them later on? That way, the parameters in the Policy.yaml would act like a fallback / default and you wouldn’t have to migrate anything, right?

  3. Is this Forum the right place for discussions like this or should this go into the issue itself?

Thank you,
Torben

Hi,

I might add my two cents since we’re tackling a similar challenge right now but it’s our Flow-only project, so the Neos UI stuff is something we don’t have in that case.

Before I start that - regarding your questions: The accountIdentifier is part of the default Flow-Accounts if I remember correctly from the top of my head. The rest may be stuff that needs to be created. Can’t really answer the other questions but I think this forum is fine and if you have something more specific can be moved to GitHub and be referenced there.

In our case it’s not a city but it’s large corporations where they want to have the chance to grant access to only some functions (MethodPrivileges) and some entities (EntityPrivilege).
E.g. imagine a car manufacturer that is managed centrally or a large hotel chain. The car manufacturer wants to provide our service to dealerships worldwide and also wants to make sure that everything can be managed centrally in general but each local subsidiary has the chance to change some things. E.g. dealership one may be able to create new objects whereas dealership two is only allowed to edit stuff and dealership three is not allowed to create or edit anything because they pay the headquarter to manage it for them but wants to see statistics but for billing purposes, they need to access that part which is not important for the other dealerships.
That’s roughly the situation we need to solve and we’re already half-way through solving it - the thing that’s missing is a more dynamic way for MethodPrivileges.

What we did so far:

  • We’ve created a database-based Role model. Users can assign entities to that role which defines what a user may access (what objects, doesn’t tell anything about what features, yet).
  • Something similiar like this is being used: https://gist.github.com/bwaidelich/731dc47a02027346d5a4153010f62e34
    In our custom context we basically read the role (our database-role) that is assigned to a user and read all IDs of assigned objects which then results in doctrine filters applied by the EntityPrivilege. If the user has no database-role assigned or the database-role doesn’t have any object-assignments we assume that the user may access all objects of the customer he’s assigned to.
  • We’ve defined what features do exist. This is always a combination of some actions currently (usually CRUD, e.g. someone can create an object of Type A he needs to be able to access action A, B and C of Controller D). This means there’s one methodPrivilege for each function - which is a lot. Right now there are around 70 privileges.

What we need to solve:
Basically we need a way of managing that stuff and give users a way to define who has access to what features and currently there’s some different ways I can imagine:

  • Our database-role is extended in a way that the functions can be selected. This would mean that either the privileges can be selected or we create a role for each function I’ve described above which would then be assigned to the account in some way.
  • If privileges are selected instead of roles we would need a way of creating a dynamic role (a Flow role in this case). This role would then accept a class name as a parameter that is returning the privileges that are assigned to our database-role resulting in some sort of dynamic flow-role.
  • Don’t know? Maybe there’s a better way :slight_smile:

To be honest I don’t 100% grasp if this is similar to what Bastian described with the groups. Creating such dynamic role would keep the Policy.yaml a bit shorter which is bloated already.

Would be grateful if someone could think about this as well.
There’s also another topic on this forum regarding our entity privileges which I’ll update once we’re finished: EntityPrivilege persistence_object_identifier and in operator

Thanks,
David

Just to make clear what I mean with “dynamic role” is something like this:

roles:
  'Passcreator.Passcreator:BasicUser':
    dynamicPrivilegeReader: 'Passcreator\Security\Foo\Bar'
    privileges:
      -
        privilegeTarget: LoginActions
        permission: GRANT

The “dynamicPrivilegeReader” (forget the name for now :wink: ) would then return an array of privileges (PrivilegeInterface) that will be dynamically added to the privileges defined in the Policy.yaml which means the Role DTO of Flow would need to call a function of the class specified as privilege reader.

Theoretically - would something like this make sense or do you see problems there?
In general the customerUser object has assigned one or more roles that each contain a list of privilegeTargets that the user may access.
It works in my prototype but I just wanted to get feedback if possible if you see problems with that approach.
Basically I’m granting permission to the privilegeTarget if the user has assigned the role that contains the target.

/**
 * @Flow\Aspect
 */
class DynamicRoleAspect {

    /**
     * @var PartyService
     * @Flow\Inject
     */
    protected $partyService;

    /**
     * @var Context
     * @Flow\Inject
     */
    protected $securityContext;

    /**
     * @Flow\Before("method(Neos\Flow\Security\Authorization\Privilege\PrivilegeTarget->createPrivilege())")
     * @param JoinPointInterface $joinPoint
     */
    public function grantDynamicAccess(JoinPointInterface $joinPoint) {
        $account = $this->securityContext->getAccount();

        if($account !== null) {
            $customerUser = $this->partyService->getAssignedPartyOfAccount();

            // if logged in user has assigned the current privilege, set permission to GRANT
            if(in_array($joinPoint->getProxy()->getIdentifier(), $customerUser->getAssignedPrivileges())) {
                $joinPoint->setMethodArgument('permission', 'GRANT');
            }
        }
    }
}

Without (having the brain currently to be) looking into this more closely, I fear that too much dynamic/magic might make this really hard to debug & cache (e.g. Security\Context::contextHash.

I want to point to the “Groups” feature once more. Unfortunately there was never enough friction/itch to finish this, but I still think that the general concept could solve most of these issues as it would extend the notion of: “Account X has role Y” to “Account X has role Y for parameter Z”. And a parameter could be everything from a Content-Repository branch to a tenant (e.g. “account X is NewsReviewer in branch /news and NewsEditor in branch /company/news”)

Hey everybody,

interesting discussion!

@bwaidelich I am personally not sure whether groups actually solve this problem, because of the following reasoning.

As I’ve tried to formulate here, the following rules (mostly) apply to the permission system:

  • Rule 1: All “things” that are NOT matched by a Privilege Target are allowed.
  • Rule 2: All “things” matched by a Privilege Target are forbidden, if no explicit GRANTs or DENIES are given.
  • Rule 3: Always work with GRANT (Suggestion)
  • Rule 4: If several Privilege Targets apply to a thing, access is permitted, as soon as the user has granted ONE of these Privilege Targets …

(Sidenote: I feel that Rule 4 is not properly implemented by the EntityPrivileges, because the permissions are not "OR"ed together, but "AND"ed. Of course that would be really hard to change and totally breaking; but still it’s quite inconsistent currently IMHO).

My problem with groups is that by adding parameters to PrivilegeTargets, it is not possible anymore to find whether a “thing” is matched by a privilegeTarget or not, because you cannot know all possible parameter values beforehand.

So, that’s why unless proven otherwise, I think having groups will actually make the permission system more complex, not more easy. - and I wouldn’t even know how to build this properly.

@david.sporer - I am not fully sure if the dynamic privileges solve the issue fully; I’ve experimented with dynamic creation of policies here in this package (not fully working, but a rough draft).

If you like, we can have a call this or next week to discuss that topic in more depth? I’d definitley be interested in this :slight_smile: (and maybe others want to join as well - I’ve at least talked to @christianm and @markusguenther about it beforehand).

All the best,
Sebastian

I agree… Maybe it’s time to start working on a Security Framework 3.0 that incorporates our learnings (and works smoothly with the ES CR)

That’s right. It won’t work for privileges that prevent loading of doctrine entities. But once you have a resource instance (an entity or method call for example) it should be possible to tell which privileges are granted for the currently authenticated account.

But it’s quite possible that I miss some things… And after talking to David yesterday, we found out that his current problem wouldn’t be solved by this feature anyways.

Hey @bwaidelich,

You are right, although this is not my point:

In case something is matched by a privilege matcher, we abstain if you do not have a privilege for it. However, how can we determine if something is matched by a privilege matcher, if this depends on some run-time parameter? I think we can’t - and that’s my main problem.

That’s why in my mind, adding parameters would add a level of dynamicity which would be not really possible to grasp…

Or am I overlooking anything?

All the best,
Sebastian

I’d love to chat about it. I’m available tomorrow or Wednesday :slightly_smiling_face:

1 Like

I’ve created a Doodle to plan the call: https://doodle.com/poll/bdd9g2um2zau2qs3