auto-collect header claims into provider AdditionalClaims
When alpha config injects request/response headers from OIDC claims, users must manually duplicate those claim names into each provider's additionalClaims list or get empty values. This is error-prone and undocumented. collectHeaderClaimsIntoProviders scans InjectRequestHeaders and InjectResponseHeaders for ClaimSource entries, skips built-in session fields (email, groups, etc.), and appends the remainder to every provider's AdditionalClaims list with deduplication. Signed-off-by: June Kim <kimjune01@gmail.com>
This commit is contained in:
parent
65037b086c
commit
7f686206e8
|
|
@ -74,4 +74,56 @@ func (a *AlphaOptions) MergeOptionsWithDefaults(opts *Options) {
|
|||
opts.Server = a.Server
|
||||
opts.MetricsServer = a.MetricsServer
|
||||
opts.Providers = a.Providers
|
||||
|
||||
// Automatically add claims referenced in header injection to each
|
||||
// provider's AdditionalClaims so they are extracted from the ID token.
|
||||
collectHeaderClaimsIntoProviders(opts)
|
||||
}
|
||||
|
||||
// builtinSessionClaims are claims that are always available on the session
|
||||
// without needing to be listed in AdditionalClaims.
|
||||
var builtinSessionClaims = map[string]bool{
|
||||
"access_token": true,
|
||||
"id_token": true,
|
||||
"created_at": true,
|
||||
"expires_on": true,
|
||||
"refresh_token": true,
|
||||
"email": true,
|
||||
"user": true,
|
||||
"groups": true,
|
||||
"preferred_username": true,
|
||||
}
|
||||
|
||||
// collectHeaderClaimsIntoProviders inspects InjectRequestHeaders and
|
||||
// InjectResponseHeaders for ClaimSource entries whose claim is not a
|
||||
// built-in session field and adds them to every provider's
|
||||
// AdditionalClaims list (deduplicated).
|
||||
func collectHeaderClaimsIntoProviders(opts *Options) {
|
||||
needed := map[string]bool{}
|
||||
for _, header := range append(opts.InjectRequestHeaders, opts.InjectResponseHeaders...) {
|
||||
for _, value := range header.Values {
|
||||
if value.ClaimSource != nil && value.ClaimSource.Claim != "" {
|
||||
claim := value.ClaimSource.Claim
|
||||
if !builtinSessionClaims[claim] {
|
||||
needed[claim] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(needed) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for i := range opts.Providers {
|
||||
existing := map[string]bool{}
|
||||
for _, c := range opts.Providers[i].AdditionalClaims {
|
||||
existing[c] = true
|
||||
}
|
||||
for claim := range needed {
|
||||
if !existing[claim] {
|
||||
opts.Providers[i].AdditionalClaims = append(opts.Providers[i].AdditionalClaims, claim)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,190 @@
|
|||
package options
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("AlphaOptions", func() {
|
||||
Describe("collectHeaderClaimsIntoProviders", func() {
|
||||
It("adds non-builtin claims from injectRequestHeaders to provider AdditionalClaims", func() {
|
||||
opts := NewOptions()
|
||||
opts.Providers = Providers{
|
||||
{
|
||||
ID: "test",
|
||||
Type: "oidc",
|
||||
},
|
||||
}
|
||||
opts.InjectRequestHeaders = []Header{
|
||||
{
|
||||
Name: "X-Forwarded-Upn",
|
||||
Values: []HeaderValue{
|
||||
{ClaimSource: &ClaimSource{Claim: "upn"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "X-Forwarded-GivenName",
|
||||
Values: []HeaderValue{
|
||||
{ClaimSource: &ClaimSource{Claim: "given_name"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
collectHeaderClaimsIntoProviders(opts)
|
||||
|
||||
claims := opts.Providers[0].AdditionalClaims
|
||||
sort.Strings(claims)
|
||||
Expect(claims).To(ConsistOf("given_name", "upn"))
|
||||
})
|
||||
|
||||
It("does not duplicate claims already in AdditionalClaims", func() {
|
||||
opts := NewOptions()
|
||||
opts.Providers = Providers{
|
||||
{
|
||||
ID: "test",
|
||||
Type: "oidc",
|
||||
AdditionalClaims: []string{"upn"},
|
||||
},
|
||||
}
|
||||
opts.InjectRequestHeaders = []Header{
|
||||
{
|
||||
Name: "X-Forwarded-Upn",
|
||||
Values: []HeaderValue{
|
||||
{ClaimSource: &ClaimSource{Claim: "upn"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
collectHeaderClaimsIntoProviders(opts)
|
||||
|
||||
Expect(opts.Providers[0].AdditionalClaims).To(Equal([]string{"upn"}))
|
||||
})
|
||||
|
||||
It("skips builtin session claims", func() {
|
||||
opts := NewOptions()
|
||||
opts.Providers = Providers{
|
||||
{
|
||||
ID: "test",
|
||||
Type: "oidc",
|
||||
},
|
||||
}
|
||||
opts.InjectRequestHeaders = []Header{
|
||||
{
|
||||
Name: "X-Email",
|
||||
Values: []HeaderValue{
|
||||
{ClaimSource: &ClaimSource{Claim: "email"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "X-User",
|
||||
Values: []HeaderValue{
|
||||
{ClaimSource: &ClaimSource{Claim: "user"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "X-Groups",
|
||||
Values: []HeaderValue{
|
||||
{ClaimSource: &ClaimSource{Claim: "groups"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "X-Token",
|
||||
Values: []HeaderValue{
|
||||
{ClaimSource: &ClaimSource{Claim: "access_token"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
collectHeaderClaimsIntoProviders(opts)
|
||||
|
||||
Expect(opts.Providers[0].AdditionalClaims).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("also collects claims from injectResponseHeaders", func() {
|
||||
opts := NewOptions()
|
||||
opts.Providers = Providers{
|
||||
{
|
||||
ID: "test",
|
||||
Type: "oidc",
|
||||
},
|
||||
}
|
||||
opts.InjectResponseHeaders = []Header{
|
||||
{
|
||||
Name: "X-Family-Name",
|
||||
Values: []HeaderValue{
|
||||
{ClaimSource: &ClaimSource{Claim: "family_name"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
collectHeaderClaimsIntoProviders(opts)
|
||||
|
||||
Expect(opts.Providers[0].AdditionalClaims).To(ConsistOf("family_name"))
|
||||
})
|
||||
|
||||
It("adds claims to all providers", func() {
|
||||
opts := NewOptions()
|
||||
opts.Providers = Providers{
|
||||
{
|
||||
ID: "provider1",
|
||||
Type: "oidc",
|
||||
},
|
||||
{
|
||||
ID: "provider2",
|
||||
Type: "oidc",
|
||||
},
|
||||
}
|
||||
opts.InjectRequestHeaders = []Header{
|
||||
{
|
||||
Name: "X-Upn",
|
||||
Values: []HeaderValue{
|
||||
{ClaimSource: &ClaimSource{Claim: "upn"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
collectHeaderClaimsIntoProviders(opts)
|
||||
|
||||
Expect(opts.Providers[0].AdditionalClaims).To(ConsistOf("upn"))
|
||||
Expect(opts.Providers[1].AdditionalClaims).To(ConsistOf("upn"))
|
||||
})
|
||||
|
||||
It("ignores headers with only SecretSource values", func() {
|
||||
opts := NewOptions()
|
||||
opts.Providers = Providers{
|
||||
{
|
||||
ID: "test",
|
||||
Type: "oidc",
|
||||
},
|
||||
}
|
||||
opts.InjectRequestHeaders = []Header{
|
||||
{
|
||||
Name: "X-Static",
|
||||
Values: []HeaderValue{
|
||||
{SecretSource: &SecretSource{Value: []byte("static-value")}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
collectHeaderClaimsIntoProviders(opts)
|
||||
|
||||
Expect(opts.Providers[0].AdditionalClaims).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("does nothing when no headers are configured", func() {
|
||||
opts := NewOptions()
|
||||
opts.Providers = Providers{
|
||||
{
|
||||
ID: "test",
|
||||
Type: "oidc",
|
||||
},
|
||||
}
|
||||
|
||||
collectHeaderClaimsIntoProviders(opts)
|
||||
|
||||
Expect(opts.Providers[0].AdditionalClaims).To(BeNil())
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
Reference in New Issue