Per object permissions for Django

Overview

django-guardian

https://github.com/django-guardian/django-guardian/workflows/Tests/badge.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+)

GitHub Actions run tests against Django versions 2.2, 3.0, 3.1, 3.2, and main.

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.
Comments
  • 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
  • Support `only_with_perms_in` in `shortcuts.get_groups_with_perms`

    Support `only_with_perms_in` in `shortcuts.get_groups_with_perms`

    There is a filter option only_with_perms_in for shortcuts.get_users_with_perms.

    It would be nice to have the same filter option also for shortcuts.get_groups_with_perms.

    opened by Cornelicorn 0
  • get_perms returns empty list while get_user_perms returns permissions

    get_perms returns empty list while get_user_perms returns permissions

    I wrote a script to give me all objects which don't have enough permissions. I used shortcuts get_perms to check the permissions for the user and I found some entries because they returned []. But on closer inspection, I can see the obejcts have permissions and they are returned when I use get_user_perms.

    Shouldn't get_perms return a superset of get_user_perms?

    Here is my script, using guardian 2.4.0

    from guardian.shortcuts import get_user_perms, get_perms
    from app.models import PreferencesRecord
    
    for pref in tqdm.tqdm(PreferencesRecord.objects.all()):
        perms = get_perms(pref.user, pref)
        if len(perms) < 4:
            print(f"User {pref.user.username} ({pref.user.id}) has {len(perms)} permissions on {pref.id}")
            user_perms = get_user_perms(pref.user, pref)
            if user_perms != perms:
                print(f"But the user permissions return {user_perms}, while perms are {perms}")
    
    opened by BSVogler 0
  • How to suggest a selection at dropdown menu on the admin site

    How to suggest a selection at dropdown menu on the admin site

    Hi folks,

    I am try to suggest my favorite permission at admin site when adding a permission (dop down menu). How can I do that?

    image

    admin.py

    class DeploymentUserObjectPermissionAdmin(GuardedModelAdmin):
        list_display = ('permission', 'user', 'content_object')
        search_fields = ('permission', 'user', 'content_object')
        ordering = ('-permission',
    

    models.py

    class DeploymentUserObjectPermission(UserObjectPermissionBase):
        content_object = models.ForeignKey(Deployment, on_delete=models.CASCADE)
    
    opened by bauergeorg 2
  • Checking permission of tweaked direct-foreign-keys

    Checking permission of tweaked direct-foreign-keys

    Hi folks,

    I found that wonderfull example to add a special key to the permission.

    Here is my example (users/models.py):

    class DeploymentUserObjectPermission(UserObjectPermissionBase):
        content_object = models.ForeignKey(Deployment, on_delete=models.CASCADE)
    
    class DeploymentGroupObjectPermission(GroupObjectPermissionBase):
        content_object = models.ForeignKey(Deployment, on_delete=models.CASCADE)
    
    class ProjectUserObjectPermission(UserObjectPermissionBase):
        content_object = models.ForeignKey(Project, on_delete=models.CASCADE)
        type = models.ForeignKey(DeploymentType, on_delete=models.CASCADE)
    
    class ProjectGroupObjectPermission(GroupObjectPermissionBase):
        content_object = models.ForeignKey(Project, on_delete=models.CASCADE)
        type = models.ForeignKey(DeploymentType, on_delete=models.CASCADE)
    

    Here are the models of my app: (software/models.py):

    # Release, RC, Develop, Test
    class DeploymentType(models.Model):
    	name = models.CharField(max_length=100, default='unknown')
    	description = models.TextField()
    	image = models.ImageField(default='default_deploymenttype.jpg', upload_to='deploymenttype_pics')
    
    	def __str__(self):
    		return self.name
    
    class Project(models.Model):
    	title = models.CharField(max_length=100)
    	description = models.TextField()
    	date_created = models.DateTimeField(default=timezone.now)
    	author = models.ForeignKey(User, on_delete=models.CASCADE)
    	image = models.ImageField(default='default_project.jpg', upload_to='project_pics')
    
    	def __str__(self):
    		return self.title
    
    class Deployment(models.Model):
    	title = models.CharField(max_length=100)
    	project = models.ForeignKey(Project, on_delete=models.CASCADE)
    	file = models.FileField(null=True,blank=True,upload_to='Files')
    	content = models.TextField()
    	date_posted = models.DateTimeField(default=timezone.now)
    	author = models.ForeignKey(User, on_delete=models.CASCADE)
    	type = models.ForeignKey(DeploymentType, on_delete=models.CASCADE)
    
    	def __str__(self):
    		return self.title
    
    

    A Deployment is a part of a Project. Every Deployment has a type (RC, Release, Test, Develop, ...).

    I wan't to filter List Views and grant access by setting special permissions. But how? Can someone help me?

    opened by bauergeorg 2
Customize the behavior of django.contrib.auth permissions.

Customizando o comportamento do django.contrib.auth. O que queremos? Não criar as permissões padrões automaticamente (add, delete, view, read). Criar

Henrique Bastos 7 Nov 26, 2022
Django URL Shortener is a Django app to to include URL Shortening feature in your Django Project

Django URL Shortener Django URL Shortener is a Django app to to include URL Shortening feature in your Django Project Install this package to your Dja

Rishav Sinha 4 Nov 18, 2021
A pickled object field for Django

django-picklefield About django-picklefield provides an implementation of a pickled object field. Such fields can contain any picklable objects. The i

Gintautas Miliauskas 167 Oct 18, 2022
A pickled object field for Django

django-picklefield About django-picklefield provides an implementation of a pickled object field. Such fields can contain any picklable objects. The i

Gintautas Miliauskas 167 Oct 18, 2022
Meta package to combine turbo-django and stimulus-django

Hotwire + Django This repository aims to help you integrate Hotwire with Django ?? Inspiration might be taken from @hotwired/hotwire-rails. We are sti

Hotwire for Django 31 Aug 9, 2022
django-reversion is an extension to the Django web framework that provides version control for model instances.

django-reversion django-reversion is an extension to the Django web framework that provides version control for model instances. Requirements Python 3

Dave Hall 2.8k Jan 2, 2023
Django-environ allows you to utilize 12factor inspired environment variables to configure your Django application.

Django-environ django-environ allows you to use Twelve-factor methodology to configure your Django application with environment variables. import envi

Daniele Faraglia 2.7k Jan 7, 2023
Rosetta is a Django application that eases the translation process of your Django projects

Rosetta Rosetta is a Django application that facilitates the translation process of your Django projects. Because it doesn't export any models, Rosett

Marco Bonetti 909 Dec 26, 2022
Cookiecutter Django is a framework for jumpstarting production-ready Django projects quickly.

Cookiecutter Django Powered by Cookiecutter, Cookiecutter Django is a framework for jumpstarting production-ready Django projects quickly. Documentati

Daniel Feldroy 10k Dec 31, 2022
Django project starter on steroids: quickly create a Django app AND generate source code for data models + REST/GraphQL APIs (the generated code is auto-linted and has 100% test coverage).

Create Django App ?? We're a Django project starter on steroids! One-line command to create a Django app with all the dependencies auto-installed AND

imagine.ai 68 Oct 19, 2022
django-quill-editor makes Quill.js easy to use on Django Forms and admin sites

django-quill-editor django-quill-editor makes Quill.js easy to use on Django Forms and admin sites No configuration required for static files! The ent

lhy 139 Dec 5, 2022
A Django chatbot that is capable of doing math and searching Chinese poet online. Developed with django, channels, celery and redis.

Django Channels Websocket Chatbot A Django chatbot that is capable of doing math and searching Chinese poet online. Developed with django, channels, c

Yunbo Shi 8 Oct 28, 2022
A handy tool for generating Django-based backend projects without coding. On the other hand, it is a code generator of the Django framework.

Django Sage Painless The django-sage-painless is a valuable package based on Django Web Framework & Django Rest Framework for high-level and rapid web

sageteam 51 Sep 15, 2022
A beginner django project and also my first Django project which involves shortening of a longer URL into a short one using a unique id.

Django-URL-Shortener A beginner django project and also my first Django project which involves shortening of a longer URL into a short one using a uni

Rohini Rao 3 Aug 8, 2021
Dockerizing Django with Postgres, Gunicorn, Nginx and Certbot. A fully Django starter project.

Dockerizing Django with Postgres, Gunicorn, Nginx and Certbot ?? Features A Django stater project with fully basic requirements for a production-ready

null 8 Jun 27, 2022
pytest-django allows you to test your Django project/applications with the pytest testing tool.

pytest-django allows you to test your Django project/applications with the pytest testing tool.

pytest-dev 1.1k Dec 14, 2022
APIs for a Chat app. Written with Django Rest framework and Django channels.

ChatAPI APIs for a Chat app. Written with Django Rest framework and Django channels. The documentation for the http end points can be found here This

Victor Aderibigbe 18 Sep 9, 2022
django-dashing is a customisable, modular dashboard application framework for Django to visualize interesting data about your project. Inspired in the dashboard framework Dashing

django-dashing django-dashing is a customisable, modular dashboard application framework for Django to visualize interesting data about your project.

talPor Solutions 703 Dec 22, 2022
Django-MySQL extends Django's built-in MySQL and MariaDB support their specific features not available on other databases.

Django-MySQL The dolphin-pony - proof that cute + cute = double cute. Django-MySQL extends Django's built-in MySQL and MariaDB support their specific

Adam Johnson 504 Jan 4, 2023