diff --git a/README.rst b/README.rst index be1cc46..4ad4a81 100644 --- a/README.rst +++ b/README.rst @@ -139,24 +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, + TopicPermission, ) - 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.""" - # Only allow community-submission requests to be accepted after - # the rdm-curation request has been accepted + curation_request_record_review = IfRequestTypes( + [CurationRequest], + then_=[TopicPermission(permission_name="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, - ], + request_types=[CommunitySubmission], then_=[ IfCurationRequestAccepted( then_=RDMRequestsPermissionPolicy.can_action_accept, else_=[] @@ -167,18 +174,30 @@ 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 = [ + # 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(), + TopicPermission(permission_name="can_review"), + ], + else_=RDMRequestsPermissionPolicy.can_read, + ) ] + can_create_comment = can_read + # 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 = RDMRequestsPermissionPolicy.can_action_submit + + 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..9954156 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,59 @@ def _condition(self, request=None, **kwargs): return False +class EntityReferenceServicePermission(Generator): + """Request-oriented generator accessing a named permission from the entity's service config.""" + + entity_field = None + + 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.""" + if request is None: + return set() + + 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 8f545c4..2c6251b 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, + TopicPermission, ) @@ -71,16 +76,49 @@ 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()], - ), + curation_request_record_review = IfRequestTypes( + [CurationRequest], + then_=[TopicPermission(permission_name="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 = [ + # 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(), + TopicPermission(permission_name="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 + + # 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 9180b46..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 _ @@ -127,7 +128,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: