[Developer] #424 hide orphan protected objects in permission picker

This commit is contained in:
devmrko
2026-06-25 14:11:13 +09:00
parent 0a48273b6d
commit 30bd12c9a5
8 changed files with 73 additions and 1 deletions

View File

@@ -29,6 +29,10 @@ public interface PermissionMapper {
Long findPermissionId(@Param("roleId") long roleId, @Param("objectId") long objectId);
Long findObjectIdByPermissionId(@Param("permissionId") long permissionId);
int countPermissionsByObjectId(@Param("objectId") long objectId);
void insertPermission(@Param("permissionId") long permissionId,
@Param("roleId") long roleId,
@Param("objectId") long objectId,

View File

@@ -13,6 +13,8 @@ public interface ProtectedObjectMapper {
List<ProtectedObject> findEnabled();
List<ProtectedObject> findEnabledWithPermissions();
ProtectedObject findById(@Param("objectId") long objectId);
ProtectedObject findByOwnerAndName(@Param("owner") String owner, @Param("objectName") String objectName);

View File

@@ -110,12 +110,16 @@ public class PermissionService {
@Transactional
public void deletePermission(long permissionId) {
Long objectId = permissionMapper.findObjectIdByPermissionId(permissionId);
permissionMapper.deleteRules(permissionId);
permissionMapper.deleteVisibleColumns(permissionId);
int deleted = permissionMapper.deletePermission(permissionId);
if (deleted == 0) {
throw new AppException("삭제할 권한을 찾을 수 없습니다.");
}
if (objectId != null && permissionMapper.countPermissionsByObjectId(objectId) == 0) {
protectedObjectService.disableObject(objectId);
}
auditService.record(new AuditEvent("PERMISSION_DELETED", null, null, "SUCCESS", null, null,
"permissionId=" + permissionId));
}

View File

@@ -35,6 +35,10 @@ public class ProtectedObjectService {
return mapper.findEnabled();
}
public List<ProtectedObject> findEnabledWithPermissions() {
return mapper.findEnabledWithPermissions();
}
public List<DatabaseObjectOption> findDatabaseObjects() {
CacheEntry<List<DatabaseObjectOption>> cached = databaseObjectsCache;
if (cached != null && !cached.expired()) {
@@ -83,6 +87,7 @@ public class ProtectedObjectService {
for (String column : splitCsv(normalized.columns())) {
mapper.insertColumn(mapper.nextColumnId(), objectId, column, sensitive.contains(column) ? "Y" : "N");
}
databaseObjectsCache = null;
protectedColumnsCache.remove(objectId);
auditService.record(new AuditEvent("PROTECTED_OBJECT_CREATED", null, objectId, "SUCCESS", null, null,
normalized.objectName()));
@@ -96,6 +101,8 @@ public class ProtectedObjectService {
if (existing != null) {
if (!existing.enabled()) {
mapper.enableObject(existing.objectId());
databaseObjectsCache = null;
protectedColumnsCache.remove(existing.objectId());
auditService.record(new AuditEvent("PROTECTED_OBJECT_RE_ENABLED", null, existing.objectId(), "SUCCESS", null,
null, existing.displayName()));
return mapper.findById(existing.objectId());
@@ -119,6 +126,7 @@ public class ProtectedObjectService {
for (String column : columns) {
mapper.insertColumn(mapper.nextColumnId(), objectId, column, "N");
}
databaseObjectsCache = null;
protectedColumnsCache.remove(objectId);
auditService.record(new AuditEvent("PROTECTED_OBJECT_AUTO_CREATED", null, objectId, "SUCCESS", null, null,
command.objectName()));
@@ -162,6 +170,8 @@ public class ProtectedObjectService {
if (updated == 0) {
throw new AppException("보호 객체를 찾을 수 없습니다.");
}
databaseObjectsCache = null;
protectedColumnsCache.remove(objectId);
auditService.record(new AuditEvent("PROTECTED_OBJECT_DISABLED", null, objectId, "SUCCESS", null, null, null));
}

View File

@@ -63,7 +63,7 @@ public class PermissionController {
@GetMapping("/permissions")
public String permissions(Model model) {
long started = System.nanoTime();
var objects = protectedObjectService.findEnabled();
var objects = protectedObjectService.findEnabledWithPermissions();
long objectsAt = System.nanoTime();
var roles = permissionService.findRoles();
long rolesAt = System.nanoTime();

View File

@@ -84,6 +84,20 @@
AND o.object_id = #{objectId}
</select>
<select id="findObjectIdByPermissionId" resultType="long">
SELECT o.object_id
FROM cb_permission p
JOIN cb_protected_object o ON o.object_name = p.target_name
WHERE p.perm_id = #{permissionId}
</select>
<select id="countPermissionsByObjectId" resultType="int">
SELECT COUNT(*)
FROM cb_permission p
JOIN cb_protected_object o ON o.object_name = p.target_name
WHERE o.object_id = #{objectId}
</select>
<select id="findPermissionSet" resultType="com.cloudhandson.vpdbackoffice.domain.permission.PermissionSet">
SELECT p.perm_id AS permission_id,
p.role_id,

View File

@@ -9,6 +9,14 @@
ORDER BY owner, object_name
</select>
<select id="findEnabledWithPermissions" resultType="com.cloudhandson.vpdbackoffice.domain.protectedobject.ProtectedObject">
SELECT DISTINCT po.object_id, po.owner, po.object_name, po.ords_path, po.enabled_yn
FROM cb_protected_object po
JOIN cb_permission p ON p.target_name = po.object_name
WHERE po.enabled_yn = 'Y'
ORDER BY po.owner, po.object_name
</select>
<select id="findById" resultType="com.cloudhandson.vpdbackoffice.domain.protectedobject.ProtectedObject">
SELECT object_id, owner, object_name, ords_path, enabled_yn
FROM cb_protected_object
@@ -31,6 +39,7 @@
AND NOT EXISTS (
SELECT 1
FROM cb_protected_object po
JOIN cb_permission p ON p.target_name = po.object_name
WHERE po.owner = all_objects.owner
AND po.object_name = all_objects.object_name
AND po.enabled_yn = 'Y'

View File

@@ -1,5 +1,6 @@
package com.cloudhandson.vpdbackoffice.service;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import com.cloudhandson.vpdbackoffice.domain.audit.AuditEvent;
@@ -74,6 +75,24 @@ class PermissionServiceTest {
.hasMessageContaining("허용되지 않은");
}
@Test
void disablesProtectedObjectWhenLastPermissionIsDeleted() {
var mapper = new FakePermissionMapper();
boolean[] disabled = {false};
AuditService auditService = new AuditService(new NoopAuditMapper());
ProtectedObjectService objectService = new ProtectedObjectService(null, auditService) {
@Override
public void disableObject(long objectId) {
disabled[0] = objectId == 1L;
}
};
PermissionService service = new PermissionService(mapper, objectService, auditService);
service.deletePermission(1000L);
assertThat(disabled[0]).isTrue();
}
private static class NoopAuditMapper implements AuditMapper {
@Override
public void insert(AuditEvent event) {
@@ -120,6 +139,16 @@ class PermissionServiceTest {
return null;
}
@Override
public Long findObjectIdByPermissionId(long permissionId) {
return permissionId == 1000L ? 1L : null;
}
@Override
public int countPermissionsByObjectId(long objectId) {
return 0;
}
@Override
public void insertPermission(long permissionId, long roleId, long objectId, String action) {
}