package jnpf.base.service;
|
|
import jnpf.base.UserInfo;
|
import jnpf.base.entity.VisualdevEntity;
|
import jnpf.base.mapper.UploadEditDraftMapper;
|
import jnpf.base.model.uploadedit.UploadEditDraft;
|
import jnpf.base.util.FlowFormDataUtil;
|
import jnpf.exception.DataException;
|
import jnpf.flowable.WorkFlowApi;
|
import jnpf.flowable.entity.TaskEntity;
|
import jnpf.flowable.model.filepermission.FilePermissionPolicy.Action;
|
import jnpf.flowable.model.filepermission.FilePermissionView;
|
import jnpf.model.fileaccess.WorkflowFileRequest;
|
import jnpf.util.JsonUtil;
|
import jnpf.util.UserProvider;
|
import org.junit.*;
|
import org.mockito.MockedStatic;
|
import org.springframework.beans.factory.ObjectProvider;
|
import java.util.*;
|
import static org.junit.Assert.*;
|
import static org.mockito.Mockito.*;
|
|
public class WorkflowFileAccessServiceTest {
|
private WorkflowFileAccessService service;
|
private WorkflowFileRequest request;
|
private Map<String, Object> data;
|
private FilePermissionView permissions;
|
private WorkFlowApi workflow;
|
private UploadEditDraftMapper drafts;
|
private MockedStatic<UserProvider> users;
|
private VisualdevEntity model;
|
@Before public void setup() {
|
jnpf.util.context.SpringContext.setApplicationContext(mock(org.springframework.context.ApplicationContext.class));
|
UserInfo user = new UserInfo(); user.setUserId("owner"); user.setTenantId("tenant");
|
users = mockStatic(UserProvider.class); users.when(UserProvider::getUser).thenReturn(user);
|
users.when(UserProvider::getLoginUserId).thenReturn("owner");
|
ObjectProvider<VisualdevService> models = mock(ObjectProvider.class);
|
VisualdevService visual = mock(VisualdevService.class); when(models.getObject()).thenReturn(visual);
|
ObjectProvider<FlowFormDataUtil> forms = mock(ObjectProvider.class);
|
FlowFormDataUtil nativeForms = mock(FlowFormDataUtil.class); when(forms.getObject()).thenReturn(nativeForms);
|
workflow = mock(WorkFlowApi.class); drafts = mock(UploadEditDraftMapper.class);
|
service = new WorkflowFileAccessService(models, forms, drafts, workflow);
|
model = new VisualdevEntity();
|
Map<String, Object> field = new LinkedHashMap<>(); field.put("__vModel__", "files");
|
field.put("__config__", Collections.singletonMap("jnpfKey", "uploadFile"));
|
Map<String, Object> table = new LinkedHashMap<>(); table.put("__vModel__", "rows");
|
Map<String, Object> config = new LinkedHashMap<>(); config.put("jnpfKey", "table"); config.put("children", Collections.singletonList(field));
|
table.put("__config__", config);
|
model.setFormData(JsonUtil.getObjectToString(Collections.singletonMap("fields", Arrays.asList(field, table))));
|
when(visual.getReleaseInfo("model")).thenReturn(model);
|
data = new LinkedHashMap<>(); data.put("id", "record"); data.put("flowTaskId", "task"); data.put("files", files("A"));
|
when(nativeForms.getFilePermissionDataInfo(eq(model), anyString(), any())).thenAnswer(i -> data);
|
TaskEntity task = new TaskEntity(); task.setId("task"); when(workflow.getInfoSubmit("task")).thenReturn(task);
|
permissions = new FilePermissionView(); permissions.setTaskId("task"); permissions.setFormId("model");
|
permissions.setStatus("ready"); permissions.setRevision("r1"); allow("files", Action.VIEW, Action.DOWNLOAD);
|
when(workflow.filePermissions("task", "model")).thenAnswer(i -> permissions);
|
request = new WorkflowFileRequest(); request.setTenantId("tenant"); request.setOwnerId("owner"); request.setModelId("model");
|
request.setRecordId("record"); request.setTaskId("task"); request.setField("files"); request.setFileId("A"); request.setAction("VIEW");
|
}
|
@After public void close() { users.close(); }
|
private List<Map<String, Object>> files(String id) {
|
Map<String, Object> file = new LinkedHashMap<>(); file.put("fileId", id); file.put("name", id + ".docx"); return Collections.singletonList(file);
|
}
|
private void allow(String field, Action... actions) {
|
FilePermissionView.FieldAccess access = new FilePermissionView.FieldAccess(); access.setStatus("ready");
|
Map<Action, Boolean> values = new EnumMap<>(Action.class); for (Action action : actions) values.put(action, true);
|
access.setActions(values); permissions.getFields().put(field, access);
|
}
|
private void denied() { try { service.authorize(request); fail("expected denial"); } catch (DataException expected) { } }
|
@Test public void viewDoesNotRequireOnlineEditingAndReturnsNativeFile() { assertEquals("A.docx", service.authorize(request).getFile().get("name")); }
|
@Test public void downloadAndViewAreIndependent() {
|
allow("files", Action.VIEW); assertFalse(service.authorize(request).isAllowDownload()); request.setAction("DOWNLOAD"); denied();
|
}
|
@Test public void permissionIsReadAgainAfterRevocation() { service.authorize(request); allow("files"); denied(); verify(workflow, times(2)).filePermissions("task", "model"); }
|
@Test public void cannotBorrowAnotherTask() { request.setTaskId("other"); denied(); }
|
@Test public void omittedTaskStillUsesRealTask() { request.setTaskId(null); allow("files"); denied(); }
|
@Test public void wrongFileDeniedEvenWithFieldPermission() { request.setFileId("B"); denied(); }
|
@Test public void wrongOwnerDenied() { request.setOwnerId("other"); denied(); }
|
@Test public void wrongTenantDenied() { request.setTenantId("other"); denied(); }
|
@Test public void missingRuntimeAndErrorsDoNotFallBack() { permissions = null; denied(); }
|
@Test public void wrongRuntimeContextDenied() { permissions.setFormId("other"); denied(); }
|
@Test public void doesNotAcceptEditAsDownload() { request.setAction("EDIT"); denied(); }
|
@Test public void deletedRecordDenied() { data.clear(); denied(); }
|
@Test public void parentRowAndFieldAreIsolated() {
|
Map<String, Object> row = new LinkedHashMap<>(); row.put("id", "row1"); row.put("files", files("B")); data.put("rows", Collections.singletonList(row));
|
request.setParentField("rows"); request.setRowId("row1"); request.setFileId("B"); allow("rows-files", Action.VIEW);
|
assertEquals("B", service.authorize(request).getFile().get("fileId")); request.setRowId("row2"); denied();
|
}
|
@Test public void missingWorkflowWithOmittedTaskDoesNotFallBack() { request.setTaskId(null); when(workflow.getInfoSubmit("task")).thenReturn(null); denied(); }
|
@Test public void missingActualTaskDenied() { request.setTaskId(null); data.put("flowTaskId", ""); denied(); }
|
@Test public void legacyCannotUseControlledTransport() { permissions.setEnabled(false); denied(); }
|
@Test public void wrongDraftIdentityDenied() { draft().setId("other"); denied(); }
|
private UploadEditDraft draft() {
|
UploadEditDraft d = new UploadEditDraft(); d.setId("draft"); d.setTenantId("tenant"); d.setOwnerId("owner"); d.setModelId("model"); d.setRecordId("record");
|
d.setField("files"); d.setBaselineJson(JsonUtil.getObjectToString(files("A"))); d.setCurrentJson(JsonUtil.getObjectToString(files("B")));
|
request.setDraftId("draft"); request.setFileId("B"); when(drafts.find("tenant", "draft")).thenReturn(d); return d;
|
}
|
@Test public void ordinaryPreviewCannotReadUnadoptedDraft() { draft(); denied(); }
|
@Test public void cannotUseOtherPersonsDraft() { draft().setOwnerId("other"); denied(); }
|
@Test public void cannotUseOtherRecordsDraft() { draft().setRecordId("other"); denied(); }
|
@Test public void staleDraftBaselineDenied() { draft(); data.put("files", files("C")); denied(); }
|
@Test public void replacedDraftFileDenied() { draft(); request.setFileId("A"); denied(); }
|
@Test public void changedDraftRecheckedAtEachEditorRead() {
|
UploadEditDraft d = editing(); service.authorize(request);
|
d.setCurrentJson(JsonUtil.getObjectToString(files("C"))); denied();
|
}
|
private UploadEditDraft editing() {
|
UploadEditDraft d = draft(); d.setLeaseId("lease"); d.setLeaseFileId("B"); request.setLeaseId("lease");
|
model.setFormData(model.getFormData().replace("\"__vModel__\":\"files\"", "\"onlineEdit\":true,\"__vModel__\":\"files\""));
|
allow("files", Action.VIEW, Action.EDIT);
|
return d;
|
}
|
@Test public void editSourceRequiresCurrentLeaseAndViewAndEdit() {
|
editing(); assertEquals("lease", service.authorize(request).getContext().getLeaseId());
|
allow("files", Action.VIEW); denied();
|
allow("files", Action.EDIT); denied();
|
}
|
@Test public void replacedDiscardedOrAdoptedLeaseRejectsOldSource() {
|
UploadEditDraft d = editing(); service.authorize(request); d.setLeaseId(null); denied();
|
d.setLeaseId("replacement"); denied();
|
}
|
@Test public void editSourceCannotBorrowAnotherFileInTheSameDraft() {
|
UploadEditDraft d = editing(); d.setLeaseFileId("A"); denied();
|
d.setLeaseResultFileId("B"); assertEquals("B", service.authorize(request).getFile().get("fileId"));
|
}
|
@Test public void editingTicketRequiresDraftAndEnabledField() {
|
editing(); request.setDraftId(null); denied(); request.setDraftId("draft");
|
model.setFormData(model.getFormData().replace("\"onlineEdit\":true", "\"onlineEdit\":false")); denied();
|
}
|
@Test public void editingLeaseDoesNotGrantDownload() {
|
editing(); allow("files", Action.VIEW, Action.EDIT, Action.DOWNLOAD); request.setAction("DOWNLOAD"); denied();
|
}
|
|
@Test public void sharedWorkingFileCannotReplaceFormalPreviewOrLendItsLease() {
|
UploadEditDraft d = draft(); d.setOwnerId("reviewer"); d.setTaskId("task");
|
d.setId(jnpf.base.util.UploadEditWorkingCopy.id("tenant", UploadEditDraftService.context(d)));
|
request.setDraftId(d.getId()); when(drafts.find("tenant",d.getId())).thenReturn(d);
|
denied();
|
request.setDraftId(null); request.setFileId("A");
|
assertEquals("A",service.authorize(request).getFile().get("fileId"));
|
request.setDraftId(d.getId()); request.setFileId("B");
|
d.setLeaseId("reviewer-lease"); d.setLeaseFileId("B"); request.setLeaseId("reviewer-lease"); denied();
|
request.setLeaseId(null); allow("files"); denied();
|
}
|
@Test public void sharedDraftCannotBeBorrowedFromAnotherProcessEvenForTheSameRecord() {
|
UploadEditDraft d = draft(); d.setOwnerId("reviewer"); d.setTaskId("other-task");
|
d.setId(jnpf.base.util.UploadEditWorkingCopy.id("tenant", UploadEditDraftService.context(d)));
|
request.setDraftId(d.getId()); when(drafts.find("tenant",d.getId())).thenReturn(d); denied();
|
}
|
|
|
private UploadEditDraft dmsShared() {
|
UploadEditDraft d = draft();
|
model.setFormData("{\"tableName\":\"dms_wenjian_xiuding_shenhe\",\"fields\":[{\"__vModel__\":\"wenjian\",\"onlineEdit\":true,\"__config__\":{\"jnpfKey\":\"uploadFile\"}}]}");
|
d.setField("wenjian"); d.setTaskId("task");
|
d.setId(jnpf.base.util.UploadEditWorkingCopy.id("tenant", UploadEditDraftService.context(d)));
|
data.put("wenjian", files("A")); request.setField("wenjian"); request.setDraftId(d.getId());
|
request.setContentToken(jnpf.base.util.UploadEditStateMachine.contentToken(d.getCurrentJson()));
|
when(drafts.find("tenant", d.getId())).thenReturn(d); allow("wenjian", Action.VIEW); return d;
|
}
|
@Test public void dmsViewerReadsConfirmedSharedContentWithoutAnEditLeaseOrDownloadPermission() {
|
dmsShared(); assertEquals("B", service.authorize(request).getFile().get("fileId"));
|
assertFalse(service.authorize(request).isAllowDownload()); request.setAction("DOWNLOAD"); denied();
|
}
|
@Test public void dmsSharedPreviewRejectsChangedContentAndWrongFormLocation() {
|
UploadEditDraft d = dmsShared(); request.setContentToken("stale"); denied();
|
request.setContentToken(jnpf.base.util.UploadEditStateMachine.contentToken(d.getCurrentJson()));
|
d.setRowId("another-row"); denied();
|
}
|
}
|