Finish working code and most of tests.

This commit is contained in:
Daniël Franke
2023-02-15 10:06:23 +01:00
committed by Ralf Haferkamp
parent a51393e5bf
commit bed4a82ff0
3 changed files with 398 additions and 15 deletions
+40 -14
View File
@@ -215,8 +215,8 @@ func (i *LDAP) UpdateUser(ctx context.Context, nameOrID string, user libregraph.
// As we currently using uid as the naming Attribute for the user entries. (Do we even
// want to allow changing the user name?). For now just disallow it.
if user.OnPremisesSamAccountName != nil && *user.OnPremisesSamAccountName != "" {
if e.GetEqualFoldAttributeValue(i.userAttributeMap.userName) != *user.OnPremisesSamAccountName {
e, err = i.changeUserName(ctx, e.DN, user.GetOnPremisesSamAccountName())
if eu := e.GetEqualFoldAttributeValue(i.userAttributeMap.userName); eu != *user.OnPremisesSamAccountName {
e, err = i.changeUserName(ctx, e.DN, eu, user.GetOnPremisesSamAccountName())
if err != nil {
return nil, err
}
@@ -474,26 +474,20 @@ func (i *LDAP) GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*lib
}
func (i *LDAP) changeUserName(ctx context.Context, dn, originalUserName, newUserName string) (*ldap.Entry, error) {
logger := i.logger.SubloggerWithRequestID(ctx)
groups, err := i.getGroupsForUser(dn)
if err != nil {
return nil, err
}
newDN := fmt.Sprintf("%s=%s", i.userAttributeMap.userName, newUserName)
for _, g := range groups {
err = i.renameMemberInGroup(ctx, g, dn, newDN)
// This could leave the groups in an inconsistent state, might be a good idea to
// add a defer that changes everything back on error. Ideally, this entire function
// should be atomic, but LDAP doesn't support that.
if err != nil {
return nil, err
}
}
logger.Debug().Str("originalDN", dn).Str("newDN", newDN).Msg("Modifying DN")
mrdn := ldap.NewModifyDNRequest(dn, newDN, true, "")
if err := i.conn.ModifyDN(mrdn); err != nil {
var lerr *ldap.Error
logger.Debug().Str("originalDN", dn).Str("newDN", newDN).Err(err).Msg("Failed to modify DN")
if errors.As(err, &lerr) {
if lerr.ResultCode == ldap.LDAPResultEntryAlreadyExists {
err = errorcode.New(errorcode.NameAlreadyExists, lerr.Error())
@@ -502,11 +496,43 @@ func (i *LDAP) changeUserName(ctx context.Context, dn, originalUserName, newUser
return nil, err
}
return i.getUserByDN(newDN)
u, err := i.getUserByDN(fmt.Sprintf("%s,%s", newDN, i.userBaseDN))
if err != nil {
return nil, err
}
for _, g := range groups {
err = i.renameMemberInGroup(ctx, g, dn, u.DN)
// This could leave the groups in an inconsistent state, might be a good idea to
// add a defer that changes everything back on error. Ideally, this entire function
// should be atomic, but LDAP doesn't support that.
if err != nil {
return nil, err
}
}
return u, nil
}
// TODO: Fill this in.
func (i *LDAP) renameMemberInGroup(ctx context.Context, group *ldap.Entry, oldMember, newMember) error {
func (i *LDAP) renameMemberInGroup(ctx context.Context, group *ldap.Entry, oldMember, newMember string) error {
logger := i.logger.SubloggerWithRequestID(ctx)
logger.Debug().Str("oldMember", oldMember).Str("newMember", newMember).Msg("replacing group member")
members := group.GetEqualFoldAttributeValues(i.groupAttributeMap.member)
match := -1
for i, m := range members {
if m == oldMember {
match = i
}
}
if match != -1 {
members[match] = newMember
mr := ldap.NewModifyRequest(group.DN, nil)
mr.Replace(i.groupAttributeMap.member, members)
if err := i.conn.Modify(mr); err != nil {
return err
}
}
return nil
}
func (i *LDAP) updateUserPassowrd(ctx context.Context, dn, password string) error {
+1 -1
View File
@@ -423,7 +423,7 @@ func (i *LDAP) getGroupsForUser(dn string) ([]*ldap.Entry, error) {
"(%s=%s)",
i.groupAttributeMap.member, dn,
)
userGroups, err := i.getLDAPGroupsByFilter(groupFilter, false, false)
userGroups, err := i.getLDAPGroupsByFilter(groupFilter, true, false)
if err != nil {
return nil, err
}
+357
View File
@@ -278,3 +278,360 @@ func TestGetUsers(t *testing.T) {
t.Errorf("Expected zero length user slice")
}
}
func TestLDAP_UpdateUser(t *testing.T) {
type userProps struct {
id string
mail string
displayName string
onPremisesSamAccountName string
}
type args struct {
nameOrID string
userProps userProps
}
type mockInputs struct {
funcName string
args []interface{}
returns []interface{}
}
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 ...interface{}) bool {
return assert.NotNil(t, err, args...)
},
ldapMocks: []mockInputs{
{
funcName: "Search",
args: []interface{}{
ldap.NewSearchRequest(
"ou=people,dc=test",
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases, 1, 0, false,
"(&(objectClass=inetOrgPerson)(|(uid=testUser)(entryUUID=testUser)))",
[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"},
nil,
),
},
returns: []interface{}{
&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",
},
assertion: func(t assert.TestingT, err error, args ...interface{}) bool {
return assert.Nil(t, err, args...)
},
ldapMocks: []mockInputs{
{
funcName: "Search",
args: []interface{}{
ldap.NewSearchRequest(
"ou=people,dc=test",
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases, 1, 0, false,
"(&(objectClass=inetOrgPerson)(|(uid=testUser)(entryUUID=testUser)))",
[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"},
nil,
),
},
returns: []interface{}{
&ldap.SearchResult{
Entries: []*ldap.Entry{
{
DN: "ua=foo",
Attributes: []*ldap.EntryAttribute{
{
Name: "displayname",
Values: []string{"oldmail@example.org"},
},
},
},
{
DN: "ua=foo",
Attributes: []*ldap.EntryAttribute{
{
Name: "entryUUID",
Values: []string{"testUser"},
},
},
},
{
DN: "ua=foo",
Attributes: []*ldap.EntryAttribute{
{
Name: "mail",
Values: []string{"oldmail@example.org"},
},
},
},
},
},
nil,
},
},
{
funcName: "Search",
args: []interface{}{
&ldap.SearchRequest{
BaseDN: "ua=foo",
Scope: 0,
DerefAliases: 0,
SizeLimit: 1,
TimeLimit: 0,
TypesOnly: false,
Filter: "(objectClass=inetOrgPerson)",
Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"},
Controls: []ldap.Control(nil),
},
},
returns: []interface{}{
&ldap.SearchResult{
Entries: []*ldap.Entry{
{
DN: "ua=foo",
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"},
},
},
},
},
},
nil,
},
},
{
funcName: "Modify",
args: []interface{}{
&ldap.ModifyRequest{
DN: "ua=foo",
Changes: []ldap.Change{
{
Operation: 0x2,
Modification: ldap.PartialAttribute{
Type: "mail",
Vals: []string{"testuser@example.org"},
},
},
},
Controls: []ldap.Control(nil),
},
},
returns: []interface{}{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",
},
assertion: func(t assert.TestingT, err error, args ...interface{}) bool {
return assert.Nil(t, err, args...)
},
ldapMocks: []mockInputs{
{
funcName: "Search",
args: []interface{}{
ldap.NewSearchRequest(
"ou=people,dc=test",
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases, 1, 0, false,
"(&(objectClass=inetOrgPerson)(|(uid=testUser)(entryUUID=testUser)))",
[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"},
nil,
),
},
returns: []interface{}{
&ldap.SearchResult{
Entries: []*ldap.Entry{
{
DN: "ua=foo",
Attributes: []*ldap.EntryAttribute{
{
Name: "displayname",
Values: []string{"testUser"},
},
},
},
{
DN: "ua=foo",
Attributes: []*ldap.EntryAttribute{
{
Name: "entryUUID",
Values: []string{"testUser"},
},
},
},
{
DN: "ua=foo",
Attributes: []*ldap.EntryAttribute{
{
Name: "mail",
Values: []string{"testuser@example.org"},
},
},
},
},
},
nil,
},
},
{
funcName: "Search",
args: []interface{}{
&ldap.SearchRequest{
BaseDN: "ua=foo",
Scope: 0,
DerefAliases: 0,
SizeLimit: 1,
TimeLimit: 0,
TypesOnly: false,
Filter: "(objectClass=inetOrgPerson)",
Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"},
Controls: []ldap.Control(nil),
},
},
returns: []interface{}{
&ldap.SearchResult{
Entries: []*ldap.Entry{
{
DN: "ua=foo",
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"},
},
},
},
},
},
nil,
},
},
{
funcName: "Modify",
args: []interface{}{
&ldap.ModifyRequest{
DN: "ua=foo",
Changes: []ldap.Change{
{
Operation: 0x2,
Modification: ldap.PartialAttribute{
Type: lconfig.UserDisplayNameAttribute,
Vals: []string{"newName"},
},
},
},
Controls: []ldap.Control(nil),
},
},
returns: []interface{}{nil},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
lm := &mocks.Client{}
for _, mock := range tt.ldapMocks {
lm.On(mock.funcName, mock.args...).Return(mock.returns...)
}
i, _ := getMockedBackend(lm, lconfig, &logger)
user := libregraph.User{
Id: &tt.args.userProps.id,
Mail: &tt.args.userProps.mail,
DisplayName: &tt.args.userProps.displayName,
OnPremisesSamAccountName: &tt.args.userProps.onPremisesSamAccountName,
}
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: &emptyString,
}
}
got, err := i.UpdateUser(context.Background(), tt.args.nameOrID, user)
tt.assertion(t, err)
assert.Equal(t, want, got)
})
}
}