[Developer] #619 focus POC on KB VPD permissions
This commit is contained in:
@@ -13,40 +13,40 @@
|
|||||||
- `CB_AGENT_BEARER_KEY.STAKEHOLDER_USER_ID`가 `KB_STAKEHOLDERS.USER_ID`를 가리킨다. 기존 숫자형 `USER_ID`는 기존 기능 호환용 application-user bridge로 유지한다.
|
- `CB_AGENT_BEARER_KEY.STAKEHOLDER_USER_ID`가 `KB_STAKEHOLDERS.USER_ID`를 가리킨다. 기존 숫자형 `USER_ID`는 기존 기능 호환용 application-user bridge로 유지한다.
|
||||||
- 이해당사자마다 `CB_APP_USER.STAKEHOLDER_USER_ID` bridge를 하나 만들고, `KB_STAKEHOLDERS.ROLE`이 `지점장`이면 `KB_BRANCH_MANAGER_ROLE`, `설계사`이면 `KB_DESIGNER_ROLE`을 부여한다. 다른 역할은 토큰은 발급되지만 별도 권한을 추가하기 전까지 fail-closed다.
|
- 이해당사자마다 `CB_APP_USER.STAKEHOLDER_USER_ID` bridge를 하나 만들고, `KB_STAKEHOLDERS.ROLE`이 `지점장`이면 `KB_BRANCH_MANAGER_ROLE`, `설계사`이면 `KB_DESIGNER_ROLE`을 부여한다. 다른 역할은 토큰은 발급되지만 별도 권한을 추가하기 전까지 fail-closed다.
|
||||||
- VPD package는 토큰에서 `STAKEHOLDER_USER_ID`, `STAKEHOLDER_ROLE`, 정규화된 `STAKEHOLDER_CHANNEL`을 application context에 설정한다. `설계사채널 → 설계사`, `GA채널 → GA`를 정규화한다.
|
- VPD package는 토큰에서 `STAKEHOLDER_USER_ID`, `STAKEHOLDER_ROLE`, 정규화된 `STAKEHOLDER_CHANNEL`을 application context에 설정한다. `설계사채널 → 설계사`, `GA채널 → GA`를 정규화한다.
|
||||||
- `지점장` 여부는 package의 하드코딩 분기가 아니라 `KB_BRANCH_MANAGER_ROLE`의 rule value(`지점장`)와 `KB_STAKEHOLDERS.ROLE` 일치로 결정한다. 설계사 분기는 같은 방식으로 `설계사`를 확인한다.
|
- 토큰의 업무 역할은 `KB_STAKEHOLDERS`에서 한 번 해석해 context에 넣고, 그 역할에 매핑된 `CB_PERMISSION`이 어떤 조건 코드를 사용할지 결정한다. filter predicate에 업무 역할명은 직접 저장하지 않는다.
|
||||||
- `RLS_FILTER`의 문자열은 실행하지 않는다. 권한 테이블에 저장된 allowlist rule type만 predicate로 변환한다.
|
- 행 규칙은 두 가지다. allowlist condition code는 secure context 값으로 치환하고, `STATIC_SQL`은 현재 보호 객체 컬럼만 사용하는 검증된 정적 WHERE 조건식을 그대로 추가한다.
|
||||||
|
|
||||||
## 3. 예제 권한
|
## 3. 예제 권한
|
||||||
|
|
||||||
| 역할 | 보호 객체 | 행 규칙 | 결과 |
|
| 역할 | 보호 객체 | 행 규칙 | 결과 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| KB_DESIGNER_ROLE | KB_CONTRACTS | `FC_ID / STAKEHOLDER_SELF` | 본인 담당 계약 |
|
| KB_DESIGNER_ROLE | KB_CONTRACTS | `FC_ID / OWN_CONTRACT` | 본인 담당 계약 |
|
||||||
| KB_BRANCH_MANAGER_ROLE | KB_CONTRACTS | `FC_CHANNEL / STAKEHOLDER_CHANNEL` | 같은 채널 계약 |
|
| KB_BRANCH_MANAGER_ROLE | KB_CONTRACTS | `FC_CHANNEL / CHANNEL_CONTRACT` | 같은 채널 계약 |
|
||||||
| KB_DESIGNER_ROLE | 고객·담보·청구·외부보유 | `CUST_ID / STAKEHOLDER_SELF` | 본인 담당 고객 기준 |
|
| KB_DESIGNER_ROLE | 고객·담보·청구·외부보유 | `CUST_ID / OWN_CUSTOMER` | 본인 담당 고객 기준 |
|
||||||
| KB_BRANCH_MANAGER_ROLE | 고객·담보·청구·외부보유 | `CUST_ID / STAKEHOLDER_CHANNEL` | 같은 채널 고객 기준 |
|
| KB_BRANCH_MANAGER_ROLE | 고객·담보·청구·외부보유 | `CUST_ID / CHANNEL_CUSTOMER` | 같은 채널 고객 기준 |
|
||||||
| 두 역할 | KB_STAKEHOLDERS | `USER_ID / STAKEHOLDER_SELF` | 토큰 주체 자신의 매핑 행 |
|
| KB_STAKEHOLDER_IDENTITY_ROLE | KB_STAKEHOLDERS | `USER_ID / TOKEN_SUBJECT` | 토큰 주체 자신의 매핑 행 |
|
||||||
|
|
||||||
`STAKEHOLDER_SELF`와 `STAKEHOLDER_CHANNEL`은 접근 규칙 화면에서 선택 가능한 행 규칙이다. 두 규칙에는 대상 컬럼과 업무 역할값을 함께 저장한다. Bearer Token을 해석할 때 `KB_STAKEHOLDERS`를 한 번만 조회해 `STAKEHOLDER_USER_ID`, `STAKEHOLDER_ROLE`, `STAKEHOLDER_CHANNEL` secure context를 만들고, 이후 `FC_ID`/`FC_CHANNEL`과 `CUST_ID`/`CONTRACT_NO` 조건은 계약원장과 그 context만 사용한다. 허용된 역할별 분기는 **OR**로 합쳐진다.
|
`OWN_CONTRACT`, `CHANNEL_CONTRACT`, `OWN_CUSTOMER`, `CHANNEL_CUSTOMER`, `TOKEN_SUBJECT`는 접근 규칙 화면에서 선택하는 filter 조건 코드다. Bearer Token을 해석할 때 `KB_STAKEHOLDERS`를 한 번만 조회해 `STAKEHOLDER_USER_ID`, `STAKEHOLDER_ROLE`, `STAKEHOLDER_CHANNEL` secure context를 만들고, VPD filter가 condition code를 대상 object의 WHERE 조각으로 확장한다.
|
||||||
|
|
||||||
|
각 접근 규칙 안에서는 치환된 condition code와 `STATIC_SQL`을 **AND**로 합친다. 예를 들어 `본인 담당 고객 / CUST_ID`에 `CONTRACT_STATUS = '정상'` 정적 조건을 더하면, 담당 고객이면서 상태가 정상인 행만 허용된다. 서로 다른 ALLOW 권한(역할/권한 세트)은 **OR**, DENY 권한은 최종 허용 결과에서 제외한다. `STATIC_SQL`은 세미콜론·주석·서브쿼리·다른 테이블 참조를 차단하고 현재 보호 객체의 컬럼과 allowlist SQL 연산자만 허용한다.
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- 고객·청구·외부보유의 CUST_ID 예시: 역할별 허용 분기
|
-- 고객·청구·외부보유의 CUST_ID 예시: permission condition code 확장 결과
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM POC_2.KB_CONTRACTS c
|
FROM POC_2.KB_CONTRACTS c
|
||||||
WHERE c.CUST_ID = <현재행>.CUST_ID
|
WHERE c.CUST_ID = <현재행>.CUST_ID
|
||||||
AND SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_ROLE') = '설계사'
|
|
||||||
AND c.FC_ID = SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_USER_ID')
|
AND c.FC_ID = SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_USER_ID')
|
||||||
)
|
)
|
||||||
OR EXISTS (
|
OR EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM POC_2.KB_CONTRACTS c
|
FROM POC_2.KB_CONTRACTS c
|
||||||
WHERE c.CUST_ID = <현재행>.CUST_ID
|
WHERE c.CUST_ID = <현재행>.CUST_ID
|
||||||
AND SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_ROLE') = '지점장'
|
|
||||||
AND c.FC_CHANNEL = SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_CHANNEL')
|
AND c.FC_CHANNEL = SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_CHANNEL')
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
위의 `'설계사'`/`'지점장'`은 함수 하드코딩이 아니라 각 permission rule의 `rule_value`에서 온다. 토큰 원문은 predicate에 노출하지 않으며, `CB_AGENT_BEARER_KEY → KB_STAKEHOLDERS`에서 한 번 검증·해석한 subject context만 사용한다.
|
어느 분기를 사용할지는 token context에 매핑된 effective role의 permission이 결정한다. 토큰 원문은 predicate에 노출하지 않으며, `CB_AGENT_BEARER_KEY → KB_STAKEHOLDERS`에서 한 번 검증·해석한 subject context만 사용한다.
|
||||||
|
|
||||||
## 4. CLS / NULL 처리
|
## 4. CLS / NULL 처리
|
||||||
|
|
||||||
@@ -85,6 +85,7 @@ Bearer Token → CB_AGENT_CTX_PKG → application context
|
|||||||
- [x] 설계사 역할은 `FC_ID = stakeholder user_id`와 담당 고객 범위 규칙으로 등록된다.
|
- [x] 설계사 역할은 `FC_ID = stakeholder user_id`와 담당 고객 범위 규칙으로 등록된다.
|
||||||
- [x] 지정 7개 민감 컬럼은 원문 표시 예외가 없으면 NULL 처리하는 VPD column policy가 등록된다.
|
- [x] 지정 7개 민감 컬럼은 원문 표시 예외가 없으면 NULL 처리하는 VPD column policy가 등록된다.
|
||||||
- [x] 토큰 없음·역할 없음·지원하지 않는 rule mapping은 VPD filter에서 `1 = 0`으로 fail-closed 된다.
|
- [x] 토큰 없음·역할 없음·지원하지 않는 rule mapping은 VPD filter에서 `1 = 0`으로 fail-closed 된다.
|
||||||
|
- [x] 조건 코드와 정적 SQL 조건은 같은 접근 규칙 안에서 AND로 결합되며, 정적 SQL은 단일 안전 조건식만 저장할 수 있다.
|
||||||
|
|
||||||
## 7. 검증 계획
|
## 7. 검증 계획
|
||||||
|
|
||||||
@@ -103,5 +104,5 @@ Bearer Token → CB_AGENT_CTX_PKG → application context
|
|||||||
## 8. 리스크와 제한
|
## 8. 리스크와 제한
|
||||||
|
|
||||||
- `CB_APP_USER`는 업무 원장이 아니라 permission engine용 bridge다. 업무 identity의 원천은 반드시 `KB_STAKEHOLDERS`다.
|
- `CB_APP_USER`는 업무 원장이 아니라 permission engine용 bridge다. 업무 identity의 원천은 반드시 `KB_STAKEHOLDERS`다.
|
||||||
- `RLS_FILTER`는 사람이 읽는 설명으로만 보존한다. 저장된 SQL을 실행하면 rule injection 위험이 있으므로 사용하지 않는다.
|
- 정적 SQL은 백오피스 검증과 VPD 함수의 이중 검증을 통과한 단일 조건식만 사용한다. 임의 SQL, 주석, 바인드, 서브쿼리, 다른 테이블 참조는 허용하지 않는다.
|
||||||
- 지점장의 “집계만” 요구는 원시 행 조회를 완전히 차단하는 별도 집계 View/ORDS endpoint가 필요하다. 이번 예제는 채널 행 범위와 민감 컬럼 NULL 처리까지 구현하며, 집계 전용 endpoint는 후속 범위다.
|
- 지점장의 “집계만” 요구는 원시 행 조회를 완전히 차단하는 별도 집계 View/ORDS endpoint가 필요하다. 이번 예제는 채널 행 범위와 민감 컬럼 NULL 처리까지 구현하며, 집계 전용 endpoint는 후속 범위다.
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ CREATE TABLE cb_permission_rule (
|
|||||||
perm_id NUMBER NOT NULL REFERENCES cb_permission(perm_id),
|
perm_id NUMBER NOT NULL REFERENCES cb_permission(perm_id),
|
||||||
rule_column VARCHAR2(128),
|
rule_column VARCHAR2(128),
|
||||||
rule_type VARCHAR2(30) NOT NULL,
|
rule_type VARCHAR2(30) NOT NULL,
|
||||||
rule_value VARCHAR2(100)
|
rule_value VARCHAR2(1000 CHAR)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE cb_permission_column (
|
CREATE TABLE cb_permission_column (
|
||||||
|
|||||||
@@ -6,8 +6,9 @@
|
|||||||
-- CB_AGENT_CTX.USER_ID -> direct CB_USER_ROLE + group CB_USER_GROUP/CB_GROUP_ROLE
|
-- CB_AGENT_CTX.USER_ID -> direct CB_USER_ROLE + group CB_USER_GROUP/CB_GROUP_ROLE
|
||||||
-- -> CB_PERMISSION -> CB_PERMISSION_RULE
|
-- -> CB_PERMISSION -> CB_PERMISSION_RULE
|
||||||
-- and builds a row predicate for the object passed by DBMS_RLS.
|
-- and builds a row predicate for the object passed by DBMS_RLS.
|
||||||
-- Supported rule types include TAG. TAG matches a token in a comma-delimited
|
-- A permission's rules are combined with AND. Separate ALLOW permissions are
|
||||||
-- TECH_TAG value, allowing the vector search view to remain one row per chunk.
|
-- combined with OR and DENY permissions are subtracted at the end. This lets
|
||||||
|
-- a token-context condition be narrowed by a literal static SQL condition.
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
||||||
SET ECHO OFF
|
SET ECHO OFF
|
||||||
@@ -27,6 +28,10 @@ AS
|
|||||||
v_target VARCHAR2(128);
|
v_target VARCHAR2(128);
|
||||||
v_allow_predicate VARCHAR2(32767);
|
v_allow_predicate VARCHAR2(32767);
|
||||||
v_deny_predicate VARCHAR2(32767);
|
v_deny_predicate VARCHAR2(32767);
|
||||||
|
v_active_permission_id NUMBER;
|
||||||
|
v_active_permission_effect VARCHAR2(10);
|
||||||
|
v_permission_predicate VARCHAR2(32767);
|
||||||
|
v_permission_invalid BOOLEAN := FALSE;
|
||||||
|
|
||||||
FUNCTION quote_literal(p_value IN VARCHAR2) RETURN VARCHAR2 IS
|
FUNCTION quote_literal(p_value IN VARCHAR2) RETURN VARCHAR2 IS
|
||||||
BEGIN
|
BEGIN
|
||||||
@@ -64,7 +69,79 @@ AS
|
|||||||
RETURN NULL;
|
RETURN NULL;
|
||||||
END;
|
END;
|
||||||
|
|
||||||
PROCEDURE append_or(p_effect IN VARCHAR2, p_clause IN VARCHAR2) IS
|
FUNCTION safe_static_predicate(
|
||||||
|
p_owner IN VARCHAR2,
|
||||||
|
p_table_name IN VARCHAR2,
|
||||||
|
p_predicate IN VARCHAR2
|
||||||
|
) RETURN VARCHAR2 IS
|
||||||
|
v_predicate VARCHAR2(32767) := TRIM(p_predicate);
|
||||||
|
v_lexical VARCHAR2(32767);
|
||||||
|
v_token VARCHAR2(128);
|
||||||
|
v_index PLS_INTEGER := 1;
|
||||||
|
v_count NUMBER;
|
||||||
|
v_has_column BOOLEAN := FALSE;
|
||||||
|
BEGIN
|
||||||
|
-- STATIC_SQL is intentionally an expression only: no statement
|
||||||
|
-- separator/comment/bind/database-link/qualified object is accepted.
|
||||||
|
-- Every identifier outside a string literal must be a column of the
|
||||||
|
-- protected object or an allowlisted SQL operator/function.
|
||||||
|
IF v_predicate IS NULL
|
||||||
|
OR LENGTH(v_predicate) > 1000
|
||||||
|
OR INSTR(v_predicate, ';') > 0
|
||||||
|
OR INSTR(v_predicate, '--') > 0
|
||||||
|
OR INSTR(v_predicate, '/*') > 0
|
||||||
|
OR INSTR(v_predicate, '*/') > 0
|
||||||
|
OR INSTR(v_predicate, ':') > 0
|
||||||
|
OR INSTR(v_predicate, '@') > 0
|
||||||
|
OR INSTR(v_predicate, '||') > 0
|
||||||
|
OR REGEXP_LIKE(v_predicate, '[[:cntrl:]]')
|
||||||
|
OR MOD(LENGTH(v_predicate) - LENGTH(REPLACE(v_predicate, '''', '')), 2) <> 0 THEN
|
||||||
|
RETURN NULL;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Replace SQL string literals before extracting identifiers. Doubled
|
||||||
|
-- quotes in a literal are supported by the expression below.
|
||||||
|
v_lexical := REGEXP_REPLACE(v_predicate, '''(''''|[^''])*''', ' ');
|
||||||
|
IF REGEXP_LIKE(v_lexical, '[[:alpha:]_$#][[:alnum:]_$#]*[[:space:]]*\.') THEN
|
||||||
|
RETURN NULL;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
LOOP
|
||||||
|
v_token := UPPER(REGEXP_SUBSTR(
|
||||||
|
v_lexical, '[[:alpha:]_$#][[:alnum:]_$#]*', 1, v_index
|
||||||
|
));
|
||||||
|
EXIT WHEN v_token IS NULL;
|
||||||
|
v_index := v_index + 1;
|
||||||
|
|
||||||
|
IF v_token IN (
|
||||||
|
'AND', 'OR', 'NOT', 'NULL', 'IS', 'IN', 'LIKE', 'BETWEEN', 'ESCAPE',
|
||||||
|
'TRUE', 'FALSE', 'UPPER', 'LOWER', 'TRIM', 'NVL', 'COALESCE',
|
||||||
|
'TO_CHAR', 'TO_DATE', 'REGEXP_LIKE', 'INSTR', 'LENGTH', 'SUBSTR',
|
||||||
|
'REPLACE', 'CAST', 'AS', 'DATE', 'TIMESTAMP', 'NUMBER', 'VARCHAR2', 'CHAR'
|
||||||
|
) THEN
|
||||||
|
NULL;
|
||||||
|
ELSE
|
||||||
|
SELECT COUNT(*)
|
||||||
|
INTO v_count
|
||||||
|
FROM all_tab_columns
|
||||||
|
WHERE owner = UPPER(p_owner)
|
||||||
|
AND table_name = UPPER(p_table_name)
|
||||||
|
AND column_name = v_token;
|
||||||
|
|
||||||
|
IF v_count = 0 THEN
|
||||||
|
RETURN NULL;
|
||||||
|
END IF;
|
||||||
|
v_has_column := TRUE;
|
||||||
|
END IF;
|
||||||
|
END LOOP;
|
||||||
|
|
||||||
|
RETURN CASE WHEN v_has_column THEN v_predicate ELSE NULL END;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN
|
||||||
|
RETURN NULL;
|
||||||
|
END;
|
||||||
|
|
||||||
|
PROCEDURE append_permission(p_effect IN VARCHAR2, p_clause IN VARCHAR2) IS
|
||||||
BEGIN
|
BEGIN
|
||||||
IF p_clause IS NULL THEN
|
IF p_clause IS NULL THEN
|
||||||
RETURN;
|
RETURN;
|
||||||
@@ -72,16 +149,39 @@ AS
|
|||||||
|
|
||||||
IF p_effect = 'DENY' THEN
|
IF p_effect = 'DENY' THEN
|
||||||
IF v_deny_predicate IS NULL THEN
|
IF v_deny_predicate IS NULL THEN
|
||||||
v_deny_predicate := '(' || p_clause || ')';
|
v_deny_predicate := p_clause;
|
||||||
ELSE
|
ELSE
|
||||||
v_deny_predicate := v_deny_predicate || ' OR (' || p_clause || ')';
|
v_deny_predicate := '(' || v_deny_predicate || ') OR (' || p_clause || ')';
|
||||||
END IF;
|
END IF;
|
||||||
ELSIF v_allow_predicate IS NULL THEN
|
ELSIF v_allow_predicate IS NULL THEN
|
||||||
v_allow_predicate := '(' || p_clause || ')';
|
v_allow_predicate := p_clause;
|
||||||
ELSE
|
ELSE
|
||||||
v_allow_predicate := v_allow_predicate || ' OR (' || p_clause || ')';
|
v_allow_predicate := '(' || v_allow_predicate || ') OR (' || p_clause || ')';
|
||||||
END IF;
|
END IF;
|
||||||
END;
|
END;
|
||||||
|
|
||||||
|
PROCEDURE append_rule(p_clause IN VARCHAR2) IS
|
||||||
|
BEGIN
|
||||||
|
IF p_clause IS NULL THEN
|
||||||
|
v_permission_invalid := TRUE;
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF v_permission_predicate IS NULL THEN
|
||||||
|
v_permission_predicate := p_clause;
|
||||||
|
ELSE
|
||||||
|
v_permission_predicate := '(' || v_permission_predicate || ') AND (' || p_clause || ')';
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
|
||||||
|
PROCEDURE flush_permission IS
|
||||||
|
BEGIN
|
||||||
|
IF NOT v_permission_invalid THEN
|
||||||
|
append_permission(v_active_permission_effect, v_permission_predicate);
|
||||||
|
END IF;
|
||||||
|
v_permission_predicate := NULL;
|
||||||
|
v_permission_invalid := FALSE;
|
||||||
|
END;
|
||||||
BEGIN
|
BEGIN
|
||||||
BEGIN
|
BEGIN
|
||||||
v_user_id := TO_NUMBER(SYS_CONTEXT('CB_AGENT_CTX', 'USER_ID'));
|
v_user_id := TO_NUMBER(SYS_CONTEXT('CB_AGENT_CTX', 'USER_ID'));
|
||||||
@@ -111,7 +211,8 @@ BEGIN
|
|||||||
ON gr.group_id = ug.group_id
|
ON gr.group_id = ug.group_id
|
||||||
WHERE ug.user_id = v_user_id
|
WHERE ug.user_id = v_user_id
|
||||||
)
|
)
|
||||||
SELECT UPPER(TRIM(r.rule_type)) AS rule_type,
|
SELECT p.perm_id,
|
||||||
|
UPPER(TRIM(r.rule_type)) AS rule_type,
|
||||||
UPPER(TRIM(r.rule_column)) AS rule_column,
|
UPPER(TRIM(r.rule_column)) AS rule_column,
|
||||||
NVL(UPPER(TRIM(p.permission_effect)), 'ALLOW') AS permission_effect,
|
NVL(UPPER(TRIM(p.permission_effect)), 'ALLOW') AS permission_effect,
|
||||||
r.rule_value
|
r.rule_value
|
||||||
@@ -122,148 +223,135 @@ BEGIN
|
|||||||
ON r.perm_id = p.perm_id
|
ON r.perm_id = p.perm_id
|
||||||
WHERE p.target_name = v_target
|
WHERE p.target_name = v_target
|
||||||
AND p.action_name = 'SELECT'
|
AND p.action_name = 'SELECT'
|
||||||
ORDER BY r.rule_id
|
ORDER BY p.perm_id, r.rule_id
|
||||||
) LOOP
|
) LOOP
|
||||||
|
IF v_active_permission_id IS NULL THEN
|
||||||
|
v_active_permission_id := r.perm_id;
|
||||||
|
v_active_permission_effect := r.permission_effect;
|
||||||
|
ELSIF v_active_permission_id <> r.perm_id THEN
|
||||||
|
flush_permission;
|
||||||
|
v_active_permission_id := r.perm_id;
|
||||||
|
v_active_permission_effect := r.permission_effect;
|
||||||
|
END IF;
|
||||||
|
|
||||||
DECLARE
|
DECLARE
|
||||||
v_column VARCHAR2(128);
|
v_column VARCHAR2(128);
|
||||||
|
v_static_predicate VARCHAR2(32767);
|
||||||
BEGIN
|
BEGIN
|
||||||
IF r.rule_type = 'ALL' THEN
|
IF r.rule_type = 'ALL' THEN
|
||||||
IF r.permission_effect = 'DENY' THEN
|
append_rule('1 = 1');
|
||||||
RETURN '1 = 0';
|
|
||||||
END IF;
|
|
||||||
v_allow_predicate := '(1 = 1)';
|
|
||||||
ELSIF r.rule_type = 'MY_DEPT' THEN
|
ELSIF r.rule_type = 'MY_DEPT' THEN
|
||||||
-- NULL rule_column keeps backward compatibility with the demo seed.
|
-- NULL rule_column keeps backward compatibility with the demo seed.
|
||||||
v_column := safe_column(p_schema, p_object, NVL(r.rule_column, 'DEPT_CODE'));
|
v_column := safe_column(p_schema, p_object, NVL(r.rule_column, 'DEPT_CODE'));
|
||||||
IF v_column IS NOT NULL THEN
|
IF v_column IS NOT NULL THEN
|
||||||
append_or(r.permission_effect, v_column || ' = SYS_CONTEXT(''CB_AGENT_CTX'', ''DEPT_CODE'')');
|
append_rule(v_column || ' = SYS_CONTEXT(''CB_AGENT_CTX'', ''DEPT_CODE'')');
|
||||||
END IF;
|
END IF;
|
||||||
ELSIF r.rule_type = 'SELF' THEN
|
ELSIF r.rule_type = 'SELF' THEN
|
||||||
-- NULL rule_column keeps backward compatibility with the demo seed.
|
-- NULL rule_column keeps backward compatibility with the demo seed.
|
||||||
v_column := safe_column(p_schema, p_object, NVL(r.rule_column, 'OWNER_EMP_NO'));
|
v_column := safe_column(p_schema, p_object, NVL(r.rule_column, 'OWNER_EMP_NO'));
|
||||||
IF v_column IS NOT NULL THEN
|
IF v_column IS NOT NULL THEN
|
||||||
append_or(r.permission_effect, v_column || ' = SYS_CONTEXT(''CB_AGENT_CTX'', ''EMP_NO'')');
|
append_rule(v_column || ' = SYS_CONTEXT(''CB_AGENT_CTX'', ''EMP_NO'')');
|
||||||
END IF;
|
END IF;
|
||||||
ELSIF r.rule_type = 'DEPT' THEN
|
ELSIF r.rule_type = 'DEPT' THEN
|
||||||
v_column := safe_column(p_schema, p_object, NVL(r.rule_column, 'DEPT_CODE'));
|
v_column := safe_column(p_schema, p_object, NVL(r.rule_column, 'DEPT_CODE'));
|
||||||
IF v_column IS NOT NULL THEN
|
IF v_column IS NOT NULL THEN
|
||||||
append_or(r.permission_effect, v_column || ' = ' || quote_literal(r.rule_value));
|
append_rule(v_column || ' = ' || quote_literal(r.rule_value));
|
||||||
END IF;
|
END IF;
|
||||||
ELSIF r.rule_type = 'EMP_NO' THEN
|
ELSIF r.rule_type = 'EMP_NO' THEN
|
||||||
v_column := safe_column(p_schema, p_object, NVL(r.rule_column, 'OWNER_EMP_NO'));
|
v_column := safe_column(p_schema, p_object, NVL(r.rule_column, 'OWNER_EMP_NO'));
|
||||||
IF v_column IS NOT NULL THEN
|
IF v_column IS NOT NULL THEN
|
||||||
append_or(r.permission_effect, v_column || ' = ' || quote_literal(r.rule_value));
|
append_rule(v_column || ' = ' || quote_literal(r.rule_value));
|
||||||
END IF;
|
END IF;
|
||||||
ELSIF r.rule_type = 'TAG' THEN
|
ELSIF r.rule_type = 'TAG' THEN
|
||||||
-- A TAG rule defaults to the ingestion metadata column TECH_TAG.
|
-- A TAG rule defaults to the ingestion metadata column TECH_TAG.
|
||||||
-- Multiple ALLOW TAG rows are accumulated by append_or, so any
|
|
||||||
-- matching tag grants visibility. DENY rows are accumulated
|
|
||||||
-- separately and removed from the final result.
|
|
||||||
v_column := safe_column(p_schema, p_object, NVL(r.rule_column, 'TECH_TAG'));
|
v_column := safe_column(p_schema, p_object, NVL(r.rule_column, 'TECH_TAG'));
|
||||||
IF v_column IS NOT NULL
|
IF v_column IS NOT NULL
|
||||||
AND r.rule_value IS NOT NULL
|
AND r.rule_value IS NOT NULL
|
||||||
AND REGEXP_LIKE(TRIM(r.rule_value), '^[A-Za-z0-9_-]+$') THEN
|
AND REGEXP_LIKE(TRIM(r.rule_value), '^[A-Za-z0-9_-]+$') THEN
|
||||||
append_or(
|
append_rule(
|
||||||
r.permission_effect,
|
|
||||||
'REGEXP_LIKE(UPPER(' || v_column || '), ' ||
|
'REGEXP_LIKE(UPPER(' || v_column || '), ' ||
|
||||||
quote_literal('(^|,)' || UPPER(TRIM(r.rule_value)) || '(,|$)') || ')'
|
quote_literal('(^|,)' || UPPER(TRIM(r.rule_value)) || '(,|$)') || ')'
|
||||||
);
|
);
|
||||||
END IF;
|
END IF;
|
||||||
ELSIF r.rule_type IN ('STAKEHOLDER_SELF', 'STAKEHOLDER_CHANNEL') THEN
|
ELSIF r.rule_type IN (
|
||||||
-- CB_AGENT_CTX_PKG가 Bearer token -> KB_STAKEHOLDERS를 한 번
|
'TOKEN_SUBJECT', 'OWN_CONTRACT', 'CHANNEL_CONTRACT', 'OWN_CUSTOMER', 'CHANNEL_CUSTOMER'
|
||||||
-- 해석해 USER_ID/ROLE/CHANNEL을 secure context로 설정한다.
|
) THEN
|
||||||
-- 따라서 보호 테이블 predicate에서 KB_STAKEHOLDERS를 다시 조인하지
|
-- 백오피스 권한 설정의 조건 코드를 안전한 WHERE 조각으로 확장한다.
|
||||||
-- 않고, 해당 context와 KB_CONTRACTS 관계만 사용한다.
|
-- token -> stakeholder 해석은 context package가 한 번만 수행하며,
|
||||||
|
-- VPD filter는 context와 대상/계약원장만 쓴다.
|
||||||
v_column := safe_column(p_schema, p_object, r.rule_column);
|
v_column := safe_column(p_schema, p_object, r.rule_column);
|
||||||
IF v_column IS NOT NULL
|
IF v_column IS NOT NULL
|
||||||
AND UPPER(p_schema) = 'POC_2' THEN
|
AND UPPER(p_schema) = 'POC_2' THEN
|
||||||
DECLARE
|
IF r.rule_type = 'TOKEN_SUBJECT'
|
||||||
v_contract_key VARCHAR2(30);
|
|
||||||
v_role_condition VARCHAR2(500);
|
|
||||||
BEGIN
|
|
||||||
-- 각 ALLOW rule은 append_or로 OR 분기가 된다. rule_value는
|
|
||||||
-- 권한 설정에 저장된 해당 분기의 역할값이다. identity rule은
|
|
||||||
-- NULL value를 허용해 모든 토큰 주체가 자기 mapping을 해석한다.
|
|
||||||
v_role_condition := CASE
|
|
||||||
WHEN TRIM(r.rule_value) IS NULL THEN '1 = 1'
|
|
||||||
ELSE 'SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_ROLE'') = '
|
|
||||||
|| quote_literal(TRIM(r.rule_value))
|
|
||||||
END;
|
|
||||||
|
|
||||||
IF r.rule_type = 'STAKEHOLDER_SELF'
|
|
||||||
AND v_target = 'KB_CONTRACTS'
|
|
||||||
AND v_column = 'FC_ID' THEN
|
|
||||||
append_or(
|
|
||||||
r.permission_effect,
|
|
||||||
'(' || v_role_condition ||
|
|
||||||
' AND FC_ID = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_USER_ID''))'
|
|
||||||
);
|
|
||||||
ELSIF r.rule_type = 'STAKEHOLDER_CHANNEL'
|
|
||||||
AND v_target = 'KB_CONTRACTS'
|
|
||||||
AND v_column = 'FC_CHANNEL' THEN
|
|
||||||
append_or(
|
|
||||||
r.permission_effect,
|
|
||||||
'(' || v_role_condition ||
|
|
||||||
' AND FC_CHANNEL = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_CHANNEL''))'
|
|
||||||
);
|
|
||||||
ELSIF r.rule_type = 'STAKEHOLDER_SELF'
|
|
||||||
AND v_target = 'KB_STAKEHOLDERS'
|
AND v_target = 'KB_STAKEHOLDERS'
|
||||||
AND v_column = 'USER_ID' THEN
|
AND v_column = 'USER_ID' THEN
|
||||||
append_or(
|
append_rule(
|
||||||
r.permission_effect,
|
'USER_ID = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_USER_ID'')'
|
||||||
'(' || v_role_condition ||
|
|
||||||
' AND USER_ID = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_USER_ID''))'
|
|
||||||
);
|
);
|
||||||
ELSIF v_target IN ('KB_CUSTOMERS', 'KB_COVERAGES', 'KB_CLAIMS', 'KB_EXTERNAL_HOLDINGS')
|
ELSIF r.rule_type = 'OWN_CONTRACT'
|
||||||
|
AND v_target = 'KB_CONTRACTS'
|
||||||
|
AND v_column = 'FC_ID' THEN
|
||||||
|
append_rule(
|
||||||
|
'FC_ID = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_USER_ID'')'
|
||||||
|
);
|
||||||
|
ELSIF r.rule_type = 'CHANNEL_CONTRACT'
|
||||||
|
AND v_target = 'KB_CONTRACTS'
|
||||||
|
AND v_column = 'FC_CHANNEL' THEN
|
||||||
|
append_rule(
|
||||||
|
'FC_CHANNEL = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_CHANNEL'')'
|
||||||
|
);
|
||||||
|
ELSIF r.rule_type IN ('OWN_CUSTOMER', 'CHANNEL_CUSTOMER')
|
||||||
|
AND v_target IN ('KB_CUSTOMERS', 'KB_COVERAGES', 'KB_CLAIMS', 'KB_EXTERNAL_HOLDINGS')
|
||||||
AND v_column IN ('CUST_ID', 'CONTRACT_NO') THEN
|
AND v_column IN ('CUST_ID', 'CONTRACT_NO') THEN
|
||||||
|
DECLARE
|
||||||
|
v_contract_key VARCHAR2(30);
|
||||||
|
v_subject_filter VARCHAR2(500);
|
||||||
|
BEGIN
|
||||||
v_contract_key := CASE v_column
|
v_contract_key := CASE v_column
|
||||||
WHEN 'CUST_ID' THEN 'CUST_ID'
|
WHEN 'CUST_ID' THEN 'CUST_ID'
|
||||||
WHEN 'CONTRACT_NO' THEN 'CONTRACT_NO'
|
WHEN 'CONTRACT_NO' THEN 'CONTRACT_NO'
|
||||||
END;
|
END;
|
||||||
IF r.rule_type = 'STAKEHOLDER_SELF' THEN
|
v_subject_filter := CASE r.rule_type
|
||||||
append_or(
|
WHEN 'OWN_CUSTOMER' THEN
|
||||||
r.permission_effect,
|
'c.FC_ID = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_USER_ID'')'
|
||||||
'EXISTS (SELECT 1 FROM (' ||
|
|
||||||
'SELECT c.' || v_contract_key || ' AS access_key ' ||
|
|
||||||
'FROM POC_2.KB_CONTRACTS c ' ||
|
|
||||||
'WHERE ' || v_role_condition ||
|
|
||||||
' AND c.FC_ID = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_USER_ID'')) allowed_contract ' ||
|
|
||||||
'WHERE allowed_contract.access_key = ' || v_column || ')'
|
|
||||||
);
|
|
||||||
ELSE
|
ELSE
|
||||||
append_or(
|
'c.FC_CHANNEL = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_CHANNEL'')'
|
||||||
r.permission_effect,
|
END;
|
||||||
'EXISTS (SELECT 1 FROM (' ||
|
append_rule(
|
||||||
'SELECT c.' || v_contract_key || ' AS access_key ' ||
|
'EXISTS (SELECT 1 FROM (SELECT c.' || v_contract_key || ' AS access_key ' ||
|
||||||
'FROM POC_2.KB_CONTRACTS c ' ||
|
'FROM POC_2.KB_CONTRACTS c WHERE ' || v_subject_filter ||
|
||||||
'WHERE ' || v_role_condition ||
|
') allowed_contract WHERE allowed_contract.access_key = ' || v_column || ')'
|
||||||
' AND c.FC_CHANNEL = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_CHANNEL'')) allowed_contract ' ||
|
|
||||||
'WHERE allowed_contract.access_key = ' || v_column || ')'
|
|
||||||
);
|
);
|
||||||
END IF;
|
|
||||||
END IF;
|
|
||||||
END;
|
END;
|
||||||
END IF;
|
END IF;
|
||||||
|
END IF;
|
||||||
|
ELSIF r.rule_type = 'STATIC_SQL' THEN
|
||||||
|
v_static_predicate := safe_static_predicate(p_schema, p_object, r.rule_value);
|
||||||
|
append_rule(v_static_predicate);
|
||||||
ELSIF r.rule_type = '=' THEN
|
ELSIF r.rule_type = '=' THEN
|
||||||
v_column := safe_column(p_schema, p_object, r.rule_column);
|
v_column := safe_column(p_schema, p_object, r.rule_column);
|
||||||
IF v_column IS NOT NULL THEN
|
IF v_column IS NOT NULL THEN
|
||||||
append_or(r.permission_effect, 'TO_CHAR(' || v_column || ') = ' || quote_literal(r.rule_value));
|
append_rule('TO_CHAR(' || v_column || ') = ' || quote_literal(r.rule_value));
|
||||||
END IF;
|
END IF;
|
||||||
ELSIF r.rule_type IN ('!=', '<>') THEN
|
ELSIF r.rule_type IN ('!=', '<>') THEN
|
||||||
v_column := safe_column(p_schema, p_object, r.rule_column);
|
v_column := safe_column(p_schema, p_object, r.rule_column);
|
||||||
IF v_column IS NOT NULL THEN
|
IF v_column IS NOT NULL THEN
|
||||||
append_or(r.permission_effect, 'TO_CHAR(' || v_column || ') <> ' || quote_literal(r.rule_value));
|
append_rule('TO_CHAR(' || v_column || ') <> ' || quote_literal(r.rule_value));
|
||||||
END IF;
|
END IF;
|
||||||
|
ELSE
|
||||||
|
append_rule(NULL);
|
||||||
END IF;
|
END IF;
|
||||||
END;
|
END;
|
||||||
END LOOP;
|
END LOOP;
|
||||||
|
|
||||||
|
flush_permission;
|
||||||
|
|
||||||
IF v_allow_predicate IS NULL THEN
|
IF v_allow_predicate IS NULL THEN
|
||||||
RETURN '1 = 0';
|
RETURN '1 = 0';
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
IF v_deny_predicate IS NULL THEN
|
IF v_deny_predicate IS NULL THEN
|
||||||
IF v_allow_predicate = '(1 = 1)' THEN
|
IF v_allow_predicate = '1 = 1' THEN
|
||||||
RETURN '1 = 1';
|
RETURN '1 = 1';
|
||||||
END IF;
|
END IF;
|
||||||
RETURN '(' || v_allow_predicate || ')';
|
RETURN '(' || v_allow_predicate || ')';
|
||||||
|
|||||||
@@ -67,6 +67,12 @@ BEGIN
|
|||||||
INSERT INTO cb_app_user(user_id, user_name, employee_no, dept_code, can_read_contents, active)
|
INSERT INTO cb_app_user(user_id, user_name, employee_no, dept_code, can_read_contents, active)
|
||||||
VALUES (456112, 'rule_test_deny_only', 'E456112', 'HR', 'N', 'Y');
|
VALUES (456112, 'rule_test_deny_only', 'E456112', 'HR', 'N', 'Y');
|
||||||
|
|
||||||
|
INSERT INTO cb_app_user(user_id, user_name, employee_no, dept_code, can_read_contents, active)
|
||||||
|
VALUES (456113, 'rule_test_static_and', 'E456113', 'HR', 'N', 'Y');
|
||||||
|
|
||||||
|
INSERT INTO cb_app_user(user_id, user_name, employee_no, dept_code, can_read_contents, active)
|
||||||
|
VALUES (456114, 'rule_test_static_invalid', 'E456114', 'HR', 'N', 'Y');
|
||||||
|
|
||||||
INSERT INTO cb_app_role(role_id, role_name) VALUES (456101, 'RULE_TEST_MY_DEPT');
|
INSERT INTO cb_app_role(role_id, role_name) VALUES (456101, 'RULE_TEST_MY_DEPT');
|
||||||
INSERT INTO cb_app_role(role_id, role_name) VALUES (456102, 'RULE_TEST_SELF');
|
INSERT INTO cb_app_role(role_id, role_name) VALUES (456102, 'RULE_TEST_SELF');
|
||||||
INSERT INTO cb_app_role(role_id, role_name) VALUES (456103, 'RULE_TEST_ALL');
|
INSERT INTO cb_app_role(role_id, role_name) VALUES (456103, 'RULE_TEST_ALL');
|
||||||
@@ -79,6 +85,8 @@ BEGIN
|
|||||||
INSERT INTO cb_app_role(role_id, role_name) VALUES (456110, 'RULE_TEST_TARGET_MISMATCH');
|
INSERT INTO cb_app_role(role_id, role_name) VALUES (456110, 'RULE_TEST_TARGET_MISMATCH');
|
||||||
INSERT INTO cb_app_role(role_id, role_name) VALUES (456111, 'RULE_TEST_ALLOW_DENY');
|
INSERT INTO cb_app_role(role_id, role_name) VALUES (456111, 'RULE_TEST_ALLOW_DENY');
|
||||||
INSERT INTO cb_app_role(role_id, role_name) VALUES (456112, 'RULE_TEST_DENY_ONLY');
|
INSERT INTO cb_app_role(role_id, role_name) VALUES (456112, 'RULE_TEST_DENY_ONLY');
|
||||||
|
INSERT INTO cb_app_role(role_id, role_name) VALUES (456113, 'RULE_TEST_STATIC_AND');
|
||||||
|
INSERT INTO cb_app_role(role_id, role_name) VALUES (456114, 'RULE_TEST_STATIC_INVALID');
|
||||||
|
|
||||||
INSERT INTO cb_user_role(user_id, role_id) VALUES (456101, 456101);
|
INSERT INTO cb_user_role(user_id, role_id) VALUES (456101, 456101);
|
||||||
INSERT INTO cb_user_role(user_id, role_id) VALUES (456102, 456102);
|
INSERT INTO cb_user_role(user_id, role_id) VALUES (456102, 456102);
|
||||||
@@ -92,6 +100,8 @@ BEGIN
|
|||||||
INSERT INTO cb_user_role(user_id, role_id) VALUES (456110, 456110);
|
INSERT INTO cb_user_role(user_id, role_id) VALUES (456110, 456110);
|
||||||
INSERT INTO cb_user_role(user_id, role_id) VALUES (456111, 456111);
|
INSERT INTO cb_user_role(user_id, role_id) VALUES (456111, 456111);
|
||||||
INSERT INTO cb_user_role(user_id, role_id) VALUES (456112, 456112);
|
INSERT INTO cb_user_role(user_id, role_id) VALUES (456112, 456112);
|
||||||
|
INSERT INTO cb_user_role(user_id, role_id) VALUES (456113, 456113);
|
||||||
|
INSERT INTO cb_user_role(user_id, role_id) VALUES (456114, 456114);
|
||||||
|
|
||||||
INSERT INTO cb_permission(perm_id, role_id, target_name, action_name)
|
INSERT INTO cb_permission(perm_id, role_id, target_name, action_name)
|
||||||
VALUES (456101, 456101, 'CB_V_SEARCH_DOCUMENTS', 'SELECT');
|
VALUES (456101, 456101, 'CB_V_SEARCH_DOCUMENTS', 'SELECT');
|
||||||
@@ -132,6 +142,12 @@ BEGIN
|
|||||||
INSERT INTO cb_permission(perm_id, role_id, target_name, action_name, permission_effect)
|
INSERT INTO cb_permission(perm_id, role_id, target_name, action_name, permission_effect)
|
||||||
VALUES (456113, 456112, 'CB_V_SEARCH_DOCUMENTS', 'SELECT', 'DENY');
|
VALUES (456113, 456112, 'CB_V_SEARCH_DOCUMENTS', 'SELECT', 'DENY');
|
||||||
|
|
||||||
|
INSERT INTO cb_permission(perm_id, role_id, target_name, action_name, permission_effect)
|
||||||
|
VALUES (456114, 456113, 'CB_V_SEARCH_DOCUMENTS', 'SELECT', 'ALLOW');
|
||||||
|
|
||||||
|
INSERT INTO cb_permission(perm_id, role_id, target_name, action_name, permission_effect)
|
||||||
|
VALUES (456115, 456114, 'CB_V_SEARCH_DOCUMENTS', 'SELECT', 'ALLOW');
|
||||||
|
|
||||||
INSERT INTO cb_permission_rule(rule_id, perm_id, rule_column, rule_type, rule_value)
|
INSERT INTO cb_permission_rule(rule_id, perm_id, rule_column, rule_type, rule_value)
|
||||||
VALUES (456101, 456101, NULL, 'MY_DEPT', NULL);
|
VALUES (456101, 456101, NULL, 'MY_DEPT', NULL);
|
||||||
|
|
||||||
@@ -171,6 +187,15 @@ BEGIN
|
|||||||
INSERT INTO cb_permission_rule(rule_id, perm_id, rule_column, rule_type, rule_value)
|
INSERT INTO cb_permission_rule(rule_id, perm_id, rule_column, rule_type, rule_value)
|
||||||
VALUES (456113, 456113, 'DEPT_CODE', '=', 'HR');
|
VALUES (456113, 456113, 'DEPT_CODE', '=', 'HR');
|
||||||
|
|
||||||
|
INSERT INTO cb_permission_rule(rule_id, perm_id, rule_column, rule_type, rule_value)
|
||||||
|
VALUES (456114, 456114, 'DEPT_CODE', '=', 'HR');
|
||||||
|
|
||||||
|
INSERT INTO cb_permission_rule(rule_id, perm_id, rule_column, rule_type, rule_value)
|
||||||
|
VALUES (456115, 456114, NULL, 'STATIC_SQL', 'OWNER_EMP_NO = ''E456113''');
|
||||||
|
|
||||||
|
INSERT INTO cb_permission_rule(rule_id, perm_id, rule_column, rule_type, rule_value)
|
||||||
|
VALUES (456116, 456115, NULL, 'STATIC_SQL', 'DEPT_CODE = ''HR''; 1 = 1');
|
||||||
|
|
||||||
COMMIT;
|
COMMIT;
|
||||||
END;
|
END;
|
||||||
/
|
/
|
||||||
@@ -260,6 +285,8 @@ BEGIN
|
|||||||
assert_contains('ALLOW_ALL_MINUS_DENY_DEPT', 456111, 'AND NOT');
|
assert_contains('ALLOW_ALL_MINUS_DENY_DEPT', 456111, 'AND NOT');
|
||||||
assert_contains('ALLOW_ALL_MINUS_DENY_DEPT_DETAIL', 456111, 'TO_CHAR(DEPT_CODE) = ''HR''');
|
assert_contains('ALLOW_ALL_MINUS_DENY_DEPT_DETAIL', 456111, 'TO_CHAR(DEPT_CODE) = ''HR''');
|
||||||
assert_equals('DENY_ONLY_DENIES', 456112, '1 = 0');
|
assert_equals('DENY_ONLY_DENIES', 456112, '1 = 0');
|
||||||
|
assert_contains('CONTEXT_AND_STATIC_SQL', 456113, 'TO_CHAR(DEPT_CODE) = ''HR'') AND (OWNER_EMP_NO = ''E456113'')');
|
||||||
|
assert_equals('INVALID_STATIC_SQL_DENIES', 456114, '1 = 0');
|
||||||
cb_agent_ctx_pkg.clear_user;
|
cb_agent_ctx_pkg.clear_user;
|
||||||
END;
|
END;
|
||||||
/
|
/
|
||||||
|
|||||||
@@ -383,20 +383,18 @@ BEGIN
|
|||||||
|
|
||||||
-- 토큰은 모든 이해당사자에게 발급되지만, 아래 역할이 없는 주체는
|
-- 토큰은 모든 이해당사자에게 발급되지만, 아래 역할이 없는 주체는
|
||||||
-- 권한 레코드가 없으므로 VPD가 1=0으로 fail-closed 된다.
|
-- 권한 레코드가 없으므로 VPD가 1=0으로 fail-closed 된다.
|
||||||
ensure_permission(v_designer_role_id, 'KB_STAKEHOLDERS', 'USER_ID', 'STAKEHOLDER_SELF', '설계사');
|
|
||||||
ensure_permission(v_manager_role_id, 'KB_STAKEHOLDERS', 'USER_ID', 'STAKEHOLDER_SELF', '지점장');
|
|
||||||
ensure_permission(v_designer_role_id, 'KB_PRODUCTS', NULL, 'ALL', NULL);
|
ensure_permission(v_designer_role_id, 'KB_PRODUCTS', NULL, 'ALL', NULL);
|
||||||
ensure_permission(v_manager_role_id, 'KB_PRODUCTS', NULL, 'ALL', NULL);
|
ensure_permission(v_manager_role_id, 'KB_PRODUCTS', NULL, 'ALL', NULL);
|
||||||
ensure_permission(v_designer_role_id, 'KB_CONTRACTS', 'FC_ID', 'STAKEHOLDER_SELF', '설계사');
|
ensure_permission(v_designer_role_id, 'KB_CONTRACTS', 'FC_ID', 'OWN_CONTRACT', NULL);
|
||||||
ensure_permission(v_manager_role_id, 'KB_CONTRACTS', 'FC_CHANNEL', 'STAKEHOLDER_CHANNEL', '지점장');
|
ensure_permission(v_manager_role_id, 'KB_CONTRACTS', 'FC_CHANNEL', 'CHANNEL_CONTRACT', NULL);
|
||||||
ensure_permission(v_designer_role_id, 'KB_CUSTOMERS', 'CUST_ID', 'STAKEHOLDER_SELF', '설계사');
|
ensure_permission(v_designer_role_id, 'KB_CUSTOMERS', 'CUST_ID', 'OWN_CUSTOMER', NULL);
|
||||||
ensure_permission(v_manager_role_id, 'KB_CUSTOMERS', 'CUST_ID', 'STAKEHOLDER_CHANNEL', '지점장');
|
ensure_permission(v_manager_role_id, 'KB_CUSTOMERS', 'CUST_ID', 'CHANNEL_CUSTOMER', NULL);
|
||||||
ensure_permission(v_designer_role_id, 'KB_COVERAGES', 'CUST_ID', 'STAKEHOLDER_SELF', '설계사');
|
ensure_permission(v_designer_role_id, 'KB_COVERAGES', 'CUST_ID', 'OWN_CUSTOMER', NULL);
|
||||||
ensure_permission(v_manager_role_id, 'KB_COVERAGES', 'CUST_ID', 'STAKEHOLDER_CHANNEL', '지점장');
|
ensure_permission(v_manager_role_id, 'KB_COVERAGES', 'CUST_ID', 'CHANNEL_CUSTOMER', NULL);
|
||||||
ensure_permission(v_designer_role_id, 'KB_CLAIMS', 'CUST_ID', 'STAKEHOLDER_SELF', '설계사');
|
ensure_permission(v_designer_role_id, 'KB_CLAIMS', 'CUST_ID', 'OWN_CUSTOMER', NULL);
|
||||||
ensure_permission(v_manager_role_id, 'KB_CLAIMS', 'CUST_ID', 'STAKEHOLDER_CHANNEL', '지점장');
|
ensure_permission(v_manager_role_id, 'KB_CLAIMS', 'CUST_ID', 'CHANNEL_CUSTOMER', NULL);
|
||||||
ensure_permission(v_designer_role_id, 'KB_EXTERNAL_HOLDINGS', 'CUST_ID', 'STAKEHOLDER_SELF', '설계사');
|
ensure_permission(v_designer_role_id, 'KB_EXTERNAL_HOLDINGS', 'CUST_ID', 'OWN_CUSTOMER', NULL);
|
||||||
ensure_permission(v_manager_role_id, 'KB_EXTERNAL_HOLDINGS', 'CUST_ID', 'STAKEHOLDER_CHANNEL', '지점장');
|
ensure_permission(v_manager_role_id, 'KB_EXTERNAL_HOLDINGS', 'CUST_ID', 'CHANNEL_CUSTOMER', NULL);
|
||||||
END;
|
END;
|
||||||
/
|
/
|
||||||
COMMIT;
|
COMMIT;
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ BEGIN
|
|||||||
INSERT INTO cb_permission(perm_id, role_id, target_name, action_name, permission_effect)
|
INSERT INTO cb_permission(perm_id, role_id, target_name, action_name, permission_effect)
|
||||||
VALUES (v_permission_id, v_role_id, 'KB_STAKEHOLDERS', 'SELECT', 'ALLOW');
|
VALUES (v_permission_id, v_role_id, 'KB_STAKEHOLDERS', 'SELECT', 'ALLOW');
|
||||||
INSERT INTO cb_permission_rule(rule_id, perm_id, rule_column, rule_type, rule_value)
|
INSERT INTO cb_permission_rule(rule_id, perm_id, rule_column, rule_type, rule_value)
|
||||||
VALUES (cb_permission_rule_seq.NEXTVAL, v_permission_id, 'USER_ID', 'STAKEHOLDER_SELF', NULL);
|
VALUES (cb_permission_rule_seq.NEXTVAL, v_permission_id, 'USER_ID', 'TOKEN_SUBJECT', NULL);
|
||||||
END;
|
END;
|
||||||
END;
|
END;
|
||||||
/
|
/
|
||||||
|
|||||||
137
sql/adb/48_kb_permission_condition_codes.sql
Normal file
137
sql/adb/48_kb_permission_condition_codes.sql
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- 48_kb_permission_condition_codes.sql
|
||||||
|
--
|
||||||
|
-- Replaces the first KB example's stakeholder-specific predicate types with
|
||||||
|
-- policy condition codes. CB_AGENT_DOC_VPD_FILTER expands these codes into
|
||||||
|
-- the target object's WHERE predicate.
|
||||||
|
-- Execute as ADMIN after 25 and 46.
|
||||||
|
-- ============================================================
|
||||||
|
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
||||||
|
SET ECHO OFF
|
||||||
|
SET FEEDBACK ON
|
||||||
|
SET DEFINE OFF
|
||||||
|
|
||||||
|
ALTER SESSION DISABLE PARALLEL DML;
|
||||||
|
|
||||||
|
PROMPT === 1. Replacing KB example rules with filter condition codes ===
|
||||||
|
UPDATE cb_permission_rule r
|
||||||
|
SET rule_type = CASE
|
||||||
|
WHEN EXISTS (
|
||||||
|
SELECT 1 FROM cb_permission p JOIN cb_app_role ar ON ar.role_id = p.role_id
|
||||||
|
WHERE p.perm_id = r.perm_id AND ar.role_name = 'KB_DESIGNER_ROLE'
|
||||||
|
AND p.target_name = 'KB_CONTRACTS'
|
||||||
|
) THEN 'OWN_CONTRACT'
|
||||||
|
WHEN EXISTS (
|
||||||
|
SELECT 1 FROM cb_permission p JOIN cb_app_role ar ON ar.role_id = p.role_id
|
||||||
|
WHERE p.perm_id = r.perm_id AND ar.role_name = 'KB_BRANCH_MANAGER_ROLE'
|
||||||
|
AND p.target_name = 'KB_CONTRACTS'
|
||||||
|
) THEN 'CHANNEL_CONTRACT'
|
||||||
|
WHEN EXISTS (
|
||||||
|
SELECT 1 FROM cb_permission p JOIN cb_app_role ar ON ar.role_id = p.role_id
|
||||||
|
WHERE p.perm_id = r.perm_id AND ar.role_name = 'KB_DESIGNER_ROLE'
|
||||||
|
AND p.target_name IN ('KB_CUSTOMERS', 'KB_COVERAGES', 'KB_CLAIMS', 'KB_EXTERNAL_HOLDINGS')
|
||||||
|
) THEN 'OWN_CUSTOMER'
|
||||||
|
WHEN EXISTS (
|
||||||
|
SELECT 1 FROM cb_permission p JOIN cb_app_role ar ON ar.role_id = p.role_id
|
||||||
|
WHERE p.perm_id = r.perm_id AND ar.role_name = 'KB_BRANCH_MANAGER_ROLE'
|
||||||
|
AND p.target_name IN ('KB_CUSTOMERS', 'KB_COVERAGES', 'KB_CLAIMS', 'KB_EXTERNAL_HOLDINGS')
|
||||||
|
) THEN 'CHANNEL_CUSTOMER'
|
||||||
|
ELSE r.rule_type
|
||||||
|
END,
|
||||||
|
rule_value = NULL
|
||||||
|
WHERE r.rule_type IN ('STAKEHOLDER_SELF', 'STAKEHOLDER_CHANNEL')
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM cb_permission p
|
||||||
|
JOIN cb_app_role ar ON ar.role_id = p.role_id
|
||||||
|
WHERE p.perm_id = r.perm_id
|
||||||
|
AND ar.role_name IN ('KB_DESIGNER_ROLE', 'KB_BRANCH_MANAGER_ROLE')
|
||||||
|
AND p.target_name IN ('KB_CONTRACTS', 'KB_CUSTOMERS', 'KB_COVERAGES', 'KB_CLAIMS', 'KB_EXTERNAL_HOLDINGS')
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
PROMPT === 2. Keeping token subject access as an identity-only permission ===
|
||||||
|
DECLARE
|
||||||
|
v_identity_role_id NUMBER;
|
||||||
|
v_permission_id NUMBER;
|
||||||
|
v_count NUMBER;
|
||||||
|
BEGIN
|
||||||
|
BEGIN
|
||||||
|
SELECT role_id INTO v_identity_role_id
|
||||||
|
FROM cb_app_role
|
||||||
|
WHERE role_name = 'KB_STAKEHOLDER_IDENTITY_ROLE';
|
||||||
|
EXCEPTION
|
||||||
|
WHEN NO_DATA_FOUND THEN
|
||||||
|
SELECT NVL(MAX(role_id), 0) + 1 INTO v_identity_role_id FROM cb_app_role;
|
||||||
|
INSERT INTO cb_app_role(role_id, role_name, max_sensitivity_level)
|
||||||
|
VALUES (v_identity_role_id, 'KB_STAKEHOLDER_IDENTITY_ROLE', 'INTERNAL');
|
||||||
|
END;
|
||||||
|
|
||||||
|
FOR u IN (
|
||||||
|
SELECT user_id FROM cb_app_user
|
||||||
|
WHERE stakeholder_user_id IS NOT NULL AND active = 'Y'
|
||||||
|
) LOOP
|
||||||
|
SELECT COUNT(*) INTO v_count
|
||||||
|
FROM cb_user_role
|
||||||
|
WHERE user_id = u.user_id AND role_id = v_identity_role_id;
|
||||||
|
IF v_count = 0 THEN
|
||||||
|
INSERT INTO cb_user_role(user_id, role_id) VALUES (u.user_id, v_identity_role_id);
|
||||||
|
END IF;
|
||||||
|
END LOOP;
|
||||||
|
|
||||||
|
-- Business roles do not need to grant the stakeholder source row. The
|
||||||
|
-- identity role alone lets a valid token resolve its own stakeholder row.
|
||||||
|
DELETE FROM cb_permission_column
|
||||||
|
WHERE permission_id IN (
|
||||||
|
SELECT p.perm_id
|
||||||
|
FROM cb_permission p JOIN cb_app_role ar ON ar.role_id = p.role_id
|
||||||
|
WHERE ar.role_name IN ('KB_DESIGNER_ROLE', 'KB_BRANCH_MANAGER_ROLE')
|
||||||
|
AND p.target_name = 'KB_STAKEHOLDERS'
|
||||||
|
);
|
||||||
|
DELETE FROM cb_permission_rule
|
||||||
|
WHERE perm_id IN (
|
||||||
|
SELECT p.perm_id
|
||||||
|
FROM cb_permission p JOIN cb_app_role ar ON ar.role_id = p.role_id
|
||||||
|
WHERE ar.role_name IN ('KB_DESIGNER_ROLE', 'KB_BRANCH_MANAGER_ROLE')
|
||||||
|
AND p.target_name = 'KB_STAKEHOLDERS'
|
||||||
|
);
|
||||||
|
DELETE FROM cb_permission
|
||||||
|
WHERE perm_id IN (
|
||||||
|
SELECT p.perm_id
|
||||||
|
FROM cb_permission p JOIN cb_app_role ar ON ar.role_id = p.role_id
|
||||||
|
WHERE ar.role_name IN ('KB_DESIGNER_ROLE', 'KB_BRANCH_MANAGER_ROLE')
|
||||||
|
AND p.target_name = 'KB_STAKEHOLDERS'
|
||||||
|
);
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
SELECT perm_id INTO v_permission_id
|
||||||
|
FROM cb_permission
|
||||||
|
WHERE role_id = v_identity_role_id
|
||||||
|
AND target_name = 'KB_STAKEHOLDERS'
|
||||||
|
AND action_name = 'SELECT';
|
||||||
|
EXCEPTION
|
||||||
|
WHEN NO_DATA_FOUND THEN
|
||||||
|
SELECT cb_permission_seq.NEXTVAL INTO v_permission_id FROM dual;
|
||||||
|
INSERT INTO cb_permission(perm_id, role_id, target_name, action_name, permission_effect)
|
||||||
|
VALUES (v_permission_id, v_identity_role_id, 'KB_STAKEHOLDERS', 'SELECT', 'ALLOW');
|
||||||
|
END;
|
||||||
|
|
||||||
|
DELETE FROM cb_permission_rule WHERE perm_id = v_permission_id;
|
||||||
|
INSERT INTO cb_permission_rule(rule_id, perm_id, rule_column, rule_type, rule_value)
|
||||||
|
VALUES (cb_permission_rule_seq.NEXTVAL, v_permission_id, 'USER_ID', 'TOKEN_SUBJECT', NULL);
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
PROMPT === 3. Condition codes consumed by the VPD filter ===
|
||||||
|
SELECT ar.role_name, p.target_name, r.rule_column, r.rule_type
|
||||||
|
FROM cb_permission p
|
||||||
|
JOIN cb_app_role ar ON ar.role_id = p.role_id
|
||||||
|
JOIN cb_permission_rule r ON r.perm_id = p.perm_id
|
||||||
|
WHERE ar.role_name IN (
|
||||||
|
'KB_DESIGNER_ROLE', 'KB_BRANCH_MANAGER_ROLE', 'KB_STAKEHOLDER_IDENTITY_ROLE'
|
||||||
|
)
|
||||||
|
ORDER BY ar.role_name, p.target_name, r.rule_id;
|
||||||
|
|
||||||
|
PROMPT === KB permission condition codes ready ===
|
||||||
35
sql/adb/49_permission_static_sql_condition.sql
Normal file
35
sql/adb/49_permission_static_sql_condition.sql
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- 49_permission_static_sql_condition.sql
|
||||||
|
--
|
||||||
|
-- Makes room for a literal, static WHERE condition attached to an
|
||||||
|
-- allowlisted permission rule. The VPD function validates the expression
|
||||||
|
-- again before returning it as part of a predicate.
|
||||||
|
-- ============================================================
|
||||||
|
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
||||||
|
SET ECHO OFF
|
||||||
|
SET FEEDBACK ON
|
||||||
|
SET DEFINE OFF
|
||||||
|
|
||||||
|
PROMPT === Enlarging rule value for static SQL conditions ===
|
||||||
|
DECLARE
|
||||||
|
v_data_length NUMBER;
|
||||||
|
BEGIN
|
||||||
|
SELECT data_length
|
||||||
|
INTO v_data_length
|
||||||
|
FROM user_tab_columns
|
||||||
|
WHERE table_name = 'CB_PERMISSION_RULE'
|
||||||
|
AND column_name = 'RULE_VALUE';
|
||||||
|
|
||||||
|
IF v_data_length < 1000 THEN
|
||||||
|
EXECUTE IMMEDIATE
|
||||||
|
'ALTER TABLE cb_permission_rule MODIFY (rule_value VARCHAR2(1000 CHAR))';
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
SELECT column_name, data_type, char_length
|
||||||
|
FROM user_tab_columns
|
||||||
|
WHERE table_name = 'CB_PERMISSION_RULE'
|
||||||
|
AND column_name = 'RULE_VALUE';
|
||||||
|
|
||||||
|
PROMPT === Static SQL condition storage ready ===
|
||||||
229
sql/adb/51_kb_vpd_admin_token.sql
Normal file
229
sql/adb/51_kb_vpd_admin_token.sql
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- 51_kb_vpd_admin_token.sql
|
||||||
|
--
|
||||||
|
-- Creates an application-level KB VPD administrator. This is not an
|
||||||
|
-- EXEMPT ACCESS POLICY database account: the token is resolved to an
|
||||||
|
-- application context and the existing VPD policy function returns ALL
|
||||||
|
-- only because KB_VPD_ADMIN_ROLE has explicit ALL permissions.
|
||||||
|
-- ============================================================
|
||||||
|
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
||||||
|
SET ECHO OFF
|
||||||
|
SET FEEDBACK ON
|
||||||
|
SET DEFINE OFF
|
||||||
|
SET SERVEROUTPUT ON
|
||||||
|
|
||||||
|
PROMPT === Creating KB VPD administrator, permissions, and one-time token ===
|
||||||
|
GRANT EXECUTE ON cb_agent_ctx_pkg TO POC_2;
|
||||||
|
|
||||||
|
DECLARE
|
||||||
|
c_stakeholder_user_id CONSTANT VARCHAR2(30) := 'KB_VPD_ADMIN';
|
||||||
|
c_role_name CONSTANT VARCHAR2(80) := 'KB_VPD_ADMIN_ROLE';
|
||||||
|
c_token_description CONSTANT VARCHAR2(200) := 'KB VPD admin bootstrap token';
|
||||||
|
|
||||||
|
v_admin_role_id NUMBER;
|
||||||
|
v_identity_role_id NUMBER;
|
||||||
|
v_app_user_id NUMBER;
|
||||||
|
v_permission_id NUMBER;
|
||||||
|
v_bearer_token VARCHAR2(128);
|
||||||
|
v_count NUMBER;
|
||||||
|
|
||||||
|
PROCEDURE ensure_role(
|
||||||
|
p_role_name IN VARCHAR2,
|
||||||
|
p_level IN VARCHAR2,
|
||||||
|
p_role_id OUT NUMBER
|
||||||
|
) IS
|
||||||
|
BEGIN
|
||||||
|
SELECT role_id
|
||||||
|
INTO p_role_id
|
||||||
|
FROM cb_app_role
|
||||||
|
WHERE role_name = p_role_name;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN NO_DATA_FOUND THEN
|
||||||
|
SELECT NVL(MAX(role_id), 0) + 1
|
||||||
|
INTO p_role_id
|
||||||
|
FROM cb_app_role;
|
||||||
|
INSERT INTO cb_app_role(role_id, role_name, max_sensitivity_level)
|
||||||
|
VALUES (p_role_id, p_role_name, p_level);
|
||||||
|
END;
|
||||||
|
|
||||||
|
PROCEDURE ensure_user_role(p_user_id IN NUMBER, p_role_id IN NUMBER) IS
|
||||||
|
BEGIN
|
||||||
|
SELECT COUNT(*)
|
||||||
|
INTO v_count
|
||||||
|
FROM cb_user_role
|
||||||
|
WHERE user_id = p_user_id
|
||||||
|
AND role_id = p_role_id;
|
||||||
|
|
||||||
|
IF v_count = 0 THEN
|
||||||
|
INSERT INTO cb_user_role(user_id, role_id) VALUES (p_user_id, p_role_id);
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
|
||||||
|
PROCEDURE ensure_all_permission(p_target_name IN VARCHAR2) IS
|
||||||
|
BEGIN
|
||||||
|
SELECT MIN(perm_id)
|
||||||
|
INTO v_permission_id
|
||||||
|
FROM cb_permission
|
||||||
|
WHERE role_id = v_admin_role_id
|
||||||
|
AND target_name = p_target_name
|
||||||
|
AND action_name = 'SELECT';
|
||||||
|
|
||||||
|
IF v_permission_id IS NULL THEN
|
||||||
|
SELECT cb_permission_seq.NEXTVAL INTO v_permission_id FROM dual;
|
||||||
|
INSERT INTO cb_permission(
|
||||||
|
perm_id, role_id, target_name, action_name, permission_effect
|
||||||
|
) VALUES (
|
||||||
|
v_permission_id, v_admin_role_id, p_target_name, 'SELECT', 'ALLOW'
|
||||||
|
);
|
||||||
|
ELSE
|
||||||
|
UPDATE cb_permission
|
||||||
|
SET permission_effect = 'ALLOW'
|
||||||
|
WHERE perm_id = v_permission_id;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
DELETE FROM cb_permission_rule WHERE perm_id = v_permission_id;
|
||||||
|
INSERT INTO cb_permission_rule(rule_id, perm_id, rule_column, rule_type, rule_value)
|
||||||
|
VALUES (cb_permission_rule_seq.NEXTVAL, v_permission_id, NULL, 'ALL', NULL);
|
||||||
|
|
||||||
|
-- CB_ORDS is the non-exempt data runtime. VPD still applies after
|
||||||
|
-- this object privilege is granted.
|
||||||
|
EXECUTE IMMEDIATE
|
||||||
|
'GRANT SELECT ON POC_2.' || DBMS_ASSERT.SIMPLE_SQL_NAME(p_target_name) || ' TO CB_ORDS';
|
||||||
|
END;
|
||||||
|
|
||||||
|
PROCEDURE allow_original_column(
|
||||||
|
p_target_name IN VARCHAR2,
|
||||||
|
p_column_name IN VARCHAR2
|
||||||
|
) IS
|
||||||
|
BEGIN
|
||||||
|
SELECT MIN(perm_id)
|
||||||
|
INTO v_permission_id
|
||||||
|
FROM cb_permission
|
||||||
|
WHERE role_id = v_admin_role_id
|
||||||
|
AND target_name = p_target_name
|
||||||
|
AND action_name = 'SELECT';
|
||||||
|
|
||||||
|
SELECT COUNT(*)
|
||||||
|
INTO v_count
|
||||||
|
FROM cb_permission_column
|
||||||
|
WHERE permission_id = v_permission_id
|
||||||
|
AND column_name = p_column_name;
|
||||||
|
|
||||||
|
IF v_count = 0 THEN
|
||||||
|
INSERT INTO cb_permission_column(permission_id, column_name)
|
||||||
|
VALUES (v_permission_id, p_column_name);
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
BEGIN
|
||||||
|
MERGE INTO poc_2.kb_stakeholders target
|
||||||
|
USING (
|
||||||
|
SELECT c_stakeholder_user_id AS user_id,
|
||||||
|
'VPD 관리자' AS user_nm,
|
||||||
|
'관리자' AS role,
|
||||||
|
'관리자채널' AS channel,
|
||||||
|
'전체 KB 원장 및 민감 컬럼 접근' AS access_scope,
|
||||||
|
'권한 테이블의 KB_VPD_ADMIN_ROLE / ALL 규칙으로 적용' AS rls_filter
|
||||||
|
FROM dual
|
||||||
|
) source
|
||||||
|
ON (target.user_id = source.user_id)
|
||||||
|
WHEN MATCHED THEN UPDATE SET
|
||||||
|
target.user_nm = source.user_nm,
|
||||||
|
target.role = source.role,
|
||||||
|
target.channel = source.channel,
|
||||||
|
target.access_scope = source.access_scope,
|
||||||
|
target.rls_filter = source.rls_filter
|
||||||
|
WHEN NOT MATCHED THEN INSERT (
|
||||||
|
user_id, user_nm, role, channel, access_scope, rls_filter
|
||||||
|
) VALUES (
|
||||||
|
source.user_id, source.user_nm, source.role, source.channel,
|
||||||
|
source.access_scope, source.rls_filter
|
||||||
|
);
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
SELECT user_id
|
||||||
|
INTO v_app_user_id
|
||||||
|
FROM cb_app_user
|
||||||
|
WHERE stakeholder_user_id = c_stakeholder_user_id;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN NO_DATA_FOUND THEN
|
||||||
|
SELECT NVL(MAX(user_id), 0) + 1 INTO v_app_user_id FROM cb_app_user;
|
||||||
|
INSERT INTO cb_app_user(
|
||||||
|
user_id, user_name, employee_no, dept_code,
|
||||||
|
stakeholder_user_id, can_read_contents, active
|
||||||
|
) VALUES (
|
||||||
|
v_app_user_id, 'VPD 관리자', c_stakeholder_user_id, '관리자채널',
|
||||||
|
c_stakeholder_user_id, 'Y', 'Y'
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
|
||||||
|
UPDATE cb_app_user
|
||||||
|
SET user_name = 'VPD 관리자',
|
||||||
|
employee_no = c_stakeholder_user_id,
|
||||||
|
dept_code = '관리자채널',
|
||||||
|
can_read_contents = 'Y',
|
||||||
|
active = 'Y'
|
||||||
|
WHERE user_id = v_app_user_id;
|
||||||
|
|
||||||
|
ensure_role(c_role_name, 'RESTRICTED', v_admin_role_id);
|
||||||
|
ensure_role('KB_STAKEHOLDER_IDENTITY_ROLE', 'INTERNAL', v_identity_role_id);
|
||||||
|
ensure_user_role(v_app_user_id, v_admin_role_id);
|
||||||
|
ensure_user_role(v_app_user_id, v_identity_role_id);
|
||||||
|
|
||||||
|
FOR object_name IN (
|
||||||
|
SELECT column_value
|
||||||
|
FROM TABLE(sys.odcivarchar2list(
|
||||||
|
'KB_CUSTOMERS', 'KB_PRODUCTS', 'KB_CONTRACTS', 'KB_COVERAGES',
|
||||||
|
'KB_CLAIMS', 'KB_EXTERNAL_HOLDINGS', 'KB_STAKEHOLDERS'
|
||||||
|
))
|
||||||
|
) LOOP
|
||||||
|
ensure_all_permission(object_name.column_value);
|
||||||
|
END LOOP;
|
||||||
|
|
||||||
|
allow_original_column('KB_CUSTOMERS', 'CUST_NM');
|
||||||
|
allow_original_column('KB_CUSTOMERS', 'RRN_MASKED');
|
||||||
|
allow_original_column('KB_CLAIMS', 'CLAIM_AMT');
|
||||||
|
allow_original_column('KB_CLAIMS', 'PAID_AMT');
|
||||||
|
allow_original_column('KB_EXTERNAL_HOLDINGS', 'EXT_INSURER');
|
||||||
|
allow_original_column('KB_EXTERNAL_HOLDINGS', 'EXT_PRODUCT_GRP');
|
||||||
|
allow_original_column('KB_EXTERNAL_HOLDINGS', 'EXT_PRODUCT_TYPE');
|
||||||
|
|
||||||
|
-- Re-running this script rotates only the bootstrap administrator token.
|
||||||
|
UPDATE cb_agent_bearer_key
|
||||||
|
SET active = 'N',
|
||||||
|
revoked_at = SYSDATE
|
||||||
|
WHERE stakeholder_user_id = c_stakeholder_user_id
|
||||||
|
AND description = c_token_description
|
||||||
|
AND active = 'Y';
|
||||||
|
|
||||||
|
v_bearer_token := 'kbvpdadm_' || DBMS_RANDOM.STRING('x', 48);
|
||||||
|
INSERT INTO cb_agent_bearer_key(
|
||||||
|
key_id, user_id, stakeholder_user_id, key_prefix, key_hash,
|
||||||
|
issued_at, expires_at, revoked_at, active, description
|
||||||
|
) VALUES (
|
||||||
|
cb_agent_bearer_key_seq.NEXTVAL, v_app_user_id, c_stakeholder_user_id,
|
||||||
|
'kbvpdadm', STANDARD_HASH(v_bearer_token, 'SHA256'),
|
||||||
|
SYSDATE, ADD_MONTHS(SYSDATE, 12), NULL, 'Y', c_token_description
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- This is the application step that binds the Bearer Token to the trusted
|
||||||
|
-- context. DBMS_RLS then invokes CB_AGENT_DOC_VPD_FILTER automatically.
|
||||||
|
cb_agent_ctx_pkg.set_user_by_bearer(v_bearer_token);
|
||||||
|
DBMS_OUTPUT.PUT_LINE('STAKEHOLDER_USER_ID='
|
||||||
|
|| SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_USER_ID'));
|
||||||
|
DBMS_OUTPUT.PUT_LINE('EFFECTIVE_ROLE=' || c_role_name);
|
||||||
|
DBMS_OUTPUT.PUT_LINE('KB_CUSTOMERS_PREDICATE='
|
||||||
|
|| cb_agent_doc_vpd_filter('POC_2', 'KB_CUSTOMERS'));
|
||||||
|
DBMS_OUTPUT.PUT_LINE('KB_CLAIMS_PREDICATE='
|
||||||
|
|| cb_agent_doc_vpd_filter('POC_2', 'KB_CLAIMS'));
|
||||||
|
DBMS_OUTPUT.PUT_LINE('KB_EXTERNAL_HOLDINGS_PREDICATE='
|
||||||
|
|| cb_agent_doc_vpd_filter('POC_2', 'KB_EXTERNAL_HOLDINGS'));
|
||||||
|
cb_agent_ctx_pkg.clear_user;
|
||||||
|
|
||||||
|
-- Show once. The raw token is never stored; only its SHA-256 hash remains.
|
||||||
|
DBMS_OUTPUT.PUT_LINE('BEARER_TOKEN=' || v_bearer_token);
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
PROMPT === KB VPD administrator token ready ===
|
||||||
223
sql/adb/52_cleanup_to_kb_vpd_poc.sql
Normal file
223
sql/adb/52_cleanup_to_kb_vpd_poc.sql
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- 52_cleanup_to_kb_vpd_poc.sql
|
||||||
|
--
|
||||||
|
-- Removes legacy document/Vector/DDS/test examples and keeps only
|
||||||
|
-- the KB structured-data VPD POC:
|
||||||
|
-- POC_2.KB_* seven ledgers, stakeholder identity, KB roles,
|
||||||
|
-- bearer-token permission metadata, and their VPD policies.
|
||||||
|
--
|
||||||
|
-- Oracle-maintained ANNOTATIONS_* / VECTOR$* auxiliary objects are
|
||||||
|
-- not dropped directly. Their owning RAG/vector tables are dropped.
|
||||||
|
-- ============================================================
|
||||||
|
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
||||||
|
SET ECHO OFF
|
||||||
|
SET FEEDBACK ON
|
||||||
|
SET DEFINE OFF
|
||||||
|
SET SERVEROUTPUT ON
|
||||||
|
ALTER SESSION DISABLE PARALLEL DML;
|
||||||
|
|
||||||
|
PROMPT === 1. Removing document, legacy role, and test permissions ===
|
||||||
|
DECLARE
|
||||||
|
c_role_keep_1 CONSTANT VARCHAR2(80) := 'KB_DESIGNER_ROLE';
|
||||||
|
c_role_keep_2 CONSTANT VARCHAR2(80) := 'KB_BRANCH_MANAGER_ROLE';
|
||||||
|
c_role_keep_3 CONSTANT VARCHAR2(80) := 'KB_STAKEHOLDER_IDENTITY_ROLE';
|
||||||
|
c_role_keep_4 CONSTANT VARCHAR2(80) := 'KB_VPD_ADMIN_ROLE';
|
||||||
|
BEGIN
|
||||||
|
DELETE FROM cb_permission_column
|
||||||
|
WHERE permission_id IN (
|
||||||
|
SELECT p.perm_id
|
||||||
|
FROM cb_permission p
|
||||||
|
JOIN cb_app_role r ON r.role_id = p.role_id
|
||||||
|
WHERE r.role_name NOT IN (c_role_keep_1, c_role_keep_2, c_role_keep_3, c_role_keep_4)
|
||||||
|
OR p.target_name NOT IN (
|
||||||
|
'KB_CUSTOMERS', 'KB_PRODUCTS', 'KB_CONTRACTS', 'KB_COVERAGES',
|
||||||
|
'KB_CLAIMS', 'KB_EXTERNAL_HOLDINGS', 'KB_STAKEHOLDERS'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM cb_permission_rule
|
||||||
|
WHERE perm_id IN (
|
||||||
|
SELECT p.perm_id
|
||||||
|
FROM cb_permission p
|
||||||
|
JOIN cb_app_role r ON r.role_id = p.role_id
|
||||||
|
WHERE r.role_name NOT IN (c_role_keep_1, c_role_keep_2, c_role_keep_3, c_role_keep_4)
|
||||||
|
OR p.target_name NOT IN (
|
||||||
|
'KB_CUSTOMERS', 'KB_PRODUCTS', 'KB_CONTRACTS', 'KB_COVERAGES',
|
||||||
|
'KB_CLAIMS', 'KB_EXTERNAL_HOLDINGS', 'KB_STAKEHOLDERS'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM cb_permission
|
||||||
|
WHERE perm_id IN (
|
||||||
|
SELECT p.perm_id
|
||||||
|
FROM cb_permission p
|
||||||
|
JOIN cb_app_role r ON r.role_id = p.role_id
|
||||||
|
WHERE r.role_name NOT IN (c_role_keep_1, c_role_keep_2, c_role_keep_3, c_role_keep_4)
|
||||||
|
OR p.target_name NOT IN (
|
||||||
|
'KB_CUSTOMERS', 'KB_PRODUCTS', 'KB_CONTRACTS', 'KB_COVERAGES',
|
||||||
|
'KB_CLAIMS', 'KB_EXTERNAL_HOLDINGS', 'KB_STAKEHOLDERS'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM cb_user_role
|
||||||
|
WHERE role_id IN (
|
||||||
|
SELECT role_id
|
||||||
|
FROM cb_app_role
|
||||||
|
WHERE role_name NOT IN (c_role_keep_1, c_role_keep_2, c_role_keep_3, c_role_keep_4)
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM cb_group_role;
|
||||||
|
DELETE FROM cb_user_group;
|
||||||
|
DELETE FROM cb_app_group;
|
||||||
|
|
||||||
|
DELETE FROM cb_app_role
|
||||||
|
WHERE role_name NOT IN (c_role_keep_1, c_role_keep_2, c_role_keep_3, c_role_keep_4);
|
||||||
|
|
||||||
|
-- Keep only tokens attached to an actual KB stakeholder.
|
||||||
|
DELETE FROM cb_agent_bearer_key
|
||||||
|
WHERE stakeholder_user_id IS NULL;
|
||||||
|
|
||||||
|
-- Non-stakeholder users were document/test sample principals.
|
||||||
|
DELETE FROM cb_app_user
|
||||||
|
WHERE stakeholder_user_id IS NULL;
|
||||||
|
|
||||||
|
DELETE FROM cb_vpd_filter_note;
|
||||||
|
DELETE FROM cb_vpd_policy_note;
|
||||||
|
DELETE FROM cb_ords_probe_audit;
|
||||||
|
|
||||||
|
DELETE FROM cb_protected_column
|
||||||
|
WHERE object_id IN (
|
||||||
|
SELECT object_id
|
||||||
|
FROM cb_protected_object
|
||||||
|
WHERE NOT (
|
||||||
|
owner = 'POC_2'
|
||||||
|
AND object_name IN (
|
||||||
|
'KB_CUSTOMERS', 'KB_PRODUCTS', 'KB_CONTRACTS', 'KB_COVERAGES',
|
||||||
|
'KB_CLAIMS', 'KB_EXTERNAL_HOLDINGS', 'KB_STAKEHOLDERS'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM cb_protected_object
|
||||||
|
WHERE NOT (
|
||||||
|
owner = 'POC_2'
|
||||||
|
AND object_name IN (
|
||||||
|
'KB_CUSTOMERS', 'KB_PRODUCTS', 'KB_CONTRACTS', 'KB_COVERAGES',
|
||||||
|
'KB_CLAIMS', 'KB_EXTERNAL_HOLDINGS', 'KB_STAKEHOLDERS'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
PROMPT === 2. Removing legacy document VPD/redaction and source objects ===
|
||||||
|
BEGIN
|
||||||
|
DBMS_REDACT.DROP_POLICY(
|
||||||
|
object_schema => 'ADMIN',
|
||||||
|
object_name => 'CB_V_SEARCH_DOCUMENTS',
|
||||||
|
policy_name => 'CB_CONTENTS_REDACT'
|
||||||
|
);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN
|
||||||
|
-- The policy may already be absent in a reduced POC schema.
|
||||||
|
DBMS_OUTPUT.PUT_LINE('redaction policy not removed: ' || SQLERRM);
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
DECLARE
|
||||||
|
v_count NUMBER;
|
||||||
|
BEGIN
|
||||||
|
SELECT COUNT(*)
|
||||||
|
INTO v_count
|
||||||
|
FROM all_policies
|
||||||
|
WHERE object_owner = 'ADMIN'
|
||||||
|
AND object_name = 'CB_V_SEARCH_DOCUMENTS'
|
||||||
|
AND policy_name = 'CB_AGENT_DOC_POLICY';
|
||||||
|
|
||||||
|
IF v_count > 0 THEN
|
||||||
|
DBMS_RLS.DROP_POLICY(
|
||||||
|
object_schema => 'ADMIN',
|
||||||
|
object_name => 'CB_V_SEARCH_DOCUMENTS',
|
||||||
|
policy_name => 'CB_AGENT_DOC_POLICY'
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
EXECUTE IMMEDIATE 'DROP VIEW cb_v_search_documents';
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN
|
||||||
|
IF SQLCODE != -942 THEN
|
||||||
|
RAISE;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
EXECUTE IMMEDIATE 'DROP TABLE cb_search_documents PURGE';
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN
|
||||||
|
IF SQLCODE != -942 THEN
|
||||||
|
RAISE;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
PROMPT === 3. Dropping unrelated POC_2 backup, RAG, and security-example tables ===
|
||||||
|
DECLARE
|
||||||
|
PROCEDURE drop_table_if_exists(p_table_name IN VARCHAR2) IS
|
||||||
|
BEGIN
|
||||||
|
EXECUTE IMMEDIATE
|
||||||
|
'DROP TABLE POC_2.' || DBMS_ASSERT.SIMPLE_SQL_NAME(p_table_name)
|
||||||
|
|| ' CASCADE CONSTRAINTS PURGE';
|
||||||
|
DBMS_OUTPUT.PUT_LINE('dropped POC_2.' || p_table_name);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN
|
||||||
|
IF SQLCODE = -942 THEN
|
||||||
|
NULL;
|
||||||
|
ELSE
|
||||||
|
RAISE;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
BEGIN
|
||||||
|
drop_table_if_exists('BKP_CLM_20260701144052');
|
||||||
|
drop_table_if_exists('BKP_CONT_20260701144052');
|
||||||
|
drop_table_if_exists('BKP_COV_20260701144052');
|
||||||
|
drop_table_if_exists('BKP_CUST_20260701144052');
|
||||||
|
drop_table_if_exists('BKP_EXT_20260701144052');
|
||||||
|
drop_table_if_exists('BKP_PROD_20260701144052');
|
||||||
|
drop_table_if_exists('KB_CUST_BAK_202607011343');
|
||||||
|
drop_table_if_exists('KB_CUST_BAK_20260701134439');
|
||||||
|
drop_table_if_exists('KB_AIDP_RAG_CORPUS_CONFIG');
|
||||||
|
drop_table_if_exists('KB_AIDP_RAG_QUERY_CACHE');
|
||||||
|
drop_table_if_exists('KB_COMPETITOR_TERMS_CHUNKS');
|
||||||
|
drop_table_if_exists('KB_COMPETITOR_TERMS_DOCUMENTS');
|
||||||
|
drop_table_if_exists('KB_OWN_TERMS_CHUNKS');
|
||||||
|
drop_table_if_exists('KB_OWN_TERMS_DOCUMENTS');
|
||||||
|
drop_table_if_exists('KB_SECURITY_AUDIT_LOG');
|
||||||
|
drop_table_if_exists('KB_SECURITY_POLICY_CATALOG');
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
PROMPT === 4. Remaining KB VPD POC inventory ===
|
||||||
|
SELECT object_name
|
||||||
|
FROM cb_protected_object
|
||||||
|
WHERE owner = 'POC_2'
|
||||||
|
ORDER BY object_name;
|
||||||
|
|
||||||
|
SELECT role_name
|
||||||
|
FROM cb_app_role
|
||||||
|
ORDER BY role_name;
|
||||||
|
|
||||||
|
SELECT key_id, stakeholder_user_id, key_prefix, active, expires_at
|
||||||
|
FROM cb_agent_bearer_key
|
||||||
|
ORDER BY key_id;
|
||||||
|
|
||||||
|
SELECT object_owner, object_name, policy_name, function, enable
|
||||||
|
FROM all_policies
|
||||||
|
WHERE object_owner = 'POC_2'
|
||||||
|
ORDER BY object_name, policy_name;
|
||||||
|
|
||||||
|
PROMPT === KB VPD POC cleanup complete ===
|
||||||
101
sql/adb/53_drop_legacy_poc_objects.sql
Normal file
101
sql/adb/53_drop_legacy_poc_objects.sql
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- 53_drop_legacy_poc_objects.sql
|
||||||
|
-- Completes the physical-object portion of the KB VPD POC cleanup
|
||||||
|
-- after 52 has removed legacy permissions, roles, tokens and users.
|
||||||
|
-- ============================================================
|
||||||
|
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
||||||
|
SET ECHO OFF
|
||||||
|
SET FEEDBACK ON
|
||||||
|
SET DEFINE OFF
|
||||||
|
SET SERVEROUTPUT ON
|
||||||
|
|
||||||
|
PROMPT === Removing legacy document VPD/redaction and source objects ===
|
||||||
|
BEGIN
|
||||||
|
DBMS_REDACT.DROP_POLICY(
|
||||||
|
object_schema => 'ADMIN',
|
||||||
|
object_name => 'CB_V_SEARCH_DOCUMENTS',
|
||||||
|
policy_name => 'CB_CONTENTS_REDACT'
|
||||||
|
);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN
|
||||||
|
DBMS_OUTPUT.PUT_LINE('redaction policy not removed: ' || SQLERRM);
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
DECLARE
|
||||||
|
v_count NUMBER;
|
||||||
|
BEGIN
|
||||||
|
SELECT COUNT(*)
|
||||||
|
INTO v_count
|
||||||
|
FROM all_policies
|
||||||
|
WHERE object_owner = 'ADMIN'
|
||||||
|
AND object_name = 'CB_V_SEARCH_DOCUMENTS'
|
||||||
|
AND policy_name = 'CB_AGENT_DOC_POLICY';
|
||||||
|
|
||||||
|
IF v_count > 0 THEN
|
||||||
|
DBMS_RLS.DROP_POLICY(
|
||||||
|
object_schema => 'ADMIN',
|
||||||
|
object_name => 'CB_V_SEARCH_DOCUMENTS',
|
||||||
|
policy_name => 'CB_AGENT_DOC_POLICY'
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
EXECUTE IMMEDIATE 'DROP VIEW cb_v_search_documents';
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN
|
||||||
|
IF SQLCODE != -942 THEN
|
||||||
|
RAISE;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
EXECUTE IMMEDIATE 'DROP TABLE cb_search_documents PURGE';
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN
|
||||||
|
IF SQLCODE != -942 THEN
|
||||||
|
RAISE;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
PROMPT === Dropping unrelated POC_2 backup, RAG, and security-example tables ===
|
||||||
|
DECLARE
|
||||||
|
PROCEDURE drop_table_if_exists(p_table_name IN VARCHAR2) IS
|
||||||
|
BEGIN
|
||||||
|
EXECUTE IMMEDIATE
|
||||||
|
'DROP TABLE POC_2.' || DBMS_ASSERT.SIMPLE_SQL_NAME(p_table_name)
|
||||||
|
|| ' CASCADE CONSTRAINTS PURGE';
|
||||||
|
DBMS_OUTPUT.PUT_LINE('dropped POC_2.' || p_table_name);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN
|
||||||
|
IF SQLCODE = -942 THEN
|
||||||
|
NULL;
|
||||||
|
ELSE
|
||||||
|
RAISE;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
BEGIN
|
||||||
|
drop_table_if_exists('BKP_CLM_20260701144052');
|
||||||
|
drop_table_if_exists('BKP_CONT_20260701144052');
|
||||||
|
drop_table_if_exists('BKP_COV_20260701144052');
|
||||||
|
drop_table_if_exists('BKP_CUST_20260701144052');
|
||||||
|
drop_table_if_exists('BKP_EXT_20260701144052');
|
||||||
|
drop_table_if_exists('BKP_PROD_20260701144052');
|
||||||
|
drop_table_if_exists('KB_CUST_BAK_202607011343');
|
||||||
|
drop_table_if_exists('KB_CUST_BAK_20260701134439');
|
||||||
|
drop_table_if_exists('KB_AIDP_RAG_CORPUS_CONFIG');
|
||||||
|
drop_table_if_exists('KB_AIDP_RAG_QUERY_CACHE');
|
||||||
|
drop_table_if_exists('KB_COMPETITOR_TERMS_CHUNKS');
|
||||||
|
drop_table_if_exists('KB_COMPETITOR_TERMS_DOCUMENTS');
|
||||||
|
drop_table_if_exists('KB_OWN_TERMS_CHUNKS');
|
||||||
|
drop_table_if_exists('KB_OWN_TERMS_DOCUMENTS');
|
||||||
|
drop_table_if_exists('KB_SECURITY_AUDIT_LOG');
|
||||||
|
drop_table_if_exists('KB_SECURITY_POLICY_CATALOG');
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
PROMPT === Legacy physical objects removed ===
|
||||||
47
sql/adb/54_drop_legacy_dds_ords_examples.sql
Normal file
47
sql/adb/54_drop_legacy_dds_ords_examples.sql
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- 54_drop_legacy_dds_ords_examples.sql
|
||||||
|
-- Removes only the former document-search ORDS/DDS example objects.
|
||||||
|
-- It deliberately keeps CB_ORDS because it is the non-exempt KB VPD
|
||||||
|
-- runtime principal.
|
||||||
|
-- ============================================================
|
||||||
|
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
||||||
|
SET ECHO OFF
|
||||||
|
SET FEEDBACK ON
|
||||||
|
SET DEFINE OFF
|
||||||
|
SET SERVEROUTPUT ON
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
ORDS.DELETE_MODULE(p_module_name => 'cb.agent.security');
|
||||||
|
DBMS_OUTPUT.PUT_LINE('dropped ORDS module cb.agent.security');
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN
|
||||||
|
DBMS_OUTPUT.PUT_LINE('ORDS module not removed: ' || SQLERRM);
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
DECLARE
|
||||||
|
PROCEDURE drop_ddl_if_present(p_ddl IN VARCHAR2) IS
|
||||||
|
BEGIN
|
||||||
|
EXECUTE IMMEDIATE p_ddl;
|
||||||
|
DBMS_OUTPUT.PUT_LINE('dropped: ' || p_ddl);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN
|
||||||
|
DBMS_OUTPUT.PUT_LINE('not present: ' || p_ddl);
|
||||||
|
END;
|
||||||
|
BEGIN
|
||||||
|
drop_ddl_if_present('DROP DATA GRANT admin.cb_dg_hr_docs');
|
||||||
|
drop_ddl_if_present('DROP DATA GRANT admin.cb_dg_fin_docs');
|
||||||
|
drop_ddl_if_present('DROP DATA GRANT admin.cb_dg_all_docs');
|
||||||
|
drop_ddl_if_present('DROP END USER "cb_dds_hr"');
|
||||||
|
drop_ddl_if_present('DROP END USER "cb_dds_fin"');
|
||||||
|
drop_ddl_if_present('DROP END USER "cb_dds_all"');
|
||||||
|
drop_ddl_if_present('DROP END USER "cb_dds_none"');
|
||||||
|
drop_ddl_if_present('DROP DATA ROLE cb_dds_hr_role');
|
||||||
|
drop_ddl_if_present('DROP DATA ROLE cb_dds_fin_role');
|
||||||
|
drop_ddl_if_present('DROP DATA ROLE cb_dds_all_role');
|
||||||
|
drop_ddl_if_present('DROP DATA ROLE cb_dds_connect_only_role');
|
||||||
|
drop_ddl_if_present('DROP ROLE cb_dds_connect_role');
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
PROMPT === Legacy DDS/ORDS examples removed ===
|
||||||
@@ -13,6 +13,8 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -22,11 +24,21 @@ public class PermissionService {
|
|||||||
|
|
||||||
private static final Set<String> RULE_TYPES = Set.of(
|
private static final Set<String> RULE_TYPES = Set.of(
|
||||||
"ALL", "=", "!=", "MY_DEPT", "SELF", "DEPT", "EMP_NO", "TAG",
|
"ALL", "=", "!=", "MY_DEPT", "SELF", "DEPT", "EMP_NO", "TAG",
|
||||||
"STAKEHOLDER_SELF", "STAKEHOLDER_CHANNEL");
|
"STAKEHOLDER_SELF", "STAKEHOLDER_CHANNEL",
|
||||||
|
"TOKEN_SUBJECT", "OWN_CONTRACT", "CHANNEL_CONTRACT", "OWN_CUSTOMER", "CHANNEL_CUSTOMER",
|
||||||
|
"STATIC_SQL");
|
||||||
private static final Set<String> VALUE_REQUIRED_RULE_TYPES = Set.of(
|
private static final Set<String> VALUE_REQUIRED_RULE_TYPES = Set.of(
|
||||||
"=", "!=", "DEPT", "EMP_NO", "TAG", "STAKEHOLDER_SELF", "STAKEHOLDER_CHANNEL");
|
"=", "!=", "DEPT", "EMP_NO", "TAG", "STAKEHOLDER_SELF", "STAKEHOLDER_CHANNEL", "STATIC_SQL");
|
||||||
private static final Set<String> DEFAULT_COLUMN_RULE_TYPES = Set.of(
|
private static final Set<String> DEFAULT_COLUMN_RULE_TYPES = Set.of(
|
||||||
"MY_DEPT", "SELF", "DEPT", "EMP_NO", "TAG");
|
"MY_DEPT", "SELF", "DEPT", "EMP_NO", "TAG", "STATIC_SQL");
|
||||||
|
private static final Set<String> STATIC_SQL_KEYWORDS = Set.of(
|
||||||
|
"AND", "OR", "NOT", "NULL", "IS", "IN", "LIKE", "BETWEEN", "ESCAPE", "TRUE", "FALSE",
|
||||||
|
"UPPER", "LOWER", "TRIM", "NVL", "COALESCE", "TO_CHAR", "TO_DATE", "REGEXP_LIKE", "INSTR",
|
||||||
|
"LENGTH", "SUBSTR", "REPLACE", "CAST", "AS", "DATE", "TIMESTAMP", "NUMBER", "VARCHAR2", "CHAR");
|
||||||
|
private static final Pattern SQL_STRING_LITERAL = Pattern.compile("'(?:''|[^'])*'");
|
||||||
|
private static final Pattern SQL_IDENTIFIER = Pattern.compile("[A-Z_$#][A-Z0-9_$#]*");
|
||||||
|
private static final Pattern SQL_QUALIFIED_IDENTIFIER =
|
||||||
|
Pattern.compile("[A-Z_$#][A-Z0-9_$#]*\\s*\\.", Pattern.CASE_INSENSITIVE);
|
||||||
private static final Set<String> PERMISSION_EFFECTS = Set.of("ALLOW", "DENY");
|
private static final Set<String> PERMISSION_EFFECTS = Set.of("ALLOW", "DENY");
|
||||||
private static final Set<String> SENSITIVITY_LEVELS = Set.of(
|
private static final Set<String> SENSITIVITY_LEVELS = Set.of(
|
||||||
"PUBLIC", "INTERNAL", "CONFIDENTIAL", "RESTRICTED");
|
"PUBLIC", "INTERNAL", "CONFIDENTIAL", "RESTRICTED");
|
||||||
@@ -225,12 +237,18 @@ public class PermissionService {
|
|||||||
if ("TAG".equals(type) && column == null && !allowedColumns.contains("TECH_TAG")) {
|
if ("TAG".equals(type) && column == null && !allowedColumns.contains("TECH_TAG")) {
|
||||||
throw new AppException("TAG 규칙의 기본 컬럼 TECH_TAG가 보호 객체에 없습니다. 컬럼을 지정하세요.");
|
throw new AppException("TAG 규칙의 기본 컬럼 TECH_TAG가 보호 객체에 없습니다. 컬럼을 지정하세요.");
|
||||||
}
|
}
|
||||||
|
if ("STATIC_SQL".equals(type) && column != null) {
|
||||||
|
throw new AppException("정적 SQL 조건은 컬럼 선택 없이 조건식 전체를 입력하세요.");
|
||||||
|
}
|
||||||
if (VALUE_REQUIRED_RULE_TYPES.contains(type) && ruleValue.isBlank()) {
|
if (VALUE_REQUIRED_RULE_TYPES.contains(type) && ruleValue.isBlank()) {
|
||||||
throw new AppException(type + " 규칙에는 값이 필요합니다.");
|
throw new AppException(type + " 규칙에는 값이 필요합니다.");
|
||||||
}
|
}
|
||||||
if ("TAG".equals(type) && !ruleValue.matches("[A-Z0-9_-]+")) {
|
if ("TAG".equals(type) && !ruleValue.matches("[A-Z0-9_-]+")) {
|
||||||
throw new AppException("TAG 값은 영문 대문자, 숫자, '_' 또는 '-'만 사용할 수 있습니다.");
|
throw new AppException("TAG 값은 영문 대문자, 숫자, '_' 또는 '-'만 사용할 수 있습니다.");
|
||||||
}
|
}
|
||||||
|
if ("STATIC_SQL".equals(type)) {
|
||||||
|
validateStaticSqlPredicate(ruleValue, allowedColumns);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (hasAll && rules.size() > 1) {
|
if (hasAll && rules.size() > 1) {
|
||||||
throw new AppException("ALL 규칙은 다른 규칙과 함께 저장할 수 없습니다.");
|
throw new AppException("ALL 규칙은 다른 규칙과 함께 저장할 수 없습니다.");
|
||||||
@@ -245,6 +263,41 @@ public class PermissionService {
|
|||||||
return allowed;
|
return allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateStaticSqlPredicate(String predicate, Set<String> allowedColumns) {
|
||||||
|
if (predicate.length() > 1000) {
|
||||||
|
throw new AppException("정적 SQL 조건은 1,000자 이하여야 합니다.");
|
||||||
|
}
|
||||||
|
if (predicate.chars().filter(value -> value == '\'').count() % 2 != 0) {
|
||||||
|
throw new AppException("정적 SQL 조건의 문자열 따옴표가 닫히지 않았습니다.");
|
||||||
|
}
|
||||||
|
if (predicate.indexOf(';') >= 0 || predicate.contains("--") || predicate.contains("/*")
|
||||||
|
|| predicate.contains("*/") || predicate.indexOf(':') >= 0 || predicate.indexOf('@') >= 0
|
||||||
|
|| predicate.contains("||") || predicate.chars().anyMatch(Character::isISOControl)) {
|
||||||
|
throw new AppException("정적 SQL 조건에는 단일 WHERE 조건식만 입력할 수 있습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String lexical = SQL_STRING_LITERAL.matcher(predicate).replaceAll(" ");
|
||||||
|
if (SQL_QUALIFIED_IDENTIFIER.matcher(lexical).find()) {
|
||||||
|
throw new AppException("정적 SQL 조건에는 다른 테이블·스키마를 참조할 수 없습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasObjectColumn = false;
|
||||||
|
Matcher matcher = SQL_IDENTIFIER.matcher(lexical.toUpperCase(Locale.ROOT));
|
||||||
|
while (matcher.find()) {
|
||||||
|
String token = matcher.group();
|
||||||
|
if (STATIC_SQL_KEYWORDS.contains(token)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!allowedColumns.contains(token)) {
|
||||||
|
throw new AppException("정적 SQL 조건에는 보호 객체 컬럼과 허용된 SQL 연산자만 사용할 수 있습니다: " + token);
|
||||||
|
}
|
||||||
|
hasObjectColumn = true;
|
||||||
|
}
|
||||||
|
if (!hasObjectColumn) {
|
||||||
|
throw new AppException("정적 SQL 조건에는 보호 객체 컬럼이 하나 이상 필요합니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void validateVisibleColumns(long objectId, List<String> visibleColumns) {
|
private void validateVisibleColumns(long objectId, List<String> visibleColumns) {
|
||||||
if (visibleColumns == null || visibleColumns.isEmpty()) {
|
if (visibleColumns == null || visibleColumns.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
SELECT p.perm_id AS permission_id,
|
SELECT p.perm_id AS permission_id,
|
||||||
r.role_id,
|
r.role_id,
|
||||||
r.role_name,
|
r.role_name,
|
||||||
o.object_id,
|
NVL(o.object_id, -1) AS object_id,
|
||||||
p.target_name AS object_name,
|
p.target_name AS object_name,
|
||||||
p.action_name AS action,
|
p.action_name AS action,
|
||||||
NVL(p.permission_effect, 'ALLOW') AS permission_effect,
|
NVL(p.permission_effect, 'ALLOW') AS permission_effect,
|
||||||
@@ -93,27 +93,24 @@
|
|||||||
WHEN pr2.rule_type = 'TAG' THEN 'REGEXP_LIKE(UPPER('
|
WHEN pr2.rule_type = 'TAG' THEN 'REGEXP_LIKE(UPPER('
|
||||||
|| NVL(pr2.rule_column, 'TECH_TAG')
|
|| NVL(pr2.rule_column, 'TECH_TAG')
|
||||||
|| '), ''(^|,)' || REPLACE(UPPER(pr2.rule_value), '''', '''''') || '(,|$)'')'
|
|| '), ''(^|,)' || REPLACE(UPPER(pr2.rule_value), '''', '''''') || '(,|$)'')'
|
||||||
WHEN pr2.rule_type = 'STAKEHOLDER_SELF' AND pr2.rule_column = 'FC_ID' THEN
|
WHEN pr2.rule_type = 'TOKEN_SUBJECT' THEN
|
||||||
'(SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_ROLE'') = ''' || NVL(pr2.rule_value, '[역할값 필요]') || ''' '
|
pr2.rule_column || ' = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_USER_ID'')'
|
||||||
|| 'AND FC_ID = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_USER_ID''))'
|
WHEN pr2.rule_type = 'OWN_CONTRACT' THEN
|
||||||
WHEN pr2.rule_type = 'STAKEHOLDER_SELF' AND pr2.rule_column = 'USER_ID' THEN
|
pr2.rule_column || ' = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_USER_ID'')'
|
||||||
'(USER_ID = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_USER_ID''))'
|
WHEN pr2.rule_type = 'CHANNEL_CONTRACT' THEN
|
||||||
WHEN pr2.rule_type = 'STAKEHOLDER_CHANNEL' AND pr2.rule_column = 'FC_CHANNEL' THEN
|
pr2.rule_column || ' = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_CHANNEL'')'
|
||||||
'(SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_ROLE'') = ''' || NVL(pr2.rule_value, '[역할값 필요]') || ''' '
|
WHEN pr2.rule_type = 'OWN_CUSTOMER' THEN
|
||||||
|| 'AND FC_CHANNEL = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_CHANNEL''))'
|
|
||||||
WHEN pr2.rule_type = 'STAKEHOLDER_SELF' THEN
|
|
||||||
'EXISTS (SELECT 1 FROM POC_2.KB_CONTRACTS c '
|
'EXISTS (SELECT 1 FROM POC_2.KB_CONTRACTS c '
|
||||||
|| 'WHERE c.' || pr2.rule_column || ' = [CURRENT_ROW].' || pr2.rule_column || ' '
|
|| 'WHERE c.' || pr2.rule_column || ' = [CURRENT_ROW].' || pr2.rule_column || ' '
|
||||||
|| 'AND SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_ROLE'') = ''' || NVL(pr2.rule_value, '[역할값 필요]') || ''' '
|
|
||||||
|| 'AND c.FC_ID = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_USER_ID''))'
|
|| 'AND c.FC_ID = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_USER_ID''))'
|
||||||
WHEN pr2.rule_type = 'STAKEHOLDER_CHANNEL' THEN
|
WHEN pr2.rule_type = 'CHANNEL_CUSTOMER' THEN
|
||||||
'EXISTS (SELECT 1 FROM POC_2.KB_CONTRACTS c '
|
'EXISTS (SELECT 1 FROM POC_2.KB_CONTRACTS c '
|
||||||
|| 'WHERE c.' || pr2.rule_column || ' = [CURRENT_ROW].' || pr2.rule_column || ' '
|
|| 'WHERE c.' || pr2.rule_column || ' = [CURRENT_ROW].' || pr2.rule_column || ' '
|
||||||
|| 'AND SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_ROLE'') = ''' || NVL(pr2.rule_value, '[역할값 필요]') || ''' '
|
|
||||||
|| 'AND c.FC_CHANNEL = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_CHANNEL''))'
|
|| 'AND c.FC_CHANNEL = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_CHANNEL''))'
|
||||||
|
WHEN pr2.rule_type = 'STATIC_SQL' THEN pr2.rule_value
|
||||||
ELSE pr2.rule_column || ' ' || pr2.rule_type || ' ' || pr2.rule_value
|
ELSE pr2.rule_column || ' ' || pr2.rule_type || ' ' || pr2.rule_value
|
||||||
END,
|
END,
|
||||||
CHR(10) || 'OR '
|
CHR(10) || 'AND '
|
||||||
) WITHIN GROUP (ORDER BY pr2.rule_id)
|
) WITHIN GROUP (ORDER BY pr2.rule_id)
|
||||||
FROM cb_permission_rule pr2
|
FROM cb_permission_rule pr2
|
||||||
WHERE pr2.perm_id = p.perm_id
|
WHERE pr2.perm_id = p.perm_id
|
||||||
|
|||||||
@@ -388,8 +388,8 @@ function syncRuleTypeHints(root = document) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const type = typeSelect.value;
|
const type = typeSelect.value;
|
||||||
const valueRequired = ['=', '!=', 'DEPT', 'EMP_NO', 'TAG', 'STAKEHOLDER_SELF', 'STAKEHOLDER_CHANNEL'].includes(type);
|
const valueRequired = ['=', '!=', 'DEPT', 'EMP_NO', 'TAG', 'STAKEHOLDER_SELF', 'STAKEHOLDER_CHANNEL', 'STATIC_SQL'].includes(type);
|
||||||
const columnOptional = ['ALL', 'MY_DEPT', 'SELF', 'DEPT', 'EMP_NO', 'TAG'].includes(type);
|
const columnOptional = ['ALL', 'MY_DEPT', 'SELF', 'DEPT', 'EMP_NO', 'TAG', 'STATIC_SQL'].includes(type);
|
||||||
const placeholderByType = {
|
const placeholderByType = {
|
||||||
ALL: '값 불필요',
|
ALL: '값 불필요',
|
||||||
MY_DEPT: '값 불필요',
|
MY_DEPT: '값 불필요',
|
||||||
@@ -399,17 +399,30 @@ function syncRuleTypeHints(root = document) {
|
|||||||
TAG: '예: SPRING_BOOT',
|
TAG: '예: SPRING_BOOT',
|
||||||
STAKEHOLDER_SELF: '예: 설계사',
|
STAKEHOLDER_SELF: '예: 설계사',
|
||||||
STAKEHOLDER_CHANNEL: '예: 지점장',
|
STAKEHOLDER_CHANNEL: '예: 지점장',
|
||||||
|
TOKEN_SUBJECT: '값 불필요',
|
||||||
|
OWN_CONTRACT: '값 불필요',
|
||||||
|
CHANNEL_CONTRACT: '값 불필요',
|
||||||
|
OWN_CUSTOMER: '값 불필요',
|
||||||
|
CHANNEL_CUSTOMER: '값 불필요',
|
||||||
|
STATIC_SQL: "예: CONTRACT_STATUS = '정상'",
|
||||||
'=': '비교 값',
|
'=': '비교 값',
|
||||||
'!=': '비교 값'
|
'!=': '비교 값'
|
||||||
};
|
};
|
||||||
|
|
||||||
columnSelect.required = !columnOptional;
|
columnSelect.required = !columnOptional;
|
||||||
|
columnSelect.disabled = ['ALL', 'MY_DEPT', 'SELF', 'DEPT', 'EMP_NO', 'TAG', 'STATIC_SQL'].includes(type);
|
||||||
valueInput.required = valueRequired;
|
valueInput.required = valueRequired;
|
||||||
valueInput.disabled = ['ALL', 'MY_DEPT', 'SELF'].includes(type);
|
valueInput.disabled = [
|
||||||
|
'ALL', 'MY_DEPT', 'SELF', 'TOKEN_SUBJECT', 'OWN_CONTRACT',
|
||||||
|
'CHANNEL_CONTRACT', 'OWN_CUSTOMER', 'CHANNEL_CUSTOMER'
|
||||||
|
].includes(type);
|
||||||
valueInput.placeholder = placeholderByType[type] || '비교 값';
|
valueInput.placeholder = placeholderByType[type] || '비교 값';
|
||||||
if (valueInput.disabled) {
|
if (valueInput.disabled) {
|
||||||
valueInput.value = '';
|
valueInput.value = '';
|
||||||
}
|
}
|
||||||
|
if (columnSelect.disabled) {
|
||||||
|
columnSelect.value = '';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -700,17 +713,29 @@ function collectWizardRules(root) {
|
|||||||
TAG: 'TECH_TAG',
|
TAG: 'TECH_TAG',
|
||||||
STAKEHOLDER_SELF: '선택 컬럼',
|
STAKEHOLDER_SELF: '선택 컬럼',
|
||||||
STAKEHOLDER_CHANNEL: '선택 컬럼',
|
STAKEHOLDER_CHANNEL: '선택 컬럼',
|
||||||
|
TOKEN_SUBJECT: '선택 컬럼',
|
||||||
|
OWN_CONTRACT: '선택 컬럼',
|
||||||
|
CHANNEL_CONTRACT: '선택 컬럼',
|
||||||
|
OWN_CUSTOMER: '선택 컬럼',
|
||||||
|
CHANNEL_CUSTOMER: '선택 컬럼',
|
||||||
|
STATIC_SQL: '',
|
||||||
ALL: ''
|
ALL: ''
|
||||||
}[type] || '';
|
}[type] || '';
|
||||||
if (type === 'ALL') {
|
if (type === 'ALL') {
|
||||||
return 'ALL';
|
return 'ALL';
|
||||||
}
|
}
|
||||||
|
if (type === 'STATIC_SQL') {
|
||||||
|
return `정적 SQL: ${value}`;
|
||||||
|
}
|
||||||
if (['MY_DEPT', 'SELF'].includes(type)) {
|
if (['MY_DEPT', 'SELF'].includes(type)) {
|
||||||
return `${displayColumn} ${type}`;
|
return `${displayColumn} ${type}`;
|
||||||
}
|
}
|
||||||
if (['STAKEHOLDER_SELF', 'STAKEHOLDER_CHANNEL'].includes(type)) {
|
if (['STAKEHOLDER_SELF', 'STAKEHOLDER_CHANNEL'].includes(type)) {
|
||||||
return `${displayColumn} ${type} 역할=${value}`;
|
return `${displayColumn} ${type} 역할=${value}`;
|
||||||
}
|
}
|
||||||
|
if (['TOKEN_SUBJECT', 'OWN_CONTRACT', 'CHANNEL_CONTRACT', 'OWN_CUSTOMER', 'CHANNEL_CUSTOMER'].includes(type)) {
|
||||||
|
return `${displayColumn} ${type}`;
|
||||||
|
}
|
||||||
if (type === 'TAG') {
|
if (type === 'TAG') {
|
||||||
return `${displayColumn} TAG ${value.toUpperCase()}`;
|
return `${displayColumn} TAG ${value.toUpperCase()}`;
|
||||||
}
|
}
|
||||||
@@ -731,11 +756,20 @@ function collectWizardPredicates(root) {
|
|||||||
TAG: 'TECH_TAG',
|
TAG: 'TECH_TAG',
|
||||||
STAKEHOLDER_SELF: '선택 컬럼',
|
STAKEHOLDER_SELF: '선택 컬럼',
|
||||||
STAKEHOLDER_CHANNEL: '선택 컬럼',
|
STAKEHOLDER_CHANNEL: '선택 컬럼',
|
||||||
|
TOKEN_SUBJECT: '선택 컬럼',
|
||||||
|
OWN_CONTRACT: '선택 컬럼',
|
||||||
|
CHANNEL_CONTRACT: '선택 컬럼',
|
||||||
|
OWN_CUSTOMER: '선택 컬럼',
|
||||||
|
CHANNEL_CUSTOMER: '선택 컬럼',
|
||||||
|
STATIC_SQL: '',
|
||||||
ALL: ''
|
ALL: ''
|
||||||
}[type] || '';
|
}[type] || '';
|
||||||
if (type === 'ALL') {
|
if (type === 'ALL') {
|
||||||
return '1 = 1';
|
return '1 = 1';
|
||||||
}
|
}
|
||||||
|
if (type === 'STATIC_SQL') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
if (type === 'MY_DEPT') {
|
if (type === 'MY_DEPT') {
|
||||||
return `${displayColumn} = SYS_CONTEXT('CB_AGENT_CTX', 'DEPT_CODE')`;
|
return `${displayColumn} = SYS_CONTEXT('CB_AGENT_CTX', 'DEPT_CODE')`;
|
||||||
}
|
}
|
||||||
@@ -748,6 +782,21 @@ function collectWizardPredicates(root) {
|
|||||||
if (type === 'STAKEHOLDER_CHANNEL') {
|
if (type === 'STAKEHOLDER_CHANNEL') {
|
||||||
return `EXISTS (KB_CONTRACTS c: c.${displayColumn} = <현재행>.${displayColumn} AND TOKEN_ROLE = '${value}' AND c.FC_CHANNEL = TOKEN_CHANNEL)`;
|
return `EXISTS (KB_CONTRACTS c: c.${displayColumn} = <현재행>.${displayColumn} AND TOKEN_ROLE = '${value}' AND c.FC_CHANNEL = TOKEN_CHANNEL)`;
|
||||||
}
|
}
|
||||||
|
if (type === 'TOKEN_SUBJECT') {
|
||||||
|
return `${displayColumn} = SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_USER_ID')`;
|
||||||
|
}
|
||||||
|
if (type === 'OWN_CONTRACT') {
|
||||||
|
return `${displayColumn} = SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_USER_ID')`;
|
||||||
|
}
|
||||||
|
if (type === 'CHANNEL_CONTRACT') {
|
||||||
|
return `${displayColumn} = SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_CHANNEL')`;
|
||||||
|
}
|
||||||
|
if (type === 'OWN_CUSTOMER') {
|
||||||
|
return `${displayColumn} IN (SELECT CUST_ID FROM KB_CONTRACTS WHERE FC_ID = SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_USER_ID'))`;
|
||||||
|
}
|
||||||
|
if (type === 'CHANNEL_CUSTOMER') {
|
||||||
|
return `${displayColumn} IN (SELECT CUST_ID FROM KB_CONTRACTS WHERE FC_CHANNEL = SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_CHANNEL'))`;
|
||||||
|
}
|
||||||
if (type === 'DEPT' || type === 'EMP_NO') {
|
if (type === 'DEPT' || type === 'EMP_NO') {
|
||||||
return `${displayColumn} = ${sqlLiteral(value)}`;
|
return `${displayColumn} = ${sqlLiteral(value)}`;
|
||||||
}
|
}
|
||||||
@@ -794,8 +843,8 @@ function updatePermissionWizardPreview(root = document) {
|
|||||||
? `거부 규칙: ${ruleText}`
|
? `거부 규칙: ${ruleText}`
|
||||||
: `허용 규칙: ${ruleText}`;
|
: `허용 규칙: ${ruleText}`;
|
||||||
const predicateText = effect === 'DENY'
|
const predicateText = effect === 'DENY'
|
||||||
? (rules.includes('ALL') ? 'DENY ALL: 1 = 0' : `DENY 후보: NOT (${predicates.join(' OR ') || '조건 없음'})`)
|
? (rules.includes('ALL') ? 'DENY ALL: 1 = 0' : `DENY 후보: NOT (${predicates.join(' AND ') || '조건 없음'})`)
|
||||||
: `ALLOW 후보: ${predicates.join(' OR ') || '조건 없음'}`;
|
: `ALLOW 후보: ${predicates.join(' AND ') || '조건 없음'}`;
|
||||||
const columnPolicy = visibleColumns
|
const columnPolicy = visibleColumns
|
||||||
? `이 권한에서 원문 표시 허용: ${visibleColumns}`
|
? `이 권한에서 원문 표시 허용: ${visibleColumns}`
|
||||||
: '마스킹 대상 컬럼은 기본 정책대로 NULL/마스킹 처리';
|
: '마스킹 대상 컬럼은 기본 정책대로 NULL/마스킹 처리';
|
||||||
@@ -872,19 +921,25 @@ function permissionWizardRuleValidationMessage(wizard) {
|
|||||||
if (values.some((rule) => rule.type === 'ALL') && values.length > 1) {
|
if (values.some((rule) => rule.type === 'ALL') && values.length > 1) {
|
||||||
return 'ALL 규칙은 다른 조건 규칙과 함께 저장할 수 없습니다. ALL만 남기거나 ALL을 삭제하세요.';
|
return 'ALL 규칙은 다른 조건 규칙과 함께 저장할 수 없습니다. ALL만 남기거나 ALL을 삭제하세요.';
|
||||||
}
|
}
|
||||||
const valueRequired = ['=', '!=', 'DEPT', 'EMP_NO', 'TAG', 'STAKEHOLDER_SELF', 'STAKEHOLDER_CHANNEL'];
|
const valueRequired = ['=', '!=', 'DEPT', 'EMP_NO', 'TAG', 'STAKEHOLDER_SELF', 'STAKEHOLDER_CHANNEL', 'STATIC_SQL'];
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
for (const [index, rule] of values.entries()) {
|
for (const [index, rule] of values.entries()) {
|
||||||
const position = index + 1;
|
const position = index + 1;
|
||||||
if (!rule.type) {
|
if (!rule.type) {
|
||||||
return `${position}번째 행 규칙 유형을 선택하세요.`;
|
return `${position}번째 행 규칙 유형을 선택하세요.`;
|
||||||
}
|
}
|
||||||
if (['=', '!=', 'STAKEHOLDER_SELF', 'STAKEHOLDER_CHANNEL'].includes(rule.type) && !rule.column) {
|
if ([
|
||||||
|
'=', '!=', 'STAKEHOLDER_SELF', 'STAKEHOLDER_CHANNEL', 'TOKEN_SUBJECT',
|
||||||
|
'OWN_CONTRACT', 'CHANNEL_CONTRACT', 'OWN_CUSTOMER', 'CHANNEL_CUSTOMER'
|
||||||
|
].includes(rule.type) && !rule.column) {
|
||||||
return `${position}번째 ${rule.type} 규칙에는 비교할 컬럼이 필요합니다.`;
|
return `${position}번째 ${rule.type} 규칙에는 비교할 컬럼이 필요합니다.`;
|
||||||
}
|
}
|
||||||
if (valueRequired.includes(rule.type) && !rule.value) {
|
if (valueRequired.includes(rule.type) && !rule.value) {
|
||||||
return `${position}번째 ${rule.type} 규칙의 값을 입력하세요.`;
|
return `${position}번째 ${rule.type} 규칙의 값을 입력하세요.`;
|
||||||
}
|
}
|
||||||
|
if (rule.type === 'STATIC_SQL' && rule.column) {
|
||||||
|
return `${position}번째 정적 SQL 조건은 컬럼을 따로 선택하지 마세요.`;
|
||||||
|
}
|
||||||
if (rule.type === 'TAG' && rule.value && !/^[A-Za-z0-9_-]+$/.test(rule.value)) {
|
if (rule.type === 'TAG' && rule.value && !/^[A-Za-z0-9_-]+$/.test(rule.value)) {
|
||||||
return `${position}번째 TAG 값은 영문·숫자, '_' 또는 '-'만 사용할 수 있습니다.`;
|
return `${position}번째 TAG 값은 영문·숫자, '_' 또는 '-'만 사용할 수 있습니다.`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<h1>접근 규칙</h1>
|
<h1>접근 규칙</h1>
|
||||||
<details class="explanation-details">
|
<details class="explanation-details">
|
||||||
<summary>도움말</summary>
|
<summary>도움말</summary>
|
||||||
<p>VPD policy function이 참조할 사용자, 역할, 행 규칙, 컬럼 원문 허용 규칙을 저장합니다. KB 원장 예제는 토큰 주체에서 한 번 해석한 이해당사자 ID·역할·채널을 secure context로 사용하고, 대상 고객은 계약원장으로만 연결합니다. <code>STAKEHOLDER_SELF</code>와 <code>STAKEHOLDER_CHANNEL</code>은 각각의 역할 분기로 만들고 허용 분기끼리는 OR로 계산합니다.</p>
|
<p>VPD policy function이 참조할 사용자, 역할, 행 규칙, 컬럼 원문 허용 규칙을 저장합니다. KB 조건 코드는 토큰 context 값으로 치환되고, 정적 SQL 조건은 대상 객체의 WHERE 절에 그대로 추가됩니다. 한 권한의 규칙은 AND, 서로 다른 ALLOW 권한은 OR로 합치며 DENY는 허용 결과에서 제외합니다.</p>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -154,31 +154,32 @@
|
|||||||
<div id="rowRuleList" class="rule-list">
|
<div id="rowRuleList" class="rule-list">
|
||||||
<div class="rule-row">
|
<div class="rule-row">
|
||||||
<select class="form-select rule-column-select" name="ruleColumn">
|
<select class="form-select rule-column-select" name="ruleColumn">
|
||||||
<option value="">기본 컬럼</option>
|
<option value="">객체 컬럼 선택</option>
|
||||||
<option value="DEPT_CODE">DEPT_CODE</option>
|
|
||||||
<option value="OWNER_EMP_NO">OWNER_EMP_NO</option>
|
|
||||||
<option value="DOC_ID">DOC_ID</option>
|
|
||||||
<option value="TITLE">TITLE</option>
|
|
||||||
</select>
|
</select>
|
||||||
<select class="form-select rule-type-select" name="ruleType">
|
<select class="form-select rule-type-select" name="ruleType">
|
||||||
<option value="ALL">ALL</option>
|
<option value="ALL">ALL</option>
|
||||||
<option value="MY_DEPT">현재 사용자 부서</option>
|
<optgroup label="KB · 본인 범위">
|
||||||
<option value="SELF">현재 사용자 사번</option>
|
<option value="OWN_CONTRACT">본인 담당 계약</option>
|
||||||
<option value="DEPT">지정 부서</option>
|
<option value="OWN_CUSTOMER">본인 담당 고객</option>
|
||||||
<option value="EMP_NO">지정 사번</option>
|
</optgroup>
|
||||||
<option value="TAG">특정 기술 태그</option>
|
<optgroup label="KB · 채널 범위">
|
||||||
<option value="STAKEHOLDER_SELF">이해당사자 본인 담당</option>
|
<option value="CHANNEL_CONTRACT">토큰 채널 계약</option>
|
||||||
<option value="STAKEHOLDER_CHANNEL">이해당사자 채널 범위</option>
|
<option value="CHANNEL_CUSTOMER">토큰 채널 고객</option>
|
||||||
<option value="=">=</option>
|
</optgroup>
|
||||||
<option value="!=">!=</option>
|
<optgroup label="KB · 토큰 식별">
|
||||||
|
<option value="TOKEN_SUBJECT">토큰 사용자 본인</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label="SQL · 정적 조건">
|
||||||
|
<option value="STATIC_SQL">정적 SQL 조건식</option>
|
||||||
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
<input class="form-control" name="ruleValue" placeholder="비교 값">
|
<input class="form-control" name="ruleValue" maxlength="1000" placeholder="비교 값">
|
||||||
<button class="btn btn-outline-secondary" type="button" data-rule-add>추가</button>
|
<button class="btn btn-outline-secondary" type="button" data-rule-add>추가</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<details class="explanation-details">
|
<details class="explanation-details">
|
||||||
<summary>행 규칙과 기술 태그 OR 조건 보기</summary>
|
<summary>행 규칙의 두 가지 적용 방식 보기</summary>
|
||||||
<p class="wizard-hint">특정 기술 태그는 기본적으로 <code>TECH_TAG</code> 컬럼을 봅니다. 같은 ALLOW 권한에 태그를 여러 개 추가하면 “태그 A 또는 태그 B”로 조회됩니다. 이해당사자 규칙은 <strong>비교 컬럼</strong>과 <strong>업무 역할값</strong>(예: 설계사, 지점장)을 모두 입력하세요. 토큰을 해석한 뒤 <code>STAKEHOLDER_USER_ID</code>·<code>STAKEHOLDER_ROLE</code>·<code>STAKEHOLDER_CHANNEL</code> context를 사용합니다. <code>STAKEHOLDER_SELF</code>은 <code>c.FC_ID = context 사용자</code>, <code>STAKEHOLDER_CHANNEL</code>은 <code>c.FC_CHANNEL = context 채널</code>이며 서로 다른 허용 규칙은 OR로 결합됩니다.</p>
|
<p class="wizard-hint"><strong>조건 코드</strong>는 토큰 context로 치환됩니다. 예를 들어 <code>본인 담당 고객 / CUST_ID</code>는 계약원장에서 토큰 사용자 ID의 담당 고객을 찾아 현재 객체의 <code>CUST_ID</code>에 적용합니다. <strong>정적 SQL 조건식</strong>은 <code>CONTRACT_STATUS = '정상'</code>처럼 현재 객체 컬럼을 사용한 WHERE 절을 그대로 추가합니다. 한 권한 안의 규칙은 모두 AND로 좁혀지고, 서로 다른 역할의 ALLOW 권한은 OR로 합쳐집니다. 역할명은 VPD WHERE 절에 직접 들어가지 않습니다.</p>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -241,7 +242,7 @@
|
|||||||
|
|
||||||
<section class="content-band">
|
<section class="content-band">
|
||||||
<h2>접근 규칙 목록</h2>
|
<h2>접근 규칙 목록</h2>
|
||||||
<p class="section-subtitle">같은 보호 대상에 적용되는 ALLOW 역할 분기와 한 권한 안의 여러 행 규칙은 OR로 합칩니다. 예를 들어 설계사 본인계약 분기 또는 지점장 채널 분기 중 하나라도 성립하면 행이 허용됩니다. DENY 분기는 허용 결과에서 별도로 제외됩니다.</p>
|
<p class="section-subtitle">한 접근 규칙 안에서는 조건 코드와 정적 SQL 조건을 AND로 합쳐 범위를 좁힙니다. 같은 보호 대상의 서로 다른 ALLOW 권한은 OR, DENY 권한은 허용 결과에서 제외됩니다.</p>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-sm align-middle permission-list-table">
|
<table class="table table-sm align-middle permission-list-table">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -221,6 +221,83 @@ class PermissionServiceTest {
|
|||||||
.hasMessageContaining("값이 필요");
|
.hasMessageContaining("값이 필요");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void acceptsKbFilterConditionCodesWithoutAStaticRuleValue() {
|
||||||
|
var command = new PermissionSetCommand(
|
||||||
|
10L,
|
||||||
|
1L,
|
||||||
|
"SELECT",
|
||||||
|
"ALLOW",
|
||||||
|
List.of(
|
||||||
|
new RuleCommand("DEPT_CODE", "OWN_CONTRACT", null),
|
||||||
|
new RuleCommand("OWNER_EMP_NO", "CHANNEL_CUSTOMER", null)
|
||||||
|
),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
|
||||||
|
permissionService.savePermissionSet(command);
|
||||||
|
|
||||||
|
FakePermissionMapper mapper = (FakePermissionMapper) permissionMapper;
|
||||||
|
assertThat(mapper.insertedRules)
|
||||||
|
.extracting(PermissionRule::ruleType)
|
||||||
|
.containsExactly("OWN_CONTRACT", "CHANNEL_CUSTOMER");
|
||||||
|
assertThat(mapper.insertedRules)
|
||||||
|
.extracting(PermissionRule::ruleValue)
|
||||||
|
.containsExactly("", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void acceptsStaticSqlPredicateAlongsideAContextCondition() {
|
||||||
|
var command = new PermissionSetCommand(
|
||||||
|
10L,
|
||||||
|
1L,
|
||||||
|
"SELECT",
|
||||||
|
"ALLOW",
|
||||||
|
List.of(
|
||||||
|
new RuleCommand("DEPT_CODE", "OWN_CONTRACT", null),
|
||||||
|
new RuleCommand(null, "STATIC_SQL", "DEPT_CODE IN ('HR', 'FIN')")
|
||||||
|
),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
|
||||||
|
permissionService.savePermissionSet(command);
|
||||||
|
|
||||||
|
FakePermissionMapper mapper = (FakePermissionMapper) permissionMapper;
|
||||||
|
assertThat(mapper.insertedRules)
|
||||||
|
.extracting(PermissionRule::ruleType)
|
||||||
|
.containsExactly("OWN_CONTRACT", "STATIC_SQL");
|
||||||
|
assertThat(mapper.insertedRules)
|
||||||
|
.extracting(PermissionRule::ruleValue)
|
||||||
|
.containsExactly("", "DEPT_CODE IN ('HR', 'FIN')");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsStaticSqlStatementOrUnknownColumn() {
|
||||||
|
var statementCommand = new PermissionSetCommand(
|
||||||
|
10L,
|
||||||
|
1L,
|
||||||
|
"SELECT",
|
||||||
|
"ALLOW",
|
||||||
|
List.of(new RuleCommand(null, "STATIC_SQL", "DEPT_CODE = 'HR'; DELETE FROM CB_APP_USER")),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
var unknownColumnCommand = new PermissionSetCommand(
|
||||||
|
10L,
|
||||||
|
1L,
|
||||||
|
"SELECT",
|
||||||
|
"ALLOW",
|
||||||
|
List.of(new RuleCommand(null, "STATIC_SQL", "UNSUPPORTED_COLUMN = 'HR'")),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> permissionService.savePermissionSet(statementCommand))
|
||||||
|
.isInstanceOf(AppException.class)
|
||||||
|
.hasMessageContaining("단일 WHERE");
|
||||||
|
assertThatThrownBy(() -> permissionService.savePermissionSet(unknownColumnCommand))
|
||||||
|
.isInstanceOf(AppException.class)
|
||||||
|
.hasMessageContaining("보호 객체 컬럼");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void acceptsMultipleVisibleColumns() {
|
void acceptsMultipleVisibleColumns() {
|
||||||
var command = new PermissionSetCommand(
|
var command = new PermissionSetCommand(
|
||||||
|
|||||||
@@ -157,14 +157,16 @@ class GuidedFlowTemplateTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void permissionWizardExplainsTagRulesAndOrSemantics() throws IOException {
|
void permissionWizardExplainsConditionCodeAndStaticSqlSemantics() throws IOException {
|
||||||
String html = template("permissions.html");
|
String html = template("permissions.html");
|
||||||
String javascript = Files.readString(Path.of("src/main/resources/static/js/app.js"));
|
String javascript = Files.readString(Path.of("src/main/resources/static/js/app.js"));
|
||||||
|
|
||||||
assertThat(html)
|
assertThat(html)
|
||||||
.contains("value=\"TAG\">특정 기술 태그")
|
.contains("value=\"OWN_CUSTOMER\">본인 담당 고객")
|
||||||
.contains("TECH_TAG")
|
.contains("value=\"STATIC_SQL\">정적 SQL 조건식")
|
||||||
.contains("태그를 여러 개 추가하면")
|
.contains("행 규칙의 두 가지 적용 방식 보기")
|
||||||
|
.contains("한 권한 안의 규칙은 모두 AND")
|
||||||
|
.contains("서로 다른 역할의 ALLOW 권한은 OR")
|
||||||
.contains("data-wizard-validation")
|
.contains("data-wizard-validation")
|
||||||
.contains("다른 조건 규칙은 한 권한에 함께 저장할 수 없습니다")
|
.contains("다른 조건 규칙은 한 권한에 함께 저장할 수 없습니다")
|
||||||
.contains("저장 영향")
|
.contains("저장 영향")
|
||||||
|
|||||||
Reference in New Issue
Block a user