Per object permissions for Django

Overview

django-guardian

https://travis-ci.org/django-guardian/django-guardian.svg?branch=devel

django-guardian is an implementation of per object permissions [1] on top of Django's authorization backend

Documentation

Online documentation is available at https://django-guardian.readthedocs.io/.

Requirements

  • Python 3.5+
  • A supported version of Django (currently 2.2+)

Travis CI tests on Django version 2.2, 3.0, 3.1, and master.

Installation

To install django-guardian simply run:

pip install django-guardian

Configuration

We need to hook django-guardian into our project.

  1. Put guardian into your INSTALLED_APPS at settings module:
INSTALLED_APPS = (
 ...
 'guardian',
)
  1. Add extra authorization backend to your settings.py:
AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend', # default
    'guardian.backends.ObjectPermissionBackend',
)
  1. Create guardian database tables by running:

    python manage.py migrate
    

Usage

After installation and project hooks we can finally use object permissions with Django.

Lets start really quickly:

>>> from django.contrib.auth.models import User, Group
>>> jack = User.objects.create_user('jack', '[email protected]', 'topsecretagentjack')
>>> admins = Group.objects.create(name='admins')
>>> jack.has_perm('change_group', admins)
False
>>> from guardian.models import UserObjectPermission
>>> UserObjectPermission.objects.assign_perm('change_group', jack, obj=admins)
<UserObjectPermission: admins | jack | change_group>
>>> jack.has_perm('change_group', admins)
True

Of course our agent jack here would not be able to change_group globally:

>>> jack.has_perm('change_group')
False

Admin integration

Replace admin.ModelAdmin with GuardedModelAdmin for those models which should have object permissions support within admin panel.

For example:

from django.contrib import admin
from myapp.models import Author
from guardian.admin import GuardedModelAdmin

# Old way:
#class AuthorAdmin(admin.ModelAdmin):
#    pass

# With object permissions support
class AuthorAdmin(GuardedModelAdmin):
    pass

admin.site.register(Author, AuthorAdmin)
[1] Great paper about this feature is available at djangoadvent articles.
Issues
  • get_objects_for_user with any_perm=True leads to type error in postgres.

    get_objects_for_user with any_perm=True leads to type error in postgres.

    This, I believe, is related to object_pk being a VARCHAR(255), whereas the id fields are usually INT types in the UserObjectPermission model

    A sample of query being generated which is failing (at the "app_model"."id" IN clause). The query is relatively longer. I am posting only a part of it.

    SELECT <Fields> FROM "report_report"   
    WHERE ("app_model"."id" IN (  
          SELECT U0."object_pk" FROM "guardian_userobjectpermission" U0   
         INNER JOIN "auth_permission" U2 ON ( U0."permission_id" = U2."id" )   
         WHERE (U0."user_id" = 11 AND U2."content_type_id" = 10 AND U2."codename" IN (abc, xyz, efg)))
    

    Terminal error:

    psycopg2.ProgrammingError: operator does not exist: integer = character varying
    LINE 1: ... FROM "app_model" WHERE ("app_model"."id" IN (SELECT...
                                                                 ^
    HINT:  No operator matches the given name and argument type(s). You might need to add explicit type casts.
    

    Stack: Django==1.8.7 django-guardian==1.3.2 python==3.4.3 Linux Mint==17.2 (64-bit) Postgres==9.3.10

    opened by liquidscorpio 41
  • user.has_perm(

    user.has_perm("perm", obj) behaves unexpectedly

    If I use standard user.has_perm("perm") method, then it will return True only, if user has a global permission "perm". And if user.has_perm("perm", obj) is used, it'll teturn True if user have permission to access this particular object. But it will return False, even if user has a global permission "perm", which is quite unexpected for me, because I assume, that having global permission should give user access to all objects. Am I right?

    Enhancement 
    opened by Dzejkob 28
  • Permission for specific object in AdminPanel

    Permission for specific object in AdminPanel

    Hi, is possibile have the same feature of this example:

    >>> from django.contrib.auth.models import User, Group
    >>> jack = User.objects.create_user('jack', '[email protected]', 'topsecretagentjack')
    >>> admins = Group.objects.create(name='admins')
    >>> jack.has_perm('change_group', admins)
    False
    >>> from guardian.models import UserObjectPermission
    >>> UserObjectPermission.objects.assign_perm('change_group', jack, obj=admins)
    <UserObjectPermission: admins | jack | change_group>
    >>> jack.has_perm('change_group', admins)
    True
    
    

    Inside the Panel admin?

    Thanks for support. Best Regards, Allan

    opened by Allan-Nava 21
  • LoginRequired and PermissionRequired view mixins

    LoginRequired and PermissionRequired view mixins

    Using the django's or guardian's 'loginrequired' and 'permissionrequired' decorators with the new class based views is tricky and cumbersome - django documetations shows some pointers here: https://docs.djangoproject.com/en/dev/topics/class-based-views/#decorating-class-based-views

    A different approach would to use view mixins which in my opinion is the easiest and most readable solution. An example class based view with a permission required check could look like this:

    class FitterEditView(PermissionRequiredMixin, UpdateView):
        """
        ...
        """
    
        ### PermissionRequiredMixin settings
        permission_required = 'fitters.change_fitter'
    
        ### UpdateView settings
        context_object_name="fitter"
        queryset = Fitter.objects.all()
        form_class = FitterForm
        ...
    

    Below is my first attempt at these mixins created for a project I am currently working on. These mixins are designed to work both with vanilla django or with django-guradian. If this is something you would consider including in the future I would love to assist.

    On the final note I want to say thank you for this fantastic app which I have been using it since ver 0.2.

    class LoginRequiredMixin(object):
        """ 
        A login required mixin for use with class based views. This Class is a light wrapper around the
        `login_required` decorator and hence function parameters are just attributes defined on the class.
    
        Due to parent class order traversal this mixin must be added as the left most 
        mixin of a view.
    
        The mixin has exaclty the same flow as `login_required` decorator:
    
            If the user isn't logged in, redirect to settings.LOGIN_URL, passing the current 
            absolute path in the query string. Example: /accounts/login/?next=/polls/3/.
    
            If the user is logged in, execute the view normally. The view code is free to 
            assume the user is logged in.
    
        **Class Settings**
            `redirect_field_name - defaults to "next"
            `login_url` - the login url of your site
    
        """
        redirect_field_name = REDIRECT_FIELD_NAME
        login_url = None
    
        @method_decorator(login_required(redirect_field_name=redirect_field_name, login_url=login_url))
        def dispatch(self, request, *args, **kwargs):
            return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs)
    
    class PermissionRequiredMixin(object):
        """ 
        A view mixin that verifies if the current logged in user has the specified permission 
        by wrapping the ``request.user.has_perm(..)`` method.
    
        If a `get_object()` method is defined either manually or by including another mixin (for example
        ``SingleObjectMixin``) or ``self.object`` is defiend then the permission will be tested against 
        that specific instance.
    
        .. NOTE: Testing of a permission against a specific object instance requires an authentication backend
                 that supports. Please see ``django-guardian`` to add object level permissions to your project.  
    
        The mixin does the following:  
    
            If the user isn't logged in, redirect to settings.LOGIN_URL, passing the current 
            absolute path in the query string. Example: /accounts/login/?next=/polls/3/.
    
            If the `raise_exception` is set to True than rather than redirect to login page
            a `PermisionDenied` (403) is raised.
    
            If the user is logged in, and passes the permission check than the view is executed
            normally.
    
        **Example Usage**
    
            class FitterEditView(PermissionRequiredMixin, UpdateView):
                ...
                ### PermissionRequiredMixin settings
                permission_required = 'fitters.change_fitter'
    
                ### other view settings
                context_object_name="fitter"
                queryset = Fitter.objects.all()
                form_class = FitterForm
                ...
    
        **Class Settings**
            `permission_required` - the permission to check of form "<app_label>.<permission codename>"
                                    i.e. 'polls.can_vote' for a permission on a model in the polls application.
    
            `login_url` - the login url of your site
            `redirect_field_name - defaults to "next"
            `raise_exception` - defaults to False - raise PermisionDenied (403) if set to True
    
        """
        ### default class view settings
        login_url = settings.LOGIN_URL
        raise_exception = False
        permission_required = None
        redirect_field_name=REDIRECT_FIELD_NAME
    
        def dispatch(self, request, *args, **kwargs):
            # call the parent dispatch first to pre-populate few things before we check for permissions
            original_return_value = super(PermissionRequiredMixin, self).dispatch(request, *args, **kwargs)
    
            # verify class settings
            if self.permission_required == None or len(self.permission_required.split('.')) != 2:
                raise ImproperlyConfigured("'PermissionRequiredMixin' requires 'permission_required' attribute to be set to '<app_label>.<permission codename>' but is set to '%s' instead" % self.permission_required)
    
            # verify permission on object instance if needed
            has_permission = False
            if hasattr(self, 'object')  and self.object is not None: 
                has_permission = request.user.has_perm(self.permission_required, self.object)
            elif hasattr(self, 'get_object') and callable(self.get_object):
                has_permission = request.user.has_perm(self.permission_required, self.get_object())
            else:
                has_permission = request.user.has_perm(self.permission_required)
    
            # user failed permission
            if not has_permission:
                if self.raise_exception:
                    return HttpResponseForbidden()
                else:
                    path = urlquote(request.get_full_path())
                    tup = self.login_url, self.redirect_field_name, path
                    return HttpResponseRedirect("%s?%s=%s" % tup)
    
            # user passed permission check so just return the result of calling .dispatch()
            return original_return_value
    
    opened by danielsokolowski 21
  • Added method to prefetch the permissions for an iterable of objects

    Added method to prefetch the permissions for an iterable of objects

    The permissions are then stored in the cache.

    This helps reduce the number of database calls required when getting the permissions on a list of objects.

    Performance 
    opened by keattang 17
  • Django 1.8 GuardedModelAdminMixin.get_queryset exception

    Django 1.8 GuardedModelAdminMixin.get_queryset exception

    Hi,

    I'm using django-guardian (through django-userena) with Django 1.8. When trying to access User model in django admin, GuardedModelAdminMixin raises the following Exception:

    'super' object has no attribute 'queryset'

    In GuardedModelAdminMixin.get_queryset the problematic code is:

        def get_queryset(self, request):
            # Prefer the Django >= 1.6 interface but maintain
            # backward compatibility
            method = getattr(
                super(GuardedModelAdminMixin, self), 'get_queryset',
                super(GuardedModelAdminMixin, self).queryset)
    
    

    In Django 1.8 (I guess in all Django >= 1.6) queryset method does not exist, but this part

    super(GuardedModelAdminMixin, self).queryset is evaluated and raising Exception even though get_queryset method exists and it's found.

    opened by lalstef 17
  • Allow specifying an empty list of permissions for get_objects_for_user

    Allow specifying an empty list of permissions for get_objects_for_user

    This allows one to ask for all objects for which an user has any permission. In a way it mirrors get_users_with_perms.

    In addition, I fixed how the final query is made. Instead of creating an in-memory list of all IDs and then passing it in, you can pass all IDs simply as a subquery. This both improves the query and also allows more complicated basic queries (I was getting an error that I cannot use OR for two queries with "extra"). Also, it works now with documents which do not have integers as primary keys (I have UUIDs and int(v) does not work).

    Fixes #244.

    opened by mitar 17
  • Is this Django 1.8 ready?

    Is this Django 1.8 ready?

    I have never used django-guardian, and i am about to start a new project using Django 1.8 and was thinking about using it.

    i see many issues regarding erros when testing with Django 1.8 so my question is should i be using this right away? If not how long more or less till the official 1.8 version??

    thanks guys

    opened by psychok7 16
  • Update CHANGES for v2.3.0

    Update CHANGES for v2.3.0

    I tried the following picking list for the release, so the cherry-pick commands would apply successfully:

    git cherry-pick 9206ee4b32b3c97a7bc60440ce8221d483f1a24a
    git cherry-pick 448a6149be8de47a62bd31c71eee1d94e688c5ef
    git cherry-pick b12212babd8736015cd5b97a0a45dcc16c58c8a2
    git cherry-pick 414d3a4a7bbd0ac4c2ddac85e0dcc098cf130ca1
    git cherry-pick b338092f829b631cd59827bf29171f1ab405ff18
    git cherry-pick 8e8dab207296ee37aa1d19eaeebfad7d0642f138
    git cherry-pick 9c981804bbebe9d91f1d4d78292bedf292745986
    git cherry-pick d816cfe4dad04dca78417d9c8613b49822256c60
    
    opened by lnagel 14
  • Any staff user can gain permissions

    Any staff user can gain permissions

    Guardian admin views for managing object permissions does not check for user permission to make any changes in admin site. Any user logged in to admin site can access URL like http://127.0.0.1:8080/admin/main/complex/2/permissions/ and give change permissions to himself. Hows that possible? I'm using Django 1.7.7, guardian 1.2.5 with python 3.4

    Bug 
    opened by Sovetnikov 14
  • Cast object_pk to UUIDField when PK is a UUID

    Cast object_pk to UUIDField when PK is a UUID

    This fixes

    ProgrammingError: operator does not exist: uuid = character varying

    when having UUIDs as primary keys.

    UUIDs used to work up until 2.1, but when I upgraded to 2.2 it threw the above error. PR #637 changed the way the object_pk was casted/compared, but only for Integer/AutoField PKs. This PR handles UUIDs as well.

    opened by richarddewit 13
  • Cannot batch different combinations of multiple permissions, multiple users, and multiple objects

    Cannot batch different combinations of multiple permissions, multiple users, and multiple objects

    Currently, the methods in the BaseObjectPermissionManager do not allow assigning multiple permissions for multiple users and for multiple objects. Likewise, the assign_perm function in shortcuts does not even allow assigning multiple permissions at once. The problem with this is that permission assignments are very slow, because they cannot be properly batched together.

    I actually went ahead and did a PR for this, but forgot to create the issue. Here it is:

    #777

    This PR adds in methods to the object permission manager, and also adds two new shortcuts: bulk_assign_perms and bulk_remove_perms. In addition, I have also added a commit parameter, which allows the user of the shortcuts to go even further with the batching, and accumulate permissions so that they can all be persisted in one fell swoop. I have ensured that previous behaviour is left intact, and the new behaviour is well tested.

    Having applied these changes in a work project which relies heavily on django guardian, I have seen a big decrease in the time taken for the entire test suite to complete (down to ~20 seconds from 3 minutes).

    opened by JCourt1 0
  • Polymorphic abstract class fails to generate permissions

    Polymorphic abstract class fails to generate permissions

    Hi,

    I was trying to create a model hierarchy where the parent class is a PolymorphicModel (from django-polymorphic) and is also abstract. The subclasses then are trying to make use of Django's object permissions. There is an admin class inheriting GuardedModelAdmin to be able to manage those permissions for the children models. However, no permissions are available in the admin UI, the drop downs are simply empty. It appears that the combination of abstract and polymorphic is breaking the permissions somehow.

    The application has set the GUARDIAN_GET_CONTENT_TYPE to use the polymorphic content type getter since other models in the same codebase need it.

     GUARDIAN_GET_CONTENT_TYPE = (
            "polymorphic.contrib.guardian.get_polymorphic_base_content_type"
        )
    

    Is this a known limitation of the framework or I am doing something wrong? Thanks!

    opened by adrianbn 0
  • Allow batch assignment of multiple permissions to multiple users/groups for multiple objects

    Allow batch assignment of multiple permissions to multiple users/groups for multiple objects

    Currently, the methods in the BaseObjectPermissionManager do not allow assigning multiple permissions for multiple users and for multiple objects. Likewise, the assign_perm function in shortcuts does not even allow assigning multiple permissions at once. The problem with this is that permission assignments are very slow, because they cannot be properly batched together.

    This PR adds in methods to the object permission manager, and also adds two new shortcuts: bulk_assign_perms and bulk_remove_perms. In addition, I have also added a commit parameter, which allows the user of the shortcuts to go even further with the batching, and accumulate permissions so that they can all be persisted in one fell swoop. I have ensured that previous behaviour is left intact, and the new behaviour is well tested.

    Having applied these changes in a work project which relies heavily on django guardian, I have seen a big decrease in the time taken for the entire test suite to complete (down to ~20 seconds from 3 minutes).

    opened by JCourt1 0
  • Unexpected permisions for each model

    Unexpected permisions for each model

    Hi! I read your documentation. After I installed your app, new users wasn't able to see anything in Admin panel. As I understood later there are a lot of permissions per each model, which are installed automatically. I found them form superuser admin panel on Users model objects. It looks something like that (goods — my app, activation system — model on screenshot for example) изображение

    And without Can view (model name) permission my customers even can't see anything by /admin/ url.

    Explain me, please, or give url in your documentation, how to work correctly with this permissions? By what principles they are created, how can I get them for all my project?

    opened by mihalt 0
  • I think that PermissionRequiredMixin and PermissionListMixin could be better implemented

    I think that PermissionRequiredMixin and PermissionListMixin could be better implemented

    If I want to limit access at both the model and object levels, using these two mixins is not a very easy task. Because the permissions_required attribute is overridden. To get around this I had to create a new attr "object_permission" and do the following: Model Looks like:

    # Create your models here.
    from django.db import models
    from localflavor.br import models as localModels
    from django.contrib.auth.models import AbstractUser
    
    class User(AbstractUser):
        pass
    
    
    class Customer(models.Model):
        user: User = models.OneToOneField(User, on_delete=models.CASCADE)
    
        def __str__(self):
            return f'{self.user.first_name}  {self.user.last_name}'
    
    
    class Company(models.Model):
        user: User = models.OneToOneField(User, on_delete=models.CASCADE)
        customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='comapnies')
    
        def __str__(self):
            return f'{self.user.first_name}  {self.user.last_name}'
    
    
    class Project(models.Model):
        name = models.CharField(max_length=100)
        owner = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='projects')
    
        class Meta:
            permissions = (('read_project', 'Read Project'),)
    
        def __str__(self):
            return self.name
    
    
    class House(models.Model):
        rooms = models.IntegerField()
        postal_code = localModels.BRPostalCodeField()
        project = models.ForeignKey(Project, on_delete=models.CASCADE)
    
    
    

    Here I needed to create a new attribute and View:

    class ProjectsListView(PermissionRequiredMixin, PermissionListMixin, ListView):
        template_name = 'home/projects.html'
        model = models.Project
        permission_required = ["homepage.view_project"]
        object_permission = ["read_project"]
        redirect_field_name = 'next'
        login_url = 'login/'
    
        get_objects_for_user_extra_kwargs = {}
    
        def get_object_permission(self, request: HttpRequest = None) -> List[str]:
            if isinstance(self.object_permission, str):
                perms = [self.object_permission]
            elif isinstance(self.object_permission, Iterable):
                perms = [p for p in self.object_permission]
            else:
                raise ImproperlyConfigured("'PermissionRequiredMixin' requires "
                                           "'permission_required' attribute to be set to "
                                           "'<app_label>.<permission codename>' but is set to '%s' instead"
                                           % self.permission_required)
            return perms
    
        def get_get_objects_for_user_kwargs(self, queryset):
            return dict(user=self.request.user,
                        perms=self.get_object_permission(self.request),
                        klass=queryset,
                        **self.get_objects_for_user_extra_kwargs)
    
    opened by iaggocapitanio1 0
A flask extension for managing permissions and scopes

Flask-Pundit A simple flask extension to organize resource authorization and scoping. This extension is heavily inspired by the ruby Pundit library. I

Anurag Chaudhury 39 Jan 23, 2021
An enhanced permission system which support object permission in Django

django-permission Author Alisue <[email protected]> Supported python versions Python 2.7, 3.3, 3.4, 3.5, 3.6 Supported django versions Django 1

Alisue 298 May 7, 2022
Django CAS 1.0/2.0/3.0 client authentication library, support Django 2.0, 2.1, 2.2, 3.0 and Python 3.5+

django-cas-ng django-cas-ng is Django CAS (Central Authentication Service) 1.0/2.0/3.0 client library to support SSO (Single Sign On) and Single Logou

django-cas-ng 342 Jun 14, 2022
Django-registration (redux) provides user registration functionality for Django websites.

Description: Django-registration provides user registration functionality for Django websites. maintainers: Macropin, DiCato, and joshblum contributor

Andrew Cutler 897 Jun 29, 2022
Complete Two-Factor Authentication for Django providing the easiest integration into most Django projects.

Django Two-Factor Authentication Complete Two-Factor Authentication for Django. Built on top of the one-time password framework django-otp and Django'

Bouke Haarsma 1.2k Jun 20, 2022
Django Admin Two-Factor Authentication, allows you to login django admin with google authenticator.

Django Admin Two-Factor Authentication Django Admin Two-Factor Authentication, allows you to login django admin with google authenticator. Why Django

Iman Karimi 7 Apr 20, 2022
Django-react-firebase-auth - A web app showcasing OAuth2.0 + OpenID Connect using Firebase, Django-Rest-Framework and React

Demo app to show Django Rest Framework working with Firebase for authentication

Teshank Raut 4 Feb 22, 2022
Object Moderation Layer

django-oml Welcome to the documentation for django-oml! OML means Object Moderation Layer, the idea is to have a mixin model that allows you to modera

Angel Velásquez 12 Aug 22, 2019
Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication.

Welcome to django-allauth! Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (soc

Raymond Penners 7.3k Jun 21, 2022
A JSON Web Token authentication plugin for the Django REST Framework.

Simple JWT Abstract Simple JWT is a JSON Web Token authentication plugin for the Django REST Framework. For full documentation, visit django-rest-fram

Simple JWT 3k Jun 26, 2022
REST implementation of Django authentication system.

djoser REST implementation of Django authentication system. djoser library provides a set of Django Rest Framework views to handle basic actions such

Sunscrapers 2.1k Jun 19, 2022
Authentication Module for django rest auth

django-rest-knox Authentication Module for django rest auth Knox provides easy to use authentication for Django REST Framework The aim is to allow for

James McMahon 768 Jun 27, 2022
Authentication for Django Rest Framework

Dj-Rest-Auth Drop-in API endpoints for handling authentication securely in Django Rest Framework. Works especially well with SPAs (e.g React, Vue, Ang

Michael 958 Jun 20, 2022
Get inside your stronghold and make all your Django views default login_required

Stronghold Get inside your stronghold and make all your Django views default login_required Stronghold is a very small and easy to use django app that

Mike Grouchy 382 Jun 20, 2022
JSON Web Token Authentication support for Django REST Framework

REST framework JWT Auth Notice This project is currently unmaintained. Check #484 for more details and suggested alternatives. JSON Web Token Authenti

José Padilla 3.1k Jun 27, 2022
Awesome Django authorization, without the database

rules rules is a tiny but powerful app providing object-level permissions to Django, without requiring a database. At its core, it is a generic framew

null 1.5k Jun 26, 2022
An extension of django rest framework, providing a configurable password reset strategy

Django Rest Password Reset This python package provides a simple password reset strategy for django rest framework, where users can request password r

Anexia 334 Jun 18, 2022
This app makes it extremely easy to build Django powered SPA's (Single Page App) or Mobile apps exposing all registration and authentication related functionality as CBV's (Class Base View) and REST (JSON)

Welcome to django-rest-auth Repository is unmaintained at the moment (on pause). More info can be found on this issue page: https://github.com/Tivix/d

Tivix 2.3k Jun 15, 2022
Authentication for Django Rest Framework

Dj-Rest-Auth Drop-in API endpoints for handling authentication securely in Django Rest Framework. Works especially well with SPAs (e.g React, Vue, Ang

Michael 958 Jun 20, 2022