fix(auth_v2): gate OIDC login roles through the provider allowlist

The browser OIDC login/callback path accepted IdP-asserted roles straight into
the session, so a malicious or misconfigured IdP could assert platform_admin
over SSO and have it land in the Principal - the same escalation the bearer
token path already closes. The callback now runs the mapped claims through the
shared _apply_role_policy with the matched provider config before minting the
session, so roles outside allowed_roles are dropped and platform roles require
allow_platform_roles. With the defaults (empty allowlist, platform off) no
IdP-asserted role survives.
This commit is contained in:
Yassin Kortam 2026-06-10 19:52:15 -07:00
parent 341e75ab45
commit fc6d51cfc0

View file

@ -36,7 +36,7 @@ def _mapped_claims(userinfo: Dict[str, Any]) -> Dict[str, Any]:
def build_oidc_router(auth: AuthSecurity) -> APIRouter:
session = auth.config.session
issuers = {_provider_key(p): p.issuer for p in auth.config.oidc_providers}
providers = {_provider_key(p): p for p in auth.config.oidc_providers}
oauth = OAuth()
for provider in auth.config.oidc_providers:
oauth.register(
@ -118,17 +118,22 @@ def build_oidc_router(auth: AuthSecurity) -> APIRouter:
userinfo = await client.parse_id_token(token, nonce=txn.get("nonce"))
else:
userinfo = await client.userinfo(token=token)
from ..authenticators import _apply_role_policy
info = dict(userinfo)
provider_config = providers[provider]
store = cast(ProvisioningStore, auth.resolver)
await store.upsert_user(_user_from_userinfo(info))
claims = _mapped_claims(info)
_apply_role_policy(claims, provider_config)
session_id = auth.session_store.create_session(
{
"method": "oidc",
"subject": info.get("sub"),
"issuer": info.get("iss") or issuers.get(provider),
"claims": _mapped_claims(info),
"issuer": info.get("iss") or provider_config.issuer,
"claims": claims,
}
)
target = safe_relay_state(txn.get("relay"), session.default_redirect_path)