[Developer] #424 show VPD policy details
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
package com.cloudhandson.vpdbackoffice.domain.vpd;
|
||||||
|
|
||||||
|
public record VpdPolicyDetail(
|
||||||
|
VpdPolicyView policy,
|
||||||
|
String ddl
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -10,6 +10,12 @@ public interface VpdPolicyMapper {
|
|||||||
|
|
||||||
List<VpdPolicyView> findPolicies();
|
List<VpdPolicyView> findPolicies();
|
||||||
|
|
||||||
|
VpdPolicyView findPolicy(
|
||||||
|
@Param("objectOwner") String objectOwner,
|
||||||
|
@Param("objectName") String objectName,
|
||||||
|
@Param("policyName") String policyName
|
||||||
|
);
|
||||||
|
|
||||||
String findFunctionSource(
|
String findFunctionSource(
|
||||||
@Param("owner") String owner,
|
@Param("owner") String owner,
|
||||||
@Param("objectName") String objectName,
|
@Param("objectName") String objectName,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.cloudhandson.vpdbackoffice.service;
|
package com.cloudhandson.vpdbackoffice.service;
|
||||||
|
|
||||||
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdFunctionSource;
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdFunctionSource;
|
||||||
|
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyDetail;
|
||||||
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;
|
||||||
@@ -30,6 +31,72 @@ public class VpdPolicyService {
|
|||||||
return new VpdFunctionSource(normalizedOwner, objectName, objectType, source);
|
return new VpdFunctionSource(normalizedOwner, objectName, objectType, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public VpdPolicyDetail findPolicyDetail(String objectOwner, String objectName, String policyName) {
|
||||||
|
String normalizedOwner = requiredIdentifier(objectOwner, "Object owner");
|
||||||
|
String normalizedObject = requiredIdentifier(objectName, "Object name");
|
||||||
|
String normalizedPolicy = requiredIdentifier(policyName, "Policy name");
|
||||||
|
VpdPolicyView policy = mapper.findPolicy(normalizedOwner, normalizedObject, normalizedPolicy);
|
||||||
|
if (policy == null) {
|
||||||
|
throw new AppException("VPD policy를 찾을 수 없습니다.");
|
||||||
|
}
|
||||||
|
return new VpdPolicyDetail(policy, buildAddPolicyBlock(policy));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildAddPolicyBlock(VpdPolicyView policy) {
|
||||||
|
return """
|
||||||
|
BEGIN
|
||||||
|
DBMS_RLS.ADD_POLICY(
|
||||||
|
object_schema => '%s',
|
||||||
|
object_name => '%s',
|
||||||
|
policy_name => '%s',
|
||||||
|
function_schema => '%s',
|
||||||
|
policy_function => '%s',
|
||||||
|
statement_types => '%s',
|
||||||
|
update_check => %s,
|
||||||
|
enable => %s,
|
||||||
|
static_policy => %s,
|
||||||
|
policy_type => %s,
|
||||||
|
long_predicate => %s
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
""".formatted(
|
||||||
|
policy.objectOwner(),
|
||||||
|
policy.objectName(),
|
||||||
|
policy.policyName(),
|
||||||
|
policy.functionOwner(),
|
||||||
|
policyFunctionArgument(policy),
|
||||||
|
blankToDefault(policy.statementTypes(), "SELECT"),
|
||||||
|
yesNoBoolean(policy.checkOption()),
|
||||||
|
yesNoBoolean(policy.enabled()),
|
||||||
|
yesNoBoolean(policy.staticPolicy()),
|
||||||
|
policyTypeArgument(policy.policyType()),
|
||||||
|
yesNoBoolean(policy.longPredicate())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String policyFunctionArgument(VpdPolicyView policy) {
|
||||||
|
if (policy.packageName() == null || policy.packageName().isBlank()) {
|
||||||
|
return policy.functionName();
|
||||||
|
}
|
||||||
|
return policy.packageName() + "." + policy.functionName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String blankToDefault(String value, String defaultValue) {
|
||||||
|
return value == null || value.isBlank() ? defaultValue : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String yesNoBoolean(String value) {
|
||||||
|
return "YES".equalsIgnoreCase(value) ? "TRUE" : "FALSE";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String policyTypeArgument(String policyType) {
|
||||||
|
if (policyType == null || policyType.isBlank()) {
|
||||||
|
return "NULL";
|
||||||
|
}
|
||||||
|
return "DBMS_RLS." + policyType.trim().toUpperCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
private String requiredIdentifier(String value, String label) {
|
private String requiredIdentifier(String value, String label) {
|
||||||
String normalized = normalizeOptionalIdentifier(value);
|
String normalized = normalizeOptionalIdentifier(value);
|
||||||
if (normalized == null) {
|
if (normalized == null) {
|
||||||
|
|||||||
@@ -47,4 +47,22 @@ public class VpdPolicyController {
|
|||||||
}
|
}
|
||||||
return "fragments/vpd-function-source :: source";
|
return "fragments/vpd-function-source :: source";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/vpd-policies/policy-detail")
|
||||||
|
public String policyDetail(
|
||||||
|
@RequestParam String objectOwner,
|
||||||
|
@RequestParam String objectName,
|
||||||
|
@RequestParam String policyName,
|
||||||
|
Model model
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
model.addAttribute("detail", vpdPolicyService.findPolicyDetail(objectOwner, objectName, policyName));
|
||||||
|
} catch (AppException exception) {
|
||||||
|
model.addAttribute("errorMessage", exception.getMessage());
|
||||||
|
} catch (DataAccessException exception) {
|
||||||
|
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(exception);
|
||||||
|
model.addAttribute("errorMessage", message.message());
|
||||||
|
}
|
||||||
|
return "fragments/vpd-policy-detail :: detail";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,32 +2,43 @@
|
|||||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
<mapper namespace="com.cloudhandson.vpdbackoffice.mapper.VpdPolicyMapper">
|
<mapper namespace="com.cloudhandson.vpdbackoffice.mapper.VpdPolicyMapper">
|
||||||
|
<sql id="policyColumns">
|
||||||
|
object_owner,
|
||||||
|
object_name,
|
||||||
|
policy_group,
|
||||||
|
policy_name,
|
||||||
|
pf_owner AS function_owner,
|
||||||
|
package AS package_name,
|
||||||
|
function AS function_name,
|
||||||
|
RTRIM(
|
||||||
|
CASE WHEN sel = 'YES' THEN 'SELECT,' ELSE '' END ||
|
||||||
|
CASE WHEN ins = 'YES' THEN 'INSERT,' ELSE '' END ||
|
||||||
|
CASE WHEN upd = 'YES' THEN 'UPDATE,' ELSE '' END ||
|
||||||
|
CASE WHEN del = 'YES' THEN 'DELETE,' ELSE '' END ||
|
||||||
|
CASE WHEN idx = 'YES' THEN 'INDEX,' ELSE '' END,
|
||||||
|
','
|
||||||
|
) AS statement_types,
|
||||||
|
chk_option AS check_option,
|
||||||
|
enable AS enabled,
|
||||||
|
static_policy,
|
||||||
|
policy_type,
|
||||||
|
long_predicate
|
||||||
|
</sql>
|
||||||
|
|
||||||
<select id="findPolicies" resultType="com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView">
|
<select id="findPolicies" resultType="com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView">
|
||||||
SELECT
|
SELECT <include refid="policyColumns"/>
|
||||||
object_owner,
|
|
||||||
object_name,
|
|
||||||
policy_group,
|
|
||||||
policy_name,
|
|
||||||
pf_owner AS function_owner,
|
|
||||||
package AS package_name,
|
|
||||||
function AS function_name,
|
|
||||||
RTRIM(
|
|
||||||
CASE WHEN sel = 'YES' THEN 'SELECT,' ELSE '' END ||
|
|
||||||
CASE WHEN ins = 'YES' THEN 'INSERT,' ELSE '' END ||
|
|
||||||
CASE WHEN upd = 'YES' THEN 'UPDATE,' ELSE '' END ||
|
|
||||||
CASE WHEN del = 'YES' THEN 'DELETE,' ELSE '' END ||
|
|
||||||
CASE WHEN idx = 'YES' THEN 'INDEX,' ELSE '' END,
|
|
||||||
','
|
|
||||||
) AS statement_types,
|
|
||||||
chk_option AS check_option,
|
|
||||||
enable AS enabled,
|
|
||||||
static_policy,
|
|
||||||
policy_type,
|
|
||||||
long_predicate
|
|
||||||
FROM all_policies
|
FROM all_policies
|
||||||
ORDER BY object_owner, object_name, policy_name
|
ORDER BY object_owner, object_name, policy_name
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="findPolicy" resultType="com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyView">
|
||||||
|
SELECT <include refid="policyColumns"/>
|
||||||
|
FROM all_policies
|
||||||
|
WHERE object_owner = UPPER(#{objectOwner,jdbcType=VARCHAR})
|
||||||
|
AND object_name = UPPER(#{objectName,jdbcType=VARCHAR})
|
||||||
|
AND policy_name = UPPER(#{policyName,jdbcType=VARCHAR})
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="findFunctionSource" resultType="string">
|
<select id="findFunctionSource" resultType="string">
|
||||||
SELECT LISTAGG(text, '') WITHIN GROUP (ORDER BY line)
|
SELECT LISTAGG(text, '') WITHIN GROUP (ORDER BY line)
|
||||||
FROM all_source
|
FROM all_source
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<body>
|
||||||
|
<div th:fragment="detail">
|
||||||
|
<div class="alert alert-warning mb-0" th:if="${errorMessage}" th:text="${errorMessage}"></div>
|
||||||
|
<div class="probe-exchange-grid" th:if="${detail}">
|
||||||
|
<section class="probe-exchange">
|
||||||
|
<h3>Policy Metadata</h3>
|
||||||
|
<pre th:text="${'Object: ' + detail.policy().objectDisplayName()
|
||||||
|
+ '\nPolicy Group: ' + detail.policy().policyGroup()
|
||||||
|
+ '\nPolicy Name: ' + detail.policy().policyName()
|
||||||
|
+ '\nFunction: ' + detail.policy().functionDisplayName()
|
||||||
|
+ '\nStatement Types: ' + detail.policy().statementTypes()
|
||||||
|
+ '\nEnabled: ' + detail.policy().enabled()
|
||||||
|
+ '\nPolicy Type: ' + detail.policy().policyType()
|
||||||
|
+ '\nCheck Option: ' + detail.policy().checkOption()
|
||||||
|
+ '\nStatic Policy: ' + detail.policy().staticPolicy()
|
||||||
|
+ '\nLong Predicate: ' + detail.policy().longPredicate()}"></pre>
|
||||||
|
</section>
|
||||||
|
<section class="probe-exchange">
|
||||||
|
<h3>DBMS_RLS.ADD_POLICY</h3>
|
||||||
|
<pre th:text="${detail.ddl()}"></pre>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -40,7 +40,15 @@
|
|||||||
<strong th:text="${policy.objectDisplayName()}">ADMIN.TABLE</strong>
|
<strong th:text="${policy.objectDisplayName()}">ADMIN.TABLE</strong>
|
||||||
<div class="text-muted small" th:text="${policy.policyGroup()}">SYS_DEFAULT</div>
|
<div class="text-muted small" th:text="${policy.policyGroup()}">SYS_DEFAULT</div>
|
||||||
</td>
|
</td>
|
||||||
<td><code th:text="${policy.policyName()}">POLICY</code></td>
|
<td>
|
||||||
|
<button class="btn btn-sm btn-outline-secondary policy-function-button"
|
||||||
|
type="button"
|
||||||
|
th:hx-get="@{/vpd-policies/policy-detail(objectOwner=${policy.objectOwner()},objectName=${policy.objectName()},policyName=${policy.policyName()})}"
|
||||||
|
th:hx-target="${'#policy-source-' + iter.index}"
|
||||||
|
hx-swap="innerHTML">
|
||||||
|
<code th:text="${policy.policyName()}">POLICY</code>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-sm btn-outline-secondary policy-function-button"
|
<button class="btn btn-sm btn-outline-secondary policy-function-button"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -66,7 +74,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan="7" class="policy-source-cell">
|
<td colspan="7" class="policy-source-cell">
|
||||||
<div th:id="${'policy-source-' + iter.index}" class="text-muted small">
|
<div th:id="${'policy-source-' + iter.index}" class="text-muted small">
|
||||||
Policy function을 클릭하면 소스가 표시됩니다.
|
Policy 또는 policy function을 클릭하면 상세 내역이 표시됩니다.
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
Reference in New Issue
Block a user