Skip to content

[FEATURE]: External group to team mapping seam + in-core mapping table #5976

Description

@jonpspri

Epic: #5885 | Added: 2026-07-29 per ultimate-user feedback

Depends on: #5895 (Epic 1 gate). Blocks: #5900, #6272.

What

External IdP tokens carry group memberships (e.g., Entra group object ID GUIDs) that never match ContextForge team IDs. This story adds the translation layer so Layer-1 token scoping keeps working unchanged in trust mode:

Group-to-role mapping (BASF requirement)

Entra group membership must drive both Layer-1 (team visibility) and Layer-2 (invocation permission) authorisation decisions without requiring the Entra token to carry ContextForge-native role names. The cf_role column on external_group_mappings enables this:

Deployment contract

For per-agent invocation isolation, agents MUST be registered with visibility = "team" and assigned to a dedicated CF team. visibility = "public" agents are accessible to all authenticated callers regardless of token_teams — the Layer-1 gate does not apply. Deployers who require per-agent isolation (e.g., Agent A accessible only to AgentA-Team members) must set visibility = "team". This constraint must be documented in the trust-mode deployment guide (story #5906 docs sweep).

Must NOT

Acceptance criteria

  • Single alembic head; migration up/down/up clean on SQLite and Postgres, including the new cf_role nullable column.
  • uv run pytest tests -k "group_mapping or external_group" -q green, including fail-closed and CRUD deny-path cases (unauthenticated, wrong team, insufficient permissions).
  • Trust-mode request with mapped Entra group GUIDs yields token_teams = mapped CF team IDs (integration test through the funnel branch; strict-xfail until Trust-mode branch in get_current_user (skip user lookup, keep jti revocation) #5900 lands).
  • AC-visibility-gate: Two integration tests covering agent visibility semantics:
    • Team-visibility isolation: Agent-A (visibility=team, team=CF-Team-A) and Agent-B (visibility=team, team=CF-Team-B). Caller token maps only to CF-Team-A. POST /a2a/agent-a/invoke → 200; POST /a2a/agent-b/invoke → 404 (404-not-403 convention per existing _check_agent_access behaviour to avoid leaking agent existence).
    • Public-visibility baseline: Both agents set visibility=public. Both invocations → 200. Documents the deliberate behaviour that public agents are accessible platform-wide regardless of team membership.
  • AC-e2e-team-isolation: Full end-to-end test (strict-xfail until Trust-mode branch in get_current_user (skip user lookup, keep jti revocation) #5900 lands):
    1. Create CF-Team-A and CF-Team-B; register Agent-A (team=CF-Team-A, visibility=team) and Agent-B (team=CF-Team-B, visibility=team).
    2. Insert external_group_mappings row: Entra-Group-GUID-1 → CF-Team-A; cf_role = developer.
    3. Mint trust-mode JWT with groups=[Entra-Group-GUID-1].
    4. POST /a2a/agent-a/invoke → 200; POST /a2a/agent-b/invoke → 404.
  • AC-e2e-no-mapping: Trust-mode JWT carrying an Entra group GUID with no external_group_mappings row → token_teams = [] → any visibility=team agent → 404; visibility=public agents → 200.

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 requestsecurityImproves security

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions