mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Auth: Support for user authentication via reverse proxy header (like X-Authenticated-User, or X-WEBAUTH-USER), Closes #1921
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
**Backend**
|
||||
- [Issue #1905](https://github.com/grafana/grafana/issues/1905). Github OAuth: You can now configure a Github team membership requirement, thx @dewski
|
||||
- [Issue #1891](https://github.com/grafana/grafana/issues/1891). Security: New config option to disable the use of gravatar for profile images
|
||||
|
||||
- [Issue #1921](https://github.com/grafana/grafana/issues/1921). Auth: Support for user authentication via reverse proxy header (like X-Authenticated-User, or X-WEBAUTH-USER)
|
||||
|
||||
# 2.0.3 (unreleased - 2.0.x branch)
|
||||
|
||||
|
||||
67
pkg/middleware/auth_proxy.go
Normal file
67
pkg/middleware/auth_proxy.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func initContextWithAuthProxy(ctx *Context) bool {
|
||||
if !setting.AuthProxyEnabled {
|
||||
return false
|
||||
}
|
||||
|
||||
proxyHeaderValue := ctx.Req.Header.Get(setting.AuthProxyHeaderName)
|
||||
if len(proxyHeaderValue) <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
query := getSignedInUserQueryForProxyAuth(proxyHeaderValue)
|
||||
if err := bus.Dispatch(query); err != nil {
|
||||
if err != m.ErrUserNotFound {
|
||||
ctx.Handle(500, "Failed find user specifed in auth proxy header", err)
|
||||
return true
|
||||
}
|
||||
|
||||
if setting.AuthProxyAutoSignUp {
|
||||
cmd := getCreateUserCommandForProxyAuth(proxyHeaderValue)
|
||||
if err := bus.Dispatch(cmd); err != nil {
|
||||
ctx.Handle(500, "Failed to create user specified in auth proxy header", err)
|
||||
return true
|
||||
}
|
||||
query = &m.GetSignedInUserQuery{UserId: cmd.Result.Id}
|
||||
if err := bus.Dispatch(query); err != nil {
|
||||
ctx.Handle(500, "Failed find user after creation", err)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SignedInUser = query.Result
|
||||
ctx.IsSignedIn = true
|
||||
return true
|
||||
}
|
||||
|
||||
func getSignedInUserQueryForProxyAuth(headerVal string) *m.GetSignedInUserQuery {
|
||||
query := m.GetSignedInUserQuery{}
|
||||
if setting.AuthProxyHeaderProperty == "username" {
|
||||
query.Login = headerVal
|
||||
} else if setting.AuthProxyHeaderProperty == "email" {
|
||||
query.Email = headerVal
|
||||
} else {
|
||||
panic("Auth proxy header property invalid")
|
||||
}
|
||||
return &query
|
||||
}
|
||||
|
||||
func getCreateUserCommandForProxyAuth(headerVal string) *m.CreateUserCommand {
|
||||
cmd := m.CreateUserCommand{}
|
||||
if setting.AuthProxyHeaderProperty == "username" {
|
||||
cmd.Login = headerVal
|
||||
} else if setting.AuthProxyHeaderProperty == "email" {
|
||||
cmd.Email = headerVal
|
||||
} else {
|
||||
panic("Auth proxy header property invalid")
|
||||
}
|
||||
return &cmd
|
||||
}
|
||||
35
pkg/middleware/auth_test.go
Normal file
35
pkg/middleware/auth_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestMiddlewareAuth(t *testing.T) {
|
||||
|
||||
Convey("Given the grafana middleware", t, func() {
|
||||
reqSignIn := Auth(&AuthOptions{ReqSignedIn: true})
|
||||
|
||||
middlewareScenario("ReqSignIn true and unauthenticated request", func(sc *scenarioContext) {
|
||||
sc.m.Get("/secure", reqSignIn, sc.defaultHandler)
|
||||
|
||||
sc.fakeReq("GET", "/secure").exec()
|
||||
|
||||
Convey("Should redirect to login", func() {
|
||||
So(sc.resp.Code, ShouldEqual, 302)
|
||||
})
|
||||
})
|
||||
|
||||
middlewareScenario("ReqSignIn true and unauthenticated API request", func(sc *scenarioContext) {
|
||||
sc.m.Get("/api/secure", reqSignIn, sc.defaultHandler)
|
||||
|
||||
sc.fakeReq("GET", "/api/secure").exec()
|
||||
|
||||
Convey("Should return 401", func() {
|
||||
So(sc.resp.Code, ShouldEqual, 401)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -41,6 +40,7 @@ func GetContextHandler() macaron.Handler {
|
||||
// then look for api key in session (special case for render calls via api)
|
||||
// then test if anonymous access is enabled
|
||||
if initContextWithApiKey(ctx) ||
|
||||
initContextWithAuthProxy(ctx) ||
|
||||
initContextWithUserSessionCookie(ctx) ||
|
||||
initContextWithApiKeyFromSession(ctx) ||
|
||||
initContextWithAnonymousUser(ctx) {
|
||||
@@ -79,7 +79,6 @@ func initContextWithUserSessionCookie(ctx *Context) bool {
|
||||
|
||||
var userId int64
|
||||
if userId = getRequestUserId(ctx); userId == 0 {
|
||||
fmt.Printf("Not userId")
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -128,6 +128,62 @@ func TestMiddlewareContext(t *testing.T) {
|
||||
So(sc.context.IsSignedIn, ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
|
||||
middlewareScenario("When auth_proxy is enabled enabled and user exists", func(sc *scenarioContext) {
|
||||
setting.AuthProxyEnabled = true
|
||||
setting.AuthProxyHeaderName = "X-WEBAUTH-USER"
|
||||
setting.AuthProxyHeaderProperty = "username"
|
||||
|
||||
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
|
||||
query.Result = &m.SignedInUser{OrgId: 2, UserId: 12}
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.fakeReq("GET", "/")
|
||||
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo")
|
||||
sc.exec()
|
||||
|
||||
Convey("should init context with user info", func() {
|
||||
So(sc.context.IsSignedIn, ShouldBeTrue)
|
||||
So(sc.context.UserId, ShouldEqual, 12)
|
||||
So(sc.context.OrgId, ShouldEqual, 2)
|
||||
})
|
||||
})
|
||||
|
||||
middlewareScenario("When auth_proxy is enabled enabled and user does not exists", func(sc *scenarioContext) {
|
||||
setting.AuthProxyEnabled = true
|
||||
setting.AuthProxyHeaderName = "X-WEBAUTH-USER"
|
||||
setting.AuthProxyHeaderProperty = "username"
|
||||
setting.AuthProxyAutoSignUp = true
|
||||
|
||||
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
|
||||
if query.UserId > 0 {
|
||||
query.Result = &m.SignedInUser{OrgId: 4, UserId: 33}
|
||||
return nil
|
||||
} else {
|
||||
return m.ErrUserNotFound
|
||||
}
|
||||
})
|
||||
|
||||
var createUserCmd *m.CreateUserCommand
|
||||
bus.AddHandler("test", func(cmd *m.CreateUserCommand) error {
|
||||
createUserCmd = cmd
|
||||
cmd.Result = m.User{Id: 33}
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.fakeReq("GET", "/")
|
||||
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo")
|
||||
sc.exec()
|
||||
|
||||
Convey("Should create user if auto sign up is enabled", func() {
|
||||
So(sc.context.IsSignedIn, ShouldBeTrue)
|
||||
So(sc.context.UserId, ShouldEqual, 33)
|
||||
So(sc.context.OrgId, ShouldEqual, 4)
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@@ -149,24 +205,27 @@ func middlewareScenario(desc string, fn scenarioFunc) {
|
||||
startSessionGC = func() {}
|
||||
sc.m.Use(Sessioner(&session.Options{}))
|
||||
|
||||
sc.m.Get("/", func(c *Context) {
|
||||
sc.defaultHandler = func(c *Context) {
|
||||
sc.context = c
|
||||
if sc.handlerFunc != nil {
|
||||
sc.handlerFunc(sc.context)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
sc.m.Get("/", sc.defaultHandler)
|
||||
|
||||
fn(sc)
|
||||
})
|
||||
}
|
||||
|
||||
type scenarioContext struct {
|
||||
m *macaron.Macaron
|
||||
context *Context
|
||||
resp *httptest.ResponseRecorder
|
||||
apiKey string
|
||||
respJson map[string]interface{}
|
||||
handlerFunc handlerFunc
|
||||
m *macaron.Macaron
|
||||
context *Context
|
||||
resp *httptest.ResponseRecorder
|
||||
apiKey string
|
||||
respJson map[string]interface{}
|
||||
handlerFunc handlerFunc
|
||||
defaultHandler macaron.Handler
|
||||
|
||||
req *http.Request
|
||||
}
|
||||
|
||||
@@ -89,6 +89,8 @@ type GetUserByIdQuery struct {
|
||||
|
||||
type GetSignedInUserQuery struct {
|
||||
UserId int64
|
||||
Login string
|
||||
Email string
|
||||
Result *SignedInUser
|
||||
}
|
||||
|
||||
|
||||
@@ -263,8 +263,15 @@ func GetSignedInUser(query *m.GetSignedInUserQuery) error {
|
||||
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
|
||||
WHERE u.id=?`
|
||||
LEFT OUTER JOIN org on org.id = u.org_id `
|
||||
|
||||
if query.UserId > 0 {
|
||||
rawSql += "WHERE u.id=?"
|
||||
} else if query.Login != "" {
|
||||
rawSql += "WHERE u.login=?"
|
||||
} else if query.Email != "" {
|
||||
rawSql += "WHERE u.email=?"
|
||||
}
|
||||
|
||||
var user m.SignedInUser
|
||||
sess := x.Table("user")
|
||||
|
||||
Reference in New Issue
Block a user