From 9a359927494c27f7f100f740d62c47f830b8339f Mon Sep 17 00:00:00 2001 From: melton-jason Date: Mon, 6 May 2024 20:59:08 -0500 Subject: [PATCH 1/2] Assign read permissions to dependent tables Fixes #2714 --- specifyweb/permissions/views.py | 35 +++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/specifyweb/permissions/views.py b/specifyweb/permissions/views.py index db4d30e53ec..bf237ad1e8e 100644 --- a/specifyweb/permissions/views.py +++ b/specifyweb/permissions/views.py @@ -8,11 +8,18 @@ from django.views import View from specifyweb.specify import models as spmodels +from specifyweb.specify.datamodel import datamodel from specifyweb.specify.views import openapi, check_collection_access_against_agents from . import models from .permissions import PermissionTarget, PermissionTargetAction, \ NoAdminUsersException, check_permission_targets, registry, query +def dependent_resources_from_resource(resource: str) -> List[str]: + table = resource.split("/")[-1] + model = datamodel.get_table_strict(table) + dependent_relationships = tuple(filter(lambda rel: rel.dependent, model.relationships)) + return tuple(map(lambda rel: f"/table/{rel.relatedModelName.lower()}", dependent_relationships)) + Specifyuser = getattr(spmodels, "Specifyuser") class ListAdminsPT(PermissionTarget): @@ -243,6 +250,8 @@ def put(self, request, collectionid: Optional[int], userid: int) -> http.HttpRes specifyuser_id=userid, resource=resource, action=action) + if "/table/" in resource: + self.assign_dependent_access(resource, collectionid, userid) if not models.UserPolicy.objects.filter(collection__isnull=True, resource='%', action='%').exists(): raise NoAdminUsersException() @@ -250,6 +259,18 @@ def put(self, request, collectionid: Optional[int], userid: int) -> http.HttpRes check_collection_access_against_agents(userid) return http.HttpResponse('', status=204) + + def assign_dependent_access(self, resource: str, collectionid: int, specifyuserid: int): + for dependent_resource in dependent_resources_from_resource(resource): + if models.UserPolicy.objects.filter(resource=dependent_resource, specifyuser_id=specifyuserid).exists(): + continue + + models.UserPolicy.objects.create( + collection_id=collectionid, + specifyuser_id=specifyuserid, + resource=dependent_resource, + action="read" + ) user_policies = openapi(schema={ "get": { @@ -575,6 +596,8 @@ def put(self, request, roleid: int) -> http.HttpResponse: for resource, actions in data['policies'].items(): for action in actions: r.policies.create(resource=resource, action=action) + if "/table/" in resource: + self.assign_dependent_access(resource, r) affected_users = Specifyuser.objects.select_for_update().filter(roles__role=r).values_list('id', flat=True) for userid in affected_users: @@ -591,6 +614,11 @@ def delete(self, request, roleid: int) -> http.HttpResponse: # at least without there being DENY policies return http.HttpResponse('', status=204) + def assign_dependent_access(self, resource: str, role: models.Role): + for dependent_resource in dependent_resources_from_resource(resource): + if role.policies.filter(resource=dependent_resource).exists(): continue + role.policies.create(resource=dependent_resource, action="read") + role = openapi(schema={ "get": { @@ -963,6 +991,8 @@ def put(self, request, roleid: int) -> http.HttpResponse: for resource, actions in data['policies'].items(): for action in actions: r.policies.create(resource=resource, action=action) + if "/table/" in resource: + self.assign_dependent_access(resource, r) return http.HttpResponse('', status=204) @@ -971,6 +1001,11 @@ def delete(self, request, roleid: int) -> http.HttpResponse: check_permission_targets(None, request.specify_user.id, [LibraryRolePT.delete]) r.delete() return http.HttpResponse('', status=204) + + def assign_dependent_access(self, resource: str, lb: models.LibraryRole): + for dependent_resource in dependent_resources_from_resource(resource): + if lb.policies.filter(resource=dependent_resource).exists(): continue + lb.policies.create(resource=dependent_resource, action="read") library_role = openapi(schema={ From 43e523eb08cf3af63e2def58d1057bb867f6ac00 Mon Sep 17 00:00:00 2001 From: melton-jason Date: Mon, 6 May 2024 21:06:58 -0500 Subject: [PATCH 2/2] Resolve MyPy errors --- specifyweb/permissions/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specifyweb/permissions/views.py b/specifyweb/permissions/views.py index bf237ad1e8e..9b5b33285b5 100644 --- a/specifyweb/permissions/views.py +++ b/specifyweb/permissions/views.py @@ -1,6 +1,6 @@ import json from collections import defaultdict -from typing import Dict, Union, Optional, List +from typing import Dict, Union, Optional, List, Tuple from django import http from django.contrib.auth.mixins import LoginRequiredMixin @@ -14,7 +14,7 @@ from .permissions import PermissionTarget, PermissionTargetAction, \ NoAdminUsersException, check_permission_targets, registry, query -def dependent_resources_from_resource(resource: str) -> List[str]: +def dependent_resources_from_resource(resource: str) -> Tuple[str, ...]: table = resource.split("/")[-1] model = datamodel.get_table_strict(table) dependent_relationships = tuple(filter(lambda rel: rel.dependent, model.relationships)) @@ -260,7 +260,7 @@ def put(self, request, collectionid: Optional[int], userid: int) -> http.HttpRes return http.HttpResponse('', status=204) - def assign_dependent_access(self, resource: str, collectionid: int, specifyuserid: int): + def assign_dependent_access(self, resource: str, collectionid: Optional[int], specifyuserid: int): for dependent_resource in dependent_resources_from_resource(resource): if models.UserPolicy.objects.filter(resource=dependent_resource, specifyuser_id=specifyuserid).exists(): continue