-
Notifications
You must be signed in to change notification settings - Fork 87
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
access request: add secret_link_expiration to guest access request payload schema #1410
Changes from all commits
809686b
fcb6d3c
ef6fa2d
809b6ca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -6,14 +6,19 @@ | |||||||||||
# it under the terms of the MIT License; see LICENSE file for more details. | ||||||||||||
|
||||||||||||
"""Access requests for records.""" | ||||||||||||
from datetime import datetime, timedelta | ||||||||||||
|
||||||||||||
import marshmallow as ma | ||||||||||||
from flask import current_app, g | ||||||||||||
from invenio_access.permissions import authenticated_user, system_identity | ||||||||||||
from invenio_i18n import lazy_gettext as _ | ||||||||||||
from invenio_mail.tasks import send_email | ||||||||||||
from invenio_records_resources.services.uow import Operation, RecordCommitOp | ||||||||||||
from invenio_requests import current_events_service | ||||||||||||
from invenio_requests.customizations import RequestType, actions | ||||||||||||
from marshmallow import fields | ||||||||||||
from invenio_requests.customizations.event_types import CommentEventType | ||||||||||||
from marshmallow import ValidationError, fields, validates | ||||||||||||
from marshmallow_utils.permissions import FieldPermissionsMixin | ||||||||||||
|
||||||||||||
from ...proxies import current_rdm_records_service as service | ||||||||||||
|
||||||||||||
|
@@ -58,7 +63,9 @@ class GuestSubmitAction(actions.SubmitAction): | |||||||||||
|
||||||||||||
def execute(self, identity, uow): | ||||||||||||
"""Execute the submit action.""" | ||||||||||||
self.request["title"] = self.request.topic.resolve().metadata["title"] | ||||||||||||
record = self.request.topic.resolve() | ||||||||||||
self.request["title"] = record.metadata["title"] | ||||||||||||
|
||||||||||||
super().execute(identity, uow) | ||||||||||||
|
||||||||||||
|
||||||||||||
|
@@ -82,11 +89,22 @@ def execute(self, identity, uow): | |||||||||||
"origin": f"request:{self.request.id}", | ||||||||||||
} | ||||||||||||
|
||||||||||||
# secret link will never expire if secret_link_expiration is empty | ||||||||||||
days = int(payload["secret_link_expiration"]) | ||||||||||||
# TODO date calculation could be done elsewhere ? | ||||||||||||
if days: | ||||||||||||
data["expires_at"] = ( | ||||||||||||
(datetime.utcnow() + timedelta(days=days)).date().isoformat() | ||||||||||||
) | ||||||||||||
link = service.access.create_secret_link(identity, record.id, data) | ||||||||||||
|
||||||||||||
access_url = f"{record.links['self_html']}?token={link._link.token}" | ||||||||||||
plain_message = _("Access the record here: %(url)s", url=access_url) | ||||||||||||
|
||||||||||||
plain_message = _("Access the record here: {url}".format(url=access_url)) | ||||||||||||
message = _( | ||||||||||||
'Click <a href="%(url)s">here</a> to access the record.', url=access_url | ||||||||||||
'Click <a href="{url}">here</a> to access the record.'.format( | ||||||||||||
url=access_url | ||||||||||||
) | ||||||||||||
) | ||||||||||||
|
||||||||||||
uow.register(RecordCommitOp(record._record.parent)) | ||||||||||||
|
@@ -101,6 +119,22 @@ def execute(self, identity, uow): | |||||||||||
|
||||||||||||
super().execute(identity, uow) | ||||||||||||
|
||||||||||||
confirmation_message = { | ||||||||||||
"payload": { | ||||||||||||
"content": 'Click <a href="{url}">here</a> to access the record.'.format( | ||||||||||||
url=access_url | ||||||||||||
) | ||||||||||||
} | ||||||||||||
} | ||||||||||||
current_events_service.create( | ||||||||||||
system_identity, | ||||||||||||
self.request.id, | ||||||||||||
confirmation_message, | ||||||||||||
CommentEventType, | ||||||||||||
uow=uow, | ||||||||||||
notify=False, | ||||||||||||
) | ||||||||||||
|
||||||||||||
|
||||||||||||
class UserAcceptAction(actions.AcceptAction): | ||||||||||||
"""Accept action.""" | ||||||||||||
|
@@ -175,15 +209,44 @@ class GuestAccessRequest(RequestType): | |||||||||||
allowed_receiver_ref_types = ["user", "community"] | ||||||||||||
allowed_topic_ref_types = ["record"] | ||||||||||||
|
||||||||||||
@classmethod | ||||||||||||
def _create_payload_cls(cls): | ||||||||||||
zzacharo marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||
class PayloadBaseSchema(ma.Schema, FieldPermissionsMixin): | ||||||||||||
field_load_permissions = { | ||||||||||||
"secret_link_expiration": "manage_access_options", | ||||||||||||
} | ||||||||||||
|
||||||||||||
class Meta: | ||||||||||||
unknown = ma.RAISE | ||||||||||||
|
||||||||||||
cls.payload_schema_cls = PayloadBaseSchema | ||||||||||||
|
||||||||||||
def _update_link_config(self, **context_vars): | ||||||||||||
"""Fix the prefix required for "self_html".""" | ||||||||||||
identity = context_vars.get("identity", g.identity) | ||||||||||||
prefix = "/me" | ||||||||||||
if authenticated_user not in identity.provides: | ||||||||||||
prefix = "/access-requests" | ||||||||||||
|
||||||||||||
if hasattr(g, "identity"): | ||||||||||||
identity = context_vars.get("identity", g.identity) | ||||||||||||
|
||||||||||||
if authenticated_user not in identity.provides: | ||||||||||||
prefix = "/access-requests" | ||||||||||||
|
||||||||||||
return {"ui": context_vars["ui"] + prefix} | ||||||||||||
|
||||||||||||
@validates("secret_link_expiration") | ||||||||||||
def _validate_days(self, value): | ||||||||||||
try: | ||||||||||||
if int(value) < 0: | ||||||||||||
raise ValidationError( | ||||||||||||
message="Not a valid number of days.", | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. translate? |
||||||||||||
field_name="secret_link_expiration", | ||||||||||||
) | ||||||||||||
except ValueError: | ||||||||||||
raise ValidationError( | ||||||||||||
message="Not a valid number of days.", | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. translate? |
||||||||||||
field_name="secret_link_expiration", | ||||||||||||
) | ||||||||||||
|
||||||||||||
available_actions = { | ||||||||||||
"create": actions.CreateAction, | ||||||||||||
"submit": GuestSubmitAction, | ||||||||||||
|
@@ -200,4 +263,5 @@ def _update_link_config(self, **context_vars): | |||||||||||
"full_name": fields.String(required=True), | ||||||||||||
"token": fields.String(required=True), | ||||||||||||
"message": fields.String(required=False), | ||||||||||||
"secret_link_expiration": fields.String(required=True), | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. possible to be done with class PayloadBaseSchema(ma.Schema, FieldPermissionsMixin) in the core There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mutuall decided to leave it as it is since this class attribute is cleaner if contains only fields, no other class attributes |
||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -615,13 +615,14 @@ def create_user_access_request( | |
def create_guest_access_request_token( | ||
self, identity, id_, data, expand=False, uow=None | ||
): | ||
"""Create an request token that can be used to create an access request.""" | ||
"""Create a request token that can be used to create an access request.""" | ||
# Permissions | ||
if authenticated_user in identity.provides: | ||
raise PermissionDeniedError("request_guest_access") | ||
|
||
record = self.record_cls.pid.resolve(id_) | ||
if current_app.config.get("MAIL_SUPPRESS_SEND", False): | ||
# TODO should be handled globally, not here, maybe EmailOp? | ||
current_app.logger.warn( | ||
"Cannot proceed with guest based access request - " | ||
"email sending has been disabled!" | ||
|
@@ -636,6 +637,7 @@ def create_guest_access_request_token( | |
) | ||
|
||
# Create the URL for the email verification endpoint | ||
# TODO why replace api? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand the comment...is it still relevant? |
||
verify_url = url_for( | ||
"invenio_rdm_records_ext.verify_access_request_token", | ||
_external=True, | ||
|
@@ -680,7 +682,7 @@ def create_guest_access_request(self, identity, token, expand=False, uow=None): | |
|
||
access_token = AccessRequestToken.get_by_token(token) | ||
if access_token is None: | ||
return None | ||
return | ||
|
||
access_token_data = access_token.to_dict() | ||
record = self.record_cls.pid.resolve(access_token_data["record_pid"]) | ||
|
@@ -703,14 +705,16 @@ def create_guest_access_request(self, identity, token, expand=False, uow=None): | |
|
||
if requests: | ||
raise DuplicateAccessRequestError([str(r.id) for r in requests]) | ||
|
||
data = { | ||
"payload": { | ||
"permission": "view", | ||
"email": access_token_data["email"], | ||
"full_name": access_token_data["full_name"], | ||
"token": access_token_data["token"], | ||
"message": access_token_data.get("message") or "", | ||
"secret_link_expiration": str( | ||
record.parent.access.settings.secret_link_expiration | ||
), | ||
} | ||
} | ||
|
||
|
@@ -719,9 +723,6 @@ def create_guest_access_request(self, identity, token, expand=False, uow=None): | |
if record_owner: | ||
receiver = record_owner | ||
|
||
if receiver is None: | ||
pass | ||
|
||
access_token.delete() | ||
request = current_requests_service.create( | ||
system_identity, | ||
|
@@ -730,21 +731,13 @@ def create_guest_access_request(self, identity, token, expand=False, uow=None): | |
receiver, | ||
creator=data["payload"]["email"], | ||
topic=record, | ||
expires_at=None, | ||
expires_at=None, # TODO expire request ? | ||
expand=expand, | ||
uow=uow, | ||
) | ||
|
||
if request.errors: | ||
return request | ||
|
||
prefix = _( | ||
"%(full_name)s (%(email)s) commented", | ||
full_name=access_token_data["full_name"], | ||
email=data["payload"]["email"], | ||
) | ||
message = data["payload"].get("message") or "" | ||
comment = {"payload": {"content": f"{prefix}: {message}"}} | ||
comment = {"payload": {"content": message}} | ||
|
||
return current_requests_service.execute_action( | ||
system_identity, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -288,6 +288,14 @@ def needs(self, record=None, file_key=None, **kwargs): | |
return [] | ||
|
||
|
||
class IfCreate(ConditionalGenerator): | ||
"""Check if the request is created or modified.""" | ||
|
||
def _condition(self, record=None, request=None, **kwargs): | ||
"""Check if record is empty - meaning it is created for the first time .""" | ||
return record is None and request is None | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need both to be None? Maybe a more detailed comment on when is this happening? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will add a comment, I think I have described it in another place |
||
|
||
|
||
class IfRequestType(ConditionalGenerator): | ||
"""Conditional generator for requests of a certain type.""" | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check this - probably not needed, it refers to guest access request expiration date, not the access link
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
checked - this is accept action, it should be here