-
Notifications
You must be signed in to change notification settings - Fork 599
[Eng-496] fix: authz check for every nested routing api #3686
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 13 commits
3e14d10
0e1c827
bfaac38
8cb6d4e
d5e56f2
49ef098
c5b793f
d715531
70967ca
facead8
0d07d83
ad9076a
8c0d859
03cdb69
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -350,18 +350,42 @@ class DeviceLocationHistoryViewSet(EMRModelReadOnlyViewSet): | |
| def get_device(self): | ||
| return get_object_or_404(Device, external_id=self.kwargs["device_external_id"]) | ||
|
|
||
| def get_queryset(self): | ||
| def authorize_retrieve(self, model_instance): | ||
| device = self.get_device() | ||
| if not AuthorizationController.call( | ||
| "can_read_device", self.request.user, device | ||
| "can_read_device", | ||
| self.request.user, | ||
| device, | ||
| ): | ||
| raise PermissionDenied("You do not have permission to access the device") | ||
| if device.id != model_instance.device_id: | ||
| raise ValidationError( | ||
| "device does not match with the device location history" | ||
| ) | ||
|
|
||
| return ( | ||
| DeviceLocationHistory.objects.filter(device=device) | ||
| .select_related("location") | ||
| .order_by("-end") | ||
| def get_queryset(self): | ||
| device = self.get_device() | ||
| queryset = ( | ||
| super() | ||
| .get_queryset() | ||
| .select_related("created_by", "updated_by") | ||
| .order_by("-modified_date") | ||
| ) | ||
| if self.action == "list": | ||
| if not AuthorizationController.call( | ||
| "can_read_device", | ||
| self.request.user, | ||
| device, | ||
| ): | ||
| raise PermissionDenied( | ||
| "You do not have permission to access the device" | ||
| ) | ||
| return ( | ||
| DeviceLocationHistory.objects.filter(device=device) | ||
| .select_related("location") | ||
| .order_by("-end") | ||
| ) | ||
| return queryset | ||
|
|
||
|
|
||
| class DeviceEncounterHistoryViewSet(EMRModelReadOnlyViewSet): | ||
|
|
@@ -371,22 +395,47 @@ class DeviceEncounterHistoryViewSet(EMRModelReadOnlyViewSet): | |
| def get_device(self): | ||
| return get_object_or_404(Device, external_id=self.kwargs["device_external_id"]) | ||
|
|
||
| def get_queryset(self): | ||
| """ | ||
| Encounter history access is limited to everyone within the location or associated with the managing org | ||
| """ | ||
| def authorize_retrieve(self, model_instance): | ||
| device = self.get_device() | ||
| if not AuthorizationController.call( | ||
| "can_read_device", | ||
| self.request.user, | ||
| device, | ||
| ): | ||
| raise PermissionDenied("You do not have permission to access the device") | ||
|
nandkishorr marked this conversation as resolved.
|
||
| return ( | ||
| DeviceEncounterHistory.objects.filter(device=device) | ||
| .select_related("encounter", "encounter__patient", "encounter__facility") | ||
| .order_by("-end") | ||
| if device.id != model_instance.device_id: | ||
| raise ValidationError( | ||
| "device does not match with the device encounter history" | ||
| ) | ||
|
|
||
| def get_queryset(self): | ||
| """ | ||
| Encounter history access is limited to everyone within the location or associated with the managing org | ||
| """ | ||
| device = self.get_device() | ||
| queryset = ( | ||
| super() | ||
| .get_queryset() | ||
| .select_related("created_by", "updated_by") | ||
| .order_by("-modified_date") | ||
| ) | ||
| if self.action == "list": | ||
| if not AuthorizationController.call( | ||
| "can_read_device", | ||
| self.request.user, | ||
| device, | ||
| ): | ||
| raise PermissionDenied( | ||
| "You do not have permission to access the device" | ||
| ) | ||
| return ( | ||
| DeviceEncounterHistory.objects.filter(device=device) | ||
| .select_related( | ||
| "encounter", "encounter__patient", "encounter__facility" | ||
| ) | ||
| .order_by("-end") | ||
| ) | ||
| return queryset | ||
|
|
||
|
|
||
| class DeviceServiceHistoryViewSet( | ||
|
|
@@ -421,6 +470,19 @@ def authorize_create(self, instance): | |
| def authorize_update(self, request_obj, model_instance): | ||
| self.authorize_create(model_instance) | ||
|
|
||
| def authorize_retrieve(self, model_instance): | ||
| device = self.get_device() | ||
| if not AuthorizationController.call( | ||
| "can_read_device", | ||
| self.request.user, | ||
| device, | ||
| ): | ||
| raise PermissionDenied("You do not have permission to access the device") | ||
| if device.id != model_instance.device_id: | ||
| raise ValidationError( | ||
| "device does not match with the device service history" | ||
| ) | ||
|
Comment on lines
470
to
+484
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The refactoring moved parent-scoped filtering in The same gap exists in every other viewset refactored in this PR that has write operations:
Each of these needs a parent-ID mismatch guard (mirroring the |
||
|
|
||
|
nandkishorr marked this conversation as resolved.
|
||
| def perform_update(self, instance): | ||
| if instance.edit_history and len(instance.edit_history) >= 50: # noqa PLR2004 | ||
| raise ValidationError("Cannot Edit instance anymore") | ||
|
|
@@ -441,15 +503,25 @@ def get_queryset(self): | |
| Encounter history access is limited to everyone within the location or associated with the managing org | ||
| """ | ||
| device = self.get_device() | ||
| if not AuthorizationController.call( | ||
| "can_read_device", | ||
| self.request.user, | ||
| device, | ||
| ): | ||
| raise PermissionDenied("You do not have permission to access the device") | ||
| return DeviceServiceHistory.objects.filter(device=device).order_by( | ||
| "-serviced_on" | ||
| queryset = ( | ||
| super() | ||
| .get_queryset() | ||
| .select_related("created_by", "updated_by") | ||
| .order_by("-modified_date") | ||
| ) | ||
| if self.action == "list": | ||
| if not AuthorizationController.call( | ||
| "can_read_device", | ||
| self.request.user, | ||
| device, | ||
| ): | ||
| raise PermissionDenied( | ||
| "You do not have permission to access the device" | ||
| ) | ||
| return DeviceServiceHistory.objects.filter(device=device).order_by( | ||
| "-serviced_on" | ||
| ) | ||
| return queryset | ||
|
|
||
|
|
||
| def disassociate_device_from_encounter(instance): | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,6 @@ | ||||||||||||||||||
| from django.db.models import Q | ||||||||||||||||||
| from django_filters import rest_framework as filters | ||||||||||||||||||
| from rest_framework.exceptions import PermissionDenied | ||||||||||||||||||
| from rest_framework.exceptions import PermissionDenied, ValidationError | ||||||||||||||||||
| from rest_framework.filters import OrderingFilter | ||||||||||||||||||
|
|
||||||||||||||||||
| from care.emr.api.viewsets.base import EMRBaseViewSet, EMRListMixin, EMRRetrieveMixin | ||||||||||||||||||
|
|
@@ -46,19 +46,26 @@ def authorize_location_read(self, location): | |||||||||||||||||
| raise PermissionDenied("You do not have permission to read inventory items") | ||||||||||||||||||
|
|
||||||||||||||||||
| def authorize_retrieve(self, model_instance): | ||||||||||||||||||
| location = self.get_location_obj() | ||||||||||||||||||
| self.authorize_location_read(model_instance.location) | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Authorize the URL location, not the item's current location. Line 50 checks permissions against Proposed fix def authorize_retrieve(self, model_instance):
location = self.get_location_obj()
- self.authorize_location_read(model_instance.location)
+ self.authorize_location_read(location)
if location.id != model_instance.location.id:
raise ValidationError(
"Inventory item does not belong to the specified location"📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| if location.id != model_instance.location.id: | ||||||||||||||||||
| raise ValidationError( | ||||||||||||||||||
| "Inventory item does not belong to the specified location" | ||||||||||||||||||
| ) | ||||||||||||||||||
|
nandkishorr marked this conversation as resolved.
|
||||||||||||||||||
|
|
||||||||||||||||||
| def get_queryset(self): | ||||||||||||||||||
| queryset = super().get_queryset() | ||||||||||||||||||
| location = self.get_location_obj() | ||||||||||||||||||
| self.authorize_location_read(location) | ||||||||||||||||||
| include_children = ( | ||||||||||||||||||
| self.request.GET.get("include_children", "false").lower() == "true" | ||||||||||||||||||
| ) | ||||||||||||||||||
| if include_children: | ||||||||||||||||||
| queryset = queryset.filter( | ||||||||||||||||||
| Q(location__parent_cache__overlap=[location.id]) | Q(location=location) | ||||||||||||||||||
| if self.action == "list": | ||||||||||||||||||
| self.authorize_location_read(location) | ||||||||||||||||||
| include_children = ( | ||||||||||||||||||
| self.request.GET.get("include_children", "false").lower() == "true" | ||||||||||||||||||
| ) | ||||||||||||||||||
| else: | ||||||||||||||||||
| queryset = queryset.filter(location=location) | ||||||||||||||||||
| if include_children: | ||||||||||||||||||
| queryset = queryset.filter( | ||||||||||||||||||
| Q(location__parent_cache__overlap=[location.id]) | ||||||||||||||||||
| | Q(location=location) | ||||||||||||||||||
| ) | ||||||||||||||||||
| else: | ||||||||||||||||||
| queryset = queryset.filter(location=location) | ||||||||||||||||||
| return queryset | ||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.