fix #557: guide permission-driven VPD flow
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
package com.cloudhandson.vpdbackoffice.domain.probe;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ProbeResultTest {
|
||||
|
||||
@Test
|
||||
void explainsSuccessfulResultInPlainLanguage() {
|
||||
ProbeResult result = new ProbeResult(
|
||||
ProbeStatus.SUCCESS,
|
||||
List.of("DOC_ID"),
|
||||
List.of(Map.of("DOC_ID", 1), Map.of("DOC_ID", 2)),
|
||||
2,
|
||||
List.of(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
assertThat(result.successLike()).isTrue();
|
||||
assertThat(result.title()).contains("데이터를 볼 수 있습니다");
|
||||
assertThat(result.plainSummary()).contains("2개").contains("VPD");
|
||||
assertThat(result.nextAction()).contains("예상한 범위");
|
||||
}
|
||||
|
||||
@Test
|
||||
void treatsEmptyRowsAsAnEnforcedPermissionOutcome() {
|
||||
ProbeResult result = new ProbeResult(
|
||||
ProbeStatus.VPD_DENY_EMPTY_RESULT,
|
||||
List.of(),
|
||||
List.of(),
|
||||
0,
|
||||
List.of(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
assertThat(result.successLike()).isTrue();
|
||||
assertThat(result.title()).contains("볼 수 있는 행이 없습니다");
|
||||
assertThat(result.plainSummary()).contains("오류가 아닐 수 있습니다");
|
||||
assertThat(result.nextAction()).contains("유효 권한");
|
||||
}
|
||||
|
||||
@Test
|
||||
void givesActionableGuidanceForUnknownToken() {
|
||||
ProbeResult result = ProbeResult.blocked(
|
||||
ProbeStatus.TOKEN_NOT_FOUND,
|
||||
"TOKEN_NOT_FOUND",
|
||||
"토큰을 찾을 수 없습니다."
|
||||
);
|
||||
|
||||
assertThat(result.successLike()).isFalse();
|
||||
assertThat(result.title()).contains("등록되지 않은 토큰");
|
||||
assertThat(result.plainSummary()).contains("DB").contains("원문");
|
||||
assertThat(result.nextAction()).contains("새 토큰").contains("발급");
|
||||
}
|
||||
|
||||
@Test
|
||||
void distinguishesInactiveTokenAndOrdsFailure() {
|
||||
ProbeResult inactive = ProbeResult.blocked(
|
||||
ProbeStatus.TOKEN_INACTIVE,
|
||||
"TOKEN_INACTIVE",
|
||||
"만료되었거나 회수된 토큰입니다."
|
||||
);
|
||||
ProbeResult unavailable = ProbeResult.blocked(
|
||||
ProbeStatus.ORDS_UNAVAILABLE,
|
||||
"ORDS_UNAVAILABLE",
|
||||
"연결할 수 없습니다."
|
||||
);
|
||||
|
||||
assertThat(inactive.title()).contains("만료되었거나 회수");
|
||||
assertThat(inactive.nextAction()).contains("활성 토큰");
|
||||
assertThat(unavailable.title()).contains("ORDS");
|
||||
assertThat(unavailable.nextAction()).contains("권한 설정을 바꾸지 말고");
|
||||
}
|
||||
|
||||
@Test
|
||||
void identifiesBrokenCustomVpdFilterSeparatelyFromTokenErrors() {
|
||||
ProbeResult result = ProbeResult.blocked(
|
||||
ProbeStatus.VPD_FILTER_ERROR,
|
||||
"VPD_FILTER_ERROR",
|
||||
"ORA-28110"
|
||||
);
|
||||
|
||||
assertThat(result.title()).contains("VPD Filter");
|
||||
assertThat(result.plainSummary()).contains("토큰과 사용자 권한은 확인");
|
||||
assertThat(result.nextAction()).contains("토큰이나 권한을 바꾸지 말고").contains("자동 Filter");
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,14 @@ class ProbeErrorClassifierTest {
|
||||
.isEqualTo(ProbeStatus.ORDS_PATH_NOT_FOUND);
|
||||
}
|
||||
|
||||
@Test
|
||||
void classifiesBrokenVpdPolicyFunction() {
|
||||
assertThat(classifier.classify(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
"SQL Error Code 28110, Error Message: ORA-28110: The VPD policy function has error"
|
||||
)).isEqualTo(ProbeStatus.VPD_FILTER_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
void detectsOrdsConnectionRefused() {
|
||||
assertThat(classifier.isUnavailable(new ResourceAccessException(
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
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.vpd.VpdFunctionOption;
|
||||
import com.cloudhandson.vpdbackoffice.domain.vpd.VpdPolicyFormOptions;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
class VpdPolicyServiceTest {
|
||||
|
||||
@Test
|
||||
void protectsTheDynamicPermissionFunctionFromManualOverwrite() {
|
||||
RecordingJdbcTemplate jdbcTemplate = new RecordingJdbcTemplate();
|
||||
VpdPolicyService service = new VpdPolicyService(null, jdbcTemplate, null);
|
||||
|
||||
assertThatThrownBy(() -> service.saveFilterFunction(
|
||||
"ADMIN",
|
||||
"CB_AGENT_DOC_VPD_FILTER",
|
||||
"1=1"
|
||||
))
|
||||
.isInstanceOf(AppException.class)
|
||||
.hasMessageContaining("권한체계")
|
||||
.hasMessageContaining("수정할 수 없습니다");
|
||||
|
||||
assertThat(jdbcTemplate.updateCount).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultApplyAlwaysUsesTheDynamicPermissionFunction() {
|
||||
RecordingJdbcTemplate jdbcTemplate = new RecordingJdbcTemplate();
|
||||
VpdPolicyService service = new VpdPolicyService(null, jdbcTemplate, null) {
|
||||
@Override
|
||||
public VpdPolicyFormOptions formOptions() {
|
||||
return new VpdPolicyFormOptions(
|
||||
List.of(),
|
||||
List.of("ADMIN"),
|
||||
List.of("ADMIN"),
|
||||
List.of(new VpdFunctionOption("ADMIN", null, "CB_AGENT_DOC_VPD_FILTER", "FUNCTION")),
|
||||
List.of(),
|
||||
List.of("SELECT")
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
service.createDefaultPermissionPolicy("ADMIN.DOCUMENTS");
|
||||
|
||||
assertThat(jdbcTemplate.lastSql).contains("DBMS_RLS.ADD_POLICY").contains("DBMS_RLS.DYNAMIC");
|
||||
assertThat(jdbcTemplate.lastArgs).containsExactly(
|
||||
"ADMIN",
|
||||
"DOCUMENTS",
|
||||
"CB_PERMISSION_SELECT_POLICY",
|
||||
"ADMIN",
|
||||
"CB_AGENT_DOC_VPD_FILTER",
|
||||
"SELECT"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultApplyFailsClosedWhenPermissionFunctionIsNotInstalled() {
|
||||
VpdPolicyService service = new VpdPolicyService(null, new RecordingJdbcTemplate(), null) {
|
||||
@Override
|
||||
public VpdPolicyFormOptions formOptions() {
|
||||
return new VpdPolicyFormOptions(
|
||||
List.of(), List.of(), List.of(), List.of(), List.of(), List.of("SELECT")
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
assertThatThrownBy(() -> service.createDefaultPermissionPolicy("ADMIN.DOCUMENTS"))
|
||||
.isInstanceOf(AppException.class)
|
||||
.hasMessageContaining("동적 권한 필터")
|
||||
.hasMessageContaining("설치");
|
||||
}
|
||||
|
||||
private static class RecordingJdbcTemplate extends JdbcTemplate {
|
||||
private String lastSql;
|
||||
private Object[] lastArgs = new Object[0];
|
||||
private int updateCount;
|
||||
|
||||
@Override
|
||||
public <T> T queryForObject(String sql, Class<T> requiredType) {
|
||||
return requiredType.cast("ADMIN");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(String sql, Object... args) {
|
||||
lastSql = sql;
|
||||
lastArgs = args;
|
||||
updateCount++;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.cloudhandson.vpdbackoffice.web;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class GuidedFlowTemplateTest {
|
||||
|
||||
@Test
|
||||
void dashboardExplainsTheFourStepPermissionJourney() throws IOException {
|
||||
String html = template("dashboard.html");
|
||||
|
||||
assertThat(html)
|
||||
.contains("1. 권한 설계")
|
||||
.contains("2. DB 보호 연결")
|
||||
.contains("3. 토큰 발급")
|
||||
.contains("4. 결과 확인");
|
||||
}
|
||||
|
||||
@Test
|
||||
void probeUsesOneTokenInputAndHidesTechnicalExchangeByDefault() throws IOException {
|
||||
String probe = template("probe.html");
|
||||
String result = template("fragments/probe-result.html");
|
||||
|
||||
assertThat(probe).contains("name=\"bearerToken\"").doesNotContain("name=\"tokenKeyId\"");
|
||||
assertThat(result)
|
||||
.contains("적용된 사용자와 권한")
|
||||
.contains("다음에 할 일")
|
||||
.contains("<details")
|
||||
.contains("기술 상세");
|
||||
}
|
||||
|
||||
@Test
|
||||
void vpdDefaultApplyAndAdvancedCustomFiltersAreVisuallySeparated() throws IOException {
|
||||
String policies = template("vpd-policies.html");
|
||||
String filters = template("vpd-filter-policies.html");
|
||||
|
||||
assertThat(policies)
|
||||
.contains("action=\"/vpd-policies/default\"")
|
||||
.contains("권한체계 자동 적용")
|
||||
.contains("객체만 선택");
|
||||
assertThat(filters)
|
||||
.contains("기본 동작: 권한체계 자동 반영")
|
||||
.contains("고급: 별도 Filter 만들기")
|
||||
.contains("fail-closed")
|
||||
.contains("CB_AGENT_DOC_VPD_FILTER");
|
||||
}
|
||||
|
||||
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