OidcPolicy.php
Table of Contents
Classes
- OidcPolicy
- OpenID Connect surface controls (X7 G8, plan §4.6/§4.8; T21.4). Settings that are not
password rules, here because this is the org-baseline-plus-tenant-override surface every
other per-tenant control lives on. They are not all of the same kind as each other, and
which is which is the whole of what [`validate_tenant_override`] and
[`clamp_overrides_to_org`] read, so it is set out rather than inferred. **Ordered** — a
tenant may be stricter than its organization and never more permissive: *
[`Self::sensitive_scopes_enabled`], validated **disable-only** — the mirror image of
`mfa_enforced`, because releasing personal data is the less-restrictive direction, so a
tenant can turn its organization's decision off but never on. * [`Self::saml_idp_enabled`],
validated **disable-only** exactly like [`Self::sensitive_scopes_enabled`] (D-20): a tenant
may turn its organization's `true` off and never its `false` on. * [`Self::ssf_enabled`],
validated **disable-only** the same way (D-45). * [`Self::dynamic_registration`], on the
ladder `disabled` → `initial_access_token` → `anonymous`: a tenant may move down it and
never up. * [`Self::dcr_max_clients`] and [`Self::dcr_unused_client_ttl_days`], on the
ordinary `tenant <= org` rule — with the wrinkle that `0` on the second means *never sweep*,
which is the longest window of all and is handled by [`dcr_ttl_strictness`]. **Not
ordered**, therefore never validated against the baseline and never clamped: *
[`Self::default_locale`]. A language is a presentation preference; there is no sense in
which Italian is stricter than French. * [`Self::dcr_allowed_scopes`],
[`Self::dcr_allowed_redirect_hosts`] and [`Self::external_client_allowed_resources`]. Each
names per-tenant resources — *this* tenant's MCP servers, *this* tenant's callback hosts —
and there is no sense in which one such list is stricter than another. A subset rule would
force an organization to enumerate every tenant's resource servers in its own baseline
before any tenant could name one. The model's rule is "a tenant may only be more
restrictive", which binds every field that *has* a restrictiveness; a field that has none
cannot violate it. One cross-field interlock spans both groups and is checked on the
resolved policy rather than on either input: see [`validate_dcr_policy`].