mirror of
https://github.com/grafana/grafana.git
synced 2025-09-22 01:36:24 +08:00
LDAP: Search all DNs for users (#38891)
This commit is contained in:
@ -2,226 +2,319 @@ package ldap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/ldap.v3"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
)
|
||||
|
||||
func TestPublicAPI(t *testing.T) {
|
||||
Convey("New()", t, func() {
|
||||
Convey("Should return ", func() {
|
||||
result := New(&ServerConfig{
|
||||
func TestNew(t *testing.T) {
|
||||
result := New(&ServerConfig{
|
||||
Attr: AttributeMap{},
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
})
|
||||
|
||||
assert.Implements(t, (*IServer)(nil), result)
|
||||
}
|
||||
|
||||
func TestServer_Close(t *testing.T) {
|
||||
t.Run("close the connection", func(t *testing.T) {
|
||||
connection := &MockConnection{}
|
||||
|
||||
server := &Server{
|
||||
Config: &ServerConfig{
|
||||
Attr: AttributeMap{},
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
})
|
||||
},
|
||||
Connection: connection,
|
||||
}
|
||||
|
||||
So(result, ShouldImplement, (*IServer)(nil))
|
||||
})
|
||||
assert.NotPanics(t, server.Close)
|
||||
assert.True(t, connection.CloseCalled)
|
||||
})
|
||||
|
||||
Convey("Close()", t, func() {
|
||||
Convey("Should close the connection", func() {
|
||||
connection := &MockConnection{}
|
||||
t.Run("panic if no connection", func(t *testing.T) {
|
||||
server := &Server{
|
||||
Config: &ServerConfig{
|
||||
Attr: AttributeMap{},
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
},
|
||||
Connection: nil,
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
Config: &ServerConfig{
|
||||
Attr: AttributeMap{},
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
},
|
||||
Connection: connection,
|
||||
}
|
||||
|
||||
So(server.Close, ShouldNotPanic)
|
||||
So(connection.CloseCalled, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Should panic if no connection is established", func() {
|
||||
server := &Server{
|
||||
Config: &ServerConfig{
|
||||
Attr: AttributeMap{},
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
},
|
||||
Connection: nil,
|
||||
}
|
||||
|
||||
So(server.Close, ShouldPanic)
|
||||
})
|
||||
})
|
||||
Convey("Users()", t, func() {
|
||||
Convey("Finds one user", func() {
|
||||
MockConnection := &MockConnection{}
|
||||
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}}
|
||||
MockConnection.setSearchResult(&result)
|
||||
|
||||
// Set up attribute map without surname and email
|
||||
server := &Server{
|
||||
Config: &ServerConfig{
|
||||
Attr: AttributeMap{
|
||||
Username: "username",
|
||||
Name: "name",
|
||||
MemberOf: "memberof",
|
||||
},
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
},
|
||||
Connection: MockConnection,
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
searchResult, err := server.Users([]string{"roelgerrits"})
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(searchResult, ShouldNotBeNil)
|
||||
|
||||
// User should be searched in ldap
|
||||
So(MockConnection.SearchCalled, ShouldBeTrue)
|
||||
|
||||
// No empty attributes should be added to the search request
|
||||
So(len(MockConnection.SearchAttributes), ShouldEqual, 3)
|
||||
})
|
||||
|
||||
Convey("Handles a error", func() {
|
||||
expected := errors.New("Killa-gorilla")
|
||||
MockConnection := &MockConnection{}
|
||||
MockConnection.setSearchError(expected)
|
||||
|
||||
// Set up attribute map without surname and email
|
||||
server := &Server{
|
||||
Config: &ServerConfig{
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
},
|
||||
Connection: MockConnection,
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
_, err := server.Users([]string{"roelgerrits"})
|
||||
|
||||
So(err, ShouldEqual, expected)
|
||||
})
|
||||
|
||||
Convey("Should return empty slice if none were found", func() {
|
||||
MockConnection := &MockConnection{}
|
||||
result := ldap.SearchResult{Entries: []*ldap.Entry{}}
|
||||
MockConnection.setSearchResult(&result)
|
||||
|
||||
// Set up attribute map without surname and email
|
||||
server := &Server{
|
||||
Config: &ServerConfig{
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
},
|
||||
Connection: MockConnection,
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
searchResult, err := server.Users([]string{"roelgerrits"})
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(searchResult, ShouldBeEmpty)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("UserBind()", t, func() {
|
||||
Convey("Should use provided DN and password", 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=admin,dc=grafana,dc=org",
|
||||
},
|
||||
}
|
||||
|
||||
dn := "cn=user,ou=users,dc=grafana,dc=org"
|
||||
err := server.UserBind(dn, "pwd")
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(actualUsername, ShouldEqual, dn)
|
||||
So(actualPassword, ShouldEqual, "pwd")
|
||||
})
|
||||
|
||||
Convey("Should handle an error", func() {
|
||||
connection := &MockConnection{}
|
||||
expected := &ldap.Error{
|
||||
ResultCode: uint16(25),
|
||||
}
|
||||
connection.BindProvider = func(username, password string) error {
|
||||
return expected
|
||||
}
|
||||
server := &Server{
|
||||
Connection: connection,
|
||||
Config: &ServerConfig{
|
||||
BindDN: "cn=%s,ou=users,dc=grafana,dc=org",
|
||||
},
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
err := server.UserBind("user", "pwd")
|
||||
So(err, ShouldEqual, expected)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("AdminBind()", t, func() {
|
||||
Convey("Should use admin DN and password", func() {
|
||||
connection := &MockConnection{}
|
||||
var actualUsername, actualPassword string
|
||||
connection.BindProvider = func(username, password string) error {
|
||||
actualUsername = username
|
||||
actualPassword = password
|
||||
return nil
|
||||
}
|
||||
|
||||
dn := "cn=admin,dc=grafana,dc=org"
|
||||
|
||||
server := &Server{
|
||||
Connection: connection,
|
||||
Config: &ServerConfig{
|
||||
BindPassword: "pwd",
|
||||
BindDN: dn,
|
||||
},
|
||||
}
|
||||
|
||||
err := server.AdminBind()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(actualUsername, ShouldEqual, dn)
|
||||
So(actualPassword, ShouldEqual, "pwd")
|
||||
})
|
||||
|
||||
Convey("Should handle an error", func() {
|
||||
connection := &MockConnection{}
|
||||
expected := &ldap.Error{
|
||||
ResultCode: uint16(25),
|
||||
}
|
||||
connection.BindProvider = func(username, password string) error {
|
||||
return expected
|
||||
}
|
||||
|
||||
dn := "cn=admin,dc=grafana,dc=org"
|
||||
|
||||
server := &Server{
|
||||
Connection: connection,
|
||||
Config: &ServerConfig{
|
||||
BindPassword: "pwd",
|
||||
BindDN: dn,
|
||||
},
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
err := server.AdminBind()
|
||||
So(err, ShouldEqual, expected)
|
||||
})
|
||||
assert.Panics(t, server.Close)
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_Users(t *testing.T) {
|
||||
t.Run("one user", func(t *testing.T) {
|
||||
conn := &MockConnection{}
|
||||
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}}
|
||||
conn.setSearchResult(&result)
|
||||
|
||||
// Set up attribute map without surname and email
|
||||
server := &Server{
|
||||
Config: &ServerConfig{
|
||||
Attr: AttributeMap{
|
||||
Username: "username",
|
||||
Name: "name",
|
||||
MemberOf: "memberof",
|
||||
},
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
},
|
||||
Connection: conn,
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
searchResult, err := server.Users([]string{"roelgerrits"})
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, searchResult)
|
||||
|
||||
// User should be searched in ldap
|
||||
assert.True(t, conn.SearchCalled)
|
||||
// No empty attributes should be added to the search request
|
||||
assert.Len(t, conn.SearchAttributes, 3)
|
||||
})
|
||||
|
||||
t.Run("error", func(t *testing.T) {
|
||||
expected := errors.New("Killa-gorilla")
|
||||
conn := &MockConnection{}
|
||||
conn.setSearchError(expected)
|
||||
|
||||
// Set up attribute map without surname and email
|
||||
server := &Server{
|
||||
Config: &ServerConfig{
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
},
|
||||
Connection: conn,
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
_, err := server.Users([]string{"roelgerrits"})
|
||||
|
||||
assert.ErrorIs(t, err, expected)
|
||||
})
|
||||
|
||||
t.Run("no user", func(t *testing.T) {
|
||||
conn := &MockConnection{}
|
||||
result := ldap.SearchResult{Entries: []*ldap.Entry{}}
|
||||
conn.setSearchResult(&result)
|
||||
|
||||
// Set up attribute map without surname and email
|
||||
server := &Server{
|
||||
Config: &ServerConfig{
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
},
|
||||
Connection: conn,
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
searchResult, err := server.Users([]string{"roelgerrits"})
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, searchResult)
|
||||
})
|
||||
|
||||
t.Run("multiple DNs", func(t *testing.T) {
|
||||
conn := &MockConnection{}
|
||||
serviceDN := "dc=svc,dc=example,dc=org"
|
||||
serviceEntry := ldap.Entry{
|
||||
DN: "dn", Attributes: []*ldap.EntryAttribute{
|
||||
{Name: "username", Values: []string{"imgrenderer"}},
|
||||
{Name: "name", Values: []string{"Image renderer"}},
|
||||
}}
|
||||
services := ldap.SearchResult{Entries: []*ldap.Entry{&serviceEntry}}
|
||||
|
||||
userDN := "dc=users,dc=example,dc=org"
|
||||
userEntry := ldap.Entry{
|
||||
DN: "dn", Attributes: []*ldap.EntryAttribute{
|
||||
{Name: "username", Values: []string{"grot"}},
|
||||
{Name: "name", Values: []string{"Grot"}},
|
||||
}}
|
||||
users := ldap.SearchResult{Entries: []*ldap.Entry{&userEntry}}
|
||||
|
||||
conn.setSearchFunc(func(request *ldap.SearchRequest) (*ldap.SearchResult, error) {
|
||||
switch request.BaseDN {
|
||||
case userDN:
|
||||
return &users, nil
|
||||
case serviceDN:
|
||||
return &services, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("test case not defined for baseDN: '%s'", request.BaseDN)
|
||||
}
|
||||
})
|
||||
|
||||
server := &Server{
|
||||
Config: &ServerConfig{
|
||||
Attr: AttributeMap{
|
||||
Username: "username",
|
||||
Name: "name",
|
||||
},
|
||||
SearchBaseDNs: []string{serviceDN, userDN},
|
||||
},
|
||||
Connection: conn,
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
searchResult, err := server.Users([]string{"imgrenderer", "grot"})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, searchResult, 2)
|
||||
})
|
||||
|
||||
t.Run("same user in multiple DNs", func(t *testing.T) {
|
||||
conn := &MockConnection{}
|
||||
firstDN := "dc=users1,dc=example,dc=org"
|
||||
firstEntry := ldap.Entry{
|
||||
DN: "dn", Attributes: []*ldap.EntryAttribute{
|
||||
{Name: "username", Values: []string{"grot"}},
|
||||
{Name: "name", Values: []string{"Grot the First"}},
|
||||
}}
|
||||
firsts := ldap.SearchResult{Entries: []*ldap.Entry{&firstEntry}}
|
||||
|
||||
secondDN := "dc=users2,dc=example,dc=org"
|
||||
secondEntry := ldap.Entry{
|
||||
DN: "dn", Attributes: []*ldap.EntryAttribute{
|
||||
{Name: "username", Values: []string{"grot"}},
|
||||
{Name: "name", Values: []string{"Grot the Second"}},
|
||||
}}
|
||||
seconds := ldap.SearchResult{Entries: []*ldap.Entry{&secondEntry}}
|
||||
|
||||
conn.setSearchFunc(func(request *ldap.SearchRequest) (*ldap.SearchResult, error) {
|
||||
switch request.BaseDN {
|
||||
case secondDN:
|
||||
return &seconds, nil
|
||||
case firstDN:
|
||||
return &firsts, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("test case not defined for baseDN: '%s'", request.BaseDN)
|
||||
}
|
||||
})
|
||||
|
||||
server := &Server{
|
||||
Config: &ServerConfig{
|
||||
Attr: AttributeMap{
|
||||
Username: "username",
|
||||
Name: "name",
|
||||
},
|
||||
SearchBaseDNs: []string{firstDN, secondDN},
|
||||
},
|
||||
Connection: conn,
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
res, err := server.Users([]string{"grot"})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res, 1)
|
||||
assert.Equal(t, "Grot the First", res[0].Name)
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_UserBind(t *testing.T) {
|
||||
t.Run("use provided DN and password", func(t *testing.T) {
|
||||
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=admin,dc=grafana,dc=org",
|
||||
},
|
||||
}
|
||||
|
||||
dn := "cn=user,ou=users,dc=grafana,dc=org"
|
||||
err := server.UserBind(dn, "pwd")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, dn, actualUsername)
|
||||
assert.Equal(t, "pwd", actualPassword)
|
||||
})
|
||||
|
||||
t.Run("error", func(t *testing.T) {
|
||||
connection := &MockConnection{}
|
||||
expected := &ldap.Error{
|
||||
ResultCode: uint16(25),
|
||||
}
|
||||
connection.BindProvider = func(username, password string) error {
|
||||
return expected
|
||||
}
|
||||
server := &Server{
|
||||
Connection: connection,
|
||||
Config: &ServerConfig{
|
||||
BindDN: "cn=%s,ou=users,dc=grafana,dc=org",
|
||||
},
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
err := server.UserBind("user", "pwd")
|
||||
assert.ErrorIs(t, err, expected)
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_AdminBind(t *testing.T) {
|
||||
t.Run("use admin DN and password", func(t *testing.T) {
|
||||
connection := &MockConnection{}
|
||||
var actualUsername, actualPassword string
|
||||
connection.BindProvider = func(username, password string) error {
|
||||
actualUsername = username
|
||||
actualPassword = password
|
||||
return nil
|
||||
}
|
||||
|
||||
dn := "cn=admin,dc=grafana,dc=org"
|
||||
|
||||
server := &Server{
|
||||
Connection: connection,
|
||||
Config: &ServerConfig{
|
||||
BindPassword: "pwd",
|
||||
BindDN: dn,
|
||||
},
|
||||
}
|
||||
|
||||
err := server.AdminBind()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, dn, actualUsername)
|
||||
assert.Equal(t, "pwd", actualPassword)
|
||||
})
|
||||
|
||||
t.Run("error", func(t *testing.T) {
|
||||
connection := &MockConnection{}
|
||||
expected := &ldap.Error{
|
||||
ResultCode: uint16(25),
|
||||
}
|
||||
connection.BindProvider = func(username, password string) error {
|
||||
return expected
|
||||
}
|
||||
|
||||
dn := "cn=admin,dc=grafana,dc=org"
|
||||
|
||||
server := &Server{
|
||||
Connection: connection,
|
||||
Config: &ServerConfig{
|
||||
BindPassword: "pwd",
|
||||
BindDN: dn,
|
||||
},
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
err := server.AdminBind()
|
||||
assert.ErrorIs(t, err, expected)
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user