Auth: Support for user authentication via reverse proxy header (like X-Authenticated-User, or X-WEBAUTH-USER), Closes #1921

This commit is contained in:
Torkel Ödegaard
2015-05-02 12:06:58 +02:00
parent ba883d25fe
commit be589d81c7
7 changed files with 182 additions and 13 deletions
+67
View 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
View 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 -2
View File
@@ -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
}
+67 -8
View File
@@ -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
}