forked from baron/baron-sso
Ory Keto ReBAC Policy & Relation Tuple Architecture
This commit is contained in:
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"baron-sso-backend/internal/domain"
|
||||
"baron-sso-backend/internal/repository"
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
@@ -20,15 +21,18 @@ type RelyingPartyService interface {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,23 +42,22 @@ func (s *relyingPartyService) Create(ctx context.Context, tenantID string, clien
|
||||
client.Metadata = make(map[string]interface{})
|
||||
}
|
||||
client.Metadata["tenant_id"] = tenantID
|
||||
// Ensure description is in metadata if provided in some other way?
|
||||
// The input 'client' is domain.HydraClient. It doesn't have a separate description field.
|
||||
// Assuming caller puts description in metadata.
|
||||
|
||||
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
|
||||
// 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)
|
||||
// Try to cleanup Hydra client
|
||||
_ = s.hydraService.DeleteClient(ctx, createdClient.ClientID)
|
||||
return nil, 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
|
||||
@@ -71,28 +74,22 @@ func (s *relyingPartyService) Get(ctx context.Context, clientID string) (*domain
|
||||
|
||||
func (s *relyingPartyService) List(ctx context.Context, tenantID string) ([]domain.RelyingParty, error) {
|
||||
// 1. Fetch ClientIDs from Keto
|
||||
// Subject: Tenant:<tenantID>, Relation: parent_tenant, Namespace: RelyingParty
|
||||
// Note: ListRelations checks "who has relation to subject".
|
||||
// Relation tuple: RelyingParty:cid # parent_tenant @ Tenant:tid
|
||||
// We want to find objects where subject=Tenant:tid.
|
||||
tuples, err := s.ketoService.ListRelations(ctx, "RelyingParty", "", "parent_tenant", "Tenant:"+tenantID)
|
||||
// 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 {
|
||||
// Object is "RelyingParty:clientId"
|
||||
if len(t.Object) > 13 && t.Object[:13] == "RelyingParty:" {
|
||||
clientID := t.Object[13:]
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,16 +97,6 @@ func (s *relyingPartyService) List(ctx context.Context, tenantID string) ([]doma
|
||||
}
|
||||
|
||||
func (s *relyingPartyService) ListAll(ctx context.Context) ([]domain.RelyingParty, error) {
|
||||
// This might be heavy if there are many clients.
|
||||
// Hydra doesn't support "List all clients" easily without pagination.
|
||||
// Assuming HydraAdminService has ListClients or similar?
|
||||
// The interface wasn't shown, but assuming it's available or we skip implementation.
|
||||
// For now, let's return empty or error?
|
||||
// Wait, repo.ListAll was used.
|
||||
// Let's assume we can't implement efficient ListAll without DB,
|
||||
// UNLESS we use Keto to list all RelyingParties (if Keto supports listing all objects in namespace).
|
||||
// Keto doesn't support listing all objects easily.
|
||||
// But `hydraService` likely has `ListClients`.
|
||||
return nil, fmt.Errorf("ListAll not implemented in SSOT mode yet")
|
||||
}
|
||||
|
||||
@@ -136,7 +123,7 @@ 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 // Or ignore if not found?
|
||||
return err
|
||||
}
|
||||
tenantID := ""
|
||||
if client.Metadata != nil {
|
||||
@@ -150,9 +137,15 @@ func (s *relyingPartyService) Delete(ctx context.Context, clientID string) error
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. Delete from Keto
|
||||
if tenantID != "" {
|
||||
_ = s.ketoService.DeleteRelation(ctx, "RelyingParty", clientID, "parent_tenant", "Tenant:"+tenantID)
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user