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
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:
parent
e51a2d74e9
commit
f1f7fa9dd7
8 changed files with 271 additions and 32 deletions
|
|
@ -594,6 +594,66 @@ func TestAuthorizeCallback_MFASubmission_ValidToken_RedirectsWithCode(t *testing
|
|||
if _, ok := h.PendingStates().Load("random-state"); ok {
|
||||
t.Error("expected pending MFA state to be deleted after successful submission")
|
||||
}
|
||||
|
||||
// KEY-WP-0005-T01: the resulting session must record that MFA was
|
||||
// actually verified in this flow, feeding token.go's assurance claim
|
||||
// (aal2, not aal1).
|
||||
code := parsed.Query().Get("code")
|
||||
sess, ok := sessions.Get(code)
|
||||
if !ok {
|
||||
t.Fatal("expected a PKCE session for the issued code")
|
||||
}
|
||||
if !sess.MFAVerified {
|
||||
t.Error("MFAVerified: want true after a successful MFA submission")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorizeCallback_MFANotRequired_SessionRecordsMFAVerifiedFalse(t *testing.T) {
|
||||
auth := &mockAuthProvider{
|
||||
callbackResult: &domain.AuthResult{Username: "alice"},
|
||||
}
|
||||
mfa := &mockMFAProvider{required: false}
|
||||
emitter := &captureEmitter{}
|
||||
|
||||
sessions := oidc.NewSessionStore()
|
||||
h := &oidc.AuthorizeHandler{
|
||||
ClientConfig: testClient(),
|
||||
Auth: auth,
|
||||
MFA: mfa,
|
||||
Sessions: sessions,
|
||||
Emitter: emitter,
|
||||
}
|
||||
|
||||
h.PendingStates().Store("random-state", &oidc.PendingState{
|
||||
ClientID: "test-client",
|
||||
RedirectURI: "https://app.example.com/callback",
|
||||
PKCEChallenge: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
|
||||
PKCEChallengeMethod: "S256",
|
||||
State: "random-state",
|
||||
Scopes: []string{"openid"},
|
||||
ExpiresAt: time.Now().Add(5 * time.Minute),
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet,
|
||||
"/authorize/callback?code=authelia-code&state=random-state", nil)
|
||||
w := httptest.NewRecorder()
|
||||
h.ServeHTTPCallback(w, req)
|
||||
|
||||
if w.Code != http.StatusFound {
|
||||
t.Fatalf("expected 302 redirect, got %d (body: %s)", w.Code, w.Body.String())
|
||||
}
|
||||
loc, err := url.Parse(w.Header().Get("Location"))
|
||||
if err != nil {
|
||||
t.Fatalf("invalid Location header: %v", err)
|
||||
}
|
||||
code := loc.Query().Get("code")
|
||||
sess, ok := sessions.Get(code)
|
||||
if !ok {
|
||||
t.Fatal("expected a PKCE session for the issued code")
|
||||
}
|
||||
if sess.MFAVerified {
|
||||
t.Error("MFAVerified: want false when MFA was never required for this flow")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorizeCallback_MFASubmission_InvalidToken_AuthFailure(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue