mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Apply Custom Headers to datasource queries. (#47860)
Unlike dashboard queries, alerting queries were not correctly applying custom headers to datasource queries. This change mimics the dashboard query code to apply custom headers. Fixes #44460 Signed-off-by: Joe Blubaugh <joe.blubaugh@grafana.com>
This commit is contained in:
parent
d6d358ef26
commit
53a4f3913c
@ -3,9 +3,11 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/adapters"
|
||||
@ -14,6 +16,11 @@ import (
|
||||
"github.com/grafana/grafana/pkg/tsdb/legacydata"
|
||||
)
|
||||
|
||||
const (
|
||||
headerName = "httpHeaderName"
|
||||
headerValue = "httpHeaderValue"
|
||||
)
|
||||
|
||||
var oAuthIsOAuthPassThruEnabledFunc = func(oAuthTokenService oauthtoken.OAuthTokenService, ds *models.DataSource) bool {
|
||||
return oAuthTokenService.IsOAuthPassThruEnabled(ds)
|
||||
}
|
||||
@ -35,70 +42,23 @@ func ProvideService(pluginsClient plugins.Client, oAuthTokenService oauthtoken.O
|
||||
|
||||
//nolint: staticcheck // legacydata.DataResponse deprecated
|
||||
func (h *Service) HandleRequest(ctx context.Context, ds *models.DataSource, query legacydata.DataQuery) (legacydata.DataResponse, error) {
|
||||
jsonDataBytes, err := ds.JsonData.MarshalJSON()
|
||||
decryptedJsonData, err := h.dataSourcesService.DecryptedValues(ctx, ds)
|
||||
if err != nil {
|
||||
return legacydata.DataResponse{}, err
|
||||
}
|
||||
|
||||
decryptedValues, err := h.dataSourcesService.DecryptedValues(ctx, ds)
|
||||
req, err := generateRequest(ctx, ds, decryptedJsonData, query)
|
||||
if err != nil {
|
||||
return legacydata.DataResponse{}, err
|
||||
}
|
||||
|
||||
instanceSettings := &backend.DataSourceInstanceSettings{
|
||||
ID: ds.Id,
|
||||
Name: ds.Name,
|
||||
URL: ds.Url,
|
||||
Database: ds.Database,
|
||||
User: ds.User,
|
||||
BasicAuthEnabled: ds.BasicAuth,
|
||||
BasicAuthUser: ds.BasicAuthUser,
|
||||
JSONData: jsonDataBytes,
|
||||
DecryptedSecureJSONData: decryptedValues,
|
||||
Updated: ds.Updated,
|
||||
UID: ds.Uid,
|
||||
}
|
||||
|
||||
if query.Headers == nil {
|
||||
query.Headers = make(map[string]string)
|
||||
}
|
||||
|
||||
// Attach Auth information
|
||||
if oAuthIsOAuthPassThruEnabledFunc(h.oAuthTokenService, ds) {
|
||||
if token := h.oAuthTokenService.GetCurrentOAuthToken(ctx, query.User); token != nil {
|
||||
delete(query.Headers, "Authorization")
|
||||
query.Headers["Authorization"] = fmt.Sprintf("%s %s", token.Type(), token.AccessToken)
|
||||
}
|
||||
}
|
||||
|
||||
req := &backend.QueryDataRequest{
|
||||
PluginContext: backend.PluginContext{
|
||||
OrgID: ds.OrgId,
|
||||
PluginID: ds.Type,
|
||||
User: adapters.BackendUserFromSignedInUser(query.User),
|
||||
DataSourceInstanceSettings: instanceSettings,
|
||||
},
|
||||
Queries: []backend.DataQuery{},
|
||||
Headers: query.Headers,
|
||||
}
|
||||
|
||||
for _, q := range query.Queries {
|
||||
modelJSON, err := q.Model.MarshalJSON()
|
||||
if err != nil {
|
||||
return legacydata.DataResponse{}, err
|
||||
}
|
||||
req.Queries = append(req.Queries, backend.DataQuery{
|
||||
RefID: q.RefID,
|
||||
Interval: time.Duration(q.IntervalMS) * time.Millisecond,
|
||||
MaxDataPoints: q.MaxDataPoints,
|
||||
TimeRange: backend.TimeRange{
|
||||
From: query.TimeRange.GetFromAsTimeUTC(),
|
||||
To: query.TimeRange.GetToAsTimeUTC(),
|
||||
},
|
||||
QueryType: q.QueryType,
|
||||
JSON: modelJSON,
|
||||
})
|
||||
}
|
||||
|
||||
resp, err := h.pluginsClient.QueryData(ctx, req)
|
||||
if err != nil {
|
||||
return legacydata.DataResponse{}, err
|
||||
@ -131,4 +91,84 @@ func (h *Service) HandleRequest(ctx context.Context, ds *models.DataSource, quer
|
||||
return tR, nil
|
||||
}
|
||||
|
||||
func generateRequest(ctx context.Context, ds *models.DataSource, decryptedJsonData map[string]string, query legacydata.DataQuery) (*backend.QueryDataRequest, error) {
|
||||
jsonDataBytes, err := ds.JsonData.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
instanceSettings := &backend.DataSourceInstanceSettings{
|
||||
ID: ds.Id,
|
||||
Name: ds.Name,
|
||||
URL: ds.Url,
|
||||
Database: ds.Database,
|
||||
User: ds.User,
|
||||
BasicAuthEnabled: ds.BasicAuth,
|
||||
BasicAuthUser: ds.BasicAuthUser,
|
||||
JSONData: jsonDataBytes,
|
||||
DecryptedSecureJSONData: decryptedJsonData,
|
||||
Updated: ds.Updated,
|
||||
UID: ds.Uid,
|
||||
}
|
||||
|
||||
if query.Headers == nil {
|
||||
query.Headers = make(map[string]string)
|
||||
}
|
||||
|
||||
req := &backend.QueryDataRequest{
|
||||
PluginContext: backend.PluginContext{
|
||||
OrgID: ds.OrgId,
|
||||
PluginID: ds.Type,
|
||||
User: adapters.BackendUserFromSignedInUser(query.User),
|
||||
DataSourceInstanceSettings: instanceSettings,
|
||||
},
|
||||
Queries: []backend.DataQuery{},
|
||||
Headers: query.Headers,
|
||||
}
|
||||
|
||||
// Apply Configured Custom Headers to query request.
|
||||
for k, v := range customHeaders(ds.JsonData, instanceSettings.DecryptedSecureJSONData) {
|
||||
req.Headers[k] = v
|
||||
}
|
||||
|
||||
for _, q := range query.Queries {
|
||||
modelJSON, err := q.Model.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Queries = append(req.Queries, backend.DataQuery{
|
||||
RefID: q.RefID,
|
||||
Interval: time.Duration(q.IntervalMS) * time.Millisecond,
|
||||
MaxDataPoints: q.MaxDataPoints,
|
||||
TimeRange: backend.TimeRange{
|
||||
From: query.TimeRange.GetFromAsTimeUTC(),
|
||||
To: query.TimeRange.GetToAsTimeUTC(),
|
||||
},
|
||||
QueryType: q.QueryType,
|
||||
JSON: modelJSON,
|
||||
})
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func customHeaders(jsonData *simplejson.Json, decryptedJsonData map[string]string) map[string]string {
|
||||
if jsonData == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
data := jsonData.MustMap()
|
||||
|
||||
headers := map[string]string{}
|
||||
for k := range data {
|
||||
if strings.HasPrefix(k, headerName) {
|
||||
if header, ok := data[k].(string); ok {
|
||||
valueKey := strings.ReplaceAll(k, headerName, headerValue)
|
||||
headers[header] = decryptedJsonData[valueKey]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
var _ legacydata.RequestHandler = &Service{}
|
||||
|
@ -59,6 +59,39 @@ func TestHandleRequest(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_generateRequest(t *testing.T) {
|
||||
t.Run("Should attach custom headers to request if present", func(t *testing.T) {
|
||||
jsonData := simplejson.New()
|
||||
jsonData.Set(headerName+"testOne", "x-test-one")
|
||||
jsonData.Set("testOne", "x-test-wrong")
|
||||
jsonData.Set(headerName+"testTwo", "x-test-two")
|
||||
|
||||
decryptedJsonData := map[string]string{
|
||||
headerValue + "testOne": "secret-value-one",
|
||||
headerValue + "testTwo": "secret-value-two",
|
||||
"something": "else",
|
||||
}
|
||||
|
||||
ds := &models.DataSource{Id: 12, Type: "unregisteredType", JsonData: jsonData}
|
||||
query := legacydata.DataQuery{
|
||||
TimeRange: &legacydata.DataTimeRange{},
|
||||
Queries: []legacydata.DataSubQuery{
|
||||
{RefID: "A", DataSource: &models.DataSource{Id: 1, Type: "test"}, Model: simplejson.New()},
|
||||
{RefID: "B", DataSource: &models.DataSource{Id: 1, Type: "test"}, Model: simplejson.New()},
|
||||
},
|
||||
}
|
||||
|
||||
req, err := generateRequest(context.Background(), ds, decryptedJsonData, query)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, req)
|
||||
require.EqualValues(t,
|
||||
map[string]string{
|
||||
"x-test-one": "secret-value-one",
|
||||
"x-test-two": "secret-value-two",
|
||||
}, req.Headers)
|
||||
})
|
||||
}
|
||||
|
||||
type fakePluginsClient struct {
|
||||
plugins.Client
|
||||
backend.QueryDataHandlerFunc
|
||||
|
Loading…
Reference in New Issue
Block a user