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
This commit is contained in:
committed by
Ralf Haferkamp
parent
b8c4f581fb
commit
a931e53c26
@@ -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),
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -45,6 +45,7 @@ func DefaultConfig() *config.Config {
|
||||
GroupBaseDN: "ou=groups,o=libregraph-idm",
|
||||
UserScope: "sub",
|
||||
GroupScope: "sub",
|
||||
TenantScope: "sub",
|
||||
UserSubstringFilterType: "any",
|
||||
UserFilter: "",
|
||||
GroupFilter: "",
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user