@@ -0,0 +1,18 @@
|
||||
package com.cloudhandson.vpdbackoffice.domain.ords;
|
||||
|
||||
public record OrdsHandlerView(
|
||||
String schemaName,
|
||||
String moduleName,
|
||||
String basePath,
|
||||
String template,
|
||||
String method,
|
||||
String sourceType,
|
||||
String source,
|
||||
String parameters,
|
||||
String packageSource
|
||||
) {
|
||||
|
||||
public String fullPath() {
|
||||
return "/ords/" + schemaName + "/" + basePath + template;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ public record PermissionView(
|
||||
String objectName,
|
||||
String action,
|
||||
String rules,
|
||||
String visibleColumns
|
||||
String visibleColumns,
|
||||
String filterPreview,
|
||||
String nullPolicyPreview
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.cloudhandson.vpdbackoffice.mapper;
|
||||
|
||||
import com.cloudhandson.vpdbackoffice.domain.ords.OrdsHandlerView;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface OrdsMetadataMapper {
|
||||
|
||||
List<OrdsHandlerView> findHandlers();
|
||||
|
||||
String findHandlerPackageSource();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.cloudhandson.vpdbackoffice.service;
|
||||
|
||||
import com.cloudhandson.vpdbackoffice.domain.ords.OrdsHandlerView;
|
||||
import com.cloudhandson.vpdbackoffice.mapper.OrdsMetadataMapper;
|
||||
import java.util.List;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class OrdsMetadataService {
|
||||
|
||||
private final OrdsMetadataMapper mapper;
|
||||
|
||||
public OrdsMetadataService(OrdsMetadataMapper mapper) {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
public List<OrdsHandlerView> findHandlers() {
|
||||
String packageSource = mapper.findHandlerPackageSource();
|
||||
return mapper.findHandlers().stream()
|
||||
.map(handler -> new OrdsHandlerView(
|
||||
handler.schemaName(),
|
||||
handler.moduleName(),
|
||||
handler.basePath(),
|
||||
handler.template(),
|
||||
handler.method(),
|
||||
handler.sourceType(),
|
||||
handler.source(),
|
||||
handler.parameters(),
|
||||
packageSource
|
||||
))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.cloudhandson.vpdbackoffice.web;
|
||||
|
||||
import com.cloudhandson.vpdbackoffice.service.OrdsMetadataService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
public class OrdsHandlerController {
|
||||
|
||||
private final OrdsMetadataService ordsMetadataService;
|
||||
|
||||
public OrdsHandlerController(OrdsMetadataService ordsMetadataService) {
|
||||
this.ordsMetadataService = ordsMetadataService;
|
||||
}
|
||||
|
||||
@GetMapping("/ords-handlers")
|
||||
public String handlers(Model model) {
|
||||
model.addAttribute("handlers", ordsMetadataService.findHandlers());
|
||||
return "ords-handlers";
|
||||
}
|
||||
}
|
||||
35
src/main/resources/mapper/OrdsMetadataMapper.xml
Normal file
35
src/main/resources/mapper/OrdsMetadataMapper.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.cloudhandson.vpdbackoffice.mapper.OrdsMetadataMapper">
|
||||
<select id="findHandlers" resultType="com.cloudhandson.vpdbackoffice.domain.ords.OrdsHandlerView">
|
||||
SELECT s.pattern AS schema_name,
|
||||
m.name AS module_name,
|
||||
m.uri_prefix AS base_path,
|
||||
t.uri_template AS template,
|
||||
h.method,
|
||||
h.source_type,
|
||||
DBMS_LOB.SUBSTR(h.source, 12000, 1) AS source,
|
||||
(
|
||||
SELECT LISTAGG(p.name || ' -> :' || p.bind_variable_name || ' (' || p.source_type || ')', CHR(10))
|
||||
WITHIN GROUP (ORDER BY p.name)
|
||||
FROM dba_ords_parameters p
|
||||
WHERE p.handler_id = h.id
|
||||
) AS parameters,
|
||||
CAST(NULL AS VARCHAR2(1)) AS package_source
|
||||
FROM dba_ords_schemas s
|
||||
JOIN dba_ords_modules m ON m.schema_id = s.id
|
||||
JOIN dba_ords_templates t ON t.module_id = m.id
|
||||
JOIN dba_ords_handlers h ON h.template_id = t.id
|
||||
WHERE s.parsing_schema = 'CB_ORDS'
|
||||
ORDER BY m.name, t.uri_template, h.method
|
||||
</select>
|
||||
|
||||
<select id="findHandlerPackageSource" resultType="string">
|
||||
SELECT LISTAGG(text, '') WITHIN GROUP (ORDER BY line)
|
||||
FROM all_source
|
||||
WHERE owner = 'CB_ORDS'
|
||||
AND name = 'CB_ORDS_HANDLER_PKG'
|
||||
AND type = 'PACKAGE BODY'
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -46,7 +46,28 @@
|
||||
SELECT LISTAGG(pc.column_name, ', ') WITHIN GROUP (ORDER BY pc.column_name)
|
||||
FROM cb_permission_column pc
|
||||
WHERE pc.permission_id = p.perm_id
|
||||
) AS visible_columns
|
||||
) AS visible_columns,
|
||||
(
|
||||
SELECT LISTAGG(
|
||||
CASE
|
||||
WHEN pr2.rule_type = 'ALL' THEN 'ALL ROWS'
|
||||
WHEN pr2.rule_type = 'MY_DEPT' THEN pr2.rule_column || ' = SYS_CONTEXT(CB_AGENT_CTX.DEPT_CODE)'
|
||||
WHEN pr2.rule_type = 'SELF' THEN pr2.rule_column || ' = SYS_CONTEXT(CB_AGENT_CTX.EMP_NO)'
|
||||
ELSE pr2.rule_column || ' ' || pr2.rule_type || ' ' || pr2.rule_value
|
||||
END,
|
||||
CHR(10) || 'AND '
|
||||
) WITHIN GROUP (ORDER BY pr2.rule_id)
|
||||
FROM cb_permission_rule pr2
|
||||
WHERE pr2.perm_id = p.perm_id
|
||||
) AS filter_preview,
|
||||
(
|
||||
SELECT CASE
|
||||
WHEN COUNT(*) = 0 THEN '모든 민감 컬럼 NULL 처리'
|
||||
ELSE 'NULL 제외: ' || LISTAGG(pc2.column_name, ', ') WITHIN GROUP (ORDER BY pc2.column_name)
|
||||
END
|
||||
FROM cb_permission_column pc2
|
||||
WHERE pc2.permission_id = p.perm_id
|
||||
) AS null_policy_preview
|
||||
FROM cb_permission p
|
||||
JOIN cb_app_role r ON r.role_id = p.role_id
|
||||
LEFT JOIN cb_protected_object o ON o.object_name = p.target_name
|
||||
|
||||
@@ -347,6 +347,14 @@ body {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.table-pre {
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font-size: .78rem;
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.form-grid .span-2 {
|
||||
grid-column: span 1;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<a class="nav-link" href="/objects">테이블/뷰</a>
|
||||
<a class="nav-link" href="/tokens">토큰</a>
|
||||
<a class="nav-link" href="/probe">ORDS 검증</a>
|
||||
<a class="nav-link" href="/ords-handlers">ORDS 핸들러</a>
|
||||
<a class="nav-link" href="/settings">설정</a>
|
||||
</div>
|
||||
<form method="post" action="/logout" class="ms-auto">
|
||||
|
||||
39
src/main/resources/templates/ords-handlers.html
Normal file
39
src/main/resources/templates/ords-handlers.html
Normal file
@@ -0,0 +1,39 @@
|
||||
<!doctype html>
|
||||
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
|
||||
<head th:replace="~{fragments/layout :: head('ORDS 핸들러')}"></head>
|
||||
<body>
|
||||
<nav th:replace="~{fragments/layout :: nav}"></nav>
|
||||
<main class="container py-4">
|
||||
<div class="page-title">
|
||||
<h1>ORDS 핸들러</h1>
|
||||
<p>Bearer Token을 DB 컨텍스트로 변환하는 ORDS Handler와 PL/SQL 흐름을 확인합니다.</p>
|
||||
</div>
|
||||
|
||||
<section class="content-band" th:each="handler : ${handlers}">
|
||||
<div class="section-heading">
|
||||
<h2 th:text="${handler.method() + ' ' + handler.fullPath()}">POST /ords/...</h2>
|
||||
<span class="badge text-bg-secondary" th:text="${handler.sourceType()}">plsql</span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span>Schema: <strong th:text="${handler.schemaName()}">CB_ORDS</strong></span>
|
||||
<span>Module: <strong th:text="${handler.moduleName()}">cb.agent.security</strong></span>
|
||||
<span>Template: <strong th:text="${handler.template()}">vpd/documents</strong></span>
|
||||
</div>
|
||||
<div class="probe-exchange-grid">
|
||||
<section class="probe-exchange">
|
||||
<h3>Header Bindings</h3>
|
||||
<pre th:text="${handler.parameters()} ?: 'No parameters'"></pre>
|
||||
</section>
|
||||
<section class="probe-exchange">
|
||||
<h3>Handler Source</h3>
|
||||
<pre th:text="${handler.source()}"></pre>
|
||||
</section>
|
||||
<section class="probe-exchange span-2">
|
||||
<h3>Bearer Token -> User Context Package</h3>
|
||||
<pre th:text="${handler.packageSource()}"></pre>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -79,6 +79,7 @@
|
||||
<th>테이블/뷰</th>
|
||||
<th>Action</th>
|
||||
<th>행 규칙</th>
|
||||
<th>적용 필터</th>
|
||||
<th>NULL 제외 컬럼</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
@@ -90,6 +91,7 @@
|
||||
<td th:text="${permission.objectName()}">CB_V_SEARCH_DOCUMENTS</td>
|
||||
<td th:text="${permission.action()}">SELECT</td>
|
||||
<td th:text="${permission.rules()} ?: '-'">ALL</td>
|
||||
<td><pre class="table-pre" th:text="${permission.filterPreview()} ?: '-'">ALL ROWS</pre></td>
|
||||
<td th:text="${permission.visibleColumns()} ?: '-'">CONTENTS</td>
|
||||
<td>
|
||||
<form method="post" action="/permissions/delete" class="inline-form">
|
||||
@@ -100,7 +102,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr th:if="${#lists.isEmpty(permissions)}">
|
||||
<td colspan="7" class="text-muted">등록된 권한이 없습니다.</td>
|
||||
<td colspan="8" class="text-muted">등록된 권한이 없습니다.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Reference in New Issue
Block a user