@ -11,7 +11,6 @@ import (
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/macaron.v1"
@ -45,226 +44,245 @@ func resetGetTime() {
}
func TestMiddleWareSecurityHeaders ( t * testing . T ) {
origErrTemplateName := setting . ErrTemplateName
t . Cleanup ( func ( ) {
setting . ErrTemplateName = origErrTemplateName
} )
setting . ErrTemplateName = errorTemplate
Convey ( "Given the grafana middleware" , t , func ( ) {
middlewareScenario ( t , "middleware should get correct x-xss-protection header" , func ( sc * scenarioContext ) {
setting . XSSProtectionHeader = true
sc . fakeReq ( "GET" , "/api/" ) . exec ( )
So ( sc . resp . Header ( ) . Get ( "X-XSS-Protection" ) , ShouldEqual , "1; mode=block" )
middlewareScenario ( t , "middleware should get correct x-xss-protection header" , func ( sc * scenarioContext ) {
origXSSProtectionHeader := setting . XSSProtectionHeader
t . Cleanup ( func ( ) {
setting . XSSProtectionHeader = origXSSProtectionHeader
} )
setting . XSSProtectionHeader = true
sc . fakeReq ( "GET" , "/api/" ) . exec ( )
assert . Equal ( t , "1; mode=block" , sc . resp . Header ( ) . Get ( "X-XSS-Protection" ) )
} )
middlewareScenario ( t , "middleware should not get x-xss-protection when disabled" , func ( sc * scenarioContext ) {
setting . XSSProtectionHeader = false
sc . fakeReq ( "GET" , "/api/" ) . exec ( )
So ( sc . resp . Header ( ) . Get ( "X-XSS-Protection" ) , ShouldBeEmpty )
middlewareScenario ( t , "middleware should not get x-xss-protection when disabled" , func ( sc * scenarioContext ) {
origXSSProtectionHeader := setting . XSSProtectionHeader
t . Cleanup ( func ( ) {
setting . XSSProtectionHeader = origXSSProtectionHeader
} )
setting . XSSProtectionHeader = false
sc . fakeReq ( "GET" , "/api/" ) . exec ( )
assert . Empty ( t , sc . resp . Header ( ) . Get ( "X-XSS-Protection" ) )
} )
middlewareScenario ( t , "middleware should add correct Strict-Transport-Security header" , func ( sc * scenarioContext ) {
setting . StrictTransportSecurity = true
setting . Protocol = setting . HTTPSScheme
setting . StrictTransportSecurityMaxAge = 64000
sc . fakeReq ( "GET" , "/api/" ) . exec ( )
So ( sc . resp . Header ( ) . Get ( "Strict-Transport-Security" ) , ShouldEqual , "max-age=64000" )
setting . StrictTransportSecurityPreload = true
sc . fakeReq ( "GET" , "/api/" ) . exec ( )
So ( sc . resp . Header ( ) . Get ( "Strict-Transport-Security" ) , ShouldEqual , "max-age=64000; preload" )
setting . StrictTransportSecuritySubDomains = true
sc . fakeReq ( "GET" , "/api/" ) . exec ( )
So ( sc . resp . Header ( ) . Get ( "Strict-Transport-Security" ) , ShouldEqual , "max-age=64000; preload; includeSubDomains" )
middlewareScenario ( t , "middleware should add correct Strict-Transport-Security header" , func ( sc * scenarioContext ) {
origStrictTransportSecurity := setting . StrictTransportSecurity
origProtocol := setting . Protocol
origStrictTransportSecurityMaxAge := setting . StrictTransportSecurityMaxAge
t . Cleanup ( func ( ) {
setting . StrictTransportSecurity = origStrictTransportSecurity
setting . Protocol = origProtocol
setting . StrictTransportSecurityMaxAge = origStrictTransportSecurityMaxAge
} )
setting . StrictTransportSecurity = true
setting . Protocol = setting . HTTPSScheme
setting . StrictTransportSecurityMaxAge = 64000
sc . fakeReq ( "GET" , "/api/" ) . exec ( )
assert . Equal ( t , "max-age=64000" , sc . resp . Header ( ) . Get ( "Strict-Transport-Security" ) )
setting . StrictTransportSecurityPreload = true
sc . fakeReq ( "GET" , "/api/" ) . exec ( )
assert . Equal ( t , "max-age=64000; preload" , sc . resp . Header ( ) . Get ( "Strict-Transport-Security" ) )
setting . StrictTransportSecuritySubDomains = true
sc . fakeReq ( "GET" , "/api/" ) . exec ( )
assert . Equal ( t , "max-age=64000; preload; includeSubDomains" , sc . resp . Header ( ) . Get ( "Strict-Transport-Security" ) )
} )
}
func TestMiddlewareContext ( t * testing . T ) {
origErrTemplateName := setting . ErrTemplateName
t . Cleanup ( func ( ) {
setting . ErrTemplateName = origErrTemplateName
} )
setting . ErrTemplateName = errorTemplate
Convey ( "Given the grafana middleware" , t , func ( ) {
middlewareScenario ( t , "middleware should add context to injector" , func ( sc * scenarioContext ) {
sc . fakeReq ( "GET" , "/" ) . exec ( )
So ( sc . context , ShouldNotBeNil )
} )
middlewareScenario ( t , "middleware should add context to injector" , func ( sc * scenarioContext ) {
sc . fakeReq ( "GET" , "/" ) . exec ( )
assert . NotNil ( t , sc . context )
} )
middlewareScenario ( t , "Default middleware should allow get request" , func ( sc * scenarioContext ) {
sc . fakeReq ( "GET" , "/" ) . exec ( )
So ( sc . resp . Code , ShouldEqual , 200 )
} )
middlewareScenario ( t , "Default middleware should allow get request" , func ( sc * scenarioContext ) {
sc . fakeReq ( "GET" , "/" ) . exec ( )
assert . Equal ( t , 200 , sc . resp . Code )
} )
middlewareScenario ( t , "middleware should add Cache-Control header for requests to API" , func ( sc * scenarioContext ) {
sc . fakeReq ( "GET" , "/api/search" ) . exec ( )
So ( sc . resp . Header ( ) . Get ( "Cache-Control" ) , ShouldEqual , "no-cache" )
So ( sc . resp . Header ( ) . Get ( "Pragma" ) , ShouldEqual , "no-cache" )
So ( sc . resp . Header ( ) . Get ( "Expires" ) , ShouldEqual , "-1" )
} )
middlewareScenario ( t , "middleware should add Cache-Control header for requests to API" , func ( sc * scenarioContext ) {
sc . fakeReq ( "GET" , "/api/search" ) . exec ( )
assert . Equal ( t , "no-cache" , sc . resp . Header ( ) . Get ( "Cache-Control" ) )
assert . Equal ( t , "no-cache" , sc . resp . Header ( ) . Get ( "Pragma" ) )
assert . Equal ( t , "-1" , sc . resp . Header ( ) . Get ( "Expires" ) )
} )
middlewareScenario ( t , "middleware should not add Cache-Control header for requests to datasource proxy API" , func ( sc * scenarioContext ) {
sc . fakeReq ( "GET" , "/api/datasources/proxy/1/test" ) . exec ( )
So ( sc . resp . Header ( ) . Get ( "Cache-Control" ) , ShouldBeEmpty )
So ( sc . resp . Header ( ) . Get ( "Pragma" ) , ShouldBeEmpty )
So ( sc . resp . Header ( ) . Get ( "Expires" ) , ShouldBeEmpty )
} )
middlewareScenario ( t , "middleware should not add Cache-Control header for requests to datasource proxy API" , func ( sc * scenarioContext ) {
sc . fakeReq ( "GET" , "/api/datasources/proxy/1/test" ) . exec ( )
assert . Empty ( t , sc . resp . Header ( ) . Get ( "Cache-Control" ) )
assert . Empty ( t , sc . resp . Header ( ) . Get ( "Pragma" ) )
assert . Empty ( t , sc . resp . Header ( ) . Get ( "Expires" ) )
} )
middlewareScenario ( t , "middleware should add Cache-Control header for requests with html response" , func ( sc * scenarioContext ) {
sc . handler ( func ( c * models . ReqContext ) {
data := & dtos . IndexViewData {
User : & dtos . CurrentUser { } ,
Settings : map [ string ] interface { } { } ,
NavTree : [ ] * dtos . NavLink { } ,
}
c . HTML ( 200 , "index-template" , data )
} )
sc . fakeReq ( "GET" , "/" ) . exec ( )
So ( sc . resp . Code , ShouldEqual , 200 )
So ( sc . resp . Header ( ) . Get ( "Cache-Control" ) , ShouldEqual , "no-cache" )
So ( sc . resp . Header ( ) . Get ( "Pragma" ) , ShouldEqual , "no-cache" )
So ( sc . resp . Header ( ) . Get ( "Expires" ) , ShouldEqual , "-1" )
middlewareScenario ( t , "middleware should add Cache-Control header for requests with html response" , func ( sc * scenarioContext ) {
sc . handler ( func ( c * models . ReqContext ) {
data := & dtos . IndexViewData {
User : & dtos . CurrentUser { } ,
Settings : map [ string ] interface { } { } ,
NavTree : [ ] * dtos . NavLink { } ,
}
c . HTML ( 200 , "index-template" , data )
} )
sc . fakeReq ( "GET" , "/" ) . exec ( )
assert . Equal ( t , 200 , sc . resp . Code )
assert . Equal ( t , "no-cache" , sc . resp . Header ( ) . Get ( "Cache-Control" ) )
assert . Equal ( t , "no-cache" , sc . resp . Header ( ) . Get ( "Pragma" ) )
assert . Equal ( t , "-1" , sc . resp . Header ( ) . Get ( "Expires" ) )
} )
middlewareScenario ( t , "middleware should add X-Frame-Options header with deny for request when not allowing embedding" , func ( sc * scenarioContext ) {
sc . fakeReq ( "GET" , "/api/search" ) . exec ( )
So ( sc . resp . Header ( ) . Get ( "X-Frame-Options" ) , ShouldEqual , "deny" )
} )
middlewareScenario ( t , "middleware should add X-Frame-Options header with deny for request when not allowing embedding" , func ( sc * scenarioContext ) {
sc . fakeReq ( "GET" , "/api/search" ) . exec ( )
assert . Equal ( t , "deny" , sc . resp . Header ( ) . Get ( "X-Frame-Options" ) )
} )
middlewareScenario ( t , "middleware should not add X-Frame-Options header for request when allowing embedding" , func ( sc * scenarioContext ) {
setting . AllowEmbedding = true
sc . fakeReq ( "GET" , "/api/search" ) . exec ( )
So ( sc . resp . Header ( ) . Get ( "X-Frame-Options" ) , ShouldBeEmpty )
middlewareScenario ( t , "middleware should not add X-Frame-Options header for request when allowing embedding" , func ( sc * scenarioContext ) {
origAllowEmbedding := setting . AllowEmbedding
t . Cleanup ( func ( ) {
setting . AllowEmbedding = origAllowEmbedding
} )
setting . AllowEmbedding = true
sc . fakeReq ( "GET" , "/api/search" ) . exec ( )
assert . Empty ( t , sc . resp . Header ( ) . Get ( "X-Frame-Options" ) )
} )
middlewareScenario ( t , "Invalid api key" , func ( sc * scenarioContext ) {
sc . apiKey = "invalid_key_test"
sc . fakeReq ( "GET" , "/" ) . exec ( )
middlewareScenario ( t , "Invalid api key" , func ( sc * scenarioContext ) {
sc . apiKey = "invalid_key_test"
sc . fakeReq ( "GET" , "/" ) . exec ( )
Convey ( "Should not init session" , func ( ) {
So ( sc . resp . Header ( ) . Get ( "Set-Cookie" ) , ShouldBeEmpty )
} )
assert . Empty ( t , sc . resp . Header ( ) . Get ( "Set-Cookie" ) )
assert . Equal ( t , 401 , sc . resp . Code )
assert . Equal ( t , errStringInvalidAPIKey , sc . respJson [ "message" ] )
} )
Convey ( "Should return 401" , func ( ) {
So ( sc . resp . Code , ShouldEqual , 401 )
So ( sc . respJson [ "message" ] , ShouldEqual , errStringInvalidAPIKey )
} )
middlewareScenario ( t , "Valid api key" , func ( sc * scenarioContext ) {
const orgID int64 = 12
keyhash , err := util . EncodePassword ( "v5nAwpMafFP6znaS4urhdWDLS5511M42" , "asd" )
require . NoError ( t , err )
bus . AddHandler ( "test" , func ( query * models . GetApiKeyByNameQuery ) error {
query . Result = & models . ApiKey { OrgId : orgID , Role : models . ROLE_EDITOR , Key : keyhash }
return nil
} )
middlewareScenario ( t , "Valid api key" , func ( sc * scenarioContext ) {
keyhash , err := util . EncodePassword ( "v5nAwpMafFP6znaS4urhdWDLS5511M42" , "asd" )
So ( err , ShouldBeNil )
sc . fakeReq ( "GET" , "/" ) . withValidApiKey ( ) . exec ( )
bus . AddHandler ( "test" , func ( query * models . GetApiKeyByNameQuery ) error {
query . Result = & models . ApiKey { OrgId : 12 , Role : models . ROLE_EDITOR , Key : keyhash }
return nil
} )
assert . Equal ( t , 200 , sc . resp . Code )
sc . fakeReq ( "GET" , "/" ) . withValidApiKey ( ) . exec ( )
assert . True ( t , sc . context . IsSignedIn )
assert . Equal ( t , orgID , sc . context . OrgId )
assert . Equal ( t , models . ROLE_EDITOR , sc . context . OrgRole )
} )
Convey ( "Should return 200" , func ( ) {
So ( sc . resp . Code , ShouldEqual , 200 )
} )
middlewareScenario ( t , "Valid api key, but does not match db hash" , func ( sc * scenarioContext ) {
keyhash := "Something_not_matching"
Convey ( "Should init middleware context" , func ( ) {
So ( sc . context . IsSignedIn , ShouldEqual , true )
So ( sc . context . OrgId , ShouldEqual , 12 )
So ( sc . context . OrgRole , ShouldEqual , models . ROLE_EDITOR )
} )
bus . AddHandler ( "test" , func ( query * models . GetApiKeyByNameQuery ) error {
query . Result = & models . ApiKey { OrgId : 12 , Role : models . ROLE_EDITOR , Key : keyhash }
return nil
} )
middlewareScenario ( t , "Valid api key, but does not match db hash" , func ( sc * scenarioContext ) {
keyhash := "Something_not_matching"
sc . fakeReq ( "GET" , "/" ) . withValidApiKey ( ) . exec ( )
bus . AddHandler ( "test" , func ( query * models . GetApiKeyByNameQuery ) error {
query . Result = & models . ApiKey { OrgId : 12 , Role : models . ROLE_EDITOR , Key : keyhash }
return nil
} )
assert . Equal ( t , 401 , sc . resp . Code )
assert . Equal ( t , errStringInvalidAPIKey , sc . respJson [ "message" ] )
} )
sc . fakeReq ( "GET" , "/" ) . withValidApiKey ( ) . exec ( )
middlewareScenario ( t , "Valid api key, but expired" , func ( sc * scenarioContext ) {
mockGetTime ( )
defer resetGetTime ( )
Convey ( "Should return api key invalid" , func ( ) {
So ( sc . resp . Code , ShouldEqual , 401 )
So ( sc . respJson [ "message" ] , ShouldEqual , errStringInvalidAPIKey )
} )
keyhash , err := util . EncodePassword ( "v5nAwpMafFP6znaS4urhdWDLS5511M42" , "asd" )
require . NoError ( t , err )
bus . AddHandler ( "test" , func ( query * models . GetApiKeyByNameQuery ) error {
// api key expired one second before
expires := getTime ( ) . Add ( - 1 * time . Second ) . Unix ( )
query . Result = & models . ApiKey { OrgId : 12 , Role : models . ROLE_EDITOR , Key : keyhash ,
Expires : & expires }
return nil
} )
middlewareScenario ( t , "Valid api key, but expired" , func ( sc * scenarioContext ) {
mockGetTime ( )
defer resetGetTime ( )
sc . fakeReq ( "GET" , "/" ) . withValidApiKey ( ) . exec ( )
keyhash , err := util . EncodePassword ( "v5nAwpMafFP6znaS4urhdWDLS5511M42" , "asd" )
So ( err , ShouldBeNil )
assert . Equal ( t , 401 , sc . resp . Code )
assert . Equal ( t , "Expired API key" , sc . respJson [ "message" ] )
} )
bus . AddHandler ( "test" , func ( query * models . GetApiKeyByNameQuery ) error {
// api key expired one second before
expires := getTime ( ) . Add ( - 1 * time . Second ) . Unix ( )
query . Result = & models . ApiKey { OrgId : 12 , Role : models . ROLE_EDITOR , Key : keyhash ,
Expires : & expires }
return nil
} )
middlewareScenario ( t , "Non-expired auth token in cookie which not are being rotated" , func ( sc * scenarioContext ) {
const userID int64 = 12
sc . fakeReq ( "GET" , "/" ) . withValidApiKey ( ) . exec ( )
sc . withTokenSessionCookie ( "token" )
Convey ( "Should return 401" , func ( ) {
So ( sc . resp . Code , ShouldEqual , 401 )
So ( sc . respJson [ "message" ] , ShouldEqual , "Expired API key" )
} )
bus . AddHandler ( "test" , func ( query * models . GetSignedInUserQuery ) error {
query . Result = & models . SignedInUser { OrgId : 2 , UserId : userID }
return nil
} )
middlewareScenario ( t , "Non-expired auth token in cookie which not are being rotated" , func ( sc * scenarioContext ) {
sc . withTokenSessionCookie ( "token" )
sc . userAuthTokenService . LookupTokenProvider = func ( ctx context . Context , unhashedToken string ) ( * models . UserToken , error ) {
return & models . UserToken {
UserId : userID ,
UnhashedToken : unhashedToken ,
} , nil
}
bus . AddHandler ( "test" , func ( query * models . GetSignedInUserQuery ) error {
query . Result = & models . SignedInUser { OrgId : 2 , UserId : 12 }
return nil
} )
sc . fakeReq ( "GET" , "/" ) . exec ( )
sc . userAuthTokenService . LookupTokenProvider = func ( ctx context . Context , unhashedToken string ) ( * models . UserToken , error ) {
return & models . UserToken {
UserId : 12 ,
UnhashedToken : unhashedToken ,
} , nil
}
assert . True ( t , sc . context . IsSignedIn )
assert . Equal ( t , userID , sc . context . UserId )
assert . Equal ( t , userID , sc . context . UserToken . UserId )
assert . Equal ( t , "token" , sc . context . UserToken . UnhashedToken )
assert . Equal ( t , "" , sc . resp . Header ( ) . Get ( "Set-Cookie" ) )
} )
sc . fakeReq ( "GET" , "/" ) . exec ( )
middlewareScenario ( t , "Non-expired auth token in cookie which are being rotated" , func ( sc * scenarioContext ) {
const userID int64 = 12
Convey ( "Should init context with user info" , func ( ) {
So ( sc . context . IsSignedIn , ShouldBeTrue )
So ( sc . context . UserId , ShouldEqual , 12 )
So ( sc . context . UserToken . UserId , ShouldEqual , 12 )
So ( sc . context . UserToken . UnhashedToken , ShouldEqual , "token" )
} )
sc . withTokenSessionCookie ( "token" )
Convey ( "Should no t s et cookie " , func ( ) {
So ( sc . resp . Header ( ) . Get ( "Set-Cookie" ) , ShouldEqual , "" )
} )
bus . AddHandler ( "test" , func ( query * models . GetSignedInUserQuery ) error {
query . Result = & models . SignedInUser { OrgId : 2 , UserId : userID }
return nil
} )
middlewareScenario ( t , "Non-expired auth token in cookie which are being rotated" , func ( sc * scenarioContext ) {
sc . withTokenSessionCookie ( "token" )
bus . AddHandler ( "test" , func ( query * models . GetSignedInUserQuery ) error {
query . Result = & models . SignedInUser { OrgId : 2 , UserId : 12 }
return nil
} )
sc . userAuthTokenService . LookupTokenProvider = func ( ctx context . Context , unhashedToken string ) ( * models . UserToken , error ) {
return & models . UserToken {
UserId : userID ,
UnhashedToken : "" ,
} , nil
}
sc . userAuthTokenService . LookupTokenProvider = func ( ctx context . Context , unhashedToken string ) ( * models . UserToken , error ) {
return & models . UserToken {
UserId : 12 ,
UnhashedToken : "" ,
} , nil
}
sc . userAuthTokenService . TryRotateTokenProvider = func ( ctx context . Context , userToken * models . UserToken ,
clientIP net . IP , userAgent string ) ( bool , error ) {
userToken . UnhashedToken = "rotated"
return true , nil
}
sc . userAuthTokenService . TryRotateTokenProvider = func ( ctx context . Context , userToken * models . UserToken ,
clientIP net . IP , userAgent string ) ( bool , error ) {
userToken . UnhashedToken = "rotated"
return true , nil
}
maxAge := int ( setting . LoginMaxLifetime . Seconds ( ) )
maxAge := int ( setting . LoginMaxLifetime . Seconds ( ) )
sameSiteModes := [ ] http . SameSite {
http . SameSiteNoneMode ,
http . SameSiteLaxMode ,
http . SameSiteStrictMode ,
}
for _ , sameSiteMode := range sameSiteModes {
t . Run ( fmt . Sprintf ( "Same site mode %d" , sameSiteMode ) , func ( t * testing . T ) {
origCookieSameSiteMode := setting . CookieSameSiteMode
t . Cleanup ( func ( ) {
setting . CookieSameSiteMode = origCookieSameSiteMode
} )
setting . CookieSameSiteMode = sameSiteMode
sameSitePolicies := [ ] http . SameSite {
http . SameSiteNoneMode ,
http . SameSiteLaxMode ,
http . SameSiteStrictMode ,
}
for _ , sameSitePolicy := range sameSitePolicies {
setting . CookieSameSiteMode = sameSitePolicy
expectedCookiePath := "/"
if len ( setting . AppSubUrl ) > 0 {
expectedCookiePath = setting . AppSubUrl
@ -276,283 +294,334 @@ func TestMiddlewareContext(t *testing.T) {
HttpOnly : true ,
MaxAge : maxAge ,
Secure : setting . CookieSecure ,
SameSite : sameSitePolicy ,
SameSite : sameSiteMode ,
}
sc . fakeReq ( "GET" , "/" ) . exec ( )
Convey ( fmt . Sprintf ( "Should init context with user info and setting.SameSite=%v" , sameSitePolicy ) , func ( ) {
So ( sc . context . IsSignedIn , ShouldBeTrue )
So ( sc . context . UserId , ShouldEqual , 12 )
So ( sc . context . UserToken . UserId , ShouldEqual , 12 )
So ( sc . context . UserToken . UnhashedToken , ShouldEqual , "rotated" )
} )
assert . True ( t , sc . context . IsSignedIn )
assert . Equal ( t , userID , sc . context . UserId )
assert . Equal ( t , userID , sc . context . UserToken . UserId )
assert . Equal ( t , "rotated" , sc . context . UserToken . UnhashedToken )
assert . Equal ( t , expectedCookie . String ( ) , sc . resp . Header ( ) . Get ( "Set-Cookie" ) )
} )
}
Convey ( fmt . Sprintf ( "Should set cookie with setting.SameSite=%v" , sameSitePolicy ) , func ( ) {
So ( sc . resp . Header ( ) . Get ( "Set-Cookie" ) , ShouldEqual , expectedCookie . String ( ) )
} )
}
t . Run ( "Should not set cookie with SameSite attribute when setting.CookieSameSiteDisabled is true" , func ( t * testing . T ) {
origCookieSameSiteDisabled := setting . CookieSameSiteDisabled
origCookieSameSiteMode := setting . CookieSameSiteMode
t . Cleanup ( func ( ) {
setting . CookieSameSiteDisabled = origCookieSameSiteDisabled
setting . CookieSameSiteMode = origCookieSameSiteMode
} )
setting . CookieSameSiteDisabled = true
setting . CookieSameSiteMode = http . SameSiteLaxMode
Convey ( "Should not set cookie with SameSite attribute when setting.CookieSameSiteDisabled is true" , func ( ) {
setting . CookieSameSiteDisabled = true
setting . CookieSameSiteMode = http . SameSiteLaxMode
expectedCookiePath := "/"
if len ( setting . AppSubUrl ) > 0 {
expectedCookiePath = setting . AppSubUrl
}
expectedCookie := & http . Cookie {
Name : setting . LoginCookieName ,
Value : "rotated" ,
Path : expectedCookiePath ,
HttpOnly : true ,
MaxAge : maxAge ,
Secure : setting . CookieSecure ,
}
expectedCookiePath := "/"
if len ( setting . AppSubUrl ) > 0 {
expectedCookiePath = setting . AppSubUrl
}
expectedCookie := & http . Cookie {
Name : setting . LoginCookieName ,
Value : "rotated" ,
Path : expectedCookiePath ,
HttpOnly : true ,
MaxAge : maxAge ,
Secure : setting . CookieSecure ,
}
sc . fakeReq ( "GET" , "/" ) . exec ( )
So ( sc . resp . Header ( ) . Get ( "Set-Cookie" ) , ShouldEqual , expectedCookie . String ( ) )
} )
sc . fakeReq ( "GET" , "/" ) . exec ( )
assert . Equal ( t , expectedCookie . String ( ) , sc . resp . Header ( ) . Get ( "Set-Cookie" ) )
} )
} )
middlewareScenario ( t , "Invalid/expired auth token in cookie" , func ( sc * scenarioContext ) {
sc . withTokenSessionCookie ( "token" )
middlewareScenario ( t , "Invalid/expired auth token in cookie" , func ( sc * scenarioContext ) {
sc . withTokenSessionCookie ( "token" )
sc . userAuthTokenService . LookupTokenProvider = func ( ctx context . Context , unhashedToken string ) ( * models . UserToken , error ) {
return nil , models . ErrUserTokenNotFound
}
sc . userAuthTokenService . LookupTokenProvider = func ( ctx context . Context , unhashedToken string ) ( * models . UserToken , error ) {
return nil , models . ErrUserTokenNotFound
}
sc . fakeReq ( "GET" , "/" ) . exec ( )
sc . fakeReq ( "GET" , "/" ) . exec ( )
Convey ( "Should not init context with user info" , func ( ) {
So ( sc . context . IsSignedIn , ShouldBeFalse )
So ( sc . context . UserId , ShouldEqual , 0 )
So ( sc . context . UserToken , ShouldBeNil )
} )
assert . False ( t , sc . context . IsSignedIn )
assert . Equal ( t , int64 ( 0 ) , sc . context . UserId )
assert . Nil ( t , sc . context . UserToken )
} )
middlewareScenario ( t , "When anonymous access is enabled" , func ( sc * scenarioContext ) {
const orgID int64 = 2
origAnonymousEnabled := setting . AnonymousEnabled
origAnonymousOrgName := setting . AnonymousOrgName
origAnonymousOrgRole := setting . AnonymousOrgRole
t . Cleanup ( func ( ) {
setting . AnonymousEnabled = origAnonymousEnabled
setting . AnonymousOrgName = origAnonymousOrgName
setting . AnonymousOrgRole = origAnonymousOrgRole
} )
setting . AnonymousEnabled = true
setting . AnonymousOrgName = "test"
setting . AnonymousOrgRole = string ( models . ROLE_EDITOR )
middlewareScenario ( t , "When anonymous access is enabled" , func ( sc * scenarioContext ) {
setting . AnonymousEnabled = true
setting . AnonymousOrgName = "test"
setting . AnonymousOrgRole = string ( models . ROLE_EDITOR )
bus . AddHandler ( "test" , func ( query * models . GetOrgByNameQuery ) error {
assert . Equal ( t , "test" , query . Name )
bus . AddHandler ( "test" , func ( query * models . GetOrgByNameQuery ) error {
So ( query . Name , ShouldEqual , "test" )
query . Result = & models . Org { Id : orgID , Name : "test" }
return nil
} )
query . Result = & models . Org { Id : 2 , Name : "test" }
sc . fakeReq ( "GET" , "/" ) . exec ( )
assert . Equal ( t , int64 ( 0 ) , sc . context . UserId )
assert . Equal ( t , orgID , sc . context . OrgId )
assert . Equal ( t , models . ROLE_EDITOR , sc . context . OrgRole )
assert . False ( t , sc . context . IsSignedIn )
} )
t . Run ( "auth_proxy" , func ( t * testing . T ) {
const userID int64 = 33
const orgID int64 = 4
origAuthProxyEnabled := setting . AuthProxyEnabled
origAuthProxyWhitelist := setting . AuthProxyWhitelist
origAuthProxyAutoSignUp := setting . AuthProxyAutoSignUp
origLDAPEnabled := setting . LDAPEnabled
origAuthProxyHeaderName := setting . AuthProxyHeaderName
origAuthProxyHeaderProperty := setting . AuthProxyHeaderProperty
origAuthProxyHeaders := setting . AuthProxyHeaders
t . Cleanup ( func ( ) {
setting . AuthProxyEnabled = origAuthProxyEnabled
setting . AuthProxyWhitelist = origAuthProxyWhitelist
setting . AuthProxyAutoSignUp = origAuthProxyAutoSignUp
setting . LDAPEnabled = origLDAPEnabled
setting . AuthProxyHeaderName = origAuthProxyHeaderName
setting . AuthProxyHeaderProperty = origAuthProxyHeaderProperty
setting . AuthProxyHeaders = origAuthProxyHeaders
} )
setting . AuthProxyEnabled = true
setting . AuthProxyWhitelist = ""
setting . AuthProxyAutoSignUp = true
setting . LDAPEnabled = true
setting . AuthProxyHeaderName = "X-WEBAUTH-USER"
setting . AuthProxyHeaderProperty = "username"
setting . AuthProxyHeaders = map [ string ] string { "Groups" : "X-WEBAUTH-GROUPS" }
const hdrName = "markelog"
const group = "grafana-core-team"
middlewareScenario ( t , "Should not sync the user if it's in the cache" , func ( sc * scenarioContext ) {
bus . AddHandler ( "test" , func ( query * models . GetSignedInUserQuery ) error {
query . Result = & models . SignedInUser { OrgId : orgID , UserId : query . UserId }
return nil
} )
sc . fakeReq ( "GET" , "/" ) . exec ( )
key := fmt . Sprintf ( authproxy . CachePrefix , authproxy . HashCacheKey ( hdrName + "-" + group ) )
err := sc . remoteCacheService . Set ( key , userID , 0 )
require . NoError ( t , err )
sc . fakeReq ( "GET" , "/" )
Convey ( "Should init context with org info" , func ( ) {
So ( sc . context . UserId , ShouldEqual , 0 )
So ( sc . context . OrgId , ShouldEqual , 2 )
So ( sc . context . OrgRole , ShouldEqual , models . ROLE_EDITOR )
} )
sc . req . Header . Set ( setting . AuthProxyHeaderName , hdrName )
sc . req . Header . Set ( "X-WEBAUTH-GROUPS" , group )
sc . exec ( )
Convey ( "context signed in should be false" , func ( ) {
So ( sc . context . IsSignedIn , ShouldBeFalse )
} )
assert . True ( t , sc . context . IsSignedIn )
assert . Equal ( t , userID , sc . context . UserId )
assert . Equal ( t , orgID , sc . context . OrgId )
} )
Convey ( "auth_proxy" , func ( ) {
setting . AuthProxyEnabled = true
setting . AuthProxyWhitelist = ""
setting . AuthProxyAutoSignUp = true
setting . LDAPEnabled = true
setting . AuthProxyHeaderName = "X-WEBAUTH-USER"
setting . AuthProxyHeaderProperty = "username"
setting . AuthProxyHeaders = map [ string ] string { "Groups" : "X-WEBAUTH-GROUPS" }
name := "markelog"
group := "grafana-core-team"
middlewareScenario ( t , "Should not sync the user if it's in the cache" , func ( sc * scenarioContext ) {
bus . AddHandler ( "test" , func ( query * models . GetSignedInUserQuery ) error {
query . Result = & models . SignedInUser { OrgId : 4 , UserId : query . UserId }
return nil
} )
key := fmt . Sprintf ( authproxy . CachePrefix , authproxy . HashCacheKey ( name + "-" + group ) )
err := sc . remoteCacheService . Set ( key , int64 ( 33 ) , 0 )
So ( err , ShouldBeNil )
sc . fakeReq ( "GET" , "/" )
middlewareScenario ( t , "Should respect auto signup option" , func ( sc * scenarioContext ) {
origLDAPEnabled = setting . LDAPEnabled
origAuthProxyAutoSignUp = setting . AuthProxyAutoSignUp
t . Cleanup ( func ( ) {
setting . LDAPEnabled = origLDAPEnabled
setting . AuthProxyAutoSignUp = origAuthProxyAutoSignUp
} )
setting . LDAPEnabled = false
setting . AuthProxyAutoSignUp = false
sc . req . Header . Add ( setting . AuthProxyHeaderName , name )
sc . req . Header . Add ( "X-WEBAUTH-GROUPS" , group )
sc . exec ( )
var actualAuthProxyAutoSignUp * bool = nil
Convey ( "Should init user via cache" , func ( ) {
So ( sc . context . IsSignedIn , ShouldBeTrue )
So ( sc . context . UserId , ShouldEqual , 33 )
So ( sc . context . OrgId , ShouldEqual , 4 )
} )
bus . AddHandler ( "test" , func ( cmd * models . UpsertUserCommand ) error {
actualAuthProxyAutoSignUp = & cmd . SignupAllowed
return login . ErrInvalidCredentials
} )
middlewareScenario ( t , "Should respect auto signup option" , func ( sc * scenarioContext ) {
setting . LDAPEnabled = false
setting . AuthProxyAutoSignUp = false
var actualAuthProxyAutoSignUp * bool = nil
sc . fakeReq ( "GET" , "/" )
sc . req . Header . Set ( setting . AuthProxyHeaderName , hdrName )
sc . exec ( )
bus . AddHandler ( "test" , func ( cmd * models . UpsertUserCommand ) error {
actualAuthProxyAutoSignUp = & cmd . SignupAllowed
return login . ErrInvalidCredentials
} )
assert . False ( t , * actualAuthProxyAutoSignUp )
assert . Equal ( t , sc . resp . Code , 407 )
assert . Nil ( t , sc . context )
} )
sc . fakeReq ( "GET" , "/" )
sc . req . Header . Add ( setting . AuthProxyHeaderName , name )
sc . exec ( )
middlewareScenario ( t , "Should create an user from a header" , func ( sc * scenarioContext ) {
origLDAPEnabled = setting . LDAPEnabled
origAuthProxyAutoSignUp = setting . AuthProxyAutoSignUp
t . Cleanup ( func ( ) {
setting . LDAPEnabled = origLDAPEnabled
setting . AuthProxyAutoSignUp = origAuthProxyAutoSignUp
} )
setting . LDAPEnabled = false
setting . AuthProxyAutoSignUp = true
assert . False ( t , * actualAuthProxyAutoSignUp )
assert . Equal ( t , sc . resp . Code , 407 )
assert . Nil ( t , sc . context )
bus . AddHandler ( "test" , func ( query * models . GetSignedInUserQuery ) error {
if query . UserId > 0 {
query . Result = & models . SignedInUser { OrgId : orgID , UserId : userID }
return nil
}
return models . ErrUserNotFound
} )
middlewareScenario ( t , "Should create an user from a header" , func ( sc * scenarioContext ) {
setting . LDAPEnabled = false
setting . AuthProxyAutoSignUp = true
bus . AddHandler ( "test" , func ( cmd * models . UpsertUserCommand ) error {
cmd . Result = & models . User { Id : userID }
return nil
} )
bus . AddHandler ( "test" , func ( query * models . GetSignedInUserQuery ) error {
if query . UserId > 0 {
query . Result = & models . SignedInUser { OrgId : 4 , UserId : 33 }
return nil
}
return models . ErrUserNotFound
} )
sc . fakeReq ( "GET" , "/" )
sc . req . Header . Set ( setting . AuthProxyHeaderName , hdrName )
sc . exec ( )
bus . AddHandler ( "test" , func ( cmd * models . UpsertUserCommand ) error {
cmd . Result = & models . User { Id : 33 }
return nil
} )
assert . True ( t , sc . context . IsSignedIn )
assert . Equal ( t , userID , sc . context . UserId )
assert . Equal ( t , orgID , sc . context . OrgId )
} )
sc . fakeReq ( "GET" , "/" )
sc . req . Header . Add ( setting . AuthProxyHeaderName , name )
sc . exec ( )
middlewareScenario ( t , "Should get an existing user from header" , func ( sc * scenarioContext ) {
const userID int64 = 12
const orgID int64 = 2
Convey ( "Should create user from header info" , func ( ) {
So ( sc . context . IsSignedIn , ShouldBeTrue )
So ( sc . context . UserId , ShouldEqual , 33 )
So ( sc . context . OrgId , ShouldEqual , 4 )
} )
origLDAPEnabled = setting . LDAPEnabled
t . Cleanup ( func ( ) {
setting . LDAPEnabled = origLDAPEnabled
} )
setting . LDAPEnabled = false
middlewareScenario ( t , "Should get an existing user from header" , func ( sc * scenarioContext ) {
setting . LDAPEnabled = false
bus . AddHandler ( "test" , func ( query * models . GetSignedInUserQuery ) error {
query . Result = & models . SignedInUser { OrgId : orgID , UserId : userID }
return nil
} )
bus . AddHandler ( "test" , func ( query * models . GetSignedInUserQuery ) error {
query . Result = & models . SignedIn User{ OrgId : 2 , UserId : 12 }
return nil
} )
bus . AddHandler ( "test" , func ( cmd * models . UpsertUserCommand ) error {
cmd . Result = & models . User { Id : userID }
return nil
} )
bus . AddHandler ( "test" , func ( cmd * models . UpsertUserCommand ) error {
cmd . Result = & models . User { Id : 12 }
return nil
} )
sc . fakeReq ( "GET" , "/" )
sc . req . Header . Set ( setting . AuthProxyHeaderName , hdrName )
sc . exec ( )
sc . fakeReq ( "GET" , "/" )
sc . req . Header . Add ( setting . AuthProxyHeaderName , name )
sc . exec ( )
assert . True ( t , sc . context . IsSignedIn )
assert . Equal ( t , userID , sc . context . UserId )
assert . Equal ( t , orgID , sc . context . OrgId )
} )
Convey ( "Should init context with user info" , func ( ) {
So ( sc . context . IsSignedIn , ShouldBeTrue )
So ( sc . context . UserId , ShouldEqual , 12 )
So ( sc . context . OrgId , ShouldEqual , 2 )
} )
middlewareScenario ( t , "Should allow the request from whitelist IP" , func ( sc * scenarioContext ) {
origAuthProxyWhitelist = setting . AuthProxyWhitelist
origLDAPEnabled = setting . LDAPEnabled
t . Cleanup ( func ( ) {
setting . AuthProxyWhitelist = origAuthProxyWhitelist
setting . LDAPEnabled = origLDAPEnabled
} )
setting . AuthProxyWhitelist = "192.168.1.0/24, 2001::0/120"
setting . LDAPEnabled = false
middlewareScenario ( t , "Should allow the request from whitelist IP" , func ( sc * scenarioContext ) {
setting . AuthProxyWhitelist = "192.168.1.0/24, 2001::0/120"
setting . LDAPEnabled = false
bus . AddHandler ( "test" , func ( query * models . GetSignedInUserQuery ) error {
query . Result = & models . SignedInUser { OrgId : orgID , UserId : userID }
return nil
} )
bus . AddHandler ( "test" , func ( query * models . GetSignedInUserQuery ) error {
query . Result = & models . SignedIn User{ OrgId : 4 , UserId : 33 }
return nil
} )
bus . AddHandler ( "test" , func ( cmd * models . UpsertUserCommand ) error {
cmd . Result = & models . User { Id : userID }
return nil
} )
bus . AddHandler ( "test" , func ( cmd * models . UpsertUserCommand ) error {
cmd . Result = & models . User { Id : 33 }
return nil
} )
sc . fakeReq ( "GET" , "/" )
sc . req . Header . Set ( setting . AuthProxyHeaderName , hdrName )
sc . req . RemoteAddr = "[2001::23]:12345"
sc . exec ( )
sc . fakeReq ( "GET" , "/" )
sc . req . Header . Add ( setting . AuthProxyHeaderName , name )
sc . req . RemoteAddr = "[2001::23]:12345"
sc . exec ( )
assert . True ( t , sc . context . IsSignedIn )
assert . Equal ( t , userID , sc . context . UserId )
assert . Equal ( t , orgID , sc . context . OrgId )
} )
Convey ( "Should init context with user info" , func ( ) {
So ( sc . context . IsSignedIn , ShouldBeTrue )
So ( sc . context . UserId , ShouldEqual , 33 )
So ( sc . context . OrgId , ShouldEqual , 4 )
} )
middlewareScenario ( t , "Should not allow the request from whitelist IP" , func ( sc * scenarioContext ) {
origAuthProxyWhitelist = setting . AuthProxyWhitelist
origLDAPEnabled = setting . LDAPEnabled
t . Cleanup ( func ( ) {
setting . AuthProxyWhitelist = origAuthProxyWhitelist
setting . LDAPEnabled = origLDAPEnabled
} )
setting . AuthProxyWhitelist = "8.8.8.8"
setting . LDAPEnabled = false
middlewareScenario ( t , "Should not allow the request from whitelist IP" , func ( sc * scenarioContext ) {
setting . AuthProxyWhitelist = "8.8.8.8"
setting . LDAPEnabled = false
bus . AddHandler ( "test" , func ( query * models . GetSignedInUserQuery ) error {
query . Result = & models . SignedInUser { OrgId : orgID , UserId : userID }
return nil
} )
bus . AddHandler ( "test" , func ( query * models . GetSignedInUserQuery ) error {
query . Result = & models . SignedIn User{ OrgId : 4 , UserId : 33 }
return nil
} )
bus . AddHandler ( "test" , func ( cmd * models . UpsertUserCommand ) error {
cmd . Result = & models . User { Id : userID }
return nil
} )
bus . AddHandler ( "test" , func ( cmd * models . UpsertUserCommand ) error {
cmd . Result = & models . User { Id : 33 }
return nil
} )
sc . fakeReq ( "GET" , "/" )
sc . req . Header . Set ( setting . AuthProxyHeaderName , hdrName )
sc . req . RemoteAddr = "[2001::23]:12345"
sc . exec ( )
sc . fakeReq ( "GET" , "/" )
sc . req . Header . Add ( setting . AuthProxyHeaderName , name )
sc . req . RemoteAddr = "[2001::23]:12345"
sc . exec ( )
assert . Equal ( t , 407 , sc . resp . Code )
assert . Nil ( t , sc . context )
} )
Convey ( "Should return 407 status code" , func ( ) {
So ( sc . resp . Code , ShouldEqual , 407 )
So ( sc . context , ShouldBeNil )
} )
middlewareScenario ( t , "Should return 407 status code if LDAP says no" , func ( sc * scenarioContext ) {
bus . AddHandler ( "LDAP" , func ( cmd * models . UpsertUserCommand ) error {
return errors . New ( "Do not add user" )
} )
middlewareScenario ( t , "Should return 407 status code if LDAP says no" , func ( sc * scenarioContext ) {
bus . AddHandler ( "LDAP" , func ( cmd * models . UpsertUserCommand ) error {
return errors . New ( "Do not add user" )
} )
sc . fakeReq ( "GET" , "/" )
sc . req . Header . Set ( setting . AuthProxyHeaderName , hdrName )
sc . exec ( )
sc . fakeReq ( "GET" , "/" )
sc . req . Header . Add ( setting . AuthProxyHeaderName , name )
sc . exec ( )
assert . Equal ( t , 407 , sc . resp . Code )
assert . Nil ( t , sc . context )
} )
Convey ( "Should return 407 status code" , func ( ) {
So ( sc . resp . Code , ShouldEqual , 407 )
So ( sc . context , ShouldBeNil )
} )
middlewareScenario ( t , "Should return 407 status code if there is cache mishap" , func ( sc * scenarioContext ) {
bus . AddHandler ( "Do not have the user" , func ( query * models . GetSignedInUserQuery ) error {
return errors . New ( "Do not add user" )
} )
middlewareScenario ( t , "Should return 407 status code if there is cache mishap" , func ( sc * scenarioContext ) {
bus . AddHandler ( "Do not have the user" , func ( query * models . GetSignedInUserQuery ) error {
return errors . New ( "Do not add user" )
} )
sc . fakeReq ( "GET" , "/" )
sc . req . Header . Set ( setting . AuthProxyHeaderName , hdrName )
sc . exec ( )
sc . fakeReq ( "GET" , "/" )
sc . req . Header . Add ( setting . AuthProxyHeaderName , name )
sc . exec ( )
Convey ( "Should return 407 status code" , func ( ) {
So ( sc . resp . Code , ShouldEqual , 407 )
So ( sc . context , ShouldBeNil )
} )
} )
assert . Equal ( t , 407 , sc . resp . Code )
assert . Nil ( t , sc . context )
} )
} )
}
func middlewareScenario ( t * testing . T , desc string , fn scenarioFunc ) {
Convey ( desc , func ( ) {
defer bus . ClearBusHandlers ( )
t . Helper ( )
t . Run ( desc , func ( t * testing . T ) {
t . Cleanup ( bus . ClearBusHandlers )
origLoginCookieName := setting . LoginCookieName
origLoginMaxLifetime := setting . LoginMaxLifetime
t . Cleanup ( func ( ) {
setting . LoginCookieName = origLoginCookieName
setting . LoginMaxLifetime = origLoginMaxLifetime
} )
setting . LoginCookieName = "grafana_session"
var err error
setting . LoginMaxLifetime , err = gtime . ParseDuration ( "30d" )
require . NoError ( t , err )
sc := & scenarioContext { }
sc := & scenarioContext { t : t }
viewsPath , err := filepath . Abs ( "../../public/views" )
require . NoError ( t , err )
@ -590,7 +659,7 @@ func middlewareScenario(t *testing.T, desc string, fn scenarioFunc) {
func TestDontRotateTokensOnCancelledRequests ( t * testing . T ) {
ctx , cancel := context . WithCancel ( context . Background ( ) )
reqContext , _ , err := initTokenRotationTest ( ctx )
reqContext , _ , err := initTokenRotationTest ( ctx , t )
require . NoError ( t , err )
tryRotateCallCount := 0
@ -612,7 +681,7 @@ func TestDontRotateTokensOnCancelledRequests(t *testing.T) {
}
func TestTokenRotationAtEndOfRequest ( t * testing . T ) {
reqContext , rr , err := initTokenRotationTest ( context . Background ( ) )
reqContext , rr , err := initTokenRotationTest ( context . Background ( ) , t )
require . NoError ( t , err )
uts := & auth . FakeUserAuthTokenService {
@ -643,7 +712,15 @@ func TestTokenRotationAtEndOfRequest(t *testing.T) {
assert . True ( t , foundLoginCookie , "Could not find cookie" )
}
func initTokenRotationTest ( ctx context . Context ) ( * models . ReqContext , * httptest . ResponseRecorder , error ) {
func initTokenRotationTest ( ctx context . Context , t * testing . T ) ( * models . ReqContext , * httptest . ResponseRecorder , error ) {
t . Helper ( )
origLoginCookieName := setting . LoginCookieName
origLoginMaxLifetime := setting . LoginMaxLifetime
t . Cleanup ( func ( ) {
setting . LoginCookieName = origLoginCookieName
setting . LoginMaxLifetime = origLoginMaxLifetime
} )
setting . LoginCookieName = "login_token"
var err error
setting . LoginMaxLifetime , err = gtime . ParseDuration ( "7d" )