첫 커밋: 로컬 프로젝트 업로드
This commit is contained in:
215
baron-sso/backend/internal/service/relying_party_service.go
Normal file
215
baron-sso/backend/internal/service/relying_party_service.go
Normal file
@@ -0,0 +1,215 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"baron-sso-backend/internal/domain"
|
||||
"baron-sso-backend/internal/repository"
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
var defaultRelyingPartyOperatorRelations = []string{
|
||||
"admins",
|
||||
"creator",
|
||||
"config_editor",
|
||||
"secret_viewer",
|
||||
"secret_rotator",
|
||||
"jwks_viewer",
|
||||
"jwks_operator",
|
||||
"consent_viewer",
|
||||
"consent_revoker",
|
||||
"relationship_viewer",
|
||||
"audit_viewer",
|
||||
"status_operator",
|
||||
}
|
||||
|
||||
func NewRelyingPartyService(
|
||||
hydraService *HydraAdminService,
|
||||
ketoService KetoService,
|
||||
outboxRepo repository.KetoOutboxRepository,
|
||||
) RelyingPartyService {
|
||||
return &relyingPartyService{
|
||||
hydraService: hydraService,
|
||||
ketoService: ketoService,
|
||||
outboxRepo: outboxRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func extractRelyingPartyCreatorSubject(client *domain.HydraClient) string {
|
||||
if client == nil || client.Metadata == nil {
|
||||
return ""
|
||||
}
|
||||
raw, _ := client.Metadata["user_id"].(string)
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return ""
|
||||
}
|
||||
return "User:" + raw
|
||||
}
|
||||
|
||||
func (s *relyingPartyService) enqueueRelyingPartyTuple(ctx context.Context, action, object, relation, subject string) {
|
||||
if s.outboxRepo == nil || strings.TrimSpace(object) == "" || strings.TrimSpace(relation) == "" || strings.TrimSpace(subject) == "" {
|
||||
return
|
||||
}
|
||||
_ = s.outboxRepo.Create(ctx, &domain.KetoOutbox{
|
||||
Namespace: "RelyingParty",
|
||||
Object: object,
|
||||
Relation: relation,
|
||||
Subject: subject,
|
||||
Action: action,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *relyingPartyService) enqueueDefaultRelyingPartyRelations(ctx context.Context, action string, client *domain.HydraClient, tenantID string) {
|
||||
if client == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tenantID = strings.TrimSpace(tenantID)
|
||||
if tenantID != "" {
|
||||
s.enqueueRelyingPartyTuple(ctx, action, client.ClientID, "parents", "Tenant:"+tenantID)
|
||||
}
|
||||
|
||||
creatorSubject := extractRelyingPartyCreatorSubject(client)
|
||||
if creatorSubject == "" {
|
||||
return
|
||||
}
|
||||
|
||||
for _, relation := range defaultRelyingPartyOperatorRelations {
|
||||
s.enqueueRelyingPartyTuple(ctx, action, client.ClientID, relation, creatorSubject)
|
||||
}
|
||||
}
|
||||
|
||||
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]any)
|
||||
}
|
||||
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 default relations in Keto via Outbox.
|
||||
s.enqueueDefaultRelyingPartyRelations(ctx, domain.KetoOutboxActionCreate, createdClient, tenantID)
|
||||
|
||||
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 default relations from Keto via Outbox.
|
||||
s.enqueueDefaultRelyingPartyRelations(ctx, domain.KetoOutboxActionDelete, client, tenantID)
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user