mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-15 04:18:03 +00:00
fixup! Cleanup tests.
This commit is contained in:
@@ -0,0 +1,433 @@
|
||||
package traefikoidc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPostLogoutRedirectURIConfiguration(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
postLogoutRedirectURI string
|
||||
expectDefault bool
|
||||
expectedValue string
|
||||
}{
|
||||
{
|
||||
name: "custom post logout redirect URI",
|
||||
postLogoutRedirectURI: "/home",
|
||||
expectDefault: false,
|
||||
expectedValue: "/home",
|
||||
},
|
||||
{
|
||||
name: "empty uses default",
|
||||
postLogoutRedirectURI: "",
|
||||
expectDefault: true,
|
||||
expectedValue: "/",
|
||||
},
|
||||
{
|
||||
name: "external URL allowed",
|
||||
postLogoutRedirectURI: "https://example.com/goodbye",
|
||||
expectDefault: false,
|
||||
expectedValue: "https://example.com/goodbye",
|
||||
},
|
||||
{
|
||||
name: "relative path with query",
|
||||
postLogoutRedirectURI: "/logout-success?msg=goodbye",
|
||||
expectDefault: false,
|
||||
expectedValue: "/logout-success?msg=goodbye",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
config := createTestConfig()
|
||||
config.PostLogoutRedirectURI = tt.postLogoutRedirectURI
|
||||
|
||||
oidc, _ := setupTestOIDCMiddleware(t, config)
|
||||
|
||||
// Check the configured value
|
||||
if tt.expectDefault {
|
||||
assert.Equal(t, tt.expectedValue, oidc.postLogoutRedirectURI)
|
||||
} else {
|
||||
assert.Equal(t, tt.postLogoutRedirectURI, oidc.postLogoutRedirectURI)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogoutWithPostLogoutRedirect(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
postLogoutRedirectURI string
|
||||
oidcEndSessionURL string
|
||||
expectRedirectTo string
|
||||
expectEndSession bool
|
||||
}{
|
||||
{
|
||||
name: "redirect to custom URI without end session",
|
||||
postLogoutRedirectURI: "/goodbye",
|
||||
oidcEndSessionURL: "",
|
||||
expectRedirectTo: "http://example.com/goodbye",
|
||||
expectEndSession: false,
|
||||
},
|
||||
{
|
||||
name: "redirect to default when not configured",
|
||||
postLogoutRedirectURI: "",
|
||||
oidcEndSessionURL: "",
|
||||
expectRedirectTo: "http://example.com/",
|
||||
expectEndSession: false,
|
||||
},
|
||||
{
|
||||
name: "end session URL takes precedence",
|
||||
postLogoutRedirectURI: "/goodbye",
|
||||
oidcEndSessionURL: "https://auth.example.com/logout",
|
||||
expectRedirectTo: "https://auth.example.com/logout",
|
||||
expectEndSession: true,
|
||||
},
|
||||
{
|
||||
name: "external post logout redirect",
|
||||
postLogoutRedirectURI: "https://app.example.com/logged-out",
|
||||
oidcEndSessionURL: "",
|
||||
expectRedirectTo: "https://app.example.com/logged-out",
|
||||
expectEndSession: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
config := createTestConfig()
|
||||
config.PostLogoutRedirectURI = tt.postLogoutRedirectURI
|
||||
config.LogoutURL = "/logout"
|
||||
|
||||
oidc, _ := setupTestOIDCMiddleware(t, config)
|
||||
oidc.endSessionURL = tt.oidcEndSessionURL
|
||||
|
||||
// Create authenticated session
|
||||
session := createTestSession()
|
||||
session.SetIDToken(createMockJWT(t, "user123", "[email protected]"))
|
||||
session.SetAccessToken("test-access-token")
|
||||
|
||||
// Create logout request
|
||||
req := httptest.NewRequest("GET", "/logout", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
// Inject session into request
|
||||
injectSessionIntoRequest(t, req, session)
|
||||
|
||||
// Handle logout
|
||||
oidc.ServeHTTP(rec, req)
|
||||
|
||||
// Check redirect
|
||||
assert.Equal(t, http.StatusFound, rec.Code)
|
||||
location := rec.Header().Get("Location")
|
||||
|
||||
if tt.expectEndSession {
|
||||
// When end session URL is present, it should redirect there
|
||||
assert.Contains(t, location, tt.oidcEndSessionURL)
|
||||
// Should include id_token_hint
|
||||
assert.Contains(t, location, "id_token_hint=")
|
||||
// Should include post_logout_redirect_uri
|
||||
if tt.postLogoutRedirectURI != "" {
|
||||
assert.Contains(t, location, "post_logout_redirect_uri=")
|
||||
}
|
||||
} else {
|
||||
// Otherwise, should redirect to post logout redirect URI
|
||||
assert.Equal(t, tt.expectRedirectTo, location)
|
||||
}
|
||||
|
||||
// Session should be cleared
|
||||
cookies := rec.Result().Cookies()
|
||||
for _, cookie := range cookies {
|
||||
if cookie.Name == "oidc_session" {
|
||||
assert.Equal(t, -1, cookie.MaxAge, "Session cookie should be deleted")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildLogoutURLWithPostLogoutRedirect(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
oidcEndSessionURL string
|
||||
postLogoutRedirectURI string
|
||||
idToken string
|
||||
expectedParams map[string]string
|
||||
}{
|
||||
{
|
||||
name: "includes all parameters",
|
||||
oidcEndSessionURL: "https://auth.example.com/logout",
|
||||
postLogoutRedirectURI: "https://app.example.com/goodbye",
|
||||
idToken: "test-id-token",
|
||||
expectedParams: map[string]string{
|
||||
"id_token_hint": "test-id-token",
|
||||
"post_logout_redirect_uri": "https://app.example.com/goodbye",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "relative post logout URI",
|
||||
oidcEndSessionURL: "https://auth.example.com/logout",
|
||||
postLogoutRedirectURI: "/logout-success",
|
||||
idToken: "test-id-token",
|
||||
expectedParams: map[string]string{
|
||||
"id_token_hint": "test-id-token",
|
||||
"post_logout_redirect_uri": "/logout-success",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty post logout URI omitted",
|
||||
oidcEndSessionURL: "https://auth.example.com/logout",
|
||||
postLogoutRedirectURI: "",
|
||||
idToken: "test-id-token",
|
||||
expectedParams: map[string]string{
|
||||
"id_token_hint": "test-id-token",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "special characters in URI",
|
||||
oidcEndSessionURL: "https://auth.example.com/logout",
|
||||
postLogoutRedirectURI: "/logout?msg=Thank you!",
|
||||
idToken: "test-id-token",
|
||||
expectedParams: map[string]string{
|
||||
"id_token_hint": "test-id-token",
|
||||
"post_logout_redirect_uri": "/logout?msg=Thank you!",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Test the BuildLogoutURL function directly without middleware setup
|
||||
logoutURL, err := BuildLogoutURL(tt.oidcEndSessionURL, tt.idToken, tt.postLogoutRedirectURI)
|
||||
require.NoError(t, err)
|
||||
|
||||
parsedURL, err := url.Parse(logoutURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check base URL
|
||||
expectedBase := tt.oidcEndSessionURL
|
||||
actualBase := parsedURL.Scheme + "://" + parsedURL.Host + parsedURL.Path
|
||||
assert.Equal(t, expectedBase, actualBase)
|
||||
|
||||
// Check query parameters
|
||||
params := parsedURL.Query()
|
||||
for key, expectedValue := range tt.expectedParams {
|
||||
assert.Equal(t, expectedValue, params.Get(key), "Parameter %s mismatch", key)
|
||||
}
|
||||
|
||||
// Ensure no extra parameters
|
||||
if tt.postLogoutRedirectURI == "" {
|
||||
assert.Empty(t, params.Get("post_logout_redirect_uri"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogoutFlowIntegration(t *testing.T) {
|
||||
// Mock provider's end session endpoint
|
||||
providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// This won't be called in a unit test, but we keep it for completeness
|
||||
if r.URL.Path == "/endsession" {
|
||||
// Provider would handle logout and redirect to post_logout_redirect_uri
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}))
|
||||
defer providerServer.Close()
|
||||
|
||||
config := createTestConfig()
|
||||
config.LogoutURL = "/logout"
|
||||
config.PostLogoutRedirectURI = "/thank-you"
|
||||
config.OIDCEndSessionURL = providerServer.URL + "/endsession"
|
||||
|
||||
oidc, _ := setupTestOIDCMiddleware(t, config)
|
||||
oidc.endSessionURL = config.OIDCEndSessionURL
|
||||
oidc.postLogoutRedirectURI = config.PostLogoutRedirectURI
|
||||
|
||||
// Create authenticated session
|
||||
idToken := createMockJWT(t, "user123", "[email protected]")
|
||||
session := createTestSession()
|
||||
session.SetIDToken(idToken)
|
||||
session.SetAccessToken("test-access-token")
|
||||
|
||||
// Initiate logout
|
||||
req := httptest.NewRequest("GET", "/logout", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
// Inject session into request
|
||||
injectSessionIntoRequest(t, req, session)
|
||||
|
||||
oidc.ServeHTTP(rec, req)
|
||||
|
||||
// Verify redirect to provider's end session
|
||||
assert.Equal(t, http.StatusFound, rec.Code)
|
||||
location := rec.Header().Get("Location")
|
||||
|
||||
// Parse the redirect URL to check parameters
|
||||
parsedURL, err := url.Parse(location)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify it's redirecting to the correct endpoint
|
||||
assert.Equal(t, providerServer.URL+"/endsession", parsedURL.Scheme+"://"+parsedURL.Host+parsedURL.Path)
|
||||
|
||||
// Verify query parameters
|
||||
queryParams := parsedURL.Query()
|
||||
assert.Equal(t, idToken, queryParams.Get("id_token_hint"))
|
||||
assert.Equal(t, "http://example.com/thank-you", queryParams.Get("post_logout_redirect_uri"))
|
||||
|
||||
// Note: The provider server won't actually be called in a unit test,
|
||||
// as the redirect response is returned to the test client
|
||||
}
|
||||
|
||||
func TestLogoutWithoutSession(t *testing.T) {
|
||||
config := createTestConfig()
|
||||
config.LogoutURL = "/logout"
|
||||
config.PostLogoutRedirectURI = "/goodbye"
|
||||
|
||||
oidc, _ := setupTestOIDCMiddleware(t, config)
|
||||
|
||||
// Logout request without session
|
||||
req := httptest.NewRequest("GET", "/logout", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
oidc.ServeHTTP(rec, req)
|
||||
|
||||
// Should still redirect to post logout URI
|
||||
assert.Equal(t, http.StatusFound, rec.Code)
|
||||
// Relative URLs get converted to absolute URLs
|
||||
assert.Equal(t, "http://example.com/goodbye", rec.Header().Get("Location"))
|
||||
}
|
||||
|
||||
func TestPostLogoutRedirectEdgeCases(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
postLogoutRedirectURI string
|
||||
requestURL string
|
||||
expectedBehavior string
|
||||
}{
|
||||
{
|
||||
name: "preserves fragment in redirect",
|
||||
postLogoutRedirectURI: "/app#section",
|
||||
requestURL: "/logout",
|
||||
expectedBehavior: "Should preserve URL fragment",
|
||||
},
|
||||
{
|
||||
name: "handles encoded characters",
|
||||
postLogoutRedirectURI: "/message?text=Thank%20you%21",
|
||||
requestURL: "/logout",
|
||||
expectedBehavior: "Should handle URL encoding properly",
|
||||
},
|
||||
{
|
||||
name: "absolute URL with different domain",
|
||||
postLogoutRedirectURI: "https://other-app.com/logout-landing",
|
||||
requestURL: "/logout",
|
||||
expectedBehavior: "Should allow external redirects",
|
||||
},
|
||||
{
|
||||
name: "protocol-relative URL",
|
||||
postLogoutRedirectURI: "//example.com/logout",
|
||||
requestURL: "/logout",
|
||||
expectedBehavior: "Should handle protocol-relative URLs",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
config := createTestConfig()
|
||||
config.LogoutURL = "/logout"
|
||||
config.PostLogoutRedirectURI = tt.postLogoutRedirectURI
|
||||
|
||||
oidc, _ := setupTestOIDCMiddleware(t, config)
|
||||
|
||||
req := httptest.NewRequest("GET", tt.requestURL, nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
// Add minimal session
|
||||
session := createTestSession()
|
||||
session.SetIDToken("dummy-token")
|
||||
|
||||
// Inject session into request
|
||||
injectSessionIntoRequest(t, req, session)
|
||||
|
||||
oidc.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusFound, rec.Code)
|
||||
location := rec.Header().Get("Location")
|
||||
|
||||
// Check based on the type of URL
|
||||
switch {
|
||||
case strings.HasPrefix(tt.postLogoutRedirectURI, "https://") || strings.HasPrefix(tt.postLogoutRedirectURI, "http://"):
|
||||
// Absolute URLs should be preserved
|
||||
assert.Equal(t, tt.postLogoutRedirectURI, location, tt.expectedBehavior)
|
||||
case strings.HasPrefix(tt.postLogoutRedirectURI, "//"):
|
||||
// Protocol-relative URLs get the scheme prepended
|
||||
assert.Equal(t, "http://example.com"+tt.postLogoutRedirectURI, location, tt.expectedBehavior)
|
||||
default:
|
||||
// Relative URLs get the full base URL prepended
|
||||
assert.Equal(t, "http://example.com"+tt.postLogoutRedirectURI, location, tt.expectedBehavior)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogoutURLConfiguration(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
logoutURL string
|
||||
callbackURL string
|
||||
expectedLogoutURL string
|
||||
}{
|
||||
{
|
||||
name: "custom logout URL",
|
||||
logoutURL: "/auth/logout",
|
||||
callbackURL: "/auth/callback",
|
||||
expectedLogoutURL: "/auth/logout",
|
||||
},
|
||||
{
|
||||
name: "default logout URL from callback",
|
||||
logoutURL: "",
|
||||
callbackURL: "/oauth2/callback",
|
||||
expectedLogoutURL: "/oauth2/callback/logout",
|
||||
},
|
||||
{
|
||||
name: "logout URL with trailing slash",
|
||||
logoutURL: "/logout/",
|
||||
callbackURL: "/callback",
|
||||
expectedLogoutURL: "/logout/",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
config := createTestConfig()
|
||||
config.LogoutURL = tt.logoutURL
|
||||
config.CallbackURL = tt.callbackURL
|
||||
|
||||
oidc, _ := setupTestOIDCMiddleware(t, config)
|
||||
|
||||
// The logout URL should be set correctly
|
||||
assert.Equal(t, tt.expectedLogoutURL, oidc.logoutURLPath)
|
||||
|
||||
// Test that the logout URL is recognized
|
||||
req := httptest.NewRequest("GET", tt.expectedLogoutURL, nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
// Add session to trigger logout logic
|
||||
session := createTestSession()
|
||||
session.SetIDToken("test-token")
|
||||
|
||||
// Inject session into request
|
||||
injectSessionIntoRequest(t, req, session)
|
||||
|
||||
oidc.ServeHTTP(rec, req)
|
||||
|
||||
// Should trigger logout (redirect)
|
||||
assert.Equal(t, http.StatusFound, rec.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user