graph: Use userType property to distinguish between Member and Guest accounts

Fixes 5603

- Calling POST /graph/v1.0/users with userType not set will create a user as "Member"
- Calling POST /graph/v1.0/users with userType set as "Member" or "Guest" will create a user as "Member" or "Guest"
- Calling POST /graph/v1.0/users with userType set as anything but "Member" or "Guest" returns error
- Calling POST /graph/v1.0/education/users with userType not set will create a user as "Member"
- Calling POST /graph/v1.0/education/users with userType set as "Member" will create a user as "Member" and primary role as parameter specifies
- Calling POST /graph/v1.0/education/users with userType set as "Guest" will create a user as "Guest" and primary role as parameter specifies
- Calling POST /graph/v1.0/education/users with userType not set as anything but "Member" or "Guest" returns error
- Calling PATCH on /users or /education/users will update attribute in the same way as for POST
This commit is contained in:
Daniel Swärd
2023-03-10 13:08:04 +01:00
committed by Ralf Haferkamp
parent 36d0c8c939
commit 23ba180e8a
13 changed files with 410 additions and 20 deletions

View File

@@ -61,6 +61,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."`
UserTypeAttribute string `yaml:"user_type_attribute" env:"LDAP_USER_SCHEMA_USER_TYPE;GRAPH_LDAP_USER_TYPE_ATTRIBUTE" desc:"LDAP Attribute to distinguish between 'Member' and 'Guest' users."`
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."`
DisableUserMechanism string `yaml:"disable_user_mechanism" env:"LDAP_DISABLE_USER_MECHANISM;GRAPH_DISABLE_USER_MECHANISM" desc:"An option to control the behavior for disabling users. Supported options are 'none', 'attribute' and 'group'. If set to 'group', disabling a user via API will add the user to the configured group for disabled users, if set to 'attribute' this will be done in the ldap user entry, if set to 'none' the disable request is not processed. Default is 'attribute'."`
LdapDisabledUsersGroupDN string `yaml:"ldap_disabled_users_group_dn" env:"LDAP_DISABLED_USERS_GROUP_DN;GRAPH_DISABLED_USERS_GROUP_DN" desc:"The distinguished name of the group to which added users will be classified as disabled when 'disable_user_mechanism' is set to 'group'."`

View File

@@ -71,6 +71,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",
UserTypeAttribute: "ownCloudUserType",
UserEnabledAttribute: "ownCloudUserEnabled",
DisableUserMechanism: "attribute",
LdapDisabledUsersGroupDN: "cn=DisabledUsersGroup,ou=groups,o=libregraph-idm",

View File

@@ -77,6 +77,7 @@ type userAttributeMap struct {
givenName string
surname string
accountEnabled string
userType string
}
type ldapAttributeValues map[string][]string
@@ -105,6 +106,7 @@ func NewLDAPBackend(lc ldap.Client, config config.LDAP, logger *log.Logger) (*LD
accountEnabled: config.UserEnabledAttribute,
givenName: _givenNameAttribute,
surname: _surNameAttribute,
userType: config.UserTypeAttribute,
}
if config.GroupNameAttribute == "" || config.GroupIDAttribute == "" {
@@ -296,6 +298,12 @@ func (i *LDAP) UpdateUser(ctx context.Context, nameOrID string, user libregraph.
updateNeeded = true
}
}
if user.GetUserType() != "" {
if e.GetEqualFoldAttributeValue(i.userAttributeMap.userType) != user.GetUserType() {
mr.Replace(i.userAttributeMap.userType, []string{user.GetUserType()})
updateNeeded = true
}
}
if user.PasswordProfile != nil && user.PasswordProfile.GetPassword() != "" {
if i.usePwModifyExOp {
if err := i.updateUserPassowrd(ctx, e.DN, user.PasswordProfile.GetPassword()); err != nil {
@@ -372,6 +380,7 @@ func (i *LDAP) getUserByDN(dn string) (*ldap.Entry, error) {
i.userAttributeMap.surname,
i.userAttributeMap.givenName,
i.userAttributeMap.accountEnabled,
i.userAttributeMap.userType,
}
filter := fmt.Sprintf("(objectClass=%s)", i.userObjectClass)
@@ -469,6 +478,7 @@ func (i *LDAP) getLDAPUserByFilter(filter string) (*ldap.Entry, error) {
i.userAttributeMap.surname,
i.userAttributeMap.givenName,
i.userAttributeMap.accountEnabled,
i.userAttributeMap.userType,
}
return i.searchLDAPEntryByFilter(i.userBaseDN, attrs, filter)
}
@@ -707,6 +717,7 @@ func (i *LDAP) createUserModelFromLDAP(e *ldap.Entry) *libregraph.User {
id := e.GetEqualFoldAttributeValue(i.userAttributeMap.id)
givenName := e.GetEqualFoldAttributeValue(i.userAttributeMap.givenName)
surname := e.GetEqualFoldAttributeValue(i.userAttributeMap.surname)
userType := e.GetEqualFoldAttributeValue(i.userAttributeMap.userType)
if id != "" && opsan != "" {
return &libregraph.User{
@@ -716,6 +727,7 @@ func (i *LDAP) createUserModelFromLDAP(e *ldap.Entry) *libregraph.User {
Id: &id,
GivenName: &givenName,
Surname: &surname,
UserType: &userType,
AccountEnabled: booleanOrNil(e.GetEqualFoldAttributeValue(i.userAttributeMap.accountEnabled)),
}
}
@@ -730,6 +742,7 @@ func (i *LDAP) userToLDAPAttrValues(user libregraph.User) (map[string][]string,
i.userAttributeMap.mail: {user.GetMail()},
"objectClass": {"inetOrgPerson", "organizationalPerson", "person", "top", "ownCloudUser"},
"cn": {user.GetOnPremisesSamAccountName()},
i.userAttributeMap.userType: {user.GetUserType()},
}
if !i.useServerUUID {
@@ -778,6 +791,7 @@ func (i *LDAP) getUserAttrTypes() []string {
"owncloudUUID",
"userPassword",
i.userAttributeMap.accountEnabled,
i.userAttributeMap.userType,
}
}

View File

@@ -275,7 +275,7 @@ func TestGetEducationClassMembers(t *testing.T) {
Scope: 0,
SizeLimit: 1,
Filter: "(objectClass=inetOrgPerson)",
Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "userTypeAttribute"},
Controls: []ldap.Control(nil),
}
lm.On("Search", user_sr).Return(&ldap.SearchResult{Entries: []*ldap.Entry{userEntry}}, nil)

View File

@@ -22,6 +22,7 @@ var eduConfig = config.LDAP{
UserEmailAttribute: "mail",
UserNameAttribute: "uid",
UserEnabledAttribute: "userEnabledAttribute",
UserTypeAttribute: "userTypeAttribute",
GroupBaseDN: "ou=groups,dc=test",
GroupObjectClass: "groupOfNames",

View File

@@ -133,6 +133,12 @@ func (i *LDAP) UpdateEducationUser(ctx context.Context, nameOrID string, user li
updateNeeded = true
}
}
if user.GetUserType() != "" {
if e.GetEqualFoldAttributeValue(i.userAttributeMap.userType) != user.GetUserType() {
mr.Replace(i.userAttributeMap.userType, []string{user.GetUserType()})
updateNeeded = true
}
}
if user.PasswordProfile != nil && user.PasswordProfile.GetPassword() != "" {
if i.usePwModifyExOp {
if err := i.updateUserPassowrd(ctx, e.DN, user.PasswordProfile.GetPassword()); err != nil {
@@ -250,6 +256,8 @@ func (i *LDAP) educationUserToUser(eduUser libregraph.EducationUser) *libregraph
user.GivenName = eduUser.GivenName
user.DisplayName = eduUser.DisplayName
user.Mail = eduUser.Mail
user.UserType = eduUser.UserType
return user
}
@@ -262,6 +270,7 @@ func (i *LDAP) userToEducationUser(user libregraph.User, e *ldap.Entry) *libregr
eduUser.GivenName = user.GivenName
eduUser.DisplayName = user.DisplayName
eduUser.Mail = user.Mail
eduUser.UserType = user.UserType
if e != nil {
// Set the education User specific Attributes from the supplied LDAP Entry
@@ -345,6 +354,7 @@ func (i *LDAP) getEducationUserAttrTypes() []string {
i.userAttributeMap.surname,
i.userAttributeMap.givenName,
i.userAttributeMap.accountEnabled,
i.userAttributeMap.userType,
i.educationConfig.userAttributeMap.identities,
i.educationConfig.userAttributeMap.primaryRole,
i.educationConfig.memberOfSchoolAttribute,

View File

@@ -11,7 +11,19 @@ import (
"github.com/test-go/testify/mock"
)
var eduUserAttrs = []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "oCExternalIdentity", "userClass", "ocMemberOfSchool"}
var eduUserAttrs = []string{
"displayname",
"entryUUID",
"mail",
"uid",
"sn",
"givenname",
"userEnabledAttribute",
"userTypeAttribute",
"oCExternalIdentity",
"userClass",
"ocMemberOfSchool",
}
var eduUserEntry = ldap.NewEntry("uid=user,ou=people,dc=test",
map[string][]string{
@@ -24,6 +36,7 @@ var eduUserEntry = ldap.NewEntry("uid=user,ou=people,dc=test",
"$ http://idp $ testuser",
"xxx $ http://idpnew $ xxxxx-xxxxx-xxxxx",
},
"userTypeAttribute": {"Member"},
})
var renamedEduUserEntry = ldap.NewEntry("uid=newtestuser,ou=people,dc=test",
map[string][]string{
@@ -36,6 +49,7 @@ var renamedEduUserEntry = ldap.NewEntry("uid=newtestuser,ou=people,dc=test",
"$ http://idp $ testuser",
"xxx $ http://idpnew $ xxxxx-xxxxx-xxxxx",
},
"userTypeAttribute": {"Guest"},
})
var eduUserEntryWithSchool = ldap.NewEntry("uid=user,ou=people,dc=test",
map[string][]string{
@@ -88,6 +102,7 @@ func TestCreateEducationUser(t *testing.T) {
user.SetOnPremisesSamAccountName("testuser")
user.SetMail("testuser@example.org")
user.SetPrimaryRole("student")
user.SetUserType(("Member"))
eduUser, err := b.CreateEducationUser(context.Background(), *user)
lm.AssertNumberOfCalls(t, "Add", 1)
lm.AssertNumberOfCalls(t, "Search", 1)
@@ -97,6 +112,7 @@ func TestCreateEducationUser(t *testing.T) {
assert.Equal(t, eduUser.GetOnPremisesSamAccountName(), user.GetOnPremisesSamAccountName())
assert.Equal(t, "abcd-defg", eduUser.GetId())
assert.Equal(t, eduUser.GetPrimaryRole(), user.GetPrimaryRole())
assert.Equal(t, eduUser.GetUserType(), user.GetUserType())
}
func TestDeleteEducationUser(t *testing.T) {
@@ -174,7 +190,7 @@ func TestUpdateEducationUser(t *testing.T) {
Scope: 0,
SizeLimit: 1,
Filter: "(objectClass=inetOrgPerson)",
Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "userTypeAttribute"},
}
eduUserLookupReq := &ldap.SearchRequest{
BaseDN: "uid=newtestuser,ou=people,dc=test",
@@ -249,4 +265,5 @@ func TestUpdateEducationUser(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, eduUser.GetOnPremisesSamAccountName(), "newtestuser")
assert.Equal(t, "abcd-defg", eduUser.GetId())
assert.Equal(t, "Guest", eduUser.GetUserType())
}

View File

@@ -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", "userEnabledAttribute"},
Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "userTypeAttribute"},
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", "userEnabledAttribute"},
Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "userTypeAttribute"},
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", "userEnabledAttribute"},
Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "userTypeAttribute"},
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", "userEnabledAttribute"},
Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "userTypeAttribute"},
Controls: []ldap.Control(nil),
}

View File

@@ -36,6 +36,7 @@ var lconfig = config.LDAP{
UserEmailAttribute: "mail",
UserNameAttribute: "uid",
UserEnabledAttribute: "userEnabledAttribute",
UserTypeAttribute: "userTypeAttribute",
LdapDisabledUsersGroupDN: disableUsersGroup,
DisableUserMechanism: "attribute",
@@ -58,6 +59,7 @@ var userEntry = ldap.NewEntry("uid=user",
"sn": {"surname"},
"givenname": {"givenName"},
"userenabledattribute": {"TRUE"},
"usertypeattribute": {"Member"},
})
var invalidUserEntry = ldap.NewEntry("uid=user",
@@ -107,6 +109,7 @@ func TestCreateUser(t *testing.T) {
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})
@@ -117,6 +120,7 @@ func TestCreateUser(t *testing.T) {
ar.Attribute("objectClass", []string{"inetOrgPerson", "organizationalPerson", "person", "top", "ownCloudUser"})
ar.Attribute("cn", []string{userName})
ar.Attribute(lconfig.UserEnabledAttribute, []string{"TRUE"})
ar.Attribute(lconfig.UserTypeAttribute, []string{"Member"})
l := &mocks.Client{}
l.On("Search", mock.Anything).
@@ -135,6 +139,7 @@ func TestCreateUser(t *testing.T) {
user.SetSurname(surname)
user.SetGivenName(givenName)
user.SetAccountEnabled(true)
user.SetUserType(userType)
c := lconfig
c.UseServerUUID = true
@@ -148,6 +153,7 @@ func TestCreateUser(t *testing.T) {
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) {
@@ -301,6 +307,7 @@ func TestUpdateUser(t *testing.T) {
displayName string
onPremisesSamAccountName string
accountEnabled *bool
userType string
}
type args struct {
nameOrID string
@@ -340,7 +347,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", "userEnabledAttribute"},
[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "userTypeAttribute"},
nil,
),
},
@@ -384,7 +391,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", "userEnabledAttribute"},
[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "userTypeAttribute"},
nil,
),
},
@@ -428,7 +435,7 @@ func TestUpdateUser(t *testing.T) {
TimeLimit: 0,
TypesOnly: false,
Filter: "(objectClass=inetOrgPerson)",
Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "userTypeAttribute"},
Controls: []ldap.Control(nil),
},
},
@@ -513,7 +520,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", "userEnabledAttribute"},
[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "userTypeAttribute"},
nil,
),
},
@@ -557,7 +564,7 @@ func TestUpdateUser(t *testing.T) {
TimeLimit: 0,
TypesOnly: false,
Filter: "(objectClass=inetOrgPerson)",
Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "userTypeAttribute"},
Controls: []ldap.Control(nil),
},
},
@@ -642,7 +649,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", "userEnabledAttribute"},
[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "userTypeAttribute"},
nil,
),
},
@@ -738,7 +745,7 @@ func TestUpdateUser(t *testing.T) {
TimeLimit: 0,
TypesOnly: false,
Filter: "(objectClass=inetOrgPerson)",
Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "userTypeAttribute"},
Controls: []ldap.Control(nil),
},
},
@@ -831,7 +838,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", "userEnabledAttribute"},
[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "userTypeAttribute"},
nil,
),
},
@@ -875,7 +882,7 @@ func TestUpdateUser(t *testing.T) {
TimeLimit: 0,
TypesOnly: false,
Filter: "(objectClass=inetOrgPerson)",
Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
Attributes: []string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "userTypeAttribute"},
Controls: []ldap.Control(nil),
},
},
@@ -961,7 +968,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", "userEnabledAttribute"},
[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "userTypeAttribute"},
nil,
),
},
@@ -1064,7 +1071,7 @@ func TestUpdateUser(t *testing.T) {
ldap.ScopeBaseObject,
ldap.NeverDerefAliases, 1, 0, false,
"(objectClass=inetOrgPerson)",
[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "userTypeAttribute"},
[]ldap.Control(nil),
),
},
@@ -1127,7 +1134,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", "userEnabledAttribute"},
[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "userTypeAttribute"},
nil,
),
},
@@ -1230,7 +1237,7 @@ func TestUpdateUser(t *testing.T) {
ldap.ScopeBaseObject,
ldap.NeverDerefAliases, 1, 0, false,
"(objectClass=inetOrgPerson)",
[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute"},
[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "userTypeAttribute"},
[]ldap.Control(nil),
),
},
@@ -1265,6 +1272,144 @@ func TestUpdateUser(t *testing.T) {
},
},
},
{
name: "Test changing userType",
args: args{
nameOrID: "testUser",
userProps: userProps{
userType: "Member",
},
disableUserMechanism: "group",
},
want: &userProps{
id: "testUser",
mail: "testuser@example.org",
displayName: "testUser",
onPremisesSamAccountName: "testUser",
userType: "Member",
},
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", "userTypeAttribute"},
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.UserTypeAttribute,
Values: []string{"Guest"},
},
},
},
},
},
nil,
},
},
{
funcName: "Modify",
args: []interface{}{
&ldap.ModifyRequest{
DN: "uid=name",
Changes: []ldap.Change{
{
Operation: ldap.ReplaceAttribute,
Modification: ldap.PartialAttribute{
Type: "userTypeAttribute",
Vals: []string{"Member"},
},
},
},
Controls: []ldap.Control(nil),
},
},
returns: []interface{}{nil},
},
{
funcName: "Modify",
args: []interface{}{
&ldap.ModifyRequest{
DN: "uid=name",
Changes: []ldap.Change(nil),
Controls: []ldap.Control(nil),
},
},
returns: []interface{}{nil},
},
{
funcName: "Search",
args: []interface{}{
ldap.NewSearchRequest(
"uid=name",
ldap.ScopeBaseObject,
ldap.NeverDerefAliases, 1, 0, false,
"(objectClass=inetOrgPerson)",
[]string{"displayname", "entryUUID", "mail", "uid", "sn", "givenname", "userEnabledAttribute", "userTypeAttribute"},
[]ldap.Control(nil),
),
},
returns: []interface{}{
&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) {
@@ -1283,6 +1428,7 @@ func TestUpdateUser(t *testing.T) {
DisplayName: &tt.args.userProps.displayName,
OnPremisesSamAccountName: &tt.args.userProps.onPremisesSamAccountName,
AccountEnabled: tt.args.userProps.accountEnabled,
UserType: &tt.args.userProps.userType,
}
emptyString := ""
@@ -1295,6 +1441,7 @@ func TestUpdateUser(t *testing.T) {
OnPremisesSamAccountName: &tt.want.onPremisesSamAccountName,
Surname: &emptyString,
GivenName: &emptyString,
UserType: &tt.want.userType,
}
if tt.want.accountEnabled != nil {

View File

@@ -132,6 +132,16 @@ func (g Graph) PostEducationUser(w http.ResponseWriter, r *http.Request) {
return
}
if u.HasUserType() {
if !isValidUserType(*u.UserType) {
logger.Debug().Interface("user", u).Msg("invalid userType attribute")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid userType attribute, valid options are 'Member' or 'Guest'")
return
}
} else {
u.SetUserType("Member")
}
if _, ok := u.GetPrimaryRoleOk(); !ok {
logger.Debug().Err(err).Interface("user", u).Msg("could not create education user: missing required Attribute: 'primaryRole'")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing required Attribute: 'primaryRole'")
@@ -368,6 +378,14 @@ func (g Graph) PatchEducationUser(w http.ResponseWriter, r *http.Request) {
features = append(features, events.UserFeature{Name: "displayname", Value: *name})
}
if changes.HasUserType() {
if !isValidUserType(*changes.UserType) {
logger.Debug().Interface("user", changes).Msg("invalid userType attribute")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid userType attribute, valid options are 'Member' or 'Guest'")
return
}
}
logger.Debug().Str("nameid", nameOrID).Interface("changes", *changes).Msg("calling update education user on backend")
u, err := g.identityEducationBackend.UpdateEducationUser(r.Context(), nameOrID, *changes)
if err != nil {

View File

@@ -297,7 +297,65 @@ var _ = Describe("EducationUsers", func() {
svc.PostEducationUser(rr, r)
Expect(rr.Code).To(Equal(http.StatusOK))
data, err := io.ReadAll(rr.Body)
Expect(err).ToNot(HaveOccurred())
createdUser := libregraph.EducationUser{}
err = json.Unmarshal(data, &createdUser)
Expect(err).ToNot(HaveOccurred())
Expect(createdUser.GetUserType()).To(Equal("Member"))
})
It("creates a guest user", func() {
roleService.On("AssignRoleToUser", mock.Anything, mock.Anything).Return(&settingssvc.AssignRoleToUserResponse{}, nil)
identityEducationBackend.On("CreateEducationUser", mock.Anything, mock.Anything).Return(func(ctx context.Context, user libregraph.EducationUser) *libregraph.EducationUser {
user.SetId("/users/user")
return &user
}, nil)
user.SetUserType("Guest")
userJson, err := json.Marshal(user)
Expect(err).ToNot(HaveOccurred())
r := httptest.NewRequest(http.MethodPost, "/graph/v1.0/education/users", bytes.NewBuffer(userJson))
r = r.WithContext(revactx.ContextSetUser(ctx, currentUser))
svc.PostEducationUser(rr, r)
Expect(rr.Code).To(Equal(http.StatusOK))
data, err := io.ReadAll(rr.Body)
Expect(err).ToNot(HaveOccurred())
createdUser := libregraph.EducationUser{}
err = json.Unmarshal(data, &createdUser)
Expect(err).ToNot(HaveOccurred())
Expect(createdUser.GetUserType()).To(Equal("Guest"))
})
It("creates a member user", func() {
roleService.On("AssignRoleToUser", mock.Anything, mock.Anything).Return(&settingssvc.AssignRoleToUserResponse{}, nil)
identityEducationBackend.On("CreateEducationUser", mock.Anything, mock.Anything).Return(func(ctx context.Context, user libregraph.EducationUser) *libregraph.EducationUser {
user.SetId("/users/user")
return &user
}, nil)
user.SetUserType("Member")
userJson, err := json.Marshal(user)
Expect(err).ToNot(HaveOccurred())
r := httptest.NewRequest(http.MethodPost, "/graph/v1.0/education/users", bytes.NewBuffer(userJson))
r = r.WithContext(revactx.ContextSetUser(ctx, currentUser))
svc.PostEducationUser(rr, r)
Expect(rr.Code).To(Equal(http.StatusOK))
data, err := io.ReadAll(rr.Body)
Expect(err).ToNot(HaveOccurred())
createdUser := libregraph.EducationUser{}
err = json.Unmarshal(data, &createdUser)
Expect(err).ToNot(HaveOccurred())
Expect(createdUser.GetUserType()).To(Equal("Member"))
})
})
Describe("DeleteEducationUser", func() {
@@ -406,6 +464,20 @@ var _ = Describe("EducationUsers", func() {
Expect(rr.Code).To(Equal(http.StatusBadRequest))
})
It("handles invalid userType", func() {
user.SetUserType("Clown")
data, err := json.Marshal(user)
Expect(err).ToNot(HaveOccurred())
r := httptest.NewRequest(http.MethodPost, "/graph/v1.0/education/users?$invalid=true", bytes.NewBuffer(data))
rctx := chi.NewRouteContext()
rctx.URLParams.Add("userID", user.GetId())
r = r.WithContext(context.WithValue(revactx.ContextSetUser(ctx, currentUser), chi.RouteCtxKey, rctx))
svc.PatchEducationUser(rr, r)
Expect(rr.Code).To(Equal(http.StatusBadRequest))
})
It("updates attributes", func() {
identityEducationBackend.On("UpdateEducationUser", mock.Anything, user.GetId(), mock.Anything).Return(user, nil)

View File

@@ -305,6 +305,16 @@ func (g Graph) PostUser(w http.ResponseWriter, r *http.Request) {
return
}
if u.HasUserType() {
if !isValidUserType(*u.UserType) {
logger.Debug().Interface("user", u).Msg("invalid userType attribute")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid userType attribute, valid options are 'Member' or 'Guest'")
return
}
} else {
u.SetUserType("Member")
}
logger.Debug().Interface("user", u).Msg("calling create user on backend")
if u, err = g.identityBackend.CreateUser(r.Context(), *u); err != nil {
logger.Debug().Err(err).Msg("could not create user: backend error")
@@ -652,6 +662,14 @@ func (g Graph) PatchUser(w http.ResponseWriter, r *http.Request) {
features = append(features, events.UserFeature{Name: "displayname", Value: *name})
}
if changes.HasUserType() {
if !isValidUserType(*changes.UserType) {
logger.Debug().Interface("user", changes).Msg("invalid userType attribute")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "invalid userType attribute, valid options are 'Member' or 'Guest'")
return
}
}
logger.Debug().Str("nameid", nameOrID).Interface("changes", *changes).Msg("calling update user on backend")
u, err := g.identityBackend.UpdateUser(r.Context(), nameOrID, *changes)
if err != nil {
@@ -743,3 +761,15 @@ func sortUsers(req *godata.GoDataRequest, users []*libregraph.User) ([]*libregra
}
return users, nil
}
func isValidUserType(userType string) bool {
userType = strings.ToLower(userType)
for _, value := range []string{"member", "guest"} {
if userType == value {
return true
}
}
return false
}

View File

@@ -588,6 +588,11 @@ var _ = Describe("Users", func() {
assertHandleBadAttributes(user)
})
It("handles invalid userType", func() {
user.SetUserType("Clown")
assertHandleBadAttributes(user)
})
It("creates a user", func() {
roleService.On("AssignRoleToUser", mock.Anything, mock.Anything).Return(&settings.AssignRoleToUserResponse{}, nil)
identityBackend.On("CreateUser", mock.Anything, mock.Anything).Return(func(ctx context.Context, user libregraph.User) *libregraph.User {
@@ -602,6 +607,63 @@ var _ = Describe("Users", func() {
svc.PostUser(rr, r)
Expect(rr.Code).To(Equal(http.StatusOK))
data, err := io.ReadAll(rr.Body)
Expect(err).ToNot(HaveOccurred())
createdUser := libregraph.User{}
err = json.Unmarshal(data, &createdUser)
Expect(err).ToNot(HaveOccurred())
Expect(createdUser.GetUserType()).To(Equal("Member"))
})
It("creates a guest user", func() {
roleService.On("AssignRoleToUser", mock.Anything, mock.Anything).Return(&settings.AssignRoleToUserResponse{}, nil)
identityBackend.On("CreateUser", mock.Anything, mock.Anything).Return(func(ctx context.Context, user libregraph.User) *libregraph.User {
user.SetId("/users/user")
return &user
}, nil)
user.SetUserType("Guest")
userJson, err := json.Marshal(user)
Expect(err).ToNot(HaveOccurred())
r := httptest.NewRequest(http.MethodPost, "/graph/v1.0/users", bytes.NewBuffer(userJson))
r = r.WithContext(revactx.ContextSetUser(ctx, currentUser))
svc.PostUser(rr, r)
Expect(rr.Code).To(Equal(http.StatusOK))
data, err := io.ReadAll(rr.Body)
Expect(err).ToNot(HaveOccurred())
createdUser := libregraph.User{}
err = json.Unmarshal(data, &createdUser)
Expect(err).ToNot(HaveOccurred())
Expect(createdUser.GetUserType()).To(Equal("Guest"))
})
It("creates a member user", func() {
roleService.On("AssignRoleToUser", mock.Anything, mock.Anything).Return(&settings.AssignRoleToUserResponse{}, nil)
identityBackend.On("CreateUser", mock.Anything, mock.Anything).Return(func(ctx context.Context, user libregraph.User) *libregraph.User {
user.SetId("/users/user")
return &user
}, nil)
user.SetUserType("Member")
userJson, err := json.Marshal(user)
Expect(err).ToNot(HaveOccurred())
r := httptest.NewRequest(http.MethodPost, "/graph/v1.0/users", bytes.NewBuffer(userJson))
r = r.WithContext(revactx.ContextSetUser(ctx, currentUser))
svc.PostUser(rr, r)
Expect(rr.Code).To(Equal(http.StatusOK))
data, err := io.ReadAll(rr.Body)
Expect(err).ToNot(HaveOccurred())
createdUser := libregraph.User{}
err = json.Unmarshal(data, &createdUser)
Expect(err).ToNot(HaveOccurred())
Expect(createdUser.GetUserType()).To(Equal("Member"))
})
Describe("Handling usernames with spaces", func() {
@@ -768,9 +830,25 @@ var _ = Describe("Users", func() {
Expect(rr.Code).To(Equal(http.StatusBadRequest))
})
It("handles invalid userType", func() {
user.SetUserType("Clown")
data, err := json.Marshal(user)
Expect(err).ToNot(HaveOccurred())
r := httptest.NewRequest(http.MethodPost, "/graph/v1.0/users?$invalid=true", bytes.NewBuffer(data))
rctx := chi.NewRouteContext()
rctx.URLParams.Add("userID", user.GetId())
r = r.WithContext(context.WithValue(revactx.ContextSetUser(ctx, currentUser), chi.RouteCtxKey, rctx))
svc.PatchUser(rr, r)
Expect(rr.Code).To(Equal(http.StatusBadRequest))
})
It("updates attributes", func() {
user.SetUserType("Member")
identityBackend.On("UpdateUser", mock.Anything, user.GetId(), mock.Anything).Return(user, nil)
user.SetUserType(("Member"))
user.SetDisplayName("New Display Name")
data, err := json.Marshal(user)
Expect(err).ToNot(HaveOccurred())
@@ -788,6 +866,7 @@ var _ = Describe("Users", func() {
updatedUser := libregraph.User{}
err = json.Unmarshal(data, &updatedUser)
Expect(err).ToNot(HaveOccurred())
Expect(updatedUser.GetUserType()).To(Equal("Member"))
Expect(updatedUser.GetDisplayName()).To(Equal("New Display Name"))
})
})