# [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<System, "super_admins">)[]
    admins: (User | SubjectSet<System, "super_admins">)[]
    members: (User | SubjectSet<System, "super_admins"> | SubjectSet<Tenant, "admins"> | SubjectSet<Tenant, "owners">)[]
    parents: Tenant[]
    developer_console_viewer: (User | SubjectSet<System, "super_admins">)[]
    developer_console_grant_manager: (User | SubjectSet<System, "super_admins">)[]

    // 🌟 신규 직접 관계 (Direct Relations) 정의
    profile_viewers: (User | SubjectSet<System, "super_admins">)[]
    profile_managers: (User | SubjectSet<System, "super_admins">)[]

    permissions_viewers: (User | SubjectSet<System, "super_admins">)[]
    permissions_managers: (User | SubjectSet<System, "super_admins">)[]

    organization_viewers: (User | SubjectSet<System, "super_admins">)[]
    organization_managers: (User | SubjectSet<System, "super_admins">)[]

    schema_viewers: (User | SubjectSet<System, "super_admins">)[]
    schema_managers: (User | SubjectSet<System, "super_admins">)[]
  }

  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:<ID>#profile_managers@User:<UserID>` 튜플 반영.

#### 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 렌더 테스트를 작성합니다.
