mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
LDAP: reduce API and allow its extension (#17209)
* Removes Add/Remove methods * Publicise necessary fields and methods so we could extend it * Publicise mock API * More comments and additional simplifications * Sync with master Still having low coverage :/ - should be addressed in #17208
This commit is contained in:
parent
5884e235fc
commit
de92c360a1
@ -29,18 +29,17 @@ type IConnection interface {
|
||||
// IServer is interface for LDAP authorization
|
||||
type IServer interface {
|
||||
Login(*models.LoginUserQuery) (*models.ExternalUserInfo, error)
|
||||
Add(string, map[string][]string) error
|
||||
Remove(string) error
|
||||
Users([]string) ([]*models.ExternalUserInfo, error)
|
||||
ExtractGrafanaUser(*UserInfo) (*models.ExternalUserInfo, error)
|
||||
InitialBind(string, string) error
|
||||
Dial() error
|
||||
Close()
|
||||
}
|
||||
|
||||
// Server is basic struct of LDAP authorization
|
||||
type Server struct {
|
||||
config *ServerConfig
|
||||
connection IConnection
|
||||
Config *ServerConfig
|
||||
Connection IConnection
|
||||
requireSecondBind bool
|
||||
log log.Logger
|
||||
}
|
||||
@ -49,7 +48,6 @@ var (
|
||||
|
||||
// ErrInvalidCredentials is returned if username and password do not match
|
||||
ErrInvalidCredentials = errors.New("Invalid Username or Password")
|
||||
ErrLDAPUserNotFound = errors.New("LDAP user not found")
|
||||
)
|
||||
|
||||
var dial = func(network, addr string) (IConnection, error) {
|
||||
@ -59,7 +57,7 @@ var dial = func(network, addr string) (IConnection, error) {
|
||||
// New creates the new LDAP auth
|
||||
func New(config *ServerConfig) IServer {
|
||||
return &Server{
|
||||
config: config,
|
||||
Config: config,
|
||||
log: log.New("ldap"),
|
||||
}
|
||||
}
|
||||
@ -68,9 +66,9 @@ func New(config *ServerConfig) IServer {
|
||||
func (server *Server) Dial() error {
|
||||
var err error
|
||||
var certPool *x509.CertPool
|
||||
if server.config.RootCACert != "" {
|
||||
if server.Config.RootCACert != "" {
|
||||
certPool = x509.NewCertPool()
|
||||
for _, caCertFile := range strings.Split(server.config.RootCACert, " ") {
|
||||
for _, caCertFile := range strings.Split(server.Config.RootCACert, " ") {
|
||||
pem, err := ioutil.ReadFile(caCertFile)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -81,35 +79,35 @@ func (server *Server) Dial() error {
|
||||
}
|
||||
}
|
||||
var clientCert tls.Certificate
|
||||
if server.config.ClientCert != "" && server.config.ClientKey != "" {
|
||||
clientCert, err = tls.LoadX509KeyPair(server.config.ClientCert, server.config.ClientKey)
|
||||
if server.Config.ClientCert != "" && server.Config.ClientKey != "" {
|
||||
clientCert, err = tls.LoadX509KeyPair(server.Config.ClientCert, server.Config.ClientKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, host := range strings.Split(server.config.Host, " ") {
|
||||
address := fmt.Sprintf("%s:%d", host, server.config.Port)
|
||||
if server.config.UseSSL {
|
||||
for _, host := range strings.Split(server.Config.Host, " ") {
|
||||
address := fmt.Sprintf("%s:%d", host, server.Config.Port)
|
||||
if server.Config.UseSSL {
|
||||
tlsCfg := &tls.Config{
|
||||
InsecureSkipVerify: server.config.SkipVerifySSL,
|
||||
InsecureSkipVerify: server.Config.SkipVerifySSL,
|
||||
ServerName: host,
|
||||
RootCAs: certPool,
|
||||
}
|
||||
if len(clientCert.Certificate) > 0 {
|
||||
tlsCfg.Certificates = append(tlsCfg.Certificates, clientCert)
|
||||
}
|
||||
if server.config.StartTLS {
|
||||
server.connection, err = dial("tcp", address)
|
||||
if server.Config.StartTLS {
|
||||
server.Connection, err = dial("tcp", address)
|
||||
if err == nil {
|
||||
if err = server.connection.StartTLS(tlsCfg); err == nil {
|
||||
if err = server.Connection.StartTLS(tlsCfg); err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
server.connection, err = ldap.DialTLS("tcp", address, tlsCfg)
|
||||
server.Connection, err = ldap.DialTLS("tcp", address, tlsCfg)
|
||||
}
|
||||
} else {
|
||||
server.connection, err = dial("tcp", address)
|
||||
server.Connection, err = dial("tcp", address)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
@ -121,16 +119,16 @@ func (server *Server) Dial() error {
|
||||
|
||||
// Close closes the LDAP connection
|
||||
func (server *Server) Close() {
|
||||
server.connection.Close()
|
||||
server.Connection.Close()
|
||||
}
|
||||
|
||||
// Log in user by searching and serializing it
|
||||
// Login user by searching and serializing it
|
||||
func (server *Server) Login(query *models.LoginUserQuery) (
|
||||
*models.ExternalUserInfo, error,
|
||||
) {
|
||||
|
||||
// Perform initial authentication
|
||||
err := server.initialBind(query.Username, query.Password)
|
||||
err := server.InitialBind(query.Username, query.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -160,56 +158,6 @@ func (server *Server) Login(query *models.LoginUserQuery) (
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// Add adds stuff to LDAP
|
||||
func (server *Server) Add(dn string, values map[string][]string) error {
|
||||
err := server.initialBind(
|
||||
server.config.BindDN,
|
||||
server.config.BindPassword,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attributes := make([]ldap.Attribute, 0)
|
||||
for key, value := range values {
|
||||
attributes = append(attributes, ldap.Attribute{
|
||||
Type: key,
|
||||
Vals: value,
|
||||
})
|
||||
}
|
||||
|
||||
request := &ldap.AddRequest{
|
||||
DN: dn,
|
||||
Attributes: attributes,
|
||||
}
|
||||
|
||||
err = server.connection.Add(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes stuff from LDAP
|
||||
func (server *Server) Remove(dn string) error {
|
||||
err := server.initialBind(
|
||||
server.config.BindDN,
|
||||
server.config.BindPassword,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
request := ldap.NewDelRequest(dn, nil)
|
||||
err = server.connection.Del(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Users gets LDAP users
|
||||
func (server *Server) Users(logins []string) (
|
||||
[]*models.ExternalUserInfo,
|
||||
@ -217,10 +165,10 @@ func (server *Server) Users(logins []string) (
|
||||
) {
|
||||
var result *ldap.SearchResult
|
||||
var err error
|
||||
var config = server.config
|
||||
var Config = server.Config
|
||||
|
||||
for _, base := range config.SearchBaseDNs {
|
||||
result, err = server.connection.Search(
|
||||
for _, base := range Config.SearchBaseDNs {
|
||||
result, err = server.Connection.Search(
|
||||
server.getSearchRequest(base, logins),
|
||||
)
|
||||
if err != nil {
|
||||
@ -254,7 +202,7 @@ func (server *Server) ExtractGrafanaUser(user *UserInfo) (*models.ExternalUserIn
|
||||
// If there are no ldap group mappings access is true
|
||||
// otherwise a single group must match
|
||||
func (server *Server) validateGrafanaUser(user *models.ExternalUserInfo) error {
|
||||
if len(server.config.Groups) > 0 && len(user.OrgRoles) < 1 {
|
||||
if len(server.Config.Groups) > 0 && len(user.OrgRoles) < 1 {
|
||||
server.log.Error(
|
||||
"user does not belong in any of the specified LDAP groups",
|
||||
"username", user.Login,
|
||||
@ -301,7 +249,7 @@ func (server *Server) getSearchRequest(
|
||||
) *ldap.SearchRequest {
|
||||
attributes := []string{}
|
||||
|
||||
inputs := server.config.Attr
|
||||
inputs := server.Config.Attr
|
||||
attributes = appendIfNotEmpty(
|
||||
attributes,
|
||||
inputs.Username,
|
||||
@ -314,7 +262,7 @@ func (server *Server) getSearchRequest(
|
||||
search := ""
|
||||
for _, login := range logins {
|
||||
query := strings.Replace(
|
||||
server.config.SearchFilter,
|
||||
server.Config.SearchFilter,
|
||||
"%s", ldap.EscapeFilter(login),
|
||||
-1,
|
||||
)
|
||||
@ -347,7 +295,7 @@ func (server *Server) buildGrafanaUser(user *UserInfo) *models.ExternalUserInfo
|
||||
OrgRoles: map[int64]models.RoleType{},
|
||||
}
|
||||
|
||||
for _, group := range server.config.Groups {
|
||||
for _, group := range server.Config.Groups {
|
||||
// only use the first match for each org
|
||||
if extUser.OrgRoles[group.OrgId] != "" {
|
||||
continue
|
||||
@ -366,15 +314,15 @@ func (server *Server) buildGrafanaUser(user *UserInfo) *models.ExternalUserInfo
|
||||
|
||||
func (server *Server) serverBind() error {
|
||||
bindFn := func() error {
|
||||
return server.connection.Bind(
|
||||
server.config.BindDN,
|
||||
server.config.BindPassword,
|
||||
return server.Connection.Bind(
|
||||
server.Config.BindDN,
|
||||
server.Config.BindPassword,
|
||||
)
|
||||
}
|
||||
|
||||
if server.config.BindPassword == "" {
|
||||
if server.Config.BindPassword == "" {
|
||||
bindFn = func() error {
|
||||
return server.connection.UnauthenticatedBind(server.config.BindDN)
|
||||
return server.Connection.UnauthenticatedBind(server.Config.BindDN)
|
||||
}
|
||||
}
|
||||
|
||||
@ -397,7 +345,7 @@ func (server *Server) secondBind(
|
||||
user *models.ExternalUserInfo,
|
||||
userPassword string,
|
||||
) error {
|
||||
err := server.connection.Bind(user.AuthId, userPassword)
|
||||
err := server.Connection.Bind(user.AuthId, userPassword)
|
||||
if err != nil {
|
||||
server.log.Info("Second bind failed", "error", err)
|
||||
|
||||
@ -412,24 +360,25 @@ func (server *Server) secondBind(
|
||||
return nil
|
||||
}
|
||||
|
||||
func (server *Server) initialBind(username, userPassword string) error {
|
||||
if server.config.BindPassword != "" || server.config.BindDN == "" {
|
||||
userPassword = server.config.BindPassword
|
||||
// InitialBind intiates first bind to LDAP server
|
||||
func (server *Server) InitialBind(username, userPassword string) error {
|
||||
if server.Config.BindPassword != "" || server.Config.BindDN == "" {
|
||||
userPassword = server.Config.BindPassword
|
||||
server.requireSecondBind = true
|
||||
}
|
||||
|
||||
bindPath := server.config.BindDN
|
||||
bindPath := server.Config.BindDN
|
||||
if strings.Contains(bindPath, "%s") {
|
||||
bindPath = fmt.Sprintf(server.config.BindDN, username)
|
||||
bindPath = fmt.Sprintf(server.Config.BindDN, username)
|
||||
}
|
||||
|
||||
bindFn := func() error {
|
||||
return server.connection.Bind(bindPath, userPassword)
|
||||
return server.Connection.Bind(bindPath, userPassword)
|
||||
}
|
||||
|
||||
if userPassword == "" {
|
||||
bindFn = func() error {
|
||||
return server.connection.UnauthenticatedBind(bindPath)
|
||||
return server.Connection.UnauthenticatedBind(bindPath)
|
||||
}
|
||||
}
|
||||
|
||||
@ -451,16 +400,16 @@ func (server *Server) initialBind(username, userPassword string) error {
|
||||
func (server *Server) requestMemberOf(searchResult *ldap.SearchResult) ([]string, error) {
|
||||
var memberOf []string
|
||||
|
||||
for _, groupSearchBase := range server.config.GroupSearchBaseDNs {
|
||||
for _, groupSearchBase := range server.Config.GroupSearchBaseDNs {
|
||||
var filterReplace string
|
||||
if server.config.GroupSearchFilterUserAttribute == "" {
|
||||
filterReplace = getLDAPAttr(server.config.Attr.Username, searchResult)
|
||||
if server.Config.GroupSearchFilterUserAttribute == "" {
|
||||
filterReplace = getLDAPAttr(server.Config.Attr.Username, searchResult)
|
||||
} else {
|
||||
filterReplace = getLDAPAttr(server.config.GroupSearchFilterUserAttribute, searchResult)
|
||||
filterReplace = getLDAPAttr(server.Config.GroupSearchFilterUserAttribute, searchResult)
|
||||
}
|
||||
|
||||
filter := strings.Replace(
|
||||
server.config.GroupSearchFilter, "%s",
|
||||
server.Config.GroupSearchFilter, "%s",
|
||||
ldap.EscapeFilter(filterReplace),
|
||||
-1,
|
||||
)
|
||||
@ -468,7 +417,7 @@ func (server *Server) requestMemberOf(searchResult *ldap.SearchResult) ([]string
|
||||
server.log.Info("Searching for user's groups", "filter", filter)
|
||||
|
||||
// support old way of reading settings
|
||||
groupIDAttribute := server.config.Attr.MemberOf
|
||||
groupIDAttribute := server.Config.Attr.MemberOf
|
||||
// but prefer dn attribute if default settings are used
|
||||
if groupIDAttribute == "" || groupIDAttribute == "memberOf" {
|
||||
groupIDAttribute = "dn"
|
||||
@ -482,7 +431,7 @@ func (server *Server) requestMemberOf(searchResult *ldap.SearchResult) ([]string
|
||||
Filter: filter,
|
||||
}
|
||||
|
||||
groupSearchResult, err := server.connection.Search(&groupSearchReq)
|
||||
groupSearchResult, err := server.Connection.Search(&groupSearchReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -518,22 +467,22 @@ func (server *Server) serializeUsers(
|
||||
index,
|
||||
),
|
||||
LastName: getLDAPAttrN(
|
||||
server.config.Attr.Surname,
|
||||
server.Config.Attr.Surname,
|
||||
users,
|
||||
index,
|
||||
),
|
||||
FirstName: getLDAPAttrN(
|
||||
server.config.Attr.Name,
|
||||
server.Config.Attr.Name,
|
||||
users,
|
||||
index,
|
||||
),
|
||||
Username: getLDAPAttrN(
|
||||
server.config.Attr.Username,
|
||||
server.Config.Attr.Username,
|
||||
users,
|
||||
index,
|
||||
),
|
||||
Email: getLDAPAttrN(
|
||||
server.config.Attr.Email,
|
||||
server.Config.Attr.Email,
|
||||
users,
|
||||
index,
|
||||
),
|
||||
@ -553,8 +502,8 @@ func (server *Server) serializeUsers(
|
||||
func (server *Server) getMemberOf(search *ldap.SearchResult) (
|
||||
[]string, error,
|
||||
) {
|
||||
if server.config.GroupSearchFilter == "" {
|
||||
memberOf := getLDAPAttrArray(server.config.Attr.MemberOf, search)
|
||||
if server.Config.GroupSearchFilter == "" {
|
||||
memberOf := getLDAPAttrArray(server.Config.Attr.MemberOf, search)
|
||||
|
||||
return memberOf, nil
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ func TestLDAPHelpers(t *testing.T) {
|
||||
Convey("serializeUsers()", t, func() {
|
||||
Convey("simple case", func() {
|
||||
server := &Server{
|
||||
config: &ServerConfig{
|
||||
Config: &ServerConfig{
|
||||
Attr: AttributeMap{
|
||||
Username: "username",
|
||||
Name: "name",
|
||||
@ -22,7 +22,7 @@ func TestLDAPHelpers(t *testing.T) {
|
||||
},
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
},
|
||||
connection: &mockConnection{},
|
||||
Connection: &MockConnection{},
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ func TestLDAPHelpers(t *testing.T) {
|
||||
|
||||
Convey("without lastname", func() {
|
||||
server := &Server{
|
||||
config: &ServerConfig{
|
||||
Config: &ServerConfig{
|
||||
Attr: AttributeMap{
|
||||
Username: "username",
|
||||
Name: "name",
|
||||
@ -55,7 +55,7 @@ func TestLDAPHelpers(t *testing.T) {
|
||||
},
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
},
|
||||
connection: &mockConnection{},
|
||||
Connection: &MockConnection{},
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
@ -75,74 +75,9 @@ func TestLDAPHelpers(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
Convey("initialBind", t, func() {
|
||||
Convey("Given bind dn and password configured", func() {
|
||||
connection := &mockConnection{}
|
||||
var actualUsername, actualPassword string
|
||||
connection.bindProvider = func(username, password string) error {
|
||||
actualUsername = username
|
||||
actualPassword = password
|
||||
return nil
|
||||
}
|
||||
server := &Server{
|
||||
connection: connection,
|
||||
config: &ServerConfig{
|
||||
BindDN: "cn=%s,o=users,dc=grafana,dc=org",
|
||||
BindPassword: "bindpwd",
|
||||
},
|
||||
}
|
||||
err := server.initialBind("user", "pwd")
|
||||
So(err, ShouldBeNil)
|
||||
So(server.requireSecondBind, ShouldBeTrue)
|
||||
So(actualUsername, ShouldEqual, "cn=user,o=users,dc=grafana,dc=org")
|
||||
So(actualPassword, ShouldEqual, "bindpwd")
|
||||
})
|
||||
|
||||
Convey("Given bind dn configured", func() {
|
||||
connection := &mockConnection{}
|
||||
var actualUsername, actualPassword string
|
||||
connection.bindProvider = func(username, password string) error {
|
||||
actualUsername = username
|
||||
actualPassword = password
|
||||
return nil
|
||||
}
|
||||
server := &Server{
|
||||
connection: connection,
|
||||
config: &ServerConfig{
|
||||
BindDN: "cn=%s,o=users,dc=grafana,dc=org",
|
||||
},
|
||||
}
|
||||
err := server.initialBind("user", "pwd")
|
||||
So(err, ShouldBeNil)
|
||||
So(server.requireSecondBind, ShouldBeFalse)
|
||||
So(actualUsername, ShouldEqual, "cn=user,o=users,dc=grafana,dc=org")
|
||||
So(actualPassword, ShouldEqual, "pwd")
|
||||
})
|
||||
|
||||
Convey("Given empty bind dn and password", func() {
|
||||
connection := &mockConnection{}
|
||||
unauthenticatedBindWasCalled := false
|
||||
var actualUsername string
|
||||
connection.unauthenticatedBindProvider = func(username string) error {
|
||||
unauthenticatedBindWasCalled = true
|
||||
actualUsername = username
|
||||
return nil
|
||||
}
|
||||
server := &Server{
|
||||
connection: connection,
|
||||
config: &ServerConfig{},
|
||||
}
|
||||
err := server.initialBind("user", "pwd")
|
||||
So(err, ShouldBeNil)
|
||||
So(server.requireSecondBind, ShouldBeTrue)
|
||||
So(unauthenticatedBindWasCalled, ShouldBeTrue)
|
||||
So(actualUsername, ShouldBeEmpty)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("serverBind()", t, func() {
|
||||
Convey("Given bind dn and password configured", func() {
|
||||
connection := &mockConnection{}
|
||||
connection := &MockConnection{}
|
||||
var actualUsername, actualPassword string
|
||||
connection.bindProvider = func(username, password string) error {
|
||||
actualUsername = username
|
||||
@ -150,8 +85,8 @@ func TestLDAPHelpers(t *testing.T) {
|
||||
return nil
|
||||
}
|
||||
server := &Server{
|
||||
connection: connection,
|
||||
config: &ServerConfig{
|
||||
Connection: connection,
|
||||
Config: &ServerConfig{
|
||||
BindDN: "o=users,dc=grafana,dc=org",
|
||||
BindPassword: "bindpwd",
|
||||
},
|
||||
@ -163,7 +98,7 @@ func TestLDAPHelpers(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Given bind dn configured", func() {
|
||||
connection := &mockConnection{}
|
||||
connection := &MockConnection{}
|
||||
unauthenticatedBindWasCalled := false
|
||||
var actualUsername string
|
||||
connection.unauthenticatedBindProvider = func(username string) error {
|
||||
@ -172,8 +107,8 @@ func TestLDAPHelpers(t *testing.T) {
|
||||
return nil
|
||||
}
|
||||
server := &Server{
|
||||
connection: connection,
|
||||
config: &ServerConfig{
|
||||
Connection: connection,
|
||||
Config: &ServerConfig{
|
||||
BindDN: "o=users,dc=grafana,dc=org",
|
||||
},
|
||||
}
|
||||
@ -184,7 +119,7 @@ func TestLDAPHelpers(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Given empty bind dn and password", func() {
|
||||
connection := &mockConnection{}
|
||||
connection := &MockConnection{}
|
||||
unauthenticatedBindWasCalled := false
|
||||
var actualUsername string
|
||||
connection.unauthenticatedBindProvider = func(username string) error {
|
||||
@ -193,8 +128,8 @@ func TestLDAPHelpers(t *testing.T) {
|
||||
return nil
|
||||
}
|
||||
server := &Server{
|
||||
connection: connection,
|
||||
config: &ServerConfig{},
|
||||
Connection: connection,
|
||||
Config: &ServerConfig{},
|
||||
}
|
||||
err := server.serverBind()
|
||||
So(err, ShouldBeNil)
|
||||
|
@ -13,12 +13,12 @@ import (
|
||||
|
||||
func TestLDAPLogin(t *testing.T) {
|
||||
Convey("Login()", t, func() {
|
||||
authScenario("When user is log in and updated", func(sc *scenarioContext) {
|
||||
serverScenario("When user is log in and updated", func(sc *scenarioContext) {
|
||||
// arrange
|
||||
mockConnection := &mockConnection{}
|
||||
mockConnection := &MockConnection{}
|
||||
|
||||
auth := &Server{
|
||||
config: &ServerConfig{
|
||||
server := &Server{
|
||||
Config: &ServerConfig{
|
||||
Host: "",
|
||||
RootCACert: "",
|
||||
Groups: []*GroupToOrgRole{
|
||||
@ -33,7 +33,7 @@ func TestLDAPLogin(t *testing.T) {
|
||||
},
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
},
|
||||
connection: mockConnection,
|
||||
Connection: mockConnection,
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ func TestLDAPLogin(t *testing.T) {
|
||||
sc.userOrgsQueryReturns([]*models.UserOrgDTO{})
|
||||
|
||||
// act
|
||||
extUser, _ := auth.Login(query)
|
||||
extUser, _ := server.Login(query)
|
||||
userInfo, err := user.Upsert(&user.UpsertArgs{
|
||||
SignupAllowed: true,
|
||||
ExternalUser: extUser,
|
||||
@ -73,7 +73,7 @@ func TestLDAPLogin(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// User should be searched in ldap
|
||||
So(mockConnection.searchCalled, ShouldBeTrue)
|
||||
So(mockConnection.SearchCalled, ShouldBeTrue)
|
||||
|
||||
// Info should be updated (email differs)
|
||||
So(userInfo.Email, ShouldEqual, "roel@test.com")
|
||||
@ -82,8 +82,8 @@ func TestLDAPLogin(t *testing.T) {
|
||||
So(sc.addOrgUserCmd.Role, ShouldEqual, "Admin")
|
||||
})
|
||||
|
||||
authScenario("When login with invalid credentials", func(scenario *scenarioContext) {
|
||||
connection := &mockConnection{}
|
||||
serverScenario("When login with invalid credentials", func(scenario *scenarioContext) {
|
||||
connection := &MockConnection{}
|
||||
entry := ldap.Entry{}
|
||||
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
|
||||
connection.setSearchResult(&result)
|
||||
@ -93,8 +93,8 @@ func TestLDAPLogin(t *testing.T) {
|
||||
ResultCode: 49,
|
||||
}
|
||||
}
|
||||
auth := &Server{
|
||||
config: &ServerConfig{
|
||||
server := &Server{
|
||||
Config: &ServerConfig{
|
||||
Attr: AttributeMap{
|
||||
Username: "username",
|
||||
Name: "name",
|
||||
@ -102,19 +102,19 @@ func TestLDAPLogin(t *testing.T) {
|
||||
},
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
},
|
||||
connection: connection,
|
||||
Connection: connection,
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
_, err := auth.Login(scenario.loginUserQuery)
|
||||
_, err := server.Login(scenario.loginUserQuery)
|
||||
|
||||
Convey("it should return invalid credentials error", func() {
|
||||
So(err, ShouldEqual, ErrInvalidCredentials)
|
||||
})
|
||||
})
|
||||
|
||||
authScenario("When login with valid credentials", func(scenario *scenarioContext) {
|
||||
connection := &mockConnection{}
|
||||
serverScenario("When login with valid credentials", func(scenario *scenarioContext) {
|
||||
connection := &MockConnection{}
|
||||
entry := ldap.Entry{
|
||||
DN: "dn", Attributes: []*ldap.EntryAttribute{
|
||||
{Name: "username", Values: []string{"markelog"}},
|
||||
@ -130,8 +130,8 @@ func TestLDAPLogin(t *testing.T) {
|
||||
connection.bindProvider = func(username, password string) error {
|
||||
return nil
|
||||
}
|
||||
auth := &Server{
|
||||
config: &ServerConfig{
|
||||
server := &Server{
|
||||
Config: &ServerConfig{
|
||||
Attr: AttributeMap{
|
||||
Username: "username",
|
||||
Name: "name",
|
||||
@ -139,18 +139,18 @@ func TestLDAPLogin(t *testing.T) {
|
||||
},
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
},
|
||||
connection: connection,
|
||||
Connection: connection,
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
resp, err := auth.Login(scenario.loginUserQuery)
|
||||
resp, err := server.Login(scenario.loginUserQuery)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.Login, ShouldEqual, "markelog")
|
||||
})
|
||||
|
||||
authScenario("When user not found in LDAP, but exist in Grafana", func(scenario *scenarioContext) {
|
||||
connection := &mockConnection{}
|
||||
serverScenario("When user not found in LDAP, but exist in Grafana", func(scenario *scenarioContext) {
|
||||
connection := &MockConnection{}
|
||||
result := ldap.SearchResult{Entries: []*ldap.Entry{}}
|
||||
connection.setSearchResult(&result)
|
||||
|
||||
@ -160,15 +160,15 @@ func TestLDAPLogin(t *testing.T) {
|
||||
connection.bindProvider = func(username, password string) error {
|
||||
return nil
|
||||
}
|
||||
auth := &Server{
|
||||
config: &ServerConfig{
|
||||
server := &Server{
|
||||
Config: &ServerConfig{
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
},
|
||||
connection: connection,
|
||||
Connection: connection,
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
_, err := auth.Login(scenario.loginUserQuery)
|
||||
_, err := server.Login(scenario.loginUserQuery)
|
||||
|
||||
Convey("it should disable user", func() {
|
||||
So(scenario.disableExternalUserCalled, ShouldBeTrue)
|
||||
@ -181,8 +181,8 @@ func TestLDAPLogin(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
authScenario("When user not found in LDAP, and disabled in Grafana already", func(scenario *scenarioContext) {
|
||||
connection := &mockConnection{}
|
||||
serverScenario("When user not found in LDAP, and disabled in Grafana already", func(scenario *scenarioContext) {
|
||||
connection := &MockConnection{}
|
||||
result := ldap.SearchResult{Entries: []*ldap.Entry{}}
|
||||
connection.setSearchResult(&result)
|
||||
|
||||
@ -192,15 +192,15 @@ func TestLDAPLogin(t *testing.T) {
|
||||
connection.bindProvider = func(username, password string) error {
|
||||
return nil
|
||||
}
|
||||
auth := &Server{
|
||||
config: &ServerConfig{
|
||||
server := &Server{
|
||||
Config: &ServerConfig{
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
},
|
||||
connection: connection,
|
||||
Connection: connection,
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
_, err := auth.Login(scenario.loginUserQuery)
|
||||
_, err := server.Login(scenario.loginUserQuery)
|
||||
|
||||
Convey("it should't call disable function", func() {
|
||||
So(scenario.disableExternalUserCalled, ShouldBeFalse)
|
||||
@ -211,8 +211,8 @@ func TestLDAPLogin(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
authScenario("When user found in LDAP, and disabled in Grafana", func(scenario *scenarioContext) {
|
||||
connection := &mockConnection{}
|
||||
serverScenario("When user found in LDAP, and disabled in Grafana", func(scenario *scenarioContext) {
|
||||
connection := &MockConnection{}
|
||||
entry := ldap.Entry{}
|
||||
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
|
||||
connection.setSearchResult(&result)
|
||||
@ -221,15 +221,15 @@ func TestLDAPLogin(t *testing.T) {
|
||||
connection.bindProvider = func(username, password string) error {
|
||||
return nil
|
||||
}
|
||||
auth := &Server{
|
||||
config: &ServerConfig{
|
||||
server := &Server{
|
||||
Config: &ServerConfig{
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
},
|
||||
connection: connection,
|
||||
Connection: connection,
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
extUser, _ := auth.Login(scenario.loginUserQuery)
|
||||
extUser, _ := server.Login(scenario.loginUserQuery)
|
||||
_, err := user.Upsert(&user.UpsertArgs{
|
||||
SignupAllowed: true,
|
||||
ExternalUser: extUser,
|
||||
|
@ -9,114 +9,10 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
)
|
||||
|
||||
func TestAuth(t *testing.T) {
|
||||
Convey("Add()", t, func() {
|
||||
connection := &mockConnection{}
|
||||
|
||||
auth := &Server{
|
||||
config: &ServerConfig{
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
},
|
||||
connection: connection,
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
Convey("Adds user", func() {
|
||||
err := auth.Add(
|
||||
"cn=ldap-tuz,ou=users,dc=grafana,dc=org",
|
||||
map[string][]string{
|
||||
"mail": {"ldap-viewer@grafana.com"},
|
||||
"userPassword": {"grafana"},
|
||||
"objectClass": {
|
||||
"person",
|
||||
"top",
|
||||
"inetOrgPerson",
|
||||
"organizationalPerson",
|
||||
},
|
||||
"sn": {"ldap-tuz"},
|
||||
"cn": {"ldap-tuz"},
|
||||
},
|
||||
)
|
||||
|
||||
hasMail := false
|
||||
hasUserPassword := false
|
||||
hasObjectClass := false
|
||||
hasSN := false
|
||||
hasCN := false
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(connection.addParams.Controls, ShouldBeNil)
|
||||
So(connection.addCalled, ShouldBeTrue)
|
||||
So(
|
||||
connection.addParams.DN,
|
||||
ShouldEqual,
|
||||
"cn=ldap-tuz,ou=users,dc=grafana,dc=org",
|
||||
)
|
||||
|
||||
attrs := connection.addParams.Attributes
|
||||
for _, value := range attrs {
|
||||
if value.Type == "mail" {
|
||||
So(value.Vals, ShouldContain, "ldap-viewer@grafana.com")
|
||||
hasMail = true
|
||||
}
|
||||
|
||||
if value.Type == "userPassword" {
|
||||
hasUserPassword = true
|
||||
So(value.Vals, ShouldContain, "grafana")
|
||||
}
|
||||
|
||||
if value.Type == "objectClass" {
|
||||
hasObjectClass = true
|
||||
So(value.Vals, ShouldContain, "person")
|
||||
So(value.Vals, ShouldContain, "top")
|
||||
So(value.Vals, ShouldContain, "inetOrgPerson")
|
||||
So(value.Vals, ShouldContain, "organizationalPerson")
|
||||
}
|
||||
|
||||
if value.Type == "sn" {
|
||||
hasSN = true
|
||||
So(value.Vals, ShouldContain, "ldap-tuz")
|
||||
}
|
||||
|
||||
if value.Type == "cn" {
|
||||
hasCN = true
|
||||
So(value.Vals, ShouldContain, "ldap-tuz")
|
||||
}
|
||||
}
|
||||
|
||||
So(hasMail, ShouldBeTrue)
|
||||
So(hasUserPassword, ShouldBeTrue)
|
||||
So(hasObjectClass, ShouldBeTrue)
|
||||
So(hasSN, ShouldBeTrue)
|
||||
So(hasCN, ShouldBeTrue)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Remove()", t, func() {
|
||||
connection := &mockConnection{}
|
||||
|
||||
auth := &Server{
|
||||
config: &ServerConfig{
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
},
|
||||
connection: connection,
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
Convey("Removes the user", func() {
|
||||
dn := "cn=ldap-tuz,ou=users,dc=grafana,dc=org"
|
||||
err := auth.Remove(dn)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(connection.delCalled, ShouldBeTrue)
|
||||
So(connection.delParams.Controls, ShouldBeNil)
|
||||
So(connection.delParams.DN, ShouldEqual, dn)
|
||||
})
|
||||
})
|
||||
|
||||
func TestPublicAPI(t *testing.T) {
|
||||
Convey("Users()", t, func() {
|
||||
Convey("find one user", func() {
|
||||
mockConnection := &mockConnection{}
|
||||
MockConnection := &MockConnection{}
|
||||
entry := ldap.Entry{
|
||||
DN: "dn", Attributes: []*ldap.EntryAttribute{
|
||||
{Name: "username", Values: []string{"roelgerrits"}},
|
||||
@ -126,11 +22,11 @@ func TestAuth(t *testing.T) {
|
||||
{Name: "memberof", Values: []string{"admins"}},
|
||||
}}
|
||||
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
|
||||
mockConnection.setSearchResult(&result)
|
||||
MockConnection.setSearchResult(&result)
|
||||
|
||||
// Set up attribute map without surname and email
|
||||
server := &Server{
|
||||
config: &ServerConfig{
|
||||
Config: &ServerConfig{
|
||||
Attr: AttributeMap{
|
||||
Username: "username",
|
||||
Name: "name",
|
||||
@ -138,7 +34,7 @@ func TestAuth(t *testing.T) {
|
||||
},
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
},
|
||||
connection: mockConnection,
|
||||
Connection: MockConnection,
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
@ -148,10 +44,75 @@ func TestAuth(t *testing.T) {
|
||||
So(searchResult, ShouldNotBeNil)
|
||||
|
||||
// User should be searched in ldap
|
||||
So(mockConnection.searchCalled, ShouldBeTrue)
|
||||
So(MockConnection.SearchCalled, ShouldBeTrue)
|
||||
|
||||
// No empty attributes should be added to the search request
|
||||
So(len(mockConnection.searchAttributes), ShouldEqual, 3)
|
||||
So(len(MockConnection.SearchAttributes), ShouldEqual, 3)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("InitialBind", t, func() {
|
||||
Convey("Given bind dn and password configured", func() {
|
||||
connection := &MockConnection{}
|
||||
var actualUsername, actualPassword string
|
||||
connection.bindProvider = func(username, password string) error {
|
||||
actualUsername = username
|
||||
actualPassword = password
|
||||
return nil
|
||||
}
|
||||
server := &Server{
|
||||
Connection: connection,
|
||||
Config: &ServerConfig{
|
||||
BindDN: "cn=%s,o=users,dc=grafana,dc=org",
|
||||
BindPassword: "bindpwd",
|
||||
},
|
||||
}
|
||||
err := server.InitialBind("user", "pwd")
|
||||
So(err, ShouldBeNil)
|
||||
So(server.requireSecondBind, ShouldBeTrue)
|
||||
So(actualUsername, ShouldEqual, "cn=user,o=users,dc=grafana,dc=org")
|
||||
So(actualPassword, ShouldEqual, "bindpwd")
|
||||
})
|
||||
|
||||
Convey("Given bind dn configured", func() {
|
||||
connection := &MockConnection{}
|
||||
var actualUsername, actualPassword string
|
||||
connection.bindProvider = func(username, password string) error {
|
||||
actualUsername = username
|
||||
actualPassword = password
|
||||
return nil
|
||||
}
|
||||
server := &Server{
|
||||
Connection: connection,
|
||||
Config: &ServerConfig{
|
||||
BindDN: "cn=%s,o=users,dc=grafana,dc=org",
|
||||
},
|
||||
}
|
||||
err := server.InitialBind("user", "pwd")
|
||||
So(err, ShouldBeNil)
|
||||
So(server.requireSecondBind, ShouldBeFalse)
|
||||
So(actualUsername, ShouldEqual, "cn=user,o=users,dc=grafana,dc=org")
|
||||
So(actualPassword, ShouldEqual, "pwd")
|
||||
})
|
||||
|
||||
Convey("Given empty bind dn and password", func() {
|
||||
connection := &MockConnection{}
|
||||
unauthenticatedBindWasCalled := false
|
||||
var actualUsername string
|
||||
connection.unauthenticatedBindProvider = func(username string) error {
|
||||
unauthenticatedBindWasCalled = true
|
||||
actualUsername = username
|
||||
return nil
|
||||
}
|
||||
server := &Server{
|
||||
Connection: connection,
|
||||
Config: &ServerConfig{},
|
||||
}
|
||||
err := server.InitialBind("user", "pwd")
|
||||
So(err, ShouldBeNil)
|
||||
So(server.requireSecondBind, ShouldBeTrue)
|
||||
So(unauthenticatedBindWasCalled, ShouldBeTrue)
|
||||
So(actualUsername, ShouldBeEmpty)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -12,22 +12,24 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
)
|
||||
|
||||
type mockConnection struct {
|
||||
searchResult *ldap.SearchResult
|
||||
searchCalled bool
|
||||
searchAttributes []string
|
||||
// MockConnection struct for testing
|
||||
type MockConnection struct {
|
||||
SearchResult *ldap.SearchResult
|
||||
SearchCalled bool
|
||||
SearchAttributes []string
|
||||
|
||||
addParams *ldap.AddRequest
|
||||
addCalled bool
|
||||
AddParams *ldap.AddRequest
|
||||
AddCalled bool
|
||||
|
||||
delParams *ldap.DelRequest
|
||||
delCalled bool
|
||||
DelParams *ldap.DelRequest
|
||||
DelCalled bool
|
||||
|
||||
bindProvider func(username, password string) error
|
||||
unauthenticatedBindProvider func(username string) error
|
||||
}
|
||||
|
||||
func (c *mockConnection) Bind(username, password string) error {
|
||||
// Bind mocks Bind connection function
|
||||
func (c *MockConnection) Bind(username, password string) error {
|
||||
if c.bindProvider != nil {
|
||||
return c.bindProvider(username, password)
|
||||
}
|
||||
@ -35,7 +37,8 @@ func (c *mockConnection) Bind(username, password string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mockConnection) UnauthenticatedBind(username string) error {
|
||||
// UnauthenticatedBind mocks UnauthenticatedBind connection function
|
||||
func (c *MockConnection) UnauthenticatedBind(username string) error {
|
||||
if c.unauthenticatedBindProvider != nil {
|
||||
return c.unauthenticatedBindProvider(username)
|
||||
}
|
||||
@ -43,35 +46,40 @@ func (c *mockConnection) UnauthenticatedBind(username string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mockConnection) Close() {}
|
||||
// Close mocks Close connection function
|
||||
func (c *MockConnection) Close() {}
|
||||
|
||||
func (c *mockConnection) setSearchResult(result *ldap.SearchResult) {
|
||||
c.searchResult = result
|
||||
func (c *MockConnection) setSearchResult(result *ldap.SearchResult) {
|
||||
c.SearchResult = result
|
||||
}
|
||||
|
||||
func (c *mockConnection) Search(sr *ldap.SearchRequest) (*ldap.SearchResult, error) {
|
||||
c.searchCalled = true
|
||||
c.searchAttributes = sr.Attributes
|
||||
return c.searchResult, nil
|
||||
// Search mocks Search connection function
|
||||
func (c *MockConnection) Search(sr *ldap.SearchRequest) (*ldap.SearchResult, error) {
|
||||
c.SearchCalled = true
|
||||
c.SearchAttributes = sr.Attributes
|
||||
return c.SearchResult, nil
|
||||
}
|
||||
|
||||
func (c *mockConnection) Add(request *ldap.AddRequest) error {
|
||||
c.addCalled = true
|
||||
c.addParams = request
|
||||
// Add mocks Add connection function
|
||||
func (c *MockConnection) Add(request *ldap.AddRequest) error {
|
||||
c.AddCalled = true
|
||||
c.AddParams = request
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mockConnection) Del(request *ldap.DelRequest) error {
|
||||
c.delCalled = true
|
||||
c.delParams = request
|
||||
// Del mocks Del connection function
|
||||
func (c *MockConnection) Del(request *ldap.DelRequest) error {
|
||||
c.DelCalled = true
|
||||
c.DelParams = request
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mockConnection) StartTLS(*tls.Config) error {
|
||||
// StartTLS mocks StartTLS connection function
|
||||
func (c *MockConnection) StartTLS(*tls.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func authScenario(desc string, fn scenarioFunc) {
|
||||
func serverScenario(desc string, fn scenarioFunc) {
|
||||
Convey(desc, func() {
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
|
@ -35,9 +35,6 @@ type IMultiLDAP interface {
|
||||
User(login string) (
|
||||
*models.ExternalUserInfo, error,
|
||||
)
|
||||
|
||||
Add(dn string, values map[string][]string) error
|
||||
Remove(dn string) error
|
||||
}
|
||||
|
||||
// MultiLDAP is basic struct of LDAP authorization
|
||||
@ -52,55 +49,6 @@ func New(configs []*ldap.ServerConfig) IMultiLDAP {
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds user to the *first* defined LDAP
|
||||
func (multiples *MultiLDAP) Add(
|
||||
dn string,
|
||||
values map[string][]string,
|
||||
) error {
|
||||
if len(multiples.configs) == 0 {
|
||||
return ErrNoLDAPServers
|
||||
}
|
||||
|
||||
config := multiples.configs[0]
|
||||
ldap := ldap.New(config)
|
||||
|
||||
if err := ldap.Dial(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer ldap.Close()
|
||||
|
||||
err := ldap.Add(dn, values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes user from the *first* defined LDAP
|
||||
func (multiples *MultiLDAP) Remove(dn string) error {
|
||||
if len(multiples.configs) == 0 {
|
||||
return ErrNoLDAPServers
|
||||
}
|
||||
|
||||
config := multiples.configs[0]
|
||||
ldap := ldap.New(config)
|
||||
|
||||
if err := ldap.Dial(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer ldap.Close()
|
||||
|
||||
err := ldap.Remove(dn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Login tries to log in the user in multiples LDAP
|
||||
func (multiples *MultiLDAP) Login(query *models.LoginUserQuery) (
|
||||
*models.ExternalUserInfo, error,
|
||||
|
Loading…
Reference in New Issue
Block a user