From a931e53c26e7067a27c4dd5dd325c9caaca7ca16 Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Thu, 2 Apr 2026 16:58:57 +0200 Subject: [PATCH] proxy: Allow mapping from an external tenant id to the internal id When the tenant id coming in via the OIDC claims doesn't match the tenant id on the provisioned user, a mapping can be configured and resolved via the reva TenantAPI service (now started as part of the "users" service). Closes: #2310 --- services/proxy/pkg/command/server.go | 3 + services/proxy/pkg/config/config.go | 1 + .../proxy/pkg/middleware/account_resolver.go | 96 ++++++++---- .../pkg/middleware/account_resolver_test.go | 138 +++++++++++++++++- services/proxy/pkg/middleware/options.go | 23 ++- services/users/pkg/config/config.go | 11 ++ .../pkg/config/defaults/defaultconfig.go | 1 + services/users/pkg/revaconfig/config.go | 9 ++ 8 files changed, 252 insertions(+), 30 deletions(-) diff --git a/services/proxy/pkg/command/server.go b/services/proxy/pkg/command/server.go index ff77d25ed..2debb13dd 100644 --- a/services/proxy/pkg/command/server.go +++ b/services/proxy/pkg/command/server.go @@ -367,6 +367,9 @@ func loadMiddlewares(logger log.Logger, cfg *config.Config, middleware.UserOIDCClaim(cfg.UserOIDCClaim), middleware.UserCS3Claim(cfg.UserCS3Claim), middleware.TenantOIDCClaim(cfg.TenantOIDCClaim), + middleware.TenantIDMappingEnabled(cfg.TenantIDMappingEnabled), + middleware.ServiceAccount(cfg.ServiceAccount), + middleware.WithRevaGatewaySelector(gatewaySelector), middleware.AutoprovisionAccounts(cfg.AutoprovisionAccounts), middleware.MultiTenantEnabled(cfg.Commons.MultiTenantEnabled), middleware.EventsPublisher(publisher), diff --git a/services/proxy/pkg/config/config.go b/services/proxy/pkg/config/config.go index 1b82bf7a7..8fd74e2db 100644 --- a/services/proxy/pkg/config/config.go +++ b/services/proxy/pkg/config/config.go @@ -35,6 +35,7 @@ type Config struct { UserOIDCClaim string `yaml:"user_oidc_claim" env:"PROXY_USER_OIDC_CLAIM" desc:"The name of an OpenID Connect claim that is used for resolving users with the account backend. The value of the claim must hold a per user unique, stable and non re-assignable identifier. The availability of claims depends on your Identity Provider. There are common claims available for most Identity providers like 'email' or 'preferred_username' but you can also add your own claim." introductionVersion:"1.0.0"` UserCS3Claim string `yaml:"user_cs3_claim" env:"PROXY_USER_CS3_CLAIM" desc:"The name of a CS3 user attribute (claim) that should be mapped to the 'user_oidc_claim'. Supported values are 'username', 'mail' and 'userid'." introductionVersion:"1.0.0"` TenantOIDCClaim string `yaml:"tenant_oidc_claim" env:"PROXY_TENANT_OIDC_CLAIM" desc:"JMESPath expression to extract the tenant ID from the OIDC token claims. When set, the extracted value is verified against the tenant ID returned by the user backend, rejecting requests where they do not match. Only relevant when multi-tenancy is enabled." introductionVersion:"%%NEXT%%"` + TenantIDMappingEnabled bool `yaml:"tenant_id_mapping_enabled" env:"PROXY_TENANT_ID_MAPPING_ENABLED" desc:"When set to 'true', the proxy will resolve the internal tenant ID from the external tenant ID provided in the OIDC claims by calling the TenantAPI before verifying the tenant. Use this when the external tenant ID in the OIDC token differs from the internal tenant ID stored on the user. Requires 'tenant_oidc_claim' to be set. Only relevant when multi-tenancy is enabled." introductionVersion:"%%NEXT%%"` MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OC_MACHINE_AUTH_API_KEY;PROXY_MACHINE_AUTH_API_KEY" desc:"Machine auth API key used to validate internal requests necessary to access resources from other services." introductionVersion:"1.0.0" mask:"password"` AutoprovisionAccounts bool `yaml:"auto_provision_accounts" env:"PROXY_AUTOPROVISION_ACCOUNTS" desc:"Set this to 'true' to automatically provision users that do not yet exist in the users service on-demand upon first sign-in. To use this a write-enabled libregraph user backend needs to be setup an running." introductionVersion:"1.0.0"` AutoProvisionClaims AutoProvisionClaims `yaml:"auto_provision_claims"` diff --git a/services/proxy/pkg/middleware/account_resolver.go b/services/proxy/pkg/middleware/account_resolver.go index 68c388bc2..33702ad63 100644 --- a/services/proxy/pkg/middleware/account_resolver.go +++ b/services/proxy/pkg/middleware/account_resolver.go @@ -1,12 +1,16 @@ package middleware import ( + "context" "errors" "fmt" "net/http" "time" "github.com/jellydator/ttlcache/v3" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + tenantpb "github.com/cs3org/go-cs3apis/cs3/identity/tenant/v1beta1" + rpcpb "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" "github.com/opencloud-eu/opencloud/services/proxy/pkg/router" "github.com/opencloud-eu/opencloud/services/proxy/pkg/user/backend" "github.com/opencloud-eu/opencloud/services/proxy/pkg/userroles" @@ -16,8 +20,10 @@ import ( cs3user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/pkg/oidc" + "github.com/opencloud-eu/opencloud/services/proxy/pkg/config" revactx "github.com/opencloud-eu/reva/v2/pkg/ctx" "github.com/opencloud-eu/reva/v2/pkg/events" + "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" "github.com/opencloud-eu/reva/v2/pkg/utils" ) @@ -36,33 +42,39 @@ func AccountResolver(optionSetters ...Option) func(next http.Handler) http.Handl return func(next http.Handler) http.Handler { return &accountResolver{ - next: next, - logger: logger, - tracer: tracer, - userProvider: options.UserProvider, - userOIDCClaim: options.UserOIDCClaim, - userCS3Claim: options.UserCS3Claim, - tenantOIDCClaim: options.TenantOIDCClaim, - userRoleAssigner: options.UserRoleAssigner, - autoProvisionAccounts: options.AutoprovisionAccounts, - multiTenantEnabled: options.MultiTenantEnabled, - lastGroupSyncCache: lastGroupSyncCache, - eventsPublisher: options.EventsPublisher, + next: next, + logger: logger, + tracer: tracer, + userProvider: options.UserProvider, + userOIDCClaim: options.UserOIDCClaim, + userCS3Claim: options.UserCS3Claim, + tenantOIDCClaim: options.TenantOIDCClaim, + tenantIDMappingEnabled: options.TenantIDMappingEnabled, + gatewaySelector: options.RevaGatewaySelector, + serviceAccount: options.ServiceAccount, + userRoleAssigner: options.UserRoleAssigner, + autoProvisionAccounts: options.AutoprovisionAccounts, + multiTenantEnabled: options.MultiTenantEnabled, + lastGroupSyncCache: lastGroupSyncCache, + eventsPublisher: options.EventsPublisher, } } } type accountResolver struct { - next http.Handler - logger log.Logger - tracer trace.Tracer - userProvider backend.UserBackend - userRoleAssigner userroles.UserRoleAssigner - autoProvisionAccounts bool - multiTenantEnabled bool - userOIDCClaim string - userCS3Claim string - tenantOIDCClaim string + next http.Handler + logger log.Logger + tracer trace.Tracer + userProvider backend.UserBackend + userRoleAssigner userroles.UserRoleAssigner + autoProvisionAccounts bool + multiTenantEnabled bool + tenantIDMappingEnabled bool + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] + serviceAccount config.ServiceAccount + userOIDCClaim string + userCS3Claim string + tenantOIDCClaim string // lastGroupSyncCache is used to keep track of when the last sync of group // memberships was done for a specific user. This is used to trigger a sync // with every single request. @@ -173,7 +185,7 @@ func (m accountResolver) ServeHTTP(w http.ResponseWriter, req *http.Request) { // if a tenant claim is configured, verify it matches the tenant id on the resolved user if m.tenantOIDCClaim != "" { - if err = m.verifyTenantClaim(user.GetId().GetTenantId(), claims); err != nil { + if err = m.verifyTenantClaim(req.Context(), user.GetId().GetTenantId(), claims); err != nil { m.logger.Error().Err(err).Str("userid", user.GetId().GetOpaqueId()).Msg("Tenant claim mismatch") w.WriteHeader(http.StatusUnauthorized) return @@ -260,13 +272,47 @@ func (m accountResolver) ServeHTTP(w http.ResponseWriter, req *http.Request) { m.next.ServeHTTP(w, req) } -func (m accountResolver) verifyTenantClaim(userTenantID string, claims map[string]interface{}) error { +func (m accountResolver) verifyTenantClaim(ctx context.Context, userTenantID string, claims map[string]interface{}) error { claimTenantID, err := readStringClaim(m.tenantOIDCClaim, claims) if err != nil { return fmt.Errorf("could not read tenant claim: %w", err) } - if claimTenantID != userTenantID { + + internalTenantID := claimTenantID + if m.tenantIDMappingEnabled { + internalTenantID, err = m.resolveInternalTenantID(ctx, claimTenantID) + if err != nil { + return fmt.Errorf("could not resolve internal tenant id for external tenant id %q: %w", claimTenantID, err) + } + } + + if internalTenantID != userTenantID { return fmt.Errorf("tenant id from claim %q does not match user tenant id %q", claimTenantID, userTenantID) } return nil } + +// resolveInternalTenantID calls the gateway's TenantAPI to map an external tenant ID (as it +// appears in OIDC claims) to the internal tenant ID stored on the user object. +// The call is authenticated using the configured service account. +func (m accountResolver) resolveInternalTenantID(ctx context.Context, externalTenantID string) (string, error) { + gwc, err := m.gatewaySelector.Next() + if err != nil { + return "", fmt.Errorf("could not get gateway client: %w", err) + } + authCtx, err := utils.GetServiceUserContextWithContext(ctx, gwc, m.serviceAccount.ServiceAccountID, m.serviceAccount.ServiceAccountSecret) + if err != nil { + return "", fmt.Errorf("could not authenticate service account: %w", err) + } + resp, err := gwc.GetTenantByClaim(authCtx, &tenantpb.GetTenantByClaimRequest{ + Claim: "externalid", + Value: externalTenantID, + }) + if err != nil { + return "", err + } + if resp.GetStatus().GetCode() != rpcpb.Code_CODE_OK { + return "", fmt.Errorf("TenantAPI returned status %s: %s", resp.GetStatus().GetCode(), resp.GetStatus().GetMessage()) + } + return resp.GetTenant().GetId(), nil +} diff --git a/services/proxy/pkg/middleware/account_resolver_test.go b/services/proxy/pkg/middleware/account_resolver_test.go index 79bb6cca0..bf9b0aa71 100644 --- a/services/proxy/pkg/middleware/account_resolver_test.go +++ b/services/proxy/pkg/middleware/account_resolver_test.go @@ -6,25 +6,35 @@ import ( "net/http/httptest" "testing" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + tenantpb "github.com/cs3org/go-cs3apis/cs3/identity/tenant/v1beta1" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + rpcpb "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/pkg/oidc" + "github.com/opencloud-eu/opencloud/services/proxy/pkg/config" "github.com/opencloud-eu/opencloud/services/proxy/pkg/router" "github.com/opencloud-eu/opencloud/services/proxy/pkg/user/backend" "github.com/opencloud-eu/opencloud/services/proxy/pkg/user/backend/mocks" userRoleMocks "github.com/opencloud-eu/opencloud/services/proxy/pkg/userroles/mocks" "github.com/opencloud-eu/reva/v2/pkg/auth/scope" revactx "github.com/opencloud-eu/reva/v2/pkg/ctx" + "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" "github.com/opencloud-eu/reva/v2/pkg/token/manager/jwt" + cs3mocks "github.com/opencloud-eu/reva/v2/tests/cs3mocks/mocks" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "google.golang.org/grpc" ) const ( - testIdP = "https://idx.example.com" - testTenantA = "tenant-a" - testTenantB = "tenant-b" - testJWTSecret = "change-me" + testIdP = "https://idx.example.com" + testTenantA = "tenant-a" + testTenantB = "tenant-b" + testJWTSecret = "change-me" + testSvcAccountID = "svc-account-id" + testSvcAccountSecret = "svc-account-secret" + testSvcAccountToken = "svc-account-token" ) func TestTokenIsAddedWithMailClaim(t *testing.T) { @@ -336,3 +346,123 @@ func mockRequest(claims map[string]interface{}) (*http.Request, *httptest.Respon type mockHandler struct{} func (m mockHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {} + +func TestTenantIDMapping(t *testing.T) { + const ( + externalTenantID = "external-tenant-x" + internalTenantID = testTenantA + ) + + user := &userv1beta1.User{ + Id: &userv1beta1.UserId{ + Idp: testIdP, + OpaqueId: "123", + TenantId: internalTenantID, + }, + Username: "foo", + } + + tokenManager, _ := jwt.New(map[string]interface{}{"secret": testJWTSecret, "expires": int64(60)}) + s, _ := scope.AddOwnerScope(nil) + token, _ := tokenManager.MintToken(context.Background(), user, s) + + newSUT := func(t *testing.T, gatewayClient gateway.GatewayAPIClient) http.Handler { + t.Helper() + gatewaySelector := pool.GetSelector[gateway.GatewayAPIClient]( + "GatewaySelector", + "eu.opencloud.api.gateway", + func(cc grpc.ClientConnInterface) gateway.GatewayAPIClient { + return gatewayClient + }, + ) + t.Cleanup(func() { pool.RemoveSelector("GatewaySelector" + "eu.opencloud.api.gateway") }) + + ub := mocks.UserBackend{} + ub.On("GetUserByClaims", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(user, token, nil) + ra := userRoleMocks.UserRoleAssigner{} + ra.On("UpdateUserRoleAssignment", mock.Anything, mock.Anything, mock.Anything).Return(user, nil) + + return AccountResolver( + Logger(log.NewLogger()), + UserProvider(&ub), + UserRoleAssigner(&ra), + UserOIDCClaim(oidc.PreferredUsername), + UserCS3Claim("username"), + TenantOIDCClaim("tenant_id"), + MultiTenantEnabled(true), + TenantIDMappingEnabled(true), + ServiceAccount(config.ServiceAccount{ + ServiceAccountID: testSvcAccountID, + ServiceAccountSecret: testSvcAccountSecret, + }), + WithRevaGatewaySelector(gatewaySelector), + )(mockHandler{}) + } + + tests := []struct { + name string + tenantResponse *tenantpb.GetTenantByClaimResponse + wantToken bool + wantStatusCode int + }{ + { + name: "token added when external tenant maps to user internal tenant", + tenantResponse: &tenantpb.GetTenantByClaimResponse{ + Status: &rpcpb.Status{Code: rpcpb.Code_CODE_OK}, + Tenant: &tenantpb.Tenant{Id: internalTenantID, ExternalId: externalTenantID}, + }, + wantToken: true, + wantStatusCode: http.StatusOK, + }, + { + name: "unauthorized when external tenant maps to a different internal tenant", + tenantResponse: &tenantpb.GetTenantByClaimResponse{ + Status: &rpcpb.Status{Code: rpcpb.Code_CODE_OK}, + Tenant: &tenantpb.Tenant{Id: testTenantB, ExternalId: externalTenantID}, + }, + wantToken: false, + wantStatusCode: http.StatusUnauthorized, + }, + { + name: "unauthorized when external tenant is not found", + tenantResponse: &tenantpb.GetTenantByClaimResponse{ + Status: &rpcpb.Status{Code: rpcpb.Code_CODE_NOT_FOUND, Message: "not found"}, + }, + wantToken: false, + wantStatusCode: http.StatusUnauthorized, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + gwc := &cs3mocks.GatewayAPIClient{} + gwc.On("Authenticate", mock.Anything, &gateway.AuthenticateRequest{ + Type: "serviceaccounts", + ClientId: testSvcAccountID, + ClientSecret: testSvcAccountSecret, + }).Return(&gateway.AuthenticateResponse{ + Status: &rpcpb.Status{Code: rpcpb.Code_CODE_OK}, + Token: testSvcAccountToken, + }, nil) + gwc.On("GetTenantByClaim", mock.Anything, &tenantpb.GetTenantByClaimRequest{ + Claim: "externalid", + Value: externalTenantID, + }).Return(tc.tenantResponse, nil) + + req, rw := mockRequest(map[string]interface{}{ + oidc.Iss: testIdP, + oidc.PreferredUsername: "foo", + "tenant_id": externalTenantID, + }) + newSUT(t, gwc).ServeHTTP(rw, req) + + if tc.wantToken { + assert.NotEmpty(t, req.Header.Get(revactx.TokenHeader)) + } else { + assert.Empty(t, req.Header.Get(revactx.TokenHeader)) + } + assert.Equal(t, tc.wantStatusCode, rw.Code) + gwc.AssertExpectations(t) + }) + } +} diff --git a/services/proxy/pkg/middleware/options.go b/services/proxy/pkg/middleware/options.go index 9d434bbbb..7e57d13ba 100644 --- a/services/proxy/pkg/middleware/options.go +++ b/services/proxy/pkg/middleware/options.go @@ -76,7 +76,12 @@ type Options struct { SkipUserInfo bool // MultiTenantEnabled causes the account resolve middleware to reject users that don't have a tenant id assigned MultiTenantEnabled bool - EventsPublisher events.Publisher + // TenantIDMappingEnabled causes the account resolver to resolve the internal tenant ID from the external + // tenant ID in the OIDC claims via the gateway's TenantAPI before comparing it to the user's stored tenant ID. + TenantIDMappingEnabled bool + // ServiceAccount holds credentials used to authenticate internal service calls (e.g. TenantAPI lookups). + ServiceAccount config.ServiceAccount + EventsPublisher events.Publisher } // newOptions initializes the available default options. @@ -258,6 +263,22 @@ func MultiTenantEnabled(val bool) Option { } } +// ServiceAccount sets the service account credentials used for authenticated internal calls. +func ServiceAccount(sa config.ServiceAccount) Option { + return func(o *Options) { + o.ServiceAccount = sa + } +} + +// TenantIDMappingEnabled sets the TenantIDMappingEnabled flag. +// When true, the account resolver resolves the internal tenant ID from the external tenant ID +// provided in the OIDC claims by calling the gateway's TenantAPI, instead of comparing directly. +func TenantIDMappingEnabled(val bool) Option { + return func(o *Options) { + o.TenantIDMappingEnabled = val + } +} + // EventsPublisher sets the events publisher. func EventsPublisher(ep events.Publisher) Option { return func(o *Options) { diff --git a/services/users/pkg/config/config.go b/services/users/pkg/config/config.go index f9b667c8c..9cc1954eb 100644 --- a/services/users/pkg/config/config.go +++ b/services/users/pkg/config/config.go @@ -62,6 +62,11 @@ type LDAPDriver struct { BindPassword string `yaml:"bind_password" env:"OC_LDAP_BIND_PASSWORD;USERS_LDAP_BIND_PASSWORD" desc:"Password to use for authenticating the 'bind_dn'." introductionVersion:"1.0.0"` UserBaseDN string `yaml:"user_base_dn" env:"OC_LDAP_USER_BASE_DN;USERS_LDAP_USER_BASE_DN" desc:"Search base DN for looking up LDAP users." introductionVersion:"1.0.0"` GroupBaseDN string `yaml:"group_base_dn" env:"OC_LDAP_GROUP_BASE_DN;USERS_LDAP_GROUP_BASE_DN" desc:"Search base DN for looking up LDAP groups." introductionVersion:"1.0.0"` + TenantBaseDN string `yaml:"tenant_base_dn" env:"OC_LDAP_TENANT_BASE_DN;USERS_LDAP_TENANT_BASE_DN" desc:"Search base DN for looking up LDAP tenants. Only relevant in multi-tenant setups." introductionVersion:"%%NEXT%%"` + TenantScope string `yaml:"tenant_scope" env:"OC_LDAP_TENANT_SCOPE;USERS_LDAP_TENANT_SCOPE" desc:"LDAP search scope to use when looking up tenants. Supported values are 'base', 'one' and 'sub'. Only relevant in multi-tenant setups." introductionVersion:"%%NEXT%%"` + TenantFilter string `yaml:"tenant_filter" env:"OC_LDAP_TENANT_FILTER;USERS_LDAP_TENANT_FILTER" desc:"LDAP filter to add to the default filters for tenant searches. Only relevant in multi-tenant setups." introductionVersion:"%%NEXT%%"` + TenantObjectClass string `yaml:"tenant_object_class" env:"OC_LDAP_TENANT_OBJECTCLASS;USERS_LDAP_TENANT_OBJECTCLASS" desc:"The object class to use for tenants in the default tenant search filter. Only relevant in multi-tenant setups." introductionVersion:"%%NEXT%%"` + TenantSchema LDAPTenantSchema `yaml:"tenant_schema"` UserScope string `yaml:"user_scope" env:"OC_LDAP_USER_SCOPE;USERS_LDAP_USER_SCOPE" desc:"LDAP search scope to use when looking up users. Supported values are 'base', 'one' and 'sub'." introductionVersion:"1.0.0"` GroupScope string `yaml:"group_scope" env:"OC_LDAP_GROUP_SCOPE;USERS_LDAP_GROUP_SCOPE" desc:"LDAP search scope to use when looking up groups. Supported values are 'base', 'one' and 'sub'." introductionVersion:"1.0.0"` UserSubstringFilterType string `yaml:"user_substring_filter_type" env:"LDAP_USER_SUBSTRING_FILTER_TYPE;USERS_LDAP_USER_SUBSTRING_FILTER_TYPE" desc:"Type of substring search filter to use for substring searches for users. Possible values: 'initial' for doing prefix only searches, 'final' for doing suffix only searches or 'any' for doing full substring searches" introductionVersion:"1.0.0"` @@ -96,6 +101,12 @@ type LDAPGroupSchema struct { Member string `yaml:"member" env:"OC_LDAP_GROUP_SCHEMA_MEMBER;USERS_LDAP_GROUP_SCHEMA_MEMBER" desc:"LDAP Attribute that is used for group members." introductionVersion:"1.0.0"` } +type LDAPTenantSchema struct { + ID string `yaml:"id" env:"OC_LDAP_TENANT_SCHEMA_ID;USERS_LDAP_TENANT_SCHEMA_ID" desc:"LDAP Attribute to use as the unique internal ID for tenants. Only relevant in multi-tenant setups." introductionVersion:"%%NEXT%%"` + ExternalID string `yaml:"external_id" env:"OC_LDAP_TENANT_SCHEMA_EXTERNAL_ID;USERS_LDAP_TENANT_SCHEMA_EXTERNAL_ID" desc:"LDAP Attribute that holds the external tenant ID as it appears in OIDC claims. Only relevant in multi-tenant setups." introductionVersion:"%%NEXT%%"` + Name string `yaml:"name" env:"OC_LDAP_TENANT_SCHEMA_NAME;USERS_LDAP_TENANT_SCHEMA_NAME" desc:"LDAP Attribute to use for the human-readable name of a tenant. Only relevant in multi-tenant setups." introductionVersion:"%%NEXT%%"` +} + type OwnCloudSQLDriver struct { DBUsername string `yaml:"db_username" env:"USERS_OWNCLOUDSQL_DB_USERNAME" desc:"Database user to use for authenticating with the owncloud database." introductionVersion:"1.0.0"` DBPassword string `yaml:"db_password" env:"USERS_OWNCLOUDSQL_DB_PASSWORD" desc:"Password for the database user." introductionVersion:"1.0.0"` diff --git a/services/users/pkg/config/defaults/defaultconfig.go b/services/users/pkg/config/defaults/defaultconfig.go index a604080de..c2f3d7be0 100644 --- a/services/users/pkg/config/defaults/defaultconfig.go +++ b/services/users/pkg/config/defaults/defaultconfig.go @@ -45,6 +45,7 @@ func DefaultConfig() *config.Config { GroupBaseDN: "ou=groups,o=libregraph-idm", UserScope: "sub", GroupScope: "sub", + TenantScope: "sub", UserSubstringFilterType: "any", UserFilter: "", GroupFilter: "", diff --git a/services/users/pkg/revaconfig/config.go b/services/users/pkg/revaconfig/config.go index 98b690d02..d8bc90b65 100644 --- a/services/users/pkg/revaconfig/config.go +++ b/services/users/pkg/revaconfig/config.go @@ -66,13 +66,17 @@ func ldapConfigFromString(cfg config.LDAPDriver) map[string]interface{} { "bind_password": cfg.BindPassword, "user_base_dn": cfg.UserBaseDN, "group_base_dn": cfg.GroupBaseDN, + "tenant_base_dn": cfg.TenantBaseDN, "user_scope": cfg.UserScope, "group_scope": cfg.GroupScope, + "tenant_search_scope": cfg.TenantScope, "user_substring_filter_type": cfg.UserSubstringFilterType, "user_filter": cfg.UserFilter, "group_filter": cfg.GroupFilter, + "tenant_filter": cfg.TenantFilter, "user_objectclass": cfg.UserObjectClass, "group_objectclass": cfg.GroupObjectClass, + "tenant_objectclass": cfg.TenantObjectClass, "user_disable_mechanism": cfg.DisableUserMechanism, "user_enabled_property": cfg.UserSchema.Enabled, "user_type_property": cfg.UserTypeAttribute, @@ -94,5 +98,10 @@ func ldapConfigFromString(cfg config.LDAPDriver) map[string]interface{} { "groupName": cfg.GroupSchema.Groupname, "member": cfg.GroupSchema.Member, }, + "tenant_schema": map[string]interface{}{ + "id": cfg.TenantSchema.ID, + "externalId": cfg.TenantSchema.ExternalID, + "name": cfg.TenantSchema.Name, + }, } }