iam: Refresh live connection when ID tokens expire (#107209)
* iam: refresh live connection when ID tokens expire * add coverage for the handler functions * reinstate inadvertently broken unit test
This commit is contained in:
@@ -3,6 +3,7 @@ module github.com/grafana/grafana/pkg/apimachinery
|
||||
go 1.24.4
|
||||
|
||||
require (
|
||||
github.com/go-jose/go-jose/v3 v3.0.4 // @grafana/identity-access-team
|
||||
github.com/grafana/authlib v0.0.0-20250618124654-54543efcfeed // @grafana/identity-access-team
|
||||
github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d // @grafana/identity-access-team
|
||||
github.com/stretchr/testify v1.10.0
|
||||
@@ -15,7 +16,6 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
|
||||
@@ -3,7 +3,9 @@ package identity
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-jose/go-jose/v3/jwt"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
|
||||
claims "github.com/grafana/authlib/types"
|
||||
@@ -125,3 +127,31 @@ func intIdentifier(typ claims.IdentityType, id string, expected ...claims.Identi
|
||||
|
||||
return 0, ErrNotIntIdentifier
|
||||
}
|
||||
|
||||
// IsIDTokenExpired returns true if the ID token is expired.
|
||||
// If no ID token exists, returns false.
|
||||
func IsIDTokenExpired(requester Requester) bool {
|
||||
idToken := requester.GetIDToken()
|
||||
if idToken == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
parsed, err := jwt.ParseSigned(idToken)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var claims struct {
|
||||
Expiry *jwt.NumericDate `json:"exp"`
|
||||
}
|
||||
if err := parsed.UnsafeClaimsWithoutVerification(&claims); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if claims.Expiry != nil {
|
||||
expiryTime := claims.Expiry.Time()
|
||||
return time.Now().After(expiryTime)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package identity_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-jose/go-jose/v3"
|
||||
"github.com/go-jose/go-jose/v3/jwt"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
)
|
||||
|
||||
func TestIsIDTokenExpired(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
token func(t *testing.T) string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "should return false when ID token is not set",
|
||||
token: func(t *testing.T) string {
|
||||
return ""
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "should return false when ID token is not expired",
|
||||
token: func(t *testing.T) string {
|
||||
expiration := time.Now().Add(time.Hour)
|
||||
return createToken(t, &expiration)
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "should return true when ID token is expired",
|
||||
token: func(t *testing.T) string {
|
||||
expiration := time.Now().Add(-time.Hour)
|
||||
return createToken(t, &expiration)
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "should return false when ID token has no expiry claim",
|
||||
token: func(t *testing.T) string {
|
||||
return createToken(t, nil)
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "should return false when ID token is malformed",
|
||||
token: func(t *testing.T) string {
|
||||
return "invalid.jwt.token"
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "should handle token that expires exactly now",
|
||||
token: func(t *testing.T) string {
|
||||
expiration := time.Now().Add(-time.Millisecond)
|
||||
return createToken(t, &expiration)
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
token := tt.token(t)
|
||||
requester := &identity.StaticRequester{IDToken: token}
|
||||
|
||||
result := identity.IsIDTokenExpired(requester)
|
||||
require.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createToken(t *testing.T, exp *time.Time) string {
|
||||
key := []byte("test-secret-key")
|
||||
signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.HS256, Key: key}, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
claims := struct {
|
||||
jwt.Claims
|
||||
}{
|
||||
Claims: jwt.Claims{
|
||||
Subject: "test-user",
|
||||
},
|
||||
}
|
||||
|
||||
if exp != nil {
|
||||
claims.Expiry = jwt.NewNumericDate(*exp)
|
||||
}
|
||||
|
||||
token, err := jwt.Signed(signer).Claims(claims).CompactSerialize()
|
||||
require.NoError(t, err)
|
||||
return token
|
||||
}
|
||||
Reference in New Issue
Block a user