-- ============================================================
-- 34_dds_token_data_grant_common_auth.sql
-- Token-driven DDS enforcement using the common CB_* permission model.
--
-- This is the product-shaped DDS path:
--   1. One local DDS END USER is used as the technical query identity.
--   2. The application calls CB_AGENT_CTX_PKG.SET_USER_BY_BEARER(token).
--   3. A DATA GRANT is attached to one DDS DATA ROLE on the protected VIEW.
--   4. Its predicate function reads CB_PERMISSION / CB_PERMISSION_RULE,
--      including direct roles and active group-inherited roles.
--
-- The token is not a bind parameter in CREATE DATA GRANT syntax. It is
-- resolved before SELECT and stored in the trusted CB_AGENT_CTX namespace;
-- the DATA GRANT predicate then evaluates the common permission tables.
--
-- Prerequisite: 17_agent_ords_security_local_vpd_setup.sql,
--               25_agent_ords_security_backoffice_support.sql,
--               31_dds_standalone_demo_setup.sql,
--               32_dds_vector_tag_setup.sql
-- Run as ADMIN on an Oracle AI Database release with Deep Data Security.
-- ============================================================
WHENEVER SQLERROR EXIT SQL.SQLCODE
SET ECHO ON
SET FEEDBACK ON
SET DEFINE ON
SET VERIFY OFF

PROMPT === 1. Creating one technical DDS END USER and its DATA ROLE ===
CREATE END USER IF NOT EXISTS "dds_demo_token" IDENTIFIED BY "&DDSUSER_TOKEN_PASSWORD";

BEGIN
  EXECUTE IMMEDIATE 'CREATE ROLE cb_dds_token_connect_role';
EXCEPTION
  WHEN OTHERS THEN
    IF SQLCODE != -1921 THEN
      RAISE;
    END IF;
END;
/
GRANT CREATE SESSION TO cb_dds_token_connect_role;

CREATE DATA ROLE IF NOT EXISTS cb_dds_token_role;
GRANT cb_dds_token_connect_role TO cb_dds_token_role;
GRANT DATA ROLE cb_dds_token_role TO "dds_demo_token";

-- A DDS END USER is not a conventional database user.  Standard object
-- privileges therefore go to a standard role inherited by the DATA ROLE.
GRANT EXECUTE ON admin.cb_agent_ctx_pkg TO cb_dds_token_connect_role;

PROMPT === 2. Creating the common permission predicate function ===
CREATE OR REPLACE FUNCTION admin.cb_dds_vector_tag_allowed(
    p_tech_tag IN VARCHAR2
) RETURN NUMBER
AUTHID DEFINER
AS
  v_user_id NUMBER;
  v_allow   NUMBER := 0;
  v_deny    NUMBER := 0;
BEGIN
  BEGIN
    v_user_id := TO_NUMBER(SYS_CONTEXT('CB_AGENT_CTX', 'USER_ID'));
  EXCEPTION
    WHEN OTHERS THEN
      RETURN 0;
  END;

  IF v_user_id IS NULL OR p_tech_tag IS NULL THEN
    RETURN 0;
  END IF;

  -- Direct roles and roles inherited from active application groups are
  -- deliberately expanded at query time.  CB_* remains the source of truth.
  SELECT COUNT(*)
  INTO   v_allow
  FROM (
    SELECT ur.role_id
    FROM   cb_user_role ur
    WHERE  ur.user_id = v_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 = v_user_id
  ) effective_role
  JOIN cb_app_user u
  ON   u.user_id = v_user_id
  AND  u.active = 'Y'
  JOIN cb_permission p
  ON   p.role_id = effective_role.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'
  AND   p.permission_effect = 'ALLOW'
  AND   (
          r.rule_type = 'ALL'
          OR (
            r.rule_type = 'TAG'
            AND REGEXP_LIKE(
              UPPER(p_tech_tag),
              '(^|,)' || UPPER(TRIM(r.rule_value)) || '(,|$)'
            )
          )
        );

  SELECT COUNT(*)
  INTO   v_deny
  FROM (
    SELECT ur.role_id
    FROM   cb_user_role ur
    WHERE  ur.user_id = v_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 = v_user_id
  ) effective_role
  JOIN cb_permission p
  ON   p.role_id = effective_role.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'
  AND   p.permission_effect = 'DENY'
  AND   r.rule_type = 'TAG'
  AND   REGEXP_LIKE(
         UPPER(p_tech_tag),
         '(^|,)' || UPPER(TRIM(r.rule_value)) || '(,|$)'
       );

  IF v_allow > 0 AND v_deny = 0 THEN
    RETURN 1;
  END IF;
  RETURN 0;
END;
/

SHOW ERRORS

PROMPT === 3. Creating one object-level DATA GRANT ===
CREATE OR REPLACE DATA GRANT admin.dds_demo_token_vector_grant
  AS SELECT
  ON admin.cb_dds_vector_search_documents
  WHERE admin.cb_dds_vector_tag_allowed(tech_tag) = 1
  TO cb_dds_token_role;

PROMPT === 4. Verifying the object-level grant ===
SELECT grant_name, object_name, grantee
FROM   dba_data_grants
WHERE  grant_name = 'DDS_DEMO_TOKEN_VECTOR_GRANT';

PROMPT === Token-driven DDS DATA GRANT setup complete ===
PROMPT Call ADMIN.CB_AGENT_CTX_PKG.SET_USER_BY_BEARER(:token) on the
PROMPT technical connection, then SELECT from ADMIN.CB_DDS_VECTOR_SEARCH_DOCUMENTS.
EXIT;
