8.4 KiB
[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)을 유지하여 하위 호환성 및 기존 관리체계의 안정성을 완벽히 보장합니다.
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:
{ "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:
{ "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:
{ "userId": "00000000-0000-0000-0000-000000000010", "relation": "profile_managers" } - 동작: 트랜잭셔널 아웃박스에 적재하여 Keto 내 튜플 삭제 반영.
2.3 프론트엔드 UI 설계
사용자에게 역할(Role) 외에 세부적인 설정을 직관적으로 관리할 수 있도록, 기존 "권한 관리" 탭 하단에 "세부 권한 설정 (Fine-grained Permissions)" 섹션을 신설합니다.
A. 구성 요소
- 유저 검색/추가 패널: 테넌트 소속 사용자를 검색하여 격리 설정 테이블(Matrix)에 추가합니다.
- 세부 권한 격리 매트릭스 (Matrix Table):
- 컬럼:
이름|이메일|테넌트 프로필|권한 관리|조직 관리|사용자 스키마|작업 - 각 탭 컬럼은 드롭다운 셀렉트 박스로 채워집니다:
권한 없음 (None)/조회 가능 (Read)/수정 가능 (Write)
- 컬럼:
- 상태 동기화 연동:
- 셀렉트 박스에서
조회 가능(Read)선택 시:_viewers관계 추가(POST) &_managers관계 회수(DELETE). - 셀렉트 박스에서
수정 가능(Write)선택 시:_managers관계 추가(POST) &_viewers관계 회수(DELETE). - 셀렉트 박스에서
권한 없음(None)선택 시: 둘 다 회수(DELETE).
- 셀렉트 박스에서
3. 작업 계획 및 테스트 전략
- OPL 컴파일 및 빌드 검증:
- namespaces.ts 수정 후 Keto OPL 테스트를 구동하여 컴파일 문법에 문제가 없는지 사전 검증합니다.
- 백엔드 구현 및 DB 연동:
tenant_handler.go에 신규 핸들러 추가 후 gg/gorm 아웃박스 통합을 완료합니다.
- 프론트엔드 연동 및 Matrix UI 개발:
TenantAdminsAndOwnersTab.tsx하단부 카드에 매트릭스 테이블 영역을 추가합니다.
- 유형 및 단위 테스트:
- 신설된 REST API 명세를 테스트하는 고성능 백엔드 단위 테스트를 작성합니다.
- 프론트엔드에서 체크박스 변경 시 올바른 릴레이션이 트리거되는지 검증하는 Vitest 렌더 테스트를 작성합니다.