Fix up the excluded URLs configuration.

This commit is contained in:
2024-10-03 14:00:42 +01:00
parent 23adb28a54
commit e9e1fccf5e
2 changed files with 59 additions and 49 deletions
+54 -41
View File
@@ -59,6 +59,10 @@ type ProviderMetadata struct {
JWKSURL string `json:"jwks_uri"` JWKSURL string `json:"jwks_uri"`
} }
var defaultExcludedURLs = map[string]struct{}{
"/favicon": {},
}
func (t *TraefikOidc) VerifyToken(token string) error { func (t *TraefikOidc) VerifyToken(token string) error {
t.logger.Debugf("Verifying token") t.logger.Debugf("Verifying token")
if !t.limiter.Allow() { if !t.limiter.Allow() {
@@ -188,6 +192,10 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h
}(), }(),
redirectURL: "", redirectURL: "",
} }
// add defaultExcludedURLs to excludedURLs
for k, v := range defaultExcludedURLs {
t.excludedURLs[k] = v
}
t.tokenVerifier = t t.tokenVerifier = t
t.jwtVerifier = t t.jwtVerifier = t
@@ -219,6 +227,7 @@ func discoverProviderMetadata(providerURL string, httpClient http.Client) (*Prov
} }
func (t *TraefikOidc) ServeHTTP(rw http.ResponseWriter, req *http.Request) { func (t *TraefikOidc) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// Check if the URL is excluded first
if t.determineExcludedURL(req.URL.Path) { if t.determineExcludedURL(req.URL.Path) {
t.next.ServeHTTP(rw, req) t.next.ServeHTTP(rw, req)
return return
@@ -237,6 +246,7 @@ func (t *TraefikOidc) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
t.logger.Debugf("Redirect URL updated to: %s", t.redirectURL) t.logger.Debugf("Redirect URL updated to: %s", t.redirectURL)
} }
// Only get or create a session if the URL is not excluded
session, err := t.store.Get(req, cookieName) session, err := t.store.Get(req, cookieName)
if err != nil { if err != nil {
t.logger.Errorf("Error getting session: %v", err) t.logger.Errorf("Error getting session: %v", err)
@@ -255,7 +265,7 @@ func (t *TraefikOidc) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
return return
} }
authenticated, tokenExpired, needsRefresh := t.isUserAuthenticated(session) authenticated, needsRefresh := t.isUserAuthenticated(session)
if authenticated { if authenticated {
if needsRefresh { if needsRefresh {
// Attempt to refresh the token silently // Attempt to refresh the token silently
@@ -269,22 +279,18 @@ func (t *TraefikOidc) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
return return
} }
if tokenExpired { // If the user is not authenticated, initiate authentication
t.logger.Debugf("Token has expired, initiating reauthentication")
t.initiateAuthentication(rw, req, session, t.redirectURL)
return
}
// User is not authenticated, start the auth process
t.initiateAuthentication(rw, req, session, t.redirectURL) t.initiateAuthentication(rw, req, session, t.redirectURL)
} }
func (t *TraefikOidc) determineExcludedURL(currentRequest string) bool { func (t *TraefikOidc) determineExcludedURL(currentRequest string) bool {
for excludedURL := range t.excludedURLs { for excludedURL := range t.excludedURLs {
if strings.HasPrefix(currentRequest, excludedURL) { if strings.HasPrefix(currentRequest, excludedURL) {
t.logger.Debug("URL is excluded - got %s / excluded hit: %s", currentRequest, excludedURL)
return true return true
} }
} }
t.logger.Debug("URL is not excluded - got %s", currentRequest)
return false return false
} }
@@ -308,41 +314,48 @@ func (t *TraefikOidc) determineHost(req *http.Request) string {
return req.Host return req.Host
} }
func (t *TraefikOidc) isUserAuthenticated(session *sessions.Session) (bool, bool, bool) { func (t *TraefikOidc) isUserAuthenticated(session *sessions.Session) (bool, bool) {
authenticated, _ := session.Values["authenticated"].(bool) authenticated, _ := session.Values["authenticated"].(bool)
if authenticated { if !authenticated {
idToken, ok := session.Values["id_token"].(string) return false, false
if !ok || idToken == "" {
return false, false, false
}
claims, err := extractClaims(idToken)
if err != nil {
t.logger.Errorf("Failed to extract claims: %v", err)
return false, false, false
}
exp, ok := claims["exp"].(float64)
if !ok {
t.logger.Errorf("Failed to get expiration time from claims")
return false, false, false
}
now := time.Now().Unix()
expTime := int64(exp)
if now > expTime {
return false, true, false // Token has expired
}
gracePeriod := time.Minute * 5
if time.Now().Add(gracePeriod).Unix() > expTime {
return true, false, true // Token will expire soon, needs refresh
}
return t.verifyToken(idToken) == nil, false, false
} }
return false, false, false
idToken, ok := session.Values["id_token"].(string)
if !ok || idToken == "" {
return false, false
}
// Verify the token
if err := t.verifyToken(idToken); err != nil {
t.logger.Errorf("Token verification failed: %v", err)
return false, false
}
claims, err := extractClaims(idToken)
if err != nil {
t.logger.Errorf("Failed to extract claims: %v", err)
return false, false
}
exp, ok := claims["exp"].(float64)
if !ok {
t.logger.Errorf("Failed to get expiration time from claims")
return false, false
}
now := time.Now().Unix()
expTime := int64(exp)
if now > expTime {
return false, false // Token has expired
}
gracePeriod := time.Minute * 5
if time.Now().Add(gracePeriod).Unix() > expTime {
return true, true // Token will expire soon, needs refresh
}
return true, false
} }
func (t *TraefikOidc) initiateAuthentication(rw http.ResponseWriter, req *http.Request, session *sessions.Session, redirectURL string) { func (t *TraefikOidc) initiateAuthentication(rw http.ResponseWriter, req *http.Request, session *sessions.Session, redirectURL string) {
+5 -8
View File
@@ -409,7 +409,6 @@ func (suite *TraefikOidcTestSuite) TestIsUserAuthenticated() {
name string name string
setupSession func() *sessions.Session setupSession func() *sessions.Session
expectedAuth bool expectedAuth bool
expectedExpired bool
expectedRefresh bool expectedRefresh bool
}{ }{
{ {
@@ -421,7 +420,6 @@ func (suite *TraefikOidcTestSuite) TestIsUserAuthenticated() {
return session return session
}, },
expectedAuth: true, expectedAuth: true,
expectedExpired: false,
expectedRefresh: false, expectedRefresh: false,
}, },
{ {
@@ -433,7 +431,6 @@ func (suite *TraefikOidcTestSuite) TestIsUserAuthenticated() {
return session return session
}, },
expectedAuth: false, expectedAuth: false,
expectedExpired: true,
expectedRefresh: false, expectedRefresh: false,
}, },
{ {
@@ -448,7 +445,6 @@ func (suite *TraefikOidcTestSuite) TestIsUserAuthenticated() {
return session return session
}, },
expectedAuth: true, expectedAuth: true,
expectedExpired: false,
expectedRefresh: true, expectedRefresh: true,
}, },
{ {
@@ -459,7 +455,6 @@ func (suite *TraefikOidcTestSuite) TestIsUserAuthenticated() {
return session return session
}, },
expectedAuth: false, expectedAuth: false,
expectedExpired: false,
expectedRefresh: false, expectedRefresh: false,
}, },
} }
@@ -468,14 +463,12 @@ func (suite *TraefikOidcTestSuite) TestIsUserAuthenticated() {
suite.Run(tc.name, func() { suite.Run(tc.name, func() {
session := tc.setupSession() session := tc.setupSession()
suite.mockTokenVerifier.On("VerifyToken", mock.AnythingOfType("string")).Return(nil).Maybe() suite.mockTokenVerifier.On("VerifyToken", mock.AnythingOfType("string")).Return(nil).Maybe()
authenticated, tokenExpired, needsRefresh := suite.oidc.isUserAuthenticated(session) authenticated, needsRefresh := suite.oidc.isUserAuthenticated(session)
suite.Equal(tc.expectedAuth, authenticated) suite.Equal(tc.expectedAuth, authenticated)
suite.Equal(tc.expectedExpired, tokenExpired)
suite.Equal(tc.expectedRefresh, needsRefresh) suite.Equal(tc.expectedRefresh, needsRefresh)
}) })
} }
} }
func (suite *TraefikOidcTestSuite) TestInitiateAuthentication() { func (suite *TraefikOidcTestSuite) TestInitiateAuthentication() {
req := httptest.NewRequest("GET", "http://example.com", nil) req := httptest.NewRequest("GET", "http://example.com", nil)
rw := httptest.NewRecorder() rw := httptest.NewRecorder()
@@ -523,9 +516,13 @@ func (suite *TraefikOidcTestSuite) TestServeHTTP_ExpiredToken() {
suite.mockStore.On("Get", req, cookieName).Return(session, nil) suite.mockStore.On("Get", req, cookieName).Return(session, nil)
suite.mockStore.On("Save", mock.Anything, mock.Anything, mock.Anything).Return(nil) suite.mockStore.On("Save", mock.Anything, mock.Anything, mock.Anything).Return(nil)
// Set up expectation for VerifyToken
suite.mockTokenVerifier.On("VerifyToken", "expired.eyJleHAiOjF9.signature").Return(fmt.Errorf("token has expired"))
suite.oidc.ServeHTTP(rw, req) suite.oidc.ServeHTTP(rw, req)
suite.Equal(http.StatusFound, rw.Code) // Should redirect to authentication suite.Equal(http.StatusFound, rw.Code) // Should redirect to authentication
suite.mockTokenVerifier.AssertExpectations(suite.T())
} }
func (suite *TraefikOidcTestSuite) TestHandleCallback_InvalidState() { func (suite *TraefikOidcTestSuite) TestHandleCallback_InvalidState() {