Files
traefikoidc/internal/token/refresher_test.go
T
lukaszraczylo e64fc7f730 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>
2025-11-30 02:18:46 +00:00

352 lines
8.2 KiB
Go

//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")
}
}