package jnpf.flowable.util;
|
|
import java.nio.file.*;
|
import java.io.InputStream;
|
import java.util.*;
|
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
|
import org.apache.ibatis.session.Configuration;
|
import jnpf.flowable.entity.TemplateNodeEntity;
|
import jnpf.flowable.model.filepermission.*;
|
import jnpf.flowable.model.filepermission.FilePermissionPolicy.*;
|
import jnpf.flowable.service.NativeFilePermissionRuntimeSource;
|
import jnpf.flowable.util.filepermission.*;
|
|
/** 可在现有 Checks 入口执行;覆盖原生运行事实投影而非只验证规则 JSON。 */
|
public class FilePermissionRuntimeChecks {
|
private static int checks;
|
public static void main(String[] args) throws Exception {
|
FilePermissionSnapshot snapshot = snapshot("ACTIVE", "CURRENT_HANDLERS", "qa", null);
|
FilePermissionNativeState state = state(1, "qa,review");
|
state.getMembers().add(member("operator", "qa", "qa-user", 1, 0));
|
state.getMembers().add(member("operator", "review", "review-user", 1, 0));
|
FilePermissionRuntime.Facts qa = project(snapshot, state, "qa-user");
|
check(qa.verified && qa.currentHandlers.get("qa").contains("qa-user"), "native current handlers");
|
check(!resolve(snapshot, qa).allows("files", Action.DOWNLOAD), "active-node handler denial");
|
check(resolve(snapshot, project(snapshot, state, "review-user")).allows("files", Action.DOWNLOAD), "parallel node isolation");
|
check(resolve(snapshot, qa).allows("rows-files", Action.DOWNLOAD), "child field independent");
|
check(resolve(snapshot, qa).allows("files", Action.EDIT), "current handler native writable");
|
state.getMembers().get(0).setStartHandleTime(null);
|
FilePermissionRuntime.Facts assigned = project(snapshot, state, "qa-user");
|
check(assigned.verified && assigned.participants.get("qa").contains("qa-user"), "assigned candidate remains participant");
|
check(!assigned.currentHandlers.containsKey("qa") && !resolve(snapshot, assigned).allows("files", Action.EDIT), "assigned candidate is not current handler before start");
|
state.getMembers().get(0).setStartHandleTime(new Date(1L));
|
check(project(snapshot, state, "qa-user").currentHandlers.get("qa").contains("qa-user"), "started candidate becomes current handler");
|
check(!resolve(snapshot, project(snapshot, state, "creator")).allows("files", Action.EDIT), "creator not writable during approval");
|
check(resolve(snapshot, project(snapshot, state, "creator")).allows("files", Action.VIEW), "creator can read start form");
|
check(!project(snapshot, state, "stranger").verified, "unrelated user not admitted");
|
qa.transitioning = true;
|
check(!resolve(snapshot, qa).allows("files", Action.VIEW), "mixed state denied");
|
qa.transitioning = false;
|
qa.nativeUpperBounds.get("files").put(Action.VIEW, false);
|
check(!resolve(snapshot, qa).allows("files", Action.VIEW), "native read restriction retained");
|
|
FilePermissionSnapshot completed = snapshot("COMPLETED", "PARTICIPANTS", null, null);
|
state.setStatus(2); state.setCurrentNodeCode(null);
|
FilePermissionView end = resolve(completed, project(completed, state, "qa-user"));
|
check(!end.allows("files", Action.DOWNLOAD) && end.allows("files", Action.VIEW), "terminal download denied and view retained");
|
check(!end.allows("files", Action.EDIT), "terminal not writable");
|
FilePermissionSnapshot cancelled = snapshot("CANCELLED", "CREATOR", null, null);
|
state.setStatus(7);
|
check(!resolve(cancelled, project(cancelled, state, "creator")).allows("files", Action.DOWNLOAD), "cancelled trigger");
|
FilePermissionSnapshot terminated = snapshot("TERMINATED", "CREATOR", null, null);
|
state.setStatus(4);
|
check(!resolve(terminated, project(terminated, state, "creator")).allows("files", Action.DOWNLOAD), "terminated trigger");
|
|
FilePermissionSnapshot keep = snapshot("ENTERED", "CREATOR", "qa", "KEEP");
|
state.setStatus(8);
|
FilePermissionNativeState.Member back = member("record", "qa", "qa-user", -1, null);
|
back.setHandleType(3); state.getMembers().add(back);
|
FilePermissionRuntime.Facts returned = project(keep, state, "creator");
|
check(returned.verified && returned.activeEnteredRules.contains("rule"), "KEEP retains entered evidence on return");
|
check(!resolve(keep, returned).allows("files", Action.DOWNLOAD), "KEEP still denies after return");
|
check(resolve(keep, returned).allows("files", Action.EDIT), "draft/back creator writable");
|
FilePermissionSnapshot release = snapshot("ENTERED", "CREATOR", "qa", "RELEASE");
|
release = read(release.withEnteredNodes(Collections.singleton("qa")));
|
FilePermissionRuntime.Facts activeRelease = project(release, state, "creator");
|
check(activeRelease.verified && activeRelease.activeEnteredRules.contains("rule"), "RELEASE active after trigger entry");
|
check(!resolve(release, activeRelease).allows("files", Action.DOWNLOAD), "active RELEASE rule denies action");
|
release = read(release.withReturnedPastNodes(Collections.singleton("review")));
|
check(project(release, state, "creator").activeEnteredRules.contains("rule"), "return after trigger retains RELEASE rule");
|
release = read(release.withReturnedPastNodes(Collections.singleton("qa")));
|
FilePermissionRuntime.Facts released = project(release, state, "creator");
|
check(released.verified && !released.activeEnteredRules.contains("rule"), "return before trigger releases RELEASE rule");
|
check(resolve(release, released).allows("files", Action.DOWNLOAD), "released rule restores inherited action");
|
release = read(release.withEnteredNodes(Collections.singleton("qa")));
|
check(project(release, state, "creator").activeEnteredRules.contains("rule"), "trigger re-entry reactivates RELEASE rule");
|
state.setFlowType(3);
|
check(!project(keep, state, "creator").verified, "unmapped free-flow unavailable");
|
state.setFlowType(1); state.setStatus(10);
|
check(!project(keep, state, "creator").verified, "exception state unavailable");
|
state.setStatus(1); state.setCurrentNodeCode("unknown");
|
check(!project(keep, state, "creator").verified, "unknown active node unavailable");
|
state.setCurrentNodeCode("qa"); state.setMembers(new ArrayList<>());
|
check(!project(keep, state, "creator").verified, "current handler not yet persisted unavailable");
|
state.getMembers().add(member("operator", "qa", "future", -2, 0));
|
check(!project(keep, state, "future").verified, "future operator not admitted");
|
state.setStatus(0);
|
FilePermissionRuntime.Facts draft = project(keep, state, "creator");
|
check(draft.verified && !draft.activeEnteredRules.contains("rule"), "future operator is not entered evidence");
|
check(resolve(keep, draft).allows("files", Action.DOWNLOAD), "not-yet-entered rule inactive");
|
|
List<TemplateNodeEntity> nodes = nodes();
|
nodes.get(0).setNodeJson("{\"formOperates\":[{\"id\":\"rows\",\"read\":true,\"write\":false}]}");
|
FilePermissionRuntime.Facts bounds = NativeFilePermissionRuntimeSource.project("0", "form", "creator", keep, state, nodes, form());
|
check(!resolve(keep, bounds).allows("rows-files", Action.EDIT), "child mutation bounded by parent write");
|
check(resolve(keep, bounds).allows("files", Action.EDIT), "parent restriction does not affect main file");
|
nodes.get(0).setNodeJson("{\"formOperates\":[{\"id\":\"files\",\"read\":false,\"write\":true}]}");
|
bounds = NativeFilePermissionRuntimeSource.project("0", "form", "creator", keep, state, nodes, form());
|
check(!resolve(keep, bounds).allows("files", Action.EDIT), "write cannot bypass read restriction");
|
check(!NativeFilePermissionRuntimeSource.project("0", "foreign", "creator", keep, state, nodes, form()).verified, "foreign form not admitted");
|
Map<String,Object> disabled = form(); ((Map<String,Object>) ((List<?>)disabled.get("fields")).get(0)).put("onlineEdit", false);
|
bounds = NativeFilePermissionRuntimeSource.project("0", "form", "creator", keep, state, nodes(), disabled);
|
check(!resolve(keep, bounds).allows("files", Action.EDIT) && resolve(keep, bounds).allows("files", Action.DOWNLOAD), "online edit switch independent of downloads");
|
FilePermissionView legacy = FilePermissionView.legacy("task", "form");
|
check(!legacy.isEnabled() && legacy.allows("files", Action.DOWNLOAD), "legacy defers to original checks");
|
legacy.setStatus("error");
|
check(!legacy.allows("files", Action.DOWNLOAD), "malformed legacy response denied");
|
check(!FilePermissionView.unavailable("task", "form", "offline").allows("files", Action.DOWNLOAD), "unavailable not legacy");
|
|
Configuration configuration = new Configuration();
|
Path xml = Path.of("jnpf-flowable/jnpf-flowable-biz/src/main/resources/mapper/FilePermissionRuntimeMapper.xml");
|
try (InputStream stream = Files.newInputStream(xml)) { new XMLMapperBuilder(stream, configuration, xml.toString(), configuration.getSqlFragments()).parse(); }
|
org.apache.ibatis.mapping.MappedStatement statement = configuration.getMappedStatement("jnpf.flowable.mapper.FilePermissionRuntimeMapper.find");
|
String sql = statement.getBoundSql(Map.of("taskId", "task")).getSql();
|
check(sql.contains("UNION ALL") && statement.getBoundSql(Map.of("taskId", "task")).getParameterMappings().size() == 4, "state SQL binds all four task scopes");
|
check(sql.contains("f_start_handle_time") && statement.getResultMaps().get(0).getResultMappings().stream()
|
.anyMatch(mapping -> "members".equals(mapping.getProperty()) && mapping.getNestedResultMapId() != null), "state SQL includes start-handle time");
|
check(statement.getResultMaps().get(0).hasNestedResultMaps(), "explicit native state and member mappings");
|
System.out.println("FilePermissionRuntimeChecks: " + checks + " passed");
|
}
|
private static FilePermissionNativeState state(int status, String active) {
|
FilePermissionNativeState state = new FilePermissionNativeState(); state.setTaskId("task"); state.setCreatorId("creator");
|
state.setFlowId("flow"); state.setFlowType(1); state.setStatus(status); state.setCurrentNodeCode(active); return state;
|
}
|
private static FilePermissionNativeState.Member member(String kind, String node, String user, Integer status, Integer completion) {
|
FilePermissionNativeState.Member m = new FilePermissionNativeState.Member(); m.setId(kind + node + user); m.setKind(kind); m.setNodeCode(node);
|
m.setUserId(user); m.setStatus(status); m.setCompletion(completion);
|
if ("operator".equals(kind) && status != null && status >= 0) m.setStartHandleTime(new Date(1L));
|
return m;
|
}
|
private static FilePermissionSnapshot snapshot(String trigger, String subject, String node, String onReturn) {
|
Map<String,Object> rule = new HashMap<>(Map.of("id", "rule", "formId", "form", "field", "files", "subject", subject,
|
"trigger", trigger, "actions", Map.of("DOWNLOAD", "DENY")));
|
if (node != null) rule.put("triggerNodeId", node);
|
if (onReturn != null) rule.put("onReturn", onReturn);
|
return FilePermissionSnapshot.read(FilePermissionSnapshot.create("0", "task", "flow", Map.of("version", 1, "rules", List.of(rule))), "0", "task");
|
}
|
private static FilePermissionSnapshot read(String json) { return FilePermissionSnapshot.read(json, "0", "task"); }
|
private static List<TemplateNodeEntity> nodes() {
|
List<TemplateNodeEntity> list = new ArrayList<>();
|
for (String code : List.of("start", "qa", "review")) {
|
TemplateNodeEntity node = new TemplateNodeEntity(); node.setNodeCode(code); node.setNodeType(code.equals("start") ? "start" : "approver");
|
node.setFormId("form"); node.setNodeJson("{}"); list.add(node);
|
}
|
return list;
|
}
|
private static Map<String,Object> form() {
|
Map<String,Object> upload = new HashMap<>(Map.of("__vModel__", "files", "onlineEdit", true, "__config__", Map.of("jnpfKey", "uploadFile")));
|
return new HashMap<>(Map.of("fields", List.of(upload, Map.of("__vModel__", "rows", "__config__", Map.of("jnpfKey", "table", "children", List.of(upload))))));
|
}
|
private static FilePermissionRuntime.Facts project(FilePermissionSnapshot snapshot, FilePermissionNativeState state, String user) {
|
return NativeFilePermissionRuntimeSource.project("0", "form", user, snapshot, state, nodes(), form());
|
}
|
private static FilePermissionView resolve(FilePermissionSnapshot snapshot, FilePermissionRuntime.Facts facts) { return FilePermissionRuntime.resolve(snapshot.getRules(), facts); }
|
private static void check(boolean value, String label) { if (!value) throw new AssertionError(label); checks++; }
|
}
|