2015-05-01 17:55:59 +08:00
package middleware
import (
2022-10-19 00:17:28 +08:00
"errors"
2015-05-01 17:55:59 +08:00
"net/http"
2015-05-02 04:26:16 +08:00
"path/filepath"
2015-05-01 17:55:59 +08:00
"testing"
2022-09-27 19:58:49 +08:00
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
2023-02-02 21:36:16 +08:00
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
2019-05-06 15:22:59 +08:00
"github.com/grafana/grafana/pkg/api/dtos"
2020-12-16 02:09:04 +08:00
"github.com/grafana/grafana/pkg/infra/fs"
2021-01-12 14:42:32 +08:00
"github.com/grafana/grafana/pkg/infra/log"
2023-08-09 14:54:52 +08:00
"github.com/grafana/grafana/pkg/services/authn"
2022-12-02 22:10:03 +08:00
"github.com/grafana/grafana/pkg/services/authn/authntest"
2020-12-11 18:44:44 +08:00
"github.com/grafana/grafana/pkg/services/contexthandler"
2023-01-27 15:50:36 +08:00
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
2024-09-25 00:38:09 +08:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
2022-09-23 04:04:48 +08:00
"github.com/grafana/grafana/pkg/services/navtree"
2022-08-04 21:44:14 +08:00
"github.com/grafana/grafana/pkg/services/user/usertest"
2015-05-02 15:24:56 +08:00
"github.com/grafana/grafana/pkg/setting"
2021-10-11 20:30:59 +08:00
"github.com/grafana/grafana/pkg/web"
2015-05-01 17:55:59 +08:00
)
2019-06-19 02:24:23 +08:00
func TestMiddleWareSecurityHeaders ( t * testing . T ) {
2020-12-04 18:09:32 +08:00
middlewareScenario ( t , "middleware should get correct x-xss-protection header" , func ( t * testing . T , sc * scenarioContext ) {
2020-12-03 15:28:54 +08:00
sc . fakeReq ( "GET" , "/api/" ) . exec ( )
assert . Equal ( t , "1; mode=block" , sc . resp . Header ( ) . Get ( "X-XSS-Protection" ) )
2020-12-11 18:44:44 +08:00
} , func ( cfg * setting . Cfg ) {
cfg . XSSProtectionHeader = true
2020-12-03 15:28:54 +08:00
} )
2019-06-19 02:24:23 +08:00
2020-12-04 18:09:32 +08:00
middlewareScenario ( t , "middleware should not get x-xss-protection when disabled" , func ( t * testing . T , sc * scenarioContext ) {
2020-12-03 15:28:54 +08:00
sc . fakeReq ( "GET" , "/api/" ) . exec ( )
assert . Empty ( t , sc . resp . Header ( ) . Get ( "X-XSS-Protection" ) )
2020-12-11 18:44:44 +08:00
} , func ( cfg * setting . Cfg ) {
cfg . XSSProtectionHeader = false
2020-12-03 15:28:54 +08:00
} )
2019-06-19 02:24:23 +08:00
2020-12-04 18:09:32 +08:00
middlewareScenario ( t , "middleware should add correct Strict-Transport-Security header" , func ( t * testing . T , sc * scenarioContext ) {
2020-12-03 15:28:54 +08:00
sc . fakeReq ( "GET" , "/api/" ) . exec ( )
assert . Equal ( t , "max-age=64000" , sc . resp . Header ( ) . Get ( "Strict-Transport-Security" ) )
2020-12-11 18:44:44 +08:00
sc . cfg . StrictTransportSecurityPreload = true
2020-12-03 15:28:54 +08:00
sc . fakeReq ( "GET" , "/api/" ) . exec ( )
assert . Equal ( t , "max-age=64000; preload" , sc . resp . Header ( ) . Get ( "Strict-Transport-Security" ) )
2020-12-11 18:44:44 +08:00
sc . cfg . StrictTransportSecuritySubDomains = true
2020-12-03 15:28:54 +08:00
sc . fakeReq ( "GET" , "/api/" ) . exec ( )
assert . Equal ( t , "max-age=64000; preload; includeSubDomains" , sc . resp . Header ( ) . Get ( "Strict-Transport-Security" ) )
2020-12-11 18:44:44 +08:00
} , func ( cfg * setting . Cfg ) {
cfg . StrictTransportSecurity = true
cfg . StrictTransportSecurityMaxAge = 64000
2019-06-19 02:24:23 +08:00
} )
}
2022-11-17 01:11:26 +08:00
func TestMiddleWareContentSecurityPolicyHeaders ( t * testing . T ) {
policy := ` script-src 'self' 'strict-dynamic' 'nonce-[^']+';connect-src 'self' ws://localhost:3000/ wss://localhost:3000/; `
middlewareScenario ( t , "middleware should add Content-Security-Policy" , func ( t * testing . T , sc * scenarioContext ) {
sc . fakeReq ( "GET" , "/api/" ) . exec ( )
assert . Regexp ( t , policy , sc . resp . Header ( ) . Get ( "Content-Security-Policy" ) )
} , func ( cfg * setting . Cfg ) {
cfg . CSPEnabled = true
cfg . CSPTemplate = "script-src 'self' 'strict-dynamic' $NONCE;connect-src 'self' ws://$ROOT_PATH wss://$ROOT_PATH;"
cfg . AppURL = "http://localhost:3000/"
} )
middlewareScenario ( t , "middleware should add Content-Security-Policy-Report-Only" , func ( t * testing . T , sc * scenarioContext ) {
sc . fakeReq ( "GET" , "/api/" ) . exec ( )
assert . Regexp ( t , policy , sc . resp . Header ( ) . Get ( "Content-Security-Policy-Report-Only" ) )
} , func ( cfg * setting . Cfg ) {
cfg . CSPReportOnlyEnabled = true
cfg . CSPReportOnlyTemplate = "script-src 'self' 'strict-dynamic' $NONCE;connect-src 'self' ws://$ROOT_PATH wss://$ROOT_PATH;"
cfg . AppURL = "http://localhost:3000/"
} )
middlewareScenario ( t , "middleware can add both CSP and CSP-Report-Only" , func ( t * testing . T , sc * scenarioContext ) {
sc . fakeReq ( "GET" , "/api/" ) . exec ( )
cspHeader := sc . resp . Header ( ) . Get ( "Content-Security-Policy" )
cspReportOnlyHeader := sc . resp . Header ( ) . Get ( "Content-Security-Policy-Report-Only" )
assert . Regexp ( t , policy , cspHeader )
assert . Regexp ( t , policy , cspReportOnlyHeader )
// assert CSP-Report-Only reuses the same nonce as CSP
assert . Equal ( t , cspHeader , cspReportOnlyHeader )
} , func ( cfg * setting . Cfg ) {
cfg . CSPEnabled = true
cfg . CSPTemplate = "script-src 'self' 'strict-dynamic' $NONCE;connect-src 'self' ws://$ROOT_PATH wss://$ROOT_PATH;"
cfg . CSPReportOnlyEnabled = true
cfg . CSPReportOnlyTemplate = "script-src 'self' 'strict-dynamic' $NONCE;connect-src 'self' ws://$ROOT_PATH wss://$ROOT_PATH;"
cfg . AppURL = "http://localhost:3000/"
} )
}
2015-05-01 22:23:36 +08:00
func TestMiddlewareContext ( t * testing . T ) {
2023-01-25 22:09:27 +08:00
const noStore = "no-store"
2015-05-01 17:55:59 +08:00
2020-12-04 18:09:32 +08:00
middlewareScenario ( t , "middleware should add context to injector" , func ( t * testing . T , sc * scenarioContext ) {
2020-12-03 15:28:54 +08:00
sc . fakeReq ( "GET" , "/" ) . exec ( )
assert . NotNil ( t , sc . context )
} )
2015-05-01 17:55:59 +08:00
2020-12-04 18:09:32 +08:00
middlewareScenario ( t , "Default middleware should allow get request" , func ( t * testing . T , sc * scenarioContext ) {
2020-12-03 15:28:54 +08:00
sc . fakeReq ( "GET" , "/" ) . exec ( )
assert . Equal ( t , 200 , sc . resp . Code )
} )
2015-05-01 22:23:36 +08:00
2020-12-04 18:09:32 +08:00
middlewareScenario ( t , "middleware should add Cache-Control header for requests to API" , func ( t * testing . T , sc * scenarioContext ) {
2020-12-03 15:28:54 +08:00
sc . fakeReq ( "GET" , "/api/search" ) . exec ( )
2023-01-25 22:09:27 +08:00
assert . Equal ( t , noStore , sc . resp . Header ( ) . Get ( "Cache-Control" ) )
assert . Empty ( t , sc . resp . Header ( ) . Get ( "Pragma" ) )
assert . Empty ( t , sc . resp . Header ( ) . Get ( "Expires" ) )
2020-12-03 15:28:54 +08:00
} )
2017-07-04 22:33:37 +08:00
2024-09-17 16:28:38 +08:00
middlewareScenario ( t , "middleware should pass cache-control on datasource resources with private cache control" , func ( t * testing . T , sc * scenarioContext ) {
2023-02-03 04:34:55 +08:00
sc = sc . fakeReq ( "GET" , "/api/datasources/1/resources/foo" )
2023-02-28 04:55:22 +08:00
sc . resp . Header ( ) . Add ( "Cache-Control" , "private, max-age=86400" )
2023-02-03 04:34:55 +08:00
sc . resp . Header ( ) . Add ( "X-Grafana-Cache" , "true" )
sc . exec ( )
2023-02-28 04:55:22 +08:00
assert . Equal ( t , "private, max-age=86400" , sc . resp . Header ( ) . Get ( "Cache-Control" ) )
2023-02-03 04:34:55 +08:00
} )
2024-09-17 16:28:38 +08:00
middlewareScenario ( t , "middleware should not pass cache-control on datasource resources with public cache control" , func ( t * testing . T , sc * scenarioContext ) {
2023-02-03 04:34:55 +08:00
sc = sc . fakeReq ( "GET" , "/api/datasources/1/resources/foo" )
2023-02-28 04:55:22 +08:00
sc . resp . Header ( ) . Add ( "Cache-Control" , "public, max-age=86400, private" )
2023-02-03 04:34:55 +08:00
sc . resp . Header ( ) . Add ( "X-Grafana-Cache" , "true" )
sc . exec ( )
assert . Equal ( t , noStore , sc . resp . Header ( ) . Get ( "Cache-Control" ) )
2024-09-17 16:28:38 +08:00
} )
middlewareScenario ( t , "middleware should pass cache-control on plugins resources with private cache control" , func ( t * testing . T , sc * scenarioContext ) {
sc = sc . fakeReq ( "GET" , "/api/plugins/1/resources/foo" )
sc . resp . Header ( ) . Add ( "Cache-Control" , "private, max-age=86400" )
sc . resp . Header ( ) . Add ( "X-Grafana-Cache" , "true" )
sc . exec ( )
assert . Equal ( t , "private, max-age=86400" , sc . resp . Header ( ) . Get ( "Cache-Control" ) )
} )
middlewareScenario ( t , "middleware should not pass cache-control on plugins resources with public cache control" , func ( t * testing . T , sc * scenarioContext ) {
sc = sc . fakeReq ( "GET" , "/api/plugins/1/resources/foo" )
sc . resp . Header ( ) . Add ( "Cache-Control" , "public, max-age=86400, private" )
sc . resp . Header ( ) . Add ( "X-Grafana-Cache" , "true" )
sc . exec ( )
assert . Equal ( t , noStore , sc . resp . Header ( ) . Get ( "Cache-Control" ) )
2023-02-03 04:34:55 +08:00
} )
2020-12-04 18:09:32 +08:00
middlewareScenario ( t , "middleware should not add Cache-Control header for requests to datasource proxy API" , func (
t * testing . T , sc * scenarioContext ) {
2020-12-03 15:28:54 +08:00
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" ) )
} )
2019-05-06 15:22:59 +08:00
2020-12-16 02:09:04 +08:00
middlewareScenario ( t , "middleware should add Cache-Control header for requests with HTML response" , func (
2020-12-04 18:09:32 +08:00
t * testing . T , sc * scenarioContext ) {
2023-01-27 15:50:36 +08:00
sc . handlerFunc = func ( c * contextmodel . ReqContext ) {
2020-12-16 02:09:04 +08:00
t . Log ( "Handler called" )
2020-12-03 15:28:54 +08:00
data := & dtos . IndexViewData {
User : & dtos . CurrentUser { } ,
2023-02-01 03:14:15 +08:00
Settings : & dtos . FrontendSettingsDTO { } ,
2022-09-28 14:29:35 +08:00
NavTree : & navtree . NavTreeRoot { } ,
2023-12-05 15:34:22 +08:00
Assets : & dtos . EntryPointAssets {
2024-01-04 15:00:07 +08:00
JSFiles : [ ] dtos . EntryPointAsset { } ,
Dark : "dark.css" ,
Light : "light.css" ,
2023-12-05 15:34:22 +08:00
} ,
2020-12-03 15:28:54 +08:00
}
2021-08-10 19:29:46 +08:00
t . Log ( "Calling HTML" , "data" , data )
2023-12-05 15:34:22 +08:00
c . HTML ( http . StatusOK , "index" , data )
2020-12-16 02:09:04 +08:00
t . Log ( "Returned HTML with code 200" )
}
2020-12-03 15:28:54 +08:00
sc . fakeReq ( "GET" , "/" ) . exec ( )
2020-12-16 02:09:04 +08:00
require . Equal ( t , 200 , sc . resp . Code )
2023-01-25 22:09:27 +08:00
assert . Equal ( t , noStore , sc . resp . Header ( ) . Get ( "Cache-Control" ) )
assert . Empty ( t , sc . resp . Header ( ) . Get ( "Pragma" ) )
assert . Empty ( t , sc . resp . Header ( ) . Get ( "Expires" ) )
2020-12-03 15:28:54 +08:00
} )
2017-07-04 22:33:37 +08:00
2020-12-04 18:09:32 +08:00
middlewareScenario ( t , "middleware should add X-Frame-Options header with deny for request when not allowing embedding" , func (
t * testing . T , sc * scenarioContext ) {
2020-12-03 15:28:54 +08:00
sc . fakeReq ( "GET" , "/api/search" ) . exec ( )
assert . Equal ( t , "deny" , sc . resp . Header ( ) . Get ( "X-Frame-Options" ) )
} )
2019-05-06 15:56:23 +08:00
2020-12-04 18:09:32 +08:00
middlewareScenario ( t , "middleware should not add X-Frame-Options header for request when allowing embedding" , func (
t * testing . T , sc * scenarioContext ) {
2020-12-03 15:28:54 +08:00
sc . fakeReq ( "GET" , "/api/search" ) . exec ( )
assert . Empty ( t , sc . resp . Header ( ) . Get ( "X-Frame-Options" ) )
2020-12-11 18:44:44 +08:00
} , func ( cfg * setting . Cfg ) {
cfg . AllowEmbedding = true
2020-12-03 15:28:54 +08:00
} )
2019-05-06 15:56:23 +08:00
2022-12-01 01:12:34 +08:00
middlewareScenario ( t , "middleware should add custom response headers" , func ( t * testing . T , sc * scenarioContext ) {
sc . fakeReq ( "GET" , "/api/" ) . exec ( )
assert . Regexp ( t , "test" , sc . resp . Header ( ) . Get ( "X-Custom-Header" ) )
assert . Regexp ( t , "other-test" , sc . resp . Header ( ) . Get ( "X-Other-Header" ) )
} , func ( cfg * setting . Cfg ) {
cfg . CustomResponseHeaders = map [ string ] string {
"X-Custom-Header" : "test" ,
"X-Other-Header" : "other-test" ,
}
} )
2023-12-18 20:21:57 +08:00
middlewareScenario ( t , "middleware should not add Cache-Control header for requests to render pdf" , func (
t * testing . T , sc * scenarioContext ) {
sc . fakeReq ( "GET" , "/api/reports/render/pdf/" ) . 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 not add Cache-Control header for requests to render panel as image" , func (
t * testing . T , sc * scenarioContext ) {
sc . fakeReq ( "GET" , "/render/d-solo/" ) . 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" ) )
} )
2015-05-01 17:55:59 +08:00
}
2015-05-02 15:24:56 +08:00
2020-12-11 18:44:44 +08:00
func middlewareScenario ( t * testing . T , desc string , fn scenarioFunc , cbs ... func ( * setting . Cfg ) ) {
2020-12-03 15:28:54 +08:00
t . Helper ( )
t . Run ( desc , func ( t * testing . T ) {
2021-01-12 14:42:32 +08:00
logger := log . New ( "test" )
2020-12-11 18:44:44 +08:00
loginMaxLifetime , err := gtime . ParseDuration ( "30d" )
2020-11-03 02:26:19 +08:00
require . NoError ( t , err )
2020-12-11 18:44:44 +08:00
cfg := setting . NewCfg ( )
cfg . LoginCookieName = "grafana_session"
cfg . LoginMaxLifetime = loginMaxLifetime
2020-12-16 02:09:04 +08:00
// Required when rendering errors
2023-12-05 15:34:22 +08:00
cfg . ErrTemplateName = "error"
2020-12-11 18:44:44 +08:00
for _ , cb := range cbs {
cb ( cfg )
}
2019-02-06 04:14:23 +08:00
2020-12-11 18:44:44 +08:00
sc := & scenarioContext { t : t , cfg : cfg }
2020-02-18 00:31:44 +08:00
viewsPath , err := filepath . Abs ( "../../public/views" )
require . NoError ( t , err )
2020-12-16 02:09:04 +08:00
exists , err := fs . Exists ( viewsPath )
require . NoError ( t , err )
require . Truef ( t , exists , "Views directory should exist at %q" , viewsPath )
2015-05-02 15:24:56 +08:00
2021-10-11 20:30:59 +08:00
sc . m = web . New ( )
2022-12-01 01:12:34 +08:00
sc . m . Use ( AddCustomResponseHeaders ( cfg ) )
2020-12-11 18:44:44 +08:00
sc . m . Use ( AddDefaultResponseHeaders ( cfg ) )
2022-11-17 01:11:26 +08:00
sc . m . UseMiddleware ( ContentSecurityPolicy ( cfg , logger ) )
2021-10-11 20:30:59 +08:00
sc . m . UseMiddleware ( web . Renderer ( viewsPath , "[[" , "]]" ) )
2015-05-02 15:24:56 +08:00
2023-08-09 14:54:52 +08:00
// defalut to not authenticated request
sc . authnService = & authntest . FakeService { ExpectedErr : errors . New ( "no auth" ) }
2022-08-16 22:08:59 +08:00
sc . userService = usertest . NewUserServiceFake ( )
2023-08-09 14:54:52 +08:00
ctxHdlr := getContextHandler ( t , cfg , sc . authnService )
2020-12-11 18:44:44 +08:00
sc . m . Use ( ctxHdlr . Middleware )
2022-09-27 19:58:49 +08:00
sc . m . Use ( OrgRedirect ( sc . cfg , sc . userService ) )
2024-09-10 22:45:27 +08:00
// handle action urls
sc . m . Use ( ValidateActionUrl ( sc . cfg , logger ) )
2015-05-02 15:24:56 +08:00
2023-01-27 15:50:36 +08:00
sc . defaultHandler = func ( c * contextmodel . ReqContext ) {
2020-12-11 18:44:44 +08:00
require . NotNil ( t , c )
t . Log ( "Default HTTP handler called" )
2015-05-02 15:24:56 +08:00
sc . context = c
if sc . handlerFunc != nil {
sc . handlerFunc ( sc . context )
2022-08-09 20:58:50 +08:00
if ! c . Resp . Written ( ) {
c . Resp . WriteHeader ( http . StatusOK )
}
2020-01-15 20:03:12 +08:00
} else {
2020-12-16 02:09:04 +08:00
t . Log ( "Returning JSON OK" )
2023-08-30 23:46:47 +08:00
resp := make ( map [ string ] any )
2020-11-17 18:51:31 +08:00
resp [ "message" ] = "OK"
2022-04-15 20:01:58 +08:00
c . JSON ( http . StatusOK , resp )
2015-05-02 15:24:56 +08:00
}
2015-05-02 18:06:58 +08:00
}
sc . m . Get ( "/" , sc . defaultHandler )
2015-05-02 15:24:56 +08:00
2020-12-04 18:09:32 +08:00
fn ( t , sc )
2015-05-02 15:24:56 +08:00
} )
}
2020-02-18 00:31:44 +08:00
2023-08-09 14:54:52 +08:00
func getContextHandler ( t * testing . T , cfg * setting . Cfg , authnService authn . Service ) * contexthandler . ContextHandler {
2020-12-03 15:28:54 +08:00
t . Helper ( )
2025-02-15 10:08:00 +08:00
return contexthandler . ProvideService ( cfg , authnService , featuremgmt . WithFeatures ( ) )
2020-06-30 01:15:11 +08:00
}