guettli
guettli

Reputation: 27806

Django: Decoupling Permission-Check from Calling the View

Django: Decoupling Permission-Check from Calling the View

I would like to decouple the permission checking in django.

Current draw-back: If you use decorators like login_required, then you can't know in advance whether a user has the permission to do so or not.

I would like to split this into two steps:

  1. check permissions
  2. call the view.

Use Case 1: Admin tool

I want an tool for admins where they can check the access-permissions of users. This requires:

  1. The check must not use the current request.user since this is the wrong user object.
  2. The check must not actually call the view, since this might alter data.

Use Case 2: Show Link as disabled.

I want to show links as disabled (grayed out and without "href") if a user does not have the permission to see linked page.

Dream

Returning a boolean for "ok" and "permission denied" is nice. But the big benefit would be if the admin could get a reason.

Example:

  1. Admin opens "Check Perm Tool"
  2. He selects a view/URL
  3. The admin hits "submit"

Result:

------------------------------
| User     | Allowed | Reason
------------------------------
| fooadmin |  Yes    | is_superuser
| foouser  |  No     | missing permission "view-bar-at-midnight"
| foobar   |  Yes    | User has permission "view-bar-at-midnight"

Question

How to get this dream come true?

... Just for the records, I posted the idea on the django-develop list: https://groups.google.com/forum/#!topic/django-developers/rpTh4G3BgIQ

Upvotes: 1

Views: 611

Answers (1)

Ryan Schuster
Ryan Schuster

Reputation: 506

I'm assuming you already have a list of all the permissions needed to view each view/URL? i.e. You know that URL /bar requires permissions superuser and view-bar-at-night ahead of time?

If so, try adding a ViewPermissions model to models.py first:

from django.db import models

class ViewPermissions(models.Model):
    view_url = models.CharField(help_text="The URL of this view")

Then in admin.py:

from models import ViewPermissions
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.contrib.auth.models import Permission
from django.contrib import admin
from django import forms

class ViewPermissionFormField(forms.ModelForm):
    permissions = forms.ModelMultipleChoiceField(
        widget=FilteredSelectMultiple("Permissions", is_stacked=False),
        queryset=models.Permission.objects.all()
    )

    def save(self, commit=True):
        extra_field = self.cleaned_data.get('extra_field', None)
    return super(ViewPermissionFormField, self).save(commit=commit)

    class Meta:
        fields = '__all__'
        model = ViewPermissions

class ViewPermissionsAdmin(admin.ModelAdmin):
    form = ViewPermissionsFormField
    fieldsets = (
        (None, {
            'fields': 'view_url', 'permissions'
        })
    )
            

Now you can add permissions to each view URL in admin to keep track of things. FYI you can create your own custom permissions and add them to Django's existing ones.

You can create an admin form that pulls up the users, lists their permissions, then leverages ViewPermissions to see what URLs that user can visit based on what permissions they have (Use case 1).

You can now check the user's permission in your view function (or class if that's how you're defining your views) like so:

from django.contrib.auth.models import Permission
from models import ViewPermissions

def my_view(request)
    view_url = request.get_current_url()
    permissions_required = ViewPermissions.objects.filter(view_url = view_url).permissions
    context['user_permissions'] = []
    for p in permissions_required:
        if request.user.has_permission(p):
            context['user_permissions'].append(p)

Now you can pass those permissions into your template and grey out links accordingly (Use case 2).

So something like that might work...

Edit:

The link to Django docs no longer available. One might visit this instead.

Upvotes: 3

Related Questions