刘光辉
5 小时以前 06f314545ac3514d7ce28939920866ae8188d31c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
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++; }
}