[v8.5.x] Chore: Release 8.5.14 (#56698)
* remove support for v1 (cherry picked from commit 8630a7a991af74edc4030f57d37a4bc263202fde) * Security: Make proxy endpoints not leak sensitive HTTP headers Fixes CVE-2022-31130 (cherry picked from commit 2974574a53ab6d26be7b706e76271173a91fea3a) * Security: Fix do not forward login cookie in outgoing requests (cherry picked from commit 54a32fc83b233f5910495b5fcca0b4f881221538) * Add test for username/login field conflict (cherry picked from commit7aabcf2694) * Swap order of login fields (cherry picked from commit5ec176cada) * "Release: Updated versions in package to 8.5.14" (#547) Co-authored-by: Will Browne <will.browne@grafana.com> Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com> Co-authored-by: linoman <2051016+linoman@users.noreply.github.com> Co-authored-by: Grot (@grafanabot) <43478413+grafanabot@users.noreply.github.com>
This commit is contained in:
co-authored by
Will Browne
Marcus Efraimsson
linoman
Grot
parent
811b6c06b0
commit
58b7ae14ce
+1
-1
@@ -4,5 +4,5 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "8.5.13"
|
||||
"version": "8.5.14"
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
"license": "AGPL-3.0-only",
|
||||
"private": true,
|
||||
"name": "grafana",
|
||||
"version": "8.5.13",
|
||||
"version": "8.5.14",
|
||||
"repository": "github:grafana/grafana",
|
||||
"scripts": {
|
||||
"api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/data",
|
||||
"version": "8.5.13",
|
||||
"version": "8.5.14",
|
||||
"description": "Grafana Data Library",
|
||||
"keywords": [
|
||||
"typescript"
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "6.0.0",
|
||||
"@grafana/schema": "8.5.13",
|
||||
"@grafana/schema": "8.5.14",
|
||||
"@types/d3-interpolate": "^1.4.0",
|
||||
"d3-interpolate": "1.4.0",
|
||||
"date-fns": "2.28.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/e2e-selectors",
|
||||
"version": "8.5.13",
|
||||
"version": "8.5.14",
|
||||
"description": "Grafana End-to-End Test Selectors Library",
|
||||
"keywords": [
|
||||
"cli",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/e2e",
|
||||
"version": "8.5.13",
|
||||
"version": "8.5.14",
|
||||
"description": "Grafana End-to-End Test Library",
|
||||
"keywords": [
|
||||
"cli",
|
||||
@@ -48,7 +48,7 @@
|
||||
"@babel/core": "7.17.8",
|
||||
"@babel/preset-env": "7.16.11",
|
||||
"@cypress/webpack-preprocessor": "5.11.1",
|
||||
"@grafana/e2e-selectors": "8.5.13",
|
||||
"@grafana/e2e-selectors": "8.5.14",
|
||||
"@grafana/tsconfig": "^1.2.0-rc1",
|
||||
"@mochajs/json-file-reporter": "^1.2.0",
|
||||
"babel-loader": "8.2.4",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/runtime",
|
||||
"version": "8.5.13",
|
||||
"version": "8.5.14",
|
||||
"description": "Grafana Runtime Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -22,9 +22,9 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grafana/data": "8.5.13",
|
||||
"@grafana/e2e-selectors": "8.5.13",
|
||||
"@grafana/ui": "8.5.13",
|
||||
"@grafana/data": "8.5.14",
|
||||
"@grafana/e2e-selectors": "8.5.14",
|
||||
"@grafana/ui": "8.5.14",
|
||||
"@sentry/browser": "6.19.1",
|
||||
"history": "4.10.1",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/schema",
|
||||
"version": "8.5.13",
|
||||
"version": "8.5.14",
|
||||
"description": "Grafana Schema Library",
|
||||
"keywords": [
|
||||
"typescript"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/toolkit",
|
||||
"version": "8.5.13",
|
||||
"version": "8.5.14",
|
||||
"description": "Grafana Toolkit",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -28,10 +28,10 @@
|
||||
"dependencies": {
|
||||
"@babel/core": "7.13.14",
|
||||
"@babel/preset-env": "7.13.12",
|
||||
"@grafana/data": "8.5.13",
|
||||
"@grafana/data": "8.5.14",
|
||||
"@grafana/eslint-config": "2.5.2",
|
||||
"@grafana/tsconfig": "^1.2.0-rc1",
|
||||
"@grafana/ui": "8.5.13",
|
||||
"@grafana/ui": "8.5.14",
|
||||
"@jest/core": "26.6.3",
|
||||
"@rushstack/eslint-patch": "1.0.6",
|
||||
"@types/command-exists": "^1.2.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/ui",
|
||||
"version": "8.5.13",
|
||||
"version": "8.5.14",
|
||||
"description": "Grafana Components Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -33,9 +33,9 @@
|
||||
"@emotion/css": "11.7.1",
|
||||
"@emotion/react": "11.8.2",
|
||||
"@grafana/aws-sdk": "0.0.35",
|
||||
"@grafana/data": "8.5.13",
|
||||
"@grafana/e2e-selectors": "8.5.13",
|
||||
"@grafana/schema": "8.5.13",
|
||||
"@grafana/data": "8.5.14",
|
||||
"@grafana/e2e-selectors": "8.5.14",
|
||||
"@grafana/schema": "8.5.14",
|
||||
"@monaco-editor/react": "4.3.1",
|
||||
"@popperjs/core": "2.11.4",
|
||||
"@react-aria/button": "3.4.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jaegertracing/jaeger-ui-components",
|
||||
"version": "8.5.13",
|
||||
"version": "8.5.14",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -26,8 +26,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.7.1",
|
||||
"@grafana/data": "8.5.13",
|
||||
"@grafana/ui": "8.5.13",
|
||||
"@grafana/data": "8.5.14",
|
||||
"@grafana/ui": "8.5.14",
|
||||
"chance": "^1.0.10",
|
||||
"classnames": "^2.2.5",
|
||||
"combokeys": "^3.0.0",
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/web/webtest"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
@@ -498,7 +499,7 @@ func TestAPIEndpoint_Metrics_ParseDashboardQueryParams(t *testing.T) {
|
||||
// `/ds/query` endpoint test
|
||||
func TestAPIEndpoint_Metrics_QueryMetricsV2(t *testing.T) {
|
||||
qds := query.ProvideService(
|
||||
nil,
|
||||
setting.NewCfg(),
|
||||
nil,
|
||||
nil,
|
||||
&fakePluginRequestValidator{},
|
||||
|
||||
@@ -214,7 +214,7 @@ func (proxy *DataSourceProxy) director(req *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
proxyutil.ClearCookieHeader(req, keepCookieNames)
|
||||
proxyutil.ClearCookieHeader(req, keepCookieNames, []string{proxy.cfg.LoginCookieName})
|
||||
req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion))
|
||||
|
||||
jsonData := make(map[string]interface{})
|
||||
|
||||
+10
-1
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/installer"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsettings"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
@@ -546,7 +547,15 @@ func (hs *HTTPServer) makePluginResourceRequest(w http.ResponseWriter, req *http
|
||||
hs.log.Warn("failed to to unpack JSONData in datasource instance settings", "err", err)
|
||||
}
|
||||
}
|
||||
proxyutil.ClearCookieHeader(req, keepCookieModel.KeepCookies)
|
||||
|
||||
list := contexthandler.AuthHTTPHeaderListFromContext(req.Context())
|
||||
if list != nil {
|
||||
for _, name := range list.Items {
|
||||
req.Header.Del(name)
|
||||
}
|
||||
}
|
||||
|
||||
proxyutil.ClearCookieHeader(req, keepCookieModel.KeepCookies, []string{hs.Cfg.LoginCookieName})
|
||||
proxyutil.PrepareProxyRequest(req)
|
||||
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/web/webtest"
|
||||
)
|
||||
@@ -275,6 +276,12 @@ func TestMakePluginResourceRequest(t *testing.T) {
|
||||
pluginClient: pluginClient,
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
|
||||
const customHeader = "X-CUSTOM"
|
||||
req.Header.Set(customHeader, "val")
|
||||
ctx := contexthandler.WithAuthHTTPHeader(req.Context(), customHeader)
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
pCtx := backend.PluginContext{}
|
||||
err := hs.makePluginResourceRequest(resp, req, pCtx)
|
||||
@@ -287,6 +294,7 @@ func TestMakePluginResourceRequest(t *testing.T) {
|
||||
}
|
||||
|
||||
require.Equal(t, "sandbox", resp.Header().Get("Content-Security-Policy"))
|
||||
require.Empty(t, req.Header.Get(customHeader))
|
||||
}
|
||||
|
||||
func callGetPluginAsset(sc *scenarioContext) {
|
||||
|
||||
@@ -36,6 +36,9 @@ func TestMiddlewareBasicAuth(t *testing.T) {
|
||||
assert.True(t, sc.context.IsSignedIn)
|
||||
assert.Equal(t, orgID, sc.context.OrgId)
|
||||
assert.Equal(t, models.ROLE_EDITOR, sc.context.OrgRole)
|
||||
list := contexthandler.AuthHTTPHeaderListFromContext(sc.context.Req.Context())
|
||||
require.NotNil(t, list)
|
||||
require.EqualValues(t, []string{"Authorization"}, list.Items)
|
||||
}, configure)
|
||||
|
||||
middlewareScenario(t, "Handle auth", func(t *testing.T, sc *scenarioContext) {
|
||||
@@ -69,6 +72,9 @@ func TestMiddlewareBasicAuth(t *testing.T) {
|
||||
|
||||
assert.True(t, sc.context.IsSignedIn)
|
||||
assert.Equal(t, id, sc.context.UserId)
|
||||
list := contexthandler.AuthHTTPHeaderListFromContext(sc.context.Req.Context())
|
||||
require.NotNil(t, list)
|
||||
require.EqualValues(t, []string{"Authorization"}, list.Items)
|
||||
}, configure)
|
||||
|
||||
middlewareScenario(t, "Should return error if user is not found", func(t *testing.T, sc *scenarioContext) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler"
|
||||
@@ -54,6 +55,9 @@ func TestMiddlewareJWTAuth(t *testing.T) {
|
||||
assert.Equal(t, orgID, sc.context.OrgId)
|
||||
assert.Equal(t, id, sc.context.UserId)
|
||||
assert.Equal(t, myUsername, sc.context.Login)
|
||||
list := contexthandler.AuthHTTPHeaderListFromContext(sc.context.Req.Context())
|
||||
require.NotNil(t, list)
|
||||
require.EqualValues(t, []string{sc.cfg.JWTAuthHeaderName}, list.Items)
|
||||
}, configure, configureUsernameClaim)
|
||||
|
||||
middlewareScenario(t, "Valid token with valid email claim", func(t *testing.T, sc *scenarioContext) {
|
||||
|
||||
@@ -395,6 +395,11 @@ func TestMiddlewareContext(t *testing.T) {
|
||||
assert.True(t, sc.context.IsSignedIn)
|
||||
assert.Equal(t, userID, sc.context.UserId)
|
||||
assert.Equal(t, orgID, sc.context.OrgId)
|
||||
list := contexthandler.AuthHTTPHeaderListFromContext(sc.context.Req.Context())
|
||||
require.NotNil(t, list)
|
||||
require.Contains(t, list.Items, sc.cfg.AuthProxyHeaderName)
|
||||
require.Contains(t, list.Items, "X-WEBAUTH-GROUPS")
|
||||
require.Contains(t, list.Items, "X-WEBAUTH-ROLE")
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
cfg.LDAPEnabled = false
|
||||
|
||||
@@ -312,7 +312,7 @@ func TestLoader_Load(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Load an unsigned plugin with modified signature (production)",
|
||||
name: "Load a plugin with v1 manifest should return signatureInvalid",
|
||||
class: plugins.External,
|
||||
cfg: &plugins.Cfg{
|
||||
PluginsPath: filepath.Join(parentDir),
|
||||
@@ -322,12 +322,12 @@ func TestLoader_Load(t *testing.T) {
|
||||
pluginErrors: map[string]*plugins.Error{
|
||||
"test": {
|
||||
PluginID: "test",
|
||||
ErrorCode: "signatureModified",
|
||||
ErrorCode: "signatureInvalid",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Load an unsigned plugin with modified signature using PluginsAllowUnsigned config (production) still includes a signing error",
|
||||
name: "Load a plugin with v1 manifest using PluginsAllowUnsigned config should return signatureInvalid",
|
||||
class: plugins.External,
|
||||
cfg: &plugins.Cfg{
|
||||
PluginsPath: filepath.Join(parentDir),
|
||||
@@ -338,7 +338,7 @@ func TestLoader_Load(t *testing.T) {
|
||||
pluginErrors: map[string]*plugins.Error{
|
||||
"test": {
|
||||
PluginID: "test",
|
||||
ErrorCode: "signatureModified",
|
||||
ErrorCode: "signatureInvalid",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -130,6 +130,12 @@ func Calculate(mlog log.Logger, plugin *plugins.Plugin) (plugins.Signature, erro
|
||||
}, nil
|
||||
}
|
||||
|
||||
if !manifest.isV2() {
|
||||
return plugins.Signature{
|
||||
Status: plugins.SignatureInvalid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Make sure the versions all match
|
||||
if manifest.Plugin != plugin.ID || manifest.Version != plugin.Info.Version {
|
||||
return plugins.Signature{
|
||||
@@ -165,21 +171,19 @@ func Calculate(mlog log.Logger, plugin *plugins.Plugin) (plugins.Signature, erro
|
||||
manifestFiles[p] = struct{}{}
|
||||
}
|
||||
|
||||
if manifest.isV2() {
|
||||
// Track files missing from the manifest
|
||||
var unsignedFiles []string
|
||||
for _, f := range pluginFiles {
|
||||
if _, exists := manifestFiles[f]; !exists {
|
||||
unsignedFiles = append(unsignedFiles, f)
|
||||
}
|
||||
// Track files missing from the manifest
|
||||
var unsignedFiles []string
|
||||
for _, f := range pluginFiles {
|
||||
if _, exists := manifestFiles[f]; !exists {
|
||||
unsignedFiles = append(unsignedFiles, f)
|
||||
}
|
||||
}
|
||||
|
||||
if len(unsignedFiles) > 0 {
|
||||
mlog.Warn("The following files were not included in the signature", "plugin", plugin.ID, "files", unsignedFiles)
|
||||
return plugins.Signature{
|
||||
Status: plugins.SignatureModified,
|
||||
}, nil
|
||||
}
|
||||
if len(unsignedFiles) > 0 {
|
||||
mlog.Warn("The following files were not included in the signature", "plugin", plugin.ID, "files", unsignedFiles)
|
||||
return plugins.Signature{
|
||||
Status: plugins.SignatureModified,
|
||||
}, nil
|
||||
}
|
||||
|
||||
mlog.Debug("Plugin signature valid", "id", plugin.ID)
|
||||
|
||||
@@ -93,6 +93,9 @@ func (h *ContextHandler) initContextWithJWT(ctx *models.ReqContext, orgId int64)
|
||||
return true
|
||||
}
|
||||
|
||||
newCtx := WithAuthHTTPHeader(ctx.Req.Context(), h.Cfg.JWTAuthHeaderName)
|
||||
*ctx.Req = *ctx.Req.WithContext(newCtx)
|
||||
|
||||
ctx.SignedInUser = query.Result
|
||||
ctx.IsSignedIn = true
|
||||
|
||||
|
||||
@@ -211,6 +211,9 @@ func (h *ContextHandler) initContextWithAPIKey(reqContext *models.ReqContext) bo
|
||||
return true
|
||||
}
|
||||
|
||||
ctx := WithAuthHTTPHeader(reqContext.Req.Context(), "Authorization")
|
||||
*reqContext.Req = *reqContext.Req.WithContext(ctx)
|
||||
|
||||
// fetch key
|
||||
keyQuery := models.GetApiKeyByNameQuery{KeyName: decoded.Name, OrgId: decoded.OrgId}
|
||||
if err := h.SQLStore.GetApiKeyByName(reqContext.Req.Context(), &keyQuery); err != nil {
|
||||
@@ -287,7 +290,7 @@ func (h *ContextHandler) initContextWithBasicAuth(reqContext *models.ReqContext,
|
||||
return false
|
||||
}
|
||||
|
||||
ctx, span := h.tracer.Start(reqContext.Req.Context(), "initContextWithBasicAuth")
|
||||
_, span := h.tracer.Start(reqContext.Req.Context(), "initContextWithBasicAuth")
|
||||
defer span.End()
|
||||
|
||||
username, password, err := util.DecodeBasicAuthHeader(header)
|
||||
@@ -296,12 +299,15 @@ func (h *ContextHandler) initContextWithBasicAuth(reqContext *models.ReqContext,
|
||||
return true
|
||||
}
|
||||
|
||||
ctx := WithAuthHTTPHeader(reqContext.Req.Context(), "Authorization")
|
||||
*reqContext.Req = *reqContext.Req.WithContext(ctx)
|
||||
|
||||
authQuery := models.LoginUserQuery{
|
||||
Username: username,
|
||||
Password: password,
|
||||
Cfg: h.Cfg,
|
||||
}
|
||||
if err := h.authenticator.AuthenticateUser(reqContext.Req.Context(), &authQuery); err != nil {
|
||||
if err := h.authenticator.AuthenticateUser(ctx, &authQuery); err != nil {
|
||||
reqContext.Logger.Debug(
|
||||
"Failed to authorize the user",
|
||||
"username", username,
|
||||
@@ -523,6 +529,15 @@ func (h *ContextHandler) initContextWithAuthProxy(reqContext *models.ReqContext,
|
||||
|
||||
logger.Debug("Successfully got user info", "userID", user.UserId, "username", user.Login)
|
||||
|
||||
ctx := WithAuthHTTPHeader(reqContext.Req.Context(), h.Cfg.AuthProxyHeaderName)
|
||||
for _, header := range h.Cfg.AuthProxyHeaders {
|
||||
if header != "" {
|
||||
ctx = WithAuthHTTPHeader(ctx, header)
|
||||
}
|
||||
}
|
||||
|
||||
*reqContext.Req = *reqContext.Req.WithContext(ctx)
|
||||
|
||||
// Add user info to context
|
||||
reqContext.SignedInUser = user
|
||||
reqContext.IsSignedIn = true
|
||||
@@ -542,3 +557,38 @@ func (h *ContextHandler) initContextWithAuthProxy(reqContext *models.ReqContext,
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type authHTTPHeaderListContextKey struct{}
|
||||
|
||||
var authHTTPHeaderListKey = authHTTPHeaderListContextKey{}
|
||||
|
||||
// AuthHTTPHeaderList used to record HTTP headers that being when verifying authentication
|
||||
// of an incoming HTTP request.
|
||||
type AuthHTTPHeaderList struct {
|
||||
Items []string
|
||||
}
|
||||
|
||||
// WithAuthHTTPHeader returns a copy of parent in which the named HTTP header will be included
|
||||
// and later retrievable by AuthHTTPHeaderListFromContext.
|
||||
func WithAuthHTTPHeader(parent context.Context, name string) context.Context {
|
||||
list := AuthHTTPHeaderListFromContext(parent)
|
||||
|
||||
if list == nil {
|
||||
list = &AuthHTTPHeaderList{
|
||||
Items: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
list.Items = append(list.Items, name)
|
||||
|
||||
return context.WithValue(parent, authHTTPHeaderListKey, list)
|
||||
}
|
||||
|
||||
// AuthHTTPHeaderListFromContext returns the AuthHTTPHeaderList in a context.Context, if any,
|
||||
// and will include any HTTP headers used when verifying authentication of an incoming HTTP request.
|
||||
func AuthHTTPHeaderListFromContext(c context.Context) *AuthHTTPHeaderList {
|
||||
if list, ok := c.Value(authHTTPHeaderListKey).(*AuthHTTPHeaderList); ok {
|
||||
return list
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
|
||||
@@ -295,20 +295,25 @@ func (ss *SQLStore) GetUserByLogin(ctx context.Context, query *models.GetUserByL
|
||||
return models.ErrUserNotFound
|
||||
}
|
||||
|
||||
// Try and find the user by login first.
|
||||
// It's not sufficient to assume that a LoginOrEmail with an "@" is an email.
|
||||
var has bool
|
||||
var err error
|
||||
user := &models.User{Login: query.LoginOrEmail}
|
||||
has, err := sess.Where(notServiceAccountFilter(ss)).Get(user)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !has && strings.Contains(query.LoginOrEmail, "@") {
|
||||
// If the user wasn't found, and it contains an "@" fallback to finding the
|
||||
// user by email.
|
||||
// Since username can be an email address, attempt login with email address
|
||||
// first if the login field has the "@" symbol.
|
||||
if strings.Contains(query.LoginOrEmail, "@") {
|
||||
user = &models.User{Email: query.LoginOrEmail}
|
||||
has, err = sess.Get(user)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup the login field instead of email field
|
||||
if !has {
|
||||
user = &models.User{Login: query.LoginOrEmail}
|
||||
has, err = sess.Where(notServiceAccountFilter(ss)).Get(user)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -47,6 +47,45 @@ func TestUserDataAccess(t *testing.T) {
|
||||
require.False(t, query.Result.IsDisabled)
|
||||
})
|
||||
|
||||
t.Run("Get User by login - user_2 uses user_1.email as login", func(t *testing.T) {
|
||||
ss = InitTestDB(t)
|
||||
|
||||
// create user_1
|
||||
cmd := models.CreateUserCommand{
|
||||
Email: "user_1@mail.com",
|
||||
Name: "user_1",
|
||||
Login: "user_1",
|
||||
Password: "user_1_password",
|
||||
IsDisabled: true,
|
||||
}
|
||||
user_1, err := ss.CreateUser(context.Background(), cmd)
|
||||
require.Nil(t, err)
|
||||
|
||||
// create user_2
|
||||
cmd = models.CreateUserCommand{
|
||||
Email: "user_2@mail.com",
|
||||
Name: "user_2",
|
||||
Login: "user_1@mail.com",
|
||||
Password: "user_2_password",
|
||||
IsDisabled: true,
|
||||
}
|
||||
user_2, err := ss.CreateUser(context.Background(), cmd)
|
||||
require.Nil(t, err)
|
||||
|
||||
// query user database for user_1 email
|
||||
query := models.GetUserByLoginQuery{LoginOrEmail: "user_1@mail.com"}
|
||||
err = ss.GetUserByLogin(context.Background(), &query)
|
||||
require.Nil(t, err)
|
||||
|
||||
// expect user_1 as result
|
||||
require.Equal(t, user_1.Email, query.Result.Email)
|
||||
require.Equal(t, user_1.Login, query.Result.Login)
|
||||
require.Equal(t, user_1.Name, query.Result.Name)
|
||||
require.NotEqual(t, user_2.Email, query.Result.Email)
|
||||
require.NotEqual(t, user_2.Login, query.Result.Login)
|
||||
require.NotEqual(t, user_2.Name, query.Result.Name)
|
||||
})
|
||||
|
||||
t.Run("Testing DB - creates and loads disabled user", func(t *testing.T) {
|
||||
ss = InitTestDB(t)
|
||||
cmd := models.CreateUserCommand{
|
||||
|
||||
@@ -3,6 +3,7 @@ package proxyutil
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// PrepareProxyRequest prepares a request for being proxied.
|
||||
@@ -26,19 +27,31 @@ func PrepareProxyRequest(req *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// ClearCookieHeader clear cookie header, except for cookies specified to be kept.
|
||||
func ClearCookieHeader(req *http.Request, keepCookiesNames []string) {
|
||||
var keepCookies []*http.Cookie
|
||||
// ClearCookieHeader clear cookie header, except for cookies specified to be kept (keepCookiesNames) if not in skipCookiesNames.
|
||||
func ClearCookieHeader(req *http.Request, keepCookiesNames []string, skipCookiesNames []string) {
|
||||
keepCookies := map[string]*http.Cookie{}
|
||||
for _, c := range req.Cookies() {
|
||||
for _, v := range keepCookiesNames {
|
||||
if c.Name == v {
|
||||
keepCookies = append(keepCookies, c)
|
||||
keepCookies[c.Name] = c
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range skipCookiesNames {
|
||||
delete(keepCookies, v)
|
||||
}
|
||||
|
||||
req.Header.Del("Cookie")
|
||||
for _, c := range keepCookies {
|
||||
|
||||
sortedCookies := []string{}
|
||||
for name := range keepCookies {
|
||||
sortedCookies = append(sortedCookies, name)
|
||||
}
|
||||
sort.Strings(sortedCookies)
|
||||
|
||||
for _, name := range sortedCookies {
|
||||
c := keepCookies[name]
|
||||
req.AddCookie(c)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ func TestClearCookieHeader(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
req.AddCookie(&http.Cookie{Name: "cookie"})
|
||||
|
||||
ClearCookieHeader(req, nil)
|
||||
ClearCookieHeader(req, nil, nil)
|
||||
require.NotContains(t, req.Header, "Cookie")
|
||||
})
|
||||
|
||||
@@ -60,8 +60,20 @@ func TestClearCookieHeader(t *testing.T) {
|
||||
req.AddCookie(&http.Cookie{Name: "cookie2"})
|
||||
req.AddCookie(&http.Cookie{Name: "cookie3"})
|
||||
|
||||
ClearCookieHeader(req, []string{"cookie1", "cookie3"})
|
||||
ClearCookieHeader(req, []string{"cookie1", "cookie3"}, nil)
|
||||
require.Contains(t, req.Header, "Cookie")
|
||||
require.Equal(t, "cookie1=; cookie3=", req.Header.Get("Cookie"))
|
||||
})
|
||||
|
||||
t.Run("Clear cookie header with cookies to keep and skip should clear Cookie header and keep cookies", func(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet, "/", nil)
|
||||
require.NoError(t, err)
|
||||
req.AddCookie(&http.Cookie{Name: "cookie1"})
|
||||
req.AddCookie(&http.Cookie{Name: "cookie2"})
|
||||
req.AddCookie(&http.Cookie{Name: "cookie3"})
|
||||
|
||||
ClearCookieHeader(req, []string{"cookie1", "cookie3"}, []string{"cookie3"})
|
||||
require.Contains(t, req.Header, "Cookie")
|
||||
require.Equal(t, "cookie1=", req.Header.Get("Cookie"))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
glog "github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler"
|
||||
)
|
||||
|
||||
// StatusClientClosedRequest A non-standard status code introduced by nginx
|
||||
@@ -66,6 +67,13 @@ func NewReverseProxy(logger glog.Logger, director func(*http.Request), opts ...R
|
||||
// wrapDirector wraps a director and adds additional functionality.
|
||||
func wrapDirector(d func(*http.Request)) func(req *http.Request) {
|
||||
return func(req *http.Request) {
|
||||
list := contexthandler.AuthHTTPHeaderListFromContext(req.Context())
|
||||
if list != nil {
|
||||
for _, name := range list.Items {
|
||||
req.Header.Del(name)
|
||||
}
|
||||
}
|
||||
|
||||
d(req)
|
||||
PrepareProxyRequest(req)
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -30,6 +31,11 @@ func TestReverseProxy(t *testing.T) {
|
||||
req.Header.Set("Referer", "https://test.com/api")
|
||||
req.RemoteAddr = "10.0.0.1"
|
||||
|
||||
const customHeader = "X-CUSTOM"
|
||||
req.Header.Set(customHeader, "val")
|
||||
ctx := contexthandler.WithAuthHTTPHeader(req.Context(), customHeader)
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
rp := NewReverseProxy(log.New("test"), func(req *http.Request) {
|
||||
req.Header.Set("X-KEY", "value")
|
||||
})
|
||||
@@ -49,6 +55,7 @@ func TestReverseProxy(t *testing.T) {
|
||||
require.Empty(t, resp.Cookies())
|
||||
require.Equal(t, "sandbox", resp.Header.Get("Content-Security-Policy"))
|
||||
require.NoError(t, resp.Body.Close())
|
||||
require.Empty(t, actualReq.Header.Get(customHeader))
|
||||
})
|
||||
|
||||
t.Run("When proxying a request using WithModifyResponse should call it before default ModifyResponse func", func(t *testing.T) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@grafana-plugins/input-datasource",
|
||||
"version": "8.5.13",
|
||||
"version": "8.5.14",
|
||||
"description": "Input Datasource",
|
||||
"private": true,
|
||||
"repository": {
|
||||
@@ -24,9 +24,9 @@
|
||||
"webpack": "5.58.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grafana/data": "8.5.13",
|
||||
"@grafana/toolkit": "8.5.13",
|
||||
"@grafana/ui": "8.5.13",
|
||||
"@grafana/data": "8.5.14",
|
||||
"@grafana/toolkit": "8.5.14",
|
||||
"@grafana/ui": "8.5.14",
|
||||
"jquery": "3.5.1",
|
||||
"react": "17.0.1",
|
||||
"react-dom": "17.0.1",
|
||||
|
||||
@@ -3949,9 +3949,9 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana-plugins/input-datasource@workspace:plugins-bundled/internal/input-datasource"
|
||||
dependencies:
|
||||
"@grafana/data": 8.5.13
|
||||
"@grafana/toolkit": 8.5.13
|
||||
"@grafana/ui": 8.5.13
|
||||
"@grafana/data": 8.5.14
|
||||
"@grafana/toolkit": 8.5.14
|
||||
"@grafana/ui": 8.5.14
|
||||
"@types/jest": 26.0.15
|
||||
"@types/lodash": 4.14.149
|
||||
"@types/react": 17.0.30
|
||||
@@ -3992,12 +3992,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/data@8.5.13, @grafana/data@workspace:*, @grafana/data@workspace:packages/grafana-data":
|
||||
"@grafana/data@8.5.14, @grafana/data@workspace:*, @grafana/data@workspace:packages/grafana-data":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/data@workspace:packages/grafana-data"
|
||||
dependencies:
|
||||
"@braintree/sanitize-url": 6.0.0
|
||||
"@grafana/schema": 8.5.13
|
||||
"@grafana/schema": 8.5.14
|
||||
"@grafana/tsconfig": ^1.2.0-rc1
|
||||
"@rollup/plugin-commonjs": 21.0.2
|
||||
"@rollup/plugin-json": 4.1.0
|
||||
@@ -4050,7 +4050,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/e2e-selectors@8.5.13, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors":
|
||||
"@grafana/e2e-selectors@8.5.14, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors"
|
||||
dependencies:
|
||||
@@ -4074,7 +4074,7 @@ __metadata:
|
||||
"@babel/core": 7.17.8
|
||||
"@babel/preset-env": 7.16.11
|
||||
"@cypress/webpack-preprocessor": 5.11.1
|
||||
"@grafana/e2e-selectors": 8.5.13
|
||||
"@grafana/e2e-selectors": 8.5.14
|
||||
"@grafana/tsconfig": ^1.2.0-rc1
|
||||
"@mochajs/json-file-reporter": ^1.2.0
|
||||
"@rollup/plugin-commonjs": 21.0.2
|
||||
@@ -4181,10 +4181,10 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/runtime@workspace:packages/grafana-runtime"
|
||||
dependencies:
|
||||
"@grafana/data": 8.5.13
|
||||
"@grafana/e2e-selectors": 8.5.13
|
||||
"@grafana/data": 8.5.14
|
||||
"@grafana/e2e-selectors": 8.5.14
|
||||
"@grafana/tsconfig": ^1.2.0-rc1
|
||||
"@grafana/ui": 8.5.13
|
||||
"@grafana/ui": 8.5.14
|
||||
"@rollup/plugin-commonjs": 21.0.2
|
||||
"@rollup/plugin-node-resolve": 13.1.3
|
||||
"@sentry/browser": 6.19.1
|
||||
@@ -4213,7 +4213,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/schema@8.5.13, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema":
|
||||
"@grafana/schema@8.5.14, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/schema@workspace:packages/grafana-schema"
|
||||
dependencies:
|
||||
@@ -4231,16 +4231,16 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/toolkit@8.5.13, @grafana/toolkit@workspace:*, @grafana/toolkit@workspace:packages/grafana-toolkit":
|
||||
"@grafana/toolkit@8.5.14, @grafana/toolkit@workspace:*, @grafana/toolkit@workspace:packages/grafana-toolkit":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/toolkit@workspace:packages/grafana-toolkit"
|
||||
dependencies:
|
||||
"@babel/core": 7.13.14
|
||||
"@babel/preset-env": 7.13.12
|
||||
"@grafana/data": 8.5.13
|
||||
"@grafana/data": 8.5.14
|
||||
"@grafana/eslint-config": 2.5.2
|
||||
"@grafana/tsconfig": ^1.2.0-rc1
|
||||
"@grafana/ui": 8.5.13
|
||||
"@grafana/ui": 8.5.14
|
||||
"@jest/core": 26.6.3
|
||||
"@rushstack/eslint-patch": 1.0.6
|
||||
"@types/command-exists": ^1.2.0
|
||||
@@ -4324,7 +4324,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/ui@8.5.13, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui":
|
||||
"@grafana/ui@8.5.14, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/ui@workspace:packages/grafana-ui"
|
||||
dependencies:
|
||||
@@ -4332,9 +4332,9 @@ __metadata:
|
||||
"@emotion/css": 11.7.1
|
||||
"@emotion/react": 11.8.2
|
||||
"@grafana/aws-sdk": 0.0.35
|
||||
"@grafana/data": 8.5.13
|
||||
"@grafana/e2e-selectors": 8.5.13
|
||||
"@grafana/schema": 8.5.13
|
||||
"@grafana/data": 8.5.14
|
||||
"@grafana/e2e-selectors": 8.5.14
|
||||
"@grafana/schema": 8.5.14
|
||||
"@grafana/tsconfig": ^1.2.0-rc1
|
||||
"@mdx-js/react": 1.6.22
|
||||
"@monaco-editor/react": 4.3.1
|
||||
@@ -4570,9 +4570,9 @@ __metadata:
|
||||
resolution: "@jaegertracing/jaeger-ui-components@workspace:packages/jaeger-ui-components"
|
||||
dependencies:
|
||||
"@emotion/css": 11.7.1
|
||||
"@grafana/data": 8.5.13
|
||||
"@grafana/data": 8.5.14
|
||||
"@grafana/tsconfig": ^1.2.0-rc1
|
||||
"@grafana/ui": 8.5.13
|
||||
"@grafana/ui": 8.5.14
|
||||
"@types/classnames": ^2.2.7
|
||||
"@types/deep-freeze": ^0.1.1
|
||||
"@types/hoist-non-react-statics": ^3.3.1
|
||||
|
||||
Reference in New Issue
Block a user