Search code examples
pythondjangodjango-authentication

Define permissions to existing users for viewing or modifying other users


I'm currently learning my way around the permission framework in Django and I'm looking to define a set of permissions to a user, which defines if they can see or modify other users in the system.

I'm using the django.auth framework on a multi-schema database where each schema has its own set of users.

I want to essentially apply this, but to the built-in users model.

class Meta:
        permissions = (
            ("can_view_users", "Can view all users"),
            ("can_modify_users", "Can modify all users"),
            ("can_password_reset_users", "Can reset passwords for all users"),
            ("can_delete_users", "Can delete all users"),
        )

However I cannot seem to find any documentation on applying this logic to the built-in authentication framework.


Solution

  • I have resolved this by deploying my own permission app. I still create and manage users & sessions via the built-in frameworks however utilize my own model for managing user permissions.

    It works as I intended and lets me customize as much as I would like from a per user perspective.


    Steps

    1. Create new app for managing permissions called users
    2. Create a model called Permissions with ForeignKey to AUTH_USER_MODEL
    3. Create a custom decorator for checking a users permission on calling a view
    4. Apply decorator to views, specifying which permission is required for the view

    At this point, pages cannot be utilize if the user does not have the specified permission. This is great as if the user tried to post to users/new they server will respond with HTTP 503.


    The next problem was modifying the base template so users would only see the menu options they were permitted to see. I achieved this by using a custom context_processor.

    1. Create a custom context_processor which utilizes Permissions user_has_perms to retrieve all permissions for the authenticated user.
    2. Add the processor to settings.py
    3. Utilize djangos template processing to render fields user has access to

    Code

    model Permissions

    class Permissions(models.Model):
        permission_id = models.AutoField(primary_key=True)
        user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
        
        ## User Management ##
        
        can_user_view_staff = models.BooleanField(default = False)
    
    def user_has_perms(user, perm):
        user_exists = Permissions.objects.filter(user_id=user).count()
       
        if user_exists:
            if perm == None:
                ## if no permission specified, return all permissions for user ##
                return Permissions.objects.filter(user_id=user)
            else:            
                return Permissions.objects.filter(user_id=user).values(perm).last()[perm]
        else:
            return False
    

    decorator authorization

    from django.http import HttpResponseForbidden
    from apps.users.models import user_has_perms
    
    
    ## Check if user has permission to load page ##
    
    def authorization_required(permissions=''):
        def decorator(view):
            def wrapper(request, *args, **kwargs):
                can_user = user_has_perms(request.user, permissions)
    
                if can_user or request.user.is_superuser:
                    return view(request, *args, **kwargs)
                else:
                    return HttpResponseForbidden()
            return wrapper
        return decorator
    

    views view with decorator

    from decorators.authorization import authorization_required
    
    @login_required
    @authorization_required('can_user_view_staff')
    def users(request):
        user_model = get_user_model()
        user_list = user_model.objects.all()
    
        context = {
                    'users': user_list
                }
                
        return render(request, 'users/users.html', context)
    

    context_processor

    from apps.users.models import user_has_perms
    
    def permissions(request):
        if request.user.is_authenticated:
            user_perms = user_has_perms(request.user, None)
        else:
            user_perms = None
    
        return {
            'user_perms': user_perms
            }
    

    Modify settings.py to add new processor

    TEMPLATES = [
        {
            'OPTIONS': {
                'context_processors': [
    
                    'context_processors.perm_context.permissions',
                    ],
            },
        },
    ]
    

    Update base.html to check user permissions before rendering fields

    {% if user_perms.can_user_view_staff or request.user.is_superuser %}
        <a class="dropdown-item" href="/users">User Management</a>
    {% endif %}