[Developer] #424 default protected object lookup mapping
This commit is contained in:
@@ -13,4 +13,8 @@ public record DatabaseObjectOption(
|
|||||||
public String label() {
|
public String label() {
|
||||||
return owner + "." + objectName + " (" + objectType + ")";
|
return owner + "." + objectName + " (" + objectType + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String defaultOrdsPath() {
|
||||||
|
return owner.toLowerCase() + "/" + objectName.toLowerCase();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,14 +51,15 @@ public class ProtectedObjectService {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void createObject(ProtectedObjectCreateCommand command) {
|
public void createObject(ProtectedObjectCreateCommand command) {
|
||||||
|
ProtectedObjectCreateCommand normalized = normalizeCreateCommand(command);
|
||||||
long objectId = mapper.nextObjectId();
|
long objectId = mapper.nextObjectId();
|
||||||
mapper.insertObject(objectId, command);
|
mapper.insertObject(objectId, normalized);
|
||||||
Set<String> sensitive = splitCsv(command.sensitiveColumns());
|
Set<String> sensitive = splitCsv(normalized.sensitiveColumns());
|
||||||
for (String column : splitCsv(command.columns())) {
|
for (String column : splitCsv(normalized.columns())) {
|
||||||
mapper.insertColumn(mapper.nextColumnId(), objectId, column, sensitive.contains(column) ? "Y" : "N");
|
mapper.insertColumn(mapper.nextColumnId(), objectId, column, sensitive.contains(column) ? "Y" : "N");
|
||||||
}
|
}
|
||||||
auditService.record(new AuditEvent("PROTECTED_OBJECT_CREATED", null, objectId, "SUCCESS", null, null,
|
auditService.record(new AuditEvent("PROTECTED_OBJECT_CREATED", null, objectId, "SUCCESS", null, null,
|
||||||
command.objectName()));
|
normalized.objectName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -92,6 +93,20 @@ public class ProtectedObjectService {
|
|||||||
return schemaPath + "/" + objectName.toLowerCase(Locale.ROOT);
|
return schemaPath + "/" + objectName.toLowerCase(Locale.ROOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ProtectedObjectCreateCommand normalizeCreateCommand(ProtectedObjectCreateCommand command) {
|
||||||
|
String owner = command.owner().trim().toUpperCase(Locale.ROOT);
|
||||||
|
String objectName = command.objectName().trim().toUpperCase(Locale.ROOT);
|
||||||
|
String ordsPath = command.ordsPath();
|
||||||
|
if (ordsPath == null || ordsPath.isBlank()) {
|
||||||
|
ordsPath = defaultOrdsPath(owner, objectName);
|
||||||
|
}
|
||||||
|
String columns = command.columns();
|
||||||
|
if (columns == null || columns.isBlank()) {
|
||||||
|
columns = String.join(",", mapper.findDatabaseColumns(owner, objectName));
|
||||||
|
}
|
||||||
|
return new ProtectedObjectCreateCommand(owner, objectName, ordsPath.trim(), columns, command.sensitiveColumns());
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void disableObject(long objectId) {
|
public void disableObject(long objectId) {
|
||||||
int updated = mapper.disableObject(objectId);
|
int updated = mapper.disableObject(objectId);
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public class ProtectedObjectController {
|
|||||||
@GetMapping("/objects")
|
@GetMapping("/objects")
|
||||||
public String objects(Model model) {
|
public String objects(Model model) {
|
||||||
model.addAttribute("objects", protectedObjectService.findEnabled());
|
model.addAttribute("objects", protectedObjectService.findEnabled());
|
||||||
|
model.addAttribute("dbObjects", protectedObjectService.findDatabaseObjects());
|
||||||
return "objects";
|
return "objects";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,43 @@ async function syncRuleColumnOptions() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function syncObjectCatalogSelection() {
|
||||||
|
const catalog = document.getElementById('objectCatalogSelect');
|
||||||
|
if (!catalog || !catalog.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selected = catalog.options[catalog.selectedIndex];
|
||||||
|
const ownerInput = document.querySelector('input[name="owner"]');
|
||||||
|
const objectInput = document.querySelector('input[name="objectName"]');
|
||||||
|
const ordsPathInput = document.querySelector('input[name="ordsPath"]');
|
||||||
|
const columnsInput = document.querySelector('input[name="columns"]');
|
||||||
|
if (ownerInput) {
|
||||||
|
ownerInput.value = selected.dataset.owner || '';
|
||||||
|
}
|
||||||
|
if (objectInput) {
|
||||||
|
objectInput.value = selected.dataset.objectName || '';
|
||||||
|
}
|
||||||
|
if (ordsPathInput) {
|
||||||
|
ordsPathInput.value = selected.dataset.defaultPath || '';
|
||||||
|
}
|
||||||
|
if (!columnsInput) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
columnsInput.value = '';
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/permissions/object-columns?objectRef=${encodeURIComponent(`db:${catalog.value}`)}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const columns = await response.json();
|
||||||
|
if (Array.isArray(columns)) {
|
||||||
|
columnsInput.value = columns.join(',');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
columnsInput.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const master = document.getElementById('userRoleMaster');
|
const master = document.getElementById('userRoleMaster');
|
||||||
if (master) {
|
if (master) {
|
||||||
@@ -71,6 +108,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
objectSelect.addEventListener('change', syncRuleColumnOptions);
|
objectSelect.addEventListener('change', syncRuleColumnOptions);
|
||||||
syncRuleColumnOptions();
|
syncRuleColumnOptions();
|
||||||
}
|
}
|
||||||
|
const catalog = document.getElementById('objectCatalogSelect');
|
||||||
|
if (catalog) {
|
||||||
|
catalog.addEventListener('change', syncObjectCatalogSelection);
|
||||||
|
}
|
||||||
document.querySelectorAll('[data-rule-add]').forEach((button) => {
|
document.querySelectorAll('[data-rule-add]').forEach((button) => {
|
||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', () => {
|
||||||
const row = button.closest('.rule-row');
|
const row = button.closest('.rule-row');
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<main class="container py-4">
|
<main class="container py-4">
|
||||||
<div class="page-title">
|
<div class="page-title">
|
||||||
<h1>테이블/뷰 관리</h1>
|
<h1>테이블/뷰 관리</h1>
|
||||||
<p>권한에 사용할 보호 객체와 ORDS 경로를 등록합니다.</p>
|
<p>권한에 사용할 보호 객체와 이미 구성된 ORDS 조회 경로를 매핑합니다.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="alert alert-success" th:if="${message}" th:text="${message}"></div>
|
<div class="alert alert-success" th:if="${message}" th:text="${message}"></div>
|
||||||
@@ -15,17 +15,29 @@
|
|||||||
<h2>테이블/뷰 추가</h2>
|
<h2>테이블/뷰 추가</h2>
|
||||||
<form method="post" action="/objects" class="form-grid">
|
<form method="post" action="/objects" class="form-grid">
|
||||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||||
|
<label>
|
||||||
|
DB 객체 선택
|
||||||
|
<select class="form-select" id="objectCatalogSelect">
|
||||||
|
<option value="">직접 입력</option>
|
||||||
|
<option th:each="dbObject : ${dbObjects}"
|
||||||
|
th:value="${dbObject.value()}"
|
||||||
|
th:data-owner="${dbObject.owner()}"
|
||||||
|
th:data-object-name="${dbObject.objectName()}"
|
||||||
|
th:data-default-path="${dbObject.defaultOrdsPath()}"
|
||||||
|
th:text="${dbObject.label()}"></option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
<label>
|
<label>
|
||||||
Owner
|
Owner
|
||||||
<input class="form-control" name="owner" value="ADMIN" required>
|
<input class="form-control" name="owner" value="ADMIN" autocomplete="off" required>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
Object
|
Object
|
||||||
<input class="form-control" name="objectName" placeholder="CB_V_SEARCH_DOCUMENTS" required>
|
<input class="form-control" name="objectName" placeholder="CB_V_SEARCH_DOCUMENTS" autocomplete="off" required>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
ORDS Path
|
ORDS Path
|
||||||
<input class="form-control" name="ordsPath" placeholder="cb-ords/cb-agent-security/vpd/documents" required>
|
<input class="form-control" name="ordsPath" placeholder="admin/cb_v_search_documents">
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
컬럼
|
컬럼
|
||||||
|
|||||||
Reference in New Issue
Block a user