You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
UFAL/Fix dspace-customers#1083 V7: check bulk-access-control target uuids however they are spelled - #1544
For REST calls to bulk-access-control, BulkAccessControlScriptConfiguration.isAllowedToExecute collected the target uuids only from parameters named exactly -u, and then required isAdmin(context, dso) for each of them. Commons CLI's DefaultParser, which the script uses to parse the same arguments, also accepts --uuid <id>, --uuid=<id>, -u<id> and unambiguous long-option prefixes like --uu <id>. With any of those forms the uuid list in the check was empty, so the method returned true. The REST runnable does not check again (BulkAccessControl.isAuthorized returns true, because the REST layer has already checked). Starting a process only requires hasAuthority('AUTHENTICATED') (ScriptProcessesController, ScriptRestRepository). As a result, any logged-in user, not only community, collection or item admins, could change the resource policies of any community, collection or item in the repository. The CLI was not affected: BulkAccessControlCli.isAuthorized builds the parameters from the parsed command line, so they always reach the check as -u.
Before the fix, POST /api/system/scripts/bulk-access-control/processes with --uuid <community the user does not administer> returned 202 Accepted. Now it returns 403.
Instructions for Reviewers
List of changes in this PR (only BulkAccessControlScriptConfiguration changes in main code):
The target uuids now come from parsing the parameters with the same DefaultParser and getOptions() that DSpaceRunnable uses. The arguments are built the same way as in ScriptRestRepository.constructArgs. So every spelling the script accepts is checked per object. ScriptRestRepository.startProcess is the only place that starts the REST runnable, and it always calls this check first.
Fail closed: if parameters are given but no target uuid can be resolved (no -u, or arguments that do not parse, for example a missing required option, an unknown option, or -h only), only a site admin passes (isAdmin(context)). Before, any authenticated user passed this case. The broader isAdmin || isComColAdmin || isItemAdmin check is kept only for the parameterless call that lists and shows the script (ScriptServiceImpl.getScriptConfigurations, ScriptRestRepository.findOne). Side effect: a community, collection or item admin who sends only -h through REST now gets 403 instead of a help-only process. The UI does not send -h for this script.
BulkAccessControlScriptIT: 3 new tests, 1 changed.
--uuid <id>, --uu <id> (commons-cli 1.9.0 DefaultParser matches long-option prefixes by default), --uuid=<id> and -u<id> on a community the user does not administer → 403 each.
--uuid <id> on a community the user administers → 202, so the long form still works for legitimate users.
No uuid, sent by a community admin → 403.
Changed: bulkAccessScriptWithoutTargetUUIDParameterTest now runs as a site admin and still expects the 500 with "At least one target uuid must be provided". Before, it ran as a community admin, who is now rejected earlier.
How to test (the dspace module must be in the reactor, otherwise the test environment zip has no config-definition.xml):
With the fix (57f5c2090f): BulkAccessControlIT (CLI) 31/31 and BulkAccessControlScriptIT 10/10 pass. Checkstyle passes on dspace-api and dspace-server-webapp.
Without the fix (BulkAccessControlScriptConfiguration from dtq-dev, new tests): bulkAccessScriptWithAlternativeUuidSpellingsOfNotAdministeredCommunityTest fails with Status expected:<403> but was:<202>, which confirms the bypass. bulkAccessScriptWithoutTargetUUIDParameterWithAdminUserOfCommunityTest fails with expected:<403> but was:<500>. The other 8 tests pass. In an earlier run on 5ec06cc0fc, --uuid=<id> and -u<id> each returned 202 on the old code too.
AI Disclosure (REQUIRED)
Were AI tools used in the creation of this PR?
No (This PR does NOT include AI-generated or AI-assisted content that requires disclosure.)
Yes (This PR includes AI-generated or AI-assisted content that requires disclosure.)
Claude Code wrote the patch and the tests. They were run as described above.
Checklist
My PR is created against the main branch of code (unless it is a backport or is fixing an issue specific to an older branch).
My PR is small in size (e.g. less than 1,000 lines of code, not including comments & integration tests). Exceptions may be made if previously agreed upon.
My PR passes Checkstyle validation based on the Code Style Guide.
My PR includes Javadoc for all new (or modified) public methods and classes. It also includes Javadoc for large or complex private methods.
My PR passes all tests and includes new/updated Unit or Integration Tests based on the Code Testing Guide.
My PR includes details on how to test it. I've provided clear instructions to reviewers on how to successfully test this fix or feature.
If my PR includes new libraries/dependencies (in any pom.xml), I've made sure their licenses align with the DSpace BSD License based on the Licensing of Contributions documentation.
If my PR modifies REST API endpoints, I've opened a separate REST Contract PR related to this change.
If my PR includes new configurations, I've provided basic technical documentation in the PR itself.
…uids however they are spelled
BulkAccessControlScriptConfiguration.isAllowedToExecute only collected target
uuids from parameters named exactly "-u". Commons CLI also accepts "--uuid",
"--uuid=<id>", "-u<id>" and unambiguous prefixes like "--uu", so a community,
collection or item admin could pass any of those forms, skip the per-object
admin check, and change resource policies of objects they do not administer.
- Resolve the target uuids by parsing the parameters with the same
DefaultParser and options the runnable uses. Unparseable arguments fall back
to the general eligibility check; they fail the same way when the script
initializes, so nothing is modified.
- Re-check authorization at runtime in BulkAccessControl (REST runnable) against
the parsed command line, as the CLI subclass already did; the CLI override is
now the inherited default.
- Add REST ITs for "--uuid", "--uuid=<id>" and "-u<id>" on a not administered
community (403) and for "--uuid" on an administered one (202).
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
It changes an authorization check on a script that modifies resource policies, and the fallback for unparseable input needs a human decision before merge.
This PR closes an authorization bypass in the bulk access control script. The REST pre-check only looked at parameters named exactly -u, so forms such as --uuid <id>, --uuid=<id>, -u<id> and --uu <id> skipped the per-object admin check. The fix parses the arguments with the same parser the script uses, so every accepted spelling is checked.
Changes:
The pre-check in BulkAccessControlScriptConfiguration now reads target UUIDs from the parsed command line. If parsing fails, it uses the general admin check, and the script then rejects the same input during initialization.
BulkAccessControl.isAuthorized re-checks permissions at run time against the parsed command line. The command-line subclass's duplicate override was removed, so its behavior is unchanged.
BulkAccessControlScriptIT gains three tests: the long --uuid form and the inline --uuid= and -u forms on a community the user does not administer, plus the long form on an administered community.
…ntrol target uuid resolves
Address review: when parameters are given but no target uuid can be
resolved (no -u, or arguments that do not parse), only a site admin passes
the check now. Before, any community, collection or item admin passed and the
safety relied on the script failing the same parse during initialize. The
broader admin check stays only for the parameterless call used to list and
show the script.
- bulkAccessScriptWithoutTargetUUIDParameterTest runs as a site admin and
still expects the "At least one target uuid" error.
- New: a community admin without a uuid gets 403.
- New: the abbreviated long option "--uu <id>" on a not administered
community gets 403.
… only
The REST runnable is started only by ScriptRestRepository.startProcess,
which always runs BulkAccessControlScriptConfiguration.isAllowedToExecute
on the same arguments that the runnable parses. Now that the pre-check
parses them with the same parser and options, re-checking at run time adds
nothing. Restore BulkAccessControl.isAuthorized and the
BulkAccessControlCli override as they were.
Merge the four tests for alternative uuid spellings (--uuid, --uu,
--uuid=<id>, -u<id>) into one test with the same assertions.
The name suggests a success case, but the test expects 403 for a community admin who sends no target uuid. Rename it so the expected denial is clear, for example to bulkAccessScriptWithoutTargetUUIDParameterForbiddenForCommunityAdminTest.
The pre-check and the script read the uuid parameters differently. Now the check parses them the same way as the script.
flowchart TD
R["REST call<br/>--uuid X"] --> C{"isAllowedToExecute"}
C -->|before| B["sees only -u,<br/>finds no target,<br/>allows"]
B --> S["script reads<br/>--uuid as -u,<br/>changes policies<br/>under X"]
C -->|after| A["parses like the<br/>script, finds X"]
A --> F["403 unless<br/>admin of X"]
Loading
Who
Sends
Before
After
Any logged-in user
--uuid X, --uuid=X, -uX or --uu X
202, policies under X change
403
Same user
-u X
403
403
Admin of community A, via UI
-u A
202
202
Admin of community A
--uuid A
202
202
Admin of community A
no uuid, or only -h
passes the check, then 500 or help
403
"Under X" means the items the caller can find in search. The script looks them up in Solr with the caller's rights.
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
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
dspace-customers issue 1083 - finding V7.
Description
For REST calls to
bulk-access-control,BulkAccessControlScriptConfiguration.isAllowedToExecutecollected the target uuids only from parameters named exactly-u, and then requiredisAdmin(context, dso)for each of them. Commons CLI'sDefaultParser, which the script uses to parse the same arguments, also accepts--uuid <id>,--uuid=<id>,-u<id>and unambiguous long-option prefixes like--uu <id>. With any of those forms the uuid list in the check was empty, so the method returnedtrue. The REST runnable does not check again (BulkAccessControl.isAuthorizedreturnstrue, because the REST layer has already checked). Starting a process only requireshasAuthority('AUTHENTICATED')(ScriptProcessesController,ScriptRestRepository). As a result, any logged-in user, not only community, collection or item admins, could change the resource policies of any community, collection or item in the repository. The CLI was not affected:BulkAccessControlCli.isAuthorizedbuilds the parameters from the parsed command line, so they always reach the check as-u.Before the fix,
POST /api/system/scripts/bulk-access-control/processeswith--uuid <community the user does not administer>returned202 Accepted. Now it returns403.Instructions for Reviewers
List of changes in this PR (only
BulkAccessControlScriptConfigurationchanges in main code):DefaultParserandgetOptions()thatDSpaceRunnableuses. The arguments are built the same way as inScriptRestRepository.constructArgs. So every spelling the script accepts is checked per object.ScriptRestRepository.startProcessis the only place that starts the REST runnable, and it always calls this check first.-u, or arguments that do not parse, for example a missing required option, an unknown option, or-honly), only a site admin passes (isAdmin(context)). Before, any authenticated user passed this case. The broaderisAdmin || isComColAdmin || isItemAdmincheck is kept only for the parameterless call that lists and shows the script (ScriptServiceImpl.getScriptConfigurations,ScriptRestRepository.findOne). Side effect: a community, collection or item admin who sends only-hthrough REST now gets 403 instead of a help-only process. The UI does not send-hfor this script.BulkAccessControlScriptIT: 3 new tests, 1 changed.--uuid <id>,--uu <id>(commons-cli 1.9.0DefaultParsermatches long-option prefixes by default),--uuid=<id>and-u<id>on a community the user does not administer → 403 each.--uuid <id>on a community the user administers → 202, so the long form still works for legitimate users.bulkAccessScriptWithoutTargetUUIDParameterTestnow runs as a site admin and still expects the 500 with "At least one target uuid must be provided". Before, it ran as a community admin, who is now rejected earlier.How to test (the
dspacemodule must be in the reactor, otherwise the test environment zip has noconfig-definition.xml):Results:
57f5c2090f):BulkAccessControlIT(CLI) 31/31 andBulkAccessControlScriptIT10/10 pass. Checkstyle passes ondspace-apianddspace-server-webapp.BulkAccessControlScriptConfigurationfromdtq-dev, new tests):bulkAccessScriptWithAlternativeUuidSpellingsOfNotAdministeredCommunityTestfails withStatus expected:<403> but was:<202>, which confirms the bypass.bulkAccessScriptWithoutTargetUUIDParameterWithAdminUserOfCommunityTestfails withexpected:<403> but was:<500>. The other 8 tests pass. In an earlier run on5ec06cc0fc,--uuid=<id>and-u<id>each returned 202 on the old code too.AI Disclosure (REQUIRED)
Were AI tools used in the creation of this PR?
Claude Code wrote the patch and the tests. They were run as described above.
Checklist
mainbranch of code (unless it is a backport or is fixing an issue specific to an older branch).pom.xml), I've made sure their licenses align with the DSpace BSD License based on the Licensing of Contributions documentation.Written on
dtq-dev(c1b0423240). Cherry-picks cleanly ontocustomer/lindat.