2022-10-19 00:17:28 +08:00
package oauthtoken
import (
"context"
"testing"
"time"
2025-01-21 17:06:55 +08:00
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"golang.org/x/oauth2"
claims "github.com/grafana/authlib/types"
2024-06-13 12:11:35 +08:00
"github.com/grafana/grafana/pkg/apimachinery/identity"
2024-08-20 00:57:37 +08:00
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/serverlock"
"github.com/grafana/grafana/pkg/infra/tracing"
2024-02-05 23:44:25 +08:00
"github.com/grafana/grafana/pkg/login/social"
2023-12-08 18:20:42 +08:00
"github.com/grafana/grafana/pkg/login/social/socialtest"
2024-11-27 18:06:39 +08:00
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/auth/authtest"
2024-02-05 23:44:25 +08:00
"github.com/grafana/grafana/pkg/services/authn"
2024-11-27 18:06:39 +08:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
2022-10-19 00:17:28 +08:00
"github.com/grafana/grafana/pkg/services/login"
2024-02-05 23:44:25 +08:00
"github.com/grafana/grafana/pkg/services/login/authinfotest"
2022-10-19 00:17:28 +08:00
"github.com/grafana/grafana/pkg/setting"
2024-08-20 00:57:37 +08:00
"github.com/grafana/grafana/pkg/tests/testsuite"
2025-09-08 21:49:49 +08:00
"github.com/grafana/grafana/pkg/util/testutil"
2022-10-19 00:17:28 +08:00
)
2024-11-21 21:36:28 +08:00
const EXPIRED_ID_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoiMTIzNDU2Nzg5MCIsImF1ZCI6InlvdXItY2xpZW50LWlkIiwiZXhwIjoxNjAwMDAwMDAwLCJpYXQiOjE2MDAwMDAwMDAsIm5hbWUiOiJKb2huIERvZSIsImVtYWlsIjoiam9obkBleGFtcGxlLmNvbSJ9.c2lnbmF0dXJl" // #nosec G101 not a hardcoded credential
const UNEXPIRED_ID_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoiMTIzNDU2Nzg5MCIsImF1ZCI6InlvdXItY2xpZW50LWlkIiwiZXhwIjo0ODg1NjA4MDAwLCJpYXQiOjE2ODU2MDgwMDAsIm5hbWUiOiJKb2huIERvZSIsImVtYWlsIjoiam9obkBleGFtcGxlLmNvbSJ9.c2lnbmF0dXJl" // #nosec G101 not a hardcoded credential
2024-02-05 23:44:25 +08:00
2024-08-20 00:57:37 +08:00
func TestMain ( m * testing . M ) {
testsuite . Run ( m )
}
2025-10-07 16:52:43 +08:00
var (
unexpiredTokenWithoutRefresh = & oauth2 . Token {
AccessToken : "testaccess" ,
Expiry : time . Now ( ) . Add ( time . Hour ) ,
TokenType : "Bearer" ,
}
2024-02-05 23:44:25 +08:00
2025-10-07 16:52:43 +08:00
unexpiredTokenWithoutRefreshWithIDToken = unexpiredTokenWithoutRefresh . WithExtra ( map [ string ] interface { } {
"id_token" : UNEXPIRED_ID_TOKEN ,
} )
2024-12-17 00:03:39 +08:00
2025-10-07 16:52:43 +08:00
unexpiredToken = & oauth2 . Token {
2024-11-21 21:36:28 +08:00
AccessToken : "testaccess" ,
RefreshToken : "testrefresh" ,
Expiry : time . Now ( ) . Add ( time . Hour ) ,
TokenType : "Bearer" ,
}
2025-10-07 16:52:43 +08:00
unexpiredTokenWithIDToken = unexpiredToken . WithExtra ( map [ string ] interface { } {
2024-11-21 21:36:28 +08:00
"id_token" : UNEXPIRED_ID_TOKEN ,
} )
2025-10-07 16:52:43 +08:00
expiredToken = & oauth2 . Token {
2024-11-21 21:36:28 +08:00
AccessToken : "testaccess" ,
RefreshToken : "testrefresh" ,
Expiry : time . Now ( ) . Add ( - time . Hour ) ,
TokenType : "Bearer" ,
}
2025-10-07 16:52:43 +08:00
)
2024-11-21 21:36:28 +08:00
2025-10-07 16:52:43 +08:00
type environment struct {
sessionService * authtest . MockUserAuthTokenService
authInfoService * authinfotest . MockAuthInfoService
serverLock * serverlock . ServerLockService
socialConnector * socialtest . MockSocialConnector
socialService * socialtest . FakeSocialService
2024-02-05 23:44:25 +08:00
2025-10-07 16:52:43 +08:00
store db . DB
service * Service
}
func TestIntegration_TryTokenRefresh ( t * testing . T ) {
testutil . SkipIntegrationTestInShortMode ( t )
2024-11-21 21:36:28 +08:00
2024-02-05 23:44:25 +08:00
type testCase struct {
2025-10-07 16:52:43 +08:00
desc string
identity identity . Requester
refreshMetadata * TokenRefreshMetadata
setup func ( env * environment )
expectedToken * oauth2 . Token
expectedErr error
2024-11-21 21:36:28 +08:00
}
userIdentity := & authn . Identity {
AuthenticatedBy : login . GenericOAuthModule ,
ID : "1234" ,
Type : claims . TypeUser ,
2024-02-05 23:44:25 +08:00
}
tests := [ ] testCase {
{
desc : "should skip sync when identity is nil" ,
} ,
{
2024-11-21 21:36:28 +08:00
desc : "should skip sync when identity is not a user" ,
identity : & authn . Identity { ID : "1" , Type : claims . TypeServiceAccount } ,
2024-02-05 23:44:25 +08:00
} ,
{
2024-11-21 21:36:28 +08:00
desc : "should skip token refresh and return nil if namespace and id cannot be converted to user ID" ,
identity : & authn . Identity { ID : "invalid" , Type : claims . TypeUser } ,
2024-02-05 23:44:25 +08:00
} ,
{
2025-10-07 16:52:43 +08:00
desc : "should skip token refresh when no oauth provider was found" ,
identity : userIdentity ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . SAMLAuthModule } ,
2024-02-05 23:44:25 +08:00
} ,
{
2025-10-07 16:52:43 +08:00
desc : "should skip token refresh when oauth provider token handling is disabled (UseRefreshToken is false)" ,
identity : userIdentity ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
2024-02-05 23:44:25 +08:00
setup : func ( env * environment ) {
2025-10-07 16:52:43 +08:00
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : false ,
2024-11-21 21:36:28 +08:00
}
2024-02-05 23:44:25 +08:00
} ,
} ,
{
2025-10-07 16:52:43 +08:00
desc : "should skip token refresh if there's an unexpected error while looking up the user auth entry, additionally, no error should be returned" ,
identity : userIdentity ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
2024-02-05 23:44:25 +08:00
setup : func ( env * environment ) {
2025-10-07 16:52:43 +08:00
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . Anything ) . Return ( nil , assert . AnError ) . Once ( )
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
2024-02-05 23:44:25 +08:00
}
} ,
} ,
{
2025-10-07 16:52:43 +08:00
desc : "should skip token refresh when there is no refresh token and the provider does not require one" ,
identity : userIdentity ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
2024-02-05 23:44:25 +08:00
setup : func ( env * environment ) {
2024-11-21 21:36:28 +08:00
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : false ,
}
2024-02-05 23:44:25 +08:00
} ,
2025-10-07 16:52:43 +08:00
expectedToken : nil ,
2024-02-05 23:44:25 +08:00
} ,
{
2025-10-07 16:52:43 +08:00
desc : "should return error when there is no refresh token and provider requires one" ,
identity : userIdentity ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
2024-02-05 23:44:25 +08:00
setup : func ( env * environment ) {
2025-10-07 16:52:43 +08:00
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . Anything ) . Return ( & login . UserAuth {
2024-11-21 21:36:28 +08:00
AuthModule : login . GenericOAuthModule ,
2025-10-07 16:52:43 +08:00
OAuthAccessToken : expiredToken . AccessToken ,
OAuthRefreshToken : "" ,
OAuthExpiry : expiredToken . Expiry ,
} , nil )
2024-11-21 21:36:28 +08:00
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
2024-02-05 23:44:25 +08:00
}
} ,
2025-10-07 16:52:43 +08:00
expectedToken : nil ,
expectedErr : ErrNoRefreshTokenFound ,
2024-02-05 23:44:25 +08:00
} ,
{
2025-10-07 16:52:43 +08:00
desc : "should skip token refresh when the token is still valid and no id token is present" ,
identity : userIdentity ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
2024-02-05 23:44:25 +08:00
setup : func ( env * environment ) {
2025-10-07 16:52:43 +08:00
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . Anything ) . Return ( & login . UserAuth {
2024-11-21 21:36:28 +08:00
AuthModule : login . GenericOAuthModule ,
OAuthAccessToken : unexpiredTokenWithIDToken . AccessToken ,
OAuthRefreshToken : unexpiredTokenWithIDToken . RefreshToken ,
OAuthExpiry : unexpiredTokenWithIDToken . Expiry ,
OAuthTokenType : unexpiredTokenWithIDToken . TokenType ,
2025-10-07 16:52:43 +08:00
} , nil )
2024-11-21 21:36:28 +08:00
2024-02-05 23:44:25 +08:00
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
2024-11-21 21:36:28 +08:00
UseRefreshToken : true ,
2024-02-05 23:44:25 +08:00
}
} ,
2025-10-07 16:52:43 +08:00
expectedToken : unexpiredToken ,
2024-02-05 23:44:25 +08:00
} ,
{
2025-10-07 16:52:43 +08:00
desc : "should not refresh the tokens if access token or id token have not expired yet" ,
identity : userIdentity ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
2024-02-05 23:44:25 +08:00
setup : func ( env * environment ) {
2025-10-07 16:52:43 +08:00
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . Anything ) . Return ( & login . UserAuth {
2024-02-05 23:44:25 +08:00
AuthModule : login . GenericOAuthModule ,
2025-10-07 16:52:43 +08:00
OAuthIdToken : UNEXPIRED_ID_TOKEN ,
2024-11-21 21:36:28 +08:00
OAuthAccessToken : unexpiredTokenWithIDToken . AccessToken ,
2025-10-07 16:52:43 +08:00
OAuthRefreshToken : unexpiredTokenWithIDToken . RefreshToken ,
2024-11-21 21:36:28 +08:00
OAuthExpiry : unexpiredTokenWithIDToken . Expiry ,
2025-10-07 16:52:43 +08:00
OAuthTokenType : unexpiredTokenWithIDToken . TokenType ,
} , nil )
2024-02-05 23:44:25 +08:00
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
} ,
2025-10-07 16:52:43 +08:00
expectedToken : unexpiredTokenWithIDToken ,
2024-02-05 23:44:25 +08:00
} ,
{
2025-10-07 16:52:43 +08:00
desc : "should do token refresh when the token is expired" ,
identity : userIdentity ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
2024-02-05 23:44:25 +08:00
setup : func ( env * environment ) {
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
2025-10-07 16:52:43 +08:00
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . Anything ) . Return ( & login . UserAuth {
2024-02-05 23:44:25 +08:00
AuthModule : login . GenericOAuthModule ,
AuthId : "subject" ,
UserId : 1 ,
2024-11-21 21:36:28 +08:00
OAuthAccessToken : expiredToken . AccessToken ,
OAuthRefreshToken : expiredToken . RefreshToken ,
OAuthExpiry : expiredToken . Expiry ,
OAuthTokenType : expiredToken . TokenType ,
OAuthIdToken : EXPIRED_ID_TOKEN ,
2025-10-07 16:52:43 +08:00
} , nil )
env . authInfoService . On ( "UpdateAuthInfo" , mock . Anything , mock . MatchedBy ( func ( cmd * login . UpdateAuthInfoCommand ) bool {
return cmd . UserId == 1234 && cmd . AuthModule == login . GenericOAuthModule &&
cmd . OAuthToken . AccessToken == unexpiredTokenWithIDToken . AccessToken &&
cmd . OAuthToken . RefreshToken == unexpiredTokenWithIDToken . RefreshToken &&
cmd . OAuthToken . Expiry . Equal ( unexpiredTokenWithIDToken . Expiry ) &&
cmd . OAuthToken . TokenType == unexpiredTokenWithIDToken . TokenType
} ) ) . Return ( nil ) . Once ( )
2024-12-18 18:27:50 +08:00
env . sessionService . On ( "UpdateExternalSession" , mock . Anything , int64 ( 1 ) , mock . MatchedBy ( verifyUpdateExternalSessionCommand ( unexpiredTokenWithIDToken ) ) ) . Return ( nil ) . Once ( )
2024-11-21 21:36:28 +08:00
env . socialConnector . On ( "TokenSource" , mock . Anything , mock . Anything ) . Return ( oauth2 . StaticTokenSource ( unexpiredTokenWithIDToken ) ) . Once ( )
2024-02-05 23:44:25 +08:00
} ,
2024-11-21 21:36:28 +08:00
expectedToken : unexpiredTokenWithIDToken ,
2024-02-05 23:44:25 +08:00
} ,
{
2025-10-07 16:52:43 +08:00
desc : "should refresh token when the id token is expired" ,
identity : & authn . Identity { ID : "1234" , Type : claims . TypeUser , AuthenticatedBy : login . GenericOAuthModule } ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
2024-02-05 23:44:25 +08:00
setup : func ( env * environment ) {
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
2025-10-07 16:52:43 +08:00
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . Anything ) . Return ( & login . UserAuth {
2024-02-05 23:44:25 +08:00
AuthModule : login . GenericOAuthModule ,
AuthId : "subject" ,
UserId : 1 ,
2024-11-21 21:36:28 +08:00
OAuthAccessToken : unexpiredTokenWithIDToken . AccessToken ,
OAuthRefreshToken : unexpiredTokenWithIDToken . RefreshToken ,
OAuthExpiry : unexpiredTokenWithIDToken . Expiry ,
OAuthTokenType : unexpiredTokenWithIDToken . TokenType ,
OAuthIdToken : EXPIRED_ID_TOKEN ,
2025-10-07 16:52:43 +08:00
} , nil )
env . authInfoService . On ( "UpdateAuthInfo" , mock . Anything , mock . MatchedBy ( func ( cmd * login . UpdateAuthInfoCommand ) bool {
return cmd . UserId == 1234 && cmd . AuthModule == login . GenericOAuthModule &&
cmd . OAuthToken . AccessToken == unexpiredTokenWithIDToken . AccessToken &&
cmd . OAuthToken . RefreshToken == unexpiredTokenWithIDToken . RefreshToken &&
cmd . OAuthToken . Expiry . Equal ( unexpiredTokenWithIDToken . Expiry ) &&
cmd . OAuthToken . TokenType == unexpiredTokenWithIDToken . TokenType
} ) ) . Return ( nil ) . Once ( )
2024-12-18 18:27:50 +08:00
env . sessionService . On ( "UpdateExternalSession" , mock . Anything , int64 ( 1 ) , mock . MatchedBy ( verifyUpdateExternalSessionCommand ( unexpiredTokenWithIDToken ) ) ) . Return ( nil ) . Once ( )
2024-11-21 21:36:28 +08:00
env . socialConnector . On ( "TokenSource" , mock . Anything , mock . Anything ) . Return ( oauth2 . StaticTokenSource ( unexpiredTokenWithIDToken ) ) . Once ( )
2024-02-05 23:44:25 +08:00
} ,
2024-11-21 21:36:28 +08:00
expectedToken : unexpiredTokenWithIDToken ,
2024-02-05 23:44:25 +08:00
} ,
2024-12-17 00:03:39 +08:00
{
2025-10-07 16:52:43 +08:00
desc : "should return ErrRetriesExhausted when lock cannot be acquired" ,
identity : & authn . Identity { ID : "1234" , Type : claims . TypeUser , AuthenticatedBy : login . GenericOAuthModule } ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
2024-12-17 00:03:39 +08:00
setup : func ( env * environment ) {
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
2025-10-07 16:52:43 +08:00
2024-12-17 00:03:39 +08:00
_ = env . store . WithDbSession ( context . Background ( ) , func ( sess * db . Session ) error {
_ , err := sess . Exec ( ` INSERT INTO server_lock (operation_uid, last_execution, version) VALUES (?, ?, ?) ` , "oauth-refresh-token-1234" , time . Now ( ) . Add ( 2 * time . Second ) . Unix ( ) , 0 )
return err
} )
} ,
expectedErr : ErrRetriesExhausted ,
} ,
2025-10-07 16:52:43 +08:00
{
desc : "should be able to refresh token when the caller is render service and the access token is expired" ,
identity : & authn . Identity {
AuthenticatedBy : login . RenderModule ,
ID : "1" ,
Type : claims . TypeUser ,
} ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
setup : func ( env * environment ) {
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . MatchedBy ( func ( query * login . GetAuthInfoQuery ) bool {
return query . UserId == 1
} ) ) . Return ( & login . UserAuth {
AuthModule : login . GenericOAuthModule ,
AuthId : "subject" ,
UserId : 1 ,
OAuthAccessToken : expiredToken . AccessToken ,
OAuthRefreshToken : expiredToken . RefreshToken ,
OAuthExpiry : expiredToken . Expiry ,
OAuthTokenType : expiredToken . TokenType ,
OAuthIdToken : EXPIRED_ID_TOKEN ,
} , nil ) . Once ( )
env . authInfoService . On ( "UpdateAuthInfo" , mock . Anything , mock . MatchedBy ( func ( cmd * login . UpdateAuthInfoCommand ) bool {
return cmd . UserId == 1 && cmd . AuthModule == login . GenericOAuthModule &&
cmd . OAuthToken . AccessToken == unexpiredTokenWithIDToken . AccessToken &&
cmd . OAuthToken . RefreshToken == unexpiredTokenWithIDToken . RefreshToken &&
cmd . OAuthToken . Expiry . Equal ( unexpiredTokenWithIDToken . Expiry ) &&
cmd . OAuthToken . TokenType == unexpiredTokenWithIDToken . TokenType
} ) ) . Return ( nil ) . Once ( )
env . sessionService . On ( "UpdateExternalSession" , mock . Anything , int64 ( 1 ) , mock . MatchedBy ( verifyUpdateExternalSessionCommand ( unexpiredTokenWithIDToken ) ) ) . Return ( nil ) . Once ( )
env . socialConnector . On ( "TokenSource" , mock . Anything , mock . Anything ) . Return ( oauth2 . StaticTokenSource ( unexpiredTokenWithIDToken ) ) . Once ( )
} ,
expectedToken : unexpiredTokenWithIDToken ,
} ,
2024-02-05 23:44:25 +08:00
}
for _ , tt := range tests {
t . Run ( tt . desc , func ( t * testing . T ) {
2024-11-21 21:36:28 +08:00
socialConnector := socialtest . NewMockSocialConnector ( t )
2024-02-05 23:44:25 +08:00
2024-08-20 00:57:37 +08:00
store := db . InitTestDB ( t )
2024-02-05 23:44:25 +08:00
env := environment {
2024-12-18 18:27:50 +08:00
sessionService : authtest . NewMockUserAuthTokenService ( t ) ,
2025-10-07 16:52:43 +08:00
authInfoService : authinfotest . NewMockAuthInfoService ( t ) ,
2024-08-20 00:57:37 +08:00
serverLock : serverlock . ProvideService ( store , tracing . InitializeTracerForTest ( ) ) ,
2024-02-05 23:44:25 +08:00
socialConnector : socialConnector ,
socialService : & socialtest . FakeSocialService {
ExpectedConnector : socialConnector ,
} ,
2024-12-17 00:03:39 +08:00
store : store ,
2024-02-05 23:44:25 +08:00
}
if tt . setup != nil {
tt . setup ( & env )
}
2024-08-20 00:57:37 +08:00
env . service = ProvideService (
env . socialService ,
env . authInfoService ,
setting . NewCfg ( ) ,
prometheus . NewRegistry ( ) ,
env . serverLock ,
tracing . InitializeTracerForTest ( ) ,
2024-12-18 18:27:50 +08:00
env . sessionService ,
2024-11-27 18:06:39 +08:00
featuremgmt . WithFeatures ( ) ,
2024-08-20 00:57:37 +08:00
)
2024-02-05 23:44:25 +08:00
// token refresh
2025-10-07 16:52:43 +08:00
actualToken , err := env . service . TryTokenRefresh ( context . Background ( ) , tt . identity , tt . refreshMetadata )
2024-02-05 23:44:25 +08:00
2024-11-21 21:36:28 +08:00
if tt . expectedErr != nil {
assert . ErrorIs ( t , err , tt . expectedErr )
return
}
if tt . expectedToken == nil {
assert . Nil ( t , actualToken )
return
}
assert . Equal ( t , tt . expectedToken . AccessToken , actualToken . AccessToken )
assert . Equal ( t , tt . expectedToken . RefreshToken , actualToken . RefreshToken )
assert . Equal ( t , tt . expectedToken . Expiry , actualToken . Expiry )
assert . Equal ( t , tt . expectedToken . TokenType , actualToken . TokenType )
if tt . expectedToken . Extra ( "id_token" ) != nil {
assert . Equal ( t , tt . expectedToken . Extra ( "id_token" ) . ( string ) , actualToken . Extra ( "id_token" ) . ( string ) )
} else {
assert . Nil ( t , actualToken . Extra ( "id_token" ) )
}
2024-02-05 23:44:25 +08:00
} )
}
}
2024-12-17 00:03:39 +08:00
func TestIntegration_TryTokenRefresh_WithExternalSessions ( t * testing . T ) {
2025-09-08 21:49:49 +08:00
testutil . SkipIntegrationTestInShortMode ( t )
2024-12-17 00:03:39 +08:00
2024-11-27 18:06:39 +08:00
userIdentity := & authn . Identity {
AuthenticatedBy : login . GenericOAuthModule ,
ID : "1234" ,
Type : claims . TypeUser ,
}
type testCase struct {
2025-10-07 16:52:43 +08:00
desc string
identity identity . Requester
refreshMetadata * TokenRefreshMetadata
setup func ( env * environment )
expectedToken * oauth2 . Token
expectedErr error
2024-11-27 18:06:39 +08:00
}
tests := [ ] testCase {
{
desc : "should skip sync when identity is nil" ,
} ,
{
desc : "should skip sync when identity is not a user" ,
identity : & authn . Identity { ID : "1" , Type : claims . TypeServiceAccount } ,
} ,
{
desc : "should skip token refresh and return nil if namespace and id cannot be converted to user ID" ,
identity : & authn . Identity { ID : "invalid" , Type : claims . TypeUser } ,
} ,
{
2025-10-07 16:52:43 +08:00
desc : "should skip token refresh when no oauth provider was found" ,
identity : userIdentity ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . SAMLAuthModule } ,
} ,
{
desc : "should skip token refresh if there's an unexpected error while looking up the external session entry, additionally, no error should be returned" ,
identity : userIdentity ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
2024-11-27 18:06:39 +08:00
setup : func ( env * environment ) {
env . sessionService . On ( "GetExternalSession" , mock . Anything , int64 ( 1 ) ) . Return ( nil , assert . AnError ) . Once ( )
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
} ,
} ,
2025-10-07 16:52:43 +08:00
// Edge case, can only happen after the feature is enabled and logged in users don't have their external sessions set
2024-11-27 18:06:39 +08:00
{
2025-10-07 16:52:43 +08:00
desc : "should skip token refresh if the user doesn't have an external session" ,
identity : userIdentity ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
2024-11-27 18:06:39 +08:00
setup : func ( env * environment ) {
env . sessionService . On ( "GetExternalSession" , mock . Anything , int64 ( 1 ) ) . Return ( nil , auth . ErrExternalSessionNotFound ) . Once ( )
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
} ,
} ,
{
2025-10-07 16:52:43 +08:00
desc : "should skip token refresh when no oauth provider was found" ,
identity : userIdentity ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
2024-11-27 18:06:39 +08:00
setup : func ( env * environment ) {
env . socialService . ExpectedAuthInfoProvider = nil
} ,
} ,
{
2025-10-07 16:52:43 +08:00
desc : "should skip token refresh when oauth provider token handling is disabled (UseRefreshToken is false)" ,
identity : userIdentity ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
2024-11-27 18:06:39 +08:00
setup : func ( env * environment ) {
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : false ,
}
} ,
} ,
{
2025-10-07 16:52:43 +08:00
desc : "should skip token refresh when the token is still valid and no id token is present" ,
identity : userIdentity ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
2024-11-27 18:06:39 +08:00
setup : func ( env * environment ) {
env . sessionService . On ( "GetExternalSession" , mock . Anything , int64 ( 1 ) ) . Return ( & auth . ExternalSession {
ID : 1 ,
UserID : 1 ,
AccessToken : unexpiredTokenWithIDToken . AccessToken ,
RefreshToken : unexpiredTokenWithIDToken . RefreshToken ,
ExpiresAt : unexpiredTokenWithIDToken . Expiry ,
} , nil ) . Once ( )
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
} ,
expectedToken : unexpiredToken ,
} ,
{
2025-10-07 16:52:43 +08:00
desc : "should skip token refresh when there is no refresh token and the provider does not require one" ,
identity : userIdentity ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
setup : func ( env * environment ) {
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : false ,
}
} ,
expectedToken : nil ,
} ,
{
desc : "should return error when there is no refresh token and provider requires one" ,
identity : userIdentity ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
2024-11-27 18:06:39 +08:00
setup : func ( env * environment ) {
env . sessionService . On ( "GetExternalSession" , mock . Anything , int64 ( 1 ) ) . Return ( & auth . ExternalSession {
ID : 1 ,
UserID : 1 ,
2025-10-07 16:52:43 +08:00
AccessToken : expiredToken . AccessToken ,
RefreshToken : "" ,
ExpiresAt : expiredToken . Expiry ,
2024-11-27 18:06:39 +08:00
} , nil ) . Once ( )
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
} ,
2025-10-07 16:52:43 +08:00
expectedToken : nil ,
expectedErr : ErrNoRefreshTokenFound ,
2024-11-27 18:06:39 +08:00
} ,
{
2025-10-07 16:52:43 +08:00
desc : "should not do token refresh if access token or id token have not expired yet" ,
identity : userIdentity ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
2024-11-27 18:06:39 +08:00
setup : func ( env * environment ) {
env . sessionService . On ( "GetExternalSession" , mock . Anything , int64 ( 1 ) ) . Return ( & auth . ExternalSession {
ID : 1 ,
UserID : 1 ,
AccessToken : unexpiredTokenWithIDToken . AccessToken ,
2025-10-07 16:52:43 +08:00
RefreshToken : unexpiredTokenWithIDToken . RefreshToken ,
2024-11-27 18:06:39 +08:00
ExpiresAt : unexpiredTokenWithIDToken . Expiry ,
2025-10-07 16:52:43 +08:00
IDToken : UNEXPIRED_ID_TOKEN ,
2024-11-27 18:06:39 +08:00
} , nil ) . Once ( )
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
} ,
2025-10-07 16:52:43 +08:00
expectedToken : unexpiredTokenWithIDToken ,
2024-11-27 18:06:39 +08:00
} ,
{
2025-10-07 16:52:43 +08:00
desc : "should refresh token when the access token is expired" ,
identity : userIdentity ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
2024-11-27 18:06:39 +08:00
setup : func ( env * environment ) {
env . sessionService . On ( "GetExternalSession" , mock . Anything , int64 ( 1 ) ) . Return ( & auth . ExternalSession {
ID : 1 ,
UserID : 1 ,
AccessToken : expiredToken . AccessToken ,
RefreshToken : expiredToken . RefreshToken ,
ExpiresAt : expiredToken . Expiry ,
2025-10-07 16:52:43 +08:00
IDToken : UNEXPIRED_ID_TOKEN ,
2024-11-27 18:06:39 +08:00
} , nil ) . Once ( )
env . sessionService . On ( "UpdateExternalSession" , mock . Anything , int64 ( 1 ) , mock . MatchedBy ( verifyUpdateExternalSessionCommand ( unexpiredTokenWithIDToken ) ) ) . Return ( nil ) . Once ( )
env . socialConnector . On ( "TokenSource" , mock . Anything , mock . Anything ) . Return ( oauth2 . StaticTokenSource ( unexpiredTokenWithIDToken ) ) . Once ( )
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
} ,
expectedToken : unexpiredTokenWithIDToken ,
} ,
{
2025-10-07 16:52:43 +08:00
desc : "should refresh token when the id token is expired" ,
identity : userIdentity ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
2024-11-27 18:06:39 +08:00
setup : func ( env * environment ) {
env . sessionService . On ( "GetExternalSession" , mock . Anything , int64 ( 1 ) ) . Return ( & auth . ExternalSession {
ID : 1 ,
2025-10-07 16:52:43 +08:00
UserID : 1234 ,
2024-11-27 18:06:39 +08:00
AccessToken : unexpiredTokenWithIDToken . AccessToken ,
RefreshToken : unexpiredTokenWithIDToken . RefreshToken ,
ExpiresAt : unexpiredTokenWithIDToken . Expiry ,
IDToken : EXPIRED_ID_TOKEN ,
} , nil ) . Once ( )
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
env . sessionService . On ( "UpdateExternalSession" , mock . Anything , int64 ( 1 ) , mock . MatchedBy ( verifyUpdateExternalSessionCommand ( unexpiredTokenWithIDToken ) ) ) . Return ( nil ) . Once ( )
env . socialConnector . On ( "TokenSource" , mock . Anything , mock . Anything ) . Return ( oauth2 . StaticTokenSource ( unexpiredTokenWithIDToken ) ) . Once ( )
} ,
expectedToken : unexpiredTokenWithIDToken ,
} ,
2024-12-17 00:03:39 +08:00
{
2025-10-07 16:52:43 +08:00
desc : "should be able to refresh token when the caller is render service and the access token is expired" ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
identity : & authn . Identity {
AuthenticatedBy : login . RenderModule ,
ID : "1" ,
Type : claims . TypeUser ,
} ,
setup : func ( env * environment ) {
env . sessionService . On ( "GetExternalSession" , mock . Anything , int64 ( 1 ) ) . Return ( & auth . ExternalSession {
ID : 1 ,
UserID : 1 ,
AuthModule : login . RenderModule ,
AccessToken : expiredToken . AccessToken ,
RefreshToken : expiredToken . RefreshToken ,
ExpiresAt : expiredToken . Expiry ,
IDToken : UNEXPIRED_ID_TOKEN ,
} , nil ) . Once ( )
env . sessionService . On ( "UpdateExternalSession" , mock . Anything , int64 ( 1 ) , mock . MatchedBy ( verifyUpdateExternalSessionCommand ( unexpiredTokenWithIDToken ) ) ) . Return ( nil ) . Once ( )
env . socialConnector . On ( "TokenSource" , mock . Anything , mock . Anything ) . Return ( oauth2 . StaticTokenSource ( unexpiredTokenWithIDToken ) ) . Once ( )
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
} ,
expectedToken : unexpiredTokenWithIDToken ,
} ,
{
desc : "should return ErrRetriesExhausted when lock cannot be acquired" ,
identity : & authn . Identity { ID : "1234" , Type : claims . TypeUser , AuthenticatedBy : login . GenericOAuthModule } ,
refreshMetadata : & TokenRefreshMetadata { ExternalSessionID : 1 , AuthModule : login . GenericOAuthModule } ,
2024-12-17 00:03:39 +08:00
setup : func ( env * environment ) {
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
_ = env . store . WithDbSession ( context . Background ( ) , func ( sess * db . Session ) error {
_ , err := sess . Exec ( ` INSERT INTO server_lock (operation_uid, last_execution, version) VALUES (?, ?, ?) ` , "oauth-refresh-token-1234-1" , time . Now ( ) . Add ( 2 * time . Second ) . Unix ( ) , 0 )
return err
} )
} ,
expectedErr : ErrRetriesExhausted ,
} ,
2024-11-27 18:06:39 +08:00
}
for _ , tt := range tests {
t . Run ( tt . desc , func ( t * testing . T ) {
socialConnector := socialtest . NewMockSocialConnector ( t )
store := db . InitTestDB ( t )
env := environment {
sessionService : authtest . NewMockUserAuthTokenService ( t ) ,
2025-10-07 16:52:43 +08:00
authInfoService : authinfotest . NewMockAuthInfoService ( t ) ,
2024-11-27 18:06:39 +08:00
serverLock : serverlock . ProvideService ( store , tracing . InitializeTracerForTest ( ) ) ,
socialConnector : socialConnector ,
socialService : & socialtest . FakeSocialService {
ExpectedConnector : socialConnector ,
} ,
2024-12-17 00:03:39 +08:00
store : store ,
2024-11-27 18:06:39 +08:00
}
if tt . setup != nil {
tt . setup ( & env )
}
env . service = ProvideService (
env . socialService ,
2025-10-07 16:52:43 +08:00
env . authInfoService ,
2024-11-27 18:06:39 +08:00
setting . NewCfg ( ) ,
prometheus . NewRegistry ( ) ,
env . serverLock ,
tracing . InitializeTracerForTest ( ) ,
env . sessionService ,
featuremgmt . WithFeatures ( featuremgmt . FlagImprovedExternalSessionHandling ) ,
)
// token refresh
2025-10-07 16:52:43 +08:00
actualToken , err := env . service . TryTokenRefresh ( context . Background ( ) , tt . identity , tt . refreshMetadata )
2024-11-27 18:06:39 +08:00
if tt . expectedErr != nil {
assert . ErrorIs ( t , err , tt . expectedErr )
return
}
assert . NoError ( t , err )
if tt . expectedToken == nil {
assert . Nil ( t , actualToken )
return
}
assert . Equal ( t , tt . expectedToken . AccessToken , actualToken . AccessToken )
assert . Equal ( t , tt . expectedToken . RefreshToken , actualToken . RefreshToken )
assert . Equal ( t , tt . expectedToken . Expiry , actualToken . Expiry )
if tt . expectedToken . Extra ( "id_token" ) != nil {
assert . Equal ( t , tt . expectedToken . Extra ( "id_token" ) . ( string ) , actualToken . Extra ( "id_token" ) . ( string ) )
} else {
assert . Nil ( t , actualToken . Extra ( "id_token" ) )
}
} )
}
}
func verifyUpdateExternalSessionCommand ( token * oauth2 . Token ) func ( * auth . UpdateExternalSessionCommand ) bool {
return func ( cmd * auth . UpdateExternalSessionCommand ) bool {
idToken := cmd . Token . Extra ( "id_token" )
return cmd . Token . AccessToken == token . AccessToken &&
cmd . Token . RefreshToken == token . RefreshToken &&
2025-04-10 20:42:23 +08:00
cmd . Token . Expiry . Equal ( token . Expiry ) &&
2024-11-27 18:06:39 +08:00
idToken == token . Extra ( "id_token" )
}
}
2024-02-05 23:44:25 +08:00
func TestOAuthTokenSync_needTokenRefresh ( t * testing . T ) {
tests := [ ] struct {
name string
2025-10-07 16:52:43 +08:00
token * oauth2 . Token
2024-02-05 23:44:25 +08:00
expectedTokenRefreshFlag bool
expectedTokenDuration time . Duration
} {
{
2025-10-07 16:52:43 +08:00
name : "should not need token refresh when token has no expiration date" ,
token : & oauth2 . Token {
AccessToken : "some_access_token" ,
Expiry : time . Time { } ,
} ,
2024-02-05 23:44:25 +08:00
expectedTokenRefreshFlag : false ,
} ,
{
name : "should not need token refresh with an invalid jwt token that might result in an error when parsing" ,
2025-10-07 16:52:43 +08:00
token : ( & oauth2 . Token {
AccessToken : "some_access_token" ,
} ) . WithExtra ( map [ string ] any { "id_token" : "invalid_jwt_format" } ) ,
2024-02-05 23:44:25 +08:00
expectedTokenRefreshFlag : false ,
} ,
{
2025-10-07 16:52:43 +08:00
name : "should flag token refresh when access token is empty" ,
token : & oauth2 . Token {
AccessToken : "" ,
2024-02-05 23:44:25 +08:00
} ,
expectedTokenRefreshFlag : true ,
2025-10-07 16:52:43 +08:00
} ,
{
name : "should flag token refresh with id token is expired" ,
token : ( & oauth2 . Token {
AccessToken : "some_access_token" } ) . WithExtra ( map [ string ] any { "id_token" : EXPIRED_ID_TOKEN } ) ,
expectedTokenRefreshFlag : true ,
2024-02-05 23:44:25 +08:00
expectedTokenDuration : time . Second ,
} ,
{
name : "should flag token refresh when expiry date is zero" ,
2025-10-07 16:52:43 +08:00
token : & oauth2 . Token {
AccessToken : "some_access_token" ,
Expiry : time . Unix ( 0 , 0 ) ,
2024-02-05 23:44:25 +08:00
} ,
expectedTokenRefreshFlag : true ,
expectedTokenDuration : time . Second ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
2025-10-07 16:52:43 +08:00
needsTokenRefresh := needTokenRefresh ( context . Background ( ) , tt . token )
2024-02-05 23:44:25 +08:00
assert . Equal ( t , tt . expectedTokenRefreshFlag , needsTokenRefresh )
} )
}
}
2025-10-07 16:52:43 +08:00
func TestIntegration_GetCurrentOAuthToken ( t * testing . T ) {
testutil . SkipIntegrationTestInShortMode ( t )
type testCase struct {
desc string
identity identity . Requester
sessionToken * auth . UserToken
setup func ( env * environment )
expectedToken * oauth2 . Token
}
userIdentity := & authn . Identity {
AuthenticatedBy : login . GenericOAuthModule ,
ID : "1234" ,
Type : claims . TypeUser ,
}
tests := [ ] testCase {
{
desc : "should return nil when identity is nil" ,
identity : nil ,
expectedToken : nil ,
} ,
{
desc : "should return nil when identity is not a user" ,
identity : & authn . Identity { ID : "1" , Type : claims . TypeServiceAccount } ,
expectedToken : nil ,
} ,
{
desc : "should refresh token for render service user" ,
identity : & authn . Identity { ID : "1" , Type : claims . TypeUser , AuthenticatedBy : login . RenderModule } ,
setup : func ( env * environment ) {
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . Anything ) . Return ( & login . UserAuth {
AuthModule : login . GenericOAuthModule ,
AuthId : "subject" ,
UserId : 1 ,
OAuthAccessToken : expiredToken . AccessToken ,
OAuthRefreshToken : expiredToken . RefreshToken ,
OAuthExpiry : expiredToken . Expiry ,
OAuthTokenType : expiredToken . TokenType ,
OAuthIdToken : EXPIRED_ID_TOKEN ,
} , nil )
env . sessionService . On ( "FindExternalSessions" , mock . Anything , & auth . ListExternalSessionQuery { UserID : 1 } ) . Return ( [ ] * auth . ExternalSession {
{
ID : 1 ,
UserID : 1 ,
AuthModule : login . GenericOAuthModule ,
AccessToken : expiredToken . AccessToken ,
RefreshToken : expiredToken . RefreshToken ,
ExpiresAt : expiredToken . Expiry ,
IDToken : EXPIRED_ID_TOKEN ,
} ,
} , nil ) . Once ( )
env . authInfoService . On ( "UpdateAuthInfo" , mock . Anything , mock . MatchedBy ( func ( cmd * login . UpdateAuthInfoCommand ) bool {
return cmd . UserId == 1 && cmd . AuthModule == login . GenericOAuthModule &&
cmd . OAuthToken . AccessToken == unexpiredTokenWithIDToken . AccessToken &&
cmd . OAuthToken . RefreshToken == unexpiredTokenWithIDToken . RefreshToken &&
cmd . OAuthToken . Expiry . Equal ( unexpiredTokenWithIDToken . Expiry ) &&
cmd . OAuthToken . TokenType == unexpiredTokenWithIDToken . TokenType
} ) ) . Return ( nil ) . Once ( )
env . sessionService . On ( "UpdateExternalSession" , mock . Anything , int64 ( 1 ) , mock . MatchedBy ( verifyUpdateExternalSessionCommand ( unexpiredTokenWithIDToken ) ) ) . Return ( nil ) . Once ( )
env . socialConnector . On ( "TokenSource" , mock . Anything , mock . Anything ) . Return ( oauth2 . StaticTokenSource ( unexpiredTokenWithIDToken ) ) . Once ( )
} ,
expectedToken : unexpiredTokenWithIDToken ,
} ,
{
desc : "should refresh token for render service user with multiple external sessions" ,
identity : & authn . Identity { ID : "1" , Type : claims . TypeUser , AuthenticatedBy : login . RenderModule } ,
setup : func ( env * environment ) {
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . Anything ) . Return ( & login . UserAuth {
AuthModule : login . GenericOAuthModule ,
AuthId : "subject" ,
UserId : 1 ,
OAuthAccessToken : expiredToken . AccessToken ,
OAuthRefreshToken : expiredToken . RefreshToken ,
OAuthExpiry : expiredToken . Expiry ,
OAuthTokenType : expiredToken . TokenType ,
OAuthIdToken : EXPIRED_ID_TOKEN ,
} , nil )
// Return multiple external sessions, the most recent one is returned first by the query
env . sessionService . On ( "FindExternalSessions" , mock . Anything , & auth . ListExternalSessionQuery { UserID : 1 } ) . Return ( [ ] * auth . ExternalSession {
{
ID : 2 , // newer session
UserID : 1 ,
AuthModule : login . GenericOAuthModule ,
AccessToken : expiredToken . AccessToken ,
RefreshToken : expiredToken . RefreshToken ,
ExpiresAt : expiredToken . Expiry ,
IDToken : EXPIRED_ID_TOKEN ,
} ,
{
ID : 1 , // older session
UserID : 1 ,
AuthModule : login . GenericOAuthModule ,
} } , nil ) . Once ( )
env . authInfoService . On ( "UpdateAuthInfo" , mock . Anything , mock . MatchedBy ( func ( cmd * login . UpdateAuthInfoCommand ) bool {
return cmd . UserId == 1 && cmd . AuthModule == login . GenericOAuthModule &&
cmd . OAuthToken . AccessToken == unexpiredTokenWithIDToken . AccessToken &&
cmd . OAuthToken . RefreshToken == unexpiredTokenWithIDToken . RefreshToken &&
cmd . OAuthToken . Expiry . Equal ( unexpiredTokenWithIDToken . Expiry ) &&
cmd . OAuthToken . TokenType == unexpiredTokenWithIDToken . TokenType
} ) ) . Return ( nil ) . Once ( )
env . sessionService . On ( "UpdateExternalSession" , mock . Anything , int64 ( 2 ) , mock . MatchedBy ( verifyUpdateExternalSessionCommand ( unexpiredTokenWithIDToken ) ) ) . Return ( nil ) . Once ( )
env . socialConnector . On ( "TokenSource" , mock . Anything , mock . Anything ) . Return ( oauth2 . StaticTokenSource ( unexpiredTokenWithIDToken ) ) . Once ( )
} ,
expectedToken : unexpiredTokenWithIDToken ,
} ,
{
desc : "should skip token refresh when the token is still valid and no id token is present" ,
identity : userIdentity ,
sessionToken : & auth . UserToken { ExternalSessionId : 1 } ,
setup : func ( env * environment ) {
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . Anything ) . Return ( & login . UserAuth {
AuthModule : login . GenericOAuthModule ,
OAuthAccessToken : unexpiredToken . AccessToken ,
OAuthRefreshToken : unexpiredToken . RefreshToken ,
OAuthExpiry : unexpiredToken . Expiry ,
OAuthTokenType : unexpiredToken . TokenType ,
} , nil )
env . sessionService . On ( "GetExternalSession" , mock . Anything , int64 ( 1 ) ) . Return ( & auth . ExternalSession {
ID : 1 ,
UserID : 1234 ,
AuthModule : login . GenericOAuthModule ,
AccessToken : unexpiredToken . AccessToken ,
RefreshToken : unexpiredToken . RefreshToken ,
ExpiresAt : unexpiredToken . Expiry ,
} , nil ) . Once ( )
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
} ,
expectedToken : unexpiredToken ,
} ,
{
desc : "should not do token refresh if access token or id token have not expired yet" ,
identity : userIdentity ,
sessionToken : & auth . UserToken { ExternalSessionId : 1 } ,
setup : func ( env * environment ) {
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . Anything ) . Return ( & login . UserAuth {
AuthModule : login . GenericOAuthModule ,
OAuthIdToken : UNEXPIRED_ID_TOKEN ,
OAuthAccessToken : unexpiredTokenWithIDToken . AccessToken ,
OAuthRefreshToken : unexpiredTokenWithIDToken . RefreshToken ,
OAuthExpiry : unexpiredTokenWithIDToken . Expiry ,
OAuthTokenType : unexpiredTokenWithIDToken . TokenType ,
} , nil )
env . sessionService . On ( "GetExternalSession" , mock . Anything , int64 ( 1 ) ) . Return ( & auth . ExternalSession {
ID : 1 ,
UserID : 1234 ,
AuthModule : login . GenericOAuthModule ,
AccessToken : unexpiredToken . AccessToken ,
RefreshToken : unexpiredToken . RefreshToken ,
ExpiresAt : unexpiredToken . Expiry ,
} , nil ) . Once ( )
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
} ,
expectedToken : unexpiredTokenWithIDToken ,
} ,
{
desc : "should return the unexpired access and id token when token refresh is disabled" ,
identity : userIdentity ,
sessionToken : & auth . UserToken { ExternalSessionId : 1 } ,
setup : func ( env * environment ) {
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . Anything ) . Return ( & login . UserAuth {
AuthModule : login . GenericOAuthModule ,
OAuthIdToken : UNEXPIRED_ID_TOKEN ,
OAuthAccessToken : unexpiredTokenWithIDToken . AccessToken ,
OAuthExpiry : unexpiredTokenWithIDToken . Expiry ,
OAuthTokenType : unexpiredTokenWithIDToken . TokenType ,
} , nil )
env . sessionService . On ( "GetExternalSession" , mock . Anything , int64 ( 1 ) ) . Return ( & auth . ExternalSession {
ID : 1 ,
UserID : 1234 ,
AuthModule : login . GenericOAuthModule ,
AccessToken : unexpiredToken . AccessToken ,
ExpiresAt : unexpiredToken . Expiry ,
} , nil ) . Once ( )
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : false ,
}
} ,
expectedToken : unexpiredTokenWithoutRefreshWithIDToken ,
} ,
// Edge case, can only happen after the feature is enabled and logged in users don't have their external sessions set,
{
desc : "should refresh token when the access token is expired and the external session was not found" ,
identity : userIdentity ,
sessionToken : & auth . UserToken { ExternalSessionId : 1 } ,
setup : func ( env * environment ) {
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . Anything ) . Return ( & login . UserAuth {
AuthModule : login . GenericOAuthModule ,
AuthId : "subject" ,
UserId : 1234 ,
OAuthAccessToken : expiredToken . AccessToken ,
OAuthRefreshToken : expiredToken . RefreshToken ,
OAuthExpiry : expiredToken . Expiry ,
OAuthTokenType : expiredToken . TokenType ,
OAuthIdToken : EXPIRED_ID_TOKEN ,
} , nil )
env . sessionService . On ( "GetExternalSession" , mock . Anything , int64 ( 1 ) ) . Return ( nil , auth . ErrExternalSessionNotFound ) . Once ( )
env . authInfoService . On ( "UpdateAuthInfo" , mock . Anything , mock . MatchedBy ( func ( cmd * login . UpdateAuthInfoCommand ) bool {
return cmd . UserId == 1234 && cmd . AuthModule == login . GenericOAuthModule &&
cmd . OAuthToken . AccessToken == unexpiredTokenWithIDToken . AccessToken &&
cmd . OAuthToken . RefreshToken == unexpiredTokenWithIDToken . RefreshToken &&
cmd . OAuthToken . Expiry . Equal ( unexpiredTokenWithIDToken . Expiry ) &&
cmd . OAuthToken . TokenType == unexpiredTokenWithIDToken . TokenType
} ) ) . Return ( nil ) . Once ( )
env . socialConnector . On ( "TokenSource" , mock . Anything , mock . Anything ) . Return ( oauth2 . StaticTokenSource ( unexpiredTokenWithIDToken ) ) . Once ( )
} ,
expectedToken : unexpiredTokenWithIDToken ,
} ,
{
desc : "should refresh token when the access token is expired" ,
identity : userIdentity ,
sessionToken : & auth . UserToken { ExternalSessionId : 1 } ,
setup : func ( env * environment ) {
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . Anything ) . Return ( & login . UserAuth {
AuthModule : login . GenericOAuthModule ,
AuthId : "subject" ,
UserId : 1234 ,
OAuthAccessToken : expiredToken . AccessToken ,
OAuthRefreshToken : expiredToken . RefreshToken ,
OAuthExpiry : expiredToken . Expiry ,
OAuthTokenType : expiredToken . TokenType ,
OAuthIdToken : EXPIRED_ID_TOKEN ,
} , nil )
env . sessionService . On ( "GetExternalSession" , mock . Anything , int64 ( 1 ) ) . Return ( & auth . ExternalSession {
ID : 1 ,
UserID : 1234 ,
AccessToken : expiredToken . AccessToken ,
RefreshToken : expiredToken . RefreshToken ,
ExpiresAt : expiredToken . Expiry ,
IDToken : UNEXPIRED_ID_TOKEN ,
} , nil ) . Once ( )
env . authInfoService . On ( "UpdateAuthInfo" , mock . Anything , mock . MatchedBy ( func ( cmd * login . UpdateAuthInfoCommand ) bool {
return cmd . UserId == 1234 && cmd . AuthModule == login . GenericOAuthModule &&
cmd . OAuthToken . AccessToken == unexpiredTokenWithIDToken . AccessToken &&
cmd . OAuthToken . RefreshToken == unexpiredTokenWithIDToken . RefreshToken &&
cmd . OAuthToken . Expiry . Equal ( unexpiredTokenWithIDToken . Expiry ) &&
cmd . OAuthToken . TokenType == unexpiredTokenWithIDToken . TokenType
} ) ) . Return ( nil ) . Once ( )
env . sessionService . On ( "UpdateExternalSession" , mock . Anything , int64 ( 1 ) , mock . MatchedBy ( verifyUpdateExternalSessionCommand ( unexpiredTokenWithIDToken ) ) ) . Return ( nil ) . Once ( )
env . socialConnector . On ( "TokenSource" , mock . Anything , mock . Anything ) . Return ( oauth2 . StaticTokenSource ( unexpiredTokenWithIDToken ) ) . Once ( )
} ,
expectedToken : unexpiredTokenWithIDToken ,
} ,
{
desc : "should refresh token when the id token is expired" ,
identity : userIdentity ,
sessionToken : & auth . UserToken { ExternalSessionId : 1 } ,
setup : func ( env * environment ) {
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . Anything ) . Return ( & login . UserAuth {
AuthModule : login . GenericOAuthModule ,
AuthId : "subject" ,
UserId : 1234 ,
OAuthAccessToken : unexpiredTokenWithIDToken . AccessToken ,
OAuthRefreshToken : unexpiredTokenWithIDToken . RefreshToken ,
OAuthExpiry : unexpiredTokenWithIDToken . Expiry ,
OAuthTokenType : unexpiredTokenWithIDToken . TokenType ,
OAuthIdToken : EXPIRED_ID_TOKEN ,
} , nil )
env . sessionService . On ( "GetExternalSession" , mock . Anything , int64 ( 1 ) ) . Return ( & auth . ExternalSession {
ID : 1 ,
UserID : 1234 ,
AuthModule : login . GenericOAuthModule ,
AccessToken : unexpiredToken . AccessToken ,
RefreshToken : unexpiredToken . RefreshToken ,
ExpiresAt : unexpiredToken . Expiry ,
IDToken : EXPIRED_ID_TOKEN ,
} , nil ) . Once ( )
env . authInfoService . On ( "UpdateAuthInfo" , mock . Anything , mock . MatchedBy ( func ( cmd * login . UpdateAuthInfoCommand ) bool {
return cmd . UserId == 1234 && cmd . AuthModule == login . GenericOAuthModule &&
cmd . OAuthToken . AccessToken == unexpiredTokenWithIDToken . AccessToken &&
cmd . OAuthToken . RefreshToken == unexpiredTokenWithIDToken . RefreshToken &&
cmd . OAuthToken . Expiry . Equal ( unexpiredTokenWithIDToken . Expiry ) &&
cmd . OAuthToken . TokenType == unexpiredTokenWithIDToken . TokenType
} ) ) . Return ( nil ) . Once ( )
env . sessionService . On ( "UpdateExternalSession" , mock . Anything , int64 ( 1 ) , mock . MatchedBy ( verifyUpdateExternalSessionCommand ( unexpiredTokenWithIDToken ) ) ) . Return ( nil ) . Once ( )
env . socialConnector . On ( "TokenSource" , mock . Anything , mock . Anything ) . Return ( oauth2 . StaticTokenSource ( unexpiredTokenWithIDToken ) ) . Once ( )
} ,
expectedToken : unexpiredTokenWithIDToken ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . desc , func ( t * testing . T ) {
socialConnector := socialtest . NewMockSocialConnector ( t )
store := db . InitTestDB ( t )
features := featuremgmt . WithFeatures ( )
env := environment {
sessionService : authtest . NewMockUserAuthTokenService ( t ) ,
authInfoService : authinfotest . NewMockAuthInfoService ( t ) ,
serverLock : serverlock . ProvideService ( store , tracing . InitializeTracerForTest ( ) ) ,
socialConnector : socialConnector ,
socialService : & socialtest . FakeSocialService {
ExpectedConnector : socialConnector ,
} ,
store : store ,
}
if tt . setup != nil {
tt . setup ( & env )
}
env . service = ProvideService (
env . socialService ,
env . authInfoService ,
setting . NewCfg ( ) ,
prometheus . NewRegistry ( ) ,
env . serverLock ,
tracing . InitializeTracerForTest ( ) ,
env . sessionService ,
features ,
)
actualToken := env . service . GetCurrentOAuthToken ( context . Background ( ) , tt . identity , tt . sessionToken )
if tt . expectedToken == nil {
assert . Nil ( t , actualToken )
return
}
assert . NotNil ( t , actualToken )
assert . Equal ( t , tt . expectedToken . AccessToken , actualToken . AccessToken )
assert . Equal ( t , tt . expectedToken . RefreshToken , actualToken . RefreshToken )
assert . WithinDuration ( t , tt . expectedToken . Expiry , actualToken . Expiry , time . Second )
assert . Equal ( t , tt . expectedToken . TokenType , actualToken . TokenType )
if tt . expectedToken . Extra ( "id_token" ) != nil {
assert . Equal ( t , tt . expectedToken . Extra ( "id_token" ) , actualToken . Extra ( "id_token" ) )
} else {
assert . Nil ( t , actualToken . Extra ( "id_token" ) )
}
} )
}
}
func TestIntegration_GetCurrentOAuthToken_WithExternalSessions ( t * testing . T ) {
testutil . SkipIntegrationTestInShortMode ( t )
type testCase struct {
desc string
identity identity . Requester
sessionToken * auth . UserToken
setup func ( env * environment )
expectedToken * oauth2 . Token
}
userIdentity := & authn . Identity {
AuthenticatedBy : login . GenericOAuthModule ,
ID : "1234" ,
Type : claims . TypeUser ,
}
tests := [ ] testCase {
{
desc : "should return nil when identity is nil" ,
identity : nil ,
expectedToken : nil ,
} ,
{
desc : "should return nil when identity is not a user" ,
identity : & authn . Identity { ID : "1" , Type : claims . TypeServiceAccount } ,
expectedToken : nil ,
} ,
{
desc : "should refresh token for render service user" ,
identity : & authn . Identity { ID : "1" , Type : claims . TypeUser , AuthenticatedBy : login . RenderModule } ,
setup : func ( env * environment ) {
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . Anything ) . Return ( & login . UserAuth {
AuthModule : login . GenericOAuthModule ,
} , nil )
env . sessionService . On ( "GetExternalSession" , mock . Anything , int64 ( 3 ) ) . Return ( & auth . ExternalSession {
ID : 3 ,
UserID : 1 ,
AuthModule : login . GenericOAuthModule ,
AccessToken : expiredToken . AccessToken ,
RefreshToken : expiredToken . RefreshToken ,
ExpiresAt : expiredToken . Expiry ,
IDToken : EXPIRED_ID_TOKEN ,
} , nil ) . Once ( )
env . sessionService . On ( "FindExternalSessions" , mock . Anything , & auth . ListExternalSessionQuery { UserID : 1 } ) . Return ( [ ] * auth . ExternalSession {
{
ID : 3 ,
UserID : 1 ,
AuthModule : login . GenericOAuthModule ,
AccessToken : expiredToken . AccessToken ,
RefreshToken : expiredToken . RefreshToken ,
ExpiresAt : expiredToken . Expiry ,
IDToken : EXPIRED_ID_TOKEN ,
} ,
} , nil ) . Once ( )
env . sessionService . On ( "UpdateExternalSession" , mock . Anything , int64 ( 3 ) , mock . MatchedBy ( verifyUpdateExternalSessionCommand ( unexpiredTokenWithIDToken ) ) ) . Return ( nil ) . Once ( )
env . socialConnector . On ( "TokenSource" , mock . Anything , mock . Anything ) . Return ( oauth2 . StaticTokenSource ( unexpiredTokenWithIDToken ) ) . Once ( )
} ,
expectedToken : unexpiredTokenWithIDToken ,
} ,
{
desc : "should refresh token for render service user with multiple external sessions" ,
identity : & authn . Identity { ID : "1" , Type : claims . TypeUser , AuthenticatedBy : login . RenderModule } ,
setup : func ( env * environment ) {
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . Anything ) . Return ( & login . UserAuth {
AuthModule : login . GenericOAuthModule ,
AuthId : "subject" ,
UserId : 1 ,
OAuthAccessToken : expiredToken . AccessToken ,
OAuthRefreshToken : expiredToken . RefreshToken ,
OAuthExpiry : expiredToken . Expiry ,
OAuthTokenType : expiredToken . TokenType ,
OAuthIdToken : EXPIRED_ID_TOKEN ,
} , nil )
// Return multiple external sessions, the most recent one is returned first by the query
env . sessionService . On ( "FindExternalSessions" , mock . Anything , & auth . ListExternalSessionQuery { UserID : 1 } ) . Return ( [ ] * auth . ExternalSession {
{
ID : 2 , // newer session
UserID : 1 ,
AuthModule : login . GenericOAuthModule ,
AccessToken : expiredToken . AccessToken ,
RefreshToken : expiredToken . RefreshToken ,
ExpiresAt : expiredToken . Expiry ,
IDToken : EXPIRED_ID_TOKEN ,
} ,
{
ID : 1 , // older session
UserID : 1 ,
AuthModule : login . GenericOAuthModule ,
} } , nil ) . Once ( )
env . sessionService . On ( "GetExternalSession" , mock . Anything , int64 ( 2 ) ) . Return ( & auth . ExternalSession {
ID : 2 ,
UserID : 1 ,
AuthModule : login . GenericOAuthModule ,
AccessToken : expiredToken . AccessToken ,
RefreshToken : expiredToken . RefreshToken ,
ExpiresAt : expiredToken . Expiry ,
IDToken : EXPIRED_ID_TOKEN ,
} , nil ) . Once ( )
env . sessionService . On ( "UpdateExternalSession" , mock . Anything , int64 ( 2 ) , mock . MatchedBy ( verifyUpdateExternalSessionCommand ( unexpiredTokenWithIDToken ) ) ) . Return ( nil ) . Once ( )
env . socialConnector . On ( "TokenSource" , mock . Anything , mock . Anything ) . Return ( oauth2 . StaticTokenSource ( unexpiredTokenWithIDToken ) ) . Once ( )
} ,
expectedToken : unexpiredTokenWithIDToken ,
} ,
{
desc : "should skip token refresh when the token is still valid and no id token is present" ,
identity : userIdentity ,
sessionToken : & auth . UserToken { ExternalSessionId : 1 } ,
setup : func ( env * environment ) {
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . Anything ) . Return ( & login . UserAuth {
AuthModule : login . GenericOAuthModule ,
} , nil )
env . sessionService . On ( "GetExternalSession" , mock . Anything , int64 ( 1 ) ) . Return ( & auth . ExternalSession {
ID : 1 ,
UserID : 1234 ,
AuthModule : login . GenericOAuthModule ,
AccessToken : unexpiredToken . AccessToken ,
RefreshToken : unexpiredToken . RefreshToken ,
ExpiresAt : unexpiredToken . Expiry ,
} , nil ) . Once ( )
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
} ,
expectedToken : unexpiredToken ,
} ,
{
desc : "should return the unexpired access and id token when token refresh is disabled" ,
identity : userIdentity ,
sessionToken : & auth . UserToken { ExternalSessionId : 1 } ,
setup : func ( env * environment ) {
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . Anything ) . Return ( & login . UserAuth {
AuthModule : login . GenericOAuthModule ,
} , nil )
env . sessionService . On ( "GetExternalSession" , mock . Anything , int64 ( 1 ) ) . Return ( & auth . ExternalSession {
ID : 1 ,
UserID : 1234 ,
AuthModule : login . GenericOAuthModule ,
AccessToken : unexpiredTokenWithIDToken . AccessToken ,
ExpiresAt : unexpiredTokenWithIDToken . Expiry ,
IDToken : UNEXPIRED_ID_TOKEN ,
} , nil ) . Once ( )
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : false ,
}
} ,
expectedToken : unexpiredTokenWithoutRefreshWithIDToken ,
} ,
{
desc : "should not do token refresh if access token or id token have not expired yet" ,
identity : userIdentity ,
sessionToken : & auth . UserToken { ExternalSessionId : 1 } ,
setup : func ( env * environment ) {
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . Anything ) . Return ( & login . UserAuth {
AuthModule : login . GenericOAuthModule ,
} , nil )
env . sessionService . On ( "GetExternalSession" , mock . Anything , int64 ( 1 ) ) . Return ( & auth . ExternalSession {
ID : 1 ,
UserID : 1234 ,
AuthModule : login . GenericOAuthModule ,
AccessToken : unexpiredTokenWithIDToken . AccessToken ,
RefreshToken : unexpiredTokenWithIDToken . RefreshToken ,
ExpiresAt : unexpiredTokenWithIDToken . Expiry ,
IDToken : UNEXPIRED_ID_TOKEN ,
} , nil ) . Once ( )
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
} ,
expectedToken : unexpiredTokenWithIDToken ,
} ,
{
desc : "should refresh token when the access token is expired" ,
identity : userIdentity ,
sessionToken : & auth . UserToken { ExternalSessionId : 1 } ,
setup : func ( env * environment ) {
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . Anything ) . Return ( & login . UserAuth {
AuthModule : login . GenericOAuthModule ,
} , nil )
env . sessionService . On ( "GetExternalSession" , mock . Anything , int64 ( 1 ) ) . Return ( & auth . ExternalSession {
ID : 1 ,
UserID : 1 ,
AccessToken : expiredToken . AccessToken ,
RefreshToken : expiredToken . RefreshToken ,
ExpiresAt : expiredToken . Expiry ,
IDToken : UNEXPIRED_ID_TOKEN ,
} , nil ) . Twice ( )
env . sessionService . On ( "UpdateExternalSession" , mock . Anything , int64 ( 1 ) , mock . MatchedBy ( verifyUpdateExternalSessionCommand ( unexpiredTokenWithIDToken ) ) ) . Return ( nil ) . Once ( )
env . socialConnector . On ( "TokenSource" , mock . Anything , mock . Anything ) . Return ( oauth2 . StaticTokenSource ( unexpiredTokenWithIDToken ) ) . Once ( )
} ,
expectedToken : unexpiredTokenWithIDToken ,
} ,
{
desc : "should refresh token when the id token is expired" ,
identity : userIdentity ,
sessionToken : & auth . UserToken { ExternalSessionId : 1 } ,
setup : func ( env * environment ) {
env . socialService . ExpectedAuthInfoProvider = & social . OAuthInfo {
UseRefreshToken : true ,
}
env . authInfoService . On ( "GetAuthInfo" , mock . Anything , mock . Anything ) . Return ( & login . UserAuth {
AuthModule : login . GenericOAuthModule ,
} , nil )
env . sessionService . On ( "GetExternalSession" , mock . Anything , int64 ( 1 ) ) . Return ( & auth . ExternalSession {
ID : 1 ,
UserID : 1234 ,
AuthModule : login . GenericOAuthModule ,
AccessToken : unexpiredToken . AccessToken ,
RefreshToken : unexpiredToken . RefreshToken ,
ExpiresAt : unexpiredToken . Expiry ,
IDToken : EXPIRED_ID_TOKEN ,
} , nil ) . Twice ( )
env . sessionService . On ( "UpdateExternalSession" , mock . Anything , int64 ( 1 ) , mock . MatchedBy ( verifyUpdateExternalSessionCommand ( unexpiredTokenWithIDToken ) ) ) . Return ( nil ) . Once ( )
env . socialConnector . On ( "TokenSource" , mock . Anything , mock . Anything ) . Return ( oauth2 . StaticTokenSource ( unexpiredTokenWithIDToken ) ) . Once ( )
} ,
expectedToken : unexpiredTokenWithIDToken ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . desc , func ( t * testing . T ) {
socialConnector := socialtest . NewMockSocialConnector ( t )
store := db . InitTestDB ( t )
features := featuremgmt . WithFeatures ( featuremgmt . FlagImprovedExternalSessionHandling )
env := environment {
sessionService : authtest . NewMockUserAuthTokenService ( t ) ,
authInfoService : authinfotest . NewMockAuthInfoService ( t ) ,
serverLock : serverlock . ProvideService ( store , tracing . InitializeTracerForTest ( ) ) ,
socialConnector : socialConnector ,
socialService : & socialtest . FakeSocialService {
ExpectedConnector : socialConnector ,
} ,
store : store ,
}
if tt . setup != nil {
tt . setup ( & env )
}
env . service = ProvideService (
env . socialService ,
env . authInfoService ,
setting . NewCfg ( ) ,
prometheus . NewRegistry ( ) ,
env . serverLock ,
tracing . InitializeTracerForTest ( ) ,
env . sessionService ,
features ,
)
actualToken := env . service . GetCurrentOAuthToken ( context . Background ( ) , tt . identity , tt . sessionToken )
if tt . expectedToken == nil {
assert . Nil ( t , actualToken )
return
}
assert . NotNil ( t , actualToken )
assert . Equal ( t , tt . expectedToken . AccessToken , actualToken . AccessToken )
assert . Equal ( t , tt . expectedToken . RefreshToken , actualToken . RefreshToken )
assert . WithinDuration ( t , tt . expectedToken . Expiry , actualToken . Expiry , time . Second )
if tt . expectedToken . Extra ( "id_token" ) != nil {
assert . Equal ( t , tt . expectedToken . Extra ( "id_token" ) , actualToken . Extra ( "id_token" ) )
} else {
assert . Nil ( t , actualToken . Extra ( "id_token" ) )
}
} )
}
}