Finish KEY-WP-0008: registration handoff and client MFA isolation
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 34s
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 34s
Add signed registration/enrollment handoffs, per-request assurance policy with login-session isolation, and /logout. coulomb-social stays AAL1 unless acr_values or another client raises the bar.
This commit is contained in:
parent
fff9e39478
commit
b6af6c5268
22 changed files with 1636 additions and 42 deletions
|
|
@ -38,8 +38,10 @@ type ClientConfig struct {
|
|||
SecretRef string `yaml:"secretRef,omitempty"`
|
||||
ServiceSubject string `yaml:"serviceSubject,omitempty"`
|
||||
Tenant string `yaml:"tenant,omitempty"`
|
||||
Roles []string `yaml:"roles,omitempty"`
|
||||
MFARequired *bool `yaml:"mfaRequired,omitempty"`
|
||||
Roles []string `yaml:"roles,omitempty"`
|
||||
MFARequired *bool `yaml:"mfaRequired,omitempty"`
|
||||
RegistrationURL string `yaml:"registrationUrl,omitempty"`
|
||||
EnrollmentURL string `yaml:"enrollmentUrl,omitempty"`
|
||||
}
|
||||
|
||||
// Load reads and parses the YAML config file at path.
|
||||
|
|
|
|||
|
|
@ -127,6 +127,50 @@ clients:
|
|||
}
|
||||
}
|
||||
|
||||
func TestLoad_ClientMFAAndRegistrationURL(t *testing.T) {
|
||||
keyPath := writeTempFile(t, "placeholder-key")
|
||||
yaml := `
|
||||
issuer: "https://kc.example.com"
|
||||
port: 8080
|
||||
tokenLifetime: "15m"
|
||||
privateKeyPem: "` + keyPath + `"
|
||||
environment: "dev"
|
||||
clients:
|
||||
- clientId: "coulomb-social"
|
||||
displayName: "coulomb.social"
|
||||
redirectUris:
|
||||
- "https://coulomb.social/auth/callback/"
|
||||
clientType: "public"
|
||||
mfaRequired: false
|
||||
registrationUrl: "https://users.example.com/register"
|
||||
`
|
||||
cfgPath := writeTempFile(t, yaml)
|
||||
cfg, err := config.Load(cfgPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Load: unexpected error: %v", err)
|
||||
}
|
||||
if len(cfg.Clients) != 1 {
|
||||
t.Fatalf("clients: got %d", len(cfg.Clients))
|
||||
}
|
||||
c := cfg.Clients[0]
|
||||
if c.MFARequired == nil || *c.MFARequired {
|
||||
t.Fatalf("mfaRequired: want false, got %+v", c.MFARequired)
|
||||
}
|
||||
if c.RegistrationURL != "https://users.example.com/register" {
|
||||
t.Errorf("registrationUrl: got %q", c.RegistrationURL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate_InvalidRegistrationURL(t *testing.T) {
|
||||
keyPath := writeTempFile(t, "key")
|
||||
cfg := validConfig(keyPath)
|
||||
cfg.Clients[0].RegistrationURL = "javascript:alert(1)"
|
||||
errs := config.ValidateConfig(cfg)
|
||||
if !containsErr(errs, "registrationUrl") {
|
||||
t.Errorf("expected registrationUrl error, got %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_PrivacyIDEARequireForAll(t *testing.T) {
|
||||
keyPath := writeTempFile(t, "placeholder-key")
|
||||
yaml := `
|
||||
|
|
|
|||
|
|
@ -63,6 +63,16 @@ func ValidateConfig(cfg *Config) []string {
|
|||
errs = append(errs, prefix+fmt.Sprintf(": redirect_uri %q must not contain wildcards", uri))
|
||||
}
|
||||
}
|
||||
if c.RegistrationURL != "" {
|
||||
if err := validateHandoffURL(c.RegistrationURL); err != nil {
|
||||
errs = append(errs, prefix+": registrationUrl: "+err.Error())
|
||||
}
|
||||
}
|
||||
if c.EnrollmentURL != "" {
|
||||
if err := validateHandoffURL(c.EnrollmentURL); err != nil {
|
||||
errs = append(errs, prefix+": enrollmentUrl: "+err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Private key PEM path must be provided (existence is checked at startup).
|
||||
|
|
@ -81,3 +91,20 @@ func contains(values []string, wanted string) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func validateHandoffURL(raw string) error {
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil || u.Scheme == "" || u.Host == "" {
|
||||
return fmt.Errorf("%q is not an absolute URL", raw)
|
||||
}
|
||||
if u.Scheme != "http" && u.Scheme != "https" {
|
||||
return fmt.Errorf("%q scheme must be http or https", raw)
|
||||
}
|
||||
if u.User != nil {
|
||||
return fmt.Errorf("%q must not contain userinfo", raw)
|
||||
}
|
||||
if strings.ContainsAny(u.Host, "*?") || strings.ContainsAny(u.Path, "*") {
|
||||
return fmt.Errorf("%q must not contain wildcards", raw)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue