mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-14 04:07:46 +00:00
207 lines
6.0 KiB
Go
207 lines
6.0 KiB
Go
package traefikoidc
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
"text/template"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestTemplatedHeaderMissingField tests that accessing non-existent claim fields doesn't cause panics (issue #60)
|
|
func TestTemplatedHeaderMissingField(t *testing.T) {
|
|
t.Run("Missing_Claim_Field_Returns_Empty", func(t *testing.T) {
|
|
// Create a template with the missingkey=zero option
|
|
funcMap := template.FuncMap{
|
|
"default": func(defaultVal interface{}, val interface{}) interface{} {
|
|
if val == nil || val == "" {
|
|
return defaultVal
|
|
}
|
|
return val
|
|
},
|
|
"get": func(m interface{}, key string) interface{} {
|
|
if mapVal, ok := m.(map[string]interface{}); ok {
|
|
if val, exists := mapVal[key]; exists {
|
|
return val
|
|
}
|
|
}
|
|
return ""
|
|
},
|
|
}
|
|
|
|
tmpl := template.New("test").Funcs(funcMap).Option("missingkey=zero")
|
|
parsed, err := tmpl.Parse("{{.Claims.internal_role}}")
|
|
require.NoError(t, err)
|
|
|
|
// Create template data with claims that don't have internal_role
|
|
claims := map[string]interface{}{
|
|
"email": "[email protected]",
|
|
"sub": "1234567890",
|
|
"name": "John Doe",
|
|
// Note: internal_role is NOT present
|
|
}
|
|
|
|
templateData := map[string]interface{}{
|
|
"Claims": claims,
|
|
}
|
|
|
|
// Execute template - should not panic
|
|
var buf bytes.Buffer
|
|
err = parsed.Execute(&buf, templateData)
|
|
require.NoError(t, err, "Template execution should not fail for missing field")
|
|
|
|
// Should return empty string for missing field with missingkey=zero
|
|
assert.Equal(t, "<no value>", buf.String(), "Missing field should return <no value>")
|
|
})
|
|
|
|
t.Run("Safe_Access_Pattern_For_Nested_Fields", func(t *testing.T) {
|
|
funcMap := template.FuncMap{
|
|
"get": func(m interface{}, key string) interface{} {
|
|
if mapVal, ok := m.(map[string]interface{}); ok {
|
|
if val, exists := mapVal[key]; exists {
|
|
return val
|
|
}
|
|
}
|
|
return ""
|
|
},
|
|
}
|
|
|
|
tmpl := template.New("test").Funcs(funcMap)
|
|
// Use 'with' to safely check if field exists before accessing nested properties
|
|
parsed, err := tmpl.Parse(`{{with .Claims.groups}}{{.admin}}{{end}}`)
|
|
require.NoError(t, err)
|
|
|
|
claims := map[string]interface{}{
|
|
"email": "[email protected]",
|
|
// groups field doesn't exist
|
|
}
|
|
|
|
templateData := map[string]interface{}{
|
|
"Claims": claims,
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
err = parsed.Execute(&buf, templateData)
|
|
require.NoError(t, err, "Should handle nested missing fields with 'with' construct")
|
|
assert.Equal(t, "", buf.String(), "Should return empty string when field doesn't exist")
|
|
})
|
|
|
|
t.Run("Using_Get_Function_For_Safe_Access", func(t *testing.T) {
|
|
funcMap := template.FuncMap{
|
|
"get": func(m interface{}, key string) interface{} {
|
|
if mapVal, ok := m.(map[string]interface{}); ok {
|
|
if val, exists := mapVal[key]; exists {
|
|
return val
|
|
}
|
|
}
|
|
return ""
|
|
},
|
|
}
|
|
|
|
tmpl := template.New("test").Funcs(funcMap)
|
|
// Use the get function to safely access the field
|
|
parsed, err := tmpl.Parse(`{{get .Claims "internal_role"}}`)
|
|
require.NoError(t, err)
|
|
|
|
claims := map[string]interface{}{
|
|
"email": "[email protected]",
|
|
// internal_role not present
|
|
}
|
|
|
|
templateData := map[string]interface{}{
|
|
"Claims": claims,
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
err = parsed.Execute(&buf, templateData)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "", buf.String(), "get function should return empty string for missing field")
|
|
})
|
|
|
|
t.Run("Using_Default_Function_For_Fallback", func(t *testing.T) {
|
|
funcMap := template.FuncMap{
|
|
"default": func(defaultVal interface{}, val interface{}) interface{} {
|
|
if val == nil || val == "" || val == "<no value>" {
|
|
return defaultVal
|
|
}
|
|
return val
|
|
},
|
|
}
|
|
|
|
tmpl := template.New("test").Funcs(funcMap).Option("missingkey=zero")
|
|
// Use default to provide a fallback value
|
|
parsed, err := tmpl.Parse(`{{default "guest" .Claims.role}}`)
|
|
require.NoError(t, err)
|
|
|
|
claims := map[string]interface{}{
|
|
"email": "[email protected]",
|
|
// role not present
|
|
}
|
|
|
|
templateData := map[string]interface{}{
|
|
"Claims": claims,
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
err = parsed.Execute(&buf, templateData)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "guest", buf.String(), "default function should provide fallback value")
|
|
})
|
|
|
|
t.Run("Existing_Field_Still_Works", func(t *testing.T) {
|
|
funcMap := template.FuncMap{
|
|
"get": func(m interface{}, key string) interface{} {
|
|
if mapVal, ok := m.(map[string]interface{}); ok {
|
|
if val, exists := mapVal[key]; exists {
|
|
return val
|
|
}
|
|
}
|
|
return ""
|
|
},
|
|
}
|
|
|
|
tmpl := template.New("test").Funcs(funcMap).Option("missingkey=zero")
|
|
parsed, err := tmpl.Parse("{{.Claims.email}}")
|
|
require.NoError(t, err)
|
|
|
|
claims := map[string]interface{}{
|
|
"email": "[email protected]",
|
|
"role": "admin",
|
|
}
|
|
|
|
templateData := map[string]interface{}{
|
|
"Claims": claims,
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
err = parsed.Execute(&buf, templateData)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "[email protected]", buf.String(), "Existing fields should work normally")
|
|
})
|
|
}
|
|
|
|
// TestHeaderTemplateIntegration tests the full integration of templated headers
|
|
func TestHeaderTemplateIntegration(t *testing.T) {
|
|
t.Run("Headers_With_Missing_Claims_Dont_Crash", func(t *testing.T) {
|
|
config := CreateConfig()
|
|
config.ProviderURL = "https://example.com"
|
|
config.ClientID = "test-client"
|
|
config.ClientSecret = "test-secret"
|
|
config.CallbackURL = "/callback"
|
|
config.SessionEncryptionKey = "test-encryption-key-32-characters"
|
|
|
|
// Add headers that reference potentially missing fields
|
|
config.Headers = []TemplatedHeader{
|
|
{Name: "X-User-Email", Value: "{{.Claims.email}}"},
|
|
{Name: "X-User-Role", Value: "{{.Claims.internal_role}}"}, // This field might not exist
|
|
{Name: "X-User-Groups", Value: "{{.Claims.groups}}"}, // This field might not exist
|
|
}
|
|
|
|
// We can't fully initialize the plugin without network access,
|
|
// but we can test that the configuration validates
|
|
err := config.Validate()
|
|
assert.NoError(t, err, "Configuration should be valid even with potentially missing fields")
|
|
})
|
|
}
|