fix #557: guide permission-driven VPD flow

This commit is contained in:
devmrko
2026-06-29 12:11:25 +09:00
parent fbe3d4682b
commit 908ac9a386
57 changed files with 1952 additions and 312 deletions

View File

@@ -372,6 +372,17 @@ BEGIN
IF v_column IS NOT NULL THEN
append_or(r.permission_effect, v_column || ' = ' || quote_literal(r.rule_value));
END IF;
ELSIF r.rule_type = 'TAG' THEN
v_column := safe_column(p_schema, p_object, NVL(r.rule_column, 'TECH_TAG'));
IF v_column IS NOT NULL
AND r.rule_value IS NOT NULL
AND REGEXP_LIKE(TRIM(r.rule_value), '^[A-Za-z0-9_-]+$') THEN
append_or(
r.permission_effect,
'REGEXP_LIKE(UPPER(' || v_column || '), ' ||
quote_literal('(^|,)' || UPPER(TRIM(r.rule_value)) || '(,|$)') || ')'
);
END IF;
ELSIF r.rule_type = '=' THEN
v_column := safe_column(p_schema, p_object, r.rule_column);
IF v_column IS NOT NULL THEN

View File

@@ -125,9 +125,20 @@ CREATE TABLE cb_protected_object (
owner VARCHAR2(128) NOT NULL,
object_name VARCHAR2(128) NOT NULL UNIQUE,
ords_path VARCHAR2(300) NOT NULL,
enabled_yn CHAR(1) DEFAULT 'Y' CHECK (enabled_yn IN ('Y','N')) NOT NULL
enabled_yn CHAR(1) DEFAULT 'Y' CHECK (enabled_yn IN ('Y','N')) NOT NULL,
description VARCHAR2(200)
);
BEGIN
EXECUTE IMMEDIATE 'ALTER TABLE cb_protected_object ADD (description VARCHAR2(200))';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -1430 THEN
RAISE;
END IF;
END;
/
CREATE TABLE cb_protected_column (
column_id NUMBER PRIMARY KEY,
object_id NUMBER NOT NULL REFERENCES cb_protected_object(object_id),
@@ -184,6 +195,41 @@ CREATE TABLE cb_ords_probe_audit (
created_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL
);
BEGIN
EXECUTE IMMEDIATE '
CREATE TABLE cb_vpd_policy_note (
object_owner VARCHAR2(128) NOT NULL,
object_name VARCHAR2(128) NOT NULL,
policy_name VARCHAR2(128) NOT NULL,
description VARCHAR2(500) NOT NULL,
updated_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL,
CONSTRAINT cb_vpd_policy_note_pk PRIMARY KEY (object_owner, object_name, policy_name)
)';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -955 THEN
RAISE;
END IF;
END;
/
BEGIN
EXECUTE IMMEDIATE '
CREATE TABLE cb_vpd_filter_note (
function_owner VARCHAR2(128) NOT NULL,
function_name VARCHAR2(128) NOT NULL,
description VARCHAR2(500) NOT NULL,
updated_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL,
CONSTRAINT cb_vpd_filter_note_pk PRIMARY KEY (function_owner, function_name)
)';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -955 THEN
RAISE;
END IF;
END;
/
PROMPT === Creating backoffice settings ===
BEGIN
EXECUTE IMMEDIATE '

View File

@@ -6,6 +6,8 @@
-- CB_AGENT_CTX.USER_ID -> direct CB_USER_ROLE + group CB_USER_GROUP/CB_GROUP_ROLE
-- -> CB_PERMISSION -> CB_PERMISSION_RULE
-- and builds a row predicate for the object passed by DBMS_RLS.
-- Supported rule types include TAG. TAG matches a token in a comma-delimited
-- TECH_TAG value, allowing the vector search view to remain one row per chunk.
-- ============================================================
WHENEVER SQLERROR EXIT SQL.SQLCODE
SET ECHO OFF
@@ -152,6 +154,21 @@ BEGIN
IF v_column IS NOT NULL THEN
append_or(r.permission_effect, v_column || ' = ' || quote_literal(r.rule_value));
END IF;
ELSIF r.rule_type = 'TAG' THEN
-- A TAG rule defaults to the ingestion metadata column TECH_TAG.
-- Multiple ALLOW TAG rows are accumulated by append_or, so any
-- matching tag grants visibility. DENY rows are accumulated
-- separately and removed from the final result.
v_column := safe_column(p_schema, p_object, NVL(r.rule_column, 'TECH_TAG'));
IF v_column IS NOT NULL
AND r.rule_value IS NOT NULL
AND REGEXP_LIKE(TRIM(r.rule_value), '^[A-Za-z0-9_-]+$') THEN
append_or(
r.permission_effect,
'REGEXP_LIKE(UPPER(' || v_column || '), ' ||
quote_literal('(^|,)' || UPPER(TRIM(r.rule_value)) || '(,|$)') || ')'
);
END IF;
ELSIF r.rule_type = '=' THEN
v_column := safe_column(p_schema, p_object, r.rule_column);
IF v_column IS NOT NULL THEN

View File

@@ -0,0 +1,257 @@
-- ============================================================
-- 28_agent_ords_vector_tag_vpd_setup.sql
-- Vector/chunk knowledge search data plane with tag-based VPD.
--
-- Run as ADMIN after:
-- * 25_agent_ords_security_backoffice_support.sql
-- * 26_agent_ords_security_dynamic_vpd_filter.sql
--
-- Target: Oracle AI Database 23ai/26ai (VECTOR data type).
-- The embedding is produced by the ingestion pipeline. The database
-- stores the chunk, its vector, and one or more normalized TECH_TAG rows.
-- The VPD policy protects a one-row-per-chunk view whose TECH_TAG value is a
-- comma-delimited tag list, so ALLOW and DENY apply to the whole chunk.
-- ============================================================
WHENEVER SQLERROR EXIT SQL.SQLCODE
SET ECHO ON
SET FEEDBACK ON
SET DEFINE OFF
PROMPT === 1. Creating vector chunk and tag tables ===
BEGIN
EXECUTE IMMEDIATE q'!
CREATE TABLE cb_vector_document_chunk (
chunk_id NUMBER PRIMARY KEY,
document_id VARCHAR2(200) NOT NULL,
chunk_no NUMBER NOT NULL,
title VARCHAR2(500),
chunk_text CLOB NOT NULL,
source_uri VARCHAR2(1000),
embedding VECTOR,
created_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL,
CONSTRAINT cb_vector_chunk_uk UNIQUE (document_id, chunk_no)
)!';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -955 THEN
RAISE;
END IF;
END;
/
BEGIN
EXECUTE IMMEDIATE q'!
CREATE TABLE cb_vector_document_tag (
chunk_id NUMBER NOT NULL REFERENCES cb_vector_document_chunk(chunk_id) ON DELETE CASCADE,
tech_tag VARCHAR2(100) NOT NULL,
CONSTRAINT cb_vector_tag_pk PRIMARY KEY (chunk_id, tech_tag),
CONSTRAINT cb_vector_tag_format_ck CHECK (REGEXP_LIKE(tech_tag, '^[A-Z0-9_-]+$'))
)!';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -955 THEN
RAISE;
END IF;
END;
/
BEGIN
EXECUTE IMMEDIATE 'CREATE INDEX cb_vector_tag_ix ON cb_vector_document_tag (tech_tag, chunk_id)';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -955 THEN
RAISE;
END IF;
END;
/
PROMPT === 2. Seeding a small, repeatable vector search example ===
-- These four-dimensional vectors are only a smoke-test fixture. Replace
-- them with the embedding model's dimension in the real ingestion pipeline.
INSERT INTO cb_vector_document_chunk (
chunk_id, document_id, chunk_no, title, chunk_text, source_uri, embedding
)
SELECT 28001, 'knowledge-001', 1, 'Spring Boot VPD 시작하기',
TO_CLOB('Spring Boot 애플리케이션은 토큰으로 사용자를 식별하고 ORDS를 호출한다.'),
'kb://security/spring-boot-vpd', TO_VECTOR('[0.10,0.20,0.30,0.40]')
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM cb_vector_document_chunk WHERE chunk_id = 28001);
INSERT INTO cb_vector_document_chunk (
chunk_id, document_id, chunk_no, title, chunk_text, source_uri, embedding
)
SELECT 28002, 'knowledge-001', 2, 'Oracle VPD 정책 연결',
TO_CLOB('Oracle VPD는 현재 사용자 권한에 맞는 행만 반환하도록 WHERE 조건을 동적으로 붙인다.'),
'kb://security/oracle-vpd', TO_VECTOR('[0.20,0.30,0.40,0.50]')
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM cb_vector_document_chunk WHERE chunk_id = 28002);
INSERT INTO cb_vector_document_chunk (
chunk_id, document_id, chunk_no, title, chunk_text, source_uri, embedding
)
SELECT 28003, 'knowledge-002', 1, 'MCP 도구 권한 설계',
TO_CLOB('MCP 도구도 같은 역할과 기술 태그 규칙을 사용해 지식자료 검색 범위를 제한할 수 있다.'),
'kb://platform/mcp-permission', TO_VECTOR('[0.80,0.70,0.60,0.50]')
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM cb_vector_document_chunk WHERE chunk_id = 28003);
INSERT INTO cb_vector_document_tag (chunk_id, tech_tag)
SELECT 28001, 'SPRING_BOOT' FROM dual
WHERE NOT EXISTS (
SELECT 1 FROM cb_vector_document_tag WHERE chunk_id = 28001 AND tech_tag = 'SPRING_BOOT'
);
INSERT INTO cb_vector_document_tag (chunk_id, tech_tag)
SELECT 28001, 'ORDS' FROM dual
WHERE NOT EXISTS (
SELECT 1 FROM cb_vector_document_tag WHERE chunk_id = 28001 AND tech_tag = 'ORDS'
);
INSERT INTO cb_vector_document_tag (chunk_id, tech_tag)
SELECT 28002, 'ORACLE_VPD' FROM dual
WHERE NOT EXISTS (
SELECT 1 FROM cb_vector_document_tag WHERE chunk_id = 28002 AND tech_tag = 'ORACLE_VPD'
);
INSERT INTO cb_vector_document_tag (chunk_id, tech_tag)
SELECT 28002, 'ORDS' FROM dual
WHERE NOT EXISTS (
SELECT 1 FROM cb_vector_document_tag WHERE chunk_id = 28002 AND tech_tag = 'ORDS'
);
INSERT INTO cb_vector_document_tag (chunk_id, tech_tag)
SELECT 28003, 'MCP' FROM dual
WHERE NOT EXISTS (
SELECT 1 FROM cb_vector_document_tag WHERE chunk_id = 28003 AND tech_tag = 'MCP'
);
PROMPT === 3. Creating the tag-aware one-row-per-chunk protected view ===
CREATE OR REPLACE VIEW cb_vector_search_documents AS
SELECT c.chunk_id,
c.document_id,
c.chunk_no,
c.title,
c.chunk_text,
c.source_uri,
c.embedding,
(
SELECT LISTAGG(t.tech_tag, ',') WITHIN GROUP (ORDER BY t.tech_tag)
FROM cb_vector_document_tag t
WHERE t.chunk_id = c.chunk_id
) AS tech_tag
FROM cb_vector_document_chunk c
WHERE EXISTS (
SELECT 1
FROM cb_vector_document_tag t
WHERE t.chunk_id = c.chunk_id
);
PROMPT === 4. Attaching dynamic VPD to the view ===
BEGIN
DBMS_RLS.DROP_POLICY(
object_schema => USER,
object_name => 'CB_VECTOR_SEARCH_DOCUMENTS',
policy_name => 'CB_VECTOR_TAG_SELECT_POLICY'
);
EXCEPTION
WHEN OTHERS THEN NULL;
END;
/
BEGIN
DBMS_RLS.ADD_POLICY(
object_schema => USER,
object_name => 'CB_VECTOR_SEARCH_DOCUMENTS',
policy_name => 'CB_VECTOR_TAG_SELECT_POLICY',
function_schema => USER,
policy_function => 'CB_AGENT_DOC_VPD_FILTER',
statement_types => 'SELECT',
policy_type => DBMS_RLS.DYNAMIC,
enable => TRUE
);
END;
/
PROMPT === 5. Registering the protected object and its columns ===
DECLARE
v_object_id NUMBER;
PROCEDURE ensure_column(
p_column_name IN VARCHAR2,
p_sensitive_yn IN CHAR,
p_sensitivity_level IN VARCHAR2,
p_redaction_method IN VARCHAR2
) IS
v_count NUMBER;
v_id NUMBER;
BEGIN
SELECT COUNT(*)
INTO v_count
FROM cb_protected_column
WHERE object_id = v_object_id
AND column_name = p_column_name;
IF v_count = 0 THEN
SELECT NVL(MAX(column_id), 0) + 1
INTO v_id
FROM cb_protected_column;
INSERT INTO cb_protected_column (
column_id, object_id, column_name, sensitive_yn, sensitivity_level, redaction_method
) VALUES (
v_id, v_object_id, p_column_name, p_sensitive_yn, p_sensitivity_level, p_redaction_method
);
ELSE
UPDATE cb_protected_column
SET sensitive_yn = p_sensitive_yn,
sensitivity_level = p_sensitivity_level,
redaction_method = p_redaction_method
WHERE object_id = v_object_id
AND column_name = p_column_name;
END IF;
END;
BEGIN
BEGIN
SELECT object_id
INTO v_object_id
FROM cb_protected_object
WHERE object_name = 'CB_VECTOR_SEARCH_DOCUMENTS';
UPDATE cb_protected_object
SET owner = USER,
ords_path = 'cb-ords/cb-agent-vector/search',
enabled_yn = 'Y',
description = '기술 태그 권한이 적용된 벡터 청크 검색 전용 ORDS Handler'
WHERE object_id = v_object_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
SELECT NVL(MAX(object_id), 0) + 1
INTO v_object_id
FROM cb_protected_object;
INSERT INTO cb_protected_object (
object_id, owner, object_name, ords_path, enabled_yn, description
) VALUES (
v_object_id, USER, 'CB_VECTOR_SEARCH_DOCUMENTS',
'cb-ords/cb-agent-vector/search', 'Y',
'기술 태그 권한이 적용된 벡터 청크 검색 전용 ORDS Handler'
);
END;
-- Column policy here is only a display-policy hint. Row access is the TAG
-- VPD rule above; the vector handler never returns EMBEDDING to the caller.
ensure_column('CHUNK_ID', 'N', 'PUBLIC', 'NONE');
ensure_column('DOCUMENT_ID', 'N', 'PUBLIC', 'NONE');
ensure_column('CHUNK_NO', 'N', 'PUBLIC', 'NONE');
ensure_column('TITLE', 'N', 'PUBLIC', 'NONE');
ensure_column('CHUNK_TEXT', 'N', 'PUBLIC', 'NONE');
ensure_column('SOURCE_URI', 'N', 'PUBLIC', 'NONE');
ensure_column('TECH_TAG', 'N', 'PUBLIC', 'NONE');
ensure_column('EMBEDDING', 'Y', 'RESTRICTED', 'NULLIFY');
END;
/
PROMPT === 6. Granting the ORDS runtime read access to the protected view ===
GRANT SELECT ON cb_vector_search_documents TO cb_ords;
COMMIT;
PROMPT === Vector/tag VPD setup complete ===
PROMPT Next: run 29_agent_ords_vector_search_ords.sql as CB_ORDS.
EXIT;

View File

@@ -0,0 +1,153 @@
-- ============================================================
-- 29_agent_ords_vector_search_ords.sql
-- Dedicated ORDS vector search endpoint for the tag-protected view.
--
-- Run as CB_ORDS after 22_agent_ords_security_ords_handler_setup.sql and
-- 28_agent_ords_vector_tag_vpd_setup.sql.
--
-- Request:
-- POST /ords/cb-ords/cb-agent-vector/search?limit=10
-- Authorization: Bearer <key>
-- {"embedding":[0.10,0.20,0.30,0.40]}
--
-- The handler sets the application context, lets VPD filter one row per
-- chunk by the aggregated TECH_TAG list, then calculates vector distance.
-- EMBEDDING is never returned.
-- ============================================================
WHENEVER SQLERROR EXIT SQL.SQLCODE
SET ECHO ON
SET FEEDBACK ON
SET DEFINE OFF
PROMPT === Creating the vector search ORDS module ===
BEGIN
ORDS.DELETE_MODULE(p_module_name => 'cb.agent.vector');
EXCEPTION
WHEN OTHERS THEN NULL;
END;
/
BEGIN
ORDS.DEFINE_MODULE(
p_module_name => 'cb.agent.vector',
p_base_path => 'cb-agent-vector/',
p_items_per_page => 25,
p_status => 'PUBLISHED'
);
ORDS.DEFINE_TEMPLATE(
p_module_name => 'cb.agent.vector',
p_pattern => 'search'
);
ORDS.DEFINE_HANDLER(
p_module_name => 'cb.agent.vector',
p_pattern => 'search',
p_method => 'POST',
p_source_type => ORDS.source_type_plsql,
p_source => q'!
DECLARE
v_rows SYS_REFCURSOR;
v_embedding_text VARCHAR2(32767);
BEGIN
cb_ords_handler_pkg.set_vpd_context(:auth_header);
-- JSON_QUERY accepts a numeric JSON array. The JSON_VALUE fallback also
-- permits clients that send the vector as the text "[0.1,0.2,...]".
v_embedding_text := JSON_QUERY(
:body_text,
'$.embedding' RETURNING VARCHAR2(32767)
);
IF v_embedding_text IS NULL THEN
v_embedding_text := JSON_VALUE(
:body_text,
'$.embedding' RETURNING VARCHAR2(32767)
);
END IF;
IF v_embedding_text IS NULL OR TRIM(v_embedding_text) IS NULL THEN
RAISE_APPLICATION_ERROR(-20801, 'embedding is required');
END IF;
OPEN v_rows FOR
SELECT chunk_id,
document_id,
chunk_no,
title,
chunk_text,
source_uri,
tech_tag,
score
FROM (
SELECT d.chunk_id,
d.document_id,
d.chunk_no,
d.title,
d.chunk_text,
d.source_uri,
d.tech_tag,
VECTOR_DISTANCE(
d.embedding,
TO_VECTOR(v_embedding_text),
COSINE
) AS score
FROM admin.cb_vector_search_documents d
WHERE d.embedding IS NOT NULL
ORDER BY score
) ranked_chunks
WHERE ROWNUM <= LEAST(GREATEST(NVL(:row_limit, 10), 1), 100);
: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('items', v_rows);
APEX_JSON.CLOSE_OBJECT;
cb_ords_handler_pkg.clear_vpd_context;
EXCEPTION
WHEN OTHERS THEN
cb_ords_handler_pkg.clear_vpd_context;
:status_code := CASE WHEN SQLCODE = -20801 THEN 400 ELSE 403 END;
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('error', SQLERRM);
APEX_JSON.CLOSE_OBJECT;
END;
!',
p_items_per_page => 0
);
ORDS.DEFINE_PARAMETER(
p_module_name => 'cb.agent.vector',
p_pattern => 'search',
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 => 'cb.agent.vector',
p_pattern => 'search',
p_method => 'POST',
p_name => 'limit',
p_bind_variable_name => 'row_limit',
p_source_type => 'URI',
p_param_type => 'INT',
p_access_method => 'IN'
);
COMMIT;
END;
/
PROMPT === Vector search ORDS endpoint ready ===
PROMPT Path: /ords/cb-ords/cb-agent-vector/search
EXIT;

View File

@@ -0,0 +1,130 @@
-- ============================================================
-- 30_agent_ords_vector_tag_vpd_test.sql
-- Unit-style checks for TAG rules on CB_VECTOR_SEARCH_DOCUMENTS.
--
-- Run as ADMIN after 26_agent_ords_security_dynamic_vpd_filter.sql and
-- 28_agent_ords_vector_tag_vpd_setup.sql.
-- This script only creates temporary permission-plane rows in the 458xxx
-- range, checks the generated predicates, and removes those rows.
-- ============================================================
WHENEVER SQLERROR EXIT SQL.SQLCODE
SET ECHO OFF
SET FEEDBACK ON
SET DEFINE OFF
SET SERVEROUTPUT ON
PROMPT === Preparing TAG rule test data ===
BEGIN
cb_agent_ctx_pkg.clear_user;
DELETE FROM cb_permission_rule WHERE perm_id BETWEEN 458000 AND 458999;
DELETE FROM cb_permission WHERE perm_id BETWEEN 458000 AND 458999;
DELETE FROM cb_user_role WHERE role_id BETWEEN 458000 AND 458999;
DELETE FROM cb_app_role WHERE role_id BETWEEN 458000 AND 458999;
DELETE FROM cb_app_user WHERE user_id BETWEEN 458000 AND 458999;
INSERT INTO cb_app_user(user_id, user_name, employee_no, dept_code, can_read_contents, active)
VALUES (458101, 'tag_test_or', 'E458101', 'QA', 'N', 'Y');
INSERT INTO cb_app_user(user_id, user_name, employee_no, dept_code, can_read_contents, active)
VALUES (458102, 'tag_test_deny', 'E458102', 'QA', 'N', 'Y');
INSERT INTO cb_app_user(user_id, user_name, employee_no, dept_code, can_read_contents, active)
VALUES (458103, 'tag_test_none', 'E458103', 'QA', 'N', 'Y');
INSERT INTO cb_app_role(role_id, role_name) VALUES (458101, 'TAG_TEST_OR');
INSERT INTO cb_app_role(role_id, role_name) VALUES (458102, 'TAG_TEST_ALLOW_DENY');
INSERT INTO cb_app_role(role_id, role_name) VALUES (458103, 'TAG_TEST_NONE');
INSERT INTO cb_user_role(user_id, role_id) VALUES (458101, 458101);
INSERT INTO cb_user_role(user_id, role_id) VALUES (458102, 458102);
INSERT INTO cb_user_role(user_id, role_id) VALUES (458103, 458103);
INSERT INTO cb_permission(perm_id, role_id, target_name, action_name, permission_effect)
VALUES (458101, 458101, 'CB_VECTOR_SEARCH_DOCUMENTS', 'SELECT', 'ALLOW');
INSERT INTO cb_permission(perm_id, role_id, target_name, action_name, permission_effect)
VALUES (458102, 458102, 'CB_VECTOR_SEARCH_DOCUMENTS', 'SELECT', 'ALLOW');
INSERT INTO cb_permission(perm_id, role_id, target_name, action_name, permission_effect)
VALUES (458103, 458102, 'CB_VECTOR_SEARCH_DOCUMENTS', 'SELECT', 'DENY');
INSERT INTO cb_permission(perm_id, role_id, target_name, action_name, permission_effect)
VALUES (458104, 458103, 'CB_VECTOR_SEARCH_DOCUMENTS', 'SELECT', 'DENY');
INSERT INTO cb_permission_rule(rule_id, perm_id, rule_column, rule_type, rule_value)
VALUES (458101, 458101, NULL, 'TAG', 'SPRING_BOOT');
INSERT INTO cb_permission_rule(rule_id, perm_id, rule_column, rule_type, rule_value)
VALUES (458102, 458101, NULL, 'TAG', 'ORACLE_VPD');
INSERT INTO cb_permission_rule(rule_id, perm_id, rule_column, rule_type, rule_value)
VALUES (458103, 458102, NULL, 'TAG', 'SPRING_BOOT');
INSERT INTO cb_permission_rule(rule_id, perm_id, rule_column, rule_type, rule_value)
VALUES (458104, 458102, NULL, 'TAG', 'ORACLE_VPD');
INSERT INTO cb_permission_rule(rule_id, perm_id, rule_column, rule_type, rule_value)
VALUES (458105, 458103, NULL, 'TAG', 'ORDS');
INSERT INTO cb_permission_rule(rule_id, perm_id, rule_column, rule_type, rule_value)
VALUES (458106, 458104, NULL, 'TAG', 'SPRING_BOOT');
COMMIT;
END;
/
PROMPT === Checking ALLOW TAG OR semantics and DENY precedence ===
DECLARE
PROCEDURE assert_contains(
p_label IN VARCHAR2,
p_user_id IN NUMBER,
p_expected IN VARCHAR2
) IS
v_actual VARCHAR2(32767);
BEGIN
cb_agent_ctx_pkg.set_user(p_user_id);
v_actual := cb_agent_doc_vpd_filter('ADMIN', 'CB_VECTOR_SEARCH_DOCUMENTS');
IF INSTR(v_actual, p_expected) = 0 THEN
RAISE_APPLICATION_ERROR(-20458,
p_label || ' expected [' || p_expected || '] actual [' || v_actual || ']');
END IF;
DBMS_OUTPUT.PUT_LINE('PASS ' || p_label || ': ' || v_actual);
END;
PROCEDURE assert_equals(
p_label IN VARCHAR2,
p_user_id IN NUMBER,
p_expected IN VARCHAR2
) IS
v_actual VARCHAR2(32767);
BEGIN
cb_agent_ctx_pkg.set_user(p_user_id);
v_actual := cb_agent_doc_vpd_filter('ADMIN', 'CB_VECTOR_SEARCH_DOCUMENTS');
IF v_actual != p_expected THEN
RAISE_APPLICATION_ERROR(-20458,
p_label || ' expected [' || p_expected || '] actual [' || v_actual || ']');
END IF;
DBMS_OUTPUT.PUT_LINE('PASS ' || p_label || ': ' || v_actual);
END;
BEGIN
assert_contains('TWO_ALLOW_TAGS_ARE_OR', 458101, 'OR');
assert_contains('TWO_ALLOW_TAGS_INCLUDE_SPRING', 458101, 'REGEXP_LIKE(UPPER(TECH_TAG), ''(^|,)SPRING_BOOT(,|$)'')');
assert_contains('TWO_ALLOW_TAGS_INCLUDE_VPD', 458101, 'REGEXP_LIKE(UPPER(TECH_TAG), ''(^|,)ORACLE_VPD(,|$)'')');
assert_contains('DENY_TAG_IS_SUBTRACTED', 458102, 'AND NOT');
assert_contains('DENY_TAG_DETAIL', 458102, 'REGEXP_LIKE(UPPER(TECH_TAG), ''(^|,)ORDS(,|$)'')');
assert_equals('DENY_ONLY_FAILS_CLOSED', 458103, '1 = 0');
cb_agent_ctx_pkg.clear_user;
END;
/
PROMPT === Cleaning TAG rule test data ===
BEGIN
cb_agent_ctx_pkg.clear_user;
DELETE FROM cb_permission_rule WHERE perm_id BETWEEN 458000 AND 458999;
DELETE FROM cb_permission WHERE perm_id BETWEEN 458000 AND 458999;
DELETE FROM cb_user_role WHERE role_id BETWEEN 458000 AND 458999;
DELETE FROM cb_app_role WHERE role_id BETWEEN 458000 AND 458999;
DELETE FROM cb_app_user WHERE user_id BETWEEN 458000 AND 458999;
COMMIT;
END;
/
PROMPT === TAG rule checks passed ===
EXIT;

View File

@@ -0,0 +1,58 @@
-- ============================================================
-- 31_agent_ords_backoffice_ux_metadata.sql
-- Upgrade existing backoffice metadata for the screen explanations.
-- Run as ADMIN before deploying this application version.
-- ============================================================
WHENEVER SQLERROR EXIT SQL.SQLCODE
SET ECHO ON
SET FEEDBACK ON
SET DEFINE OFF
BEGIN
EXECUTE IMMEDIATE 'ALTER TABLE cb_protected_object ADD (description VARCHAR2(200))';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -1430 THEN
RAISE;
END IF;
END;
/
BEGIN
EXECUTE IMMEDIATE q'!
CREATE TABLE cb_vpd_policy_note (
object_owner VARCHAR2(128) NOT NULL,
object_name VARCHAR2(128) NOT NULL,
policy_name VARCHAR2(128) NOT NULL,
description VARCHAR2(500) NOT NULL,
updated_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL,
CONSTRAINT cb_vpd_policy_note_pk PRIMARY KEY (object_owner, object_name, policy_name)
)!';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -955 THEN
RAISE;
END IF;
END;
/
BEGIN
EXECUTE IMMEDIATE q'!
CREATE TABLE cb_vpd_filter_note (
function_owner VARCHAR2(128) NOT NULL,
function_name VARCHAR2(128) NOT NULL,
description VARCHAR2(500) NOT NULL,
updated_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL,
CONSTRAINT cb_vpd_filter_note_pk PRIMARY KEY (function_owner, function_name)
)!';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -955 THEN
RAISE;
END IF;
END;
/
COMMIT;
PROMPT === Backoffice UX metadata upgrade complete ===
EXIT;