2019-03-08 08:15:38 -06:00
package api
import (
2019-04-30 07:42:01 -05:00
"context"
2020-11-13 02:52:38 -06:00
"fmt"
2023-03-23 08:39:04 -05:00
"net/http"
2019-03-08 08:15:38 -06:00
"testing"
"time"
2022-08-02 09:58:05 -05:00
"github.com/stretchr/testify/assert"
2023-03-23 08:39:04 -05:00
"github.com/stretchr/testify/require"
2022-08-02 09:58:05 -05:00
2021-01-15 07:43:20 -06:00
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
2023-03-23 08:39:04 -05:00
"github.com/grafana/grafana/pkg/infra/log"
2019-03-08 08:15:38 -06:00
"github.com/grafana/grafana/pkg/services/auth"
2022-11-18 02:56:06 -06:00
"github.com/grafana/grafana/pkg/services/auth/authtest"
2023-01-27 01:50:36 -06:00
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
2023-03-23 08:39:04 -05:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
2022-08-10 04:56:48 -05:00
"github.com/grafana/grafana/pkg/services/org"
2022-06-28 07:32:25 -05:00
"github.com/grafana/grafana/pkg/services/user"
2022-08-02 09:58:05 -05:00
"github.com/grafana/grafana/pkg/services/user/usertest"
2023-03-23 08:39:04 -05:00
"github.com/grafana/grafana/pkg/setting"
2019-03-08 08:15:38 -06:00
)
2020-11-13 02:52:38 -06:00
func TestUserTokenAPIEndpoint ( t * testing . T ) {
2022-08-02 09:58:05 -05:00
userMock := usertest . NewUserServiceFake ( )
2020-11-13 02:52:38 -06:00
t . Run ( "When current user attempts to revoke an auth token for a non-existing user" , func ( t * testing . T ) {
2022-11-18 02:56:06 -06:00
cmd := auth . RevokeAuthTokenCmd { AuthTokenId : 2 }
2022-08-02 09:58:05 -05:00
userMock . ExpectedError = user . ErrUserNotFound
2020-11-13 02:52:38 -06:00
revokeUserAuthTokenScenario ( t , "Should return not found when calling POST on" , "/api/user/revoke-auth-token" ,
"/api/user/revoke-auth-token" , cmd , 200 , func ( sc * scenarioContext ) {
sc . fakeReqWithParams ( "POST" , sc . url , map [ string ] string { } ) . exec ( )
assert . Equal ( t , 404 , sc . resp . Code )
2022-08-02 09:58:05 -05:00
} , userMock )
2019-03-08 08:15:38 -06:00
} )
2020-11-13 02:52:38 -06:00
t . Run ( "When current user gets auth tokens for a non-existing user" , func ( t * testing . T ) {
2022-08-02 09:58:05 -05:00
mockUser := & usertest . FakeUserService {
2022-06-28 07:32:25 -05:00
ExpectedUser : & user . User { ID : 200 } ,
2022-07-20 07:50:06 -05:00
ExpectedError : user . ErrUserNotFound ,
2022-02-03 02:20:20 -06:00
}
2020-11-13 02:52:38 -06:00
getUserAuthTokensScenario ( t , "Should return not found when calling GET on" , "/api/user/auth-tokens" , "/api/user/auth-tokens" , 200 , func ( sc * scenarioContext ) {
2019-03-08 08:15:38 -06:00
sc . fakeReqWithParams ( "GET" , sc . url , map [ string ] string { } ) . exec ( )
2020-11-13 02:52:38 -06:00
assert . Equal ( t , 404 , sc . resp . Code )
2022-08-02 09:58:05 -05:00
} , mockUser )
2019-03-08 08:15:38 -06:00
} )
2020-11-13 02:52:38 -06:00
t . Run ( "When logging out an existing user from all devices" , func ( t * testing . T ) {
2022-08-02 09:58:05 -05:00
userMock := & usertest . FakeUserService {
2022-06-28 07:32:25 -05:00
ExpectedUser : & user . User { ID : 200 } ,
2022-02-03 02:20:20 -06:00
}
2020-11-13 02:52:38 -06:00
logoutUserFromAllDevicesInternalScenario ( t , "Should be successful" , 1 , func ( sc * scenarioContext ) {
2019-03-08 08:15:38 -06:00
sc . fakeReqWithParams ( "POST" , sc . url , map [ string ] string { } ) . exec ( )
2020-11-13 02:52:38 -06:00
assert . Equal ( t , 200 , sc . resp . Code )
2022-08-02 09:58:05 -05:00
} , userMock )
2019-03-08 08:15:38 -06:00
} )
2020-11-13 02:52:38 -06:00
t . Run ( "When logout a non-existing user from all devices" , func ( t * testing . T ) {
logoutUserFromAllDevicesInternalScenario ( t , "Should return not found" , testUserID , func ( sc * scenarioContext ) {
2022-08-02 09:58:05 -05:00
userMock . ExpectedError = user . ErrUserNotFound
2019-03-08 08:15:38 -06:00
sc . fakeReqWithParams ( "POST" , sc . url , map [ string ] string { } ) . exec ( )
2020-11-13 02:52:38 -06:00
assert . Equal ( t , 404 , sc . resp . Code )
2022-08-02 09:58:05 -05:00
} , userMock )
2019-03-08 08:15:38 -06:00
} )
2020-11-13 02:52:38 -06:00
t . Run ( "When revoke an auth token for a user" , func ( t * testing . T ) {
2022-11-18 02:56:06 -06:00
cmd := auth . RevokeAuthTokenCmd { AuthTokenId : 2 }
token := & auth . UserToken { Id : 1 }
2022-08-02 09:58:05 -05:00
mockUser := & usertest . FakeUserService {
2022-06-28 07:32:25 -05:00
ExpectedUser : & user . User { ID : 200 } ,
2022-02-03 02:20:20 -06:00
}
2019-03-08 08:15:38 -06:00
2020-11-13 02:52:38 -06:00
revokeUserAuthTokenInternalScenario ( t , "Should be successful" , cmd , 200 , token , func ( sc * scenarioContext ) {
2022-11-18 02:56:06 -06:00
sc . userAuthTokenService . GetUserTokenProvider = func ( ctx context . Context , userId , userTokenId int64 ) ( * auth . UserToken , error ) {
return & auth . UserToken { Id : 2 } , nil
2019-03-08 08:15:38 -06:00
}
sc . fakeReqWithParams ( "POST" , sc . url , map [ string ] string { } ) . exec ( )
2020-11-13 02:52:38 -06:00
assert . Equal ( t , 200 , sc . resp . Code )
2022-08-02 09:58:05 -05:00
} , mockUser )
2019-03-08 08:15:38 -06:00
} )
2020-11-13 02:52:38 -06:00
t . Run ( "When revoke the active auth token used by himself" , func ( t * testing . T ) {
2022-11-18 02:56:06 -06:00
cmd := auth . RevokeAuthTokenCmd { AuthTokenId : 2 }
token := & auth . UserToken { Id : 2 }
2022-08-02 09:58:05 -05:00
mockUser := usertest . NewUserServiceFake ( )
2020-11-13 02:52:38 -06:00
revokeUserAuthTokenInternalScenario ( t , "Should not be successful" , cmd , testUserID , token , func ( sc * scenarioContext ) {
2022-11-18 02:56:06 -06:00
sc . userAuthTokenService . GetUserTokenProvider = func ( ctx context . Context , userId , userTokenId int64 ) ( * auth . UserToken , error ) {
2019-03-08 08:15:38 -06:00
return token , nil
}
sc . fakeReqWithParams ( "POST" , sc . url , map [ string ] string { } ) . exec ( )
2020-11-13 02:52:38 -06:00
assert . Equal ( t , 400 , sc . resp . Code )
2022-08-02 09:58:05 -05:00
} , mockUser )
2019-03-08 08:15:38 -06:00
} )
2020-11-13 02:52:38 -06:00
t . Run ( "When gets auth tokens for a user" , func ( t * testing . T ) {
2022-11-18 02:56:06 -06:00
currentToken := & auth . UserToken { Id : 1 }
2022-08-02 09:58:05 -05:00
mockUser := usertest . NewUserServiceFake ( )
2020-11-13 02:52:38 -06:00
getUserAuthTokensInternalScenario ( t , "Should be successful" , currentToken , func ( sc * scenarioContext ) {
2022-11-18 02:56:06 -06:00
tokens := [ ] * auth . UserToken {
2019-03-08 08:15:38 -06:00
{
Id : 1 ,
ClientIp : "127.0.0.1" ,
UserAgent : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36" ,
CreatedAt : time . Now ( ) . Unix ( ) ,
SeenAt : time . Now ( ) . Unix ( ) ,
} ,
{
Id : 2 ,
ClientIp : "127.0.0.2" ,
UserAgent : "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1" ,
CreatedAt : time . Now ( ) . Unix ( ) ,
2019-07-08 07:30:02 -05:00
SeenAt : 0 ,
2019-03-08 08:15:38 -06:00
} ,
}
2022-11-18 02:56:06 -06:00
sc . userAuthTokenService . GetUserTokensProvider = func ( ctx context . Context , userId int64 ) ( [ ] * auth . UserToken , error ) {
2019-03-08 08:15:38 -06:00
return tokens , nil
}
sc . fakeReqWithParams ( "GET" , sc . url , map [ string ] string { } ) . exec ( )
2020-11-13 02:52:38 -06:00
assert . Equal ( t , 200 , sc . resp . Code )
2019-03-08 08:15:38 -06:00
result := sc . ToJSON ( )
2020-11-13 02:52:38 -06:00
assert . Len ( t , result . MustArray ( ) , 2 )
2019-03-08 08:15:38 -06:00
resultOne := result . GetIndex ( 0 )
2020-11-13 02:52:38 -06:00
assert . Equal ( t , tokens [ 0 ] . Id , resultOne . Get ( "id" ) . MustInt64 ( ) )
assert . True ( t , resultOne . Get ( "isActive" ) . MustBool ( ) )
assert . Equal ( t , "127.0.0.1" , resultOne . Get ( "clientIp" ) . MustString ( ) )
assert . Equal ( t , time . Unix ( tokens [ 0 ] . CreatedAt , 0 ) . Format ( time . RFC3339 ) , resultOne . Get ( "createdAt" ) . MustString ( ) )
assert . Equal ( t , time . Unix ( tokens [ 0 ] . SeenAt , 0 ) . Format ( time . RFC3339 ) , resultOne . Get ( "seenAt" ) . MustString ( ) )
assert . Equal ( t , "Other" , resultOne . Get ( "device" ) . MustString ( ) )
assert . Equal ( t , "Chrome" , resultOne . Get ( "browser" ) . MustString ( ) )
assert . Equal ( t , "72.0" , resultOne . Get ( "browserVersion" ) . MustString ( ) )
assert . Equal ( t , "Linux" , resultOne . Get ( "os" ) . MustString ( ) )
assert . Empty ( t , resultOne . Get ( "osVersion" ) . MustString ( ) )
2019-06-11 07:12:52 -05:00
2019-03-08 08:15:38 -06:00
resultTwo := result . GetIndex ( 1 )
2020-11-13 02:52:38 -06:00
assert . Equal ( t , tokens [ 1 ] . Id , resultTwo . Get ( "id" ) . MustInt64 ( ) )
assert . False ( t , resultTwo . Get ( "isActive" ) . MustBool ( ) )
assert . Equal ( t , "127.0.0.2" , resultTwo . Get ( "clientIp" ) . MustString ( ) )
assert . Equal ( t , time . Unix ( tokens [ 1 ] . CreatedAt , 0 ) . Format ( time . RFC3339 ) , resultTwo . Get ( "createdAt" ) . MustString ( ) )
assert . Equal ( t , time . Unix ( tokens [ 1 ] . CreatedAt , 0 ) . Format ( time . RFC3339 ) , resultTwo . Get ( "seenAt" ) . MustString ( ) )
assert . Equal ( t , "iPhone" , resultTwo . Get ( "device" ) . MustString ( ) )
assert . Equal ( t , "Mobile Safari" , resultTwo . Get ( "browser" ) . MustString ( ) )
assert . Equal ( t , "11.0" , resultTwo . Get ( "browserVersion" ) . MustString ( ) )
assert . Equal ( t , "iOS" , resultTwo . Get ( "os" ) . MustString ( ) )
assert . Equal ( t , "11.0" , resultTwo . Get ( "osVersion" ) . MustString ( ) )
2022-08-02 09:58:05 -05:00
} , mockUser )
2019-03-08 08:15:38 -06:00
} )
}
2023-03-23 08:39:04 -05:00
func TestHTTPServer_RotateUserAuthToken ( t * testing . T ) {
type testCase struct {
desc string
cookie * http . Cookie
rotatedToken * auth . UserToken
rotatedErr error
expectedStatus int
expectNewSession bool
expectSessionDeleted bool
}
tests := [ ] testCase {
{
desc : "Should return 401 and delete cookie if the token is invalid" ,
cookie : & http . Cookie { Name : "grafana_session" , Value : "123" , Path : "/" } ,
rotatedErr : auth . ErrInvalidSessionToken ,
expectSessionDeleted : true ,
expectedStatus : http . StatusUnauthorized ,
} ,
{
2023-03-31 09:44:08 -05:00
desc : "Should return 401 and when token not found" ,
2023-03-23 08:39:04 -05:00
cookie : & http . Cookie { Name : "grafana_session" , Value : "123" , Path : "/" } ,
rotatedErr : auth . ErrUserTokenNotFound ,
2023-03-31 09:44:08 -05:00
expectedStatus : http . StatusUnauthorized ,
2023-03-23 08:39:04 -05:00
} ,
{
desc : "Should return 200 and but not set new cookie if token was not rotated" ,
cookie : & http . Cookie { Name : "grafana_session" , Value : "123" , Path : "/" } ,
rotatedToken : & auth . UserToken { UnhashedToken : "123" } ,
expectedStatus : http . StatusOK ,
} ,
{
desc : "Should return 200 and set new session and expiry cookies" ,
cookie : & http . Cookie { Name : "grafana_session" , Value : "123" , Path : "/" } ,
rotatedToken : & auth . UserToken { UnhashedToken : "new" } ,
expectNewSession : true ,
expectedStatus : http . StatusOK ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . desc , func ( t * testing . T ) {
server := SetupAPITestServer ( t , func ( hs * HTTPServer ) {
cfg := setting . NewCfg ( )
cfg . LoginCookieName = "grafana_session"
cfg . LoginMaxLifetime = 10 * time . Hour
hs . Cfg = cfg
hs . log = log . New ( )
hs . Cfg . LoginCookieName = "grafana_session"
hs . Features = featuremgmt . WithFeatures ( featuremgmt . FlagClientTokenRotation )
hs . AuthTokenService = & authtest . FakeUserAuthTokenService {
RotateTokenProvider : func ( ctx context . Context , cmd auth . RotateCommand ) ( * auth . UserToken , error ) {
return tt . rotatedToken , tt . rotatedErr
} ,
}
} )
req := server . NewPostRequest ( "/api/user/auth-tokens/rotate" , nil )
if tt . cookie != nil {
req . AddCookie ( tt . cookie )
}
res , err := server . Send ( req )
require . NoError ( t , err )
assert . Equal ( t , tt . expectedStatus , res . StatusCode )
if tt . expectedStatus != http . StatusOK {
if tt . expectSessionDeleted {
cookies := res . Header . Values ( "Set-Cookie" )
require . Len ( t , cookies , 2 )
assert . Equal ( t , "grafana_session=; Path=/; Max-Age=0; HttpOnly" , cookies [ 0 ] )
assert . Equal ( t , "grafana_session_expiry=; Path=/; Max-Age=0" , cookies [ 1 ] )
} else {
assert . Empty ( t , res . Header . Get ( "Set-Cookie" ) )
}
} else {
if tt . expectNewSession {
cookies := res . Header . Values ( "Set-Cookie" )
require . Len ( t , cookies , 2 )
assert . Equal ( t , "grafana_session=new; Path=/; Max-Age=36000; HttpOnly" , cookies [ 0 ] )
assert . Equal ( t , "grafana_session_expiry=-5; Path=/; Max-Age=36000" , cookies [ 1 ] )
} else {
assert . Empty ( t , res . Header . Get ( "Set-Cookie" ) )
}
}
require . NoError ( t , res . Body . Close ( ) )
} )
}
}
2022-11-18 02:56:06 -06:00
func revokeUserAuthTokenScenario ( t * testing . T , desc string , url string , routePattern string , cmd auth . RevokeAuthTokenCmd ,
2022-08-02 09:58:05 -05:00
userId int64 , fn scenarioFunc , userService user . Service ) {
2020-11-13 02:52:38 -06:00
t . Run ( fmt . Sprintf ( "%s %s" , desc , url ) , func ( t * testing . T ) {
2022-11-18 02:56:06 -06:00
fakeAuthTokenService := authtest . NewFakeUserAuthTokenService ( )
2019-03-08 08:15:38 -06:00
hs := HTTPServer {
AuthTokenService : fakeAuthTokenService ,
2022-08-02 09:58:05 -05:00
userService : userService ,
2019-03-08 08:15:38 -06:00
}
2020-11-13 02:52:38 -06:00
sc := setupScenarioContext ( t , url )
2019-03-08 08:15:38 -06:00
sc . userAuthTokenService = fakeAuthTokenService
2023-01-27 01:50:36 -06:00
sc . defaultHandler = routing . Wrap ( func ( c * contextmodel . ReqContext ) response . Response {
2021-11-29 03:18:01 -06:00
c . Req . Body = mockRequestBody ( cmd )
2019-03-08 08:15:38 -06:00
sc . context = c
2022-08-11 06:28:55 -05:00
sc . context . UserID = userId
sc . context . OrgID = testOrgID
2022-08-10 04:56:48 -05:00
sc . context . OrgRole = org . RoleAdmin
2019-03-08 08:15:38 -06:00
2021-11-29 03:18:01 -06:00
return hs . RevokeUserAuthToken ( c )
2019-03-08 08:15:38 -06:00
} )
sc . m . Post ( routePattern , sc . defaultHandler )
fn ( sc )
} )
}
2022-08-02 09:58:05 -05:00
func getUserAuthTokensScenario ( t * testing . T , desc string , url string , routePattern string , userId int64 , fn scenarioFunc , userService user . Service ) {
2020-11-13 02:52:38 -06:00
t . Run ( fmt . Sprintf ( "%s %s" , desc , url ) , func ( t * testing . T ) {
2022-11-18 02:56:06 -06:00
fakeAuthTokenService := authtest . NewFakeUserAuthTokenService ( )
2019-03-08 08:15:38 -06:00
hs := HTTPServer {
AuthTokenService : fakeAuthTokenService ,
2022-08-02 09:58:05 -05:00
userService : userService ,
2019-03-08 08:15:38 -06:00
}
2020-11-13 02:52:38 -06:00
sc := setupScenarioContext ( t , url )
2019-03-08 08:15:38 -06:00
sc . userAuthTokenService = fakeAuthTokenService
2023-01-27 01:50:36 -06:00
sc . defaultHandler = routing . Wrap ( func ( c * contextmodel . ReqContext ) response . Response {
2019-03-08 08:15:38 -06:00
sc . context = c
2022-08-11 06:28:55 -05:00
sc . context . UserID = userId
sc . context . OrgID = testOrgID
2022-08-10 04:56:48 -05:00
sc . context . OrgRole = org . RoleAdmin
2019-03-08 08:15:38 -06:00
return hs . GetUserAuthTokens ( c )
} )
sc . m . Get ( routePattern , sc . defaultHandler )
fn ( sc )
} )
}
2022-08-02 09:58:05 -05:00
func logoutUserFromAllDevicesInternalScenario ( t * testing . T , desc string , userId int64 , fn scenarioFunc , userService user . Service ) {
2020-11-13 02:52:38 -06:00
t . Run ( desc , func ( t * testing . T ) {
2019-03-08 08:15:38 -06:00
hs := HTTPServer {
2022-11-18 02:56:06 -06:00
AuthTokenService : authtest . NewFakeUserAuthTokenService ( ) ,
2022-08-02 09:58:05 -05:00
userService : userService ,
2019-03-08 08:15:38 -06:00
}
2020-11-13 02:52:38 -06:00
sc := setupScenarioContext ( t , "/" )
2023-01-27 01:50:36 -06:00
sc . defaultHandler = routing . Wrap ( func ( c * contextmodel . ReqContext ) response . Response {
2019-03-08 08:15:38 -06:00
sc . context = c
2022-08-11 06:28:55 -05:00
sc . context . UserID = testUserID
sc . context . OrgID = testOrgID
2022-08-10 04:56:48 -05:00
sc . context . OrgRole = org . RoleAdmin
2019-03-08 08:15:38 -06:00
2019-04-30 07:42:01 -05:00
return hs . logoutUserFromAllDevicesInternal ( context . Background ( ) , userId )
2019-03-08 08:15:38 -06:00
} )
sc . m . Post ( "/" , sc . defaultHandler )
fn ( sc )
} )
}
2022-11-18 02:56:06 -06:00
func revokeUserAuthTokenInternalScenario ( t * testing . T , desc string , cmd auth . RevokeAuthTokenCmd , userId int64 ,
token * auth . UserToken , fn scenarioFunc , userService user . Service ) {
2020-11-13 02:52:38 -06:00
t . Run ( desc , func ( t * testing . T ) {
2022-11-18 02:56:06 -06:00
fakeAuthTokenService := authtest . NewFakeUserAuthTokenService ( )
2019-03-08 08:15:38 -06:00
hs := HTTPServer {
AuthTokenService : fakeAuthTokenService ,
2022-08-02 09:58:05 -05:00
userService : userService ,
2019-03-08 08:15:38 -06:00
}
2020-11-13 02:52:38 -06:00
sc := setupScenarioContext ( t , "/" )
2019-03-08 08:15:38 -06:00
sc . userAuthTokenService = fakeAuthTokenService
2023-01-27 01:50:36 -06:00
sc . defaultHandler = routing . Wrap ( func ( c * contextmodel . ReqContext ) response . Response {
2019-03-08 08:15:38 -06:00
sc . context = c
2022-08-11 06:28:55 -05:00
sc . context . UserID = testUserID
sc . context . OrgID = testOrgID
2022-08-10 04:56:48 -05:00
sc . context . OrgRole = org . RoleAdmin
2019-03-08 08:15:38 -06:00
sc . context . UserToken = token
return hs . revokeUserAuthTokenInternal ( c , userId , cmd )
} )
sc . m . Post ( "/" , sc . defaultHandler )
fn ( sc )
} )
}
2022-11-18 02:56:06 -06:00
func getUserAuthTokensInternalScenario ( t * testing . T , desc string , token * auth . UserToken , fn scenarioFunc , userService user . Service ) {
2020-11-13 02:52:38 -06:00
t . Run ( desc , func ( t * testing . T ) {
2022-11-18 02:56:06 -06:00
fakeAuthTokenService := authtest . NewFakeUserAuthTokenService ( )
2019-03-08 08:15:38 -06:00
hs := HTTPServer {
AuthTokenService : fakeAuthTokenService ,
2022-08-02 09:58:05 -05:00
userService : userService ,
2019-03-08 08:15:38 -06:00
}
2020-11-13 02:52:38 -06:00
sc := setupScenarioContext ( t , "/" )
2019-03-08 08:15:38 -06:00
sc . userAuthTokenService = fakeAuthTokenService
2023-01-27 01:50:36 -06:00
sc . defaultHandler = routing . Wrap ( func ( c * contextmodel . ReqContext ) response . Response {
2019-03-08 08:15:38 -06:00
sc . context = c
2022-08-11 06:28:55 -05:00
sc . context . UserID = testUserID
sc . context . OrgID = testOrgID
2022-08-10 04:56:48 -05:00
sc . context . OrgRole = org . RoleAdmin
2019-03-08 08:15:38 -06:00
sc . context . UserToken = token
2020-11-13 02:52:38 -06:00
return hs . getUserAuthTokensInternal ( c , testUserID )
2019-03-08 08:15:38 -06:00
} )
sc . m . Get ( "/" , sc . defaultHandler )
fn ( sc )
} )
}