# [RFC/Design] adminfront: 각 탭별 ReBAC 기반 세부 권한 직접 부여 기능 설계 ## 1. 배경 및 목적 현재 `adminfront` 테넌트 상세 페이지는 대략적인 역할 기반 제어(Coarse-grained RBAC/ReBAC) 형태로만 동작합니다. 운영자는 사용자를 **"소유자(Owner)"** 또는 **"테넌트 관리자(Admin)"**로만 임명할 수 있으며, 이 역할에 의해 테넌트 하위의 4개 탭(프로필, 권한 관리, 조직 관리, 사용자 스키마)의 읽기/쓰기 권한이 통째로 결정됩니다. 하지만 더욱 세밀한 운영 권한 관리가 필요하다는 비즈니스 요구사항에 따라, **"사용자 A에게는 조직 관리 및 스키마 읽기 권한만 부여"**, **"사용자 B에게는 스키마 수정 권한만 부여"**와 같이 탭 레벨에서 세분화된(Fine-grained) 권한을 직접 지정할 수 있는 기능을 신설합니다. 이 설계는 `devfront`에서 이슈 #1029를 통해 구현 완료한 **"RP 세부 관계 직접 부여"** 철학과 완벽히 동일하며, Ory Keto(ReBAC) 및 아웃박스 정합성 엔진을 관통하여 설계됩니다. --- ## 2. 세부 설계 사양 ### 2.1 Ory Keto OPL 스키마 변경 (`docker/ory/keto/namespaces.ts`) `Tenant` 네임스페이스 하위에 각 탭별 읽기(`_viewers`)와 쓰기(`_managers`)를 결정하는 **물리적인 직접 관계(Direct Relations)**를 추가합니다. 기존 `members`, `admins`, `owners`에 의한 상속 허가 식(Permits)을 유지하여 하위 호환성 및 기존 관리체계의 안정성을 완벽히 보장합니다. ```typescript class Tenant implements Namespace { related: { owners: (User | SubjectSet)[] admins: (User | SubjectSet)[] members: (User | SubjectSet | SubjectSet | SubjectSet)[] parents: Tenant[] developer_console_viewer: (User | SubjectSet)[] developer_console_grant_manager: (User | SubjectSet)[] // 🌟 신규 직접 관계 (Direct Relations) 정의 profile_viewers: (User | SubjectSet)[] profile_managers: (User | SubjectSet)[] permissions_viewers: (User | SubjectSet)[] permissions_managers: (User | SubjectSet)[] organization_viewers: (User | SubjectSet)[] organization_managers: (User | SubjectSet)[] schema_viewers: (User | SubjectSet)[] schema_managers: (User | SubjectSet)[] } permits = { // 1. 프로필 (Profile) 탭 허가 규칙 view_profile: (ctx: Context): boolean => this.related.profile_viewers.includes(ctx.subject) || this.permits.manage_profile(ctx) || this.permits.view(ctx), // 멤버/관리자/소유자는 기본 조회 가능 manage_profile: (ctx: Context): boolean => this.related.profile_managers.includes(ctx.subject) || this.permits.manage(ctx), // 관리자/소유자는 기본 수정 가능 // 2. 권한 관리 (Permissions) 탭 허가 규칙 view_permissions: (ctx: Context): boolean => this.related.permissions_viewers.includes(ctx.subject) || this.permits.manage_permissions(ctx) || this.permits.view(ctx), manage_permissions: (ctx: Context): boolean => this.related.permissions_managers.includes(ctx.subject) || this.permits.manage_admins(ctx), // 소유자는 기본 관리 가능 // 3. 조직 관리 (Organization) 탭 허가 규칙 view_organization: (ctx: Context): boolean => this.related.organization_viewers.includes(ctx.subject) || this.permits.manage_organization(ctx) || this.permits.view(ctx), manage_organization: (ctx: Context): boolean => this.related.organization_managers.includes(ctx.subject) || this.permits.manage(ctx), // 4. 사용자 스키마 (Schema) 탭 허가 규칙 view_schema: (ctx: Context): boolean => this.related.schema_viewers.includes(ctx.subject) || this.permits.manage_schema(ctx) || this.permits.view(ctx), manage_schema: (ctx: Context): boolean => this.related.schema_managers.includes(ctx.subject) || this.permits.manage(ctx), // --- 기존 마스터 및 상속 규칙 보존 --- view: (ctx: Context): boolean => this.related.members.includes(ctx.subject) || this.related.admins.includes(ctx.subject) || this.related.owners.includes(ctx.subject) || this.related.parents.traverse((p) => p.permits.view(ctx)), manage: (ctx: Context): boolean => this.related.admins.includes(ctx.subject) || this.related.owners.includes(ctx.subject) || this.related.parents.traverse((p) => p.permits.manage(ctx)), manage_admins: (ctx: Context): boolean => this.related.owners.includes(ctx.subject) || this.related.parents.traverse((p) => p.permits.manage_admins(ctx)) } } ``` --- ### 2.2 백엔드 API 설계 (`backend/internal/handler/tenant_handler.go`) 세부 권한 부여/회수 API는 해당 테넌트의 최상위 권한 관리자만 수행할 수 있도록 **`Tenant#manage_admins`** 허가 규칙으로 강력하게 인가 보호합니다. #### A. 세부 권한 관계 전체 조회 API * **Endpoint**: `GET /api/v1/admin/tenants/:id/relations` * **인가 필터**: `RequireKetoPermission(config, "Tenant", "manage_admins")` * **반환 DTO**: ```json { "items": [ { "userId": "00000000-0000-0000-0000-000000000010", "name": "홍길동", "email": "kildong@hmac.kr", "relations": ["profile_managers", "schema_viewers"] } ] } ``` #### B. 세부 권한 관계 부여 API * **Endpoint**: `POST /api/v1/admin/tenants/:id/relations` * **인가 필터**: `RequireKetoPermission(config, "Tenant", "manage_admins")` * **Payload**: ```json { "userId": "00000000-0000-0000-0000-000000000010", "relation": "profile_managers" } ``` * **동작**: 트랜잭셔널 아웃박스에 적재하여 Keto에 `Tenant:#profile_managers@User:` 튜플 반영. #### C. 세부 권한 관계 회수 API * **Endpoint**: `DELETE /api/v1/admin/tenants/:id/relations` * **인가 필터**: `RequireKetoPermission(config, "Tenant", "manage_admins")` * **Payload**: ```json { "userId": "00000000-0000-0000-0000-000000000010", "relation": "profile_managers" } ``` * **동작**: 트랜잭셔널 아웃박스에 적재하여 Keto 내 튜플 삭제 반영. --- ### 2.3 프론트엔드 UI 설계 사용자에게 역할(Role) 외에 세부적인 설정을 직관적으로 관리할 수 있도록, 기존 **"권한 관리"** 탭 하단에 **"세부 권한 설정 (Fine-grained Permissions)"** 섹션을 신설합니다. #### A. 구성 요소 1. **유저 검색/추가 패널**: 테넌트 소속 사용자를 검색하여 격리 설정 테이블(Matrix)에 추가합니다. 2. **세부 권한 격리 매트릭스 (Matrix Table)**: * 컬럼: `이름` | `이메일` | `테넌트 프로필` | `권한 관리` | `조직 관리` | `사용자 스키마` | `작업` * 각 탭 컬럼은 드롭다운 셀렉트 박스로 채워집니다: * **`권한 없음 (None)`** / **`조회 가능 (Read)`** / **`수정 가능 (Write)`** 3. **상태 동기화 연동**: * 셀렉트 박스에서 `조회 가능(Read)` 선택 시: `_viewers` 관계 추가(`POST`) & `_managers` 관계 회수(`DELETE`). * 셀렉트 박스에서 `수정 가능(Write)` 선택 시: `_managers` 관계 추가(`POST`) & `_viewers` 관계 회수(`DELETE`). * 셀렉트 박스에서 `권한 없음(None)` 선택 시: 둘 다 회수(`DELETE`). --- ## 3. 작업 계획 및 테스트 전략 1. **OPL 컴파일 및 빌드 검증**: * namespaces.ts 수정 후 Keto OPL 테스트를 구동하여 컴파일 문법에 문제가 없는지 사전 검증합니다. 2. **백엔드 구현 및 DB 연동**: * `tenant_handler.go`에 신규 핸들러 추가 후 gg/gorm 아웃박스 통합을 완료합니다. 3. **프론트엔드 연동 및 Matrix UI 개발**: * `TenantAdminsAndOwnersTab.tsx` 하단부 카드에 매트릭스 테이블 영역을 추가합니다. 4. **유형 및 단위 테스트**: * 신설된 REST API 명세를 테스트하는 고성능 백엔드 단위 테스트를 작성합니다. * 프론트엔드에서 체크박스 변경 시 올바른 릴레이션이 트리거되는지 검증하는 Vitest 렌더 테스트를 작성합니다.