Source code for nlpmed_portal.annotations.permissions

# SPDX-FileCopyrightText: Copyright (C) 2026 Omid Jafari <omidjafari.com>
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from django.contrib.auth.mixins import PermissionRequiredMixin
from django.http import Http404
from django.http import HttpRequest
from rest_framework.exceptions import NotFound
from rest_framework.exceptions import PermissionDenied
from rest_framework.exceptions import ValidationError
from rest_framework.permissions import BasePermission
from rest_framework.request import Request
from rest_framework.views import APIView

from nlpmed_portal.annotations.models import Patient
from nlpmed_portal.annotations.models import Project
from nlpmed_portal.annotations.models import ProjectMembership


[docs] def has_global_permission(user, codename, app_label="annotations") -> bool: """ Check if the user has the required permission globally. """ full_codename = f"{app_label}.{codename}" return user.has_perm(full_codename)
[docs] def has_permission(user, codename, project=None) -> bool: """ Check if the user has the required permission either globally or within a specific project. """ # Check global permission if has_global_permission(user, codename): return True # Check scoped permission for a specific project if project: return ProjectMembership.objects.filter( assignee=user, project=project, group__permissions__codename=codename, ).exists() # Check scoped permission across any project return ProjectMembership.objects.filter( assignee=user, group__permissions__codename=codename, ).exists()
[docs] class MethodBasePermission(BasePermission): """Enforce HTTP-method-specific permissions declared by a view. Views using this permission class must define ``required_perms``, mapping HTTP method names to Django permission codenames. """
[docs] def has_permission(self, request: Request, view: APIView) -> bool: # ruff: ignore[complex-structure, too-many-return-statements] required_perms = getattr(view, "required_perms", {}) if not required_perms: return False required_perm = required_perms.get(request.method) if not required_perm: return False # This is a list view if not view.kwargs.get("pk"): return has_permission(user=request.user, codename=required_perm) # This is a detail view, so check local and global permissions if hasattr(view, "get_object"): try: obj = view.get_object() except (NotFound, Http404): raise except PermissionDenied: raise except Exception: # ruff: ignore[blind-except] raise ValidationError( # ruff: ignore[raise-without-from-inside-except] { "error": "The requested resource either does \ not exist or you do not have access to it.", }, ) if isinstance(obj, Project): return has_permission( user=request.user, codename=required_perm, project=obj, ) if isinstance(obj, Patient): return has_permission( user=request.user, codename=required_perm, project=obj.project, ) if hasattr(obj, "project"): return has_permission( user=request.user, codename=required_perm, project=obj.project, ) if hasattr(obj, "patient"): return has_permission( user=request.user, codename=required_perm, project=obj.patient.project, ) return False
[docs] class ScopedPermissionRequiredMixin(PermissionRequiredMixin): request: HttpRequest
[docs] def has_permission(self): perms = self.get_permission_required() return has_permission(user=self.request.user, codename=perms[0])