Skip to content

feat: optional authorisation without groups - #116

Merged
uniqueg merged 4 commits into
devfrom
feat/78-claim-authorisation
Nov 7, 2026
Merged

uniqueg merged 4 commits into
devfrom
feat/78-claim-authorisation

Conversation

@uniqueg

@uniqueg uniqueg commented Nov 7, 2026 •

Copy link
Copy Markdown
Member

Summary

This adds opt-in authorisation without groups, with the "configurable claim" policy, for OIDC providers that release no group memberships (e.g. LS Login). It is off by default; with the setting off, behaviour is unchanged.

New settings (tesk.api.authorisation.claim.*):

TESK_API_AUTHORISATION_CLAIM_ENABLED   true/false (default false)
TESK_API_AUTHORISATION_CLAIM_NAME      claim in the user info, e.g. eduperson_assurance
TESK_API_AUTHORISATION_CLAIM_VALUES    comma-separated; a user needs at least one

The Helm chart of TESK doesn't set these yet; that follows separately.

With the setting on:

  • Claim users have one of the values in the claim; the claim can be a string or a list, and values are compared exactly. They may create tasks without a group; a GROUP_NAME tag is refused unless the group rules allow it. They may list, get and cancel their own tasks without a group, and no others. Their list selector is creator-user-id=<user>,!creator-group-name.
  • Group rules come first and are unchanged. Super admins list all tasks, including those of claim users: getLabelSelector returns null for admins before the claim branch.
  • Startup check: TESK API fails at startup if the setting is on without a claim name or a non-blank value.
  • Docs:
    • docs/access-control.md gets a new section "Authorisation without groups": settings, rules, security implications, and the listing caveat for members and team admins with the claim.
    • docs/configuration.md and docs/upgrading.md.
    • Mentions in the intros, index.md and the 403 row of api.md.
    • application.properties lists the default.

Closes #78

Verification

  • I wrote the tests first: ClaimAuthorisationTest (unit) and ClaimAuthIT (integration, setting on), plus AuthIT.claimWithoutGroups_settingOff (setting off). With compile stubs, 7 unit and 5 integration tests failed; after the implementation, all pass.
  • Coverage of the tests:
    • setting on and off;
    • claim users, members and super admins, with and without the claim;
    • claim present, absent and with a wrong value;
    • create (no group, a foreign group, the own group), list, get and cancel;
    • configuration errors (no name, no, null or blank values);
    • trimming, non-string claims, and equals/hashCode.
  • After rebasing on dev (with fix: validate paging parameters of ListTasks #115): ./mvnw -B spotless:check checkstyle:check clean verify (Temurin 8) gives BUILD SUCCESS, with 7348 unit tests and 77 integration tests and no failures.
  • I couldn't build the docs locally (no Material theme installed); the Docs job in CI covers them.
  • CI on 4ec32b6: all checks pass (CI passed, Lint, Test, Docs, Smoke test, Scan image, CodeQL, codecov/patch and project).
  • Reviews: two rounds each of adversarial and quality review. Neither found a high finding. The round-1 mediums (tests, dead code, coverage) are fixed; round 2 found only lows.

Declined review findings

  • A team member or team admin who also has the claim doesn't list their own tasks without a team, but can still get and cancel them by ID. Kubernetes label selectors can't express OR, and a second list call would break paging. This is documented in docs/access-control.md.
  • cancelTask's @PreAuthorize lets claim users through, and ownership is checked by the controller's getTask (@PostAuthorize), as it already was for group members. claimUser_cancelOthersTask covers this, and the Javadoc says so.
  • No shared base class for ClaimAuthIT and AuthIT; they keep the existing self-contained style. The stub for a task without a group moved to MockUtil.
  • No integration test for the startup failure; the unit tests cover the IllegalStateException.
  • A user with team memberships but without the base group role who has the claim gets a task labelled with one of their teams, as members do. Elixir always includes the parent group, so this needs an unusual provider.
  • Sourcery: sub values that collide after @ is replaced by _ would share tasks. This mapping predates this PR and applies to group members as well; changing it would orphan the labels of existing tasks, so it needs its own change with a migration path.
  • tags: {"GROUP_NAME": null} passes like no tag; this was already the case for members before, so it is out of scope.

Open questions

None.

🤖 Generated with Claude Code

uniqueg and others added 4 commits November 7, 2026 21:26
Some OIDC providers (e.g. LS Login) give no group memberships, so the
group-based authorisation of TESK API cannot be used with them.

Add tesk.api.authorisation.claim.enabled/name/values (off by default).
When on, users whose user info has one of the values in the claim may
create tasks without a group, and list, get and cancel their own tasks
without a group. Group members, team admins and super admins keep their
rules; super admins still list all tasks (no user-ID selector). The
setting fails at startup without a claim name or a non-blank value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings, rules and security implications in the access control and
configuration docs; an entry in the upgrade notes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Trim the configured claim values; drop the array branch (user info
  comes from JSON: a string or a list).
- List the default of tesk.api.authorisation.claim.enabled in
  application.properties.
- Tests: null and trimmed values, non-string claims, equals/hashCode,
  setting off for get and cancel, cancel without or with a wrong claim,
  admins and members with the claim, creating for the own group.
- Docs: mention the claim in the intros, the 403 of the API and the
  index; document that members and team admins with the claim do not
  list their own tasks without a team.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@uniqueg uniqueg added this to the v1.2.0 milestone Nov 7, 2026
@sourcery-ai

sourcery-ai Bot commented Nov 7, 2026

Copy link
Copy Markdown

Reviewer's Guide

Data structures

Add claim-authorisation state to User and derive the task-list selector for claim-only users while preserving admin and group selectors.

  • src/main/java/uk/ac/ebi/tsc/tesk/config/security/User.java L35-40, L104-110, L133-141 (+9 small edits)

API

Extend the service authorization contracts so claim users can create ungrouped tasks, access their own ungrouped tasks, list tasks, and reach cancellation subject to ownership checks.

  • src/main/java/uk/ac/ebi/tsc/tesk/tes/service/TesService.java L21-28, L66-73 (+2 small edits)

Implementation

Extract the configured claim from user-info responses, validate enabled configuration at startup, and mark users authorised when a string claim value matches exactly.

  • src/main/java/uk/ac/ebi/tsc/tesk/config/security/ElixirPrincipalExtractor.java L27-30, L49-85 (+3 small edits)

Configuration

Introduce claim-authorisation configuration properties and document the default-off setting in application properties.

  • src/main/java/uk/ac/ebi/tsc/tesk/config/security/AuthorisationProperties.java L3-4, L9-10, L38-82
  • src/main/resources/application.properties L48-50

Documentation

Explain claim-based authorisation, its settings and security implications, and update the API, configuration, index and upgrade documentation.

  • docs/access-control.md L5-6, L41, L89-149
  • docs/api.md L135
  • docs/configuration.md L33
  • docs/index.md L18
  • docs/upgrading.md L71-78

Tests

Cover enabled and disabled claim authorisation across user-info extraction, task permissions, configuration errors, selectors, and preservation of group-based rights.

  • src/integration-test/java/uk/ac/ebi/tsc/tesk/AuthIT.java L658-698
  • src/integration-test/java/uk/ac/ebi/tsc/tesk/ClaimAuthIT.java L1-391
  • src/integration-test/java/uk/ac/ebi/tsc/tesk/MockUtil.java L39-60
  • src/test/java/uk/ac/ebi/tsc/tesk/config/security/ClaimAuthorisationTest.java L1-175

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@codecov

codecov Bot commented Nov 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 64.05%. Comparing base (3b7980d) to head (4ec32b6).

Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #116      +/-   ##
==========================================
+ Coverage   63.24%   64.05%   +0.81%     
==========================================
  Files          66       66              
  Lines        2530     2579      +49     
  Branches      262      268       +6     
==========================================
+ Hits         1600     1652      +52     
+ Misses        790      778      -12     
- Partials      140      149       +9     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@uniqueg
uniqueg marked this pull request as ready for review November 7, 2026 20:31

@sourcery-ai sourcery-ai Bot 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.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/main/java/uk/ac/ebi/tsc/tesk/config/security/User.java" line_range="138" />
<code_context>
+            // above, so that they keep seeing all tasks)
+            return Constants.LABEL_USERID_KEY
+                    + "="
+                    + getUsername()
+                    + ",!"
+                    + Constants.LABEL_GROUPNAME_KEY;
</code_context>
<issue_to_address>
**Colliding subjects expose ungrouped tasks**

When two distinct subjects that are both authorised by the claim become equal after `@` is replaced with `_`, `getUsername()` maps both `a@b` and `a_b` to the same label value, so the selector returns both users’ ungrouped tasks and the ownership check also lets either user get or cancel the other’s task.

Use a collision-resistant, Kubernetes-label-safe encoding of the raw subject consistently for task labels, selectors, and ownership checks.
</issue_to_address>

Linked issues

#78 feat: optional authorisation without groups · 3 of 3 met

# Requirement Status Notes
1 Opt-in authorisation without group membership, with the policy decided and documented ✅ Met The configurable-claim policy is off by default, validates its configuration at startup, and is described with its rules and security implications in the access-control docs.
2 Admins can still list all tasks with the setting on ✅ Met The admin branch in User.getLabelSelector() returns null before the claim-user selector; ClaimAuthIT tests listing with and without the claim.
3 Tests cover the setting on and off, for users and admins ✅ Met ClaimAuthIT covers users and admins with the setting on; AuthIT covers users with it off and includes the existing super-admin list test with the default setting off.

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and a matching user-info claim now lets a user without group membership submit tasks, including containers and commands that run on the cluster. Reverting removes that authorization for future requests, but it cannot undo jobs already launched or their side effects.

Blocking findings: src/main/java/uk/ac/ebi/tsc/tesk/config/security/User.java:138


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

@uniqueg
uniqueg merged commit fa3a97b into dev Nov 7, 2026
48 of 52 checks passed
@uniqueg
uniqueg deleted the feat/78-claim-authorisation branch November 7, 2026 20:37
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.

feat: optional authorisation without groups

1 participant