Understanding Kolibri’s user model, roles (learners, coaches, admins), collections, memberships, and permission system
Kolibri implements a sophisticated role-based permission system that supports multi-facility deployments, hierarchical collections, and fine-grained access control.
class Membership(FacilityDataSyncableModel): """ Links a user to a collection. Being a member of a collection also means being a member of all parent collections in the hierarchy. """ id = UUIDField(primary_key=True) user = models.ForeignKey("FacilityUser", on_delete=models.CASCADE) collection = models.ForeignKey("Collection", on_delete=models.CASCADE) dataset = models.ForeignKey("FacilityDataset", on_delete=models.CASCADE)
class Role(FacilityDataSyncableModel): """ Grants a user a role with respect to a collection and all collections below it in the hierarchy. """ id = UUIDField(primary_key=True) user = models.ForeignKey("FacilityUser", on_delete=models.CASCADE) collection = models.ForeignKey("Collection", on_delete=models.CASCADE) kind = models.CharField(max_length=26, choices=role_kinds.choices) dataset = models.ForeignKey("FacilityDataset", on_delete=models.CASCADE)
Role Kinds:
from kolibri.core.auth.constants.role_kinds import ADMIN, COACH, ASSIGNABLE_COACH# ADMIN: Full administrative access to facility# COACH: Can manage classes and view learner progress# ASSIGNABLE_COACH: Can be assigned as coach for specific classrooms
Users who manage classes and monitor learner progress:
# Create a coachcoach = FacilityUser.objects.create_user( username="coach1", password="password", facility=facility, full_name="John Coach")# Grant coach role for a classroomRole.objects.create( user=coach, collection=classroom, kind=role_kinds.COACH)
Users with full administrative control over a facility:
# Create a facility adminadmin = FacilityUser.objects.create_user( username="admin1", password="password", facility=facility, full_name="Admin User")# Grant admin role for the facilityRole.objects.create( user=admin, collection=facility, kind=role_kinds.ADMIN)
from kolibri.core.device.models import DevicePermissions# Grant device permissionsDevicePermissions.objects.create( user=user, is_superuser=True, can_manage_content=True)
from kolibri.core.auth.constants.user_kinds import ANONYMOUS# Check if guest access is allowedif get_device_setting("allow_guest_access"): # Anonymous users can browse content pass
class BasePermissions(object): """ Base class for all permission classes. Defines CRUD permission checks. """ def user_can_create_object(self, user, obj): """Returns True if user can create obj.""" raise NotImplementedError def user_can_read_object(self, user, obj): """Returns True if user can read obj.""" raise NotImplementedError def user_can_update_object(self, user, obj): """Returns True if user can update obj.""" raise NotImplementedError def user_can_delete_object(self, user, obj): """Returns True if user can delete obj.""" raise NotImplementedError def readable_by_user_filter(self, user): """Returns a Q object filtering objects readable by user.""" raise NotImplementedError
class RoleBasedPermissions(BasePermissions): """ Permissions based on user's roles with respect to collections. """ def __init__( self, target_field, can_be_created_by, can_be_read_by, can_be_updated_by, can_be_deleted_by, collection_field="collection", ): """ :param target_field: Field through which role target is referenced :param can_be_created_by: Tuple of role kinds that can create :param can_be_read_by: Tuple of role kinds that can read :param can_be_updated_by: Tuple of role kinds that can update :param can_be_deleted_by: Tuple of role kinds that can delete """ pass
Permissions can be combined using | (OR) and & (AND):
# Allow if user is owner OR adminpermissions = IsOwn() | IsAdminForOwnFacility()# Require both conditionspermissions = IsSelf() & IsAdminForOwnFacility()
Kolibri provides several built-in permission classes:
from kolibri.core.auth.permissions.general import ( IsOwn, # User owns the object IsSelf, # Object is the user themselves IsAdminForOwnFacility, # User is admin for their facility)from kolibri.core.auth.permissions.auth import ( CoachesCanManageGroupsForTheirClasses, CoachesCanManageMembershipsForTheirGroups, FacilityAdminCanEditForOwnFacilityDataset,)
# Check if user can read an objectif obj.permissions.user_can_read_object(request.user, obj): # Allow access pass# Get queryset filtered by read permissionsreadable_objects = MyModel.objects.filter( MyModel.permissions.readable_by_user_filter(request.user))
# Check if user has a role for a collectionif user.has_role_for_collection(role_kinds.COACH, classroom): # User is a coach for this classroom pass# Check if user is admin for a facilityif user.is_facility_user and user.has_role_for_collection(role_kinds.ADMIN, facility): # User is a facility admin pass
from kolibri.core.auth.constants import role_kindsdef user_can_manage_lesson(user, lesson): """Check if user can manage a lesson.""" if not user.is_authenticated: return False # Check if user is coach or admin for the lesson's collection return user.has_role_for_collection( role_kinds.COACH, lesson.collection ) or user.has_role_for_collection( role_kinds.ADMIN, lesson.collection.get_root() )