1
0
forked from baron/baron-sso

개발자 권한 앱 생성 오류 수정

This commit is contained in:
2026-04-29 14:00:47 +09:00
parent 0844befb35
commit 68e7fb9ba2
2 changed files with 110 additions and 13 deletions

View File

@@ -3295,8 +3295,8 @@ func (h *DevHandler) ensureDeveloperGrantRelation(c *fiber.Ctx, userID, tenantID
return
}
subject := "User:" + strings.TrimSpace(userID)
for _, relation := range []string{"view_dev_console", "grant_dev_permissions"} {
if !h.hasDirectTenantRelation(c, tenantID, relation, subject) {
for _, relation := range []string{"developer_console_grant_manager", "view_dev_console", "grant_dev_permissions"} {
if h.hasDirectTenantRelation(c, tenantID, relation, subject) {
continue
}
_ = h.KetoOutbox.Create(c.Context(), &domain.KetoOutbox{
@@ -3304,19 +3304,14 @@ func (h *DevHandler) ensureDeveloperGrantRelation(c *fiber.Ctx, userID, tenantID
Object: tenantID,
Relation: relation,
Subject: subject,
Action: domain.KetoOutboxActionDelete,
Action: domain.KetoOutboxActionCreate,
})
if h.Keto != nil {
if err := h.Keto.CreateRelation(c.Context(), "Tenant", tenantID, relation, subject); err != nil {
slog.Warn("failed to grant immediate developer tenant relation", "tenantID", tenantID, "userID", userID, "relation", relation, "error", err)
}
}
}
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) {
@@ -3332,6 +3327,11 @@ func (h *DevHandler) revokeDeveloperGrantRelation(c *fiber.Ctx, userID, tenantID
Subject: subject,
Action: domain.KetoOutboxActionDelete,
})
if h.Keto != nil {
if err := h.Keto.DeleteRelation(c.Context(), "Tenant", tenantID, relation, subject); err != nil {
slog.Warn("failed to revoke immediate developer tenant relation", "tenantID", tenantID, "userID", userID, "relation", relation, "error", err)
}
}
}
}

View File

@@ -1102,6 +1102,103 @@ func TestCreateClient_ApprovedDeveloperCanCreatePrivateClient(t *testing.T) {
mockOutbox.AssertExpectations(t)
}
func TestEnsureDeveloperGrantRelation_CreatesRequiredTenantRelations(t *testing.T) {
mockKeto := new(devMockKetoService)
for _, relation := range []string{"developer_console_grant_manager", "view_dev_console", "grant_dev_permissions"} {
mockKeto.On("ListRelations", mock.Anything, "Tenant", "tenant-a", relation, "User:user-1").Return([]service.RelationTuple{}, nil).Once()
mockKeto.On("CreateRelation", mock.Anything, "Tenant", "tenant-a", relation, "User:user-1").Return(nil).Once()
}
mockOutbox := new(devMockKetoOutboxRepository)
for _, relation := range []string{"developer_console_grant_manager", "view_dev_console", "grant_dev_permissions"} {
expectedRelation := relation
mockOutbox.On("Create", mock.Anything, mock.MatchedBy(func(entry *domain.KetoOutbox) bool {
return entry.Namespace == "Tenant" &&
entry.Object == "tenant-a" &&
entry.Relation == expectedRelation &&
entry.Subject == "User:user-1" &&
entry.Action == domain.KetoOutboxActionCreate
})).Return(nil).Once()
}
h := &DevHandler{
Keto: mockKeto,
KetoOutbox: mockOutbox,
}
app := fiber.New()
app.Get("/test", func(c *fiber.Ctx) error {
h.ensureDeveloperGrantRelation(c, "user-1", "tenant-a")
return c.SendStatus(fiber.StatusOK)
})
req := httptest.NewRequest(http.MethodGet, "/test", nil)
resp, _ := app.Test(req, -1)
assert.Equal(t, http.StatusOK, resp.StatusCode)
mockKeto.AssertExpectations(t)
mockOutbox.AssertExpectations(t)
}
func TestEnsureDeveloperGrantRelation_SkipsExistingTenantRelations(t *testing.T) {
mockKeto := new(devMockKetoService)
for _, relation := range []string{"developer_console_grant_manager", "view_dev_console", "grant_dev_permissions"} {
mockKeto.On("ListRelations", mock.Anything, "Tenant", "tenant-a", relation, "User:user-1").
Return([]service.RelationTuple{{Namespace: "Tenant", Object: "tenant-a", Relation: relation, SubjectID: "User:user-1"}}, nil).Once()
}
h := &DevHandler{
Keto: mockKeto,
KetoOutbox: new(devMockKetoOutboxRepository),
}
app := fiber.New()
app.Get("/test", func(c *fiber.Ctx) error {
h.ensureDeveloperGrantRelation(c, "user-1", "tenant-a")
return c.SendStatus(fiber.StatusOK)
})
req := httptest.NewRequest(http.MethodGet, "/test", nil)
resp, _ := app.Test(req, -1)
assert.Equal(t, http.StatusOK, resp.StatusCode)
mockKeto.AssertExpectations(t)
}
func TestRevokeDeveloperGrantRelation_DeletesRequiredTenantRelations(t *testing.T) {
mockKeto := new(devMockKetoService)
for _, relation := range []string{"developer_console_grant_manager", "view_dev_console", "grant_dev_permissions"} {
mockKeto.On("DeleteRelation", mock.Anything, "Tenant", "tenant-a", relation, "User:user-1").Return(nil).Once()
}
mockOutbox := new(devMockKetoOutboxRepository)
for _, relation := range []string{"developer_console_grant_manager", "view_dev_console", "grant_dev_permissions"} {
expectedRelation := relation
mockOutbox.On("Create", mock.Anything, mock.MatchedBy(func(entry *domain.KetoOutbox) bool {
return entry.Namespace == "Tenant" &&
entry.Object == "tenant-a" &&
entry.Relation == expectedRelation &&
entry.Subject == "User:user-1" &&
entry.Action == domain.KetoOutboxActionDelete
})).Return(nil).Once()
}
h := &DevHandler{
Keto: mockKeto,
KetoOutbox: mockOutbox,
}
app := fiber.New()
app.Get("/test", func(c *fiber.Ctx) error {
h.revokeDeveloperGrantRelation(c, "user-1", "tenant-a")
return c.SendStatus(fiber.StatusOK)
})
req := httptest.NewRequest(http.MethodGet, "/test", nil)
resp, _ := app.Test(req, -1)
assert.Equal(t, http.StatusOK, resp.StatusCode)
mockKeto.AssertExpectations(t)
mockOutbox.AssertExpectations(t)
}
func TestGetStats_Success(t *testing.T) {
transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
if r.URL.Path == "/clients" {