mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
LDAP: Search all DNs for users (#38891)
This commit is contained in:
parent
98cca6317d
commit
ad971cc9be
@ -12,9 +12,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"gopkg.in/ldap.v3"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"gopkg.in/ldap.v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// IConnection is interface for LDAP connection manipulation
|
// IConnection is interface for LDAP connection manipulation
|
||||||
@ -252,16 +253,11 @@ func (server *Server) Users(logins []string) (
|
|||||||
[]*models.ExternalUserInfo,
|
[]*models.ExternalUserInfo,
|
||||||
error,
|
error,
|
||||||
) {
|
) {
|
||||||
var users []*ldap.Entry
|
var users [][]*ldap.Entry
|
||||||
err := getUsersIteration(logins, func(previous, current int) error {
|
err := getUsersIteration(logins, func(previous, current int) error {
|
||||||
entries, err := server.users(logins[previous:current])
|
var err error
|
||||||
if err != nil {
|
users, err = server.users(logins[previous:current])
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
users = append(users, entries...)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -308,13 +304,15 @@ func getUsersIteration(logins []string, fn func(int, int) error) error {
|
|||||||
|
|
||||||
// users is helper method for the Users()
|
// users is helper method for the Users()
|
||||||
func (server *Server) users(logins []string) (
|
func (server *Server) users(logins []string) (
|
||||||
[]*ldap.Entry,
|
[][]*ldap.Entry,
|
||||||
error,
|
error,
|
||||||
) {
|
) {
|
||||||
var result *ldap.SearchResult
|
var result *ldap.SearchResult
|
||||||
var Config = server.Config
|
var Config = server.Config
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
var entries = make([][]*ldap.Entry, 0, len(Config.SearchBaseDNs))
|
||||||
|
|
||||||
for _, base := range Config.SearchBaseDNs {
|
for _, base := range Config.SearchBaseDNs {
|
||||||
result, err = server.Connection.Search(
|
result, err = server.Connection.Search(
|
||||||
server.getSearchRequest(base, logins),
|
server.getSearchRequest(base, logins),
|
||||||
@ -324,11 +322,11 @@ func (server *Server) users(logins []string) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(result.Entries) > 0 {
|
if len(result.Entries) > 0 {
|
||||||
break
|
entries = append(entries, result.Entries)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.Entries, nil
|
return entries, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateGrafanaUser validates user access.
|
// validateGrafanaUser validates user access.
|
||||||
@ -557,17 +555,26 @@ func (server *Server) requestMemberOf(entry *ldap.Entry) ([]string, error) {
|
|||||||
// serializeUsers serializes the users
|
// serializeUsers serializes the users
|
||||||
// from LDAP result to ExternalInfo struct
|
// from LDAP result to ExternalInfo struct
|
||||||
func (server *Server) serializeUsers(
|
func (server *Server) serializeUsers(
|
||||||
entries []*ldap.Entry,
|
entries [][]*ldap.Entry,
|
||||||
) ([]*models.ExternalUserInfo, error) {
|
) ([]*models.ExternalUserInfo, error) {
|
||||||
var serialized []*models.ExternalUserInfo
|
var serialized []*models.ExternalUserInfo
|
||||||
|
var users = map[string]struct{}{}
|
||||||
|
|
||||||
for _, user := range entries {
|
for _, dn := range entries {
|
||||||
extUser, err := server.buildGrafanaUser(user)
|
for _, user := range dn {
|
||||||
if err != nil {
|
extUser, err := server.buildGrafanaUser(user)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := users[extUser.Login]; exists {
|
||||||
|
// ignore duplicates
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
users[extUser.Login] = struct{}{}
|
||||||
|
|
||||||
|
serialized = append(serialized, extUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
serialized = append(serialized, extUser)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return serialized, nil
|
return serialized, nil
|
||||||
|
@ -1,191 +1,141 @@
|
|||||||
package ldap
|
package ldap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
"github.com/stretchr/testify/assert"
|
||||||
"gopkg.in/ldap.v3"
|
"gopkg.in/ldap.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLDAPHelpers(t *testing.T) {
|
func TestIsMemberOf(t *testing.T) {
|
||||||
Convey("isMemberOf()", t, func() {
|
tests := []struct {
|
||||||
Convey("Wildcard", func() {
|
memberOf []string
|
||||||
result := isMemberOf([]string{}, "*")
|
group string
|
||||||
So(result, ShouldBeTrue)
|
expected bool
|
||||||
})
|
}{
|
||||||
|
{memberOf: []string{}, group: "*", expected: true},
|
||||||
|
{memberOf: []string{"one", "Two", "three"}, group: "two", expected: true},
|
||||||
|
{memberOf: []string{"one", "Two", "three"}, group: "twos", expected: false},
|
||||||
|
}
|
||||||
|
|
||||||
Convey("Should find one", func() {
|
for _, tc := range tests {
|
||||||
result := isMemberOf([]string{"one", "Two", "three"}, "two")
|
t.Run(fmt.Sprintf("isMemberOf(%v, \"%s\") = %v", tc.memberOf, tc.group, tc.expected), func(t *testing.T) {
|
||||||
So(result, ShouldBeTrue)
|
assert.Equal(t, tc.expected, isMemberOf(tc.memberOf, tc.group))
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Convey("Should not find one", func() {
|
func TestGetUsersIteration(t *testing.T) {
|
||||||
result := isMemberOf([]string{"one", "Two", "three"}, "twos")
|
const pageSize = UsersMaxRequest
|
||||||
So(result, ShouldBeFalse)
|
iterations := map[int]int{
|
||||||
})
|
0: 0,
|
||||||
})
|
400: 1,
|
||||||
|
600: 2,
|
||||||
|
1500: 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
for userCount, expectedIterations := range iterations {
|
||||||
|
t.Run(fmt.Sprintf("getUserIteration iterates %d times for %d users", expectedIterations, userCount), func(t *testing.T) {
|
||||||
|
logins := make([]string, userCount)
|
||||||
|
|
||||||
Convey("getUsersIteration()", t, func() {
|
|
||||||
Convey("it should execute twice for 600 users", func() {
|
|
||||||
logins := make([]string, 600)
|
|
||||||
i := 0
|
i := 0
|
||||||
|
_ = getUsersIteration(logins, func(first int, last int) error {
|
||||||
|
assert.Equal(t, pageSize*i, first)
|
||||||
|
|
||||||
result := getUsersIteration(logins, func(previous, current int) error {
|
expectedLast := pageSize*i + pageSize
|
||||||
i++
|
if expectedLast > userCount {
|
||||||
|
expectedLast = userCount
|
||||||
if i == 1 {
|
|
||||||
So(previous, ShouldEqual, 0)
|
|
||||||
So(current, ShouldEqual, 500)
|
|
||||||
} else {
|
|
||||||
So(previous, ShouldEqual, 500)
|
|
||||||
So(current, ShouldEqual, 600)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
assert.Equal(t, expectedLast, last)
|
||||||
})
|
|
||||||
|
|
||||||
So(i, ShouldEqual, 2)
|
|
||||||
So(result, ShouldBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("it should execute three times for 1500 users", func() {
|
|
||||||
logins := make([]string, 1500)
|
|
||||||
i := 0
|
|
||||||
|
|
||||||
result := getUsersIteration(logins, func(previous, current int) error {
|
|
||||||
i++
|
|
||||||
switch i {
|
|
||||||
case 1:
|
|
||||||
So(previous, ShouldEqual, 0)
|
|
||||||
So(current, ShouldEqual, 500)
|
|
||||||
case 2:
|
|
||||||
So(previous, ShouldEqual, 500)
|
|
||||||
So(current, ShouldEqual, 1000)
|
|
||||||
default:
|
|
||||||
So(previous, ShouldEqual, 1000)
|
|
||||||
So(current, ShouldEqual, 1500)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
So(i, ShouldEqual, 3)
|
|
||||||
So(result, ShouldBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("it should execute once for 400 users", func() {
|
|
||||||
logins := make([]string, 400)
|
|
||||||
i := 0
|
|
||||||
|
|
||||||
result := getUsersIteration(logins, func(previous, current int) error {
|
|
||||||
i++
|
|
||||||
if i == 1 {
|
|
||||||
So(previous, ShouldEqual, 0)
|
|
||||||
So(current, ShouldEqual, 400)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
So(i, ShouldEqual, 1)
|
|
||||||
So(result, ShouldBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("it should not execute for 0 users", func() {
|
|
||||||
logins := make([]string, 0)
|
|
||||||
i := 0
|
|
||||||
|
|
||||||
result := getUsersIteration(logins, func(previous, current int) error {
|
|
||||||
i++
|
i++
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
So(i, ShouldEqual, 0)
|
assert.Equal(t, expectedIterations, i)
|
||||||
So(result, ShouldBeNil)
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetAttribute(t *testing.T) {
|
||||||
|
t.Run("DN", func(t *testing.T) {
|
||||||
|
entry := &ldap.Entry{
|
||||||
|
DN: "test",
|
||||||
|
}
|
||||||
|
|
||||||
|
result := getAttribute("dn", entry)
|
||||||
|
assert.Equal(t, "test", result)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("getAttribute()", t, func() {
|
t.Run("username", func(t *testing.T) {
|
||||||
Convey("Should get DN", func() {
|
value := "roelgerrits"
|
||||||
entry := &ldap.Entry{
|
entry := &ldap.Entry{
|
||||||
DN: "test",
|
Attributes: []*ldap.EntryAttribute{
|
||||||
}
|
{
|
||||||
|
Name: "username", Values: []string{value},
|
||||||
result := getAttribute("dn", entry)
|
|
||||||
|
|
||||||
So(result, ShouldEqual, "test")
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should get username", func() {
|
|
||||||
value := []string{"roelgerrits"}
|
|
||||||
entry := &ldap.Entry{
|
|
||||||
Attributes: []*ldap.EntryAttribute{
|
|
||||||
{
|
|
||||||
Name: "username", Values: value,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
}
|
||||||
|
|
||||||
result := getAttribute("username", entry)
|
result := getAttribute("username", entry)
|
||||||
|
assert.Equal(t, value, result)
|
||||||
So(result, ShouldEqual, value[0])
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should not get anything", func() {
|
|
||||||
value := []string{"roelgerrits"}
|
|
||||||
entry := &ldap.Entry{
|
|
||||||
Attributes: []*ldap.EntryAttribute{
|
|
||||||
{
|
|
||||||
Name: "killa", Values: value,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
result := getAttribute("username", entry)
|
|
||||||
|
|
||||||
So(result, ShouldEqual, "")
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("getArrayAttribute()", t, func() {
|
t.Run("no result", func(t *testing.T) {
|
||||||
Convey("Should get DN", func() {
|
value := []string{"roelgerrits"}
|
||||||
entry := &ldap.Entry{
|
entry := &ldap.Entry{
|
||||||
DN: "test",
|
Attributes: []*ldap.EntryAttribute{
|
||||||
}
|
{
|
||||||
|
Name: "killa", Values: value,
|
||||||
result := getArrayAttribute("dn", entry)
|
|
||||||
|
|
||||||
So(result, ShouldResemble, []string{"test"})
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should get username", func() {
|
|
||||||
value := []string{"roelgerrits"}
|
|
||||||
entry := &ldap.Entry{
|
|
||||||
Attributes: []*ldap.EntryAttribute{
|
|
||||||
{
|
|
||||||
Name: "username", Values: value,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
}
|
||||||
|
|
||||||
result := getArrayAttribute("username", entry)
|
result := getAttribute("username", entry)
|
||||||
|
assert.Empty(t, result)
|
||||||
So(result, ShouldResemble, value)
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
Convey("Should not get anything", func() {
|
func TestGetArrayAttribute(t *testing.T) {
|
||||||
value := []string{"roelgerrits"}
|
t.Run("DN", func(t *testing.T) {
|
||||||
entry := &ldap.Entry{
|
entry := &ldap.Entry{
|
||||||
Attributes: []*ldap.EntryAttribute{
|
DN: "test",
|
||||||
{
|
}
|
||||||
Name: "username", Values: value,
|
|
||||||
},
|
result := getArrayAttribute("dn", entry)
|
||||||
},
|
|
||||||
}
|
assert.EqualValues(t, []string{"test"}, result)
|
||||||
|
})
|
||||||
result := getArrayAttribute("something", entry)
|
|
||||||
|
t.Run("username", func(t *testing.T) {
|
||||||
So(result, ShouldResemble, []string{})
|
value := []string{"roelgerrits"}
|
||||||
})
|
entry := &ldap.Entry{
|
||||||
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
{
|
||||||
|
Name: "username", Values: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := getArrayAttribute("username", entry)
|
||||||
|
|
||||||
|
assert.EqualValues(t, value, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no result", func(t *testing.T) {
|
||||||
|
value := []string{"roelgerrits"}
|
||||||
|
entry := &ldap.Entry{
|
||||||
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
{
|
||||||
|
Name: "username", Values: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := getArrayAttribute("something", entry)
|
||||||
|
|
||||||
|
assert.Empty(t, result)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -4,231 +4,227 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"gopkg.in/ldap.v3"
|
"gopkg.in/ldap.v3"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLDAPLogin(t *testing.T) {
|
var defaultLogin = &models.LoginUserQuery{
|
||||||
defaultLogin := &models.LoginUserQuery{
|
Username: "user",
|
||||||
Username: "user",
|
Password: "pwd",
|
||||||
Password: "pwd",
|
IpAddress: "192.168.1.1:56433",
|
||||||
IpAddress: "192.168.1.1:56433",
|
}
|
||||||
|
|
||||||
|
func TestServer_Login_UserBind_Fail(t *testing.T) {
|
||||||
|
connection := &MockConnection{}
|
||||||
|
entry := ldap.Entry{}
|
||||||
|
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
|
||||||
|
connection.setSearchResult(&result)
|
||||||
|
|
||||||
|
connection.BindProvider = func(username, password string) error {
|
||||||
|
return &ldap.Error{
|
||||||
|
ResultCode: 49,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
server := &Server{
|
||||||
|
Config: &ServerConfig{
|
||||||
|
SearchBaseDNs: []string{"BaseDNHere"},
|
||||||
|
},
|
||||||
|
Connection: connection,
|
||||||
|
log: log.New("test-logger"),
|
||||||
}
|
}
|
||||||
|
|
||||||
Convey("Login()", t, func() {
|
_, err := server.Login(defaultLogin)
|
||||||
Convey("Should get invalid credentials when userBind fails", func() {
|
|
||||||
connection := &MockConnection{}
|
|
||||||
entry := ldap.Entry{}
|
|
||||||
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
|
|
||||||
connection.setSearchResult(&result)
|
|
||||||
|
|
||||||
connection.BindProvider = func(username, password string) error {
|
assert.ErrorIs(t, err, ErrInvalidCredentials)
|
||||||
return &ldap.Error{
|
}
|
||||||
ResultCode: 49,
|
|
||||||
}
|
func TestServer_Login_Search_NoResult(t *testing.T) {
|
||||||
}
|
connection := &MockConnection{}
|
||||||
server := &Server{
|
result := ldap.SearchResult{Entries: []*ldap.Entry{}}
|
||||||
Config: &ServerConfig{
|
connection.setSearchResult(&result)
|
||||||
SearchBaseDNs: []string{"BaseDNHere"},
|
|
||||||
},
|
connection.BindProvider = func(username, password string) error {
|
||||||
Connection: connection,
|
return nil
|
||||||
log: log.New("test-logger"),
|
}
|
||||||
}
|
server := &Server{
|
||||||
|
Config: &ServerConfig{
|
||||||
_, err := server.Login(defaultLogin)
|
SearchBaseDNs: []string{"BaseDNHere"},
|
||||||
|
},
|
||||||
So(err, ShouldEqual, ErrInvalidCredentials)
|
Connection: connection,
|
||||||
})
|
log: log.New("test-logger"),
|
||||||
|
}
|
||||||
Convey("Returns an error when search didn't find anything", func() {
|
|
||||||
connection := &MockConnection{}
|
_, err := server.Login(defaultLogin)
|
||||||
result := ldap.SearchResult{Entries: []*ldap.Entry{}}
|
assert.ErrorIs(t, err, ErrCouldNotFindUser)
|
||||||
connection.setSearchResult(&result)
|
}
|
||||||
|
|
||||||
connection.BindProvider = func(username, password string) error {
|
func TestServer_Login_Search_Error(t *testing.T) {
|
||||||
return nil
|
connection := &MockConnection{}
|
||||||
}
|
expected := errors.New("Killa-gorilla")
|
||||||
server := &Server{
|
connection.setSearchError(expected)
|
||||||
Config: &ServerConfig{
|
|
||||||
SearchBaseDNs: []string{"BaseDNHere"},
|
connection.BindProvider = func(username, password string) error {
|
||||||
},
|
return nil
|
||||||
Connection: connection,
|
}
|
||||||
log: log.New("test-logger"),
|
server := &Server{
|
||||||
}
|
Config: &ServerConfig{
|
||||||
|
SearchBaseDNs: []string{"BaseDNHere"},
|
||||||
_, err := server.Login(defaultLogin)
|
},
|
||||||
|
Connection: connection,
|
||||||
So(err, ShouldEqual, ErrCouldNotFindUser)
|
log: log.New("test-logger"),
|
||||||
})
|
}
|
||||||
|
|
||||||
Convey("When search returns an error", func() {
|
_, err := server.Login(defaultLogin)
|
||||||
connection := &MockConnection{}
|
assert.ErrorIs(t, err, expected)
|
||||||
expected := errors.New("Killa-gorilla")
|
}
|
||||||
connection.setSearchError(expected)
|
|
||||||
|
func TestServer_Login_ValidCredentials(t *testing.T) {
|
||||||
connection.BindProvider = func(username, password string) error {
|
connection := &MockConnection{}
|
||||||
return nil
|
entry := ldap.Entry{
|
||||||
}
|
DN: "dn", Attributes: []*ldap.EntryAttribute{
|
||||||
server := &Server{
|
{Name: "username", Values: []string{"markelog"}},
|
||||||
Config: &ServerConfig{
|
{Name: "surname", Values: []string{"Gaidarenko"}},
|
||||||
SearchBaseDNs: []string{"BaseDNHere"},
|
{Name: "email", Values: []string{"markelog@gmail.com"}},
|
||||||
},
|
{Name: "name", Values: []string{"Oleg"}},
|
||||||
Connection: connection,
|
{Name: "memberof", Values: []string{"admins"}},
|
||||||
log: log.New("test-logger"),
|
},
|
||||||
}
|
}
|
||||||
|
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
|
||||||
_, err := server.Login(defaultLogin)
|
connection.setSearchResult(&result)
|
||||||
|
|
||||||
So(err, ShouldEqual, expected)
|
connection.BindProvider = func(username, password string) error {
|
||||||
})
|
return nil
|
||||||
|
}
|
||||||
Convey("When login with valid credentials", func() {
|
server := &Server{
|
||||||
connection := &MockConnection{}
|
Config: &ServerConfig{
|
||||||
entry := ldap.Entry{
|
Attr: AttributeMap{
|
||||||
DN: "dn", Attributes: []*ldap.EntryAttribute{
|
Username: "username",
|
||||||
{Name: "username", Values: []string{"markelog"}},
|
Name: "name",
|
||||||
{Name: "surname", Values: []string{"Gaidarenko"}},
|
MemberOf: "memberof",
|
||||||
{Name: "email", Values: []string{"markelog@gmail.com"}},
|
},
|
||||||
{Name: "name", Values: []string{"Oleg"}},
|
SearchBaseDNs: []string{"BaseDNHere"},
|
||||||
{Name: "memberof", Values: []string{"admins"}},
|
},
|
||||||
},
|
Connection: connection,
|
||||||
}
|
log: log.New("test-logger"),
|
||||||
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
|
}
|
||||||
connection.setSearchResult(&result)
|
|
||||||
|
resp, err := server.Login(defaultLogin)
|
||||||
connection.BindProvider = func(username, password string) error {
|
require.NoError(t, err)
|
||||||
return nil
|
assert.Equal(t, "markelog", resp.Login)
|
||||||
}
|
}
|
||||||
server := &Server{
|
|
||||||
Config: &ServerConfig{
|
// TestServer_Login_UnauthenticatedBind tests that unauthenticated bind
|
||||||
Attr: AttributeMap{
|
// is called when there is no admin password or user wildcard in the
|
||||||
Username: "username",
|
// bind_dn.
|
||||||
Name: "name",
|
func TestServer_Login_UnauthenticatedBind(t *testing.T) {
|
||||||
MemberOf: "memberof",
|
connection := &MockConnection{}
|
||||||
},
|
entry := ldap.Entry{
|
||||||
SearchBaseDNs: []string{"BaseDNHere"},
|
DN: "test",
|
||||||
},
|
}
|
||||||
Connection: connection,
|
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
|
||||||
log: log.New("test-logger"),
|
connection.setSearchResult(&result)
|
||||||
}
|
|
||||||
|
connection.UnauthenticatedBindProvider = func() error {
|
||||||
resp, err := server.Login(defaultLogin)
|
return nil
|
||||||
|
}
|
||||||
So(err, ShouldBeNil)
|
server := &Server{
|
||||||
So(resp.Login, ShouldEqual, "markelog")
|
Config: &ServerConfig{
|
||||||
})
|
SearchBaseDNs: []string{"BaseDNHere"},
|
||||||
|
},
|
||||||
Convey("Should perform unauthenticated bind without admin", func() {
|
Connection: connection,
|
||||||
connection := &MockConnection{}
|
log: log.New("test-logger"),
|
||||||
entry := ldap.Entry{
|
}
|
||||||
DN: "test",
|
|
||||||
}
|
user, err := server.Login(defaultLogin)
|
||||||
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
|
require.NoError(t, err)
|
||||||
connection.setSearchResult(&result)
|
assert.Equal(t, "test", user.AuthId)
|
||||||
|
assert.True(t, connection.UnauthenticatedBindCalled)
|
||||||
connection.UnauthenticatedBindProvider = func() error {
|
}
|
||||||
return nil
|
|
||||||
}
|
func TestServer_Login_AuthenticatedBind(t *testing.T) {
|
||||||
server := &Server{
|
connection := &MockConnection{}
|
||||||
Config: &ServerConfig{
|
entry := ldap.Entry{
|
||||||
SearchBaseDNs: []string{"BaseDNHere"},
|
DN: "test",
|
||||||
},
|
}
|
||||||
Connection: connection,
|
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
|
||||||
log: log.New("test-logger"),
|
connection.setSearchResult(&result)
|
||||||
}
|
|
||||||
|
adminUsername := ""
|
||||||
user, err := server.Login(defaultLogin)
|
adminPassword := ""
|
||||||
|
username := ""
|
||||||
So(err, ShouldBeNil)
|
password := ""
|
||||||
So(user.AuthId, ShouldEqual, "test")
|
|
||||||
So(connection.UnauthenticatedBindCalled, ShouldBeTrue)
|
i := 0
|
||||||
})
|
connection.BindProvider = func(name, pass string) error {
|
||||||
|
i++
|
||||||
Convey("Should perform authenticated binds", func() {
|
if i == 1 {
|
||||||
connection := &MockConnection{}
|
adminUsername = name
|
||||||
entry := ldap.Entry{
|
adminPassword = pass
|
||||||
DN: "test",
|
}
|
||||||
}
|
|
||||||
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
|
if i == 2 {
|
||||||
connection.setSearchResult(&result)
|
username = name
|
||||||
|
password = pass
|
||||||
adminUsername := ""
|
}
|
||||||
adminPassword := ""
|
|
||||||
username := ""
|
return nil
|
||||||
password := ""
|
}
|
||||||
|
server := &Server{
|
||||||
i := 0
|
Config: &ServerConfig{
|
||||||
connection.BindProvider = func(name, pass string) error {
|
BindDN: "killa",
|
||||||
i++
|
BindPassword: "gorilla",
|
||||||
if i == 1 {
|
SearchBaseDNs: []string{"BaseDNHere"},
|
||||||
adminUsername = name
|
},
|
||||||
adminPassword = pass
|
Connection: connection,
|
||||||
}
|
log: log.New("test-logger"),
|
||||||
|
}
|
||||||
if i == 2 {
|
|
||||||
username = name
|
user, err := server.Login(defaultLogin)
|
||||||
password = pass
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
|
assert.Equal(t, "test", user.AuthId)
|
||||||
return nil
|
assert.True(t, connection.BindCalled)
|
||||||
}
|
|
||||||
server := &Server{
|
assert.Equal(t, "killa", adminUsername)
|
||||||
Config: &ServerConfig{
|
assert.Equal(t, "gorilla", adminPassword)
|
||||||
BindDN: "killa",
|
|
||||||
BindPassword: "gorilla",
|
assert.Equal(t, "test", username)
|
||||||
SearchBaseDNs: []string{"BaseDNHere"},
|
assert.Equal(t, "pwd", password)
|
||||||
},
|
}
|
||||||
Connection: connection,
|
|
||||||
log: log.New("test-logger"),
|
func TestServer_Login_UserWildcardBind(t *testing.T) {
|
||||||
}
|
connection := &MockConnection{}
|
||||||
|
entry := ldap.Entry{
|
||||||
user, err := server.Login(defaultLogin)
|
DN: "test",
|
||||||
|
}
|
||||||
So(err, ShouldBeNil)
|
connection.setSearchResult(&ldap.SearchResult{Entries: []*ldap.Entry{&entry}})
|
||||||
|
|
||||||
So(user.AuthId, ShouldEqual, "test")
|
authBindUser := ""
|
||||||
So(connection.BindCalled, ShouldBeTrue)
|
authBindPassword := ""
|
||||||
|
|
||||||
So(adminUsername, ShouldEqual, "killa")
|
connection.BindProvider = func(name, pass string) error {
|
||||||
So(adminPassword, ShouldEqual, "gorilla")
|
authBindUser = name
|
||||||
|
authBindPassword = pass
|
||||||
So(username, ShouldEqual, "test")
|
return nil
|
||||||
So(password, ShouldEqual, "pwd")
|
}
|
||||||
})
|
server := &Server{
|
||||||
Convey("Should bind with user if %s exists in the bind_dn", func() {
|
Config: &ServerConfig{
|
||||||
connection := &MockConnection{}
|
BindDN: "cn=%s,ou=users,dc=grafana,dc=org",
|
||||||
entry := ldap.Entry{
|
SearchBaseDNs: []string{"BaseDNHere"},
|
||||||
DN: "test",
|
},
|
||||||
}
|
Connection: connection,
|
||||||
connection.setSearchResult(&ldap.SearchResult{Entries: []*ldap.Entry{&entry}})
|
log: log.New("test-logger"),
|
||||||
|
}
|
||||||
authBindUser := ""
|
|
||||||
authBindPassword := ""
|
_, err := server.Login(defaultLogin)
|
||||||
|
require.NoError(t, err)
|
||||||
connection.BindProvider = func(name, pass string) error {
|
|
||||||
authBindUser = name
|
assert.Equal(t, "cn=user,ou=users,dc=grafana,dc=org", authBindUser)
|
||||||
authBindPassword = pass
|
assert.Equal(t, "pwd", authBindPassword)
|
||||||
return nil
|
assert.True(t, connection.BindCalled)
|
||||||
}
|
|
||||||
server := &Server{
|
|
||||||
Config: &ServerConfig{
|
|
||||||
BindDN: "cn=%s,ou=users,dc=grafana,dc=org",
|
|
||||||
SearchBaseDNs: []string{"BaseDNHere"},
|
|
||||||
},
|
|
||||||
Connection: connection,
|
|
||||||
log: log.New("test-logger"),
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := server.Login(defaultLogin)
|
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
So(authBindUser, ShouldEqual, "cn=user,ou=users,dc=grafana,dc=org")
|
|
||||||
So(authBindPassword, ShouldEqual, "pwd")
|
|
||||||
So(connection.BindCalled, ShouldBeTrue)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@ -3,271 +3,252 @@ package ldap
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"gopkg.in/ldap.v3"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
|
||||||
"gopkg.in/ldap.v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLDAPPrivateMethods(t *testing.T) {
|
func TestServer_getSearchRequest(t *testing.T) {
|
||||||
Convey("getSearchRequest()", t, func() {
|
expected := &ldap.SearchRequest{
|
||||||
Convey("with enabled GroupSearchFilterUserAttribute setting", func() {
|
BaseDN: "killa",
|
||||||
server := &Server{
|
Scope: 2,
|
||||||
Config: &ServerConfig{
|
DerefAliases: 0,
|
||||||
Attr: AttributeMap{
|
SizeLimit: 0,
|
||||||
Username: "username",
|
TimeLimit: 0,
|
||||||
Name: "name",
|
TypesOnly: false,
|
||||||
MemberOf: "memberof",
|
Filter: "(|)",
|
||||||
Email: "email",
|
Attributes: []string{
|
||||||
},
|
"username",
|
||||||
GroupSearchFilterUserAttribute: "gansta",
|
"email",
|
||||||
SearchBaseDNs: []string{"BaseDNHere"},
|
"name",
|
||||||
},
|
"memberof",
|
||||||
log: log.New("test-logger"),
|
"gansta",
|
||||||
}
|
},
|
||||||
|
Controls: nil,
|
||||||
|
}
|
||||||
|
|
||||||
result := server.getSearchRequest("killa", []string{"gorilla"})
|
server := &Server{
|
||||||
|
Config: &ServerConfig{
|
||||||
|
Attr: AttributeMap{
|
||||||
|
Username: "username",
|
||||||
|
Name: "name",
|
||||||
|
MemberOf: "memberof",
|
||||||
|
Email: "email",
|
||||||
|
},
|
||||||
|
GroupSearchFilterUserAttribute: "gansta",
|
||||||
|
SearchBaseDNs: []string{"BaseDNHere"},
|
||||||
|
},
|
||||||
|
log: log.New("test-logger"),
|
||||||
|
}
|
||||||
|
|
||||||
So(result, ShouldResemble, &ldap.SearchRequest{
|
result := server.getSearchRequest("killa", []string{"gorilla"})
|
||||||
BaseDN: "killa",
|
|
||||||
Scope: 2,
|
assert.EqualValues(t, expected, result)
|
||||||
DerefAliases: 0,
|
}
|
||||||
SizeLimit: 0,
|
|
||||||
TimeLimit: 0,
|
func TestSerializeUsers(t *testing.T) {
|
||||||
TypesOnly: false,
|
t.Run("simple case", func(t *testing.T) {
|
||||||
Filter: "(|)",
|
server := &Server{
|
||||||
Attributes: []string{
|
Config: &ServerConfig{
|
||||||
"username",
|
Attr: AttributeMap{
|
||||||
"email",
|
Username: "username",
|
||||||
"name",
|
Name: "name",
|
||||||
"memberof",
|
MemberOf: "memberof",
|
||||||
"gansta",
|
Email: "email",
|
||||||
},
|
},
|
||||||
Controls: nil,
|
SearchBaseDNs: []string{"BaseDNHere"},
|
||||||
})
|
},
|
||||||
})
|
Connection: &MockConnection{},
|
||||||
|
log: log.New("test-logger"),
|
||||||
|
}
|
||||||
|
|
||||||
|
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"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
users := [][]*ldap.Entry{{&entry}}
|
||||||
|
|
||||||
|
result, err := server.serializeUsers(users)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "roelgerrits", result[0].Login)
|
||||||
|
assert.Equal(t, "roel@test.com", result[0].Email)
|
||||||
|
assert.Contains(t, result[0].Groups, "admins")
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("serializeUsers()", t, func() {
|
t.Run("without lastname", func(t *testing.T) {
|
||||||
Convey("simple case", func() {
|
server := &Server{
|
||||||
server := &Server{
|
Config: &ServerConfig{
|
||||||
Config: &ServerConfig{
|
Attr: AttributeMap{
|
||||||
Attr: AttributeMap{
|
Username: "username",
|
||||||
Username: "username",
|
Name: "name",
|
||||||
Name: "name",
|
MemberOf: "memberof",
|
||||||
MemberOf: "memberof",
|
Email: "email",
|
||||||
Email: "email",
|
|
||||||
},
|
|
||||||
SearchBaseDNs: []string{"BaseDNHere"},
|
|
||||||
},
|
},
|
||||||
Connection: &MockConnection{},
|
SearchBaseDNs: []string{"BaseDNHere"},
|
||||||
log: log.New("test-logger"),
|
},
|
||||||
}
|
Connection: &MockConnection{},
|
||||||
|
log: log.New("test-logger"),
|
||||||
|
}
|
||||||
|
|
||||||
entry := ldap.Entry{
|
entry := ldap.Entry{
|
||||||
DN: "dn",
|
DN: "dn",
|
||||||
Attributes: []*ldap.EntryAttribute{
|
Attributes: []*ldap.EntryAttribute{
|
||||||
{Name: "username", Values: []string{"roelgerrits"}},
|
{Name: "username", Values: []string{"roelgerrits"}},
|
||||||
{Name: "surname", Values: []string{"Gerrits"}},
|
{Name: "email", Values: []string{"roel@test.com"}},
|
||||||
{Name: "email", Values: []string{"roel@test.com"}},
|
{Name: "name", Values: []string{"Roel"}},
|
||||||
{Name: "name", Values: []string{"Roel"}},
|
{Name: "memberof", Values: []string{"admins"}},
|
||||||
{Name: "memberof", Values: []string{"admins"}},
|
},
|
||||||
},
|
}
|
||||||
}
|
users := [][]*ldap.Entry{{&entry}}
|
||||||
users := []*ldap.Entry{&entry}
|
|
||||||
|
|
||||||
result, err := server.serializeUsers(users)
|
result, err := server.serializeUsers(users)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
assert.False(t, result[0].IsDisabled)
|
||||||
So(result[0].Login, ShouldEqual, "roelgerrits")
|
assert.Equal(t, "Roel", result[0].Name)
|
||||||
So(result[0].Email, ShouldEqual, "roel@test.com")
|
|
||||||
So(result[0].Groups, ShouldContain, "admins")
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("without lastname", func() {
|
|
||||||
server := &Server{
|
|
||||||
Config: &ServerConfig{
|
|
||||||
Attr: AttributeMap{
|
|
||||||
Username: "username",
|
|
||||||
Name: "name",
|
|
||||||
MemberOf: "memberof",
|
|
||||||
Email: "email",
|
|
||||||
},
|
|
||||||
SearchBaseDNs: []string{"BaseDNHere"},
|
|
||||||
},
|
|
||||||
Connection: &MockConnection{},
|
|
||||||
log: log.New("test-logger"),
|
|
||||||
}
|
|
||||||
|
|
||||||
entry := ldap.Entry{
|
|
||||||
DN: "dn",
|
|
||||||
Attributes: []*ldap.EntryAttribute{
|
|
||||||
{Name: "username", Values: []string{"roelgerrits"}},
|
|
||||||
{Name: "email", Values: []string{"roel@test.com"}},
|
|
||||||
{Name: "name", Values: []string{"Roel"}},
|
|
||||||
{Name: "memberof", Values: []string{"admins"}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
users := []*ldap.Entry{&entry}
|
|
||||||
|
|
||||||
result, err := server.serializeUsers(users)
|
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(result[0].IsDisabled, ShouldBeFalse)
|
|
||||||
So(result[0].Name, ShouldEqual, "Roel")
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("a user without matching groups should be marked as disabled", func() {
|
|
||||||
server := &Server{
|
|
||||||
Config: &ServerConfig{
|
|
||||||
Groups: []*GroupToOrgRole{{
|
|
||||||
GroupDN: "foo",
|
|
||||||
OrgId: 1,
|
|
||||||
OrgRole: models.ROLE_EDITOR,
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
Connection: &MockConnection{},
|
|
||||||
log: log.New("test-logger"),
|
|
||||||
}
|
|
||||||
|
|
||||||
entry := ldap.Entry{
|
|
||||||
DN: "dn",
|
|
||||||
Attributes: []*ldap.EntryAttribute{
|
|
||||||
{Name: "memberof", Values: []string{"admins"}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
users := []*ldap.Entry{&entry}
|
|
||||||
|
|
||||||
result, err := server.serializeUsers(users)
|
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(len(result), ShouldEqual, 1)
|
|
||||||
So(result[0].IsDisabled, ShouldBeTrue)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("validateGrafanaUser()", t, func() {
|
t.Run("mark user without matching group as disabled", func(t *testing.T) {
|
||||||
Convey("Returns error when user does not belong in any of the specified LDAP groups", func() {
|
server := &Server{
|
||||||
server := &Server{
|
Config: &ServerConfig{
|
||||||
Config: &ServerConfig{
|
Groups: []*GroupToOrgRole{{
|
||||||
Groups: []*GroupToOrgRole{
|
GroupDN: "foo",
|
||||||
{
|
OrgId: 1,
|
||||||
OrgId: 1,
|
OrgRole: models.ROLE_EDITOR,
|
||||||
},
|
}},
|
||||||
},
|
},
|
||||||
},
|
Connection: &MockConnection{},
|
||||||
log: logger.New("test"),
|
log: log.New("test-logger"),
|
||||||
}
|
}
|
||||||
|
|
||||||
user := &models.ExternalUserInfo{
|
entry := ldap.Entry{
|
||||||
Login: "markelog",
|
DN: "dn",
|
||||||
}
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
{Name: "memberof", Values: []string{"admins"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
users := [][]*ldap.Entry{{&entry}}
|
||||||
|
|
||||||
result := server.validateGrafanaUser(user)
|
result, err := server.serializeUsers(users)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
So(result, ShouldEqual, ErrInvalidCredentials)
|
assert.Len(t, result, 1)
|
||||||
})
|
assert.True(t, result[0].IsDisabled)
|
||||||
|
})
|
||||||
Convey("Does not return error when group config is empty", func() {
|
}
|
||||||
server := &Server{
|
|
||||||
Config: &ServerConfig{
|
func TestServer_validateGrafanaUser(t *testing.T) {
|
||||||
Groups: []*GroupToOrgRole{},
|
t.Run("no group config", func(t *testing.T) {
|
||||||
},
|
server := &Server{
|
||||||
log: logger.New("test"),
|
Config: &ServerConfig{
|
||||||
}
|
Groups: []*GroupToOrgRole{},
|
||||||
|
},
|
||||||
user := &models.ExternalUserInfo{
|
log: logger.New("test"),
|
||||||
Login: "markelog",
|
}
|
||||||
}
|
|
||||||
|
user := &models.ExternalUserInfo{
|
||||||
result := server.validateGrafanaUser(user)
|
Login: "markelog",
|
||||||
|
}
|
||||||
So(result, ShouldBeNil)
|
|
||||||
})
|
err := server.validateGrafanaUser(user)
|
||||||
|
require.NoError(t, err)
|
||||||
Convey("Does not return error when groups are there", func() {
|
})
|
||||||
server := &Server{
|
|
||||||
Config: &ServerConfig{
|
t.Run("user in group", func(t *testing.T) {
|
||||||
Groups: []*GroupToOrgRole{
|
server := &Server{
|
||||||
{
|
Config: &ServerConfig{
|
||||||
OrgId: 1,
|
Groups: []*GroupToOrgRole{
|
||||||
},
|
{
|
||||||
},
|
OrgId: 1,
|
||||||
},
|
},
|
||||||
log: logger.New("test"),
|
},
|
||||||
}
|
},
|
||||||
|
log: logger.New("test"),
|
||||||
user := &models.ExternalUserInfo{
|
}
|
||||||
Login: "markelog",
|
|
||||||
OrgRoles: map[int64]models.RoleType{
|
user := &models.ExternalUserInfo{
|
||||||
1: "test",
|
Login: "markelog",
|
||||||
},
|
OrgRoles: map[int64]models.RoleType{
|
||||||
}
|
1: "test",
|
||||||
|
},
|
||||||
result := server.validateGrafanaUser(user)
|
}
|
||||||
|
|
||||||
So(result, ShouldBeNil)
|
err := server.validateGrafanaUser(user)
|
||||||
})
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("shouldAdminBind()", t, func() {
|
t.Run("user not in group", func(t *testing.T) {
|
||||||
Convey("it should require admin userBind", func() {
|
server := &Server{
|
||||||
server := &Server{
|
Config: &ServerConfig{
|
||||||
Config: &ServerConfig{
|
Groups: []*GroupToOrgRole{
|
||||||
BindPassword: "test",
|
{
|
||||||
},
|
OrgId: 1,
|
||||||
}
|
},
|
||||||
|
},
|
||||||
result := server.shouldAdminBind()
|
},
|
||||||
So(result, ShouldBeTrue)
|
log: logger.New("test"),
|
||||||
})
|
}
|
||||||
|
|
||||||
Convey("it should not require admin userBind", func() {
|
user := &models.ExternalUserInfo{
|
||||||
server := &Server{
|
Login: "markelog",
|
||||||
Config: &ServerConfig{
|
}
|
||||||
BindPassword: "",
|
|
||||||
},
|
err := server.validateGrafanaUser(user)
|
||||||
}
|
require.ErrorIs(t, err, ErrInvalidCredentials)
|
||||||
|
})
|
||||||
result := server.shouldAdminBind()
|
}
|
||||||
So(result, ShouldBeFalse)
|
|
||||||
})
|
func TestServer_binds(t *testing.T) {
|
||||||
})
|
t.Run("single bind with cn wildcard", func(t *testing.T) {
|
||||||
|
server := &Server{
|
||||||
Convey("shouldSingleBind()", t, func() {
|
Config: &ServerConfig{
|
||||||
Convey("it should allow single bind", func() {
|
BindDN: "cn=%s,dc=grafana,dc=org",
|
||||||
server := &Server{
|
},
|
||||||
Config: &ServerConfig{
|
}
|
||||||
BindDN: "cn=%s,dc=grafana,dc=org",
|
|
||||||
},
|
assert.True(t, server.shouldSingleBind())
|
||||||
}
|
assert.Equal(t, "cn=test,dc=grafana,dc=org", server.singleBindDN("test"))
|
||||||
|
})
|
||||||
result := server.shouldSingleBind()
|
|
||||||
So(result, ShouldBeTrue)
|
t.Run("don't single bind", func(t *testing.T) {
|
||||||
})
|
server := &Server{
|
||||||
|
Config: &ServerConfig{
|
||||||
Convey("it should not allow single bind", func() {
|
BindDN: "cn=admin,dc=grafana,dc=org",
|
||||||
server := &Server{
|
},
|
||||||
Config: &ServerConfig{
|
}
|
||||||
BindDN: "cn=admin,dc=grafana,dc=org",
|
|
||||||
},
|
assert.False(t, server.shouldSingleBind())
|
||||||
}
|
})
|
||||||
|
|
||||||
result := server.shouldSingleBind()
|
t.Run("admin user bind", func(t *testing.T) {
|
||||||
So(result, ShouldBeFalse)
|
server := &Server{
|
||||||
})
|
Config: &ServerConfig{
|
||||||
})
|
BindPassword: "test",
|
||||||
|
},
|
||||||
Convey("singleBindDN()", t, func() {
|
}
|
||||||
Convey("it should allow single bind", func() {
|
|
||||||
server := &Server{
|
assert.True(t, server.shouldAdminBind())
|
||||||
Config: &ServerConfig{
|
})
|
||||||
BindDN: "cn=%s,dc=grafana,dc=org",
|
|
||||||
},
|
t.Run("don't admin user bind", func(t *testing.T) {
|
||||||
}
|
server := &Server{
|
||||||
|
Config: &ServerConfig{
|
||||||
result := server.singleBindDN("test")
|
BindPassword: "",
|
||||||
So(result, ShouldEqual, "cn=test,dc=grafana,dc=org")
|
},
|
||||||
})
|
}
|
||||||
|
|
||||||
|
assert.False(t, server.shouldAdminBind())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -2,226 +2,319 @@ package ldap
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/stretchr/testify/assert"
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
"github.com/stretchr/testify/require"
|
||||||
"gopkg.in/ldap.v3"
|
"gopkg.in/ldap.v3"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPublicAPI(t *testing.T) {
|
func TestNew(t *testing.T) {
|
||||||
Convey("New()", t, func() {
|
result := New(&ServerConfig{
|
||||||
Convey("Should return ", func() {
|
Attr: AttributeMap{},
|
||||||
result := New(&ServerConfig{
|
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{},
|
Attr: AttributeMap{},
|
||||||
SearchBaseDNs: []string{"BaseDNHere"},
|
SearchBaseDNs: []string{"BaseDNHere"},
|
||||||
})
|
},
|
||||||
|
Connection: connection,
|
||||||
|
}
|
||||||
|
|
||||||
So(result, ShouldImplement, (*IServer)(nil))
|
assert.NotPanics(t, server.Close)
|
||||||
})
|
assert.True(t, connection.CloseCalled)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Close()", t, func() {
|
t.Run("panic if no connection", func(t *testing.T) {
|
||||||
Convey("Should close the connection", func() {
|
server := &Server{
|
||||||
connection := &MockConnection{}
|
Config: &ServerConfig{
|
||||||
|
Attr: AttributeMap{},
|
||||||
|
SearchBaseDNs: []string{"BaseDNHere"},
|
||||||
|
},
|
||||||
|
Connection: nil,
|
||||||
|
}
|
||||||
|
|
||||||
server := &Server{
|
assert.Panics(t, server.Close)
|
||||||
Config: &ServerConfig{
|
})
|
||||||
Attr: AttributeMap{},
|
}
|
||||||
SearchBaseDNs: []string{"BaseDNHere"},
|
|
||||||
},
|
func TestServer_Users(t *testing.T) {
|
||||||
Connection: connection,
|
t.Run("one user", func(t *testing.T) {
|
||||||
}
|
conn := &MockConnection{}
|
||||||
|
entry := ldap.Entry{
|
||||||
So(server.Close, ShouldNotPanic)
|
DN: "dn", Attributes: []*ldap.EntryAttribute{
|
||||||
So(connection.CloseCalled, ShouldBeTrue)
|
{Name: "username", Values: []string{"roelgerrits"}},
|
||||||
})
|
{Name: "surname", Values: []string{"Gerrits"}},
|
||||||
|
{Name: "email", Values: []string{"roel@test.com"}},
|
||||||
Convey("Should panic if no connection is established", func() {
|
{Name: "name", Values: []string{"Roel"}},
|
||||||
server := &Server{
|
{Name: "memberof", Values: []string{"admins"}},
|
||||||
Config: &ServerConfig{
|
}}
|
||||||
Attr: AttributeMap{},
|
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
|
||||||
SearchBaseDNs: []string{"BaseDNHere"},
|
conn.setSearchResult(&result)
|
||||||
},
|
|
||||||
Connection: nil,
|
// Set up attribute map without surname and email
|
||||||
}
|
server := &Server{
|
||||||
|
Config: &ServerConfig{
|
||||||
So(server.Close, ShouldPanic)
|
Attr: AttributeMap{
|
||||||
})
|
Username: "username",
|
||||||
})
|
Name: "name",
|
||||||
Convey("Users()", t, func() {
|
MemberOf: "memberof",
|
||||||
Convey("Finds one user", func() {
|
},
|
||||||
MockConnection := &MockConnection{}
|
SearchBaseDNs: []string{"BaseDNHere"},
|
||||||
entry := ldap.Entry{
|
},
|
||||||
DN: "dn", Attributes: []*ldap.EntryAttribute{
|
Connection: conn,
|
||||||
{Name: "username", Values: []string{"roelgerrits"}},
|
log: log.New("test-logger"),
|
||||||
{Name: "surname", Values: []string{"Gerrits"}},
|
}
|
||||||
{Name: "email", Values: []string{"roel@test.com"}},
|
|
||||||
{Name: "name", Values: []string{"Roel"}},
|
searchResult, err := server.Users([]string{"roelgerrits"})
|
||||||
{Name: "memberof", Values: []string{"admins"}},
|
|
||||||
}}
|
require.NoError(t, err)
|
||||||
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
|
assert.NotNil(t, searchResult)
|
||||||
MockConnection.setSearchResult(&result)
|
|
||||||
|
// User should be searched in ldap
|
||||||
// Set up attribute map without surname and email
|
assert.True(t, conn.SearchCalled)
|
||||||
server := &Server{
|
// No empty attributes should be added to the search request
|
||||||
Config: &ServerConfig{
|
assert.Len(t, conn.SearchAttributes, 3)
|
||||||
Attr: AttributeMap{
|
})
|
||||||
Username: "username",
|
|
||||||
Name: "name",
|
t.Run("error", func(t *testing.T) {
|
||||||
MemberOf: "memberof",
|
expected := errors.New("Killa-gorilla")
|
||||||
},
|
conn := &MockConnection{}
|
||||||
SearchBaseDNs: []string{"BaseDNHere"},
|
conn.setSearchError(expected)
|
||||||
},
|
|
||||||
Connection: MockConnection,
|
// Set up attribute map without surname and email
|
||||||
log: log.New("test-logger"),
|
server := &Server{
|
||||||
}
|
Config: &ServerConfig{
|
||||||
|
SearchBaseDNs: []string{"BaseDNHere"},
|
||||||
searchResult, err := server.Users([]string{"roelgerrits"})
|
},
|
||||||
|
Connection: conn,
|
||||||
So(err, ShouldBeNil)
|
log: log.New("test-logger"),
|
||||||
So(searchResult, ShouldNotBeNil)
|
}
|
||||||
|
|
||||||
// User should be searched in ldap
|
_, err := server.Users([]string{"roelgerrits"})
|
||||||
So(MockConnection.SearchCalled, ShouldBeTrue)
|
|
||||||
|
assert.ErrorIs(t, err, expected)
|
||||||
// No empty attributes should be added to the search request
|
})
|
||||||
So(len(MockConnection.SearchAttributes), ShouldEqual, 3)
|
|
||||||
})
|
t.Run("no user", func(t *testing.T) {
|
||||||
|
conn := &MockConnection{}
|
||||||
Convey("Handles a error", func() {
|
result := ldap.SearchResult{Entries: []*ldap.Entry{}}
|
||||||
expected := errors.New("Killa-gorilla")
|
conn.setSearchResult(&result)
|
||||||
MockConnection := &MockConnection{}
|
|
||||||
MockConnection.setSearchError(expected)
|
// Set up attribute map without surname and email
|
||||||
|
server := &Server{
|
||||||
// Set up attribute map without surname and email
|
Config: &ServerConfig{
|
||||||
server := &Server{
|
SearchBaseDNs: []string{"BaseDNHere"},
|
||||||
Config: &ServerConfig{
|
},
|
||||||
SearchBaseDNs: []string{"BaseDNHere"},
|
Connection: conn,
|
||||||
},
|
log: log.New("test-logger"),
|
||||||
Connection: MockConnection,
|
}
|
||||||
log: log.New("test-logger"),
|
|
||||||
}
|
searchResult, err := server.Users([]string{"roelgerrits"})
|
||||||
|
|
||||||
_, err := server.Users([]string{"roelgerrits"})
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, searchResult)
|
||||||
So(err, ShouldEqual, expected)
|
})
|
||||||
})
|
|
||||||
|
t.Run("multiple DNs", func(t *testing.T) {
|
||||||
Convey("Should return empty slice if none were found", func() {
|
conn := &MockConnection{}
|
||||||
MockConnection := &MockConnection{}
|
serviceDN := "dc=svc,dc=example,dc=org"
|
||||||
result := ldap.SearchResult{Entries: []*ldap.Entry{}}
|
serviceEntry := ldap.Entry{
|
||||||
MockConnection.setSearchResult(&result)
|
DN: "dn", Attributes: []*ldap.EntryAttribute{
|
||||||
|
{Name: "username", Values: []string{"imgrenderer"}},
|
||||||
// Set up attribute map without surname and email
|
{Name: "name", Values: []string{"Image renderer"}},
|
||||||
server := &Server{
|
}}
|
||||||
Config: &ServerConfig{
|
services := ldap.SearchResult{Entries: []*ldap.Entry{&serviceEntry}}
|
||||||
SearchBaseDNs: []string{"BaseDNHere"},
|
|
||||||
},
|
userDN := "dc=users,dc=example,dc=org"
|
||||||
Connection: MockConnection,
|
userEntry := ldap.Entry{
|
||||||
log: log.New("test-logger"),
|
DN: "dn", Attributes: []*ldap.EntryAttribute{
|
||||||
}
|
{Name: "username", Values: []string{"grot"}},
|
||||||
|
{Name: "name", Values: []string{"Grot"}},
|
||||||
searchResult, err := server.Users([]string{"roelgerrits"})
|
}}
|
||||||
|
users := ldap.SearchResult{Entries: []*ldap.Entry{&userEntry}}
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(searchResult, ShouldBeEmpty)
|
conn.setSearchFunc(func(request *ldap.SearchRequest) (*ldap.SearchResult, error) {
|
||||||
})
|
switch request.BaseDN {
|
||||||
})
|
case userDN:
|
||||||
|
return &users, nil
|
||||||
Convey("UserBind()", t, func() {
|
case serviceDN:
|
||||||
Convey("Should use provided DN and password", func() {
|
return &services, nil
|
||||||
connection := &MockConnection{}
|
default:
|
||||||
var actualUsername, actualPassword string
|
return nil, fmt.Errorf("test case not defined for baseDN: '%s'", request.BaseDN)
|
||||||
connection.BindProvider = func(username, password string) error {
|
}
|
||||||
actualUsername = username
|
})
|
||||||
actualPassword = password
|
|
||||||
return nil
|
server := &Server{
|
||||||
}
|
Config: &ServerConfig{
|
||||||
server := &Server{
|
Attr: AttributeMap{
|
||||||
Connection: connection,
|
Username: "username",
|
||||||
Config: &ServerConfig{
|
Name: "name",
|
||||||
BindDN: "cn=admin,dc=grafana,dc=org",
|
},
|
||||||
},
|
SearchBaseDNs: []string{serviceDN, userDN},
|
||||||
}
|
},
|
||||||
|
Connection: conn,
|
||||||
dn := "cn=user,ou=users,dc=grafana,dc=org"
|
log: log.New("test-logger"),
|
||||||
err := server.UserBind(dn, "pwd")
|
}
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
searchResult, err := server.Users([]string{"imgrenderer", "grot"})
|
||||||
So(actualUsername, ShouldEqual, dn)
|
require.NoError(t, err)
|
||||||
So(actualPassword, ShouldEqual, "pwd")
|
|
||||||
})
|
assert.Len(t, searchResult, 2)
|
||||||
|
})
|
||||||
Convey("Should handle an error", func() {
|
|
||||||
connection := &MockConnection{}
|
t.Run("same user in multiple DNs", func(t *testing.T) {
|
||||||
expected := &ldap.Error{
|
conn := &MockConnection{}
|
||||||
ResultCode: uint16(25),
|
firstDN := "dc=users1,dc=example,dc=org"
|
||||||
}
|
firstEntry := ldap.Entry{
|
||||||
connection.BindProvider = func(username, password string) error {
|
DN: "dn", Attributes: []*ldap.EntryAttribute{
|
||||||
return expected
|
{Name: "username", Values: []string{"grot"}},
|
||||||
}
|
{Name: "name", Values: []string{"Grot the First"}},
|
||||||
server := &Server{
|
}}
|
||||||
Connection: connection,
|
firsts := ldap.SearchResult{Entries: []*ldap.Entry{&firstEntry}}
|
||||||
Config: &ServerConfig{
|
|
||||||
BindDN: "cn=%s,ou=users,dc=grafana,dc=org",
|
secondDN := "dc=users2,dc=example,dc=org"
|
||||||
},
|
secondEntry := ldap.Entry{
|
||||||
log: log.New("test-logger"),
|
DN: "dn", Attributes: []*ldap.EntryAttribute{
|
||||||
}
|
{Name: "username", Values: []string{"grot"}},
|
||||||
err := server.UserBind("user", "pwd")
|
{Name: "name", Values: []string{"Grot the Second"}},
|
||||||
So(err, ShouldEqual, expected)
|
}}
|
||||||
})
|
seconds := ldap.SearchResult{Entries: []*ldap.Entry{&secondEntry}}
|
||||||
})
|
|
||||||
|
conn.setSearchFunc(func(request *ldap.SearchRequest) (*ldap.SearchResult, error) {
|
||||||
Convey("AdminBind()", t, func() {
|
switch request.BaseDN {
|
||||||
Convey("Should use admin DN and password", func() {
|
case secondDN:
|
||||||
connection := &MockConnection{}
|
return &seconds, nil
|
||||||
var actualUsername, actualPassword string
|
case firstDN:
|
||||||
connection.BindProvider = func(username, password string) error {
|
return &firsts, nil
|
||||||
actualUsername = username
|
default:
|
||||||
actualPassword = password
|
return nil, fmt.Errorf("test case not defined for baseDN: '%s'", request.BaseDN)
|
||||||
return nil
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
dn := "cn=admin,dc=grafana,dc=org"
|
server := &Server{
|
||||||
|
Config: &ServerConfig{
|
||||||
server := &Server{
|
Attr: AttributeMap{
|
||||||
Connection: connection,
|
Username: "username",
|
||||||
Config: &ServerConfig{
|
Name: "name",
|
||||||
BindPassword: "pwd",
|
},
|
||||||
BindDN: dn,
|
SearchBaseDNs: []string{firstDN, secondDN},
|
||||||
},
|
},
|
||||||
}
|
Connection: conn,
|
||||||
|
log: log.New("test-logger"),
|
||||||
err := server.AdminBind()
|
}
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
res, err := server.Users([]string{"grot"})
|
||||||
So(actualUsername, ShouldEqual, dn)
|
require.NoError(t, err)
|
||||||
So(actualPassword, ShouldEqual, "pwd")
|
require.Len(t, res, 1)
|
||||||
})
|
assert.Equal(t, "Grot the First", res[0].Name)
|
||||||
|
})
|
||||||
Convey("Should handle an error", func() {
|
}
|
||||||
connection := &MockConnection{}
|
|
||||||
expected := &ldap.Error{
|
func TestServer_UserBind(t *testing.T) {
|
||||||
ResultCode: uint16(25),
|
t.Run("use provided DN and password", func(t *testing.T) {
|
||||||
}
|
connection := &MockConnection{}
|
||||||
connection.BindProvider = func(username, password string) error {
|
var actualUsername, actualPassword string
|
||||||
return expected
|
connection.BindProvider = func(username, password string) error {
|
||||||
}
|
actualUsername = username
|
||||||
|
actualPassword = password
|
||||||
dn := "cn=admin,dc=grafana,dc=org"
|
return nil
|
||||||
|
}
|
||||||
server := &Server{
|
server := &Server{
|
||||||
Connection: connection,
|
Connection: connection,
|
||||||
Config: &ServerConfig{
|
Config: &ServerConfig{
|
||||||
BindPassword: "pwd",
|
BindDN: "cn=admin,dc=grafana,dc=org",
|
||||||
BindDN: dn,
|
},
|
||||||
},
|
}
|
||||||
log: log.New("test-logger"),
|
|
||||||
}
|
dn := "cn=user,ou=users,dc=grafana,dc=org"
|
||||||
|
err := server.UserBind(dn, "pwd")
|
||||||
err := server.AdminBind()
|
|
||||||
So(err, ShouldEqual, expected)
|
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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,11 @@ import (
|
|||||||
"gopkg.in/ldap.v3"
|
"gopkg.in/ldap.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type searchFunc = func(request *ldap.SearchRequest) (*ldap.SearchResult, error)
|
||||||
|
|
||||||
// MockConnection struct for testing
|
// MockConnection struct for testing
|
||||||
type MockConnection struct {
|
type MockConnection struct {
|
||||||
SearchResult *ldap.SearchResult
|
SearchFunc searchFunc
|
||||||
SearchError error
|
|
||||||
SearchCalled bool
|
SearchCalled bool
|
||||||
SearchAttributes []string
|
SearchAttributes []string
|
||||||
|
|
||||||
@ -56,11 +57,19 @@ func (c *MockConnection) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *MockConnection) setSearchResult(result *ldap.SearchResult) {
|
func (c *MockConnection) setSearchResult(result *ldap.SearchResult) {
|
||||||
c.SearchResult = result
|
c.SearchFunc = func(request *ldap.SearchRequest) (*ldap.SearchResult, error) {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *MockConnection) setSearchError(err error) {
|
func (c *MockConnection) setSearchError(err error) {
|
||||||
c.SearchError = err
|
c.SearchFunc = func(request *ldap.SearchRequest) (*ldap.SearchResult, error) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockConnection) setSearchFunc(fn searchFunc) {
|
||||||
|
c.SearchFunc = fn
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search mocks Search connection function
|
// Search mocks Search connection function
|
||||||
@ -68,11 +77,7 @@ func (c *MockConnection) Search(sr *ldap.SearchRequest) (*ldap.SearchResult, err
|
|||||||
c.SearchCalled = true
|
c.SearchCalled = true
|
||||||
c.SearchAttributes = sr.Attributes
|
c.SearchAttributes = sr.Attributes
|
||||||
|
|
||||||
if c.SearchError != nil {
|
return c.SearchFunc(sr)
|
||||||
return nil, c.SearchError
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.SearchResult, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add mocks Add connection function
|
// Add mocks Add connection function
|
||||||
|
Loading…
Reference in New Issue
Block a user