1
0
forked from baron/baron-sso

개발자 권한 신청 승인/취소 및 RP 생성 흐름 개선

This commit is contained in:
2026-04-22 14:39:06 +09:00
parent 2216d9c4e4
commit 685923a03e
12 changed files with 382 additions and 44 deletions

View File

@@ -349,21 +349,47 @@ func (h *DevHandler) canViewClientByPermit(c *fiber.Ctx, profile *domain.UserPro
if profile == nil {
return false
}
if normalizeUserRole(profile.Role) == domain.RoleSuperAdmin {
role := normalizeUserRole(profile.Role)
if role == domain.RoleSuperAdmin {
return true
}
if h.hasDirectRelyingPartyOperatorRelation(c, profile, summary.ID) {
return true
}
clientTenantID := resolveClientTenantID(summary)
if clientTenantID != "" {
if role != domain.RoleUser && clientTenantID != "" {
if allowed, err := h.checkProfileKetoPermission(c, profile, "Tenant", clientTenantID, "view_dev_console"); err == nil && allowed {
return true
}
}
if role == domain.RoleUser {
return false
}
allowed, err := h.checkProfileKetoPermission(c, profile, "RelyingParty", summary.ID, "view")
return err == nil && allowed
}
func (h *DevHandler) hasDirectRelyingPartyOperatorRelation(c *fiber.Ctx, profile *domain.UserProfileResponse, clientID string) bool {
if h.Keto == nil || profile == nil {
return false
}
subject := ketoSubjectFromProfile(profile)
if subject == "" || strings.TrimSpace(clientID) == "" {
return false
}
for relation := range allowedRelyingPartyOperatorRelations {
tuples, err := h.Keto.ListRelations(c.Context(), "RelyingParty", clientID, relation, subject)
if err == nil && len(tuples) > 0 {
return true
}
}
return false
}
func (h *DevHandler) canManageTenantClientsByPermit(c *fiber.Ctx, profile *domain.UserProfileResponse, tenantID string) bool {
if strings.TrimSpace(tenantID) == "" {
return false
@@ -1428,7 +1454,7 @@ func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
if tenantID == "" && profile.TenantID != nil {
tenantID = *profile.TenantID
}
if role == domain.RoleRPAdmin && !h.canManageTenantClientsByPermit(c, profile, tenantID) {
if (role == domain.RoleRPAdmin || role == domain.RoleUser) && !h.canManageTenantClientsByPermit(c, profile, tenantID) {
return errorJSON(c, fiber.StatusForbidden, "forbidden: tenant grant permission is required")
}
@@ -1473,7 +1499,7 @@ func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
if err != nil {
return errorJSON(c, fiber.StatusInternalServerError, "permission check error")
}
if !isAppManager {
if !isAppManager && !h.canManageTenantClientsByPermit(c, profile, tenantID) {
return errorJSON(c, fiber.StatusForbidden, "forbidden: insufficient permissions to create private client")
}
}
@@ -1557,11 +1583,12 @@ func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
// [New] Automatically grant admin permission to the creator in Keto
if h.KetoOutbox != nil && profile != nil {
subject := "User:" + profile.ID
err := h.KetoOutbox.Create(c.Context(), &domain.KetoOutbox{
Namespace: "RelyingParty",
Object: created.ClientID,
Relation: "admins",
Subject: "User:" + profile.ID,
Subject: subject,
Action: domain.KetoOutboxActionCreate,
})
if err != nil {
@@ -1569,6 +1596,11 @@ func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
} else {
slog.Info("granted automatic admin permission to creator", "clientID", created.ClientID, "userID", profile.ID)
}
if h.Keto != nil {
if err := h.Keto.CreateRelation(c.Context(), "RelyingParty", created.ClientID, "admins", subject); err != nil {
slog.Warn("failed to grant immediate admin permission to creator", "clientID", created.ClientID, "userID", profile.ID, "error", err)
}
}
}
// Store secret in metadata for later retrieval
@@ -2863,10 +2895,66 @@ func (h *DevHandler) GetDeveloperRequestStatus(c *fiber.Ctx) error {
if status == nil {
return c.JSON(fiber.Map{"status": "none"})
}
if status.Status == domain.DeveloperRequestStatusApproved {
h.ensureDeveloperGrantRelation(c, status.UserID, status.TenantID)
}
return c.JSON(status)
}
func (h *DevHandler) ensureDeveloperGrantRelation(c *fiber.Ctx, userID, tenantID string) {
if h.KetoOutbox == nil || strings.TrimSpace(userID) == "" || strings.TrimSpace(tenantID) == "" {
return
}
subject := "User:" + strings.TrimSpace(userID)
for _, relation := range []string{"view_dev_console", "grant_dev_permissions"} {
if !h.hasDirectTenantRelation(c, tenantID, relation, subject) {
continue
}
_ = h.KetoOutbox.Create(c.Context(), &domain.KetoOutbox{
Namespace: "Tenant",
Object: tenantID,
Relation: relation,
Subject: subject,
Action: domain.KetoOutboxActionDelete,
})
}
if h.hasDirectTenantRelation(c, tenantID, "developer_console_grant_manager", subject) {
return
}
_ = h.KetoOutbox.Create(c.Context(), &domain.KetoOutbox{
Namespace: "Tenant",
Object: tenantID,
Relation: "developer_console_grant_manager",
Subject: subject,
Action: domain.KetoOutboxActionCreate,
})
}
func (h *DevHandler) revokeDeveloperGrantRelation(c *fiber.Ctx, userID, tenantID string) {
if h.KetoOutbox == nil || strings.TrimSpace(userID) == "" || strings.TrimSpace(tenantID) == "" {
return
}
subject := "User:" + strings.TrimSpace(userID)
for _, relation := range []string{"developer_console_grant_manager", "view_dev_console", "grant_dev_permissions"} {
_ = h.KetoOutbox.Create(c.Context(), &domain.KetoOutbox{
Namespace: "Tenant",
Object: tenantID,
Relation: relation,
Subject: subject,
Action: domain.KetoOutboxActionDelete,
})
}
}
func (h *DevHandler) hasDirectTenantRelation(c *fiber.Ctx, tenantID, relation, subject string) bool {
if h.Keto == nil {
return false
}
tuples, err := h.Keto.ListRelations(c.Context(), "Tenant", tenantID, relation, subject)
return err == nil && len(tuples) > 0
}
func (h *DevHandler) ListDeveloperRequests(c *fiber.Ctx) error {
profile := h.getCurrentProfile(c)
if profile == nil {
@@ -2923,18 +3011,7 @@ func (h *DevHandler) ApproveDeveloperRequest(c *fiber.Ctx) error {
// Grant Keto Permissions
if h.KetoOutbox != nil {
subject := "User:" + devReq.UserID
permissions := []string{"view_dev_console", "grant_dev_permissions"}
for _, relation := range permissions {
_ = h.KetoOutbox.Create(c.Context(), &domain.KetoOutbox{
Namespace: "Tenant",
Object: devReq.TenantID,
Relation: relation,
Subject: subject,
Action: domain.KetoOutboxActionCreate,
})
}
h.ensureDeveloperGrantRelation(c, devReq.UserID, devReq.TenantID)
}
return c.JSON(fiber.Map{"status": "ok"})
@@ -2968,3 +3045,42 @@ func (h *DevHandler) RejectDeveloperRequest(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"status": "ok"})
}
func (h *DevHandler) CancelDeveloperRequestApproval(c *fiber.Ctx) error {
profile := h.getCurrentProfile(c)
if profile == nil {
return errorJSON(c, fiber.StatusUnauthorized, "unauthorized")
}
if normalizeUserRole(profile.Role) != domain.RoleSuperAdmin {
return errorJSON(c, fiber.StatusForbidden, "forbidden: super_admin only")
}
idStr := c.Params("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
return errorJSON(c, fiber.StatusBadRequest, "invalid request id")
}
var reqBody struct {
AdminNotes string `json:"adminNotes"`
}
if err := c.BodyParser(&reqBody); err != nil {
return errorJSON(c, fiber.StatusBadRequest, "invalid request body")
}
devReq, err := h.DeveloperSvc.GetRequestByID(c.Context(), uint(id))
if err != nil {
return errorJSON(c, fiber.StatusInternalServerError, "failed to fetch request details")
}
if devReq.Status != domain.DeveloperRequestStatusApproved {
return errorJSON(c, fiber.StatusBadRequest, "only approved requests can be cancelled")
}
if err := h.DeveloperSvc.CancelApprovedRequest(c.Context(), uint(id), reqBody.AdminNotes); err != nil {
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
}
h.revokeDeveloperGrantRelation(c, devReq.UserID, devReq.TenantID)
return c.JSON(fiber.Map{"status": "ok"})
}