301 lines
16 KiB
HTML
301 lines
16 KiB
HTML
<!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>
|
|
<details class="explanation-details">
|
|
<summary>도움말</summary>
|
|
<p>VPD policy function이 참조할 사용자, 역할, 행 규칙, 컬럼 원문 허용 규칙을 저장합니다. KB 조건 코드는 토큰 context 값으로 치환되고, 정적 SQL 조건은 대상 객체의 WHERE 절에 그대로 추가됩니다. 한 권한의 규칙은 AND, 서로 다른 ALLOW 권한은 OR로 합치며 DENY는 허용 결과에서 제외합니다.</p>
|
|
</details>
|
|
</div>
|
|
|
|
<div class="alert alert-success" th:if="${message}" th:text="${message}"></div>
|
|
<div class="alert alert-danger" th:if="${error}" th:text="${error}"></div>
|
|
|
|
<section class="content-band">
|
|
<div class="section-heading">
|
|
<div>
|
|
<h2>새 접근 규칙</h2>
|
|
<p class="section-description">저장 전 적용 대상과 행 범위를 검토한 뒤 VPD 권한을 반영합니다.</p>
|
|
</div>
|
|
<div class="wizard-progress" aria-label="권한 추가 단계">
|
|
<button class="wizard-step-indicator active" type="button" data-wizard-target="1">역할</button>
|
|
<button class="wizard-step-indicator" type="button" data-wizard-target="2">대상</button>
|
|
<button class="wizard-step-indicator" type="button" data-wizard-target="3">접근 범위</button>
|
|
<button class="wizard-step-indicator" type="button" data-wizard-target="4">표시 예외</button>
|
|
<button class="wizard-step-indicator" type="button" data-wizard-target="5">검토</button>
|
|
</div>
|
|
</div>
|
|
<form method="post" action="/permissions" class="permission-wizard" data-permission-wizard>
|
|
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
|
<div class="wizard-validation" data-wizard-validation role="alert" hidden></div>
|
|
<aside class="wizard-summary" aria-live="polite">
|
|
<div>
|
|
<span>역할</span>
|
|
<strong data-wizard-summary="role">선택 전</strong>
|
|
</div>
|
|
<div>
|
|
<span>객체</span>
|
|
<strong data-wizard-summary="object">선택 전</strong>
|
|
</div>
|
|
<div>
|
|
<span>효과</span>
|
|
<strong data-wizard-summary="effect">ALLOW</strong>
|
|
</div>
|
|
<div>
|
|
<span>행 규칙</span>
|
|
<strong data-wizard-summary="rules">ALL</strong>
|
|
</div>
|
|
<div>
|
|
<span>적용 대상</span>
|
|
<strong data-wizard-summary="affected">역할 선택 후 확인</strong>
|
|
</div>
|
|
<div>
|
|
<span>저장 상태</span>
|
|
<strong data-wizard-summary="readiness">역할과 객체를 선택하세요</strong>
|
|
</div>
|
|
</aside>
|
|
|
|
<div class="wizard-panel active" data-wizard-step="1">
|
|
<div class="wizard-panel-heading">
|
|
<div>
|
|
<h3>역할 선택</h3>
|
|
<p>권한을 부여하거나 거부할 역할을 선택합니다.</p>
|
|
</div>
|
|
</div>
|
|
<label>
|
|
역할
|
|
<select class="form-select" name="roleId" required>
|
|
<option value="" selected disabled>권한을 적용할 역할 선택</option>
|
|
<option th:each="role : ${roles}"
|
|
th:value="${role.roleId()}"
|
|
th:text="${role.roleName()}"
|
|
th:attr="data-max-sensitivity=${role.maxSensitivityLevel()},
|
|
data-direct-users=${#strings.listJoin(directUsersByRole[role.roleId()], '|')},
|
|
data-groups=${#strings.listJoin(groupsByRole[role.roleId()], '|')},
|
|
data-group-users=${#strings.listJoin(groupUsersByRole[role.roleId()], '|')}"></option>
|
|
</select>
|
|
</label>
|
|
<details class="explanation-details">
|
|
<summary>역할 선택 기준 보기</summary>
|
|
<p class="wizard-hint">선택한 역할의 표시 보호 등급 상한(참고)도 preview에 반영됩니다. 행을 볼 수 있는 권한과는 별개입니다.</p>
|
|
</details>
|
|
<div class="effective-preview">
|
|
<div class="field-block-title">선택 역할 영향도</div>
|
|
<dl>
|
|
<div><dt>직접 사용자</dt><dd data-preview="directUsers">-</dd></div>
|
|
<div><dt>역할 보유 그룹</dt><dd data-preview="roleGroups">-</dd></div>
|
|
<div><dt>그룹 상속 사용자</dt><dd data-preview="groupUsers">-</dd></div>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="wizard-panel" data-wizard-step="2">
|
|
<div class="wizard-panel-heading">
|
|
<div>
|
|
<h3>보호 객체 선택</h3>
|
|
<p>등록된 보호 객체 또는 DB 스키마 객체를 선택합니다.</p>
|
|
</div>
|
|
</div>
|
|
<label>
|
|
보호 객체
|
|
<select class="form-select" name="objectRef" required>
|
|
<option value="" selected disabled>권한을 적용할 보호 객체 선택</option>
|
|
<optgroup label="등록된 보호 객체">
|
|
<option th:each="object : ${objects}"
|
|
th:value="${'protected:' + object.objectId()}"
|
|
th:attr="data-object-display=${object.displayName()},
|
|
data-columns=${#strings.listJoin(columnsByObject[object.objectId()], ',')},
|
|
data-maskable-columns=${#strings.listJoin(maskableColumnsByObject[object.objectId()], ',')},
|
|
data-maskable-labels=${#strings.listJoin(maskableColumnLabelsByObject[object.objectId()], '|')}"
|
|
th:text="${object.displayName()}"></option>
|
|
</optgroup>
|
|
<optgroup label="DB 스키마 객체">
|
|
<option th:each="dbObject : ${dbObjects}"
|
|
th:value="${'db:' + dbObject.value()}"
|
|
th:attr="data-object-display=${dbObject.value()}"
|
|
th:text="${dbObject.label()}"></option>
|
|
</optgroup>
|
|
</select>
|
|
</label>
|
|
<details class="explanation-details">
|
|
<summary>객체 선택 기준 보기</summary>
|
|
<p class="wizard-hint">DB 스키마 객체를 선택하면 저장 시 보호 객체가 자동 등록됩니다.</p>
|
|
</details>
|
|
<div class="effective-preview">
|
|
<div class="field-block-title">선택 객체 컬럼 (표시 보호 참고)</div>
|
|
<dl>
|
|
<div><dt>전체 컬럼</dt><dd data-preview="objectColumns">-</dd></div>
|
|
<div><dt>표시 보호 대상</dt><dd data-preview="maskableColumns">-</dd></div>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="wizard-panel" data-wizard-step="3">
|
|
<div class="wizard-panel-heading">
|
|
<div>
|
|
<h3>권한 효과와 행 규칙</h3>
|
|
<p>허용/거부 방향과 VPD 행 필터 조건을 설정합니다.</p>
|
|
</div>
|
|
</div>
|
|
<label>
|
|
권한 효과
|
|
<select class="form-select" name="permissionEffect" required>
|
|
<option value="ALLOW">허용</option>
|
|
<option value="DENY">거부</option>
|
|
</select>
|
|
</label>
|
|
<div>
|
|
<div class="field-block-title">행 규칙</div>
|
|
<p class="rule-safety-note"><strong>주의:</strong> <code>ALL</code>은 전체 행을 뜻합니다. <code>ALL</code>과 다른 조건 규칙은 한 권한에 함께 저장할 수 없습니다.</p>
|
|
<div id="rowRuleList" class="rule-list">
|
|
<div class="rule-row">
|
|
<select class="form-select rule-column-select" name="ruleColumn">
|
|
<option value="">객체 컬럼 선택</option>
|
|
</select>
|
|
<select class="form-select rule-type-select" name="ruleType">
|
|
<option value="ALL">ALL</option>
|
|
<optgroup label="KB · 본인 범위">
|
|
<option value="OWN_CONTRACT">본인 담당 계약</option>
|
|
<option value="OWN_CUSTOMER">본인 담당 고객</option>
|
|
</optgroup>
|
|
<optgroup label="KB · 채널 범위">
|
|
<option value="CHANNEL_CONTRACT">토큰 채널 계약</option>
|
|
<option value="CHANNEL_CUSTOMER">토큰 채널 고객</option>
|
|
</optgroup>
|
|
<optgroup label="KB · 토큰 식별">
|
|
<option value="TOKEN_SUBJECT">토큰 사용자 본인</option>
|
|
</optgroup>
|
|
<optgroup label="SQL · 정적 조건">
|
|
<option value="STATIC_SQL">정적 SQL 조건식</option>
|
|
</optgroup>
|
|
</select>
|
|
<input class="form-control" name="ruleValue" maxlength="1000" placeholder="비교 값">
|
|
<button class="btn btn-outline-secondary" type="button" data-rule-add>추가</button>
|
|
</div>
|
|
</div>
|
|
<details class="explanation-details">
|
|
<summary>행 규칙의 두 가지 적용 방식 보기</summary>
|
|
<p class="wizard-hint"><strong>조건 코드</strong>는 토큰 context로 치환됩니다. 예를 들어 <code>본인 담당 고객 / CUST_ID</code>는 계약원장에서 토큰 사용자 ID의 담당 고객을 찾아 현재 객체의 <code>CUST_ID</code>에 적용합니다. <strong>정적 SQL 조건식</strong>은 <code>CONTRACT_STATUS = '정상'</code>처럼 현재 객체 컬럼을 사용한 WHERE 절을 그대로 추가합니다. 한 권한 안의 규칙은 모두 AND로 좁혀지고, 서로 다른 역할의 ALLOW 권한은 OR로 합쳐집니다. 역할명은 VPD WHERE 절에 직접 들어가지 않습니다.</p>
|
|
</details>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="wizard-panel" data-wizard-step="4">
|
|
<div class="wizard-panel-heading">
|
|
<div>
|
|
<h3>권한별 원문 표시 예외</h3>
|
|
<p>이 역할이 이미 볼 수 있는 행에서 마스킹을 제외할 컬럼을 여러 개 선택합니다.</p>
|
|
</div>
|
|
</div>
|
|
<div class="masking-column-picker">
|
|
<div class="field-block-title">선택 가능한 표시 보호 컬럼</div>
|
|
<div class="question-presets" data-maskable-column-list>
|
|
<span class="text-muted small">보호 객체를 선택하면 마스킹 대상 컬럼이 표시됩니다.</span>
|
|
</div>
|
|
</div>
|
|
<label>
|
|
원문 표시 허용(마스킹 제외) 컬럼 · 여러 개 가능
|
|
<input class="form-control" name="visibleColumns" placeholder="예: CONTENTS, SOURCE_URI">
|
|
<span class="selected-column-list" data-selected-visible-columns aria-live="polite"></span>
|
|
</label>
|
|
<details class="explanation-details">
|
|
<summary>원문 표시 허용 컬럼 안내 보기</summary>
|
|
<p class="wizard-hint">쉼표로 여러 컬럼을 등록하거나 위 버튼을 여러 번 누르세요. 등록하지 않은 민감 컬럼은 기본 표시 보호를 유지합니다. 행을 볼 수 있는지는 이 목록이 아니라 권한 규칙과 VPD가 결정합니다.</p>
|
|
</details>
|
|
</div>
|
|
|
|
<div class="wizard-panel" data-wizard-step="5">
|
|
<div class="wizard-panel-heading">
|
|
<div>
|
|
<h3>저장 전 검토</h3>
|
|
<p>저장될 권한과 예상 effective policy를 확인합니다.</p>
|
|
</div>
|
|
</div>
|
|
<div class="policy-preview" data-policy-preview>
|
|
<dl>
|
|
<div><dt>역할</dt><dd data-preview="role">-</dd></div>
|
|
<div><dt>표시 보호 등급 상한(참고)</dt><dd data-preview="sensitivity">-</dd></div>
|
|
<div><dt>보호 객체</dt><dd data-preview="object">-</dd></div>
|
|
<div><dt>적용 대상</dt><dd data-preview="affectedPrincipals">-</dd></div>
|
|
<div><dt>객체 컬럼</dt><dd data-preview="objectColumnsFinal">-</dd></div>
|
|
<div><dt>행 접근</dt><dd data-preview="rowPolicy">-</dd></div>
|
|
<div><dt>VPD predicate 예상</dt><dd data-preview="predicatePreview">-</dd></div>
|
|
<div><dt>권한별 컬럼 마스킹</dt><dd data-preview="columnPolicy">-</dd></div>
|
|
<div><dt>NULL 처리 예상</dt><dd data-preview="nullPolicy">-</dd></div>
|
|
<div class="policy-preview-emphasis"><dt>저장 영향</dt><dd data-preview="saveGuard">-</dd></div>
|
|
<div><dt>되돌리기</dt><dd>저장 후 아래 권한 목록에서 삭제할 수 있습니다. 이 객체의 마지막 권한을 삭제하면 보호 객체가 비활성화될 수 있습니다.</dd></div>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="wizard-actions">
|
|
<button class="btn btn-outline-secondary" type="button" data-wizard-prev disabled>이전</button>
|
|
<button class="btn btn-outline-primary" type="button" data-wizard-next>다음</button>
|
|
<button class="btn btn-primary" type="submit" data-wizard-submit hidden>저장</button>
|
|
</div>
|
|
</form>
|
|
</section>
|
|
|
|
<section class="content-band">
|
|
<h2>접근 규칙 목록</h2>
|
|
<p class="section-subtitle">한 접근 규칙 안에서는 조건 코드와 정적 SQL 조건을 AND로 합쳐 범위를 좁힙니다. 같은 보호 대상의 서로 다른 ALLOW 권한은 OR, DENY 권한은 허용 결과에서 제외됩니다.</p>
|
|
<div class="table-responsive">
|
|
<table class="table table-sm align-middle permission-list-table">
|
|
<thead>
|
|
<tr>
|
|
<th>역할</th>
|
|
<th>보호 대상</th>
|
|
<th>효과</th>
|
|
<th>접근 범위</th>
|
|
<th>표시 예외</th>
|
|
<th>관리</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr th:each="permission : ${permissions}"
|
|
th:with="lastPermission=${lastPermissionByPermissionId[permission.permissionId()]}">
|
|
<td th:text="${permission.roleName()}">HR_DEPT_ROLE</td>
|
|
<td th:text="${permission.objectName()}">CB_V_SEARCH_DOCUMENTS</td>
|
|
<td>
|
|
<span class="badge"
|
|
th:classappend="${permission.permissionEffect() == 'DENY'} ? ' text-bg-danger' : ' text-bg-success'"
|
|
th:text="${permission.permissionEffect()}">ALLOW</span>
|
|
</td>
|
|
<td><pre class="table-pre" th:text="${permission.filterPreview() ?: permission.rules() ?: '-'}">ALL ROWS</pre></td>
|
|
<td th:text="${permission.visibleColumns()} ?: '-'">CONTENTS</td>
|
|
<td>
|
|
<details class="row-management">
|
|
<summary>관리</summary>
|
|
<div class="delete-impact">
|
|
<span>이 역할은 이 대상의 SELECT 권한을 잃습니다.</span>
|
|
<strong th:text="${permission.roleName() + ' → ' + permission.objectName()}">ROLE → OBJECT</strong>
|
|
<small th:if="${lastPermission}" class="text-danger">마지막 권한입니다. 삭제 후 보호 대상이 비활성화될 수 있습니다.</small>
|
|
<small th:unless="${lastPermission}">같은 대상에 다른 역할 권한이 남아 있습니다.</small>
|
|
</div>
|
|
<form method="post" action="/permissions/delete" class="inline-form mt-2">
|
|
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
|
<input type="hidden" name="permissionId" th:value="${permission.permissionId()}">
|
|
<label class="delete-confirm">
|
|
<input type="checkbox" name="confirmImpact" value="true" required>
|
|
영향 확인
|
|
</label>
|
|
<button class="btn btn-sm btn-outline-danger" type="submit">삭제</button>
|
|
</form>
|
|
</details>
|
|
</td>
|
|
</tr>
|
|
<tr th:if="${#lists.isEmpty(permissions)}">
|
|
<td colspan="6" class="text-muted">등록된 접근 규칙이 없습니다.</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
</body>
|
|
</html>
|