fix #557: guide permission-driven VPD flow
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 '
|
||||
|
||||
@@ -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
|
||||
|
||||
257
sql/adb/28_agent_ords_vector_tag_vpd_setup.sql
Normal file
257
sql/adb/28_agent_ords_vector_tag_vpd_setup.sql
Normal 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;
|
||||
153
sql/adb/29_agent_ords_vector_search_ords.sql
Normal file
153
sql/adb/29_agent_ords_vector_search_ords.sql
Normal 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;
|
||||
130
sql/adb/30_agent_ords_vector_tag_vpd_test.sql
Normal file
130
sql/adb/30_agent_ords_vector_tag_vpd_test.sql
Normal 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;
|
||||
58
sql/adb/31_agent_ords_backoffice_ux_metadata.sql
Normal file
58
sql/adb/31_agent_ords_backoffice_ux_metadata.sql
Normal 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;
|
||||
Reference in New Issue
Block a user