-- ============================================================
-- 51_kb_vpd_admin_token.sql
--
-- Creates an application-level KB VPD administrator.  This is not an
-- EXEMPT ACCESS POLICY database account: the token is resolved to an
-- application context and the existing VPD policy function returns ALL
-- only because KB_VPD_ADMIN_ROLE has explicit ALL permissions.
-- ============================================================
WHENEVER SQLERROR EXIT SQL.SQLCODE
SET ECHO OFF
SET FEEDBACK ON
SET DEFINE OFF
SET SERVEROUTPUT ON

PROMPT === Creating KB VPD administrator, permissions, and one-time token ===
GRANT EXECUTE ON cb_agent_ctx_pkg TO POC_2;

DECLARE
  c_stakeholder_user_id CONSTANT VARCHAR2(30) := 'KB_VPD_ADMIN';
  c_role_name           CONSTANT VARCHAR2(80) := 'KB_VPD_ADMIN_ROLE';
  c_token_description   CONSTANT VARCHAR2(200) := 'KB VPD admin bootstrap token';

  v_admin_role_id       NUMBER;
  v_identity_role_id    NUMBER;
  v_app_user_id         NUMBER;
  v_permission_id       NUMBER;
  v_bearer_token        VARCHAR2(128);
  v_count               NUMBER;

  PROCEDURE ensure_role(
    p_role_name IN VARCHAR2,
    p_level     IN VARCHAR2,
    p_role_id   OUT NUMBER
  ) IS
  BEGIN
    SELECT role_id
    INTO   p_role_id
    FROM   cb_app_role
    WHERE  role_name = p_role_name;
  EXCEPTION
    WHEN NO_DATA_FOUND THEN
      SELECT NVL(MAX(role_id), 0) + 1
      INTO   p_role_id
      FROM   cb_app_role;
      INSERT INTO cb_app_role(role_id, role_name, max_sensitivity_level)
      VALUES (p_role_id, p_role_name, p_level);
  END;

  PROCEDURE ensure_user_role(p_user_id IN NUMBER, p_role_id IN NUMBER) IS
  BEGIN
    SELECT COUNT(*)
    INTO   v_count
    FROM   cb_user_role
    WHERE  user_id = p_user_id
    AND    role_id = p_role_id;

    IF v_count = 0 THEN
      INSERT INTO cb_user_role(user_id, role_id) VALUES (p_user_id, p_role_id);
    END IF;
  END;

  PROCEDURE ensure_all_permission(p_target_name IN VARCHAR2) IS
  BEGIN
    SELECT MIN(perm_id)
    INTO   v_permission_id
    FROM   cb_permission
    WHERE  role_id = v_admin_role_id
    AND    target_name = p_target_name
    AND    action_name = 'SELECT';

    IF v_permission_id IS NULL THEN
      SELECT cb_permission_seq.NEXTVAL INTO v_permission_id FROM dual;
      INSERT INTO cb_permission(
        perm_id, role_id, target_name, action_name, permission_effect
      ) VALUES (
        v_permission_id, v_admin_role_id, p_target_name, 'SELECT', 'ALLOW'
      );
    ELSE
      UPDATE cb_permission
      SET permission_effect = 'ALLOW'
      WHERE perm_id = v_permission_id;
    END IF;

    DELETE FROM cb_permission_rule WHERE perm_id = v_permission_id;
    INSERT INTO cb_permission_rule(rule_id, perm_id, rule_column, rule_type, rule_value)
    VALUES (cb_permission_rule_seq.NEXTVAL, v_permission_id, NULL, 'ALL', NULL);

    -- CB_ORDS is the non-exempt data runtime.  VPD still applies after
    -- this object privilege is granted.
    EXECUTE IMMEDIATE
      'GRANT SELECT ON POC_2.' || DBMS_ASSERT.SIMPLE_SQL_NAME(p_target_name) || ' TO CB_ORDS';
  END;

  PROCEDURE allow_original_column(
    p_target_name IN VARCHAR2,
    p_column_name IN VARCHAR2
  ) IS
  BEGIN
    SELECT MIN(perm_id)
    INTO   v_permission_id
    FROM   cb_permission
    WHERE  role_id = v_admin_role_id
    AND    target_name = p_target_name
    AND    action_name = 'SELECT';

    SELECT COUNT(*)
    INTO   v_count
    FROM   cb_permission_column
    WHERE  permission_id = v_permission_id
    AND    column_name = p_column_name;

    IF v_count = 0 THEN
      INSERT INTO cb_permission_column(permission_id, column_name)
      VALUES (v_permission_id, p_column_name);
    END IF;
  END;
BEGIN
  MERGE INTO poc_2.kb_stakeholders target
  USING (
    SELECT c_stakeholder_user_id AS user_id,
           'VPD 관리자' AS user_nm,
           '관리자' AS role,
           '관리자채널' AS channel,
           '전체 KB 원장 및 민감 컬럼 접근' AS access_scope,
           '권한 테이블의 KB_VPD_ADMIN_ROLE / ALL 규칙으로 적용' AS rls_filter
    FROM dual
  ) source
  ON (target.user_id = source.user_id)
  WHEN MATCHED THEN UPDATE SET
    target.user_nm = source.user_nm,
    target.role = source.role,
    target.channel = source.channel,
    target.access_scope = source.access_scope,
    target.rls_filter = source.rls_filter
  WHEN NOT MATCHED THEN INSERT (
    user_id, user_nm, role, channel, access_scope, rls_filter
  ) VALUES (
    source.user_id, source.user_nm, source.role, source.channel,
    source.access_scope, source.rls_filter
  );

  BEGIN
    SELECT user_id
    INTO   v_app_user_id
    FROM   cb_app_user
    WHERE  stakeholder_user_id = c_stakeholder_user_id;
  EXCEPTION
    WHEN NO_DATA_FOUND THEN
      SELECT NVL(MAX(user_id), 0) + 1 INTO v_app_user_id FROM cb_app_user;
      INSERT INTO cb_app_user(
        user_id, user_name, employee_no, dept_code,
        stakeholder_user_id, can_read_contents, active
      ) VALUES (
        v_app_user_id, 'VPD 관리자', c_stakeholder_user_id, '관리자채널',
        c_stakeholder_user_id, 'Y', 'Y'
      );
  END;

  UPDATE cb_app_user
  SET user_name = 'VPD 관리자',
      employee_no = c_stakeholder_user_id,
      dept_code = '관리자채널',
      can_read_contents = 'Y',
      active = 'Y'
  WHERE user_id = v_app_user_id;

  ensure_role(c_role_name, 'RESTRICTED', v_admin_role_id);
  ensure_role('KB_STAKEHOLDER_IDENTITY_ROLE', 'INTERNAL', v_identity_role_id);
  ensure_user_role(v_app_user_id, v_admin_role_id);
  ensure_user_role(v_app_user_id, v_identity_role_id);

  FOR object_name IN (
    SELECT column_value
    FROM TABLE(sys.odcivarchar2list(
      'KB_CUSTOMERS', 'KB_PRODUCTS', 'KB_CONTRACTS', 'KB_COVERAGES',
      'KB_CLAIMS', 'KB_EXTERNAL_HOLDINGS', 'KB_STAKEHOLDERS'
    ))
  ) LOOP
    ensure_all_permission(object_name.column_value);
  END LOOP;

  allow_original_column('KB_CUSTOMERS', 'CUST_NM');
  allow_original_column('KB_CUSTOMERS', 'RRN_MASKED');
  allow_original_column('KB_CLAIMS', 'CLAIM_AMT');
  allow_original_column('KB_CLAIMS', 'PAID_AMT');
  allow_original_column('KB_EXTERNAL_HOLDINGS', 'EXT_INSURER');
  allow_original_column('KB_EXTERNAL_HOLDINGS', 'EXT_PRODUCT_GRP');
  allow_original_column('KB_EXTERNAL_HOLDINGS', 'EXT_PRODUCT_TYPE');

  -- Re-running this script rotates only the bootstrap administrator token.
  UPDATE cb_agent_bearer_key
  SET active = 'N',
      revoked_at = SYSDATE
  WHERE stakeholder_user_id = c_stakeholder_user_id
    AND description = c_token_description
    AND active = 'Y';

  v_bearer_token := 'kbvpdadm_' || DBMS_RANDOM.STRING('x', 48);
  INSERT INTO cb_agent_bearer_key(
    key_id, user_id, stakeholder_user_id, key_prefix, key_hash,
    issued_at, expires_at, revoked_at, active, description
  ) VALUES (
    cb_agent_bearer_key_seq.NEXTVAL, v_app_user_id, c_stakeholder_user_id,
    'kbvpdadm', STANDARD_HASH(v_bearer_token, 'SHA256'),
    SYSDATE, ADD_MONTHS(SYSDATE, 12), NULL, 'Y', c_token_description
  );

  COMMIT;

  -- This is the application step that binds the Bearer Token to the trusted
  -- context. DBMS_RLS then invokes CB_AGENT_DOC_VPD_FILTER automatically.
  cb_agent_ctx_pkg.set_user_by_bearer(v_bearer_token);
  DBMS_OUTPUT.PUT_LINE('STAKEHOLDER_USER_ID='
    || SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_USER_ID'));
  DBMS_OUTPUT.PUT_LINE('EFFECTIVE_ROLE=' || c_role_name);
  DBMS_OUTPUT.PUT_LINE('KB_CUSTOMERS_PREDICATE='
    || cb_agent_doc_vpd_filter('POC_2', 'KB_CUSTOMERS'));
  DBMS_OUTPUT.PUT_LINE('KB_CLAIMS_PREDICATE='
    || cb_agent_doc_vpd_filter('POC_2', 'KB_CLAIMS'));
  DBMS_OUTPUT.PUT_LINE('KB_EXTERNAL_HOLDINGS_PREDICATE='
    || cb_agent_doc_vpd_filter('POC_2', 'KB_EXTERNAL_HOLDINGS'));
  cb_agent_ctx_pkg.clear_user;

  -- Show once. The raw token is never stored; only its SHA-256 hash remains.
  DBMS_OUTPUT.PUT_LINE('BEARER_TOKEN=' || v_bearer_token);
END;
/

PROMPT === KB VPD administrator token ready ===
