Consolidate data access control backoffice updates
This commit is contained in:
94
sql/adb/16_agent_ords_security_local_cleanup.sql
Normal file
94
sql/adb/16_agent_ords_security_local_cleanup.sql
Normal file
@@ -0,0 +1,94 @@
|
||||
-- ============================================================
|
||||
-- 16_agent_ords_security_local_cleanup.sql
|
||||
-- Agent ORDS security local-only cleanup.
|
||||
--
|
||||
-- This removes only CB_* objects used by the local ADB example.
|
||||
-- It does not touch the existing RDS DB Link POC objects.
|
||||
-- ============================================================
|
||||
WHENEVER SQLERROR CONTINUE
|
||||
SET ECHO OFF
|
||||
SET FEEDBACK ON
|
||||
|
||||
PROMPT === Cleaning VPD policy ===
|
||||
BEGIN
|
||||
DBMS_REDACT.DROP_POLICY(
|
||||
object_schema => USER,
|
||||
object_name => 'CB_V_SEARCH_DOCUMENTS',
|
||||
policy_name => 'CB_CONTENTS_REDACT'
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN NULL;
|
||||
END;
|
||||
/
|
||||
|
||||
BEGIN
|
||||
DBMS_RLS.DROP_POLICY(
|
||||
object_schema => USER,
|
||||
object_name => 'CB_V_SEARCH_DOCUMENTS',
|
||||
policy_name => 'CB_AGENT_DOC_POLICY'
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN NULL;
|
||||
END;
|
||||
/
|
||||
|
||||
PROMPT === Cleaning DDS data grants ===
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP DATA GRANT admin.cb_dg_hr_docs'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP DATA GRANT admin.cb_dg_fin_docs'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP DATA GRANT admin.cb_dg_all_docs'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
|
||||
PROMPT === Cleaning DDS end users and roles ===
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP END USER "cb_dds_hr"'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP END USER "cb_dds_fin"'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP END USER "cb_dds_all"'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP END USER "cb_dds_none"'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP DATA ROLE cb_dds_hr_role'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP DATA ROLE cb_dds_fin_role'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP DATA ROLE cb_dds_all_role'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP DATA ROLE cb_dds_connect_only_role'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP ROLE cb_dds_connect_role'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
|
||||
PROMPT === Cleaning DB user and local objects ===
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP USER cb_ords CASCADE'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP VIEW cb_dds_v_search_documents'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP VIEW cb_v_search_documents'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP FUNCTION cb_agent_doc_vpd_filter'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP CONTEXT cb_agent_ctx'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP PACKAGE cb_agent_ctx_pkg'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP TABLE cb_agent_bearer_key PURGE'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP TABLE cb_permission_rule PURGE'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP TABLE cb_permission PURGE'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP TABLE cb_user_role PURGE'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP TABLE cb_app_role PURGE'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP TABLE cb_app_user PURGE'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP TABLE cb_dds_documents PURGE'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
BEGIN EXECUTE IMMEDIATE 'DROP TABLE cb_search_documents PURGE'; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
/
|
||||
|
||||
PROMPT === Agent ORDS security local cleanup complete ===
|
||||
EXIT;
|
||||
115
sql/adb/18_agent_ords_security_local_vpd_test.sql
Normal file
115
sql/adb/18_agent_ords_security_local_vpd_test.sql
Normal file
@@ -0,0 +1,115 @@
|
||||
-- ============================================================
|
||||
-- 18_agent_ords_security_local_vpd_test.sql
|
||||
-- Run as CB_ORDS.
|
||||
--
|
||||
-- Expected:
|
||||
-- no context -> 0 rows
|
||||
-- cb_hr_key -> HR rows only, 3 rows
|
||||
-- cb_fin_key -> owner_emp_no = E2001 only, 1 row
|
||||
-- cb_all_key -> all rows, 6 rows
|
||||
-- contents column -> NULL unless CAN_READ_CONTENTS = Y
|
||||
-- invalid key -> ORA-20002, then 0 rows
|
||||
-- direct base table -> ORA-00942
|
||||
-- ============================================================
|
||||
WHENEVER SQLERROR CONTINUE
|
||||
SET ECHO OFF
|
||||
SET FEEDBACK ON
|
||||
SET LINESIZE 220
|
||||
SET PAGESIZE 100
|
||||
COLUMN ctx_user_id FORMAT A12
|
||||
COLUMN ctx_emp_no FORMAT A12
|
||||
COLUMN ctx_dept_code FORMAT A12
|
||||
COLUMN ctx_can_read_contents FORMAT A22
|
||||
COLUMN title FORMAT A28
|
||||
COLUMN owner_emp_no FORMAT A12
|
||||
COLUMN dept_code FORMAT A10
|
||||
COLUMN contents FORMAT A35
|
||||
|
||||
PROMPT
|
||||
PROMPT === VPD 1. No Bearer key / context: fail closed ===
|
||||
BEGIN
|
||||
admin.cb_agent_ctx_pkg.clear_user;
|
||||
END;
|
||||
/
|
||||
|
||||
SELECT SYS_CONTEXT('CB_AGENT_CTX','USER_ID') AS ctx_user_id,
|
||||
SYS_CONTEXT('CB_AGENT_CTX','EMP_NO') AS ctx_emp_no,
|
||||
SYS_CONTEXT('CB_AGENT_CTX','DEPT_CODE') AS ctx_dept_code,
|
||||
SYS_CONTEXT('CB_AGENT_CTX','CAN_READ_CONTENTS') AS ctx_can_read_contents
|
||||
FROM dual;
|
||||
|
||||
SELECT COUNT(*) AS rows_visible
|
||||
FROM admin.cb_v_search_documents;
|
||||
|
||||
PROMPT
|
||||
PROMPT === VPD 2. Authorization: Bearer cb_hr_key -> HR department rows ===
|
||||
BEGIN
|
||||
admin.cb_agent_ctx_pkg.set_user_by_bearer('cb_hr_key');
|
||||
END;
|
||||
/
|
||||
|
||||
SELECT SYS_CONTEXT('CB_AGENT_CTX','USER_ID') AS ctx_user_id,
|
||||
SYS_CONTEXT('CB_AGENT_CTX','EMP_NO') AS ctx_emp_no,
|
||||
SYS_CONTEXT('CB_AGENT_CTX','DEPT_CODE') AS ctx_dept_code,
|
||||
SYS_CONTEXT('CB_AGENT_CTX','CAN_READ_CONTENTS') AS ctx_can_read_contents
|
||||
FROM dual;
|
||||
|
||||
SELECT doc_id, title, owner_emp_no, dept_code, contents
|
||||
FROM admin.cb_v_search_documents
|
||||
ORDER BY doc_id;
|
||||
|
||||
SELECT COUNT(*) AS rows_visible
|
||||
FROM admin.cb_v_search_documents;
|
||||
|
||||
PROMPT
|
||||
PROMPT === VPD 3. Authorization: Bearer cb_fin_key -> self row only ===
|
||||
BEGIN
|
||||
admin.cb_agent_ctx_pkg.set_user_by_bearer('cb_fin_key');
|
||||
END;
|
||||
/
|
||||
|
||||
SELECT SYS_CONTEXT('CB_AGENT_CTX','USER_ID') AS ctx_user_id,
|
||||
SYS_CONTEXT('CB_AGENT_CTX','EMP_NO') AS ctx_emp_no,
|
||||
SYS_CONTEXT('CB_AGENT_CTX','DEPT_CODE') AS ctx_dept_code,
|
||||
SYS_CONTEXT('CB_AGENT_CTX','CAN_READ_CONTENTS') AS ctx_can_read_contents
|
||||
FROM dual;
|
||||
|
||||
SELECT doc_id, title, owner_emp_no, dept_code, contents
|
||||
FROM admin.cb_v_search_documents
|
||||
ORDER BY doc_id;
|
||||
|
||||
SELECT COUNT(*) AS rows_visible
|
||||
FROM admin.cb_v_search_documents;
|
||||
|
||||
SELECT doc_id, title, owner_emp_no, dept_code, contents
|
||||
FROM admin.cb_v_search_documents
|
||||
ORDER BY doc_id
|
||||
FETCH FIRST 3 ROWS ONLY;
|
||||
|
||||
PROMPT
|
||||
PROMPT === VPD 4. Authorization: Bearer cb_all_key -> all rows ===
|
||||
BEGIN
|
||||
admin.cb_agent_ctx_pkg.set_user_by_bearer('cb_all_key');
|
||||
END;
|
||||
/
|
||||
|
||||
SELECT COUNT(*) AS rows_visible
|
||||
FROM admin.cb_v_search_documents;
|
||||
|
||||
PROMPT
|
||||
PROMPT === VPD 5. Invalid Bearer key -> ORA-20002 and context cleared ===
|
||||
BEGIN
|
||||
admin.cb_agent_ctx_pkg.set_user_by_bearer('wrong_key');
|
||||
END;
|
||||
/
|
||||
|
||||
SELECT COUNT(*) AS rows_visible_after_invalid_key
|
||||
FROM admin.cb_v_search_documents;
|
||||
|
||||
PROMPT
|
||||
PROMPT === VPD 6. Bypass attempts: direct base/permission tables are not granted ===
|
||||
SELECT COUNT(*) FROM admin.cb_search_documents;
|
||||
SELECT COUNT(*) FROM admin.cb_app_user;
|
||||
SELECT COUNT(*) FROM admin.cb_agent_bearer_key;
|
||||
|
||||
EXIT;
|
||||
89
sql/adb/19_agent_ords_security_local_dds_setup.sql
Normal file
89
sql/adb/19_agent_ords_security_local_dds_setup.sql
Normal file
@@ -0,0 +1,89 @@
|
||||
-- ============================================================
|
||||
-- 19_agent_ords_security_local_dds_setup.sql
|
||||
-- Local-only executable DDS example for Agent ORDS security.
|
||||
--
|
||||
-- Scenario:
|
||||
-- * DDS uses END USER -> DATA ROLE -> DATA GRANT.
|
||||
-- * No permission table lookup is used.
|
||||
-- * Each DATA GRANT declares the protected object, row filter,
|
||||
-- and column restriction.
|
||||
-- ============================================================
|
||||
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
||||
SET ECHO OFF
|
||||
SET FEEDBACK ON
|
||||
SET DEFINE OFF
|
||||
|
||||
PROMPT === 1. Creating DDS-only local table and view ===
|
||||
CREATE TABLE cb_dds_documents (
|
||||
doc_id NUMBER PRIMARY KEY,
|
||||
title VARCHAR2(100) NOT NULL,
|
||||
owner_emp_no VARCHAR2(20) NOT NULL,
|
||||
dept_code VARCHAR2(20) NOT NULL,
|
||||
contents VARCHAR2(4000),
|
||||
created_at DATE DEFAULT SYSDATE NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO cb_dds_documents VALUES (1, 'HR payroll guide', 'E1001', 'HR', 'Payroll policy and HR guide', SYSDATE);
|
||||
INSERT INTO cb_dds_documents VALUES (2, 'HR recruiting plan', 'E1002', 'HR', 'Recruiting plan for HR team', SYSDATE);
|
||||
INSERT INTO cb_dds_documents VALUES (3, 'Finance close checklist','E2001', 'FIN', 'Monthly close checklist', SYSDATE);
|
||||
INSERT INTO cb_dds_documents VALUES (4, 'Finance audit memo', 'E2002', 'FIN', 'Audit memo for finance team', SYSDATE);
|
||||
INSERT INTO cb_dds_documents VALUES (5, 'Sales forecast', 'E3001', 'SALES', 'Quarterly sales forecast', SYSDATE);
|
||||
INSERT INTO cb_dds_documents VALUES (6, 'HR benefits notice', 'E1003', 'HR', 'Benefits notice for employees', SYSDATE);
|
||||
|
||||
CREATE OR REPLACE VIEW cb_dds_v_search_documents AS
|
||||
SELECT doc_id,
|
||||
title,
|
||||
owner_emp_no,
|
||||
dept_code,
|
||||
contents,
|
||||
created_at
|
||||
FROM cb_dds_documents;
|
||||
|
||||
COMMIT;
|
||||
|
||||
PROMPT === 2. Creating DDS end users ===
|
||||
CREATE END USER "cb_dds_hr" IDENTIFIED BY "CbDds#Hr2026Local1";
|
||||
CREATE END USER "cb_dds_fin" IDENTIFIED BY "CbDds#Fin2026Local1";
|
||||
CREATE END USER "cb_dds_all" IDENTIFIED BY "CbDds#All2026Local1";
|
||||
CREATE END USER "cb_dds_none" IDENTIFIED BY "CbDds#None2026Local1";
|
||||
|
||||
PROMPT === 3. Creating DATA ROLEs and connection carrier role ===
|
||||
CREATE ROLE cb_dds_connect_role;
|
||||
GRANT CREATE SESSION TO cb_dds_connect_role;
|
||||
|
||||
CREATE DATA ROLE cb_dds_hr_role;
|
||||
CREATE DATA ROLE cb_dds_fin_role;
|
||||
CREATE DATA ROLE cb_dds_all_role;
|
||||
CREATE DATA ROLE cb_dds_connect_only_role;
|
||||
|
||||
GRANT cb_dds_connect_role TO cb_dds_hr_role;
|
||||
GRANT cb_dds_connect_role TO cb_dds_fin_role;
|
||||
GRANT cb_dds_connect_role TO cb_dds_all_role;
|
||||
GRANT cb_dds_connect_role TO cb_dds_connect_only_role;
|
||||
|
||||
PROMPT === 4. Mapping END USERs to DATA ROLEs ===
|
||||
GRANT DATA ROLE cb_dds_hr_role TO "cb_dds_hr";
|
||||
GRANT DATA ROLE cb_dds_fin_role TO "cb_dds_fin";
|
||||
GRANT DATA ROLE cb_dds_all_role TO "cb_dds_all";
|
||||
GRANT DATA ROLE cb_dds_connect_only_role TO "cb_dds_none";
|
||||
|
||||
PROMPT === 5. Creating DATA GRANTs ===
|
||||
CREATE DATA GRANT admin.cb_dg_hr_docs
|
||||
AS SELECT (ALL COLUMNS EXCEPT contents)
|
||||
ON admin.cb_dds_v_search_documents
|
||||
WHERE dept_code = 'HR'
|
||||
TO cb_dds_hr_role;
|
||||
|
||||
CREATE DATA GRANT admin.cb_dg_fin_docs
|
||||
AS SELECT (ALL COLUMNS EXCEPT contents)
|
||||
ON admin.cb_dds_v_search_documents
|
||||
WHERE dept_code = 'FIN'
|
||||
TO cb_dds_fin_role;
|
||||
|
||||
CREATE DATA GRANT admin.cb_dg_all_docs
|
||||
AS SELECT
|
||||
ON admin.cb_dds_v_search_documents
|
||||
TO cb_dds_all_role;
|
||||
|
||||
PROMPT === DDS local setup complete ===
|
||||
EXIT;
|
||||
46
sql/adb/20_agent_ords_security_local_dds_test.sql
Normal file
46
sql/adb/20_agent_ords_security_local_dds_test.sql
Normal file
@@ -0,0 +1,46 @@
|
||||
-- ============================================================
|
||||
-- 20_agent_ords_security_local_dds_test.sql
|
||||
-- Run as cb_dds_hr / cb_dds_fin / cb_dds_all / cb_dds_none.
|
||||
--
|
||||
-- Expected:
|
||||
-- cb_dds_hr -> 3 rows from admin.cb_dds_v_search_documents
|
||||
-- cb_dds_fin -> 2 rows from admin.cb_dds_v_search_documents
|
||||
-- cb_dds_all -> 6 rows from admin.cb_dds_v_search_documents
|
||||
-- contents -> NULL for hr/fin grants, visible for all grant
|
||||
-- cb_dds_none -> ORA-00942 on admin.cb_dds_v_search_documents
|
||||
-- base table -> ORA-00942 for every DDS end user
|
||||
-- ============================================================
|
||||
WHENEVER SQLERROR CONTINUE
|
||||
SET ECHO OFF
|
||||
SET FEEDBACK ON
|
||||
SET LINESIZE 220
|
||||
SET PAGESIZE 100
|
||||
COLUMN end_user_name FORMAT A16
|
||||
COLUMN title FORMAT A28
|
||||
COLUMN owner_emp_no FORMAT A12
|
||||
COLUMN dept_code FORMAT A10
|
||||
COLUMN contents FORMAT A35
|
||||
|
||||
PROMPT
|
||||
PROMPT === DDS 1. End user context ===
|
||||
SELECT ORA_END_USER_CONTEXT.username AS end_user_name
|
||||
FROM dual;
|
||||
|
||||
PROMPT
|
||||
PROMPT === DDS 2. Protected view row count ===
|
||||
SELECT COUNT(*) AS rows_visible
|
||||
FROM admin.cb_dds_v_search_documents;
|
||||
|
||||
PROMPT
|
||||
PROMPT === DDS 3. Protected view sample ===
|
||||
SELECT doc_id, title, owner_emp_no, dept_code, contents
|
||||
FROM admin.cb_dds_v_search_documents
|
||||
ORDER BY doc_id;
|
||||
|
||||
PROMPT
|
||||
PROMPT === DDS 4. Bypass attempts: no DATA GRANT on base/VPD objects ===
|
||||
SELECT COUNT(*) FROM admin.cb_dds_documents;
|
||||
SELECT COUNT(*) FROM admin.cb_search_documents;
|
||||
SELECT COUNT(*) FROM admin.cb_v_search_documents;
|
||||
|
||||
EXIT;
|
||||
25
sql/adb/21_agent_ords_security_ords_enable_schema.sql
Normal file
25
sql/adb/21_agent_ords_security_ords_enable_schema.sql
Normal file
@@ -0,0 +1,25 @@
|
||||
-- ============================================================
|
||||
-- 21_agent_ords_security_ords_enable_schema.sql
|
||||
-- Enable the CB_ORDS schema for ORDS module/handler definitions.
|
||||
--
|
||||
-- Run as ADMIN after CB_ORDS has been created.
|
||||
-- ============================================================
|
||||
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
||||
SET ECHO OFF
|
||||
SET FEEDBACK ON
|
||||
|
||||
PROMPT === Enabling CB_ORDS as an ORDS schema ===
|
||||
BEGIN
|
||||
ORDS.ENABLE_SCHEMA(
|
||||
p_enabled => TRUE,
|
||||
p_schema => 'CB_ORDS',
|
||||
p_url_mapping_type => 'BASE_PATH',
|
||||
p_url_mapping_pattern => 'cb-ords',
|
||||
p_auto_rest_auth => FALSE
|
||||
);
|
||||
COMMIT;
|
||||
END;
|
||||
/
|
||||
|
||||
PROMPT === CB_ORDS ORDS schema enabled ===
|
||||
EXIT;
|
||||
44
sql/adb/23_agent_ords_security_ords_handler_test.sql
Normal file
44
sql/adb/23_agent_ords_security_ords_handler_test.sql
Normal file
@@ -0,0 +1,44 @@
|
||||
-- ============================================================
|
||||
-- 23_agent_ords_security_ords_handler_test.sql
|
||||
-- Run as CB_ORDS.
|
||||
--
|
||||
-- This directly executes the same package that the ORDS handlers
|
||||
-- call. It proves the database-side behavior without requiring an
|
||||
-- external ORDS URL.
|
||||
-- ============================================================
|
||||
WHENEVER SQLERROR CONTINUE
|
||||
SET ECHO OFF
|
||||
SET FEEDBACK ON
|
||||
SET LONG 100000
|
||||
SET LONGCHUNKSIZE 100000
|
||||
SET LINESIZE 220
|
||||
SET PAGESIZE 100
|
||||
SET SERVEROUTPUT ON
|
||||
COLUMN response FORMAT A180 WORD_WRAPPED
|
||||
|
||||
PROMPT
|
||||
PROMPT === ORDS handler probe 1. VPD path with mandatory Bearer header ===
|
||||
SELECT cb_ords_handler_pkg.vpd_search_json('Bearer cb_hr_key') AS response
|
||||
FROM dual;
|
||||
|
||||
PROMPT
|
||||
PROMPT === ORDS handler probe 2. VPD path without Bearer header ===
|
||||
BEGIN
|
||||
DBMS_OUTPUT.PUT_LINE(cb_ords_handler_pkg.vpd_search_json(NULL));
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
DBMS_OUTPUT.PUT_LINE(SQLERRM);
|
||||
END;
|
||||
/
|
||||
|
||||
PROMPT
|
||||
PROMPT === ORDS handler probe 3. DDS path with Bearer header only ===
|
||||
SELECT cb_ords_handler_pkg.dds_bearer_probe_json('Bearer cb_hr_key') AS response
|
||||
FROM dual;
|
||||
|
||||
PROMPT
|
||||
PROMPT === ORDS handler probe 4. DDS path with all-access Bearer header only ===
|
||||
SELECT cb_ords_handler_pkg.dds_bearer_probe_json('Bearer cb_all_key') AS response
|
||||
FROM dual;
|
||||
|
||||
EXIT;
|
||||
99
sql/adb/24_agent_ords_security_inventory.sql
Normal file
99
sql/adb/24_agent_ords_security_inventory.sql
Normal file
@@ -0,0 +1,99 @@
|
||||
-- ============================================================
|
||||
-- 24_agent_ords_security_inventory.sql
|
||||
-- Central security inventory for Agent ORDS security.
|
||||
--
|
||||
-- Run as ADMIN after VPD, DDS, and ORDS handler setup.
|
||||
-- ============================================================
|
||||
WHENEVER SQLERROR CONTINUE
|
||||
SET ECHO OFF
|
||||
SET FEEDBACK ON
|
||||
SET LINESIZE 260
|
||||
SET PAGESIZE 200
|
||||
|
||||
COLUMN object_owner FORMAT A12
|
||||
COLUMN object_name FORMAT A32
|
||||
COLUMN policy_name FORMAT A26
|
||||
COLUMN pf_owner FORMAT A12
|
||||
COLUMN function FORMAT A30
|
||||
COLUMN sel FORMAT A5
|
||||
COLUMN ins FORMAT A5
|
||||
COLUMN upd FORMAT A5
|
||||
COLUMN del FORMAT A5
|
||||
COLUMN enable FORMAT A8
|
||||
COLUMN expression FORMAT A70 WORD_WRAPPED
|
||||
COLUMN grant_name FORMAT A24
|
||||
COLUMN grantee FORMAT A28
|
||||
COLUMN privilege FORMAT A10
|
||||
COLUMN allowed_columns FORMAT A76 WORD_WRAPPED
|
||||
COLUMN excluded_columns FORMAT A22
|
||||
COLUMN predicate FORMAT A28
|
||||
COLUMN end_user_name FORMAT A26
|
||||
COLUMN data_role FORMAT A28
|
||||
|
||||
PROMPT
|
||||
PROMPT === Inventory 1. VPD policies attached to protected objects ===
|
||||
SELECT object_owner,
|
||||
object_name,
|
||||
policy_name,
|
||||
pf_owner,
|
||||
function,
|
||||
sel,
|
||||
ins,
|
||||
upd,
|
||||
del,
|
||||
enable
|
||||
FROM dba_policies
|
||||
WHERE object_owner = 'ADMIN'
|
||||
AND object_name LIKE 'CB%'
|
||||
ORDER BY object_name, policy_name;
|
||||
|
||||
PROMPT
|
||||
PROMPT === Inventory 2. Redaction policies attached to protected objects ===
|
||||
SELECT object_owner,
|
||||
object_name,
|
||||
policy_name,
|
||||
enable,
|
||||
expression
|
||||
FROM redaction_policies
|
||||
WHERE object_owner = 'ADMIN'
|
||||
AND object_name LIKE 'CB%'
|
||||
ORDER BY object_name, policy_name;
|
||||
|
||||
PROMPT
|
||||
PROMPT === Inventory 3. DDS grant summary, one row per DATA GRANT ===
|
||||
SELECT grant_name,
|
||||
object_name,
|
||||
grantee,
|
||||
privilege,
|
||||
COALESCE(
|
||||
LISTAGG(column_name, ', ') WITHIN GROUP (ORDER BY column_name),
|
||||
'ALL COLUMNS'
|
||||
) AS allowed_columns,
|
||||
COALESCE(MAX(granted_with_all_columns_except), '-') AS excluded_columns,
|
||||
predicate
|
||||
FROM dba_data_grants
|
||||
WHERE owner = 'ADMIN'
|
||||
AND object_name LIKE 'CB%'
|
||||
GROUP BY grant_name, object_name, grantee, privilege, predicate
|
||||
ORDER BY object_name, grant_name;
|
||||
|
||||
PROMPT
|
||||
PROMPT === Inventory 4. DDS end user -> data role -> data grant matrix ===
|
||||
SELECT rg.grantee AS end_user_name,
|
||||
rg.data_role,
|
||||
dg.grant_name,
|
||||
dg.object_name,
|
||||
dg.predicate,
|
||||
COALESCE(MAX(dg.granted_with_all_columns_except), '-') AS excluded_columns
|
||||
FROM dba_data_role_grants rg
|
||||
LEFT JOIN dba_data_grants dg
|
||||
ON dg.grantee = rg.data_role
|
||||
WHERE rg.grantee LIKE 'cb\_dds\_%' ESCAPE '\'
|
||||
GROUP BY rg.grantee,
|
||||
rg.data_role,
|
||||
dg.grant_name,
|
||||
dg.object_name,
|
||||
dg.predicate
|
||||
ORDER BY rg.grantee, dg.object_name, dg.grant_name;
|
||||
|
||||
EXIT;
|
||||
24
sql/adb/41_dds_row_column_fixture.sql
Normal file
24
sql/adb/41_dds_row_column_fixture.sql
Normal file
@@ -0,0 +1,24 @@
|
||||
-- DDS row/column verification fixtures. Run as ADMIN after 31 and 34.
|
||||
CREATE TABLE admin.cb_dds_security_fixture (
|
||||
fixture_id NUMBER PRIMARY KEY,
|
||||
dept_code VARCHAR2(30) NOT NULL,
|
||||
owner_emp_no VARCHAR2(30) NOT NULL,
|
||||
tech_tag VARCHAR2(100) NOT NULL,
|
||||
title VARCHAR2(200) NOT NULL,
|
||||
contents VARCHAR2(2000) NOT NULL,
|
||||
public_summary VARCHAR2(500) NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO admin.cb_dds_security_fixture VALUES (1,'SALES','E2001','SALES','영업 파이프라인','영업 원문','영업 요약');
|
||||
INSERT INTO admin.cb_dds_security_fixture VALUES (2,'HR','E10234','HR','인사 운영','인사 원문','인사 요약');
|
||||
INSERT INTO admin.cb_dds_security_fixture VALUES (3,'FIN','E99999','FINANCE','재무 마감','재무 원문','재무 요약');
|
||||
COMMIT;
|
||||
|
||||
CREATE OR REPLACE DATA GRANT admin.dds_demo_token_fixture_grant
|
||||
AS SELECT (fixture_id, dept_code, owner_emp_no, tech_tag, title, public_summary)
|
||||
ON admin.cb_dds_security_fixture
|
||||
WHERE admin.cb_dds_vector_tag_allowed(tech_tag) = 1
|
||||
TO cb_dds_token_role;
|
||||
|
||||
-- Expected: token mapped to SALES sees row 1; HR/FIN rows are denied.
|
||||
-- CONTENTS is deliberately excluded: this is the column-level deny case.
|
||||
32
sql/adb/42_dds_fga_execution_evidence.sql
Normal file
32
sql/adb/42_dds_fga_execution_evidence.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
-- DDS execution evidence: durable FGA audit for the protected vector view.
|
||||
-- Run as ADMIN/AUDIT_ADMIN. In unified auditing the records are queried from
|
||||
-- UNIFIED_AUDIT_TRAIL with AUDIT_TYPE = 'FineGrainedAudit'.
|
||||
BEGIN
|
||||
DBMS_FGA.DROP_POLICY(
|
||||
object_schema => 'ADMIN', object_name => 'CB_DDS_VECTOR_SEARCH_DOCUMENTS',
|
||||
policy_name => 'DDS_VECTOR_EXECUTION_EVIDENCE');
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
IF SQLCODE != -28102 THEN RAISE; END IF;
|
||||
END;
|
||||
/
|
||||
BEGIN
|
||||
DBMS_FGA.ADD_POLICY(
|
||||
object_schema => 'ADMIN',
|
||||
object_name => 'CB_DDS_VECTOR_SEARCH_DOCUMENTS',
|
||||
policy_name => 'DDS_VECTOR_EXECUTION_EVIDENCE',
|
||||
audit_condition => NULL,
|
||||
audit_column => NULL,
|
||||
statement_types => 'SELECT',
|
||||
audit_trail => DBMS_FGA.DB_EXTENDED,
|
||||
enable => TRUE);
|
||||
END;
|
||||
/
|
||||
-- Evidence lookup (grant AUDIT_VIEWER or expose a definer-rights reader view
|
||||
-- to the backoffice; never grant this to DDS END USER accounts):
|
||||
-- SELECT event_timestamp, client_identifier, dbusername, object_name,
|
||||
-- return_code, sql_text, application_contexts
|
||||
-- FROM unified_audit_trail
|
||||
-- WHERE audit_type = 'FineGrainedAudit'
|
||||
-- AND object_schema = 'ADMIN'
|
||||
-- AND object_name = 'CB_DDS_VECTOR_SEARCH_DOCUMENTS'
|
||||
-- ORDER BY event_timestamp DESC;
|
||||
@@ -432,88 +432,27 @@ BEGIN
|
||||
END;
|
||||
/
|
||||
|
||||
PROMPT === 5. Applying column NULL policies from permission display exceptions ===
|
||||
-- ADB rejects application PL/SQL functions inside DBMS_REDACT expressions.
|
||||
-- SEC_RELEVANT_COL_OPT=ALL_ROWS is the VPD equivalent: it retains the row
|
||||
-- but returns NULL for the protected column while the column predicate is false.
|
||||
CREATE OR REPLACE FUNCTION cb_kb_cust_nm_cls(p_schema IN VARCHAR2, p_object IN VARCHAR2)
|
||||
RETURN VARCHAR2 AUTHID DEFINER AS
|
||||
BEGIN
|
||||
RETURN CASE WHEN cb_agent_can_read_column('KB_CUSTOMERS', 'CUST_NM') = 'Y' THEN '1 = 1' ELSE '1 = 0' END;
|
||||
END;
|
||||
/
|
||||
CREATE OR REPLACE FUNCTION cb_kb_rrn_cls(p_schema IN VARCHAR2, p_object IN VARCHAR2)
|
||||
RETURN VARCHAR2 AUTHID DEFINER AS
|
||||
BEGIN
|
||||
RETURN CASE WHEN cb_agent_can_read_column('KB_CUSTOMERS', 'RRN_MASKED') = 'Y' THEN '1 = 1' ELSE '1 = 0' END;
|
||||
END;
|
||||
/
|
||||
CREATE OR REPLACE FUNCTION cb_kb_claim_amt_cls(p_schema IN VARCHAR2, p_object IN VARCHAR2)
|
||||
RETURN VARCHAR2 AUTHID DEFINER AS
|
||||
BEGIN
|
||||
RETURN CASE WHEN cb_agent_can_read_column('KB_CLAIMS', 'CLAIM_AMT') = 'Y' THEN '1 = 1' ELSE '1 = 0' END;
|
||||
END;
|
||||
/
|
||||
CREATE OR REPLACE FUNCTION cb_kb_paid_amt_cls(p_schema IN VARCHAR2, p_object IN VARCHAR2)
|
||||
RETURN VARCHAR2 AUTHID DEFINER AS
|
||||
BEGIN
|
||||
RETURN CASE WHEN cb_agent_can_read_column('KB_CLAIMS', 'PAID_AMT') = 'Y' THEN '1 = 1' ELSE '1 = 0' END;
|
||||
END;
|
||||
/
|
||||
CREATE OR REPLACE FUNCTION cb_kb_ext_insurer_cls(p_schema IN VARCHAR2, p_object IN VARCHAR2)
|
||||
RETURN VARCHAR2 AUTHID DEFINER AS
|
||||
BEGIN
|
||||
RETURN CASE WHEN cb_agent_can_read_column('KB_EXTERNAL_HOLDINGS', 'EXT_INSURER') = 'Y' THEN '1 = 1' ELSE '1 = 0' END;
|
||||
END;
|
||||
/
|
||||
CREATE OR REPLACE FUNCTION cb_kb_ext_product_grp_cls(p_schema IN VARCHAR2, p_object IN VARCHAR2)
|
||||
RETURN VARCHAR2 AUTHID DEFINER AS
|
||||
BEGIN
|
||||
RETURN CASE WHEN cb_agent_can_read_column('KB_EXTERNAL_HOLDINGS', 'EXT_PRODUCT_GRP') = 'Y' THEN '1 = 1' ELSE '1 = 0' END;
|
||||
END;
|
||||
/
|
||||
CREATE OR REPLACE FUNCTION cb_kb_ext_product_type_cls(p_schema IN VARCHAR2, p_object IN VARCHAR2)
|
||||
RETURN VARCHAR2 AUTHID DEFINER AS
|
||||
BEGIN
|
||||
RETURN CASE WHEN cb_agent_can_read_column('KB_EXTERNAL_HOLDINGS', 'EXT_PRODUCT_TYPE') = 'Y' THEN '1 = 1' ELSE '1 = 0' END;
|
||||
END;
|
||||
/
|
||||
|
||||
PROMPT === 5. Removing deprecated VPD column NULL policies ===
|
||||
-- Architecture rule: VPD applies row predicates only. ASO/Data Redaction
|
||||
-- owns all column masking and its user-specific original-value exceptions.
|
||||
DECLARE
|
||||
PROCEDURE replace_column_policy(
|
||||
p_object_name IN VARCHAR2,
|
||||
p_column_name IN VARCHAR2,
|
||||
p_policy_name IN VARCHAR2,
|
||||
p_function_name IN VARCHAR2
|
||||
) IS
|
||||
PROCEDURE drop_column_policy(p_object_name IN VARCHAR2, p_policy_name IN VARCHAR2) IS
|
||||
BEGIN
|
||||
BEGIN
|
||||
DBMS_RLS.DROP_POLICY(
|
||||
object_schema => 'POC_2', object_name => p_object_name, policy_name => p_policy_name
|
||||
);
|
||||
EXCEPTION WHEN OTHERS THEN NULL;
|
||||
END;
|
||||
DBMS_RLS.ADD_POLICY(
|
||||
object_schema => 'POC_2',
|
||||
object_name => p_object_name,
|
||||
policy_name => p_policy_name,
|
||||
function_schema => USER,
|
||||
policy_function => p_function_name,
|
||||
statement_types => 'SELECT',
|
||||
policy_type => DBMS_RLS.DYNAMIC,
|
||||
sec_relevant_cols => p_column_name,
|
||||
sec_relevant_cols_opt => DBMS_RLS.ALL_ROWS,
|
||||
enable => TRUE
|
||||
DBMS_RLS.DROP_POLICY(
|
||||
object_schema => 'POC_2', object_name => p_object_name, policy_name => p_policy_name
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
IF SQLCODE != -28102 THEN RAISE; END IF;
|
||||
END;
|
||||
BEGIN
|
||||
replace_column_policy('KB_CUSTOMERS', 'CUST_NM', 'KB_CUST_NM_CLS_POLICY', 'CB_KB_CUST_NM_CLS');
|
||||
replace_column_policy('KB_CUSTOMERS', 'RRN_MASKED', 'KB_RRN_CLS_POLICY', 'CB_KB_RRN_CLS');
|
||||
replace_column_policy('KB_CLAIMS', 'CLAIM_AMT', 'KB_CLAIM_AMT_CLS_POLICY', 'CB_KB_CLAIM_AMT_CLS');
|
||||
replace_column_policy('KB_CLAIMS', 'PAID_AMT', 'KB_PAID_AMT_CLS_POLICY', 'CB_KB_PAID_AMT_CLS');
|
||||
replace_column_policy('KB_EXTERNAL_HOLDINGS', 'EXT_INSURER', 'KB_EXT_INSURER_CLS_POLICY', 'CB_KB_EXT_INSURER_CLS');
|
||||
replace_column_policy('KB_EXTERNAL_HOLDINGS', 'EXT_PRODUCT_GRP', 'KB_EXT_PRODUCT_GRP_CLS_POLICY', 'CB_KB_EXT_PRODUCT_GRP_CLS');
|
||||
replace_column_policy('KB_EXTERNAL_HOLDINGS', 'EXT_PRODUCT_TYPE', 'KB_EXT_PRODUCT_TYPE_CLS_POLICY', 'CB_KB_EXT_PRODUCT_TYPE_CLS');
|
||||
drop_column_policy('KB_CUSTOMERS', 'KB_CUST_NM_CLS_POLICY');
|
||||
drop_column_policy('KB_CUSTOMERS', 'KB_RRN_CLS_POLICY');
|
||||
drop_column_policy('KB_CLAIMS', 'KB_CLAIM_AMT_CLS_POLICY');
|
||||
drop_column_policy('KB_CLAIMS', 'KB_PAID_AMT_CLS_POLICY');
|
||||
drop_column_policy('KB_EXTERNAL_HOLDINGS', 'KB_EXT_INSURER_CLS_POLICY');
|
||||
drop_column_policy('KB_EXTERNAL_HOLDINGS', 'KB_EXT_PRODUCT_GRP_CLS_POLICY');
|
||||
drop_column_policy('KB_EXTERNAL_HOLDINGS', 'KB_EXT_PRODUCT_TYPE_CLS_POLICY');
|
||||
END;
|
||||
/
|
||||
|
||||
|
||||
@@ -65,13 +65,6 @@ BEGIN
|
||||
require_runtime_object('CB_AGENT_CTX_PKG', 'PACKAGE BODY');
|
||||
require_runtime_object('CB_AGENT_DOC_VPD_FILTER', 'FUNCTION');
|
||||
require_runtime_object('CB_AGENT_CAN_READ_COLUMN', 'FUNCTION');
|
||||
require_runtime_object('CB_KB_CUST_NM_CLS', 'FUNCTION');
|
||||
require_runtime_object('CB_KB_RRN_CLS', 'FUNCTION');
|
||||
require_runtime_object('CB_KB_CLAIM_AMT_CLS', 'FUNCTION');
|
||||
require_runtime_object('CB_KB_PAID_AMT_CLS', 'FUNCTION');
|
||||
require_runtime_object('CB_KB_EXT_INSURER_CLS', 'FUNCTION');
|
||||
require_runtime_object('CB_KB_EXT_PRODUCT_GRP_CLS', 'FUNCTION');
|
||||
require_runtime_object('CB_KB_EXT_PRODUCT_TYPE_CLS', 'FUNCTION');
|
||||
END;
|
||||
/
|
||||
|
||||
@@ -128,50 +121,28 @@ BEGIN
|
||||
END;
|
||||
/
|
||||
|
||||
PROMPT === 4. Reapplying column NULL policies ===
|
||||
PROMPT === 4. Removing deprecated VPD column NULL policies ===
|
||||
-- Architecture rule: VPD filters rows only. Column masking is handled solely
|
||||
-- by ASO / DBMS_REDACT through 63_kb_aso_masking_rule_runtime.sql.
|
||||
DECLARE
|
||||
PROCEDURE replace_column_policy(
|
||||
p_object_name IN VARCHAR2,
|
||||
p_column_name IN VARCHAR2,
|
||||
p_policy_name IN VARCHAR2,
|
||||
p_function_name IN VARCHAR2
|
||||
) IS
|
||||
v_count NUMBER;
|
||||
PROCEDURE drop_column_policy(p_object_name IN VARCHAR2, p_policy_name IN VARCHAR2) IS
|
||||
BEGIN
|
||||
SELECT COUNT(*)
|
||||
INTO v_count
|
||||
FROM all_policies
|
||||
WHERE object_owner = 'POC_2'
|
||||
AND object_name = p_object_name
|
||||
AND policy_name = p_policy_name;
|
||||
IF v_count > 0 THEN
|
||||
DBMS_RLS.DROP_POLICY(
|
||||
object_schema => 'POC_2',
|
||||
object_name => p_object_name,
|
||||
policy_name => p_policy_name
|
||||
);
|
||||
END IF;
|
||||
DBMS_RLS.ADD_POLICY(
|
||||
object_schema => 'POC_2',
|
||||
object_name => p_object_name,
|
||||
policy_name => p_policy_name,
|
||||
function_schema => 'ADMIN',
|
||||
policy_function => p_function_name,
|
||||
statement_types => 'SELECT',
|
||||
policy_type => DBMS_RLS.DYNAMIC,
|
||||
sec_relevant_cols => p_column_name,
|
||||
sec_relevant_cols_opt => DBMS_RLS.ALL_ROWS,
|
||||
enable => TRUE
|
||||
DBMS_RLS.DROP_POLICY(
|
||||
object_schema => 'POC_2', object_name => p_object_name, policy_name => p_policy_name
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
-- A missing policy is already the desired state. Do not hide other errors.
|
||||
IF SQLCODE != -28102 THEN RAISE; END IF;
|
||||
END;
|
||||
BEGIN
|
||||
replace_column_policy('KB_CUSTOMERS', 'CUST_NM', 'KB_CUST_NM_CLS_POLICY', 'CB_KB_CUST_NM_CLS');
|
||||
replace_column_policy('KB_CUSTOMERS', 'RRN_MASKED', 'KB_RRN_CLS_POLICY', 'CB_KB_RRN_CLS');
|
||||
replace_column_policy('KB_CLAIMS', 'CLAIM_AMT', 'KB_CLAIM_AMT_CLS_POLICY', 'CB_KB_CLAIM_AMT_CLS');
|
||||
replace_column_policy('KB_CLAIMS', 'PAID_AMT', 'KB_PAID_AMT_CLS_POLICY', 'CB_KB_PAID_AMT_CLS');
|
||||
replace_column_policy('KB_EXTERNAL_HOLDINGS', 'EXT_INSURER', 'KB_EXT_INSURER_CLS_POLICY', 'CB_KB_EXT_INSURER_CLS');
|
||||
replace_column_policy('KB_EXTERNAL_HOLDINGS', 'EXT_PRODUCT_GRP', 'KB_EXT_PRODUCT_GRP_CLS_POLICY', 'CB_KB_EXT_PRODUCT_GRP_CLS');
|
||||
replace_column_policy('KB_EXTERNAL_HOLDINGS', 'EXT_PRODUCT_TYPE', 'KB_EXT_PRODUCT_TYPE_CLS_POLICY', 'CB_KB_EXT_PRODUCT_TYPE_CLS');
|
||||
drop_column_policy('KB_CUSTOMERS', 'KB_CUST_NM_CLS_POLICY');
|
||||
drop_column_policy('KB_CUSTOMERS', 'KB_RRN_CLS_POLICY');
|
||||
drop_column_policy('KB_CLAIMS', 'KB_CLAIM_AMT_CLS_POLICY');
|
||||
drop_column_policy('KB_CLAIMS', 'KB_PAID_AMT_CLS_POLICY');
|
||||
drop_column_policy('KB_EXTERNAL_HOLDINGS', 'KB_EXT_INSURER_CLS_POLICY');
|
||||
drop_column_policy('KB_EXTERNAL_HOLDINGS', 'KB_EXT_PRODUCT_GRP_CLS_POLICY');
|
||||
drop_column_policy('KB_EXTERNAL_HOLDINGS', 'KB_EXT_PRODUCT_TYPE_CLS_POLICY');
|
||||
END;
|
||||
/
|
||||
|
||||
|
||||
100
sql/adb/62_kb_aso_masking_backoffice_metadata.sql
Normal file
100
sql/adb/62_kb_aso_masking_backoffice_metadata.sql
Normal file
@@ -0,0 +1,100 @@
|
||||
-- ============================================================
|
||||
-- 62_kb_aso_masking_backoffice_metadata.sql
|
||||
--
|
||||
-- Creates the metadata used by the masking-rule backoffice pages.
|
||||
-- Run as ADMIN with SQLcl. This script does not create a DBMS_REDACT
|
||||
-- policy; run 63_kb_aso_masking_rule_runtime.sql after rules are
|
||||
-- connected to protected columns.
|
||||
-- ============================================================
|
||||
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
||||
SET DEFINE OFF
|
||||
SET FEEDBACK ON
|
||||
|
||||
PROMPT === Creating masking-rule metadata tables ===
|
||||
DECLARE
|
||||
PROCEDURE ensure_table(p_name IN VARCHAR2, p_ddl IN CLOB) IS
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE p_ddl;
|
||||
DBMS_OUTPUT.PUT_LINE('created ' || p_name);
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
IF SQLCODE = -955 THEN
|
||||
DBMS_OUTPUT.PUT_LINE('exists ' || p_name);
|
||||
ELSE
|
||||
RAISE;
|
||||
END IF;
|
||||
END;
|
||||
BEGIN
|
||||
ensure_table('CB_MASKING_RULE', q'[
|
||||
CREATE TABLE cb_masking_rule (
|
||||
rule_id NUMBER PRIMARY KEY,
|
||||
rule_code VARCHAR2(64) NOT NULL UNIQUE,
|
||||
rule_name VARCHAR2(100) NOT NULL,
|
||||
template_code VARCHAR2(30) NOT NULL,
|
||||
description VARCHAR2(400),
|
||||
enabled_yn CHAR(1) DEFAULT 'Y' CHECK (enabled_yn IN ('Y','N')) NOT NULL
|
||||
)]');
|
||||
|
||||
ensure_table('CB_COLUMN_MASKING_RULE', q'[
|
||||
CREATE TABLE cb_column_masking_rule (
|
||||
column_id NUMBER PRIMARY KEY,
|
||||
rule_id NUMBER NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL
|
||||
)]');
|
||||
|
||||
ensure_table('CB_USER_MASKING_RULE', q'[
|
||||
CREATE TABLE cb_user_masking_rule (
|
||||
user_id NUMBER NOT NULL,
|
||||
column_id NUMBER NOT NULL,
|
||||
decision VARCHAR2(10) NOT NULL CHECK (decision IN ('MASK','UNMASK')),
|
||||
active_yn CHAR(1) DEFAULT 'Y' CHECK (active_yn IN ('Y','N')) NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL,
|
||||
CONSTRAINT cb_user_masking_rule_pk PRIMARY KEY (user_id, column_id)
|
||||
)]');
|
||||
END;
|
||||
/
|
||||
|
||||
PROMPT === Seeding curated masking templates ===
|
||||
DECLARE
|
||||
v_next_rule_id NUMBER;
|
||||
|
||||
PROCEDURE ensure_rule(
|
||||
p_code IN VARCHAR2,
|
||||
p_name IN VARCHAR2,
|
||||
p_template IN VARCHAR2,
|
||||
p_description IN VARCHAR2
|
||||
) IS
|
||||
BEGIN
|
||||
MERGE INTO cb_masking_rule dst
|
||||
USING (
|
||||
SELECT p_code rule_code,
|
||||
p_name rule_name,
|
||||
p_template template_code,
|
||||
p_description description
|
||||
FROM dual
|
||||
) src
|
||||
ON (dst.rule_code = src.rule_code)
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (rule_id, rule_code, rule_name, template_code, description, enabled_yn)
|
||||
VALUES (v_next_rule_id, src.rule_code, src.rule_name, src.template_code, src.description, 'Y');
|
||||
v_next_rule_id := v_next_rule_id + 1;
|
||||
END;
|
||||
BEGIN
|
||||
SELECT NVL(MAX(rule_id), 0) + 1 INTO v_next_rule_id FROM cb_masking_rule;
|
||||
ensure_rule('MASK_NULLIFY', '값 숨김 (NULL)', 'NULLIFY',
|
||||
'값을 NULL로 반환하는 기본 마스킹 방식');
|
||||
ensure_rule('MASK_FULL', '전체 마스킹', 'FULL',
|
||||
'문자형은 공백, 숫자형은 0으로 반환하는 전체 마스킹 방식');
|
||||
ensure_rule('MASK_TEXT_PARTIAL', '문자열 일부 마스킹', 'TEXT_PARTIAL',
|
||||
'첫 글자만 보이고 나머지는 가리는 문자열 마스킹 방식');
|
||||
ensure_rule('MASK_RRN_PARTIAL', '주민등록번호 부분 마스킹', 'RRN_PARTIAL',
|
||||
'앞 6자리만 보이고 나머지는 가리는 식별번호 마스킹 방식');
|
||||
END;
|
||||
/
|
||||
COMMIT;
|
||||
|
||||
PROMPT === Metadata ready ===
|
||||
SELECT rule_id, rule_code, rule_name, template_code, enabled_yn
|
||||
FROM cb_masking_rule
|
||||
ORDER BY rule_id;
|
||||
EXIT
|
||||
679
sql/adb/63_kb_aso_masking_rule_runtime.sql
Normal file
679
sql/adb/63_kb_aso_masking_rule_runtime.sql
Normal file
@@ -0,0 +1,679 @@
|
||||
-- ============================================================
|
||||
-- 63_kb_aso_masking_rule_runtime.sql
|
||||
--
|
||||
-- Synchronizes CB_MASKING_RULE / CB_COLUMN_MASKING_RULE and
|
||||
-- CB_USER_MASKING_RULE into Oracle Data Redaction policies.
|
||||
--
|
||||
-- Important behaviour
|
||||
-- * One selected masking rule is applied to each protected column.
|
||||
-- * A direct user rule controls whether that user is masked or receives
|
||||
-- an original-value exception (UNMASK).
|
||||
-- * VPD still determines row visibility. Existing VPD NULL policies
|
||||
-- remain in force for users without column permission.
|
||||
--
|
||||
-- Run as ADMIN with SQLcl after 62 to initialize the runtime package, or as
|
||||
-- a manual recovery/reconciliation action. The backoffice synchronizes
|
||||
-- DBMS_REDACT on normal configuration changes; this script follows the same
|
||||
-- source-of-truth rule for existing installations.
|
||||
-- Requires EXECUTE on DBMS_REDACT and ADMINISTER REDACTION POLICY for POC_2
|
||||
-- objects.
|
||||
-- ============================================================
|
||||
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
||||
SET DEFINE OFF
|
||||
SET FEEDBACK ON
|
||||
|
||||
PROMPT === Validating masking-rule metadata ===
|
||||
DECLARE
|
||||
PROCEDURE require_table(p_table_name IN VARCHAR2) IS
|
||||
v_count NUMBER;
|
||||
BEGIN
|
||||
SELECT COUNT(*)
|
||||
INTO v_count
|
||||
FROM user_tables
|
||||
WHERE table_name = p_table_name;
|
||||
IF v_count = 0 THEN
|
||||
RAISE_APPLICATION_ERROR(-20001,
|
||||
'Missing ' || p_table_name || '. Run 62_kb_aso_masking_backoffice_metadata.sql first.');
|
||||
END IF;
|
||||
END;
|
||||
BEGIN
|
||||
require_table('CB_MASKING_RULE');
|
||||
require_table('CB_COLUMN_MASKING_RULE');
|
||||
require_table('CB_USER_MASKING_RULE');
|
||||
END;
|
||||
/
|
||||
|
||||
PROMPT === Updating trusted bearer context with column exception flags ===
|
||||
CREATE OR REPLACE PACKAGE cb_agent_ctx_pkg AUTHID DEFINER AS
|
||||
PROCEDURE clear_user;
|
||||
PROCEDURE set_user(p_user_id IN NUMBER);
|
||||
PROCEDURE set_user_values(
|
||||
p_user_id IN NUMBER,
|
||||
p_emp_no IN VARCHAR2,
|
||||
p_dept_code IN VARCHAR2,
|
||||
p_can_read_contents IN VARCHAR2
|
||||
);
|
||||
PROCEDURE set_user_by_bearer(p_bearer_key IN VARCHAR2);
|
||||
END;
|
||||
/
|
||||
|
||||
CREATE OR REPLACE PACKAGE BODY cb_agent_ctx_pkg AS
|
||||
PROCEDURE clear_stakeholder_context AS
|
||||
BEGIN
|
||||
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_USER_ID', NULL);
|
||||
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_ROLE', NULL);
|
||||
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_CHANNEL', NULL);
|
||||
END;
|
||||
|
||||
PROCEDURE clear_masking_rule_context AS
|
||||
BEGIN
|
||||
FOR r IN (SELECT column_id FROM cb_column_masking_rule) LOOP
|
||||
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'MR_' || TO_CHAR(r.column_id), NULL);
|
||||
END LOOP;
|
||||
END;
|
||||
|
||||
PROCEDURE set_masking_rule_context(p_user_id IN NUMBER) AS
|
||||
BEGIN
|
||||
clear_masking_rule_context;
|
||||
FOR r IN (
|
||||
SELECT link.column_id,
|
||||
CASE WHEN EXISTS (
|
||||
SELECT 1
|
||||
FROM cb_user_masking_rule user_rule
|
||||
WHERE user_rule.user_id = p_user_id
|
||||
AND user_rule.column_id = link.column_id
|
||||
AND user_rule.active_yn = 'Y'
|
||||
AND user_rule.decision = 'UNMASK'
|
||||
) THEN 'Y' ELSE 'N' END AS can_unmask
|
||||
FROM cb_column_masking_rule link
|
||||
JOIN cb_masking_rule rule ON rule.rule_id = link.rule_id
|
||||
WHERE rule.enabled_yn = 'Y'
|
||||
) LOOP
|
||||
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'MR_' || TO_CHAR(r.column_id), r.can_unmask);
|
||||
END LOOP;
|
||||
END;
|
||||
|
||||
PROCEDURE clear_user AS
|
||||
BEGIN
|
||||
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'USER_ID', NULL);
|
||||
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'EMP_NO', NULL);
|
||||
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'DEPT_CODE', NULL);
|
||||
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'CAN_READ_CONTENTS', NULL);
|
||||
clear_stakeholder_context;
|
||||
clear_masking_rule_context;
|
||||
END;
|
||||
|
||||
PROCEDURE set_user_values(
|
||||
p_user_id IN NUMBER,
|
||||
p_emp_no IN VARCHAR2,
|
||||
p_dept_code IN VARCHAR2,
|
||||
p_can_read_contents IN VARCHAR2
|
||||
) AS
|
||||
BEGIN
|
||||
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'USER_ID', TO_CHAR(p_user_id));
|
||||
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'EMP_NO', p_emp_no);
|
||||
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'DEPT_CODE', p_dept_code);
|
||||
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'CAN_READ_CONTENTS', p_can_read_contents);
|
||||
clear_stakeholder_context;
|
||||
clear_masking_rule_context;
|
||||
END;
|
||||
|
||||
PROCEDURE set_stakeholder_context(
|
||||
p_stakeholder_user_id IN VARCHAR2,
|
||||
p_role IN VARCHAR2,
|
||||
p_channel IN VARCHAR2
|
||||
) AS
|
||||
v_channel VARCHAR2(50);
|
||||
BEGIN
|
||||
v_channel := CASE TRIM(p_channel)
|
||||
WHEN '설계사채널' THEN '설계사'
|
||||
WHEN 'GA채널' THEN 'GA'
|
||||
ELSE TRIM(p_channel)
|
||||
END;
|
||||
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_USER_ID', p_stakeholder_user_id);
|
||||
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_ROLE', p_role);
|
||||
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_CHANNEL', v_channel);
|
||||
END;
|
||||
|
||||
PROCEDURE set_user(p_user_id IN NUMBER) AS
|
||||
v_emp_no cb_app_user.employee_no%TYPE;
|
||||
v_dept_code cb_app_user.dept_code%TYPE;
|
||||
v_can_read_contents cb_app_user.can_read_contents%TYPE;
|
||||
BEGIN
|
||||
SELECT employee_no,
|
||||
dept_code,
|
||||
CASE
|
||||
WHEN EXISTS (
|
||||
SELECT 1
|
||||
FROM cb_user_role ur
|
||||
JOIN cb_permission p ON p.role_id = ur.role_id
|
||||
JOIN cb_permission_column pc ON pc.permission_id = p.perm_id
|
||||
WHERE ur.user_id = p_user_id
|
||||
AND p.target_name = 'CB_V_SEARCH_DOCUMENTS'
|
||||
AND p.action_name = 'SELECT'
|
||||
AND pc.column_name = 'CONTENTS'
|
||||
) THEN 'Y'
|
||||
ELSE 'N'
|
||||
END
|
||||
INTO v_emp_no, v_dept_code, v_can_read_contents
|
||||
FROM cb_app_user
|
||||
WHERE user_id = p_user_id
|
||||
AND active = 'Y';
|
||||
|
||||
set_user_values(p_user_id, v_emp_no, v_dept_code, v_can_read_contents);
|
||||
set_masking_rule_context(p_user_id);
|
||||
EXCEPTION
|
||||
WHEN NO_DATA_FOUND THEN
|
||||
clear_user;
|
||||
RAISE_APPLICATION_ERROR(-20003, 'Mapped application user not found');
|
||||
END;
|
||||
|
||||
PROCEDURE set_user_by_bearer(p_bearer_key IN VARCHAR2) AS
|
||||
v_user_id cb_app_user.user_id%TYPE;
|
||||
v_emp_no cb_app_user.employee_no%TYPE;
|
||||
v_dept_code cb_app_user.dept_code%TYPE;
|
||||
v_can_read_contents cb_app_user.can_read_contents%TYPE;
|
||||
v_stakeholder_user_id VARCHAR2(30);
|
||||
v_stakeholder_role VARCHAR2(50);
|
||||
v_stakeholder_channel VARCHAR2(50);
|
||||
BEGIN
|
||||
IF p_bearer_key IS NULL THEN
|
||||
clear_user;
|
||||
RAISE_APPLICATION_ERROR(-20001, 'Authorization Bearer key is required');
|
||||
END IF;
|
||||
|
||||
SELECT u.user_id,
|
||||
u.employee_no,
|
||||
u.dept_code,
|
||||
CASE
|
||||
WHEN EXISTS (
|
||||
SELECT 1
|
||||
FROM cb_user_role ur
|
||||
JOIN cb_permission p ON p.role_id = ur.role_id
|
||||
JOIN cb_permission_column pc ON pc.permission_id = p.perm_id
|
||||
WHERE ur.user_id = u.user_id
|
||||
AND p.target_name = 'CB_V_SEARCH_DOCUMENTS'
|
||||
AND p.action_name = 'SELECT'
|
||||
AND pc.column_name = 'CONTENTS'
|
||||
) THEN 'Y'
|
||||
ELSE 'N'
|
||||
END,
|
||||
COALESCE(k.stakeholder_user_id, u.stakeholder_user_id)
|
||||
INTO v_user_id, v_emp_no, v_dept_code, v_can_read_contents, v_stakeholder_user_id
|
||||
FROM cb_agent_bearer_key k
|
||||
JOIN cb_app_user u ON u.user_id = k.user_id
|
||||
WHERE k.key_hash = STANDARD_HASH(p_bearer_key, 'SHA256')
|
||||
AND k.active = 'Y'
|
||||
AND k.revoked_at IS NULL
|
||||
AND (k.expires_at IS NULL OR k.expires_at > SYSDATE)
|
||||
AND u.active = 'Y';
|
||||
|
||||
set_user_values(v_user_id, v_emp_no, v_dept_code, v_can_read_contents);
|
||||
|
||||
IF v_stakeholder_user_id IS NOT NULL THEN
|
||||
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_USER_ID', v_stakeholder_user_id);
|
||||
SELECT role, channel
|
||||
INTO v_stakeholder_role, v_stakeholder_channel
|
||||
FROM poc_2.kb_stakeholders
|
||||
WHERE user_id = v_stakeholder_user_id;
|
||||
set_stakeholder_context(v_stakeholder_user_id, v_stakeholder_role, v_stakeholder_channel);
|
||||
END IF;
|
||||
|
||||
set_masking_rule_context(v_user_id);
|
||||
EXCEPTION
|
||||
WHEN NO_DATA_FOUND THEN
|
||||
clear_user;
|
||||
RAISE_APPLICATION_ERROR(-20002, 'Invalid or expired Bearer key');
|
||||
END;
|
||||
END;
|
||||
/
|
||||
SHOW ERRORS PACKAGE BODY cb_agent_ctx_pkg
|
||||
|
||||
GRANT EXECUTE ON cb_agent_ctx_pkg TO cb_ords;
|
||||
|
||||
PROMPT === Preserving the all-access admin raw-display contract ===
|
||||
MERGE INTO cb_user_masking_rule dst
|
||||
USING (
|
||||
SELECT user_row.user_id, column_rule.column_id
|
||||
FROM cb_app_user user_row
|
||||
CROSS JOIN cb_column_masking_rule column_rule
|
||||
JOIN cb_masking_rule rule ON rule.rule_id = column_rule.rule_id
|
||||
WHERE user_row.stakeholder_user_id = 'KB_VPD_ADMIN'
|
||||
AND user_row.active = 'Y'
|
||||
AND rule.enabled_yn = 'Y'
|
||||
) src
|
||||
ON (dst.user_id = src.user_id AND dst.column_id = src.column_id)
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (user_id, column_id, decision, active_yn, updated_at)
|
||||
VALUES (src.user_id, src.column_id, 'UNMASK', 'Y', SYSTIMESTAMP);
|
||||
COMMIT;
|
||||
|
||||
PROMPT === Removing stale columns and disabling unconfigured KB policies ===
|
||||
-- A policy definition is deliberately retained when no column is configured.
|
||||
-- DISABLE_POLICY makes the effective state match the empty backoffice setting
|
||||
-- and lets a later column assignment re-enable the same named policy safely.
|
||||
DECLARE
|
||||
PROCEDURE reconcile_existing_policy(
|
||||
p_object_name IN VARCHAR2,
|
||||
p_policy_name IN VARCHAR2
|
||||
) IS
|
||||
v_policy_count NUMBER;
|
||||
v_configured_count NUMBER;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO v_policy_count
|
||||
FROM redaction_policies
|
||||
WHERE object_owner = 'POC_2' AND object_name = p_object_name AND policy_name = p_policy_name;
|
||||
IF v_policy_count = 0 THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
SELECT COUNT(*) INTO v_configured_count
|
||||
FROM cb_column_masking_rule link
|
||||
JOIN cb_masking_rule rule ON rule.rule_id = link.rule_id
|
||||
JOIN cb_protected_column column_row ON column_row.column_id = link.column_id
|
||||
JOIN cb_protected_object object_row ON object_row.object_id = column_row.object_id
|
||||
WHERE object_row.owner = 'POC_2'
|
||||
AND object_row.object_name = p_object_name
|
||||
AND rule.enabled_yn = 'Y';
|
||||
|
||||
IF v_configured_count = 0 THEN
|
||||
DBMS_REDACT.DISABLE_POLICY('POC_2', p_object_name, p_policy_name);
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
FOR stale_column IN (
|
||||
SELECT policy_column.column_name
|
||||
FROM redaction_columns policy_column
|
||||
WHERE policy_column.object_owner = 'POC_2'
|
||||
AND policy_column.object_name = p_object_name
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM cb_column_masking_rule link
|
||||
JOIN cb_masking_rule rule ON rule.rule_id = link.rule_id
|
||||
JOIN cb_protected_column column_row ON column_row.column_id = link.column_id
|
||||
JOIN cb_protected_object object_row ON object_row.object_id = column_row.object_id
|
||||
WHERE object_row.owner = 'POC_2'
|
||||
AND object_row.object_name = p_object_name
|
||||
AND column_row.column_name = policy_column.column_name
|
||||
AND rule.enabled_yn = 'Y'
|
||||
)
|
||||
) LOOP
|
||||
DBMS_REDACT.ALTER_POLICY(
|
||||
object_schema => 'POC_2', object_name => p_object_name, policy_name => p_policy_name,
|
||||
action => DBMS_REDACT.DROP_COLUMN, column_name => stale_column.column_name
|
||||
);
|
||||
END LOOP;
|
||||
END;
|
||||
BEGIN
|
||||
reconcile_existing_policy('KB_CUSTOMERS', 'KB_CUSTOMER_PII_REDACT');
|
||||
reconcile_existing_policy('KB_CLAIMS', 'KB_CLAIM_AMOUNT_REDACT');
|
||||
reconcile_existing_policy('KB_CONTRACTS', 'KB_CONTRACT_PREMIUM_REDACT');
|
||||
reconcile_existing_policy('KB_EXTERNAL_HOLDINGS', 'KB_EXT_HOLDING_REDACT');
|
||||
END;
|
||||
/
|
||||
|
||||
PROMPT === Integrating configured columns into the existing KB Data Redaction policies ===
|
||||
DECLARE
|
||||
v_policy_name VARCHAR2(128);
|
||||
v_expression_name VARCHAR2(128);
|
||||
v_expression VARCHAR2(4000);
|
||||
|
||||
PROCEDURE require_existing_policy(
|
||||
p_object_name IN VARCHAR2,
|
||||
p_policy_name IN VARCHAR2
|
||||
) IS
|
||||
v_count NUMBER;
|
||||
BEGIN
|
||||
SELECT COUNT(*)
|
||||
INTO v_count
|
||||
FROM redaction_policies
|
||||
WHERE object_owner = 'POC_2'
|
||||
AND object_name = p_object_name
|
||||
AND policy_name = p_policy_name;
|
||||
IF v_count = 0 THEN
|
||||
RAISE_APPLICATION_ERROR(-20012,
|
||||
'Existing Data Redaction policy not found: POC_2.' || p_object_name || '.' || p_policy_name);
|
||||
END IF;
|
||||
END;
|
||||
|
||||
PROCEDURE upsert_expression(
|
||||
p_expression_name IN VARCHAR2,
|
||||
p_expression IN VARCHAR2
|
||||
) IS
|
||||
v_count NUMBER;
|
||||
BEGIN
|
||||
SELECT COUNT(*)
|
||||
INTO v_count
|
||||
FROM redaction_expressions
|
||||
WHERE policy_expression_name = p_expression_name;
|
||||
IF v_count = 0 THEN
|
||||
DBMS_REDACT.CREATE_POLICY_EXPRESSION(
|
||||
policy_expression_name => p_expression_name,
|
||||
expression => p_expression,
|
||||
policy_expression_description => 'Mask unless trusted bearer context grants an original-value exception'
|
||||
);
|
||||
ELSE
|
||||
DBMS_REDACT.UPDATE_POLICY_EXPRESSION(
|
||||
policy_expression_name => p_expression_name,
|
||||
expression => p_expression,
|
||||
policy_expression_description => 'Mask unless trusted bearer context grants an original-value exception'
|
||||
);
|
||||
END IF;
|
||||
END;
|
||||
|
||||
PROCEDURE modify_column(
|
||||
p_object_name IN VARCHAR2,
|
||||
p_policy_name IN VARCHAR2,
|
||||
p_column_name IN VARCHAR2,
|
||||
p_template_code IN VARCHAR2
|
||||
) IS
|
||||
PROCEDURE apply(
|
||||
p_function_type IN BINARY_INTEGER,
|
||||
p_pattern IN VARCHAR2 DEFAULT NULL,
|
||||
p_replace IN VARCHAR2 DEFAULT NULL
|
||||
) IS
|
||||
BEGIN
|
||||
DBMS_REDACT.ALTER_POLICY(
|
||||
object_schema => 'POC_2',
|
||||
object_name => p_object_name,
|
||||
policy_name => p_policy_name,
|
||||
action => DBMS_REDACT.MODIFY_COLUMN,
|
||||
column_name => p_column_name,
|
||||
function_type => p_function_type,
|
||||
regexp_pattern => p_pattern,
|
||||
regexp_replace_string => p_replace
|
||||
);
|
||||
END;
|
||||
BEGIN
|
||||
CASE p_template_code
|
||||
WHEN 'NULLIFY' THEN
|
||||
apply(DBMS_REDACT.NULLIFY);
|
||||
WHEN 'FULL' THEN
|
||||
apply(DBMS_REDACT.FULL);
|
||||
WHEN 'TEXT_PARTIAL' THEN
|
||||
apply(DBMS_REDACT.REGEXP, '(^.).*$', '\1***');
|
||||
WHEN 'RRN_PARTIAL' THEN
|
||||
apply(DBMS_REDACT.REGEXP, '(^[0-9]{6})-?[0-9]{7}$', '\1-*******');
|
||||
ELSE
|
||||
RAISE_APPLICATION_ERROR(-20013, 'Unsupported masking template: ' || p_template_code);
|
||||
END CASE;
|
||||
END;
|
||||
|
||||
BEGIN
|
||||
FOR configured_column IN (
|
||||
SELECT protected_object.object_name,
|
||||
protected_column.column_id,
|
||||
protected_column.column_name,
|
||||
masking_rule.template_code
|
||||
FROM cb_column_masking_rule link
|
||||
JOIN cb_masking_rule masking_rule ON masking_rule.rule_id = link.rule_id
|
||||
JOIN cb_protected_column protected_column ON protected_column.column_id = link.column_id
|
||||
JOIN cb_protected_object protected_object ON protected_object.object_id = protected_column.object_id
|
||||
WHERE protected_object.owner = 'POC_2'
|
||||
AND masking_rule.enabled_yn = 'Y'
|
||||
ORDER BY protected_object.object_name, protected_column.column_id
|
||||
) LOOP
|
||||
v_policy_name := CASE configured_column.object_name
|
||||
WHEN 'KB_CUSTOMERS' THEN 'KB_CUSTOMER_PII_REDACT'
|
||||
WHEN 'KB_CLAIMS' THEN 'KB_CLAIM_AMOUNT_REDACT'
|
||||
WHEN 'KB_CONTRACTS' THEN 'KB_CONTRACT_PREMIUM_REDACT'
|
||||
WHEN 'KB_EXTERNAL_HOLDINGS' THEN 'KB_EXT_HOLDING_REDACT'
|
||||
ELSE NULL
|
||||
END;
|
||||
IF v_policy_name IS NULL THEN
|
||||
RAISE_APPLICATION_ERROR(-20014,
|
||||
'No existing KB Data Redaction policy mapping for ' || configured_column.object_name);
|
||||
END IF;
|
||||
|
||||
require_existing_policy(configured_column.object_name, v_policy_name);
|
||||
DBMS_REDACT.ENABLE_POLICY('POC_2', configured_column.object_name, v_policy_name);
|
||||
modify_column(
|
||||
configured_column.object_name,
|
||||
v_policy_name,
|
||||
configured_column.column_name,
|
||||
configured_column.template_code
|
||||
);
|
||||
|
||||
v_expression_name := 'CBMR_' || TO_CHAR(configured_column.column_id);
|
||||
v_expression := 'SYS_CONTEXT(''CB_AGENT_CTX'', ''MR_' || TO_CHAR(configured_column.column_id)
|
||||
|| ''') IS NULL OR SYS_CONTEXT(''CB_AGENT_CTX'', ''MR_' || TO_CHAR(configured_column.column_id) || ''') <> ''Y''';
|
||||
upsert_expression(v_expression_name, v_expression);
|
||||
DBMS_REDACT.APPLY_POLICY_EXPR_TO_COL(
|
||||
object_schema => 'POC_2',
|
||||
object_name => configured_column.object_name,
|
||||
column_name => configured_column.column_name,
|
||||
policy_expression_name => v_expression_name
|
||||
);
|
||||
END LOOP;
|
||||
END;
|
||||
/
|
||||
COMMIT;
|
||||
|
||||
PROMPT === Verification ===
|
||||
SELECT object_name, policy_name, expression
|
||||
FROM redaction_policies
|
||||
WHERE object_owner = 'POC_2'
|
||||
AND object_name IN ('KB_CUSTOMERS', 'KB_CLAIMS', 'KB_CONTRACTS', 'KB_EXTERNAL_HOLDINGS')
|
||||
ORDER BY object_name, policy_name;
|
||||
|
||||
SELECT object_name, column_name, function_type
|
||||
FROM redaction_columns
|
||||
WHERE object_owner = 'POC_2'
|
||||
AND object_name IN ('KB_CUSTOMERS', 'KB_CLAIMS', 'KB_CONTRACTS', 'KB_EXTERNAL_HOLDINGS')
|
||||
ORDER BY object_name, column_name;
|
||||
|
||||
SELECT policy_expression_name, object_name, column_name, expression
|
||||
FROM redaction_expressions
|
||||
WHERE policy_expression_name LIKE 'CBMR_%'
|
||||
ORDER BY policy_expression_name;
|
||||
|
||||
SELECT user_row.user_name,
|
||||
protected_object.object_name,
|
||||
protected_column.column_name,
|
||||
user_rule.decision
|
||||
FROM cb_user_masking_rule user_rule
|
||||
JOIN cb_app_user user_row ON user_row.user_id = user_rule.user_id
|
||||
JOIN cb_protected_column protected_column ON protected_column.column_id = user_rule.column_id
|
||||
JOIN cb_protected_object protected_object ON protected_object.object_id = protected_column.object_id
|
||||
ORDER BY user_row.user_name, protected_object.object_name, protected_column.column_name;
|
||||
EXIT
|
||||
|
||||
-- Legacy replacement implementation retained below for migration reference.
|
||||
-- SQLcl exits above, so it never executes.
|
||||
PROMPT === Rebuilding managed Oracle Data Redaction policies ===
|
||||
DECLARE
|
||||
v_policy_name VARCHAR2(128);
|
||||
v_expression_name VARCHAR2(128);
|
||||
v_first BOOLEAN;
|
||||
|
||||
PROCEDURE apply_column(
|
||||
p_first IN BOOLEAN,
|
||||
p_object_name IN VARCHAR2,
|
||||
p_policy_name IN VARCHAR2,
|
||||
p_column_name IN VARCHAR2,
|
||||
p_template_code IN VARCHAR2
|
||||
) IS
|
||||
PROCEDURE first_policy(
|
||||
p_function_type IN BINARY_INTEGER,
|
||||
p_pattern IN VARCHAR2 DEFAULT NULL,
|
||||
p_replace IN VARCHAR2 DEFAULT NULL
|
||||
) IS
|
||||
BEGIN
|
||||
DBMS_REDACT.ADD_POLICY(
|
||||
object_schema => 'POC_2',
|
||||
object_name => p_object_name,
|
||||
policy_name => p_policy_name,
|
||||
policy_description => 'Managed by CB masking rule backoffice',
|
||||
column_name => p_column_name,
|
||||
function_type => p_function_type,
|
||||
expression => '1=1',
|
||||
regexp_pattern => p_pattern,
|
||||
regexp_replace_string => p_replace,
|
||||
enable => TRUE
|
||||
);
|
||||
END;
|
||||
|
||||
PROCEDURE additional_column(
|
||||
p_function_type IN BINARY_INTEGER,
|
||||
p_pattern IN VARCHAR2 DEFAULT NULL,
|
||||
p_replace IN VARCHAR2 DEFAULT NULL
|
||||
) IS
|
||||
BEGIN
|
||||
DBMS_REDACT.ALTER_POLICY(
|
||||
object_schema => 'POC_2',
|
||||
object_name => p_object_name,
|
||||
policy_name => p_policy_name,
|
||||
action => DBMS_REDACT.ADD_COLUMN,
|
||||
column_name => p_column_name,
|
||||
function_type => p_function_type,
|
||||
regexp_pattern => p_pattern,
|
||||
regexp_replace_string => p_replace
|
||||
);
|
||||
END;
|
||||
|
||||
PROCEDURE invoke(
|
||||
p_function_type IN BINARY_INTEGER,
|
||||
p_pattern IN VARCHAR2 DEFAULT NULL,
|
||||
p_replace IN VARCHAR2 DEFAULT NULL
|
||||
) IS
|
||||
BEGIN
|
||||
IF p_first THEN
|
||||
first_policy(p_function_type, p_pattern, p_replace);
|
||||
ELSE
|
||||
additional_column(p_function_type, p_pattern, p_replace);
|
||||
END IF;
|
||||
END;
|
||||
BEGIN
|
||||
CASE p_template_code
|
||||
WHEN 'NULLIFY' THEN
|
||||
invoke(DBMS_REDACT.NULLIFY);
|
||||
WHEN 'FULL' THEN
|
||||
invoke(DBMS_REDACT.FULL);
|
||||
WHEN 'TEXT_PARTIAL' THEN
|
||||
invoke(DBMS_REDACT.REGEXP, '(^.).*$', '\1***');
|
||||
WHEN 'RRN_PARTIAL' THEN
|
||||
invoke(DBMS_REDACT.REGEXP, '(^[0-9]{6})-?[0-9]{7}$', '\1-*******');
|
||||
ELSE
|
||||
RAISE_APPLICATION_ERROR(-20011, 'Unsupported template: ' || p_template_code);
|
||||
END CASE;
|
||||
END;
|
||||
|
||||
PROCEDURE drop_managed_policy(p_object_name IN VARCHAR2, p_policy_name IN VARCHAR2) IS
|
||||
BEGIN
|
||||
DBMS_REDACT.DROP_POLICY(
|
||||
object_schema => 'POC_2',
|
||||
object_name => p_object_name,
|
||||
policy_name => p_policy_name
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
NULL;
|
||||
END;
|
||||
|
||||
PROCEDURE drop_expression(p_expression_name IN VARCHAR2) IS
|
||||
BEGIN
|
||||
DBMS_REDACT.DROP_POLICY_EXPRESSION(p_expression_name);
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
NULL;
|
||||
END;
|
||||
|
||||
BEGIN
|
||||
-- These three policies are legacy test policies found on the KB objects.
|
||||
-- Data Redaction permits only one policy per object, so replace them with
|
||||
-- the managed policy rather than leaving their client-identifier tests live.
|
||||
drop_managed_policy('KB_CUSTOMERS', 'KB_CUSTOMER_PII_REDACT');
|
||||
drop_managed_policy('KB_CLAIMS', 'KB_CLAIM_AMOUNT_REDACT');
|
||||
drop_managed_policy('KB_EXTERNAL_HOLDINGS', 'KB_EXT_HOLDING_REDACT');
|
||||
|
||||
FOR object_row IN (
|
||||
SELECT DISTINCT po.object_name
|
||||
FROM cb_column_masking_rule link
|
||||
JOIN cb_masking_rule rule ON rule.rule_id = link.rule_id
|
||||
JOIN cb_protected_column pc ON pc.column_id = link.column_id
|
||||
JOIN cb_protected_object po ON po.object_id = pc.object_id
|
||||
WHERE rule.enabled_yn = 'Y'
|
||||
AND po.owner = 'POC_2'
|
||||
) LOOP
|
||||
v_policy_name := 'CB_ASO_' || object_row.object_name || '_MASK';
|
||||
drop_managed_policy(object_row.object_name, v_policy_name);
|
||||
END LOOP;
|
||||
|
||||
FOR column_row IN (
|
||||
SELECT link.column_id
|
||||
FROM cb_column_masking_rule link
|
||||
) LOOP
|
||||
drop_expression('CBMR_' || TO_CHAR(column_row.column_id));
|
||||
END LOOP;
|
||||
|
||||
FOR object_row IN (
|
||||
SELECT DISTINCT po.object_name
|
||||
FROM cb_column_masking_rule link
|
||||
JOIN cb_masking_rule rule ON rule.rule_id = link.rule_id
|
||||
JOIN cb_protected_column pc ON pc.column_id = link.column_id
|
||||
JOIN cb_protected_object po ON po.object_id = pc.object_id
|
||||
WHERE rule.enabled_yn = 'Y'
|
||||
AND po.owner = 'POC_2'
|
||||
ORDER BY po.object_name
|
||||
) LOOP
|
||||
v_policy_name := 'CB_ASO_' || object_row.object_name || '_MASK';
|
||||
v_first := TRUE;
|
||||
FOR column_row IN (
|
||||
SELECT pc.column_id,
|
||||
pc.column_name,
|
||||
rule.template_code,
|
||||
rule.rule_name
|
||||
FROM cb_column_masking_rule link
|
||||
JOIN cb_masking_rule rule ON rule.rule_id = link.rule_id
|
||||
JOIN cb_protected_column pc ON pc.column_id = link.column_id
|
||||
JOIN cb_protected_object po ON po.object_id = pc.object_id
|
||||
WHERE po.owner = 'POC_2'
|
||||
AND po.object_name = object_row.object_name
|
||||
AND rule.enabled_yn = 'Y'
|
||||
ORDER BY pc.column_id
|
||||
) LOOP
|
||||
apply_column(v_first, object_row.object_name, v_policy_name,
|
||||
column_row.column_name, column_row.template_code);
|
||||
v_expression_name := 'CBMR_' || TO_CHAR(column_row.column_id);
|
||||
DBMS_REDACT.CREATE_POLICY_EXPRESSION(
|
||||
policy_expression_name => v_expression_name,
|
||||
expression => 'SYS_CONTEXT(''CB_AGENT_CTX'', ''MR_' || TO_CHAR(column_row.column_id)
|
||||
|| ''') IS NULL OR SYS_CONTEXT(''CB_AGENT_CTX'', ''MR_' || TO_CHAR(column_row.column_id) || ''') <> ''Y''',
|
||||
policy_expression_description => 'Mask unless trusted bearer context grants an original-value exception'
|
||||
);
|
||||
DBMS_REDACT.APPLY_POLICY_EXPR_TO_COL(
|
||||
object_schema => 'POC_2',
|
||||
object_name => object_row.object_name,
|
||||
column_name => column_row.column_name,
|
||||
policy_expression_name => v_expression_name
|
||||
);
|
||||
v_first := FALSE;
|
||||
END LOOP;
|
||||
END LOOP;
|
||||
END;
|
||||
/
|
||||
COMMIT;
|
||||
|
||||
PROMPT === Verification ===
|
||||
SELECT object_name, policy_name, expression
|
||||
FROM redaction_policies
|
||||
WHERE object_owner = 'POC_2'
|
||||
AND policy_name LIKE 'CB_ASO_%'
|
||||
ORDER BY object_name, policy_name;
|
||||
|
||||
SELECT object_name, column_name, function_type
|
||||
FROM redaction_columns
|
||||
WHERE object_owner = 'POC_2'
|
||||
AND object_name IN ('KB_CUSTOMERS', 'KB_CLAIMS', 'KB_EXTERNAL_HOLDINGS')
|
||||
ORDER BY object_name, column_name;
|
||||
|
||||
SELECT user_row.user_name,
|
||||
protected_object.object_name,
|
||||
protected_column.column_name,
|
||||
user_rule.decision
|
||||
FROM cb_user_masking_rule user_rule
|
||||
JOIN cb_app_user user_row ON user_row.user_id = user_rule.user_id
|
||||
JOIN cb_protected_column protected_column ON protected_column.column_id = user_rule.column_id
|
||||
JOIN cb_protected_object protected_object ON protected_object.object_id = protected_column.object_id
|
||||
ORDER BY user_row.user_name, protected_object.object_name, protected_column.column_name;
|
||||
EXIT
|
||||
54
sql/adb/64_kb_aso_masking_default_column_rules.sql
Normal file
54
sql/adb/64_kb_aso_masking_default_column_rules.sql
Normal file
@@ -0,0 +1,54 @@
|
||||
-- ============================================================
|
||||
-- 64_kb_aso_masking_default_column_rules.sql
|
||||
--
|
||||
-- Initial blacklist of KB columns subject to ASO masking.
|
||||
-- Run as ADMIN after 62. Execute 63 afterwards to apply the
|
||||
-- configured rules as DBMS_REDACT policies.
|
||||
-- ============================================================
|
||||
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
||||
SET DEFINE OFF
|
||||
SET FEEDBACK ON
|
||||
|
||||
PROMPT === Assigning the KB masking target blacklist ===
|
||||
MERGE INTO cb_column_masking_rule dst
|
||||
USING (
|
||||
SELECT protected_column.column_id,
|
||||
masking_rule.rule_id
|
||||
FROM cb_protected_column protected_column
|
||||
JOIN cb_protected_object protected_object
|
||||
ON protected_object.object_id = protected_column.object_id
|
||||
JOIN cb_masking_rule masking_rule
|
||||
ON masking_rule.rule_code = CASE
|
||||
WHEN protected_object.object_name = 'KB_CUSTOMERS'
|
||||
AND protected_column.column_name = 'RRN_MASKED'
|
||||
THEN 'MASK_RRN_PARTIAL'
|
||||
ELSE 'MASK_NULLIFY'
|
||||
END
|
||||
WHERE (protected_object.object_name = 'KB_CUSTOMERS'
|
||||
AND protected_column.column_name IN ('RRN_MASKED'))
|
||||
OR (protected_object.object_name = 'KB_CLAIMS'
|
||||
AND protected_column.column_name IN ('CLAIM_AMT', 'PAID_AMT'))
|
||||
OR (protected_object.object_name = 'KB_EXTERNAL_HOLDINGS'
|
||||
AND protected_column.column_name IN ('EXT_INSURER', 'EXT_PRODUCT_GRP', 'EXT_PRODUCT_TYPE'))
|
||||
) src
|
||||
ON (dst.column_id = src.column_id)
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET dst.rule_id = src.rule_id, dst.updated_at = SYSTIMESTAMP
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (column_id, rule_id, updated_at)
|
||||
VALUES (src.column_id, src.rule_id, SYSTIMESTAMP);
|
||||
COMMIT;
|
||||
|
||||
PROMPT === Configured masking target blacklist ===
|
||||
SELECT protected_object.owner,
|
||||
protected_object.object_name,
|
||||
protected_column.column_name,
|
||||
masking_rule.rule_code,
|
||||
masking_rule.rule_name,
|
||||
masking_rule.template_code
|
||||
FROM cb_column_masking_rule link
|
||||
JOIN cb_protected_column protected_column ON protected_column.column_id = link.column_id
|
||||
JOIN cb_protected_object protected_object ON protected_object.object_id = protected_column.object_id
|
||||
JOIN cb_masking_rule masking_rule ON masking_rule.rule_id = link.rule_id
|
||||
ORDER BY protected_object.object_name, protected_column.column_name;
|
||||
EXIT
|
||||
221
sql/adb/65_kb_select_ai_vpd_query_api.sql
Normal file
221
sql/adb/65_kb_select_ai_vpd_query_api.sql
Normal file
@@ -0,0 +1,221 @@
|
||||
-- ============================================================
|
||||
-- 65_kb_select_ai_vpd_query_api.sql
|
||||
--
|
||||
-- Natural-language KB query API backing package.
|
||||
-- Run as POC_2. The caller (CB_ORDS) establishes CB_AGENT_CTX before
|
||||
-- invoking this package, so generated SQL is still subject to POC_2 VPD.
|
||||
-- ============================================================
|
||||
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
||||
SET DEFINE OFF
|
||||
SET FEEDBACK ON
|
||||
|
||||
PROMPT === Annotating KB schema for Select AI ===
|
||||
COMMENT ON TABLE poc_2.kb_customers IS '고객원장. CUST_ID는 C로 시작하는 고객 식별자 예: C1001006.';
|
||||
COMMENT ON COLUMN poc_2.kb_customers.cust_id IS '고객 식별자. C로 시작하는 값 예: C1001006.';
|
||||
|
||||
COMMENT ON TABLE poc_2.kb_products IS '상품원장 및 약관상품 마스터. PRODUCT_CD는 숫자형 상품/약관 코드 예: 41048.';
|
||||
COMMENT ON COLUMN poc_2.kb_products.product_cd IS '상품 또는 약관상품 코드. 숫자형 코드 예: 41048. KB_CONTRACTS.PRODUCT_CD와 조인한다.';
|
||||
COMMENT ON COLUMN poc_2.kb_products.clause_product_nm IS '약관상품명 또는 상품명. 고객 설명용 상품 비교에 사용한다.';
|
||||
COMMENT ON COLUMN poc_2.kb_products.insurance_type IS '보험 대분류. 예: 자동차, 장기, 일반.';
|
||||
COMMENT ON COLUMN poc_2.kb_products.product_type IS '상품 유형 또는 세부 상품군.';
|
||||
COMMENT ON COLUMN poc_2.kb_products.clause_version IS '약관 버전.';
|
||||
COMMENT ON COLUMN poc_2.kb_products.sale_status IS '판매 상태.';
|
||||
COMMENT ON COLUMN poc_2.kb_products.active_yn IS '상품 활성 여부.';
|
||||
|
||||
COMMENT ON TABLE poc_2.kb_contracts IS '계약원장. CONTRACT_NO는 CT로 시작하는 계약번호이고, PRODUCT_CD는 숫자형 상품/약관 코드다.';
|
||||
COMMENT ON COLUMN poc_2.kb_contracts.contract_no IS '계약번호. CT로 시작하는 값 예: CT2699001. 41048 같은 순수 숫자 코드는 계약번호가 아니라 PRODUCT_CD일 가능성이 높다.';
|
||||
COMMENT ON COLUMN poc_2.kb_contracts.cust_id IS '계약 고객 식별자. KB_CUSTOMERS.CUST_ID와 조인한다.';
|
||||
COMMENT ON COLUMN poc_2.kb_contracts.product_cd IS '계약의 상품/약관 코드. 숫자형 코드 예: 41048. KB_PRODUCTS.PRODUCT_CD와 조인한다.';
|
||||
COMMENT ON COLUMN poc_2.kb_contracts.fc_id IS '담당 설계사 또는 FC 사용자 ID. 예: FC00789.';
|
||||
COMMENT ON COLUMN poc_2.kb_contracts.fc_channel IS '담당 채널. 예: 설계사, GA, 다이렉트.';
|
||||
COMMENT ON COLUMN poc_2.kb_contracts.contract_status IS '계약 상태. 예: 정상, 실효.';
|
||||
COMMENT ON COLUMN poc_2.kb_contracts.premium IS '계약 보험료.';
|
||||
COMMENT ON COLUMN poc_2.kb_contracts.pay_cycle IS '보험료 납입 주기.';
|
||||
|
||||
COMMENT ON TABLE poc_2.kb_coverages IS '담보원장. 계약번호 CONTRACT_NO 기준으로 KB_CONTRACTS와 조인해 보장/특약을 설명한다.';
|
||||
COMMENT ON COLUMN poc_2.kb_coverages.contract_no IS '담보가 속한 계약번호. KB_CONTRACTS.CONTRACT_NO와 조인한다.';
|
||||
COMMENT ON COLUMN poc_2.kb_coverages.cust_id IS '담보 고객 식별자.';
|
||||
COMMENT ON COLUMN poc_2.kb_coverages.coverage_nm IS '담보명 또는 특약명.';
|
||||
COMMENT ON COLUMN poc_2.kb_coverages.coverage_type IS '담보 유형.';
|
||||
COMMENT ON COLUMN poc_2.kb_coverages.coverage_div IS '담보 구분.';
|
||||
COMMENT ON COLUMN poc_2.kb_coverages.insured_amt IS '가입금액 또는 보장금액.';
|
||||
COMMENT ON COLUMN poc_2.kb_coverages.renew_due_dt IS '갱신 예정일.';
|
||||
|
||||
COMMENT ON TABLE poc_2.kb_external_holdings IS '외부보유정보 원장. 타 보험사 보유계약과 약관상품을 고객 CUST_ID 기준으로 비교한다.';
|
||||
COMMENT ON COLUMN poc_2.kb_external_holdings.cust_id IS '외부보유정보 고객 식별자. KB_CUSTOMERS.CUST_ID와 조인한다.';
|
||||
COMMENT ON COLUMN poc_2.kb_external_holdings.ext_insurer IS '외부 보험사명. 예: 삼성화재.';
|
||||
COMMENT ON COLUMN poc_2.kb_external_holdings.ext_product_grp IS '외부 상품군. 예: 자동차.';
|
||||
COMMENT ON COLUMN poc_2.kb_external_holdings.ext_product_type IS '외부 상품 유형. 예: 개인용.';
|
||||
COMMENT ON COLUMN poc_2.kb_external_holdings.ext_clause_nm IS '외부 약관상품명.';
|
||||
COMMENT ON COLUMN poc_2.kb_external_holdings.ext_sale_status IS '외부 상품 판매 상태.';
|
||||
COMMENT ON COLUMN poc_2.kb_external_holdings.ext_renew_month IS '외부 보유계약 갱신월.';
|
||||
|
||||
PROMPT === Creating VPD-aware Select AI query API ===
|
||||
CREATE OR REPLACE PACKAGE poc_2.kb_select_ai_vpd_query_api AUTHID DEFINER AS
|
||||
PROCEDURE open_query(
|
||||
p_prompt IN CLOB,
|
||||
p_limit IN PLS_INTEGER,
|
||||
p_rows OUT SYS_REFCURSOR,
|
||||
p_generated_sql OUT CLOB
|
||||
);
|
||||
END kb_select_ai_vpd_query_api;
|
||||
/
|
||||
|
||||
CREATE OR REPLACE PACKAGE BODY poc_2.kb_select_ai_vpd_query_api AS
|
||||
c_profile_name CONSTANT VARCHAR2(128) := 'KB_AIDP_SELECTAI_GPT54_MINI_FULLMETA_PROFILE_V1';
|
||||
c_max_prompt_length CONSTANT PLS_INTEGER := 4000;
|
||||
c_max_rows CONSTANT PLS_INTEGER := 100;
|
||||
|
||||
FUNCTION normalized_sql(p_generated_sql IN CLOB) RETURN VARCHAR2 IS
|
||||
v_sql VARCHAR2(32767);
|
||||
v_comment_end PLS_INTEGER;
|
||||
BEGIN
|
||||
IF p_generated_sql IS NULL OR DBMS_LOB.GETLENGTH(p_generated_sql) = 0 THEN
|
||||
RAISE_APPLICATION_ERROR(-20811, 'Select AI did not generate SQL.');
|
||||
END IF;
|
||||
IF DBMS_LOB.GETLENGTH(p_generated_sql) > 32767 THEN
|
||||
RAISE_APPLICATION_ERROR(-20812, 'Generated SQL is too long.');
|
||||
END IF;
|
||||
|
||||
v_sql := TRIM(DBMS_LOB.SUBSTR(p_generated_sql, 32767, 1));
|
||||
|
||||
-- Select AI sometimes wraps an otherwise valid SQL statement in markdown
|
||||
-- fences or adds a short leading explanation comment. Strip only those
|
||||
-- wrapper artifacts before the strict safety validator runs. Comments that
|
||||
-- remain inside the executable statement are still rejected below.
|
||||
LOOP
|
||||
v_sql := TRIM(v_sql);
|
||||
v_sql := REGEXP_REPLACE(v_sql, '^[[:space:]]*```[[:alpha:]]*[[:space:]]*', '');
|
||||
v_sql := REGEXP_REPLACE(v_sql, '[[:space:]]*```[[:space:]]*$', '');
|
||||
v_sql := TRIM(v_sql);
|
||||
|
||||
IF SUBSTR(v_sql, 1, 2) = '--' THEN
|
||||
v_comment_end := INSTR(v_sql, CHR(10));
|
||||
IF v_comment_end = 0 THEN
|
||||
RAISE_APPLICATION_ERROR(-20813, 'Select AI returned only a comment, not SQL.');
|
||||
END IF;
|
||||
v_sql := SUBSTR(v_sql, v_comment_end + 1);
|
||||
ELSIF SUBSTR(v_sql, 1, 2) = '/*' THEN
|
||||
v_comment_end := INSTR(v_sql, '*/');
|
||||
IF v_comment_end = 0 THEN
|
||||
RAISE_APPLICATION_ERROR(-20814, 'Unclosed generated SQL comment is not allowed.');
|
||||
END IF;
|
||||
v_sql := SUBSTR(v_sql, v_comment_end + 2);
|
||||
ELSE
|
||||
EXIT;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
v_sql := RTRIM(TRIM(v_sql), ';');
|
||||
RETURN TRIM(v_sql);
|
||||
END normalized_sql;
|
||||
|
||||
PROCEDURE assert_safe_kb_select(p_sql IN VARCHAR2) IS
|
||||
-- Select AI normally double-quotes Oracle identifiers. Validate a quote-free
|
||||
-- copy so POC_2.KB_CLAIMS and "POC_2"."KB_CLAIMS" follow the same policy.
|
||||
v_upper VARCHAR2(32767) := REPLACE(UPPER(p_sql), '"', '');
|
||||
-- Mask string literals before checking semicolons, keywords, packages, and
|
||||
-- data dictionary names. A harmless CASE output string should not be treated
|
||||
-- as executable SQL syntax.
|
||||
v_lexical VARCHAR2(32767) :=
|
||||
REGEXP_REPLACE(REPLACE(UPPER(p_sql), '"', ''), '''(''''|[^''])*''', '''X''');
|
||||
BEGIN
|
||||
IF NOT REGEXP_LIKE(v_lexical, '^(SELECT|WITH)[[:space:]]') THEN
|
||||
RAISE_APPLICATION_ERROR(
|
||||
-20813,
|
||||
'Only a single SELECT or WITH query is allowed. Generated prefix: '
|
||||
|| SUBSTR(v_upper, 1, 160)
|
||||
);
|
||||
END IF;
|
||||
IF INSTR(v_lexical, ';') > 0
|
||||
OR INSTR(v_lexical, '--') > 0
|
||||
OR INSTR(v_lexical, '/*') > 0
|
||||
OR INSTR(v_lexical, '*/') > 0 THEN
|
||||
RAISE_APPLICATION_ERROR(
|
||||
-20814,
|
||||
'Comments or multiple SQL statements remain after normalization. Generated prefix: '
|
||||
|| SUBSTR(v_upper, 1, 160)
|
||||
);
|
||||
END IF;
|
||||
IF REGEXP_LIKE(
|
||||
v_lexical,
|
||||
'(^|[^A-Z_])(ALTER|BEGIN|COMMIT|CREATE|DECLARE|DELETE|DROP|EXECUTE|GRANT|INSERT|MERGE|REVOKE|ROLLBACK|TRUNCATE|UPDATE)([^A-Z_]|$)'
|
||||
) THEN
|
||||
RAISE_APPLICATION_ERROR(-20815, 'Only read-only SQL is allowed.');
|
||||
END IF;
|
||||
IF INSTR(v_lexical, 'DBMS_') > 0
|
||||
OR INSTR(v_lexical, 'UTL_') > 0
|
||||
OR INSTR(v_lexical, 'SYS.') > 0
|
||||
OR INSTR(v_lexical, 'ADMIN.') > 0
|
||||
OR INSTR(v_lexical, 'CB_') > 0
|
||||
OR INSTR(v_lexical, 'ALL_') > 0
|
||||
OR INSTR(v_lexical, 'DBA_') > 0
|
||||
OR INSTR(v_lexical, 'USER_') > 0
|
||||
OR INSTR(v_lexical, 'FOR UPDATE') > 0 THEN
|
||||
RAISE_APPLICATION_ERROR(-20816, 'System, backoffice, and locking objects are not allowed.');
|
||||
END IF;
|
||||
IF NOT REGEXP_LIKE(
|
||||
v_lexical,
|
||||
'(FROM|JOIN)[[:space:]]+(POC_2[.])?KB_(CUSTOMERS|PRODUCTS|CONTRACTS|COVERAGES|CLAIMS|EXTERNAL_HOLDINGS|STAKEHOLDERS)([[:space:],)]|$)'
|
||||
) THEN
|
||||
RAISE_APPLICATION_ERROR(-20817, 'The generated SQL must query a permitted KB business table.');
|
||||
END IF;
|
||||
END assert_safe_kb_select;
|
||||
|
||||
PROCEDURE open_query(
|
||||
p_prompt IN CLOB,
|
||||
p_limit IN PLS_INTEGER,
|
||||
p_rows OUT SYS_REFCURSOR,
|
||||
p_generated_sql OUT CLOB
|
||||
) IS
|
||||
v_prompt VARCHAR2(4000);
|
||||
v_generation_prompt CLOB;
|
||||
v_generated_sql CLOB;
|
||||
v_sql VARCHAR2(32767);
|
||||
v_limit PLS_INTEGER;
|
||||
BEGIN
|
||||
IF p_prompt IS NULL OR DBMS_LOB.GETLENGTH(TRIM(p_prompt)) = 0 THEN
|
||||
RAISE_APPLICATION_ERROR(-20801, 'prompt is required.');
|
||||
END IF;
|
||||
IF DBMS_LOB.GETLENGTH(p_prompt) > c_max_prompt_length THEN
|
||||
RAISE_APPLICATION_ERROR(-20802, 'prompt must be 4,000 characters or fewer.');
|
||||
END IF;
|
||||
|
||||
v_prompt := TRIM(DBMS_LOB.SUBSTR(p_prompt, c_max_prompt_length, 1));
|
||||
IF REGEXP_LIKE(UPPER(v_prompt), 'SELECT[[:space:]]+AI') THEN
|
||||
RAISE_APPLICATION_ERROR(-20803, 'SELECT AI action prefixes are not allowed in prompt.');
|
||||
END IF;
|
||||
v_limit := LEAST(GREATEST(NVL(p_limit, 50), 1), c_max_rows);
|
||||
|
||||
v_generation_prompt :=
|
||||
'Return exactly one Oracle SQL SELECT or WITH statement and no markdown. '
|
||||
|| 'Use only these POC_2 business tables: KB_CUSTOMERS, KB_PRODUCTS, KB_CONTRACTS, '
|
||||
|| 'KB_COVERAGES, KB_CLAIMS, KB_EXTERNAL_HOLDINGS, KB_STAKEHOLDERS. '
|
||||
|| 'Never use DDL, DML, PL/SQL, system views, backoffice tables, comments, or FOR UPDATE. '
|
||||
|| 'The caller request is: ' || v_prompt;
|
||||
|
||||
v_generated_sql := DBMS_CLOUD_AI.GENERATE(
|
||||
prompt => v_generation_prompt,
|
||||
profile_name => c_profile_name,
|
||||
action => 'showsql'
|
||||
);
|
||||
v_sql := normalized_sql(v_generated_sql);
|
||||
assert_safe_kb_select(v_sql);
|
||||
|
||||
-- CB_AGENT_CTX was set by CB_ORDS before this call. Parsing this statement
|
||||
-- in the same database session makes Oracle evaluate POC_2 VPD policies.
|
||||
OPEN p_rows FOR
|
||||
'SELECT * FROM (' || v_sql || ') WHERE ROWNUM <= :row_limit'
|
||||
USING v_limit;
|
||||
p_generated_sql := v_sql;
|
||||
END open_query;
|
||||
END kb_select_ai_vpd_query_api;
|
||||
/
|
||||
|
||||
SHOW ERRORS
|
||||
|
||||
PROMPT === Granting fixed query API to CB_ORDS ===
|
||||
GRANT EXECUTE ON poc_2.kb_select_ai_vpd_query_api TO cb_ords;
|
||||
|
||||
PROMPT === POC_2 Select AI VPD query API ready ===
|
||||
EXIT
|
||||
149
sql/adb/66_kb_select_ai_vpd_query_ords.sql
Normal file
149
sql/adb/66_kb_select_ai_vpd_query_ords.sql
Normal file
@@ -0,0 +1,149 @@
|
||||
-- ============================================================
|
||||
-- 66_kb_select_ai_vpd_query_ords.sql
|
||||
--
|
||||
-- POST /ords/cb-ords/kb-select-ai-vpd/query
|
||||
-- Authorization: Bearer <VPD token>
|
||||
-- {"prompt":"채널별 청구 건수를 보여줘", "limit":50}
|
||||
--
|
||||
-- Run as CB_ORDS after 65. The Bearer token is resolved before Select AI
|
||||
-- generates and executes its single validated read-only SQL statement.
|
||||
-- ============================================================
|
||||
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
||||
SET DEFINE OFF
|
||||
SET FEEDBACK ON
|
||||
|
||||
PROMPT === Granting context setup to the ORDS gateway ===
|
||||
GRANT EXECUTE ON cb_ords_handler_pkg TO ords_public_user;
|
||||
|
||||
PROMPT === Resetting only the VPD Select AI query module ===
|
||||
BEGIN
|
||||
ORDS.DELETE_MODULE(p_module_name => 'kb.select.ai.vpd');
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN NULL;
|
||||
END;
|
||||
/
|
||||
|
||||
PROMPT === Creating VPD-aware Select AI query endpoint ===
|
||||
BEGIN
|
||||
ORDS.DEFINE_MODULE(
|
||||
p_module_name => 'kb.select.ai.vpd',
|
||||
p_base_path => 'kb-select-ai-vpd/',
|
||||
p_items_per_page => 0,
|
||||
p_status => 'PUBLISHED'
|
||||
);
|
||||
|
||||
ORDS.DEFINE_TEMPLATE(
|
||||
p_module_name => 'kb.select.ai.vpd',
|
||||
p_pattern => 'query'
|
||||
);
|
||||
|
||||
ORDS.DEFINE_HANDLER(
|
||||
p_module_name => 'kb.select.ai.vpd',
|
||||
p_pattern => 'query',
|
||||
p_method => 'POST',
|
||||
p_source_type => ORDS.source_type_plsql,
|
||||
p_source => q'~
|
||||
DECLARE
|
||||
v_body_text CLOB;
|
||||
v_prompt VARCHAR2(4000);
|
||||
v_limit PLS_INTEGER;
|
||||
v_rows SYS_REFCURSOR;
|
||||
v_generated_sql CLOB;
|
||||
v_response CLOB;
|
||||
v_error_code NUMBER;
|
||||
v_error_message VARCHAR2(4000);
|
||||
BEGIN
|
||||
-- Resolves the Bearer token and sets the VPD/ASO application context in
|
||||
-- this very ORDS database session before the generated SQL is parsed.
|
||||
cb_ords_handler_pkg.set_vpd_context(:auth_header, :probe_id);
|
||||
|
||||
v_body_text := :body_text;
|
||||
v_prompt := JSON_VALUE(v_body_text, '$.prompt' RETURNING VARCHAR2(4000) NULL ON ERROR);
|
||||
v_limit := JSON_VALUE(v_body_text, '$.limit' RETURNING NUMBER DEFAULT 50 ON ERROR);
|
||||
|
||||
IF v_prompt IS NULL OR TRIM(v_prompt) IS NULL THEN
|
||||
RAISE_APPLICATION_ERROR(-20801, 'prompt is required');
|
||||
END IF;
|
||||
IF v_limit IS NULL OR v_limit < 1 OR v_limit > 100 THEN
|
||||
RAISE_APPLICATION_ERROR(-20802, 'limit must be between 1 and 100');
|
||||
END IF;
|
||||
|
||||
-- Dynamic PL/SQL keeps ORDS gateway validation limited to the local handler
|
||||
-- package while CB_ORDS invokes only the fixed POC_2 API granted to it.
|
||||
EXECUTE IMMEDIATE
|
||||
'BEGIN poc_2.kb_select_ai_vpd_query_api.open_query(:prompt, :row_limit, :rows, :sql); END;'
|
||||
USING IN v_prompt, IN v_limit, OUT v_rows, OUT v_generated_sql;
|
||||
|
||||
:status_code := 200;
|
||||
OWA_UTIL.MIME_HEADER('application/json', FALSE);
|
||||
HTP.P('Cache-Control: no-store');
|
||||
OWA_UTIL.HTTP_HEADER_CLOSE;
|
||||
|
||||
APEX_JSON.OPEN_OBJECT;
|
||||
APEX_JSON.WRITE('profile', 'KB_AIDP_SELECTAI_GPT54_MINI_FULLMETA_PROFILE_V1');
|
||||
APEX_JSON.WRITE('generatedSql', v_generated_sql);
|
||||
APEX_JSON.WRITE('items', v_rows);
|
||||
APEX_JSON.CLOSE_OBJECT;
|
||||
|
||||
cb_ords_handler_pkg.clear_vpd_context;
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
v_error_code := SQLCODE;
|
||||
v_error_message := SQLERRM;
|
||||
cb_ords_handler_pkg.clear_vpd_context;
|
||||
:status_code := CASE
|
||||
WHEN v_error_code IN (-20001, -20002) THEN 403
|
||||
WHEN v_error_code BETWEEN -20899 AND -20800 THEN 400
|
||||
WHEN v_error_code BETWEEN -20199 AND -20100 THEN 403
|
||||
ELSE 500
|
||||
END;
|
||||
SELECT JSON_OBJECT(
|
||||
'errorCode' VALUE v_error_code,
|
||||
'error' VALUE CASE
|
||||
WHEN v_error_code IN (-20001, -20002) THEN
|
||||
'VPD token is missing, invalid, expired, or has no access permission.'
|
||||
WHEN v_error_code BETWEEN -20899 AND -20800 THEN v_error_message
|
||||
ELSE 'Select AI query execution failed.'
|
||||
END
|
||||
RETURNING CLOB
|
||||
)
|
||||
INTO v_response
|
||||
FROM dual;
|
||||
OWA_UTIL.MIME_HEADER('application/json', FALSE);
|
||||
HTP.P('Cache-Control: no-store');
|
||||
OWA_UTIL.HTTP_HEADER_CLOSE;
|
||||
HTP.P(v_response);
|
||||
END;
|
||||
~',
|
||||
p_items_per_page => 0
|
||||
);
|
||||
|
||||
ORDS.DEFINE_PARAMETER(
|
||||
p_module_name => 'kb.select.ai.vpd',
|
||||
p_pattern => 'query',
|
||||
p_method => 'POST',
|
||||
p_name => 'Authorization',
|
||||
p_bind_variable_name => 'auth_header',
|
||||
p_source_type => 'HEADER',
|
||||
p_param_type => 'STRING',
|
||||
p_access_method => 'IN'
|
||||
);
|
||||
|
||||
ORDS.DEFINE_PARAMETER(
|
||||
p_module_name => 'kb.select.ai.vpd',
|
||||
p_pattern => 'query',
|
||||
p_method => 'POST',
|
||||
p_name => 'X-VPD-Probe-Id',
|
||||
p_bind_variable_name => 'probe_id',
|
||||
p_source_type => 'HEADER',
|
||||
p_param_type => 'STRING',
|
||||
p_access_method => 'IN'
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
END;
|
||||
/
|
||||
|
||||
PROMPT === VPD-aware Select AI query endpoint ready ===
|
||||
PROMPT Path: /ords/cb-ords/kb-select-ai-vpd/query
|
||||
EXIT
|
||||
125
sql/adb/67_drop_legacy_kb_contracts_premium_vpd_cls_policy.sql
Normal file
125
sql/adb/67_drop_legacy_kb_contracts_premium_vpd_cls_policy.sql
Normal file
@@ -0,0 +1,125 @@
|
||||
-- ============================================================
|
||||
-- 67_drop_legacy_kb_contracts_premium_vpd_cls_policy.sql
|
||||
--
|
||||
-- 목적:
|
||||
-- 과거 VPD Column-Level Security(CLS) PoC에서 생성된
|
||||
-- POC_2.KB_CONTRACTS.PREMIUM 대상 VPD 컬럼 정책을 제거한다.
|
||||
--
|
||||
-- 배경:
|
||||
-- 현재 운영 설계는 다음처럼 역할을 분리한다.
|
||||
--
|
||||
-- * VPD/DBMS_RLS : 행 수준 필터링만 담당
|
||||
-- * ASO/Data Redaction : 컬럼 마스킹만 담당
|
||||
--
|
||||
-- 따라서 DBMS_RLS.ADD_POLICY의 sec_relevant_cols 기반 컬럼 제어 정책은
|
||||
-- 운영 정책에서 제거한다. 이 스크립트는 named policy 하나만 삭제하며,
|
||||
-- ASO/Data Redaction 정책은 건드리지 않는다.
|
||||
--
|
||||
-- 삭제 대상:
|
||||
-- object_schema : POC_2
|
||||
-- object_name : KB_CONTRACTS
|
||||
-- policy_name : KB_PREMIUM_CLS_POLICY
|
||||
-- column : PREMIUM
|
||||
--
|
||||
-- 실행 권한:
|
||||
-- ADMIN 또는 DBMS_RLS.DROP_POLICY 수행 권한이 있는 계정.
|
||||
-- ============================================================
|
||||
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
||||
SET DEFINE OFF
|
||||
SET FEEDBACK ON
|
||||
|
||||
PROMPT === Before: legacy VPD CLS policy ===
|
||||
COLUMN object_owner FORMAT A18
|
||||
COLUMN object_name FORMAT A32
|
||||
COLUMN policy_name FORMAT A36
|
||||
COLUMN function FORMAT A40
|
||||
COLUMN enable FORMAT A10
|
||||
COLUMN policy_type FORMAT A20
|
||||
|
||||
SELECT object_owner,
|
||||
object_name,
|
||||
policy_name,
|
||||
pf_owner,
|
||||
package,
|
||||
function,
|
||||
enable,
|
||||
policy_type,
|
||||
sel
|
||||
FROM dba_policies
|
||||
WHERE object_owner = 'POC_2'
|
||||
AND object_name = 'KB_CONTRACTS'
|
||||
AND UPPER(policy_name) = 'KB_PREMIUM_CLS_POLICY';
|
||||
|
||||
PROMPT === Before: security relevant columns ===
|
||||
COLUMN sec_rel_column FORMAT A32
|
||||
|
||||
SELECT object_owner,
|
||||
object_name,
|
||||
policy_name,
|
||||
sec_rel_column
|
||||
FROM dba_sec_relevant_cols
|
||||
WHERE object_owner = 'POC_2'
|
||||
AND object_name = 'KB_CONTRACTS'
|
||||
AND UPPER(policy_name) = 'KB_PREMIUM_CLS_POLICY';
|
||||
|
||||
PROMPT === Dropping only the legacy VPD CLS policy ===
|
||||
DECLARE
|
||||
v_count NUMBER;
|
||||
BEGIN
|
||||
SELECT COUNT(*)
|
||||
INTO v_count
|
||||
FROM dba_policies
|
||||
WHERE object_owner = 'POC_2'
|
||||
AND object_name = 'KB_CONTRACTS'
|
||||
AND UPPER(policy_name) = 'KB_PREMIUM_CLS_POLICY';
|
||||
|
||||
IF v_count > 0 THEN
|
||||
DBMS_RLS.DROP_POLICY(
|
||||
object_schema => 'POC_2',
|
||||
object_name => 'KB_CONTRACTS',
|
||||
policy_name => 'KB_PREMIUM_CLS_POLICY'
|
||||
);
|
||||
END IF;
|
||||
END;
|
||||
/
|
||||
|
||||
PROMPT === After: legacy VPD CLS policy ===
|
||||
SELECT object_owner,
|
||||
object_name,
|
||||
policy_name,
|
||||
pf_owner,
|
||||
package,
|
||||
function,
|
||||
enable,
|
||||
policy_type,
|
||||
sel
|
||||
FROM dba_policies
|
||||
WHERE object_owner = 'POC_2'
|
||||
AND object_name = 'KB_CONTRACTS'
|
||||
AND UPPER(policy_name) = 'KB_PREMIUM_CLS_POLICY';
|
||||
|
||||
PROMPT === After: security relevant columns ===
|
||||
SELECT object_owner,
|
||||
object_name,
|
||||
policy_name,
|
||||
sec_rel_column
|
||||
FROM dba_sec_relevant_cols
|
||||
WHERE object_owner = 'POC_2'
|
||||
AND object_name = 'KB_CONTRACTS'
|
||||
AND UPPER(policy_name) = 'KB_PREMIUM_CLS_POLICY';
|
||||
|
||||
PROMPT === ASO/Data Redaction on PREMIUM is intentionally not changed ===
|
||||
COLUMN column_name FORMAT A24
|
||||
COLUMN function_type FORMAT A28
|
||||
|
||||
SELECT object_owner,
|
||||
object_name,
|
||||
column_name,
|
||||
function_type,
|
||||
function_parameters
|
||||
FROM redaction_columns
|
||||
WHERE object_owner = 'POC_2'
|
||||
AND object_name = 'KB_CONTRACTS'
|
||||
AND UPPER(column_name) = 'PREMIUM';
|
||||
|
||||
PROMPT === Legacy KB_CONTRACTS premium VPD CLS cleanup complete ===
|
||||
Reference in New Issue
Block a user