package ad
import (
"errors"
"fmt"
"reflect"
"testing"
"gopkg.in/ldap.v2"
"github.com/openshift/origin/pkg/auth/ldaputil"
"github.com/openshift/origin/pkg/auth/ldaputil/testclient"
)
func newTestADLDAPInterface(client ldap.Client) *ADLDAPInterface {
// below are common test implementations of LDAPInterface fields
userQuery := ldaputil.LDAPQuery{
BaseDN: "ou=users,dc=example,dc=com",
Scope: ldaputil.ScopeWholeSubtree,
DerefAliases: ldaputil.DerefAliasesAlways,
TimeLimit: 0,
Filter: "objectClass=inetOrgPerson",
}
groupMembershipAttributes := []string{"memberOf"}
userNameAttributes := []string{"cn"}
return NewADLDAPInterface(testclient.NewConfig(client),
userQuery,
groupMembershipAttributes,
userNameAttributes)
}
// newTestUser(testUserDN, testUserCN, testGroupUID) returns a new LDAP entry with the given CN,
// as a member of the given group
func newTestUser(CN, groupUID string) *ldap.Entry {
return ldap.NewEntry(fmt.Sprintf("cn=%s,ou=users,dc=example,dc=com", CN), map[string][]string{"cn": {CN}, "memberOf": {groupUID}})
}
func TestExtractMembers(t *testing.T) {
// we don't have a test case for an error on a bad search request as search request errors can only occur if
// the search attribute is the DN, and we do not allow DN to be a group UID for this schema
var testCases = []struct {
name string
cacheSeed map[string][]*ldap.Entry
client ldap.Client
expectedError error
expectedMembers []*ldap.Entry
}{
{
name: "members cached",
cacheSeed: map[string][]*ldap.Entry{
"testGroup": {newTestUser("testUser", "testGroup")},
},
expectedError: nil,
expectedMembers: []*ldap.Entry{newTestUser("testUser", "testGroup")},
},
{
name: "user query error",
client: testclient.NewMatchingSearchErrorClient(
testclient.New(),
"ou=users,dc=example,dc=com",
errors.New("generic search error"),
),
expectedError: errors.New("generic search error"),
expectedMembers: nil,
},
{
name: "no errors",
client: testclient.NewDNMappingClient(
testclient.New(),
map[string][]*ldap.Entry{
"ou=users,dc=example,dc=com": {newTestUser("testUser", "testGroup")},
},
),
expectedError: nil,
expectedMembers: []*ldap.Entry{newTestUser("testUser", "testGroup")},
},
}
for _, testCase := range testCases {
ldapInterface := newTestADLDAPInterface(testCase.client)
if len(testCase.cacheSeed) > 0 {
ldapInterface.ldapGroupToLDAPMembers = testCase.cacheSeed
}
members, err := ldapInterface.ExtractMembers("testGroup")
if !reflect.DeepEqual(err, testCase.expectedError) {
t.Errorf("%s: incorrect error returned:\n\texpected:\n\t%v\n\tgot:\n\t%v\n", testCase.name, testCase.expectedError, err)
}
if !reflect.DeepEqual(members, testCase.expectedMembers) {
t.Errorf("%s: incorrect members returned:\n\texpected:\n\t%v\n\tgot:\n\t%v\n", testCase.name, testCase.expectedMembers, members)
}
}
}
func TestListGroups(t *testing.T) {
client := testclient.NewDNMappingClient(
testclient.New(),
map[string][]*ldap.Entry{
"ou=users,dc=example,dc=com": {newTestUser("testUser", "testGroup")},
},
)
ldapInterface := newTestADLDAPInterface(client)
groups, err := ldapInterface.ListGroups()
if !reflect.DeepEqual(err, nil) {
t.Errorf("listing groups: incorrect error returned:\n\texpected:\n\t%v\n\tgot:\n\t%v\n", nil, err)
}
if !reflect.DeepEqual(groups, []string{"testGroup"}) {
t.Errorf("listing groups: incorrect group list:\n\texpected:\n\t%v\n\tgot:\n\t%v\n", []string{"testGroup"}, groups)
}
}
func TestPopulateCache(t *testing.T) {
var testCases = []struct {
name string
cacheSeed map[string][]*ldap.Entry
searchDNOverride string
client ldap.Client
expectedError error
expectedCache map[string][]*ldap.Entry
}{
{
name: "cache already populated",
cacheSeed: map[string][]*ldap.Entry{
"testGroup": {newTestUser("testUser", "testGroup")},
},
expectedError: nil,
expectedCache: map[string][]*ldap.Entry{
"testGroup": {newTestUser("testUser", "testGroup")},
},
},
{
name: "user query error",
client: testclient.NewMatchingSearchErrorClient(
testclient.New(),
"ou=users,dc=example,dc=com",
errors.New("generic search error"),
),
expectedError: errors.New("generic search error"),
expectedCache: make(map[string][]*ldap.Entry), // won't be nil but will be empty
},
{
name: "cache populated correctly",
client: testclient.NewDNMappingClient(
testclient.New(),
map[string][]*ldap.Entry{
"ou=users,dc=example,dc=com": {newTestUser("testUser", "testGroup")},
},
),
expectedError: nil,
expectedCache: map[string][]*ldap.Entry{
"testGroup": {newTestUser("testUser", "testGroup")},
},
},
}
for _, testCase := range testCases {
ldapInterface := newTestADLDAPInterface(testCase.client)
if len(testCase.cacheSeed) > 0 {
ldapInterface.ldapGroupToLDAPMembers = testCase.cacheSeed
ldapInterface.cacheFullyPopulated = true
}
err := ldapInterface.populateCache()
if !reflect.DeepEqual(err, testCase.expectedError) {
t.Errorf("%s: incorrect error returned:\n\texpected:\n\t%v\n\tgot:\n\t%v\n", testCase.name, testCase.expectedError, err)
}
if !reflect.DeepEqual(testCase.expectedCache, ldapInterface.ldapGroupToLDAPMembers) {
t.Errorf("%s: incorrect cache state:\n\texpected:\n\t%v\n\tgot:\n\t%v\n", testCase.name, testCase.expectedCache, ldapInterface.ldapGroupToLDAPMembers)
}
}
}
// TestPopulateCacheAfterExtractMembers ensures that the cache is only listed as fully populated after a
// populateCache call and not after a partial fill from an ExtractMembers call
func TestPopulateCacheAfterExtractMembers(t *testing.T) {
client := testclient.NewDNMappingClient(
testclient.New(),
map[string][]*ldap.Entry{
"ou=users,dc=example,dc=com": {newTestUser("testUser", "testGroup")},
},
)
ldapInterface := newTestADLDAPInterface(client)
_, err := ldapInterface.ExtractMembers("testGroup")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
// both queries use the same BaseDN so we change what the client returns to simulate not applying the group-specific filter
client.(*testclient.DNMappingClient).DNMapping["ou=users,dc=example,dc=com"] = []*ldap.Entry{
newTestUser("testUser", "testGroup"),
newTestUser("testUser2", "testGroup2")}
expectedCache := map[string][]*ldap.Entry{
"testGroup": {newTestUser("testUser", "testGroup")},
"testGroup2": {newTestUser("testUser2", "testGroup2")},
}
err = ldapInterface.populateCache()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(expectedCache, ldapInterface.ldapGroupToLDAPMembers) {
t.Errorf("incorrect cache state:\n\texpected:\n\t%v\n\tgot:\n\t%v\n", expectedCache, ldapInterface.ldapGroupToLDAPMembers)
}
}