forked from baron/baron-sso
tenant 삭제 시 RP 허용 테넌트 정리 및 재유입 방지
This commit is contained in:
178
backend/internal/handler/tenant_access_cleanup_test.go
Normal file
178
backend/internal/handler/tenant_access_cleanup_test.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"baron-sso-backend/internal/domain"
|
||||
"baron-sso-backend/internal/repository"
|
||||
"baron-sso-backend/internal/service"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestPruneDeletedTenantReferences_PreservesOtherAllowedTenants(t *testing.T) {
|
||||
metadata := map[string]any{
|
||||
clientTenantAccessRestrictedKey: true,
|
||||
clientAllowedTenantsKey: []string{"keep-tenant", "deleted-tenant"},
|
||||
"tenant_id": "deleted-tenant",
|
||||
}
|
||||
|
||||
updated, changed, removedOwnerTenantID := pruneDeletedTenantReferences(metadata, map[string]struct{}{
|
||||
"deleted-tenant": {},
|
||||
})
|
||||
|
||||
require.True(t, changed)
|
||||
assert.Equal(t, "deleted-tenant", removedOwnerTenantID)
|
||||
assert.Equal(t, true, updated[clientTenantAccessRestrictedKey])
|
||||
assert.Equal(t, []string{"keep-tenant"}, updated[clientAllowedTenantsKey])
|
||||
_, exists := updated["tenant_id"]
|
||||
assert.False(t, exists)
|
||||
}
|
||||
|
||||
func TestPruneDeletedTenantReferences_DisablesRestrictionWhenLastTenantRemoved(t *testing.T) {
|
||||
metadata := map[string]any{
|
||||
clientTenantAccessRestrictedKey: true,
|
||||
clientAllowedTenantsKey: []string{"deleted-tenant"},
|
||||
"tenant_id": "deleted-tenant",
|
||||
}
|
||||
|
||||
updated, changed, removedOwnerTenantID := pruneDeletedTenantReferences(metadata, map[string]struct{}{
|
||||
"deleted-tenant": {},
|
||||
})
|
||||
|
||||
require.True(t, changed)
|
||||
assert.Equal(t, "deleted-tenant", removedOwnerTenantID)
|
||||
assert.Equal(t, false, updated[clientTenantAccessRestrictedKey])
|
||||
_, exists := updated[clientAllowedTenantsKey]
|
||||
assert.False(t, exists)
|
||||
_, exists = updated["tenant_id"]
|
||||
assert.False(t, exists)
|
||||
}
|
||||
|
||||
func TestCleanupDeletedTenantReferences_PrunesClientsAndRevokesConsents(t *testing.T) {
|
||||
var (
|
||||
mu sync.Mutex
|
||||
page0Called bool
|
||||
updated = map[string]map[string]any{}
|
||||
revokes []string
|
||||
)
|
||||
|
||||
transport := roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
switch {
|
||||
case req.Method == http.MethodGet && req.URL.Path == "/clients":
|
||||
switch req.URL.Query().Get("offset") {
|
||||
case "":
|
||||
page0Called = true
|
||||
return httpJSONAny(req, http.StatusOK, []domain.HydraClient{
|
||||
{
|
||||
ClientID: "client-keep",
|
||||
Metadata: map[string]any{
|
||||
clientTenantAccessRestrictedKey: true,
|
||||
clientAllowedTenantsKey: []string{"keep-tenant", "deleted-tenant"},
|
||||
"tenant_id": "deleted-tenant",
|
||||
},
|
||||
},
|
||||
{
|
||||
ClientID: "client-drop",
|
||||
Metadata: map[string]any{
|
||||
clientTenantAccessRestrictedKey: true,
|
||||
clientAllowedTenantsKey: []string{"deleted-tenant"},
|
||||
"tenant_id": "deleted-tenant",
|
||||
},
|
||||
},
|
||||
}), nil
|
||||
default:
|
||||
return httpResponse(req, http.StatusBadRequest, "unexpected offset"), nil
|
||||
}
|
||||
|
||||
case req.Method == http.MethodPut && strings.HasPrefix(req.URL.Path, "/clients/"):
|
||||
var client domain.HydraClient
|
||||
require.NoError(t, json.NewDecoder(req.Body).Decode(&client))
|
||||
updated[client.ClientID] = client.Metadata
|
||||
return httpJSONAny(req, http.StatusOK, client), nil
|
||||
|
||||
case req.Method == http.MethodDelete && req.URL.Path == "/oauth2/auth/sessions/consent":
|
||||
revokes = append(revokes, req.URL.Query().Get("subject")+"|"+req.URL.Query().Get("client"))
|
||||
return httpResponse(req, http.StatusNoContent, ""), nil
|
||||
|
||||
default:
|
||||
return httpResponse(req, http.StatusNotFound, "unexpected request"), nil
|
||||
}
|
||||
})
|
||||
|
||||
hydra := &service.HydraAdminService{
|
||||
AdminURL: "http://hydra.test",
|
||||
HTTPClient: &http.Client{Transport: transport},
|
||||
}
|
||||
consentRepo := &mockConsentRepo{
|
||||
consents: []domain.ClientConsent{
|
||||
{ClientID: "client-keep", Subject: "user-a"},
|
||||
{ClientID: "client-drop", Subject: "user-b"},
|
||||
},
|
||||
}
|
||||
outbox := &tenantCleanupMockKetoOutboxRepository{}
|
||||
|
||||
err := cleanupDeletedTenantReferences(context.Background(), hydra, consentRepo, outbox, []string{"deleted-tenant"})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, page0Called)
|
||||
assert.Equal(t, map[string]any{
|
||||
clientTenantAccessRestrictedKey: true,
|
||||
clientAllowedTenantsKey: []any{"keep-tenant"},
|
||||
}, updated["client-keep"])
|
||||
assert.Equal(t, map[string]any{
|
||||
clientTenantAccessRestrictedKey: false,
|
||||
}, updated["client-drop"])
|
||||
assert.ElementsMatch(t, []string{"user-a|client-keep", "user-b|client-drop"}, revokes)
|
||||
assert.Empty(t, consentRepo.consents)
|
||||
require.Len(t, outbox.entries, 2)
|
||||
assert.ElementsMatch(t, []string{"client-keep", "client-drop"}, []string{outbox.entries[0].Object, outbox.entries[1].Object})
|
||||
for _, entry := range outbox.entries {
|
||||
assert.Equal(t, "RelyingParty", entry.Namespace)
|
||||
assert.Equal(t, "parents", entry.Relation)
|
||||
assert.Equal(t, "Tenant:deleted-tenant", entry.Subject)
|
||||
assert.Equal(t, domain.KetoOutboxActionDelete, entry.Action)
|
||||
}
|
||||
}
|
||||
|
||||
type tenantCleanupMockKetoOutboxRepository struct {
|
||||
entries []domain.KetoOutbox
|
||||
}
|
||||
|
||||
var _ repository.KetoOutboxRepository = (*tenantCleanupMockKetoOutboxRepository)(nil)
|
||||
|
||||
func (m *tenantCleanupMockKetoOutboxRepository) Create(ctx context.Context, entry *domain.KetoOutbox) error {
|
||||
if entry == nil {
|
||||
return nil
|
||||
}
|
||||
m.entries = append(m.entries, *entry)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *tenantCleanupMockKetoOutboxRepository) CreateWithTx(tx *gorm.DB, entry *domain.KetoOutbox) error {
|
||||
return m.Create(context.Background(), entry)
|
||||
}
|
||||
|
||||
func (m *tenantCleanupMockKetoOutboxRepository) FindPending(ctx context.Context, limit int) ([]domain.KetoOutbox, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *tenantCleanupMockKetoOutboxRepository) ListCurrentBySubject(ctx context.Context, namespace, subject string) ([]domain.KetoOutbox, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *tenantCleanupMockKetoOutboxRepository) UpdateStatus(ctx context.Context, id string, status string, retryCount int, lastError string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *tenantCleanupMockKetoOutboxRepository) MarkProcessed(ctx context.Context, id string) error {
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user