OidcPolicy
in package
implements
JsonSerializable
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`].
Table of Contents
Interfaces
- JsonSerializable
Properties
- $cimd : CimdPolicy|null
- $dcrAllowedRedirectHosts : array<string|int, mixed>|null
- $dcrAllowedScopes : array<string|int, mixed>|null
- $dcrMaxClients : int|null
- $dcrUnusedClientTtlDays : int|null
- $defaultLocale : string|null
- $dynamicRegistration : string|null
- $externalClientAllowedResources : array<string|int, mixed>|null
- $samlIdpEnabled : bool|null
- $sensitiveScopesEnabled : bool
- $ssfEnabled : bool|null
- $ssfInactiveReason : string|null
Methods
- __construct() : mixed
- Constructs a OidcPolicy.
- fromArray() : self
- Rebuilds a OidcPolicy from one decoded JSON object.
- jsonSerialize() : array<string, mixed>
- Renders this object for `json_encode()`.
- toArray() : array<string, mixed>
- Renders this object back to its wire form.
Properties
$cimd read-only
public
CimdPolicy|null
$cimd
= null
$dcrAllowedRedirectHosts read-only
public
array<string|int, mixed>|null
$dcrAllowedRedirectHosts
= null
$dcrAllowedScopes read-only
public
array<string|int, mixed>|null
$dcrAllowedScopes
= null
$dcrMaxClients read-only
public
int|null
$dcrMaxClients
= null
$dcrUnusedClientTtlDays read-only
public
int|null
$dcrUnusedClientTtlDays
= null
$defaultLocale read-only
public
string|null
$defaultLocale
= null
$dynamicRegistration read-only
public
string|null
$dynamicRegistration
= null
$externalClientAllowedResources read-only
public
array<string|int, mixed>|null
$externalClientAllowedResources
= null
$samlIdpEnabled read-only
public
bool|null
$samlIdpEnabled
= null
$sensitiveScopesEnabled read-only
public
bool
$sensitiveScopesEnabled
$ssfEnabled read-only
public
bool|null
$ssfEnabled
= null
$ssfInactiveReason read-only
public
string|null
$ssfInactiveReason
= null
Methods
__construct()
Constructs a OidcPolicy.
public
__construct(bool $sensitiveScopesEnabled[, CimdPolicy|null $cimd = null ][, array<int, string>|null $dcrAllowedRedirectHosts = null ][, array<int, string>|null $dcrAllowedScopes = null ][, int|null $dcrMaxClients = null ][, int|null $dcrUnusedClientTtlDays = null ][, string|null $defaultLocale = null ][, string|null $dynamicRegistration = null ][, array<int, string>|null $externalClientAllowedResources = null ][, bool|null $samlIdpEnabled = null ][, bool|null $ssfEnabled = null ][, string|null $ssfInactiveReason = null ]) : mixed
Parameters
- $sensitiveScopesEnabled : bool
-
Whether
addressandphonemay be registered on a client, requested at the authorization endpoint, and released at UserInfo (X7 G8). Off unless an organization turns it on. The two scopes release a postal address and a telephone number — categories of personal data AXIAM has no other use for — so the deployment that has never thought about them releases nothing, and the operator who has thought about them says so once, at the organization level, where the lawful basis for holding the data was decided. The switch is a capability, not a grant: with it on, a client still has to register the scope, the request still has to ask for it, and the user still has to have consented. It is the first of four gates, and it is the only one an operator can close for everybody at once. - $cimd : CimdPolicy|null = null
-
T21.5 — whether a URL-shaped
client_idis resolved by fetching the document it names, and on what terms. See [CimdPolicy]; off unless somebody turns it on (I1). Nested, and therefore inherited or overridden whole: the fields are terms of one decision, and a half-merged posture is one neither the organization nor the tenant wrote. (optional) - $dcrAllowedRedirectHosts : array<int, string>|null = null
-
T21.4 — hosts a self-registered client's
redirect_urismay point at, as globs (*.example.com, or*for any). The loopback hosts (127.0.0.1,[::1],localhost) are always allowed whatever this says, because RFC 8252 §7.3 is how every desktop MCP client receives its callback and a tenant that forbade them would have turned registration on for nobody. (optional) - $dcrAllowedScopes : array<int, string>|null = null
-
T21.4 — the scopes a self-registered client may ask for. A
scopea registration names that is not on this list isinvalid_client_metadata; an empty list means a self-registered client gets no scopes at all, which is the honest default for a tenant that has turned registration on without deciding what it grants. May not containaddressorphone— see this module's [sensitive_scope_in_dcr_list]. (optional) - $dcrMaxClients : int|null = null
-
T21.4 — how many externally registered clients this tenant may hold. See [
DEFAULT_DCR_MAX_CLIENTS]. Counted once per mechanism, against the same number (T21.8):managed_by: dcrrows andmanaged_by: cimdrows each have this many. So a tenant running both cannot have shadow rows materialised from documents exhaust the allowance for self-registration, or the reverse. The CIMD count is checked before* the document is fetched, so a tenant at its ceiling is not an outbound amplifier either. It keeps itsdcr_name because dynamic registration defined it, on the same precedent as [Self::dcr_allowed_scopes]. (optional) - $dcrUnusedClientTtlDays : int|null = null
-
T21.4 — how long an externally registered client survives without being used. See [
DEFAULT_DCR_UNUSED_CLIENT_TTL_DAYS].0disables the sweep for this tenant, which an operator who prunes out of band may legitimately want. Two sweeps read it, over different clocks (T21.8). Amanaged_by: dcrrow is measured from its last authorization, falling back to when it was registered. Amanaged_by: cimdrow is measured from the last time its document was presented, which every authorize, token and PAR request moves — so a document in daily use is never swept however old its registration is, and one nobody has presented since the window is, and re-materialises on the next request if it is still published. Like the ceiling, it keeps itsdcr_name. (optional) - $defaultLocale : string|null = null
-
The BCP 47 tag the sign-in page falls back to when the relying party's
ui_localesselects nothing (W5's chain, plan §4.6).Nonemeans "no tenant preference", which lands on the deployment default (en) — the behaviour every deployment had before this field existed. A tag this build does not ship also lands there: the parse is exact rather than a language lookup, so a storedfr-CAreads as "somebody wrote something this binary does not ship" rather than as a guess at French. Stored as a string rather than as theLocaleenum because that enum lives inaxiam-oauth2, four layers above this crate, and the crate layering points inward. (optional) - $dynamicRegistration : string|null = null
-
T21.4 — whether a client may register itself (RFC 7591), and on what terms.
disabledunless somebody says otherwise (I1). (optional) - $externalClientAllowedResources : array<int, string>|null = null
-
D3 — the audiences an externally registered client may address. The single most important field on this policy, and the reason the settings handler refuses
dynamic_registration: anonymouswhile it is empty. A client an unrelated party registered cannot declare its ownallowed_resources; it inherits this list verbatim, so what a stranger can mint a token for* is a decision the tenant took in advance rather than one the registration request makes. Empty means an externally registered client can obtain only today'saxiam:usertokens — which AXIAM's own APIs accept. That is why the interlock exists: the empty list is not a safe default for an open registration endpoint, it is the most dangerous one. Shared with T5 (CIMD), which inherits the same list for the same reason. (optional) - $samlIdpEnabled : bool|null = null
-
G-2 / D-20 — whether this tenant may act as a SAML 2.0 identity provider: publish IdP metadata and accept
AuthnRequests on/saml/v2/{tenant}/{metadata,sso,slo}. Off unless an organization turns it on. A SAML IdP issues assertions that other systems accept as proof of identity, so a deployment that has never decided to be one issues none, and the three endpoints answer404as if they did not exist. The switch lives on this policy, beside the other OpenID Provider surface controls, because the SSO endpoint is the same browser login hop and OP session with a different wire format. Disable-only, with the shape of [Self::sensitive_scopes_enabled]: a tenant may turn its organization'strueoff but never itsfalseon, because the decision to issue identity assertions on behalf of the organization's tenants is the organization's. A deployment built without thesamlfeature answers404whatever this says; the setting is a capability, not a grant (each SP must still be registered, andallow_idp_initiatedis its own opt-in). (optional) - $ssfEnabled : bool|null = null
-
G-5 / D-45 — whether the tenant is a Shared Signals Framework transmitter: its
/.well-known/ssf-configurationis served, its receivers can use the stream management API, and events are signed and transmitted on its streams. Defaultfalse. Disable-only, with the shape of [Self::saml_idp_enabled]: sending security events about the organization's users to third parties is the organization's decision. Streams can be registered while it is off; they carry nothing until it is on. (optional) - $ssfInactiveReason : string|null = null
-
Read-only, D-55: set on a settings response when
ssf_enabledis on but the transmitter is inactive anyway, saying why — the deployment holds more than one tenant and serves no per-tenant issuers. Never stored. (optional)
fromArray()
Rebuilds a OidcPolicy from one decoded JSON object.
public
static fromArray(array<string, mixed> $data) : self
Parameters
- $data : array<string, mixed>
-
The raw wire object.
Return values
selfjsonSerialize()
Renders this object for `json_encode()`.
public
jsonSerialize() : array<string, mixed>
Any Sensitive it carries stays WRAPPED here, so a log line or a
json_encode($model) in application code prints [SENSITIVE]. The one place a secret
is revealed is ManagementTransport, on the way to the wire
and nowhere else (§27.5).
Rendering for a log line never fails: an unknown union arm renders its discriminator and
an unrecognised enum value its empty Unknown spelling here, and only self::toArray() -- the request path -- refuses to send it (CONTRACT.md §31.2, §34.2
P12.2).
Return values
array<string, mixed>toArray()
Renders this object back to its wire form.
public
toArray() : array<string, mixed>
§27.4 rule 5: a null property is OMITTED, not emitted as null. On a sparse update those two say opposite things — "leave this alone" versus "set this to nothing" — and only omission means the first.