fix #424: use dropdowns for VPD policy form
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
package com.cloudhandson.vpdbackoffice.domain.vpd;
|
||||||
|
|
||||||
|
public record VpdFunctionOption(
|
||||||
|
String owner,
|
||||||
|
String packageName,
|
||||||
|
String functionName,
|
||||||
|
String objectType
|
||||||
|
) {
|
||||||
|
|
||||||
|
public String value() {
|
||||||
|
String packagePrefix = packageName == null || packageName.isBlank() ? "" : packageName + ".";
|
||||||
|
return owner + "." + packagePrefix + functionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String label() {
|
||||||
|
return value() + " / " + objectType;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ public record VpdPolicyCreateCommand(
|
|||||||
String objectOwner,
|
String objectOwner,
|
||||||
String objectName,
|
String objectName,
|
||||||
String policyName,
|
String policyName,
|
||||||
|
String functionKey,
|
||||||
String functionOwner,
|
String functionOwner,
|
||||||
String functionName,
|
String functionName,
|
||||||
String statementTypes,
|
String statementTypes,
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.cloudhandson.vpdbackoffice.domain.vpd;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record VpdPolicyFormOptions(
|
||||||
|
List<String> policyNames,
|
||||||
|
List<String> owners,
|
||||||
|
List<VpdFunctionOption> functions,
|
||||||
|
List<String> statementTypes
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.cloudhandson.vpdbackoffice.mapper;
|
package com.cloudhandson.vpdbackoffice.mapper;
|
||||||
|
|
||||||
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdFunctionOption;
|
||||||
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView;
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
@@ -10,6 +11,12 @@ public interface VpdPolicyMapper {
|
|||||||
|
|
||||||
List<VpdPolicyView> findPolicies();
|
List<VpdPolicyView> findPolicies();
|
||||||
|
|
||||||
|
List<String> findPolicyNameOptions();
|
||||||
|
|
||||||
|
List<String> findOwnerOptions();
|
||||||
|
|
||||||
|
List<VpdFunctionOption> findFunctionOptions();
|
||||||
|
|
||||||
VpdPolicyView findPolicy(
|
VpdPolicyView findPolicy(
|
||||||
@Param("objectOwner") String objectOwner,
|
@Param("objectOwner") String objectOwner,
|
||||||
@Param("objectName") String objectName,
|
@Param("objectName") String objectName,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.cloudhandson.vpdbackoffice.domain.vpd.VpdFunctionSource;
|
|||||||
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyCreateCommand;
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyCreateCommand;
|
||||||
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyDetail;
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyDetail;
|
||||||
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyExplanation;
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyExplanation;
|
||||||
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyFormOptions;
|
||||||
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView;
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView;
|
||||||
import com.cloudhandson.vpdbackoffice.mapper.VpdPolicyMapper;
|
import com.cloudhandson.vpdbackoffice.mapper.VpdPolicyMapper;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -32,6 +33,24 @@ public class VpdPolicyService {
|
|||||||
return mapper.findPolicies();
|
return mapper.findPolicies();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public VpdPolicyFormOptions formOptions() {
|
||||||
|
return new VpdPolicyFormOptions(
|
||||||
|
mapper.findPolicyNameOptions(),
|
||||||
|
mapper.findOwnerOptions(),
|
||||||
|
mapper.findFunctionOptions(),
|
||||||
|
List.of("SELECT", "INSERT", "UPDATE", "DELETE", "INDEX")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VpdPolicyFormOptions emptyFormOptions() {
|
||||||
|
return new VpdPolicyFormOptions(
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of("SELECT", "INSERT", "UPDATE", "DELETE", "INDEX")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public VpdFunctionSource findFunctionSource(String owner, String packageName, String functionName) {
|
public VpdFunctionSource findFunctionSource(String owner, String packageName, String functionName) {
|
||||||
String normalizedOwner = requiredIdentifier(owner, "Function owner");
|
String normalizedOwner = requiredIdentifier(owner, "Function owner");
|
||||||
String normalizedFunction = requiredIdentifier(functionName, "Function name");
|
String normalizedFunction = requiredIdentifier(functionName, "Function name");
|
||||||
@@ -59,12 +78,23 @@ public class VpdPolicyService {
|
|||||||
String objectName = requiredIdentifier(command.objectName(), "Object name");
|
String objectName = requiredIdentifier(command.objectName(), "Object name");
|
||||||
String policyName = requiredIdentifier(command.policyName(), "Policy name");
|
String policyName = requiredIdentifier(command.policyName(), "Policy name");
|
||||||
String currentUser = jdbcTemplate.queryForObject("SELECT USER FROM dual", String.class);
|
String currentUser = jdbcTemplate.queryForObject("SELECT USER FROM dual", String.class);
|
||||||
String functionOwner = command.functionOwner() == null || command.functionOwner().isBlank()
|
FunctionRef functionRef = parseFunctionRef(command.functionKey());
|
||||||
|
String functionOwner;
|
||||||
|
String packageName;
|
||||||
|
String functionName;
|
||||||
|
if (functionRef != null) {
|
||||||
|
functionOwner = functionRef.owner();
|
||||||
|
packageName = functionRef.packageName();
|
||||||
|
functionName = functionRef.functionName();
|
||||||
|
} else {
|
||||||
|
functionOwner = command.functionOwner() == null || command.functionOwner().isBlank()
|
||||||
? currentUser
|
? currentUser
|
||||||
: requiredIdentifier(command.functionOwner(), "Function owner");
|
: requiredIdentifier(command.functionOwner(), "Function owner");
|
||||||
String functionName = command.functionName() == null || command.functionName().isBlank()
|
packageName = null;
|
||||||
|
functionName = command.functionName() == null || command.functionName().isBlank()
|
||||||
? generatedFunctionName(policyName)
|
? generatedFunctionName(policyName)
|
||||||
: requiredIdentifier(command.functionName(), "Function name");
|
: requiredIdentifier(command.functionName(), "Function name");
|
||||||
|
}
|
||||||
String statementTypes = normalizeStatementTypes(command.statementTypes());
|
String statementTypes = normalizeStatementTypes(command.statementTypes());
|
||||||
String filterPredicate = command.filterPredicate() == null ? "" : command.filterPredicate().trim();
|
String filterPredicate = command.filterPredicate() == null ? "" : command.filterPredicate().trim();
|
||||||
|
|
||||||
@@ -95,7 +125,7 @@ public class VpdPolicyService {
|
|||||||
objectName,
|
objectName,
|
||||||
policyName,
|
policyName,
|
||||||
functionOwner,
|
functionOwner,
|
||||||
functionName,
|
packageName == null ? functionName : packageName + "." + functionName,
|
||||||
statementTypes);
|
statementTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,6 +301,22 @@ public class VpdPolicyService {
|
|||||||
return generated.length() > 128 ? generated.substring(0, 128) : generated;
|
return generated.length() > 128 ? generated.substring(0, 128) : generated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private FunctionRef parseFunctionRef(String functionKey) {
|
||||||
|
if (functionKey == null || functionKey.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String[] parts = functionKey.trim().toUpperCase(Locale.ROOT).split("\\.");
|
||||||
|
if (parts.length == 2) {
|
||||||
|
return new FunctionRef(requiredIdentifier(parts[0], "Function owner"), null,
|
||||||
|
requiredIdentifier(parts[1], "Function name"));
|
||||||
|
}
|
||||||
|
if (parts.length == 3) {
|
||||||
|
return new FunctionRef(requiredIdentifier(parts[0], "Function owner"),
|
||||||
|
requiredIdentifier(parts[1], "Package name"), requiredIdentifier(parts[2], "Function name"));
|
||||||
|
}
|
||||||
|
throw new AppException("Function 선택 값이 올바르지 않습니다: " + functionKey);
|
||||||
|
}
|
||||||
|
|
||||||
private String normalizeStatementTypes(String value) {
|
private String normalizeStatementTypes(String value) {
|
||||||
String raw = value == null || value.isBlank() ? "SELECT" : value;
|
String raw = value == null || value.isBlank() ? "SELECT" : value;
|
||||||
List<String> statements = List.of(raw.split(",")).stream()
|
List<String> statements = List.of(raw.split(",")).stream()
|
||||||
@@ -293,6 +339,9 @@ public class VpdPolicyService {
|
|||||||
return value.replace("'", "''");
|
return value.replace("'", "''");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record FunctionRef(String owner, String packageName, String functionName) {
|
||||||
|
}
|
||||||
|
|
||||||
private String policyFunctionArgument(VpdPolicyView policy) {
|
private String policyFunctionArgument(VpdPolicyView policy) {
|
||||||
if (policy.packageName() == null || policy.packageName().isBlank()) {
|
if (policy.packageName() == null || policy.packageName().isBlank()) {
|
||||||
return policy.functionName();
|
return policy.functionName();
|
||||||
|
|||||||
@@ -29,11 +29,13 @@ public class VpdPolicyController {
|
|||||||
try {
|
try {
|
||||||
model.addAttribute("policies", vpdPolicyService.findPolicies());
|
model.addAttribute("policies", vpdPolicyService.findPolicies());
|
||||||
model.addAttribute("objects", protectedObjectService.findEnabled());
|
model.addAttribute("objects", protectedObjectService.findEnabled());
|
||||||
|
model.addAttribute("formOptions", vpdPolicyService.formOptions());
|
||||||
} catch (DataAccessException exception) {
|
} catch (DataAccessException exception) {
|
||||||
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
||||||
model.addAttribute("runtimeError", message);
|
model.addAttribute("runtimeError", message);
|
||||||
model.addAttribute("policies", List.of());
|
model.addAttribute("policies", List.of());
|
||||||
model.addAttribute("objects", List.of());
|
model.addAttribute("objects", List.of());
|
||||||
|
model.addAttribute("formOptions", vpdPolicyService.emptyFormOptions());
|
||||||
}
|
}
|
||||||
return "vpd-policies";
|
return "vpd-policies";
|
||||||
}
|
}
|
||||||
@@ -42,9 +44,10 @@ public class VpdPolicyController {
|
|||||||
public String createPolicy(
|
public String createPolicy(
|
||||||
@RequestParam String objectKey,
|
@RequestParam String objectKey,
|
||||||
@RequestParam String policyName,
|
@RequestParam String policyName,
|
||||||
|
@RequestParam(required = false) String functionKey,
|
||||||
@RequestParam(required = false) String functionOwner,
|
@RequestParam(required = false) String functionOwner,
|
||||||
@RequestParam(required = false) String functionName,
|
@RequestParam(required = false) String functionName,
|
||||||
@RequestParam(defaultValue = "SELECT") String statementTypes,
|
@RequestParam(defaultValue = "SELECT") List<String> statementTypes,
|
||||||
@RequestParam(defaultValue = "false") boolean enabled,
|
@RequestParam(defaultValue = "false") boolean enabled,
|
||||||
@RequestParam(defaultValue = "false") boolean updateCheck,
|
@RequestParam(defaultValue = "false") boolean updateCheck,
|
||||||
@RequestParam(required = false) String filterPredicate,
|
@RequestParam(required = false) String filterPredicate,
|
||||||
@@ -59,9 +62,10 @@ public class VpdPolicyController {
|
|||||||
objectParts[0],
|
objectParts[0],
|
||||||
objectParts[1],
|
objectParts[1],
|
||||||
policyName,
|
policyName,
|
||||||
|
functionKey,
|
||||||
functionOwner,
|
functionOwner,
|
||||||
functionName,
|
functionName,
|
||||||
statementTypes,
|
String.join(",", statementTypes),
|
||||||
enabled,
|
enabled,
|
||||||
updateCheck,
|
updateCheck,
|
||||||
filterPredicate
|
filterPredicate
|
||||||
|
|||||||
@@ -38,6 +38,47 @@
|
|||||||
ORDER BY p.object_owner, p.object_name, p.policy_name
|
ORDER BY p.object_owner, p.object_name, p.policy_name
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="findPolicyNameOptions" resultType="string">
|
||||||
|
SELECT DISTINCT policy_name
|
||||||
|
FROM all_policies
|
||||||
|
WHERE policy_name IS NOT NULL
|
||||||
|
UNION
|
||||||
|
SELECT object_name || '_POLICY'
|
||||||
|
FROM cb_protected_object
|
||||||
|
WHERE enabled_yn = 'Y'
|
||||||
|
ORDER BY 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findOwnerOptions" resultType="string">
|
||||||
|
SELECT USER FROM dual
|
||||||
|
UNION
|
||||||
|
SELECT owner FROM cb_protected_object WHERE enabled_yn = 'Y'
|
||||||
|
UNION
|
||||||
|
SELECT pf_owner FROM all_policies WHERE pf_owner IS NOT NULL
|
||||||
|
ORDER BY 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findFunctionOptions" resultType="com.cloudhandson.vpdbackoffice.domain.vpd.VpdFunctionOption">
|
||||||
|
WITH allowed_owners AS (
|
||||||
|
SELECT USER AS owner FROM dual
|
||||||
|
UNION
|
||||||
|
SELECT owner FROM cb_protected_object WHERE enabled_yn = 'Y'
|
||||||
|
UNION
|
||||||
|
SELECT pf_owner FROM all_policies WHERE pf_owner IS NOT NULL
|
||||||
|
)
|
||||||
|
SELECT owner,
|
||||||
|
package_name,
|
||||||
|
object_name AS function_name,
|
||||||
|
CASE WHEN package_name IS NULL THEN 'FUNCTION' ELSE 'PACKAGE FUNCTION' END AS object_type
|
||||||
|
FROM all_arguments
|
||||||
|
WHERE owner IN (SELECT owner FROM allowed_owners)
|
||||||
|
AND position = 0
|
||||||
|
AND data_type = 'VARCHAR2'
|
||||||
|
AND object_name IS NOT NULL
|
||||||
|
ORDER BY owner, package_name, function_name
|
||||||
|
FETCH FIRST 300 ROWS ONLY
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="findPolicy" resultType="com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView">
|
<select id="findPolicy" resultType="com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView">
|
||||||
SELECT <include refid="policyColumns"><property name="alias" value="p."/></include>
|
SELECT <include refid="policyColumns"><property name="alias" value="p."/></include>
|
||||||
FROM all_policies p
|
FROM all_policies p
|
||||||
|
|||||||
@@ -348,6 +348,18 @@ body {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.checkbox-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: .75rem 1rem;
|
||||||
|
min-height: 2.45rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-row .form-check {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.rw-btn,
|
.rw-btn,
|
||||||
.btn {
|
.btn {
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
|
|||||||
@@ -32,20 +32,52 @@
|
|||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
Policy 이름
|
Policy 이름
|
||||||
<input class="form-control" name="policyName" placeholder="예: BOARD_POSTS_POLICY" required>
|
<select class="form-select" name="policyName" required>
|
||||||
|
<option th:each="policyName : ${formOptions.policyNames()}"
|
||||||
|
th:value="${policyName}"
|
||||||
|
th:text="${policyName}"></option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="span-2">
|
||||||
|
기존 Function 선택
|
||||||
|
<select class="form-select" name="functionKey">
|
||||||
|
<option value="">Filter predicate로 새 function 자동 생성</option>
|
||||||
|
<option th:each="function : ${formOptions.functions()}"
|
||||||
|
th:value="${function.value()}"
|
||||||
|
th:text="${function.label()}"></option>
|
||||||
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
Function Owner
|
Function Owner
|
||||||
<input class="form-control" name="functionOwner" placeholder="비우면 현재 연결 사용자">
|
<select class="form-select" name="functionOwner">
|
||||||
|
<option value="">현재 연결 사용자</option>
|
||||||
|
<option th:each="owner : ${formOptions.owners()}"
|
||||||
|
th:value="${owner}"
|
||||||
|
th:text="${owner}"></option>
|
||||||
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
Function 이름
|
Function 이름
|
||||||
<input class="form-control" name="functionName" placeholder="비우면 POLICY_NAME_FILTER 자동 생성">
|
<select class="form-select" name="functionName">
|
||||||
|
<option value="">POLICY_NAME_FILTER 자동 생성</option>
|
||||||
|
<option th:each="function : ${formOptions.functions()}"
|
||||||
|
th:value="${function.functionName()}"
|
||||||
|
th:text="${function.functionName()}"></option>
|
||||||
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<div>
|
||||||
Statement Types
|
Statement Types
|
||||||
<input class="form-control" name="statementTypes" value="SELECT" placeholder="SELECT,INSERT,UPDATE,DELETE">
|
<div class="checkbox-row">
|
||||||
|
<label class="form-check" th:each="statement : ${formOptions.statementTypes()}">
|
||||||
|
<input class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
name="statementTypes"
|
||||||
|
th:value="${statement}"
|
||||||
|
th:checked="${statement == 'SELECT'}">
|
||||||
|
<span class="form-check-label" th:text="${statement}">SELECT</span>
|
||||||
</label>
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-check align-self-end">
|
<div class="form-check align-self-end">
|
||||||
<input class="form-check-input" id="vpd-enabled" type="checkbox" name="enabled" value="true" checked>
|
<input class="form-check-input" id="vpd-enabled" type="checkbox" name="enabled" value="true" checked>
|
||||||
<label class="form-check-label" for="vpd-enabled">등록 즉시 활성화</label>
|
<label class="form-check-label" for="vpd-enabled">등록 즉시 활성화</label>
|
||||||
|
|||||||
Reference in New Issue
Block a user