Permissions

Kinto-Core provides a mechanism to handle authorization on the stored objects.

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

Resource

from kinto.core import resource

@resource.register()
class Toadstool(resource.Resource):
    schema = MushroomSchema

With this resource class, Kinto-Core 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 /{resource} read If not allowed by setting kinto.{resource}_read_principals, will return list of records where user has read permission.
POST /{resource} create Allowed by setting kinto.{resource}_create_principals
DELETE /{resource} write If not allowed by setting kinto.{resource}_write_principals, will delete the list of records where user has write permission.
GET / HEAD /{resource}/{id} read If not allowed by setting kinto.{resource}_read_principals, will check record permissions
PUT /{resource}/{id} create if record doesn’t exist, write otherwise Allowed by setting kinto.{resource}_create_principals, or kinto.{resource}_create_principals or existing record permissions
PATCH /{resource}/{id} write If not allowed by setting kinto.{resource}_write_principals, will check record permissions
DELETE /{resource}/{id} write If not allowed by setting kinto.{resource}_write_principals, will check record permissions

The objects 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 object.

When an object 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.

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):
    # ...
    kinto.core.initialize(config, __version__)
    # ...

    some_user_id = 'ldap:[email protected]'
    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 Kinto-Core 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 kinto.core import resource

class MyViewSet(resource.ViewSet):

    def get_view_arguments(self, endpoint_type, resource_cls, method):
        args = super().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().get_service_arguments()
        args['factory'] = myapp.MyRootFactory
        return args


@resource.register(viewset=MyViewSet())
class Resource(resource.Resource):
    schema = 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:
    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:
    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

Redis

Memory

API

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