From fcf5783a1bfbacba87e8efa077bdeeffd4355329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sw=C3=A4rd?= Date: Fri, 17 Feb 2023 13:48:12 +0100 Subject: [PATCH] graph: Add accountEnabled flag to ldap backend. (#5588) * graph: Add accountEnabled flag to ldap backend. * Add missing accountEnabled attribute to user listing. --- services/graph/pkg/config/config.go | 1 + .../pkg/config/defaults/defaultconfig.go | 1 + services/graph/pkg/identity/ldap.go | 64 ++++-- .../pkg/identity/ldap_education_class_test.go | 2 +- .../identity/ldap_education_school_test.go | 1 + .../graph/pkg/identity/ldap_group_test.go | 8 +- services/graph/pkg/identity/ldap_test.go | 211 +++++++++++++++--- 7 files changed, 234 insertions(+), 54 deletions(-) diff --git a/services/graph/pkg/config/config.go b/services/graph/pkg/config/config.go index f140389ab..78978a754 100644 --- a/services/graph/pkg/config/config.go +++ b/services/graph/pkg/config/config.go @@ -60,6 +60,7 @@ type LDAP struct { UserDisplayNameAttribute string `yaml:"user_displayname_attribute" env:"LDAP_USER_SCHEMA_DISPLAY_NAME;GRAPH_LDAP_USER_DISPLAYNAME_ATTRIBUTE" desc:"LDAP Attribute to use for the displayname of users."` UserNameAttribute string `yaml:"user_name_attribute" env:"LDAP_USER_SCHEMA_USERNAME;GRAPH_LDAP_USER_NAME_ATTRIBUTE" desc:"LDAP Attribute to use for username of users."` UserIDAttribute string `yaml:"user_id_attribute" env:"LDAP_USER_SCHEMA_ID;GRAPH_LDAP_USER_UID_ATTRIBUTE" desc:"LDAP Attribute to use as the unique ID for users. This should be a stable globally unique ID like a UUID."` + UserEnabledAttribute string `yaml:"user_enabled_attribute" env:"LDAP_USER_ENABLED_ATTRIBUTE;GRAPH_USER_ENABLED_ATTRIBUTE" desc:"LDAP Attribute to use as a flag telling if the user is enabled or disabled."` GroupBaseDN string `yaml:"group_base_dn" env:"LDAP_GROUP_BASE_DN;GRAPH_LDAP_GROUP_BASE_DN" desc:"Search base DN for looking up LDAP groups."` GroupSearchScope string `yaml:"group_search_scope" env:"LDAP_GROUP_SCOPE;GRAPH_LDAP_GROUP_SEARCH_SCOPE" desc:"LDAP search scope to use when looking up groups. Supported scopes are 'base', 'one' and 'sub'."` diff --git a/services/graph/pkg/config/defaults/defaultconfig.go b/services/graph/pkg/config/defaults/defaultconfig.go index 1c494566e..0819334eb 100644 --- a/services/graph/pkg/config/defaults/defaultconfig.go +++ b/services/graph/pkg/config/defaults/defaultconfig.go @@ -68,6 +68,7 @@ func DefaultConfig() *config.Config { // FIXME: switch this to some more widely available attribute by default // ideally this needs to be constant for the lifetime of a users UserIDAttribute: "owncloudUUID", + UserEnabledAttribute: "ownCloudUserEnabled", GroupBaseDN: "ou=groups,o=libregraph-idm", GroupSearchScope: "sub", GroupFilter: "", diff --git a/services/graph/pkg/identity/ldap.go b/services/graph/pkg/identity/ldap.go index e54a6034a..4bd7488c6 100644 --- a/services/graph/pkg/identity/ldap.go +++ b/services/graph/pkg/identity/ldap.go @@ -4,6 +4,8 @@ import ( "context" "errors" "fmt" + "strconv" + "strings" "github.com/CiscoM31/godata" "github.com/go-ldap/ldap/v3" @@ -46,12 +48,13 @@ type LDAP struct { } type userAttributeMap struct { - displayName string - id string - mail string - userName string - givenName string - surname string + displayName string + id string + mail string + userName string + givenName string + surname string + accountEnabled string } type ldapAttributeValues map[string][]string @@ -62,12 +65,13 @@ func NewLDAPBackend(lc ldap.Client, config config.LDAP, logger *log.Logger) (*LD return nil, errors.New("invalid user attribute mappings") } uam := userAttributeMap{ - displayName: config.UserDisplayNameAttribute, - id: config.UserIDAttribute, - mail: config.UserEmailAttribute, - userName: config.UserNameAttribute, - givenName: givenNameAttribute, - surname: surNameAttribute, + displayName: config.UserDisplayNameAttribute, + id: config.UserIDAttribute, + mail: config.UserEmailAttribute, + userName: config.UserNameAttribute, + accountEnabled: config.UserEnabledAttribute, + givenName: givenNameAttribute, + surname: surNameAttribute, } if config.GroupNameAttribute == "" || config.GroupIDAttribute == "" { @@ -245,7 +249,17 @@ func (i *LDAP) UpdateUser(ctx context.Context, nameOrID string, user libregraph. updateNeeded = true } } - // TODO implement account disabled/enabled + + if user.AccountEnabled != nil { + boolString := strings.ToUpper(strconv.FormatBool(*user.AccountEnabled)) + ldapValue := e.GetEqualFoldAttributeValue(i.userAttributeMap.accountEnabled) + updateNeeded = true + if ldapValue != "" { + mr.Replace(i.userAttributeMap.accountEnabled, []string{boolString}) + } else { + mr.Add(i.userAttributeMap.accountEnabled, []string{boolString}) + } + } if updateNeeded { if err := i.conn.Modify(&mr); err != nil { @@ -269,6 +283,7 @@ func (i *LDAP) getUserByDN(dn string) (*ldap.Entry, error) { i.userAttributeMap.userName, i.userAttributeMap.surname, i.userAttributeMap.givenName, + i.userAttributeMap.accountEnabled, } filter := fmt.Sprintf("(objectClass=%s)", i.userObjectClass) @@ -365,6 +380,7 @@ func (i *LDAP) getLDAPUserByFilter(filter string) (*ldap.Entry, error) { i.userAttributeMap.userName, i.userAttributeMap.surname, i.userAttributeMap.givenName, + i.userAttributeMap.accountEnabled, } return i.searchLDAPEntryByFilter(i.userBaseDN, attrs, filter) } @@ -435,6 +451,7 @@ func (i *LDAP) GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*lib i.userAttributeMap.userName, i.userAttributeMap.surname, i.userAttributeMap.givenName, + i.userAttributeMap.accountEnabled, }, nil, ) @@ -581,6 +598,7 @@ func (i *LDAP) createUserModelFromLDAP(e *ldap.Entry) *libregraph.User { Id: &id, GivenName: &givenName, Surname: &surname, + AccountEnabled: booleanOrNil(e.GetEqualFoldAttributeValue(i.userAttributeMap.accountEnabled)), } } i.logger.Warn().Str("dn", e.DN).Msg("Invalid User. Missing username or id attribute") @@ -592,13 +610,18 @@ func (i *LDAP) userToLDAPAttrValues(user libregraph.User) (map[string][]string, i.userAttributeMap.displayName: {user.GetDisplayName()}, i.userAttributeMap.userName: {user.GetOnPremisesSamAccountName()}, i.userAttributeMap.mail: {user.GetMail()}, - "objectClass": {"inetOrgPerson", "organizationalPerson", "person", "top"}, + "objectClass": {"inetOrgPerson", "organizationalPerson", "person", "top", "ownCloudUser"}, "cn": {user.GetOnPremisesSamAccountName()}, } if !i.useServerUUID { attrs["owncloudUUID"] = []string{uuid.Must(uuid.NewV4()).String()} - attrs["objectClass"] = append(attrs["objectClass"], "owncloud") + } + + if user.AccountEnabled != nil { + attrs[i.userAttributeMap.accountEnabled] = []string{ + strings.ToUpper(strconv.FormatBool(*user.AccountEnabled)), + } } // inetOrgPerson requires "sn" to be set. Set it to the Username if @@ -636,6 +659,7 @@ func (i *LDAP) getUserAttrTypes() []string { "cn", "owncloudUUID", "userPassword", + i.userAttributeMap.accountEnabled, } } @@ -666,6 +690,16 @@ func pointerOrNil(val string) *string { return &val } +func booleanOrNil(val string) *bool { + boolValue, err := strconv.ParseBool(val) + + if err != nil { + return nil + } + + return &boolValue +} + func stringToScope(scope string) (int, error) { var s int switch scope { diff --git a/services/graph/pkg/identity/ldap_education_class_test.go b/services/graph/pkg/identity/ldap_education_class_test.go index df48c36e8..682e4d907 100644 --- a/services/graph/pkg/identity/ldap_education_class_test.go +++ b/services/graph/pkg/identity/ldap_education_class_test.go @@ -275,7 +275,7 @@ func TestGetEducationClassMembers(t *testing.T) { Scope: 0, SizeLimit: 1, Filter: "(objectClass=inetOrgPerson)", - Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"}, + Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"}, Controls: []ldap.Control(nil), } lm.On("Search", user_sr).Return(&ldap.SearchResult{Entries: []*ldap.Entry{userEntry}}, nil) diff --git a/services/graph/pkg/identity/ldap_education_school_test.go b/services/graph/pkg/identity/ldap_education_school_test.go index a26982e51..a97d4804d 100644 --- a/services/graph/pkg/identity/ldap_education_school_test.go +++ b/services/graph/pkg/identity/ldap_education_school_test.go @@ -21,6 +21,7 @@ var eduConfig = config.LDAP{ UserIDAttribute: "entryUUID", UserEmailAttribute: "mail", UserNameAttribute: "uid", + UserEnabledAttribute: "userEnabledAttribute", GroupBaseDN: "ou=groups,dc=test", GroupObjectClass: "groupOfNames", diff --git a/services/graph/pkg/identity/ldap_group_test.go b/services/graph/pkg/identity/ldap_group_test.go index da79a9772..511076b01 100644 --- a/services/graph/pkg/identity/ldap_group_test.go +++ b/services/graph/pkg/identity/ldap_group_test.go @@ -100,14 +100,14 @@ func TestGetGroup(t *testing.T) { BaseDN: "uid=user,ou=people,dc=test", SizeLimit: 1, Filter: "(objectClass=inetOrgPerson)", - Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"}, + Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"}, Controls: []ldap.Control(nil), } sr3 := &ldap.SearchRequest{ BaseDN: "uid=invalid,ou=people,dc=test", SizeLimit: 1, Filter: "(objectClass=inetOrgPerson)", - Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"}, + Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"}, Controls: []ldap.Control(nil), } @@ -195,14 +195,14 @@ func TestGetGroups(t *testing.T) { BaseDN: "uid=user,ou=people,dc=test", SizeLimit: 1, Filter: "(objectClass=inetOrgPerson)", - Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"}, + Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"}, Controls: []ldap.Control(nil), } sr3 := &ldap.SearchRequest{ BaseDN: "uid=invalid,ou=people,dc=test", SizeLimit: 1, Filter: "(objectClass=inetOrgPerson)", - Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"}, + Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"}, Controls: []ldap.Control(nil), } diff --git a/services/graph/pkg/identity/ldap_test.go b/services/graph/pkg/identity/ldap_test.go index 93cfe52ad..04f5f479f 100644 --- a/services/graph/pkg/identity/ldap_test.go +++ b/services/graph/pkg/identity/ldap_test.go @@ -30,6 +30,7 @@ var lconfig = config.LDAP{ UserIDAttribute: "entryUUID", UserEmailAttribute: "mail", UserNameAttribute: "uid", + UserEnabledAttribute: "userEnabledAttribute", GroupBaseDN: "ou=groups,dc=test", GroupObjectClass: "groupOfNames", @@ -43,12 +44,13 @@ var lconfig = config.LDAP{ var userEntry = ldap.NewEntry("uid=user", map[string][]string{ - "uid": {"user"}, - "displayname": {"DisplayName"}, - "mail": {"user@example"}, - "entryuuid": {"abcd-defg"}, - "sn": {"surname"}, - "givenname": {"givenName"}, + "uid": {"user"}, + "displayname": {"DisplayName"}, + "mail": {"user@example"}, + "entryuuid": {"abcd-defg"}, + "sn": {"surname"}, + "givenname": {"givenName"}, + "userenabledattribute": {"TRUE"}, }) var invalidUserEntry = ldap.NewEntry("uid=user", @@ -105,8 +107,9 @@ func TestCreateUser(t *testing.T) { ar.Attribute(lconfig.UserEmailAttribute, []string{mail}) ar.Attribute("sn", []string{surname}) ar.Attribute("givenname", []string{givenName}) - ar.Attribute("objectClass", []string{"inetOrgPerson", "organizationalPerson", "person", "top"}) + ar.Attribute("objectClass", []string{"inetOrgPerson", "organizationalPerson", "person", "top", "ownCloudUser"}) ar.Attribute("cn", []string{userName}) + ar.Attribute(lconfig.UserEnabledAttribute, []string{"TRUE"}) l := &mocks.Client{} l.On("Search", mock.Anything). @@ -124,6 +127,7 @@ func TestCreateUser(t *testing.T) { user.SetOnPremisesSamAccountName(userName) user.SetSurname(surname) user.SetGivenName(givenName) + user.SetAccountEnabled(true) c := lconfig c.UseServerUUID = true @@ -136,6 +140,7 @@ func TestCreateUser(t *testing.T) { assert.Equal(t, userName, newUser.GetOnPremisesSamAccountName()) assert.Equal(t, givenName, newUser.GetGivenName()) assert.Equal(t, surname, newUser.GetSurname()) + assert.True(t, newUser.GetAccountEnabled()) } func TestCreateUserModelFromLDAP(t *testing.T) { @@ -285,6 +290,7 @@ func TestUpdateUser(t *testing.T) { mail string displayName string onPremisesSamAccountName string + accountEnabled bool } type args struct { nameOrID string @@ -323,7 +329,7 @@ func TestUpdateUser(t *testing.T) { ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 0, false, "(&(objectClass=inetOrgPerson)(|(uid=testUser)(entryUUID=testUser)))", - []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"}, + []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"}, nil, ), }, @@ -353,6 +359,7 @@ func TestUpdateUser(t *testing.T) { mail: "testuser@example.org", displayName: "testUser", onPremisesSamAccountName: "testUser", + accountEnabled: true, }, assertion: func(t assert.TestingT, err error, args ...interface{}) bool { return assert.Nil(t, err, args...) @@ -366,7 +373,7 @@ func TestUpdateUser(t *testing.T) { ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 0, false, "(&(objectClass=inetOrgPerson)(|(uid=testUser)(entryUUID=testUser)))", - []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"}, + []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"}, nil, ), }, @@ -380,24 +387,18 @@ func TestUpdateUser(t *testing.T) { Name: "displayname", Values: []string{"oldmail@example.org"}, }, - }, - }, - { - DN: "uid=oldName", - Attributes: []*ldap.EntryAttribute{ { Name: "entryUUID", Values: []string{"testUser"}, }, - }, - }, - { - DN: "uid=oldName", - Attributes: []*ldap.EntryAttribute{ { Name: "mail", Values: []string{"oldmail@example.org"}, }, + { + Name: lconfig.UserEnabledAttribute, + Values: []string{"TRUE"}, + }, }, }, }, @@ -416,7 +417,7 @@ func TestUpdateUser(t *testing.T) { TimeLimit: 0, TypesOnly: false, Filter: "(objectClass=inetOrgPerson)", - Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"}, + Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"}, Controls: []ldap.Control(nil), }, }, @@ -442,6 +443,10 @@ func TestUpdateUser(t *testing.T) { Name: lconfig.UserNameAttribute, Values: []string{"testUser"}, }, + { + Name: lconfig.UserEnabledAttribute, + Values: []string{"TRUE"}, + }, }, }, }, @@ -483,6 +488,7 @@ func TestUpdateUser(t *testing.T) { mail: "testuser@example.org", displayName: "newName", onPremisesSamAccountName: "testUser", + accountEnabled: true, }, assertion: func(t assert.TestingT, err error, args ...interface{}) bool { return assert.Nil(t, err, args...) @@ -496,7 +502,7 @@ func TestUpdateUser(t *testing.T) { ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 0, false, "(&(objectClass=inetOrgPerson)(|(uid=testUser)(entryUUID=testUser)))", - []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"}, + []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"}, nil, ), }, @@ -510,24 +516,18 @@ func TestUpdateUser(t *testing.T) { Name: "displayname", Values: []string{"testUser"}, }, - }, - }, - { - DN: "uid=oldName", - Attributes: []*ldap.EntryAttribute{ { Name: "entryUUID", Values: []string{"testUser"}, }, - }, - }, - { - DN: "uid=oldName", - Attributes: []*ldap.EntryAttribute{ { Name: "mail", Values: []string{"testuser@example.org"}, }, + { + Name: lconfig.UserEnabledAttribute, + Values: []string{"TRUE"}, + }, }, }, }, @@ -546,7 +546,7 @@ func TestUpdateUser(t *testing.T) { TimeLimit: 0, TypesOnly: false, Filter: "(objectClass=inetOrgPerson)", - Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"}, + Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"}, Controls: []ldap.Control(nil), }, }, @@ -572,6 +572,10 @@ func TestUpdateUser(t *testing.T) { Name: lconfig.UserNameAttribute, Values: []string{"testUser"}, }, + { + Name: lconfig.UserEnabledAttribute, + Values: []string{"TRUE"}, + }, }, }, }, @@ -613,6 +617,7 @@ func TestUpdateUser(t *testing.T) { mail: "testuser@example.org", displayName: "newName", onPremisesSamAccountName: "newName", + accountEnabled: true, }, assertion: func(t assert.TestingT, err error, args ...interface{}) bool { return assert.Nil(t, err, args...) @@ -626,7 +631,7 @@ func TestUpdateUser(t *testing.T) { ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 0, false, "(&(objectClass=inetOrgPerson)(|(uid=testUser)(entryUUID=testUser)))", - []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"}, + []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"}, nil, ), }, @@ -652,6 +657,10 @@ func TestUpdateUser(t *testing.T) { Name: lconfig.UserNameAttribute, Values: []string{"oldName"}, }, + { + Name: lconfig.UserEnabledAttribute, + Values: []string{"TRUE"}, + }, }, }, }, @@ -718,7 +727,7 @@ func TestUpdateUser(t *testing.T) { TimeLimit: 0, TypesOnly: false, Filter: "(objectClass=inetOrgPerson)", - Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname"}, + Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"}, Controls: []ldap.Control(nil), }, }, @@ -744,6 +753,10 @@ func TestUpdateUser(t *testing.T) { Name: lconfig.UserNameAttribute, Values: []string{"newName"}, }, + { + Name: lconfig.UserEnabledAttribute, + Values: []string{"TRUE"}, + }, }, }, }, @@ -779,6 +792,135 @@ func TestUpdateUser(t *testing.T) { }, }, }, + { + name: "Test changing accountEnabled", + args: args{ + nameOrID: "testUser", + userProps: userProps{ + accountEnabled: false, + }, + }, + want: &userProps{ + id: "testUser", + mail: "testuser@example.org", + displayName: "testUser", + onPremisesSamAccountName: "testUser", + accountEnabled: false, + }, + 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", "userEnabledAttribute"}, + nil, + ), + }, + returns: []interface{}{ + &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: []interface{}{ + &ldap.SearchRequest{ + BaseDN: "uid=name", + Scope: 0, + DerefAliases: 0, + SizeLimit: 1, + TimeLimit: 0, + TypesOnly: false, + Filter: "(objectClass=inetOrgPerson)", + Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"}, + Controls: []ldap.Control(nil), + }, + }, + returns: []interface{}{ + &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: []interface{}{ + &ldap.ModifyRequest{ + DN: "uid=name", + Changes: []ldap.Change{ + { + Operation: 0x2, + Modification: ldap.PartialAttribute{ + Type: lconfig.UserEnabledAttribute, + Vals: []string{"FALSE"}, + }, + }, + }, + Controls: []ldap.Control(nil), + }, + }, + returns: []interface{}{nil}, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -805,6 +947,7 @@ func TestUpdateUser(t *testing.T) { OnPremisesSamAccountName: &tt.want.onPremisesSamAccountName, Surname: &emptyString, GivenName: &emptyString, + AccountEnabled: &tt.want.accountEnabled, } }