mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-14 04:07:46 +00:00
Release 0.7.5 (#70)
* Resolve issue with opaque tokens not being parsed correctly * Increase test coverage * Further improvements to test coverage and code quality * Add new providers. * fixup! Add new providers. * Cleanup. * fixup! Cleanup. * fixup! fixup! Cleanup. * fixup! fixup! fixup! Cleanup. * fixup! fixup! fixup! fixup! Cleanup. * Memory management optimisation 24 bytes per Put < 256-4096 bytes per buffer allocation avoided (10-170x difference) * Pooling cleanup.
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestGoogleProvider_NewGoogleProvider tests the constructor
|
||||
func TestGoogleProvider_NewGoogleProvider(t *testing.T) {
|
||||
provider := NewGoogleProvider()
|
||||
|
||||
if provider == nil {
|
||||
t.Fatal("Expected provider to be created, got nil")
|
||||
}
|
||||
|
||||
if provider.BaseProvider == nil {
|
||||
t.Error("BaseProvider should be initialized")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoogleProvider_GetType tests provider type
|
||||
func TestGoogleProvider_GetType(t *testing.T) {
|
||||
provider := NewGoogleProvider()
|
||||
|
||||
if provider.GetType() != ProviderTypeGoogle {
|
||||
t.Errorf("Expected ProviderTypeGoogle, got %v", provider.GetType())
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoogleProvider_GetCapabilities tests Google-specific capabilities
|
||||
func TestGoogleProvider_GetCapabilities(t *testing.T) {
|
||||
provider := NewGoogleProvider()
|
||||
capabilities := provider.GetCapabilities()
|
||||
|
||||
if !capabilities.SupportsRefreshTokens {
|
||||
t.Error("Expected SupportsRefreshTokens to be true")
|
||||
}
|
||||
|
||||
if capabilities.RequiresOfflineAccessScope {
|
||||
t.Error("Expected RequiresOfflineAccessScope to be false for Google")
|
||||
}
|
||||
|
||||
if !capabilities.RequiresPromptConsent {
|
||||
t.Error("Expected RequiresPromptConsent to be true for Google")
|
||||
}
|
||||
|
||||
if capabilities.PreferredTokenValidation != "id" {
|
||||
t.Errorf("Expected PreferredTokenValidation 'id', got '%s'", capabilities.PreferredTokenValidation)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoogleProvider_BuildAuthParams tests Google-specific auth parameters
|
||||
func TestGoogleProvider_BuildAuthParams(t *testing.T) {
|
||||
provider := NewGoogleProvider()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
inputScopes []string
|
||||
expectedScopes []string
|
||||
shouldHaveAccessType bool
|
||||
shouldHavePrompt bool
|
||||
}{
|
||||
{
|
||||
name: "Basic scopes without offline_access",
|
||||
inputScopes: []string{"openid", "profile", "email"},
|
||||
expectedScopes: []string{"openid", "profile", "email"},
|
||||
shouldHaveAccessType: true,
|
||||
shouldHavePrompt: true,
|
||||
},
|
||||
{
|
||||
name: "Scopes with offline_access (should be filtered out)",
|
||||
inputScopes: []string{"openid", "profile", "offline_access", "email"},
|
||||
expectedScopes: []string{"openid", "profile", "email"},
|
||||
shouldHaveAccessType: true,
|
||||
shouldHavePrompt: true,
|
||||
},
|
||||
{
|
||||
name: "Only offline_access scope (should be filtered out)",
|
||||
inputScopes: []string{"offline_access"},
|
||||
expectedScopes: []string{},
|
||||
shouldHaveAccessType: true,
|
||||
shouldHavePrompt: true,
|
||||
},
|
||||
{
|
||||
name: "Empty scopes",
|
||||
inputScopes: []string{},
|
||||
expectedScopes: []string{},
|
||||
shouldHaveAccessType: true,
|
||||
shouldHavePrompt: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
baseParams := make(url.Values)
|
||||
baseParams.Set("client_id", "test-client")
|
||||
|
||||
result, err := provider.BuildAuthParams(baseParams, tt.inputScopes)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Check Google-specific parameters
|
||||
if tt.shouldHaveAccessType {
|
||||
if result.URLValues.Get("access_type") != "offline" {
|
||||
t.Errorf("Expected access_type 'offline', got '%s'", result.URLValues.Get("access_type"))
|
||||
}
|
||||
}
|
||||
|
||||
if tt.shouldHavePrompt {
|
||||
if result.URLValues.Get("prompt") != "consent" {
|
||||
t.Errorf("Expected prompt 'consent', got '%s'", result.URLValues.Get("prompt"))
|
||||
}
|
||||
}
|
||||
|
||||
// Check filtered scopes
|
||||
if len(result.Scopes) != len(tt.expectedScopes) {
|
||||
t.Errorf("Expected %d scopes, got %d", len(tt.expectedScopes), len(result.Scopes))
|
||||
}
|
||||
|
||||
for _, expectedScope := range tt.expectedScopes {
|
||||
found := false
|
||||
for _, actualScope := range result.Scopes {
|
||||
if actualScope == expectedScope {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Expected scope '%s' not found in result", expectedScope)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure offline_access is not in the result scopes
|
||||
for _, scope := range result.Scopes {
|
||||
if scope == "offline_access" {
|
||||
t.Error("offline_access scope should be filtered out for Google")
|
||||
}
|
||||
}
|
||||
|
||||
// Verify original base parameters are preserved
|
||||
if result.URLValues.Get("client_id") != "test-client" {
|
||||
t.Errorf("Expected client_id 'test-client', got '%s'", result.URLValues.Get("client_id"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoogleProvider_ValidateConfig tests configuration validation
|
||||
func TestGoogleProvider_ValidateConfig(t *testing.T) {
|
||||
provider := NewGoogleProvider()
|
||||
|
||||
err := provider.ValidateConfig()
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoogleProvider_InterfaceCompliance tests that Google provider implements OIDCProvider
|
||||
func TestGoogleProvider_InterfaceCompliance(t *testing.T) {
|
||||
provider := NewGoogleProvider()
|
||||
|
||||
// Verify it implements the OIDCProvider interface
|
||||
var _ OIDCProvider = provider
|
||||
}
|
||||
|
||||
// TestGoogleProvider_OfflineAccessFiltering tests comprehensive offline_access filtering
|
||||
func TestGoogleProvider_OfflineAccessFiltering(t *testing.T) {
|
||||
provider := NewGoogleProvider()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
inputScopes []string
|
||||
description string
|
||||
}{
|
||||
{
|
||||
name: "Multiple offline_access occurrences",
|
||||
inputScopes: []string{"openid", "offline_access", "profile", "offline_access", "email"},
|
||||
description: "Should remove all instances of offline_access",
|
||||
},
|
||||
{
|
||||
name: "Case sensitive filtering",
|
||||
inputScopes: []string{"openid", "OFFLINE_ACCESS", "profile", "offline_access"},
|
||||
description: "Should only remove exact case matches",
|
||||
},
|
||||
{
|
||||
name: "Similar but different scopes",
|
||||
inputScopes: []string{"openid", "offline_access_extended", "profile", "offline_access"},
|
||||
description: "Should only remove exact offline_access matches",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
baseParams := make(url.Values)
|
||||
result, err := provider.BuildAuthParams(baseParams, tt.inputScopes)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Count offline_access occurrences in result
|
||||
offlineAccessCount := 0
|
||||
for _, scope := range result.Scopes {
|
||||
if scope == "offline_access" {
|
||||
offlineAccessCount++
|
||||
}
|
||||
}
|
||||
|
||||
if offlineAccessCount != 0 {
|
||||
t.Errorf("Expected 0 offline_access scopes in result, got %d", offlineAccessCount)
|
||||
}
|
||||
|
||||
// Verify other scopes are preserved
|
||||
for _, originalScope := range tt.inputScopes {
|
||||
if originalScope == "offline_access" {
|
||||
continue // Skip the filtered scope
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, resultScope := range result.Scopes {
|
||||
if resultScope == originalScope {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Expected scope '%s' to be preserved", originalScope)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoogleProvider_BaseProviderInheritance tests inherited functionality from BaseProvider
|
||||
func TestGoogleProvider_BaseProviderInheritance(t *testing.T) {
|
||||
provider := NewGoogleProvider()
|
||||
|
||||
// Test ValidateTokens (inherited from BaseProvider)
|
||||
session := &mockSession{
|
||||
authenticated: true,
|
||||
idToken: "test-token",
|
||||
accessToken: "access-token", // Add access token for proper validation
|
||||
}
|
||||
verifier := &mockTokenVerifier{}
|
||||
cache := &mockTokenCache{
|
||||
claims: map[string]map[string]interface{}{
|
||||
"test-token": {
|
||||
"exp": float64(9999999999), // Far future
|
||||
"sub": "user123",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result, err := provider.ValidateTokens(session, verifier, cache, 0)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !result.Authenticated {
|
||||
t.Error("Expected result to be authenticated")
|
||||
}
|
||||
|
||||
// Test HandleTokenRefresh (inherited from BaseProvider)
|
||||
tokenData := &TokenResult{IDToken: "new-token"}
|
||||
err = provider.HandleTokenRefresh(tokenData)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGoogleProvider_AuthParamsPreservation tests that base parameters are not overwritten
|
||||
func TestGoogleProvider_AuthParamsPreservation(t *testing.T) {
|
||||
provider := NewGoogleProvider()
|
||||
|
||||
baseParams := make(url.Values)
|
||||
baseParams.Set("client_id", "test-client")
|
||||
baseParams.Set("redirect_uri", "https://example.com/callback")
|
||||
baseParams.Set("response_type", "code")
|
||||
baseParams.Set("state", "test-state")
|
||||
baseParams.Set("nonce", "test-nonce")
|
||||
|
||||
scopes := []string{"openid", "profile"}
|
||||
|
||||
result, err := provider.BuildAuthParams(baseParams, scopes)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Verify all original parameters are preserved
|
||||
expectedParams := map[string]string{
|
||||
"client_id": "test-client",
|
||||
"redirect_uri": "https://example.com/callback",
|
||||
"response_type": "code",
|
||||
"state": "test-state",
|
||||
"nonce": "test-nonce",
|
||||
"access_type": "offline", // Added by Google provider
|
||||
"prompt": "consent", // Added by Google provider
|
||||
}
|
||||
|
||||
for key, expectedValue := range expectedParams {
|
||||
actualValue := result.URLValues.Get(key)
|
||||
if actualValue != expectedValue {
|
||||
t.Errorf("Expected %s '%s', got '%s'", key, expectedValue, actualValue)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify scopes
|
||||
if len(result.Scopes) != 2 {
|
||||
t.Errorf("Expected 2 scopes, got %d", len(result.Scopes))
|
||||
}
|
||||
|
||||
expectedScopes := []string{"openid", "profile"}
|
||||
for _, expectedScope := range expectedScopes {
|
||||
found := false
|
||||
for _, actualScope := range result.Scopes {
|
||||
if actualScope == expectedScope {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Expected scope '%s' not found", expectedScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark tests
|
||||
func BenchmarkGoogleProvider_BuildAuthParams(b *testing.B) {
|
||||
provider := NewGoogleProvider()
|
||||
baseParams := make(url.Values)
|
||||
baseParams.Set("client_id", "test-client")
|
||||
scopes := []string{"openid", "profile", "email", "offline_access"}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
provider.BuildAuthParams(baseParams, scopes)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGoogleProvider_GetCapabilities(b *testing.B) {
|
||||
provider := NewGoogleProvider()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
provider.GetCapabilities()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user