mirror of
https://github.com/owncloud/ocis
synced 2026-04-25 17:25:21 +02:00
feat: refine way of adding users to instances
Signed-off-by: Julian Koberg <julian.koberg@kiteworks.com>
This commit is contained in:
@@ -6,3 +6,15 @@ objectClass: top
|
||||
objectClass: organizationalRole
|
||||
description: to be used for refint in empty groups
|
||||
cn: nobody
|
||||
|
||||
dn: cn=ec730a6c-1b63-4b45-b83b-9e2311afdf85,dc=owncloud,dc=com
|
||||
objectClass: top
|
||||
objectClass: organizationalRole
|
||||
description: base
|
||||
cn: ec730a6c-1b63-4b45-b83b-9e2311afdf85
|
||||
|
||||
dn: cn=8d24cb5f-6ee6-4b98-86df-c4c268dddb46,dc=owncloud,dc=com
|
||||
objectClass: top
|
||||
objectClass: organizationalRole
|
||||
description: ocm
|
||||
cn: 8d24cb5f-6ee6-4b98-86df-c4c268dddb46
|
||||
|
||||
@@ -17,7 +17,7 @@ uidNumber: 20000
|
||||
gidNumber: 30000
|
||||
homeDirectory: /home/einstein
|
||||
ownCloudUUID: 4c510ada-c86b-4815-8820-42cdf82c3d51
|
||||
owncloudMemberOf: base
|
||||
owncloudMemberOf: ec730a6c-1b63-4b45-b83b-9e2311afdf85
|
||||
ownCloudRole: ocisUser
|
||||
userPassword:: e1NTSEF9TXJEcXpFNGdKbXZxbVRVTGhvWEZ1VzJBbkV3NWFLK3J3WTIvbHc9PQ==
|
||||
|
||||
@@ -39,7 +39,7 @@ uidNumber: 20001
|
||||
gidNumber: 30000
|
||||
homeDirectory: /home/marie
|
||||
ownCloudUUID: f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c
|
||||
owncloudMemberOf: ocm
|
||||
owncloudMemberOf: 8d24cb5f-6ee6-4b98-86df-c4c268dddb46
|
||||
ownCloudRole: ocisUser
|
||||
userPassword:: e1NTSEF9UmFvQWs3TU9jRHBIUWY3bXN3MGhHNnVraFZQWnRIRlhOSUNNZEE9PQ==
|
||||
|
||||
@@ -82,7 +82,7 @@ uidNumber: 20002
|
||||
gidNumber: 30000
|
||||
homeDirectory: /home/katherine
|
||||
ownCloudUUID: 534bb038-6f9d-4093-946f-133be61fa4e7
|
||||
owncloudMemberOf: base
|
||||
owncloudMemberOf: ec730a6c-1b63-4b45-b83b-9e2311afdf85
|
||||
ownCloudRole: ocisSpaceAdmin
|
||||
userPassword:: e1NTSEF9Z05LZTRreHdmOGRUREY5eHlhSmpySTZ3MGxSVUM1d1RGcWROTVE9PQ==
|
||||
|
||||
@@ -104,7 +104,7 @@ uidNumber: 20003
|
||||
gidNumber: 30000
|
||||
homeDirectory: /home/moss
|
||||
ownCloudUUID: 058bff95-6708-4fe5-91e4-9ea3d377588b
|
||||
owncloudMemberOf: ocm
|
||||
owncloudMemberOf: 8d24cb5f-6ee6-4b98-86df-c4c268dddb46
|
||||
ownCloudRole: ocisAdmin
|
||||
userPassword:: e1NTSEF9N0hEdTRoMkFDVExFWWt4U0RtSDZVQjhmUlpKRExDZDc=
|
||||
|
||||
@@ -126,7 +126,7 @@ uidNumber: 20004
|
||||
gidNumber: 30000
|
||||
homeDirectory: /home/admin
|
||||
ownCloudUUID: ddc2004c-0977-11eb-9d3f-a793888cd0f8
|
||||
owncloudMemberOf: base
|
||||
owncloudMemberOf: ocm
|
||||
owncloudMemberOf: ec730a6c-1b63-4b45-b83b-9e2311afdf85
|
||||
owncloudMemberOf: 8d24cb5f-6ee6-4b98-86df-c4c268dddb46
|
||||
ownCloudRole: ocisAdmin
|
||||
userPassword:: e1NTSEF9UWhmaFB3dERydTUydURoWFFObDRMbzVIckI3TkI5Nmo=
|
||||
|
||||
@@ -88,11 +88,13 @@ services:
|
||||
OCIS_LDAP_INSECURE: "true"
|
||||
OCIS_LDAP_BIND_DN: "cn=admin,dc=owncloud,dc=com"
|
||||
OCIS_LDAP_BIND_PASSWORD: ${LDAP_ADMIN_PASSWORD:-admin}
|
||||
OCIS_LDAP_USER_FILTER: "(objectclass=owncloud)"
|
||||
OCIS_LDAP_GROUP_BASE_DN: "ou=groups,dc=owncloud,dc=com"
|
||||
OCIS_LDAP_GROUP_FILTER: "(objectclass=owncloud)"
|
||||
OCIS_LDAP_GROUP_OBJECTCLASS: "groupOfNames"
|
||||
OCIS_LDAP_USER_BASE_DN: "ou=users,dc=owncloud,dc=com"
|
||||
OCIS_LDAP_USER_OBJECTCLASS: "inetOrgPerson"
|
||||
OCIS_LDAP_PRECISE_SEARCH_ATTRIBUTE: "cn"
|
||||
LDAP_LOGIN_ATTRIBUTES: "uid"
|
||||
OCIS_ADMIN_USER_ID: "ddc2004c-0977-11eb-9d3f-a793888cd0f8"
|
||||
IDP_LDAP_LOGIN_ATTRIBUTE: "uid"
|
||||
@@ -102,9 +104,8 @@ services:
|
||||
GRAPH_LDAP_REFINT_ENABLED: "true" # osixia has refint enabled.
|
||||
# Multi-Instance Configuration
|
||||
OCIS_MULTI_INSTANCE_ENABLED: true
|
||||
OCIS_MULTI_INSTANCE_INSTANCEID: "base"
|
||||
OCIS_MULTI_INSTANCE_INSTANCEID: "ec730a6c-1b63-4b45-b83b-9e2311afdf85"
|
||||
# user filter required for multi-instance ocis
|
||||
OCIS_LDAP_USER_FILTER: "(&(objectclass=owncloud)(|(ownCloudMemberOf=base)(ownCloudGuestOf=base)))"
|
||||
# Workaround needed to show external users - can be removed once fixed
|
||||
OCIS_SHOW_USER_EMAIL_IN_RESULTS: true
|
||||
PROXY_ROLE_ASSIGNMENT_OIDC_CLAIM: ownCloudRole
|
||||
@@ -186,7 +187,7 @@ services:
|
||||
GRAPH_LDAP_REFINT_ENABLED: "true" # osixia has refint enabled.
|
||||
# Multi-Instance
|
||||
OCIS_MULTI_INSTANCE_ENABLED: true
|
||||
OCIS_MULTI_INSTANCE_INSTANCEID: "ocm"
|
||||
OCIS_MULTI_INSTANCE_INSTANCEID: "8d24cb5f-6ee6-4b98-86df-c4c268dddb46"
|
||||
# user filter required for multi-instance ocis
|
||||
OCIS_LDAP_USER_FILTER: "(&(objectclass=owncloud)(|(ownCloudMemberOf=ocm)(ownCloudGuestOf=ocm)))"
|
||||
# Workaround needed to show external users - can be removed once fixed
|
||||
|
||||
@@ -77,7 +77,6 @@ type LDAP struct {
|
||||
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"`
|
||||
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"`
|
||||
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:"8.0.0"`
|
||||
UserGuestAttribute string `yaml:"user_guest_attribute" env:"OCIS_LDAP_USER_GUEST_ATTRIBUTE" desc:"LDAP Attribute to signal the user is guest of the instance. Requires OCIS_MULTI_INSTANCE_ENABLED." introductionVersion:"Curie"`
|
||||
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"`
|
||||
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"`
|
||||
|
||||
@@ -94,6 +93,15 @@ type LDAP struct {
|
||||
EducationResourcesEnabled bool `yaml:"education_resources_enabled" env:"GRAPH_LDAP_EDUCATION_RESOURCES_ENABLED" desc:"Enable LDAP support for managing education related resources." introductionVersion:"pre5.0"`
|
||||
EducationConfig LDAPEducationConfig
|
||||
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:"8.0.0"`
|
||||
|
||||
// Multi-Instance Only
|
||||
UserMemberAttribute string `yaml:"user_member_attribute" env:"OCIS_LDAP_USER_MEMBER_ATTRIBUTE" desc:"LDAP Attribute to signal the user is member of an instance. Requires OCIS_MULTI_INSTANCE_ENABLED." introductionVersion:"Curie"`
|
||||
UserGuestAttribute string `yaml:"user_guest_attribute" env:"OCIS_LDAP_USER_GUEST_ATTRIBUTE" desc:"LDAP Attribute to signal the user is guest of an instance. Requires OCIS_MULTI_INSTANCE_ENABLED." introductionVersion:"Curie"`
|
||||
PreciseSearchAttribute string `yaml:"precise_search_attribute" env:"OCIS_LDAP_PRECISE_SEARCH_ATTRIBUTE" desc:"LDAP Attribute to be used for searching user on other instances. Requires OCIS_MULTI_INSTANCE_ENABLED." introductionVersion:"Curie"`
|
||||
InstanceMapperEnabled bool `yaml:"instance_mapper_enabled" env:"OCIS_LDAP_INSTANCE_MAPPER_ENABLED" desc:"The InstanceMapper allows mapping instance names (user readable) to instance ids (machine readable) based on an LDAP query. See other _INSTANCE_MAPPER_ env vars. Requires OCIS_MULTI_INSTANCE_ENABLED." introductionVersion:"Curie"`
|
||||
InstanceMapperBaseDN string `yaml:"instance_mapper_base_dn" env:"OCIS_LDAP_INSTANCE_MAPPER_BASE_DN" desc:"BaseDN of the 'instancename to instanceid' mapper in LDAP. Requires OCIS_MULTI_INSTANCE_ENABLED." introductionVersion:"Curie"`
|
||||
InstanceMapperNameAttribute string `yaml:"instance_mapper_name_attribute" env:"OCIS_LDAP_INSTANCE_MAPPER_NAME_ATTRIBUTE" desc:"LDAP Attribute of the instance name. Requires OCIS_MULTI_INSTANCE_ENABLED." introductionVersion:"Curie"`
|
||||
InstanceMapperIDAttribute string `yaml:"instance_mapper_id_attribute" env:"OCIS_LDAP_INSTANCE_MAPPER_ID_ATTRIBUTE" desc:"LDAP Attribute of the instance id. Requires OCIS_MULTI_INSTANCE_ENABLED." introductionVersion:"Curie"`
|
||||
}
|
||||
|
||||
// LDAPEducationConfig represents the LDAP configuration for education related resources
|
||||
@@ -167,6 +175,7 @@ type Validation struct {
|
||||
|
||||
// MultiInstanceConfig holds configuration for multi-instance-ocis
|
||||
type MultiInstanceConfig struct {
|
||||
Enabled bool `yaml:"enabled" env:"OCIS_MULTI_INSTANCE_ENABLED" desc:"Enable multiple instances of Infinite Scale." introductionVersion:"Curie"`
|
||||
InstanceID string `yaml:"instanceid" env:"OCIS_MULTI_INSTANCE_INSTANCEID" desc:"The unique id of this instance" introductionVersion:"Curie"`
|
||||
Enabled bool `yaml:"enabled" env:"OCIS_MULTI_INSTANCE_ENABLED" desc:"Enable multiple instances of Infinite Scale." introductionVersion:"Curie"`
|
||||
InstanceID string `yaml:"instanceid" env:"OCIS_MULTI_INSTANCE_INSTANCEID" desc:"The unique id of this instance" introductionVersion:"Curie"`
|
||||
QueryRegexp string `yaml:"query_regexp" env:"OCIS_MULTI_INSTANCE_QUERY_TEMPLATE" desc:"The regular expression extracting username and instancename from a user provided search." introductionVersion:"Curie"`
|
||||
}
|
||||
|
||||
@@ -101,7 +101,6 @@ func DefaultConfig() *config.Config {
|
||||
UserIDAttribute: "owncloudUUID",
|
||||
UserTypeAttribute: "ownCloudUserType",
|
||||
UserEnabledAttribute: "ownCloudUserEnabled",
|
||||
UserGuestAttribute: "ownCloudGuestOf",
|
||||
ExternalIDAttribute: "owncloudExternalID",
|
||||
DisableUserMechanism: "attribute",
|
||||
LdapDisabledUsersGroupDN: "cn=DisabledUsersGroup,ou=groups,o=libregraph-idm",
|
||||
@@ -113,6 +112,14 @@ func DefaultConfig() *config.Config {
|
||||
GroupMemberAttribute: "member",
|
||||
GroupIDAttribute: "owncloudUUID",
|
||||
EducationResourcesEnabled: false,
|
||||
// Multi-Instance example config. Note: Multi-Instance is disabled by default
|
||||
UserMemberAttribute: "owncloudMemberOf",
|
||||
UserGuestAttribute: "ownCloudGuestOf",
|
||||
PreciseSearchAttribute: "cn",
|
||||
InstanceMapperEnabled: true,
|
||||
InstanceMapperBaseDN: "dc=owncloud,dc=com",
|
||||
InstanceMapperNameAttribute: "description",
|
||||
InstanceMapperIDAttribute: "cn",
|
||||
},
|
||||
},
|
||||
Cache: &config.Cache{
|
||||
@@ -133,6 +140,9 @@ func DefaultConfig() *config.Config {
|
||||
Validation: config.Validation{
|
||||
MaxTagLength: 100,
|
||||
},
|
||||
MultiInstance: config.MultiInstanceConfig{
|
||||
QueryRegexp: "([^@]+)@(.+)",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ type Backend interface {
|
||||
AddUser(ctx context.Context, id string, instanceID string) (libregraph.User, error) // FIXME
|
||||
|
||||
GetUser(ctx context.Context, nameOrID string, oreq *godata.GoDataRequest) (*libregraph.User, error)
|
||||
GetPreciseUser(ctx context.Context, name string, oreq *godata.GoDataRequest) (*libregraph.User, error)
|
||||
GetPreciseUser(ctx context.Context, name string, instancename string, oreq *godata.GoDataRequest) (*libregraph.User, error)
|
||||
GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*libregraph.User, error)
|
||||
// FilterUsers returns a list of users that match the filter
|
||||
FilterUsers(ctx context.Context, oreq *godata.GoDataRequest, filter *godata.ParseNode) ([]*libregraph.User, error)
|
||||
|
||||
@@ -77,7 +77,7 @@ func (i *CS3) GetUser(ctx context.Context, userID string, _ *godata.GoDataReques
|
||||
}
|
||||
|
||||
// GetPreciseUser is not implemented
|
||||
func (i *CS3) GetPreciseUser(ctx context.Context, name string, oreq *godata.GoDataRequest) (*libregraph.User, error) {
|
||||
func (i *CS3) GetPreciseUser(ctx context.Context, name string, instancename string, oreq *godata.GoDataRequest) (*libregraph.User, error) {
|
||||
return nil, errNotImplemented
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,6 @@ type LDAP struct {
|
||||
|
||||
disableUserMechanism DisableUserMechanismType
|
||||
localUserDisableGroupDN string
|
||||
userGuestClaim string
|
||||
|
||||
groupBaseDN string
|
||||
groupCreateBaseDN string
|
||||
@@ -77,6 +76,15 @@ type LDAP struct {
|
||||
|
||||
logger *log.Logger
|
||||
conn ldap.Client
|
||||
|
||||
// multi instance only
|
||||
userMemberAttribute string
|
||||
userGuestAttribute string
|
||||
preciseSearchAttribute string
|
||||
instanceMapperEnabled bool
|
||||
instanceMapperBaseDN string
|
||||
instanceMapperNameAttribute string
|
||||
instanceMapperIDAttribute string
|
||||
}
|
||||
|
||||
type userAttributeMap struct {
|
||||
@@ -110,7 +118,7 @@ func ParseDisableMechanismType(disableMechanism string) (DisableUserMechanismTyp
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func NewLDAPBackend(lc ldap.Client, config config.LDAP, logger *log.Logger) (*LDAP, error) {
|
||||
func NewLDAPBackend(lc ldap.Client, config config.LDAP, logger *log.Logger, instanceID string) (*LDAP, error) {
|
||||
if config.UserDisplayNameAttribute == "" || config.UserIDAttribute == "" ||
|
||||
config.UserEmailAttribute == "" || config.UserNameAttribute == "" {
|
||||
return nil, errors.New("invalid user attribute mappings")
|
||||
@@ -158,31 +166,42 @@ func NewLDAPBackend(lc ldap.Client, config config.LDAP, logger *log.Logger) (*LD
|
||||
return nil, fmt.Errorf("error configuring disable user mechanism: %w", err)
|
||||
}
|
||||
|
||||
userFilter := config.UserFilter
|
||||
if iid := ldap.EscapeFilter(instanceID); iid != "" { // passing an instanceID will implictly activate multi-instance
|
||||
instanceFilter := fmt.Sprintf("(|(%s=%s)(%s=%s))", config.UserMemberAttribute, iid, config.UserGuestAttribute, iid)
|
||||
userFilter = fmt.Sprintf("(&%s%s)", instanceFilter, userFilter)
|
||||
}
|
||||
return &LDAP{
|
||||
useServerUUID: config.UseServerUUID,
|
||||
usePwModifyExOp: config.UsePasswordModExOp,
|
||||
userBaseDN: config.UserBaseDN,
|
||||
userFilter: config.UserFilter,
|
||||
userObjectClass: config.UserObjectClass,
|
||||
userIDisOctetString: config.UserIDIsOctetString,
|
||||
userScope: userScope,
|
||||
userAttributeMap: uam,
|
||||
userGuestClaim: config.UserGuestAttribute,
|
||||
groupBaseDN: config.GroupBaseDN,
|
||||
groupCreateBaseDN: config.GroupCreateBaseDN,
|
||||
groupFilter: config.GroupFilter,
|
||||
groupObjectClass: config.GroupObjectClass,
|
||||
groupIDisOctetString: config.GroupIDIsOctetString,
|
||||
groupScope: groupScope,
|
||||
groupAttributeMap: gam,
|
||||
educationConfig: educationConfig,
|
||||
disableUserMechanism: disableMechanismType,
|
||||
localUserDisableGroupDN: config.LdapDisabledUsersGroupDN,
|
||||
logger: logger,
|
||||
conn: lc,
|
||||
writeEnabled: config.WriteEnabled,
|
||||
refintEnabled: config.RefintEnabled,
|
||||
useExternalID: config.RequireExternalID,
|
||||
useServerUUID: config.UseServerUUID,
|
||||
usePwModifyExOp: config.UsePasswordModExOp,
|
||||
userBaseDN: config.UserBaseDN,
|
||||
userFilter: userFilter,
|
||||
userObjectClass: config.UserObjectClass,
|
||||
userIDisOctetString: config.UserIDIsOctetString,
|
||||
userScope: userScope,
|
||||
userAttributeMap: uam,
|
||||
groupBaseDN: config.GroupBaseDN,
|
||||
groupCreateBaseDN: config.GroupCreateBaseDN,
|
||||
groupFilter: config.GroupFilter,
|
||||
groupObjectClass: config.GroupObjectClass,
|
||||
groupIDisOctetString: config.GroupIDIsOctetString,
|
||||
groupScope: groupScope,
|
||||
groupAttributeMap: gam,
|
||||
educationConfig: educationConfig,
|
||||
disableUserMechanism: disableMechanismType,
|
||||
localUserDisableGroupDN: config.LdapDisabledUsersGroupDN,
|
||||
logger: logger,
|
||||
conn: lc,
|
||||
writeEnabled: config.WriteEnabled,
|
||||
refintEnabled: config.RefintEnabled,
|
||||
useExternalID: config.RequireExternalID,
|
||||
userMemberAttribute: config.UserMemberAttribute,
|
||||
userGuestAttribute: config.UserGuestAttribute,
|
||||
preciseSearchAttribute: config.PreciseSearchAttribute,
|
||||
instanceMapperEnabled: config.InstanceMapperEnabled,
|
||||
instanceMapperBaseDN: config.InstanceMapperBaseDN,
|
||||
instanceMapperNameAttribute: config.InstanceMapperNameAttribute,
|
||||
instanceMapperIDAttribute: config.InstanceMapperIDAttribute,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -415,18 +434,19 @@ func (i *LDAP) UpdateUser(ctx context.Context, nameOrID string, user libregraph.
|
||||
|
||||
// AddUser adds a user to the instance by setting the corresponding guest attribute.
|
||||
func (i *LDAP) AddUser(ctx context.Context, id string, instanceID string) (libregraph.User, error) {
|
||||
e, err := i.getPreciseLDAPUser(id)
|
||||
e, err := i.getPreciseLDAPUser(id, "")
|
||||
if err != nil {
|
||||
return libregraph.User{}, err
|
||||
}
|
||||
mr := ldap.ModifyRequest{DN: e.DN}
|
||||
mr.Add(i.userGuestClaim, []string{instanceID})
|
||||
mr.Add(i.userGuestAttribute, []string{instanceID})
|
||||
if err := i.conn.Modify(&mr); err != nil {
|
||||
i.logger.Error().Err(err).Msg("error adding user")
|
||||
i.logger.Error().Err(err).Str("userid", id).Str("instanceid", instanceID).Msg("error adding user")
|
||||
return libregraph.User{}, err
|
||||
}
|
||||
u, err := i.refineUser(e, nil)
|
||||
if err != nil {
|
||||
i.logger.Error().Err(err).Str("userid", id).Str("instanceid", instanceID).Interface("entry", e).Msg("error refining user")
|
||||
return libregraph.User{}, err
|
||||
}
|
||||
return *u, nil
|
||||
@@ -562,8 +582,12 @@ func (i *LDAP) getLDAPUserByNameOrID(nameOrID string) (*ldap.Entry, error) {
|
||||
return i.getLDAPUserByFilter(filter, i.userFilter)
|
||||
}
|
||||
|
||||
func (i *LDAP) getPreciseLDAPUser(uniqueID string) (*ldap.Entry, error) {
|
||||
filter := fmt.Sprintf("(|(%s=%s)(%s=%s))", i.userAttributeMap.mail, ldap.EscapeFilter(uniqueID), i.userAttributeMap.id, ldap.EscapeFilter(uniqueID))
|
||||
func (i *LDAP) getPreciseLDAPUser(uniqueID string, instanceID string) (*ldap.Entry, error) {
|
||||
uid := ldap.EscapeFilter(uniqueID)
|
||||
filter := fmt.Sprintf("(%s=%s)", i.userAttributeMap.id, uid)
|
||||
if iid := ldap.EscapeFilter(instanceID); iid != "" {
|
||||
filter = fmt.Sprintf("(&(%s=%s)(|(%s=%s)(%s=%s)))", i.preciseSearchAttribute, uid, i.userMemberAttribute, iid, i.userGuestAttribute, iid)
|
||||
}
|
||||
return i.getLDAPUserByFilter(filter, "") // no user filter for precise search
|
||||
}
|
||||
|
||||
@@ -590,12 +614,19 @@ func (i *LDAP) GetUser(ctx context.Context, nameOrID string, oreq *godata.GoData
|
||||
}
|
||||
|
||||
// GetPreciseUser gets a user using its exact email address or id. The overall user filter will be ignored.
|
||||
func (i *LDAP) GetPreciseUser(ctx context.Context, name string, oreq *godata.GoDataRequest) (*libregraph.User, error) {
|
||||
func (i *LDAP) GetPreciseUser(ctx context.Context, name string, instancename string, oreq *godata.GoDataRequest) (*libregraph.User, error) {
|
||||
logger := i.logger.SubloggerWithRequestID(ctx)
|
||||
logger.Debug().Str("backend", "ldap").Msg("GetPreciseUser")
|
||||
|
||||
e, err := i.getPreciseLDAPUser(name)
|
||||
iid, err := i.getInstanceID(instancename)
|
||||
if err != nil {
|
||||
i.logger.Error().Err(err).Str("username", name).Str("instancename", instancename).Msg("error getting instanceid")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e, err := i.getPreciseLDAPUser(name, iid)
|
||||
if err != nil {
|
||||
i.logger.Error().Err(err).Str("username", name).Str("instancename", instancename).Str("instanceid", iid).Msg("error getting precise user")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1403,6 +1434,32 @@ func (i *LDAP) oDataFilterToLDAPFilter(filter *godata.ParseNode) (string, error)
|
||||
return fmt.Sprintf("(%s<=%s)", i.userAttributeMap.lastSignIn, ldap.EscapeFilter(ldapDateTime)), nil
|
||||
}
|
||||
|
||||
func (i *LDAP) getInstanceID(instancename string) (string, error) {
|
||||
if instancename == "" || !i.instanceMapperEnabled {
|
||||
return instancename, nil
|
||||
}
|
||||
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
i.instanceMapperBaseDN,
|
||||
ldap.ScopeWholeSubtree,
|
||||
ldap.NeverDerefAliases, 1, 0, false,
|
||||
fmt.Sprintf("(%s=%s)", i.instanceMapperNameAttribute, instancename),
|
||||
[]string{i.instanceMapperIDAttribute},
|
||||
nil,
|
||||
)
|
||||
|
||||
res, err := i.conn.Search(searchRequest)
|
||||
if err != nil {
|
||||
i.logger.Error().Err(err).Str("backend", "ldap").Str("dn", i.instanceMapperBaseDN).Str("instancename", instancename).Str("attribute", i.instanceMapperIDAttribute).Msg("Search instance by name failed")
|
||||
return "", errorcode.New(errorcode.ItemNotFound, "instanceid search failed")
|
||||
}
|
||||
if len(res.Entries) == 0 || len(res.Entries[0].Attributes) == 0 || len(res.Entries[0].Attributes[0].Values) == 0 {
|
||||
i.logger.Error().Str("backend", "ldap").Str("dn", i.instanceMapperBaseDN).Str("instancename", instancename).Interface("result", res).Msg("Search instance by name returned malformed response")
|
||||
return "", errorcode.New(errorcode.ItemNotFound, "instanceid search response malformed")
|
||||
}
|
||||
return res.Entries[0].Attributes[0].Values[0], nil
|
||||
}
|
||||
|
||||
func isLastSuccessFullSignInDateTimeFilter(node *godata.ParseNode) bool {
|
||||
if node.Token.Type != godata.ExpressionTokenNav {
|
||||
return false
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
func getMockedBackend(l ldap.Client, lc config.LDAP, logger *log.Logger) (*LDAP, error) {
|
||||
return NewLDAPBackend(l, lc, logger)
|
||||
return NewLDAPBackend(l, lc, logger, "")
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -80,29 +80,29 @@ func TestNewLDAPBackend(t *testing.T) {
|
||||
|
||||
tc := lconfig
|
||||
tc.UserDisplayNameAttribute = ""
|
||||
if _, err := NewLDAPBackend(l, tc, &logger); err == nil {
|
||||
if _, err := NewLDAPBackend(l, tc, &logger, ""); err == nil {
|
||||
t.Error("Should fail with incomplete user attr config")
|
||||
}
|
||||
|
||||
tc = lconfig
|
||||
tc.GroupIDAttribute = ""
|
||||
if _, err := NewLDAPBackend(l, tc, &logger); err == nil {
|
||||
if _, err := NewLDAPBackend(l, tc, &logger, ""); err == nil {
|
||||
t.Errorf("Should fail with incomplete group config")
|
||||
}
|
||||
|
||||
tc = lconfig
|
||||
tc.UserSearchScope = ""
|
||||
if _, err := NewLDAPBackend(l, tc, &logger); err == nil {
|
||||
if _, err := NewLDAPBackend(l, tc, &logger, ""); err == nil {
|
||||
t.Errorf("Should fail with invalid user search scope")
|
||||
}
|
||||
|
||||
tc = lconfig
|
||||
tc.GroupSearchScope = ""
|
||||
if _, err := NewLDAPBackend(l, tc, &logger); err == nil {
|
||||
if _, err := NewLDAPBackend(l, tc, &logger, ""); err == nil {
|
||||
t.Errorf("Should fail with invalid group search scope")
|
||||
}
|
||||
|
||||
if _, err := NewLDAPBackend(l, lconfig, &logger); err != nil {
|
||||
if _, err := NewLDAPBackend(l, lconfig, &logger, ""); err != nil {
|
||||
t.Errorf("Should fail with invalid group search scope")
|
||||
}
|
||||
}
|
||||
@@ -147,7 +147,7 @@ func TestCreateUser(t *testing.T) {
|
||||
|
||||
c := lconfig
|
||||
c.UseServerUUID = true
|
||||
b, _ := NewLDAPBackend(l, c, &logger)
|
||||
b, _ := NewLDAPBackend(l, c, &logger, "")
|
||||
|
||||
newUser, err := b.CreateUser(context.Background(), *user)
|
||||
assert.Nil(t, err)
|
||||
@@ -164,7 +164,7 @@ func TestCreateUserModelFromLDAP(t *testing.T) {
|
||||
l := &mocks.Client{}
|
||||
logger := log.NewLogger(log.Level("debug"))
|
||||
|
||||
b, _ := NewLDAPBackend(l, lconfig, &logger)
|
||||
b, _ := NewLDAPBackend(l, lconfig, &logger, "")
|
||||
if user := b.createUserModelFromLDAP(nil); user != nil {
|
||||
t.Errorf("createUserModelFromLDAP should return on nil Entry")
|
||||
}
|
||||
|
||||
@@ -586,9 +586,9 @@ func (_c *Backend_GetGroups_Call) RunAndReturn(run func(context.Context, *godata
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetPreciseUser provides a mock function with given fields: ctx, name, oreq
|
||||
func (_m *Backend) GetPreciseUser(ctx context.Context, name string, oreq *godata.GoDataRequest) (*libregraph.User, error) {
|
||||
ret := _m.Called(ctx, name, oreq)
|
||||
// GetPreciseUser provides a mock function with given fields: ctx, name, instancename, oreq
|
||||
func (_m *Backend) GetPreciseUser(ctx context.Context, name string, instancename string, oreq *godata.GoDataRequest) (*libregraph.User, error) {
|
||||
ret := _m.Called(ctx, name, instancename, oreq)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetPreciseUser")
|
||||
@@ -596,19 +596,19 @@ func (_m *Backend) GetPreciseUser(ctx context.Context, name string, oreq *godata
|
||||
|
||||
var r0 *libregraph.User
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, *godata.GoDataRequest) (*libregraph.User, error)); ok {
|
||||
return rf(ctx, name, oreq)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, *godata.GoDataRequest) (*libregraph.User, error)); ok {
|
||||
return rf(ctx, name, instancename, oreq)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, *godata.GoDataRequest) *libregraph.User); ok {
|
||||
r0 = rf(ctx, name, oreq)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, *godata.GoDataRequest) *libregraph.User); ok {
|
||||
r0 = rf(ctx, name, instancename, oreq)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*libregraph.User)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, *godata.GoDataRequest) error); ok {
|
||||
r1 = rf(ctx, name, oreq)
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, *godata.GoDataRequest) error); ok {
|
||||
r1 = rf(ctx, name, instancename, oreq)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
@@ -624,14 +624,15 @@ type Backend_GetPreciseUser_Call struct {
|
||||
// GetPreciseUser is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - name string
|
||||
// - instancename string
|
||||
// - oreq *godata.GoDataRequest
|
||||
func (_e *Backend_Expecter) GetPreciseUser(ctx interface{}, name interface{}, oreq interface{}) *Backend_GetPreciseUser_Call {
|
||||
return &Backend_GetPreciseUser_Call{Call: _e.mock.On("GetPreciseUser", ctx, name, oreq)}
|
||||
func (_e *Backend_Expecter) GetPreciseUser(ctx interface{}, name interface{}, instancename interface{}, oreq interface{}) *Backend_GetPreciseUser_Call {
|
||||
return &Backend_GetPreciseUser_Call{Call: _e.mock.On("GetPreciseUser", ctx, name, instancename, oreq)}
|
||||
}
|
||||
|
||||
func (_c *Backend_GetPreciseUser_Call) Run(run func(ctx context.Context, name string, oreq *godata.GoDataRequest)) *Backend_GetPreciseUser_Call {
|
||||
func (_c *Backend_GetPreciseUser_Call) Run(run func(ctx context.Context, name string, instancename string, oreq *godata.GoDataRequest)) *Backend_GetPreciseUser_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(*godata.GoDataRequest))
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(*godata.GoDataRequest))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
@@ -641,7 +642,7 @@ func (_c *Backend_GetPreciseUser_Call) Return(_a0 *libregraph.User, _a1 error) *
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Backend_GetPreciseUser_Call) RunAndReturn(run func(context.Context, string, *godata.GoDataRequest) (*libregraph.User, error)) *Backend_GetPreciseUser_Call {
|
||||
func (_c *Backend_GetPreciseUser_Call) RunAndReturn(run func(context.Context, string, string, *godata.GoDataRequest) (*libregraph.User, error)) *Backend_GetPreciseUser_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ var _ = Describe("Users changing their own password", func() {
|
||||
GroupSearchScope: "sub",
|
||||
}
|
||||
loggger := log.NewLogger()
|
||||
identityBackend, err = identity.NewLDAPBackend(ldapClient, ldapConfig, &loggger)
|
||||
identityBackend, err = identity.NewLDAPBackend(ldapClient, ldapConfig, &loggger, "")
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
eventsPublisher = mocks.Publisher{}
|
||||
|
||||
@@ -473,7 +473,11 @@ func setIdentityBackends(options Options, svc *Graph) error {
|
||||
TLSConfig: tlsConf,
|
||||
},
|
||||
)
|
||||
lb, err := identity.NewLDAPBackend(conn, options.Config.Identity.LDAP, &options.Logger)
|
||||
var iid string
|
||||
if options.Config.MultiInstance.Enabled {
|
||||
iid = options.Config.MultiInstance.InstanceID
|
||||
}
|
||||
lb, err := identity.NewLDAPBackend(conn, options.Config.Identity.LDAP, &options.Logger, iid)
|
||||
if err != nil {
|
||||
options.Logger.Error().Err(err).Msg("Error initializing LDAP Backend")
|
||||
return err
|
||||
|
||||
@@ -358,12 +358,12 @@ func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) {
|
||||
logger.Debug().Interface("query", r.URL.Query()).Msg("calling get users on backend")
|
||||
|
||||
var users []*libregraph.User
|
||||
username := g.parseExternalSearch(odataReq)
|
||||
username, instancename := g.parseExternalSearch(odataReq)
|
||||
|
||||
switch {
|
||||
case username != "":
|
||||
case username != "" && instancename != "":
|
||||
users = make([]*libregraph.User, 1)
|
||||
users[0], err = g.identityBackend.GetPreciseUser(r.Context(), username, odataReq)
|
||||
users[0], err = g.identityBackend.GetPreciseUser(r.Context(), username, instancename, odataReq)
|
||||
case odataReq.Query.Filter != nil:
|
||||
users, err = g.applyUserFilter(r.Context(), odataReq, nil)
|
||||
default:
|
||||
@@ -1177,15 +1177,20 @@ func (g Graph) searchOCMAcceptedUsers(ctx context.Context, odataReq *godata.GoDa
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (g Graph) parseExternalSearch(req *godata.GoDataRequest) string {
|
||||
// with multi-instance enabled, one can share to another instance by entering `username@instancename`
|
||||
func (g Graph) parseExternalSearch(req *godata.GoDataRequest) (string, string) {
|
||||
if !g.config.MultiInstance.Enabled {
|
||||
return ""
|
||||
return "", ""
|
||||
}
|
||||
if req == nil || req.Query == nil || req.Query.Search == nil {
|
||||
return ""
|
||||
return "", ""
|
||||
}
|
||||
if !strings.Contains(req.Query.Search.RawValue, "@") {
|
||||
return ""
|
||||
unquoted := strings.Trim(req.Query.Search.RawValue, "\"")
|
||||
parts := regexp.MustCompile(g.config.MultiInstance.QueryRegexp).FindStringSubmatch(unquoted)
|
||||
// parts[0] contains complete expression
|
||||
if len(parts) != 3 {
|
||||
return "", ""
|
||||
}
|
||||
return strings.Trim(req.Query.Search.RawValue, "\"") // remove quotes too
|
||||
|
||||
return parts[1], parts[2]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user