Skip to content
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

resources: add param to filter shared with my uploads #1956

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,16 @@ class CommunityHeaderComponent extends Component {
imagePlaceholderLink,
showCommunitySelectionButton,
disableCommunitySelectionButton,
showCommunityHeader,
userCanManageRecord,
record,
showCommunityHeader,
} = this.props;
const { modalOpen } = this.state;

const isNewUpload = !record.id;
const isCommunitySelectionDisabled =
(!isNewUpload && !userCanManageRecord) || disableCommunitySelectionButton;

return (
showCommunityHeader && (
<Container
Expand Down Expand Up @@ -82,7 +87,7 @@ class CommunityHeaderComponent extends Component {
<Overridable id="InvenioRdmRecords.CommunityHeader.CommunitySelectionButton.Container">
<Button
className="community-header-button"
disabled={disableCommunitySelectionButton}
disabled={isCommunitySelectionDisabled}
onClick={() => this.setState({ modalOpen: true })}
primary
size="mini"
Expand Down Expand Up @@ -110,7 +115,7 @@ class CommunityHeaderComponent extends Component {
onClick={() => changeSelectedCommunity(null)}
content={i18next.t("Remove")}
icon="close"
disabled={disableCommunitySelectionButton}
disabled={isCommunitySelectionDisabled}
/>
)}
</Overridable>
Expand All @@ -133,6 +138,7 @@ CommunityHeaderComponent.propTypes = {
showCommunityHeader: PropTypes.bool.isRequired,
changeSelectedCommunity: PropTypes.func.isRequired,
record: PropTypes.object.isRequired,
userCanManageRecord: PropTypes.bool.isRequired,
};

CommunityHeaderComponent.defaultProps = {
Expand All @@ -146,6 +152,7 @@ const mapStateToProps = (state) => ({
showCommunitySelectionButton:
state.deposit.editorState.ui.showCommunitySelectionButton,
showCommunityHeader: state.deposit.editorState.ui.showCommunityHeader,
userCanManageRecord: state.deposit.permissions.can_manage,
});

const mapDispatchToProps = (dispatch) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ class SubmitReviewButtonComponent extends Component {
};

isDisabled = (disableSubmitForReviewButton, filesState) => {
const { formik } = this.props;
const { formik, userCanManageRecord } = this.props;
const { values, isSubmitting } = formik;

if (disableSubmitForReviewButton || isSubmitting) {
if (!userCanManageRecord || disableSubmitForReviewButton || isSubmitting) {
return true;
}

Expand Down Expand Up @@ -131,6 +131,7 @@ SubmitReviewButtonComponent.propTypes = {
formik: PropTypes.object.isRequired,
publishModalExtraContent: PropTypes.string,
filesState: PropTypes.object,
userCanManageRecord: PropTypes.bool.isRequired,
};

SubmitReviewButtonComponent.defaultProps = {
Expand All @@ -150,6 +151,7 @@ const mapStateToProps = (state) => ({
state.deposit.editorState.ui.disableSubmitForReviewButton,
publishModalExtraContent: state.deposit.config.publish_modal_extra,
filesState: state.files,
userCanManageRecord: state.deposit.permissions.can_manage,
});

export const SubmitReviewButton = connect(
Expand Down
1 change: 1 addition & 0 deletions invenio_rdm_records/requests/community_inclusion.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ class CommunityInclusion(RequestType):
allowed_topic_ref_types = ["record"]
needs_context = {
"community_roles": ["owner", "manager", "curator"],
"record_permission": "preview",
}

available_actions = {
Expand Down
1 change: 1 addition & 0 deletions invenio_rdm_records/requests/community_submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ class CommunitySubmission(ReviewRequest):
allowed_topic_ref_types = ["record"]
needs_context = {
"community_roles": ["owner", "manager", "curator"],
"record_permission": "preview",
}

available_actions = {
Expand Down
15 changes: 15 additions & 0 deletions invenio_rdm_records/requests/entity_resolvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from invenio_users_resources.services.schemas import SystemUserSchema
from sqlalchemy.orm.exc import NoResultFound

from ..proxies import current_rdm_records_service
from ..records.api import RDMDraft, RDMRecord
from ..services.config import RDMRecordServiceConfig
from ..services.dummy import DummyExpandingService
Expand Down Expand Up @@ -63,6 +64,20 @@ def ghost_record(self, record):
"""
return {"id": record}

def get_needs(self, ctx=None):
"""Enrich request with record needs.

A user that can preview a record can also read its requests.
"""
record = self.resolve()
needs = []
record_permission = ctx.get("record_permission")
if record_permission:
needs = current_rdm_records_service.config.permission_policy_cls(
record_permission, record=record
).needs
return needs


class RDMRecordResolver(RecordResolver):
"""RDM Record entity resolver."""
Expand Down
1 change: 1 addition & 0 deletions invenio_rdm_records/resources/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ class RDMSearchRequestArgsSchema(SearchRequestArgsSchema):
locale = fields.Str()
status = fields.Str()
include_deleted = fields.Bool()
shared_with_me = fields.Bool()
20 changes: 19 additions & 1 deletion invenio_rdm_records/services/access/service.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-

Check failure on line 1 in invenio_rdm_records/services/access/service.py

View workflow job for this annotation

GitHub Actions / Python / Tests (3.9, postgresql14, opensearch2)

isort-check from invenio_notifications.services.uow import NotificationOp from invenio_records_resources.services.errors import PermissionDeniedError from invenio_records_resources.services.records.schema import ServiceSchemaWrapper -from invenio_records_resources.services.uow import unit_of_work, RecordCommitOp +from invenio_records_resources.services.uow import RecordCommitOp, unit_of_work from invenio_requests.proxies import current_requests_service from invenio_search.engine import dsl from invenio_users_resources.proxies import current_user_resources

Check failure on line 1 in invenio_rdm_records/services/access/service.py

View workflow job for this annotation

GitHub Actions / Python / Tests (3.12, postgresql14, opensearch2)

isort-check from invenio_notifications.services.uow import NotificationOp from invenio_records_resources.services.errors import PermissionDeniedError from invenio_records_resources.services.records.schema import ServiceSchemaWrapper -from invenio_records_resources.services.uow import unit_of_work, RecordCommitOp +from invenio_records_resources.services.uow import RecordCommitOp, unit_of_work from invenio_requests.proxies import current_requests_service from invenio_search.engine import dsl from invenio_users_resources.proxies import current_user_resources
#
# Copyright (C) 2020-2024 CERN.
# Copyright (C) 2020-2021 Northwestern University.
Expand All @@ -22,7 +22,7 @@
from invenio_notifications.services.uow import NotificationOp
from invenio_records_resources.services.errors import PermissionDeniedError
from invenio_records_resources.services.records.schema import ServiceSchemaWrapper
from invenio_records_resources.services.uow import unit_of_work
from invenio_records_resources.services.uow import unit_of_work, RecordCommitOp
from invenio_requests.proxies import current_requests_service
from invenio_search.engine import dsl
from invenio_users_resources.proxies import current_user_resources
Expand Down Expand Up @@ -104,6 +104,16 @@
GrantSubjectExpandableField("subject"),
]

#
# Update parent request on access changes
#

def _update_parent_request(self, parent, uow):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just for my information to understand how this works: why is it needed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is needed so when a user changes the access in a record via the share button, the record/request is shown to the user that the record was shared with under their dashboard.

"""Update the parent record request."""
if parent.review:
request = parent.review.get_object()
uow.register(RecordCommitOp(request, indexer=self.indexer))

#
# Secret links
#
Expand Down Expand Up @@ -201,6 +211,7 @@
)

uow.register(ParentRecordCommitOp(parent, indexer_context=dict(service=self)))
self._update_parent_request(parent, uow)

return self.link_result_item(
self,
Expand Down Expand Up @@ -301,6 +312,7 @@
link.description = data.get("description", link.description)

uow.register(ParentRecordCommitOp(parent, indexer_context=dict(service=self)))
self._update_parent_request(parent, uow)

return self.link_result_item(
self,
Expand Down Expand Up @@ -330,6 +342,7 @@
link.revoke()

uow.register(ParentRecordCommitOp(parent, indexer_context=dict(service=self)))
self._update_parent_request(parent, uow)

return True

Expand Down Expand Up @@ -423,6 +436,7 @@
new_grants.append(new_grant)

uow.register(ParentRecordCommitOp(parent, indexer_context=dict(service=self)))
self._update_parent_request(parent, uow)

return self.grants_result_list(
self,
Expand Down Expand Up @@ -517,6 +531,7 @@
parent.access.grants[grant_id] = new_grant

uow.register(ParentRecordCommitOp(parent, indexer_context=dict(service=self)))
self._update_parent_request(parent, uow)

return self.grant_result_item(
self,
Expand Down Expand Up @@ -575,6 +590,7 @@
parent.access.grants.pop(grant_id)

uow.register(ParentRecordCommitOp(parent, indexer_context=dict(service=self)))
self._update_parent_request(parent, uow)

return True

Expand Down Expand Up @@ -942,6 +958,7 @@
parent.access.grants[grant_index] = new_grant

uow.register(ParentRecordCommitOp(parent, indexer_context=dict(service=self)))
self._update_parent_request(parent, uow)

return self.grant_result_item(
self,
Expand Down Expand Up @@ -972,5 +989,6 @@
raise LookupError(subject_id)

uow.register(ParentRecordCommitOp(parent, indexer_context=dict(service=self)))
self._update_parent_request(parent, uow)

return True
4 changes: 2 additions & 2 deletions invenio_rdm_records/services/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-

Check failure on line 1 in invenio_rdm_records/services/config.py

View workflow job for this annotation

GitHub Actions / Python / Tests (3.9, postgresql14, opensearch2)

isort-check from .schemas.tombstone import TombstoneSchema from .search_params import ( MetricsParam, + PublishedRecordsParam, SharedOrMineDraftsParam, - PublishedRecordsParam, StatusParam, ) from .sort import VerifiedRecordsSortParam

Check failure on line 1 in invenio_rdm_records/services/config.py

View workflow job for this annotation

GitHub Actions / Python / Tests (3.12, postgresql14, opensearch2)

isort-check from .schemas.tombstone import TombstoneSchema from .search_params import ( MetricsParam, + PublishedRecordsParam, SharedOrMineDraftsParam, - PublishedRecordsParam, StatusParam, ) from .sort import VerifiedRecordsSortParam
#
# Copyright (C) 2020-2024 CERN.
# Copyright (C) 2020-2021 Northwestern University.
Expand Down Expand Up @@ -97,7 +97,7 @@
from .schemas.tombstone import TombstoneSchema
from .search_params import (
MetricsParam,
MyDraftsParam,
SharedOrMineDraftsParam,
PublishedRecordsParam,
StatusParam,
)
Expand Down Expand Up @@ -243,7 +243,7 @@
}

params_interpreters_cls = [
MyDraftsParam
SharedOrMineDraftsParam
] + SearchDraftsOptions.params_interpreters_cls


Expand Down
6 changes: 3 additions & 3 deletions invenio_rdm_records/services/review/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def read(self, identity, id_):
def update(self, identity, id_, data, revision_id=None, uow=None):
"""Create or update an existing review."""
draft = self.draft_cls.pid.resolve(id_, registered_only=False)
self.require_permission(identity, "update_draft", record=draft)
self.require_permission(identity, "manage", record=draft)

# If an existing review exists, delete it.
if draft.parent.review is not None:
Expand All @@ -124,7 +124,7 @@ def update(self, identity, id_, data, revision_id=None, uow=None):
def delete(self, identity, id_, revision_id=None, uow=None):
"""Delete a review."""
draft = self.draft_cls.pid.resolve(id_, registered_only=False)
self.require_permission(identity, "update_draft", record=draft)
self.require_permission(identity, "manage", record=draft)

# Preconditions
if draft.parent.review is None:
Expand Down Expand Up @@ -176,7 +176,7 @@ def submit(self, identity, id_, data=None, require_review=False, uow=None):
community = draft.parent.review.receiver.resolve()

# Check permission
self.require_permission(identity, "update_draft", record=draft)
self.require_permission(identity, "manage", record=draft)

community_id = (
draft.parent.review.get_object().get("receiver", {}).get("community", "")
Expand Down
57 changes: 51 additions & 6 deletions invenio_rdm_records/services/search_params.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-

Check failure on line 1 in invenio_rdm_records/services/search_params.py

View workflow job for this annotation

GitHub Actions / Python / Tests (3.9, postgresql14, opensearch2)

isort-check """Search parameter interpreter API.""" -from invenio_search.engine import dsl - from invenio_access.permissions import authenticated_user from invenio_records_resources.services.records.params.base import ParamInterpreter +from invenio_search.engine import dsl from invenio_rdm_records.records.systemfields.access.grants import Grant from invenio_rdm_records.records.systemfields.deletion_status import (

Check failure on line 1 in invenio_rdm_records/services/search_params.py

View workflow job for this annotation

GitHub Actions / Python / Tests (3.12, postgresql14, opensearch2)

isort-check """Search parameter interpreter API.""" -from invenio_search.engine import dsl - from invenio_access.permissions import authenticated_user from invenio_records_resources.services.records.params.base import ParamInterpreter +from invenio_search.engine import dsl from invenio_rdm_records.records.systemfields.access.grants import Grant from invenio_rdm_records.records.systemfields.deletion_status import (
#
# Copyright (C) 2023-2024 CERN.
#
Expand All @@ -6,11 +6,14 @@
# modify it under the terms of the MIT License; see LICENSE file for more
# details.

"""Sort parameter interpreter API."""
"""Search parameter interpreter API."""

from invenio_search.engine import dsl

from invenio_access.permissions import authenticated_user
from invenio_records_resources.services.records.params.base import ParamInterpreter

from invenio_rdm_records.records.systemfields.access.grants import Grant
from invenio_rdm_records.records.systemfields.deletion_status import (
RecordDeletionStatusEnum,
)
Expand Down Expand Up @@ -42,8 +45,38 @@
return search


class MyDraftsParam(ParamInterpreter):
"""Evaluates the include_deleted parameter."""
class SharedOrMineDraftsParam(ParamInterpreter):
"""Evaluates the shared_with_me parameter.

Returns only drafts owned by the user or shared with the user via grant subject user or role.
"""

def _make_grant_token(self, subj_type, subj_id, permission):
"""Create a grant token from the specified parts."""
# NOTE: `Grant.to_token()` doesn't need the actual subject to be set
return Grant(
subject=None,
origin=None,
permission=permission,
subject_type=subj_type,
subject_id=subj_id,
).to_token()

def _grant_tokens(self, identity, permissions):
"""Parse a list of grant tokens provided by the given identity."""
tokens = []
for _permission in permissions:
for need in identity.provides:
token = None
if need.method == "id":
token = self._make_grant_token("user", need.value, _permission)
elif need.method == "role":
token = self._make_grant_token("role", need.value, _permission)

if token is not None:
tokens.append(token)

return tokens

def apply(self, identity, search, params):
"""Evaluate the include_deleted parameter on the search."""
Expand All @@ -55,9 +88,21 @@
return authenticated_user in identity.provides

if value is None and is_user_authenticated():
search = search.filter(
"term", **{"parent.access.owned_by.user": identity.id}
)
if params.get("shared_with_me") is True:
# Shared with me
tokens = self._grant_tokens(
identity, permissions=["preview", "edit", "manage"]
)
shared_with_me = dsl.Q(
"terms", **{"parent.access.grant_tokens": tokens}
)
return search.filter(shared_with_me)
elif params.get("shared_with_me") is False:
# My uploads
my_uploads = dsl.Q(
"term", **{"parent.access.owned_by.user": identity.id}
)
search = search.filter(my_uploads)
return search


Expand Down
Loading