Manage user roles and permissions


The rolepermissions app builds on django-role-permissions . It includes support for tenant-aware user roles and permissions, object-level permissions, and custom user permissions.

Changes to the core User model

The User model has been extended to include:

  • roles - list of user roles

  • user_tenant_permissions - list of permissions for that tenant

Defining roles

Roles are defined in config/ roles.py .

python
from enum import Enum
from apps.rolepermissions.roles import AbstractUserRole


class WorkspacePermissions(str, Enum):
    """
    Workspace permissions
    """
    create_workspace = "create_workspace"
    update_workspace = "update_workspace"
    delete_workspace = "delete_workspace"
    view_workspace = "view_workspace"
    manage_workspace_users = "manage_workspace_users"
    manage_workspace_invitations = "manage_workspace_invitations"
    manage_workspace_roles = "manage_workspace_roles"
    manage_billing = "manage_billing"


ws_perm = WorkspacePermissions


class WorkspaceAdmin(AbstractUserRole):
    """
    available_permissions lists all the permissions that this role has.
    """
    available_permissions = {
        WorkspacePermissions.update_workspace: True,
        WorkspacePermissions.delete_workspace: True,
        ...
    }

Defining permissions

Object-level permissions can be defined in each app. Here is an example of how you can implement this for a CMS.

python
from apps.rolepermissions.permissions import register_object_checker
from apps.collection.models import Collection
from config.roles import CollectionAdmin, CollectionMember


@register_object_checker()
def access_collection(role, user, team):
    """User Collections"""

    user_collections = Collection.objects.get_user_teams(user=user)
    if role in [CollectionAdmin, CollectionMember] and collection in user_collection:
        return True

    return False

Utilities

In the next section, we will look at some utilities for assigning roles and permissions

Assign a role

text
from apps.rolepermissions.roles import assign_role

user = User.objects.get(id=1)
assign_role(user, 'workspace_member')

Check permissions

python
from rolepermissions.checkers import has_permission

# usage
has_permission(user, 'workspace_member')
# >> True

has_permission(user, 'edit_patient_file')
# >> False

Change permissions

python
from rolepermissions.permissions import grant_permission, revoke_permission

# usage
revoke_permission(user, 'create_medical_record')
grant_permission(user, 'edit_patient_file')

has_permission(user, 'create_medical_record')
# >> False

has_permission(user, 'edit_patient_file')
# >> True

Get a user’s roles

python
from apps.rolepermissions.roles import get_user_roles

role = get_user_roles(user)

Assigns a role to the user using strings

python
from apps.rolepermissions.roles import assign_role

assign_role(user, 'doctor')

Removes a role from a user using strings

python
from apps.rolepermissions.roles import remove_role

remove_role(user, 'doctor')

Any permissions that were explicitly granted to the user that are also defined to be granted by this role will be revoked when this role is revoked.

python
class TeamMember(AbstractUserRole):
     available_permissions = {
         "change_file": False,
     }

class TeamManager(AbstractUserRole):
     available_permissions = {
         "change_file": True,
     }

# usage
grant_permission(user, "change_file")
remove_role(user, TeamManager)

has_permission(user, "change_file")

In the example, the user no longer has the "change_file" permissions, even though it was set explicitly before the TeamManager role was removed.

Clear roles

python
from apps.rolepermissions.roles import clear_roles

clear_roles(user)

A brief look at some functions

  1. function available_perm_status(user)

Returns a dictionary containing all permissions available across all the specified user's roles. Note that if a permission is granted in one role, it overrides any permissions set to False in other roles. Permissions are the keys of the dictionary, and values are True or False indicating if the permission is granted or not.

python
from apps.rolepermissions.permissions import available_perm_status

permissions = available_perm_status(user)

if permissions['create_medical_record']:
    print('user can create medical record')
  1. function grant_permission(user, permission_name)

Grants a permission to a user. Will raise a RolePermissionScopeException for a permission that is not listed in the user's roles' available_permissions .

python

python
from apps.rolepermissions.permissions import grant_permission

grant_permission(user, 'create_medical_record')
  1. function revoke_permission(user, permission_name)

Revokes a permission from a user. Will raise a RolePermissionScopeException for a permission that is not listed in the user's roles' available_permissions .

python
from apps.rolepermissions.permissions import revoke_permission

revoke_permission(user, 'create_medical_record')

Permission and role verification

The following functions will always return True for users with superuser status.

  1. function has_role(user, roles)

Receives a user and a role and returns True if user has the specified role. Roles can be passed as an object, snake cased string representation, or inside a list.

python

text
from apps.rolepermissions.checkers import has_role
from config.roles import Doctor

if has_role(user, [Doctor, 'nurse']):
    print 'User is a Doctor or a nurse'
  1. function has_permission(user, permission)

Receives a user and a permission and returns True is the user has ths specified permission.

python
from apps.rolepermissions.checkers import has_permission
from config.roles import Doctor
from records.models import MedicalRecord

if has_permission(user, 'create_medical_record'):
    medical_record = MedicalRecord(...)
    medical_record.save()
  1. function has_object_permission(checker_name, user, obj)

Receives a string referencing the object permission checker, a user and the object to be verified.

python
from app.rolepermissions.checkers import has_object_permission
from clinics.models import Clinic

clinic = Clinic.objects.get(id=1)

if has_object_permission('access_clinic', user, clinic):
    print 'access granted'

Tests

The app is fully tested. All the tests from the original library have been ported over to pytest.