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 data; private FilePermissionView permissions; private WorkFlowApi workflow; private UploadEditDraftMapper drafts; private MockedStatic 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 models = mock(ObjectProvider.class); VisualdevService visual = mock(VisualdevService.class); when(models.getObject()).thenReturn(visual); ObjectProvider 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 field = new LinkedHashMap<>(); field.put("__vModel__", "files"); field.put("__config__", Collections.singletonMap("jnpfKey", "uploadFile")); Map table = new LinkedHashMap<>(); table.put("__vModel__", "rows"); Map 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> files(String id) { Map 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 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 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(); } }