fix #493: add effective access matrix
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
package com.cloudhandson.vpdbackoffice.domain.effective;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record EffectiveMatrixView(
|
||||
List<UserEffectiveAccessView> users,
|
||||
List<GroupEffectiveAccessView> groups,
|
||||
List<RoleEffectiveImpactView> roles,
|
||||
int permissionCount
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.cloudhandson.vpdbackoffice.domain.effective;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record GroupEffectiveAccessView(
|
||||
long groupId,
|
||||
String groupCode,
|
||||
String groupName,
|
||||
boolean active,
|
||||
List<String> users,
|
||||
List<String> roles,
|
||||
int permissionCount,
|
||||
List<String> objectNames
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.cloudhandson.vpdbackoffice.domain.effective;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record RoleEffectiveImpactView(
|
||||
long roleId,
|
||||
String roleName,
|
||||
String maxSensitivityLevel,
|
||||
List<String> directUsers,
|
||||
List<String> groups,
|
||||
List<String> inheritedUsers,
|
||||
List<String> affectedUsers,
|
||||
int permissionCount,
|
||||
List<String> objectNames
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.cloudhandson.vpdbackoffice.domain.effective;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record UserEffectiveAccessView(
|
||||
long userId,
|
||||
String username,
|
||||
String empNo,
|
||||
String deptCode,
|
||||
boolean active,
|
||||
List<String> directRoles,
|
||||
List<String> groups,
|
||||
List<String> inheritedRoles,
|
||||
List<String> effectiveRoles,
|
||||
int permissionCount,
|
||||
List<String> objectNames
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
package com.cloudhandson.vpdbackoffice.service;
|
||||
|
||||
import com.cloudhandson.vpdbackoffice.domain.effective.EffectiveMatrixView;
|
||||
import com.cloudhandson.vpdbackoffice.domain.effective.GroupEffectiveAccessView;
|
||||
import com.cloudhandson.vpdbackoffice.domain.effective.RoleEffectiveImpactView;
|
||||
import com.cloudhandson.vpdbackoffice.domain.effective.UserEffectiveAccessView;
|
||||
import com.cloudhandson.vpdbackoffice.domain.group.AppGroup;
|
||||
import com.cloudhandson.vpdbackoffice.domain.group.GroupRoleView;
|
||||
import com.cloudhandson.vpdbackoffice.domain.group.GroupUserView;
|
||||
import com.cloudhandson.vpdbackoffice.domain.permission.AppRole;
|
||||
import com.cloudhandson.vpdbackoffice.domain.permission.PermissionView;
|
||||
import com.cloudhandson.vpdbackoffice.domain.user.AppUser;
|
||||
import com.cloudhandson.vpdbackoffice.domain.user.UserRoleView;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class EffectiveMatrixService {
|
||||
|
||||
private final UserService userService;
|
||||
private final GroupService groupService;
|
||||
private final PermissionService permissionService;
|
||||
|
||||
public EffectiveMatrixService(
|
||||
UserService userService,
|
||||
GroupService groupService,
|
||||
PermissionService permissionService
|
||||
) {
|
||||
this.userService = userService;
|
||||
this.groupService = groupService;
|
||||
this.permissionService = permissionService;
|
||||
}
|
||||
|
||||
public EffectiveMatrixView matrix() {
|
||||
List<AppUser> users = userService.findAll();
|
||||
List<AppGroup> groups = groupService.findAll();
|
||||
List<AppRole> roles = permissionService.findRoles();
|
||||
List<UserRoleView> userRoles = userService.findUserRoles();
|
||||
List<GroupUserView> groupUsers = groupService.findGroupUsers();
|
||||
List<GroupRoleView> groupRoles = groupService.findGroupRoles();
|
||||
List<PermissionView> permissions = permissionService.findPermissionViews();
|
||||
|
||||
Map<Long, Set<Long>> directRoleIdsByUser = directRoleIdsByUser(userRoles);
|
||||
Map<Long, List<String>> directRoleNamesByUser = directRoleNamesByUser(userRoles);
|
||||
Map<Long, Set<Long>> groupIdsByUser = groupIdsByUser(groupUsers);
|
||||
Map<Long, List<String>> groupNamesByUser = groupNamesByUser(groupUsers);
|
||||
Map<Long, List<String>> userNamesByGroup = userNamesByGroup(groupUsers);
|
||||
Map<Long, Set<Long>> roleIdsByGroup = roleIdsByGroup(groupRoles);
|
||||
Map<Long, List<String>> roleNamesByGroup = roleNamesByGroup(groupRoles);
|
||||
Map<Long, List<String>> groupNamesByRole = groupNamesByRole(groupRoles);
|
||||
Map<Long, List<String>> directUserNamesByRole = directUserNamesByRole(userRoles);
|
||||
Map<Long, List<PermissionView>> permissionsByRole = permissionsByRole(permissions);
|
||||
Map<Long, String> roleNameById = roleNameById(roles);
|
||||
|
||||
List<UserEffectiveAccessView> userViews = users.stream()
|
||||
.map(user -> userView(
|
||||
user,
|
||||
directRoleIdsByUser,
|
||||
directRoleNamesByUser,
|
||||
groupIdsByUser,
|
||||
groupNamesByUser,
|
||||
roleIdsByGroup,
|
||||
roleNameById,
|
||||
permissionsByRole
|
||||
))
|
||||
.toList();
|
||||
|
||||
List<GroupEffectiveAccessView> groupViews = groups.stream()
|
||||
.map(group -> groupView(group, userNamesByGroup, roleIdsByGroup, roleNamesByGroup, permissionsByRole))
|
||||
.toList();
|
||||
|
||||
List<RoleEffectiveImpactView> roleViews = roles.stream()
|
||||
.map(role -> roleView(role, directUserNamesByRole, groupNamesByRole, groupUsers, groupRoles, permissionsByRole))
|
||||
.toList();
|
||||
|
||||
return new EffectiveMatrixView(userViews, groupViews, roleViews, permissions.size());
|
||||
}
|
||||
|
||||
private UserEffectiveAccessView userView(
|
||||
AppUser user,
|
||||
Map<Long, Set<Long>> directRoleIdsByUser,
|
||||
Map<Long, List<String>> directRoleNamesByUser,
|
||||
Map<Long, Set<Long>> groupIdsByUser,
|
||||
Map<Long, List<String>> groupNamesByUser,
|
||||
Map<Long, Set<Long>> roleIdsByGroup,
|
||||
Map<Long, String> roleNameById,
|
||||
Map<Long, List<PermissionView>> permissionsByRole
|
||||
) {
|
||||
Set<Long> directRoleIds = directRoleIdsByUser.getOrDefault(user.userId(), Set.of());
|
||||
Set<Long> inheritedRoleIds = new LinkedHashSet<>();
|
||||
for (Long groupId : groupIdsByUser.getOrDefault(user.userId(), Set.of())) {
|
||||
inheritedRoleIds.addAll(roleIdsByGroup.getOrDefault(groupId, Set.of()));
|
||||
}
|
||||
|
||||
Set<Long> effectiveRoleIds = new LinkedHashSet<>(directRoleIds);
|
||||
effectiveRoleIds.addAll(inheritedRoleIds);
|
||||
|
||||
return new UserEffectiveAccessView(
|
||||
user.userId(),
|
||||
user.username(),
|
||||
user.empNo(),
|
||||
user.deptCode(),
|
||||
user.active(),
|
||||
directRoleNamesByUser.getOrDefault(user.userId(), List.of()),
|
||||
groupNamesByUser.getOrDefault(user.userId(), List.of()),
|
||||
names(inheritedRoleIds, roleNameById),
|
||||
names(effectiveRoleIds, roleNameById),
|
||||
permissionCount(effectiveRoleIds, permissionsByRole),
|
||||
objectNames(effectiveRoleIds, permissionsByRole)
|
||||
);
|
||||
}
|
||||
|
||||
private GroupEffectiveAccessView groupView(
|
||||
AppGroup group,
|
||||
Map<Long, List<String>> userNamesByGroup,
|
||||
Map<Long, Set<Long>> roleIdsByGroup,
|
||||
Map<Long, List<String>> roleNamesByGroup,
|
||||
Map<Long, List<PermissionView>> permissionsByRole
|
||||
) {
|
||||
Set<Long> roleIds = roleIdsByGroup.getOrDefault(group.groupId(), Set.of());
|
||||
return new GroupEffectiveAccessView(
|
||||
group.groupId(),
|
||||
group.groupCode(),
|
||||
group.groupName(),
|
||||
group.active(),
|
||||
userNamesByGroup.getOrDefault(group.groupId(), List.of()),
|
||||
roleNamesByGroup.getOrDefault(group.groupId(), List.of()),
|
||||
permissionCount(roleIds, permissionsByRole),
|
||||
objectNames(roleIds, permissionsByRole)
|
||||
);
|
||||
}
|
||||
|
||||
private RoleEffectiveImpactView roleView(
|
||||
AppRole role,
|
||||
Map<Long, List<String>> directUserNamesByRole,
|
||||
Map<Long, List<String>> groupNamesByRole,
|
||||
List<GroupUserView> groupUsers,
|
||||
List<GroupRoleView> groupRoles,
|
||||
Map<Long, List<PermissionView>> permissionsByRole
|
||||
) {
|
||||
Set<Long> roleIds = Set.of(role.roleId());
|
||||
Set<String> inheritedUsers = new LinkedHashSet<>();
|
||||
Set<Long> roleGroupIds = new LinkedHashSet<>();
|
||||
for (GroupRoleView groupRole : groupRoles) {
|
||||
if (groupRole.roleId() == role.roleId()) {
|
||||
roleGroupIds.add(groupRole.groupId());
|
||||
}
|
||||
}
|
||||
for (GroupUserView groupUser : groupUsers) {
|
||||
if (roleGroupIds.contains(groupUser.groupId())) {
|
||||
inheritedUsers.add(groupUser.username());
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> affectedUsers = new LinkedHashSet<>(directUserNamesByRole.getOrDefault(role.roleId(), List.of()));
|
||||
affectedUsers.addAll(inheritedUsers);
|
||||
|
||||
return new RoleEffectiveImpactView(
|
||||
role.roleId(),
|
||||
role.roleName(),
|
||||
role.maxSensitivityLevel(),
|
||||
directUserNamesByRole.getOrDefault(role.roleId(), List.of()),
|
||||
groupNamesByRole.getOrDefault(role.roleId(), List.of()),
|
||||
new ArrayList<>(inheritedUsers),
|
||||
new ArrayList<>(affectedUsers),
|
||||
permissionCount(roleIds, permissionsByRole),
|
||||
objectNames(roleIds, permissionsByRole)
|
||||
);
|
||||
}
|
||||
|
||||
private Map<Long, Set<Long>> directRoleIdsByUser(List<UserRoleView> userRoles) {
|
||||
Map<Long, Set<Long>> result = new LinkedHashMap<>();
|
||||
for (UserRoleView view : userRoles) {
|
||||
result.computeIfAbsent(view.userId(), key -> new LinkedHashSet<>()).add(view.roleId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<Long, List<String>> directRoleNamesByUser(List<UserRoleView> userRoles) {
|
||||
Map<Long, Set<String>> result = new LinkedHashMap<>();
|
||||
for (UserRoleView view : userRoles) {
|
||||
result.computeIfAbsent(view.userId(), key -> new LinkedHashSet<>()).add(view.roleName());
|
||||
}
|
||||
return toListMap(result);
|
||||
}
|
||||
|
||||
private Map<Long, Set<Long>> groupIdsByUser(List<GroupUserView> groupUsers) {
|
||||
Map<Long, Set<Long>> result = new LinkedHashMap<>();
|
||||
for (GroupUserView view : groupUsers) {
|
||||
result.computeIfAbsent(view.userId(), key -> new LinkedHashSet<>()).add(view.groupId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<Long, List<String>> groupNamesByUser(List<GroupUserView> groupUsers) {
|
||||
Map<Long, Set<String>> result = new LinkedHashMap<>();
|
||||
for (GroupUserView view : groupUsers) {
|
||||
result.computeIfAbsent(view.userId(), key -> new LinkedHashSet<>()).add(groupLabel(view.groupCode(), view.groupName()));
|
||||
}
|
||||
return toListMap(result);
|
||||
}
|
||||
|
||||
private Map<Long, List<String>> userNamesByGroup(List<GroupUserView> groupUsers) {
|
||||
Map<Long, Set<String>> result = new LinkedHashMap<>();
|
||||
for (GroupUserView view : groupUsers) {
|
||||
result.computeIfAbsent(view.groupId(), key -> new LinkedHashSet<>()).add(view.username());
|
||||
}
|
||||
return toListMap(result);
|
||||
}
|
||||
|
||||
private Map<Long, Set<Long>> roleIdsByGroup(List<GroupRoleView> groupRoles) {
|
||||
Map<Long, Set<Long>> result = new LinkedHashMap<>();
|
||||
for (GroupRoleView view : groupRoles) {
|
||||
result.computeIfAbsent(view.groupId(), key -> new LinkedHashSet<>()).add(view.roleId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<Long, List<String>> roleNamesByGroup(List<GroupRoleView> groupRoles) {
|
||||
Map<Long, Set<String>> result = new LinkedHashMap<>();
|
||||
for (GroupRoleView view : groupRoles) {
|
||||
result.computeIfAbsent(view.groupId(), key -> new LinkedHashSet<>()).add(view.roleName());
|
||||
}
|
||||
return toListMap(result);
|
||||
}
|
||||
|
||||
private Map<Long, List<String>> groupNamesByRole(List<GroupRoleView> groupRoles) {
|
||||
Map<Long, Set<String>> result = new LinkedHashMap<>();
|
||||
for (GroupRoleView view : groupRoles) {
|
||||
result.computeIfAbsent(view.roleId(), key -> new LinkedHashSet<>()).add(groupLabel(view.groupCode(), view.groupName()));
|
||||
}
|
||||
return toListMap(result);
|
||||
}
|
||||
|
||||
private Map<Long, List<String>> directUserNamesByRole(List<UserRoleView> userRoles) {
|
||||
Map<Long, Set<String>> result = new LinkedHashMap<>();
|
||||
for (UserRoleView view : userRoles) {
|
||||
result.computeIfAbsent(view.roleId(), key -> new LinkedHashSet<>()).add(view.username());
|
||||
}
|
||||
return toListMap(result);
|
||||
}
|
||||
|
||||
private Map<Long, List<PermissionView>> permissionsByRole(List<PermissionView> permissions) {
|
||||
Map<Long, List<PermissionView>> result = new LinkedHashMap<>();
|
||||
for (PermissionView permission : permissions) {
|
||||
result.computeIfAbsent(permission.roleId(), key -> new ArrayList<>()).add(permission);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<Long, String> roleNameById(List<AppRole> roles) {
|
||||
Map<Long, String> result = new LinkedHashMap<>();
|
||||
for (AppRole role : roles) {
|
||||
result.put(role.roleId(), role.roleName());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<String> names(Set<Long> ids, Map<Long, String> nameById) {
|
||||
return ids.stream()
|
||||
.map(nameById::get)
|
||||
.filter(name -> name != null && !name.isBlank())
|
||||
.toList();
|
||||
}
|
||||
|
||||
private int permissionCount(Set<Long> roleIds, Map<Long, List<PermissionView>> permissionsByRole) {
|
||||
return roleIds.stream()
|
||||
.mapToInt(roleId -> permissionsByRole.getOrDefault(roleId, List.of()).size())
|
||||
.sum();
|
||||
}
|
||||
|
||||
private List<String> objectNames(Set<Long> roleIds, Map<Long, List<PermissionView>> permissionsByRole) {
|
||||
Set<String> result = new LinkedHashSet<>();
|
||||
for (Long roleId : roleIds) {
|
||||
for (PermissionView permission : permissionsByRole.getOrDefault(roleId, List.of())) {
|
||||
result.add(permission.objectName() + " / " + permission.permissionEffect());
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(result);
|
||||
}
|
||||
|
||||
private Map<Long, List<String>> toListMap(Map<Long, Set<String>> source) {
|
||||
Map<Long, List<String>> result = new LinkedHashMap<>();
|
||||
source.forEach((key, value) -> result.put(key, new ArrayList<>(value)));
|
||||
return result;
|
||||
}
|
||||
|
||||
private String groupLabel(String groupCode, String groupName) {
|
||||
return groupCode + " / " + groupName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.cloudhandson.vpdbackoffice.web;
|
||||
|
||||
import com.cloudhandson.vpdbackoffice.domain.effective.EffectiveMatrixView;
|
||||
import com.cloudhandson.vpdbackoffice.service.EffectiveMatrixService;
|
||||
import java.util.List;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
public class EffectiveMatrixController {
|
||||
|
||||
private final EffectiveMatrixService matrixService;
|
||||
|
||||
public EffectiveMatrixController(EffectiveMatrixService matrixService) {
|
||||
this.matrixService = matrixService;
|
||||
}
|
||||
|
||||
@GetMapping("/effective-matrix")
|
||||
public String matrix(Model model) {
|
||||
try {
|
||||
model.addAttribute("matrix", matrixService.matrix());
|
||||
} catch (DataAccessException e) {
|
||||
RuntimeErrorMessage message = RuntimeErrorMessages.dataAccess(e);
|
||||
model.addAttribute("matrix", new EffectiveMatrixView(List.of(), List.of(), List.of(), 0));
|
||||
model.addAttribute("runtimeError", message);
|
||||
}
|
||||
return "effective-matrix";
|
||||
}
|
||||
}
|
||||
@@ -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