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

@ -7,13 +7,20 @@ import "time"
// User is the canonical identity entity — source of truth for all user data.
type User struct {
ID string `yaml:"id" json:"id"`
Username string `yaml:"username" json:"username"`
DisplayName string `yaml:"displayName" json:"displayName"`
Email string `yaml:"email" json:"email"`
Enabled bool `yaml:"enabled" json:"enabled"`
Groups []string `yaml:"groups" json:"groups"`
Roles []string `yaml:"roles" json:"roles"`
ID string `yaml:"id" json:"id"`
Username string `yaml:"username" json:"username"`
DisplayName string `yaml:"displayName" json:"displayName"`
Email string `yaml:"email" json:"email"`
Enabled bool `yaml:"enabled" json:"enabled"`
Groups []string `yaml:"groups" json:"groups"`
Roles []string `yaml:"roles" json:"roles"`
// Tenant is the NetKingdom IAM Profile tenant claim value
// (e.g. "tenant:friendly:binky"), per net-kingdom/canon/standards/
// iam-profile_v0.3.md. Empty means "not yet assigned" -- token
// issuance falls back to the platform default (KEY-WP-0005-T01) rather
// than emitting an empty tenant claim, since the profile requires
// tenant on every token.
Tenant string `yaml:"tenant,omitempty" json:"tenant,omitempty"`
MFAEnrollment *MFAEnrollment `yaml:"mfaEnrollment,omitempty" json:"mfaEnrollment,omitempty"`
LDAPAttributes map[string]string `yaml:"ldapAttributes,omitempty" json:"ldapAttributes,omitempty"`
}