forked from baron/baron-sso
adminfront 및 백엔드: 세부 권한 변경 시 Keto 동기식 실시간 쓰기 및 프론트 일괄 갱신 적용하여 지연/롤백 버그 해결 완료
This commit is contained in:
@@ -88,6 +88,15 @@ type tenantPermissions struct {
|
||||
View bool `json:"view"`
|
||||
Manage bool `json:"manage"`
|
||||
ManageAdmins bool `json:"manage_admins"`
|
||||
|
||||
ViewProfile bool `json:"view_profile"`
|
||||
ManageProfile bool `json:"manage_profile"`
|
||||
ViewPermissions bool `json:"view_permissions"`
|
||||
ManagePermissions bool `json:"manage_permissions"`
|
||||
ViewOrganization bool `json:"view_organization"`
|
||||
ManageOrganization bool `json:"manage_organization"`
|
||||
ViewSchema bool `json:"view_schema"`
|
||||
ManageSchema bool `json:"manage_schema"`
|
||||
}
|
||||
|
||||
type tenantSummary struct {
|
||||
@@ -1691,9 +1700,17 @@ func (h *TenantHandler) GetTenant(c *fiber.Ctx) error {
|
||||
role := domain.NormalizeRole(profile.Role)
|
||||
if role == domain.RoleSuperAdmin {
|
||||
summary.UserPermissions = &tenantPermissions{
|
||||
View: true,
|
||||
Manage: true,
|
||||
ManageAdmins: true,
|
||||
View: true,
|
||||
Manage: true,
|
||||
ManageAdmins: true,
|
||||
ViewProfile: true,
|
||||
ManageProfile: true,
|
||||
ViewPermissions: true,
|
||||
ManagePermissions: true,
|
||||
ViewOrganization: true,
|
||||
ManageOrganization: true,
|
||||
ViewSchema: true,
|
||||
ManageSchema: true,
|
||||
}
|
||||
} else {
|
||||
// Query Keto in parallel for maximum performance
|
||||
@@ -1703,8 +1720,14 @@ func (h *TenantHandler) GetTenant(c *fiber.Ctx) error {
|
||||
allowed bool
|
||||
err error
|
||||
}
|
||||
ch := make(chan checkResult, 3)
|
||||
relations := []string{"view", "manage", "manage_admins"}
|
||||
ch := make(chan checkResult, 11)
|
||||
relations := []string{
|
||||
"view", "manage", "manage_admins",
|
||||
"view_profile", "manage_profile",
|
||||
"view_permissions", "manage_permissions",
|
||||
"view_organization", "manage_organization",
|
||||
"view_schema", "manage_schema",
|
||||
}
|
||||
for _, rel := range relations {
|
||||
go func(r string) {
|
||||
allowed, err := h.Keto.CheckPermission(c.Context(), subject, "Tenant", tenant.ID, r)
|
||||
@@ -1726,6 +1749,22 @@ func (h *TenantHandler) GetTenant(c *fiber.Ctx) error {
|
||||
perms.Manage = res.allowed
|
||||
case "manage_admins":
|
||||
perms.ManageAdmins = res.allowed
|
||||
case "view_profile":
|
||||
perms.ViewProfile = res.allowed
|
||||
case "manage_profile":
|
||||
perms.ManageProfile = res.allowed
|
||||
case "view_permissions":
|
||||
perms.ViewPermissions = res.allowed
|
||||
case "manage_permissions":
|
||||
perms.ManagePermissions = res.allowed
|
||||
case "view_organization":
|
||||
perms.ViewOrganization = res.allowed
|
||||
case "manage_organization":
|
||||
perms.ManageOrganization = res.allowed
|
||||
case "view_schema":
|
||||
perms.ViewSchema = res.allowed
|
||||
case "manage_schema":
|
||||
perms.ManageSchema = res.allowed
|
||||
}
|
||||
}
|
||||
summary.UserPermissions = perms
|
||||
@@ -3331,16 +3370,35 @@ func (h *TenantHandler) AddRelation(c *fiber.Ctx) error {
|
||||
}
|
||||
}
|
||||
|
||||
var directWriteErr error
|
||||
if h.Keto != nil {
|
||||
directWriteErr = h.Keto.CreateRelation(c.Context(), "Tenant", tenantID, req.Relation, "User:"+req.UserID)
|
||||
}
|
||||
|
||||
if h.KetoOutbox != nil {
|
||||
status := domain.KetoOutboxStatusPending
|
||||
var processedAt *time.Time
|
||||
if directWriteErr == nil && h.Keto != nil {
|
||||
status = domain.KetoOutboxStatusProcessed
|
||||
now := time.Now()
|
||||
processedAt = &now
|
||||
}
|
||||
|
||||
_ = h.KetoOutbox.Create(c.Context(), &domain.KetoOutbox{
|
||||
Namespace: "Tenant",
|
||||
Object: tenantID,
|
||||
Relation: req.Relation,
|
||||
Subject: "User:" + req.UserID,
|
||||
Action: domain.KetoOutboxActionCreate,
|
||||
Namespace: "Tenant",
|
||||
Object: tenantID,
|
||||
Relation: req.Relation,
|
||||
Subject: "User:" + req.UserID,
|
||||
Action: domain.KetoOutboxActionCreate,
|
||||
Status: status,
|
||||
ProcessedAt: processedAt,
|
||||
})
|
||||
}
|
||||
|
||||
if directWriteErr != nil {
|
||||
return errorJSON(c, fiber.StatusInternalServerError, "Keto 동기화 실패: "+directWriteErr.Error())
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
@@ -3359,16 +3417,35 @@ func (h *TenantHandler) RemoveRelation(c *fiber.Ctx) error {
|
||||
return errorJSON(c, fiber.StatusBadRequest, "userId and relation are required")
|
||||
}
|
||||
|
||||
var directWriteErr error
|
||||
if h.Keto != nil {
|
||||
directWriteErr = h.Keto.DeleteRelation(c.Context(), "Tenant", tenantID, req.Relation, "User:"+req.UserID)
|
||||
}
|
||||
|
||||
if h.KetoOutbox != nil {
|
||||
status := domain.KetoOutboxStatusPending
|
||||
var processedAt *time.Time
|
||||
if directWriteErr == nil && h.Keto != nil {
|
||||
status = domain.KetoOutboxStatusProcessed
|
||||
now := time.Now()
|
||||
processedAt = &now
|
||||
}
|
||||
|
||||
_ = h.KetoOutbox.Create(c.Context(), &domain.KetoOutbox{
|
||||
Namespace: "Tenant",
|
||||
Object: tenantID,
|
||||
Relation: req.Relation,
|
||||
Subject: "User:" + req.UserID,
|
||||
Action: domain.KetoOutboxActionDelete,
|
||||
Namespace: "Tenant",
|
||||
Object: tenantID,
|
||||
Relation: req.Relation,
|
||||
Subject: "User:" + req.UserID,
|
||||
Action: domain.KetoOutboxActionDelete,
|
||||
Status: status,
|
||||
ProcessedAt: processedAt,
|
||||
})
|
||||
}
|
||||
|
||||
if directWriteErr != nil {
|
||||
return errorJSON(c, fiber.StatusInternalServerError, "Keto 동기화 실패: "+directWriteErr.Error())
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
@@ -3390,6 +3467,18 @@ func (h *TenantHandler) ListSystemRelations(c *fiber.Ctx) error {
|
||||
"auth_guard_viewers": true,
|
||||
"api_keys_viewers": true,
|
||||
"audit_logs_viewers": true,
|
||||
|
||||
"overview_managers": true,
|
||||
"tenants_managers": true,
|
||||
"org_chart_managers": true,
|
||||
"worksmobile_managers": true,
|
||||
"ory_ssot_managers": true,
|
||||
"data_integrity_managers": true,
|
||||
"users_managers": true,
|
||||
"permissions_direct_managers": true,
|
||||
"auth_guard_managers": true,
|
||||
"api_keys_managers": true,
|
||||
"audit_logs_managers": true,
|
||||
}
|
||||
|
||||
type userRelationInfo struct {
|
||||
@@ -3474,6 +3563,18 @@ func (h *TenantHandler) AddSystemRelation(c *fiber.Ctx) error {
|
||||
"auth_guard_viewers": true,
|
||||
"api_keys_viewers": true,
|
||||
"audit_logs_viewers": true,
|
||||
|
||||
"overview_managers": true,
|
||||
"tenants_managers": true,
|
||||
"org_chart_managers": true,
|
||||
"worksmobile_managers": true,
|
||||
"ory_ssot_managers": true,
|
||||
"data_integrity_managers": true,
|
||||
"users_managers": true,
|
||||
"permissions_direct_managers": true,
|
||||
"auth_guard_managers": true,
|
||||
"api_keys_managers": true,
|
||||
"audit_logs_managers": true,
|
||||
}
|
||||
|
||||
if !allowedRelations[req.Relation] {
|
||||
@@ -3487,16 +3588,35 @@ func (h *TenantHandler) AddSystemRelation(c *fiber.Ctx) error {
|
||||
}
|
||||
}
|
||||
|
||||
var directWriteErr error
|
||||
if h.Keto != nil {
|
||||
directWriteErr = h.Keto.CreateRelation(c.Context(), "System", "system", req.Relation, "User:"+req.UserID)
|
||||
}
|
||||
|
||||
if h.KetoOutbox != nil {
|
||||
status := domain.KetoOutboxStatusPending
|
||||
var processedAt *time.Time
|
||||
if directWriteErr == nil && h.Keto != nil {
|
||||
status = domain.KetoOutboxStatusProcessed
|
||||
now := time.Now()
|
||||
processedAt = &now
|
||||
}
|
||||
|
||||
_ = h.KetoOutbox.Create(c.Context(), &domain.KetoOutbox{
|
||||
Namespace: "System",
|
||||
Object: "system",
|
||||
Relation: req.Relation,
|
||||
Subject: "User:" + req.UserID,
|
||||
Action: domain.KetoOutboxActionCreate,
|
||||
Namespace: "System",
|
||||
Object: "system",
|
||||
Relation: req.Relation,
|
||||
Subject: "User:" + req.UserID,
|
||||
Action: domain.KetoOutboxActionCreate,
|
||||
Status: status,
|
||||
ProcessedAt: processedAt,
|
||||
})
|
||||
}
|
||||
|
||||
if directWriteErr != nil {
|
||||
return errorJSON(c, fiber.StatusInternalServerError, "Keto 동기화 실패: "+directWriteErr.Error())
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
@@ -3510,15 +3630,34 @@ func (h *TenantHandler) RemoveSystemRelation(c *fiber.Ctx) error {
|
||||
return errorJSON(c, fiber.StatusBadRequest, "userId and relation are required")
|
||||
}
|
||||
|
||||
var directWriteErr error
|
||||
if h.Keto != nil {
|
||||
directWriteErr = h.Keto.DeleteRelation(c.Context(), "System", "system", req.Relation, "User:"+req.UserID)
|
||||
}
|
||||
|
||||
if h.KetoOutbox != nil {
|
||||
status := domain.KetoOutboxStatusPending
|
||||
var processedAt *time.Time
|
||||
if directWriteErr == nil && h.Keto != nil {
|
||||
status = domain.KetoOutboxStatusProcessed
|
||||
now := time.Now()
|
||||
processedAt = &now
|
||||
}
|
||||
|
||||
_ = h.KetoOutbox.Create(c.Context(), &domain.KetoOutbox{
|
||||
Namespace: "System",
|
||||
Object: "system",
|
||||
Relation: req.Relation,
|
||||
Subject: "User:" + req.UserID,
|
||||
Action: domain.KetoOutboxActionDelete,
|
||||
Namespace: "System",
|
||||
Object: "system",
|
||||
Relation: req.Relation,
|
||||
Subject: "User:" + req.UserID,
|
||||
Action: domain.KetoOutboxActionDelete,
|
||||
Status: status,
|
||||
ProcessedAt: processedAt,
|
||||
})
|
||||
}
|
||||
|
||||
if directWriteErr != nil {
|
||||
return errorJSON(c, fiber.StatusInternalServerError, "Keto 동기화 실패: "+directWriteErr.Error())
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ func TestTenantHandler_Relations(t *testing.T) {
|
||||
app.Post("/tenants/:id/relations", h.AddRelation)
|
||||
|
||||
mockKeto.On("ListRelations", mock.Anything, "Tenant", tenantID, "schema_managers", "User:"+userID).Return([]service.RelationTuple{}, nil).Once()
|
||||
mockKeto.On("CreateRelation", mock.Anything, "Tenant", tenantID, "schema_managers", "User:"+userID).Return(nil).Once()
|
||||
|
||||
body, _ := json.Marshal(map[string]string{
|
||||
"userId": userID,
|
||||
@@ -135,6 +136,7 @@ func TestTenantHandler_Relations(t *testing.T) {
|
||||
assert.Len(t, outboxEntries, 1)
|
||||
assert.Equal(t, "Tenant", outboxEntries[0].Namespace)
|
||||
assert.Equal(t, "User:"+userID, outboxEntries[0].Subject)
|
||||
assert.Equal(t, domain.KetoOutboxStatusProcessed, outboxEntries[0].Status)
|
||||
mockKeto.AssertExpectations(t)
|
||||
})
|
||||
|
||||
@@ -142,6 +144,8 @@ func TestTenantHandler_Relations(t *testing.T) {
|
||||
app := fiber.New()
|
||||
app.Delete("/tenants/:id/relations", h.RemoveRelation)
|
||||
|
||||
mockKeto.On("DeleteRelation", mock.Anything, "Tenant", tenantID, "schema_managers", "User:"+userID).Return(nil).Once()
|
||||
|
||||
body, _ := json.Marshal(map[string]string{
|
||||
"userId": userID,
|
||||
"relation": "schema_managers",
|
||||
@@ -165,6 +169,7 @@ func TestTenantHandler_Relations(t *testing.T) {
|
||||
assert.Len(t, outboxEntries, 1)
|
||||
assert.Equal(t, "Tenant", outboxEntries[0].Namespace)
|
||||
assert.Equal(t, "User:"+userID, outboxEntries[0].Subject)
|
||||
assert.Equal(t, domain.KetoOutboxStatusProcessed, outboxEntries[0].Status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -241,6 +246,7 @@ func TestTenantHandler_SystemRelations(t *testing.T) {
|
||||
app.Post("/system/relations", h.AddSystemRelation)
|
||||
|
||||
mockKeto.On("ListRelations", mock.Anything, "System", "system", "ory_ssot_viewers", "User:"+userID).Return([]service.RelationTuple{}, nil).Once()
|
||||
mockKeto.On("CreateRelation", mock.Anything, "System", "system", "ory_ssot_viewers", "User:"+userID).Return(nil).Once()
|
||||
|
||||
body, _ := json.Marshal(map[string]string{
|
||||
"userId": userID,
|
||||
@@ -264,6 +270,7 @@ func TestTenantHandler_SystemRelations(t *testing.T) {
|
||||
assert.Len(t, outboxEntries, 1)
|
||||
assert.Equal(t, "System", outboxEntries[0].Namespace)
|
||||
assert.Equal(t, "User:"+userID, outboxEntries[0].Subject)
|
||||
assert.Equal(t, domain.KetoOutboxStatusProcessed, outboxEntries[0].Status)
|
||||
mockKeto.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user