SSE: Make sure to forward headers, user and cookies/OAuth token (#58897)
Fixes #58793 and Fixes https://github.com/grafana/azure-data-explorer-datasource/issues/513
Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
(cherry picked from commit 5623b5afaf)
Co-authored-by: Kyle Brandt <kyle@grafana.com>
This commit is contained in:
co-authored by
Kyle Brandt
parent
8bb5b17692
commit
9b7ad3d663
+6
-5
@@ -143,11 +143,12 @@ func (s *Service) buildGraph(req *Request) (*simple.DirectedGraph, error) {
|
||||
}
|
||||
|
||||
rn := &rawNode{
|
||||
Query: rawQueryProp,
|
||||
RefID: query.RefID,
|
||||
TimeRange: query.TimeRange,
|
||||
QueryType: query.QueryType,
|
||||
DataSource: query.DataSource,
|
||||
Query: rawQueryProp,
|
||||
RefID: query.RefID,
|
||||
TimeRange: query.TimeRange,
|
||||
QueryType: query.QueryType,
|
||||
DataSource: query.DataSource,
|
||||
QueryEnricher: query.QueryEnricher,
|
||||
}
|
||||
|
||||
var node Node
|
||||
|
||||
+41
-33
@@ -42,11 +42,12 @@ type baseNode struct {
|
||||
}
|
||||
|
||||
type rawNode struct {
|
||||
RefID string `json:"refId"`
|
||||
Query map[string]interface{}
|
||||
QueryType string
|
||||
TimeRange TimeRange
|
||||
DataSource *datasources.DataSource
|
||||
RefID string `json:"refId"`
|
||||
Query map[string]interface{}
|
||||
QueryType string
|
||||
TimeRange TimeRange
|
||||
DataSource *datasources.DataSource
|
||||
QueryEnricher QueryDataRequestEnricher
|
||||
}
|
||||
|
||||
func (rn *rawNode) GetCommandType() (c CommandType, err error) {
|
||||
@@ -137,8 +138,9 @@ const (
|
||||
// DSNode is a DPNode that holds a datasource request.
|
||||
type DSNode struct {
|
||||
baseNode
|
||||
query json.RawMessage
|
||||
datasource *datasources.DataSource
|
||||
query json.RawMessage
|
||||
datasource *datasources.DataSource
|
||||
queryEnricher QueryDataRequestEnricher
|
||||
|
||||
orgID int64
|
||||
queryType string
|
||||
@@ -164,14 +166,15 @@ func (s *Service) buildDSNode(dp *simple.DirectedGraph, rn *rawNode, req *Reques
|
||||
id: dp.NewNode().ID(),
|
||||
refID: rn.RefID,
|
||||
},
|
||||
orgID: req.OrgId,
|
||||
query: json.RawMessage(encodedQuery),
|
||||
queryType: rn.QueryType,
|
||||
intervalMS: defaultIntervalMS,
|
||||
maxDP: defaultMaxDP,
|
||||
timeRange: rn.TimeRange,
|
||||
request: *req,
|
||||
datasource: rn.DataSource,
|
||||
orgID: req.OrgId,
|
||||
query: json.RawMessage(encodedQuery),
|
||||
queryType: rn.QueryType,
|
||||
intervalMS: defaultIntervalMS,
|
||||
maxDP: defaultMaxDP,
|
||||
timeRange: rn.TimeRange,
|
||||
request: *req,
|
||||
datasource: rn.DataSource,
|
||||
queryEnricher: rn.QueryEnricher,
|
||||
}
|
||||
|
||||
var floatIntervalMS float64
|
||||
@@ -205,27 +208,32 @@ func (dn *DSNode) Execute(ctx context.Context, vars mathexp.Vars, s *Service) (m
|
||||
OrgID: dn.orgID,
|
||||
DataSourceInstanceSettings: dsInstanceSettings,
|
||||
PluginID: dn.datasource.Type,
|
||||
User: dn.request.User,
|
||||
}
|
||||
|
||||
q := []backend.DataQuery{
|
||||
{
|
||||
RefID: dn.refID,
|
||||
MaxDataPoints: dn.maxDP,
|
||||
Interval: time.Duration(int64(time.Millisecond) * dn.intervalMS),
|
||||
JSON: dn.query,
|
||||
TimeRange: backend.TimeRange{
|
||||
From: dn.timeRange.From,
|
||||
To: dn.timeRange.To,
|
||||
},
|
||||
QueryType: dn.queryType,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := s.dataService.QueryData(ctx, &backend.QueryDataRequest{
|
||||
req := &backend.QueryDataRequest{
|
||||
PluginContext: pc,
|
||||
Queries: q,
|
||||
Headers: dn.request.Headers,
|
||||
})
|
||||
Queries: []backend.DataQuery{
|
||||
{
|
||||
RefID: dn.refID,
|
||||
MaxDataPoints: dn.maxDP,
|
||||
Interval: time.Duration(int64(time.Millisecond) * dn.intervalMS),
|
||||
JSON: dn.query,
|
||||
TimeRange: backend.TimeRange{
|
||||
From: dn.timeRange.From,
|
||||
To: dn.timeRange.To,
|
||||
},
|
||||
QueryType: dn.queryType,
|
||||
},
|
||||
},
|
||||
Headers: dn.request.Headers,
|
||||
}
|
||||
|
||||
if dn.queryEnricher != nil {
|
||||
ctx = dn.queryEnricher(ctx, req)
|
||||
}
|
||||
|
||||
resp, err := s.dataService.QueryData(ctx, req)
|
||||
if err != nil {
|
||||
return mathexp.Results{}, err
|
||||
}
|
||||
|
||||
@@ -35,14 +35,19 @@ type Request struct {
|
||||
Debug bool
|
||||
OrgId int64
|
||||
Queries []Query
|
||||
User *backend.User
|
||||
}
|
||||
|
||||
// QueryDataRequestEnricher function definition for enriching a backend.QueryDataRequest request.
|
||||
type QueryDataRequestEnricher func(ctx context.Context, req *backend.QueryDataRequest) context.Context
|
||||
|
||||
// Query is like plugins.DataSubQuery, but with a a time range, and only the UID
|
||||
// for the data source. Also interval is a time.Duration.
|
||||
type Query struct {
|
||||
RefID string
|
||||
TimeRange TimeRange
|
||||
DataSource *datasources.DataSource `json:"datasource"`
|
||||
QueryEnricher QueryDataRequestEnricher
|
||||
JSON json.RawMessage
|
||||
Interval time.Duration
|
||||
QueryType string
|
||||
|
||||
@@ -132,7 +132,7 @@ func buildQueryDataService(t *testing.T, cs datasources.CacheService, fpc *fakeP
|
||||
}
|
||||
|
||||
return query.ProvideService(
|
||||
nil,
|
||||
setting.NewCfg(),
|
||||
cs,
|
||||
nil,
|
||||
&fakePluginRequestValidator{},
|
||||
|
||||
+13
-15
@@ -3,7 +3,6 @@ package query
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
@@ -112,10 +111,17 @@ func (s *Service) QueryDataMultipleSources(ctx context.Context, user *user.Signe
|
||||
// handleExpressions handles POST /api/ds/query when there is an expression.
|
||||
func (s *Service) handleExpressions(ctx context.Context, user *user.SignedInUser, parsedReq *parsedRequest) (*backend.QueryDataResponse, error) {
|
||||
exprReq := expr.Request{
|
||||
OrgId: user.OrgID,
|
||||
Queries: []expr.Query{},
|
||||
}
|
||||
|
||||
if user != nil { // for passthrough authentication, SSE does not authenticate
|
||||
exprReq.User = adapters.BackendUserFromSignedInUser(user)
|
||||
exprReq.OrgId = user.OrgID
|
||||
}
|
||||
|
||||
disallowedCookies := []string{s.cfg.LoginCookieName}
|
||||
queryEnrichers := parsedReq.createDataSourceQueryEnrichers(ctx, user, s.oAuthTokenService, disallowedCookies)
|
||||
|
||||
for _, pq := range parsedReq.parsedQueries {
|
||||
if pq.datasource == nil {
|
||||
return nil, ErrMissingDataSourceInfo.Build(errutil.TemplateData{
|
||||
@@ -136,6 +142,7 @@ func (s *Service) handleExpressions(ctx context.Context, user *user.SignedInUser
|
||||
From: pq.query.TimeRange.From,
|
||||
To: pq.query.TimeRange.To,
|
||||
},
|
||||
QueryEnricher: queryEnrichers[pq.datasource.Uid],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -168,10 +175,11 @@ func (s *Service) handleQueryData(ctx context.Context, user *user.SignedInUser,
|
||||
Queries: []backend.DataQuery{},
|
||||
}
|
||||
|
||||
disallowedCookies := []string{s.cfg.LoginCookieName}
|
||||
middlewares := []httpclient.Middleware{}
|
||||
if parsedReq.httpRequest != nil {
|
||||
middlewares = append(middlewares,
|
||||
httpclientprovider.ForwardedCookiesMiddleware(parsedReq.httpRequest.Cookies(), ds.AllowedCookies(), []string{s.cfg.LoginCookieName}),
|
||||
httpclientprovider.ForwardedCookiesMiddleware(parsedReq.httpRequest.Cookies(), ds.AllowedCookies(), disallowedCookies),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -188,7 +196,7 @@ func (s *Service) handleQueryData(ctx context.Context, user *user.SignedInUser,
|
||||
}
|
||||
|
||||
if parsedReq.httpRequest != nil {
|
||||
proxyutil.ClearCookieHeader(parsedReq.httpRequest, ds.AllowedCookies(), []string{s.cfg.LoginCookieName})
|
||||
proxyutil.ClearCookieHeader(parsedReq.httpRequest, ds.AllowedCookies(), disallowedCookies)
|
||||
if cookieStr := parsedReq.httpRequest.Header.Get("Cookie"); cookieStr != "" {
|
||||
req.Headers["Cookie"] = cookieStr
|
||||
}
|
||||
@@ -203,17 +211,7 @@ func (s *Service) handleQueryData(ctx context.Context, user *user.SignedInUser,
|
||||
return s.pluginClient.QueryData(ctx, req)
|
||||
}
|
||||
|
||||
type parsedQuery struct {
|
||||
datasource *datasources.DataSource
|
||||
query backend.DataQuery
|
||||
}
|
||||
|
||||
type parsedRequest struct {
|
||||
hasExpression bool
|
||||
parsedQueries []parsedQuery
|
||||
httpRequest *http.Request
|
||||
}
|
||||
|
||||
// parseRequest parses a request into parsed queries grouped by datasource uid
|
||||
func (s *Service) parseMetricRequest(ctx context.Context, user *user.SignedInUser, skipCache bool, reqDTO dtos.MetricRequest) (*parsedRequest, error) {
|
||||
if len(reqDTO.Queries) == 0 {
|
||||
return nil, ErrNoQueriesFound
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
"github.com/grafana/grafana/pkg/expr"
|
||||
"github.com/grafana/grafana/pkg/infra/httpclient/httpclientprovider"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/oauthtoken"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/util/proxyutil"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type parsedQuery struct {
|
||||
datasource *datasources.DataSource
|
||||
query backend.DataQuery
|
||||
}
|
||||
|
||||
type parsedRequest struct {
|
||||
hasExpression bool
|
||||
parsedQueries []parsedQuery
|
||||
httpRequest *http.Request
|
||||
}
|
||||
|
||||
func (pr parsedRequest) createDataSourceQueryEnrichers(ctx context.Context, signedInUser *user.SignedInUser, oAuthTokenService oauthtoken.OAuthTokenService, disallowedCookies []string) map[string]expr.QueryDataRequestEnricher {
|
||||
datasourcesHeaderProvider := map[string]expr.QueryDataRequestEnricher{}
|
||||
|
||||
if pr.httpRequest == nil {
|
||||
return datasourcesHeaderProvider
|
||||
}
|
||||
|
||||
if len(pr.parsedQueries) == 0 || pr.parsedQueries[0].datasource == nil {
|
||||
return datasourcesHeaderProvider
|
||||
}
|
||||
|
||||
for _, q := range pr.parsedQueries {
|
||||
ds := q.datasource
|
||||
uid := ds.Uid
|
||||
|
||||
if expr.IsDataSource(uid) {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exists := datasourcesHeaderProvider[uid]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
allowedCookies := ds.AllowedCookies()
|
||||
clonedReq := pr.httpRequest.Clone(pr.httpRequest.Context())
|
||||
|
||||
var token *oauth2.Token
|
||||
if oAuthTokenService.IsOAuthPassThruEnabled(ds) {
|
||||
token = oAuthTokenService.GetCurrentOAuthToken(ctx, signedInUser)
|
||||
}
|
||||
|
||||
datasourcesHeaderProvider[uid] = func(ctx context.Context, req *backend.QueryDataRequest) context.Context {
|
||||
if len(req.Headers) == 0 {
|
||||
req.Headers = map[string]string{}
|
||||
}
|
||||
|
||||
if len(allowedCookies) > 0 {
|
||||
proxyutil.ClearCookieHeader(clonedReq, allowedCookies, disallowedCookies)
|
||||
if cookieStr := clonedReq.Header.Get("Cookie"); cookieStr != "" {
|
||||
req.Headers["Cookie"] = cookieStr
|
||||
}
|
||||
|
||||
ctx = httpclient.WithContextualMiddleware(ctx, httpclientprovider.ForwardedCookiesMiddleware(clonedReq.Cookies(), allowedCookies, disallowedCookies))
|
||||
}
|
||||
|
||||
if token != nil {
|
||||
req.Headers["Authorization"] = fmt.Sprintf("%s %s", token.Type(), token.AccessToken)
|
||||
|
||||
idToken, ok := token.Extra("id_token").(string)
|
||||
if ok && idToken != "" {
|
||||
req.Headers["X-ID-Token"] = idToken
|
||||
}
|
||||
|
||||
ctx = httpclient.WithContextualMiddleware(ctx, httpclientprovider.ForwardedOAuthIdentityMiddleware(token))
|
||||
}
|
||||
|
||||
return ctx
|
||||
}
|
||||
}
|
||||
|
||||
return datasourcesHeaderProvider
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
package query_test
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/expr"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/expr"
|
||||
"github.com/grafana/grafana/pkg/infra/httpclient/httpclientprovider"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
@@ -20,15 +20,243 @@ import (
|
||||
fakeDatasources "github.com/grafana/grafana/pkg/services/datasources/fakes"
|
||||
dsSvc "github.com/grafana/grafana/pkg/services/datasources/service"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/query"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||
secretskvs "github.com/grafana/grafana/pkg/services/secrets/kvstore"
|
||||
secretsmng "github.com/grafana/grafana/pkg/services/secrets/manager"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
func TestParseMetricRequest(t *testing.T) {
|
||||
t.Run("Test a simple single datasource query", func(t *testing.T) {
|
||||
tc := setup(t)
|
||||
json, err := simplejson.NewJson([]byte(`{
|
||||
"keepCookies": [ "cookie1", "cookie3", "login" ]
|
||||
}`))
|
||||
require.NoError(t, err)
|
||||
tc.dataSourceCache.dsByUid = func(ctx context.Context, datasourceUID string, user *user.SignedInUser, skipCache bool) (*datasources.DataSource, error) {
|
||||
if datasourceUID == "gIEkMvIVz" {
|
||||
return &datasources.DataSource{
|
||||
Uid: "gIEkMvIVz",
|
||||
JsonData: json,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
token := &oauth2.Token{
|
||||
TokenType: "bearer",
|
||||
AccessToken: "access-token",
|
||||
}
|
||||
token = token.WithExtra(map[string]interface{}{"id_token": "id-token"})
|
||||
|
||||
tc.oauthTokenService.passThruEnabled = true
|
||||
tc.oauthTokenService.token = token
|
||||
|
||||
mr := metricRequestWithQueries(t, `{
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"uid": "gIEkMvIVz",
|
||||
"type": "postgres"
|
||||
}
|
||||
}`, `{
|
||||
"refId": "B",
|
||||
"datasource": {
|
||||
"uid": "gIEkMvIVz",
|
||||
"type": "postgres"
|
||||
}
|
||||
}`)
|
||||
parsedReq, err := tc.queryService.parseMetricRequest(context.Background(), tc.signedInUser, true, mr)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, parsedReq)
|
||||
assert.False(t, parsedReq.hasExpression)
|
||||
assert.Len(t, parsedReq.parsedQueries, 2)
|
||||
assert.Equal(t, "gIEkMvIVz", parsedReq.parsedQueries[0].datasource.Uid)
|
||||
assert.Equal(t, "gIEkMvIVz", parsedReq.parsedQueries[1].datasource.Uid)
|
||||
|
||||
t.Run("createDataSourceQueryEnrichers should return 0 enrichers when no HTTP request", func(t *testing.T) {
|
||||
enrichers := parsedReq.createDataSourceQueryEnrichers(context.Background(), nil, tc.oauthTokenService, []string{})
|
||||
require.Empty(t, enrichers)
|
||||
})
|
||||
|
||||
t.Run("createDataSourceQueryEnrichers should return 1 enricher", func(t *testing.T) {
|
||||
parsedReq.httpRequest = httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
parsedReq.httpRequest.AddCookie(&http.Cookie{Name: "cookie1"})
|
||||
parsedReq.httpRequest.AddCookie(&http.Cookie{Name: "cookie2"})
|
||||
parsedReq.httpRequest.AddCookie(&http.Cookie{Name: "cookie3"})
|
||||
parsedReq.httpRequest.AddCookie(&http.Cookie{Name: "login"})
|
||||
|
||||
enrichers := parsedReq.createDataSourceQueryEnrichers(context.Background(), nil, tc.oauthTokenService, []string{"login"})
|
||||
require.Len(t, enrichers, 1)
|
||||
require.NotNil(t, enrichers["gIEkMvIVz"])
|
||||
req := &backend.QueryDataRequest{}
|
||||
ctx := enrichers["gIEkMvIVz"](context.Background(), req)
|
||||
require.Len(t, req.Headers, 3)
|
||||
require.Equal(t, "Bearer access-token", req.Headers["Authorization"])
|
||||
require.Equal(t, "id-token", req.Headers["X-ID-Token"])
|
||||
require.Equal(t, "cookie1=; cookie3=", req.Headers["Cookie"])
|
||||
middlewares := httpclient.ContextualMiddlewareFromContext(ctx)
|
||||
require.Len(t, middlewares, 2)
|
||||
require.Equal(t, httpclientprovider.ForwardedCookiesMiddlewareName, middlewares[0].(httpclient.MiddlewareName).MiddlewareName())
|
||||
require.Equal(t, httpclientprovider.ForwardedOAuthIdentityMiddlewareName, middlewares[1].(httpclient.MiddlewareName).MiddlewareName())
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Test a single datasource query with expressions", func(t *testing.T) {
|
||||
tc := setup(t)
|
||||
json, err := simplejson.NewJson([]byte(`{
|
||||
"keepCookies": [ "cookie1", "cookie3", "login" ]
|
||||
}`))
|
||||
require.NoError(t, err)
|
||||
tc.dataSourceCache.dsByUid = func(ctx context.Context, datasourceUID string, user *user.SignedInUser, skipCache bool) (*datasources.DataSource, error) {
|
||||
if datasourceUID == "gIEkMvIVz" {
|
||||
return &datasources.DataSource{
|
||||
Uid: "gIEkMvIVz",
|
||||
JsonData: json,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
token := &oauth2.Token{
|
||||
TokenType: "bearer",
|
||||
AccessToken: "access-token",
|
||||
}
|
||||
token = token.WithExtra(map[string]interface{}{"id_token": "id-token"})
|
||||
|
||||
tc.oauthTokenService.passThruEnabled = true
|
||||
tc.oauthTokenService.token = token
|
||||
|
||||
mr := metricRequestWithQueries(t, `{
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"uid": "gIEkMvIVz",
|
||||
"type": "postgres"
|
||||
}
|
||||
}`, `{
|
||||
"refId": "B",
|
||||
"datasource": {
|
||||
"type": "__expr__",
|
||||
"uid": "__expr__",
|
||||
"name": "Expression"
|
||||
},
|
||||
"type": "math",
|
||||
"expression": "$A - 50"
|
||||
}`)
|
||||
parsedReq, err := tc.queryService.parseMetricRequest(context.Background(), tc.signedInUser, true, mr)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, parsedReq)
|
||||
assert.True(t, parsedReq.hasExpression)
|
||||
assert.Len(t, parsedReq.parsedQueries, 2)
|
||||
assert.Equal(t, "gIEkMvIVz", parsedReq.parsedQueries[0].datasource.Uid)
|
||||
assert.Equal(t, expr.DatasourceUID, parsedReq.parsedQueries[1].datasource.Uid)
|
||||
|
||||
// Make sure we end up with something valid
|
||||
_, err = tc.queryService.handleExpressions(context.Background(), tc.signedInUser, parsedReq)
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("createDataSourceQueryEnrichers should return 1 enricher", func(t *testing.T) {
|
||||
parsedReq.httpRequest = httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
parsedReq.httpRequest.AddCookie(&http.Cookie{Name: "cookie1"})
|
||||
parsedReq.httpRequest.AddCookie(&http.Cookie{Name: "cookie2"})
|
||||
parsedReq.httpRequest.AddCookie(&http.Cookie{Name: "cookie3"})
|
||||
parsedReq.httpRequest.AddCookie(&http.Cookie{Name: "login"})
|
||||
|
||||
enrichers := parsedReq.createDataSourceQueryEnrichers(context.Background(), nil, tc.oauthTokenService, []string{"login"})
|
||||
require.Len(t, enrichers, 1)
|
||||
require.NotNil(t, enrichers["gIEkMvIVz"])
|
||||
|
||||
req := &backend.QueryDataRequest{}
|
||||
ctx := enrichers["gIEkMvIVz"](context.Background(), req)
|
||||
require.Len(t, req.Headers, 3)
|
||||
require.Equal(t, "Bearer access-token", req.Headers["Authorization"])
|
||||
require.Equal(t, "id-token", req.Headers["X-ID-Token"])
|
||||
require.Equal(t, "cookie1=; cookie3=", req.Headers["Cookie"])
|
||||
middlewares := httpclient.ContextualMiddlewareFromContext(ctx)
|
||||
require.Len(t, middlewares, 2)
|
||||
require.Equal(t, httpclientprovider.ForwardedCookiesMiddlewareName, middlewares[0].(httpclient.MiddlewareName).MiddlewareName())
|
||||
require.Equal(t, httpclientprovider.ForwardedOAuthIdentityMiddlewareName, middlewares[1].(httpclient.MiddlewareName).MiddlewareName())
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Test a mixed datasource query with expressions", func(t *testing.T) {
|
||||
tc := setup(t)
|
||||
mr := metricRequestWithQueries(t, `{
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"uid": "gIEkMvIVz",
|
||||
"type": "postgres"
|
||||
}
|
||||
}`, `{
|
||||
"refId": "B",
|
||||
"datasource": {
|
||||
"uid": "sEx6ZvSVk",
|
||||
"type": "testdata"
|
||||
}
|
||||
}`, `{
|
||||
"refId": "A_resample",
|
||||
"datasource": {
|
||||
"type": "__expr__",
|
||||
"uid": "__expr__",
|
||||
"name": "Expression"
|
||||
},
|
||||
"expression": "A",
|
||||
"type": "resample",
|
||||
"downsampler": "mean",
|
||||
"upsampler": "fillna",
|
||||
"window": "10s"
|
||||
}`, `{
|
||||
"refId": "B_resample",
|
||||
"datasource": {
|
||||
"type": "__expr__",
|
||||
"uid": "__expr__",
|
||||
"name": "Expression"
|
||||
},
|
||||
"expression": "B",
|
||||
"type": "resample",
|
||||
"downsampler": "mean",
|
||||
"upsampler": "fillna",
|
||||
"window": "10s"
|
||||
}`, `{
|
||||
"refId": "C",
|
||||
"datasource": {
|
||||
"type": "__expr__",
|
||||
"uid": "__expr__",
|
||||
"name": "Expression"
|
||||
},
|
||||
"type": "math",
|
||||
"expression": "$A_resample + $B_resample"
|
||||
}`)
|
||||
parsedReq, err := tc.queryService.parseMetricRequest(context.Background(), tc.signedInUser, true, mr)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, parsedReq)
|
||||
assert.True(t, parsedReq.hasExpression)
|
||||
assert.Len(t, parsedReq.parsedQueries, 5)
|
||||
assert.Equal(t, "gIEkMvIVz", parsedReq.parsedQueries[0].datasource.Uid)
|
||||
assert.Equal(t, "sEx6ZvSVk", parsedReq.parsedQueries[1].datasource.Uid)
|
||||
assert.Equal(t, expr.DatasourceUID, parsedReq.parsedQueries[2].datasource.Uid)
|
||||
assert.Equal(t, expr.DatasourceUID, parsedReq.parsedQueries[3].datasource.Uid)
|
||||
assert.Equal(t, expr.DatasourceUID, parsedReq.parsedQueries[4].datasource.Uid)
|
||||
// Make sure we end up with something valid
|
||||
_, err = tc.queryService.handleExpressions(context.Background(), tc.signedInUser, parsedReq)
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("createDataSourceQueryEnrichers should return 2 enrichers", func(t *testing.T) {
|
||||
parsedReq.httpRequest = &http.Request{}
|
||||
enrichers := parsedReq.createDataSourceQueryEnrichers(context.Background(), nil, tc.oauthTokenService, []string{})
|
||||
require.Len(t, enrichers, 2)
|
||||
require.NotNil(t, enrichers["gIEkMvIVz"])
|
||||
require.NotNil(t, enrichers["sEx6ZvSVk"])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestQueryDataMultipleSources(t *testing.T) {
|
||||
t.Run("can query multiple datasources", func(t *testing.T) {
|
||||
tc := setup(t)
|
||||
@@ -127,7 +355,12 @@ func TestQueryData(t *testing.T) {
|
||||
tc.oauthTokenService.passThruEnabled = true
|
||||
tc.oauthTokenService.token = token
|
||||
|
||||
_, err := tc.queryService.QueryData(context.Background(), nil, true, metricRequest(), false)
|
||||
metricReq := metricRequest()
|
||||
httpReq, err := http.NewRequest(http.MethodGet, "/", nil)
|
||||
require.NoError(t, err)
|
||||
metricReq.HTTPRequest = httpReq
|
||||
|
||||
_, err = tc.queryService.QueryData(context.Background(), nil, true, metricReq, false)
|
||||
require.Nil(t, err)
|
||||
|
||||
expected := map[string]string{
|
||||
@@ -190,7 +423,9 @@ func setup(t *testing.T) *testContext {
|
||||
DataSources: nil,
|
||||
SimulatePluginFailure: false,
|
||||
}
|
||||
exprService := expr.ProvideService(nil, pc, fakeDatasourceService)
|
||||
cfg := setting.NewCfg()
|
||||
cfg.ExpressionsEnabled = true
|
||||
exprService := expr.ProvideService(cfg, pc, fakeDatasourceService)
|
||||
|
||||
return &testContext{
|
||||
pluginContext: pc,
|
||||
@@ -198,7 +433,8 @@ func setup(t *testing.T) *testContext {
|
||||
dataSourceCache: dc,
|
||||
oauthTokenService: tc,
|
||||
pluginRequestValidator: rv,
|
||||
queryService: query.ProvideService(setting.NewCfg(), dc, exprService, rv, ds, pc, tc),
|
||||
queryService: ProvideService(setting.NewCfg(), dc, exprService, rv, ds, pc, tc),
|
||||
signedInUser: &user.SignedInUser{OrgID: 1},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,7 +444,8 @@ type testContext struct {
|
||||
dataSourceCache *fakeDataSourceCache
|
||||
oauthTokenService *fakeOAuthTokenService
|
||||
pluginRequestValidator *fakePluginRequestValidator
|
||||
queryService *query.Service
|
||||
queryService *Service
|
||||
signedInUser *user.SignedInUser
|
||||
}
|
||||
|
||||
func metricRequest() dtos.MetricRequest {
|
||||
@@ -221,6 +458,22 @@ func metricRequest() dtos.MetricRequest {
|
||||
}
|
||||
}
|
||||
|
||||
func metricRequestWithQueries(t *testing.T, rawQueries ...string) dtos.MetricRequest {
|
||||
t.Helper()
|
||||
queries := make([]*simplejson.Json, 0)
|
||||
for _, q := range rawQueries {
|
||||
json, err := simplejson.NewJson([]byte(q))
|
||||
require.NoError(t, err)
|
||||
queries = append(queries, json)
|
||||
}
|
||||
return dtos.MetricRequest{
|
||||
From: "now-1h",
|
||||
To: "now",
|
||||
Queries: queries,
|
||||
Debug: false,
|
||||
}
|
||||
}
|
||||
|
||||
type fakePluginRequestValidator struct {
|
||||
err error
|
||||
}
|
||||
@@ -243,7 +496,8 @@ func (ts *fakeOAuthTokenService) IsOAuthPassThruEnabled(*datasources.DataSource)
|
||||
}
|
||||
|
||||
type fakeDataSourceCache struct {
|
||||
ds *datasources.DataSource
|
||||
ds *datasources.DataSource
|
||||
dsByUid func(ctx context.Context, datasourceUID string, user *user.SignedInUser, skipCache bool) (*datasources.DataSource, error)
|
||||
}
|
||||
|
||||
func (c *fakeDataSourceCache) GetDatasource(ctx context.Context, datasourceID int64, user *user.SignedInUser, skipCache bool) (*datasources.DataSource, error) {
|
||||
@@ -251,7 +505,13 @@ func (c *fakeDataSourceCache) GetDatasource(ctx context.Context, datasourceID in
|
||||
}
|
||||
|
||||
func (c *fakeDataSourceCache) GetDatasourceByUID(ctx context.Context, datasourceUID string, user *user.SignedInUser, skipCache bool) (*datasources.DataSource, error) {
|
||||
return c.ds, nil
|
||||
if c.dsByUid != nil {
|
||||
return c.dsByUid(ctx, datasourceUID, user, skipCache)
|
||||
}
|
||||
|
||||
return &datasources.DataSource{
|
||||
Uid: datasourceUID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type fakePluginClient struct {
|
||||
|
||||
Reference in New Issue
Block a user