- {error ? (
+ {isRelationshipViewForbidden ? (
+
+ {relationshipViewForbiddenMessage}
+
+ ) : error ? (
{t(
"msg.dev.clients.relationships.load_error",
diff --git a/devfront/src/lib/devApi.ts b/devfront/src/lib/devApi.ts
index 6446d4f0..6ef6b0fd 100644
--- a/devfront/src/lib/devApi.ts
+++ b/devfront/src/lib/devApi.ts
@@ -202,11 +202,15 @@ export async function fetchClientRelations(clientId: string) {
return data;
}
-export async function fetchDevUsers(search: string, limit = 10) {
+export async function fetchDevUsers(
+ search: string,
+ limit = 10,
+ clientId?: string,
+) {
const { data } = await apiClient.get(
"/dev/users",
{
- params: { search, limit },
+ params: { search, limit, clientId },
},
);
return data;
diff --git a/devfront/src/locales/en.toml b/devfront/src/locales/en.toml
index bf5ff541..ea885824 100644
--- a/devfront/src/locales/en.toml
+++ b/devfront/src/locales/en.toml
@@ -388,8 +388,10 @@ list_description = "Lists operator relations directly assigned to this RP."
load_error = "Failed to load relationships: {{error}}"
loading = "Loading relationships..."
empty = "No direct relationships assigned."
+view_forbidden = "You do not have permission to view relationships for this RP. Ask an administrator to grant Relationship Viewer or RP Admin relationship."
search_loading = "Searching users..."
search_empty = "No users found."
+search_forbidden_user = "General users cannot use user search for relationship assignment."
selected_user = "Selected user: {{user}}"
[msg.dev.clients.federation]
@@ -1491,6 +1493,10 @@ description = "Marks the operator who created this RP."
label = "RP General Settings"
description = "Edit the name, redirect URIs, and general metadata."
+[ui.dev.clients.relationships.option.secret_viewer]
+label = "Secret View"
+description = "View the Client secret for this RP."
+
[ui.dev.clients.relationships.option.secret_rotator]
label = "Secret Rotation"
description = "Rotate and reissue the client secret."
diff --git a/devfront/src/locales/ko.toml b/devfront/src/locales/ko.toml
index 61fcc171..36b80e62 100644
--- a/devfront/src/locales/ko.toml
+++ b/devfront/src/locales/ko.toml
@@ -388,8 +388,10 @@ list_description = "현재 RP에 직접 부여된 operator relation 목록입니
load_error = "관계 조회 실패: {{error}}"
loading = "관계를 불러오는 중입니다..."
empty = "직접 부여된 관계가 없습니다."
+view_forbidden = "이 RP의 관계를 조회할 권한이 없습니다. 관리자에게 관계 조회 또는 RP 관리자 관계 부여를 요청해 주세요."
search_loading = "사용자를 찾는 중입니다..."
search_empty = "검색 결과가 없습니다."
+search_forbidden_user = "일반 사용자는 관계 추가를 위한 사용자 검색을 사용할 수 없습니다."
selected_user = "선택된 사용자: {{user}}"
[msg.dev.clients.federation]
@@ -1490,6 +1492,10 @@ description = "이 RP를 생성한 운영 주체를 표시합니다."
label = "RP 일반 설정"
description = "이름, Redirect URI, 메타데이터 같은 일반 설정을 수정합니다."
+[ui.dev.clients.relationships.option.secret_viewer]
+label = "시크릿 조회"
+description = "이 RP의 Client secret을 조회합니다."
+
[ui.dev.clients.relationships.option.secret_rotator]
label = "시크릿 재발급"
description = "Client secret 재발급과 회전을 수행합니다."
diff --git a/devfront/src/locales/template.toml b/devfront/src/locales/template.toml
index 94c2c4fe..9e43f416 100644
--- a/devfront/src/locales/template.toml
+++ b/devfront/src/locales/template.toml
@@ -388,8 +388,10 @@ list_description = ""
load_error = ""
loading = ""
empty = ""
+view_forbidden = ""
search_loading = ""
search_empty = ""
+search_forbidden_user = ""
selected_user = ""
[msg.dev.clients.federation]
@@ -1490,6 +1492,10 @@ description = ""
label = ""
description = ""
+[ui.dev.clients.relationships.option.secret_viewer]
+label = ""
+description = ""
+
[ui.dev.clients.relationships.option.secret_rotator]
label = ""
description = ""
diff --git a/docker/ory/keto/namespaces.ts b/docker/ory/keto/namespaces.ts
index 2e142757..2d387e27 100644
--- a/docker/ory/keto/namespaces.ts
+++ b/docker/ory/keto/namespaces.ts
@@ -63,6 +63,7 @@ class RelyingParty implements Namespace {
access: (User | SubjectSet | SubjectSet | SubjectSet)[]
creator: (User | SubjectSet)[]
config_editor: (User | SubjectSet)[]
+ secret_viewer: (User | SubjectSet)[]
secret_rotator: (User | SubjectSet)[]
jwks_viewer: (User | SubjectSet)[]
jwks_operator: (User | SubjectSet)[]
@@ -77,6 +78,7 @@ class RelyingParty implements Namespace {
view: (ctx: Context): boolean =>
this.related.admins.includes(ctx.subject) ||
this.related.config_editor.includes(ctx.subject) ||
+ this.related.secret_viewer.includes(ctx.subject) ||
this.related.secret_rotator.includes(ctx.subject) ||
this.related.jwks_viewer.includes(ctx.subject) ||
this.related.jwks_operator.includes(ctx.subject) ||
@@ -101,6 +103,11 @@ class RelyingParty implements Namespace {
this.related.config_editor.includes(ctx.subject) ||
this.permits.manage(ctx),
+ view_secret: (ctx: Context): boolean =>
+ this.related.secret_viewer.includes(ctx.subject) ||
+ this.permits.rotate_secret(ctx) ||
+ this.permits.manage(ctx),
+
rotate_secret: (ctx: Context): boolean =>
this.related.secret_rotator.includes(ctx.subject) ||
this.permits.manage(ctx),
diff --git a/docs/devfront-rp-relationships-guide.md b/docs/devfront-rp-relationships-guide.md
index ae2543c6..9cc6e931 100644
--- a/docs/devfront-rp-relationships-guide.md
+++ b/docs/devfront-rp-relationships-guide.md
@@ -25,8 +25,9 @@
| 화면 표시명 | Relation key | 의미 | 주요 허용 기능 |
|---|---|---|---|
-| RP 관리자 | `admins` | RP 운영 전반을 관리할 수 있는 관리자 관계 | RP 조회, 설정 관리, secret 재발급, JWKS 운영, consent 조회/회수, 관계 조회, 감사 로그 조회, 상태 변경 |
+| RP 관리자 | `admins` | RP 운영 전반을 관리할 수 있는 관리자 관계 | RP 조회, 설정 관리, secret 조회/재발급, JWKS 운영, consent 조회/회수, 관계 조회, 감사 로그 조회, 상태 변경 |
| RP 일반 설정 | `config_editor` | RP 이름, Redirect URI, 메타데이터 같은 일반 설정을 수정할 수 있는 관계 | RP 조회, 일반 설정 수정 |
+| 시크릿 조회 | `secret_viewer` | Client secret을 조회할 수 있는 관계 | RP 조회, client secret 조회 |
| 시크릿 재발급 | `secret_rotator` | Client secret 재발급과 rotation을 수행할 수 있는 관계 | RP 조회, client secret 재발급 |
| JWKS 조회 | `jwks_viewer` | JWKS 상태, 캐시 정보, key summary를 조회할 수 있는 관계 | RP 조회, JWKS 상태/캐시/key summary 조회 |
| JWKS 운영 | `jwks_operator` | JWKS refresh/revoke 같은 운영 작업을 수행할 수 있는 관계 | RP 조회, JWKS 조회, JWKS refresh/revoke |
@@ -42,10 +43,11 @@ Keto namespace 기준으로 relation은 다음 permit으로 계산된다.
| Permit | 허용 relation / 조건 | 기능 의미 |
|---|---|---|
-| `view` | `admins`, `config_editor`, `secret_rotator`, `jwks_viewer`, `jwks_operator`, `consent_viewer`, `consent_revoker`, `relationship_viewer`, `audit_viewer`, `status_operator`, 부모 tenant의 `view` 또는 `view_dev_console` | RP 기본 조회 및 목록 노출 |
+| `view` | `admins`, `config_editor`, `secret_viewer`, `secret_rotator`, `jwks_viewer`, `jwks_operator`, `consent_viewer`, `consent_revoker`, `relationship_viewer`, `audit_viewer`, `status_operator`, 부모 tenant의 `view` 또는 `view_dev_console` | RP 기본 조회 및 목록 노출 |
| `manage` | `admins`, 부모 tenant의 `manage` | RP 관리 상위 권한 |
| `create` | `creator`, 부모 tenant의 `grant_dev_permissions`, `manage` | RP 생성. `creator`는 현재 수동 부여하지 않는 내부 relation이다. |
| `edit_config` | `config_editor`, `manage` | RP 일반 설정 수정 |
+| `view_secret` | `secret_viewer`, `rotate_secret`, `manage` | client secret 조회 |
| `rotate_secret` | `secret_rotator`, `manage` | client secret 재발급/회전 |
| `view_jwks` | `jwks_viewer`, `operate_jwks`, `manage` | JWKS 상태/캐시/key summary 조회 |
| `operate_jwks` | `jwks_operator`, `manage` | JWKS refresh/revoke |
@@ -138,6 +140,7 @@ RP 생성 시 `metadata.user_id`가 존재하면 생성자에게 기본 운영 r
- `admins`
- `creator`
- `config_editor`
+- `secret_viewer`
- `secret_rotator`
- `jwks_viewer`
- `jwks_operator`