forked from baron/baron-sso
ReBAC 고도화 및 애플리케이션 관리 시스템 통합 구현
This commit is contained in:
155
backend/internal/service/relying_party_service.go
Normal file
155
backend/internal/service/relying_party_service.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"baron-sso-backend/internal/domain"
|
||||
"baron-sso-backend/internal/repository"
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
type RelyingPartyService interface {
|
||||
Create(ctx context.Context, tenantID string, client domain.HydraClient) (*domain.RelyingParty, error)
|
||||
Get(ctx context.Context, clientID string) (*domain.RelyingParty, *domain.HydraClient, error)
|
||||
List(ctx context.Context, tenantID string) ([]domain.RelyingParty, error)
|
||||
ListAll(ctx context.Context) ([]domain.RelyingParty, error)
|
||||
ListByTenantIDs(ctx context.Context, tenantIDs []string) ([]domain.RelyingParty, error)
|
||||
Update(ctx context.Context, clientID string, client domain.HydraClient) (*domain.RelyingParty, error)
|
||||
Delete(ctx context.Context, clientID string) error
|
||||
}
|
||||
|
||||
func (s *relyingPartyService) ListAll(ctx context.Context) ([]domain.RelyingParty, error) {
|
||||
return s.repo.ListAll(ctx)
|
||||
}
|
||||
|
||||
func (s *relyingPartyService) ListByTenantIDs(ctx context.Context, tenantIDs []string) ([]domain.RelyingParty, error) {
|
||||
// Simple implementation for now, repository could be optimized with IN clause
|
||||
var allRps []domain.RelyingParty
|
||||
for _, tid := range tenantIDs {
|
||||
rps, _ := s.repo.ListByTenantID(ctx, tid)
|
||||
allRps = append(allRps, rps...)
|
||||
}
|
||||
return allRps, nil
|
||||
}
|
||||
|
||||
type relyingPartyService struct {
|
||||
repo repository.RelyingPartyRepository
|
||||
hydraService *HydraAdminService
|
||||
ketoService KetoService
|
||||
}
|
||||
|
||||
func NewRelyingPartyService(
|
||||
repo repository.RelyingPartyRepository,
|
||||
hydraService *HydraAdminService,
|
||||
ketoService KetoService,
|
||||
) RelyingPartyService {
|
||||
return &relyingPartyService{
|
||||
repo: repo,
|
||||
hydraService: hydraService,
|
||||
ketoService: ketoService,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *relyingPartyService) Create(ctx context.Context, tenantID string, client domain.HydraClient) (*domain.RelyingParty, error) {
|
||||
// 1. Create Client in Hydra
|
||||
// Ensure metadata contains tenant_id for reference
|
||||
if client.Metadata == nil {
|
||||
client.Metadata = make(map[string]interface{})
|
||||
}
|
||||
client.Metadata["tenant_id"] = tenantID
|
||||
|
||||
createdClient, err := s.hydraService.CreateClient(ctx, client)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create hydra client: %w", err)
|
||||
}
|
||||
|
||||
// 2. Create Record in DB
|
||||
rp := &domain.RelyingParty{
|
||||
ClientID: createdClient.ClientID,
|
||||
TenantID: tenantID,
|
||||
Name: createdClient.ClientName,
|
||||
Description: "", // Hydra doesn't have description field standard, maybe in metadata?
|
||||
}
|
||||
|
||||
if err := s.repo.Create(ctx, rp); err != nil {
|
||||
// Rollback: Delete Hydra Client
|
||||
_ = s.hydraService.DeleteClient(ctx, createdClient.ClientID)
|
||||
return nil, fmt.Errorf("failed to create relying party in db: %w", err)
|
||||
}
|
||||
|
||||
// 3. Create Relation in Keto
|
||||
// RelyingParty:<client_id>#parent_tenant@Tenant:<tenant_id>
|
||||
err = s.ketoService.CreateRelation(ctx, "RelyingParty", createdClient.ClientID, "parent_tenant", "Tenant:"+tenantID)
|
||||
if err != nil {
|
||||
slog.Error("Failed to create keto relation for relying party", "error", err, "client_id", createdClient.ClientID)
|
||||
// We don't rollback here, but we should probably have a background job to fix this.
|
||||
// Or return error and let caller decide? For MVP, logging error is acceptable as per issue discussion (Eventual Consistency preferred).
|
||||
}
|
||||
|
||||
return rp, nil
|
||||
}
|
||||
|
||||
func (s *relyingPartyService) Get(ctx context.Context, clientID string) (*domain.RelyingParty, *domain.HydraClient, error) {
|
||||
// Get from DB
|
||||
rp, err := s.repo.FindByID(ctx, clientID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Get from Hydra
|
||||
hydraClient, err := s.hydraService.GetClient(ctx, clientID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return rp, hydraClient, nil
|
||||
}
|
||||
|
||||
func (s *relyingPartyService) List(ctx context.Context, tenantID string) ([]domain.RelyingParty, error) {
|
||||
return s.repo.ListByTenantID(ctx, tenantID)
|
||||
}
|
||||
|
||||
func (s *relyingPartyService) Update(ctx context.Context, clientID string, client domain.HydraClient) (*domain.RelyingParty, error) {
|
||||
// Update Hydra
|
||||
updatedClient, err := s.hydraService.UpdateClient(ctx, clientID, client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update DB
|
||||
rp, err := s.repo.FindByID(ctx, clientID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rp.Name = updatedClient.ClientName
|
||||
// Update other fields if necessary
|
||||
|
||||
if err := s.repo.Update(ctx, rp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rp, nil
|
||||
}
|
||||
|
||||
func (s *relyingPartyService) Delete(ctx context.Context, clientID string) error {
|
||||
// Delete from DB
|
||||
if err := s.repo.Delete(ctx, clientID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete from Hydra
|
||||
if err := s.hydraService.DeleteClient(ctx, clientID); err != nil {
|
||||
slog.Error("Failed to delete hydra client", "error", err, "client_id", clientID)
|
||||
// Proceeding...
|
||||
}
|
||||
|
||||
// Delete from Keto (Optional, but good practice to clean up)
|
||||
// We might not know the tenant ID here without querying DB first, but if DB is deleted, we might miss it.
|
||||
//Ideally, we should query DB first.
|
||||
// But `DeleteRelation` requires specific object/relation/subject.
|
||||
// If we want to delete ALL relations for this object, Keto API supports that?
|
||||
// `DeleteRelation` in our service wrapper is specific.
|
||||
// We can skip explicit Keto deletion for now as orphaned tuples are less critical than orphaned resources.
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user