Skip to content

Claims extraction module + trust-mode virtual principal contract #5899

Description

@jonpspri

Parent epic: #5885
Depends on: #5896, #5898, #6272. Blocks: #5900, #5902, #5903. Parallel with: #5897.

What to do

New module (e.g. mcpgateway/utils/trusted_claims.py) implementing configurable claim extraction per #5898 settings and the PRINCIPAL CONTRACT (documented in-module):

  • user_id (required, opaque string, NO UUID heuristic in trust mode)
  • email (optional attribute — audit/observability degrade to the "unknown" sentinel when absent, NEVER a None write: ObservabilityTrace.user_email is nullable (db.py:2894) but AuditTrail.user_id is nullable=False (db.py:6631-6632), so the sentinel string is mandatory on that path)
  • full_name (optional, default None)
  • teams (pinned shape: list of strings OR list of {id,name} — normalize both, matching auth.py:1640-1643/1904-1907 branch behavior)
  • roles (list of role names — RESOLUTION RULE, pinned: role names resolve to permission sets via the server-side roles table ONLY (db.py:1154-1205; reading roles is allowed, it is not email_users); permissions are NEVER embedded in or read from the token; unknown role names are ignored with a warning log (fail-closed); team-scoped role names apply within the teams the token carries)
  • is_admin (bool, default False)
  • auth_provider ("jwt-trust" or issuer)
  • token_use (new value "trusted")

Returns a virtual principal object/dict exposing the same attributes downstream consumers read (.email, .is_admin, .full_name) — precedents: _bootstrap_platform_admin_user (auth.py:1298-1316), _user_from_cached_dict. TDD: failing tests for every mapping incl. custom claim names (Entra "groups", Keycloak "realm_access.roles" style nested paths if supported — decide and document support level).

Amendment (2026-07-29)

Per ultimate-user feedback, the contract gains:

Amendment (2026-07-31) — group→role annotations

Per requirements review, the virtual principal contract must also consume role annotations produced by the group mapping resolver (#5976):

  • resolve_external_groups_to_teams now returns both (team_ids, role_names). When cf_role is set on a mapping row, the associated role name is included in role_names.
  • This module MUST merge role_names from the resolver into the roles list before resolution against the server-side roles table. The merged list is processed identically to inline claim roles (unknown names ignored with a warning, fail-closed).
  • This is the mechanism by which a deployment can grant a2a.invoke (via the developer or viewer role) through Entra group membership alone, without requiring the token to carry a native roles claim. Full design is tracked in [BUG]: /admin/llm/* and /v1/admin/llm/* diverge on CSRF validation immediately after login #5978.

Must NOT do

Do not touch get_current_user yet.

References

  • mcpgateway/auth.py:1298-1316, 1582-1589, 1640-1643, 1904-1907, 2090-2108
  • mcpgateway/middleware/auth_middleware.py:184-189
  • mcpgateway/middleware/observability_middleware.py:138-139
  • mcpgateway/db.py:2894, 6632 (nullability)

Acceptance criteria

  • uv run pytest tests -k "trusted_claims or claims" -q green
  • Contract doc-comment enumerates every field + optionality
  • Nested-claim support level explicitly tested (either works or documented-unsupported with clear error)
  • Resolver-supplied role names are merged with claim roles before server-side resolution; test asserts that a principal with no roles claim but a mapping row with cf_role=developer resolves to the developer permission set.

QA scenarios

  • Happy: full-claim token yields complete principal
  • Failure: missing sub/user_id → extraction error (never defaults to email), missing email → principal with email=None and identity intact
  • Group→role: token with no roles claim + mapping row with cf_role → Layer-2 permission check passes for a2a.invoke

Commit: feat(auth): add configurable claims extraction and virtual principal contract

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestpythonPython / backend development (FastAPI)securityImproves security

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions