All Projects â†’ pgorecki â†’ django-cancan

pgorecki / django-cancan

Licence: MIT license
🔓Authorization library for Django

Programming Languages

python
139335 projects - #7 most used programming language
Jupyter Notebook
11667 projects
HTML
75241 projects

Projects that are alternatives of or similar to django-cancan

Laratrust
Handle roles and permissions in your Laravel application
Stars: ✭ 1,799 (+4897.22%)
Mutual labels:  permissions, authorization
Appy
🚀 A full stack boilerplate web app
Stars: ✭ 225 (+525%)
Mutual labels:  permissions, authorization
Laravel Authz
An authorization library that supports access control models like ACL, RBAC, ABAC in Laravel.
Stars: ✭ 136 (+277.78%)
Mutual labels:  permissions, authorization
Accesscontrol
Role and Attribute based Access Control for Node.js
Stars: ✭ 1,723 (+4686.11%)
Mutual labels:  permissions, authorization
spicedb
Open Source, Google Zanzibar-inspired fine-grained permissions database
Stars: ✭ 3,358 (+9227.78%)
Mutual labels:  permissions, authorization
Laravel Auth
A powerful authentication, authorization and verification package built on top of Laravel. It provides developers with Role Based Access Control, Two-Factor Authentication, Social Authentication, and much more, compatible Laravel’s standard API and fully featured out of the box.
Stars: ✭ 128 (+255.56%)
Mutual labels:  permissions, authorization
Drf Access Policy
Declarative access policies/permissions modeled after AWS' IAM policies.
Stars: ✭ 200 (+455.56%)
Mutual labels:  permissions, authorization
Appy Backend
A user system to bootstrap your app.
Stars: ✭ 96 (+166.67%)
Mutual labels:  permissions, authorization
Rbac
Hierarchical Role-Based Access Control for Node.js
Stars: ✭ 254 (+605.56%)
Mutual labels:  permissions, authorization
Bouncer
Eloquent roles and abilities.
Stars: ✭ 2,763 (+7575%)
Mutual labels:  permissions, authorization
Rbac.dev
A collection of good practices and tools for Kubernetes RBAC
Stars: ✭ 115 (+219.44%)
Mutual labels:  permissions, authorization
nova-permissions
Add Permissions based authorization for your Nova installation via User-based Roles and Permissions. Roles are defined in the database whereas Permissions are defined in the code base.
Stars: ✭ 115 (+219.44%)
Mutual labels:  permissions, authorization
Simpleacl
Simple ACL for PHP
Stars: ✭ 105 (+191.67%)
Mutual labels:  permissions, authorization
Laravel Governor
Manage authorization with granular role-based permissions in your Laravel Apps.
Stars: ✭ 131 (+263.89%)
Mutual labels:  permissions, authorization
Sentinel
A framework agnostic authentication & authorization system.
Stars: ✭ 1,354 (+3661.11%)
Mutual labels:  permissions, authorization
Think Authz
An authorization library that supports access control models like ACL, RBAC, ABAC in ThinkPHP 6.0 .
Stars: ✭ 155 (+330.56%)
Mutual labels:  permissions, authorization
Django Rules
Awesome Django authorization, without the database
Stars: ✭ 1,255 (+3386.11%)
Mutual labels:  permissions, authorization
Vakt
Attribute-based access control (ABAC) SDK for Python
Stars: ✭ 92 (+155.56%)
Mutual labels:  permissions, authorization
Vue Router User Roles
A Vue.js plugin that protects routes based on user roles. Add your own authentication.
Stars: ✭ 237 (+558.33%)
Mutual labels:  permissions, authorization
graphql authorize
Authorization helpers for ruby-graphql fields
Stars: ✭ 23 (-36.11%)
Mutual labels:  permissions, authorization

django-cancan

Logo

Build Status PyPI version

django-cancan is an authorization library for Django. It works on top of default Django permissions and allows to restrict the resources (models and objects) a given user can access.

This library is inspired by cancancan for Ruby on Rails.

Key features

  • All of your permissions logic is kept in one place. User permissions are defined in a single function and not scattered across views, querysets, etc.

  • Same permissions logic is used to check permissions on a single model instance and to generate queryset containing all instances that the user can access

  • Easy unit testing

  • Integration with built-in Django default permissions system and Django admin (coming soon)

  • Intergration with Django Rest Framework (coming soon)

How to install

Using pip:

pip install django-cancan

Quick start

  1. Add cancan to your INSTALLED_APPS setting like this:
INSTALLED_APPS = [
    ...,
    'cancan',
]
  1. Create a function that define the access rules for a given user. For example, create abilities.py in myapp module:
def define_access_rules(user, rules):
    # Anybody can view published articles
    rules.allow('view', Article, published=True)

    if not user.is_authenticated:
        return 

    # Allow logged in user to view his own articles, regardless of the `published` status
    # allow accepts the same kwargs that you would provide to QuerySet.filter method
    rules.allow('view', Article, author=user)

    if user.has_perm('article.view_unpublished'):
        # You can also check for custom model permissions (i.e. view_unpublished)
        rules.allow('view', Article, published=False)
    

    if user.is_superuser:
        # Superuser gets unlimited access to all articles
        rules.allow('add', Article)
        rules.allow('view', Article)
        rules.allow('change', Article)
        rules.allow('delete', Article)
  1. In settings.py add CANCAN section, so that cancan library will know where to search for define_access_rules function from the previous step:
CANCAN = {
    'ABILITIES': 'myapp.abilities.define_access_rules'
}

The define_access_rules function will be executed automatically per each request by the cancan middleware. The middleware will call the function to determine the abilities of a current user.

Let's add cancan middleware, just after AuthenticationMiddleware:

MIDDLEWARE = [
    ...
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'cancan.middleware.CanCanMiddleware',
    ...
]

By adding the middleware you will also get an access to request.ability instance which you can use to:

  • check model permissions,
  • check object permissions,
  • generate model querysets (i.e. when inheriting from ListView)
  1. Check for abilities in views:
class ArticleListView(ListView):
    model = Article

    def get_queryset(self):
        # this is how you can retrieve all objects that current user can access
        qs = self.request.ability.queryset_for('view', Article)
        return qs


class ArticleDetailView(PermissionRequiredMixin, DetailView):
    queryset = Article.objects.all()

    def has_permission(self):
        article = self.get_object()
        # this is how you can check if user can access an object
        return self.request.ability.can('view', article)
  1. Check for abilities in templates

You can also check for abilities in template files, i. e. to show/hide/disable buttons or links.

First you need to add cancan processor to context_processors in TEMPLATES section of settings.py:

TEMPLATES = [
    {
        ...,
        "OPTIONS": {
            "context_processors": [
                ...,
                "cancan.context_processors.abilities",
            ],
        },
    },
]

This will give you access to ability object in a template. You also need add {% load cancan_tags %} at the beginning of the template file.

Next you can check for object permissions:

{% load cancan_tags %}

...

{% if ability|can:"change"|subject:article %}
    <a href="{% url 'article_edit' pk=article.id %}">Edit article</a>
{% endif %}

or model permissions:

{% if ability|can:"add"|subject:"myapp.Article" %}
    <a href="{% url 'article_new' %}">Create new article</a>
{% endif %}

You can also use can template tag to create a reusable variable:

{% can "add" "core.Project" as can_add_project %}
...
{% if can_add_project %}
    ...
{% endif %}

Checking for abilities in Django Rest Framework

Let's start by creating a pemission class:

from rest_framework import permissions

def set_aliases_for_drf_actions(ability):
    """
    map DRF actions to default Django permissions
    """
    ability.access_rules.alias_action("list", "view")
    ability.access_rules.alias_action("retrieve", "view")
    ability.access_rules.alias_action("create", "add")
    ability.access_rules.alias_action("update", "change")
    ability.access_rules.alias_action("partial_update", "change")
    ability.access_rules.alias_action("destroy", "delete")


class AbilityPermission(permissions.BasePermission):
    def has_permission(self, request, view=None):
        ability = request.ability
        set_aliases_for_drf_actions(ability)
        return ability.can(view.action, view.get_queryset().model)

    def has_object_permission(self, request, view, obj):
        ability = request.ability
        set_aliases_for_drf_actions(ability)
        return ability.can(view.action, obj)

Next, secure the ViewSet with AbilityPermission and override get_queryset method to list objects based on the access rights.

class ArticleViewset(ModelViewSet):
    permission_classes = [AbilityPermission]

    def get_queryset(self):
        return self.request.ability.queryset_for(self.action, Article).distinct()

Itegrating with admin panel

To inegrate django-cancan with the admin panel, add the following mixin to your admin.ModelAdmin class.

class AbilityModelAdminMixin:
    def get_queryset(self, request):
        if request.user.is_superuser:
            return super().get_queryset(request)
        return request.ability.queryset_for("view", self.model)

    def has_module_permission(self, request):
        if request.user.is_superuser:
            return True

        can = request.ability.can
        return (
                can("view", self.model)
                or can("change", self.model)
                or can("delete", self.model)
        )

    def has_add_permission(self, request, obj=None):
        if request.user.is_superuser:
            return True
        return request.ability.can("add", self.model)

    def has_view_permission(self, request, obj=None):
        if request.user.is_superuser:
            return True
        return request.ability.can("view", self.model)

    def has_change_permission(self, request, obj=None):
        if request.user.is_superuser:
            return True
        return request.ability.can("change", self.model)

    def has_delete_permission(self, request, obj=None):
        if request.user.is_superuser:
            return True
        return request.ability.can("delete", self.model)

like so:

class AbilityModelAdmin(AbilityModelAdminMixin, admin.ModelAdmin):
    pass
    
admin.site.register(Article, AbilityModelAdmin)

Unit testing

This is how you can unit test your define_access_rules function.

from cancan.access_rules import AccessRules
from cancan.ability import Ability
from myapp.abilities import define_access_rules

user = somehow_create_user(...)
instance1 = MyModel.objects.create(...)

access_rules = AccessRules(user)
define_access_rules(user1, access_rules)
ability = Ability(access_rules)

assert instance1 in ability.queryset_for("view", MyModel)
assert ability.can("update", instance1)

ability.queryset_for and rules.allow explained

When executing rules.allow you specify 2 positional arguments: action and subject. Any additional parameters passed to allow will filter the results in the same way as for Django QuerySet.fiter method.

Let's say that we have the following models in core.models.py:

class Project(models.Model):
    name = models.CharField(max_length=128)
    description = models.TextField(default="", blank=True)
    members = models.ManyToManyField(User, through="Membership")
    created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name="owner")

class Membership(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    project = models.ForeignKey(Project, on_delete=models.CASCADE)

If you have the following rules:

rules.allow('view', Project, name="Foo")

then executing:

ability.queryset_for('view', Project)

will result in the following query:

SELECT "core_project"."id", "core_project"."name", "core_project"."description", "core_project"."created_by_id" FROM "core_project" WHERE "core_project"."name" = Foo

Similarly, rules.allow('view', Project, name="Foo", description__contains="Bar")

will generate a query:

SELECT "core_project"."id", "core_project"."name", "core_project"."description", "core_project"."created_by_id" FROM "core_project" WHERE ("core_project"."description" LIKE %Bar% ESCAPE '\' AND "core_project"."name" = Foo)

Multiple rules for the same action and model will result in OR'ed queries, i.e.:

rules.allow('view', Project, name="Foo")
rules.allow('view', Project, description__contains="Bar")

will generate a query:

SELECT "core_project"."id", "core_project"."name", "core_project"."description", "core_project"."created_by_id" FROM "core_project" WHERE ("core_project"."description" LIKE %Bar% ESCAPE '\' OR "core_project"."name" = Foo)

See example_project/cancan_playground.ipynb for more examples.

Sponsors

STX Next
Ermlab
Logo made by Freepik from www.flaticon.com
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].