369
sql/adb/17_agent_ords_security_local_vpd_setup.sql
Normal file
369
sql/adb/17_agent_ords_security_local_vpd_setup.sql
Normal file
@@ -0,0 +1,369 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- 17_agent_ords_security_local_vpd_setup.sql
|
||||||
|
-- Local-only executable VPD example for Agent ORDS security.
|
||||||
|
--
|
||||||
|
-- Scenario:
|
||||||
|
-- * ORDS connects as CB_ORDS.
|
||||||
|
-- * ORDS receives Authorization: Bearer <key>.
|
||||||
|
-- * ORDS handler calls ADMIN.CB_AGENT_CTX_PKG.SET_USER_BY_BEARER.
|
||||||
|
-- * VPD reads SYS_CONTEXT and filters ADMIN.CB_V_SEARCH_DOCUMENTS.
|
||||||
|
-- * DBMS_REDACT masks the CONTENTS column unless the key user is allowed.
|
||||||
|
-- ============================================================
|
||||||
|
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
||||||
|
SET ECHO OFF
|
||||||
|
SET FEEDBACK ON
|
||||||
|
SET DEFINE OFF
|
||||||
|
|
||||||
|
PROMPT === 1. Creating local base table and sample documents ===
|
||||||
|
CREATE TABLE cb_search_documents (
|
||||||
|
doc_id NUMBER PRIMARY KEY,
|
||||||
|
title VARCHAR2(100) NOT NULL,
|
||||||
|
owner_emp_no VARCHAR2(20) NOT NULL,
|
||||||
|
dept_code VARCHAR2(20) NOT NULL,
|
||||||
|
contents VARCHAR2(4000),
|
||||||
|
created_at DATE DEFAULT SYSDATE NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO cb_search_documents VALUES (1, 'HR payroll guide', 'E1001', 'HR', 'Payroll policy and HR guide', SYSDATE);
|
||||||
|
INSERT INTO cb_search_documents VALUES (2, 'HR recruiting plan', 'E1002', 'HR', 'Recruiting plan for HR team', SYSDATE);
|
||||||
|
INSERT INTO cb_search_documents VALUES (3, 'Finance close checklist','E2001', 'FIN', 'Monthly close checklist', SYSDATE);
|
||||||
|
INSERT INTO cb_search_documents VALUES (4, 'Finance audit memo', 'E2002', 'FIN', 'Audit memo for finance team', SYSDATE);
|
||||||
|
INSERT INTO cb_search_documents VALUES (5, 'Sales forecast', 'E3001', 'SALES', 'Quarterly sales forecast', SYSDATE);
|
||||||
|
INSERT INTO cb_search_documents VALUES (6, 'HR benefits notice', 'E1003', 'HR', 'Benefits notice for employees', SYSDATE);
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW cb_v_search_documents AS
|
||||||
|
SELECT doc_id,
|
||||||
|
title,
|
||||||
|
owner_emp_no,
|
||||||
|
dept_code,
|
||||||
|
contents,
|
||||||
|
created_at
|
||||||
|
FROM cb_search_documents;
|
||||||
|
|
||||||
|
PROMPT === 2. Creating user/role/permission tables ===
|
||||||
|
CREATE TABLE cb_app_user (
|
||||||
|
user_id NUMBER PRIMARY KEY,
|
||||||
|
user_name VARCHAR2(50) NOT NULL,
|
||||||
|
employee_no VARCHAR2(20) NOT NULL,
|
||||||
|
dept_code VARCHAR2(20) NOT NULL,
|
||||||
|
can_read_contents CHAR(1) DEFAULT 'N' CHECK (can_read_contents IN ('Y','N')) NOT NULL,
|
||||||
|
active CHAR(1) DEFAULT 'Y' CHECK (active IN ('Y','N')) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE cb_app_role (
|
||||||
|
role_id NUMBER PRIMARY KEY,
|
||||||
|
role_name VARCHAR2(50) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE cb_user_role (
|
||||||
|
user_id NUMBER NOT NULL REFERENCES cb_app_user(user_id),
|
||||||
|
role_id NUMBER NOT NULL REFERENCES cb_app_role(role_id),
|
||||||
|
CONSTRAINT cb_user_role_pk PRIMARY KEY (user_id, role_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE cb_permission (
|
||||||
|
perm_id NUMBER PRIMARY KEY,
|
||||||
|
role_id NUMBER NOT NULL REFERENCES cb_app_role(role_id),
|
||||||
|
target_name VARCHAR2(128) NOT NULL,
|
||||||
|
action_name VARCHAR2(30) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE cb_permission_rule (
|
||||||
|
rule_id NUMBER PRIMARY KEY,
|
||||||
|
perm_id NUMBER NOT NULL REFERENCES cb_permission(perm_id),
|
||||||
|
rule_type VARCHAR2(30) NOT NULL,
|
||||||
|
rule_value VARCHAR2(100)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE cb_permission_column (
|
||||||
|
permission_id NUMBER NOT NULL REFERENCES cb_permission(perm_id),
|
||||||
|
column_name VARCHAR2(128) NOT NULL,
|
||||||
|
CONSTRAINT cb_permission_column_pk PRIMARY KEY (permission_id, column_name)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE cb_agent_bearer_key (
|
||||||
|
key_id NUMBER PRIMARY KEY,
|
||||||
|
user_id NUMBER NOT NULL REFERENCES cb_app_user(user_id),
|
||||||
|
key_hash VARCHAR2(128) NOT NULL UNIQUE,
|
||||||
|
key_prefix VARCHAR2(16),
|
||||||
|
issued_at DATE DEFAULT SYSDATE NOT NULL,
|
||||||
|
expires_at DATE,
|
||||||
|
revoked_at DATE,
|
||||||
|
active CHAR(1) DEFAULT 'Y' CHECK (active IN ('Y','N')) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
PROMPT === 3. Seeding permissions ===
|
||||||
|
INSERT INTO cb_app_user VALUES (101, 'agent_hr', 'E10234', 'HR', 'N', 'Y');
|
||||||
|
INSERT INTO cb_app_user VALUES (102, 'agent_fin_self', 'E2001', 'FIN', 'N', 'Y');
|
||||||
|
INSERT INTO cb_app_user VALUES (103, 'agent_all', 'E99999', 'HQ', 'Y', 'Y');
|
||||||
|
|
||||||
|
INSERT INTO cb_app_role VALUES (10, 'HR_DEPT_ROLE');
|
||||||
|
INSERT INTO cb_app_role VALUES (20, 'FIN_SELF_ROLE');
|
||||||
|
INSERT INTO cb_app_role VALUES (30, 'ALL_DOC_ROLE');
|
||||||
|
|
||||||
|
INSERT INTO cb_user_role VALUES (101, 10);
|
||||||
|
INSERT INTO cb_user_role VALUES (102, 20);
|
||||||
|
INSERT INTO cb_user_role VALUES (103, 30);
|
||||||
|
|
||||||
|
INSERT INTO cb_permission VALUES (100, 10, 'CB_V_SEARCH_DOCUMENTS', 'SELECT');
|
||||||
|
INSERT INTO cb_permission VALUES (200, 20, 'CB_V_SEARCH_DOCUMENTS', 'SELECT');
|
||||||
|
INSERT INTO cb_permission VALUES (300, 30, 'CB_V_SEARCH_DOCUMENTS', 'SELECT');
|
||||||
|
|
||||||
|
INSERT INTO cb_permission_rule VALUES (1000, 100, 'MY_DEPT', NULL);
|
||||||
|
INSERT INTO cb_permission_rule VALUES (2000, 200, 'SELF', NULL);
|
||||||
|
INSERT INTO cb_permission_rule VALUES (3000, 300, 'ALL', NULL);
|
||||||
|
|
||||||
|
INSERT INTO cb_permission_column VALUES (300, 'CONTENTS');
|
||||||
|
|
||||||
|
INSERT INTO cb_agent_bearer_key(key_id, user_id, key_hash, key_prefix)
|
||||||
|
VALUES (1, 101, STANDARD_HASH('cb_hr_key', 'SHA256'), 'cb_hr');
|
||||||
|
INSERT INTO cb_agent_bearer_key(key_id, user_id, key_hash, key_prefix)
|
||||||
|
VALUES (2, 102, STANDARD_HASH('cb_fin_key', 'SHA256'), 'cb_fin');
|
||||||
|
INSERT INTO cb_agent_bearer_key(key_id, user_id, key_hash, key_prefix)
|
||||||
|
VALUES (3, 103, STANDARD_HASH('cb_all_key', 'SHA256'), 'cb_all');
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
PROMPT === 4. Creating secure application context package ===
|
||||||
|
CREATE OR REPLACE PACKAGE cb_agent_ctx_pkg AUTHID DEFINER AS
|
||||||
|
PROCEDURE clear_user;
|
||||||
|
PROCEDURE set_user(p_user_id IN NUMBER);
|
||||||
|
PROCEDURE set_user_values(
|
||||||
|
p_user_id IN NUMBER,
|
||||||
|
p_emp_no IN VARCHAR2,
|
||||||
|
p_dept_code IN VARCHAR2,
|
||||||
|
p_can_read_contents IN VARCHAR2
|
||||||
|
);
|
||||||
|
PROCEDURE set_user_by_bearer(p_bearer_key IN VARCHAR2);
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
CREATE OR REPLACE CONTEXT cb_agent_ctx USING cb_agent_ctx_pkg;
|
||||||
|
|
||||||
|
CREATE OR REPLACE PACKAGE BODY cb_agent_ctx_pkg AS
|
||||||
|
PROCEDURE clear_user AS
|
||||||
|
BEGIN
|
||||||
|
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'USER_ID', NULL);
|
||||||
|
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'EMP_NO', NULL);
|
||||||
|
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'DEPT_CODE', NULL);
|
||||||
|
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'CAN_READ_CONTENTS', NULL);
|
||||||
|
END;
|
||||||
|
|
||||||
|
PROCEDURE set_user_values(
|
||||||
|
p_user_id IN NUMBER,
|
||||||
|
p_emp_no IN VARCHAR2,
|
||||||
|
p_dept_code IN VARCHAR2,
|
||||||
|
p_can_read_contents IN VARCHAR2
|
||||||
|
) AS
|
||||||
|
BEGIN
|
||||||
|
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'USER_ID', TO_CHAR(p_user_id));
|
||||||
|
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'EMP_NO', p_emp_no);
|
||||||
|
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'DEPT_CODE', p_dept_code);
|
||||||
|
DBMS_SESSION.SET_CONTEXT('CB_AGENT_CTX', 'CAN_READ_CONTENTS', p_can_read_contents);
|
||||||
|
END;
|
||||||
|
|
||||||
|
PROCEDURE set_user(p_user_id IN NUMBER) AS
|
||||||
|
v_emp_no cb_app_user.employee_no%TYPE;
|
||||||
|
v_dept_code cb_app_user.dept_code%TYPE;
|
||||||
|
v_can_read_contents cb_app_user.can_read_contents%TYPE;
|
||||||
|
BEGIN
|
||||||
|
SELECT employee_no,
|
||||||
|
dept_code,
|
||||||
|
CASE
|
||||||
|
WHEN EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM cb_user_role ur
|
||||||
|
JOIN cb_permission p
|
||||||
|
ON p.role_id = ur.role_id
|
||||||
|
JOIN cb_permission_column pc
|
||||||
|
ON pc.permission_id = p.perm_id
|
||||||
|
WHERE ur.user_id = p_user_id
|
||||||
|
AND p.target_name = 'CB_V_SEARCH_DOCUMENTS'
|
||||||
|
AND p.action_name = 'SELECT'
|
||||||
|
AND pc.column_name = 'CONTENTS'
|
||||||
|
) THEN 'Y'
|
||||||
|
ELSE 'N'
|
||||||
|
END
|
||||||
|
INTO v_emp_no, v_dept_code, v_can_read_contents
|
||||||
|
FROM cb_app_user
|
||||||
|
WHERE user_id = p_user_id
|
||||||
|
AND active = 'Y';
|
||||||
|
|
||||||
|
set_user_values(p_user_id, v_emp_no, v_dept_code, v_can_read_contents);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN NO_DATA_FOUND THEN
|
||||||
|
clear_user;
|
||||||
|
RAISE_APPLICATION_ERROR(-20003, 'Mapped application user not found');
|
||||||
|
END;
|
||||||
|
|
||||||
|
PROCEDURE set_user_by_bearer(p_bearer_key IN VARCHAR2) AS
|
||||||
|
v_user_id cb_app_user.user_id%TYPE;
|
||||||
|
v_emp_no cb_app_user.employee_no%TYPE;
|
||||||
|
v_dept_code cb_app_user.dept_code%TYPE;
|
||||||
|
v_can_read_contents cb_app_user.can_read_contents%TYPE;
|
||||||
|
BEGIN
|
||||||
|
IF p_bearer_key IS NULL THEN
|
||||||
|
clear_user;
|
||||||
|
RAISE_APPLICATION_ERROR(-20001, 'Authorization Bearer key is required');
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT u.user_id,
|
||||||
|
u.employee_no,
|
||||||
|
u.dept_code,
|
||||||
|
CASE
|
||||||
|
WHEN EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM cb_user_role ur
|
||||||
|
JOIN cb_permission p
|
||||||
|
ON p.role_id = ur.role_id
|
||||||
|
JOIN cb_permission_column pc
|
||||||
|
ON pc.permission_id = p.perm_id
|
||||||
|
WHERE ur.user_id = u.user_id
|
||||||
|
AND p.target_name = 'CB_V_SEARCH_DOCUMENTS'
|
||||||
|
AND p.action_name = 'SELECT'
|
||||||
|
AND pc.column_name = 'CONTENTS'
|
||||||
|
) THEN 'Y'
|
||||||
|
ELSE 'N'
|
||||||
|
END
|
||||||
|
INTO v_user_id, v_emp_no, v_dept_code, v_can_read_contents
|
||||||
|
FROM cb_agent_bearer_key k
|
||||||
|
JOIN cb_app_user u
|
||||||
|
ON u.user_id = k.user_id
|
||||||
|
WHERE k.key_hash = STANDARD_HASH(p_bearer_key, 'SHA256')
|
||||||
|
AND k.active = 'Y'
|
||||||
|
AND k.revoked_at IS NULL
|
||||||
|
AND (k.expires_at IS NULL OR k.expires_at > SYSDATE)
|
||||||
|
AND u.active = 'Y';
|
||||||
|
|
||||||
|
set_user_values(v_user_id, v_emp_no, v_dept_code, v_can_read_contents);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN NO_DATA_FOUND THEN
|
||||||
|
clear_user;
|
||||||
|
RAISE_APPLICATION_ERROR(-20002, 'Invalid or expired Bearer key');
|
||||||
|
END;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
SHOW ERRORS
|
||||||
|
|
||||||
|
PROMPT === 5. Creating VPD policy function ===
|
||||||
|
CREATE OR REPLACE FUNCTION cb_agent_doc_vpd_filter(
|
||||||
|
p_schema IN VARCHAR2,
|
||||||
|
p_object IN VARCHAR2
|
||||||
|
) RETURN VARCHAR2
|
||||||
|
AUTHID DEFINER
|
||||||
|
AS
|
||||||
|
v_user_id VARCHAR2(30);
|
||||||
|
v_target_name VARCHAR2(128);
|
||||||
|
BEGIN
|
||||||
|
v_user_id := SYS_CONTEXT('CB_AGENT_CTX', 'USER_ID');
|
||||||
|
|
||||||
|
IF v_user_id IS NULL THEN
|
||||||
|
RETURN '1 = 0';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
v_target_name := REPLACE(UPPER(p_object), '''', '''''');
|
||||||
|
|
||||||
|
RETURN
|
||||||
|
'EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM admin.cb_user_role ur
|
||||||
|
JOIN admin.cb_permission p
|
||||||
|
ON p.role_id = ur.role_id
|
||||||
|
JOIN admin.cb_permission_rule r
|
||||||
|
ON r.perm_id = p.perm_id
|
||||||
|
WHERE ur.user_id = TO_NUMBER(SYS_CONTEXT(''CB_AGENT_CTX'', ''USER_ID''))
|
||||||
|
AND p.target_name = ''' || v_target_name || '''
|
||||||
|
AND p.action_name = ''SELECT''
|
||||||
|
AND (
|
||||||
|
r.rule_type = ''ALL''
|
||||||
|
OR (
|
||||||
|
r.rule_type = ''MY_DEPT''
|
||||||
|
AND dept_code = SYS_CONTEXT(''CB_AGENT_CTX'', ''DEPT_CODE'')
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
r.rule_type = ''DEPT''
|
||||||
|
AND r.rule_value = dept_code
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
r.rule_type = ''SELF''
|
||||||
|
AND owner_emp_no = SYS_CONTEXT(''CB_AGENT_CTX'', ''EMP_NO'')
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
r.rule_type = ''EMP_NO''
|
||||||
|
AND r.rule_value = owner_emp_no
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)';
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
SHOW ERRORS
|
||||||
|
|
||||||
|
PROMPT === 6. Attaching VPD policy to the protected view ===
|
||||||
|
BEGIN
|
||||||
|
DBMS_RLS.ADD_POLICY(
|
||||||
|
object_schema => USER,
|
||||||
|
object_name => 'CB_V_SEARCH_DOCUMENTS',
|
||||||
|
policy_name => 'CB_AGENT_DOC_POLICY',
|
||||||
|
function_schema => USER,
|
||||||
|
policy_function => 'CB_AGENT_DOC_VPD_FILTER',
|
||||||
|
statement_types => 'SELECT',
|
||||||
|
enable => TRUE
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
PROMPT === 7. Adding column redaction on CONTENTS ===
|
||||||
|
CREATE OR REPLACE FUNCTION cb_agent_can_read_column(
|
||||||
|
p_target_name IN VARCHAR2,
|
||||||
|
p_column_name IN VARCHAR2
|
||||||
|
) RETURN VARCHAR2
|
||||||
|
AUTHID DEFINER
|
||||||
|
AS
|
||||||
|
v_allowed NUMBER;
|
||||||
|
BEGIN
|
||||||
|
IF SYS_CONTEXT('CB_AGENT_CTX', 'USER_ID') IS NULL THEN
|
||||||
|
RETURN 'N';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT COUNT(*)
|
||||||
|
INTO v_allowed
|
||||||
|
FROM cb_user_role ur
|
||||||
|
JOIN cb_permission p
|
||||||
|
ON p.role_id = ur.role_id
|
||||||
|
JOIN cb_permission_column pc
|
||||||
|
ON pc.permission_id = p.perm_id
|
||||||
|
WHERE ur.user_id = TO_NUMBER(SYS_CONTEXT('CB_AGENT_CTX', 'USER_ID'))
|
||||||
|
AND p.target_name = UPPER(p_target_name)
|
||||||
|
AND p.action_name = 'SELECT'
|
||||||
|
AND pc.column_name = UPPER(p_column_name);
|
||||||
|
|
||||||
|
RETURN CASE WHEN v_allowed > 0 THEN 'Y' ELSE 'N' END;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
SHOW ERRORS
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
DBMS_REDACT.ADD_POLICY(
|
||||||
|
object_schema => USER,
|
||||||
|
object_name => 'CB_V_SEARCH_DOCUMENTS',
|
||||||
|
column_name => 'CONTENTS',
|
||||||
|
policy_name => 'CB_CONTENTS_REDACT',
|
||||||
|
function_type => DBMS_REDACT.NULLIFY,
|
||||||
|
expression => 'SYS_CONTEXT(''CB_AGENT_CTX'', ''CAN_READ_CONTENTS'') IS NULL OR SYS_CONTEXT(''CB_AGENT_CTX'', ''CAN_READ_CONTENTS'') != ''Y'''
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
PROMPT === 8. Creating ORDS runtime schema ===
|
||||||
|
CREATE USER cb_ords IDENTIFIED BY "CbOrdS#2026Local1";
|
||||||
|
GRANT CREATE SESSION TO cb_ords;
|
||||||
|
GRANT CREATE PROCEDURE TO cb_ords;
|
||||||
|
GRANT SELECT ON cb_v_search_documents TO cb_ords;
|
||||||
|
GRANT EXECUTE ON cb_agent_ctx_pkg TO cb_ords;
|
||||||
|
GRANT EXECUTE ON cb_agent_can_read_column TO cb_ords;
|
||||||
|
|
||||||
|
PROMPT === Local VPD setup complete ===
|
||||||
|
EXIT;
|
||||||
256
sql/adb/22_agent_ords_security_ords_handler_setup.sql
Normal file
256
sql/adb/22_agent_ords_security_ords_handler_setup.sql
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- 22_agent_ords_security_ords_handler_setup.sql
|
||||||
|
-- ORDS module/handler package for Agent ORDS security.
|
||||||
|
--
|
||||||
|
-- Run as CB_ORDS after:
|
||||||
|
-- * ADMIN VPD setup
|
||||||
|
-- * ADMIN DDS setup
|
||||||
|
-- * ADMIN ORDS schema enablement
|
||||||
|
--
|
||||||
|
-- Purpose:
|
||||||
|
-- * VPD path: Authorization header -> Bearer key -> SYS_CONTEXT -> VPD.
|
||||||
|
-- * DDS probe: Authorization header -> local mapping only. This does not
|
||||||
|
-- attach a DDS EndUserSecurityContext, so DDS cannot treat the request
|
||||||
|
-- as the mapped END USER.
|
||||||
|
-- ============================================================
|
||||||
|
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
||||||
|
SET ECHO OFF
|
||||||
|
SET FEEDBACK ON
|
||||||
|
|
||||||
|
PROMPT === Creating CB_ORDS handler package ===
|
||||||
|
CREATE OR REPLACE PACKAGE cb_ords_handler_pkg AUTHID DEFINER AS
|
||||||
|
FUNCTION vpd_search_json(p_authorization IN VARCHAR2) RETURN CLOB;
|
||||||
|
FUNCTION dds_bearer_probe_json(p_authorization IN VARCHAR2) RETURN CLOB;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
CREATE OR REPLACE PACKAGE BODY cb_ords_handler_pkg AS
|
||||||
|
FUNCTION extract_bearer_key(p_authorization IN VARCHAR2) RETURN VARCHAR2 AS
|
||||||
|
v_auth VARCHAR2(4000);
|
||||||
|
BEGIN
|
||||||
|
v_auth := TRIM(p_authorization);
|
||||||
|
|
||||||
|
IF v_auth IS NULL OR LOWER(SUBSTR(v_auth, 1, 7)) != 'bearer ' THEN
|
||||||
|
RAISE_APPLICATION_ERROR(-20101, 'Authorization header must be Bearer <key>');
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN TRIM(SUBSTR(v_auth, 8));
|
||||||
|
END;
|
||||||
|
|
||||||
|
FUNCTION mapped_dds_end_user(p_bearer_key IN VARCHAR2) RETURN VARCHAR2 AS
|
||||||
|
BEGIN
|
||||||
|
CASE p_bearer_key
|
||||||
|
WHEN 'cb_hr_key' THEN RETURN 'cb_dds_hr';
|
||||||
|
WHEN 'cb_fin_key' THEN RETURN 'cb_dds_fin';
|
||||||
|
WHEN 'cb_all_key' THEN RETURN 'cb_dds_all';
|
||||||
|
ELSE RETURN NULL;
|
||||||
|
END CASE;
|
||||||
|
END;
|
||||||
|
|
||||||
|
FUNCTION vpd_search_json(p_authorization IN VARCHAR2) RETURN CLOB AS
|
||||||
|
v_bearer_key VARCHAR2(4000);
|
||||||
|
v_rows_json CLOB;
|
||||||
|
v_response CLOB;
|
||||||
|
v_context_read_contents VARCHAR2(1);
|
||||||
|
BEGIN
|
||||||
|
admin.cb_agent_ctx_pkg.clear_user;
|
||||||
|
v_bearer_key := extract_bearer_key(p_authorization);
|
||||||
|
admin.cb_agent_ctx_pkg.set_user_by_bearer(v_bearer_key);
|
||||||
|
|
||||||
|
SELECT JSON_ARRAYAGG(
|
||||||
|
JSON_OBJECT(
|
||||||
|
'doc_id' VALUE doc_id,
|
||||||
|
'title' VALUE title,
|
||||||
|
'owner_emp_no' VALUE owner_emp_no,
|
||||||
|
'dept_code' VALUE dept_code,
|
||||||
|
'contents' VALUE contents
|
||||||
|
RETURNING CLOB
|
||||||
|
)
|
||||||
|
ORDER BY doc_id
|
||||||
|
RETURNING CLOB
|
||||||
|
)
|
||||||
|
INTO v_rows_json
|
||||||
|
FROM admin.cb_v_search_documents;
|
||||||
|
|
||||||
|
IF v_rows_json IS NULL THEN
|
||||||
|
v_rows_json := '[]';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
v_context_read_contents := admin.cb_agent_can_read_column('CB_V_SEARCH_DOCUMENTS', 'CONTENTS');
|
||||||
|
|
||||||
|
SELECT JSON_OBJECT(
|
||||||
|
'scenario' VALUE 'VPD_BEARER_HEADER',
|
||||||
|
'db_user' VALUE SYS_CONTEXT('USERENV', 'SESSION_USER'),
|
||||||
|
'context_user_id' VALUE SYS_CONTEXT('CB_AGENT_CTX', 'USER_ID'),
|
||||||
|
'context_emp_no' VALUE SYS_CONTEXT('CB_AGENT_CTX', 'EMP_NO'),
|
||||||
|
'context_dept_code' VALUE SYS_CONTEXT('CB_AGENT_CTX', 'DEPT_CODE'),
|
||||||
|
'context_read_contents' VALUE v_context_read_contents,
|
||||||
|
'rows' VALUE v_rows_json FORMAT JSON
|
||||||
|
RETURNING CLOB
|
||||||
|
)
|
||||||
|
INTO v_response
|
||||||
|
FROM dual;
|
||||||
|
|
||||||
|
admin.cb_agent_ctx_pkg.clear_user;
|
||||||
|
RETURN v_response;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN
|
||||||
|
admin.cb_agent_ctx_pkg.clear_user;
|
||||||
|
RAISE;
|
||||||
|
END;
|
||||||
|
|
||||||
|
FUNCTION dds_bearer_probe_json(p_authorization IN VARCHAR2) RETURN CLOB AS
|
||||||
|
v_bearer_key VARCHAR2(4000);
|
||||||
|
v_mapped_end_user VARCHAR2(128);
|
||||||
|
v_dds_context_username VARCHAR2(128);
|
||||||
|
v_dds_context_error VARCHAR2(4000);
|
||||||
|
v_rows_visible NUMBER;
|
||||||
|
v_query_error VARCHAR2(4000);
|
||||||
|
v_result VARCHAR2(40);
|
||||||
|
v_response CLOB;
|
||||||
|
BEGIN
|
||||||
|
v_bearer_key := extract_bearer_key(p_authorization);
|
||||||
|
v_mapped_end_user := mapped_dds_end_user(v_bearer_key);
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
SELECT ORA_END_USER_CONTEXT.username
|
||||||
|
INTO v_dds_context_username
|
||||||
|
FROM dual;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN
|
||||||
|
v_dds_context_error := SQLERRM;
|
||||||
|
END;
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
EXECUTE IMMEDIATE
|
||||||
|
'SELECT COUNT(*) FROM admin.cb_dds_v_search_documents'
|
||||||
|
INTO v_rows_visible;
|
||||||
|
|
||||||
|
v_result := 'UNEXPECTED_ACCESS';
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN
|
||||||
|
v_query_error := SQLERRM;
|
||||||
|
v_result := 'EXPECTED_BLOCKED';
|
||||||
|
END;
|
||||||
|
|
||||||
|
SELECT JSON_OBJECT(
|
||||||
|
'scenario' VALUE 'DDS_BEARER_HANDLER_PROBE',
|
||||||
|
'db_user' VALUE SYS_CONTEXT('USERENV', 'SESSION_USER'),
|
||||||
|
'bearer_mapped_end_user' VALUE v_mapped_end_user,
|
||||||
|
'dds_context_username' VALUE v_dds_context_username,
|
||||||
|
'dds_context_error' VALUE v_dds_context_error,
|
||||||
|
'rows_visible' VALUE v_rows_visible,
|
||||||
|
'query_error' VALUE v_query_error,
|
||||||
|
'result' VALUE v_result
|
||||||
|
RETURNING CLOB
|
||||||
|
)
|
||||||
|
INTO v_response
|
||||||
|
FROM dual;
|
||||||
|
|
||||||
|
RETURN v_response;
|
||||||
|
END;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
SHOW ERRORS
|
||||||
|
|
||||||
|
PROMPT === Defining ORDS module and handlers ===
|
||||||
|
BEGIN
|
||||||
|
ORDS.DELETE_MODULE(p_module_name => 'cb.agent.security');
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN NULL;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
ORDS.DEFINE_MODULE(
|
||||||
|
p_module_name => 'cb.agent.security',
|
||||||
|
p_base_path => 'cb-agent-security/',
|
||||||
|
p_items_per_page => 25,
|
||||||
|
p_status => 'PUBLISHED'
|
||||||
|
);
|
||||||
|
|
||||||
|
ORDS.DEFINE_TEMPLATE(
|
||||||
|
p_module_name => 'cb.agent.security',
|
||||||
|
p_pattern => 'vpd/documents'
|
||||||
|
);
|
||||||
|
|
||||||
|
ORDS.DEFINE_HANDLER(
|
||||||
|
p_module_name => 'cb.agent.security',
|
||||||
|
p_pattern => 'vpd/documents',
|
||||||
|
p_method => 'POST',
|
||||||
|
p_source_type => ORDS.source_type_plsql,
|
||||||
|
p_source => q'[
|
||||||
|
DECLARE
|
||||||
|
v_response CLOB;
|
||||||
|
BEGIN
|
||||||
|
v_response := cb_ords_handler_pkg.vpd_search_json(:auth_header);
|
||||||
|
:status_code := 200;
|
||||||
|
OWA_UTIL.MIME_HEADER('application/json', TRUE);
|
||||||
|
HTP.P(v_response);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN
|
||||||
|
:status_code := 403;
|
||||||
|
OWA_UTIL.MIME_HEADER('application/json', TRUE);
|
||||||
|
HTP.P('{"error":"' || REPLACE(SQLERRM, '"', '\"') || '"}');
|
||||||
|
END;
|
||||||
|
]',
|
||||||
|
p_items_per_page => 0
|
||||||
|
);
|
||||||
|
|
||||||
|
ORDS.DEFINE_PARAMETER(
|
||||||
|
p_module_name => 'cb.agent.security',
|
||||||
|
p_pattern => 'vpd/documents',
|
||||||
|
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_TEMPLATE(
|
||||||
|
p_module_name => 'cb.agent.security',
|
||||||
|
p_pattern => 'dds/bearer-probe'
|
||||||
|
);
|
||||||
|
|
||||||
|
ORDS.DEFINE_HANDLER(
|
||||||
|
p_module_name => 'cb.agent.security',
|
||||||
|
p_pattern => 'dds/bearer-probe',
|
||||||
|
p_method => 'POST',
|
||||||
|
p_source_type => ORDS.source_type_plsql,
|
||||||
|
p_source => q'[
|
||||||
|
DECLARE
|
||||||
|
v_response CLOB;
|
||||||
|
BEGIN
|
||||||
|
v_response := cb_ords_handler_pkg.dds_bearer_probe_json(:auth_header);
|
||||||
|
:status_code := 200;
|
||||||
|
OWA_UTIL.MIME_HEADER('application/json', TRUE);
|
||||||
|
HTP.P(v_response);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN
|
||||||
|
:status_code := 403;
|
||||||
|
OWA_UTIL.MIME_HEADER('application/json', TRUE);
|
||||||
|
HTP.P('{"error":"' || REPLACE(SQLERRM, '"', '\"') || '"}');
|
||||||
|
END;
|
||||||
|
]',
|
||||||
|
p_items_per_page => 0
|
||||||
|
);
|
||||||
|
|
||||||
|
ORDS.DEFINE_PARAMETER(
|
||||||
|
p_module_name => 'cb.agent.security',
|
||||||
|
p_pattern => 'dds/bearer-probe',
|
||||||
|
p_method => 'POST',
|
||||||
|
p_name => 'Authorization',
|
||||||
|
p_bind_variable_name => 'auth_header',
|
||||||
|
p_source_type => 'HEADER',
|
||||||
|
p_param_type => 'STRING',
|
||||||
|
p_access_method => 'IN'
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
PROMPT === ORDS handler setup complete ===
|
||||||
|
EXIT;
|
||||||
@@ -137,6 +137,68 @@ WHEN MATCHED THEN UPDATE SET dst.sensitive_yn = src.sensitive_yn
|
|||||||
WHEN NOT MATCHED THEN INSERT (column_id, object_id, column_name, sensitive_yn)
|
WHEN NOT MATCHED THEN INSERT (column_id, object_id, column_name, sensitive_yn)
|
||||||
VALUES (src.column_id, src.object_id, src.column_name, src.sensitive_yn);
|
VALUES (src.column_id, src.object_id, src.column_name, src.sensitive_yn);
|
||||||
|
|
||||||
|
MERGE INTO cb_permission_column dst
|
||||||
|
USING (
|
||||||
|
SELECT 300 permission_id, 'CONTENTS' column_name FROM dual
|
||||||
|
) src
|
||||||
|
ON (dst.permission_id = src.permission_id AND dst.column_name = src.column_name)
|
||||||
|
WHEN NOT MATCHED THEN INSERT (permission_id, column_name)
|
||||||
|
VALUES (src.permission_id, src.column_name);
|
||||||
|
|
||||||
|
PROMPT === Replacing CONTENTS redaction with role column permission check ===
|
||||||
|
CREATE OR REPLACE FUNCTION cb_agent_can_read_column(
|
||||||
|
p_target_name IN VARCHAR2,
|
||||||
|
p_column_name IN VARCHAR2
|
||||||
|
) RETURN VARCHAR2
|
||||||
|
AUTHID DEFINER
|
||||||
|
AS
|
||||||
|
v_allowed NUMBER;
|
||||||
|
BEGIN
|
||||||
|
IF SYS_CONTEXT('CB_AGENT_CTX', 'USER_ID') IS NULL THEN
|
||||||
|
RETURN 'N';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT COUNT(*)
|
||||||
|
INTO v_allowed
|
||||||
|
FROM cb_user_role ur
|
||||||
|
JOIN cb_permission p
|
||||||
|
ON p.role_id = ur.role_id
|
||||||
|
JOIN cb_permission_column pc
|
||||||
|
ON pc.permission_id = p.perm_id
|
||||||
|
WHERE ur.user_id = TO_NUMBER(SYS_CONTEXT('CB_AGENT_CTX', 'USER_ID'))
|
||||||
|
AND p.target_name = UPPER(p_target_name)
|
||||||
|
AND p.action_name = 'SELECT'
|
||||||
|
AND pc.column_name = UPPER(p_column_name);
|
||||||
|
|
||||||
|
RETURN CASE WHEN v_allowed > 0 THEN 'Y' ELSE 'N' END;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
DBMS_REDACT.DROP_POLICY(
|
||||||
|
object_schema => USER,
|
||||||
|
object_name => 'CB_V_SEARCH_DOCUMENTS',
|
||||||
|
policy_name => 'CB_CONTENTS_REDACT'
|
||||||
|
);
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN NULL;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
DBMS_REDACT.ADD_POLICY(
|
||||||
|
object_schema => USER,
|
||||||
|
object_name => 'CB_V_SEARCH_DOCUMENTS',
|
||||||
|
column_name => 'CONTENTS',
|
||||||
|
policy_name => 'CB_CONTENTS_REDACT',
|
||||||
|
function_type => DBMS_REDACT.NULLIFY,
|
||||||
|
expression => 'SYS_CONTEXT(''CB_AGENT_CTX'', ''CAN_READ_CONTENTS'') IS NULL OR SYS_CONTEXT(''CB_AGENT_CTX'', ''CAN_READ_CONTENTS'') != ''Y'''
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
GRANT EXECUTE ON cb_agent_can_read_column TO cb_ords;
|
||||||
|
|
||||||
COMMIT;
|
COMMIT;
|
||||||
|
|
||||||
PROMPT === Backoffice support setup complete ===
|
PROMPT === Backoffice support setup complete ===
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public record PermissionView(
|
|||||||
long objectId,
|
long objectId,
|
||||||
String objectName,
|
String objectName,
|
||||||
String action,
|
String action,
|
||||||
String rules
|
String rules,
|
||||||
|
String visibleColumns
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import jakarta.validation.constraints.NotBlank;
|
|||||||
public record UserCreateCommand(
|
public record UserCreateCommand(
|
||||||
@NotBlank String username,
|
@NotBlank String username,
|
||||||
@NotBlank String empNo,
|
@NotBlank String empNo,
|
||||||
@NotBlank String deptCode,
|
@NotBlank String deptCode
|
||||||
boolean canReadContents
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.cloudhandson.vpdbackoffice.domain.permission.PermissionSetCommand;
|
|||||||
import com.cloudhandson.vpdbackoffice.domain.permission.RuleCommand;
|
import com.cloudhandson.vpdbackoffice.domain.permission.RuleCommand;
|
||||||
import com.cloudhandson.vpdbackoffice.service.PermissionService;
|
import com.cloudhandson.vpdbackoffice.service.PermissionService;
|
||||||
import com.cloudhandson.vpdbackoffice.service.ProtectedObjectService;
|
import com.cloudhandson.vpdbackoffice.service.ProtectedObjectService;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
@@ -40,7 +41,7 @@ public class PermissionController {
|
|||||||
@RequestParam long objectId,
|
@RequestParam long objectId,
|
||||||
@RequestParam String ruleType,
|
@RequestParam String ruleType,
|
||||||
@RequestParam(required = false) String ruleValue,
|
@RequestParam(required = false) String ruleValue,
|
||||||
@RequestParam(required = false) List<String> visibleColumns,
|
@RequestParam(required = false) String visibleColumns,
|
||||||
RedirectAttributes redirectAttributes
|
RedirectAttributes redirectAttributes
|
||||||
) {
|
) {
|
||||||
permissionService.savePermissionSet(new PermissionSetCommand(
|
permissionService.savePermissionSet(new PermissionSetCommand(
|
||||||
@@ -48,12 +49,22 @@ public class PermissionController {
|
|||||||
objectId,
|
objectId,
|
||||||
"SELECT",
|
"SELECT",
|
||||||
List.of(new RuleCommand(ruleType, ruleValue)),
|
List.of(new RuleCommand(ruleType, ruleValue)),
|
||||||
visibleColumns == null ? List.of() : visibleColumns
|
splitColumns(visibleColumns)
|
||||||
));
|
));
|
||||||
redirectAttributes.addFlashAttribute("message", "권한을 저장했습니다.");
|
redirectAttributes.addFlashAttribute("message", "권한을 저장했습니다.");
|
||||||
return "redirect:/permissions";
|
return "redirect:/permissions";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<String> splitColumns(String visibleColumns) {
|
||||||
|
if (visibleColumns == null || visibleColumns.isBlank()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
return Arrays.stream(visibleColumns.split(","))
|
||||||
|
.map(String::trim)
|
||||||
|
.filter(value -> !value.isBlank())
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/permissions/delete")
|
@PostMapping("/permissions/delete")
|
||||||
public String delete(@RequestParam long permissionId, RedirectAttributes redirectAttributes) {
|
public String delete(@RequestParam long permissionId, RedirectAttributes redirectAttributes) {
|
||||||
permissionService.deletePermission(permissionId);
|
permissionService.deletePermission(permissionId);
|
||||||
|
|||||||
@@ -34,10 +34,9 @@ public class UserController {
|
|||||||
@RequestParam String username,
|
@RequestParam String username,
|
||||||
@RequestParam String empNo,
|
@RequestParam String empNo,
|
||||||
@RequestParam String deptCode,
|
@RequestParam String deptCode,
|
||||||
@RequestParam(defaultValue = "false") boolean canReadContents,
|
|
||||||
RedirectAttributes redirectAttributes
|
RedirectAttributes redirectAttributes
|
||||||
) {
|
) {
|
||||||
userService.createUser(new UserCreateCommand(username, empNo, deptCode, canReadContents));
|
userService.createUser(new UserCreateCommand(username, empNo, deptCode));
|
||||||
redirectAttributes.addFlashAttribute("message", "사용자를 추가했습니다.");
|
redirectAttributes.addFlashAttribute("message", "사용자를 추가했습니다.");
|
||||||
return "redirect:/users";
|
return "redirect:/users";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,12 @@
|
|||||||
p.target_name AS object_name,
|
p.target_name AS object_name,
|
||||||
p.action_name AS action,
|
p.action_name AS action,
|
||||||
LISTAGG(pr.rule_type || NVL2(pr.rule_value, ':' || pr.rule_value, ''), ', ')
|
LISTAGG(pr.rule_type || NVL2(pr.rule_value, ':' || pr.rule_value, ''), ', ')
|
||||||
WITHIN GROUP (ORDER BY pr.rule_id) AS rules
|
WITHIN GROUP (ORDER BY pr.rule_id) AS rules,
|
||||||
|
(
|
||||||
|
SELECT LISTAGG(pc.column_name, ', ') WITHIN GROUP (ORDER BY pc.column_name)
|
||||||
|
FROM cb_permission_column pc
|
||||||
|
WHERE pc.permission_id = p.perm_id
|
||||||
|
) AS visible_columns
|
||||||
FROM cb_permission p
|
FROM cb_permission p
|
||||||
JOIN cb_app_role r ON r.role_id = p.role_id
|
JOIN cb_app_role r ON r.role_id = p.role_id
|
||||||
LEFT JOIN cb_protected_object o ON o.object_name = p.target_name
|
LEFT JOIN cb_protected_object o ON o.object_name = p.target_name
|
||||||
|
|||||||
@@ -47,10 +47,7 @@
|
|||||||
#{command.username},
|
#{command.username},
|
||||||
#{command.empNo},
|
#{command.empNo},
|
||||||
#{command.deptCode},
|
#{command.deptCode},
|
||||||
<choose>
|
'N',
|
||||||
<when test="command.canReadContents"> 'Y' </when>
|
|
||||||
<otherwise> 'N' </otherwise>
|
|
||||||
</choose>,
|
|
||||||
'Y'
|
'Y'
|
||||||
)
|
)
|
||||||
</insert>
|
</insert>
|
||||||
|
|||||||
@@ -40,6 +40,10 @@
|
|||||||
규칙 값
|
규칙 값
|
||||||
<input class="form-control" name="ruleValue" placeholder="APAC 또는 HR">
|
<input class="form-control" name="ruleValue" placeholder="APAC 또는 HR">
|
||||||
</label>
|
</label>
|
||||||
|
<label>
|
||||||
|
표시 컬럼
|
||||||
|
<input class="form-control" name="visibleColumns" placeholder="CONTENTS">
|
||||||
|
</label>
|
||||||
<button class="btn btn-primary" type="submit">저장</button>
|
<button class="btn btn-primary" type="submit">저장</button>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
@@ -55,6 +59,7 @@
|
|||||||
<th>테이블/뷰</th>
|
<th>테이블/뷰</th>
|
||||||
<th>Action</th>
|
<th>Action</th>
|
||||||
<th>행 규칙</th>
|
<th>행 규칙</th>
|
||||||
|
<th>표시 컬럼</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -65,6 +70,7 @@
|
|||||||
<td th:text="${permission.objectName()}">CB_V_SEARCH_DOCUMENTS</td>
|
<td th:text="${permission.objectName()}">CB_V_SEARCH_DOCUMENTS</td>
|
||||||
<td th:text="${permission.action()}">SELECT</td>
|
<td th:text="${permission.action()}">SELECT</td>
|
||||||
<td th:text="${permission.rules()} ?: '-'">ALL</td>
|
<td th:text="${permission.rules()} ?: '-'">ALL</td>
|
||||||
|
<td th:text="${permission.visibleColumns()} ?: '-'">CONTENTS</td>
|
||||||
<td>
|
<td>
|
||||||
<form method="post" action="/permissions/delete" class="inline-form">
|
<form method="post" action="/permissions/delete" class="inline-form">
|
||||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||||
@@ -74,7 +80,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr th:if="${#lists.isEmpty(permissions)}">
|
<tr th:if="${#lists.isEmpty(permissions)}">
|
||||||
<td colspan="6" class="text-muted">등록된 권한이 없습니다.</td>
|
<td colspan="7" class="text-muted">등록된 권한이 없습니다.</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -27,10 +27,6 @@
|
|||||||
부서코드
|
부서코드
|
||||||
<input class="form-control" name="deptCode" required>
|
<input class="form-control" name="deptCode" required>
|
||||||
</label>
|
</label>
|
||||||
<label class="form-check form-switch switch-field">
|
|
||||||
<input class="form-check-input" name="canReadContents" type="checkbox" value="true">
|
|
||||||
<span class="form-check-label">민감 컬럼 표시</span>
|
|
||||||
</label>
|
|
||||||
<button class="btn btn-primary" type="submit">추가</button>
|
<button class="btn btn-primary" type="submit">추가</button>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
@@ -45,7 +41,6 @@
|
|||||||
<th>사용자명</th>
|
<th>사용자명</th>
|
||||||
<th>사번</th>
|
<th>사번</th>
|
||||||
<th>부서</th>
|
<th>부서</th>
|
||||||
<th>민감 컬럼</th>
|
|
||||||
<th>상태</th>
|
<th>상태</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -56,7 +51,6 @@
|
|||||||
<td th:text="${user.username()}">agent_hr</td>
|
<td th:text="${user.username()}">agent_hr</td>
|
||||||
<td th:text="${user.empNo()}">E10234</td>
|
<td th:text="${user.empNo()}">E10234</td>
|
||||||
<td th:text="${user.deptCode()}">HR</td>
|
<td th:text="${user.deptCode()}">HR</td>
|
||||||
<td><span class="badge" th:classappend="${user.canReadContents() == 'Y'} ? ' text-bg-success' : ' text-bg-secondary'" th:text="${user.canReadContents()}">N</span></td>
|
|
||||||
<td><span class="badge" th:classappend="${user.active()} ? ' text-bg-success' : ' text-bg-secondary'" th:text="${user.activeYn()}">Y</span></td>
|
<td><span class="badge" th:classappend="${user.active()} ? ' text-bg-success' : ' text-bg-secondary'" th:text="${user.activeYn()}">Y</span></td>
|
||||||
<td>
|
<td>
|
||||||
<form method="post" action="/users/active" class="inline-form">
|
<form method="post" action="/users/active" class="inline-form">
|
||||||
@@ -68,7 +62,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr th:if="${#lists.isEmpty(users)}">
|
<tr th:if="${#lists.isEmpty(users)}">
|
||||||
<td colspan="7" class="text-muted">등록된 사용자가 없습니다.</td>
|
<td colspan="6" class="text-muted">등록된 사용자가 없습니다.</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
Reference in New Issue
Block a user