diff --git a/docs/design/424-spring-boot-vpd-backoffice/README.md b/docs/design/424-spring-boot-vpd-backoffice/README.md new file mode 100644 index 0000000..92249e1 --- /dev/null +++ b/docs/design/424-spring-boot-vpd-backoffice/README.md @@ -0,0 +1,247 @@ +# 설계서: Spring Boot VPD/ORDS 권한 백오피스 (#424) + +> **상태**: Draft +> **작성**: [AI] Architect · **최종수정**: 2026-06-23 +> **추적성** — Redmine: #424 · 관련 ADR: 없음 +> · 구현 파일: `pom.xml`, `src/main/**`, `src/test/**` · 테스트: `mvn test` + +## 1. 목적 (Why) + +운영자와 데모 담당자가 ADB VPD/Redaction 권한 매핑, Bearer Token, ORDS 호출 결과를 한 화면에서 관리하고 검증할 수 있는 Spring Boot 백오피스를 만든다. + +## 2. 범위 (Scope) + +- **포함**: + - Spring Boot 3.x, Java 21 또는 17, HikariCP, MyBatis 기반 백오피스 골격 + - ADB 권한 관리 테이블과 ORDS Bearer Key 검증 테이블을 조회/수정하는 서비스 + - 보호 객체, 사용자, 역할, 행 규칙, 컬럼 표시 권한, Bearer Token 관리 화면 + - Bearer Token을 사용해 ORDS에 `SELECT *` 성격의 조회를 실행하고 VPD/Redaction 적용 결과를 확인하는 화면 + - Thymeleaf + HTMX + Alpine.js + Bootstrap 5 기반 서버 렌더링 프론트 + - Audit log 저장과 오류 유형 구분 표시 +- **제외 (out of scope)**: + - 외부 SSO 연동 + - 운영 승인 워크플로우 + - DDS DATA GRANT 직접 생성 UI + - 원격 PostgreSQL/MySQL 데이터 생성 자동화 + - 운영 배포 파이프라인 + +## 3. 인수조건 (Acceptance Criteria) + +- [ ] Spring Boot 애플리케이션이 HikariCP와 MyBatis로 ADB에 연결된다. +- [ ] 보호 객체 목록은 DB에 등록된 whitelist에서만 선택 가능하며, 자유 입력 객체명으로 SQL 또는 ORDS 호출을 만들 수 없다. +- [ ] 내부 사용자, 역할, 사용자-역할, 객체 권한, 행 규칙, 컬럼 표시 권한을 백오피스에서 조회하고 저장할 수 있다. +- [ ] Bearer Token은 발급 시 원문을 한 번만 보여주고 DB에는 hash, prefix, 만료/회수 상태만 저장한다. +- [ ] ORDS 호출 검증 화면에서 Bearer Token과 보호 객체를 선택하면 ORDS 호출 결과, 반환 컬럼, 행 수, 오류 유형을 확인할 수 있다. +- [ ] 권한 없음, 잘못된 토큰, 객체 권한 없음, VPD 0건, Redaction NULL 표시를 서로 구분해 보여준다. +- [ ] 권한 변경, 토큰 발급/회수, ORDS 검증 호출은 audit log에 남는다. +- [ ] `mvn test`로 핵심 서비스 단위 테스트와 Mapper/SQL 검증 테스트를 실행할 수 있다. + +## 4. 컨텍스트 & 제약 + +- 의존성: + - Oracle Autonomous Database + - ORDS Handler 또는 REST Enabled SQL 성격의 조회 API + - Oracle JDBC, MyBatis, Spring Security, Thymeleaf, HTMX +- 제약: + - `.env`와 DB 비밀번호, Bearer Token 원문은 git에 저장하지 않는다. + - 객체명은 반드시 DB에 등록된 보호 객체 whitelist로 제한한다. + - 백오피스 DB 계정은 권한 관리 테이블과 검증용 ORDS 호출에 필요한 최소 권한만 갖는다. + - VPD는 행(row) 통제, Redaction은 컬럼 값 마스킹/NULL 처리로 설명하고 구현한다. +- 가정: + - 기존 SQL의 `CB_*` ORDS/VPD Bearer Key 예제를 백오피스 데이터 모델의 기준으로 삼는다. + - 첫 구현은 단일 관리자 로그인 또는 개발용 in-memory user로 시작하고, 운영 SSO는 후속 범위로 둔다. + - ORDS 호출은 백오피스 서버에서 수행하며 브라우저에는 Bearer Token 원문을 보존하지 않는다. + +## 5. 아키텍처 개요 + +### 모듈/파일 구조 + +```text +pom.xml +src/main/java/com/cloudhandson/vpdbackoffice/ + VpdBackofficeApplication.java + config/ + DataSourceConfig.java + SecurityConfig.java + OrdsClientConfig.java + domain/ + user/ + permission/ + token/ + protectedobject/ + probe/ + audit/ + mapper/ + UserMapper.java + PermissionMapper.java + BearerTokenMapper.java + ProtectedObjectMapper.java + AuditMapper.java + service/ + PermissionService.java + BearerTokenService.java + OrdsProbeService.java + AuditService.java + web/ + PermissionController.java + TokenController.java + ProbeController.java + DashboardController.java +src/main/resources/ + application.yml + mapper/*.xml + templates/**/*.html + static/css/app.css + static/js/app.js +src/test/java/com/cloudhandson/vpdbackoffice/ +``` + +### 데이터 흐름 + +```text +관리자 브라우저 + -> Spring MVC Controller + -> Service + -> MyBatis Mapper + -> ADB 권한/토큰/보호 객체 테이블 + +검증 호출 + -> ProbeController + -> OrdsProbeService + -> BearerTokenService에서 token 상태 확인 + -> ProtectedObject whitelist 확인 + -> ORDS HTTP 호출 Authorization: Bearer + -> 응답/오류 분류 + -> Audit 저장 + -> Thymeleaf fragment로 결과 영역 갱신 +``` + +### I/O와 순수 로직 경계 + +- Controller는 요청 파라미터 검증과 화면 모델 구성만 담당한다. +- Service는 트랜잭션, 권한 변경, 토큰 발급/회수, ORDS 호출 오케스트레이션을 담당한다. +- Mapper는 SQL I/O만 담당한다. +- 순수 로직은 `TokenGenerator`, `TokenHasher`, `ProbeErrorClassifier`, `ObjectNamePolicy`로 분리해 단위 테스트한다. + +## 6. 데이터 모델 + +### 백오피스 기준 테이블 + +| 테이블 | 역할 | 주요 컬럼 | +|---|---|---| +| `CB_APP_USER` | 내부 사용자 | `user_id`, `username`, `emp_no`, `dept_code`, `active_yn` | +| `CB_APP_ROLE` | 역할 | `role_id`, `role_name`, `description` | +| `CB_USER_ROLE` | 사용자-역할 매핑 | `user_id`, `role_id` | +| `CB_PROTECTED_OBJECT` | 보호 객체 whitelist | `object_id`, `owner`, `object_name`, `ords_path`, `enabled_yn` | +| `CB_PERMISSION` | 객체 접근 권한 | `permission_id`, `role_id`, `object_id`, `action` | +| `CB_PERMISSION_RULE` | 행 조건 | `rule_id`, `permission_id`, `rule_type`, `rule_value` | +| `CB_PROTECTED_COLUMN` | 컬럼 표시 정책 | `column_id`, `object_id`, `column_name`, `sensitive_yn`, `visible_role_id` | +| `CB_AGENT_BEARER_KEY` | Bearer Token 관리 | `key_id`, `user_id`, `key_prefix`, `key_hash`, `expires_at`, `revoked_at` | +| `CB_ORDS_PROBE_AUDIT` | ORDS 검증 이력 | `audit_id`, `key_id`, `object_id`, `status`, `row_count`, `error_code`, `created_at` | + +### 경계 검증 규칙 + +- `owner`, `object_name`, `column_name`은 Oracle identifier 허용 문자로 검증하되, 검증 후에도 SQL 문자열 직접 조합에 사용하지 않는다. +- ORDS 검증 대상은 `CB_PROTECTED_OBJECT.enabled_yn = 'Y'`인 행만 허용한다. +- Token 원문은 발급 응답 화면에서만 사용하고 DB, log, audit에는 저장하지 않는다. +- `rule_type`은 `ALL`, `MY_DEPT`, `SELF`, `REGION`, `CUSTOM_PREDICATE` 중 허용 값만 저장한다. +- `CUSTOM_PREDICATE`는 초기 구현에서 비활성화한다. + +## 7. 함수 명세 (Function Specs) + +| 함수 | 책임(1줄) | 시그니처(잠정) | 입력 | 출력 | 에러/실패 | 복잡? | +|------|-----------|----------------|------|------|-----------|-------| +| `issueToken` | Bearer Token을 생성하고 hash만 저장한다 | `IssuedToken issueToken(TokenIssueCommand command)` | 사용자, 만료일, 설명 | prefix와 원문 token | 비활성 사용자, 잘못된 만료일 | **복잡** | +| `revokeToken` | Bearer Token을 회수한다 | `void revokeToken(long keyId, String reason)` | key id, 사유 | 없음 | 없는 key, 이미 회수됨 | 단순 | +| `savePermissionSet` | 역할별 객체 권한과 행/컬럼 규칙을 저장한다 | `PermissionSet savePermissionSet(PermissionSetCommand command)` | 역할, 객체, 규칙 목록 | 저장된 권한 | whitelist 위반, 중복 규칙 | **복잡** | +| `runProbe` | ORDS Bearer 호출을 실행하고 결과를 분류한다 | `ProbeResult runProbe(ProbeCommand command)` | token id, object id, limit | 결과 rows, columns, status | 토큰 만료, ORDS 오류, timeout | **복잡** | +| `classifyProbeError` | ORDS/Oracle 오류를 화면 표시 상태로 분류한다 | `ProbeStatus classifyProbeError(HttpStatus status, String body)` | HTTP 상태, 응답 body | 분류 상태 | 알 수 없는 오류 | 단순 | +| `assertAllowedObject` | 보호 객체 whitelist를 검증한다 | `ProtectedObject assertAllowedObject(long objectId)` | object id | 보호 객체 | 비활성/없는 객체 | 단순 | +| `recordAudit` | 관리 작업과 검증 호출을 audit에 기록한다 | `void recordAudit(AuditEvent event)` | 이벤트 | 없음 | DB insert 실패 | 단순 | + +복잡 함수별 상세 설계서는 다음 파일에 둔다. + +- `fn-issueToken.md` +- `fn-savePermissionSet.md` +- `fn-runProbe.md` + +## 8. 흐름 / 알고리즘 + +### 권한 저장 + +1. Controller가 역할, 보호 객체, 행 규칙, 컬럼 표시 정책 입력을 받는다. +2. Service가 role/object 존재와 활성 상태를 확인한다. +3. 행 규칙 타입과 값을 검증한다. +4. 기존 권한 세트를 transaction 안에서 갱신한다. +5. VPD 정책이 참조하는 테이블 구조에 맞춰 권한 매핑을 저장한다. +6. audit log를 남긴다. +7. HTMX fragment로 저장된 권한 요약을 반환한다. + +### Token 발급 + +1. 관리자가 사용자와 만료일을 선택한다. +2. Service가 사용자 활성 상태와 만료일을 검증한다. +3. `vpd_live_` prefix와 충분한 난수 token body를 생성한다. +4. server-side pepper를 사용해 hash를 만든다. +5. prefix, hash, 만료일만 저장한다. +6. 원문 token은 발급 완료 화면에 한 번만 표시한다. + +### ORDS 검증 + +1. 관리자가 token과 보호 객체를 선택한다. +2. Service가 token 상태와 보호 객체 whitelist를 확인한다. +3. ORDS URL은 DB에 등록된 `ords_path`와 서버 설정의 base URL로만 만든다. +4. `Authorization: Bearer ` 헤더로 ORDS를 호출한다. +5. JSON 응답이면 컬럼 목록과 행 수를 계산한다. +6. 오류 응답이면 HTTP 상태와 Oracle 오류 코드를 분류한다. +7. audit log를 남기고 화면에 결과를 표시한다. + +## 9. 엣지케이스 & 에러 처리 + +- Bearer Token 없음: `MISSING_TOKEN`으로 표시하고 ORDS 호출하지 않는다. +- 만료/회수 token: `TOKEN_INACTIVE`로 표시하고 ORDS 호출하지 않는다. +- 잘못된 token hash: `INVALID_TOKEN` 또는 ORDS 응답의 `ORA-20002`로 표시한다. +- 보호 객체 비활성: `OBJECT_DISABLED`로 차단한다. +- 권한 매핑 없음: 호출은 성공하지만 0 rows이면 `VPD_DENY_EMPTY_RESULT`로 설명한다. +- 객체 DB 권한 없음: `ORA-00942`, `ORA-01031`을 `OBJECT_NOT_ACCESSIBLE`로 분류한다. +- Redaction 적용: 민감 컬럼 값이 NULL이면 오류가 아니라 `MASKED_COLUMN` 표시로 보여준다. +- ORDS timeout: 기본 10초 timeout, 재시도 없음. 검증 화면에서 재실행하도록 한다. +- Audit 저장 실패: 권한 변경 작업은 rollback한다. 조회 검증 audit 실패는 오류를 반환한다. + +## 10. 테스트 계획 + +- 단위 테스트: + - Token 생성 결과가 충분한 길이와 prefix를 갖고 hash만 저장되는지 검증 + - 만료/회수 token 검증 실패 + - 보호 객체 whitelist 위반 차단 + - ORDS 오류 body의 `ORA-20002`, `ORA-00942`, `ORA-01031` 분류 + - 권한 저장 command의 중복/빈 rule 검증 +- 통합 테스트: + - MyBatis Mapper XML 로딩 테스트 + - Testcontainers 사용은 Oracle 제약 때문에 초기 범위에서 제외하고 Mapper SQL은 `@MybatisTest`와 mock datasource 중심으로 검증 + - 실제 ADB 연동 테스트는 `.env`가 있는 개발 환경에서 `mvn test -Pwith-adb`로 분리 +- 화면 테스트: + - Controller slice test로 목록/저장/검증 fragment 렌더링 확인 + - 수동 smoke test로 토큰 발급, 권한 저장, ORDS 검증 실행 확인 + +## 11. 리스크 & 대안 검토 + +- 프론트 대안: + - React/Vue SPA는 화면 표현력은 높지만 빌드 체인과 API 경계가 늘어난다. + - 이 백오피스는 서버 렌더링 폼, 테이블, 결과 fragment가 중심이므로 Thymeleaf + HTMX + Alpine.js + Bootstrap 5가 가장 단순하다. +- SQL 안전성: + - 객체명 whitelist 없이 `SELECT * FROM ${table}`을 만들면 SQL injection과 권한 우회 위험이 있다. + - 따라서 ORDS 호출 대상은 `CB_PROTECTED_OBJECT`의 `ords_path`만 사용한다. +- Token 저장: + - 평문 저장은 운영 위험이 크다. + - hash 저장과 one-time display를 기본으로 한다. +- DDS: + - DDS UI까지 포함하면 범위가 커진다. + - 현재 요구는 VPD 형태와 ORDS Bearer 검증이 핵심이므로 DDS는 상태 조회/문서 연결만 후속 범위로 둔다. + +## 12. 미해결 질문 (Open Questions) + +- ORDS 검증 API는 기존 `sql/adb/22_agent_ords_security_ords_handler_setup.sql`의 Handler를 그대로 사용할지, 백오피스 전용 Handler를 추가할지 결정이 필요하다. +- 백오피스 관리자 로그인은 초기에는 local user로 둘지, 사내 인증과 연결할지 후속 결정이 필요하다. +- 컬럼 정책을 Redaction DDL까지 자동 생성할지, 관리 테이블 저장 후 DBA 적용으로 둘지 결정이 필요하다. +- 실제 구현 issue를 별도 Redmine 하위 이슈로 나눌지, #424를 Developer 단계로 계속 이동할지 결정이 필요하다. diff --git a/docs/design/424-spring-boot-vpd-backoffice/fn-issueToken.md b/docs/design/424-spring-boot-vpd-backoffice/fn-issueToken.md new file mode 100644 index 0000000..f7d8aae --- /dev/null +++ b/docs/design/424-spring-boot-vpd-backoffice/fn-issueToken.md @@ -0,0 +1,86 @@ +# 함수 설계서: `issueToken` (#424) + +> **부모 설계서**: ./README.md · **상태**: Draft +> **작성**: [AI] Architect · **구현**: `BearerTokenService.issueToken` · **테스트**: `BearerTokenServiceTest` + +## 1. 시그니처 + +```java +IssuedToken issueToken(TokenIssueCommand command) +``` + +## 2. 책임 (단일 책임, 1줄) + +활성 사용자에게 새 Bearer Token을 발급하고 DB에는 원문이 아닌 hash와 prefix만 저장한다. + +## 3. 입력 + +| 파라미터 | 타입 | 제약/검증 | 설명 | +|----------|------|-----------|------| +| `command.userId` | `long` | 활성 사용자여야 함 | 토큰 소유 사용자 | +| `command.expiresAt` | `OffsetDateTime` | 현재 시각 이후, 최대 정책 기간 이내 | 만료 시각 | +| `command.description` | `String` | 200자 이하 | 운영자 식별 설명 | + +## 4. 출력 + +- **반환**: `IssuedToken` + - `keyId`: 저장된 key id + - `prefix`: 화면 식별용 prefix + - `plainToken`: 발급 직후 한 번만 보여줄 원문 token + - `expiresAt`: 만료 시각 +- **부수효과**: `CB_AGENT_BEARER_KEY` insert, audit insert + +## 5. 동작 / 알고리즘 + +1. `userId`로 사용자를 조회하고 `active_yn = 'Y'`인지 확인한다. +2. 만료일이 현재 시각 이후인지 확인한다. +3. 정책상 최대 유효 기간을 넘으면 실패한다. +4. `vpd_live_` prefix와 256bit 이상 난수 body를 생성한다. +5. 원문 token을 조합한다. +6. server-side pepper를 읽어 HMAC-SHA256 hash를 만든다. +7. `key_prefix`, `key_hash`, `expires_at`, `created_by`를 저장한다. +8. audit log를 저장한다. +9. 원문 token을 포함한 `IssuedToken`을 반환한다. + +## 6. 에러 & 실패 모드 + +| 조건 | 처리 | 반환/예외 | +|------|------|-----------| +| 사용자가 없음 | 발급 중단 | `UserNotFoundException` | +| 비활성 사용자 | 발급 중단 | `InactiveUserException` | +| 만료일이 과거 | 발급 중단 | `InvalidTokenExpiryException` | +| pepper 설정 없음 | 발급 중단 | `TokenConfigurationException` | +| DB 저장 실패 | transaction rollback | `DataAccessException` | + +## 7. 엣지케이스 + +- 같은 사용자에게 여러 token 발급은 허용한다. +- prefix는 식별용이므로 중복 가능성을 낮추되, 인증 판단에는 hash만 사용한다. +- 원문 token은 log, audit, exception message에 포함하지 않는다. + +## 8. 복잡도 / 성능 + +- 시간 복잡도는 O(1)이다. +- 호출 빈도는 낮고 운영자 작업 단위다. +- 난수 생성은 `SecureRandom` 또는 JDK 보안 API를 사용한다. + +## 9. 의존성 + +- `UserMapper` +- `BearerTokenMapper` +- `AuditService` +- `TokenHasher` +- `Clock` + +## 10. 테스트 케이스 + +- [ ] 정상: 활성 사용자와 미래 만료일 입력 시 원문 token과 저장 id 반환 +- [ ] 정상: 저장된 값에 원문 token이 포함되지 않음 +- [ ] 실패: 비활성 사용자 입력 시 예외 +- [ ] 실패: 과거 만료일 입력 시 예외 +- [ ] 실패: pepper 설정 없음 + +## 11. 추적성 + +- 인수조건: #424의 "Bearer Token은 발급 시 원문을 한 번만 보여주고 DB에는 hash, prefix, 만료/회수 상태만 저장한다." +- 관련 ADR: 없음 diff --git a/docs/design/424-spring-boot-vpd-backoffice/fn-runProbe.md b/docs/design/424-spring-boot-vpd-backoffice/fn-runProbe.md new file mode 100644 index 0000000..419e68d --- /dev/null +++ b/docs/design/424-spring-boot-vpd-backoffice/fn-runProbe.md @@ -0,0 +1,97 @@ +# 함수 설계서: `runProbe` (#424) + +> **부모 설계서**: ./README.md · **상태**: Draft +> **작성**: [AI] Architect · **구현**: `OrdsProbeService.runProbe` · **테스트**: `OrdsProbeServiceTest` + +## 1. 시그니처 + +```java +ProbeResult runProbe(ProbeCommand command) +``` + +## 2. 책임 (단일 책임, 1줄) + +선택된 Bearer Token과 보호 객체 whitelist를 사용해 ORDS 검증 호출을 실행하고 결과 또는 오류를 분류한다. + +## 3. 입력 + +| 파라미터 | 타입 | 제약/검증 | 설명 | +|----------|------|-----------|------| +| `command.keyId` | `long` | 활성 token이어야 함 | 사용할 Bearer Token | +| `command.objectId` | `long` | 활성 보호 객체 | ORDS 호출 대상 | +| `command.limit` | `int` | 1 이상 500 이하 | 조회 행 제한 | + +## 4. 출력 + +- **반환**: `ProbeResult` + - `status`: 성공/차단/오류 분류 + - `columns`: 반환 컬럼 + - `rows`: 결과 행 + - `rowCount`: 행 수 + - `maskedColumns`: NULL 또는 마스킹으로 판단된 컬럼 + - `errorCode`, `errorMessage`: 오류 시 분류 정보 +- **부수효과**: ORDS HTTP 호출, audit insert + +## 5. 동작 / 알고리즘 + +1. `keyId`로 token 상태를 조회한다. +2. token이 만료 또는 회수 상태면 ORDS 호출 없이 실패 결과를 반환한다. +3. `objectId`로 보호 객체를 조회하고 활성 상태를 확인한다. +4. ORDS URL은 설정된 base URL과 보호 객체의 `ords_path`로 만든다. +5. 요청 timeout과 행 제한 parameter를 적용한다. +6. `Authorization: Bearer ` 헤더로 ORDS를 호출한다. +7. 2xx 응답이면 JSON을 파싱해 컬럼과 행 수를 계산한다. +8. 민감 컬럼이 모두 NULL인 경우 Redaction 적용 표시를 만든다. +9. 비 2xx 또는 Oracle 오류 body이면 `classifyProbeError`로 분류한다. +10. audit log를 저장한다. +11. 화면 표시용 `ProbeResult`를 반환한다. + +## 6. 에러 & 실패 모드 + +| 조건 | 처리 | 반환/예외 | +|------|------|-----------| +| token 없음 | ORDS 호출 안 함 | `TOKEN_NOT_FOUND` | +| token 만료/회수 | ORDS 호출 안 함 | `TOKEN_INACTIVE` | +| 보호 객체 없음/비활성 | ORDS 호출 안 함 | `OBJECT_DISABLED` | +| ORDS timeout | audit 후 오류 반환 | `ORDS_TIMEOUT` | +| `ORA-20002` | 잘못된 key로 분류 | `INVALID_TOKEN` | +| `ORA-00942` / `ORA-01031` | 객체 권한 없음으로 분류 | `OBJECT_NOT_ACCESSIBLE` | +| 2xx + 빈 rows | VPD deny 가능성 표시 | `VPD_DENY_EMPTY_RESULT` | +| 알 수 없는 오류 | 원문 일부만 표시 | `UNKNOWN_ERROR` | + +## 7. 엣지케이스 + +- 응답이 JSON이 아니면 성공으로 보지 않고 `INVALID_ORDS_RESPONSE`로 분류한다. +- 결과가 500행을 넘지 않도록 limit 기본값과 최대값을 둔다. +- audit에는 token 원문을 저장하지 않고 `key_id`, `key_prefix`만 저장한다. +- ORDS 응답 body 전체를 audit에 저장하지 않고 오류 코드와 짧은 메시지만 저장한다. + +## 8. 복잡도 / 성능 + +- 네트워크 I/O가 지배적이다. +- 기본 timeout은 10초다. +- 화면 검증 호출이므로 자동 재시도는 하지 않는다. + +## 9. 의존성 + +- `BearerTokenMapper` +- `ProtectedObjectMapper` +- `RestClient` 또는 `WebClient` +- `ProbeErrorClassifier` +- `AuditService` +- `Clock` + +## 10. 테스트 케이스 + +- [ ] 정상: 2xx JSON 응답에서 컬럼과 행 수 추출 +- [ ] 정상: 빈 rows를 VPD deny 가능성으로 표시 +- [ ] 정상: 민감 컬럼 NULL을 masked column으로 표시 +- [ ] 실패: 만료 token이면 ORDS 호출하지 않음 +- [ ] 실패: 비활성 객체이면 ORDS 호출하지 않음 +- [ ] 실패: `ORA-20002` 분류 +- [ ] 실패: timeout 분류 + +## 11. 추적성 + +- 인수조건: #424의 "ORDS 호출 검증 화면에서 Bearer Token과 보호 객체를 선택하면 ORDS 호출 결과, 반환 컬럼, 행 수, 오류 유형을 확인할 수 있다." +- 관련 ADR: 없음 diff --git a/docs/design/424-spring-boot-vpd-backoffice/fn-savePermissionSet.md b/docs/design/424-spring-boot-vpd-backoffice/fn-savePermissionSet.md new file mode 100644 index 0000000..b2a51e4 --- /dev/null +++ b/docs/design/424-spring-boot-vpd-backoffice/fn-savePermissionSet.md @@ -0,0 +1,88 @@ +# 함수 설계서: `savePermissionSet` (#424) + +> **부모 설계서**: ./README.md · **상태**: Draft +> **작성**: [AI] Architect · **구현**: `PermissionService.savePermissionSet` · **테스트**: `PermissionServiceTest` + +## 1. 시그니처 + +```java +PermissionSet savePermissionSet(PermissionSetCommand command) +``` + +## 2. 책임 (단일 책임, 1줄) + +역할과 보호 객체에 대한 행 규칙 및 컬럼 표시 정책을 검증한 뒤 하나의 transaction으로 저장한다. + +## 3. 입력 + +| 파라미터 | 타입 | 제약/검증 | 설명 | +|----------|------|-----------|------| +| `command.roleId` | `long` | 존재하는 역할 | 권한을 받을 역할 | +| `command.objectId` | `long` | 활성 보호 객체 | 조회 대상 객체 | +| `command.action` | `String` | 초기 구현은 `SELECT`만 허용 | 작업 유형 | +| `command.rules` | `List` | 1개 이상, 타입별 값 검증 | 행 조건 | +| `command.visibleColumns` | `List` | 보호 객체에 등록된 컬럼만 허용 | 표시 허용 컬럼 | + +## 4. 출력 + +- **반환**: 저장된 `PermissionSet` +- **부수효과**: permission/rule/column mapping 갱신, audit insert + +## 5. 동작 / 알고리즘 + +1. 역할이 존재하는지 확인한다. +2. 보호 객체가 whitelist에 있고 활성 상태인지 확인한다. +3. action이 허용 값인지 확인한다. +4. rule 목록이 비어 있으면 실패한다. +5. rule type별 rule value 형식을 검증한다. +6. 중복 rule을 제거하거나 실패 처리한다. 초기 구현은 실패 처리한다. +7. 표시 컬럼 목록이 보호 객체 컬럼 목록의 부분집합인지 확인한다. +8. transaction 안에서 기존 권한 세트를 갱신한다. +9. audit log를 저장한다. +10. 저장된 권한 세트를 다시 조회해 반환한다. + +## 6. 에러 & 실패 모드 + +| 조건 | 처리 | 반환/예외 | +|------|------|-----------| +| 역할 없음 | 저장 중단 | `RoleNotFoundException` | +| 보호 객체 없음/비활성 | 저장 중단 | `ProtectedObjectNotFoundException` | +| 허용되지 않은 action | 저장 중단 | `InvalidPermissionActionException` | +| rule 없음 | 저장 중단 | `InvalidPermissionRuleException` | +| rule value 형식 오류 | 저장 중단 | `InvalidPermissionRuleException` | +| 컬럼 whitelist 위반 | 저장 중단 | `InvalidColumnPolicyException` | +| audit 저장 실패 | rollback | `DataAccessException` | + +## 7. 엣지케이스 + +- `ALL` rule과 다른 제한 rule이 같이 들어오면 충돌로 보고 실패한다. +- `SELF` rule은 대상 객체에 사용자 식별 컬럼이 등록되어 있어야 허용한다. +- `MY_DEPT` rule은 대상 객체에 부서 컬럼이 등록되어 있어야 허용한다. +- 컬럼 목록이 빈 값이면 민감 컬럼 표시 없음으로 처리한다. + +## 8. 복잡도 / 성능 + +- rule/column 검증은 입력 목록 크기에 대해 O(n)이다. +- 운영자 저장 작업 단위이므로 대량 처리 성능은 주요 병목이 아니다. + +## 9. 의존성 + +- `RoleMapper` +- `ProtectedObjectMapper` +- `PermissionMapper` +- `AuditService` +- `ObjectNamePolicy` + +## 10. 테스트 케이스 + +- [ ] 정상: SELECT 권한과 REGION rule 저장 +- [ ] 정상: 민감 컬럼 제외 정책 저장 +- [ ] 실패: `ALL`과 `REGION` rule 동시 입력 +- [ ] 실패: 비활성 보호 객체 입력 +- [ ] 실패: 등록되지 않은 컬럼 입력 +- [ ] 실패: audit 저장 실패 시 permission 저장 rollback + +## 11. 추적성 + +- 인수조건: #424의 "내부 사용자, 역할, 사용자-역할, 객체 권한, 행 규칙, 컬럼 표시 권한을 백오피스에서 조회하고 저장할 수 있다." +- 관련 ADR: 없음