Permissions

Cliquet provides a mechanism to handle authorization on the stored objects.

This section gives details about the behaviour of resources in regards to permissions.

User resource

This is the simplest one, as presented in the resource section.

When using a cliquet.resource.UserResource, every authenticated user can manipulate and read their own records. There is no way to restrict this or allow sharing of records.

Method URL permission
GET / HEAD /{collection} Authenticated
POST /{collection} Authenticated
DELETE /{collection} Authenticated
GET / HEAD /{collection}/{id} Authenticated
PUT /{collection}/{id} Authenticated
PATCH /{collection}/{id} Authenticated
DELETE /{collection}/{id} Authenticated

Note

When using only these resource, the permission backend remains unused. Its configuration is not necessary.

Public BasicAuth

If Basic Auth authentication is enabled, private user resources can become semi-private or public if the user:pass is publicly known and shared (for example public: is a valid user:pass combination). That’s how most simple demos of Kinto — a Cliquet-based application — are built by the way!

Shareable resource

Warning

When using this kind of resource, the permission_backend setting must be set, as described in the configuration section.

To introduce more flexibility, the cliquet.resource.ShareableResource can be used instead.

from cliquet import resource

@resource.register()
class Toadstool(resource.ShareableResource):
    mapping = MushroomSchema()

With this alternative resource class, Cliquet will register the endpoints with a specific route factory, that will take care of checking the appropriate permission for each action.

Method URL permission Comments
GET / HEAD /{collection} read If not allowed by setting cliquet.{collection}_read_principals, will return list of records where user has read permission.
POST /{collection} create Allowed by setting cliquet.{collection}_create_principals
DELETE /{collection} write If not allowed by setting cliquet.{collection}_write_principals, will delete the list of records where user has write permission.
GET / HEAD /{collection}/{id} read If not allowed by setting cliquet.{collection}_read_principals, will check record permissions
PUT /{collection}/{id} create if record doesn’t exist, write otherwise Allowed by setting cliquet.{collection}_create_principals, or cliquet.{collection}_create_principals or existing record permissions
PATCH /{collection}/{id} write If not allowed by setting cliquet.{collection}_write_principals, will check record permissions
DELETE /{collection}/{id} write If not allowed by setting cliquet.{collection}_write_principals, will check record permissions

The record permissions can be manipulated via the permissions attribute in the JSON payload, aside the data attribute. It allows to specify the list of principals allowed for each permission, as detailed in the API section.

Important

When defining permissions, there are two specific principals:

  • system.Authenticated: any authenticated user
  • system.Everyone: any user

The write permission is required to be able to modify the permissions of an existing record.

When a record is created or modified, the current user is added to list of principals for the write permission on this object. That means that a user is always able to replace or delete the records she created.

Note

Don’t hesitate to submit a contribution to introduce a way to control the current behaviour instead of always granting write on current user!

BasicAuth trickery

Like for user resources, if Basic Auth authentication is enabled, the predictable user id can be used to define semi-private or public if the user:pass is known and shared (for example public: is a valid user:pass combination).

For example, get the user id obtained in the hello root view with a user:pass combination and use it in the permissions JSON payloads, or settings:

cliquet.{collection}_read_principals = basicauth:631c2d625ee5726172cf67c6750de10a3e1a04bcd603bc9ad6d6b196fa8257a6

Manipulate permissions

One way of achieving dynamic permissions is to manipulate the permission backend manually.

For example, in some imaginary admin view:

def admin_view(request):
    # Custom Pyramid view.
    permission = request.registry.permission

    # Give `create` permission to `user_id` in POST
    some_user_id = request.POST['user_id']
    permission_object_id = '/articles'
    permission = 'create'
    permission.add_principal_to_ace(permission_object_id,
                                    permission,
                                    some_user_id)

Or during application init (or scripts):

def main(global_config, **settings):
    # ...
    cliquet.initialize(config, __version__)
    # ...

    some_user_id = 'basicauth:ut082jghnrgnjnj'
    permission_object_id = '/articles'
    permission = 'create'
    config.registry.permission.add_principal_to_ace(permission_object_id,
                                                    permission,
                                                    some_user_id)

Since principals can be anything, it is also possible to use them to define groups:

def add_to_admins(request):
    # Custom Pyramid view.
    permission = request.registry.permission

    some_user_id = request.POST['user_id']
    group_name = 'group:admins'
    permission.add_user_principal(some_user_id, group_name)

And then refer as group:admins in the list of allowed principals.

Custom permission checking

The permissions verification in Cliquet is done with usual Pyramid authorization abstractions. Most notably using an implementation of a RootFactory in conjonction with an Authorization policy.

In order to completely override (or mimic) the defaults, a custom RootFactory and a custom Authorization policy can be plugged on the resource during registration.

from cliquet import resource

class MyViewSet(resource.ViewSet):

    def get_view_arguments(self, endpoint_type, resource_cls, method):
        args = super(MyViewSet, self).get_view_arguments(endpoint_type,
                                                         resource_cls,
                                                         method)
        if method.lower() not in ('get', 'head'):
            args['permission'] = 'publish'
        return args

    def get_service_arguments(self):
        args = super(MyViewSet, self).get_service_arguments()
        args['factory'] = myapp.MyRootFactory
        return args


@resource.register(viewset=MyViewSet())
class Resource(resource.UserResource):
    mapping = BookmarkSchema()

See more details about available customization in the viewset section.

A custom RootFactory and AuthorizationPolicy should implement the permission checking using Pyramid mecanisms.

For example, a simplistic example with the previous resource viewset:

from pyramid.security import IAuthorizationPolicy

class MyRootFactory(object):
    def __init__(self, request):
        self.current_resource = None
        service = request.current_service
        if service and hasattr(service, 'resource'):
            self.current_resource = service.resource


@implementer(IAuthorizationPolicy)
class AuthorizationPolicy(object):
    def permits(self, context, principals, permission):
        if context.current_resource == BlogArticle:
            if permission == 'publish':
                return ('group:publishers' in principals)
        return False

Backends

The ACLs are stored in a permission backend. Like for Storage and Cache, it is pluggable from configuration.

PostgreSQL

class cliquet.permission.postgresql.Permission(client, *args, **kwargs)

Permission backend using PostgreSQL.

Enable in configuration:

cliquet.permission_backend = cliquet.permission.postgresql

Database location URI can be customized:

cliquet.permission_url = postgres://user:pass@db.server.lan:5432/dbname

Alternatively, username and password could also rely on system user ident or even specified in ~/.pgpass (see PostgreSQL documentation).

Note

Some tables and indices are created when cliquet migrate is run. This requires some privileges on the database, or some error will be raised.

Alternatively, the schema can be initialized outside the python application, using the SQL file located in cliquet/permission/postgresql/schema.sql. This allows to distinguish schema manipulation privileges from schema usage.

A connection pool is enabled by default:

cliquet.permission_pool_size = 10
cliquet.permission_maxoverflow = 10
cliquet.permission_max_backlog = -1
cliquet.permission_pool_recycle = -1
cliquet.permission_pool_timeout = 30
cliquet.cache_poolclass = cliquet.storage.postgresql.pool.QueuePoolWithMaxBacklog

The max_backlog limits the number of threads that can be in the queue waiting for a connection. Once this limit has been reached, any further attempts to acquire a connection will be rejected immediately, instead of locking up all threads by keeping them waiting in the queue.

See dedicated section in SQLAlchemy documentation for default values and behaviour.

Note

Using a dedicated connection pool is still recommended to allow load balancing, replication or limit the number of connections used in a multi-process deployment.

Noindex:

Redis

class cliquet.permission.redis.Permission(client, *args, **kwargs)

Permission backend implementation using Redis.

Enable in configuration:

cliquet.permission_backend = cliquet.permission.redis

(Optional) Instance location URI can be customized:

cliquet.permission_url = redis://localhost:6379/2

A threaded connection pool is enabled by default:

cliquet.permission_pool_size = 50
Noindex:

Memory

class cliquet.permission.memory.Permission(*args, **kwargs)

Permission backend implementation in local thread memory.

Enable in configuration:

cliquet.permission_backend = cliquet.permission.memory
Noindex:

API

Implementing a custom permission backend consists in implementating the following interface:

class cliquet.permission.PermissionBase(*args, **kwargs)
initialize_schema()

Create every necessary objects (like tables or indices) in the backend.

This is excuted with the cliquet migrate command.

flush()

Delete all data stored in the permission backend.

add_user_principal(user_id, principal)

Add an additional principal to a user.

Parameters:
  • user_id (str) – The user_id to add the principal to.
  • principal (str) – The principal to add.
remove_user_principal(user_id, principal)

Remove an additional principal from a user.

Parameters:
  • user_id (str) – The user_id to remove the principal to.
  • principal (str) – The principal to remove.
remove_principal(principal)

Remove a principal from every user.

Parameters:principal (str) – The principal to remove.
user_principals(user_id)

Return the set of additionnal principals given to a user.

Parameters:user_id (str) – The user_id to get the list of groups for.
Returns:The list of group principals the user is in.
Return type:set
add_principal_to_ace(object_id, permission, principal)

Add a principal to an Access Control Entry.

Parameters:
  • object_id (str) – The object to add the permission principal to.
  • permission (str) – The permission to add the principal to.
  • principal (str) – The principal to add to the ACE.
remove_principal_from_ace(object_id, permission, principal)

Remove a principal to an Access Control Entry.

Parameters:
  • object_id (str) – The object to remove the permission principal to.
  • permission (str) – The permission that should be removed.
  • principal (str) – The principal to remove to the ACE.
object_permission_principals(object_id, permission)

Return the set of principals of a bound permission (unbound permission + object id).

Parameters:
  • object_id (str) – The object_id the permission is set to.
  • permission (str) – The permission to query.
Returns:

The list of user principals

Return type:

set

principals_accessible_objects(principals, permission, object_id_match=None, get_bound_permissions=None)

Return the list of objects id where the specified principals have the specified permission.

Parameters:
  • principal (list) – List of user principals
  • permission (str) – The permission to query.
  • object_id_match (str) – Filter object ids based on a pattern (e.g. '*articles*').
  • get_bound_permissions (function) – The methods to call in order to generate the list of permission to verify against. (ie: if you can write, you can read)
Returns:

The list of object ids

Return type:

set

object_permission_authorized_principals(object_id, permission, get_bound_permissions=None)

Return the full set of authorized principals for a given permission + object (bound permission).

Parameters:
  • object_id (str) – The object_id the permission is set to.
  • permission (str) – The permission to query.
  • get_bound_permissions (function) – The methods to call in order to generate the list of permission to verify against. (ie: if you can write, you can read)
Returns:

The list of user principals

Return type:

set

check_permission(object_id, permission, principals, get_bound_permissions=None)

Test if a principal set have got a permission on an object.

Parameters:
  • object_id (str) – The identifier of the object concerned by the permission.
  • permission (str) – The permission to test.
  • principals (set) – A set of user principals to test the permission against.
  • get_bound_permissions (function) – The method to call in order to generate the set of permission to verify against. (ie: if you can write, you can read)
object_permissions(object_id, permissions=None)

Return the set of principals for each object permission.

Parameters:
  • object_id (str) – The object_id the permission is set to.
  • permissions (list) – List of permissions to retrieve. If not define will try to find them all.
Returns:

The dictionnary with the list of user principals for each object permissions

Return type:

dict

replace_object_permissions(object_id, permissions)

Replace given object permissions.

Parameters:
  • object_id (str) – The object to replace permissions to.
  • permissions (str) – The permissions dict to replace.
delete_object_permissions(*object_id_list)

Delete all listed object permissions.

Parameters:object_id (str) – Remove given objects permissions.