Finish working code and most of tests.
This commit is contained in:
committed by
Ralf Haferkamp
parent
a51393e5bf
commit
bed4a82ff0
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user