forked from baron/baron-sso
RP 생성/삭제 운영 relation 세트 반영
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RelyingPartyService interface {
|
type RelyingPartyService interface {
|
||||||
@@ -24,6 +25,19 @@ type relyingPartyService struct {
|
|||||||
outboxRepo repository.KetoOutboxRepository
|
outboxRepo repository.KetoOutboxRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var defaultRelyingPartyOperatorRelations = []string{
|
||||||
|
"admins",
|
||||||
|
"creator",
|
||||||
|
"config_editor",
|
||||||
|
"secret_rotator",
|
||||||
|
"jwks_viewer",
|
||||||
|
"jwks_operator",
|
||||||
|
"consent_viewer",
|
||||||
|
"consent_revoker",
|
||||||
|
"relationship_viewer",
|
||||||
|
"status_operator",
|
||||||
|
}
|
||||||
|
|
||||||
func NewRelyingPartyService(
|
func NewRelyingPartyService(
|
||||||
hydraService *HydraAdminService,
|
hydraService *HydraAdminService,
|
||||||
ketoService KetoService,
|
ketoService KetoService,
|
||||||
@@ -36,6 +50,51 @@ func NewRelyingPartyService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
func (s *relyingPartyService) Create(ctx context.Context, tenantID string, client domain.HydraClient) (*domain.RelyingParty, error) {
|
||||||
// 1. Create Client in Hydra
|
// 1. Create Client in Hydra
|
||||||
if client.Metadata == nil {
|
if client.Metadata == nil {
|
||||||
@@ -48,17 +107,8 @@ func (s *relyingPartyService) Create(ctx context.Context, tenantID string, clien
|
|||||||
return nil, fmt.Errorf("failed to create hydra client: %w", err)
|
return nil, fmt.Errorf("failed to create hydra client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Create Relation in Keto via Outbox
|
// 2. Create default relations in Keto via Outbox.
|
||||||
// RelyingParty:<client_id>#parents@Tenant:<tenant_id>
|
s.enqueueDefaultRelyingPartyRelations(ctx, domain.KetoOutboxActionCreate, createdClient, tenantID)
|
||||||
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
|
return s.mapHydraToDomain(createdClient), nil
|
||||||
}
|
}
|
||||||
@@ -137,16 +187,8 @@ func (s *relyingPartyService) Delete(ctx context.Context, clientID string) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Delete from Keto via Outbox
|
// 3. Delete default relations from Keto via Outbox.
|
||||||
if s.outboxRepo != nil && tenantID != "" {
|
s.enqueueDefaultRelyingPartyRelations(ctx, domain.KetoOutboxActionDelete, client, tenantID)
|
||||||
_ = s.outboxRepo.Create(ctx, &domain.KetoOutbox{
|
|
||||||
Namespace: "RelyingParty",
|
|
||||||
Object: clientID,
|
|
||||||
Relation: "parents",
|
|
||||||
Subject: "Tenant:" + tenantID,
|
|
||||||
Action: domain.KetoOutboxActionDelete,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ func TestRelyingPartyService_Create_Success(t *testing.T) {
|
|||||||
tenantID := "tenant-1"
|
tenantID := "tenant-1"
|
||||||
inputClient := domain.HydraClient{
|
inputClient := domain.HydraClient{
|
||||||
ClientName: "Test App",
|
ClientName: "Test App",
|
||||||
|
Metadata: map[string]interface{}{
|
||||||
|
"user_id": "creator-1",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hydra Mock
|
// Hydra Mock
|
||||||
@@ -81,6 +84,12 @@ func TestRelyingPartyService_Create_Success(t *testing.T) {
|
|||||||
mockOutbox.On("Create", mock.Anything, mock.MatchedBy(func(e *domain.KetoOutbox) bool {
|
mockOutbox.On("Create", mock.Anything, mock.MatchedBy(func(e *domain.KetoOutbox) bool {
|
||||||
return e.Namespace == "RelyingParty" && e.Object == "generated-client-id" && e.Relation == "parents" && e.Subject == "Tenant:"+tenantID
|
return e.Namespace == "RelyingParty" && e.Object == "generated-client-id" && e.Relation == "parents" && e.Subject == "Tenant:"+tenantID
|
||||||
})).Return(nil)
|
})).Return(nil)
|
||||||
|
for _, relation := range defaultRelyingPartyOperatorRelations {
|
||||||
|
rel := relation
|
||||||
|
mockOutbox.On("Create", mock.Anything, mock.MatchedBy(func(e *domain.KetoOutbox) bool {
|
||||||
|
return e.Namespace == "RelyingParty" && e.Object == "generated-client-id" && e.Relation == rel && e.Subject == "User:creator-1"
|
||||||
|
})).Return(nil)
|
||||||
|
}
|
||||||
|
|
||||||
svc := NewRelyingPartyService(hydraSvc, mockKeto, mockOutbox)
|
svc := NewRelyingPartyService(hydraSvc, mockKeto, mockOutbox)
|
||||||
rp, err := svc.Create(context.Background(), tenantID, inputClient)
|
rp, err := svc.Create(context.Background(), tenantID, inputClient)
|
||||||
@@ -173,6 +182,7 @@ func TestRelyingPartyService_Delete_Success(t *testing.T) {
|
|||||||
ClientID: clientID,
|
ClientID: clientID,
|
||||||
Metadata: map[string]interface{}{
|
Metadata: map[string]interface{}{
|
||||||
"tenant_id": tenantID,
|
"tenant_id": tenantID,
|
||||||
|
"user_id": "creator-1",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -192,6 +202,12 @@ func TestRelyingPartyService_Delete_Success(t *testing.T) {
|
|||||||
mockOutbox.On("Create", mock.Anything, mock.MatchedBy(func(e *domain.KetoOutbox) bool {
|
mockOutbox.On("Create", mock.Anything, mock.MatchedBy(func(e *domain.KetoOutbox) bool {
|
||||||
return e.Namespace == "RelyingParty" && e.Object == clientID && e.Relation == "parents" && e.Subject == "Tenant:"+tenantID
|
return e.Namespace == "RelyingParty" && e.Object == clientID && e.Relation == "parents" && e.Subject == "Tenant:"+tenantID
|
||||||
})).Return(nil)
|
})).Return(nil)
|
||||||
|
for _, relation := range defaultRelyingPartyOperatorRelations {
|
||||||
|
rel := relation
|
||||||
|
mockOutbox.On("Create", mock.Anything, mock.MatchedBy(func(e *domain.KetoOutbox) bool {
|
||||||
|
return e.Namespace == "RelyingParty" && e.Object == clientID && e.Relation == rel && e.Subject == "User:creator-1"
|
||||||
|
})).Return(nil)
|
||||||
|
}
|
||||||
|
|
||||||
svc := NewRelyingPartyService(hydraSvc, mockKeto, mockOutbox)
|
svc := NewRelyingPartyService(hydraSvc, mockKeto, mockOutbox)
|
||||||
err := svc.Delete(context.Background(), clientID)
|
err := svc.Delete(context.Background(), clientID)
|
||||||
|
|||||||
Reference in New Issue
Block a user