mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-14 04:07:46 +00:00
264 lines
7.5 KiB
Go
264 lines
7.5 KiB
Go
package traefikoidc
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"net/http"
|
|
"testing"
|
|
"text/template"
|
|
)
|
|
|
|
// TestTraefikConfigurationParsing tests various ways Traefik might pass configuration
|
|
// to the plugin, specifically focusing on the headers field
|
|
func TestTraefikConfigurationParsing(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
config *Config
|
|
expectError bool
|
|
description string
|
|
}{
|
|
{
|
|
name: "valid configuration with templated headers",
|
|
config: &Config{
|
|
ProviderURL: "https://accounts.google.com",
|
|
ClientID: "test-client",
|
|
ClientSecret: "test-secret",
|
|
SessionEncryptionKey: "test-encryption-key-32-bytes-long",
|
|
CallbackURL: "/oauth2/callback",
|
|
Headers: []TemplatedHeader{
|
|
{Name: "Authorization", Value: "Bearer {{.AccessToken}}"},
|
|
},
|
|
},
|
|
expectError: false,
|
|
description: "Standard configuration should work",
|
|
},
|
|
{
|
|
name: "configuration with multiple headers",
|
|
config: &Config{
|
|
ProviderURL: "https://accounts.google.com",
|
|
ClientID: "test-client",
|
|
ClientSecret: "test-secret",
|
|
SessionEncryptionKey: "test-encryption-key-32-bytes-long",
|
|
CallbackURL: "/oauth2/callback",
|
|
Headers: []TemplatedHeader{
|
|
{Name: "Authorization", Value: "Bearer {{.AccessToken}}"},
|
|
{Name: "X-User-Email", Value: "{{.Claims.email}}"},
|
|
{Name: "X-User-ID", Value: "{{.Claims.sub}}"},
|
|
},
|
|
},
|
|
expectError: false,
|
|
description: "Multiple headers should work",
|
|
},
|
|
{
|
|
name: "empty headers configuration",
|
|
config: &Config{
|
|
ProviderURL: "https://accounts.google.com",
|
|
ClientID: "test-client",
|
|
ClientSecret: "test-secret",
|
|
SessionEncryptionKey: "test-encryption-key-32-bytes-long",
|
|
CallbackURL: "/oauth2/callback",
|
|
Headers: []TemplatedHeader{},
|
|
},
|
|
expectError: false,
|
|
description: "Empty headers should not cause issues",
|
|
},
|
|
{
|
|
name: "nil headers configuration",
|
|
config: &Config{
|
|
ProviderURL: "https://accounts.google.com",
|
|
ClientID: "test-client",
|
|
ClientSecret: "test-secret",
|
|
SessionEncryptionKey: "test-encryption-key-32-bytes-long",
|
|
CallbackURL: "/oauth2/callback",
|
|
Headers: nil,
|
|
},
|
|
expectError: false,
|
|
description: "Nil headers should be handled gracefully",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
// Create a simple next handler
|
|
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
|
|
// Try to create the middleware
|
|
ctx := context.Background()
|
|
handler, err := New(ctx, next, tc.config, "test-middleware")
|
|
|
|
if tc.expectError {
|
|
if err == nil {
|
|
t.Errorf("Expected error for %s, but got none", tc.description)
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Errorf("Unexpected error for %s: %v", tc.description, err)
|
|
} else {
|
|
// Verify that the middleware was created successfully
|
|
middleware, ok := handler.(*TraefikOidc)
|
|
if !ok {
|
|
t.Fatalf("Handler is not of type *TraefikOidc")
|
|
}
|
|
|
|
// Check that templates were parsed correctly
|
|
if len(tc.config.Headers) > 0 {
|
|
if len(middleware.headerTemplates) != len(tc.config.Headers) {
|
|
t.Errorf("Expected %d templates, got %d",
|
|
len(tc.config.Headers), len(middleware.headerTemplates))
|
|
}
|
|
|
|
// Verify each template can be executed
|
|
for headerName, tmpl := range middleware.headerTemplates {
|
|
testData := map[string]interface{}{
|
|
"AccessToken": "test-token",
|
|
"Claims": map[string]interface{}{
|
|
"email": "[email protected]",
|
|
"sub": "user123",
|
|
},
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := tmpl.Execute(&buf, testData); err != nil {
|
|
t.Errorf("Failed to execute template for header %s: %v",
|
|
headerName, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestTemplateParsingDuringInitialization specifically tests template parsing
|
|
// during middleware initialization to catch any issues that might occur
|
|
func TestTemplateParsingDuringInitialization(t *testing.T) {
|
|
// Test various template expressions that might cause issues
|
|
templateTests := []struct {
|
|
name string
|
|
templateValue string
|
|
shouldFail bool
|
|
}{
|
|
{
|
|
name: "simple access token",
|
|
templateValue: "{{.AccessToken}}",
|
|
shouldFail: false,
|
|
},
|
|
{
|
|
name: "bearer token format",
|
|
templateValue: "Bearer {{.AccessToken}}",
|
|
shouldFail: false,
|
|
},
|
|
{
|
|
name: "nested claim access",
|
|
templateValue: "{{.Claims.email}}",
|
|
shouldFail: false,
|
|
},
|
|
{
|
|
name: "multiple template expressions",
|
|
templateValue: "User: {{.Claims.email}}, Token: {{.AccessToken}}",
|
|
shouldFail: false,
|
|
},
|
|
{
|
|
name: "invalid template syntax",
|
|
templateValue: "{{.AccessToken",
|
|
shouldFail: true,
|
|
},
|
|
{
|
|
name: "empty template",
|
|
templateValue: "",
|
|
shouldFail: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range templateTests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Test template parsing directly
|
|
tmpl := template.New("test")
|
|
_, err := tmpl.Parse(tt.templateValue)
|
|
|
|
if tt.shouldFail {
|
|
if err == nil {
|
|
t.Errorf("Expected template parsing to fail for %q", tt.templateValue)
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Errorf("Template parsing failed for %q: %v", tt.templateValue, err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIssue55ReproductionAttempt attempts to reproduce the exact scenario
|
|
// from GitHub issue #55 where the error occurs during configuration
|
|
func TestIssue55ReproductionAttempt(t *testing.T) {
|
|
// Create a configuration exactly as reported by the user
|
|
config := &Config{
|
|
ProviderURL: "https://accounts.google.com",
|
|
ClientID: "test-client-id",
|
|
ClientSecret: "test-client-secret",
|
|
SessionEncryptionKey: "test-session-encryption-key-32-bytes-long",
|
|
CallbackURL: "/oauth2/callback",
|
|
LogoutURL: "/oauth2/logout",
|
|
LogLevel: "debug",
|
|
Scopes: []string{"openid", "profile", "email"},
|
|
Headers: []TemplatedHeader{
|
|
{
|
|
Name: "Authorization",
|
|
Value: "Bearer {{.AccessToken}}",
|
|
},
|
|
},
|
|
}
|
|
|
|
// Create a mock HTTP handler
|
|
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
|
|
// Try to initialize the middleware
|
|
ctx := context.Background()
|
|
handler, err := New(ctx, next, config, "test-oidc")
|
|
|
|
if err != nil {
|
|
t.Fatalf("Failed to create middleware: %v", err)
|
|
}
|
|
|
|
// Verify the middleware was created correctly
|
|
middleware, ok := handler.(*TraefikOidc)
|
|
if !ok {
|
|
t.Fatalf("Handler is not of type *TraefikOidc")
|
|
}
|
|
|
|
// Check that the header template was parsed
|
|
if len(middleware.headerTemplates) != 1 {
|
|
t.Errorf("Expected 1 header template, got %d", len(middleware.headerTemplates))
|
|
}
|
|
|
|
// Verify the template exists for the Authorization header
|
|
authTmpl, exists := middleware.headerTemplates["Authorization"]
|
|
if !exists {
|
|
t.Fatal("Authorization template not found")
|
|
}
|
|
|
|
// Test executing the template
|
|
templateData := map[string]interface{}{
|
|
"AccessToken": "test-access-token",
|
|
"Claims": map[string]interface{}{
|
|
"email": "[email protected]",
|
|
},
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := authTmpl.Execute(&buf, templateData); err != nil {
|
|
t.Errorf("Failed to execute Authorization template: %v", err)
|
|
}
|
|
|
|
expectedValue := "Bearer test-access-token"
|
|
if buf.String() != expectedValue {
|
|
t.Errorf("Expected %q, got %q", expectedValue, buf.String())
|
|
}
|
|
}
|