Skip to main content

Permissions module for baseapp.

Project description

BaseApp View Action Permissions - Django

This app uses django provided permission and group model and provides the ability to add roles to a django model, and make views from the django-restframework check for them. A Permission represents the lowest single unit of access. A Group is a collection of Permissions. A Role can have many Permision Groups, many Permissions and many Excluded Permissions. The access of a Role is the aggregation of its single Permissions + the permissions on its Permission Groups - its Excluded Permissions.

Install the package

Add to requirements/base.txt (replacing everything inside brackets):

baseapp-drf-view-action-permissions @ git+https://github.com/silverlogic/baseapp-backend.git@v0.1#subdirectory=baseapp-drf-view-action-permissions

Add the app to your project INSTALLED_APPS

INSTALLED_APPS = [
    ...
    "baseapp_drf_view_action_permissions",
]

Add the mixin to your model

from django.db import models
from baseapp_drf_view_action_permissions.mixins import PermissionModelMixin

class User(PermissionModelMixin, models.Model):
    ...

Implement the permission class

from rest_framework import viewsets
from baseapp_drf_view_action_permissions.action import DjangoActionPermissions

class UserViewSet(viewsets.ModelViewSet):
    permission_classes = [DjangoActionPermissions, ]
    permission_base = "users" # if not defined the app label would be used
    queryset = User.objects.all()
    model_class = User # Only required if viewset not using queryset
    # optional perms_map_action to override default behaviour
    perms_map_action = {
        'custom_action': ['users.list_users']
    }
    ...

This permission class will check that the user calling an action in the viewset has a Permission with the codename {app_label}.{action}_{permission_base|app_label}. In this example, the viewset will raise a 403 error if the user doesn't have a Permission with the codename users.add_users and tries to call the create action (using a POST method). The same applies to the list, retrieve, update and destroy actions. Note that PUT and PATCH requests expect the same users.change_update Permission, event if DRF maps PUT requests with the partial_update action.

For additional actions added to the viewset with the @action decorator, the following codename convention is expected if you do not want to manually specify it in the view using perms_map_action: {action_name}_{permission_base|app_label}. For example, if we update the previous viewset to look like this (assuming the app name is users):

class UserViewSet(viewsets.ModelViewSet):
    permission_classes = [ActionPermission]
    permission_base = "users"
    ...

    @action(methods=['GET', 'POST'])
    def lessons(self, *args, **kwargs):
        ...

The permission class will expect the user to have a Permission with the codename users.lessons_users when a GET or POST request is made.

The perms_map_action can also accept a method with the following signature def method_name(user, view, obj=None).

def check_object_permission(user, view, obj=None):
    if not obj:
        return True
    return obj.user.id ==  user.id

class UserViewSet(viewsets.ModelViewSet):
    permission_classes = [ActionPermission]
    permission_base = "users"
    perms_map_action = {
        'update': ['users.change_users', check_object_permission]
    }
    ...

The DjangoActionPermissions class will only work with models injected to the request by one of the DRF's authentication mechanisms as the user property.

Create Permissions

Once you add the module to your project's INSTALLED_APPS, you will get access to a new section on the admin site called DJANGO-VIEW-ACTION-PERMISSIONS, with the Role table. You can set all the roles that you need there and groups using Django built in groups, but we recommend to create data migrations to ensure that your roles exists in every environment of your app.

Adding new permission

Django automatically creates four basic permissions for every model, these are add_{modelname}, change_{modelname}, view_{modelname} and delete_{modelname}. To add new ones, you can define them in the model meta class. see example below

class User(models.Model):
    ...

    class Meta:
        permissions = [
            ("disable_users", "Can be able to disable users"),
            ("activate_users", "Can be able to activate users"),
        ]

After defining the permissions, you need to generate a migration using Django command ./manage.py makemigrations. Make sure to have generated the migration before any other migration that uses this new permission i.e writing a migration to add the new permission to a group.

If you want to use the default drf list view action make sure to add view_{modelname}_list in the permissions list, except you want to specify the permission to use in perms_map_action

Creating migrations for groups

We have provided two utility function to load and remove permissions from group. See example below

from django.db import migrations

from baseapp_drf_view_action_permissions.utils import get_permission_loader, get_permission_remover

permissions = [
    {"name": "Test Group", "permissions": ['list_testmodel', 'disable_testmodel', 'add_testmodel'],},
]


class Migration(migrations.Migration):

    dependencies = [
        ("testapp", "0002_alter_testmodel_options"),
    ]

    operations = [
        migrations.RunPython(get_permission_loader(permissions=permissions), get_permission_remover(permissions=permissions, remove_group=True)),
    ]

The above migrations will create a new group if not exists and assign the following permissions. notice the remove_group=True this will delete the group on rollback. if this is an update to an existing group make sure to set it to False.

Exclude views from a viewset

If you want to exclude a view from the permission checking logic, you can specify the name of the action in the permission_exclude_views property. Example:

class UserViewSet(viewsets.ModelViewSet):
    permission_classes = [ActionPermission]
    permission_base = "users"
    permission_exclude_views = ['retrieve', 'list', 'lessons']
    ...

    @action(methods=['GET', 'POST'])
    def lessons(self, *args, **kwargs):
        ...

Specific permission_base string for a specific view

If you want to override the permission_base for a specific custom action, you can. Example:

class UserViewSet(viewsets.ModelViewSet):
    permission_classes = [ActionPermission]
    permission_base = "users"
    ...

    @action(methods=['GET'], permission_base='other_base')
    def lessons(self, *args, **kwargs):
        ...

Here, the lessons_other_base Permission would be expected.

Check permission outside of DjangoActionPermissions

You can check user permission using the has_perm (for one permission) and has_perms (for multiple permissions) method of the user object.

user = User.objects.first()
assert user.has_perm('users.add_users')
assert user.has_perms(['users.change_users', 'vendors.add_client'])

Note that super user has all the permissions.

IP Address Restrictions

To restrict Django admin and all endpoints by Ip address, Add below to the middlewares

MIDDLEWARE = [
    ...
    "django.contrib.auth.middleware.AuthenticationMiddleware", # make sure it's after the authentication middleware
    ...
    "baseapp_drf_view_action_permissions.middleware.RestrictIpMiddleware",
    ...
]

To restrict only Django admin, set IP_RESTRICT_ONLY_DJANGO_ADMIN=True. If you want to only allow whitelisted IP also set ALLOW_ONLY_WHITELISTED_IP=True.

If ALLOW_ONLY_WHITELISTED_IP is False it will allow any Ip that is not specified in Ip Restrictions.

Note: Restriction by role only works in Django admin when using the middleware since user object is not available in middleware when using drf views. Use IpAddressPermission on such views.

Restrict Viewset by Ip

You can also use IpAddressPermission to restrict specific or all view actions.

from baseapp_drf_view_action_permissions.action import IpAddressPermission


class DummyIpViewSet(viewsets.GenericViewSet):
    permission_classes =  [IpAddressPermission, ]

    def list(self, *args, **kwargs):
        return response.Response([])

    @decorators.action(methods=["GET"], detail=False, permission_classes=[IpAddressPermission, ])
    def custom_action(self, *args, **kwargs):
        return response.Response({})

Project details


Release history Release notifications | RSS feed

This version

1.0

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page