Skip to content

Add missing authorization to workflow step link subresources - #1385

Open
MatusBeke wants to merge 3 commits into
dtq-devfrom
security/workflow-step-authz
Open

Add missing authorization to workflow step link subresources#1385
MatusBeke wants to merge 3 commits into
dtq-devfrom
security/workflow-step-authz

Conversation

@MatusBeke

Copy link
Copy Markdown
Collaborator

Summary

The getStep() link methods on WorkflowItemStepLinkRepository,
PoolTaskStepLinkRepository and ClaimedTaskStepLinkRepository were missing a
@PreAuthorize check — unlike their sibling workflowitem link repositories
(collection / item / submitter) and the parent endpoints.

Because @LinkRest methods are invoked directly on the link repository, the
parent's authorization is never evaluated, so the /step subresource is reachable
without the access control its parent enforces.

Change

Apply the same READ permission check the parent repositories already use, so each
/step subresource enforces the same authorization as its parent:

Repository Check
WorkflowItemStepLinkRepository hasPermission(#workflowItemId, 'WORKFLOWITEM', 'READ')
PoolTaskStepLinkRepository hasPermission(#poolTaskId, 'POOLTASK', 'READ')
ClaimedTaskStepLinkRepository hasPermission(#claimedTaskId, 'CLAIMEDTASK', 'READ')

Origin

Found during internal security audit (2026_07_20_dq).

The getStep() link methods on WorkflowItemStepLinkRepository,
PoolTaskStepLinkRepository and ClaimedTaskStepLinkRepository had no
@PreAuthorize, unlike the sibling workflowitem link repositories
(collection/item/submitter) and the parent findOne endpoints.

Because @LinkRest methods are invoked directly on the link repository,
the parent's authorization is never evaluated, so an unauthenticated
caller reaches the handler and can disclose task existence and workflow
step state (404-vs-401 divergence on a non-existent id; 200 with step
state for a real task).

Apply the same READ permission check the parent repositories already
use, so each /step subresource enforces the same authorization as its
parent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR closes an authorization gap in the REST “link repository” layer by adding missing @PreAuthorize checks to the /step subresources for workflow items and tasks, ensuring these subresources enforce the same READ permissions as their parent endpoints.

Changes:

  • Add @PreAuthorize guard to WorkflowItemStepLinkRepository#getStep() using WORKFLOWITEM READ permission.
  • Add @PreAuthorize guard to PoolTaskStepLinkRepository#getStep() using POOLTASK READ permission.
  • Add @PreAuthorize guard to ClaimedTaskStepLinkRepository#getStep() using CLAIMEDTASK READ permission.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemStepLinkRepository.java Adds missing method-level authorization to protect the /step link for workflow items.
dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskStepLinkRepository.java Adds missing method-level authorization to protect the /step link for pool tasks.
dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskStepLinkRepository.java Adds missing method-level authorization to protect the /step link for claimed tasks.
Comments suppressed due to low confidence (3)

dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowItemStepLinkRepository.java:64

  • This new authorization behavior on the /step link is security-critical, but there doesn’t appear to be any integration test coverage ensuring the endpoint returns 401/403 when accessed without the required WORKFLOWITEM READ permission (and succeeds when authorized). Please add an IT similar to WorkflowItemRestLinkRepositoryIT’s existing submitter/collection/item link tests.
    @PreAuthorize("hasPermission(#workflowItemId, 'WORKFLOWITEM', 'READ')")
    public WorkflowStepRest getStep(@Nullable HttpServletRequest request,
                                    Integer workflowItemId,
                                    @Nullable Pageable optionalPageable,
                                    Projection projection) {

dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskStepLinkRepository.java:52

  • This adds the missing authorization guard on the pooltask /step link, but there are no integration tests asserting unauthorized/forbidden access is blocked for this subresource (only the parent /pooltasks/{id} endpoints appear to be tested). Please add IT coverage for /api/workflow/pooltasks/{id}/step for 401 (anonymous) and 403 (logged-in user without READ permission) cases.
    @PreAuthorize("hasPermission(#poolTaskId, 'POOLTASK', 'READ')")
    public WorkflowStepRest getStep(@Nullable HttpServletRequest request,
                                    Integer poolTaskId,
                                    @Nullable Pageable optionalPageable,
                                    Projection projection) {

dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskStepLinkRepository.java:52

  • This adds the missing authorization guard on the claimedtask /step link, but there are no integration tests asserting unauthorized/forbidden access is blocked for this subresource (only the parent /claimedtasks/{id} endpoints appear to be tested). Please add IT coverage for /api/workflow/claimedtasks/{id}/step for 401 (anonymous) and 403 (logged-in user without READ permission) cases.
    @PreAuthorize("hasPermission(#claimedTaskId, 'CLAIMEDTASK', 'READ')")
    public WorkflowStepRest getStep(@Nullable HttpServletRequest request,
                                    Integer claimedTaskId,
                                    @Nullable Pageable optionalPageable,
                                    Projection projection) {

Cover the /step subresource of pooltasks, claimedtasks and workflowitems:
anonymous -> 401, authenticated user without READ permission -> 403, task
owner / administrator -> 200. Guards against regressing the missing
@PreAuthorize checks (internal security audit 2026_07_20_dq).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@milanmajchrak

Copy link
Copy Markdown
Collaborator

@MatusBeke
Two things:

  1. Add tests. Nothing in the tree requests a /step URL, so CI can't tell whether the annotation fires. Clone TaskRestRepositoriesIT#findOnePoolUnauthorizedTest (:156) and #findOnePoolForbiddenTest (:198) with /step appended — anonymous → 401, non-owner → 403, owner → 200. Same for claimedtasks, plus findOneEmbedStepTest in WorkflowItemRestLinkRepositoryIT.

  • WorkflowRestPermissionEvaluatorPlugin:83 — add if (workflowItem == null) { return true; }; without it a bogus id gives 500 instead of 404 for authenticated non-admins.
  • VersionItemLinkRepository:47 / VersionHistoryLinkRepository:48 — same missing-@PreAuthorize hole.

…ator

WorkflowRestPermissionEvaluatorPlugin dereferenced the result of
workflowItemService.find() without a null check, so an unknown id from an
authenticated non-admin threw a NullPointerException (HTTP 500) instead of
404. The sibling PoolTask/ClaimedTask evaluators already guard this case;
mirror them by returning true for a null item so the handler can produce the
proper 404. Now reachable via the newly authorized /step subresource.

Add a regression test asserting an unknown workflowitem /step id yields 404.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@MatusBeke

Copy link
Copy Markdown
Collaborator Author

Correction on my point 3 above — I overstated it. Re-reading ItemConverter#getPermissionFilteredMetadata, it only strips hidden metadata fields (metadataExposureService.isHidden) and handles withdrawn/tombstone items; it does not perform an object-level READ authorization check. So for a restricted (non-withdrawn) item, its non-hidden metadata would still be returned to an anonymous/unauthorized caller via versions/{id}/item — i.e. VersionItemLinkRepository (and likely VersionHistoryLinkRepository) is a real, lower-severity metadata IDOR, not a clean non-issue as I implied.

It's still a different severity/shape from the /step leak and needs its own read-IDOR handling + owner-vs-anonymous field-set-diff test, so I'll keep it out of this PR and open a dedicated follow-up rather than expanding scope here. Thanks for pushing on it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants