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 objectName,
|
||||
String policyName,
|
||||
String functionKey,
|
||||
String functionOwner,
|
||||
String functionName,
|
||||
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;
|
||||
|
||||
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdFunctionOption;
|
||||
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@@ -10,6 +11,12 @@ public interface VpdPolicyMapper {
|
||||
|
||||
List<VpdPolicyView> findPolicies();
|
||||
|
||||
List<String> findPolicyNameOptions();
|
||||
|
||||
List<String> findOwnerOptions();
|
||||
|
||||
List<VpdFunctionOption> findFunctionOptions();
|
||||
|
||||
VpdPolicyView findPolicy(
|
||||
@Param("objectOwner") String objectOwner,
|
||||
@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.VpdPolicyDetail;
|
||||
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.mapper.VpdPolicyMapper;
|
||||
import java.util.List;
|
||||
@@ -32,6 +33,24 @@ public class VpdPolicyService {
|
||||
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) {
|
||||
String normalizedOwner = requiredIdentifier(owner, "Function owner");
|
||||
String normalizedFunction = requiredIdentifier(functionName, "Function name");
|
||||
@@ -59,12 +78,23 @@ public class VpdPolicyService {
|
||||
String objectName = requiredIdentifier(command.objectName(), "Object name");
|
||||
String policyName = requiredIdentifier(command.policyName(), "Policy name");
|
||||
String currentUser = jdbcTemplate.queryForObject("SELECT USER FROM dual", String.class);
|
||||
String functionOwner = command.functionOwner() == null || command.functionOwner().isBlank()
|
||||
? currentUser
|
||||
: requiredIdentifier(command.functionOwner(), "Function owner");
|
||||
String functionName = command.functionName() == null || command.functionName().isBlank()
|
||||
? generatedFunctionName(policyName)
|
||||
: requiredIdentifier(command.functionName(), "Function name");
|
||||
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
|
||||
: requiredIdentifier(command.functionOwner(), "Function owner");
|
||||
packageName = null;
|
||||
functionName = command.functionName() == null || command.functionName().isBlank()
|
||||
? generatedFunctionName(policyName)
|
||||
: requiredIdentifier(command.functionName(), "Function name");
|
||||
}
|
||||
String statementTypes = normalizeStatementTypes(command.statementTypes());
|
||||
String filterPredicate = command.filterPredicate() == null ? "" : command.filterPredicate().trim();
|
||||
|
||||
@@ -95,7 +125,7 @@ public class VpdPolicyService {
|
||||
objectName,
|
||||
policyName,
|
||||
functionOwner,
|
||||
functionName,
|
||||
packageName == null ? functionName : packageName + "." + functionName,
|
||||
statementTypes);
|
||||
}
|
||||
|
||||
@@ -271,6 +301,22 @@ public class VpdPolicyService {
|
||||
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) {
|
||||
String raw = value == null || value.isBlank() ? "SELECT" : value;
|
||||
List<String> statements = List.of(raw.split(",")).stream()
|
||||
@@ -293,6 +339,9 @@ public class VpdPolicyService {
|
||||
return value.replace("'", "''");
|
||||
}
|
||||
|
||||
private record FunctionRef(String owner, String packageName, String functionName) {
|
||||
}
|
||||
|
||||
private String policyFunctionArgument(VpdPolicyView policy) {
|
||||
if (policy.packageName() == null || policy.packageName().isBlank()) {
|
||||
return policy.functionName();
|
||||
|
||||
@@ -29,11 +29,13 @@ public class VpdPolicyController {
|
||||
try {
|
||||
model.addAttribute("policies", vpdPolicyService.findPolicies());
|
||||
model.addAttribute("objects", protectedObjectService.findEnabled());
|
||||
model.addAttribute("formOptions", vpdPolicyService.formOptions());
|
||||
} catch (DataAccessException exception) {
|
||||
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
||||
model.addAttribute("runtimeError", message);
|
||||
model.addAttribute("policies", List.of());
|
||||
model.addAttribute("objects", List.of());
|
||||
model.addAttribute("formOptions", vpdPolicyService.emptyFormOptions());
|
||||
}
|
||||
return "vpd-policies";
|
||||
}
|
||||
@@ -42,9 +44,10 @@ public class VpdPolicyController {
|
||||
public String createPolicy(
|
||||
@RequestParam String objectKey,
|
||||
@RequestParam String policyName,
|
||||
@RequestParam(required = false) String functionKey,
|
||||
@RequestParam(required = false) String functionOwner,
|
||||
@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 updateCheck,
|
||||
@RequestParam(required = false) String filterPredicate,
|
||||
@@ -59,9 +62,10 @@ public class VpdPolicyController {
|
||||
objectParts[0],
|
||||
objectParts[1],
|
||||
policyName,
|
||||
functionKey,
|
||||
functionOwner,
|
||||
functionName,
|
||||
statementTypes,
|
||||
String.join(",", statementTypes),
|
||||
enabled,
|
||||
updateCheck,
|
||||
filterPredicate
|
||||
|
||||
@@ -38,6 +38,47 @@
|
||||
ORDER BY p.object_owner, p.object_name, p.policy_name
|
||||
</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 <include refid="policyColumns"><property name="alias" value="p."/></include>
|
||||
FROM all_policies p
|
||||
|
||||
@@ -348,6 +348,18 @@ body {
|
||||
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,
|
||||
.btn {
|
||||
border-radius: 999px;
|
||||
|
||||
@@ -32,20 +32,52 @@
|
||||
</label>
|
||||
<label>
|
||||
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>
|
||||
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>
|
||||
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>
|
||||
<div>
|
||||
Statement Types
|
||||
<input class="form-control" name="statementTypes" value="SELECT" placeholder="SELECT,INSERT,UPDATE,DELETE">
|
||||
</label>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check align-self-end">
|
||||
<input class="form-check-input" id="vpd-enabled" type="checkbox" name="enabled" value="true" checked>
|
||||
<label class="form-check-label" for="vpd-enabled">등록 즉시 활성화</label>
|
||||
|
||||
Reference in New Issue
Block a user