From cde0aca44ede9c6ba0a6083aec28817cc53e69fb Mon Sep 17 00:00:00 2001 From: David Eckhard Date: Mon, 30 Sep 2024 18:41:28 +0200 Subject: [PATCH] permissions: Allow record reviewers to also read/update request --- README.rst | 40 ++++++++++--- invenio_curations/services/config.py | 4 +- invenio_curations/services/generators.py | 32 +++++++++++ invenio_curations/services/permissions.py | 70 ++++++++++++++++++----- invenio_curations/services/service.py | 3 +- 5 files changed, 124 insertions(+), 25 deletions(-) diff --git a/README.rst b/README.rst index be1cc46..1295fdb 100644 --- a/README.rst +++ b/README.rst @@ -139,17 +139,31 @@ Since we only want to change the behaviour of these community submission request .. code-block:: python + 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, ) - from invenio_rdm_records.requests import CommunityInclusion, CommunitySubmission - from invenio_rdm_records.services.permissions import RDMRequestsPermissionPolicy 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)], + else_=[], + ) + # Only allow community-submission requests to be accepted after # the rdm-curation request has been accepted can_action_accept = [ @@ -167,18 +181,28 @@ Since we only want to change the behaviour of these community submission request ] # Update can read and can comment with new states - can_read = RDMRequestsPermissionPolicy.can_read + [ - Status( - ["review", "critiqued", "resubmitted"], - [Creator(), Receiver()], - ), + can_read = [ + IfRequestTypes( + [CurationRequest], + then_=[ + Creator(), + Receiver(), + RequestTopicGenerators(generators=rdm_policy.can_review), + ], + else_=RDMRequestsPermissionPolicy.can_read, + ) ] + can_create_comment = can_read # Add new actions can_action_review = RDMRequestsPermissionPolicy.can_action_accept can_action_critique = RDMRequestsPermissionPolicy.can_action_accept - can_action_resubmit = RDMRequestsPermissionPolicy.can_action_submit + can_action_submit = RDMRequestsPermissionPolicy.can_action_submit + [ + curation_request_record_review + ] + can_action_resubmit = can_action_submit + REQUESTS_PERMISSION_POLICY = CurationRDMRequestsPermissionPolicy diff --git a/invenio_curations/services/config.py b/invenio_curations/services/config.py index 2a30e20..bafa31d 100644 --- a/invenio_curations/services/config.py +++ b/invenio_curations/services/config.py @@ -15,7 +15,7 @@ from invenio_curations.services import facets -from .permissions import CurationRDMRequestPermissionPolicy +from .permissions import CurationRDMRequestsPermissionPolicy class CurationsSearchOptions(RequestSearchOptions): @@ -43,7 +43,7 @@ class CurationsServiceConfig(RecordServiceConfig, ConfiguratorMixin): # common configuration permission_policy_cls = FromConfig( - "REQUESTS_PERMISSION_POLICY", default=CurationRDMRequestPermissionPolicy + "REQUESTS_PERMISSION_POLICY", default=CurationRDMRequestsPermissionPolicy ) # TODO: update search options? search = CurationsSearchOptions diff --git a/invenio_curations/services/generators.py b/invenio_curations/services/generators.py index e744da2..ae5e268 100644 --- a/invenio_curations/services/generators.py +++ b/invenio_curations/services/generators.py @@ -8,6 +8,8 @@ """Curations related generators.""" +from itertools import chain + from flask_principal import RoleNeed from invenio_access.permissions import system_identity from invenio_records_permissions.generators import ConditionalGenerator, Generator @@ -60,6 +62,36 @@ def _condition(self, request=None, **kwargs): return False +class RequestTopicGenerators(Generator): + """Request-oriented generator forwarding the request topic to the provided generators. + + 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. + """ + + def __init__(self, generators=None, **kwargs): + self._generators = generators or set() + super().__init__() + + def needs(self, request=None, **kwargs): + """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 + ] + return set(chain.from_iterable(needs)) + + 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 8f545c4..9c92449 100644 --- a/invenio_curations/services/permissions.py +++ b/invenio_curations/services/permissions.py @@ -8,17 +8,22 @@ """Curations permissions.""" +from invenio_rdm_records.requests import CommunitySubmission from invenio_rdm_records.services.generators import IfFileIsLocal -from invenio_rdm_records.services.permissions import RDMRecordPermissionPolicy +from invenio_rdm_records.services.permissions import ( + RDMRecordPermissionPolicy, + RDMRequestsPermissionPolicy, +) from invenio_records_permissions.generators import SystemProcess from invenio_requests.services.generators import Creator, Receiver, Status -from invenio_requests.services.permissions import ( - PermissionPolicy as RequestPermissionPolicy, -) +from invenio_curations.requests.curation import CurationRequest from invenio_curations.services.generators import ( CurationModerators, + IfCurationRequestAccepted, IfCurationRequestExists, + IfRequestTypes, + RequestTopicGenerators, ) @@ -71,16 +76,53 @@ class CurationRDMRecordPermissionPolicy(RDMRecordPermissionPolicy): ) -class CurationRDMRequestPermissionPolicy(RequestPermissionPolicy): - """Request permission policy for curations.""" +class CurationRDMRequestsPermissionPolicy(RDMRequestsPermissionPolicy): + """Customized permission policy for sane handling of curation requests.""" - can_read = RequestPermissionPolicy.can_read + [ - Status( - ["review", "critiqued", "resubmitted"], - [Creator(), Receiver()], - ), + # 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)], + else_=[], + ) + + # Only allow community-submission requests to be accepted after + # the rdm-curation request has been accepted + can_action_accept = [ + IfRequestTypes( + request_types=[ + CommunitySubmission, + ], + then_=[ + IfCurationRequestAccepted( + then_=RDMRequestsPermissionPolicy.can_action_accept, else_=[] + ) + ], + else_=RDMRequestsPermissionPolicy.can_action_accept, + ) ] + + # Update can read and can comment with new states + can_read = [ + IfRequestTypes( + [CurationRequest], + then_=[ + Creator(), + Receiver(), + RequestTopicGenerators(generators=rdm_policy.can_review), + ], + else_=RDMRequestsPermissionPolicy.can_read, + ) + ] + can_create_comment = can_read - can_action_review = RequestPermissionPolicy.can_action_accept - can_action_critique = RequestPermissionPolicy.can_action_accept - can_action_resubmit = RequestPermissionPolicy.can_action_cancel + + # Add new actions + can_action_review = RDMRequestsPermissionPolicy.can_action_accept + can_action_critique = RDMRequestsPermissionPolicy.can_action_accept + can_action_submit = RDMRequestsPermissionPolicy.can_action_submit + [ + curation_request_record_review + ] + can_action_resubmit = can_action_submit diff --git a/invenio_curations/services/service.py b/invenio_curations/services/service.py index 9180b46..c2431b9 100644 --- a/invenio_curations/services/service.py +++ b/invenio_curations/services/service.py @@ -127,7 +127,8 @@ def create(self, identity, data=None, uow=None, **kwargs): ), } - if self.get_review(identity, topic): + # using system identity to ensure a request is fetched, if it exists. Even if the user would not have access. + if self.get_review(system_identity, topic): raise OpenRecordCurationRequestAlreadyExists() if data: