KEY-WP-0005-T01: IAM Profile core claims for the human PKCE flow
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 1m50s

Verified first: grant_types_supported advertises client_credentials in
discovery.go, but token.go only ever accepted authorization_code -- no
service-token issuance path exists at all. Building one from scratch is
materially bigger than extending the existing flow; explicitly not
attempted here, left open in the workplan rather than declared done.

What shipped for the human Authorization Code + PKCE flow:

- domain.User.Tenant (new, omitempty) + token.go's effectiveTenant():
  falls back to tenant:coulomb (this workstation's actual tenant, ADR-0006)
  when unset -- never an empty tenant claim, never a silent reassignment.
- principal_type: "human", unconditional.
- groups/roles promoted from scope-gated to unconditional core claims,
  always [] not null when empty. One pre-existing test asserted the old
  scope-gated groups behavior -- updated to match the new intentional
  behavior, not left failing or reverted.
- assurance built from PKCESession.MFAVerified (new field, threaded
  through completeAuthorization's two call sites in authorize.go) --
  whether MFA was actually verified in this session, not static enrollment
  state. aal2 only when required-and-passed this time, aal1 otherwise.

go build/vet clean, go test ./... green repo-wide. Two new authorize_test.go
cases assert MFAVerified on both paths. tests/profile/profile_test.go's
TestCompleteTokenFlow (the repo's own full HTTP integration test) extended
with real value assertions for all five claims, not just presence checks.

Python conformance tool not run against a live instance (needs the full
Authelia+LLDAP+privacyIDEA stack); TestCompleteTokenFlow's real HTTP round
trip covers the equivalent claim checks instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-07-24 00:03:31 +02:00
parent e51a2d74e9
commit f1f7fa9dd7
8 changed files with 271 additions and 32 deletions

View file

@ -309,7 +309,7 @@ func (h *AuthorizeHandler) ServeHTTPCallback(w http.ResponseWriter, r *http.Requ
}
h.pending.Delete(state)
h.completeAuthorization(w, r, ps, result.Username)
h.completeAuthorization(w, r, ps, result.Username, mfaRequired)
}
func (h *AuthorizeHandler) serveMFASubmission(w http.ResponseWriter, r *http.Request) {
@ -350,10 +350,13 @@ func (h *AuthorizeHandler) serveMFASubmission(w http.ResponseWriter, r *http.Req
}
h.pending.Delete(state)
h.completeAuthorization(w, r, ps, ps.AuthenticatedUser)
// Reached only after ValidateMFAToken succeeded above -- MFA was
// required and passed, unlike the callback path where mfaRequired may
// be false.
h.completeAuthorization(w, r, ps, ps.AuthenticatedUser, true)
}
func (h *AuthorizeHandler) completeAuthorization(w http.ResponseWriter, r *http.Request, ps *PendingState, username string) {
func (h *AuthorizeHandler) completeAuthorization(w http.ResponseWriter, r *http.Request, ps *PendingState, username string, mfaVerified bool) {
// Generate authorization code and store PKCE session.
sess := &PKCESession{
ClientID: ps.ClientID,
@ -365,6 +368,7 @@ func (h *AuthorizeHandler) completeAuthorization(w http.ResponseWriter, r *http.
Username: username,
Scopes: ps.Scopes,
ExpiresAt: time.Now().Add(10 * time.Minute),
MFAVerified: mfaVerified,
}
authCode := h.Sessions.Create(sess)