Read-Only Websites in Policy

Hello everyone,

we have about 50 different websites in our Neos system which we want to restrict with policies. These websites belong to different business areas. The structure in a simplified version looks like this:

Business Area 1

  • Site A
  • Site B

Business Area 2

  • Site C
  • Site D

… and so on.

There are 2 main roles in our system:

  • Internal users should be able to edit all websites that belong to their business area and see all other websites outside of their business area as read-only.
    Example: Edit all sites in Business Area 1 (Site A, Site B). Read-only all sites in Business Area 2 (Site C, Site D).
  • External users don’t get whole business areas. They should only be able to see and edit specific websites.
    Example: Edit Site C. Every other site is not visible.

Currently our Policy.yaml looks like this:

Summary
privilegeTargets:
  'Neos\Neos\Security\Authorization\Privilege\NodeTreePrivilege':
      'Project:SiteARead':
        matcher: isDescendantNodeOf('/sites/site-a')
      'Project:SiteBRead':
        matcher: isDescendantNodeOf('/sites/site-b')
      'Project:SiteCRead':
        matcher: isDescendantNodeOf('/sites/site-c')
      'Project:SiteDRead':
        matcher: isDescendantNodeOf('/sites/site-d')
  
  'Neos\ContentRepository\Security\Authorization\Privilege\Node\EditNodePrivilege':
      'Project:SiteAEdit':
        matcher: isDescendantNodeOf('/sites/site-a')
      'Project:SiteBEdit':
        matcher: isDescendantNodeOf('/sites/site-b')
      'Project:SiteCEdit':
        matcher: isDescendantNodeOf('/sites/site-c')
      'Project:SiteDEdit':
        matcher: isDescendantNodeOf('/sites/site-d')

roles:
  'Project:SiteARead':
      privileges:
        -
          privilegeTarget: 'Project:SiteARead'
          permission: GRANT
  'Project:SiteAEdit':
      parentRoles: ['Project:SiteARead']
      privileges:
        -
          privilegeTarget: 'Project:SiteAEdit'
          permission: GRANT

  'Project:SiteBRead':
      privileges:
        -
          privilegeTarget: 'Project:SiteBRead'
          permission: GRANT
  'Project:SiteBEdit':
      parentRoles: ['Project:SiteBRead']
      privileges:
        -
          privilegeTarget: 'Project:SiteBEdit'
          permission: GRANT

  'Project:SiteCRead':
      privileges:
        -
          privilegeTarget: 'Project:SiteCRead'
          permission: GRANT
  'Project:SiteCEdit':
      parentRoles: ['Project:SiteCRead']
      privileges:
        -
          privilegeTarget: 'Project:SiteCEdit'
          permission: GRANT

  'Project:SiteDRead':
      privileges:
        -
          privilegeTarget: 'Project:SiteDRead'
          permission: GRANT
  'Project:SiteDEdit':
      parentRoles: ['Project:SiteDRead']
      privileges:
        -
          privilegeTarget: 'Project:SiteDEdit'
          permission: GRANT

 'Project:BusinessArea1Read':
    parentRoles: [
      'Project:SiteARead',
      'Project:SiteBRead'
    ]
 'Project:BusinessArea1Edit':
    parentRoles: [
      'Project:BusinessArea1Read',
      'Project:SiteAEdit',
      'Project:SiteBEdit'
    ]

 'Project:BusinessArea2Read':
    parentRoles: [
      'Project:SiteCRead',
      'Project:SiteDRead'
    ]
 'Project:BusinessArea2Edit':
    parentRoles: [
      'Project:BusinessArea2Read',
      'Project:SiteCEdit',
      'Project:SiteDEdit'
    ]

 'Project:AllRead':
    parentRoles: [
      'Project:BusinessArea1Read',
      'Project:BusinessArea2Read'
    ]
 'Project:AllEdit':
    parentRoles: [
      'Project:AllRead',
      'Project:BusinessArea1Edit',
      'Project:BusinessArea2Edit'
    ]

This policy works great for externals since we assign the specific site roles. The problem we have are the internal roles.

We are not able to show the sites read-only. They are either editable or not visible at all.

The ReadNodePrivilege is not recommended for use and doesn’t work anyway. As soon as we change one of the NodeTreePrivileges in the example above to ReadNodePrivilege, the whole site throws an error and doesn’t work anymore.

The NodeTreePrivilege extends the EditNodePrivilege so it already provides edit permissions. Making the site visible, but not editable works only if we grant the NodeTreePrivilege and deny the EditNodePrivilege. Denying privilegeTargets is not recommened for obvious reasons and it makes it harder for us to use the “big” roles like BusinessArea1Read or AllRead since DENY > GRANT.

I can only think of trying it with MethodPrivileges or writing a Flow Command which adds a bunch of specific site roles to users based on a given business area instead of writing big roles in the Policy.yaml.

Do you guys have any other ideas on how to tackle this problem? :slight_smile:

Hi @steezie

In general, I think your project could benefit from using the Sandstorm Neos ACL package. You can use it to add dynamic ACLs. By default, it sets the EditNodePrivilege to an allowlist-only approach.

You could combine this an EntityPrivilege/NodeTreePrivilege for the sites and then assign both roles from your static and dynamic policies.

The only pitfall here is that you have 50 sites, which makes creating the dynamic roles a little cumbersome. But I guess it shouldn’t be hard to create the dynamic ACL through a command you create specifically for your site. It would read out all your root page nodes and use those to create an ACL.

1 Like

Hi @steezie,

you should really have a look at Sandstorm Neos ACL mentioned by @lorenzulrich here. But if you do not want to have dynamic roles you need to think about the following points at least:

  • The NodeTreePrivilege extends from the EditNodePrivilege, so you also need to create privilegeTargets for the EditNodePrivilege explicitly. Maybe you will also need to add some for the ReadNodePrivilege.
  • I would really recommend using the whitelist approach of mentioned package (see Sandstorm.NeosAcl Policy.yaml). That helps finding missing spots a lot.
1 Like

Thank you both! Creating dynamic roles shouldn’t be a much of a problem since we already have a command which creates the Policy.yaml dynamically. I’ll have a look into the package. :smiley:

Hello again,

the package doesn’t seem to help in my case, but I implemented the whitelist approach so it was still helpful! However I did find another solution to my problem.

I added a check for the NodeType Startseite to the privilegeTargets:

privilegeTargets:
  'Neos\Neos\Security\Authorization\Privilege\NodeTreePrivilege':
      'Project:SiteARead':
        matcher: isDescendantNodeOf('/sites/site-a') && nodeIsOfType('Project.Nodetypes:Document.Startseite')
      'Project:SiteBRead':
        matcher: isDescendantNodeOf('/sites/site-b') && nodeIsOfType('Project.Nodetypes:Document.Startseite')
      'Project:SiteCRead':
        matcher: isDescendantNodeOf('/sites/site-c') && nodeIsOfType('Project.Nodetypes:Document.Startseite')
      'Project:SiteDRead':
        matcher: isDescendantNodeOf('/sites/site-d') && nodeIsOfType('Project.Nodetypes:Document.Startseite')

This way the NodeTreePrivilege (which also provides EditNodePrivileges) only matches nodes with the type Project.Nodetypes:Document.Startseite and the rest stays read-only. The root node of each of our websites has this type.

I also created an EditNodePrivilege to edit the Startseite again which is GRANTED for Project:Administrator and DENIED for Project:User. You need custom roles since Neos.Neos:Administrator inherits Neos.Neos:Editor and denying it for the Editor would also deny it for Administrator.

privilegeTargets:
  'Neos\ContentRepository\Security\Authorization\Privilege\Node\EditNodePrivilege':
      'Project:StartseiteEdit':
        matcher: nodeIsOfType('Project.Nodetypes:Document.Startseite')

roles:
  'Project:User':
    parentRoles: ['Neos.Neos:Editor']
    privileges:
      -
        privilegeTarget: 'Vcg.GenericContentPortal:StartseiteEdit'
        permission: DENY
  'Project:Administrator':
    parentRoles: ['Neos.Neos:Administrator']
    privileges:
      -
        privilegeTarget: 'Vcg.GenericContentPortal:StartseiteEdit'
        permission: GRANT