-
Notifications
You must be signed in to change notification settings - Fork 505
Request-a-copy improvements: Support access by secure link #3984
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
Request-a-copy improvements: Support access by secure link #3984
Conversation
ba756cc to
83f904a
Compare
|
Just a note to say that it's taken me a little while longer to fit in the google/altcha "generic" recaptcha for this, register user, feedback etc, than I first anticipated -- if it takes too long I will pull back on that "bonus extra" and we can consider this email form as just (optionally) protected by Altcha |
|
I tested the main functionality and it works great and as advertised ✅. I will test the different configuration possibilities in the next days. |
|
I did thoroughly test the different configuration options: Test basic configuration
Test the captcha=> Works ✅ Test using a size limitRequest single file below limit Test if access is closed again, after end of access period=> Works insofar as access is no longer possible ✅ Test restricted images and videos with media viewer enabled=> Image Viewer works on download page ✅ => Video player works on download page ✅ |
tdonohue
left a comment
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.
@kshepherd : Thanks for this feature! I still haven't had a chance to test the code, but I've done an initial code review. Overall, the code looks OK, but I have to admit I'm worried about the code duplication that I see with this PR.
I'm not fond of all the new item-page/access-by-token/ components, because they all look to be a copy & paste of an existing component, updated slightly to support accessTokens. I'm worried this code duplication is going to be difficult to maintain over time, and we'll eventually have bugs or improvements to the original components that don't get copied into these access-by-token versions.
I'd much rather we find a way to modify or extend the existing components to support accessTokens. Conceptually, it seems like it's just a giant "if" clause: "If accessToken exists in the URL param, then use it to download the file in our component(s). Else, do things as normal." But, the approach of this PR currently appears to be: "If accessToken exists in the URL param, then use a separate set of components entirely. Else, use the normal, default components."
All that said, I don't have a solution to present other than to look into modifying the existing components to check for the "accessToken" and use it when found. If you already did try that approach and hit a "wall" or major issue, then I'd like to better understand what that issue was, so that we can see if we can find a workaround.
Beyond that major concern, everything else inline below should be minor code cleanup. Thanks again, as this looks like a great idea, but I just want to make sure the code is easier to maintain.
src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.ts
Outdated
Show resolved
Hide resolved
src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.ts
Outdated
Show resolved
Hide resolved
src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.ts
Outdated
Show resolved
Hide resolved
src/app/item-page/access-by-token/item-access-by-token-view.component.ts
Outdated
Show resolved
Hide resolved
src/app/request-copy/grant-request-copy/grant-request-copy.component.html
Outdated
Show resolved
Hide resolved
| <!-- </a>--> | ||
| <a [attr.routerLink]="previewLinkOptions.routerLink" class="dont-break-out d-block" [target]="'_blank'" | ||
| [attr.queryParams]="previewLinkOptions.queryParams" | ||
| [attr.rel]="" |
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.
When using _blank we should set rel="noopener noreferrer"
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.
done
|
Thanks for your comments @tdonohue - I will work to address your specific points. Regarding the duplication of components, you are correct; it's a tradeoff where we took the option of "maintain very similar / near-duplicates of some common components, and avoid adding all the additional if tests / access token handling to those core common components".
Does that sound reasonable? |
|
@kshepherd : I see. I'm OK with the Item pages being different, provided we find a way to document (in code comments of the component) why we have a separate copy of the Item page component. I agree though that I'd rather see the other individual components (that you've listed) consolidated to avoid code duplication. That way if we ever update the media viewers (or similar), we aren't having to do so in multiple places. I do also want to point out that Request a Copy seems to be enabled by default as of DSpace 7. So, I expect most/many repositories do use it now (unless they've specifically turned it off). |
|
From our point of view the duplicated Bitstream sections/links and Item pages are also a red flag, especially when taking into account theming and custom Entities.
While I agree that limiting the duplication to Item components only is an improvement, I'd still argue that those components are some of the most commonly customized in DSpace as a whole. I'd like to propose two alternatives to keep Item pages deduplicated:
|
|
thanks @ybnd , I see the points and I'll think about the suggestions.. a resolver might be a good approach. Do you mind if I ping you for specific feedback on that if I try something out? regarding suggestion 2 - that is kind of what i am going for in the current code, but i think i see your concern -- that any breaking changes to ItemComponent would also affect these pages? |
|
@tdonohue @ybnd just another note to say i do like a resolver as a way of separating and simplifying the way of providing the ItemRequest object to whatever component is ultimately loaded, but in addition:
So I'm a bit stuck of the smoothest way forward on this... yes, I want to farm out all the parts of the item 'view' except for the file section... perhaps it's enough to hide the file section in an item view mode where an access token is present, and include the full file section in ThemedItemPageComponent afterwards, where it is present, but that means modifying this super-generic "ListableObjectComponentLoader" class to handle accessTokens when we only care about it in this super-specific "the object is an item and we are calling it in Standalone View Mode" case... (UPDATE - see next comment, I may have a way to move forward by wrapping the item/dso in an extended model and passing it down to base components?) |
|
Hi @kshepherd, |
|
@tdonohue @ybnd I have pushed a refactored version (I still have the original branch here to compare if/when we need it) -- it has a little console output still included so please ignore that, but I submit it to get your approval on the new approach:
Is this closer to an acceptable solution? |
85512ca to
5bf7344
Compare
a41b218 to
1fd8f7e
Compare
|
Hi @kshepherd, |
|
NOTE: This PR has merge conflicts caused by the merger of #3997. You will need to rebase this PR on the latest |
1fd8f7e to
c1b8ec4
Compare
fce5493 to
58cee5f
Compare
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.
@kshepherd @tdonohue sorry, I found a few more things to bring up after testing with another theme and "faking" an expired token
| return itemRequestDataService.getSanitizedRequestByAccessToken(accessToken).pipe( | ||
| getFirstCompletedRemoteData(), | ||
| // Handle authorization errors, not found errors and forbidden errors as normal | ||
| redirectOn4xx(router, authService), |
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.
Right now this means that you'll be shown a login page if you try to follow the access link after it expires.
It would be more informative if we could still show the Item page, but with a notice to explain what's going on.
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.
notifications now show 2 kinds of errors, depending on if the token is expired, or just was not granted or was revoked. the redirect operator here is still in place, but won't trigger in those scenarios because the backend returns 200 for those cases in the findByAccessToken method (of course, it still throws a hard authorize error in the actual bitstream download check)
| * to help components determine how the Item or its data/bitstream should be delivered | ||
| * and presented to the users, but not part of the actual database model. | ||
| */ | ||
| export class ItemWithSupplementaryData extends Item { |
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.
This class is still here; as far as I can tell it's only used in one other place as part of an instanceof check that isn't needed anymore?
| } | ||
| </div> | ||
| } | ||
| </ng-container> |
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.
Could this be moved to a separate component like the other alerts below?
If we change the REST/resolver logic to still expose expired RequestItems this would also be the place to show an alternative message, e.g. something along the lines of "you're viewing this page with an expired access token, so you no longer have access to certain files"
The date is shown as an ISO timestamp, which is a bit hard to read. Could you transform it with e.g. | date:"YYYY/MM/dd hh:mm"?

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.
done
| } | ||
| .request-a-copy-access-icon { | ||
| margin-right: 4px; | ||
| color: #26a269; |
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.
This colour is defined in two places.
Could you use a Bootstrap variable like --bs-success for it, or create a new --ds- variable in _custom_variables.scss to make sure it's easy to theme?
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.
done
| <!-- Access period dropdown --> | ||
| <div ngbDropdownMenu aria-labelledby="accessPeriod"> | ||
| @for (accessPeriod of (validAccessPeriods$ | async); track accessPeriod) { | ||
| <button |
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.
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.
i hadn't seen this and it just followed the same pattern of ngbDropdownMenu used elsewhere but I'm trying a few things out, please let me know if you still see that
|
@ybnd thanks, I'm working on these right now. I don't want to do a large refactor to support the special expiry alert vs 4xx redirects so I'm seeing what I can do with error messages rather than altering the models.. |
b8263a0 to
2c435a0
Compare
|
@ybnd just pushed changes now. It requires the latest version of the DSpace backend PR too, since I no longer thrown an AuthorizeException in that findByAccessToken repository get method, but instead return the object with "expired" set.
You can see examples of the "expired" and "not granted / revoked" errors below: |
2c435a0 to
293e4c4
Compare
1eab960 to
abd3b71
Compare
* New accessExpired flag can be checked along with acceptRequest, so expired or not-granted links will show an error and draw 'normal' download links instead of doing a hard 4xx redirect * Old ItemWithSupplementaryData component removed * Notifications moved to sub-component of item page and display a warning on valid access token, or a danger alert if expired or not-granted, with an appropriate error message * Date formatted in messages as yyyy-MM-dd
abd3b71 to
e9cf183
Compare
|
btw my tests should pass, for some reason the last few CI/CD tests fail to download+build the docker image, so please don't let that put you off |
ybnd
left a comment
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.
Many thanks for the quick turnaround, and sorry again for the short notice.
Very happy with the overall shape of the codebase & UX now 👍
The dropdown issue in the custom theme needs one more tiny change to fully work and then we can merge!
src/themes/custom/app/request-copy/email-request-copy/email-request-copy.component.ts
Outdated
Show resolved
Hide resolved
bd40391 to
87624c7
Compare
|
thanks @ybnd , as you see the change is cherry-picked, and I tested it in my custom theme and it's looking fine |
|
Thanks all! As I see that @ybnd gave this a 👍 in his last comment, and his one minor suggestion has been accepted, I'm considering this at +2. I see the backend PR is also at +2. Therefore, I'm merging this immediately. Thanks again @kshepherd and @pnbecker for all the hard work on this! Keep in mind, we will need documentation both for the Request a Copy enhancements and the Altcha captcha options added to https://wiki.lyrasis.org/display/DSDOC9x/ Therefore, I'm adding the |
|
@kshepherd or @pnbecker : Would one of you be able to add documentation for this feature in our 9.0 docs? https://wiki.lyrasis.org/display/DSDOC9x Ideally, basic documentation would exist prior to our 9.0 Testathon (which starts on Monday, April 7). But, if it cannot exist by then, it should hopefully be created shortly thereafter. |
|
Docs have now been added to https://wiki.lyrasis.org/display/DSDOC9x/Request+a+Copy and https://wiki.lyrasis.org/display/DSDOC9x/CAPTCHA+Verification |





References
Description
Request a copy is an existing feature in DSpace, whereby users can request access to files they are not authorized to download, and the item author or a delegated helpdesk can approve or deny these requests.
Up until 8.x, all approved requests resulted in an email sent to the requester as attachments.
This is not a suitable solution for large files and doesn’t allow as much control over future authorization to those resources.
In this improvement, a secret key is generated to provide limited access to the requested bitstream(s) with a URL, rather than sending files as attachments. This link can be configured to allow access to just selected bitstreams, or all bitstreams of an item, and it can be configured with an “access period”, after which the secret URL will no longer work.
Example "access by secure link" item page:

Notes for Reviewers
To test, you will also need the DSpace pull request applied: DSpace/DSpace#10407
This is halfway between an improvement and a new feature. For those doing practical testing, I recommend reading the attached PDF.
Request-a-copy-secure-links.pdf
There is a flyway sql migration - two columns are added, so make sure you are testing on an instance that doesn't need to quickly roll back to an earlier version with the existing requestitem table.
There is a new captcha added - A new GDPR complicant, proof-of-work captcha is added to the "request a copy" form. I would like to propose we make this the default for Register User as well, and am happy to open a new PR accordingly. It is under the MIT license: https://altcha.org/captcha/
Instructions for Reviewers
a. If you are a non-developer tester, you may be able to follow the docker testing instructions instead
List of changes in this PR:
Attribution
Developed by The Library Code with the support of Technische Universität Berlin
Checklist
This checklist provides a reminder of what we are going to look for when reviewing your PR. You do not need to complete this checklist prior creating your PR (draft PRs are always welcome).
However, reviewers may request that you complete any actions in this list if you have not done so. If you are unsure about an item in the checklist, don't hesitate to ask. We're here to help!
mainbranch of code (unless it is a backport or is fixing an issue specific to an older branch).npm run lintnpm run check-circ-deps)package.json), I've made sure their licenses align with the DSpace BSD License based on the Licensing of Contributions documentation.