2017-08-22 11:14:15 -04:00
package pluginproxy
import (
2018-06-12 11:39:38 -04:00
"bytes"
2021-07-07 02:54:17 -04:00
"context"
2022-04-25 12:57:45 -04:00
"encoding/json"
2022-12-01 14:51:12 -05:00
"errors"
2018-06-12 11:39:38 -04:00
"fmt"
2022-08-10 09:37:51 -04:00
"io"
2017-08-22 11:14:15 -04:00
"net/http"
2019-05-01 10:32:03 -04:00
"net/http/httptest"
2017-08-23 04:52:31 -04:00
"net/url"
2022-08-10 09:37:51 -04:00
"os"
2020-04-22 04:30:06 -04:00
"strings"
2017-08-22 11:14:15 -04:00
"testing"
2018-06-12 11:39:38 -04:00
"time"
2017-08-22 11:14:15 -04:00
2022-06-27 12:23:15 -04:00
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"
2025-01-21 04:06:55 -05:00
claims "github.com/grafana/authlib/types"
2026-06-08 06:28:30 -04:00
"github.com/grafana/grafana/pkg/api/datasource/validation"
2024-06-13 00:11:35 -04:00
"github.com/grafana/grafana/pkg/apimachinery/identity"
2021-05-19 17:53:41 -04:00
"github.com/grafana/grafana/pkg/components/simplejson"
2022-10-19 09:02:15 -04:00
"github.com/grafana/grafana/pkg/infra/db"
2023-10-02 03:14:10 -04:00
"github.com/grafana/grafana/pkg/infra/db/dbtest"
2021-05-19 17:53:41 -04:00
"github.com/grafana/grafana/pkg/infra/httpclient"
2022-08-25 17:04:44 -04:00
"github.com/grafana/grafana/pkg/infra/log"
2022-01-20 05:10:12 -05:00
"github.com/grafana/grafana/pkg/infra/tracing"
2021-03-08 01:02:49 -05:00
"github.com/grafana/grafana/pkg/plugins"
2025-10-16 10:14:05 -04:00
"github.com/grafana/grafana/pkg/plugins/manager/pluginfakes"
2023-10-02 03:14:10 -04:00
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
2024-11-27 05:06:39 -05:00
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey"
2023-01-27 02:50:36 -05:00
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
2022-06-27 12:23:15 -04:00
"github.com/grafana/grafana/pkg/services/datasources"
2022-02-11 09:52:14 -05:00
datasourceservice "github.com/grafana/grafana/pkg/services/datasources/service"
2022-02-17 08:03:45 -05:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
2021-07-07 02:54:17 -04:00
"github.com/grafana/grafana/pkg/services/oauthtoken"
2023-10-02 03:14:10 -04:00
"github.com/grafana/grafana/pkg/services/oauthtoken/oauthtokentest"
2022-08-10 05:56:48 -04:00
"github.com/grafana/grafana/pkg/services/org"
2024-05-28 09:59:06 -04:00
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginconfig"
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
2023-10-17 05:09:56 -04:00
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
2022-11-14 14:08:10 -05:00
"github.com/grafana/grafana/pkg/services/quota/quotatest"
2021-11-04 12:47:21 -04:00
"github.com/grafana/grafana/pkg/services/secrets"
"github.com/grafana/grafana/pkg/services/secrets/fakes"
2022-08-25 17:04:44 -04:00
secretskvs "github.com/grafana/grafana/pkg/services/secrets/kvstore"
secretsmng "github.com/grafana/grafana/pkg/services/secrets/manager"
2022-08-10 05:56:48 -04:00
"github.com/grafana/grafana/pkg/services/user"
2021-05-19 17:53:41 -04:00
"github.com/grafana/grafana/pkg/setting"
2024-02-09 09:35:39 -05:00
"github.com/grafana/grafana/pkg/tests/testsuite"
2025-09-08 09:49:49 -04:00
"github.com/grafana/grafana/pkg/util/testutil"
2021-10-11 08:30:59 -04:00
"github.com/grafana/grafana/pkg/web"
2017-08-22 11:14:15 -04:00
)
2024-02-09 09:35:39 -05:00
func TestMain ( m * testing . M ) {
testsuite . Run ( m )
}
2025-06-29 10:56:24 -04:00
func TestIntegrationDataSourceProxy_routeRule ( t * testing . T ) {
2025-09-08 09:49:49 -04:00
testutil . SkipIntegrationTestInShortMode ( t )
2022-04-01 07:26:49 -04:00
cfg := & setting . Cfg { }
2021-05-19 17:53:41 -04:00
2020-11-13 03:52:38 -05:00
t . Run ( "Plugin with routes" , func ( t * testing . T ) {
2021-11-01 05:53:33 -04:00
routes := [ ] * plugins . Route {
{
Path : "api/v4/" ,
URL : "https://www.google.com" ,
2022-08-10 05:56:48 -04:00
ReqRole : org . RoleEditor ,
2021-11-01 05:53:33 -04:00
Headers : [ ] plugins . Header {
{ Name : "x-header" , Content : "my secret {{.SecureJsonData.key}}" } ,
2020-11-13 03:52:38 -05:00
} ,
2021-11-01 05:53:33 -04:00
} ,
{
Path : "api/admin" ,
URL : "https://www.google.com" ,
2022-08-10 05:56:48 -04:00
ReqRole : org . RoleAdmin ,
2021-11-01 05:53:33 -04:00
Headers : [ ] plugins . Header {
{ Name : "x-header" , Content : "my secret {{.SecureJsonData.key}}" } ,
2020-11-13 03:52:38 -05:00
} ,
2021-11-01 05:53:33 -04:00
} ,
{
Path : "api/anon" ,
URL : "https://www.google.com" ,
Headers : [ ] plugins . Header {
{ Name : "x-header" , Content : "my secret {{.SecureJsonData.key}}" } ,
2017-08-22 11:14:15 -04:00
} ,
2021-11-01 05:53:33 -04:00
} ,
{
Path : "api/common" ,
URL : "{{.JsonData.dynamicUrl}}" ,
URLParams : [ ] plugins . URLParam {
{ Name : "{{.JsonData.queryParam}}" , Content : "{{.SecureJsonData.key}}" } ,
2017-08-23 04:52:31 -04:00
} ,
2021-11-01 05:53:33 -04:00
Headers : [ ] plugins . Header {
{ Name : "x-header" , Content : "my secret {{.SecureJsonData.key}}" } ,
2021-03-31 10:38:35 -04:00
} ,
2020-11-13 03:52:38 -05:00
} ,
2021-11-01 05:53:33 -04:00
{
Path : "api/restricted" ,
2022-08-10 05:56:48 -04:00
ReqRole : org . RoleAdmin ,
2021-11-01 05:53:33 -04:00
} ,
{
Path : "api/body" ,
URL : "http://www.test.com" ,
Body : [ ] byte ( ` { "url": " {{ .JsonData .dynamicUrl }} ", "secret": " {{ .SecureJsonData .key }} " } ` ) ,
} ,
2024-06-05 07:36:14 -04:00
{
Path : "mypath" ,
URL : "https://example.com/api/v1/" ,
} ,
2024-07-25 10:22:42 -04:00
{
Path : "api/rbac-home" ,
ReqAction : "datasources:read" ,
} ,
{
Path : "api/rbac-restricted" ,
ReqAction : "test-app.settings:read" ,
} ,
2025-01-07 10:27:34 -05:00
{
Path : "encodedPath" ,
URL : "http://encoded.com" ,
} ,
2020-11-13 03:52:38 -05:00
}
2017-08-22 11:14:15 -04:00
2022-06-27 12:23:15 -04:00
ds := & datasources . DataSource {
2024-07-25 10:22:42 -04:00
UID : "dsUID" ,
2023-08-30 11:46:47 -04:00
JsonData : simplejson . NewFromAny ( map [ string ] any {
2020-11-13 03:52:38 -05:00
"clientId" : "asd" ,
"dynamicUrl" : "https://dynamic.grafana.com" ,
"queryParam" : "apiKey" ,
} ) ,
}
2021-10-08 08:46:35 -04:00
jd , err := ds . JsonData . Map ( )
require . NoError ( t , err )
dsInfo := DSInfo {
2023-02-02 11:22:43 -05:00
ID : ds . ID ,
2021-10-08 08:46:35 -04:00
Updated : ds . Updated ,
JSONData : jd ,
DecryptedSecureJSONData : map [ string ] string {
"key" : "123" ,
} ,
}
2023-01-27 02:50:36 -05:00
setUp := func ( ) ( * contextmodel . ReqContext , * http . Request ) {
2020-11-13 03:52:38 -05:00
req , err := http . NewRequest ( "GET" , "http://localhost/asd" , nil )
require . NoError ( t , err )
2023-01-27 02:50:36 -05:00
ctx := & contextmodel . ReqContext {
2021-10-11 08:30:59 -04:00
Context : & web . Context { Req : req } ,
2022-08-10 05:56:48 -04:00
SignedInUser : & user . SignedInUser { OrgRole : org . RoleEditor } ,
2017-08-23 04:52:31 -04:00
}
2020-11-13 03:52:38 -05:00
return ctx , req
}
2017-08-22 11:14:15 -04:00
2020-11-13 03:52:38 -05:00
t . Run ( "When matching route path" , func ( t * testing . T ) {
ctx , req := setUp ( )
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "api/v4/some/method" )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
2021-11-01 05:53:33 -04:00
proxy . matchedRoute = routes [ 0 ]
2026-05-21 05:09:14 -04:00
ApplyRoute ( proxy . ctx . Req . Context ( ) , req , proxy . proxyPath , proxy . matchedRoute , dsInfo , proxy . settings )
2017-08-22 11:14:15 -04:00
2020-11-13 03:52:38 -05:00
assert . Equal ( t , "https://www.google.com/some/method" , req . URL . String ( ) )
assert . Equal ( t , "my secret 123" , req . Header . Get ( "x-header" ) )
} )
2017-08-23 04:52:31 -04:00
2020-11-13 03:52:38 -05:00
t . Run ( "When matching route path and has dynamic url" , func ( t * testing . T ) {
ctx , req := setUp ( )
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "api/common/some/method" )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
2021-11-01 05:53:33 -04:00
proxy . matchedRoute = routes [ 3 ]
2026-05-21 05:09:14 -04:00
ApplyRoute ( proxy . ctx . Req . Context ( ) , req , proxy . proxyPath , proxy . matchedRoute , dsInfo , proxy . settings )
2018-08-18 10:00:40 -04:00
2020-11-13 03:52:38 -05:00
assert . Equal ( t , "https://dynamic.grafana.com/some/method?apiKey=123" , req . URL . String ( ) )
assert . Equal ( t , "my secret 123" , req . Header . Get ( "x-header" ) )
} )
2018-08-18 10:00:40 -04:00
2020-11-13 03:52:38 -05:00
t . Run ( "When matching route path with no url" , func ( t * testing . T ) {
ctx , req := setUp ( )
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "" )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
2021-11-01 05:53:33 -04:00
proxy . matchedRoute = routes [ 4 ]
2026-05-21 05:09:14 -04:00
ApplyRoute ( proxy . ctx . Req . Context ( ) , req , proxy . proxyPath , proxy . matchedRoute , dsInfo , proxy . settings )
2020-09-18 07:22:07 -04:00
2020-11-13 03:52:38 -05:00
assert . Equal ( t , "http://localhost/asd" , req . URL . String ( ) )
} )
2020-09-18 07:22:07 -04:00
2024-02-02 03:23:07 -05:00
t . Run ( "When matching route path and has setting url" , func ( t * testing . T ) {
ctx , req := setUp ( )
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "api/common/some/method" )
require . NoError ( t , err )
proxy . matchedRoute = & plugins . Route {
Path : "api/common" ,
URL : "{{.URL}}" ,
Headers : [ ] plugins . Header {
{ Name : "x-header" , Content : "my secret {{.SecureJsonData.key}}" } ,
} ,
URLParams : [ ] plugins . URLParam {
{ Name : "{{.JsonData.queryParam}}" , Content : "{{.SecureJsonData.key}}" } ,
} ,
}
dsInfo := DSInfo {
ID : ds . ID ,
Updated : ds . Updated ,
JSONData : jd ,
DecryptedSecureJSONData : map [ string ] string {
"key" : "123" ,
} ,
URL : "https://dynamic.grafana.com" ,
}
2026-05-21 05:09:14 -04:00
ApplyRoute ( proxy . ctx . Req . Context ( ) , req , proxy . proxyPath , proxy . matchedRoute , dsInfo , proxy . settings )
2024-02-02 03:23:07 -05:00
assert . Equal ( t , "https://dynamic.grafana.com/some/method?apiKey=123" , req . URL . String ( ) )
assert . Equal ( t , "my secret 123" , req . Header . Get ( "x-header" ) )
} )
2021-03-31 10:38:35 -04:00
t . Run ( "When matching route path and has dynamic body" , func ( t * testing . T ) {
ctx , req := setUp ( )
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "api/body" )
2021-03-31 10:38:35 -04:00
require . NoError ( t , err )
2021-11-01 05:53:33 -04:00
proxy . matchedRoute = routes [ 5 ]
2026-05-21 05:09:14 -04:00
ApplyRoute ( proxy . ctx . Req . Context ( ) , req , proxy . proxyPath , proxy . matchedRoute , dsInfo , proxy . settings )
2021-03-31 10:38:35 -04:00
2022-08-10 09:37:51 -04:00
content , err := io . ReadAll ( req . Body )
2021-03-31 10:38:35 -04:00
require . NoError ( t , err )
require . Equal ( t , ` { "url": "https://dynamic.grafana.com", "secret": "123" } ` , string ( content ) )
} )
2024-06-05 07:36:14 -04:00
t . Run ( "When matching route path ending with a slash" , func ( t * testing . T ) {
ctx , req := setUp ( )
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "mypath/some-route/" )
require . NoError ( t , err )
proxy . matchedRoute = routes [ 6 ]
2026-05-21 05:09:14 -04:00
ApplyRoute ( proxy . ctx . Req . Context ( ) , req , proxy . proxyPath , proxy . matchedRoute , dsInfo , proxy . settings )
2024-06-05 07:36:14 -04:00
assert . Equal ( t , "https://example.com/api/v1/some-route/" , req . URL . String ( ) )
} )
2025-01-07 10:27:34 -05:00
t . Run ( "When matching proxy path is already encoded" , func ( t * testing . T ) {
ctx , req := setUp ( )
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "/our%20devices" )
require . NoError ( t , err )
proxy . matchedRoute = routes [ 9 ]
2026-05-21 05:09:14 -04:00
ApplyRoute ( proxy . ctx . Req . Context ( ) , req , proxy . proxyPath , proxy . matchedRoute , dsInfo , proxy . settings )
2025-01-07 10:27:34 -05:00
assert . Equal ( t , "http://encoded.com/our%20devices" , req . URL . String ( ) )
} )
2020-11-13 03:52:38 -05:00
t . Run ( "Validating request" , func ( t * testing . T ) {
t . Run ( "plugin route with valid role" , func ( t * testing . T ) {
ctx , _ := setUp ( )
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "api/v4/some/method" )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
err = proxy . validateRequest ( )
require . NoError ( t , err )
} )
2017-08-23 04:52:31 -04:00
2020-11-13 03:52:38 -05:00
t . Run ( "plugin route with admin role and user is editor" , func ( t * testing . T ) {
ctx , _ := setUp ( )
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "api/admin" )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
err = proxy . validateRequest ( )
require . Error ( t , err )
} )
2017-08-23 04:52:31 -04:00
2020-11-13 03:52:38 -05:00
t . Run ( "plugin route with admin role and user is admin" , func ( t * testing . T ) {
ctx , _ := setUp ( )
2025-04-10 08:42:23 -04:00
ctx . OrgRole = org . RoleAdmin
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "api/admin" )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
err = proxy . validateRequest ( )
require . NoError ( t , err )
2017-08-22 11:14:15 -04:00
} )
2025-04-24 15:15:17 -04:00
t . Run ( "path with slashes and user is editor" , func ( t * testing . T ) {
ctx , _ := setUp ( )
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "//api//admin" )
require . NoError ( t , err )
err = proxy . validateRequest ( )
require . Error ( t , err )
} )
2017-08-22 11:14:15 -04:00
} )
2024-07-25 10:22:42 -04:00
t . Run ( "plugin route with RBAC protection user is allowed" , func ( t * testing . T ) {
ctx , _ := setUp ( )
2025-04-10 08:42:23 -04:00
ctx . OrgID = int64 ( 1 )
ctx . OrgRole = identity . RoleNone
ctx . Permissions = map [ int64 ] map [ string ] [ ] string { 1 : { "test-app.settings:read" : nil } }
2024-07-25 10:22:42 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "api/rbac-restricted" )
require . NoError ( t , err )
err = proxy . validateRequest ( )
require . NoError ( t , err )
} )
t . Run ( "plugin route with RBAC protection user is not allowed" , func ( t * testing . T ) {
ctx , _ := setUp ( )
2025-04-10 08:42:23 -04:00
ctx . OrgID = int64 ( 1 )
ctx . OrgRole = identity . RoleNone
ctx . Permissions = map [ int64 ] map [ string ] [ ] string { 1 : { "test-app:read" : nil } }
2024-07-25 10:22:42 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "api/rbac-restricted" )
require . NoError ( t , err )
err = proxy . validateRequest ( )
require . Error ( t , err )
} )
t . Run ( "plugin route with dynamic RBAC protection user is allowed" , func ( t * testing . T ) {
ctx , _ := setUp ( )
2025-04-10 08:42:23 -04:00
ctx . OrgID = int64 ( 1 )
ctx . OrgRole = identity . RoleNone
ctx . Permissions = map [ int64 ] map [ string ] [ ] string { 1 : { "datasources:read" : { "datasources:uid:dsUID" } } }
2024-07-25 10:22:42 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "api/rbac-home" )
require . NoError ( t , err )
err = proxy . validateRequest ( )
require . NoError ( t , err )
} )
t . Run ( "plugin route with dynamic RBAC protection user is not allowed" , func ( t * testing . T ) {
ctx , _ := setUp ( )
2025-04-10 08:42:23 -04:00
ctx . OrgID = int64 ( 1 )
ctx . OrgRole = identity . RoleNone
2024-07-25 10:22:42 -04:00
// Has access but to another app
2025-04-10 08:42:23 -04:00
ctx . Permissions = map [ int64 ] map [ string ] [ ] string { 1 : { "datasources:read" : { "datasources:uid:notTheDsUID" } } }
2024-07-25 10:22:42 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "api/rbac-home" )
require . NoError ( t , err )
err = proxy . validateRequest ( )
require . Error ( t , err )
} )
2020-11-13 03:52:38 -05:00
} )
2017-08-22 11:14:15 -04:00
2020-11-13 03:52:38 -05:00
t . Run ( "Plugin with multiple routes for token auth" , func ( t * testing . T ) {
2021-11-01 05:53:33 -04:00
routes := [ ] * plugins . Route {
{
Path : "pathwithtoken1" ,
URL : "https://api.nr1.io/some/path" ,
TokenAuth : & plugins . JWTTokenAuth {
Url : "https://login.server.com/{{.JsonData.tenantId}}/oauth2/token" ,
Params : map [ string ] string {
"grant_type" : "client_credentials" ,
"client_id" : "{{.JsonData.clientId}}" ,
"client_secret" : "{{.SecureJsonData.clientSecret}}" ,
"resource" : "https://api.nr1.io" ,
2018-06-12 11:39:38 -04:00
} ,
2020-11-13 03:52:38 -05:00
} ,
2021-11-01 05:53:33 -04:00
} ,
{
Path : "pathwithtoken2" ,
URL : "https://api.nr2.io/some/path" ,
TokenAuth : & plugins . JWTTokenAuth {
Url : "https://login.server.com/{{.JsonData.tenantId}}/oauth2/token" ,
Params : map [ string ] string {
"grant_type" : "client_credentials" ,
"client_id" : "{{.JsonData.clientId}}" ,
"client_secret" : "{{.SecureJsonData.clientSecret}}" ,
"resource" : "https://api.nr2.io" ,
2018-06-12 11:39:38 -04:00
} ,
} ,
2020-11-13 03:52:38 -05:00
} ,
}
2018-06-12 11:39:38 -04:00
2022-06-27 12:23:15 -04:00
ds := & datasources . DataSource {
2023-08-30 11:46:47 -04:00
JsonData : simplejson . NewFromAny ( map [ string ] any {
2020-11-13 03:52:38 -05:00
"clientId" : "asd" ,
"tenantId" : "mytenantId" ,
} ) ,
}
2018-06-12 11:39:38 -04:00
2020-11-13 03:52:38 -05:00
req , err := http . NewRequest ( "GET" , "http://localhost/asd" , nil )
require . NoError ( t , err )
2023-01-27 02:50:36 -05:00
ctx := & contextmodel . ReqContext {
2021-10-11 08:30:59 -04:00
Context : & web . Context { Req : req } ,
2022-08-10 05:56:48 -04:00
SignedInUser : & user . SignedInUser { OrgRole : org . RoleEditor } ,
2020-11-13 03:52:38 -05:00
}
2018-06-12 11:39:38 -04:00
2020-11-13 03:52:38 -05:00
t . Run ( "When creating and caching access tokens" , func ( t * testing . T ) {
var authorizationHeaderCall1 string
var authorizationHeaderCall2 string
2018-06-12 11:39:38 -04:00
2020-11-13 03:52:38 -05:00
t . Run ( "first call should add authorization header with access token" , func ( t * testing . T ) {
2022-08-10 09:37:51 -04:00
json , err := os . ReadFile ( "./test-data/access-token-1.json" )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
2021-05-03 08:46:32 -04:00
originalClient := client
2020-11-13 03:52:38 -05:00
client = newFakeHTTPClient ( t , json )
2021-05-03 08:46:32 -04:00
defer func ( ) { client = originalClient } ( )
2021-10-08 08:46:35 -04:00
jd , err := ds . JsonData . Map ( )
require . NoError ( t , err )
dsInfo := DSInfo {
2023-02-02 11:22:43 -05:00
ID : ds . ID ,
2021-10-08 08:46:35 -04:00
Updated : ds . Updated ,
JSONData : jd ,
DecryptedSecureJSONData : map [ string ] string {
"clientSecret" : "123" ,
} ,
}
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "pathwithtoken1" )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
2026-05-21 05:09:14 -04:00
ApplyRoute ( proxy . ctx . Req . Context ( ) , req , proxy . proxyPath , routes [ 0 ] , dsInfo , proxy . settings )
2020-11-13 03:52:38 -05:00
authorizationHeaderCall1 = req . Header . Get ( "Authorization" )
assert . Equal ( t , "https://api.nr1.io/some/path" , req . URL . String ( ) )
assert . True ( t , strings . HasPrefix ( authorizationHeaderCall1 , "Bearer eyJ0e" ) )
t . Run ( "second call to another route should add a different access token" , func ( t * testing . T ) {
2022-08-10 09:37:51 -04:00
json2 , err := os . ReadFile ( "./test-data/access-token-2.json" )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
req , err := http . NewRequest ( "GET" , "http://localhost/asd" , nil )
require . NoError ( t , err )
client = newFakeHTTPClient ( t , json2 )
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "pathwithtoken2" )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
2023-10-02 03:14:10 -04:00
2026-05-21 05:09:14 -04:00
ApplyRoute ( proxy . ctx . Req . Context ( ) , req , proxy . proxyPath , routes [ 1 ] , dsInfo , proxy . settings )
2020-11-13 03:52:38 -05:00
authorizationHeaderCall2 = req . Header . Get ( "Authorization" )
assert . Equal ( t , "https://api.nr2.io/some/path" , req . URL . String ( ) )
assert . True ( t , strings . HasPrefix ( authorizationHeaderCall1 , "Bearer eyJ0e" ) )
assert . True ( t , strings . HasPrefix ( authorizationHeaderCall2 , "Bearer eyJ0e" ) )
assert . NotEqual ( t , authorizationHeaderCall1 , authorizationHeaderCall2 )
t . Run ( "third call to first route should add cached access token" , func ( t * testing . T ) {
req , err := http . NewRequest ( "GET" , "http://localhost/asd" , nil )
require . NoError ( t , err )
client = newFakeHTTPClient ( t , [ ] byte { } )
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "pathwithtoken1" )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
2026-05-21 05:09:14 -04:00
ApplyRoute ( proxy . ctx . Req . Context ( ) , req , proxy . proxyPath , routes [ 0 ] , dsInfo , proxy . settings )
2020-11-13 03:52:38 -05:00
authorizationHeaderCall3 := req . Header . Get ( "Authorization" )
assert . Equal ( t , "https://api.nr1.io/some/path" , req . URL . String ( ) )
assert . True ( t , strings . HasPrefix ( authorizationHeaderCall1 , "Bearer eyJ0e" ) )
assert . True ( t , strings . HasPrefix ( authorizationHeaderCall3 , "Bearer eyJ0e" ) )
assert . Equal ( t , authorizationHeaderCall1 , authorizationHeaderCall3 )
2018-06-12 11:39:38 -04:00
} )
} )
} )
} )
2020-11-13 03:52:38 -05:00
} )
2018-06-12 11:39:38 -04:00
2020-11-13 03:52:38 -05:00
t . Run ( "When proxying graphite" , func ( t * testing . T ) {
2021-11-01 05:53:33 -04:00
var routes [ ] * plugins . Route
2023-02-02 11:22:43 -05:00
ds := & datasources . DataSource { URL : "htttp://graphite:8080" , Type : datasources . DS_GRAPHITE }
2026-06-08 06:28:30 -04:00
ctx := newReqContext ( t )
2017-08-23 04:52:31 -04:00
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "/render" , func ( proxy * DataSourceProxy ) {
2026-05-21 05:09:14 -04:00
proxy . settings = & DataSourceProxySettings { }
2023-10-02 03:14:10 -04:00
} )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
req , err := http . NewRequest ( http . MethodGet , "http://grafana.com/sub" , nil )
require . NoError ( t , err )
2017-08-23 04:52:31 -04:00
2020-11-13 07:21:43 -05:00
proxy . director ( req )
2017-08-22 11:14:15 -04:00
2020-11-13 03:52:38 -05:00
t . Run ( "Can translate request URL and path" , func ( t * testing . T ) {
assert . Equal ( t , "graphite:8080" , req . URL . Host )
assert . Equal ( t , "/render" , req . URL . Path )
} )
} )
2017-08-23 04:52:31 -04:00
2020-11-13 03:52:38 -05:00
t . Run ( "When proxying InfluxDB" , func ( t * testing . T ) {
2022-06-27 12:23:15 -04:00
ds := & datasources . DataSource {
Type : datasources . DS_INFLUXDB_08 ,
2023-02-02 11:22:43 -05:00
URL : "http://influxdb:8083" ,
2020-11-13 03:52:38 -05:00
Database : "site" ,
User : "user" ,
}
2017-08-23 04:52:31 -04:00
2026-06-08 06:28:30 -04:00
ctx := newReqContext ( t )
2021-11-01 05:53:33 -04:00
var routes [ ] * plugins . Route
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "" )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
2017-08-23 04:52:31 -04:00
2020-11-13 03:52:38 -05:00
req , err := http . NewRequest ( http . MethodGet , "http://grafana.com/sub" , nil )
require . NoError ( t , err )
2017-08-22 11:14:15 -04:00
2020-11-13 07:21:43 -05:00
proxy . director ( req )
2020-11-13 03:52:38 -05:00
assert . Equal ( t , "/db/site/" , req . URL . Path )
} )
2017-08-23 11:18:43 -04:00
2020-11-13 03:52:38 -05:00
t . Run ( "When proxying a data source with no keepCookies specified" , func ( t * testing . T ) {
json , err := simplejson . NewJson ( [ ] byte ( ` { "keepCookies": []} ` ) )
require . NoError ( t , err )
2017-10-17 14:48:01 -04:00
2022-06-27 12:23:15 -04:00
ds := & datasources . DataSource {
Type : datasources . DS_GRAPHITE ,
2023-02-02 11:22:43 -05:00
URL : "http://graphite:8086" ,
2020-11-13 03:52:38 -05:00
JsonData : json ,
}
2017-10-17 14:48:01 -04:00
2026-06-08 06:28:30 -04:00
ctx := newReqContext ( t )
2021-11-01 05:53:33 -04:00
var routes [ ] * plugins . Route
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "" )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
2017-10-17 14:48:01 -04:00
2020-11-13 03:52:38 -05:00
requestURL , err := url . Parse ( "http://grafana.com/sub" )
require . NoError ( t , err )
req := http . Request { URL : requestURL , Header : make ( http . Header ) }
cookies := "grafana_user=admin; grafana_remember=99; grafana_sess=11; JSESSION_ID=test"
req . Header . Set ( "Cookie" , cookies )
2017-10-17 14:48:01 -04:00
2020-11-13 07:21:43 -05:00
proxy . director ( & req )
2017-10-17 14:48:01 -04:00
2020-11-13 03:52:38 -05:00
assert . Equal ( t , "" , req . Header . Get ( "Cookie" ) )
} )
2017-10-17 14:48:01 -04:00
2020-11-13 03:52:38 -05:00
t . Run ( "When proxying a data source with keep cookies specified" , func ( t * testing . T ) {
json , err := simplejson . NewJson ( [ ] byte ( ` { "keepCookies": ["JSESSION_ID"]} ` ) )
require . NoError ( t , err )
2017-10-17 14:48:01 -04:00
2022-06-27 12:23:15 -04:00
ds := & datasources . DataSource {
Type : datasources . DS_GRAPHITE ,
2023-02-02 11:22:43 -05:00
URL : "http://graphite:8086" ,
2020-11-13 03:52:38 -05:00
JsonData : json ,
}
2017-10-17 14:48:01 -04:00
2026-06-08 06:28:30 -04:00
ctx := newReqContext ( t )
2023-10-02 03:14:10 -04:00
var routes [ ] * plugins . Route
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "" )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
2017-10-17 14:48:01 -04:00
2020-11-13 03:52:38 -05:00
requestURL , err := url . Parse ( "http://grafana.com/sub" )
require . NoError ( t , err )
req := http . Request { URL : requestURL , Header : make ( http . Header ) }
cookies := "grafana_user=admin; grafana_remember=99; grafana_sess=11; JSESSION_ID=test"
req . Header . Set ( "Cookie" , cookies )
2017-10-17 14:48:01 -04:00
2020-11-13 07:21:43 -05:00
proxy . director ( & req )
2017-10-17 14:48:01 -04:00
2020-11-13 03:52:38 -05:00
assert . Equal ( t , "JSESSION_ID=test" , req . Header . Get ( "Cookie" ) )
} )
2017-10-17 14:48:01 -04:00
2020-11-13 03:52:38 -05:00
t . Run ( "When proxying a custom datasource" , func ( t * testing . T ) {
2022-06-27 12:23:15 -04:00
ds := & datasources . DataSource {
2020-11-13 03:52:38 -05:00
Type : "custom-datasource" ,
2023-02-02 11:22:43 -05:00
URL : "http://host/root/" ,
2020-11-13 03:52:38 -05:00
}
2026-06-08 06:28:30 -04:00
ctx := newReqContext ( t )
2021-11-01 05:53:33 -04:00
var routes [ ] * plugins . Route
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "/path/to/folder/" )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
2023-10-02 03:14:10 -04:00
2020-11-13 03:52:38 -05:00
req , err := http . NewRequest ( http . MethodGet , "http://grafana.com/sub" , nil )
2020-12-14 09:13:01 -05:00
req . Header . Set ( "Origin" , "grafana.com" )
req . Header . Set ( "Referer" , "grafana.com" )
req . Header . Set ( "X-Canary" , "stillthere" )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
2020-11-13 07:21:43 -05:00
proxy . director ( req )
2020-11-13 03:52:38 -05:00
assert . Equal ( t , "http://host/root/path/to/folder/" , req . URL . String ( ) )
assert . Equal ( t , "stillthere" , req . Header . Get ( "X-Canary" ) )
} )
2018-11-08 06:30:10 -05:00
2020-11-13 07:21:43 -05:00
t . Run ( "When proxying a datasource that has OAuth token pass-through enabled" , func ( t * testing . T ) {
2022-06-27 12:23:15 -04:00
ds := & datasources . DataSource {
2020-11-13 03:52:38 -05:00
Type : "custom-datasource" ,
2023-02-02 11:22:43 -05:00
URL : "http://host/root/" ,
2023-08-30 11:46:47 -04:00
JsonData : simplejson . NewFromAny ( map [ string ] any {
2020-11-13 03:52:38 -05:00
"oauthPassThru" : true ,
} ) ,
}
2019-02-01 19:40:57 -05:00
2020-11-13 03:52:38 -05:00
req , err := http . NewRequest ( "GET" , "http://localhost/asd" , nil )
require . NoError ( t , err )
2023-01-27 02:50:36 -05:00
ctx := & contextmodel . ReqContext {
2022-08-11 07:28:55 -04:00
SignedInUser : & user . SignedInUser { UserID : 1 } ,
2021-10-11 08:30:59 -04:00
Context : & web . Context { Req : req } ,
2020-11-13 03:52:38 -05:00
}
2021-11-29 09:40:05 -05:00
2021-11-01 05:53:33 -04:00
var routes [ ] * plugins . Route
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "/path/to/folder/" , func ( proxy * DataSourceProxy ) {
proxy . oAuthTokenService = & oauthtokentest . MockOauthTokenService {
2024-11-27 05:06:39 -05:00
GetCurrentOauthTokenFunc : func ( _ context . Context , _ identity . Requester , _ * auth . UserToken ) * oauth2 . Token {
2023-10-02 03:14:10 -04:00
return ( & oauth2 . Token {
AccessToken : "testtoken" ,
RefreshToken : "testrefreshtoken" ,
TokenType : "Bearer" ,
Expiry : time . Now ( ) . AddDate ( 0 , 0 , 1 ) ,
} ) . WithExtra ( map [ string ] any { "id_token" : "testidtoken" } )
} ,
}
} )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
2023-10-02 03:14:10 -04:00
2020-11-13 03:52:38 -05:00
req , err = http . NewRequest ( http . MethodGet , "http://grafana.com/sub" , nil )
2024-11-27 05:06:39 -05:00
req = req . WithContext ( context . WithValue ( req . Context ( ) , ctxkey . Key { } , & contextmodel . ReqContext { UserToken : nil } ) )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
2019-02-01 19:40:57 -05:00
2020-11-13 07:21:43 -05:00
proxy . director ( req )
2019-02-01 19:40:57 -05:00
2020-11-13 03:52:38 -05:00
assert . Equal ( t , "Bearer testtoken" , req . Header . Get ( "Authorization" ) )
2021-11-29 09:40:05 -05:00
assert . Equal ( t , "testidtoken" , req . Header . Get ( "X-ID-Token" ) )
2020-11-13 03:52:38 -05:00
} )
2019-03-14 13:18:00 -04:00
2020-11-13 03:52:38 -05:00
t . Run ( "When SendUserHeader config is enabled" , func ( t * testing . T ) {
req := getDatasourceProxiedRequest (
t ,
2023-01-27 02:50:36 -05:00
& contextmodel . ReqContext {
2022-08-10 05:56:48 -04:00
SignedInUser : & user . SignedInUser {
2024-04-02 11:45:15 -04:00
Login : "test_user" ,
2024-08-12 02:26:53 -04:00
FallbackType : claims . TypeUser ,
2024-07-30 01:27:23 -04:00
UserID : 1 ,
2019-03-14 08:04:47 -04:00
} ,
2020-11-13 03:52:38 -05:00
} ,
2026-05-21 05:09:14 -04:00
& DataSourceProxySettings { SendUserHeader : true } ,
2020-11-13 03:52:38 -05:00
)
assert . Equal ( t , "test_user" , req . Header . Get ( "X-Grafana-User" ) )
} )
2019-03-14 08:04:47 -04:00
2020-11-13 03:52:38 -05:00
t . Run ( "When SendUserHeader config is disabled" , func ( t * testing . T ) {
req := getDatasourceProxiedRequest (
t ,
2023-01-27 02:50:36 -05:00
& contextmodel . ReqContext {
2022-08-10 05:56:48 -04:00
SignedInUser : & user . SignedInUser {
2020-11-13 03:52:38 -05:00
Login : "test_user" ,
2019-03-14 08:04:47 -04:00
} ,
2020-11-13 03:52:38 -05:00
} ,
2026-05-21 05:09:14 -04:00
& DataSourceProxySettings { SendUserHeader : false } ,
2020-11-13 03:52:38 -05:00
)
// Get will return empty string even if header is not set
assert . Empty ( t , req . Header . Get ( "X-Grafana-User" ) )
} )
2019-03-14 11:28:32 -04:00
2020-11-13 03:52:38 -05:00
t . Run ( "When SendUserHeader config is enabled but user is anonymous" , func ( t * testing . T ) {
req := getDatasourceProxiedRequest (
t ,
2023-01-27 02:50:36 -05:00
& contextmodel . ReqContext {
2022-08-10 05:56:48 -04:00
SignedInUser : & user . SignedInUser { IsAnonymous : true } ,
2020-11-13 03:52:38 -05:00
} ,
2026-05-21 05:09:14 -04:00
& DataSourceProxySettings { SendUserHeader : true } ,
2020-11-13 03:52:38 -05:00
)
// Get will return empty string even if header is not set
assert . Empty ( t , req . Header . Get ( "X-Grafana-User" ) )
} )
2019-04-15 05:11:17 -04:00
2020-11-13 03:52:38 -05:00
t . Run ( "When proxying data source proxy should handle authentication" , func ( t * testing . T ) {
2022-10-19 09:02:15 -04:00
sqlStore := db . InitTestDB ( t )
2022-08-25 17:04:44 -04:00
secretsService := secretsmng . SetupTestService ( t , fakes . NewFakeSecretsStore ( ) )
secretsStore := secretskvs . NewSQLSecretsKVStore ( sqlStore , secretsService , log . New ( "test.logger" ) )
2021-11-04 12:47:21 -04:00
2020-11-13 03:52:38 -05:00
tests := [ ] * testCase {
2022-06-27 12:23:15 -04:00
createAuthTest ( t , secretsStore , datasources . DS_INFLUXDB_08 , "http://localhost:9090" , authTypePassword , authCheckQuery ) ,
createAuthTest ( t , secretsStore , datasources . DS_INFLUXDB_08 , "http://localhost:9090" , authTypePassword , authCheckQuery ) ,
createAuthTest ( t , secretsStore , datasources . DS_INFLUXDB , "http://localhost:9090" , authTypePassword , authCheckHeader ) ,
createAuthTest ( t , secretsStore , datasources . DS_INFLUXDB , "http://localhost:9090" , authTypePassword , authCheckHeader ) ,
createAuthTest ( t , secretsStore , datasources . DS_INFLUXDB , "http://localhost:9090" , authTypeBasic , authCheckHeader ) ,
createAuthTest ( t , secretsStore , datasources . DS_INFLUXDB , "http://localhost:9090" , authTypeBasic , authCheckHeader ) ,
2020-11-13 03:52:38 -05:00
// These two should be enough for any other datasource at the moment. Proxy has special handling
// only for Influx, others have the same path and only BasicAuth. Non BasicAuth datasources
// do not go through proxy but through TSDB API which is not tested here.
2022-06-27 12:23:15 -04:00
createAuthTest ( t , secretsStore , datasources . DS_ES , "http://localhost:9200" , authTypeBasic , authCheckHeader ) ,
createAuthTest ( t , secretsStore , datasources . DS_ES , "http://localhost:9200" , authTypeBasic , authCheckHeader ) ,
2020-11-13 03:52:38 -05:00
}
for _ , test := range tests {
2022-04-25 12:57:45 -04:00
runDatasourceAuthTest ( t , secretsService , secretsStore , cfg , test )
2020-11-13 03:52:38 -05:00
}
} )
2026-01-14 15:12:18 -05:00
t . Run ( "Regression of 116273: Fallback routes should apply fallback route roles" , func ( t * testing . T ) {
for _ , tc := range [ ] struct {
InputPath string
ConfigurationPath string
ExpectError bool
} {
{
InputPath : "api/v2/leak-ur-secrets" ,
ConfigurationPath : "" ,
ExpectError : true ,
} ,
{
InputPath : "" ,
ConfigurationPath : "" ,
ExpectError : true ,
} ,
{
InputPath : "." ,
ConfigurationPath : "." ,
ExpectError : true ,
} ,
{
InputPath : "" ,
ConfigurationPath : "." ,
ExpectError : false ,
} ,
{
InputPath : "api" ,
ConfigurationPath : "." ,
ExpectError : false ,
} ,
} {
orEmptyStr := func ( s string ) string {
if s == "" {
return "<empty>"
}
return s
}
t . Run (
fmt . Sprintf ( "with inputPath=%s, configurationPath=%s, expectError=%v" ,
orEmptyStr ( tc . InputPath ) , orEmptyStr ( tc . ConfigurationPath ) , tc . ExpectError ) ,
func ( t * testing . T ) {
ds := & datasources . DataSource {
UID : "dsUID" ,
JsonData : simplejson . New ( ) ,
}
routes := [ ] * plugins . Route {
{
Path : tc . ConfigurationPath ,
ReqRole : org . RoleAdmin ,
Method : "GET" ,
} ,
{
Path : tc . ConfigurationPath ,
ReqRole : org . RoleAdmin ,
Method : "POST" ,
} ,
{
Path : tc . ConfigurationPath ,
ReqRole : org . RoleAdmin ,
Method : "PUT" ,
} ,
{
Path : tc . ConfigurationPath ,
ReqRole : org . RoleAdmin ,
Method : "DELETE" ,
} ,
}
req , err := http . NewRequestWithContext ( t . Context ( ) , "GET" , "http://localhost/" + tc . InputPath , nil )
require . NoError ( t , err , "failed to create HTTP request" )
ctx := & contextmodel . ReqContext {
Context : & web . Context { Req : req } ,
SignedInUser : & user . SignedInUser { OrgRole : org . RoleViewer } ,
}
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , tc . InputPath )
require . NoError ( t , err , "failed to setup proxy test" )
err = proxy . validateRequest ( )
if tc . ExpectError {
require . ErrorIs ( t , err , errPluginProxyRouteAccessDenied , "request was not denied due to access denied?" )
} else {
require . NoError ( t , err , "request was unexpectedly denied access" )
}
} ,
)
}
} )
2020-11-13 07:21:43 -05:00
}
2026-05-06 06:15:38 -04:00
func TestDataSourceProxy_userAgentHeader ( t * testing . T ) {
ds := & datasources . DataSource { Type : datasources . DS_GRAPHITE , URL : "http://graphite:8080" }
var routes [ ] * plugins . Route
t . Run ( "When DataProxyForwardUserAgent config is disabled" , func ( t * testing . T ) {
2026-06-08 06:28:30 -04:00
ctx := newReqContext ( t )
2026-05-06 06:15:38 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "/render" , func ( p * DataSourceProxy ) {
2026-05-21 05:09:14 -04:00
p . settings = & DataSourceProxySettings {
2026-05-06 06:15:38 -04:00
DataProxyUserAgent : "Grafana/5.3.0" ,
DataProxyForwardUserAgent : false ,
}
} )
require . NoError ( t , err )
req , err := http . NewRequest ( http . MethodGet , "http://grafana.com/sub" , nil )
require . NoError ( t , err )
req . Header . Set ( "User-Agent" , "original-client/1.0" )
proxy . director ( req )
assert . Equal ( t , "Grafana/5.3.0" , req . Header . Get ( "User-Agent" ) )
} )
t . Run ( "When DataProxyForwardUserAgent config is enabled" , func ( t * testing . T ) {
2026-06-08 06:28:30 -04:00
ctx := newReqContext ( t )
2026-05-06 06:15:38 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "/render" , func ( p * DataSourceProxy ) {
2026-05-21 05:09:14 -04:00
p . settings = & DataSourceProxySettings {
2026-05-06 06:15:38 -04:00
DataProxyUserAgent : "Grafana/5.3.0" ,
DataProxyForwardUserAgent : true ,
}
} )
require . NoError ( t , err )
req , err := http . NewRequest ( http . MethodGet , "http://grafana.com/sub" , nil )
require . NoError ( t , err )
req . Header . Set ( "User-Agent" , "original-client/1.0" )
proxy . director ( req )
assert . Equal ( t , "Grafana/5.3.0 original-client/1.0" , req . Header . Get ( "User-Agent" ) )
} )
t . Run ( "When DataProxyForwardUserAgent config is enabled but the client User-Agent is empty" , func ( t * testing . T ) {
2026-06-08 06:28:30 -04:00
ctx := newReqContext ( t )
2026-05-06 06:15:38 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "/render" , func ( p * DataSourceProxy ) {
2026-05-21 05:09:14 -04:00
p . settings = & DataSourceProxySettings {
2026-05-06 06:15:38 -04:00
DataProxyUserAgent : "Grafana/5.3.0" ,
DataProxyForwardUserAgent : true ,
}
} )
require . NoError ( t , err )
req , err := http . NewRequest ( http . MethodGet , "http://grafana.com/sub" , nil )
require . NoError ( t , err )
// no User-Agent header set
proxy . director ( req )
assert . Equal ( t , "Grafana/5.3.0" , req . Header . Get ( "User-Agent" ) )
} )
t . Run ( "When DataProxyForwardUserAgent config is enabled with a custom DataProxyUserAgent" , func ( t * testing . T ) {
2026-06-08 06:28:30 -04:00
ctx := newReqContext ( t )
2026-05-06 06:15:38 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "/render" , func ( p * DataSourceProxy ) {
2026-05-21 05:09:14 -04:00
p . settings = & DataSourceProxySettings {
2026-05-06 06:15:38 -04:00
DataProxyUserAgent : "MyCorp/1.0" ,
DataProxyForwardUserAgent : true ,
}
} )
require . NoError ( t , err )
req , err := http . NewRequest ( http . MethodGet , "http://grafana.com/sub" , nil )
require . NoError ( t , err )
req . Header . Set ( "User-Agent" , "original-client/1.0" )
proxy . director ( req )
assert . Equal ( t , "MyCorp/1.0 original-client/1.0" , req . Header . Get ( "User-Agent" ) )
} )
t . Run ( "When DataProxyForwardUserAgent config is enabled and DataProxyUserAgent is empty" , func ( t * testing . T ) {
2026-06-08 06:28:30 -04:00
ctx := newReqContext ( t )
2026-05-06 06:15:38 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "/render" , func ( p * DataSourceProxy ) {
2026-05-21 05:09:14 -04:00
p . settings = & DataSourceProxySettings {
2026-05-06 06:15:38 -04:00
DataProxyUserAgent : "" ,
DataProxyForwardUserAgent : true ,
}
} )
require . NoError ( t , err )
req , err := http . NewRequest ( http . MethodGet , "http://grafana.com/sub" , nil )
require . NoError ( t , err )
req . Header . Set ( "User-Agent" , "original-client/1.0" )
proxy . director ( req )
assert . Equal ( t , "original-client/1.0" , req . Header . Get ( "User-Agent" ) )
} )
2026-05-13 04:17:20 -04:00
t . Run ( "When DataProxyForwardUserAgent is enabled and the client User-Agent exceeds the length cap, it is truncated" , func ( t * testing . T ) {
2026-06-08 06:28:30 -04:00
ctx := newReqContext ( t )
2026-05-13 04:17:20 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "/render" , func ( p * DataSourceProxy ) {
2026-05-21 05:09:14 -04:00
p . settings = & DataSourceProxySettings {
2026-05-13 04:17:20 -04:00
DataProxyUserAgent : "Grafana/5.3.0" ,
DataProxyForwardUserAgent : true ,
}
} )
require . NoError ( t , err )
oversized := strings . Repeat ( "a" , maxForwardedUserAgentLen + 100 )
req , err := http . NewRequest ( http . MethodGet , "http://grafana.com/sub" , nil )
require . NoError ( t , err )
req . Header . Set ( "User-Agent" , oversized )
proxy . director ( req )
expected := "Grafana/5.3.0 " + strings . Repeat ( "a" , maxForwardedUserAgentLen )
assert . Equal ( t , expected , req . Header . Get ( "User-Agent" ) )
} )
t . Run ( "When DataProxyForwardUserAgent is enabled and the client User-Agent is exactly the cap, it is forwarded unchanged" , func ( t * testing . T ) {
2026-06-08 06:28:30 -04:00
ctx := newReqContext ( t )
2026-05-13 04:17:20 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "/render" , func ( p * DataSourceProxy ) {
2026-05-21 05:09:14 -04:00
p . settings = & DataSourceProxySettings {
2026-05-13 04:17:20 -04:00
DataProxyUserAgent : "Grafana/5.3.0" ,
DataProxyForwardUserAgent : true ,
}
} )
require . NoError ( t , err )
exact := strings . Repeat ( "b" , maxForwardedUserAgentLen )
req , err := http . NewRequest ( http . MethodGet , "http://grafana.com/sub" , nil )
require . NoError ( t , err )
req . Header . Set ( "User-Agent" , exact )
proxy . director ( req )
assert . Equal ( t , "Grafana/5.3.0 " + exact , req . Header . Get ( "User-Agent" ) )
} )
2026-05-06 06:15:38 -04:00
}
2020-11-13 07:21:43 -05:00
// test DataSourceProxy request handling.
func TestDataSourceProxy_requestHandling ( t * testing . T ) {
var writeErr error
type setUpCfg struct {
headers map [ string ] string
2021-03-17 07:17:41 -04:00
writeCb func ( w http . ResponseWriter , r * http . Request )
2020-11-13 07:21:43 -05:00
}
2023-01-27 02:50:36 -05:00
setUp := func ( t * testing . T , cfgs ... setUpCfg ) ( * contextmodel . ReqContext , * datasources . DataSource ) {
2020-11-13 07:21:43 -05:00
writeErr = nil
2019-05-01 10:32:03 -04:00
2020-11-13 03:52:38 -05:00
backend := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
http . SetCookie ( w , & http . Cookie { Name : "flavor" , Value : "chocolateChip" } )
2020-11-13 07:21:43 -05:00
written := false
for _ , cfg := range cfgs {
if cfg . writeCb != nil {
t . Log ( "Writing response via callback" )
2021-03-17 07:17:41 -04:00
cfg . writeCb ( w , r )
2020-11-13 07:21:43 -05:00
written = true
}
}
if ! written {
t . Log ( "Writing default response" )
w . WriteHeader ( 200 )
_ , writeErr = w . Write ( [ ] byte ( "I am the backend" ) )
}
2020-11-13 03:52:38 -05:00
} ) )
t . Cleanup ( backend . Close )
2019-05-01 10:32:03 -04:00
2023-02-02 11:22:43 -05:00
ds := & datasources . DataSource { URL : backend . URL , Type : datasources . DS_GRAPHITE }
2020-11-13 03:52:38 -05:00
2021-10-11 08:30:59 -04:00
responseWriter := web . NewResponseWriter ( "GET" , httptest . NewRecorder ( ) )
2020-11-13 03:52:38 -05:00
2020-11-13 07:21:43 -05:00
// XXX: Really unsure why, but setting headers within the HTTP handler function doesn't stick,
// so doing it here instead
for _ , cfg := range cfgs {
for k , v := range cfg . headers {
responseWriter . Header ( ) . Set ( k , v )
2019-05-01 10:32:03 -04:00
}
2020-11-13 03:52:38 -05:00
}
2019-05-01 10:32:03 -04:00
2023-01-27 02:50:36 -05:00
return & contextmodel . ReqContext {
2022-08-10 05:56:48 -04:00
SignedInUser : & user . SignedInUser { } ,
2021-10-11 08:30:59 -04:00
Context : & web . Context {
2021-09-01 05:18:30 -04:00
Req : httptest . NewRequest ( "GET" , "/render" , nil ) ,
2020-11-13 07:21:43 -05:00
Resp : responseWriter ,
} ,
} , ds
}
2019-10-08 12:57:53 -04:00
2020-11-13 07:21:43 -05:00
t . Run ( "When response header Set-Cookie is not set should remove proxied Set-Cookie header" , func ( t * testing . T ) {
ctx , ds := setUp ( t )
2021-11-01 05:53:33 -04:00
var routes [ ] * plugins . Route
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "/render" )
2020-11-13 07:21:43 -05:00
require . NoError ( t , err )
2019-10-08 12:57:53 -04:00
2020-11-13 07:21:43 -05:00
proxy . HandleRequest ( )
require . NoError ( t , writeErr )
assert . Empty ( t , proxy . ctx . Resp . Header ( ) . Get ( "Set-Cookie" ) )
} )
t . Run ( "When response header Set-Cookie is set should remove proxied Set-Cookie header and restore the original Set-Cookie header" , func ( t * testing . T ) {
ctx , ds := setUp ( t , setUpCfg {
headers : map [ string ] string {
"Set-Cookie" : "important_cookie=important_value" ,
} ,
2020-11-13 03:52:38 -05:00
} )
2021-11-01 05:53:33 -04:00
var routes [ ] * plugins . Route
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "/render" )
2020-11-13 07:21:43 -05:00
require . NoError ( t , err )
2019-05-01 10:32:03 -04:00
2020-11-13 07:21:43 -05:00
proxy . HandleRequest ( )
2019-10-08 12:57:53 -04:00
2020-11-13 07:21:43 -05:00
require . NoError ( t , writeErr )
assert . Equal ( t , "important_cookie=important_value" , proxy . ctx . Resp . Header ( ) . Get ( "Set-Cookie" ) )
} )
2019-10-08 12:57:53 -04:00
2022-02-09 07:44:38 -05:00
t . Run ( "When response should set Content-Security-Policy header" , func ( t * testing . T ) {
ctx , ds := setUp ( t )
var routes [ ] * plugins . Route
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "/render" )
2022-02-09 07:44:38 -05:00
require . NoError ( t , err )
proxy . HandleRequest ( )
require . NoError ( t , writeErr )
assert . Equal ( t , "sandbox" , proxy . ctx . Resp . Header ( ) . Get ( "Content-Security-Policy" ) )
} )
2020-11-13 07:21:43 -05:00
t . Run ( "Data source returns status code 401" , func ( t * testing . T ) {
ctx , ds := setUp ( t , setUpCfg {
2021-03-17 07:17:41 -04:00
writeCb : func ( w http . ResponseWriter , r * http . Request ) {
2020-11-13 07:21:43 -05:00
w . WriteHeader ( 401 )
w . Header ( ) . Set ( "www-authenticate" , ` Basic realm="Access to the server" ` )
_ , err := w . Write ( [ ] byte ( "Not authenticated" ) )
require . NoError ( t , err )
t . Log ( "Wrote 401 response" )
} ,
2019-05-01 10:32:03 -04:00
} )
2021-11-01 05:53:33 -04:00
var routes [ ] * plugins . Route
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "/render" )
2020-11-13 07:21:43 -05:00
require . NoError ( t , err )
proxy . HandleRequest ( )
require . NoError ( t , writeErr )
2026-06-08 06:28:30 -04:00
assert . Equal ( t , 400 , proxy . ctx . Resp . ( web . ResponseWriter ) . Status ( ) , "Status code 401 should be converted to 400" )
2020-11-13 07:21:43 -05:00
assert . Empty ( t , proxy . ctx . Resp . Header ( ) . Get ( "www-authenticate" ) )
2017-08-23 04:52:31 -04:00
} )
2021-03-17 07:17:41 -04:00
t . Run ( "Data source should handle proxy path url encoding correctly" , func ( t * testing . T ) {
var req * http . Request
ctx , ds := setUp ( t , setUpCfg {
writeCb : func ( w http . ResponseWriter , r * http . Request ) {
req = r
w . WriteHeader ( 200 )
_ , err := w . Write ( [ ] byte ( "OK" ) )
require . NoError ( t , err )
} ,
} )
2021-09-01 05:18:30 -04:00
ctx . Req = httptest . NewRequest ( "GET" , "/api/datasources/proxy/1/path/%2Ftest%2Ftest%2F?query=%2Ftest%2Ftest%2F" , nil )
2021-11-01 05:53:33 -04:00
var routes [ ] * plugins . Route
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "/path/%2Ftest%2Ftest%2F" )
2021-03-17 07:17:41 -04:00
require . NoError ( t , err )
proxy . HandleRequest ( )
require . NoError ( t , writeErr )
require . NotNil ( t , req )
require . Equal ( t , "/path/%2Ftest%2Ftest%2F?query=%2Ftest%2Ftest%2F" , req . RequestURI )
} )
2023-10-02 03:14:10 -04:00
2022-01-20 05:10:12 -05:00
t . Run ( "Data source should handle proxy path url encoding correctly with opentelemetry" , func ( t * testing . T ) {
var req * http . Request
ctx , ds := setUp ( t , setUpCfg {
writeCb : func ( w http . ResponseWriter , r * http . Request ) {
req = r
w . WriteHeader ( 200 )
_ , err := w . Write ( [ ] byte ( "OK" ) )
require . NoError ( t , err )
} ,
} )
ctx . Req = httptest . NewRequest ( "GET" , "/api/datasources/proxy/1/path/%2Ftest%2Ftest%2F?query=%2Ftest%2Ftest%2F" , nil )
2023-10-02 03:14:10 -04:00
2022-01-20 05:10:12 -05:00
var routes [ ] * plugins . Route
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , "/path/%2Ftest%2Ftest%2F" )
2022-01-20 05:10:12 -05:00
require . NoError ( t , err )
proxy . HandleRequest ( )
require . NoError ( t , writeErr )
require . NotNil ( t , req )
require . Equal ( t , "/path/%2Ftest%2Ftest%2F?query=%2Ftest%2Ftest%2F" , req . RequestURI )
} )
2017-08-22 11:14:15 -04:00
}
2018-06-12 11:39:38 -04:00
2020-04-22 04:30:06 -04:00
func TestNewDataSourceProxy_InvalidURL ( t * testing . T ) {
2023-01-27 02:50:36 -05:00
ctx := contextmodel . ReqContext {
2021-10-11 08:30:59 -04:00
Context : & web . Context { } ,
2022-08-10 05:56:48 -04:00
SignedInUser : & user . SignedInUser { OrgRole : org . RoleEditor } ,
2020-04-22 04:30:06 -04:00
}
2022-06-27 12:23:15 -04:00
ds := datasources . DataSource {
2020-04-22 04:30:06 -04:00
Type : "test" ,
2023-02-02 11:22:43 -05:00
URL : "://host/root" ,
2020-04-22 04:30:06 -04:00
}
2023-10-02 03:14:10 -04:00
2021-11-01 05:53:33 -04:00
var routes [ ] * plugins . Route
2023-10-02 03:14:10 -04:00
_ , err := setupDSProxyTest ( t , & ctx , & ds , routes , "api/mehtod" )
2020-04-22 04:30:06 -04:00
require . Error ( t , err )
2020-10-21 06:39:41 -04:00
assert . True ( t , strings . HasPrefix ( err . Error ( ) , ` validation of data source URL "://host/root" failed ` ) )
2020-05-12 07:04:18 -04:00
}
2026-06-08 06:28:30 -04:00
func TestDataSourceProxy_LokiRouteAccessControl ( t * testing . T ) {
const (
dsUID = "dsUID"
dsScope = "datasources:uid:" + dsUID
readAction = "alert.rules.external:read"
writeAction = "alert.rules.external:write"
)
routes := [ ] * plugins . Route {
{
Path : "api/v1/rules" ,
ReqAction : readAction ,
Method : http . MethodGet ,
} ,
{
Path : "api/v1/rules" ,
ReqAction : writeAction ,
Method : http . MethodPost ,
} ,
}
for _ , tc := range [ ] struct {
name string
method string
path string
permissions map [ string ] [ ] string
wantErr error
wantRoute * plugins . Route
} {
{
name : "allows Loki alert rule reads with scoped read permission" ,
method : http . MethodGet ,
path : "api/v1/rules" ,
permissions : map [ string ] [ ] string { readAction : { dsScope } } ,
wantRoute : routes [ 0 ] ,
} ,
{
name : "allows allow-listed Loki alert rule writes with scoped write permission" ,
method : http . MethodPost ,
path : "api/v1/rules" ,
permissions : map [ string ] [ ] string { writeAction : { dsScope } } ,
wantRoute : routes [ 1 ] ,
} ,
{
name : "denies allow-listed Loki alert rule writes without scoped write permission" ,
method : http . MethodPost ,
path : "api/v1/rules" ,
permissions : map [ string ] [ ] string { readAction : { dsScope } } ,
wantErr : errPluginProxyRouteAccessDenied ,
} ,
{
name : "denies non allow-listed Loki writes" ,
method : http . MethodPost ,
path : "api/v1/unmatched" ,
permissions : map [ string ] [ ] string { writeAction : { dsScope } } ,
wantErr : errors . New ( "non allow-listed POSTs not allowed on proxied loki datasource" ) ,
} ,
} {
t . Run ( tc . name , func ( t * testing . T ) {
req , err := http . NewRequest ( tc . method , "http://localhost/" + tc . path , nil )
require . NoError ( t , err )
ctx := & contextmodel . ReqContext {
Context : & web . Context { Req : req } ,
SignedInUser : & user . SignedInUser {
OrgID : 1 ,
OrgRole : identity . RoleNone ,
Permissions : map [ int64 ] map [ string ] [ ] string { 1 : tc . permissions } ,
} ,
}
ds := & datasources . DataSource {
UID : dsUID ,
Type : datasources . DS_LOKI ,
URL : "http://loki:3100" ,
}
proxy , err := setupDSProxyTest ( t , ctx , ds , routes , tc . path )
require . NoError ( t , err )
err = proxy . validateRequest ( )
if tc . wantErr != nil {
require . Error ( t , err )
require . Equal ( t , tc . wantErr . Error ( ) , err . Error ( ) )
return
}
require . NoError ( t , err )
require . Equal ( t , tc . wantRoute , proxy . matchedRoute )
} )
}
}
2020-05-12 07:04:18 -04:00
func TestNewDataSourceProxy_ProtocolLessURL ( t * testing . T ) {
2023-01-27 02:50:36 -05:00
ctx := contextmodel . ReqContext {
2021-10-11 08:30:59 -04:00
Context : & web . Context { } ,
2022-08-10 05:56:48 -04:00
SignedInUser : & user . SignedInUser { OrgRole : org . RoleEditor } ,
2020-05-12 07:04:18 -04:00
}
2022-06-27 12:23:15 -04:00
ds := datasources . DataSource {
2020-05-12 07:04:18 -04:00
Type : "test" ,
2023-02-02 11:22:43 -05:00
URL : "127.0.01:5432" ,
2020-05-12 07:04:18 -04:00
}
2020-06-17 05:17:11 -04:00
2021-11-01 05:53:33 -04:00
var routes [ ] * plugins . Route
2023-10-02 03:14:10 -04:00
_ , err := setupDSProxyTest ( t , & ctx , & ds , routes , "api/mehtod" )
2020-05-12 07:04:18 -04:00
require . NoError ( t , err )
2020-04-22 04:30:06 -04:00
}
2020-06-17 05:17:11 -04:00
// Test wth MSSQL type data sources.
func TestNewDataSourceProxy_MSSQL ( t * testing . T ) {
2023-01-27 02:50:36 -05:00
ctx := contextmodel . ReqContext {
2021-10-11 08:30:59 -04:00
Context : & web . Context { } ,
2022-08-10 05:56:48 -04:00
SignedInUser : & user . SignedInUser { OrgRole : org . RoleEditor } ,
2020-06-17 05:17:11 -04:00
}
2022-01-20 05:10:12 -05:00
2020-06-17 05:17:11 -04:00
tcs := [ ] struct {
description string
url string
err error
} {
{
description : "Valid ODBC URL" ,
url : ` localhost\instance:1433 ` ,
} ,
{
description : "Invalid ODBC URL" ,
url : ` localhost\instance::1433 ` ,
2026-06-08 06:28:30 -04:00
err : validation . URLValidationError {
2025-11-20 06:09:09 -05:00
Err : errors . New ( ` unrecognized URL format: "localhost\\instance::1433" ` ) ,
2020-06-17 05:17:11 -04:00
URL : ` localhost\instance::1433 ` ,
} ,
} ,
}
for _ , tc := range tcs {
t . Run ( tc . description , func ( t * testing . T ) {
2022-06-27 12:23:15 -04:00
ds := datasources . DataSource {
2020-06-17 05:17:11 -04:00
Type : "mssql" ,
2023-02-02 11:22:43 -05:00
URL : tc . url ,
2020-06-17 05:17:11 -04:00
}
2021-11-01 05:53:33 -04:00
var routes [ ] * plugins . Route
2023-10-02 03:14:10 -04:00
p , err := setupDSProxyTest ( t , & ctx , & ds , routes , "api/method" )
2020-06-17 05:17:11 -04:00
if tc . err == nil {
require . NoError ( t , err )
assert . Equal ( t , & url . URL {
Scheme : "sqlserver" ,
2023-02-02 11:22:43 -05:00
Host : ds . URL ,
2020-06-17 05:17:11 -04:00
} , p . targetUrl )
} else {
require . Error ( t , err )
assert . Equal ( t , tc . err , err )
}
} )
}
}
2019-03-14 08:04:47 -04:00
// getDatasourceProxiedRequest is a helper for easier setup of tests based on global config and ReqContext.
2026-05-21 05:09:14 -04:00
func getDatasourceProxiedRequest ( t * testing . T , ctx * contextmodel . ReqContext , proxyCfg * DataSourceProxySettings ) * http . Request {
2022-06-27 12:23:15 -04:00
ds := & datasources . DataSource {
2019-03-14 08:04:47 -04:00
Type : "custom" ,
2023-02-02 11:22:43 -05:00
URL : "http://host/root/" ,
2019-03-14 08:04:47 -04:00
}
2022-06-15 06:40:41 -04:00
tracer := tracing . InitializeTracerForTest ( )
2019-03-14 08:04:47 -04:00
2021-11-01 05:53:33 -04:00
var routes [ ] * plugins . Route
2026-05-21 05:09:14 -04:00
cfg := setting . NewCfg ( )
2022-10-19 09:02:15 -04:00
sqlStore := db . InitTestDB ( t )
2022-08-25 17:04:44 -04:00
secretsService := secretsmng . SetupTestService ( t , fakes . NewFakeSecretsStore ( ) )
secretsStore := secretskvs . NewSQLSecretsKVStore ( sqlStore , secretsService , log . New ( "test.logger" ) )
2023-10-02 03:14:10 -04:00
features := featuremgmt . WithFeatures ( )
2022-11-14 14:08:10 -05:00
quotaService := quotatest . New ( false , nil )
2026-01-07 15:29:59 -05:00
dsRetriever := datasourceservice . ProvideDataSourceRetriever ( sqlStore , features )
2025-01-14 04:26:15 -05:00
dsService , err := datasourceservice . ProvideService ( nil , secretsService , secretsStore , cfg , features , acimpl . ProvideAccessControl ( features ) ,
2024-05-28 09:59:06 -04:00
& actest . FakePermissionsService { } , quotaService , & pluginstore . FakePluginStore { } , & pluginfakes . FakePluginClient { } ,
2026-01-07 15:29:59 -05:00
plugincontext . ProvideBaseService ( cfg , pluginconfig . NewFakePluginRequestConfigProvider ( ) ) , dsRetriever )
2022-11-14 14:08:10 -05:00
require . NoError ( t , err )
2026-06-08 06:28:30 -04:00
loader , err := NewDataSourceLoader ( ds , dsService )
require . NoError ( t , err )
proxy , err := NewDataSourceProxy ( loader , routes , toHTTPContext ( t , ctx ) , "" , proxyCfg , httpclient . NewProvider ( ) , & oauthtoken . Service { } , tracer , features )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
2019-03-14 08:04:47 -04:00
req , err := http . NewRequest ( http . MethodGet , "http://grafana.com/sub" , nil )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
2019-03-14 08:04:47 -04:00
2020-11-13 07:21:43 -05:00
proxy . director ( req )
2019-03-14 08:04:47 -04:00
return req
}
2018-06-14 07:39:46 -04:00
type httpClientStub struct {
2020-11-13 03:52:38 -05:00
t * testing . T
2018-06-12 11:39:38 -04:00
fakeBody [ ] byte
}
2018-06-14 07:39:46 -04:00
func ( c * httpClientStub ) Do ( req * http . Request ) ( * http . Response , error ) {
2020-11-13 03:52:38 -05:00
bodyJSON , err := simplejson . NewJson ( c . fakeBody )
require . NoError ( c . t , err )
2018-06-12 11:39:38 -04:00
_ , passedTokenCacheTest := bodyJSON . CheckGet ( "expires_on" )
2020-11-13 03:52:38 -05:00
require . True ( c . t , passedTokenCacheTest )
2018-06-12 11:39:38 -04:00
bodyJSON . Set ( "expires_on" , fmt . Sprint ( time . Now ( ) . Add ( time . Second * 60 ) . Unix ( ) ) )
2020-11-13 03:52:38 -05:00
body , err := bodyJSON . MarshalJSON ( )
require . NoError ( c . t , err )
2018-06-12 11:39:38 -04:00
resp := & http . Response {
2022-08-10 09:37:51 -04:00
Body : io . NopCloser ( bytes . NewReader ( body ) ) ,
2018-06-12 11:39:38 -04:00
}
return resp , nil
}
2020-11-13 03:52:38 -05:00
func newFakeHTTPClient ( t * testing . T , fakeBody [ ] byte ) httpClient {
2018-06-14 07:39:46 -04:00
return & httpClientStub {
2020-11-13 03:52:38 -05:00
t : t ,
2018-06-14 07:39:46 -04:00
fakeBody : fakeBody ,
2018-06-12 11:39:38 -04:00
}
}
2019-04-15 05:11:17 -04:00
2020-11-13 03:52:38 -05:00
type testCase struct {
2022-06-27 12:23:15 -04:00
datasource * datasources . DataSource
2019-04-15 05:11:17 -04:00
checkReq func ( req * http . Request )
}
const (
2020-11-13 03:52:38 -05:00
authTypePassword = "password"
authTypeBasic = "basic"
2019-04-15 05:11:17 -04:00
)
const (
2020-11-13 03:52:38 -05:00
authCheckQuery = "query"
authCheckHeader = "header"
2019-04-15 05:11:17 -04:00
)
2022-08-25 17:04:44 -04:00
func createAuthTest ( t * testing . T , secretsStore secretskvs . SecretsKVStore , dsType string , url string , authType string , authCheck string ) * testCase {
2019-04-15 05:11:17 -04:00
// Basic user:password
2020-11-13 03:52:38 -05:00
base64AuthHeader := "Basic dXNlcjpwYXNzd29yZA=="
2019-04-15 05:11:17 -04:00
2020-11-13 03:52:38 -05:00
test := & testCase {
2022-06-27 12:23:15 -04:00
datasource : & datasources . DataSource {
2023-02-02 11:22:43 -05:00
ID : 1 ,
OrgID : 1 ,
2022-06-03 11:38:22 -04:00
Name : fmt . Sprintf ( "%s,%s,%s,%s" , dsType , url , authType , authCheck ) ,
2019-04-15 05:11:17 -04:00
Type : dsType ,
JsonData : simplejson . New ( ) ,
2023-02-02 11:22:43 -05:00
URL : url ,
2019-04-15 05:11:17 -04:00
} ,
}
var message string
2021-10-07 10:33:50 -04:00
var err error
2020-11-13 03:52:38 -05:00
if authType == authTypePassword {
2019-04-15 05:11:17 -04:00
message = fmt . Sprintf ( "%v should add username and password" , dsType )
test . datasource . User = "user"
2022-06-03 11:38:22 -04:00
secureJsonData , err := json . Marshal ( map [ string ] string {
"password" : "password" ,
} )
require . NoError ( t , err )
2022-04-25 12:57:45 -04:00
2023-02-02 11:22:43 -05:00
err = secretsStore . Set ( context . Background ( ) , test . datasource . OrgID , test . datasource . Name , "datasource" , string ( secureJsonData ) )
2022-06-03 11:38:22 -04:00
require . NoError ( t , err )
2019-04-15 05:11:17 -04:00
} else {
message = fmt . Sprintf ( "%v should add basic auth username and password" , dsType )
test . datasource . BasicAuth = true
test . datasource . BasicAuthUser = "user"
2022-06-03 11:38:22 -04:00
secureJsonData , err := json . Marshal ( map [ string ] string {
"basicAuthPassword" : "password" ,
} )
require . NoError ( t , err )
2022-04-25 12:57:45 -04:00
2023-02-02 11:22:43 -05:00
err = secretsStore . Set ( context . Background ( ) , test . datasource . OrgID , test . datasource . Name , "datasource" , string ( secureJsonData ) )
2022-06-03 11:38:22 -04:00
require . NoError ( t , err )
2019-04-15 05:11:17 -04:00
}
2021-10-07 10:33:50 -04:00
require . NoError ( t , err )
2019-04-15 05:11:17 -04:00
2022-06-03 11:38:22 -04:00
message += " from securejsondata"
2019-04-15 05:11:17 -04:00
2020-11-13 03:52:38 -05:00
if authCheck == authCheckQuery {
2019-04-15 05:11:17 -04:00
message += " to query params"
test . checkReq = func ( req * http . Request ) {
2020-11-13 03:52:38 -05:00
queryVals := req . URL . Query ( )
assert . Equal ( t , "user" , queryVals [ "u" ] [ 0 ] , message )
assert . Equal ( t , "password" , queryVals [ "p" ] [ 0 ] , message )
2019-04-15 05:11:17 -04:00
}
} else {
message += " to auth header"
test . checkReq = func ( req * http . Request ) {
2020-11-13 03:52:38 -05:00
assert . Equal ( t , base64AuthHeader , req . Header . Get ( "Authorization" ) , message )
2019-04-15 05:11:17 -04:00
}
}
return test
}
2026-05-07 03:38:09 -04:00
func runDatasourceAuthTest ( t * testing . T ,
secretsService secrets . Service , //nolint:staticcheck // SA1019: Legacy envelope encryption for single-tenant feature
secretsStore secretskvs . SecretsKVStore , cfg * setting . Cfg , test * testCase ,
) {
2026-06-08 06:28:30 -04:00
ctx := newReqContext ( t )
2022-06-15 06:40:41 -04:00
tracer := tracing . InitializeTracerForTest ( )
2022-01-20 05:10:12 -05:00
2021-11-01 05:53:33 -04:00
var routes [ ] * plugins . Route
2023-10-02 03:14:10 -04:00
features := featuremgmt . WithFeatures ( )
2022-11-14 14:08:10 -05:00
quotaService := quotatest . New ( false , nil )
2026-01-07 15:29:59 -05:00
var sqlStore db . DB = nil
dsRetriever := datasourceservice . ProvideDataSourceRetriever ( sqlStore , features )
dsService , err := datasourceservice . ProvideService ( sqlStore , secretsService , secretsStore , cfg , features , acimpl . ProvideAccessControl ( features ) ,
2024-05-28 09:59:06 -04:00
& actest . FakePermissionsService { } , quotaService , & pluginstore . FakePluginStore { } , & pluginfakes . FakePluginClient { } ,
2026-01-07 15:29:59 -05:00
plugincontext . ProvideBaseService ( cfg , pluginconfig . NewFakePluginRequestConfigProvider ( ) ) , dsRetriever )
2022-11-14 14:08:10 -05:00
require . NoError ( t , err )
2026-06-08 06:28:30 -04:00
loader , err := NewDataSourceLoader ( test . datasource , dsService )
require . NoError ( t , err )
proxy , err := NewDataSourceProxy ( loader , routes , toHTTPContext ( t , ctx ) , "" , & DataSourceProxySettings { } , httpclient . NewProvider ( ) , & oauthtoken . Service { } , tracer , features )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
2019-04-15 05:11:17 -04:00
req , err := http . NewRequest ( http . MethodGet , "http://grafana.com/sub" , nil )
2020-11-13 03:52:38 -05:00
require . NoError ( t , err )
2019-04-15 05:11:17 -04:00
2020-11-13 07:21:43 -05:00
proxy . director ( req )
2019-04-15 05:11:17 -04:00
test . checkReq ( req )
}
2021-04-14 13:06:20 -04:00
func Test_PathCheck ( t * testing . T ) {
// Ensure that we test routes appropriately. This test reproduces a historical bug where two routes were defined with different role requirements but the same method and the more privileged route was tested first. Here we ensure auth checks are applied based on the correct route, not just the method.
2021-11-01 05:53:33 -04:00
routes := [ ] * plugins . Route {
{
Path : "a" ,
URL : "https://www.google.com" ,
2022-08-10 05:56:48 -04:00
ReqRole : org . RoleEditor ,
2021-11-01 05:53:33 -04:00
Method : http . MethodGet ,
} ,
{
Path : "b" ,
URL : "https://www.google.com" ,
2022-08-10 05:56:48 -04:00
ReqRole : org . RoleViewer ,
2021-11-01 05:53:33 -04:00
Method : http . MethodGet ,
2021-04-14 13:06:20 -04:00
} ,
}
2021-11-01 05:53:33 -04:00
2023-01-27 02:50:36 -05:00
setUp := func ( ) ( * contextmodel . ReqContext , * http . Request ) {
2021-04-14 13:06:20 -04:00
req , err := http . NewRequest ( "GET" , "http://localhost/asd" , nil )
require . NoError ( t , err )
2023-01-27 02:50:36 -05:00
ctx := & contextmodel . ReqContext {
2021-10-11 08:30:59 -04:00
Context : & web . Context { Req : req } ,
2022-08-10 05:56:48 -04:00
SignedInUser : & user . SignedInUser { OrgRole : org . RoleViewer } ,
2021-04-14 13:06:20 -04:00
}
return ctx , req
}
ctx , _ := setUp ( )
2023-10-02 03:14:10 -04:00
proxy , err := setupDSProxyTest ( t , ctx , & datasources . DataSource { } , routes , "b" )
2021-04-14 13:06:20 -04:00
require . NoError ( t , err )
require . Nil ( t , proxy . validateRequest ( ) )
2021-11-01 05:53:33 -04:00
require . Equal ( t , routes [ 1 ] , proxy . matchedRoute )
2021-04-14 13:06:20 -04:00
}
2021-07-07 02:54:17 -04:00
2023-10-02 03:14:10 -04:00
func setupDSProxyTest ( t * testing . T , ctx * contextmodel . ReqContext , ds * datasources . DataSource , routes [ ] * plugins . Route , path string , opts ... func ( proxy * DataSourceProxy ) ) ( * DataSourceProxy , error ) {
t . Helper ( )
2021-07-07 02:54:17 -04:00
2023-10-02 03:14:10 -04:00
cfg := setting . NewCfg ( )
secretsService := secretsmng . SetupTestService ( t , fakes . NewFakeSecretsStore ( ) )
secretsStore := secretskvs . NewSQLSecretsKVStore ( dbtest . NewFakeDB ( ) , secretsService , log . NewNopLogger ( ) )
2025-02-25 07:44:40 -05:00
features := featuremgmt . WithFeatures ( )
2026-01-07 15:29:59 -05:00
var sqlStore db . DB = nil
dsRetriever := datasourceservice . ProvideDataSourceRetriever ( sqlStore , features )
dsService , err := datasourceservice . ProvideService ( sqlStore , secretsService , secretsStore , cfg , features , acimpl . ProvideAccessControl ( features ) ,
2024-05-28 09:59:06 -04:00
& actest . FakePermissionsService { } , quotatest . New ( false , nil ) , & pluginstore . FakePluginStore { } , & pluginfakes . FakePluginClient { } ,
2026-01-07 15:29:59 -05:00
plugincontext . ProvideBaseService ( cfg , pluginconfig . NewFakePluginRequestConfigProvider ( ) ) , dsRetriever )
2023-10-02 03:14:10 -04:00
require . NoError ( t , err )
2021-07-07 02:54:17 -04:00
2023-10-02 03:14:10 -04:00
tracer := tracing . InitializeTracerForTest ( )
2022-10-18 12:17:28 -04:00
2026-06-08 06:28:30 -04:00
loader , err := NewDataSourceLoader ( ds , dsService )
require . NoError ( t , err )
proxy , err := NewDataSourceProxy ( loader , routes , toHTTPContext ( t , ctx ) , path , & DataSourceProxySettings { } , httpclient . NewProvider ( ) , & oauthtoken . Service { } , tracer , features )
2023-10-02 03:14:10 -04:00
if err != nil {
return nil , err
}
2022-10-18 12:17:28 -04:00
2023-10-02 03:14:10 -04:00
for _ , o := range opts {
o ( proxy )
}
2022-10-18 12:17:28 -04:00
2023-10-02 03:14:10 -04:00
return proxy , nil
2022-10-18 12:17:28 -04:00
}
2026-06-08 06:28:30 -04:00
// newReqContext returns a ReqContext with a minimal http.Request and a default
// SignedInUser attached. NewDataSourceProxy reads ctx.Req.Context() and requires
// a Requester in that context during construction.
func newReqContext ( t * testing . T ) * contextmodel . ReqContext {
t . Helper ( )
req , err := http . NewRequest ( http . MethodGet , "http://localhost/" , nil )
require . NoError ( t , err )
return & contextmodel . ReqContext {
Context : & web . Context { Req : req } ,
SignedInUser : & user . SignedInUser { } ,
}
}
// toHTTPContext builds an HTTPContext suitable for NewDataSourceProxy from a
// ReqContext used in tests. It fills in a default Req and SignedInUser if absent
// and injects the SignedInUser into the request context so identity.GetRequester
// can find it.
func toHTTPContext ( t * testing . T , ctx * contextmodel . ReqContext ) HTTPContext {
t . Helper ( )
if ctx . Context == nil {
ctx . Context = & web . Context { }
}
if ctx . Req == nil {
req , err := http . NewRequest ( http . MethodGet , "http://localhost/" , nil )
require . NoError ( t , err )
ctx . Req = req
}
if ctx . SignedInUser == nil {
ctx . SignedInUser = & user . SignedInUser { }
}
ctx . Req = ctx . Req . WithContext ( identity . WithRequester ( ctx . Req . Context ( ) , ctx . SignedInUser ) )
return HTTPContext {
Req : ctx . Req ,
Resp : ctx . Resp ,
UserToken : ctx . UserToken ,
}
}