From 5cbad2cae49ce554ea6942b4d21398222e6fd6c5 Mon Sep 17 00:00:00 2001 From: David Eckhard Date: Tue, 1 Oct 2024 15:56:39 +0200 Subject: [PATCH] permissions: Refactor to use generator for accessing config permission --- README.rst | 35 ++++++------- invenio_curations/services/generators.py | 61 ++++++++++++++++------- invenio_curations/services/permissions.py | 25 ++++------ invenio_curations/services/service.py | 1 + 4 files changed, 69 insertions(+), 53 deletions(-) diff --git a/README.rst b/README.rst index 1295fdb..4ad4a81 100644 --- a/README.rst +++ b/README.rst @@ -139,38 +139,31 @@ Since we only want to change the behaviour of these community submission request .. code-block:: python + from invenio_rdm_records.requests import CommunitySubmission + from invenio_rdm_records.services.permissions import RDMRequestsPermissionPolicy + from invenio_requests.services.generators import Creator, Receiver + from invenio_curations.requests.curation import CurationRequest from invenio_curations.services.generators import ( IfCurationRequestAccepted, IfRequestTypes, - RequestTopicGenerators, - ) - from invenio_rdm_records.requests import CommunitySubmission - from invenio_rdm_records.services.permissions import ( - RDMRequestsPermissionPolicy, - RDMRecordPermissionPolicy, + TopicPermission, ) - from invenio_requests.services.generators import Creator, Receiver, Status class CurationRDMRequestsPermissionPolicy(RDMRequestsPermissionPolicy): """Customized permission policy for sane handling of curation requests.""" - - rdm_policy = RDMRecordPermissionPolicy curation_request_record_review = IfRequestTypes( [CurationRequest], - then_=[RequestTopicGenerators(generators=rdm_policy.can_review)], + then_=[TopicPermission(permission_name="can_review")], else_=[], ) - # Only allow community-submission requests to be accepted after - # the rdm-curation request has been accepted + # Only allow community-submission requests to be accepted after the rdm-curation request has been accepted can_action_accept = [ IfRequestTypes( - request_types=[ - CommunitySubmission, - ], + request_types=[CommunitySubmission], then_=[ IfCurationRequestAccepted( then_=RDMRequestsPermissionPolicy.can_action_accept, else_=[] @@ -182,12 +175,13 @@ Since we only want to change the behaviour of these community submission request # Update can read and can comment with new states can_read = [ + # Have to explicitly check the request type and circumvent using status, as creator/receiver will add a query filter where one entity must be the user. IfRequestTypes( [CurationRequest], then_=[ Creator(), Receiver(), - RequestTopicGenerators(generators=rdm_policy.can_review), + TopicPermission(permission_name="can_review"), ], else_=RDMRequestsPermissionPolicy.can_read, ) @@ -195,14 +189,15 @@ Since we only want to change the behaviour of these community submission request can_create_comment = can_read - # Add new actions - can_action_review = RDMRequestsPermissionPolicy.can_action_accept - can_action_critique = RDMRequestsPermissionPolicy.can_action_accept + # Update submit to also allow record reviewers/managers for curation requests can_action_submit = RDMRequestsPermissionPolicy.can_action_submit + [ curation_request_record_review ] - can_action_resubmit = can_action_submit + # Add new actions + can_action_review = RDMRequestsPermissionPolicy.can_action_accept + can_action_critique = RDMRequestsPermissionPolicy.can_action_accept + can_action_resubmit = can_action_submit REQUESTS_PERMISSION_POLICY = CurationRDMRequestsPermissionPolicy diff --git a/invenio_curations/services/generators.py b/invenio_curations/services/generators.py index ae5e268..9954156 100644 --- a/invenio_curations/services/generators.py +++ b/invenio_curations/services/generators.py @@ -62,35 +62,58 @@ def _condition(self, request=None, **kwargs): return False -class RequestTopicGenerators(Generator): - """Request-oriented generator forwarding the request topic to the provided generators. +class EntityReferenceServicePermission(Generator): + """Request-oriented generator accessing a named permission from the entity's service config.""" - Knowing what a request topic is, it is possible to perform checks on the topic itself. - Helpful to allow record managers access to a request as well. - """ + entity_field = None - def __init__(self, generators=None, **kwargs): - self._generators = generators or set() + def __init__(self, permission_name, **kwargs): + """Constructor specifying permission_name.""" + self.permission_name = permission_name + assert self.entity_field is not None, "Subclass must define entity_field." super().__init__() + def _get_permission(self, request): + """Get the specified permission from the request entity service config.""" + entity = getattr(request, self.entity_field) + permission_policy_cls = ( + entity.get_resolver().get_service().config.permission_policy_cls + ) + + return getattr(permission_policy_cls, self.permission_name) + def needs(self, request=None, **kwargs): - """Set of Needs granting permission.""" + """Set of needs granting permission.""" if request is None: return set() - # popping as we will set this ourselves - kwargs.pop("record") - - # we could also do: return request.topic.get_needs() but get_needs is not implemented on the RDM Record Proxy - # we could also - # - subclass the rdm record entity resolver - # - implement the get_needs() for our use case - # - register the new resolver for `record` entities in invenio.cfg - needs = [ - g.needs(record=request.topic.resolve(), **kwargs) for g in self._generators - ] + permission = self._get_permission(request) + record = request.topic.resolve() + popped_record = kwargs.pop("record") + needs = [g.needs(record=record, **kwargs) for g in permission] + + kwargs["record"] = popped_record return set(chain.from_iterable(needs)) + def excludes(self, request=None, **kwargs): + """Set of excludes denying permission.""" + if request is None: + return set() + + permission = self._get_permission(request) + record = request.topic.resolve() + popped_record = kwargs.pop("record") + excludes = [g.excludes(record=record, **kwargs) for g in permission] + + kwargs["record"] = popped_record + return set(chain.from_iterable(excludes)) + + +class TopicPermission(EntityReferenceServicePermission): + """Request-oriented generator to get generators of specified permission name from the topic of the request.""" + + entity_field = "topic" + class CurationModerators(Generator): """Permission generator that allows users with the `moderation` role.""" diff --git a/invenio_curations/services/permissions.py b/invenio_curations/services/permissions.py index 9c92449..7da881c 100644 --- a/invenio_curations/services/permissions.py +++ b/invenio_curations/services/permissions.py @@ -23,7 +23,7 @@ IfCurationRequestAccepted, IfCurationRequestExists, IfRequestTypes, - RequestTopicGenerators, + TopicPermission, ) @@ -79,22 +79,16 @@ class CurationRDMRecordPermissionPolicy(RDMRecordPermissionPolicy): class CurationRDMRequestsPermissionPolicy(RDMRequestsPermissionPolicy): """Customized permission policy for sane handling of curation requests.""" - # specifying the RDM policy as we use the `can_review` permission for viewing a request. - # if needs for viewing request.topic record can be resolved otherwise, it would be great (maybe through EntityNeeds/EntityGrants as is done for Creator/Receiver) - rdm_policy = RDMRecordPermissionPolicy curation_request_record_review = IfRequestTypes( [CurationRequest], - then_=[RequestTopicGenerators(generators=rdm_policy.can_review)], + then_=[TopicPermission(permission_name="can_review")], else_=[], ) - # Only allow community-submission requests to be accepted after - # the rdm-curation request has been accepted + # Only allow community-submission requests to be accepted after the rdm-curation request has been accepted can_action_accept = [ IfRequestTypes( - request_types=[ - CommunitySubmission, - ], + request_types=[CommunitySubmission], then_=[ IfCurationRequestAccepted( then_=RDMRequestsPermissionPolicy.can_action_accept, else_=[] @@ -106,12 +100,13 @@ class CurationRDMRequestsPermissionPolicy(RDMRequestsPermissionPolicy): # Update can read and can comment with new states can_read = [ + # Have to explicitly check the request type and circumvent using status, as creator/receiver will add a query filter where one entity must be the user. IfRequestTypes( [CurationRequest], then_=[ Creator(), Receiver(), - RequestTopicGenerators(generators=rdm_policy.can_review), + TopicPermission(permission_name="can_review"), ], else_=RDMRequestsPermissionPolicy.can_read, ) @@ -119,10 +114,12 @@ class CurationRDMRequestsPermissionPolicy(RDMRequestsPermissionPolicy): can_create_comment = can_read - # Add new actions - can_action_review = RDMRequestsPermissionPolicy.can_action_accept - can_action_critique = RDMRequestsPermissionPolicy.can_action_accept + # Update submit to also allow record reviewers/managers for curation requests can_action_submit = RDMRequestsPermissionPolicy.can_action_submit + [ curation_request_record_review ] + # Add new actions + can_action_review = RDMRequestsPermissionPolicy.can_action_accept + can_action_critique = RDMRequestsPermissionPolicy.can_action_accept + can_action_resubmit = can_action_submit diff --git a/invenio_curations/services/service.py b/invenio_curations/services/service.py index c2431b9..4d6efb1 100644 --- a/invenio_curations/services/service.py +++ b/invenio_curations/services/service.py @@ -8,6 +8,7 @@ """Curation service.""" from flask import current_app +from invenio_access.permissions import system_identity from invenio_accounts.models import Role from invenio_accounts.proxies import current_datastore from invenio_i18n import gettext as _