Consolidate data access control backoffice updates

This commit is contained in:
devmrko
2026-07-13 23:06:23 +09:00
parent 403298d474
commit e18b30feab
181 changed files with 11571 additions and 954 deletions

View File

@@ -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) {
}
}