Skip to content

ZCU-DATA/fix: shibboleth special groups lost in short-lived and refreshed tokens - 403 on download (backport #1347) - #1375

Merged
milanmajchrak merged 4 commits into
customer/zcu-datafrom
zcu-data/backport-1347-shib-special-groups
Jul 20, 2026
Merged

milanmajchrak merged 4 commits into
customer/zcu-datafrom
zcu-data/backport-1347-shib-special-groups

Conversation

@milanmajchrak

@milanmajchrak milanmajchrak commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Problem

Refs #900 (the issue is reported on zcu-pub and will be closed by #1374; this PR applies the same backport to zcu-data).

After a Shibboleth login the user is implicitly a member of the special group Authenticated (authentication-shibboleth.default.auth.group, clarin-dspace.cfg). An item restricted to this group is visible, but the bitstream download returns 403.

Cause: ClarinShibAuthentication.getSpecialGroups() reads the special groups from the servlet session attribute shib.authenticated, which is set only during the Shibboleth login request. Every newly minted token recomputes the sg JWT claim (SpecialGroupClaimProvider.getValue()), so on a stateless request:

  • the short-lived token used by the UI for downloads (POST /api/authn/shortlivedtokens) is issued with an empty sg claimGET /api/core/bitstreams/{uuid}/content?authentication-token=… → 403,
  • the refreshed login token (POST /api/authn/login with Bearer) loses the special groups too, so the user loses the group-based access after the first token refresh.

Fix

Backport of #1347 (74f58627, port of ufal#1378, issue ufal#1373) from dtq-dev:

Behavior note: special groups are now echoed from the user context until re-login, so an affiliation revocation at the IdP takes effect at the next login (same semantics as upstream).

TDD

Commit 1 adds ClarinShibbolethSpecialGroupsIT (restricted download via short-lived token + token refresh). Run locally on this branch without the fix — both tests fail with 403:

IT failing without the fix - 403 instead of 200

Commit 2 is the backport — both tests pass:

IT passing with the backported fix

(The images are rendered from the real local mvn verify logs of the zcu-pub twin branch - all files touched by this PR are identical on customer/zcu-pub and customer/zcu-data, verified with git diff. The first commit intentionally fails CI when checked out alone - TDD ordering. Evidence images live on the deletable branch assets/zcu-shibboleth-pr-evidence.)

How to replicate on current zcu-data

  1. Restrict a bitstream's READ policy to the group Authenticated.
  2. Sign in via Shibboleth → the item and the file are visible.
  3. Download the bitstream → 403.

API check: POST /api/authn/shortlivedtokens with the login Bearer token and decode the returned JWT — the sg claim is empty.

Related

milanmajchrak and others added 3 commits July 16, 2026 15:51
… refreshed tokens

Replicates #900: a bitstream restricted to the default
shibboleth group (Authenticated) is readable with the login token, but the
download via a short-lived token returns 403, because the special groups
are recomputed from the (missing) servlet session instead of the user
context when a new token is minted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ted (on token refresh) (ufal#1378) (#1347)

* Issue 1373: obtain special groups from user context when new token is generated (on token refresh)

* resolve Copilot comments

* resolve Copilot Comments: compute special groups only when when user is authenticated

* Remove HttpSession dependency from ClarinShibAuthentication

Use request-scoped attributes for shib.authenticated instead of
HttpSession/JSESSIONID, aligning with upstream ShibAuthentication.
Follow-up to ufal#1373/ufal#1378.

* Guard against null special groups in Context.getSpecialGroups

A special-group UUID may reference a Group that has since been deleted;
GroupService.find returns null in that case. The list was built with an
unconditional add, so it could contain null elements, which caused an NPE
downstream (e.g. SpecialGroupClaimProvider.getValue maps group.getID()
while generating the JWT sg claim on token refresh). Filter nulls once
here so every caller is covered. Follow-up to ufal#1373/ufal#1378.

---------

(cherry picked from commit 4c294b2)

Co-authored-by: Milan Kuchtiak <kuchtiak@ufal.mff.cuni.cz>
(cherry picked from commit 74f5862)
…a raw-type read

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2950d37a-b295-414a-b13e-24b3b7634694

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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 backports a fix for Shibboleth-authenticated users losing “special groups” (e.g., Authenticated) in newly minted JWTs, which caused 403 Forbidden on bitstream downloads when using short-lived tokens or after token refresh.

Changes:

  • Add an integration test ensuring Shibboleth special groups persist into (a) short-lived download tokens and (b) refreshed login tokens.
  • Update ClarinShibAuthentication to prefer special groups restored in Context (from the JWT sg claim) and to track Shibboleth authentication via a request attribute instead of a session attribute.
  • Make Context.getSpecialGroups() resilient to deleted groups by skipping null group lookups.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
dspace-server-webapp/src/test/java/org/dspace/app/rest/security/ClarinShibbolethSpecialGroupsIT.java New IT covering restricted bitstream download via short-lived token and access persistence across token refresh.
dspace-api/src/main/java/org/dspace/core/Context.java Avoid returning null entries in special groups list when referenced groups were deleted.
dspace-api/src/main/java/org/dspace/authenticate/clarin/ClarinShibAuthentication.java Restore special groups from Context first; move shib.authenticated from session to request attribute; avoid null groups and raw empty-list returns.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- reuse AUTHORIZATION_HEADER/AUTHORIZATION_TYPE from AbstractControllerIntegrationTest
- assert the Authorization header and the token field are present before using them

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@milanmajchrak
milanmajchrak merged commit 669ffec into customer/zcu-data Jul 20, 2026
11 checks passed
milanmajchrak pushed a commit that referenced this pull request Sep 7, 2026
…) (#1424)

Replicate the ZCU Shibboleth role->group mapping from the publications
instance (issue #132, branch customer/zcu-pub) onto the data instance so
that collection access rights work for Shibboleth-authenticated users.

After login the `affiliation` header maps users into DSpace groups:
- `member@zcu.cz` -> group MEMBER_ZCU
- no matching affiliation -> default role `nogroup` -> group NOGROUP

The download-403 code fix (special groups on token refresh, PR #1375) is
already present on customer/zcu-data, so this is the only remaining repo
delta; authentication-shibboleth.cfg is now identical to customer/zcu-pub.

NOTE (runtime admin, not shippable): the EPerson groups NOGROUP and
MEMBER_ZCU must exist on the instance, and the target collections/bitstreams
must carry a READ policy for MEMBER_ZCU; also confirm the ZCU IdP emits
`affiliation = member@zcu.cz`.

Co-authored-by: Matus Kasak <matus.kasak@dataquest.sk>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants