fix #557: guide permission-driven VPD flow
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
package com.cloudhandson.vpdbackoffice.domain.protectedobject;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ProtectedColumnTest {
|
||||
|
||||
@Test
|
||||
void explainsPublicColumnAsDisplayDefaultWithoutCallingItAccessPermission() {
|
||||
ProtectedColumn column = new ProtectedColumn(1L, 1L, "TITLE", "N", null, "PUBLIC", "NONE");
|
||||
|
||||
assertThat(column.sensitive()).isFalse();
|
||||
assertThat(column.sensitivityLabel()).isEqualTo("기본 표시");
|
||||
assertThat(column.redactionLabel()).isEqualTo("마스킹 없음");
|
||||
assertThat(column.displayPolicyLabel()).isEqualTo("기본 표시 · 마스킹 없음");
|
||||
}
|
||||
|
||||
@Test
|
||||
void explainsProtectedColumnAsPostAccessDisplayHandling() {
|
||||
ProtectedColumn column = new ProtectedColumn(2L, 1L, "EMAIL", "Y", null, "CONFIDENTIAL", "NULLIFY");
|
||||
|
||||
assertThat(column.sensitive()).isTrue();
|
||||
assertThat(column.displayPolicyLabel()).isEqualTo("기밀 · 값 숨김(NULL)");
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,8 @@ class PermissionServiceTest {
|
||||
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(2L, 1L, "OWNER_EMP_NO", "N", null, "INTERNAL", "NONE"),
|
||||
new ProtectedColumn(3L, 1L, "TECH_TAG", "N", null, "PUBLIC", "NONE")
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -138,6 +139,83 @@ class PermissionServiceTest {
|
||||
.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 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(
|
||||
@@ -199,6 +277,7 @@ class PermissionServiceTest {
|
||||
|
||||
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() {
|
||||
@@ -304,6 +383,7 @@ class PermissionServiceTest {
|
||||
|
||||
@Override
|
||||
public void insertVisibleColumn(long permissionId, String columnName) {
|
||||
insertedVisibleColumns.add(columnName);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,7 +25,10 @@ class GuidedFlowTemplateTest {
|
||||
String probe = template("probe.html");
|
||||
String result = template("fragments/probe-result.html");
|
||||
|
||||
assertThat(probe).contains("name=\"bearerToken\"").doesNotContain("name=\"tokenKeyId\"");
|
||||
assertThat(probe)
|
||||
.contains("name=\"bearerToken\"")
|
||||
.contains("임시 토큰 사용자")
|
||||
.doesNotContain("name=\"tokenKeyId\"");
|
||||
assertThat(result)
|
||||
.contains("적용된 사용자와 권한")
|
||||
.contains("다음에 할 일")
|
||||
@@ -49,6 +52,63 @@ class GuidedFlowTemplateTest {
|
||||
.contains("CB_AGENT_DOC_VPD_FILTER");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ordsObjectPageSeparatesAccessFromColumnDisplayProtection() throws IOException {
|
||||
String html = template("objects.html");
|
||||
|
||||
assertThat(html)
|
||||
.contains("누가 어떤 행을 볼 수 있는지는 권한 규칙과 VPD에서 결정")
|
||||
.contains("벡터 지식자료 검색 흐름")
|
||||
.contains("VPD context 설정과 기본 SELECT")
|
||||
.contains("전용 벡터 Handler 사용")
|
||||
.contains("원문 표시 허용 컬럼")
|
||||
.doesNotContain("표시 보호 등급", "마스킹 방식");
|
||||
}
|
||||
|
||||
@Test
|
||||
void permissionWizardExplainsTagRulesAndOrSemantics() throws IOException {
|
||||
String html = template("permissions.html");
|
||||
|
||||
assertThat(html)
|
||||
.contains("value=\"TAG\">특정 기술 태그")
|
||||
.contains("TECH_TAG")
|
||||
.contains("태그를 여러 개 추가하면");
|
||||
}
|
||||
|
||||
@Test
|
||||
void roleTokenAndVpdPagesExplainTheirBoundaries() throws IOException {
|
||||
String roles = template("roles.html");
|
||||
String tokens = template("tokens.html");
|
||||
String policies = template("vpd-policies.html");
|
||||
String filters = template("vpd-filter-policies.html");
|
||||
|
||||
assertThat(roles)
|
||||
.contains("표시 보호 등급 상한")
|
||||
.contains("행 접근 권한은 권한 규칙과 VPD에서 결정합니다");
|
||||
assertThat(tokens)
|
||||
.contains("회수·만료 포함")
|
||||
.contains("현재 사용 가능한 토큰만");
|
||||
assertThat(policies)
|
||||
.contains("어느 객체의 어떤 SQL에 어떤 Filter function을 붙일지")
|
||||
.contains("이 policy가 하는 일");
|
||||
assertThat(filters)
|
||||
.contains("이 Filter가 보호하는 내용")
|
||||
.contains("기술 태그 비교 예시");
|
||||
}
|
||||
|
||||
@Test
|
||||
void mcpPagesDescribeReasoningAndProtocolBoundaries() throws IOException {
|
||||
String chatbot = template("mcp-chatbot.html");
|
||||
String reasoning = template("mcp-reasoning.html");
|
||||
String client = template("mcp-client-demo.html");
|
||||
String sse = template("mcp-sse.html");
|
||||
|
||||
assertThat(chatbot).contains("임시 토큰 사용자").contains("태그 벡터 검색");
|
||||
assertThat(reasoning).contains("MCP tool을 고르고").contains("임시 토큰 사용자").contains("MCP Tool");
|
||||
assertThat(client).contains("tool 선택과 호출은 reasoning 결과").doesNotContain("Context Path");
|
||||
assertThat(sse).contains("Instruction / parameter mapping").contains("bearerToken");
|
||||
}
|
||||
|
||||
private String template(String relativePath) throws IOException {
|
||||
return Files.readString(Path.of("src/main/resources/templates").resolve(relativePath));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user