1
0
forked from baron/baron-sso
Files
baron-sso/backend/internal/service/relying_party_service.go

172 lines
4.9 KiB
Go

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
}
type relyingPartyService struct {
hydraService *HydraAdminService
ketoService KetoService
outboxRepo repository.KetoOutboxRepository
}
func NewRelyingPartyService(
hydraService *HydraAdminService,
ketoService KetoService,
outboxRepo repository.KetoOutboxRepository,
) RelyingPartyService {
return &relyingPartyService{
hydraService: hydraService,
ketoService: ketoService,
outboxRepo: outboxRepo,
}
}
func (s *relyingPartyService) Create(ctx context.Context, tenantID string, client domain.HydraClient) (*domain.RelyingParty, error) {
// 1. Create Client in Hydra
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 Relation in Keto via Outbox
// RelyingParty:<client_id>#parents@Tenant:<tenant_id>
if s.outboxRepo != nil {
_ = s.outboxRepo.Create(ctx, &domain.KetoOutbox{
Namespace: "RelyingParty",
Object: createdClient.ClientID,
Relation: "parents",
Subject: "Tenant:" + tenantID,
Action: domain.KetoOutboxActionCreate,
})
}
return s.mapHydraToDomain(createdClient), nil
}
func (s *relyingPartyService) Get(ctx context.Context, clientID string) (*domain.RelyingParty, *domain.HydraClient, error) {
hydraClient, err := s.hydraService.GetClient(ctx, clientID)
if err != nil {
return nil, nil, err
}
return s.mapHydraToDomain(hydraClient), hydraClient, nil
}
func (s *relyingPartyService) List(ctx context.Context, tenantID string) ([]domain.RelyingParty, error) {
// 1. Fetch ClientIDs from Keto
// Relation tuple: RelyingParty:cid # parents @ Tenant:tid
tuples, err := s.ketoService.ListRelations(ctx, "RelyingParty", "", "parents", "Tenant:"+tenantID)
if err != nil {
return nil, err
}
var rps []domain.RelyingParty
for _, t := range tuples {
clientID := t.Object
client, err := s.hydraService.GetClient(ctx, clientID)
if err != nil {
slog.Warn("Failed to fetch relying party from hydra", "client_id", clientID, "error", err)
continue
}
if rp := s.mapHydraToDomain(client); rp != nil {
rps = append(rps, *rp)
}
}
return rps, nil
}
func (s *relyingPartyService) ListAll(ctx context.Context) ([]domain.RelyingParty, error) {
return nil, fmt.Errorf("ListAll not implemented in SSOT mode yet")
}
func (s *relyingPartyService) ListByTenantIDs(ctx context.Context, tenantIDs []string) ([]domain.RelyingParty, error) {
var allRps []domain.RelyingParty
for _, tid := range tenantIDs {
rps, err := s.List(ctx, tid)
if err == nil {
allRps = append(allRps, rps...)
}
}
return allRps, nil
}
func (s *relyingPartyService) Update(ctx context.Context, clientID string, client domain.HydraClient) (*domain.RelyingParty, error) {
updatedClient, err := s.hydraService.UpdateClient(ctx, clientID, client)
if err != nil {
return nil, err
}
return s.mapHydraToDomain(updatedClient), nil
}
func (s *relyingPartyService) Delete(ctx context.Context, clientID string) error {
// 1. Get client to find tenantID (for Keto cleanup)
client, err := s.hydraService.GetClient(ctx, clientID)
if err != nil {
return err
}
tenantID := ""
if client.Metadata != nil {
if tid, ok := client.Metadata["tenant_id"].(string); ok {
tenantID = tid
}
}
// 2. Delete from Hydra
if err := s.hydraService.DeleteClient(ctx, clientID); err != nil {
return err
}
// 3. Delete from Keto via Outbox
if s.outboxRepo != nil && tenantID != "" {
_ = s.outboxRepo.Create(ctx, &domain.KetoOutbox{
Namespace: "RelyingParty",
Object: clientID,
Relation: "parents",
Subject: "Tenant:" + tenantID,
Action: domain.KetoOutboxActionDelete,
})
}
return nil
}
func (s *relyingPartyService) mapHydraToDomain(client *domain.HydraClient) *domain.RelyingParty {
if client == nil {
return nil
}
rp := &domain.RelyingParty{
ClientID: client.ClientID,
Name: client.ClientName,
}
if client.Metadata != nil {
if tid, ok := client.Metadata["tenant_id"].(string); ok {
rp.TenantID = tid
}
if desc, ok := client.Metadata["description"].(string); ok {
rp.Description = desc
}
}
return rp
}