forked from baron/baron-sso
feat: implement dynamic tenant provisioning and remove hardcoded company mappings
This commit is contained in:
@@ -22,6 +22,7 @@ type TenantService interface {
|
||||
ListManageableTenants(ctx context.Context, userID string) ([]domain.Tenant, error)
|
||||
IsDomainAllowed(ctx context.Context, domainName string) (bool, error)
|
||||
ApproveTenant(ctx context.Context, id string) error
|
||||
ProvisionTenantByDomain(ctx context.Context, domainName string) (*domain.Tenant, error) // 추가
|
||||
SetKetoService(keto KetoService) // 추가
|
||||
}
|
||||
|
||||
@@ -311,3 +312,51 @@ func (s *tenantService) IsDomainAllowed(ctx context.Context, domainName string)
|
||||
}
|
||||
return tenant != nil && tenant.Status == domain.TenantStatusActive, nil
|
||||
}
|
||||
|
||||
func (s *tenantService) ProvisionTenantByDomain(ctx context.Context, domainName string) (*domain.Tenant, error) {
|
||||
// 1. Find all COMPANY_GROUP tenants
|
||||
groups, err := s.repo.ListByType(ctx, domain.TenantTypeCompanyGroup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, g := range groups {
|
||||
// 2. Check autoProvisioning config
|
||||
rawConfig, ok := g.Config["autoProvisioning"].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
enabled, _ := rawConfig["enabled"].(bool)
|
||||
if !enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
mapping, ok := rawConfig["mappingRules"].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// 3. Find rule for this domain
|
||||
rule, ok := mapping[domainName].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
slug, _ := rule["slug"].(string)
|
||||
name, _ := rule["name"].(string)
|
||||
|
||||
if slug == "" || name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// 4. Create new sub-tenant under this group
|
||||
slog.Info("[Provisioning] Found rule for domain, creating sub-tenant", "domain", domainName, "parent", g.Slug, "newTenant", slug)
|
||||
|
||||
// Use RegisterTenant to handle DB creation and Keto Outbox sync
|
||||
// creatorID is empty as per security policy (manual delegation later)
|
||||
return s.RegisterTenant(ctx, name, slug, domain.TenantTypeCompany, "Automatically provisioned via group policy", []string{domainName}, &g.ID, "")
|
||||
}
|
||||
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
@@ -64,6 +64,14 @@ func (m *MockTenantRepoForSvc) List(ctx context.Context, limit, offset int, pare
|
||||
return args.Get(0).([]domain.Tenant), int64(args.Int(1)), args.Error(2)
|
||||
}
|
||||
|
||||
func (m *MockTenantRepoForSvc) ListByType(ctx context.Context, tenantType string) ([]domain.Tenant, error) {
|
||||
args := m.Called(ctx, tenantType)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]domain.Tenant), args.Error(1)
|
||||
}
|
||||
|
||||
type MockKetoSvcForTenant struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
@@ -158,14 +158,18 @@ func (m *MockTenantRepository) FindByDomain(ctx context.Context, domainName stri
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockTenantRepository) AddDomain(ctx context.Context, tenantID string, domainName string, verified bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockTenantRepository) List(ctx context.Context, limit, offset int, parentID string) ([]domain.Tenant, int64, error) {
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
func (m *MockTenantRepository) ListByType(ctx context.Context, tenantType string) ([]domain.Tenant, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockTenantRepository) AddDomain(ctx context.Context, tenantID string, domainName string, verified bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestUserGroupService_Create(t *testing.T) {
|
||||
mockRepo := new(MockUserGroupRepository)
|
||||
mockTenantRepo := new(MockTenantRepository)
|
||||
|
||||
Reference in New Issue
Block a user