diff --git a/care/emr/api/viewsets/file_upload.py b/care/emr/api/viewsets/file_upload.py index eba3398252..36c6778318 100644 --- a/care/emr/api/viewsets/file_upload.py +++ b/care/emr/api/viewsets/file_upload.py @@ -1,24 +1,58 @@ from django.utils import timezone from pydantic import BaseModel from rest_framework.decorators import action +from rest_framework.exceptions import PermissionDenied from rest_framework.generics import get_object_or_404 from rest_framework.response import Response -from care.emr.api.viewsets.base import EMRModelViewSet -from care.emr.models import FileUpload +from care.emr.api.viewsets.base import ( + EMRBaseViewSet, + EMRCreateMixin, + EMRListMixin, + EMRRetrieveMixin, + EMRUpdateMixin, +) +from care.emr.models import Encounter, FileUpload, Patient from care.emr.resources.file_upload.spec import ( + FileTypeChoices, FileUploadCreateSpec, FileUploadListSpec, FileUploadRetrieveSpec, FileUploadUpdateSpec, ) +from care.security.authorization import AuthorizationController + + +def file_authorizer(user, file_type, associating_id, permission): + allowed = False + if file_type == FileTypeChoices.patient.value: + patient_obj = get_object_or_404(Patient, external_id=associating_id) + if permission == "read": + allowed = AuthorizationController.call( + "can_view_clinical_data", user, patient_obj + ) + elif permission == "write": + allowed = AuthorizationController.call( + "can_write_patient_obj", user, patient_obj + ) + encounter_obj = get_object_or_404(Encounter, external_id=associating_id) + if permission == "read": + allowed = AuthorizationController.call( + "can_view_encounter_obj", user, encounter_obj + ) + elif permission == "write": + allowed = AuthorizationController.call( + "can_update_encounter_obj", user, encounter_obj + ) -def file_authorizer(file_type, associating_id, permission): - return + if not allowed: + raise PermissionDenied("Cannot View File") -class FileUploadViewSet(EMRModelViewSet): +class FileUploadViewSet( + EMRCreateMixin, EMRRetrieveMixin, EMRUpdateMixin, EMRListMixin, EMRBaseViewSet +): database_model = FileUpload pydantic_model = FileUploadCreateSpec pydantic_retrieve_model = FileUploadRetrieveSpec @@ -26,7 +60,20 @@ class FileUploadViewSet(EMRModelViewSet): pydantic_read_model = FileUploadListSpec def authorize_create(self, instance): - pass + file_authorizer( + self.request.user, + instance.file_type, + instance.associating_id, + "write", + ) + + def authorize_update(self, request_obj, model_instance): + file_authorizer( + self.request.user, + model_instance.file_type, + model_instance.associating_id, + "write", + ) def get_queryset(self): if self.action == "list": @@ -36,6 +83,7 @@ def get_queryset(self): ): raise PermissionError("Cannot filter files") file_authorizer( + self.request.user, self.request.GET.get("file_type"), self.request.GET.get("associating_id"), "read", @@ -49,13 +97,13 @@ def get_queryset(self): ) ) obj = get_object_or_404(FileUpload, external_id=self.kwargs["external_id"]) - file_authorizer(obj.file_type, obj.associating_id, "read") + file_authorizer(self.request.user, obj.file_type, obj.associating_id, "read") return super().get_queryset() @action(detail=True, methods=["POST"]) def mark_upload_completed(self, request, *args, **kwargs): obj = self.get_object() - file_authorizer(obj.file_type, obj.associating_id, "write") + file_authorizer(request.user, obj.file_type, obj.associating_id, "write") obj.upload_completed = True obj.save(update_fields=["upload_completed"]) return Response(FileUploadListSpec.serialize(obj).to_json()) @@ -67,7 +115,7 @@ class ArchiveRequestSpec(BaseModel): def archive(self, request, *args, **kwargs): obj = self.get_object() request_data = self.ArchiveRequestSpec(**request.data) - file_authorizer(obj.file_type, obj.associating_id, "write") + file_authorizer(request.user, obj.file_type, obj.associating_id, "write") obj.is_archived = True obj.archive_reason = request_data.archive_reason obj.archived_datetime = timezone.now() diff --git a/care/emr/api/viewsets/medication_administration.py b/care/emr/api/viewsets/medication_administration.py index 13fa83b918..5d9fb0e42f 100644 --- a/care/emr/api/viewsets/medication_administration.py +++ b/care/emr/api/viewsets/medication_administration.py @@ -1,5 +1,7 @@ from django_filters import rest_framework as filters +from rest_framework.exceptions import PermissionDenied +from care.emr.api.viewsets.authz_base import EncounterBasedAuthorizationBase from care.emr.api.viewsets.base import EMRModelViewSet from care.emr.models.medication_administration import MedicationAdministration from care.emr.registries.system_questionnaire.system_questionnaire import ( @@ -10,6 +12,7 @@ MedicationAdministrationSpec, ) from care.emr.resources.questionnaire.spec import SubjectType +from care.security.authorization import AuthorizationController class MedicationAdministrationFilter(filters.FilterSet): @@ -19,7 +22,7 @@ class MedicationAdministrationFilter(filters.FilterSet): occurrence_period_end = filters.DateTimeFromToRangeFilter() -class MedicationAdministrationViewSet(EMRModelViewSet): +class MedicationAdministrationViewSet(EncounterBasedAuthorizationBase, EMRModelViewSet): database_model = MedicationAdministration pydantic_model = MedicationAdministrationSpec pydantic_read_model = MedicationAdministrationReadSpec @@ -31,6 +34,10 @@ class MedicationAdministrationViewSet(EMRModelViewSet): filter_backends = [filters.DjangoFilterBackend] def get_queryset(self): + if not AuthorizationController.call( + "can_view_clinical_data", self.request.user, self.get_patient_obj() + ): + raise PermissionDenied("Permission denied to user") return ( super() .get_queryset() diff --git a/care/emr/api/viewsets/medication_request.py b/care/emr/api/viewsets/medication_request.py index 20cba79efd..e387a3ff22 100644 --- a/care/emr/api/viewsets/medication_request.py +++ b/care/emr/api/viewsets/medication_request.py @@ -79,7 +79,7 @@ def get_queryset(self): def discontinue(self, request, *args, **kwargs): data = MedicationRequestDiscontinueRequest(**request.data) request: MedicationRequest = self.get_object() - + self.authorize_update({}, request) request.status = MedicationRequestStatus.ended request.status_changed = timezone.now() request.status_reason = data.status_reason diff --git a/care/emr/api/viewsets/patient.py b/care/emr/api/viewsets/patient.py index 61a975cb39..014723e0a0 100644 --- a/care/emr/api/viewsets/patient.py +++ b/care/emr/api/viewsets/patient.py @@ -9,7 +9,7 @@ from rest_framework.response import Response from care.emr.api.viewsets.base import EMRModelViewSet -from care.emr.models import PatientUser +from care.emr.models import Organization, PatientUser from care.emr.models.patient import Patient from care.emr.resources.patient.spec import ( PatientCreateSpec, @@ -18,6 +18,7 @@ PatientRetrieveSpec, ) from care.emr.resources.user.spec import UserSpec +from care.security.authorization import AuthorizationController from care.security.models import RoleModel from care.users.models import User @@ -38,14 +39,21 @@ class PatientViewSet(EMRModelViewSet): # TODO : Retrieve will work if an active encounter exists on the patient def get_queryset(self): - return ( + qs = ( super() .get_queryset() .select_related("created_by", "updated_by", "geo_organization") ) - # return AuthorizationController.call( - # "get_filtered_patients", qs, self.request.user - # ) + if self.request.GET.get("geo_organization"): + geo_organization = get_object_or_404( + Organization, + external_id=self.request.GET["geo_organization"], + org_type="govt", + ) + qs = qs.filter(organization_cache__overlap=geo_organization.id) + return AuthorizationController.call( + "get_filtered_patients", qs, self.request.user + ) class SearchRequestSpec(BaseModel): name: str = "" diff --git a/care/security/authorization/encounter.py b/care/security/authorization/encounter.py index e685325f70..1e144c2437 100644 --- a/care/security/authorization/encounter.py +++ b/care/security/authorization/encounter.py @@ -15,10 +15,19 @@ def can_create_encounter_obj(self, user, facility): [EncounterPermissions.can_create_encounter.name], user, facility=facility ) + def can_view_encounter_obj(self, user, facility): + """ + Check if the user has permission to read encounter under this facility + """ + return self.check_permission_in_facility_organization( + [EncounterPermissions.can_read_encounter.name], user, facility=facility + ) + def can_update_encounter_obj(self, user, encounter): """ Check if the user has permission to create encounter under this facility """ + # TODO check if encounter has been closed return self.check_permission_in_facility_organization( [EncounterPermissions.can_write_encounter.name], user, diff --git a/care/security/authorization/patient.py b/care/security/authorization/patient.py index 7bc173e4f6..3ad4261345 100644 --- a/care/security/authorization/patient.py +++ b/care/security/authorization/patient.py @@ -42,6 +42,15 @@ def can_view_patient_obj(self, user, patient): role__in=user_roles, ).exists() + def can_write_patient_obj(self, user, patient): + if user.is_superuser: + return True + user_roles = self.find_roles_on_patient(user, patient) + return RolePermission.objects.filter( + permission__slug__in=[PatientPermissions.can_write_patient.name], + role__in=user_roles, + ).exists() + def can_view_clinical_data(self, user, patient): if user.is_superuser: return True