fix #493: add effective access matrix
This commit is contained in:
@@ -587,6 +587,57 @@ body {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.matrix-summary {
|
||||
display: grid;
|
||||
gap: .75rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(140px, 100%), 1fr));
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.matrix-summary > div {
|
||||
background: var(--rw-surface-muted);
|
||||
border: 1px solid var(--rw-border);
|
||||
border-radius: 8px;
|
||||
padding: .85rem;
|
||||
}
|
||||
|
||||
.matrix-summary span {
|
||||
color: var(--rw-muted);
|
||||
display: block;
|
||||
font-size: .78rem;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.matrix-summary strong {
|
||||
display: block;
|
||||
font-size: 1.6rem;
|
||||
line-height: 1.1;
|
||||
margin-top: .25rem;
|
||||
}
|
||||
|
||||
.setup-steps {
|
||||
color: var(--rw-muted);
|
||||
margin: 0;
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
|
||||
.setup-steps li + li {
|
||||
margin-top: .25rem;
|
||||
}
|
||||
|
||||
.matrix-filter {
|
||||
color: var(--rw-muted);
|
||||
font-size: .82rem;
|
||||
font-weight: 700;
|
||||
min-width: min(280px, 100%);
|
||||
}
|
||||
|
||||
.matrix-list {
|
||||
max-width: 28rem;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.policy-apply-flow {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
@@ -486,6 +486,38 @@ function initTokenContextPreviews() {
|
||||
});
|
||||
}
|
||||
|
||||
function filterEffectiveMatrixTable(select) {
|
||||
const table = document.getElementById(select.dataset.effectiveFilter || '');
|
||||
if (!table) {
|
||||
return;
|
||||
}
|
||||
const value = select.value || '';
|
||||
table.querySelectorAll('.effective-empty-row').forEach((row) => row.remove());
|
||||
let visibleRows = 0;
|
||||
table.querySelectorAll('tbody tr[data-effective-id]').forEach((row) => {
|
||||
const visible = !value || row.dataset.effectiveId === value;
|
||||
row.hidden = !visible;
|
||||
if (visible) {
|
||||
visibleRows += 1;
|
||||
}
|
||||
});
|
||||
if (value && visibleRows === 0) {
|
||||
const body = table.querySelector('tbody');
|
||||
const columnCount = table.querySelectorAll('thead th').length || 1;
|
||||
const row = document.createElement('tr');
|
||||
row.className = 'effective-empty-row';
|
||||
row.innerHTML = `<td colspan="${columnCount}" class="text-muted">${table.dataset.emptyMessage || '선택한 항목이 없습니다.'}</td>`;
|
||||
body?.appendChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
function initEffectiveMatrixFilters() {
|
||||
document.querySelectorAll('[data-effective-filter]').forEach((select) => {
|
||||
select.addEventListener('change', () => filterEffectiveMatrixTable(select));
|
||||
filterEffectiveMatrixTable(select);
|
||||
});
|
||||
}
|
||||
|
||||
function sqlLiteral(value) {
|
||||
return `'${String(value || '').replaceAll("'", "''")}'`;
|
||||
}
|
||||
@@ -791,6 +823,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
filterGroupDetail(select.id);
|
||||
});
|
||||
initTokenContextPreviews();
|
||||
initEffectiveMatrixFilters();
|
||||
const objectSelect = document.querySelector('select[name="objectRef"]');
|
||||
if (objectSelect) {
|
||||
objectSelect.addEventListener('change', () => {
|
||||
|
||||
201
src/main/resources/templates/effective-matrix.html
Normal file
201
src/main/resources/templates/effective-matrix.html
Normal file
@@ -0,0 +1,201 @@
|
||||
<!doctype html>
|
||||
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
|
||||
<head th:replace="~{fragments/layout :: head('유효 권한 매트릭스')}"></head>
|
||||
<body>
|
||||
<nav th:replace="~{fragments/layout :: nav}"></nav>
|
||||
<main class="container py-4">
|
||||
<div class="page-title">
|
||||
<h1>유효 권한 매트릭스</h1>
|
||||
<p>백오피스 사용자, 그룹, 역할, 권한의 최종 상속 결과를 확인합니다.</p>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning" th:if="${runtimeError}">
|
||||
<strong th:text="${runtimeError.title()}">DB 연결 설정이 필요합니다.</strong>
|
||||
<span th:text="${runtimeError.message()}">message</span>
|
||||
<div th:if="${runtimeError.showSupportCommand()}">
|
||||
<code>./run.sh backoffice-support</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section th:replace="~{fragments/layout :: architectureStrip('permission')}"></section>
|
||||
|
||||
<section class="content-band">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<h2>권한 해석 기준</h2>
|
||||
<p class="section-subtitle">여기의 사용자는 Oracle DB schema user가 아니라 Bearer Token으로 식별되는 백오피스 application user입니다.</p>
|
||||
</div>
|
||||
<a class="btn btn-sm rw-btn-secondary" href="/permissions">권한 관리로 이동</a>
|
||||
</div>
|
||||
<div class="matrix-summary">
|
||||
<div>
|
||||
<span>사용자</span>
|
||||
<strong th:text="${#lists.size(matrix.users())}">0</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>그룹</span>
|
||||
<strong th:text="${#lists.size(matrix.groups())}">0</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>역할</span>
|
||||
<strong th:text="${#lists.size(matrix.roles())}">0</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>권한</span>
|
||||
<strong th:text="${matrix.permissionCount()}">0</strong>
|
||||
</div>
|
||||
</div>
|
||||
<ol class="setup-steps">
|
||||
<li>사용자 관리에서 application user를 등록합니다.</li>
|
||||
<li>그룹 관리에서 사용자를 그룹에 넣고 그룹에 역할을 부여합니다.</li>
|
||||
<li>권한 관리에서 역할에 TABLE/VIEW 행 규칙과 컬럼 NULL 정책을 부여합니다.</li>
|
||||
<li>토큰을 발급하면 ORDS handler가 token user context를 설정하고 VPD가 이 권한을 적용합니다.</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section class="content-band">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<h2>사용자 기준</h2>
|
||||
<p class="section-subtitle">직접 역할과 그룹을 통해 상속된 역할을 분리해서 봅니다.</p>
|
||||
</div>
|
||||
<label class="matrix-filter">
|
||||
사용자
|
||||
<select class="form-select form-select-sm" data-effective-filter="user-effective-table">
|
||||
<option value="">전체 사용자</option>
|
||||
<option th:each="row : ${matrix.users()}" th:value="${row.userId()}" th:text="${row.username()}"></option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle" id="user-effective-table" data-empty-message="선택한 사용자에 대한 유효 권한이 없습니다. 사용자 등록, 그룹 배정, 역할 부여 순서로 확인하세요.">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>사용자</th>
|
||||
<th>직접 역할</th>
|
||||
<th>소속 그룹</th>
|
||||
<th>그룹 상속 역할</th>
|
||||
<th>최종 역할</th>
|
||||
<th>권한</th>
|
||||
<th>보호 객체</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="row : ${matrix.users()}" th:attr="data-effective-id=${row.userId()}">
|
||||
<td>
|
||||
<strong th:text="${row.username()}">agent_hr</strong>
|
||||
<div class="text-muted small" th:text="${row.empNo() + ' / ' + row.deptCode()}">E100 / HR</div>
|
||||
<span class="badge" th:classappend="${row.active()} ? ' text-bg-success' : ' text-bg-secondary'"
|
||||
th:text="${row.active()} ? 'ACTIVE APP USER' : 'INACTIVE APP USER'">ACTIVE</span>
|
||||
</td>
|
||||
<td th:text="${#lists.isEmpty(row.directRoles()) ? '-' : #strings.listJoin(row.directRoles(), ', ')}">-</td>
|
||||
<td th:text="${#lists.isEmpty(row.groups()) ? '-' : #strings.listJoin(row.groups(), ', ')}">-</td>
|
||||
<td th:text="${#lists.isEmpty(row.inheritedRoles()) ? '-' : #strings.listJoin(row.inheritedRoles(), ', ')}">-</td>
|
||||
<td><strong th:text="${#lists.isEmpty(row.effectiveRoles()) ? '-' : #strings.listJoin(row.effectiveRoles(), ', ')}">-</strong></td>
|
||||
<td><span class="badge text-bg-secondary" th:text="${row.permissionCount()}">0</span></td>
|
||||
<td class="matrix-list" th:text="${#lists.isEmpty(row.objectNames()) ? '권한 객체 없음' : #strings.listJoin(row.objectNames(), ', ')}">objects</td>
|
||||
</tr>
|
||||
<tr th:if="${#lists.isEmpty(matrix.users())}">
|
||||
<td colspan="7" class="text-muted">등록된 사용자가 없습니다. 사용자 관리에서 application user를 먼저 등록하세요.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="content-band">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<h2>그룹 기준</h2>
|
||||
<p class="section-subtitle">그룹에 속한 사용자와 그룹에 부여된 역할이 제공하는 권한을 봅니다.</p>
|
||||
</div>
|
||||
<label class="matrix-filter">
|
||||
그룹
|
||||
<select class="form-select form-select-sm" data-effective-filter="group-effective-table">
|
||||
<option value="">전체 그룹</option>
|
||||
<option th:each="row : ${matrix.groups()}" th:value="${row.groupId()}" th:text="${row.groupCode() + ' / ' + row.groupName()}"></option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle" id="group-effective-table" data-empty-message="선택한 그룹의 사용자/역할 매핑이 없습니다. 그룹 사용자와 그룹 역할을 먼저 등록하세요.">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>그룹</th>
|
||||
<th>포함 사용자</th>
|
||||
<th>부여 역할</th>
|
||||
<th>권한</th>
|
||||
<th>보호 객체</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="row : ${matrix.groups()}" th:attr="data-effective-id=${row.groupId()}">
|
||||
<td>
|
||||
<code th:text="${row.groupCode()}">GROUP</code>
|
||||
<div th:text="${row.groupName()}">그룹명</div>
|
||||
<span class="badge" th:classappend="${row.active()} ? ' text-bg-success' : ' text-bg-secondary'"
|
||||
th:text="${row.active()} ? 'ACTIVE' : 'INACTIVE'">ACTIVE</span>
|
||||
</td>
|
||||
<td th:text="${#lists.isEmpty(row.users()) ? '사용자 없음' : #strings.listJoin(row.users(), ', ')}">users</td>
|
||||
<td th:text="${#lists.isEmpty(row.roles()) ? '역할 없음' : #strings.listJoin(row.roles(), ', ')}">roles</td>
|
||||
<td><span class="badge text-bg-secondary" th:text="${row.permissionCount()}">0</span></td>
|
||||
<td class="matrix-list" th:text="${#lists.isEmpty(row.objectNames()) ? '권한 객체 없음' : #strings.listJoin(row.objectNames(), ', ')}">objects</td>
|
||||
</tr>
|
||||
<tr th:if="${#lists.isEmpty(matrix.groups())}">
|
||||
<td colspan="5" class="text-muted">등록된 그룹이 없습니다. 그룹 관리에서 그룹을 먼저 생성하세요.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="content-band">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<h2>역할 기준</h2>
|
||||
<p class="section-subtitle">역할이 직접 사용자와 그룹 사용자에게 어떤 영향을 주는지 봅니다.</p>
|
||||
</div>
|
||||
<label class="matrix-filter">
|
||||
역할
|
||||
<select class="form-select form-select-sm" data-effective-filter="role-effective-table">
|
||||
<option value="">전체 역할</option>
|
||||
<option th:each="row : ${matrix.roles()}" th:value="${row.roleId()}" th:text="${row.roleName()}"></option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle" id="role-effective-table" data-empty-message="선택한 역할의 사용자/그룹/권한 연결이 없습니다. 사용자 역할, 그룹 역할, 권한 등록을 확인하세요.">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>역할</th>
|
||||
<th>직접 사용자</th>
|
||||
<th>연결 그룹</th>
|
||||
<th>그룹 상속 사용자</th>
|
||||
<th>영향 사용자</th>
|
||||
<th>권한</th>
|
||||
<th>보호 객체</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="row : ${matrix.roles()}" th:attr="data-effective-id=${row.roleId()}">
|
||||
<td>
|
||||
<strong th:text="${row.roleName()}">ROLE</strong>
|
||||
<div class="text-muted small">민감도 <span th:text="${row.maxSensitivityLevel()}">PUBLIC</span></div>
|
||||
</td>
|
||||
<td th:text="${#lists.isEmpty(row.directUsers()) ? '직접 사용자 없음' : #strings.listJoin(row.directUsers(), ', ')}">users</td>
|
||||
<td th:text="${#lists.isEmpty(row.groups()) ? '그룹 없음' : #strings.listJoin(row.groups(), ', ')}">groups</td>
|
||||
<td th:text="${#lists.isEmpty(row.inheritedUsers()) ? '상속 사용자 없음' : #strings.listJoin(row.inheritedUsers(), ', ')}">group users</td>
|
||||
<td><strong th:text="${#lists.isEmpty(row.affectedUsers()) ? '-' : #strings.listJoin(row.affectedUsers(), ', ')}">affected</strong></td>
|
||||
<td><span class="badge text-bg-secondary" th:text="${row.permissionCount()}">0</span></td>
|
||||
<td class="matrix-list" th:text="${#lists.isEmpty(row.objectNames()) ? '권한 객체 없음' : #strings.listJoin(row.objectNames(), ', ')}">objects</td>
|
||||
</tr>
|
||||
<tr th:if="${#lists.isEmpty(matrix.roles())}">
|
||||
<td colspan="7" class="text-muted">등록된 역할이 없습니다. 역할 관리에서 역할을 먼저 생성하세요.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -21,6 +21,7 @@
|
||||
<a class="nav-link" href="/users">사용자</a>
|
||||
<a class="nav-link" href="/groups">그룹</a>
|
||||
<a class="nav-link" href="/roles">역할</a>
|
||||
<a class="nav-link" href="/effective-matrix">유효 권한</a>
|
||||
<a class="nav-link" href="/permissions">권한</a>
|
||||
<a class="nav-link" href="/tokens">토큰</a>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user