Files
vpd-permission-poc/src/test/java/com/cloudhandson/vpdbackoffice/service/PermissionServiceTest.java

459 lines
13 KiB
Java

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;
import com.cloudhandson.vpdbackoffice.domain.permission.AppRole;
import com.cloudhandson.vpdbackoffice.domain.permission.PermissionRule;
import com.cloudhandson.vpdbackoffice.domain.permission.PermissionSet;
import com.cloudhandson.vpdbackoffice.domain.permission.PermissionSetCommand;
import com.cloudhandson.vpdbackoffice.domain.permission.PermissionView;
import com.cloudhandson.vpdbackoffice.domain.permission.RuleCommand;
import com.cloudhandson.vpdbackoffice.domain.protectedobject.ProtectedColumn;
import com.cloudhandson.vpdbackoffice.domain.protectedobject.ProtectedObject;
import com.cloudhandson.vpdbackoffice.mapper.AuditMapper;
import com.cloudhandson.vpdbackoffice.mapper.PermissionMapper;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class PermissionServiceTest {
private PermissionMapper permissionMapper;
private ProtectedObjectService protectedObjectService;
private PermissionService permissionService;
@BeforeEach
void setUp() {
permissionMapper = new FakePermissionMapper();
AuditService auditService = new AuditService(new NoopAuditMapper());
protectedObjectService = new ProtectedObjectService(null, auditService) {
@Override
public ProtectedObject assertEnabled(long objectId) {
return new ProtectedObject(1L, "ADMIN", "CB_V_SEARCH_DOCUMENTS", "cb-agent-security/vpd/documents", "Y");
}
@Override
public List<ProtectedColumn> findColumns(long objectId) {
return List.of(
new ProtectedColumn(1L, 1L, "DEPT_CODE", "N", null, "INTERNAL", "NONE"),
new ProtectedColumn(2L, 1L, "OWNER_EMP_NO", "N", null, "INTERNAL", "NONE"),
new ProtectedColumn(3L, 1L, "TECH_TAG", "N", null, "PUBLIC", "NONE")
);
}
};
permissionService = new PermissionService(permissionMapper, protectedObjectService, auditService);
}
@Test
void rejectsAllRuleMixedWithSpecificRule() {
var command = new PermissionSetCommand(
10L,
1L,
"SELECT",
"ALLOW",
List.of(new RuleCommand(null, "ALL", null), new RuleCommand("DEPT_CODE", "=", "APAC")),
List.of()
);
assertThatThrownBy(() -> permissionService.savePermissionSet(command))
.isInstanceOf(AppException.class)
.hasMessageContaining("ALL");
}
@Test
void rejectsCustomPredicateRule() {
var command = new PermissionSetCommand(
10L,
1L,
"SELECT",
"ALLOW",
List.of(new RuleCommand("DEPT_CODE", "CUSTOM_PREDICATE", "1=1")),
List.of()
);
assertThatThrownBy(() -> permissionService.savePermissionSet(command))
.isInstanceOf(AppException.class)
.hasMessageContaining("허용되지 않은");
}
@Test
void allowsDefaultColumnsForContextRules() {
var command = new PermissionSetCommand(
10L,
1L,
"SELECT",
"ALLOW",
List.of(new RuleCommand(null, "MY_DEPT", null), new RuleCommand(null, "SELF", null)),
List.of()
);
permissionService.savePermissionSet(command);
FakePermissionMapper mapper = (FakePermissionMapper) permissionMapper;
assertThat(mapper.insertedRules)
.extracting(PermissionRule::ruleType)
.containsExactly("MY_DEPT", "SELF");
assertThat(mapper.insertedRules)
.extracting(PermissionRule::ruleColumn)
.containsExactly(null, null);
}
@Test
void acceptsDeptAndEmpNoRulesWithDefaultColumnsAndValues() {
var command = new PermissionSetCommand(
10L,
1L,
"SELECT",
"ALLOW",
List.of(new RuleCommand(null, "DEPT", "HR"), new RuleCommand(null, "EMP_NO", "E2001")),
List.of()
);
permissionService.savePermissionSet(command);
FakePermissionMapper mapper = (FakePermissionMapper) permissionMapper;
assertThat(mapper.insertedRules)
.extracting(PermissionRule::ruleType)
.containsExactly("DEPT", "EMP_NO");
assertThat(mapper.insertedRules)
.extracting(PermissionRule::ruleValue)
.containsExactly("HR", "E2001");
}
@Test
void rejectsDeptRuleWithoutValue() {
var command = new PermissionSetCommand(
10L,
1L,
"SELECT",
"ALLOW",
List.of(new RuleCommand(null, "DEPT", "")),
List.of()
);
assertThatThrownBy(() -> permissionService.savePermissionSet(command))
.isInstanceOf(AppException.class)
.hasMessageContaining("값이 필요");
}
@Test
void acceptsMultipleTagRulesAsOrCandidates() {
var command = new PermissionSetCommand(
10L,
1L,
"SELECT",
"ALLOW",
List.of(
new RuleCommand(null, "TAG", "spring_boot"),
new RuleCommand(null, "TAG", "oracle_vpd")
),
List.of()
);
permissionService.savePermissionSet(command);
FakePermissionMapper mapper = (FakePermissionMapper) permissionMapper;
assertThat(mapper.insertedRules)
.extracting(PermissionRule::ruleType)
.containsExactly("TAG", "TAG");
assertThat(mapper.insertedRules)
.extracting(PermissionRule::ruleColumn)
.containsExactly(null, null);
assertThat(mapper.insertedRules)
.extracting(PermissionRule::ruleValue)
.containsExactly("SPRING_BOOT", "ORACLE_VPD");
}
@Test
void acceptsStakeholderContextRulesWithAnExplicitObjectColumn() {
var command = new PermissionSetCommand(
10L,
1L,
"SELECT",
"ALLOW",
List.of(
new RuleCommand("DEPT_CODE", "STAKEHOLDER_SELF", "설계사"),
new RuleCommand("OWNER_EMP_NO", "STAKEHOLDER_CHANNEL", "지점장")
),
List.of()
);
permissionService.savePermissionSet(command);
FakePermissionMapper mapper = (FakePermissionMapper) permissionMapper;
assertThat(mapper.insertedRules)
.extracting(PermissionRule::ruleType)
.containsExactly("STAKEHOLDER_SELF", "STAKEHOLDER_CHANNEL");
}
@Test
void rejectsStakeholderContextRuleWithoutAnObjectColumn() {
var command = new PermissionSetCommand(
10L,
1L,
"SELECT",
"ALLOW",
List.of(new RuleCommand(null, "STAKEHOLDER_SELF", null)),
List.of()
);
assertThatThrownBy(() -> permissionService.savePermissionSet(command))
.isInstanceOf(AppException.class)
.hasMessageContaining("컬럼이 필요");
}
@Test
void rejectsStakeholderContextRuleWithoutExpectedRole() {
var command = new PermissionSetCommand(
10L,
1L,
"SELECT",
"ALLOW",
List.of(new RuleCommand("DEPT_CODE", "STAKEHOLDER_SELF", null)),
List.of()
);
assertThatThrownBy(() -> permissionService.savePermissionSet(command))
.isInstanceOf(AppException.class)
.hasMessageContaining("값이 필요");
}
@Test
void acceptsMultipleVisibleColumns() {
var command = new PermissionSetCommand(
10L,
1L,
"SELECT",
"ALLOW",
List.of(new RuleCommand("DEPT_CODE", "=", "HR")),
List.of("DEPT_CODE", "OWNER_EMP_NO")
);
permissionService.savePermissionSet(command);
FakePermissionMapper mapper = (FakePermissionMapper) permissionMapper;
assertThat(mapper.insertedVisibleColumns).containsExactly("DEPT_CODE", "OWNER_EMP_NO");
}
@Test
void rejectsTagRuleWithoutValue() {
var command = new PermissionSetCommand(
10L,
1L,
"SELECT",
"ALLOW",
List.of(new RuleCommand(null, "TAG", "")),
List.of()
);
assertThatThrownBy(() -> permissionService.savePermissionSet(command))
.isInstanceOf(AppException.class)
.hasMessageContaining("값이 필요");
}
@Test
void rejectsUnsafeTagValue() {
var command = new PermissionSetCommand(
10L,
1L,
"SELECT",
"ALLOW",
List.of(new RuleCommand(null, "TAG", "SPRING' OR '1'='1")),
List.of()
);
assertThatThrownBy(() -> permissionService.savePermissionSet(command))
.isInstanceOf(AppException.class)
.hasMessageContaining("영문 대문자");
}
@Test
void acceptsDenyPermissionEffect() {
var command = new PermissionSetCommand(
10L,
1L,
"SELECT",
"DENY",
List.of(new RuleCommand("DEPT_CODE", "=", "HR")),
List.of()
);
permissionService.savePermissionSet(command);
FakePermissionMapper mapper = (FakePermissionMapper) permissionMapper;
assertThat(mapper.insertedEffect).isEqualTo("DENY");
}
@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();
}
@Test
void rejectsPermissionDeleteWithoutImpactConfirmation() {
assertThatThrownBy(() -> permissionService.deletePermission(1000L, false))
.isInstanceOf(AppException.class)
.hasMessageContaining("영향 확인");
}
@Test
void blocksRoleDeleteWhenDependenciesRemain() {
var mapper = new FakePermissionMapper();
mapper.userRoleCount = 1;
PermissionService service = new PermissionService(mapper, protectedObjectService, new AuditService(new NoopAuditMapper()));
assertThatThrownBy(() -> service.deleteRole(10L, true))
.isInstanceOf(AppException.class)
.hasMessageContaining("먼저 해제");
}
private static class NoopAuditMapper implements AuditMapper {
@Override
public void insert(AuditEvent event) {
}
}
private static class FakePermissionMapper implements PermissionMapper {
private final List<PermissionRule> insertedRules = new ArrayList<>();
private final List<String> insertedVisibleColumns = new ArrayList<>();
@Override
public List<AppRole> findRoles() {
return List.of(new AppRole(10L, "HR_DEPT_ROLE", null, "INTERNAL"));
}
@Override
public AppRole findRole(long roleId) {
return roleId == 10L ? new AppRole(10L, "HR_DEPT_ROLE", null, "INTERNAL") : null;
}
@Override
public long nextRoleId() {
return 40L;
}
@Override
public void insertRole(long roleId, String roleName, String description, String maxSensitivityLevel) {
}
@Override
public int updateRoleMaxSensitivity(long roleId, String maxSensitivityLevel) {
return 1;
}
@Override
public int deleteRole(long roleId) {
return 1;
}
private int userRoleCount;
private int groupRoleCount;
private int permissionCountByRole;
@Override
public int countUserRolesByRoleId(long roleId) {
return userRoleCount;
}
@Override
public int countGroupRolesByRoleId(long roleId) {
return groupRoleCount;
}
@Override
public int countPermissionsByRoleId(long roleId) {
return permissionCountByRole;
}
@Override
public List<PermissionView> findPermissionViews() {
return List.of();
}
@Override
public PermissionSet findPermissionSet(long roleId, long objectId) {
return null;
}
@Override
public Long findPermissionId(long roleId, long objectId) {
return null;
}
@Override
public Long findObjectIdByPermissionId(long permissionId) {
return permissionId == 1000L ? 1L : null;
}
@Override
public int countPermissionsByObjectId(long objectId) {
return 0;
}
private String insertedEffect;
@Override
public void insertPermission(long permissionId, long roleId, long objectId, String action, String permissionEffect) {
insertedEffect = permissionEffect;
}
@Override
public void updatePermissionAction(long permissionId, String action) {
}
@Override
public void updatePermissionEffect(long permissionId, String permissionEffect) {
insertedEffect = permissionEffect;
}
@Override
public void deleteRules(long permissionId) {
}
@Override
public void insertRule(PermissionRule rule) {
insertedRules.add(rule);
}
@Override
public void deleteVisibleColumns(long permissionId) {
}
@Override
public void insertVisibleColumn(long permissionId, String columnName) {
insertedVisibleColumns.add(columnName);
}
@Override
public int deletePermission(long permissionId) {
return 1;
}
@Override
public long nextPermissionId() {
return 1000L;
}
@Override
public long nextRuleId() {
return 10000L;
}
}
}