Package: baseapp-drf-view-action-permissions
BaseApp View Action Permissions - DjangoThis app uses django provided permission and group model and provides the ability to add roles to a django model, and make views from thedjango-restframeworkcheck for them. APermissionrepresents the lowest single unit of access. AGroupis a collection of Permissions. ARolecan have many Permision Groups, many Permissions and manyExcluded Permissions. The access of a Role is the aggregation of its single Permissions + the permissions on itsPermissionGroups - its Excluded Permissions.Install the packageAdd torequirements/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-permissionsAdd the app to your project INSTALLED_APPSINSTALLED_APPS=[..."baseapp_drf_view_action_permissions",]Add the mixin to your modelfromdjango.dbimportmodelsfrombaseapp_drf_view_action_permissions.mixinsimportPermissionModelMixinclassUser(PermissionModelMixin,models.Model):...Implement the permission classfromrest_frameworkimportviewsetsfrombaseapp_drf_view_action_permissions.actionimportDjangoActionPermissionsclassUserViewSet(viewsets.ModelViewSet):permission_classes=[DjangoActionPermissions,]permission_base="users"# if not defined the app label would be usedqueryset=User.objects.all()model_class=User# Only required if viewset not using queryset# optional perms_map_action to override default behaviourperms_map_action={'custom_action':['users.list_users']}...This permission class will check that theusercalling an action in the viewset has aPermissionwith thecodename{app_label}.{action}_{permission_base|app_label}. In this example, the viewset will raise a 403 error if the user doesn't have aPermissionwith the codenameusers.add_usersand tries to call thecreateaction (using a POST method). The same applies to thelist,retrieve,updateanddestroyactions. Note that PUT and PATCH requests expect the sameusers.change_updatePermission, event if DRF maps PUT requests with thepartial_updateaction.For additional actions added to the viewset with the@actiondecorator, the followingcodenameconvention is expected if you do not want to manually specify it in the view usingperms_map_action:{action_name}_{permission_base|app_label}. For example, if we update the previous viewset to look like this (assuming the app name isusers):classUserViewSet(viewsets.ModelViewSet):permission_classes=[ActionPermission]permission_base="users"...@action(methods=['GET','POST'])deflessons(self,*args,**kwargs):...The permission class will expect the user to have aPermissionwith the codenameusers.lessons_userswhen a GET or POST request is made.Theperms_map_actioncan also accept a method with the following signaturedef method_name(user, view, obj=None).defcheck_object_permission(user,view,obj=None):ifnotobj:returnTruereturnobj.user.id==user.idclassUserViewSet(viewsets.ModelViewSet):permission_classes=[ActionPermission]permission_base="users"perms_map_action={'update':['users.change_users',check_object_permission]}...TheDjangoActionPermissionsclass will only work with models injected to therequestby one of the DRF'sauthentication mechanismsas theuserproperty.Create PermissionsOnce you add the module to your project's INSTALLED_APPS, you will get access to a new section on the admin site calledDJANGO-VIEW-ACTION-PERMISSIONS, with theRoletable. 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 permissionDjango automatically creates four basic permissions for every model, these areadd_{modelname},change_{modelname},view_{modelname}anddelete_{modelname}. To add new ones, you can define them in the model meta class. see example belowclassUser(models.Model):...classMeta: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 addview_{modelname}_listin the permissions list, except you want to specify the permission to use inperms_map_actionCreating migrations for groupsWe have provided two utility function to load and remove permissions from group. See example belowfromdjango.dbimportmigrationsfrombaseapp_drf_view_action_permissions.utilsimportget_permission_loader,get_permission_removerpermissions=[{"name":"Test Group","permissions":['list_testmodel','disable_testmodel','add_testmodel'],},]classMigration(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 theremove_group=Truethis will delete the group on rollback. if this is an update to an existing group make sure to set it toFalse.Exclude views from a viewsetIf you want to exclude a view from the permission checking logic, you can specify the name of the action in thepermission_exclude_viewsproperty. Example:classUserViewSet(viewsets.ModelViewSet):permission_classes=[ActionPermission]permission_base="users"permission_exclude_views=['retrieve','list','lessons']...@action(methods=['GET','POST'])deflessons(self,*args,**kwargs):...Specific permission_base string for a specific viewIf you want to override thepermission_basefor a specific custom action, you can. Example:classUserViewSet(viewsets.ModelViewSet):permission_classes=[ActionPermission]permission_base="users"...@action(methods=['GET'],permission_base='other_base')deflessons(self,*args,**kwargs):...Here, thelessons_other_basePermissionwould be expected.Check permission outside ofDjangoActionPermissionsYou can check user permission using thehas_perm(for one permission) andhas_perms(for multiple permissions) method of the user object.user=User.objects.first()assertuser.has_perm('users.add_users')assertuser.has_perms(['users.change_users','vendors.add_client'])Note that super user has all the permissions.IP Address RestrictionsTo restrict Django admin and all endpoints by Ip address, Add below to the middlewaresMIDDLEWARE=[..."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, setIP_RESTRICT_ONLY_DJANGO_ADMIN=True. If you want to only allow whitelisted IP also setALLOW_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. UseIpAddressPermissionon such views.Restrict Viewset by IpYou can also useIpAddressPermissionto restrict specific or all view actions.frombaseapp_drf_view_action_permissions.actionimportIpAddressPermissionclassDummyIpViewSet(viewsets.GenericViewSet):permission_classes=[IpAddressPermission,]deflist(self,*args,**kwargs):returnresponse.Response([])@decorators.action(methods=["GET"],detail=False,permission_classes=[IpAddressPermission,])defcustom_action(self,*args,**kwargs):returnresponse.Response({})