mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-09-13 14:18:38 -04:00
Introducing gowrap as a build-time tool to generate interface delegate structs from templates: * added as a 'make go-generate' target in services/graph, * added as a build-time dependency in .bingo/ Introduce an LDAP client abstraction interface to be able to wrap the go-ldap client API with metrics transparently (and possibly hooks and such in the future), in order to use delegation patterns to measure the time LDAP (client) operations take to finish, as well as to track their results (success, failure, not-found). Has two implementations that are generated using gowrap: * a go-ldap adapter implementation that directly delegates to a go-ldap connection * a time measuring and metrics collecting implementation that delegates to another LdapClient The metrics collecting one is disabled by default, can be enabled with GRAPH_LDAP_METRICS_DISABLE=false It collects durations of outbound LDAP client operations into a histogram, as well as the number of concurrent outbound LDAP operations in a gauge (via an atomic int and a gauge function, as that performs best). Add an HTTP middleware that measures how long Graph HTTP API requests take, storing taken time into a histogram along with labels for * method, * path pattern (from the chi routes), * Graph API version prefix, * Graph API resource name, * and the resulting status code. It also tracks the number of concurrent inbound Graph API HTTP requests using a gauge (also using an atomic int and a gauge function). Disabled by default, can be enabled with GRAPH_HTTP_METRICS_DISABLE=false Add Backend and EducationBackend delegate implementations that measure execution time on the level of the higher API call operations there (CreateUser, DeleteUser, ..., CreateSchool, ...), generated using gowrap. Disabled by default, can be enabled with GRAPH_IDENTITY_BACKEND_METRICS_DISABLE=false Also added a small k6 script to produce some read-only load on the Graph API, for a casual test of the metrics, as well as k6 in mise.toml. Make an internal changes to how singular LDAP entry searches work in the LDAP identity backends: * check whether searches for a singular entry returns more than one result, in which case a new error TooManyResults is returned, instead of leaving that undetected, blindly taking the first result, and potentially risking data inconsistencies Improve the loggers in identity backends by adding attributes for their request targets (Reva gateway address or LDAP URI, respectively). Also add a "backend" attribute for all Graph API logs (set to "ldap" or "cs3"), to help debug potential issues, and remove them from all the logger debug calls at the beginning of each LDAP-related function as those should really be part of the logger and set beforehand. The LDAP identity backend logger also has two new attributes to help debugging with logs: * write (bool): whether write operations are enabled * refint (bool): whether refint is enabled or not Also adds a dedicated counter metric for user password change operations. Minor campfire improvements: * add a constructor func for the CS3 backend * add a constructor func for the LDAP backend * in the LDAP identity backend, in searchLDAPEntryByFilter (used by all search/get public functions), errors that occur when performing LDAP SEARCH operations were blindly mapped to a ItemNotFound error, instead of being analyzed as it could be caused by a technical error * in the requireadmin middleware, add debug logging to explain why a request is denied * when an LDAP password change fails because the user entry was not found in LDAP, we now have a log message that tracks that
1773 lines
45 KiB
Go
1773 lines
45 KiB
Go
package identity
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"testing"
|
|
|
|
"github.com/CiscoM31/godata"
|
|
"github.com/go-ldap/ldap/v3"
|
|
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
|
"github.com/opencloud-eu/opencloud/pkg/log"
|
|
"github.com/opencloud-eu/opencloud/services/graph/pkg/config"
|
|
"github.com/opencloud-eu/opencloud/services/graph/pkg/identity/mocks"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
)
|
|
|
|
func getMockedBackend(l ldap.Client, lc config.LDAP, logger *log.Logger) (*LDAP, error) {
|
|
return NewLDAPBackend(l, lc, logger, "opencloud", "test", prometheus.NewRegistry())
|
|
}
|
|
|
|
const (
|
|
disableUsersGroup = "cn=DisabledUsersGroup,ou=groups,o=testing"
|
|
groupSearchFilter = "(objectClass=groupOfNames)"
|
|
)
|
|
|
|
var lconfig = config.LDAP{
|
|
UserBaseDN: "ou=people,dc=test",
|
|
UserObjectClass: "inetOrgPerson",
|
|
UserSearchScope: "sub",
|
|
UserFilter: "",
|
|
UserDisplayNameAttribute: "displayname",
|
|
UserIDAttribute: "entryUUID",
|
|
UserEmailAttribute: "mail",
|
|
UserNameAttribute: "uid",
|
|
UserEnabledAttribute: "userEnabledAttribute",
|
|
UserTypeAttribute: "userTypeAttribute",
|
|
LdapDisabledUsersGroupDN: disableUsersGroup,
|
|
DisableUserMechanism: "attribute",
|
|
|
|
GroupBaseDN: "ou=groups,dc=test",
|
|
GroupObjectClass: "groupOfNames",
|
|
GroupSearchScope: "sub",
|
|
GroupFilter: "",
|
|
GroupNameAttribute: "cn",
|
|
GroupMemberAttribute: "member",
|
|
GroupIDAttribute: "entryUUID",
|
|
|
|
WriteEnabled: true,
|
|
}
|
|
|
|
var userEntry = ldap.NewEntry("uid=user",
|
|
map[string][]string{
|
|
"uid": {"user"},
|
|
"displayname": {"DisplayName"},
|
|
"mail": {"user@example"},
|
|
"entryuuid": {"abcd-defg"},
|
|
"sn": {"surname"},
|
|
"givenname": {"givenName"},
|
|
"userenabledattribute": {"TRUE"},
|
|
"usertypeattribute": {"Member"},
|
|
})
|
|
|
|
var lconfigAD = config.LDAP{
|
|
UserBaseDN: "ou=users,dc=test",
|
|
UserObjectClass: "user",
|
|
UserSearchScope: "sub",
|
|
UserFilter: "",
|
|
UserDisplayNameAttribute: "displayname",
|
|
UserIDAttribute: "objectGUID",
|
|
UserIDIsOctetString: true,
|
|
UserEmailAttribute: "mail",
|
|
UserNameAttribute: "uid",
|
|
UserEnabledAttribute: "userEnabledAttribute",
|
|
UserTypeAttribute: "userTypeAttribute",
|
|
LdapDisabledUsersGroupDN: disableUsersGroup,
|
|
DisableUserMechanism: "attribute",
|
|
|
|
GroupBaseDN: "ou=groups,dc=test",
|
|
GroupObjectClass: "group",
|
|
GroupSearchScope: "sub",
|
|
GroupFilter: "",
|
|
GroupNameAttribute: "cn",
|
|
GroupMemberAttribute: "member",
|
|
GroupIDAttribute: "objectGUID",
|
|
GroupIDIsOctetString: true,
|
|
|
|
WriteEnabled: true,
|
|
}
|
|
|
|
var invalidUserEntry = ldap.NewEntry("uid=user",
|
|
map[string][]string{
|
|
"uid": {"invalid"},
|
|
"displayname": {"DisplayName"},
|
|
"mail": {"user@example"},
|
|
})
|
|
|
|
var logger = log.NewLogger(log.Level("debug"))
|
|
|
|
var ldapUserAttributes = []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "userTypeAttribute", "openCloudExternalIdentity", "openCloudLastSignInTimestamp"}
|
|
|
|
func TestNewLDAPBackend(t *testing.T) {
|
|
l := &mocks.Client{}
|
|
|
|
newBackend := func(c config.LDAP) (*LDAP, error) {
|
|
return NewLDAPBackend(l, c, &logger, "opencloud", "test", prometheus.NewRegistry())
|
|
}
|
|
|
|
tc := lconfig
|
|
tc.UserDisplayNameAttribute = ""
|
|
if _, err := newBackend(tc); err == nil {
|
|
t.Error("Should fail with incomplete user attr config")
|
|
}
|
|
|
|
tc = lconfig
|
|
tc.GroupIDAttribute = ""
|
|
if _, err := newBackend(tc); err == nil {
|
|
t.Errorf("Should fail with incomplete group config")
|
|
}
|
|
|
|
tc = lconfig
|
|
tc.UserSearchScope = ""
|
|
if _, err := newBackend(tc); err == nil {
|
|
t.Errorf("Should fail with invalid user search scope")
|
|
}
|
|
|
|
tc = lconfig
|
|
tc.GroupSearchScope = ""
|
|
if _, err := newBackend(tc); err == nil {
|
|
t.Errorf("Should fail with invalid group search scope")
|
|
}
|
|
|
|
if _, err := newBackend(lconfig); err != nil {
|
|
t.Errorf("Should fail with invalid group search scope")
|
|
}
|
|
}
|
|
|
|
func TestCreateUser(t *testing.T) {
|
|
displayName := "DisplayName"
|
|
mail := "user@example"
|
|
userName := "user"
|
|
surname := "surname"
|
|
givenName := "givenName"
|
|
userType := "Member"
|
|
|
|
ar := ldap.NewAddRequest(fmt.Sprintf("uid=user,%s", lconfig.UserBaseDN), nil)
|
|
ar.Attribute(lconfig.UserDisplayNameAttribute, []string{displayName})
|
|
ar.Attribute(lconfig.UserEmailAttribute, []string{mail})
|
|
ar.Attribute(lconfig.UserNameAttribute, []string{userName})
|
|
ar.Attribute("sn", []string{surname})
|
|
ar.Attribute("givenname", []string{givenName})
|
|
ar.Attribute(lconfig.UserEnabledAttribute, []string{"TRUE"})
|
|
ar.Attribute(lconfig.UserTypeAttribute, []string{"Member"})
|
|
ar.Attribute("cn", []string{userName})
|
|
ar.Attribute("objectClass", []string{"inetOrgPerson", "organizationalPerson", "person", "top", "openCloudUser"})
|
|
|
|
l := &mocks.Client{}
|
|
l.On("Search", mock.Anything).
|
|
Return(
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{userEntry},
|
|
},
|
|
nil)
|
|
l.On("Add", ar).Return(nil)
|
|
logger := log.NewLogger(log.Level("debug"))
|
|
|
|
user := libregraph.NewUser(displayName, userName)
|
|
user.SetMail(mail)
|
|
user.SetSurname(surname)
|
|
user.SetGivenName(givenName)
|
|
user.SetAccountEnabled(true)
|
|
user.SetUserType(userType)
|
|
|
|
c := lconfig
|
|
c.UseServerUUID = true
|
|
b, _ := NewLDAPBackend(l, c, &logger, "opencloud", "test", prometheus.NewRegistry())
|
|
|
|
newUser, err := b.CreateUser(context.Background(), *user)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, displayName, newUser.GetDisplayName())
|
|
assert.Equal(t, mail, newUser.GetMail())
|
|
assert.Equal(t, userName, newUser.GetOnPremisesSamAccountName())
|
|
assert.Equal(t, givenName, newUser.GetGivenName())
|
|
assert.Equal(t, surname, newUser.GetSurname())
|
|
assert.True(t, newUser.GetAccountEnabled())
|
|
assert.Equal(t, userType, newUser.GetUserType())
|
|
}
|
|
|
|
func TestCreateUserModelFromLDAP(t *testing.T) {
|
|
l := &mocks.Client{}
|
|
logger := log.NewLogger(log.Level("debug"))
|
|
|
|
b, _ := NewLDAPBackend(l, lconfig, &logger, "opencloud", "test", prometheus.NewRegistry())
|
|
{
|
|
res, err := b.createUserModelFromLDAP(nil)
|
|
assert.NoError(t, err)
|
|
assert.Nil(t, res)
|
|
}
|
|
user, err := b.createUserModelFromLDAP(userEntry)
|
|
if err != nil {
|
|
t.Error("Converting a valid LDAP Entry should succeed and not return an error")
|
|
} else if user == nil {
|
|
t.Error("Converting a valid LDAP Entry should succeed")
|
|
} else {
|
|
if user.OnPremisesSamAccountName != userEntry.GetEqualFoldAttributeValue(b.userAttributeMap.userName) {
|
|
t.Errorf("Error creating msGraph User from LDAP Entry: %v != %v", user.OnPremisesSamAccountName, pointerOrNil(userEntry.GetEqualFoldAttributeValue(b.userAttributeMap.userName)))
|
|
}
|
|
if *user.Mail != userEntry.GetEqualFoldAttributeValue(b.userAttributeMap.mail) {
|
|
t.Errorf("Error creating msGraph User from LDAP Entry: %s != %s", *user.Mail, userEntry.GetEqualFoldAttributeValue(b.userAttributeMap.mail))
|
|
}
|
|
if user.DisplayName != userEntry.GetEqualFoldAttributeValue(b.userAttributeMap.displayName) {
|
|
t.Errorf("Error creating msGraph User from LDAP Entry: %s != %s", user.DisplayName, userEntry.GetEqualFoldAttributeValue(b.userAttributeMap.displayName))
|
|
}
|
|
if *user.Id != userEntry.GetEqualFoldAttributeValue(b.userAttributeMap.id) {
|
|
t.Errorf("Error creating msGraph User from LDAP Entry: %s != %s", *user.Id, userEntry.GetEqualFoldAttributeValue(b.userAttributeMap.id))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetUser(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)
|
|
|
|
odataReqDefault, err := godata.ParseRequest(context.Background(), "",
|
|
url.Values{})
|
|
if err != nil {
|
|
t.Errorf("Expected success got '%s'", err.Error())
|
|
}
|
|
|
|
odataReqExpand, err := godata.ParseRequest(context.Background(), "",
|
|
url.Values{"$expand": []string{"memberOf"}})
|
|
if err != nil {
|
|
t.Errorf("Expected success got '%s'", err.Error())
|
|
}
|
|
|
|
_, err = b.GetUser(context.Background(), "fred", odataReqDefault)
|
|
assert.ErrorContains(t, err, "user search failed")
|
|
|
|
_, err = b.GetUser(context.Background(), "fred", odataReqExpand)
|
|
assert.ErrorContains(t, err, "user search failed")
|
|
|
|
// Mock an empty Search Result
|
|
lm = &mocks.Client{}
|
|
lm.On("Search", mock.Anything).
|
|
Return(
|
|
&ldap.SearchResult{}, nil)
|
|
b, _ = getMockedBackend(lm, lconfig, &logger)
|
|
_, err = b.GetUser(context.Background(), "fred", odataReqDefault)
|
|
assert.ErrorContains(t, err, "itemNotFound:")
|
|
|
|
_, err = b.GetUser(context.Background(), "fred", odataReqExpand)
|
|
assert.ErrorContains(t, err, "itemNotFound:")
|
|
|
|
// Mock a valid Search Result
|
|
lm = &mocks.Client{}
|
|
lm.On("Search", mock.Anything).
|
|
Return(
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{userEntry},
|
|
},
|
|
nil)
|
|
|
|
b, _ = getMockedBackend(lm, lconfig, &logger)
|
|
u, err := b.GetUser(context.Background(), "user", odataReqDefault)
|
|
if err != nil {
|
|
t.Errorf("Expected GetUser to succeed. Got %s", err.Error())
|
|
} else if *u.Id != userEntry.GetEqualFoldAttributeValue(b.userAttributeMap.id) {
|
|
t.Errorf("Expected GetUser to return a valid user")
|
|
}
|
|
|
|
u, err = b.GetUser(context.Background(), "user", odataReqExpand)
|
|
if err != nil {
|
|
t.Errorf("Expected GetUser to succeed. Got %s", err.Error())
|
|
} else if *u.Id != userEntry.GetEqualFoldAttributeValue(b.userAttributeMap.id) {
|
|
t.Errorf("Expected GetUser to return a valid user")
|
|
}
|
|
|
|
// Mock invalid Search Result
|
|
lm = &mocks.Client{}
|
|
lm.On("Search", mock.Anything).
|
|
Return(
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{invalidUserEntry},
|
|
},
|
|
nil)
|
|
|
|
b, _ = getMockedBackend(lm, lconfig, &logger)
|
|
_, err = b.GetUser(context.Background(), "invalid", nil)
|
|
assert.ErrorContains(t, err, "Invalid User")
|
|
}
|
|
|
|
func TestGetUserAD(t *testing.T) {
|
|
|
|
// we have to simulate ldap / AD returning a binary encoded objectguid
|
|
byteID, err := base64.StdEncoding.DecodeString("js8n0m6YBUqIYK8ZMFYnig==")
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
userEntryAD := ldap.NewEntry("uid=user",
|
|
map[string][]string{
|
|
"uid": {"user"},
|
|
"displayname": {"DisplayName"},
|
|
"mail": {"user@example"},
|
|
"objectguid": {string(byteID)}, // ugly but works
|
|
"sn": {"surname"},
|
|
"givenname": {"givenName"},
|
|
"userenabledattribute": {"TRUE"},
|
|
"usertypeattribute": {"Member"},
|
|
})
|
|
|
|
// Mock a valid Search Result
|
|
lm := &mocks.Client{}
|
|
lm.On("Search", mock.Anything).
|
|
Return(
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{userEntryAD},
|
|
},
|
|
nil)
|
|
|
|
odataReqDefault, err := godata.ParseRequest(context.Background(), "",
|
|
url.Values{})
|
|
if err != nil {
|
|
t.Errorf("Expected success got '%s'", err.Error())
|
|
}
|
|
|
|
b, _ := getMockedBackend(lm, lconfigAD, &logger)
|
|
u, err := b.GetUser(context.Background(), "user", odataReqDefault)
|
|
if err != nil {
|
|
t.Errorf("Expected GetUser to succeed. Got %s", err.Error())
|
|
} else if *u.Id != "d227cf8e-986e-4a05-8860-af193056278a" { // this checks if we decoded the objectguid correctly
|
|
t.Errorf("Expected GetUser to return a valid user")
|
|
}
|
|
|
|
}
|
|
|
|
func TestGetUsers(t *testing.T) {
|
|
// Mock a Sizelimit Error
|
|
lm := &mocks.Client{}
|
|
lm.On("Search", mock.Anything).Return(nil, ldap.NewError(ldap.LDAPResultOperationsError, errors.New("mock")))
|
|
|
|
odataReqDefault, err := godata.ParseRequest(context.Background(), "",
|
|
url.Values{})
|
|
if err != nil {
|
|
t.Errorf("Expected success got '%s'", err.Error())
|
|
}
|
|
|
|
b, _ := getMockedBackend(lm, lconfig, &logger)
|
|
_, err = b.GetUsers(context.Background(), odataReqDefault)
|
|
assert.ErrorContains(t, err, "generalException:")
|
|
|
|
lm = &mocks.Client{}
|
|
lm.On("Search", mock.Anything).Return(&ldap.SearchResult{}, nil)
|
|
b, _ = getMockedBackend(lm, lconfig, &logger)
|
|
g, err := b.GetUsers(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 TestGetUsersSearch(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=inetOrgPerson)(|(uid=*term*)(mail=*term*)(displayname=*term*)))"
|
|
})).
|
|
Return(&ldap.SearchResult{}, nil)
|
|
b, _ := getMockedBackend(lm, lconfig, &logger)
|
|
g, err := b.GetUsers(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 TestUpdateUser(t *testing.T) {
|
|
falseBool := false
|
|
trueBool := true
|
|
memberType := "Member"
|
|
|
|
type userProps struct {
|
|
id string
|
|
mail string
|
|
displayName string
|
|
onPremisesSamAccountName string
|
|
accountEnabled *bool
|
|
givenName *string
|
|
userType *string
|
|
}
|
|
type args struct {
|
|
nameOrID string
|
|
userProps userProps
|
|
disableUserMechanism string
|
|
}
|
|
type mockInputs struct {
|
|
funcName string
|
|
args []any
|
|
returns []any
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want *userProps
|
|
assertion assert.ErrorAssertionFunc
|
|
ldapMocks []mockInputs
|
|
}{
|
|
{
|
|
name: "Test changing ID",
|
|
args: args{
|
|
nameOrID: "testUser",
|
|
userProps: userProps{
|
|
id: "testUser",
|
|
},
|
|
},
|
|
want: nil,
|
|
assertion: func(t assert.TestingT, err error, args ...any) bool {
|
|
return assert.NotNil(t, err, args...)
|
|
},
|
|
ldapMocks: []mockInputs{
|
|
{
|
|
funcName: "Search",
|
|
args: []any{
|
|
ldap.NewSearchRequest(
|
|
"ou=people,dc=test",
|
|
ldap.ScopeWholeSubtree,
|
|
ldap.NeverDerefAliases, 1, 0, false,
|
|
"(&(objectClass=inetOrgPerson)(|(uid=testUser)(entryUUID=testUser)))",
|
|
ldapUserAttributes,
|
|
nil,
|
|
),
|
|
},
|
|
returns: []any{
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{
|
|
{
|
|
DN: "ua=testUser",
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Test changing mail",
|
|
args: args{
|
|
nameOrID: "testUser",
|
|
userProps: userProps{
|
|
mail: "testuser@example.org",
|
|
},
|
|
},
|
|
want: &userProps{
|
|
id: "testUser",
|
|
mail: "testuser@example.org",
|
|
displayName: "testUser",
|
|
onPremisesSamAccountName: "testUser",
|
|
accountEnabled: nil,
|
|
userType: &memberType,
|
|
},
|
|
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=people,dc=test",
|
|
ldap.ScopeWholeSubtree,
|
|
ldap.NeverDerefAliases, 1, 0, false,
|
|
"(&(objectClass=inetOrgPerson)(|(uid=testUser)(entryUUID=testUser)))",
|
|
ldapUserAttributes,
|
|
nil,
|
|
),
|
|
},
|
|
returns: []any{
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{
|
|
{
|
|
DN: "uid=oldName",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: "displayname",
|
|
Values: []string{"oldmail@example.org"},
|
|
},
|
|
{
|
|
Name: "entryUUID",
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: "mail",
|
|
Values: []string{"oldmail@example.org"},
|
|
},
|
|
{
|
|
Name: lconfig.UserEnabledAttribute,
|
|
Values: []string{""},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
},
|
|
{
|
|
funcName: "Search",
|
|
args: []any{
|
|
&ldap.SearchRequest{
|
|
BaseDN: "uid=oldName",
|
|
Scope: 0,
|
|
DerefAliases: 0,
|
|
SizeLimit: 1,
|
|
TimeLimit: 0,
|
|
TypesOnly: false,
|
|
Filter: "(objectClass=inetOrgPerson)",
|
|
Attributes: ldapUserAttributes,
|
|
Controls: []ldap.Control(nil),
|
|
},
|
|
},
|
|
returns: []any{
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{
|
|
{
|
|
DN: "uid=oldName",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: lconfig.UserIDAttribute,
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: lconfig.UserEmailAttribute,
|
|
Values: []string{"testuser@example.org"},
|
|
},
|
|
{
|
|
Name: lconfig.UserDisplayNameAttribute,
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: lconfig.UserNameAttribute,
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: lconfig.UserEnabledAttribute,
|
|
Values: []string{""},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
},
|
|
{
|
|
funcName: "Modify",
|
|
args: []any{
|
|
&ldap.ModifyRequest{
|
|
DN: "uid=oldName",
|
|
Changes: []ldap.Change{
|
|
{
|
|
Operation: 0x2,
|
|
Modification: ldap.PartialAttribute{
|
|
Type: "mail",
|
|
Vals: []string{"testuser@example.org"},
|
|
},
|
|
},
|
|
},
|
|
Controls: []ldap.Control(nil),
|
|
},
|
|
},
|
|
returns: []any{nil},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Test changing displayName",
|
|
args: args{
|
|
nameOrID: "testUser",
|
|
userProps: userProps{
|
|
displayName: "newName",
|
|
},
|
|
},
|
|
want: &userProps{
|
|
id: "testUser",
|
|
mail: "testuser@example.org",
|
|
displayName: "newName",
|
|
onPremisesSamAccountName: "testUser",
|
|
accountEnabled: nil,
|
|
userType: &memberType,
|
|
},
|
|
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=people,dc=test",
|
|
ldap.ScopeWholeSubtree,
|
|
ldap.NeverDerefAliases, 1, 0, false,
|
|
"(&(objectClass=inetOrgPerson)(|(uid=testUser)(entryUUID=testUser)))",
|
|
ldapUserAttributes,
|
|
nil,
|
|
),
|
|
},
|
|
returns: []any{
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{
|
|
{
|
|
DN: "uid=oldName",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: "displayname",
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: "entryUUID",
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: "mail",
|
|
Values: []string{"testuser@example.org"},
|
|
},
|
|
{
|
|
Name: lconfig.UserEnabledAttribute,
|
|
Values: []string{""},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
},
|
|
{
|
|
funcName: "Search",
|
|
args: []any{
|
|
&ldap.SearchRequest{
|
|
BaseDN: "uid=oldName",
|
|
Scope: 0,
|
|
DerefAliases: 0,
|
|
SizeLimit: 1,
|
|
TimeLimit: 0,
|
|
TypesOnly: false,
|
|
Filter: "(objectClass=inetOrgPerson)",
|
|
Attributes: ldapUserAttributes,
|
|
Controls: []ldap.Control(nil),
|
|
},
|
|
},
|
|
returns: []any{
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{
|
|
{
|
|
DN: "uid=oldName",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: lconfig.UserIDAttribute,
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: lconfig.UserEmailAttribute,
|
|
Values: []string{"testuser@example.org"},
|
|
},
|
|
{
|
|
Name: lconfig.UserDisplayNameAttribute,
|
|
Values: []string{"newName"},
|
|
},
|
|
{
|
|
Name: lconfig.UserNameAttribute,
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: lconfig.UserEnabledAttribute,
|
|
Values: []string{""},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
},
|
|
{
|
|
funcName: "Modify",
|
|
args: []any{
|
|
&ldap.ModifyRequest{
|
|
DN: "uid=oldName",
|
|
Changes: []ldap.Change{
|
|
{
|
|
Operation: 0x2,
|
|
Modification: ldap.PartialAttribute{
|
|
Type: lconfig.UserDisplayNameAttribute,
|
|
Vals: []string{"newName"},
|
|
},
|
|
},
|
|
},
|
|
Controls: []ldap.Control(nil),
|
|
},
|
|
},
|
|
returns: []any{nil},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Test changing userName",
|
|
args: args{
|
|
nameOrID: "testUser",
|
|
userProps: userProps{
|
|
onPremisesSamAccountName: "newName",
|
|
},
|
|
},
|
|
want: &userProps{
|
|
id: "testUser",
|
|
mail: "testuser@example.org",
|
|
displayName: "newName",
|
|
onPremisesSamAccountName: "newName",
|
|
accountEnabled: nil,
|
|
userType: &memberType,
|
|
},
|
|
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=people,dc=test",
|
|
ldap.ScopeWholeSubtree,
|
|
ldap.NeverDerefAliases, 1, 0, false,
|
|
"(&(objectClass=inetOrgPerson)(|(uid=testUser)(entryUUID=testUser)))",
|
|
ldapUserAttributes,
|
|
nil,
|
|
),
|
|
},
|
|
returns: []any{
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{
|
|
{
|
|
DN: "uid=oldName,ou=people,dc=test,dc=net",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: lconfig.UserDisplayNameAttribute,
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: lconfig.UserIDAttribute,
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: lconfig.UserEmailAttribute,
|
|
Values: []string{"testuser@example.org"},
|
|
},
|
|
{
|
|
Name: lconfig.UserNameAttribute,
|
|
Values: []string{"oldName"},
|
|
},
|
|
{
|
|
Name: lconfig.UserEnabledAttribute,
|
|
Values: []string{""},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
},
|
|
{
|
|
funcName: "Search",
|
|
args: []any{
|
|
&ldap.SearchRequest{
|
|
BaseDN: "ou=groups,dc=test",
|
|
Scope: 2, DerefAliases: 0, SizeLimit: 0, TimeLimit: 0,
|
|
TypesOnly: false,
|
|
Filter: "(&(objectClass=groupOfNames)(member=uid=oldName,ou=people,dc=test,dc=net))",
|
|
Attributes: []string{"cn", "entryUUID"},
|
|
Controls: []ldap.Control(nil),
|
|
},
|
|
},
|
|
returns: []any{
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{
|
|
{
|
|
DN: "cn=group1",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: lconfig.GroupNameAttribute,
|
|
Values: []string{"group1"},
|
|
},
|
|
{
|
|
Name: lconfig.GroupIDAttribute,
|
|
Values: []string{"group1-id"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
},
|
|
{
|
|
funcName: "ModifyDN",
|
|
args: []any{
|
|
&ldap.ModifyDNRequest{
|
|
DN: "uid=oldName,ou=people,dc=test,dc=net",
|
|
NewRDN: "uid=newName",
|
|
DeleteOldRDN: true,
|
|
NewSuperior: "",
|
|
Controls: []ldap.Control(nil),
|
|
},
|
|
},
|
|
returns: []any{
|
|
nil,
|
|
},
|
|
},
|
|
{
|
|
funcName: "Search",
|
|
args: []any{
|
|
&ldap.SearchRequest{
|
|
BaseDN: "uid=newName,ou=people,dc=test,dc=net",
|
|
Scope: 0,
|
|
DerefAliases: 0,
|
|
SizeLimit: 1,
|
|
TimeLimit: 0,
|
|
TypesOnly: false,
|
|
Filter: "(objectClass=inetOrgPerson)",
|
|
Attributes: ldapUserAttributes,
|
|
Controls: []ldap.Control(nil),
|
|
},
|
|
},
|
|
returns: []any{
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{
|
|
{
|
|
DN: "uid=newName,ou=people,dc=test,dc=net",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: lconfig.UserIDAttribute,
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: lconfig.UserEmailAttribute,
|
|
Values: []string{"testuser@example.org"},
|
|
},
|
|
{
|
|
Name: lconfig.UserDisplayNameAttribute,
|
|
Values: []string{"newName"},
|
|
},
|
|
{
|
|
Name: lconfig.UserNameAttribute,
|
|
Values: []string{"newName"},
|
|
},
|
|
{
|
|
Name: lconfig.UserEnabledAttribute,
|
|
Values: []string{""},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
},
|
|
{
|
|
funcName: "Modify",
|
|
args: []any{
|
|
&ldap.ModifyRequest{
|
|
DN: "cn=group1",
|
|
Changes: []ldap.Change{
|
|
{
|
|
Operation: 0x1,
|
|
Modification: ldap.PartialAttribute{
|
|
Type: "member",
|
|
Vals: []string{"uid=oldName,ou=people,dc=test,dc=net"},
|
|
},
|
|
},
|
|
{
|
|
Operation: 0x0,
|
|
Modification: ldap.PartialAttribute{
|
|
Type: "member",
|
|
Vals: []string{"uid=newName,ou=people,dc=test,dc=net"},
|
|
},
|
|
},
|
|
},
|
|
Controls: []ldap.Control(nil),
|
|
},
|
|
},
|
|
returns: []any{nil},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Test changing accountEnabled",
|
|
args: args{
|
|
nameOrID: "testUser",
|
|
userProps: userProps{
|
|
accountEnabled: &falseBool,
|
|
},
|
|
disableUserMechanism: "attribute",
|
|
},
|
|
want: &userProps{
|
|
id: "testUser",
|
|
mail: "testuser@example.org",
|
|
displayName: "testUser",
|
|
onPremisesSamAccountName: "testUser",
|
|
accountEnabled: &falseBool,
|
|
userType: &memberType,
|
|
},
|
|
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=people,dc=test",
|
|
ldap.ScopeWholeSubtree,
|
|
ldap.NeverDerefAliases, 1, 0, false,
|
|
"(&(objectClass=inetOrgPerson)(|(uid=testUser)(entryUUID=testUser)))",
|
|
ldapUserAttributes,
|
|
nil,
|
|
),
|
|
},
|
|
returns: []any{
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{
|
|
{
|
|
DN: "uid=name",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: "displayname",
|
|
Values: []string{"testuser@example.org"},
|
|
},
|
|
{
|
|
Name: "entryUUID",
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: "mail",
|
|
Values: []string{"testuser@example.org"},
|
|
},
|
|
{
|
|
Name: lconfig.UserEnabledAttribute,
|
|
Values: []string{"TRUE"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
},
|
|
{
|
|
funcName: "Search",
|
|
args: []any{
|
|
&ldap.SearchRequest{
|
|
BaseDN: "uid=name",
|
|
Scope: 0,
|
|
DerefAliases: 0,
|
|
SizeLimit: 1,
|
|
TimeLimit: 0,
|
|
TypesOnly: false,
|
|
Filter: "(objectClass=inetOrgPerson)",
|
|
Attributes: ldapUserAttributes,
|
|
Controls: []ldap.Control(nil),
|
|
},
|
|
},
|
|
returns: []any{
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{
|
|
{
|
|
DN: "uid=name",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: lconfig.UserIDAttribute,
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: lconfig.UserEmailAttribute,
|
|
Values: []string{"testuser@example.org"},
|
|
},
|
|
{
|
|
Name: lconfig.UserDisplayNameAttribute,
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: lconfig.UserNameAttribute,
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: lconfig.UserEnabledAttribute,
|
|
Values: []string{"FALSE"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
},
|
|
{
|
|
funcName: "Modify",
|
|
args: []any{
|
|
&ldap.ModifyRequest{
|
|
DN: "uid=name",
|
|
Changes: []ldap.Change{
|
|
{
|
|
Operation: 0x2,
|
|
Modification: ldap.PartialAttribute{
|
|
Type: lconfig.UserEnabledAttribute,
|
|
Vals: []string{"FALSE"},
|
|
},
|
|
},
|
|
},
|
|
Controls: []ldap.Control(nil),
|
|
},
|
|
},
|
|
returns: []any{nil},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Test disabling user as local admin",
|
|
args: args{
|
|
nameOrID: "testUser",
|
|
userProps: userProps{
|
|
accountEnabled: &falseBool,
|
|
},
|
|
disableUserMechanism: "group",
|
|
},
|
|
want: &userProps{
|
|
id: "testUser",
|
|
mail: "testuser@example.org",
|
|
displayName: "testUser",
|
|
onPremisesSamAccountName: "testUser",
|
|
accountEnabled: &falseBool,
|
|
userType: &memberType,
|
|
},
|
|
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=people,dc=test",
|
|
ldap.ScopeWholeSubtree,
|
|
ldap.NeverDerefAliases, 1, 0, false,
|
|
"(&(objectClass=inetOrgPerson)(|(uid=testUser)(entryUUID=testUser)))",
|
|
ldapUserAttributes,
|
|
nil,
|
|
),
|
|
},
|
|
returns: []any{
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{
|
|
{
|
|
DN: "uid=name",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: "displayname",
|
|
Values: []string{"testuser@example.org"},
|
|
},
|
|
{
|
|
Name: "entryUUID",
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: "mail",
|
|
Values: []string{"testuser@example.org"},
|
|
},
|
|
{
|
|
Name: lconfig.UserEnabledAttribute,
|
|
Values: []string{"TRUE"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
},
|
|
{
|
|
funcName: "Search",
|
|
args: []any{
|
|
&ldap.SearchRequest{
|
|
BaseDN: disableUsersGroup,
|
|
Scope: 0,
|
|
DerefAliases: 0,
|
|
SizeLimit: 1,
|
|
TimeLimit: 0,
|
|
TypesOnly: false,
|
|
Filter: groupSearchFilter,
|
|
Attributes: []string{"member"},
|
|
Controls: []ldap.Control(nil),
|
|
},
|
|
},
|
|
returns: []any{
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{
|
|
{
|
|
DN: "uid=name",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: "member",
|
|
Values: []string{""},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
},
|
|
{
|
|
funcName: "Modify",
|
|
args: []any{
|
|
&ldap.ModifyRequest{
|
|
DN: "uid=name",
|
|
Changes: []ldap.Change{
|
|
{
|
|
Operation: ldap.AddAttribute,
|
|
Modification: ldap.PartialAttribute{
|
|
Type: "member",
|
|
Vals: []string{"uid=name"},
|
|
},
|
|
},
|
|
},
|
|
Controls: []ldap.Control(nil),
|
|
},
|
|
},
|
|
returns: []any{nil},
|
|
},
|
|
{
|
|
funcName: "Modify",
|
|
args: []any{
|
|
&ldap.ModifyRequest{
|
|
DN: "uid=name",
|
|
Changes: []ldap.Change(nil),
|
|
Controls: []ldap.Control(nil),
|
|
},
|
|
},
|
|
returns: []any{nil},
|
|
},
|
|
{
|
|
funcName: "Search",
|
|
args: []any{
|
|
ldap.NewSearchRequest(
|
|
"uid=name",
|
|
ldap.ScopeBaseObject,
|
|
ldap.NeverDerefAliases, 1, 0, false,
|
|
"(objectClass=inetOrgPerson)",
|
|
ldapUserAttributes,
|
|
[]ldap.Control(nil),
|
|
),
|
|
},
|
|
returns: []any{
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{
|
|
{
|
|
DN: "uid=name",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: "uid",
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: "displayname",
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: "entryUUID",
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: "mail",
|
|
Values: []string{"testuser@example.org"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Test enabling user as local admin",
|
|
args: args{
|
|
nameOrID: "testUser",
|
|
userProps: userProps{
|
|
accountEnabled: &trueBool,
|
|
},
|
|
disableUserMechanism: "group",
|
|
},
|
|
want: &userProps{
|
|
id: "testUser",
|
|
mail: "testuser@example.org",
|
|
displayName: "testUser",
|
|
onPremisesSamAccountName: "testUser",
|
|
accountEnabled: &trueBool,
|
|
userType: &memberType,
|
|
},
|
|
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=people,dc=test",
|
|
ldap.ScopeWholeSubtree,
|
|
ldap.NeverDerefAliases, 1, 0, false,
|
|
"(&(objectClass=inetOrgPerson)(|(uid=testUser)(entryUUID=testUser)))",
|
|
ldapUserAttributes,
|
|
nil,
|
|
),
|
|
},
|
|
returns: []any{
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{
|
|
{
|
|
DN: "uid=name",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: "displayname",
|
|
Values: []string{"testuser@example.org"},
|
|
},
|
|
{
|
|
Name: "entryUUID",
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: "mail",
|
|
Values: []string{"testuser@example.org"},
|
|
},
|
|
{
|
|
Name: lconfig.UserEnabledAttribute,
|
|
Values: []string{"TRUE"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
},
|
|
{
|
|
funcName: "Search",
|
|
args: []any{
|
|
&ldap.SearchRequest{
|
|
BaseDN: disableUsersGroup,
|
|
Scope: 0,
|
|
DerefAliases: 0,
|
|
SizeLimit: 1,
|
|
TimeLimit: 0,
|
|
TypesOnly: false,
|
|
Filter: groupSearchFilter,
|
|
Attributes: []string{"member"},
|
|
Controls: []ldap.Control(nil),
|
|
},
|
|
},
|
|
returns: []any{
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{
|
|
{
|
|
DN: "uid=name",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: "member",
|
|
Values: []string{""},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
},
|
|
{
|
|
funcName: "Modify",
|
|
args: []any{
|
|
&ldap.ModifyRequest{
|
|
DN: "uid=name",
|
|
Changes: []ldap.Change{
|
|
{
|
|
Operation: ldap.DeleteAttribute,
|
|
Modification: ldap.PartialAttribute{
|
|
Type: "member",
|
|
Vals: []string{"uid=name"},
|
|
},
|
|
},
|
|
},
|
|
Controls: []ldap.Control(nil),
|
|
},
|
|
},
|
|
returns: []any{nil},
|
|
},
|
|
{
|
|
funcName: "Modify",
|
|
args: []any{
|
|
&ldap.ModifyRequest{
|
|
DN: "uid=name",
|
|
Changes: []ldap.Change(nil),
|
|
Controls: []ldap.Control(nil),
|
|
},
|
|
},
|
|
returns: []any{nil},
|
|
},
|
|
{
|
|
funcName: "Search",
|
|
args: []any{
|
|
ldap.NewSearchRequest(
|
|
"uid=name",
|
|
ldap.ScopeBaseObject,
|
|
ldap.NeverDerefAliases, 1, 0, false,
|
|
"(objectClass=inetOrgPerson)",
|
|
ldapUserAttributes,
|
|
[]ldap.Control(nil),
|
|
),
|
|
},
|
|
returns: []any{
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{
|
|
{
|
|
DN: "uid=name",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: "uid",
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: "displayname",
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: "entryUUID",
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: "mail",
|
|
Values: []string{"testuser@example.org"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Test changing userType",
|
|
args: args{
|
|
nameOrID: "testUser",
|
|
userProps: userProps{
|
|
userType: &memberType,
|
|
},
|
|
disableUserMechanism: "group",
|
|
},
|
|
want: &userProps{
|
|
id: "testUser",
|
|
mail: "testuser@example.org",
|
|
displayName: "testUser",
|
|
onPremisesSamAccountName: "testUser",
|
|
userType: &memberType,
|
|
},
|
|
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=people,dc=test",
|
|
ldap.ScopeWholeSubtree,
|
|
ldap.NeverDerefAliases, 1, 0, false,
|
|
"(&(objectClass=inetOrgPerson)(|(uid=testUser)(entryUUID=testUser)))",
|
|
ldapUserAttributes,
|
|
nil,
|
|
),
|
|
},
|
|
returns: []any{
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{
|
|
{
|
|
DN: "uid=name",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: "displayname",
|
|
Values: []string{"testuser@example.org"},
|
|
},
|
|
{
|
|
Name: "entryUUID",
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: "mail",
|
|
Values: []string{"testuser@example.org"},
|
|
},
|
|
{
|
|
Name: lconfig.UserTypeAttribute,
|
|
Values: []string{"Guest"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
},
|
|
{
|
|
funcName: "Modify",
|
|
args: []any{
|
|
&ldap.ModifyRequest{
|
|
DN: "uid=name",
|
|
Changes: []ldap.Change{
|
|
{
|
|
Operation: ldap.ReplaceAttribute,
|
|
Modification: ldap.PartialAttribute{
|
|
Type: "userTypeAttribute",
|
|
Vals: []string{"Member"},
|
|
},
|
|
},
|
|
},
|
|
Controls: []ldap.Control(nil),
|
|
},
|
|
},
|
|
returns: []any{nil},
|
|
},
|
|
{
|
|
funcName: "Modify",
|
|
args: []any{
|
|
&ldap.ModifyRequest{
|
|
DN: "uid=name",
|
|
Changes: []ldap.Change(nil),
|
|
Controls: []ldap.Control(nil),
|
|
},
|
|
},
|
|
returns: []any{nil},
|
|
},
|
|
{
|
|
funcName: "Search",
|
|
args: []any{
|
|
ldap.NewSearchRequest(
|
|
"uid=name",
|
|
ldap.ScopeBaseObject,
|
|
ldap.NeverDerefAliases, 1, 0, false,
|
|
"(objectClass=inetOrgPerson)",
|
|
ldapUserAttributes,
|
|
[]ldap.Control(nil),
|
|
),
|
|
},
|
|
returns: []any{
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{
|
|
{
|
|
DN: "uid=name",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: "uid",
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: "displayname",
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: "entryUUID",
|
|
Values: []string{"testUser"},
|
|
},
|
|
{
|
|
Name: "mail",
|
|
Values: []string{"testuser@example.org"},
|
|
},
|
|
{
|
|
Name: lconfig.UserTypeAttribute,
|
|
Values: []string{"Member"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
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
|
|
ldapConfig.DisableUserMechanism = tt.args.disableUserMechanism
|
|
i, _ := getMockedBackend(lm, ldapConfig, &logger)
|
|
|
|
user := libregraph.UserUpdate{
|
|
Id: &tt.args.userProps.id,
|
|
Mail: &tt.args.userProps.mail,
|
|
DisplayName: &tt.args.userProps.displayName,
|
|
OnPremisesSamAccountName: &tt.args.userProps.onPremisesSamAccountName,
|
|
AccountEnabled: tt.args.userProps.accountEnabled,
|
|
UserType: tt.args.userProps.userType,
|
|
}
|
|
|
|
emptyString := ""
|
|
var want *libregraph.User = nil
|
|
if tt.want != nil {
|
|
want = &libregraph.User{
|
|
Id: &tt.want.id,
|
|
Mail: &tt.want.mail,
|
|
DisplayName: tt.want.displayName,
|
|
OnPremisesSamAccountName: tt.want.onPremisesSamAccountName,
|
|
Surname: &emptyString,
|
|
GivenName: tt.want.givenName,
|
|
UserType: tt.want.userType,
|
|
}
|
|
|
|
if tt.want.accountEnabled != nil {
|
|
want.AccountEnabled = tt.want.accountEnabled
|
|
}
|
|
}
|
|
|
|
got, err := i.UpdateUser(context.Background(), tt.args.nameOrID, user)
|
|
tt.assertion(t, err)
|
|
assert.Equal(t, want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUsersEnabledState(t *testing.T) {
|
|
aliceEnabled := ldap.Entry{
|
|
DN: "alice",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: lconfig.UserEnabledAttribute,
|
|
Values: []string{"TRUE"},
|
|
},
|
|
},
|
|
}
|
|
|
|
bobDisabled := ldap.Entry{
|
|
DN: "bob",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: lconfig.UserEnabledAttribute,
|
|
Values: []string{"FALSE"},
|
|
},
|
|
},
|
|
}
|
|
|
|
carolImplicitlyEnabled := ldap.Entry{
|
|
DN: "carol",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: lconfig.UserEnabledAttribute,
|
|
},
|
|
},
|
|
}
|
|
|
|
type args struct {
|
|
usersToCheck []*ldap.Entry
|
|
expectedUsers map[string]bool
|
|
disableUserMechanism string
|
|
}
|
|
type mockInputs struct {
|
|
funcName string
|
|
args []any
|
|
returns []any
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want map[string]bool
|
|
assertion assert.ErrorAssertionFunc
|
|
ldapMocks []mockInputs
|
|
}{
|
|
{
|
|
name: "Test no users",
|
|
args: args{
|
|
usersToCheck: []*ldap.Entry{},
|
|
expectedUsers: map[string]bool{},
|
|
disableUserMechanism: "attribute",
|
|
},
|
|
want: map[string]bool{},
|
|
assertion: func(t assert.TestingT, err error, args ...any) bool {
|
|
return assert.Nil(t, err, args...)
|
|
},
|
|
ldapMocks: []mockInputs{},
|
|
},
|
|
{
|
|
name: "Test attribute enabled users",
|
|
args: args{
|
|
usersToCheck: []*ldap.Entry{&aliceEnabled, &bobDisabled, &carolImplicitlyEnabled},
|
|
expectedUsers: map[string]bool{"alice": true, "bob": false, "carol": true},
|
|
disableUserMechanism: "attribute",
|
|
},
|
|
want: map[string]bool{"alice": true, "bob": false, "carol": true},
|
|
assertion: func(t assert.TestingT, err error, args ...any) bool {
|
|
return assert.Nil(t, err, args...)
|
|
},
|
|
ldapMocks: []mockInputs{},
|
|
},
|
|
{
|
|
name: "Test attribute enabled users not in disabled group",
|
|
args: args{
|
|
usersToCheck: []*ldap.Entry{&aliceEnabled, &bobDisabled, &carolImplicitlyEnabled},
|
|
expectedUsers: map[string]bool{"alice": true, "bob": false, "carol": true},
|
|
disableUserMechanism: "group",
|
|
},
|
|
want: map[string]bool{"alice": true, "bob": true, "carol": true},
|
|
assertion: func(t assert.TestingT, err error, args ...any) bool {
|
|
return assert.Nil(t, err, args...)
|
|
},
|
|
ldapMocks: []mockInputs{
|
|
{
|
|
funcName: "Search",
|
|
args: []any{
|
|
ldap.NewSearchRequest(
|
|
disableUsersGroup,
|
|
ldap.ScopeBaseObject,
|
|
ldap.NeverDerefAliases, 1, 0, false,
|
|
groupSearchFilter,
|
|
[]string{"member"},
|
|
[]ldap.Control(nil),
|
|
),
|
|
},
|
|
returns: []any{
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{
|
|
{
|
|
DN: "cn=DisabledGroup",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: "member",
|
|
Values: []string{""},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Test attribute enabled users in disabled group",
|
|
args: args{
|
|
usersToCheck: []*ldap.Entry{&aliceEnabled, &bobDisabled, &carolImplicitlyEnabled},
|
|
expectedUsers: map[string]bool{"alice": true, "bob": false, "carol": true},
|
|
disableUserMechanism: "group",
|
|
},
|
|
want: map[string]bool{"alice": false, "bob": true, "carol": false},
|
|
assertion: func(t assert.TestingT, err error, args ...any) bool {
|
|
return assert.Nil(t, err, args...)
|
|
},
|
|
ldapMocks: []mockInputs{
|
|
{
|
|
funcName: "Search",
|
|
args: []any{
|
|
ldap.NewSearchRequest(
|
|
disableUsersGroup,
|
|
ldap.ScopeBaseObject,
|
|
ldap.NeverDerefAliases, 1, 0, false,
|
|
groupSearchFilter,
|
|
[]string{"member"},
|
|
[]ldap.Control(nil),
|
|
),
|
|
},
|
|
returns: []any{
|
|
&ldap.SearchResult{
|
|
Entries: []*ldap.Entry{
|
|
{
|
|
DN: "cn=DisabledGroup",
|
|
Attributes: []*ldap.EntryAttribute{
|
|
{
|
|
Name: "member",
|
|
Values: []string{"alice", "carol"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Test local group disable when ldap is throwing error",
|
|
args: args{
|
|
usersToCheck: []*ldap.Entry{&aliceEnabled, &bobDisabled, &carolImplicitlyEnabled},
|
|
expectedUsers: map[string]bool{"alice": true, "bob": false, "carol": true},
|
|
disableUserMechanism: "group",
|
|
},
|
|
want: nil,
|
|
assertion: func(t assert.TestingT, err error, args ...any) bool {
|
|
return assert.NotNil(t, err, args...)
|
|
},
|
|
ldapMocks: []mockInputs{
|
|
{
|
|
funcName: "Search",
|
|
args: []any{
|
|
ldap.NewSearchRequest(
|
|
disableUsersGroup,
|
|
ldap.ScopeBaseObject,
|
|
ldap.NeverDerefAliases, 1, 0, false,
|
|
groupSearchFilter,
|
|
[]string{"member"},
|
|
[]ldap.Control(nil),
|
|
),
|
|
},
|
|
returns: []any{
|
|
nil,
|
|
&ldap.Error{
|
|
Err: fmt.Errorf("very problematic problems"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
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
|
|
ldapConfig.DisableUserMechanism = tt.args.disableUserMechanism
|
|
i, _ := getMockedBackend(lm, ldapConfig, &logger)
|
|
|
|
got, err := i.usersEnabledState(tt.args.usersToCheck)
|
|
tt.assertion(t, err)
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|