Compare commits

...

3 Commits

Author SHA1 Message Date
Grot (@grafanabot)
c7eea48209 "Release: Updated versions in package to 9.2.0" (#570) 2022-10-11 12:11:15 +02:00
Will Browne
4539c33fce Plugin fixes (#562)
* Plugins: Remove support for V1 manifests

* Plugins: Make proxy endpoints not leak sensitive HTTP headers

* Security: Fix do not forward login cookie in outgoing requests
2022-10-11 12:08:56 +02:00
linoman
1d58ef43fb Swap order of login fields (#511) (#560)
* Swap order of login fields

* Add test for username/login field conflict

(cherry picked from commit bcee2c47fd11dc716b227ee759aeeac0db20454d)
(cherry picked from commit 825ab1ab1cf1c47cc4ed1890b35b8abab8a0ab14)
2022-10-11 12:08:46 +02:00
34 changed files with 286 additions and 104 deletions

View File

@@ -4,5 +4,5 @@
"packages": [
"packages/*"
],
"version": "9.2.0-beta.1"
"version": "9.2.0"
}

View File

@@ -3,7 +3,7 @@
"license": "AGPL-3.0-only",
"private": true,
"name": "grafana",
"version": "9.2.0-beta.1",
"version": "9.2.0",
"repository": "github:grafana/grafana",
"scripts": {
"api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/data",
"version": "9.2.0-beta.1",
"version": "9.2.0",
"description": "Grafana Data Library",
"keywords": [
"typescript"
@@ -34,7 +34,7 @@
},
"dependencies": {
"@braintree/sanitize-url": "6.0.0",
"@grafana/schema": "9.2.0-beta.1",
"@grafana/schema": "9.2.0",
"@types/d3-interpolate": "^1.4.0",
"d3-interpolate": "1.4.0",
"date-fns": "2.29.1",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/e2e-selectors",
"version": "9.2.0-beta.1",
"version": "9.2.0",
"description": "Grafana End-to-End Test Selectors Library",
"keywords": [
"cli",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/e2e",
"version": "9.2.0-beta.1",
"version": "9.2.0",
"description": "Grafana End-to-End Test Library",
"keywords": [
"cli",
@@ -61,7 +61,7 @@
"@babel/core": "7.19.0",
"@babel/preset-env": "7.19.0",
"@cypress/webpack-preprocessor": "5.12.0",
"@grafana/e2e-selectors": "9.2.0-beta.1",
"@grafana/e2e-selectors": "9.2.0",
"@grafana/tsconfig": "^1.2.0-rc1",
"@mochajs/json-file-reporter": "^1.2.0",
"babel-loader": "8.2.5",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/runtime",
"version": "9.2.0-beta.1",
"version": "9.2.0",
"description": "Grafana Runtime Library",
"keywords": [
"grafana",
@@ -36,9 +36,9 @@
},
"dependencies": {
"@grafana/agent-web": "^0.4.0",
"@grafana/data": "9.2.0-beta.1",
"@grafana/e2e-selectors": "9.2.0-beta.1",
"@grafana/ui": "9.2.0-beta.1",
"@grafana/data": "9.2.0",
"@grafana/e2e-selectors": "9.2.0",
"@grafana/ui": "9.2.0",
"@sentry/browser": "6.19.7",
"history": "4.10.1",
"lodash": "4.17.21",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/schema",
"version": "9.2.0-beta.1",
"version": "9.2.0",
"description": "Grafana Schema Library",
"keywords": [
"typescript"

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/toolkit",
"version": "9.2.0-beta.1",
"version": "9.2.0",
"description": "Grafana Toolkit",
"keywords": [
"grafana",
@@ -50,10 +50,10 @@
"@babel/preset-env": "7.18.9",
"@babel/preset-react": "7.18.6",
"@babel/preset-typescript": "7.18.6",
"@grafana/data": "9.2.0-beta.1",
"@grafana/data": "9.2.0",
"@grafana/eslint-config": "5.0.0",
"@grafana/tsconfig": "^1.2.0-rc1",
"@grafana/ui": "9.2.0-beta.1",
"@grafana/ui": "9.2.0",
"@jest/core": "27.5.1",
"@types/command-exists": "^1.2.0",
"@types/eslint": "8.4.1",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/ui",
"version": "9.2.0-beta.1",
"version": "9.2.0",
"description": "Grafana Components Library",
"keywords": [
"grafana",
@@ -47,9 +47,9 @@
"dependencies": {
"@emotion/css": "11.9.0",
"@emotion/react": "11.9.3",
"@grafana/data": "9.2.0-beta.1",
"@grafana/e2e-selectors": "9.2.0-beta.1",
"@grafana/schema": "9.2.0-beta.1",
"@grafana/data": "9.2.0",
"@grafana/e2e-selectors": "9.2.0",
"@grafana/schema": "9.2.0",
"@monaco-editor/react": "4.4.5",
"@popperjs/core": "2.11.5",
"@react-aria/button": "3.6.1",

View File

@@ -1,6 +1,6 @@
{
"name": "@jaegertracing/jaeger-ui-components",
"version": "9.2.0-beta.1",
"version": "9.2.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,10 +31,10 @@
},
"dependencies": {
"@emotion/css": "11.9.0",
"@grafana/data": "9.2.0-beta.1",
"@grafana/e2e-selectors": "9.2.0-beta.1",
"@grafana/runtime": "9.2.0-beta.1",
"@grafana/ui": "9.2.0-beta.1",
"@grafana/data": "9.2.0",
"@grafana/e2e-selectors": "9.2.0",
"@grafana/runtime": "9.2.0",
"@grafana/ui": "9.2.0",
"chance": "^1.0.10",
"classnames": "^2.2.5",
"combokeys": "^3.0.0",

View File

@@ -827,7 +827,7 @@ func (hs *HTTPServer) checkDatasourceHealth(c *models.ReqContext, ds *datasource
}
}
proxyutil.ClearCookieHeader(c.Req, ds.AllowedCookies())
proxyutil.ClearCookieHeader(c.Req, ds.AllowedCookies(), []string{hs.Cfg.LoginCookieName})
if cookieStr := c.Req.Header.Get("Cookie"); cookieStr != "" {
req.Headers["Cookie"] = cookieStr
}

View File

@@ -26,6 +26,7 @@ import (
"github.com/grafana/grafana/pkg/services/query"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util/errutil"
"github.com/grafana/grafana/pkg/web/webtest"
)
@@ -59,7 +60,7 @@ func (ts *fakeOAuthTokenService) IsOAuthPassThruEnabled(*datasources.DataSource)
// `/ds/query` endpoint test
func TestAPIEndpoint_Metrics_QueryMetricsV2(t *testing.T) {
qds := query.ProvideService(
nil,
setting.NewCfg(),
nil,
nil,
&fakePluginRequestValidator{},
@@ -108,7 +109,7 @@ func TestAPIEndpoint_Metrics_QueryMetricsV2(t *testing.T) {
func TestAPIEndpoint_Metrics_PluginDecryptionFailure(t *testing.T) {
qds := query.ProvideService(
nil,
setting.NewCfg(),
nil,
nil,
&fakePluginRequestValidator{},
@@ -271,7 +272,7 @@ func TestDataSourceQueryError(t *testing.T) {
err := r.Add(context.Background(), p)
require.NoError(t, err)
hs.queryDataService = query.ProvideService(
nil,
setting.NewCfg(),
&fakeDatasources.FakeCacheService{},
nil,
&fakePluginRequestValidator{},

View File

@@ -14,6 +14,7 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/services/contexthandler"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/util/proxyutil"
"github.com/grafana/grafana/pkg/web"
@@ -117,7 +118,15 @@ func (hs *HTTPServer) makePluginResourceRequest(w http.ResponseWriter, req *http
hs.log.Warn("failed 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 := io.ReadAll(req.Body)

View File

@@ -224,7 +224,7 @@ func (proxy *DataSourceProxy) director(req *http.Request) {
applyUserHeader(proxy.cfg.SendUserHeader, req, proxy.ctx.SignedInUser)
proxyutil.ClearCookieHeader(req, proxy.ds.AllowedCookies())
proxyutil.ClearCookieHeader(req, proxy.ds.AllowedCookies(), []string{proxy.cfg.LoginCookieName})
req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion))
jsonData := make(map[string]interface{})

View File

@@ -23,6 +23,7 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/contexthandler"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/pluginsettings"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
@@ -320,6 +321,12 @@ func TestMakePluginResourceRequest(t *testing.T) {
pluginClient: &fakePluginClient{},
}
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)
@@ -332,6 +339,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) {

View File

@@ -13,6 +13,7 @@ func TestForwardedCookiesMiddleware(t *testing.T) {
tcs := []struct {
desc string
allowedCookies []string
disallowedCookies []string
expectedCookieHeader string
}{
{
@@ -30,6 +31,12 @@ func TestForwardedCookiesMiddleware(t *testing.T) {
allowedCookies: []string{"c1", "c3"},
expectedCookieHeader: "c1=1; c3=3",
},
{
desc: "When provided with allowed and not allowed cookies should populate Cookie header",
allowedCookies: []string{"c1", "c3"},
disallowedCookies: []string{"c1"},
expectedCookieHeader: "c3=3",
},
}
for _, tc := range tcs {
@@ -41,7 +48,7 @@ func TestForwardedCookiesMiddleware(t *testing.T) {
{Name: "c2", Value: "2"},
{Name: "c3", Value: "3"},
}
mw := httpclientprovider.ForwardedCookiesMiddleware(forwarded, tc.allowedCookies)
mw := httpclientprovider.ForwardedCookiesMiddleware(forwarded, tc.allowedCookies, tc.disallowedCookies)
opts := httpclient.Options{}
rt := mw.CreateMiddleware(opts, finalRoundTripper)
require.NotNil(t, rt)

View File

@@ -11,13 +11,13 @@ const ForwardedCookiesMiddlewareName = "forwarded-cookies"
// ForwardedCookiesMiddleware middleware that sets Cookie header on the
// outgoing request, if forwarded cookies configured/provided.
func ForwardedCookiesMiddleware(forwardedCookies []*http.Cookie, allowedCookies []string) httpclient.Middleware {
func ForwardedCookiesMiddleware(forwardedCookies []*http.Cookie, allowedCookies []string, disallowedCookies []string) httpclient.Middleware {
return httpclient.NamedMiddlewareFunc(ForwardedCookiesMiddlewareName, func(opts httpclient.Options, next http.RoundTripper) http.RoundTripper {
return httpclient.RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
for _, cookie := range forwardedCookies {
req.AddCookie(cookie)
}
proxyutil.ClearCookieHeader(req, allowedCookies)
proxyutil.ClearCookieHeader(req, allowedCookies, disallowedCookies)
return next.RoundTrip(req)
})
})

View File

@@ -38,6 +38,9 @@ func TestMiddlewareBasicAuth(t *testing.T) {
assert.True(t, sc.context.IsSignedIn)
assert.Equal(t, orgID, sc.context.OrgID)
assert.Equal(t, org.RoleEditor, 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) {
@@ -71,6 +74,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) {

View File

@@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/services/org"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/contexthandler"
@@ -75,6 +76,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 bearer in authorization header", func(t *testing.T, sc *scenarioContext) {

View File

@@ -422,6 +422,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

View File

@@ -298,7 +298,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: &config.Cfg{},
pluginPaths: []string{"../testdata/lacking-files"},
@@ -306,12 +306,12 @@ func TestLoader_Load(t *testing.T) {
pluginErrors: map[string]*plugins.Error{
"test-datasource": {
PluginID: "test-datasource",
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 (production) should return signatureInvali",
class: plugins.External,
cfg: &config.Cfg{
PluginsAllowUnsigned: []string{"test-datasource"},
@@ -321,7 +321,7 @@ func TestLoader_Load(t *testing.T) {
pluginErrors: map[string]*plugins.Error{
"test-datasource": {
PluginID: "test-datasource",
ErrorCode: "signatureModified",
ErrorCode: "signatureInvalid",
},
},
},

View File

@@ -132,6 +132,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{
@@ -167,21 +173,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)

View File

@@ -142,6 +142,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 = queryResult
ctx.IsSignedIn = true

View File

@@ -258,6 +258,9 @@ func (h *ContextHandler) initContextWithAPIKey(reqContext *models.ReqContext) bo
_, span := h.tracer.Start(reqContext.Req.Context(), "initContextWithAPIKey")
defer span.End()
ctx := WithAuthHTTPHeader(reqContext.Req.Context(), "Authorization")
*reqContext.Req = *reqContext.Req.WithContext(ctx)
var (
apikey *apikey.APIKey
errKey error
@@ -347,7 +350,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)
@@ -356,12 +359,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,
@@ -610,6 +616,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
@@ -629,3 +644,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
}

View File

@@ -171,7 +171,7 @@ func (s *Service) handleQueryData(ctx context.Context, user *user.SignedInUser,
middlewares := []httpclient.Middleware{}
if parsedReq.httpRequest != nil {
middlewares = append(middlewares,
httpclientprovider.ForwardedCookiesMiddleware(parsedReq.httpRequest.Cookies(), ds.AllowedCookies()),
httpclientprovider.ForwardedCookiesMiddleware(parsedReq.httpRequest.Cookies(), ds.AllowedCookies(), []string{s.cfg.LoginCookieName}),
)
}
@@ -188,7 +188,7 @@ func (s *Service) handleQueryData(ctx context.Context, user *user.SignedInUser,
}
if parsedReq.httpRequest != nil {
proxyutil.ClearCookieHeader(parsedReq.httpRequest, ds.AllowedCookies())
proxyutil.ClearCookieHeader(parsedReq.httpRequest, ds.AllowedCookies(), []string{s.cfg.LoginCookieName})
if cookieStr := parsedReq.httpRequest.Header.Get("Cookie"); cookieStr != "" {
req.Headers["Cookie"] = cookieStr
}

View File

@@ -26,6 +26,7 @@ import (
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"
)
func TestQueryDataMultipleSources(t *testing.T) {
@@ -197,7 +198,7 @@ func setup(t *testing.T) *testContext {
dataSourceCache: dc,
oauthTokenService: tc,
pluginRequestValidator: rv,
queryService: query.ProvideService(nil, dc, exprService, rv, ds, pc, tc),
queryService: query.ProvideService(setting.NewCfg(), dc, exprService, rv, ds, pc, tc),
}
}

View File

@@ -228,28 +228,33 @@ func (ss *SQLStore) GetUserByLogin(ctx context.Context, query *models.GetUserByL
return user.ErrUserNotFound
}
// Try and find the user by login first.
// It's not sufficient to assume that a LoginOrEmail with an "@" is an email.
var where string
var has bool
var err error
// Since username can be an email address, attempt login with email address
// first if the login field has the "@" symbol.
usr := &user.User{}
where := "login=?"
if ss.Cfg.CaseInsensitiveLogin {
where = "LOWER(login)=LOWER(?)"
}
has, err := sess.Where(notServiceAccountFilter(ss)).Where(where, query.LoginOrEmail).Get(usr)
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.
if strings.Contains(query.LoginOrEmail, "@") {
where = "email=?"
if ss.Cfg.CaseInsensitiveLogin {
where = "LOWER(email)=LOWER(?)"
}
has, err = sess.Where(notServiceAccountFilter(ss)).Where(where, query.LoginOrEmail).Get(usr)
if err != nil {
return err
}
}
// Look for the login field instead of email
if !has {
usr = &user.User{}
where = "login=?"
if ss.Cfg.CaseInsensitiveLogin {
where = "LOWER(login)=LOWER(?)"
}
has, err = sess.Where(notServiceAccountFilter(ss)).Where(where, query.LoginOrEmail).Get(usr)
}

View File

@@ -166,6 +166,45 @@ func TestIntegrationUserDataAccess(t *testing.T) {
})
})
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 := user.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 = user.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 := user.CreateUserCommand{

View File

@@ -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)
}
}

View File

@@ -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"))
})
}

View File

@@ -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)

View File

@@ -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) {

View File

@@ -1,6 +1,6 @@
{
"name": "@grafana-plugins/input-datasource",
"version": "9.2.0-beta.1",
"version": "9.2.0",
"description": "Input Datasource",
"private": true,
"repository": {
@@ -15,15 +15,15 @@
},
"author": "Grafana Labs",
"devDependencies": {
"@grafana/toolkit": "9.2.0-beta.1",
"@grafana/toolkit": "9.2.0",
"@types/jest": "26.0.15",
"@types/lodash": "4.14.149",
"@types/react": "17.0.30",
"lodash": "4.17.21"
},
"dependencies": {
"@grafana/data": "9.2.0-beta.1",
"@grafana/ui": "9.2.0-beta.1",
"@grafana/data": "9.2.0",
"@grafana/ui": "9.2.0",
"jquery": "3.5.1",
"react": "17.0.1",
"react-dom": "17.0.1",

View File

@@ -5182,9 +5182,9 @@ __metadata:
version: 0.0.0-use.local
resolution: "@grafana-plugins/input-datasource@workspace:plugins-bundled/internal/input-datasource"
dependencies:
"@grafana/data": 9.2.0-beta.1
"@grafana/toolkit": 9.2.0-beta.1
"@grafana/ui": 9.2.0-beta.1
"@grafana/data": 9.2.0
"@grafana/toolkit": 9.2.0
"@grafana/ui": 9.2.0
"@types/jest": 26.0.15
"@types/lodash": 4.14.149
"@types/react": 17.0.30
@@ -5228,12 +5228,12 @@ __metadata:
languageName: node
linkType: hard
"@grafana/data@9.2.0-beta.1, @grafana/data@workspace:*, @grafana/data@workspace:packages/grafana-data":
"@grafana/data@9.2.0, @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": 9.2.0-beta.1
"@grafana/schema": 9.2.0
"@grafana/tsconfig": ^1.2.0-rc1
"@rollup/plugin-commonjs": 22.0.1
"@rollup/plugin-json": 4.1.0
@@ -5292,7 +5292,7 @@ __metadata:
languageName: unknown
linkType: soft
"@grafana/e2e-selectors@9.2.0-beta.1, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors":
"@grafana/e2e-selectors@9.2.0, @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:
@@ -5318,7 +5318,7 @@ __metadata:
"@babel/core": 7.19.0
"@babel/preset-env": 7.19.0
"@cypress/webpack-preprocessor": 5.12.0
"@grafana/e2e-selectors": 9.2.0-beta.1
"@grafana/e2e-selectors": 9.2.0
"@grafana/tsconfig": ^1.2.0-rc1
"@mochajs/json-file-reporter": ^1.2.0
"@rollup/plugin-node-resolve": 13.3.0
@@ -5401,15 +5401,15 @@ __metadata:
languageName: node
linkType: hard
"@grafana/runtime@9.2.0-beta.1, @grafana/runtime@workspace:*, @grafana/runtime@workspace:packages/grafana-runtime":
"@grafana/runtime@9.2.0, @grafana/runtime@workspace:*, @grafana/runtime@workspace:packages/grafana-runtime":
version: 0.0.0-use.local
resolution: "@grafana/runtime@workspace:packages/grafana-runtime"
dependencies:
"@grafana/agent-web": ^0.4.0
"@grafana/data": 9.2.0-beta.1
"@grafana/e2e-selectors": 9.2.0-beta.1
"@grafana/data": 9.2.0
"@grafana/e2e-selectors": 9.2.0
"@grafana/tsconfig": ^1.2.0-rc1
"@grafana/ui": 9.2.0-beta.1
"@grafana/ui": 9.2.0
"@rollup/plugin-commonjs": 22.0.1
"@rollup/plugin-node-resolve": 13.3.0
"@sentry/browser": 6.19.7
@@ -5445,7 +5445,7 @@ __metadata:
languageName: unknown
linkType: soft
"@grafana/schema@9.2.0-beta.1, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema":
"@grafana/schema@9.2.0, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema":
version: 0.0.0-use.local
resolution: "@grafana/schema@workspace:packages/grafana-schema"
dependencies:
@@ -5465,7 +5465,7 @@ __metadata:
languageName: unknown
linkType: soft
"@grafana/toolkit@9.2.0-beta.1, @grafana/toolkit@workspace:*, @grafana/toolkit@workspace:packages/grafana-toolkit":
"@grafana/toolkit@9.2.0, @grafana/toolkit@workspace:*, @grafana/toolkit@workspace:packages/grafana-toolkit":
version: 0.0.0-use.local
resolution: "@grafana/toolkit@workspace:packages/grafana-toolkit"
dependencies:
@@ -5481,10 +5481,10 @@ __metadata:
"@babel/preset-env": 7.18.9
"@babel/preset-react": 7.18.6
"@babel/preset-typescript": 7.18.6
"@grafana/data": 9.2.0-beta.1
"@grafana/data": 9.2.0
"@grafana/eslint-config": 5.0.0
"@grafana/tsconfig": ^1.2.0-rc1
"@grafana/ui": 9.2.0-beta.1
"@grafana/ui": 9.2.0
"@jest/core": 27.5.1
"@types/command-exists": ^1.2.0
"@types/eslint": 8.4.1
@@ -5567,16 +5567,16 @@ __metadata:
languageName: node
linkType: hard
"@grafana/ui@9.2.0-beta.1, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui":
"@grafana/ui@9.2.0, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui":
version: 0.0.0-use.local
resolution: "@grafana/ui@workspace:packages/grafana-ui"
dependencies:
"@babel/core": 7.19.0
"@emotion/css": 11.9.0
"@emotion/react": 11.9.3
"@grafana/data": 9.2.0-beta.1
"@grafana/e2e-selectors": 9.2.0-beta.1
"@grafana/schema": 9.2.0-beta.1
"@grafana/data": 9.2.0
"@grafana/e2e-selectors": 9.2.0
"@grafana/schema": 9.2.0
"@grafana/tsconfig": ^1.2.0-rc1
"@mdx-js/react": 1.6.22
"@monaco-editor/react": 4.4.5
@@ -5860,11 +5860,11 @@ __metadata:
resolution: "@jaegertracing/jaeger-ui-components@workspace:packages/jaeger-ui-components"
dependencies:
"@emotion/css": 11.9.0
"@grafana/data": 9.2.0-beta.1
"@grafana/e2e-selectors": 9.2.0-beta.1
"@grafana/runtime": 9.2.0-beta.1
"@grafana/data": 9.2.0
"@grafana/e2e-selectors": 9.2.0
"@grafana/runtime": 9.2.0
"@grafana/tsconfig": ^1.2.0-rc1
"@grafana/ui": 9.2.0-beta.1
"@grafana/ui": 9.2.0
"@testing-library/jest-dom": 5.16.4
"@testing-library/react": 12.1.4
"@testing-library/user-event": 14.4.3