Pyramid ACL

Background

In Pyramid, every traversal resource might have an Access Conrol List, which is a set of rules containing

  • an action (Allow, Deny)
  • a principal (an arbitrary string usually denoting a role, group or userid)
  • a set of permissions to allow or deny

An example of a rule:

(Allow, 'some_group', 'some_permission')

Every view in pyramid might be protected by a permission.
The permission is tested against the principals of the current web user, when the view is entered (after traversal and view lookup).

Overview

In Portal we have two different levels of Access Roles:

  • global web user roles
  • instance level object roles

The names of the permissions should reflect the action of the web user (e.g. 'edit_artist') and should be unique, if possible.
The only exception is the permission 'authenticated', which is granted for web users after login and might be used for all views, which should be accessible for authenticated web users in general.

Global Level

A web user has one or more roles corresponding to the portal plugins (e.g. licenser for portal.plugin.repertoire).
Each plugin should have it's own role to ensure that the web user is only able to use the corresponding parts of the application.

Persistence

The web user roles are persisted in tryton:

  • WebUserRole (web.user.role)

The web user roles are imported via collection_society.xml.
The imported standard roles are:

  • licenser (for portal.plugin.repertoire)
  • licensee (for portal.plugin.events)

Retrieval

The web user roles can be retrieved via the roles method of the WebUser Model:

web_user.current_roles(request)

For convenience, the list of roles are added as request property:

request.roles

Application

The principals are automatically assigned to a web user via callback of the groupfinder method of WebUser, after the web user has been authenticated.

To set permissions on a resource to a principal:

class SomeResource(ResourceBase):

    __acl__ = [
        (Allow, '<PRINCIPAL>', (
            '<PERMISSION1>',
            '<PERMISSION2>',
            ...
        )),
    ]

To prevent inheritance of ACL from parent resources (e.g. for the main resource of a plugin), add DENY_ALL as the last rule:

class SomeResource(ResourceBase):

    __acl__ = [
        ...,
        DENY_ALL
    ]

Instance Level

Every web user might take one or more access roles for each acl capable object (e.g. Artist, Release, Creation, Content) via an access control entry.
Every access role is associated with a set of permissions to be granted.

Additional to the permissions codes used in the pyramid views directly (e.g. view_artist, view_release, etc.), there are permissions to reflect transitive rights for objects down the hierarchy (e.g. view_arist_creations, view_arist_releases).

Persistence

All access related objects are persisted in tryton and assigned to a model via the AccessControlList mixin, see also the database model:

  • AccessControlEntry (ace)
  • AccessRole (ace.role)
  • AccessPermission (ace.permission)

The access permissions are imported via collection_society.xml and readonly for the tryton client, as the codes of the permissions are hardcoded.
The access roles are free to edit and each role might be associated with any set of existing permissions.
The names of the access roles should be chosen according to the semantics of the role to be taken.

The imported standard roles are:

  • Administrator (all permissions)
  • Stakeholder (view related permissions only)

Retrieval

To retrieve permission related information, every acl capable model has two instance methods:

  • instance.permits(): asks the instance, if a web user has a certain permission:

    instance.permits(web_user, permission_code)
    
  • instance.permissions(): asks the instance, what permissions a web_user has:

    instance.permissions(web_user, valid_permission_codes=False)
    

    The returned permissions might be constrained to permissions in the list of valid codes to tighten security.

An example domain to include transitive permissions:

[
    'OR',
    [
        ('acl.web_user', '=', web_user_id),
        ('acl.roles.permissions.code', '=', '<PERMISSION>')
    ], [
        ('<FIELD>.acl.web_user', '=', web_user_id),
        ('<FIELD>.acl.roles.permissions.code', '=', '<PERMISSION>'),
    ]
]

Application

The permissions are set within the __acl__ method of a resource and bound to the authenticated userid:

class SomeResource(ResourceBase):
    _permit = ['only', 'allow', 'these', 'permissions']

    def __acl__(self):
        return [
            (Allow, self.request.authenticated_userid,
                <INSTANCE>.permissions(self.request.web_user, self._permit))
        ]