CLARIN-DSpace v9/Fix REST rel information leak: run the authorization check before entity resolution on clarinlruallowances rels - #1438
Merged
milanmajchrak merged 1 commit intoSep 10, 2026
Conversation
…ity resolution on clarinlruallowances rels
GET /api/core/clarinlruallowances/{id} answers an anonymous caller 401 whether
or not the allowance exists, but two of its three rels answered 404 for an
unknown id and 401 for an existing one. The status code was therefore an oracle
for entity existence, and the 404 body named the entity.
The cause is ordering, not the check itself: the link methods resolve the entity
first, and ClarinLicenseResourceUserAllowanceService.find returns null for a
missing row before it ever reaches authorizeClruaAction. Adding
@PreAuthorize("hasAuthority('AUTHENTICATED')") to getUserRegistration and
getUserMetadata lets Spring Security reject the caller before the body runs, the
same idiom the three sibling repositories already use, so the rels match their
parent. The AuthorizeException translation stays, so an authenticated non-owner
still gets 403.
Also fixes the message typo "for if: " to "for id: ".
ClarinResourceMappingCLicenseLinkRepository deliberately keeps no guard - its
parent findOne is permitAll() and the Angular licence agreement page follows
that rel anonymously - and now says so in a javadoc, with a test that fails if
someone guards it.
Card X-11, owner decision O-7.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
References
_sync3/cards/X-11.md), owner decisions O-6/O-7 (own PR, not bundled)recorded these two as a leftover
dtq-dev(7.6.x) — see "Not fixed here" belowDescription
GET /api/core/clarinlruallowances/{id}answers an anonymous caller 401 whether or not the allowanceexists, but two of its three rels answered 404 for an unknown id and 401 for an existing one. The
status code was an oracle for entity existence, and the 404 body named the entity. This PR runs the
authorization check before entity resolution so the rels match their parent.
Instructions for Reviewers
The defect
ClarinLicenseResourceUserAllowanceRestRepository.findOneis guarded with@PreAuthorize("hasAuthority('AUTHENTICATED')"), so anonymous callers never learn anything from it. Two ofthe three
@LinkRestrels of the same model had no guard at all, and their bodies resolve the entityfirst:
So an unknown id produced 404 and an existing id produced 401. Live, before this PR:
core/clarinlruallowances/1/resourceMappingcore/clarinlruallowances/1/userRegistrationcore/clarinlruallowances/1/userMetadatawith the body
The ClarinLicenseResourceUserAllowance for id: 1 couldn't be found(andfor if: 1on theuserMetadata rel — a typo present on both branches).
List of changes in this PR
CLRUAUUserRegistrationLinkRepository.getUserRegistrationandCLRUAUserMetadataLinkRepository.getUserMetadata— add@PreAuthorize("hasAuthority('AUTHENTICATED')")plus javadoc explaining why the guard has to run beforeentity resolution. This is the idiom the three sibling repositories fixed by CLARIN-DSpace v9/Fix anonymous HTTP 500 on three CLARIN link rels #1431 already use. The
existing
catch (AuthorizeException) -> RESTAuthorizationExceptiontranslation is kept, so anauthenticated non-owner still gets 403, not 500.
CLRUAUserMetadataLinkRepository— message typofor if:becomesfor id:.ClarinResourceMappingCLicenseLinkRepository— no guard added, on purpose, plus a javadoc saying so:its parent
ClarinLicenseResourceMappingRestRepository.findOneispermitAll()and the Angular licenceagreement page follows that rel anonymously (
clarin-license-agreement-page.component.ts,followLink('clarinLicense')). Guarding it would break the anonymous download flow. Its anonymous 404 isparity with its parent, not a leak.
ClarinLinkRestRepositoryBeanNameIT— four new tests (10 -> 14).Deliberately not done: making the rels answer 404 for existing ids too. That would close the leak but
break parity with the parent, which answers 401.
How to test
anonymousRelStatusMatchesParentForUnknownIdid = Integer.MAX_VALUE, the anonymous status of the rel equals the anonymous status of its parent. The cells are enumerated from the models' own@LinksRestannotations, so a rel added later is covered automaticallyanonymousClruaRelsRevealNothingAboutExistenceclruaUserMetadataNotFoundMessageUsesIdfor id:and notfor if:anonymousResourceMappingClarinLicenseRelStaysPublicclarinlicenseresourcemappings/{id}/clarinLicenseis 200 — the guard against over-fixingNegative control (revert only the two production link repositories to the base):
clruaUserRegistrationAndUserMetadataRelsHonourRoles(the pre-existing test on these two rels) staysgreen in that control — it only ever exercised an existing id, which is why it never saw this defect —
and so does
anonymousResourceMappingClarinLicenseRelStaysPublic.mvn clean install -P-assembly -DskipTests-> BUILD SUCCESS, 0 Checkstyle violations.Not fixed here
origin/dtq-dev(7.6.x, production):@PreAuthorizecount is 0 in all three classes there. That needs its own issue against the 7.6 line._sync3/sweeps/rest-matrix.shwas recalibrated in the workspace (it filed a bare 404 asNO-ID-OR-DROPPEDand therefore reported "0 FAIL" while this leak was live). That file is not part ofthis repository, so it is not in this PR.
Checklist
dtq-dev-9-basebranch (fork's DSpace 9 base; notmain).unauthorized caller receives is corrected from 404 to 401/403.
🤖 Generated with Claude Code