Skip to content

Commit 2b3b2cd

Browse files
authored
Merge pull request #11799 from kobergj/ProvisioningAPIExternalID
[OCISDEV-447] Use ExternalID for ProvisioningAPI
2 parents b26f754 + 2fcfa32 commit 2b3b2cd

File tree

17 files changed

+228
-20
lines changed

17 files changed

+228
-20
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Enhancement: Use externalID in Provisioning API
2+
3+
This PR adds the externalID as optional parameter to the Provisioning API that can be used as the primary identifier. It also contains a switch to enable this setting.
4+
5+
https://github.com/owncloud/ocis/pull/11799

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ require (
6565
github.com/onsi/gomega v1.38.2
6666
github.com/open-policy-agent/opa v1.6.0
6767
github.com/orcaman/concurrent-map v1.0.0
68-
github.com/owncloud/libre-graph-api-go v1.0.5-0.20250217093259-fa3804be6c27
68+
github.com/owncloud/libre-graph-api-go v1.0.5-0.20251107084958-31937a4ea3f1
6969
github.com/owncloud/reva/v2 v2.0.0-20251106102926-751223b32d48
7070
github.com/pkg/errors v0.9.1
7171
github.com/pkg/xattr v0.4.12

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -715,8 +715,8 @@ github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+
715715
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
716716
github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
717717
github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
718-
github.com/owncloud/libre-graph-api-go v1.0.5-0.20250217093259-fa3804be6c27 h1:ID8s5lGBntmrlI6TbDAjTzRyHucn3bVM2wlW+HBplv4=
719-
github.com/owncloud/libre-graph-api-go v1.0.5-0.20250217093259-fa3804be6c27/go.mod h1:+gT+x62AS9u2Farh9wE2uYmgdvTg0MQgsSI62D+xoRg=
718+
github.com/owncloud/libre-graph-api-go v1.0.5-0.20251107084958-31937a4ea3f1 h1:uW3BUPdaAhti2aP8x3Vb79vzmqAgDaWZ0yrW+4ujjU8=
719+
github.com/owncloud/libre-graph-api-go v1.0.5-0.20251107084958-31937a4ea3f1/go.mod h1:z61VMGAJRtR1nbgXWiNoCkxUXP1B3Je9rMuJbnGd+Og=
720720
github.com/owncloud/reva/v2 v2.0.0-20251106102926-751223b32d48 h1:RGnvbZNOE1ss3b0BOM8J+bPrk6prfXB0paKnWaDA/Xg=
721721
github.com/owncloud/reva/v2 v2.0.0-20251106102926-751223b32d48/go.mod h1:XenQR69s8JQ5Q4/+/vQbSg6B4+0iM6inYjNxB7SJAhI=
722722
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=

services/graph/pkg/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ type LDAP struct {
7575
UserIDIsOctetString bool `yaml:"user_id_is_octet_string" env:"OCIS_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING;GRAPH_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING" desc:"Set this to true if the defined 'ID' attribute for users is of the 'OCTETSTRING' syntax. This is required when using the 'objectGUID' attribute of Active Directory for the user ID's." introductionVersion:"pre5.0"`
7676
UserTypeAttribute string `yaml:"user_type_attribute" env:"OCIS_LDAP_USER_SCHEMA_USER_TYPE;GRAPH_LDAP_USER_TYPE_ATTRIBUTE" desc:"LDAP Attribute to distinguish between 'Member' and 'Guest' users. Default is 'ownCloudUserType'." introductionVersion:"pre5.0"`
7777
UserEnabledAttribute string `yaml:"user_enabled_attribute" env:"OCIS_LDAP_USER_ENABLED_ATTRIBUTE;GRAPH_USER_ENABLED_ATTRIBUTE" desc:"LDAP Attribute to use as a flag telling if the user is enabled or disabled." introductionVersion:"pre5.0"`
78+
ExternalIDAttribute string `yaml:"external_id_attribute" env:"OCIS_LDAP_USER_SCHEMA_EXTERNAL_ID;GRAPH_LDAP_EXTERNAL_ID_ATTRIBUTE" desc:"LDAP attribute that references the external ID of users during the provisioning process. The final ID is provided by an external identity provider. If it is not set, a default attribute will be used instead." introductionVersion:"Curie"`
7879
DisableUserMechanism string `yaml:"disable_user_mechanism" env:"OCIS_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'." introductionVersion:"pre5.0"`
7980
LdapDisabledUsersGroupDN string `yaml:"ldap_disabled_users_group_dn" env:"OCIS_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'." introductionVersion:"pre5.0"`
8081

@@ -90,6 +91,7 @@ type LDAP struct {
9091

9192
EducationResourcesEnabled bool `yaml:"education_resources_enabled" env:"GRAPH_LDAP_EDUCATION_RESOURCES_ENABLED" desc:"Enable LDAP support for managing education related resources." introductionVersion:"pre5.0"`
9293
EducationConfig LDAPEducationConfig
94+
RequireExternalID bool `yaml:"require_external_id" env:"GRAPH_LDAP_REQUIRE_EXTERNAL_ID" desc:"If enabled, the 'OCIS_LDAP_USER_SCHEMA_EXTERNAL_ID' is used as primary identifier for the provisioning API." introductionVersion:"Curie"`
9395
}
9496

9597
// LDAPEducationConfig represents the LDAP configuration for education related resources

services/graph/pkg/config/defaults/defaultconfig.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ func DefaultConfig() *config.Config {
101101
UserIDAttribute: "owncloudUUID",
102102
UserTypeAttribute: "ownCloudUserType",
103103
UserEnabledAttribute: "ownCloudUserEnabled",
104+
ExternalIDAttribute: "owncloudExternalID",
104105
DisableUserMechanism: "attribute",
105106
LdapDisabledUsersGroupDN: "cn=DisabledUsersGroup,ou=groups,o=libregraph-idm",
106107
GroupBaseDN: "ou=groups,o=libregraph-idm",

services/graph/pkg/identity/ldap.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ type LDAP struct {
7272

7373
educationConfig educationConfig
7474

75+
useExternalID bool
76+
7577
logger *log.Logger
7678
conn ldap.Client
7779
}
@@ -87,6 +89,7 @@ type userAttributeMap struct {
8789
userType string
8890
identities string
8991
lastSignIn string
92+
externalID string
9093
}
9194

9295
type ldapAttributeValues map[string][]string
@@ -122,6 +125,7 @@ func NewLDAPBackend(lc ldap.Client, config config.LDAP, logger *log.Logger) (*LD
122125
userType: config.UserTypeAttribute,
123126
identities: identitiesAttribute,
124127
lastSignIn: lastSignAttribute,
128+
externalID: config.ExternalIDAttribute,
125129
}
126130

127131
if config.GroupNameAttribute == "" || config.GroupIDAttribute == "" {
@@ -176,6 +180,7 @@ func NewLDAPBackend(lc ldap.Client, config config.LDAP, logger *log.Logger) (*LD
176180
conn: lc,
177181
writeEnabled: config.WriteEnabled,
178182
refintEnabled: config.RefintEnabled,
183+
useExternalID: config.RequireExternalID,
179184
}, nil
180185
}
181186

@@ -844,6 +849,7 @@ func (i *LDAP) createUserModelFromLDAP(e *ldap.Entry) *libregraph.User {
844849
GivenName: pointerOrNil(e.GetEqualFoldAttributeValue(i.userAttributeMap.givenName)),
845850
Surname: &surname,
846851
AccountEnabled: booleanOrNil(e.GetEqualFoldAttributeValue(i.userAttributeMap.accountEnabled)),
852+
ExternalID: pointerOrNil(e.GetEqualFoldAttributeValue(i.userAttributeMap.externalID)),
847853
}
848854

849855
userType := e.GetEqualFoldAttributeValue(i.userAttributeMap.userType)
@@ -887,6 +893,7 @@ func (i *LDAP) userToLDAPAttrValues(user libregraph.User) (map[string][]string,
887893
"objectClass": {"inetOrgPerson", "organizationalPerson", "person", "top", "ownCloudUser"},
888894
"cn": {user.GetOnPremisesSamAccountName()},
889895
i.userAttributeMap.userType: {user.GetUserType()},
896+
i.userAttributeMap.externalID: {user.GetExternalID()},
890897
}
891898

892899
if identities, ok := user.GetIdentitiesOk(); ok {
@@ -957,6 +964,7 @@ func (i *LDAP) getUserAttrTypesForSearch() []string {
957964
i.userAttributeMap.userType,
958965
i.userAttributeMap.identities,
959966
i.userAttributeMap.lastSignIn,
967+
i.userAttributeMap.externalID,
960968
}
961969
}
962970

services/graph/pkg/identity/ldap_education_school.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ func (i *LDAP) AddUsersToEducationSchool(ctx context.Context, schoolNumberOrID s
428428

429429
userEntries := make([]*ldap.Entry, 0, len(memberIDs))
430430
for _, memberID := range memberIDs {
431-
user, err := i.getEducationUserByNameOrID(memberID)
431+
user, err := i.getEducationUser(memberID)
432432
if err != nil {
433433
i.logger.Warn().Str("userid", memberID).Msg("User does not exist")
434434
return errorcode.New(errorcode.ItemNotFound, fmt.Sprintf("user '%s' not found", memberID))
@@ -472,7 +472,7 @@ func (i *LDAP) RemoveUserFromEducationSchool(ctx context.Context, schoolNumberOr
472472
}
473473

474474
schoolID := schoolEntry.GetEqualFoldAttributeValue(i.educationConfig.schoolAttributeMap.id)
475-
user, err := i.getEducationUserByNameOrID(memberID)
475+
user, err := i.getEducationUser(memberID)
476476
if err != nil {
477477
i.logger.Warn().Str("userid", memberID).Msg("User does not exist")
478478
return err

services/graph/pkg/identity/ldap_education_school_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var eduConfig = config.LDAP{
2525
UserEmailAttribute: "mail",
2626
UserNameAttribute: "uid",
2727
UserEnabledAttribute: "userEnabledAttribute",
28+
ExternalIDAttribute: "externalID",
2829
DisableUserMechanism: "attribute",
2930
UserTypeAttribute: "userTypeAttribute",
3031

services/graph/pkg/identity/ldap_education_user.go

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,13 @@ func (i *LDAP) CreateEducationUser(ctx context.Context, user libregraph.Educatio
5353
}
5454

5555
// DeleteEducationUser deletes a given education user, identified by username or id, from the backend
56-
func (i *LDAP) DeleteEducationUser(ctx context.Context, nameOrID string) error {
56+
func (i *LDAP) DeleteEducationUser(ctx context.Context, id string) error {
5757
logger := i.logger.SubloggerWithRequestID(ctx)
5858
logger.Debug().Str("backend", "ldap").Msg("DeleteEducationUser")
5959
if !i.writeEnabled {
6060
return ErrReadOnly
6161
}
62-
// TODO, implement a proper lookup for education Users here
63-
e, err := i.getEducationUserByNameOrID(nameOrID)
62+
e, err := i.getEducationUser(id)
6463
if err != nil {
6564
return err
6665
}
@@ -73,13 +72,13 @@ func (i *LDAP) DeleteEducationUser(ctx context.Context, nameOrID string) error {
7372
}
7473

7574
// UpdateEducationUser applies changes to given education user, identified by username or id
76-
func (i *LDAP) UpdateEducationUser(ctx context.Context, nameOrID string, user libregraph.EducationUser) (*libregraph.EducationUser, error) {
75+
func (i *LDAP) UpdateEducationUser(ctx context.Context, id string, user libregraph.EducationUser) (*libregraph.EducationUser, error) {
7776
logger := i.logger.SubloggerWithRequestID(ctx)
7877
logger.Debug().Str("backend", "ldap").Msg("UpdateEducationUser")
7978
if !i.writeEnabled {
8079
return nil, ErrReadOnly
8180
}
82-
e, err := i.getEducationUserByNameOrID(nameOrID)
81+
e, err := i.getEducationUser(id)
8382
if err != nil {
8483
return nil, err
8584
}
@@ -189,10 +188,10 @@ func (i *LDAP) UpdateEducationUser(ctx context.Context, nameOrID string, user li
189188
}
190189

191190
// GetEducationUser implements the EducationBackend interface for the LDAP backend.
192-
func (i *LDAP) GetEducationUser(ctx context.Context, nameOrID string) (*libregraph.EducationUser, error) {
191+
func (i *LDAP) GetEducationUser(ctx context.Context, id string) (*libregraph.EducationUser, error) {
193192
logger := i.logger.SubloggerWithRequestID(ctx)
194193
logger.Debug().Str("backend", "ldap").Msg("GetEducationUser")
195-
e, err := i.getEducationUserByNameOrID(nameOrID)
194+
e, err := i.getEducationUser(id)
196195
if err != nil {
197196
return nil, err
198197
}
@@ -257,6 +256,7 @@ func (i *LDAP) educationUserToUser(eduUser libregraph.EducationUser) *libregraph
257256
user.Mail = eduUser.Mail
258257
user.UserType = eduUser.UserType
259258
user.Identities = eduUser.Identities
259+
user.ExternalID = eduUser.ExternalID
260260

261261
return user
262262
}
@@ -272,6 +272,7 @@ func (i *LDAP) userToEducationUser(user libregraph.User, e *ldap.Entry) *libregr
272272
eduUser.Mail = user.Mail
273273
eduUser.UserType = user.UserType
274274
eduUser.Identities = user.Identities
275+
eduUser.ExternalID = user.ExternalID
275276

276277
if e != nil {
277278
// Set the education User specific Attributes from the supplied LDAP Entry
@@ -326,6 +327,7 @@ func (i *LDAP) getEducationUserAttrTypes() []string {
326327
i.userAttributeMap.accountEnabled,
327328
i.userAttributeMap.userType,
328329
i.userAttributeMap.identities,
330+
i.userAttributeMap.externalID,
329331
i.educationConfig.userAttributeMap.primaryRole,
330332
i.educationConfig.memberOfSchoolAttribute,
331333
}
@@ -341,6 +343,13 @@ func (i *LDAP) getEducationUserByDN(dn string) (*ldap.Entry, error) {
341343
return i.getEntryByDN(dn, i.getEducationUserAttrTypes(), filter)
342344
}
343345

346+
func (i *LDAP) getEducationUser(nameOrID string) (*ldap.Entry, error) {
347+
if i.useExternalID {
348+
return i.getEducationUserByExternalID(nameOrID)
349+
}
350+
return i.getEducationUserByNameOrID(nameOrID)
351+
}
352+
344353
func (i *LDAP) getEducationUserByNameOrID(nameOrID string) (*ldap.Entry, error) {
345354
return i.getEducationObjectByNameOrID(
346355
nameOrID,
@@ -353,12 +362,28 @@ func (i *LDAP) getEducationUserByNameOrID(nameOrID string) (*ldap.Entry, error)
353362
)
354363
}
355364

365+
func (i *LDAP) getEducationUserByExternalID(id string) (*ldap.Entry, error) {
366+
return i.getEducationObjectByID(
367+
id,
368+
i.userAttributeMap.externalID,
369+
i.userFilter,
370+
i.educationConfig.userObjectClass,
371+
i.userBaseDN,
372+
i.getEducationUserAttrTypes(),
373+
)
374+
}
375+
356376
func (i *LDAP) getEducationObjectByNameOrID(nameOrID, nameAttribute, idAttribute, objectFilter, objectClass, baseDN string, attributes []string) (*ldap.Entry, error) {
357377
nameOrID = ldap.EscapeFilter(nameOrID)
358378
filter := fmt.Sprintf("(|(%s=%s)(%s=%s))", nameAttribute, nameOrID, idAttribute, nameOrID)
359379
return i.getEducationObjectByFilter(filter, baseDN, objectFilter, objectClass, attributes)
360380
}
361381

382+
func (i *LDAP) getEducationObjectByID(id, idAttribute, objectFilter, objectClass, baseDN string, attributes []string) (*ldap.Entry, error) {
383+
filter := fmt.Sprintf("(%s=%s)", idAttribute, id)
384+
return i.getEducationObjectByFilter(filter, baseDN, objectFilter, objectClass, attributes)
385+
}
386+
362387
func (i *LDAP) getEducationObjectByFilter(filter, baseDN, objectFilter, objectClass string, attributes []string) (*ldap.Entry, error) {
363388
filter = fmt.Sprintf("(&%s(objectClass=%s)%s)", objectFilter, objectClass, filter)
364389
return i.searchLDAPEntryByFilter(baseDN, attributes, filter)

0 commit comments

Comments
 (0)