2026-01-09 00:51:25 +01:00
|
|
|
"""Permission handlers for the calendars core app."""
|
|
|
|
|
|
2026-03-06 02:47:03 +01:00
|
|
|
import logging
|
|
|
|
|
|
2026-01-09 00:51:25 +01:00
|
|
|
from django.core import exceptions
|
|
|
|
|
|
|
|
|
|
from rest_framework import permissions
|
|
|
|
|
|
2026-03-06 02:47:03 +01:00
|
|
|
from core.entitlements import EntitlementsUnavailableError, get_user_entitlements
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
2026-01-09 00:51:25 +01:00
|
|
|
ACTION_FOR_METHOD_TO_PERMISSION = {
|
|
|
|
|
"versions_detail": {"DELETE": "versions_destroy", "GET": "versions_retrieve"},
|
|
|
|
|
"children": {"GET": "children_list", "POST": "children_create"},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class IsAuthenticated(permissions.BasePermission):
|
|
|
|
|
"""
|
|
|
|
|
Allows access only to authenticated users. Alternative method checking the presence
|
|
|
|
|
of the auth token to avoid hitting the database.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def has_permission(self, request, view):
|
|
|
|
|
return bool(request.auth) or request.user.is_authenticated
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class IsAuthenticatedOrSafe(IsAuthenticated):
|
|
|
|
|
"""Allows access to authenticated users (or anonymous users but only on safe methods)."""
|
|
|
|
|
|
|
|
|
|
def has_permission(self, request, view):
|
|
|
|
|
if request.method in permissions.SAFE_METHODS:
|
|
|
|
|
return True
|
|
|
|
|
return super().has_permission(request, view)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class IsSelf(IsAuthenticated):
|
|
|
|
|
"""
|
|
|
|
|
Allows access only to authenticated users. Alternative method checking the presence
|
|
|
|
|
of the auth token to avoid hitting the database.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def has_object_permission(self, request, view, obj):
|
|
|
|
|
"""Write permissions are only allowed to the user itself."""
|
|
|
|
|
return obj == request.user
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class IsOwnedOrPublic(IsAuthenticated):
|
|
|
|
|
"""
|
|
|
|
|
Allows access to authenticated users only for objects that are owned or not related
|
|
|
|
|
to any user via the "owner" field.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def has_object_permission(self, request, view, obj):
|
|
|
|
|
"""Unsafe permissions are only allowed for the owner of the object."""
|
|
|
|
|
if obj.owner == request.user:
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
if request.method in permissions.SAFE_METHODS and obj.owner is None:
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
return obj.user == request.user
|
|
|
|
|
except exceptions.ObjectDoesNotExist:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
2026-03-06 02:47:03 +01:00
|
|
|
class IsEntitled(IsAuthenticated):
|
|
|
|
|
"""Allows access only to users with can_access entitlement.
|
|
|
|
|
|
|
|
|
|
Fail-closed: denies access when the entitlements service is
|
|
|
|
|
unavailable and no cached value exists.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def has_permission(self, request, view):
|
|
|
|
|
if not super().has_permission(request, view):
|
|
|
|
|
return False
|
|
|
|
|
try:
|
|
|
|
|
entitlements = get_user_entitlements(request.user.sub, request.user.email)
|
|
|
|
|
return entitlements.get("can_access", True)
|
|
|
|
|
except EntitlementsUnavailableError:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
2026-01-09 00:51:25 +01:00
|
|
|
class AccessPermission(permissions.BasePermission):
|
|
|
|
|
"""Permission class for access objects."""
|
|
|
|
|
|
|
|
|
|
def has_permission(self, request, view):
|
|
|
|
|
return request.user.is_authenticated or view.action not in [
|
|
|
|
|
"create",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
def has_object_permission(self, request, view, obj):
|
|
|
|
|
"""Check permission for a given object."""
|
|
|
|
|
abilities = obj.get_abilities(request.user)
|
|
|
|
|
action = view.action
|
|
|
|
|
try:
|
|
|
|
|
action = ACTION_FOR_METHOD_TO_PERMISSION[view.action][request.method]
|
|
|
|
|
except KeyError:
|
|
|
|
|
pass
|
|
|
|
|
return abilities.get(action, False)
|