feat: implement T01-T04 — Go module, canonical model, LDAP validator, error taxonomy
- T01: Go module (keycape), full directory skeleton, Makefile, CI workflow
- T02: spec/canonical-model.yaml with 6 entities + Go domain types
- T03: spec/ldap-schema.yaml + validator binary with structural/semantic rules
- T04: Error taxonomy — 4 stable error types, JSON format, HTTP helpers
28 tests pass, go vet clean, go build clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 01:27:54 +01:00
|
|
|
// Package domain contains the canonical identity model for KeyCape.
|
|
|
|
|
// This is the source of truth for all user, group, client, and MFA data.
|
|
|
|
|
// All provisioning, tests, and migrations derive from these types.
|
|
|
|
|
package domain
|
|
|
|
|
|
|
|
|
|
import "time"
|
|
|
|
|
|
|
|
|
|
// User is the canonical identity entity — source of truth for all user data.
|
|
|
|
|
type User struct {
|
KEY-WP-0005-T01: IAM Profile core claims for the human PKCE flow
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>
2026-07-24 00:03:31 +02:00
|
|
|
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"`
|
feat: implement T01-T04 — Go module, canonical model, LDAP validator, error taxonomy
- T01: Go module (keycape), full directory skeleton, Makefile, CI workflow
- T02: spec/canonical-model.yaml with 6 entities + Go domain types
- T03: spec/ldap-schema.yaml + validator binary with structural/semantic rules
- T04: Error taxonomy — 4 stable error types, JSON format, HTTP helpers
28 tests pass, go vet clean, go build clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 01:27:54 +01:00
|
|
|
MFAEnrollment *MFAEnrollment `yaml:"mfaEnrollment,omitempty" json:"mfaEnrollment,omitempty"`
|
|
|
|
|
LDAPAttributes map[string]string `yaml:"ldapAttributes,omitempty" json:"ldapAttributes,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Group is a named collection of users.
|
|
|
|
|
type Group struct {
|
|
|
|
|
ID string `yaml:"id" json:"id"`
|
|
|
|
|
Name string `yaml:"name" json:"name"`
|
|
|
|
|
Description string `yaml:"description" json:"description"`
|
|
|
|
|
Members []string `yaml:"members" json:"members"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Role is a named permission set.
|
|
|
|
|
type Role struct {
|
|
|
|
|
ID string `yaml:"id" json:"id"`
|
|
|
|
|
Name string `yaml:"name" json:"name"`
|
|
|
|
|
Description string `yaml:"description" json:"description"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Client is a registered OIDC client (static in v0.1 — no dynamic registration).
|
|
|
|
|
type Client struct {
|
2026-07-27 20:03:07 +02:00
|
|
|
ClientID string `yaml:"clientId" json:"clientId"`
|
|
|
|
|
DisplayName string `yaml:"displayName" json:"displayName"`
|
|
|
|
|
RedirectURIs []string `yaml:"redirectUris" json:"redirectUris"`
|
|
|
|
|
AllowedScopes []string `yaml:"allowedScopes" json:"allowedScopes"`
|
|
|
|
|
GrantTypes []string `yaml:"grantTypes" json:"grantTypes"`
|
|
|
|
|
ClientType string `yaml:"clientType" json:"clientType"` // "confidential" | "public"
|
|
|
|
|
SecretRef string `yaml:"secretRef,omitempty" json:"secretRef,omitempty"`
|
|
|
|
|
ClientSecret string `yaml:"-" json:"-"`
|
2026-09-05 00:41:17 +02:00
|
|
|
Audience string `yaml:"audience,omitempty" json:"audience,omitempty"`
|
2026-07-27 20:03:07 +02:00
|
|
|
ServiceSubject string `yaml:"serviceSubject,omitempty" json:"serviceSubject,omitempty"`
|
|
|
|
|
Tenant string `yaml:"tenant,omitempty" json:"tenant,omitempty"`
|
2026-08-23 13:10:13 +02:00
|
|
|
Roles []string `yaml:"roles,omitempty" json:"roles,omitempty"`
|
|
|
|
|
// TokenLifetime overrides the server default for this confidential client.
|
|
|
|
|
// It is internal runtime policy, not identity data serialized into tokens.
|
|
|
|
|
TokenLifetime time.Duration `yaml:"-" json:"-"`
|
|
|
|
|
MFARequired *bool `yaml:"mfaRequired,omitempty" json:"mfaRequired,omitempty"`
|
|
|
|
|
RegistrationURL string `yaml:"registrationUrl,omitempty" json:"registrationUrl,omitempty"`
|
|
|
|
|
EnrollmentURL string `yaml:"enrollmentUrl,omitempty" json:"enrollmentUrl,omitempty"`
|
feat: implement T01-T04 — Go module, canonical model, LDAP validator, error taxonomy
- T01: Go module (keycape), full directory skeleton, Makefile, CI workflow
- T02: spec/canonical-model.yaml with 6 entities + Go domain types
- T03: spec/ldap-schema.yaml + validator binary with structural/semantic rules
- T04: Error taxonomy — 4 stable error types, JSON format, HTTP helpers
28 tests pass, go vet clean, go build clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 01:27:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Membership links a user to a group.
|
|
|
|
|
type Membership struct {
|
|
|
|
|
UserID string `yaml:"userId" json:"userId"`
|
|
|
|
|
GroupID string `yaml:"groupId" json:"groupId"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MFAEnrollment records that a user has enrolled MFA via privacyIDEA.
|
|
|
|
|
type MFAEnrollment struct {
|
|
|
|
|
UserID string `yaml:"userId" json:"userId"`
|
|
|
|
|
Provider string `yaml:"provider" json:"provider"` // "privacyidea"
|
|
|
|
|
State string `yaml:"state" json:"state"` // "enabled" | "disabled" | "pending"
|
|
|
|
|
EnrolledAt time.Time `yaml:"enrolledAt,omitempty" json:"enrolledAt,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Directory is the full canonical identity directory snapshot.
|
|
|
|
|
// Used for provisioning, validation, and migration operations.
|
|
|
|
|
type Directory struct {
|
|
|
|
|
Users []User `yaml:"users" json:"users"`
|
|
|
|
|
Groups []Group `yaml:"groups" json:"groups"`
|
|
|
|
|
Roles []Role `yaml:"roles" json:"roles"`
|
|
|
|
|
Clients []Client `yaml:"clients" json:"clients"`
|
|
|
|
|
}
|