첫 커밋: 로컬 프로젝트 업로드
This commit is contained in:
154
baron-sso/backend/internal/handler/tenant_access_cleanup.go
Normal file
154
baron-sso/backend/internal/handler/tenant_access_cleanup.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"baron-sso-backend/internal/domain"
|
||||
"baron-sso-backend/internal/repository"
|
||||
"baron-sso-backend/internal/service"
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const tenantAccessCleanupClientPageSize = 500
|
||||
|
||||
func cleanupDeletedTenantReferences(ctx context.Context, hydra *service.HydraAdminService, consentRepo repository.ClientConsentRepository, ketoOutbox repository.KetoOutboxRepository, deletedTenantIDs []string) error {
|
||||
if hydra == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
deletedTenantSet := make(map[string]struct{}, len(deletedTenantIDs))
|
||||
for _, tenantID := range deletedTenantIDs {
|
||||
tenantID = strings.TrimSpace(tenantID)
|
||||
if tenantID == "" {
|
||||
continue
|
||||
}
|
||||
deletedTenantSet[tenantID] = struct{}{}
|
||||
}
|
||||
if len(deletedTenantSet) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for offset := 0; ; offset += tenantAccessCleanupClientPageSize {
|
||||
clients, err := hydra.ListClients(ctx, tenantAccessCleanupClientPageSize, offset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list hydra clients for tenant cleanup: %w", err)
|
||||
}
|
||||
|
||||
for _, client := range clients {
|
||||
beforeMetadata := maps.Clone(client.Metadata)
|
||||
updatedMetadata, changed, removedOwnerTenantID := pruneDeletedTenantReferences(beforeMetadata, deletedTenantSet)
|
||||
if !changed {
|
||||
continue
|
||||
}
|
||||
|
||||
updatedClient := client
|
||||
updatedClient.Metadata = updatedMetadata
|
||||
if _, err := hydra.UpdateClient(ctx, client.ClientID, updatedClient); err != nil {
|
||||
return fmt.Errorf("failed to update hydra client %s during tenant cleanup: %w", client.ClientID, err)
|
||||
}
|
||||
if removedOwnerTenantID != "" {
|
||||
if err := enqueueDeletedTenantRelyingPartyParentCleanup(ctx, ketoOutbox, client.ClientID, removedOwnerTenantID); err != nil {
|
||||
return fmt.Errorf("failed to cleanup RP parent relation for client %s during tenant cleanup: %w", client.ClientID, err)
|
||||
}
|
||||
}
|
||||
|
||||
if tenantAccessPolicyChanged(beforeMetadata, updatedMetadata) {
|
||||
if err := revokeClientConsentsForPolicyChange(ctx, hydra, consentRepo, client.ClientID); err != nil {
|
||||
return fmt.Errorf("failed to revoke consent sessions for client %s during tenant cleanup: %w", client.ClientID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(clients) < tenantAccessCleanupClientPageSize {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func pruneDeletedTenantReferences(metadata map[string]any, deletedTenantSet map[string]struct{}) (map[string]any, bool, string) {
|
||||
if len(deletedTenantSet) == 0 {
|
||||
return metadata, false, ""
|
||||
}
|
||||
|
||||
ownerTenantID := normalizeMetadataString(metadata["tenant_id"])
|
||||
_, ownerDeleted := deletedTenantSet[ownerTenantID]
|
||||
|
||||
allowedTenants := normalizeMetadataStringSlice(metadata[clientAllowedTenantsKey])
|
||||
filtered := make([]string, 0, len(allowedTenants))
|
||||
for _, tenantID := range allowedTenants {
|
||||
if _, ok := deletedTenantSet[tenantID]; ok {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, tenantID)
|
||||
}
|
||||
allowedChanged := len(filtered) != len(allowedTenants)
|
||||
|
||||
if !ownerDeleted && !allowedChanged {
|
||||
return metadata, false, ""
|
||||
}
|
||||
|
||||
updated := maps.Clone(metadata)
|
||||
if ownerDeleted {
|
||||
delete(updated, "tenant_id")
|
||||
}
|
||||
|
||||
if len(filtered) == 0 {
|
||||
delete(updated, clientAllowedTenantsKey)
|
||||
updated[clientTenantAccessRestrictedKey] = false
|
||||
return updated, true, ownerTenantID
|
||||
}
|
||||
|
||||
updated[clientAllowedTenantsKey] = uniqueSortedStrings(filtered)
|
||||
updated[clientTenantAccessRestrictedKey] = true
|
||||
return updated, true, ownerTenantID
|
||||
}
|
||||
|
||||
func enqueueDeletedTenantRelyingPartyParentCleanup(ctx context.Context, ketoOutbox repository.KetoOutboxRepository, clientID, tenantID string) error {
|
||||
if ketoOutbox == nil {
|
||||
return nil
|
||||
}
|
||||
clientID = strings.TrimSpace(clientID)
|
||||
tenantID = strings.TrimSpace(tenantID)
|
||||
if clientID == "" || tenantID == "" {
|
||||
return nil
|
||||
}
|
||||
return ketoOutbox.Create(ctx, &domain.KetoOutbox{
|
||||
Namespace: "RelyingParty",
|
||||
Object: clientID,
|
||||
Relation: "parents",
|
||||
Subject: "Tenant:" + tenantID,
|
||||
Action: domain.KetoOutboxActionDelete,
|
||||
})
|
||||
}
|
||||
|
||||
func revokeClientConsentsForPolicyChange(ctx context.Context, hydra *service.HydraAdminService, consentRepo repository.ClientConsentRepository, clientID string) error {
|
||||
if consentRepo == nil || hydra == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
subjects, err := consentRepo.ListSubjectsByClient(ctx, clientID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, subject := range subjects {
|
||||
subject = strings.TrimSpace(subject)
|
||||
if subject == "" {
|
||||
continue
|
||||
}
|
||||
if err := hydra.RevokeConsentSessions(ctx, subject, clientID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return consentRepo.DeleteByClient(ctx, clientID)
|
||||
}
|
||||
|
||||
func logTenantCleanupFailure(err error, deletedTenantIDs []string) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
slog.Error("Failed to cleanup RP tenant restrictions after tenant deletion", "tenant_ids", deletedTenantIDs, "error", err)
|
||||
}
|
||||
Reference in New Issue
Block a user