Commit be589d81 by Torkel Ödegaard

Auth: Support for user authentication via reverse proxy header (like…

Auth: Support for user authentication via reverse proxy header (like X-Authenticated-User, or X-WEBAUTH-USER), Closes #1921
parent ba883d25
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
**Backend** **Backend**
- [Issue #1905](https://github.com/grafana/grafana/issues/1905). Github OAuth: You can now configure a Github team membership requirement, thx @dewski - [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 #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) # 2.0.3 (unreleased - 2.0.x branch)
......
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
}
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)
})
})
})
}
package middleware package middleware
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
...@@ -41,6 +40,7 @@ func GetContextHandler() macaron.Handler { ...@@ -41,6 +40,7 @@ func GetContextHandler() macaron.Handler {
// then look for api key in session (special case for render calls via api) // then look for api key in session (special case for render calls via api)
// then test if anonymous access is enabled // then test if anonymous access is enabled
if initContextWithApiKey(ctx) || if initContextWithApiKey(ctx) ||
initContextWithAuthProxy(ctx) ||
initContextWithUserSessionCookie(ctx) || initContextWithUserSessionCookie(ctx) ||
initContextWithApiKeyFromSession(ctx) || initContextWithApiKeyFromSession(ctx) ||
initContextWithAnonymousUser(ctx) { initContextWithAnonymousUser(ctx) {
...@@ -79,7 +79,6 @@ func initContextWithUserSessionCookie(ctx *Context) bool { ...@@ -79,7 +79,6 @@ func initContextWithUserSessionCookie(ctx *Context) bool {
var userId int64 var userId int64
if userId = getRequestUserId(ctx); userId == 0 { if userId = getRequestUserId(ctx); userId == 0 {
fmt.Printf("Not userId")
return false return false
} }
......
...@@ -128,6 +128,62 @@ func TestMiddlewareContext(t *testing.T) { ...@@ -128,6 +128,62 @@ func TestMiddlewareContext(t *testing.T) {
So(sc.context.IsSignedIn, ShouldBeFalse) 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) { ...@@ -149,24 +205,27 @@ func middlewareScenario(desc string, fn scenarioFunc) {
startSessionGC = func() {} startSessionGC = func() {}
sc.m.Use(Sessioner(&session.Options{})) sc.m.Use(Sessioner(&session.Options{}))
sc.m.Get("/", func(c *Context) { sc.defaultHandler = func(c *Context) {
sc.context = c sc.context = c
if sc.handlerFunc != nil { if sc.handlerFunc != nil {
sc.handlerFunc(sc.context) sc.handlerFunc(sc.context)
} }
}) }
sc.m.Get("/", sc.defaultHandler)
fn(sc) fn(sc)
}) })
} }
type scenarioContext struct { type scenarioContext struct {
m *macaron.Macaron m *macaron.Macaron
context *Context context *Context
resp *httptest.ResponseRecorder resp *httptest.ResponseRecorder
apiKey string apiKey string
respJson map[string]interface{} respJson map[string]interface{}
handlerFunc handlerFunc handlerFunc handlerFunc
defaultHandler macaron.Handler
req *http.Request req *http.Request
} }
......
...@@ -89,6 +89,8 @@ type GetUserByIdQuery struct { ...@@ -89,6 +89,8 @@ type GetUserByIdQuery struct {
type GetSignedInUserQuery struct { type GetSignedInUserQuery struct {
UserId int64 UserId int64
Login string
Email string
Result *SignedInUser Result *SignedInUser
} }
......
...@@ -263,8 +263,15 @@ func GetSignedInUser(query *m.GetSignedInUserQuery) error { ...@@ -263,8 +263,15 @@ func GetSignedInUser(query *m.GetSignedInUserQuery) error {
org.id as org_id org.id as org_id
FROM ` + dialect.Quote("user") + ` as u 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_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 LEFT OUTER JOIN org on org.id = u.org_id `
WHERE u.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 var user m.SignedInUser
sess := x.Table("user") sess := x.Table("user")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment