Skip to content

Add Azure Entra ID scp claim support#2300

Merged
ctron merged 3 commits intoguacsec:mainfrom
mrrajan:auth-scope-changes
Mar 20, 2026
Merged

Add Azure Entra ID scp claim support#2300
ctron merged 3 commits intoguacsec:mainfrom
mrrajan:auth-scope-changes

Conversation

@mrrajan
Copy link
Contributor

@mrrajan mrrajan commented Mar 19, 2026

Added support for Azure Entra ID's scp claim format while maintaining compatibility with the standard scope claim
JIRA: https://redhat.atlassian.net/browse/TC-3762

Summary by Sourcery

Add support for interpreting scopes from both standard OAuth 'scope' and Azure Entra ID 'scp' claims when validating access tokens.

New Features:

  • Support reading Azure Entra ID 'scp' claims in addition to the standard 'scope' claim when mapping permissions from access tokens.

Tests:

  • Add unit tests covering scope extraction from 'scope' only, 'scp' only (single and multiple), and combined 'scope' and 'scp' claims.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Mar 19, 2026

Reviewer's Guide

Adds support for Azure Entra ID's scp claim format for scopes while preserving existing scope claim handling, by introducing a combined scope extraction helper and extending the access token claims model and tests.

Sequence diagram for token conversion with combined scope and scp claims

sequenceDiagram
    actor IdP
    participant AuthClient as AuthenticatorClient
    participant AccessToken as AccessTokenClaims
    participant Validator as ValidatedAccessToken

    IdP->>AccessToken: Issue token with scope and_or_scp
    AccessToken->>AuthClient: convert_token(access_token)
    activate AuthClient

    AuthClient->>AuthClient: extract_scopes(access_token)
    note over AuthClient: Build scopes String from scope and scp

    AuthClient->>AuthClient: map_scopes(scopes, scope_mappings)
    note over AuthClient: Map raw scopes to permissions

    AuthClient->>AuthClient: extend permissions with additional_permissions

    AuthClient->>AuthClient: extract_groups(extended_claims, group_selector)

    AuthClient->>Validator: construct ValidatedAccessToken
    deactivate AuthClient

    Validator-->>IdP: ValidatedAccessToken (permissions, groups)
Loading

Class diagram for updated AccessTokenClaims and AuthenticatorClient

classDiagram
    class AccessTokenClaims {
        String azp
        String sub
        Url iss
        Option~String~ aud
        i64 exp
        i64 iat
        Option~i64~ auth_time
        Value extended_claims
        String scope
        Option~SingleOrMultiple~scp~ scp
    }

    class SingleOrMultiple {
        +SingleOrMultiple~T~
        T Single
        T[] Multiple
    }

    class AuthenticatorClient {
        +convert_token(access_token AccessTokenClaims) ValidatedAccessToken
        -extract_scopes(access_token AccessTokenClaims) String
        -map_scopes(scopes String, scope_mappings ScopeMappings) Vec~String~
        additional_permissions Vec~String~
        scope_mappings ScopeMappings
        group_selector JpQuery
    }

    class ValidatedAccessToken {
        Vec~String~ permissions
        Vec~String~ groups
    }

    class ScopeMappings
    class JpQuery
    class Value
    class Url

    AccessTokenClaims --> SingleOrMultiple : uses
    AuthenticatorClient --> AccessTokenClaims : converts
    AuthenticatorClient --> ValidatedAccessToken : creates
    AuthenticatorClient --> ScopeMappings : uses
    AuthenticatorClient --> JpQuery : uses
    AccessTokenClaims --> Url : uses
    AccessTokenClaims --> Value : uses
Loading

File-Level Changes

Change Details Files
Combine scopes from both standard scope and Azure Entra ID scp claims when computing permissions.
  • Introduce extract_scopes helper on AuthenticatorClient to build a single space-delimited scope string from scope and optional scp claims, handling single and multiple scp values.
  • Update convert_token to call extract_scopes and pass the combined scopes string into map_scopes instead of using access_token.scope directly.
  • Ensure behavior when only scope, only scp, or both are present is well-defined and additive rather than fallback-only.
common/auth/src/authenticator/mod.rs
Extend access token claims model and tests to support Azure Entra ID scp claim variants.
  • Add an optional scp: Option<SingleOrMultiple<String>> field to AccessTokenClaims with serde defaults and skip-serializing when absent.
  • Import biscuit::SingleOrMultiple in tests to construct scp values representing both single and multiple scopes.
  • Add unit tests covering scope extraction from scope only, scp only (single and multiple), and combined scope + scp scenarios to validate the new extraction logic.
common/auth/src/authenticator/claims.rs
common/auth/src/authenticator/mod.rs

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

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • When concatenating scope and scp in extract_scopes, consider normalizing and de-duplicating individual scopes to avoid mapping the same permission multiple times if both claims contain overlapping values.
  • extract_scopes currently allocates intermediate Strings (clones and format!); if this path is performance-sensitive, you could refactor it (and map_scopes) to work on an iterator or Vec<&str>/Vec<String> instead of a single concatenated string.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- When concatenating `scope` and `scp` in `extract_scopes`, consider normalizing and de-duplicating individual scopes to avoid mapping the same permission multiple times if both claims contain overlapping values.
- `extract_scopes` currently allocates intermediate `String`s (clones and `format!`); if this path is performance-sensitive, you could refactor it (and `map_scopes`) to work on an iterator or `Vec<&str>`/`Vec<String>` instead of a single concatenated string.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Contributor

@ctron ctron left a comment

Choose a reason for hiding this comment

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

Thanks for the PR. I think, as discussed the call, we should make this configurable if possible. And I think it should be possible.

Similar to the groups, we could extract this using a JSON path, and then take the values and split by whitespace (like we do now). The default JSON path being $['scope','scp']

@codecov
Copy link

codecov bot commented Mar 19, 2026

Codecov Report

❌ Patch coverage is 77.55102% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.72%. Comparing base (16dab1d) to head (9509ae1).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
common/auth/src/authenticator/mod.rs 77.27% 10 Missing ⚠️
common/auth/src/authenticator/config.rs 80.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2300      +/-   ##
==========================================
+ Coverage   67.66%   67.72%   +0.06%     
==========================================
  Files         433      433              
  Lines       24851    24875      +24     
  Branches    24851    24875      +24     
==========================================
+ Hits        16815    16847      +32     
+ Misses       7114     7101      -13     
- Partials      922      927       +5     

☔ View full report in Codecov by Sentry.
📢 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.

@mrrajan
Copy link
Contributor Author

mrrajan commented Mar 19, 2026

Thanks @ctron I have made the changes, Please let me know.

@mrrajan mrrajan requested a review from ctron March 19, 2026 13:26
@mrrajan mrrajan force-pushed the auth-scope-changes branch 2 times, most recently from 42d4fff to b60c86c Compare March 19, 2026 14:54
mrrajan added 2 commits March 20, 2026 12:43
- Remove dedicated `scope` field from AccessTokenClaims so it falls
  into `extended_claims` via serde(flatten), making it visible to the
  JSON-path selector in extract_scopes
- Move DEFAULT_SCOPE_SELECTOR constant to config.rs where it belongs
  alongside the config struct that owns the field
- Change scope_selector from Option<String> to String with a serde
  default, avoiding the unwrap-or dance at runtime
- Extract scopes directly from extended_claims instead of round-tripping
  through serde_json::to_value on the full token
- Return Vec<String> from extract_scopes instead of a joined String to
  avoid split→join→split round-trips through map_scopes
- Unify map_scopes and map_groups behind a private map_items helper to
  eliminate duplicated lookup logic and align their signatures

Made-with: Cursor
Signed-off-by: mrrajan <86094767+mrrajan@users.noreply.github.com.>
@mrrajan mrrajan force-pushed the auth-scope-changes branch from b60c86c to 9509ae1 Compare March 20, 2026 07:38
@mrrajan
Copy link
Contributor Author

mrrajan commented Mar 20, 2026

@ctron Thanks for the review. I have addressed the requested changes. And refactored the map_scope and map_group functions into a single map_items function. Let me know your thoughts.

@mrrajan mrrajan requested a review from ctron March 20, 2026 09:11
Copy link
Contributor

@ctron ctron left a comment

Choose a reason for hiding this comment

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

Yay, thank you! ❤️

@ctron ctron added this pull request to the merge queue Mar 20, 2026
Merged via the queue into guacsec:main with commit fc8d690 Mar 20, 2026
7 of 8 checks passed
@github-project-automation github-project-automation bot moved this to Done in Trustify Mar 20, 2026
@mrrajan mrrajan added the backport release/0.4.z Backport (0.4.z) label Mar 20, 2026
@ctron
Copy link
Contributor

ctron commented Mar 20, 2026

/backport

@trustify-ci-bot
Copy link

Backport failed for release/0.4.z, because it was unable to cherry-pick the commit(s).

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin release/0.4.z
git worktree add -d .worktree/backport-2300-to-release/0.4.z origin/release/0.4.z
cd .worktree/backport-2300-to-release/0.4.z
git switch --create backport-2300-to-release/0.4.z
git cherry-pick -x 9fa8f3bcf0ea39409282288f68a2e566569c7fbe 88f05d7f2976d37ef144fb042b156de8ef3d154d fc8d690c771fc79019ac3f90c376b0a021a7e664

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

Labels

backport release/0.4.z Backport (0.4.z)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants