* refactor(identity)!: retire the SecurityRoot policy in favour of endpoint permissions
Completes T040. ADR 0010 already decided SecurityRoot was overloaded and that
endpoints should be authorized by their own permissions; this removes the last
of it.
Roles/Create and Applications/Create carried Policies(SecurityRoot) alongside
an existing RequirePermission, so the policy was redundant there and the line
is simply dropped.
Secrets/Hash carried only the policy. By default SecurityRoot resolved to
RequireAuthenticatedUser(), so any signed-in caller could exercise the password
hasher. It now declares identity/users:create, on the grounds that hashing a
secret is a step in provisioning a credential. This is a tightening: callers
who could hash before and hold no user-creation permission will now be refused.
The policy, its two registration paths and the IdentityPolicyNames constant are
removed. ConfigureAuthorizationOptions stays public and now defaults to a no-op
so hosts that add their own policies are unaffected.
BREAKING CHANGE: the SecurityRoot authorization policy and the
IdentityPolicyNames class are removed. Hosts referencing either should rely on
endpoint permissions, and use DefaultAdminUserFeature for initial bootstrap.
Note: SecurityRoot was the only attachment point for LocalHostPermissionRequirement,
so the localhost permission grant is now inert. The requirement type and the
EnableLocalHostPermissionGrantForSecurityRoot toggles are left in place rather
than deleted, but they no longer gate anything -- see the PR for why that path
was already incoherent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor(identity)!: delete the localhost bootstrap grant and its machinery
Follows the SecurityRoot removal in the previous commit. SecurityRoot was the
only attachment point for LocalHostPermissionRequirement, so the localhost
permission grant is now removed outright rather than left inert:
LocalHostPermissionRequirement, LocalHostRequirement (already dead -- registered
as a handler but consumed by no policy), LocalHostPermissionRequirementOptions
and the two feature toggles all go.
The grant was the weakest of the three bootstrap mechanisms Elsa already has. It
trusted network position, which stops meaning anything behind a reverse proxy,
inside a container, or across a port-forward; it granted unauthenticated access,
so the bootstrap action carried no identity; it covered only localhost, so it
did nothing for a deployed environment; and it could not perform its headline
job, because it granted identity/users:create while POST /identity/users does
not carry the policy that injected it.
The replacements already exist and both work in deployed environments:
UseDefaultAdmin(...) seeds an admin role and user at startup, idempotently, and
UseAdminApiKey(...) accepts an out-of-band key. What the localhost grant did
usefully provide was a hint that something needed configuring, so
IdentityBootstrapDiagnostic replaces that: when the user store is empty and
neither mechanism is configured, startup logs an error naming both, instead of
every endpoint answering 403 with no explanation.
BREAKING CHANGE: LocalHostRequirement, LocalHostPermissionRequirement,
LocalHostPermissionRequirementOptions and the
Enable/DisableLocalHostPermissionGrantForSecurityRoot toggles are removed. Use
UseDefaultAdmin or UseAdminApiKey to bootstrap an instance.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(identity): scope the hash endpoint's documentation to users, and pin the declarations
The hash endpoint's remarks said the callers that need it are "the ones standing up
users and applications", while the endpoint requires identity/users:create alone. An
application provisioner reading that would have been sent into a 403.
The documentation was the part that was wrong. `POST /identity/applications` generates
and hashes the client secret and the API key itself and returns both the plaintext and
the hash, so identity/applications:create is already sufficient to create an application
and the hash endpoint is not on that path at all. Say so, in the endpoint and in the
migration guide, rather than widening a grant nobody needs.
Adds EndpointPermissionTests over the three endpoints that carried the retired
SecurityRoot policy: the two that only lost a redundant policy line must keep the
permission they already declared, and Secrets/Hash must keep the one it gained. The
coverage gate only asks whether an endpoint declares something, so either half could
otherwise change unnoticed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(identity): state the hash endpoint's user-only scope in the summary, and complete the removal list
Moves the user-only scoping into the endpoint's <summary>, which is the part that reaches
the generated API description, rather than leaving it to a paragraph further down. The
remark now says outright that no application-provisioning flow reaches this endpoint and
none is documented to, with the reason: POST /identity/applications generates the client
secret and the API key itself, hashes both, and returns each plaintext alongside its hash.
The migration guide's removal list was partial — it named the requirements and the two
toggles but not the handlers, the options type, the EnableLocalHostPermissionGrant property
on either feature, or the already-obsolete DisableLocalHostRequirement() alias. A reader
hitting a compile error on any of those would not have found it in the guide. It also now
records that ConfigureAuthorizationOptions survives as a no-op default.
Adds the store-failure case to IdentityBootstrapDiagnosticTests: the broad catch is
load-bearing — an unmigrated database must not stop the host from starting — and nothing
was holding it in place. Disposes the test service provider, and folds the repeated arrange
blocks in DefaultAuthenticationFeatureTests into fields.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>