Initial QSfera import
This commit is contained in:
@@ -0,0 +1,453 @@
|
||||
package identity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/CiscoM31/godata"
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/qsfera/server/services/graph/pkg/identity/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
var groupEntry = ldap.NewEntry("cn=group",
|
||||
map[string][]string{
|
||||
"cn": {"group"},
|
||||
"entryuuid": {"abcd-defg"},
|
||||
"member": {
|
||||
"uid=user,ou=people,dc=test",
|
||||
"uid=invalid,ou=people,dc=test",
|
||||
},
|
||||
})
|
||||
|
||||
var invalidGroupEntry = ldap.NewEntry("cn=invalid",
|
||||
map[string][]string{
|
||||
"cn": {"invalid"},
|
||||
})
|
||||
|
||||
var queryParamExpand = url.Values{
|
||||
"$expand": []string{"members"},
|
||||
}
|
||||
|
||||
var queryParamSelect = url.Values{
|
||||
"$select": []string{"members"},
|
||||
}
|
||||
|
||||
var groupLookupSearchRequest = &ldap.SearchRequest{
|
||||
BaseDN: "ou=groups,dc=test",
|
||||
Scope: 2,
|
||||
SizeLimit: 1,
|
||||
Filter: "(&(objectClass=groupOfNames)(|(cn=group)(entryUUID=group)))",
|
||||
Attributes: []string{"cn", "entryUUID", "member"},
|
||||
Controls: []ldap.Control(nil),
|
||||
}
|
||||
|
||||
var groupListSearchRequest = &ldap.SearchRequest{
|
||||
BaseDN: "ou=groups,dc=test",
|
||||
Scope: 2,
|
||||
Filter: "(&(objectClass=groupOfNames))",
|
||||
Attributes: []string{"cn", "entryUUID", "member"},
|
||||
Controls: []ldap.Control(nil),
|
||||
}
|
||||
|
||||
func TestGetGroup(t *testing.T) {
|
||||
// Mock a Sizelimit Error
|
||||
lm := &mocks.Client{}
|
||||
lm.On("Search", mock.Anything).Return(nil, ldap.NewError(ldap.LDAPResultSizeLimitExceeded, errors.New("mock")))
|
||||
|
||||
b, _ := getMockedBackend(lm, lconfig, &logger)
|
||||
_, err := b.GetGroup(context.Background(), "group", nil)
|
||||
assert.ErrorContains(t, err, "itemNotFound:")
|
||||
_, err = b.GetGroup(context.Background(), "group", queryParamExpand)
|
||||
assert.ErrorContains(t, err, "itemNotFound:")
|
||||
_, err = b.GetGroup(context.Background(), "group", queryParamSelect)
|
||||
assert.ErrorContains(t, err, "itemNotFound:")
|
||||
|
||||
// Mock an empty Search Result
|
||||
lm = &mocks.Client{}
|
||||
lm.On("Search", mock.Anything).Return(&ldap.SearchResult{}, nil)
|
||||
b, _ = getMockedBackend(lm, lconfig, &logger)
|
||||
_, err = b.GetGroup(context.Background(), "group", nil)
|
||||
assert.ErrorContains(t, err, "itemNotFound:")
|
||||
_, err = b.GetGroup(context.Background(), "group", queryParamExpand)
|
||||
assert.ErrorContains(t, err, "itemNotFound:")
|
||||
_, err = b.GetGroup(context.Background(), "group", queryParamSelect)
|
||||
assert.ErrorContains(t, err, "itemNotFound:")
|
||||
|
||||
// Mock an invalid Search Result
|
||||
lm = &mocks.Client{}
|
||||
lm.On("Search", mock.Anything).Return(&ldap.SearchResult{
|
||||
Entries: []*ldap.Entry{invalidGroupEntry},
|
||||
}, nil)
|
||||
b, _ = getMockedBackend(lm, lconfig, &logger)
|
||||
_, err = b.GetGroup(context.Background(), "group", nil)
|
||||
assert.ErrorContains(t, err, "itemNotFound:")
|
||||
_, err = b.GetGroup(context.Background(), "group", queryParamExpand)
|
||||
assert.ErrorContains(t, err, "itemNotFound:")
|
||||
_, err = b.GetGroup(context.Background(), "group", queryParamSelect)
|
||||
assert.ErrorContains(t, err, "itemNotFound:")
|
||||
|
||||
// Mock a valid Search Result
|
||||
lm = &mocks.Client{}
|
||||
sr2 := &ldap.SearchRequest{
|
||||
BaseDN: "uid=user,ou=people,dc=test",
|
||||
SizeLimit: 1,
|
||||
Filter: "(objectClass=inetOrgPerson)",
|
||||
Attributes: ldapUserAttributes,
|
||||
Controls: []ldap.Control(nil),
|
||||
}
|
||||
sr3 := &ldap.SearchRequest{
|
||||
BaseDN: "uid=invalid,ou=people,dc=test",
|
||||
SizeLimit: 1,
|
||||
Filter: "(objectClass=inetOrgPerson)",
|
||||
Attributes: ldapUserAttributes,
|
||||
Controls: []ldap.Control(nil),
|
||||
}
|
||||
|
||||
lm.On("Search", groupLookupSearchRequest).Return(&ldap.SearchResult{Entries: []*ldap.Entry{groupEntry}}, nil)
|
||||
lm.On("Search", sr2).Return(&ldap.SearchResult{Entries: []*ldap.Entry{userEntry}}, nil)
|
||||
lm.On("Search", sr3).Return(&ldap.SearchResult{Entries: []*ldap.Entry{invalidUserEntry}}, nil)
|
||||
b, _ = getMockedBackend(lm, lconfig, &logger)
|
||||
g, err := b.GetGroup(context.Background(), "group", nil)
|
||||
if err != nil {
|
||||
t.Errorf("Expected GetGroup to succeed. Got %s", err.Error())
|
||||
} else if *g.Id != groupEntry.GetEqualFoldAttributeValue(b.groupAttributeMap.id) {
|
||||
t.Errorf("Expected GetGroup to return a valid group")
|
||||
}
|
||||
g, err = b.GetGroup(context.Background(), "group", queryParamExpand)
|
||||
switch {
|
||||
case err != nil:
|
||||
t.Errorf("Expected GetGroup to succeed. Got %s", err.Error())
|
||||
case g.GetId() != groupEntry.GetEqualFoldAttributeValue(b.groupAttributeMap.id):
|
||||
t.Errorf("Expected GetGroup to return a valid group")
|
||||
case len(g.Members) != 1:
|
||||
t.Errorf("Expected GetGroup with expand to return one member")
|
||||
case g.Members[0].GetId() != userEntry.GetEqualFoldAttributeValue(b.userAttributeMap.id):
|
||||
t.Errorf("Expected GetGroup with expand to return correct member")
|
||||
}
|
||||
g, err = b.GetGroup(context.Background(), "group", queryParamSelect)
|
||||
switch {
|
||||
case err != nil:
|
||||
t.Errorf("Expected GetGroup to succeed. Got %s", err.Error())
|
||||
case g.GetId() != groupEntry.GetEqualFoldAttributeValue(b.groupAttributeMap.id):
|
||||
t.Errorf("Expected GetGroup to return a valid group")
|
||||
case len(g.Members) != 1:
|
||||
t.Errorf("Expected GetGroup with expand to return one member")
|
||||
case g.Members[0].GetId() != userEntry.GetEqualFoldAttributeValue(b.userAttributeMap.id):
|
||||
t.Errorf("Expected GetGroup with expand to return correct member")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGroupReadOnlyBackend(t *testing.T) {
|
||||
readOnlyConfig := lconfig
|
||||
readOnlyConfig.WriteEnabled = false
|
||||
readOnlyConfig.GroupBaseDN = "ou=groups,dc=test"
|
||||
readOnlyConfig.GroupCreateBaseDN = "ou=local,ou=group,dc=test"
|
||||
localGroupEntry := groupEntry
|
||||
localGroupEntry.DN = "cn=local,ou=local,o=base"
|
||||
|
||||
lm := &mocks.Client{}
|
||||
lm.On("Search", groupLookupSearchRequest).Return(&ldap.SearchResult{Entries: []*ldap.Entry{groupEntry}}, nil)
|
||||
b, _ := getMockedBackend(lm, readOnlyConfig, &logger)
|
||||
g, err := b.GetGroup(context.Background(), "group", url.Values{})
|
||||
switch {
|
||||
case err != nil:
|
||||
t.Errorf("Expected GetGroup to succeed. Got %s", err.Error())
|
||||
case g.GetId() != groupEntry.GetEqualFoldAttributeValue(b.groupAttributeMap.id):
|
||||
t.Errorf("Expected GetGroup to return a valid group")
|
||||
}
|
||||
types := g.GetGroupTypes()
|
||||
switch {
|
||||
case len(types) == 0:
|
||||
t.Errorf("No groupTypes attribute on readonly Group")
|
||||
case len(types) > 1:
|
||||
t.Errorf("Expected a single groupTypes value on readonly Group")
|
||||
case types[0] != "ReadOnly":
|
||||
t.Errorf("Expected a groupTypes 'ReadOnly' on readonly Group")
|
||||
}
|
||||
}
|
||||
func TestGetGroupReadOnlySubtree(t *testing.T) {
|
||||
readOnlyTreeConfig := lconfig
|
||||
readOnlyTreeConfig.GroupCreateBaseDN = "ou=write,ou=groups,dc=test"
|
||||
var writeGroupEntry = ldap.NewEntry("cn=group,ou=write,ou=groups,dc=test",
|
||||
map[string][]string{
|
||||
"cn": {"group"},
|
||||
"entryuuid": {"abcd-defg"},
|
||||
"member": {
|
||||
"uid=user,ou=people,dc=test",
|
||||
"uid=invalid,ou=people,dc=test",
|
||||
},
|
||||
})
|
||||
|
||||
lm := &mocks.Client{}
|
||||
lm.On("Search", groupLookupSearchRequest).Return(&ldap.SearchResult{Entries: []*ldap.Entry{groupEntry}}, nil)
|
||||
b, _ := getMockedBackend(lm, readOnlyTreeConfig, &logger)
|
||||
g, err := b.GetGroup(context.Background(), "group", url.Values{})
|
||||
switch {
|
||||
case err != nil:
|
||||
t.Errorf("Expected GetGroup to succeed. Got %s", err.Error())
|
||||
case g.GetId() != groupEntry.GetEqualFoldAttributeValue(b.groupAttributeMap.id):
|
||||
t.Errorf("Expected GetGroup to return a valid group")
|
||||
}
|
||||
types := g.GetGroupTypes()
|
||||
switch {
|
||||
case len(types) == 0:
|
||||
t.Errorf("No groupTypes attribute on readonly Group")
|
||||
case len(types) > 1:
|
||||
t.Errorf("Expected a single groupTypes value on readonly Group")
|
||||
case types[0] != "ReadOnly":
|
||||
t.Errorf("Expected a groupTypes 'ReadOnly' on readonly Group")
|
||||
}
|
||||
|
||||
lm = &mocks.Client{}
|
||||
lm.On("Search", groupLookupSearchRequest).Return(&ldap.SearchResult{Entries: []*ldap.Entry{writeGroupEntry}}, nil)
|
||||
b, _ = getMockedBackend(lm, readOnlyTreeConfig, &logger)
|
||||
g, err = b.GetGroup(context.Background(), "group", url.Values{})
|
||||
switch {
|
||||
case err != nil:
|
||||
t.Errorf("Expected GetGroup to succeed. Got %s", err.Error())
|
||||
case g.GetId() != groupEntry.GetEqualFoldAttributeValue(b.groupAttributeMap.id):
|
||||
t.Errorf("Expected GetGroup to return a valid group")
|
||||
}
|
||||
types = g.GetGroupTypes()
|
||||
if len(types) != 0 {
|
||||
t.Errorf("No groupTypes attribute expected on writeable Group")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGroups(t *testing.T) {
|
||||
lm := &mocks.Client{}
|
||||
oDataReq, err := godata.ParseRequest(context.Background(), "", url.Values{})
|
||||
if err != nil {
|
||||
t.Errorf("Expected success, got '%s'", err.Error())
|
||||
}
|
||||
lm.On("Search", mock.Anything).Return(nil, ldap.NewError(ldap.LDAPResultOperationsError, errors.New("mock")))
|
||||
b, _ := getMockedBackend(lm, lconfig, &logger)
|
||||
_, err = b.GetGroups(context.Background(), oDataReq)
|
||||
assert.ErrorContains(t, err, "itemNotFound:")
|
||||
|
||||
lm = &mocks.Client{}
|
||||
lm.On("Search", mock.Anything).Return(&ldap.SearchResult{}, nil)
|
||||
b, _ = getMockedBackend(lm, lconfig, &logger)
|
||||
g, err := b.GetGroups(context.Background(), oDataReq)
|
||||
if err != nil {
|
||||
t.Errorf("Expected success, got '%s'", err.Error())
|
||||
} else if g == nil || len(g) != 0 {
|
||||
t.Errorf("Expected zero length user slice")
|
||||
}
|
||||
|
||||
lm = &mocks.Client{}
|
||||
lm.On("Search", mock.Anything).Return(&ldap.SearchResult{
|
||||
Entries: []*ldap.Entry{groupEntry},
|
||||
}, nil)
|
||||
b, _ = getMockedBackend(lm, lconfig, &logger)
|
||||
g, err = b.GetGroups(context.Background(), oDataReq)
|
||||
if err != nil {
|
||||
t.Errorf("Expected GetGroup to succeed. Got %s", err.Error())
|
||||
} else if *g[0].Id != groupEntry.GetEqualFoldAttributeValue(b.groupAttributeMap.id) {
|
||||
t.Errorf("Expected GetGroup to return a valid group")
|
||||
}
|
||||
|
||||
// Mock a valid Search Result with expanded group members
|
||||
lm = &mocks.Client{}
|
||||
sr2 := &ldap.SearchRequest{
|
||||
BaseDN: "uid=user,ou=people,dc=test",
|
||||
SizeLimit: 1,
|
||||
Filter: "(objectClass=inetOrgPerson)",
|
||||
Attributes: ldapUserAttributes,
|
||||
Controls: []ldap.Control(nil),
|
||||
}
|
||||
sr3 := &ldap.SearchRequest{
|
||||
BaseDN: "uid=invalid,ou=people,dc=test",
|
||||
SizeLimit: 1,
|
||||
Filter: "(objectClass=inetOrgPerson)",
|
||||
Attributes: ldapUserAttributes,
|
||||
Controls: []ldap.Control(nil),
|
||||
}
|
||||
|
||||
for _, param := range []url.Values{queryParamSelect, queryParamExpand} {
|
||||
oDataReq, err := godata.ParseRequest(context.Background(), "", param)
|
||||
if err != nil {
|
||||
t.Errorf("Expected success, got '%s'", err.Error())
|
||||
}
|
||||
lm.On("Search", groupListSearchRequest).Return(&ldap.SearchResult{Entries: []*ldap.Entry{groupEntry}}, nil)
|
||||
lm.On("Search", sr2).Return(&ldap.SearchResult{Entries: []*ldap.Entry{userEntry}}, nil)
|
||||
lm.On("Search", sr3).Return(&ldap.SearchResult{Entries: []*ldap.Entry{invalidUserEntry}}, nil)
|
||||
b, _ = getMockedBackend(lm, lconfig, &logger)
|
||||
g, err = b.GetGroups(context.Background(), oDataReq)
|
||||
switch {
|
||||
case err != nil:
|
||||
t.Errorf("Expected GetGroup to succeed. Got %s", err.Error())
|
||||
case g[0].GetId() != groupEntry.GetEqualFoldAttributeValue(b.groupAttributeMap.id):
|
||||
t.Errorf("Expected GetGroup to return a valid group")
|
||||
case len(g[0].Members) != 1:
|
||||
t.Errorf("Expected GetGroup to return group with one member")
|
||||
case g[0].Members[0].GetId() != userEntry.GetEqualFoldAttributeValue(b.userAttributeMap.id):
|
||||
t.Errorf("Expected GetGroup to return group with correct member")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGroupsSearch(t *testing.T) {
|
||||
lm := &mocks.Client{}
|
||||
odataReqDefault, err := godata.ParseRequest(context.Background(), "",
|
||||
url.Values{
|
||||
"$search": []string{"\"term\""},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Errorf("Expected success got '%s'", err.Error())
|
||||
}
|
||||
|
||||
// only match if the filter contains the search term unquoted
|
||||
lm.On("Search", mock.MatchedBy(
|
||||
func(req *ldap.SearchRequest) bool {
|
||||
return req.Filter == "(&(objectClass=groupOfNames)(cn=*term*))"
|
||||
})).
|
||||
Return(&ldap.SearchResult{}, nil)
|
||||
b, _ := getMockedBackend(lm, lconfig, &logger)
|
||||
g, err := b.GetGroups(context.Background(), odataReqDefault)
|
||||
if err != nil {
|
||||
t.Errorf("Expected success, got '%s'", err.Error())
|
||||
} else if g == nil || len(g) != 0 {
|
||||
t.Errorf("Expected zero length user slice")
|
||||
}
|
||||
}
|
||||
func TestUpdateGroupName(t *testing.T) {
|
||||
groupDn := "cn=TheGroup,ou=groups,dc=example,dc=org"
|
||||
|
||||
type args struct {
|
||||
groupId string
|
||||
groupName string
|
||||
newName string
|
||||
}
|
||||
|
||||
type mockInputs struct {
|
||||
funcName string
|
||||
args []any
|
||||
returns []any
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
assertion assert.ErrorAssertionFunc
|
||||
ldapMocks []mockInputs
|
||||
}{
|
||||
{
|
||||
name: "Test with no name change",
|
||||
args: args{
|
||||
groupId: "some-uuid-string",
|
||||
newName: "TheGroup",
|
||||
},
|
||||
assertion: func(t assert.TestingT, err error, args ...any) bool {
|
||||
return assert.Nil(t, err, args...)
|
||||
},
|
||||
ldapMocks: []mockInputs{
|
||||
{
|
||||
funcName: "Search",
|
||||
args: []any{
|
||||
ldap.NewSearchRequest(
|
||||
"ou=groups,dc=test",
|
||||
ldap.ScopeWholeSubtree,
|
||||
ldap.NeverDerefAliases, 1, 0, false,
|
||||
"(&(objectClass=groupOfNames)(entryUUID=some-uuid-string))",
|
||||
[]string{"cn", "entryUUID", "member"},
|
||||
nil,
|
||||
),
|
||||
},
|
||||
returns: []any{
|
||||
&ldap.SearchResult{
|
||||
Entries: []*ldap.Entry{
|
||||
{
|
||||
DN: groupDn,
|
||||
Attributes: []*ldap.EntryAttribute{
|
||||
{
|
||||
Name: "cn",
|
||||
Values: []string{"TheGroup"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test with name change",
|
||||
args: args{
|
||||
groupId: "some-uuid-string",
|
||||
newName: "TheGroupWithShinyNewName",
|
||||
},
|
||||
assertion: func(t assert.TestingT, err error, args ...any) bool {
|
||||
return assert.Nil(t, err, args...)
|
||||
},
|
||||
ldapMocks: []mockInputs{
|
||||
{
|
||||
funcName: "Search",
|
||||
args: []any{
|
||||
ldap.NewSearchRequest(
|
||||
"ou=groups,dc=test",
|
||||
ldap.ScopeWholeSubtree,
|
||||
ldap.NeverDerefAliases, 1, 0, false,
|
||||
"(&(objectClass=groupOfNames)(entryUUID=some-uuid-string))",
|
||||
[]string{"cn", "entryUUID", "member"},
|
||||
nil,
|
||||
),
|
||||
},
|
||||
returns: []any{
|
||||
&ldap.SearchResult{
|
||||
Entries: []*ldap.Entry{
|
||||
{
|
||||
DN: "cn=TheGroup,ou=groups,dc=example,dc=org",
|
||||
Attributes: []*ldap.EntryAttribute{
|
||||
{
|
||||
Name: "cn",
|
||||
Values: []string{"TheGroup"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "ModifyDN",
|
||||
args: []any{
|
||||
&ldap.ModifyDNRequest{
|
||||
DN: groupDn,
|
||||
NewRDN: "cn=TheGroupWithShinyNewName",
|
||||
DeleteOldRDN: true,
|
||||
NewSuperior: "",
|
||||
Controls: []ldap.Control(nil),
|
||||
},
|
||||
},
|
||||
returns: []any{
|
||||
nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
lm := &mocks.Client{}
|
||||
for _, ldapMock := range tt.ldapMocks {
|
||||
lm.On(ldapMock.funcName, ldapMock.args...).Return(ldapMock.returns...)
|
||||
}
|
||||
|
||||
ldapConfig := lconfig
|
||||
i, _ := getMockedBackend(lm, ldapConfig, &logger)
|
||||
|
||||
err := i.UpdateGroupName(context.Background(), tt.args.groupId, tt.args.newName)
|
||||
tt.assertion(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user