244
database/adb/44_dds_mcp_local_end_user_setup.sql
Normal file
244
database/adb/44_dds_mcp_local_end_user_setup.sql
Normal file
@@ -0,0 +1,244 @@
|
||||
-- ============================================================
|
||||
-- 44_dds_mcp_local_end_user_setup.sql
|
||||
--
|
||||
-- Publishes every active CB_APP_USER as a passwordless local DDS END USER
|
||||
-- for the DDS SSE MCP path. The application executes one protected query
|
||||
-- per request with an EndUserSecurityContext; direct DB logon is not enabled.
|
||||
--
|
||||
-- Permission source: CB_APP_USER + direct/group role assignments +
|
||||
-- CB_PERMISSION / CB_PERMISSION_RULE / CB_PERMISSION_COLUMN.
|
||||
-- Current protected target: ADMIN.CB_DDS_VECTOR_SEARCH_DOCUMENTS.
|
||||
--
|
||||
-- Prerequisite: 25_agent_ords_security_backoffice_support.sql,
|
||||
-- 28_agent_ords_vector_tag_vpd_setup.sql,
|
||||
-- 32_dds_vector_tag_setup.sql.
|
||||
-- Run as ADMIN on Oracle AI Database 26ai.
|
||||
-- ============================================================
|
||||
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
||||
SET ECHO OFF
|
||||
SET FEEDBACK ON
|
||||
SET DEFINE OFF
|
||||
|
||||
PROMPT === 1. Persisting the published local END USER mapping ===
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE q'[
|
||||
CREATE TABLE cb_dds_end_user_map (
|
||||
application_user_id NUMBER PRIMARY KEY REFERENCES cb_app_user(user_id),
|
||||
end_user_name VARCHAR2(128) NOT NULL UNIQUE,
|
||||
data_role_name VARCHAR2(128) NOT NULL UNIQUE,
|
||||
lookup_key_ref VARCHAR2(128) NOT NULL,
|
||||
grant_name VARCHAR2(128) NOT NULL,
|
||||
publish_status VARCHAR2(20) NOT NULL
|
||||
CHECK (publish_status IN ('PENDING', 'PUBLISHED', 'REVOKED', 'FAILED')),
|
||||
published_at TIMESTAMP,
|
||||
last_error VARCHAR2(1000)
|
||||
)]';
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
IF SQLCODE <> -955 THEN RAISE; END IF;
|
||||
END;
|
||||
/
|
||||
|
||||
MERGE INTO cb_dds_end_user_map target
|
||||
USING (
|
||||
SELECT user_id AS application_user_id,
|
||||
'DDS_U_' || TO_CHAR(user_id) AS end_user_name,
|
||||
'DDS_U_' || TO_CHAR(user_id) || '_ROLE' AS data_role_name,
|
||||
'DDS_OCI_IAM_CLIENT_SECRET_DERIVED_V1' AS lookup_key_ref,
|
||||
'DDS_MCP_U_' || TO_CHAR(user_id) || '_VECTOR_GRANT' AS grant_name
|
||||
FROM cb_app_user
|
||||
) source
|
||||
ON (target.application_user_id = source.application_user_id)
|
||||
WHEN MATCHED THEN UPDATE SET
|
||||
target.end_user_name = source.end_user_name,
|
||||
target.data_role_name = source.data_role_name,
|
||||
target.lookup_key_ref = source.lookup_key_ref,
|
||||
target.grant_name = source.grant_name
|
||||
WHEN NOT MATCHED THEN INSERT (
|
||||
application_user_id, end_user_name, data_role_name, lookup_key_ref,
|
||||
grant_name, publish_status
|
||||
) VALUES (
|
||||
source.application_user_id, source.end_user_name, source.data_role_name,
|
||||
source.lookup_key_ref, source.grant_name, 'PENDING'
|
||||
);
|
||||
|
||||
PROMPT === 2. Publishing END USER, DATA ROLE, and configured vector grants ===
|
||||
DECLARE
|
||||
v_predicate VARCHAR2(32767);
|
||||
v_excluded_columns VARCHAR2(32767);
|
||||
v_select_clause VARCHAR2(32767);
|
||||
|
||||
PROCEDURE drop_data_grant(p_grant_name IN VARCHAR2) IS
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'DROP DATA GRANT ADMIN.' || p_grant_name;
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN NULL; -- First publish has no generated grant.
|
||||
END;
|
||||
|
||||
PROCEDURE revoke_data_role(p_role_name IN VARCHAR2, p_end_user_name IN VARCHAR2) IS
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'REVOKE DATA ROLE ' || p_role_name || ' FROM "' || p_end_user_name || '"';
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN NULL; -- Revoke is intentionally idempotent in this publisher.
|
||||
END;
|
||||
|
||||
FUNCTION vector_predicate(p_user_id IN NUMBER) RETURN VARCHAR2 IS
|
||||
v_allow VARCHAR2(32767);
|
||||
v_deny VARCHAR2(32767);
|
||||
v_clause VARCHAR2(2000);
|
||||
v_effect VARCHAR2(10);
|
||||
v_type VARCHAR2(30);
|
||||
v_value VARCHAR2(100);
|
||||
|
||||
PROCEDURE append_clause(p_target IN OUT NOCOPY VARCHAR2, p_clause IN VARCHAR2) IS
|
||||
BEGIN
|
||||
IF p_clause IS NULL THEN RETURN; END IF;
|
||||
IF p_target IS NULL THEN p_target := '(' || p_clause || ')';
|
||||
ELSE p_target := p_target || ' OR (' || p_clause || ')'; END IF;
|
||||
END;
|
||||
BEGIN
|
||||
FOR rule IN (
|
||||
WITH effective_role AS (
|
||||
SELECT role_id FROM cb_user_role WHERE user_id = p_user_id
|
||||
UNION
|
||||
SELECT gr.role_id
|
||||
FROM cb_user_group ug
|
||||
JOIN cb_app_group g ON g.group_id = ug.group_id AND g.active_yn = 'Y'
|
||||
JOIN cb_group_role gr ON gr.group_id = ug.group_id
|
||||
WHERE ug.user_id = p_user_id
|
||||
)
|
||||
SELECT NVL(UPPER(TRIM(p.permission_effect)), 'ALLOW') AS permission_effect,
|
||||
UPPER(TRIM(r.rule_type)) AS rule_type,
|
||||
TRIM(r.rule_value) AS rule_value
|
||||
FROM effective_role er
|
||||
JOIN cb_permission p ON p.role_id = er.role_id
|
||||
JOIN cb_permission_rule r ON r.perm_id = p.perm_id
|
||||
WHERE p.target_name = 'CB_VECTOR_SEARCH_DOCUMENTS'
|
||||
AND p.action_name = 'SELECT'
|
||||
ORDER BY p.perm_id, r.rule_id
|
||||
) LOOP
|
||||
v_effect := rule.permission_effect;
|
||||
v_type := rule.rule_type;
|
||||
v_value := rule.rule_value;
|
||||
v_clause := NULL;
|
||||
IF v_type = 'ALL' THEN
|
||||
v_clause := '1 = 1';
|
||||
ELSIF v_type = 'TAG'
|
||||
AND v_value IS NOT NULL
|
||||
AND REGEXP_LIKE(v_value, '^[A-Za-z0-9_-]+$') THEN
|
||||
v_clause := 'REGEXP_LIKE(UPPER(tech_tag), ''(^|,)' || UPPER(v_value) || '(,|$)'')';
|
||||
END IF;
|
||||
IF v_effect = 'DENY' THEN append_clause(v_deny, v_clause);
|
||||
ELSE append_clause(v_allow, v_clause); END IF;
|
||||
END LOOP;
|
||||
IF v_allow IS NULL THEN RETURN NULL; END IF;
|
||||
IF v_deny IS NULL THEN RETURN v_allow; END IF;
|
||||
RETURN '(' || v_allow || ') AND NOT (' || v_deny || ')';
|
||||
END;
|
||||
|
||||
FUNCTION excluded_vector_columns(p_user_id IN NUMBER) RETURN VARCHAR2 IS
|
||||
v_columns VARCHAR2(32767);
|
||||
BEGIN
|
||||
SELECT LISTAGG(column_name, ', ') WITHIN GROUP (ORDER BY column_name)
|
||||
INTO v_columns
|
||||
FROM (
|
||||
SELECT pc.column_name
|
||||
FROM cb_protected_column pc
|
||||
JOIN cb_protected_object po ON po.object_id = pc.object_id
|
||||
WHERE po.object_name = 'CB_VECTOR_SEARCH_DOCUMENTS'
|
||||
AND pc.sensitive_yn = 'Y'
|
||||
-- VECTOR_DISTANCE needs EMBEDDING internally. The MCP response never
|
||||
-- returns it, but it must stay readable by the database engine.
|
||||
AND pc.column_name <> 'EMBEDDING'
|
||||
AND NOT EXISTS (
|
||||
WITH effective_role AS (
|
||||
SELECT role_id FROM cb_user_role WHERE user_id = p_user_id
|
||||
UNION
|
||||
SELECT gr.role_id
|
||||
FROM cb_user_group ug
|
||||
JOIN cb_app_group g ON g.group_id = ug.group_id AND g.active_yn = 'Y'
|
||||
JOIN cb_group_role gr ON gr.group_id = ug.group_id
|
||||
WHERE ug.user_id = p_user_id
|
||||
)
|
||||
SELECT 1
|
||||
FROM effective_role er
|
||||
JOIN cb_permission p ON p.role_id = er.role_id
|
||||
JOIN cb_permission_column allowed ON allowed.permission_id = p.perm_id
|
||||
WHERE p.target_name = 'CB_VECTOR_SEARCH_DOCUMENTS'
|
||||
AND p.action_name = 'SELECT'
|
||||
AND p.permission_effect = 'ALLOW'
|
||||
AND allowed.column_name = pc.column_name
|
||||
)
|
||||
);
|
||||
RETURN v_columns;
|
||||
EXCEPTION
|
||||
WHEN NO_DATA_FOUND THEN RETURN NULL;
|
||||
END;
|
||||
|
||||
BEGIN
|
||||
FOR mapped_user IN (
|
||||
SELECT m.application_user_id, m.end_user_name, m.data_role_name, m.grant_name
|
||||
FROM cb_dds_end_user_map m
|
||||
JOIN cb_app_user u ON u.user_id = m.application_user_id
|
||||
WHERE u.active = 'Y'
|
||||
ORDER BY m.application_user_id
|
||||
) LOOP
|
||||
EXECUTE IMMEDIATE 'CREATE END USER IF NOT EXISTS "' || mapped_user.end_user_name || '"';
|
||||
EXECUTE IMMEDIATE 'CREATE DATA ROLE IF NOT EXISTS ' || mapped_user.data_role_name;
|
||||
EXECUTE IMMEDIATE 'GRANT DATA ROLE ' || mapped_user.data_role_name
|
||||
|| ' TO "' || mapped_user.end_user_name || '"';
|
||||
|
||||
v_predicate := vector_predicate(mapped_user.application_user_id);
|
||||
drop_data_grant(mapped_user.grant_name);
|
||||
IF v_predicate IS NOT NULL THEN
|
||||
v_excluded_columns := excluded_vector_columns(mapped_user.application_user_id);
|
||||
v_select_clause := CASE WHEN v_excluded_columns IS NULL THEN 'AS SELECT'
|
||||
ELSE 'AS SELECT (ALL COLUMNS EXCEPT ' || v_excluded_columns || ')' END;
|
||||
EXECUTE IMMEDIATE 'CREATE DATA GRANT ADMIN.' || mapped_user.grant_name || ' '
|
||||
|| v_select_clause || ' ON ADMIN.CB_DDS_VECTOR_SEARCH_DOCUMENTS WHERE '
|
||||
|| v_predicate || ' TO ' || mapped_user.data_role_name;
|
||||
END IF;
|
||||
UPDATE cb_dds_end_user_map
|
||||
SET publish_status = 'PUBLISHED', published_at = SYSTIMESTAMP, last_error = NULL
|
||||
WHERE application_user_id = mapped_user.application_user_id;
|
||||
END LOOP;
|
||||
|
||||
-- A disabled application user must not retain a usable DDS role/grant.
|
||||
FOR inactive_user IN (
|
||||
SELECT m.application_user_id, m.end_user_name, m.data_role_name, m.grant_name
|
||||
FROM cb_dds_end_user_map m
|
||||
JOIN cb_app_user u ON u.user_id = m.application_user_id
|
||||
WHERE u.active <> 'Y'
|
||||
) LOOP
|
||||
drop_data_grant(inactive_user.grant_name);
|
||||
revoke_data_role(inactive_user.data_role_name, inactive_user.end_user_name);
|
||||
UPDATE cb_dds_end_user_map
|
||||
SET publish_status = 'REVOKED', published_at = SYSTIMESTAMP, last_error = NULL
|
||||
WHERE application_user_id = inactive_user.application_user_id;
|
||||
END LOOP;
|
||||
COMMIT;
|
||||
END;
|
||||
/
|
||||
|
||||
PROMPT === 3. Published DDS MCP identity inventory ===
|
||||
SELECT m.application_user_id,
|
||||
u.user_name,
|
||||
m.end_user_name,
|
||||
m.data_role_name,
|
||||
m.grant_name,
|
||||
m.publish_status
|
||||
FROM cb_dds_end_user_map m
|
||||
JOIN cb_app_user u ON u.user_id = m.application_user_id
|
||||
ORDER BY m.application_user_id;
|
||||
|
||||
SELECT rg.grantee AS end_user_name,
|
||||
rg.data_role,
|
||||
dg.grant_name,
|
||||
dg.object_name
|
||||
FROM dba_data_role_grants rg
|
||||
LEFT JOIN dba_data_grants dg ON dg.grantee = rg.data_role
|
||||
WHERE rg.grantee LIKE 'DDS\_U\_%' ESCAPE '\'
|
||||
ORDER BY rg.grantee, dg.grant_name;
|
||||
|
||||
PROMPT === DDS MCP local END USER publish complete ===
|
||||
EXIT;
|
||||
Reference in New Issue
Block a user