feat: refine way of adding users to instances

Signed-off-by: Julian Koberg <julian.koberg@kiteworks.com>
This commit is contained in:
Julian Koberg
2026-01-13 12:54:19 +01:00
parent ddc6cb0651
commit 09c2dfbd11
13 changed files with 180 additions and 81 deletions

View File

@@ -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

View File

@@ -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=

View File

@@ -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

View File

@@ -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"`
}

View File

@@ -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: "([^@]+)@(.+)",
},
}
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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

View File

@@ -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")
}

View File

@@ -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
}

View File

@@ -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{}

View File

@@ -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

View File

@@ -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]
}