mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-15 04:18:03 +00:00
Add redis support for distributed caching (#83)
* Add redis support for distributed caching * Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! fixup! fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! fixup! fixup! fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * ... and another all nighter. * fixup! ... and another all nighter. * fixup! fixup! ... and another all nighter. * fixup! fixup! fixup! ... and another all nighter. * Resolve issue #85 by adding ability to set custom claims in JWT tokens * Remove redundant validation in auth middleware ( issue #89 ) * Add ability to set cookie prefix for session cookies ( #87 ) * fixup! Add ability to set cookie prefix for session cookies ( #87 ) * Add ability to set cookie max age - issue #91 * Potential fix for code scanning alert no. 10: Size computation for allocation may overflow Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * fixup! Merge main into 0.8.0-redis: resolve conflicts --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
parent
5fcbd54955
commit
e64fc7f730
@@ -0,0 +1,351 @@
|
||||
//go:build !yaegi
|
||||
|
||||
package token
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Mock implementations for refresher tests
|
||||
type mockSessionManager struct{}
|
||||
|
||||
func (m *mockSessionManager) GetSession(sessionID string) (SessionDataInterface, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mockSessionManager) SaveSession(session SessionDataInterface) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type mockSessionData struct {
|
||||
idToken string
|
||||
accessToken string
|
||||
refreshToken string
|
||||
idExpiry time.Time
|
||||
accessExpiry time.Time
|
||||
saveErr error
|
||||
}
|
||||
|
||||
func (m *mockSessionData) GetIDToken() string {
|
||||
return m.idToken
|
||||
}
|
||||
|
||||
func (m *mockSessionData) GetAccessToken() string {
|
||||
return m.accessToken
|
||||
}
|
||||
|
||||
func (m *mockSessionData) GetRefreshToken() string {
|
||||
return m.refreshToken
|
||||
}
|
||||
|
||||
func (m *mockSessionData) GetIDTokenExpiry() time.Time {
|
||||
return m.idExpiry
|
||||
}
|
||||
|
||||
func (m *mockSessionData) GetAccessTokenExpiry() time.Time {
|
||||
return m.accessExpiry
|
||||
}
|
||||
|
||||
func (m *mockSessionData) SetTokens(idToken, accessToken, refreshToken string, idExp, accessExp time.Time) {
|
||||
m.idToken = idToken
|
||||
m.accessToken = accessToken
|
||||
m.refreshToken = refreshToken
|
||||
m.idExpiry = idExp
|
||||
m.accessExpiry = accessExp
|
||||
}
|
||||
|
||||
func (m *mockSessionData) SetIDToken(token string, expiry time.Time) {
|
||||
m.idToken = token
|
||||
m.idExpiry = expiry
|
||||
}
|
||||
|
||||
func (m *mockSessionData) SetAccessToken(token string, expiry time.Time) {
|
||||
m.accessToken = token
|
||||
m.accessExpiry = expiry
|
||||
}
|
||||
|
||||
func (m *mockSessionData) SetRefreshToken(token string) {
|
||||
m.refreshToken = token
|
||||
}
|
||||
|
||||
func (m *mockSessionData) SaveToCache() error {
|
||||
return m.saveErr
|
||||
}
|
||||
|
||||
type mockTokenVerifier struct {
|
||||
shouldFail bool
|
||||
}
|
||||
|
||||
func (m *mockTokenVerifier) VerifyToken(token string) error {
|
||||
if m.shouldFail {
|
||||
return fmt.Errorf("token verification failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Refresher tests
|
||||
func TestNewRefresher(t *testing.T) {
|
||||
refresher := NewRefresher(
|
||||
"client-id",
|
||||
"client-secret",
|
||||
"https://provider.example.com/token",
|
||||
&http.Client{},
|
||||
&mockLogger{},
|
||||
&mockMetrics{},
|
||||
&mockSessionManager{},
|
||||
newMockCache(),
|
||||
&mockTokenVerifier{},
|
||||
)
|
||||
|
||||
if refresher == nil {
|
||||
t.Fatal("Expected NewRefresher to return non-nil")
|
||||
}
|
||||
|
||||
if refresher.clientID != "client-id" {
|
||||
t.Error("Expected clientID to be set")
|
||||
}
|
||||
|
||||
if refresher.clientSecret != "client-secret" {
|
||||
t.Error("Expected clientSecret to be set")
|
||||
}
|
||||
|
||||
if refresher.tokenURL != "https://provider.example.com/token" {
|
||||
t.Error("Expected tokenURL to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefresher_RefreshToken_NilSession(t *testing.T) {
|
||||
refresher := NewRefresher(
|
||||
"client-id",
|
||||
"client-secret",
|
||||
"https://provider.example.com/token",
|
||||
&http.Client{},
|
||||
&mockLogger{},
|
||||
&mockMetrics{},
|
||||
&mockSessionManager{},
|
||||
newMockCache(),
|
||||
&mockTokenVerifier{},
|
||||
)
|
||||
|
||||
result := refresher.RefreshToken(nil, nil, nil)
|
||||
if result {
|
||||
t.Error("Expected RefreshToken to return false for nil session")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefresher_RefreshToken_NoRefreshToken(t *testing.T) {
|
||||
refresher := NewRefresher(
|
||||
"client-id",
|
||||
"client-secret",
|
||||
"https://provider.example.com/token",
|
||||
&http.Client{},
|
||||
&mockLogger{},
|
||||
&mockMetrics{},
|
||||
&mockSessionManager{},
|
||||
newMockCache(),
|
||||
&mockTokenVerifier{},
|
||||
)
|
||||
|
||||
session := &mockSessionData{
|
||||
refreshToken: "", // No refresh token
|
||||
}
|
||||
|
||||
result := refresher.RefreshToken(nil, nil, session)
|
||||
if result {
|
||||
t.Error("Expected RefreshToken to return false when no refresh token available")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefresher_ExchangeToken_UnsupportedGrantType(t *testing.T) {
|
||||
refresher := NewRefresher(
|
||||
"client-id",
|
||||
"client-secret",
|
||||
"https://provider.example.com/token",
|
||||
&http.Client{},
|
||||
&mockLogger{},
|
||||
&mockMetrics{},
|
||||
&mockSessionManager{},
|
||||
newMockCache(),
|
||||
&mockTokenVerifier{},
|
||||
)
|
||||
|
||||
_, err := refresher.exchangeToken("unsupported_grant", "token", "", "")
|
||||
if err == nil {
|
||||
t.Error("Expected error for unsupported grant type")
|
||||
}
|
||||
|
||||
if err.Error() != "unsupported grant type: unsupported_grant" {
|
||||
t.Errorf("Expected unsupported grant type error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefresher_ExchangeToken_RefreshToken_RequestCreation(t *testing.T) {
|
||||
// Test with valid refresh_token grant type but invalid URL to test request creation
|
||||
refresher := NewRefresher(
|
||||
"client-id",
|
||||
"client-secret",
|
||||
"://invalid-url", // Invalid URL
|
||||
&http.Client{},
|
||||
&mockLogger{},
|
||||
&mockMetrics{},
|
||||
&mockSessionManager{},
|
||||
newMockCache(),
|
||||
&mockTokenVerifier{},
|
||||
)
|
||||
|
||||
_, err := refresher.exchangeToken("refresh_token", "refresh-token-value", "", "")
|
||||
if err == nil {
|
||||
t.Error("Expected error for invalid URL")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefresher_ExchangeToken_AuthorizationCode_WithPKCE(t *testing.T) {
|
||||
// Create a test server that verifies the request
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
t.Errorf("Expected POST request, got %s", r.Method)
|
||||
}
|
||||
|
||||
if err := r.ParseForm(); err != nil {
|
||||
t.Errorf("Failed to parse form: %v", err)
|
||||
}
|
||||
|
||||
// Verify PKCE parameters are included
|
||||
if r.FormValue("code_verifier") != "test-verifier" {
|
||||
t.Error("Expected code_verifier to be included")
|
||||
}
|
||||
|
||||
if r.FormValue("code") != "auth-code" {
|
||||
t.Error("Expected authorization code to be included")
|
||||
}
|
||||
|
||||
if r.FormValue("grant_type") != "authorization_code" {
|
||||
t.Error("Expected grant_type to be authorization_code")
|
||||
}
|
||||
|
||||
// Return valid token response
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`{"access_token":"test-access","id_token":"test-id","expires_in":3600}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
refresher := NewRefresher(
|
||||
"client-id",
|
||||
"client-secret",
|
||||
server.URL,
|
||||
&http.Client{},
|
||||
&mockLogger{},
|
||||
&mockMetrics{},
|
||||
&mockSessionManager{},
|
||||
newMockCache(),
|
||||
&mockTokenVerifier{},
|
||||
)
|
||||
|
||||
resp, err := refresher.exchangeToken("authorization_code", "auth-code", "https://callback.example.com", "test-verifier")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got: %v", err)
|
||||
}
|
||||
|
||||
if resp.AccessToken != "test-access" {
|
||||
t.Errorf("Expected access token 'test-access', got '%s'", resp.AccessToken)
|
||||
}
|
||||
|
||||
if resp.IDToken != "test-id" {
|
||||
t.Errorf("Expected ID token 'test-id', got '%s'", resp.IDToken)
|
||||
}
|
||||
|
||||
if resp.ExpiresIn != 3600 {
|
||||
t.Errorf("Expected expires_in 3600, got %d", resp.ExpiresIn)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefresher_ExchangeToken_HTTPError(t *testing.T) {
|
||||
// Create a test server that returns an error
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte(`{"error":"invalid_grant"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
refresher := NewRefresher(
|
||||
"client-id",
|
||||
"client-secret",
|
||||
server.URL,
|
||||
&http.Client{},
|
||||
&mockLogger{},
|
||||
&mockMetrics{},
|
||||
&mockSessionManager{},
|
||||
newMockCache(),
|
||||
&mockTokenVerifier{},
|
||||
)
|
||||
|
||||
_, err := refresher.exchangeToken("refresh_token", "bad-token", "", "")
|
||||
if err == nil {
|
||||
t.Error("Expected error for HTTP 401 response")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefresher_ExchangeToken_InvalidJSON(t *testing.T) {
|
||||
// Create a test server that returns invalid JSON
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`{invalid json`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
refresher := NewRefresher(
|
||||
"client-id",
|
||||
"client-secret",
|
||||
server.URL,
|
||||
&http.Client{},
|
||||
&mockLogger{},
|
||||
&mockMetrics{},
|
||||
&mockSessionManager{},
|
||||
newMockCache(),
|
||||
&mockTokenVerifier{},
|
||||
)
|
||||
|
||||
_, err := refresher.exchangeToken("refresh_token", "token", "", "")
|
||||
if err == nil {
|
||||
t.Error("Expected error for invalid JSON response")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefresher_GetNewTokenWithRefreshToken(t *testing.T) {
|
||||
// Create a test server
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`{"access_token":"new-access","refresh_token":"new-refresh","expires_in":3600}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
refresher := NewRefresher(
|
||||
"client-id",
|
||||
"client-secret",
|
||||
server.URL,
|
||||
&http.Client{},
|
||||
&mockLogger{},
|
||||
&mockMetrics{},
|
||||
&mockSessionManager{},
|
||||
newMockCache(),
|
||||
&mockTokenVerifier{},
|
||||
)
|
||||
|
||||
resp, err := refresher.GetNewTokenWithRefreshToken("old-refresh")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got: %v", err)
|
||||
}
|
||||
|
||||
if resp.AccessToken != "new-access" {
|
||||
t.Error("Expected new access token")
|
||||
}
|
||||
|
||||
if resp.RefreshToken != "new-refresh" {
|
||||
t.Error("Expected new refresh token")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user