mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-06-05 22:44:17 +00:00
bde1db1c3b
* Automatic discovery of the scopes. Issue #61 raised very valid concerns about users configuring scopes that are not supported by the provider. This change introduces automatic discovery of supported scopes by fetching the provider's discovery document and filtering out unsupported scopes. Before: User configures: scopes: ["openid", "profile", "email", "offline_access"] Self-hosted GitLab: "The requested scope is invalid, unknown, or malformed" Authentication: ❌ FAILS After: User configures: scopes: ["openid", "profile", "email", "offline_access"] Middleware checks discovery doc → offline_access not supported Automatically filters to: ["openid", "profile", "email"] Authentication: ✅ SUCCEEDS * Resolves issue #74 by enabling user to specify expected audience in the configuration. * Fix flaky tests.
725 lines
23 KiB
Go
725 lines
23 KiB
Go
package traefikoidc
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
// mockLogger for testing
|
|
type mockScopeFilterLogger struct {
|
|
debugMessages []string
|
|
infoMessages []string
|
|
errorMessages []string
|
|
}
|
|
|
|
func (l *mockScopeFilterLogger) Debugf(format string, args ...interface{}) {
|
|
l.debugMessages = append(l.debugMessages, format)
|
|
}
|
|
|
|
func (l *mockScopeFilterLogger) Infof(format string, args ...interface{}) {
|
|
l.infoMessages = append(l.infoMessages, format)
|
|
}
|
|
|
|
func (l *mockScopeFilterLogger) Errorf(format string, args ...interface{}) {
|
|
l.errorMessages = append(l.errorMessages, format)
|
|
}
|
|
|
|
// TestNewScopeFilter tests the ScopeFilter constructor
|
|
func TestNewScopeFilter(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
if filter == nil {
|
|
t.Fatal("Expected ScopeFilter to be created, got nil")
|
|
}
|
|
|
|
// Logger is set correctly (we can't directly compare interface values)
|
|
if filter.logger == nil {
|
|
t.Error("Logger not set in ScopeFilter")
|
|
}
|
|
}
|
|
|
|
// TestFilterSupportedScopes_AllSupported tests when all requested scopes are supported
|
|
func TestFilterSupportedScopes_AllSupported(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
requested := []string{"openid", "profile", "email"}
|
|
supported := []string{"openid", "profile", "email", "address", "phone"}
|
|
providerURL := "https://auth.example.com"
|
|
|
|
result := filter.FilterSupportedScopes(requested, supported, providerURL)
|
|
|
|
expected := []string{"openid", "profile", "email"}
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Errorf("Expected %v, got %v", expected, result)
|
|
}
|
|
|
|
// Should log debug message that all scopes are supported
|
|
if len(logger.debugMessages) == 0 {
|
|
t.Error("Expected debug messages to be logged")
|
|
}
|
|
|
|
// Should not log any info messages (no filtering occurred)
|
|
if len(logger.infoMessages) > 0 {
|
|
t.Error("Expected no info messages when all scopes supported")
|
|
}
|
|
}
|
|
|
|
// TestFilterSupportedScopes_SomeFiltered tests when some scopes need to be filtered
|
|
func TestFilterSupportedScopes_SomeFiltered(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
requested := []string{"openid", "profile", "email", "offline_access", "custom_scope"}
|
|
supported := []string{"openid", "profile", "email"}
|
|
providerURL := "https://gitlab.example.com"
|
|
|
|
result := filter.FilterSupportedScopes(requested, supported, providerURL)
|
|
|
|
expected := []string{"openid", "profile", "email"}
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Errorf("Expected %v, got %v", expected, result)
|
|
}
|
|
|
|
// Verify offline_access and custom_scope were filtered out
|
|
for _, scope := range result {
|
|
if scope == "offline_access" || scope == "custom_scope" {
|
|
t.Errorf("Scope '%s' should have been filtered out", scope)
|
|
}
|
|
}
|
|
|
|
// Should log info message about filtered scopes
|
|
if len(logger.infoMessages) == 0 {
|
|
t.Error("Expected info message about filtered scopes")
|
|
}
|
|
|
|
// Should log debug messages about supported scopes and final result
|
|
if len(logger.debugMessages) < 2 {
|
|
t.Error("Expected debug messages about provider supported scopes and final result")
|
|
}
|
|
}
|
|
|
|
// TestFilterSupportedScopes_AllFiltered tests when all scopes are filtered (fallback to openid)
|
|
func TestFilterSupportedScopes_AllFiltered(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
requested := []string{"custom_scope1", "custom_scope2", "unsupported"}
|
|
supported := []string{"openid", "profile", "email"}
|
|
providerURL := "https://auth.example.com"
|
|
|
|
result := filter.FilterSupportedScopes(requested, supported, providerURL)
|
|
|
|
expected := []string{"openid"}
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Errorf("Expected fallback to %v, got %v", expected, result)
|
|
}
|
|
|
|
// Should log info message about all scopes being filtered (falling back to openid)
|
|
if len(logger.infoMessages) < 2 { // One for filtered scopes, one for fallback
|
|
t.Error("Expected info messages when all scopes filtered")
|
|
}
|
|
|
|
// Should log info message about filtered scopes
|
|
if len(logger.infoMessages) == 0 {
|
|
t.Error("Expected info message about filtered scopes")
|
|
}
|
|
}
|
|
|
|
// TestFilterSupportedScopes_NoSupportedScopes tests fallback behavior when no scopes_supported
|
|
func TestFilterSupportedScopes_NoSupportedScopes(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
requested := []string{"openid", "profile", "email", "offline_access"}
|
|
supported := []string{} // Empty supported list (backward compatibility)
|
|
providerURL := "https://auth.example.com"
|
|
|
|
result := filter.FilterSupportedScopes(requested, supported, providerURL)
|
|
|
|
// Should return all requested scopes unchanged
|
|
if !reflect.DeepEqual(result, requested) {
|
|
t.Errorf("Expected all requested scopes %v, got %v", requested, result)
|
|
}
|
|
|
|
// Should log debug message about no scopes_supported
|
|
if len(logger.debugMessages) == 0 {
|
|
t.Error("Expected debug message about no scopes_supported")
|
|
}
|
|
|
|
// Should not log info messages (backward compatibility mode)
|
|
if len(logger.infoMessages) > 0 {
|
|
t.Error("Expected no info messages when no supported scopes provided")
|
|
}
|
|
}
|
|
|
|
// TestFilterSupportedScopes_EmptyRequested tests when requested scopes are empty
|
|
func TestFilterSupportedScopes_EmptyRequested(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
requested := []string{}
|
|
supported := []string{"openid", "profile", "email"}
|
|
providerURL := "https://auth.example.com"
|
|
|
|
result := filter.FilterSupportedScopes(requested, supported, providerURL)
|
|
|
|
// Should return openid as fallback
|
|
expected := []string{"openid"}
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Errorf("Expected fallback to %v when requested empty, got %v", expected, result)
|
|
}
|
|
|
|
// Should log info message about empty result (fallback to openid)
|
|
if len(logger.infoMessages) == 0 {
|
|
t.Error("Expected info message when no scopes requested")
|
|
}
|
|
}
|
|
|
|
// TestFilterSupportedScopes_DuplicateScopes tests handling of duplicate scope names
|
|
func TestFilterSupportedScopes_DuplicateScopes(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
requested := []string{"openid", "profile", "openid", "email"}
|
|
supported := []string{"openid", "profile", "email"}
|
|
providerURL := "https://auth.example.com"
|
|
|
|
result := filter.FilterSupportedScopes(requested, supported, providerURL)
|
|
|
|
// Should preserve duplicates from requested
|
|
expected := []string{"openid", "profile", "openid", "email"}
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Errorf("Expected %v (preserving duplicates), got %v", expected, result)
|
|
}
|
|
}
|
|
|
|
// TestFilterSupportedScopes_WhitespaceHandling tests trimming of whitespace
|
|
func TestFilterSupportedScopes_WhitespaceHandling(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
requested := []string{" openid ", "profile", " email"}
|
|
supported := []string{"openid", "profile", "email", "phone"}
|
|
providerURL := "https://auth.example.com"
|
|
|
|
result := filter.FilterSupportedScopes(requested, supported, providerURL)
|
|
|
|
// Should trim whitespace from scopes
|
|
expected := []string{"openid", "profile", "email"}
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Errorf("Expected trimmed scopes %v, got %v", expected, result)
|
|
}
|
|
}
|
|
|
|
// TestFilterSupportedScopes_EmptyStrings tests filtering out empty strings
|
|
func TestFilterSupportedScopes_EmptyStrings(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
requested := []string{"openid", "", "profile", " ", "email"}
|
|
supported := []string{"openid", "profile", "email"}
|
|
providerURL := "https://auth.example.com"
|
|
|
|
result := filter.FilterSupportedScopes(requested, supported, providerURL)
|
|
|
|
// Should filter out empty strings
|
|
expected := []string{"openid", "profile", "email"}
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Errorf("Expected %v (without empty strings), got %v", expected, result)
|
|
}
|
|
}
|
|
|
|
// TestFilterSupportedScopes_CasePreservation tests that scope case is preserved
|
|
func TestFilterSupportedScopes_CasePreservation(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
requested := []string{"OpenID", "Profile", "Email"}
|
|
supported := []string{"OpenID", "Profile", "Email"}
|
|
providerURL := "https://auth.example.com"
|
|
|
|
result := filter.FilterSupportedScopes(requested, supported, providerURL)
|
|
|
|
// Should preserve case exactly
|
|
expected := []string{"OpenID", "Profile", "Email"}
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Errorf("Expected case-preserved %v, got %v", expected, result)
|
|
}
|
|
}
|
|
|
|
// TestFilterSupportedScopes_CaseSensitiveMatching tests case-sensitive matching
|
|
func TestFilterSupportedScopes_CaseSensitiveMatching(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
requested := []string{"openid", "Profile", "EMAIL"}
|
|
supported := []string{"openid", "profile", "email"}
|
|
providerURL := "https://auth.example.com"
|
|
|
|
result := filter.FilterSupportedScopes(requested, supported, providerURL)
|
|
|
|
// Only "openid" should match (case-sensitive)
|
|
// Profile and EMAIL won't match profile and email in supported list
|
|
expected := []string{"openid"}
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Errorf("Expected case-sensitive filtering %v, got %v", expected, result)
|
|
}
|
|
|
|
// Should log info about filtered scopes
|
|
if len(logger.infoMessages) == 0 {
|
|
t.Error("Expected info message about filtered scopes due to case mismatch")
|
|
}
|
|
}
|
|
|
|
// TestFilterSupportedScopes_OrderPreservation tests that order is preserved
|
|
func TestFilterSupportedScopes_OrderPreservation(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
requested := []string{"email", "profile", "openid", "phone"}
|
|
supported := []string{"openid", "profile", "email", "phone", "address"}
|
|
providerURL := "https://auth.example.com"
|
|
|
|
result := filter.FilterSupportedScopes(requested, supported, providerURL)
|
|
|
|
// Should preserve order from requested
|
|
expected := []string{"email", "profile", "openid", "phone"}
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Errorf("Expected order-preserved %v, got %v", expected, result)
|
|
}
|
|
}
|
|
|
|
// TestFilterSupportedScopes_GitLabScenario simulates GitLab rejecting offline_access
|
|
func TestFilterSupportedScopes_GitLabScenario(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
// User requests offline_access but GitLab doesn't support it
|
|
requested := []string{"openid", "profile", "email", "offline_access"}
|
|
supported := []string{"openid", "profile", "email", "read_user", "read_api"}
|
|
providerURL := "https://gitlab.example.com"
|
|
|
|
result := filter.FilterSupportedScopes(requested, supported, providerURL)
|
|
|
|
expected := []string{"openid", "profile", "email"}
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Errorf("Expected %v (without offline_access), got %v", expected, result)
|
|
}
|
|
|
|
// Verify offline_access was filtered out
|
|
for _, scope := range result {
|
|
if scope == "offline_access" {
|
|
t.Error("offline_access should have been filtered out for GitLab")
|
|
}
|
|
}
|
|
|
|
// Should log info about filtered scopes
|
|
if len(logger.infoMessages) == 0 {
|
|
t.Error("Expected info message about offline_access being filtered")
|
|
}
|
|
}
|
|
|
|
// TestFilterSupportedScopes_GoogleScenario simulates Google's scope handling
|
|
func TestFilterSupportedScopes_GoogleScenario(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
// Google supports these standard scopes
|
|
requested := []string{"openid", "profile", "email"}
|
|
supported := []string{"openid", "profile", "email"}
|
|
providerURL := "https://accounts.google.com"
|
|
|
|
result := filter.FilterSupportedScopes(requested, supported, providerURL)
|
|
|
|
expected := []string{"openid", "profile", "email"}
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Errorf("Expected %v, got %v", expected, result)
|
|
}
|
|
|
|
// No scopes should be filtered
|
|
if len(logger.infoMessages) > 0 {
|
|
t.Error("Expected no filtering for standard Google scopes")
|
|
}
|
|
}
|
|
|
|
// TestFilterSupportedScopes_AzureScenario simulates Azure's scope handling
|
|
func TestFilterSupportedScopes_AzureScenario(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
// Azure supports offline_access and OIDC scopes
|
|
requested := []string{"openid", "profile", "email", "offline_access"}
|
|
supported := []string{"openid", "profile", "email", "offline_access"}
|
|
providerURL := "https://login.microsoftonline.com/tenant"
|
|
|
|
result := filter.FilterSupportedScopes(requested, supported, providerURL)
|
|
|
|
expected := []string{"openid", "profile", "email", "offline_access"}
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Errorf("Expected %v (including offline_access), got %v", expected, result)
|
|
}
|
|
|
|
// All scopes should be retained
|
|
if len(logger.infoMessages) > 0 {
|
|
t.Error("Expected no filtering for standard Azure scopes with offline_access")
|
|
}
|
|
}
|
|
|
|
// TestFilterSupportedScopes_GenericWithFiltering simulates generic provider with filtering
|
|
func TestFilterSupportedScopes_GenericWithFiltering(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
requested := []string{"openid", "profile", "email", "offline_access", "custom:scope"}
|
|
supported := []string{"openid", "profile", "email", "custom:scope"}
|
|
providerURL := "https://auth.custom-provider.com"
|
|
|
|
result := filter.FilterSupportedScopes(requested, supported, providerURL)
|
|
|
|
expected := []string{"openid", "profile", "email", "custom:scope"}
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Errorf("Expected %v (without offline_access), got %v", expected, result)
|
|
}
|
|
|
|
// offline_access should be filtered
|
|
for _, scope := range result {
|
|
if scope == "offline_access" {
|
|
t.Error("offline_access should have been filtered for this provider")
|
|
}
|
|
}
|
|
|
|
// Should log info about filtering
|
|
if len(logger.infoMessages) == 0 {
|
|
t.Error("Expected info message about filtered offline_access")
|
|
}
|
|
}
|
|
|
|
// TestFilterSupportedScopes_MultipleProviderURLs tests different provider URLs
|
|
func TestFilterSupportedScopes_MultipleProviderURLs(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
providerURL string
|
|
requested []string
|
|
supported []string
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "GitLab.com",
|
|
providerURL: "https://gitlab.com",
|
|
requested: []string{"openid", "offline_access"},
|
|
supported: []string{"openid"},
|
|
expected: []string{"openid"},
|
|
},
|
|
{
|
|
name: "Self-hosted GitLab",
|
|
providerURL: "https://gitlab.example.com",
|
|
requested: []string{"openid", "profile", "offline_access"},
|
|
supported: []string{"openid", "profile"},
|
|
expected: []string{"openid", "profile"},
|
|
},
|
|
{
|
|
name: "Keycloak",
|
|
providerURL: "https://keycloak.example.com/realms/master",
|
|
requested: []string{"openid", "profile", "email"},
|
|
supported: []string{"openid", "profile", "email", "offline_access"},
|
|
expected: []string{"openid", "profile", "email"},
|
|
},
|
|
{
|
|
name: "Auth0",
|
|
providerURL: "https://tenant.auth0.com",
|
|
requested: []string{"openid", "profile", "offline_access"},
|
|
supported: []string{"openid", "profile", "offline_access"},
|
|
expected: []string{"openid", "profile", "offline_access"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
result := filter.FilterSupportedScopes(tt.requested, tt.supported, tt.providerURL)
|
|
|
|
if !reflect.DeepEqual(result, tt.expected) {
|
|
t.Errorf("Expected %v, got %v", tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestEnsureOpenIDScope_Present tests when openid is already present
|
|
func TestEnsureOpenIDScope_Present(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
scopes := []string{"openid", "profile", "email"}
|
|
result := filter.EnsureOpenIDScope(scopes)
|
|
|
|
// Should return scopes unchanged
|
|
if !reflect.DeepEqual(result, scopes) {
|
|
t.Errorf("Expected scopes unchanged %v, got %v", scopes, result)
|
|
}
|
|
|
|
// Should not log anything (openid already present)
|
|
if len(logger.debugMessages) > 0 {
|
|
t.Error("Expected no debug messages when openid already present")
|
|
}
|
|
}
|
|
|
|
// TestEnsureOpenIDScope_Missing tests when openid needs to be added
|
|
func TestEnsureOpenIDScope_Missing(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
scopes := []string{"profile", "email"}
|
|
result := filter.EnsureOpenIDScope(scopes)
|
|
|
|
// Should prepend openid
|
|
expected := []string{"openid", "profile", "email"}
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Errorf("Expected openid prepended %v, got %v", expected, result)
|
|
}
|
|
|
|
// Should log debug message about adding openid
|
|
if len(logger.debugMessages) == 0 {
|
|
t.Error("Expected debug message about adding openid scope")
|
|
}
|
|
}
|
|
|
|
// TestEnsureOpenIDScope_Empty tests with empty scopes list
|
|
func TestEnsureOpenIDScope_Empty(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
scopes := []string{}
|
|
result := filter.EnsureOpenIDScope(scopes)
|
|
|
|
// Should return just openid
|
|
expected := []string{"openid"}
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Errorf("Expected %v, got %v", expected, result)
|
|
}
|
|
|
|
// Should log debug message
|
|
if len(logger.debugMessages) == 0 {
|
|
t.Error("Expected debug message about adding openid scope")
|
|
}
|
|
}
|
|
|
|
// TestEnsureOpenIDScope_Nil tests with nil scopes list
|
|
func TestEnsureOpenIDScope_Nil(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
var scopes []string // nil slice
|
|
result := filter.EnsureOpenIDScope(scopes)
|
|
|
|
// Should return just openid
|
|
expected := []string{"openid"}
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Errorf("Expected %v, got %v", expected, result)
|
|
}
|
|
}
|
|
|
|
// TestEnsureOpenIDScope_CaseVariations tests that case matters for openid detection
|
|
func TestEnsureOpenIDScope_CaseVariations(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
scopes []string
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "Lowercase openid",
|
|
scopes: []string{"openid", "profile"},
|
|
expected: []string{"openid", "profile"},
|
|
},
|
|
{
|
|
name: "Mixed case OpenID (should add lowercase)",
|
|
scopes: []string{"OpenID", "profile"},
|
|
expected: []string{"openid", "OpenID", "profile"},
|
|
},
|
|
{
|
|
name: "OPENID uppercase (should add lowercase)",
|
|
scopes: []string{"OPENID", "profile"},
|
|
expected: []string{"openid", "OPENID", "profile"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
result := filter.EnsureOpenIDScope(tt.scopes)
|
|
|
|
if !reflect.DeepEqual(result, tt.expected) {
|
|
t.Errorf("Expected %v, got %v", tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestFilterSupportedScopes_IntegrationScenario tests realistic end-to-end scenario
|
|
func TestFilterSupportedScopes_IntegrationScenario(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
// Simulate: User configures plugin with these scopes
|
|
requested := []string{"openid", "profile", "email", "offline_access", "custom_claim"}
|
|
|
|
// Provider discovery returns these supported scopes
|
|
supported := []string{"openid", "profile", "email", "read_user"}
|
|
|
|
providerURL := "https://gitlab.company.com"
|
|
|
|
// Filter should remove offline_access and custom_claim
|
|
result := filter.FilterSupportedScopes(requested, supported, providerURL)
|
|
|
|
expected := []string{"openid", "profile", "email"}
|
|
if !reflect.DeepEqual(result, expected) {
|
|
t.Errorf("Expected %v, got %v", expected, result)
|
|
}
|
|
|
|
// Verify logging occurred
|
|
if len(logger.infoMessages) == 0 {
|
|
t.Error("Expected info message about filtered scopes")
|
|
}
|
|
|
|
if len(logger.debugMessages) < 2 {
|
|
t.Error("Expected debug messages about supported scopes and final result")
|
|
}
|
|
|
|
// Verify specific scopes were filtered
|
|
for _, scope := range result {
|
|
if scope == "offline_access" || scope == "custom_claim" {
|
|
t.Errorf("Scope '%s' should have been filtered out", scope)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestFilterSupportedScopes_LoggingBehavior tests comprehensive logging scenarios
|
|
func TestFilterSupportedScopes_LoggingBehavior(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
requested []string
|
|
supported []string
|
|
expectDebugOnly bool
|
|
expectInfoLog bool
|
|
}{
|
|
{
|
|
name: "All supported - debug only",
|
|
requested: []string{"openid", "profile"},
|
|
supported: []string{"openid", "profile", "email"},
|
|
expectDebugOnly: true,
|
|
},
|
|
{
|
|
name: "Some filtered - info + debug",
|
|
requested: []string{"openid", "offline_access"},
|
|
supported: []string{"openid"},
|
|
expectInfoLog: true,
|
|
},
|
|
{
|
|
name: "All filtered - info + debug",
|
|
requested: []string{"custom1", "custom2"},
|
|
supported: []string{"openid"},
|
|
expectInfoLog: true,
|
|
},
|
|
{
|
|
name: "No supported scopes - debug only",
|
|
requested: []string{"openid"},
|
|
supported: []string{},
|
|
expectDebugOnly: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
filter.FilterSupportedScopes(tt.requested, tt.supported, "https://example.com")
|
|
|
|
hasDebug := len(logger.debugMessages) > 0
|
|
hasInfo := len(logger.infoMessages) > 0
|
|
|
|
if tt.expectDebugOnly && (!hasDebug || hasInfo) {
|
|
t.Errorf("Expected only debug logs, got debug=%v info=%v",
|
|
hasDebug, hasInfo)
|
|
}
|
|
|
|
if tt.expectInfoLog && !hasInfo {
|
|
t.Error("Expected info log but didn't get one")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Benchmark tests
|
|
func BenchmarkFilterSupportedScopes_AllSupported(b *testing.B) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
requested := []string{"openid", "profile", "email", "phone"}
|
|
supported := []string{"openid", "profile", "email", "phone", "address"}
|
|
providerURL := "https://example.com"
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
filter.FilterSupportedScopes(requested, supported, providerURL)
|
|
}
|
|
}
|
|
|
|
func BenchmarkFilterSupportedScopes_SomeFiltered(b *testing.B) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
requested := []string{"openid", "profile", "email", "offline_access", "custom"}
|
|
supported := []string{"openid", "profile", "email"}
|
|
providerURL := "https://example.com"
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
filter.FilterSupportedScopes(requested, supported, providerURL)
|
|
}
|
|
}
|
|
|
|
func BenchmarkFilterSupportedScopes_NoSupported(b *testing.B) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
requested := []string{"openid", "profile", "email", "offline_access"}
|
|
supported := []string{}
|
|
providerURL := "https://example.com"
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
filter.FilterSupportedScopes(requested, supported, providerURL)
|
|
}
|
|
}
|
|
|
|
func BenchmarkEnsureOpenIDScope_Present(b *testing.B) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
scopes := []string{"openid", "profile", "email"}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
filter.EnsureOpenIDScope(scopes)
|
|
}
|
|
}
|
|
|
|
func BenchmarkEnsureOpenIDScope_Missing(b *testing.B) {
|
|
logger := &mockScopeFilterLogger{}
|
|
filter := NewScopeFilter(logger)
|
|
|
|
scopes := []string{"profile", "email"}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
filter.EnsureOpenIDScope(scopes)
|
|
}
|
|
}
|