feat(auth): resolve service-principal group membership via Graph for app-only trust tokens - #6757
Open
jonpspri wants to merge 3 commits into
Open
feat(auth): resolve service-principal group membership via Graph for app-only trust tokens#6757jonpspri wants to merge 3 commits into
jonpspri wants to merge 3 commits into
Conversation
jonpspri
requested review from
Lang-Akshay,
brian-hussey,
crivetimihai,
ja8zyjits and
msureshkumar88
as code owners
September 10, 2026 07:46
jonpspri
added this pull request to stack #6729
September 10, 2026 07:50
jonpspri
force-pushed
the
feat/6756-app-only-graph-lookup
branch
from
September 10, 2026 08:06
b4dc120 to
054ffd8
Compare
jonpspri
force-pushed
the
feat/6756-app-only-graph-lookup
branch
from
September 10, 2026 14:20
054ffd8 to
6c8f36b
Compare
jonpspri
removed this pull request from stack #6729
September 12, 2026 08:50
jonpspri
force-pushed
the
feat/6756-app-only-graph-lookup
branch
from
September 12, 2026 09:07
6c8f36b to
ff2fc67
Compare
jonpspri
added this pull request to stack #6798
September 12, 2026 09:08
jonpspri
force-pushed
the
feat/6756-app-only-graph-lookup
branch
from
September 12, 2026 09:20
ff2fc67 to
5ee3ba0
Compare
jonpspri
force-pushed
the
feat/6756-app-only-graph-lookup
branch
from
September 12, 2026 09:48
5ee3ba0 to
a880b4b
Compare
jonpspri
force-pushed
the
feat/6756-app-only-graph-lookup
branch
2 times, most recently
from
September 12, 2026 16:45
590012e to
899809c
Compare
jonpspri
force-pushed
the
feat/6756-app-only-graph-lookup
branch
from
September 12, 2026 17:20
899809c to
3a6e02d
Compare
jonpspri
force-pushed
the
feat/6756-app-only-graph-lookup
branch
from
September 12, 2026 17:35
3a6e02d to
44f883b
Compare
jonpspri
force-pushed
the
feat/6756-app-only-graph-lookup
branch
from
September 12, 2026 17:54
44f883b to
9a59c6d
Compare
jonpspri
force-pushed
the
feat/6756-app-only-graph-lookup
branch
3 times, most recently
from
September 12, 2026 18:52
dda407f to
6e9e93c
Compare
jonpspri
force-pushed
the
feat/6756-app-only-graph-lookup
branch
from
September 12, 2026 19:12
6e9e93c to
1418007
Compare
jonpspri
force-pushed
the
feat/6756-app-only-graph-lookup
branch
2 times, most recently
from
September 12, 2026 20:26
5fd66d7 to
f92aa3c
Compare
jonpspri
force-pushed
the
feat/6756-app-only-graph-lookup
branch
2 times, most recently
from
September 12, 2026 21:11
64a6ec4 to
e2f1c59
Compare
…app-only trust tokens Signed-off-by: Jonathan Springer <jps@s390x.com>
jonpspri
force-pushed
the
feat/6756-app-only-graph-lookup
branch
from
September 12, 2026 21:54
e2f1c59 to
d06f96c
Compare
…group mappings Replace the disabled group_exists_validator stub (#5976) with the real Microsoft Graph validator (#5977): Entra issuers resolve the SSO provider record for the issuer (same issuer->provider resolution as the trust-mode overage path) and GET /v1.0/groups/<id> with an app-only token. Graph 200 -> valid, 404 -> graph_group_not_found (recorded, not rejected; the resolver fails closed at read time), any other failure -> unknown under the existing warn-and-allow contract. Non-Entra issuers and issuers without app-only credentials keep the disabled-stub posture (valid) with a log line. The Entra hosts set is single-sourced in entra_graph_client and aliased by OAuthManager._ENTRA_HOSTS. The validator seam stays module-level injectable; sync callables remain supported. Signed-off-by: Jonathan Springer <jps@s390x.com>
…ycle; refresh identity-domains trust row Signed-off-by: Jonathan Springer <jps@s390x.com>
This was referenced Sep 13, 2026
Collaborator
Author
|
Requirement note (remediation) — validator wired The group-existence validator is real now ( Also on this PR: a lazy import broke an import cycle that the remediation set introduced ( |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Adds app-only (client-credentials) trust-token group resolution to JWT-trust mode (issue #6756, epic #5885).
App-only Entra tokens carry
idtyp="app", arolesclaim, and nogroupsclaim. Entra emits no overage markers for them, so the #5977 overage dispatch never triggers — and/users/{oid}/getMemberObjectswould fail for a service principal (a service principal is not a user). Underjwt_trust_overage_policy="graph_lookup"this PR resolves the service principal's security groups throughPOST /servicePrincipals/{oid}/getMemberObjectsand maps them throughexternal_group_mappings.Changes:
detect_app_only_token(payload): True whenidtyp == "app"(mcpgateway/utils/trusted_claims.py).EntraGraphClientendpoint selection:/servicePrincipals/{oid}/getMemberObjectsfor app-only tokens;/users/{oid}/getMemberObjectsunchanged for user tokens. Client-credentials grant only; the inbound bearer token is never used. Same oid-keyed Redis cache, TTL bounded byexp.get_current_user):idtyp == "app"ANDgroupsabsent ANDgraph_lookup-> Graph resolve (cached) -> payload copy ->resolve_external_groups_to_teams. Underfail_closed(default) andproceed_without_groupsan app token without groups authenticates withtoken_teams=[]; the app-role path (Admin claim feeds both admin tracks atomically + parity tests #5902) stays intact.make_trusted_test_jwt: newidtypkwarg; default output unchanged.TDD: 7 new tests in
tests/unit/mcpgateway/test_entra_graph_client.py(red first: ImportError on the new symbols). URL capture asserts/servicePrincipals/{id}is called and/users/never is for app tokens;fail_closedpins aroles=["viewer"]app token (authenticated,token_teams=[], viewer role granted); Graph failure undergraph_lookup-> 401; Redis read error -> cache miss -> live call. All 11 pre-existing tests in the file pass unmodified (user-token overage regression).Gate:
make ruff— All checks passed!make test— 23411 passed, 879 skipped, 2 xfailed. (Two earlier full-suite runs each flaked on the pre-existing wall-clock benchmarktest_trust_p99_within_2x_defaultunder load; it passes in isolation and in the final green run. This PR adds no timing tests.)Note: the commit also carries the pre-staged
.secrets.baselineregeneration (line-number bookkeeping for existingis_secret: falseentries), which was already in the index from the stack work.Risk to existing users: none — every new branch is trust-mode + graph_lookup gated; default mode and the user-token overage path verified green in the full suite.
Stack: B.15 of epic #5885 (base: #6755).
Closes #6756