From f490c5f12ce0a76c51fb40752848fc490e04b09d Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Fri, 14 Apr 2017 09:47:39 -0400 Subject: [PATCH] use X-Grafana-Org-Id header to ensure backend uses correct org (#8122) --- pkg/middleware/auth_proxy.go | 5 +- pkg/middleware/middleware.go | 77 ++++++++++++++----------- pkg/models/user.go | 1 + pkg/services/sqlstore/user.go | 32 +++++----- public/app/core/services/backend_srv.ts | 14 ++++- 5 files changed, 77 insertions(+), 52 deletions(-) diff --git a/pkg/middleware/auth_proxy.go b/pkg/middleware/auth_proxy.go index e02e31f9152..8e94e1582b0 100644 --- a/pkg/middleware/auth_proxy.go +++ b/pkg/middleware/auth_proxy.go @@ -13,7 +13,7 @@ import ( "github.com/grafana/grafana/pkg/setting" ) -func initContextWithAuthProxy(ctx *Context) bool { +func initContextWithAuthProxy(ctx *Context, orgId int64) bool { if !setting.AuthProxyEnabled { return false } @@ -30,6 +30,7 @@ func initContextWithAuthProxy(ctx *Context) bool { } query := getSignedInUserQueryForProxyAuth(proxyHeaderValue) + query.OrgId = orgId if err := bus.Dispatch(query); err != nil { if err != m.ErrUserNotFound { ctx.Handle(500, "Failed to find user specified in auth proxy header", err) @@ -46,7 +47,7 @@ func initContextWithAuthProxy(ctx *Context) bool { ctx.Handle(500, "Failed to create user specified in auth proxy header", err) return true } - query = &m.GetSignedInUserQuery{UserId: cmd.Result.Id} + query = &m.GetSignedInUserQuery{UserId: cmd.Result.Id, OrgId: orgId} if err := bus.Dispatch(query); err != nil { ctx.Handle(500, "Failed find user after creation", err) return true diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go index 4b59fada62e..5aafe12d374 100644 --- a/pkg/middleware/middleware.go +++ b/pkg/middleware/middleware.go @@ -39,6 +39,12 @@ func GetContextHandler() macaron.Handler { Logger: log.New("context"), } + orgId := int64(0) + orgIdHeader := ctx.Req.Header.Get("X-Grafana-Org-Id") + if orgIdHeader != "" { + orgId, _ = strconv.ParseInt(orgIdHeader, 10, 64) + } + // the order in which these are tested are important // look for api key in Authorization header first // then init session and look for userId in session @@ -46,9 +52,9 @@ func GetContextHandler() macaron.Handler { // then test if anonymous access is enabled if initContextWithRenderAuth(ctx) || initContextWithApiKey(ctx) || - initContextWithBasicAuth(ctx) || - initContextWithAuthProxy(ctx) || - initContextWithUserSessionCookie(ctx) || + initContextWithBasicAuth(ctx, orgId) || + initContextWithAuthProxy(ctx, orgId) || + initContextWithUserSessionCookie(ctx, orgId) || initContextWithAnonymousUser(ctx) { } @@ -68,18 +74,18 @@ func initContextWithAnonymousUser(ctx *Context) bool { if err := bus.Dispatch(&orgQuery); err != nil { log.Error(3, "Anonymous access organization error: '%s': %s", setting.AnonymousOrgName, err) return false - } else { - ctx.IsSignedIn = false - ctx.AllowAnonymous = true - ctx.SignedInUser = &m.SignedInUser{} - ctx.OrgRole = m.RoleType(setting.AnonymousOrgRole) - ctx.OrgId = orgQuery.Result.Id - ctx.OrgName = orgQuery.Result.Name - return true } + + ctx.IsSignedIn = false + ctx.AllowAnonymous = true + ctx.SignedInUser = &m.SignedInUser{} + ctx.OrgRole = m.RoleType(setting.AnonymousOrgRole) + ctx.OrgId = orgQuery.Result.Id + ctx.OrgName = orgQuery.Result.Name + return true } -func initContextWithUserSessionCookie(ctx *Context) bool { +func initContextWithUserSessionCookie(ctx *Context, orgId int64) bool { // initialize session if err := ctx.Session.Start(ctx); err != nil { ctx.Logger.Error("Failed to start session", "error", err) @@ -91,15 +97,15 @@ func initContextWithUserSessionCookie(ctx *Context) bool { return false } - query := m.GetSignedInUserQuery{UserId: userId} + query := m.GetSignedInUserQuery{UserId: userId, OrgId: orgId} if err := bus.Dispatch(&query); err != nil { ctx.Logger.Error("Failed to get user with id", "userId", userId) return false - } else { - ctx.SignedInUser = query.Result - ctx.IsSignedIn = true - return true } + + ctx.SignedInUser = query.Result + ctx.IsSignedIn = true + return true } func initContextWithApiKey(ctx *Context) bool { @@ -114,30 +120,31 @@ func initContextWithApiKey(ctx *Context) bool { ctx.JsonApiErr(401, "Invalid API key", err) return true } + // fetch key keyQuery := m.GetApiKeyByNameQuery{KeyName: decoded.Name, OrgId: decoded.OrgId} if err := bus.Dispatch(&keyQuery); err != nil { ctx.JsonApiErr(401, "Invalid API key", err) return true - } else { - apikey := keyQuery.Result + } - // validate api key - if !apikeygen.IsValid(decoded, apikey.Key) { - ctx.JsonApiErr(401, "Invalid API key", err) - return true - } + apikey := keyQuery.Result - ctx.IsSignedIn = true - ctx.SignedInUser = &m.SignedInUser{} - ctx.OrgRole = apikey.Role - ctx.ApiKeyId = apikey.Id - ctx.OrgId = apikey.OrgId + // validate api key + if !apikeygen.IsValid(decoded, apikey.Key) { + ctx.JsonApiErr(401, "Invalid API key", err) return true } + + ctx.IsSignedIn = true + ctx.SignedInUser = &m.SignedInUser{} + ctx.OrgRole = apikey.Role + ctx.ApiKeyId = apikey.Id + ctx.OrgId = apikey.OrgId + return true } -func initContextWithBasicAuth(ctx *Context) bool { +func initContextWithBasicAuth(ctx *Context, orgId int64) bool { if !setting.BasicAuthEnabled { return false @@ -168,15 +175,15 @@ func initContextWithBasicAuth(ctx *Context) bool { return true } - query := m.GetSignedInUserQuery{UserId: user.Id} + query := m.GetSignedInUserQuery{UserId: user.Id, OrgId: orgId} if err := bus.Dispatch(&query); err != nil { ctx.JsonApiErr(401, "Authentication error", err) return true - } else { - ctx.SignedInUser = query.Result - ctx.IsSignedIn = true - return true } + + ctx.SignedInUser = query.Result + ctx.IsSignedIn = true + return true } // Handle handles and logs error by given status. diff --git a/pkg/models/user.go b/pkg/models/user.go index e0a36be8c0a..bdf81056232 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -117,6 +117,7 @@ type GetSignedInUserQuery struct { UserId int64 Login string Email string + OrgId int64 Result *SignedInUser } diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index 9a44d6f194e..71b29bd3355 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -1,6 +1,7 @@ package sqlstore import ( + "strconv" "strings" "time" @@ -273,7 +274,7 @@ func SetUsingOrg(cmd *m.SetUsingOrgCommand) error { } if !valid { - return fmt.Errorf("user does not belong ot org") + return fmt.Errorf("user does not belong to org") } return inTransaction(func(sess *xorm.Session) error { @@ -319,19 +320,24 @@ func GetUserOrgList(query *m.GetUserOrgListQuery) error { } func GetSignedInUser(query *m.GetSignedInUserQuery) error { + orgId := "u.org_id" + if query.OrgId > 0 { + orgId = strconv.FormatInt(query.OrgId, 10) + } + var rawSql = `SELECT - u.id as user_id, - u.is_admin as is_grafana_admin, - u.email as email, - u.login as login, - u.name as name, - u.help_flags1 as help_flags1, - org.name as org_name, - org_user.role as org_role, - org.id as org_id - FROM ` + dialect.Quote("user") + ` as u - LEFT OUTER JOIN org_user on org_user.org_id = u.org_id and org_user.user_id = u.id - LEFT OUTER JOIN org on org.id = u.org_id ` + u.id as user_id, + u.is_admin as is_grafana_admin, + u.email as email, + u.login as login, + u.name as name, + u.help_flags1 as help_flags1, + org.name as org_name, + org_user.role as org_role, + org.id as org_id + FROM ` + dialect.Quote("user") + ` as u + LEFT OUTER JOIN org_user on org_user.org_id = ` + orgId + ` and org_user.user_id = u.id + LEFT OUTER JOIN org on org.id = org_user.org_id ` sess := x.Table("user") if query.UserId > 0 { diff --git a/public/app/core/services/backend_srv.ts b/public/app/core/services/backend_srv.ts index ba4f4dc2fb9..cc52de4147c 100644 --- a/public/app/core/services/backend_srv.ts +++ b/public/app/core/services/backend_srv.ts @@ -9,8 +9,8 @@ export class BackendSrv { inFlightRequests = {}; HTTP_REQUEST_CANCELLED = -1; - /** @ngInject */ - constructor(private $http, private alertSrv, private $rootScope, private $q, private $timeout) { + /** @ngInject */ + constructor(private $http, private alertSrv, private $rootScope, private $q, private $timeout, private contextSrv) { } get(url, params?) { @@ -66,6 +66,11 @@ export class BackendSrv { var requestIsLocal = options.url.indexOf('/') === 0; var firstAttempt = options.retry === 0; + if (!options.url.match('https?://') && this.contextSrv && this.contextSrv.user && this.contextSrv.user.orgId) { + options.headers = options.headers || {}; + options.headers['X-Grafana-Org-Id'] = this.contextSrv.user.orgId; + } + if (requestIsLocal && !options.hasSubUrl) { options.url = config.appSubUrl + options.url; options.hasSubUrl = true; @@ -128,6 +133,11 @@ export class BackendSrv { var requestIsLocal = options.url.indexOf('/') === 0; var firstAttempt = options.retry === 0; + if (!options.url.match('https?://') && this.contextSrv && this.contextSrv.user && this.contextSrv.user.orgId) { + options.headers = options.headers || {}; + options.headers['X-Grafana-Org-Id'] = this.contextSrv.user.orgId; + } + if (requestIsLocal && !options.hasSubUrl && options.retry === 0) { options.url = config.appSubUrl + options.url; }