mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'authproxy_ldap' of https://github.com/seuf/grafana into seuf-authproxy_ldap
This commit is contained in:
@@ -13,7 +13,7 @@
|
|||||||
* **CLI**: Make it possible to reset the admin password using the grafana-cli. [#5479](https://github.com/grafana/grafana/issues/5479)
|
* **CLI**: Make it possible to reset the admin password using the grafana-cli. [#5479](https://github.com/grafana/grafana/issues/5479)
|
||||||
* **Influxdb**: Support multiple tags in InfluxDB annotations. [#4550](https://github.com/grafana/grafana/pull/4550), thx [@adrianlzt](https://github.com/adrianlzt)
|
* **Influxdb**: Support multiple tags in InfluxDB annotations. [#4550](https://github.com/grafana/grafana/pull/4550), thx [@adrianlzt](https://github.com/adrianlzt)
|
||||||
* **LDAP**: Basic Auth now supports LDAP username and password, [#6940](https://github.com/grafana/grafana/pull/6940), thx [@utkarshcmu](https://github.com/utkarshcmu)
|
* **LDAP**: Basic Auth now supports LDAP username and password, [#6940](https://github.com/grafana/grafana/pull/6940), thx [@utkarshcmu](https://github.com/utkarshcmu)
|
||||||
|
* **LDAP**: Now works with Auth Proxy, role and organisation mapping & sync will regularly be performed. [#6895](https://github.com/grafana/grafana/pull/6895), thx [@Seuf](https://github.com/seuf)
|
||||||
|
|
||||||
### Bugfixes
|
### Bugfixes
|
||||||
* **API**: HTTP API for deleting org returning incorrect message for a non-existing org [#6679](https://github.com/grafana/grafana/issues/6679)
|
* **API**: HTTP API for deleting org returning incorrect message for a non-existing org [#6679](https://github.com/grafana/grafana/issues/6679)
|
||||||
|
|||||||
@@ -264,6 +264,8 @@ enabled = false
|
|||||||
header_name = X-WEBAUTH-USER
|
header_name = X-WEBAUTH-USER
|
||||||
header_property = username
|
header_property = username
|
||||||
auto_sign_up = true
|
auto_sign_up = true
|
||||||
|
ldap_sync_ttl = 60
|
||||||
|
whitelist =
|
||||||
|
|
||||||
#################################### Auth LDAP ###########################
|
#################################### Auth LDAP ###########################
|
||||||
[auth.ldap]
|
[auth.ldap]
|
||||||
|
|||||||
@@ -244,6 +244,8 @@
|
|||||||
;header_name = X-WEBAUTH-USER
|
;header_name = X-WEBAUTH-USER
|
||||||
;header_property = username
|
;header_property = username
|
||||||
;auto_sign_up = true
|
;auto_sign_up = true
|
||||||
|
;ldap_sync_ttl = 60
|
||||||
|
;whitelist = 192.168.1.1, 192.168.2.1
|
||||||
|
|
||||||
#################################### Basic Auth ##########################
|
#################################### Basic Auth ##########################
|
||||||
[auth.basic]
|
[auth.basic]
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ func AuthenticateUser(query *LoginUserQuery) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if setting.LdapEnabled {
|
if setting.LdapEnabled {
|
||||||
for _, server := range ldapCfg.Servers {
|
for _, server := range LdapCfg.Servers {
|
||||||
auther := NewLdapAuthenticator(server)
|
auther := NewLdapAuthenticator(server)
|
||||||
err = auther.login(query)
|
err = auther.Login(query)
|
||||||
if err == nil || err != ErrInvalidCredentials {
|
if err == nil || err != ErrInvalidCredentials {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,16 +16,34 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ILdapConn interface {
|
||||||
|
Bind(username, password string) error
|
||||||
|
Search(*ldap.SearchRequest) (*ldap.SearchResult, error)
|
||||||
|
StartTLS(*tls.Config) error
|
||||||
|
Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
type ILdapAuther interface {
|
||||||
|
Login(query *LoginUserQuery) error
|
||||||
|
SyncSignedInUser(signedInUser *m.SignedInUser) error
|
||||||
|
GetGrafanaUserFor(ldapUser *LdapUserInfo) (*m.User, error)
|
||||||
|
SyncOrgRoles(user *m.User, ldapUser *LdapUserInfo) error
|
||||||
|
}
|
||||||
|
|
||||||
type ldapAuther struct {
|
type ldapAuther struct {
|
||||||
server *LdapServerConf
|
server *LdapServerConf
|
||||||
conn *ldap.Conn
|
conn ILdapConn
|
||||||
requireSecondBind bool
|
requireSecondBind bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLdapAuthenticator(server *LdapServerConf) *ldapAuther {
|
var NewLdapAuthenticator = func(server *LdapServerConf) ILdapAuther {
|
||||||
return &ldapAuther{server: server}
|
return &ldapAuther{server: server}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ldapDial = func(network, addr string) (ILdapConn, error) {
|
||||||
|
return ldap.Dial(network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *ldapAuther) Dial() error {
|
func (a *ldapAuther) Dial() error {
|
||||||
var err error
|
var err error
|
||||||
var certPool *x509.CertPool
|
var certPool *x509.CertPool
|
||||||
@@ -60,7 +78,7 @@ func (a *ldapAuther) Dial() error {
|
|||||||
a.conn, err = ldap.DialTLS("tcp", address, tlsCfg)
|
a.conn, err = ldap.DialTLS("tcp", address, tlsCfg)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
a.conn, err = ldap.Dial("tcp", address)
|
a.conn, err = ldapDial("tcp", address)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -70,7 +88,7 @@ func (a *ldapAuther) Dial() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ldapAuther) login(query *LoginUserQuery) error {
|
func (a *ldapAuther) Login(query *LoginUserQuery) error {
|
||||||
if err := a.Dial(); err != nil {
|
if err := a.Dial(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -85,7 +103,7 @@ func (a *ldapAuther) login(query *LoginUserQuery) error {
|
|||||||
if ldapUser, err := a.searchForUser(query.Username); err != nil {
|
if ldapUser, err := a.searchForUser(query.Username); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
if ldapCfg.VerboseLogging {
|
if LdapCfg.VerboseLogging {
|
||||||
log.Info("Ldap User Info: %s", spew.Sdump(ldapUser))
|
log.Info("Ldap User Info: %s", spew.Sdump(ldapUser))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,16 +114,11 @@ func (a *ldapAuther) login(query *LoginUserQuery) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if grafanaUser, err := a.getGrafanaUserFor(ldapUser); err != nil {
|
if grafanaUser, err := a.GetGrafanaUserFor(ldapUser); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
// sync user details
|
if syncErr := a.syncInfoAndOrgRoles(grafanaUser, ldapUser); syncErr != nil {
|
||||||
if err := a.syncUserInfo(grafanaUser, ldapUser); err != nil {
|
return syncErr
|
||||||
return err
|
|
||||||
}
|
|
||||||
// sync org roles
|
|
||||||
if err := a.syncOrgRoles(grafanaUser, ldapUser); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
query.User = grafanaUser
|
query.User = grafanaUser
|
||||||
return nil
|
return nil
|
||||||
@@ -113,7 +126,55 @@ func (a *ldapAuther) login(query *LoginUserQuery) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ldapAuther) getGrafanaUserFor(ldapUser *ldapUserInfo) (*m.User, error) {
|
func (a *ldapAuther) SyncSignedInUser(signedInUser *m.SignedInUser) error {
|
||||||
|
grafanaUser := m.User{
|
||||||
|
Id: signedInUser.UserId,
|
||||||
|
Login: signedInUser.Login,
|
||||||
|
Email: signedInUser.Email,
|
||||||
|
Name: signedInUser.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.Dial(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer a.conn.Close()
|
||||||
|
if err := a.serverBind(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ldapUser, err := a.searchForUser(signedInUser.Login); err != nil {
|
||||||
|
log.Info("ERROR while searching for user in ldap %#v", err)
|
||||||
|
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
if err := a.syncInfoAndOrgRoles(&grafanaUser, ldapUser); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if LdapCfg.VerboseLogging {
|
||||||
|
log.Info("Ldap User Info: %s", spew.Sdump(ldapUser))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync info for ldap user and grafana user
|
||||||
|
func (a *ldapAuther) syncInfoAndOrgRoles(user *m.User, ldapUser *LdapUserInfo) error {
|
||||||
|
// sync user details
|
||||||
|
if err := a.syncUserInfo(user, ldapUser); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// sync org roles
|
||||||
|
if err := a.SyncOrgRoles(user, ldapUser); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ldapAuther) GetGrafanaUserFor(ldapUser *LdapUserInfo) (*m.User, error) {
|
||||||
// validate that the user has access
|
// validate that the user has access
|
||||||
// if there are no ldap group mappings access is true
|
// if there are no ldap group mappings access is true
|
||||||
// otherwise a single group must match
|
// otherwise a single group must match
|
||||||
@@ -145,7 +206,7 @@ func (a *ldapAuther) getGrafanaUserFor(ldapUser *ldapUserInfo) (*m.User, error)
|
|||||||
return userQuery.Result, nil
|
return userQuery.Result, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
func (a *ldapAuther) createGrafanaUser(ldapUser *ldapUserInfo) (*m.User, error) {
|
func (a *ldapAuther) createGrafanaUser(ldapUser *LdapUserInfo) (*m.User, error) {
|
||||||
cmd := m.CreateUserCommand{
|
cmd := m.CreateUserCommand{
|
||||||
Login: ldapUser.Username,
|
Login: ldapUser.Username,
|
||||||
Email: ldapUser.Email,
|
Email: ldapUser.Email,
|
||||||
@@ -159,7 +220,7 @@ func (a *ldapAuther) createGrafanaUser(ldapUser *ldapUserInfo) (*m.User, error)
|
|||||||
return &cmd.Result, nil
|
return &cmd.Result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ldapAuther) syncUserInfo(user *m.User, ldapUser *ldapUserInfo) error {
|
func (a *ldapAuther) syncUserInfo(user *m.User, ldapUser *LdapUserInfo) error {
|
||||||
var name = fmt.Sprintf("%s %s", ldapUser.FirstName, ldapUser.LastName)
|
var name = fmt.Sprintf("%s %s", ldapUser.FirstName, ldapUser.LastName)
|
||||||
if user.Email == ldapUser.Email && user.Name == name {
|
if user.Email == ldapUser.Email && user.Name == name {
|
||||||
return nil
|
return nil
|
||||||
@@ -174,7 +235,7 @@ func (a *ldapAuther) syncUserInfo(user *m.User, ldapUser *ldapUserInfo) error {
|
|||||||
return bus.Dispatch(&updateCmd)
|
return bus.Dispatch(&updateCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ldapAuther) syncOrgRoles(user *m.User, ldapUser *ldapUserInfo) error {
|
func (a *ldapAuther) SyncOrgRoles(user *m.User, ldapUser *LdapUserInfo) error {
|
||||||
if len(a.server.LdapGroups) == 0 {
|
if len(a.server.LdapGroups) == 0 {
|
||||||
log.Warn("Ldap: no group mappings defined")
|
log.Warn("Ldap: no group mappings defined")
|
||||||
return nil
|
return nil
|
||||||
@@ -244,9 +305,27 @@ func (a *ldapAuther) syncOrgRoles(user *m.User, ldapUser *ldapUserInfo) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ldapAuther) secondBind(ldapUser *ldapUserInfo, userPassword string) error {
|
func (a *ldapAuther) serverBind() error {
|
||||||
|
// bind_dn and bind_password to bind
|
||||||
|
if err := a.conn.Bind(a.server.BindDN, a.server.BindPassword); err != nil {
|
||||||
|
if LdapCfg.VerboseLogging {
|
||||||
|
log.Info("LDAP initial bind failed, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ldapErr, ok := err.(*ldap.Error); ok {
|
||||||
|
if ldapErr.ResultCode == 49 {
|
||||||
|
return ErrInvalidCredentials
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ldapAuther) secondBind(ldapUser *LdapUserInfo, userPassword string) error {
|
||||||
if err := a.conn.Bind(ldapUser.DN, userPassword); err != nil {
|
if err := a.conn.Bind(ldapUser.DN, userPassword); err != nil {
|
||||||
if ldapCfg.VerboseLogging {
|
if LdapCfg.VerboseLogging {
|
||||||
log.Info("LDAP second bind failed, %v", err)
|
log.Info("LDAP second bind failed, %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +352,7 @@ func (a *ldapAuther) initialBind(username, userPassword string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := a.conn.Bind(bindPath, userPassword); err != nil {
|
if err := a.conn.Bind(bindPath, userPassword); err != nil {
|
||||||
if ldapCfg.VerboseLogging {
|
if LdapCfg.VerboseLogging {
|
||||||
log.Info("LDAP initial bind failed, %v", err)
|
log.Info("LDAP initial bind failed, %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,7 +367,7 @@ func (a *ldapAuther) initialBind(username, userPassword string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ldapAuther) searchForUser(username string) (*ldapUserInfo, error) {
|
func (a *ldapAuther) searchForUser(username string) (*LdapUserInfo, error) {
|
||||||
var searchResult *ldap.SearchResult
|
var searchResult *ldap.SearchResult
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@@ -339,7 +418,7 @@ func (a *ldapAuther) searchForUser(username string) (*ldapUserInfo, error) {
|
|||||||
}
|
}
|
||||||
filter := strings.Replace(a.server.GroupSearchFilter, "%s", ldap.EscapeFilter(filter_replace), -1)
|
filter := strings.Replace(a.server.GroupSearchFilter, "%s", ldap.EscapeFilter(filter_replace), -1)
|
||||||
|
|
||||||
if ldapCfg.VerboseLogging {
|
if LdapCfg.VerboseLogging {
|
||||||
log.Info("LDAP: Searching for user's groups: %s", filter)
|
log.Info("LDAP: Searching for user's groups: %s", filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,7 +447,7 @@ func (a *ldapAuther) searchForUser(username string) (*ldapUserInfo, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ldapUserInfo{
|
return &LdapUserInfo{
|
||||||
DN: searchResult.Entries[0].DN,
|
DN: searchResult.Entries[0].DN,
|
||||||
LastName: getLdapAttr(a.server.Attr.Surname, searchResult),
|
LastName: getLdapAttr(a.server.Attr.Surname, searchResult),
|
||||||
FirstName: getLdapAttr(a.server.Attr.Name, searchResult),
|
FirstName: getLdapAttr(a.server.Attr.Name, searchResult),
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package login
|
package login
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-ldap/ldap"
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
m "github.com/grafana/grafana/pkg/models"
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
@@ -16,7 +18,7 @@ func TestLdapAuther(t *testing.T) {
|
|||||||
ldapAuther := NewLdapAuthenticator(&LdapServerConf{
|
ldapAuther := NewLdapAuthenticator(&LdapServerConf{
|
||||||
LdapGroups: []*LdapGroupToOrgRole{{}},
|
LdapGroups: []*LdapGroupToOrgRole{{}},
|
||||||
})
|
})
|
||||||
_, err := ldapAuther.getGrafanaUserFor(&ldapUserInfo{})
|
_, err := ldapAuther.GetGrafanaUserFor(&LdapUserInfo{})
|
||||||
|
|
||||||
So(err, ShouldEqual, ErrInvalidCredentials)
|
So(err, ShouldEqual, ErrInvalidCredentials)
|
||||||
})
|
})
|
||||||
@@ -32,7 +34,7 @@ func TestLdapAuther(t *testing.T) {
|
|||||||
|
|
||||||
sc.userQueryReturns(user1)
|
sc.userQueryReturns(user1)
|
||||||
|
|
||||||
result, err := ldapAuther.getGrafanaUserFor(&ldapUserInfo{})
|
result, err := ldapAuther.GetGrafanaUserFor(&LdapUserInfo{})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(result, ShouldEqual, user1)
|
So(result, ShouldEqual, user1)
|
||||||
})
|
})
|
||||||
@@ -46,7 +48,7 @@ func TestLdapAuther(t *testing.T) {
|
|||||||
|
|
||||||
sc.userQueryReturns(user1)
|
sc.userQueryReturns(user1)
|
||||||
|
|
||||||
result, err := ldapAuther.getGrafanaUserFor(&ldapUserInfo{MemberOf: []string{"cn=users"}})
|
result, err := ldapAuther.GetGrafanaUserFor(&LdapUserInfo{MemberOf: []string{"cn=users"}})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(result, ShouldEqual, user1)
|
So(result, ShouldEqual, user1)
|
||||||
})
|
})
|
||||||
@@ -62,7 +64,7 @@ func TestLdapAuther(t *testing.T) {
|
|||||||
|
|
||||||
sc.userQueryReturns(nil)
|
sc.userQueryReturns(nil)
|
||||||
|
|
||||||
result, err := ldapAuther.getGrafanaUserFor(&ldapUserInfo{
|
result, err := ldapAuther.GetGrafanaUserFor(&LdapUserInfo{
|
||||||
Username: "torkelo",
|
Username: "torkelo",
|
||||||
Email: "my@email.com",
|
Email: "my@email.com",
|
||||||
MemberOf: []string{"cn=editor"},
|
MemberOf: []string{"cn=editor"},
|
||||||
@@ -93,7 +95,7 @@ func TestLdapAuther(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
sc.userOrgsQueryReturns([]*m.UserOrgDTO{})
|
sc.userOrgsQueryReturns([]*m.UserOrgDTO{})
|
||||||
err := ldapAuther.syncOrgRoles(&m.User{}, &ldapUserInfo{
|
err := ldapAuther.SyncOrgRoles(&m.User{}, &LdapUserInfo{
|
||||||
MemberOf: []string{"cn=users"},
|
MemberOf: []string{"cn=users"},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -112,7 +114,7 @@ func TestLdapAuther(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_EDITOR}})
|
sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_EDITOR}})
|
||||||
err := ldapAuther.syncOrgRoles(&m.User{}, &ldapUserInfo{
|
err := ldapAuther.SyncOrgRoles(&m.User{}, &LdapUserInfo{
|
||||||
MemberOf: []string{"cn=users"},
|
MemberOf: []string{"cn=users"},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -131,7 +133,7 @@ func TestLdapAuther(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_EDITOR}})
|
sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_EDITOR}})
|
||||||
err := ldapAuther.syncOrgRoles(&m.User{}, &ldapUserInfo{
|
err := ldapAuther.SyncOrgRoles(&m.User{}, &LdapUserInfo{
|
||||||
MemberOf: []string{"cn=other"},
|
MemberOf: []string{"cn=other"},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -150,7 +152,7 @@ func TestLdapAuther(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_EDITOR}})
|
sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_EDITOR}})
|
||||||
err := ldapAuther.syncOrgRoles(&m.User{}, &ldapUserInfo{
|
err := ldapAuther.SyncOrgRoles(&m.User{}, &LdapUserInfo{
|
||||||
MemberOf: []string{"cn=users"},
|
MemberOf: []string{"cn=users"},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -170,7 +172,7 @@ func TestLdapAuther(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_ADMIN}})
|
sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_ADMIN}})
|
||||||
err := ldapAuther.syncOrgRoles(&m.User{}, &ldapUserInfo{
|
err := ldapAuther.SyncOrgRoles(&m.User{}, &LdapUserInfo{
|
||||||
MemberOf: []string{"cn=admins"},
|
MemberOf: []string{"cn=admins"},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -189,7 +191,7 @@ func TestLdapAuther(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
sc.userOrgsQueryReturns([]*m.UserOrgDTO{})
|
sc.userOrgsQueryReturns([]*m.UserOrgDTO{})
|
||||||
err := ldapAuther.syncOrgRoles(&m.User{}, &ldapUserInfo{
|
err := ldapAuther.SyncOrgRoles(&m.User{}, &LdapUserInfo{
|
||||||
MemberOf: []string{"cn=admins"},
|
MemberOf: []string{"cn=admins"},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -200,6 +202,95 @@ func TestLdapAuther(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("When calling SyncSignedInUser", t, func() {
|
||||||
|
|
||||||
|
mockLdapConnection := &mockLdapConn{}
|
||||||
|
ldapAuther := NewLdapAuthenticator(
|
||||||
|
&LdapServerConf{
|
||||||
|
Host: "",
|
||||||
|
RootCACert: "",
|
||||||
|
LdapGroups: []*LdapGroupToOrgRole{
|
||||||
|
{GroupDN: "*", OrgRole: "Admin"},
|
||||||
|
},
|
||||||
|
Attr: LdapAttributeMap{
|
||||||
|
Username: "username",
|
||||||
|
Surname: "surname",
|
||||||
|
Email: "email",
|
||||||
|
Name: "name",
|
||||||
|
MemberOf: "memberof",
|
||||||
|
},
|
||||||
|
SearchBaseDNs: []string{"BaseDNHere"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
dialCalled := false
|
||||||
|
ldapDial = func(network, addr string) (ILdapConn, error) {
|
||||||
|
dialCalled = true
|
||||||
|
return mockLdapConnection, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := ldap.Entry{
|
||||||
|
DN: "dn", Attributes: []*ldap.EntryAttribute{
|
||||||
|
{Name: "username", Values: []string{"roelgerrits"}},
|
||||||
|
{Name: "surname", Values: []string{"Gerrits"}},
|
||||||
|
{Name: "email", Values: []string{"roel@test.com"}},
|
||||||
|
{Name: "name", Values: []string{"Roel"}},
|
||||||
|
{Name: "memberof", Values: []string{"admins"}},
|
||||||
|
}}
|
||||||
|
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
|
||||||
|
mockLdapConnection.setSearchResult(&result)
|
||||||
|
|
||||||
|
ldapAutherScenario("When ldapUser found call syncInfo and orgRoles", func(sc *scenarioContext) {
|
||||||
|
// arrange
|
||||||
|
signedInUser := &m.SignedInUser{
|
||||||
|
Email: "roel@test.net",
|
||||||
|
UserId: 1,
|
||||||
|
Name: "Roel Gerrits",
|
||||||
|
Login: "roelgerrits",
|
||||||
|
}
|
||||||
|
|
||||||
|
sc.userOrgsQueryReturns([]*m.UserOrgDTO{})
|
||||||
|
|
||||||
|
// act
|
||||||
|
syncErrResult := ldapAuther.SyncSignedInUser(signedInUser)
|
||||||
|
|
||||||
|
// assert
|
||||||
|
So(dialCalled, ShouldBeTrue)
|
||||||
|
So(syncErrResult, ShouldBeNil)
|
||||||
|
// User should be searched in ldap
|
||||||
|
So(mockLdapConnection.searchCalled, ShouldBeTrue)
|
||||||
|
// Info should be updated (email differs)
|
||||||
|
So(sc.updateUserCmd.Email, ShouldEqual, "roel@test.com")
|
||||||
|
// User should have admin privileges
|
||||||
|
So(sc.addOrgUserCmd.UserId, ShouldEqual, 1)
|
||||||
|
So(sc.addOrgUserCmd.Role, ShouldEqual, "Admin")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockLdapConn struct {
|
||||||
|
result *ldap.SearchResult
|
||||||
|
searchCalled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockLdapConn) Bind(username, password string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockLdapConn) Close() {}
|
||||||
|
|
||||||
|
func (c *mockLdapConn) setSearchResult(result *ldap.SearchResult) {
|
||||||
|
c.result = result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockLdapConn) Search(*ldap.SearchRequest) (*ldap.SearchResult, error) {
|
||||||
|
c.searchCalled = true
|
||||||
|
return c.result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockLdapConn) StartTLS(*tls.Config) error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ldapAutherScenario(desc string, fn scenarioFunc) {
|
func ldapAutherScenario(desc string, fn scenarioFunc) {
|
||||||
@@ -229,6 +320,11 @@ func ldapAutherScenario(desc string, fn scenarioFunc) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(cmd *m.UpdateUserCommand) error {
|
||||||
|
sc.updateUserCmd = cmd
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
fn(sc)
|
fn(sc)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -238,6 +334,7 @@ type scenarioContext struct {
|
|||||||
addOrgUserCmd *m.AddOrgUserCommand
|
addOrgUserCmd *m.AddOrgUserCommand
|
||||||
updateOrgUserCmd *m.UpdateOrgUserCommand
|
updateOrgUserCmd *m.UpdateOrgUserCommand
|
||||||
removeOrgUserCmd *m.RemoveOrgUserCommand
|
removeOrgUserCmd *m.RemoveOrgUserCommand
|
||||||
|
updateUserCmd *m.UpdateUserCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *scenarioContext) userQueryReturns(user *m.User) {
|
func (sc *scenarioContext) userQueryReturns(user *m.User) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package login
|
package login
|
||||||
|
|
||||||
type ldapUserInfo struct {
|
type LdapUserInfo struct {
|
||||||
DN string
|
DN string
|
||||||
FirstName string
|
FirstName string
|
||||||
LastName string
|
LastName string
|
||||||
@@ -9,7 +9,7 @@ type ldapUserInfo struct {
|
|||||||
MemberOf []string
|
MemberOf []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ldapUserInfo) isMemberOf(group string) bool {
|
func (u *LdapUserInfo) isMemberOf(group string) bool {
|
||||||
if group == "*" {
|
if group == "*" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ type LdapGroupToOrgRole struct {
|
|||||||
OrgRole m.RoleType `toml:"org_role"`
|
OrgRole m.RoleType `toml:"org_role"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var ldapCfg LdapConfig
|
var LdapCfg LdapConfig
|
||||||
var ldapLogger log.Logger = log.New("ldap")
|
var ldapLogger log.Logger = log.New("ldap")
|
||||||
|
|
||||||
func loadLdapConfig() {
|
func loadLdapConfig() {
|
||||||
@@ -60,19 +60,19 @@ func loadLdapConfig() {
|
|||||||
|
|
||||||
ldapLogger.Info("Ldap enabled, reading config file", "file", setting.LdapConfigFile)
|
ldapLogger.Info("Ldap enabled, reading config file", "file", setting.LdapConfigFile)
|
||||||
|
|
||||||
_, err := toml.DecodeFile(setting.LdapConfigFile, &ldapCfg)
|
_, err := toml.DecodeFile(setting.LdapConfigFile, &LdapCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ldapLogger.Crit("Failed to load ldap config file", "error", err)
|
ldapLogger.Crit("Failed to load ldap config file", "error", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ldapCfg.Servers) == 0 {
|
if len(LdapCfg.Servers) == 0 {
|
||||||
ldapLogger.Crit("ldap enabled but no ldap servers defined in config file")
|
ldapLogger.Crit("ldap enabled but no ldap servers defined in config file")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// set default org id
|
// set default org id
|
||||||
for _, server := range ldapCfg.Servers {
|
for _, server := range LdapCfg.Servers {
|
||||||
assertNotEmptyCfg(server.SearchFilter, "search_filter")
|
assertNotEmptyCfg(server.SearchFilter, "search_filter")
|
||||||
assertNotEmptyCfg(server.SearchBaseDNs, "search_base_dns")
|
assertNotEmptyCfg(server.SearchBaseDNs, "search_base_dns")
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
"github.com/grafana/grafana/pkg/log"
|
"github.com/grafana/grafana/pkg/log"
|
||||||
|
"github.com/grafana/grafana/pkg/login"
|
||||||
m "github.com/grafana/grafana/pkg/models"
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
@@ -17,6 +23,12 @@ func initContextWithAuthProxy(ctx *Context) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if auth proxy ip(s) defined, check if request comes from one of those
|
||||||
|
if err := checkAuthenticationProxy(ctx, proxyHeaderValue); err != nil {
|
||||||
|
ctx.Handle(407, "Proxy authentication required", err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
query := getSignedInUserQueryForProxyAuth(proxyHeaderValue)
|
query := getSignedInUserQueryForProxyAuth(proxyHeaderValue)
|
||||||
if err := bus.Dispatch(query); err != nil {
|
if err := bus.Dispatch(query); err != nil {
|
||||||
if err != m.ErrUserNotFound {
|
if err != m.ErrUserNotFound {
|
||||||
@@ -26,6 +38,10 @@ func initContextWithAuthProxy(ctx *Context) bool {
|
|||||||
|
|
||||||
if setting.AuthProxyAutoSignUp {
|
if setting.AuthProxyAutoSignUp {
|
||||||
cmd := getCreateUserCommandForProxyAuth(proxyHeaderValue)
|
cmd := getCreateUserCommandForProxyAuth(proxyHeaderValue)
|
||||||
|
if setting.LdapEnabled {
|
||||||
|
cmd.SkipOrgSetup = true
|
||||||
|
}
|
||||||
|
|
||||||
if err := bus.Dispatch(cmd); err != nil {
|
if err := bus.Dispatch(cmd); err != nil {
|
||||||
ctx.Handle(500, "Failed to create user specified in auth proxy header", err)
|
ctx.Handle(500, "Failed to create user specified in auth proxy header", err)
|
||||||
return true
|
return true
|
||||||
@@ -46,6 +62,30 @@ func initContextWithAuthProxy(ctx *Context) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure that we cannot share a session between different users!
|
||||||
|
if getRequestUserId(ctx) > 0 && getRequestUserId(ctx) != query.Result.UserId {
|
||||||
|
// remove session
|
||||||
|
if err := ctx.Session.Destory(ctx); err != nil {
|
||||||
|
log.Error(3, "Failed to destory session, err")
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize a new session
|
||||||
|
if err := ctx.Session.Start(ctx); err != nil {
|
||||||
|
log.Error(3, "Failed to start session", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When ldap is enabled, sync userinfo and org roles
|
||||||
|
if err := syncGrafanaUserWithLdapUser(ctx, query); err != nil {
|
||||||
|
if err == login.ErrInvalidCredentials {
|
||||||
|
ctx.Handle(500, "Unable to authenticate user", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Handle(500, "Failed to sync user", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
ctx.SignedInUser = query.Result
|
ctx.SignedInUser = query.Result
|
||||||
ctx.IsSignedIn = true
|
ctx.IsSignedIn = true
|
||||||
ctx.Session.Set(SESS_KEY_USERID, ctx.UserId)
|
ctx.Session.Set(SESS_KEY_USERID, ctx.UserId)
|
||||||
@@ -53,6 +93,56 @@ func initContextWithAuthProxy(ctx *Context) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var syncGrafanaUserWithLdapUser = func(ctx *Context, query *m.GetSignedInUserQuery) error {
|
||||||
|
if setting.LdapEnabled {
|
||||||
|
expireEpoch := time.Now().Add(time.Duration(-setting.AuthProxyLdapSyncTtl) * time.Minute).Unix()
|
||||||
|
|
||||||
|
var lastLdapSync int64
|
||||||
|
if lastLdapSyncInSession := ctx.Session.Get(SESS_KEY_LASTLDAPSYNC); lastLdapSyncInSession != nil {
|
||||||
|
lastLdapSync = lastLdapSyncInSession.(int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastLdapSync < expireEpoch {
|
||||||
|
ldapCfg := login.LdapCfg
|
||||||
|
|
||||||
|
for _, server := range ldapCfg.Servers {
|
||||||
|
auther := login.NewLdapAuthenticator(server)
|
||||||
|
if err := auther.SyncSignedInUser(query.Result); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Session.Set(SESS_KEY_LASTLDAPSYNC, time.Now().Unix())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkAuthenticationProxy(ctx *Context, proxyHeaderValue string) error {
|
||||||
|
if len(strings.TrimSpace(setting.AuthProxyWhitelist)) > 0 {
|
||||||
|
proxies := strings.Split(setting.AuthProxyWhitelist, ",")
|
||||||
|
remoteAddrSplit := strings.Split(ctx.Req.RemoteAddr, ":")
|
||||||
|
sourceIP := remoteAddrSplit[0]
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, proxyIP := range proxies {
|
||||||
|
if sourceIP == strings.TrimSpace(proxyIP) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
msg := fmt.Sprintf("Request for user (%s) is not from the authentication proxy", proxyHeaderValue)
|
||||||
|
err := errors.New(msg)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func getSignedInUserQueryForProxyAuth(headerVal string) *m.GetSignedInUserQuery {
|
func getSignedInUserQueryForProxyAuth(headerVal string) *m.GetSignedInUserQuery {
|
||||||
query := m.GetSignedInUserQuery{}
|
query := m.GetSignedInUserQuery{}
|
||||||
if setting.AuthProxyHeaderProperty == "username" {
|
if setting.AuthProxyHeaderProperty == "username" {
|
||||||
|
|||||||
123
pkg/middleware/auth_proxy_test.go
Normal file
123
pkg/middleware/auth_proxy_test.go
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/login"
|
||||||
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAuthProxyWithLdapEnabled(t *testing.T) {
|
||||||
|
Convey("When calling sync grafana user with ldap user", t, func() {
|
||||||
|
|
||||||
|
setting.LdapEnabled = true
|
||||||
|
setting.AuthProxyLdapSyncTtl = 60
|
||||||
|
|
||||||
|
servers := []*login.LdapServerConf{{Host: "127.0.0.1"}}
|
||||||
|
login.LdapCfg = login.LdapConfig{Servers: servers}
|
||||||
|
mockLdapAuther := mockLdapAuthenticator{}
|
||||||
|
|
||||||
|
login.NewLdapAuthenticator = func(server *login.LdapServerConf) login.ILdapAuther {
|
||||||
|
return &mockLdapAuther
|
||||||
|
}
|
||||||
|
|
||||||
|
signedInUser := m.SignedInUser{}
|
||||||
|
query := m.GetSignedInUserQuery{Result: &signedInUser}
|
||||||
|
|
||||||
|
Convey("When session variable lastLdapSync not set, call syncSignedInUser and set lastLdapSync", func() {
|
||||||
|
// arrange
|
||||||
|
session := mockSession{}
|
||||||
|
ctx := Context{Session: &session}
|
||||||
|
So(session.Get(SESS_KEY_LASTLDAPSYNC), ShouldBeNil)
|
||||||
|
|
||||||
|
// act
|
||||||
|
syncGrafanaUserWithLdapUser(&ctx, &query)
|
||||||
|
|
||||||
|
// assert
|
||||||
|
So(mockLdapAuther.syncSignedInUserCalled, ShouldBeTrue)
|
||||||
|
So(session.Get(SESS_KEY_LASTLDAPSYNC), ShouldBeGreaterThan, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("When session variable not expired, don't sync and don't change session var", func() {
|
||||||
|
// arrange
|
||||||
|
session := mockSession{}
|
||||||
|
ctx := Context{Session: &session}
|
||||||
|
now := time.Now().Unix()
|
||||||
|
session.Set(SESS_KEY_LASTLDAPSYNC, now)
|
||||||
|
|
||||||
|
// act
|
||||||
|
syncGrafanaUserWithLdapUser(&ctx, &query)
|
||||||
|
|
||||||
|
// assert
|
||||||
|
So(session.Get(SESS_KEY_LASTLDAPSYNC), ShouldEqual, now)
|
||||||
|
So(mockLdapAuther.syncSignedInUserCalled, ShouldBeFalse)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("When lastldapsync is expired, session variable should be updated", func() {
|
||||||
|
// arrange
|
||||||
|
session := mockSession{}
|
||||||
|
ctx := Context{Session: &session}
|
||||||
|
expiredTime := time.Now().Add(time.Duration(-120) * time.Minute).Unix()
|
||||||
|
session.Set(SESS_KEY_LASTLDAPSYNC, expiredTime)
|
||||||
|
|
||||||
|
// act
|
||||||
|
syncGrafanaUserWithLdapUser(&ctx, &query)
|
||||||
|
|
||||||
|
// assert
|
||||||
|
So(session.Get(SESS_KEY_LASTLDAPSYNC), ShouldBeGreaterThan, expiredTime)
|
||||||
|
So(mockLdapAuther.syncSignedInUserCalled, ShouldBeTrue)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockSession struct {
|
||||||
|
value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *mockSession) Start(c *Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *mockSession) Set(k interface{}, v interface{}) error {
|
||||||
|
s.value = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *mockSession) Get(k interface{}) interface{} {
|
||||||
|
return s.value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *mockSession) ID() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *mockSession) Release() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *mockSession) Destory(c *Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockLdapAuthenticator struct {
|
||||||
|
syncSignedInUserCalled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *mockLdapAuthenticator) Login(query *login.LoginUserQuery) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *mockLdapAuthenticator) SyncSignedInUser(signedInUser *m.SignedInUser) error {
|
||||||
|
a.syncSignedInUserCalled = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *mockLdapAuthenticator) GetGrafanaUserFor(ldapUser *login.LdapUserInfo) (*m.User, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (a *mockLdapAuthenticator) SyncOrgRoles(user *m.User, ldapUser *login.LdapUserInfo) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -213,6 +213,99 @@ func TestMiddlewareContext(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
middlewareScenario("When auth_proxy is enabled and request RemoteAddr is not trusted", func(sc *scenarioContext) {
|
||||||
|
setting.AuthProxyEnabled = true
|
||||||
|
setting.AuthProxyHeaderName = "X-WEBAUTH-USER"
|
||||||
|
setting.AuthProxyHeaderProperty = "username"
|
||||||
|
setting.AuthProxyWhitelist = "192.168.1.1, 192.168.2.1"
|
||||||
|
|
||||||
|
sc.fakeReq("GET", "/")
|
||||||
|
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo")
|
||||||
|
sc.req.RemoteAddr = "192.168.3.1:12345"
|
||||||
|
sc.exec()
|
||||||
|
|
||||||
|
Convey("should return 407 status code", func() {
|
||||||
|
So(sc.resp.Code, ShouldEqual, 407)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
middlewareScenario("When auth_proxy is enabled and request RemoteAddr is trusted", func(sc *scenarioContext) {
|
||||||
|
setting.AuthProxyEnabled = true
|
||||||
|
setting.AuthProxyHeaderName = "X-WEBAUTH-USER"
|
||||||
|
setting.AuthProxyHeaderProperty = "username"
|
||||||
|
setting.AuthProxyWhitelist = "192.168.1.1, 192.168.2.1"
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
|
||||||
|
query.Result = &m.SignedInUser{OrgId: 4, UserId: 33}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
sc.fakeReq("GET", "/")
|
||||||
|
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo")
|
||||||
|
sc.req.RemoteAddr = "192.168.2.1:12345"
|
||||||
|
sc.exec()
|
||||||
|
|
||||||
|
Convey("Should init context with user info", func() {
|
||||||
|
So(sc.context.IsSignedIn, ShouldBeTrue)
|
||||||
|
So(sc.context.UserId, ShouldEqual, 33)
|
||||||
|
So(sc.context.OrgId, ShouldEqual, 4)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
middlewareScenario("When session exists for previous user, create a new session", func(sc *scenarioContext) {
|
||||||
|
setting.AuthProxyEnabled = true
|
||||||
|
setting.AuthProxyHeaderName = "X-WEBAUTH-USER"
|
||||||
|
setting.AuthProxyHeaderProperty = "username"
|
||||||
|
setting.AuthProxyWhitelist = ""
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
|
||||||
|
query.Result = &m.SignedInUser{OrgId: 4, UserId: 32}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// create session
|
||||||
|
sc.fakeReq("GET", "/").handler(func(c *Context) {
|
||||||
|
c.Session.Set(SESS_KEY_USERID, int64(33))
|
||||||
|
}).exec()
|
||||||
|
|
||||||
|
oldSessionID := sc.context.Session.ID()
|
||||||
|
|
||||||
|
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo")
|
||||||
|
sc.exec()
|
||||||
|
|
||||||
|
newSessionID := sc.context.Session.ID()
|
||||||
|
|
||||||
|
Convey("Should not share session with other user", func() {
|
||||||
|
So(oldSessionID, ShouldNotEqual, newSessionID)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
middlewareScenario("When auth_proxy and ldap enabled call sync with ldap user", func(sc *scenarioContext) {
|
||||||
|
setting.AuthProxyEnabled = true
|
||||||
|
setting.AuthProxyHeaderName = "X-WEBAUTH-USER"
|
||||||
|
setting.AuthProxyHeaderProperty = "username"
|
||||||
|
setting.AuthProxyWhitelist = ""
|
||||||
|
setting.LdapEnabled = true
|
||||||
|
|
||||||
|
called := false
|
||||||
|
syncGrafanaUserWithLdapUser = func(ctx *Context, query *m.GetSignedInUserQuery) error {
|
||||||
|
called = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
|
||||||
|
query.Result = &m.SignedInUser{OrgId: 4, UserId: 32}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
sc.fakeReq("GET", "/")
|
||||||
|
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo")
|
||||||
|
sc.exec()
|
||||||
|
|
||||||
|
Convey("Should call syncGrafanaUserWithLdapUser", func() {
|
||||||
|
So(called, ShouldBeTrue)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SESS_KEY_USERID = "uid"
|
SESS_KEY_USERID = "uid"
|
||||||
SESS_KEY_OAUTH_STATE = "state"
|
SESS_KEY_OAUTH_STATE = "state"
|
||||||
|
SESS_KEY_APIKEY = "apikey_id" // used for render requests with api keys
|
||||||
|
SESS_KEY_LASTLDAPSYNC = "last_ldap_sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var sessionManager *session.Manager
|
var sessionManager *session.Manager
|
||||||
|
|||||||
@@ -108,6 +108,8 @@ var (
|
|||||||
AuthProxyHeaderName string
|
AuthProxyHeaderName string
|
||||||
AuthProxyHeaderProperty string
|
AuthProxyHeaderProperty string
|
||||||
AuthProxyAutoSignUp bool
|
AuthProxyAutoSignUp bool
|
||||||
|
AuthProxyLdapSyncTtl int
|
||||||
|
AuthProxyWhitelist string
|
||||||
|
|
||||||
// Basic Auth
|
// Basic Auth
|
||||||
BasicAuthEnabled bool
|
BasicAuthEnabled bool
|
||||||
@@ -546,7 +548,10 @@ func NewConfigContext(args *CommandLineArgs) error {
|
|||||||
AuthProxyHeaderName = authProxy.Key("header_name").String()
|
AuthProxyHeaderName = authProxy.Key("header_name").String()
|
||||||
AuthProxyHeaderProperty = authProxy.Key("header_property").String()
|
AuthProxyHeaderProperty = authProxy.Key("header_property").String()
|
||||||
AuthProxyAutoSignUp = authProxy.Key("auto_sign_up").MustBool(true)
|
AuthProxyAutoSignUp = authProxy.Key("auto_sign_up").MustBool(true)
|
||||||
|
AuthProxyLdapSyncTtl = authProxy.Key("ldap_sync_ttl").MustInt()
|
||||||
|
AuthProxyWhitelist = authProxy.Key("whitelist").String()
|
||||||
|
|
||||||
|
// basic auth
|
||||||
authBasic := Cfg.Section("auth.basic")
|
authBasic := Cfg.Section("auth.basic")
|
||||||
BasicAuthEnabled = authBasic.Key("enabled").MustBool(true)
|
BasicAuthEnabled = authBasic.Key("enabled").MustBool(true)
|
||||||
|
|
||||||
|
|||||||
27
public/views/407.html
Normal file
27
public/views/407.html
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
|
||||||
|
<title>Grafana</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="[[.AppSubUrl]]/css/grafana.dark.min.css" title="Dark">
|
||||||
|
<link rel="icon" type="image/png" href="[[.AppSubUrl]]/img/fav32.png">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="gf-box" style="margin: 200px auto 0 auto; width: 500px;">
|
||||||
|
<div class="gf-box-header">
|
||||||
|
<span class="gf-box-title">
|
||||||
|
Proxy authentication required
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gf-box-body">
|
||||||
|
<h4>Proxy authenticaion required</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user