[Developer] #619 map VPD rules to token context variables

This commit is contained in:
devmrko
2026-07-07 14:24:50 +09:00
parent 2cdc738eb9
commit ef944a6758
9 changed files with 262 additions and 80 deletions

View File

@@ -170,56 +170,79 @@ BEGIN
);
END IF;
ELSIF r.rule_type IN ('STAKEHOLDER_SELF', 'STAKEHOLDER_CHANNEL') THEN
-- 업무 사용자 원장(KB_STAKEHOLDERS)에서 해석한 토큰 주체를 쓴다.
-- CUST_ID/CONTRACT_NO는 계약원장을 한 단계 감싼 EXISTS로만 연결해
-- 임의 SQL이나 외부 rule text가 predicate에 섞이지 않게 한다.
-- CB_AGENT_CTX_PKG가 Bearer token -> KB_STAKEHOLDERS를 한 번
-- 해석해 USER_ID/ROLE/CHANNEL을 secure context로 설정한다.
-- 따라서 보호 테이블 predicate에서 KB_STAKEHOLDERS를 다시 조인하지
-- 않고, 해당 context와 KB_CONTRACTS 관계만 사용한다.
v_column := safe_column(p_schema, p_object, r.rule_column);
IF v_column IS NOT NULL
AND UPPER(p_schema) = 'POC_2' THEN
IF r.rule_type = 'STAKEHOLDER_SELF'
AND ((v_target = 'KB_CONTRACTS' AND v_column = 'FC_ID')
OR (v_target = 'KB_STAKEHOLDERS' AND v_column = 'USER_ID')) THEN
append_or(
r.permission_effect,
v_column || ' = 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_column || ' = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_CHANNEL'')'
);
ELSIF v_target IN ('KB_CUSTOMERS', 'KB_COVERAGES', 'KB_CLAIMS', 'KB_EXTERNAL_HOLDINGS')
AND v_column IN ('CUST_ID', 'CONTRACT_NO') THEN
DECLARE
v_contract_key VARCHAR2(30);
v_context_key VARCHAR2(30);
v_context_name VARCHAR2(30);
BEGIN
DECLARE
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_column = 'USER_ID' THEN
append_or(
r.permission_effect,
'(' || 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')
AND v_column IN ('CUST_ID', 'CONTRACT_NO') THEN
v_contract_key := CASE v_column
WHEN 'CUST_ID' THEN 'CUST_ID'
WHEN 'CONTRACT_NO' THEN 'CONTRACT_NO'
END;
v_context_key := CASE r.rule_type
WHEN 'STAKEHOLDER_SELF' THEN 'FC_ID'
ELSE 'FC_CHANNEL'
END;
v_context_name := CASE r.rule_type
WHEN 'STAKEHOLDER_SELF' THEN 'STAKEHOLDER_USER_ID'
ELSE 'STAKEHOLDER_CHANNEL'
END;
append_or(
r.permission_effect,
'EXISTS (SELECT 1 FROM (' ||
'SELECT c.' || v_contract_key || ' AS access_key ' ||
'FROM POC_2.KB_CONTRACTS c ' ||
'WHERE c.' || v_context_key || ' = SYS_CONTEXT(''CB_AGENT_CTX'', ''' ||
v_context_name || ''')) allowed_contract ' ||
'WHERE allowed_contract.access_key = ' || v_column || ')'
);
END;
END IF;
IF r.rule_type = 'STAKEHOLDER_SELF' THEN
append_or(
r.permission_effect,
'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
append_or(
r.permission_effect,
'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_CHANNEL = SYS_CONTEXT(''CB_AGENT_CTX'', ''STAKEHOLDER_CHANNEL'')) allowed_contract ' ||
'WHERE allowed_contract.access_key = ' || v_column || ')'
);
END IF;
END IF;
END;
END IF;
ELSIF r.rule_type = '=' THEN
v_column := safe_column(p_schema, p_object, r.rule_column);

View File

@@ -313,7 +313,8 @@ DECLARE
p_role_id IN NUMBER,
p_target_name IN VARCHAR2,
p_rule_column IN VARCHAR2,
p_rule_type IN VARCHAR2
p_rule_type IN VARCHAR2,
p_rule_value IN VARCHAR2
) IS
v_permission_id NUMBER;
BEGIN
@@ -328,7 +329,7 @@ DECLARE
INSERT INTO cb_permission(perm_id, role_id, target_name, action_name, permission_effect)
VALUES (v_permission_id, p_role_id, p_target_name, 'SELECT', 'ALLOW');
INSERT INTO cb_permission_rule(rule_id, perm_id, rule_column, rule_type, rule_value)
VALUES (cb_permission_rule_seq.NEXTVAL, v_permission_id, p_rule_column, p_rule_type, NULL);
VALUES (cb_permission_rule_seq.NEXTVAL, v_permission_id, p_rule_column, p_rule_type, p_rule_value);
END;
BEGIN
ensure_role('KB_DESIGNER_ROLE', 'CONFIDENTIAL', v_designer_role_id);
@@ -382,20 +383,20 @@ BEGIN
-- 토큰은 모든 이해당사자에게 발급되지만, 아래 역할이 없는 주체는
-- 권한 레코드가 없으므로 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');
ensure_permission(v_manager_role_id, 'KB_PRODUCTS', NULL, 'ALL');
ensure_permission(v_designer_role_id, 'KB_CONTRACTS', 'FC_ID', 'STAKEHOLDER_SELF');
ensure_permission(v_manager_role_id, 'KB_CONTRACTS', 'FC_CHANNEL', 'STAKEHOLDER_CHANNEL');
ensure_permission(v_designer_role_id, 'KB_CUSTOMERS', 'CUST_ID', 'STAKEHOLDER_SELF');
ensure_permission(v_manager_role_id, 'KB_CUSTOMERS', 'CUST_ID', 'STAKEHOLDER_CHANNEL');
ensure_permission(v_designer_role_id, 'KB_COVERAGES', 'CUST_ID', 'STAKEHOLDER_SELF');
ensure_permission(v_manager_role_id, 'KB_COVERAGES', 'CUST_ID', 'STAKEHOLDER_CHANNEL');
ensure_permission(v_designer_role_id, 'KB_CLAIMS', 'CUST_ID', 'STAKEHOLDER_SELF');
ensure_permission(v_manager_role_id, 'KB_CLAIMS', 'CUST_ID', 'STAKEHOLDER_CHANNEL');
ensure_permission(v_designer_role_id, 'KB_EXTERNAL_HOLDINGS', 'CUST_ID', 'STAKEHOLDER_SELF');
ensure_permission(v_manager_role_id, 'KB_EXTERNAL_HOLDINGS', 'CUST_ID', 'STAKEHOLDER_CHANNEL');
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_manager_role_id, 'KB_PRODUCTS', NULL, 'ALL', NULL);
ensure_permission(v_designer_role_id, 'KB_CONTRACTS', 'FC_ID', 'STAKEHOLDER_SELF', '설계사');
ensure_permission(v_manager_role_id, 'KB_CONTRACTS', 'FC_CHANNEL', 'STAKEHOLDER_CHANNEL', '지점장');
ensure_permission(v_designer_role_id, 'KB_CUSTOMERS', 'CUST_ID', 'STAKEHOLDER_SELF', '설계사');
ensure_permission(v_manager_role_id, 'KB_CUSTOMERS', 'CUST_ID', 'STAKEHOLDER_CHANNEL', '지점장');
ensure_permission(v_designer_role_id, 'KB_COVERAGES', 'CUST_ID', 'STAKEHOLDER_SELF', '설계사');
ensure_permission(v_manager_role_id, 'KB_COVERAGES', 'CUST_ID', 'STAKEHOLDER_CHANNEL', '지점장');
ensure_permission(v_designer_role_id, 'KB_CLAIMS', 'CUST_ID', 'STAKEHOLDER_SELF', '설계사');
ensure_permission(v_manager_role_id, 'KB_CLAIMS', 'CUST_ID', 'STAKEHOLDER_CHANNEL', '지점장');
ensure_permission(v_designer_role_id, 'KB_EXTERNAL_HOLDINGS', 'CUST_ID', 'STAKEHOLDER_SELF', '설계사');
ensure_permission(v_manager_role_id, 'KB_EXTERNAL_HOLDINGS', 'CUST_ID', 'STAKEHOLDER_CHANNEL', '지점장');
END;
/
COMMIT;

View File

@@ -0,0 +1,104 @@
-- ============================================================
-- 47_kb_stakeholder_permission_or_branches.sql
--
-- Refines the KB example so the permission table carries the expected
-- stakeholder role and each role branch becomes an OR candidate in the
-- dynamic VPD filter. Execute after 26 and 46 as ADMIN.
-- ============================================================
WHENEVER SQLERROR EXIT SQL.SQLCODE
SET ECHO OFF
SET FEEDBACK ON
SET DEFINE OFF
PROMPT === 1. Storing expected stakeholder role in example rules ===
UPDATE cb_permission_rule r
SET rule_value = 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'
) THEN '설계사'
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'
) THEN '지점장'
END
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')
);
PROMPT === 2. Adding an identity role so every stakeholder token can resolve itself ===
DECLARE
v_role_id NUMBER;
v_permission_id NUMBER;
v_count NUMBER;
BEGIN
BEGIN
SELECT role_id INTO v_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_role_id FROM cb_app_role;
INSERT INTO cb_app_role(role_id, role_name, max_sensitivity_level)
VALUES (v_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_role_id;
IF v_count = 0 THEN
INSERT INTO cb_user_role(user_id, role_id) VALUES (u.user_id, v_role_id);
END IF;
END LOOP;
BEGIN
SELECT perm_id INTO v_permission_id
FROM cb_permission
WHERE role_id = v_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_role_id, 'KB_STAKEHOLDERS', 'SELECT', 'ALLOW');
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);
END;
END;
/
COMMIT;
PROMPT === 3. Result: role-specific permission branches ===
SELECT ar.role_name,
p.target_name,
r.rule_column,
r.rule_type,
NVL(r.rule_value, '(token role)') AS expected_stakeholder_role
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 role branches are combined as OR by CB_AGENT_DOC_VPD_FILTER ===