Consolidate data access control backoffice updates
This commit is contained in:
@@ -132,6 +132,33 @@ public class BackofficeSchemaService {
|
||||
CONSTRAINT cb_protected_column_uk UNIQUE (object_id, column_name)
|
||||
)
|
||||
"""),
|
||||
new TableDefinition("CB_MASKING_RULE", """
|
||||
CREATE TABLE cb_masking_rule (
|
||||
rule_id NUMBER PRIMARY KEY,
|
||||
rule_code VARCHAR2(64) NOT NULL UNIQUE,
|
||||
rule_name VARCHAR2(100) NOT NULL,
|
||||
template_code VARCHAR2(30) NOT NULL,
|
||||
description VARCHAR2(400),
|
||||
enabled_yn CHAR(1) DEFAULT 'Y' CHECK (enabled_yn IN ('Y','N')) NOT NULL
|
||||
)
|
||||
"""),
|
||||
new TableDefinition("CB_COLUMN_MASKING_RULE", """
|
||||
CREATE TABLE cb_column_masking_rule (
|
||||
column_id NUMBER PRIMARY KEY,
|
||||
rule_id NUMBER NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL
|
||||
)
|
||||
"""),
|
||||
new TableDefinition("CB_USER_MASKING_RULE", """
|
||||
CREATE TABLE cb_user_masking_rule (
|
||||
user_id NUMBER NOT NULL,
|
||||
column_id NUMBER NOT NULL,
|
||||
decision VARCHAR2(10) NOT NULL CHECK (decision IN ('MASK','UNMASK')),
|
||||
active_yn CHAR(1) DEFAULT 'Y' CHECK (active_yn IN ('Y','N')) NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL,
|
||||
CONSTRAINT cb_user_masking_rule_pk PRIMARY KEY (user_id, column_id)
|
||||
)
|
||||
"""),
|
||||
new TableDefinition("CB_ORDS_PROBE_AUDIT", """
|
||||
CREATE TABLE cb_ords_probe_audit (
|
||||
audit_id NUMBER PRIMARY KEY,
|
||||
@@ -224,6 +251,25 @@ public class BackofficeSchemaService {
|
||||
VALUES (src.setting_key, src.setting_value, SYSTIMESTAMP)
|
||||
""";
|
||||
|
||||
private static final List<MaskingRuleSeed> DEFAULT_MASKING_RULES = List.of(
|
||||
new MaskingRuleSeed("MASK_NULLIFY", "값 숨김 (NULL)", "NULLIFY",
|
||||
"값을 NULL로 반환하는 기본 마스킹 방식"),
|
||||
new MaskingRuleSeed("MASK_FULL", "전체 마스킹", "FULL",
|
||||
"문자형은 공백, 숫자형은 0으로 반환하는 전체 마스킹 방식"),
|
||||
new MaskingRuleSeed("MASK_TEXT_PARTIAL", "문자열 일부 마스킹", "TEXT_PARTIAL",
|
||||
"첫 글자만 보이고 나머지는 가리는 문자열 마스킹 방식"),
|
||||
new MaskingRuleSeed("MASK_RRN_PARTIAL", "주민등록번호 부분 마스킹", "RRN_PARTIAL",
|
||||
"앞 6자리만 보이고 나머지는 가리는 식별번호 마스킹 방식")
|
||||
);
|
||||
|
||||
private static final String MASKING_RULE_SEED_SQL = """
|
||||
MERGE INTO cb_masking_rule dst
|
||||
USING (SELECT ? rule_id, ? rule_code, ? rule_name, ? template_code, ? description FROM dual) src
|
||||
ON (dst.rule_code = src.rule_code)
|
||||
WHEN NOT MATCHED THEN INSERT (rule_id, rule_code, rule_name, template_code, description, enabled_yn)
|
||||
VALUES (src.rule_id, src.rule_code, src.rule_name, src.template_code, src.description, 'Y')
|
||||
""";
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final BackofficeProperties properties;
|
||||
|
||||
@@ -342,6 +388,7 @@ public class BackofficeSchemaService {
|
||||
}
|
||||
runDml(results, "CB_PROTECTED_COLUMN", "DATA", PROTECTED_COLUMN_MIGRATION_SQL,
|
||||
"민감 컬럼 legacy 값을 보강했습니다.", "UPDATED");
|
||||
seedDefaultMaskingRules(results);
|
||||
seedDefaultSettings(results);
|
||||
return results;
|
||||
}
|
||||
@@ -412,6 +459,28 @@ public class BackofficeSchemaService {
|
||||
}
|
||||
}
|
||||
|
||||
private void seedDefaultMaskingRules(List<SchemaActionResult> results) {
|
||||
long nextRuleId;
|
||||
try {
|
||||
Long currentMax = jdbcTemplate.queryForObject("SELECT NVL(MAX(rule_id), 0) FROM cb_masking_rule", Long.class);
|
||||
nextRuleId = currentMax == null ? 1L : currentMax + 1L;
|
||||
} catch (RuntimeException exception) {
|
||||
results.add(new SchemaActionResult("CB_MASKING_RULE", "MASKING_RULE", "FAILED",
|
||||
safeMessage(exception), MASKING_RULE_SEED_SQL));
|
||||
return;
|
||||
}
|
||||
for (MaskingRuleSeed seed : DEFAULT_MASKING_RULES) {
|
||||
try {
|
||||
jdbcTemplate.update(MASKING_RULE_SEED_SQL, nextRuleId++, seed.code(), seed.name(), seed.templateCode(), seed.description());
|
||||
results.add(new SchemaActionResult(seed.code(), "MASKING_RULE", "MERGED",
|
||||
"기본 마스킹 규칙을 확인했습니다.", MASKING_RULE_SEED_SQL));
|
||||
} catch (RuntimeException exception) {
|
||||
results.add(new SchemaActionResult(seed.code(), "MASKING_RULE", "FAILED",
|
||||
safeMessage(exception), MASKING_RULE_SEED_SQL));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String currentUser() {
|
||||
return jdbcTemplate.queryForObject("SELECT USER FROM dual", String.class);
|
||||
}
|
||||
@@ -623,6 +692,7 @@ public class BackofficeSchemaService {
|
||||
appendSql(builder, column.ddl());
|
||||
}
|
||||
appendSql(builder, PROTECTED_COLUMN_MIGRATION_SQL);
|
||||
appendSql(builder, MASKING_RULE_SEED_SQL.replace("?", "'<MASKING_RULE_VALUE>'"));
|
||||
appendSql(builder, SETTINGS_MERGE_SQL.replace("?", "'<BACKOFFICE_ORDS_BASE_URL>'"));
|
||||
return builder.toString();
|
||||
}
|
||||
@@ -635,6 +705,7 @@ public class BackofficeSchemaService {
|
||||
@sql/adb/17_agent_ords_security_local_vpd_setup.sql
|
||||
@sql/adb/25_agent_ords_security_backoffice_support.sql
|
||||
@sql/adb/26_agent_ords_security_dynamic_vpd_filter.sql
|
||||
@sql/adb/62_kb_aso_masking_backoffice_metadata.sql
|
||||
@sql/adb/21_agent_ords_security_ords_enable_schema.sql
|
||||
|
||||
-- 2. ORDS parsing schema로 접속
|
||||
@@ -644,8 +715,11 @@ public class BackofficeSchemaService {
|
||||
-- 3. 대표 권한 부여 SQL
|
||||
CONNECT %s/<password>@<tns_alias>
|
||||
GRANT EXECUTE ON cb_agent_ctx_pkg TO cb_ords;
|
||||
GRANT EXECUTE ON cb_agent_can_read_column TO cb_ords;
|
||||
GRANT SELECT ON <owner>.<table_or_view> TO cb_ords;
|
||||
|
||||
-- 4. 마스킹 규칙을 UI에서 컬럼에 연결한 뒤 실행
|
||||
@sql/adb/64_kb_aso_masking_default_column_rules.sql
|
||||
@sql/adb/63_kb_aso_masking_rule_runtime.sql
|
||||
""".formatted(owner.toLowerCase());
|
||||
}
|
||||
|
||||
@@ -693,4 +767,7 @@ public class BackofficeSchemaService {
|
||||
|
||||
private record ConstraintDefinition(String name, String table, String objectType) {
|
||||
}
|
||||
|
||||
private record MaskingRuleSeed(String code, String name, String templateCode, String description) {
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user