package jnpf.fileStorage; import jnpf.base.ActionResult; import jnpf.base.UserInfo; import jnpf.exception.DataException; import jnpf.file.WorkflowFileAccessApi; import jnpf.model.fileaccess.WorkflowFileGrant; import jnpf.model.fileaccess.WorkflowFileRequest; import jnpf.util.UserProvider; import org.junit.*; import org.mockito.MockedStatic; import java.util.*; import static org.junit.Assert.*; import static org.mockito.Mockito.*; public class WorkflowFileTicketServiceTest { private WorkflowFileTicketService service; private WorkflowFileAccessApi access; private WorkflowFileRequest request; private MockedStatic users; private Map cache; @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, withSettings().mockMaker(org.mockito.MockMakers.INLINE)); users.when(UserProvider::getUser).thenReturn(user); users.when(UserProvider::getLoginUserId).thenReturn("owner"); access = mock(WorkflowFileAccessApi.class); cache = new HashMap<>(); service = new WorkflowFileTicketService(access, cache::get, value -> { String id = UUID.randomUUID().toString(); cache.put(id, value); return id; }); request = new WorkflowFileRequest(); request.setModelId("model"); request.setRecordId("record"); request.setTaskId("task"); request.setField("files"); request.setFileId("A"); when(access.authorize(any())).thenAnswer(call -> { WorkflowFileRequest source = call.getArgument(0); if (source == null) return null; WorkflowFileGrant grant = new WorkflowFileGrant(); grant.setContext(source); grant.setFile(Collections.singletonMap("fileId", source.getFileId())); return ActionResult.success(grant); }); } @After public void close() { if (users != null) users.close(); } private void denied(Runnable call) { try { call.run(); fail("expected denial"); } catch (DataException expected) { } } @Test public void ignoresClientActorAndAction() { request.setOwnerId("admin"); request.setTenantId("other"); request.setAction("VIEW"); WorkflowFileRequest bound = service.authorizeDownloads(Collections.singletonList(request)).get(0).getContext(); assertEquals("owner", bound.getOwnerId()); assertEquals("tenant", bound.getTenantId()); assertEquals("DOWNLOAD", bound.getAction()); assertEquals("admin", request.getOwnerId()); } private String previewTicket() { return service.previewTicket(service.authorizePreview(request)); } @Test public void configRechecksCurrentAccess() { String ticket = previewTicket(); service.previewAccess(ticket); when(access.authorize(any())).thenReturn(ActionResult.fail("denied")); denied(() -> service.previewAccess(ticket)); verify(access, times(3)).authorize(any()); } @Test public void expiredTicketDenied() { String ticket = previewTicket(); cache.clear(); denied(() -> service.previewAccess(ticket)); } @Test public void malformedTicketDenied() { cache.put("bad", "1"); denied(() -> service.previewAccess("bad")); } @Test public void anotherUserCannotUseConfigTicket() { String ticket = previewTicket(); clearInvocations(access); users.when(UserProvider::getLoginUserId).thenReturn("other"); denied(() -> service.previewAccess(ticket)); verifyNoInteractions(access); } @Test public void anotherTenantCannotUseConfigTicket() { String ticket = previewTicket(); clearInvocations(access); UserInfo other = new UserInfo(); other.setTenantId("other"); users.when(UserProvider::getUser).thenReturn(other); denied(() -> service.previewAccess(ticket)); verifyNoInteractions(access); } @Test public void legacySourceTicketCannotBeUsedAsConfig() { cache.put("old", "{\"purpose\":\"workflow-file-office-source-v1\"}"); denied(() -> service.previewAccess("old")); verifyNoInteractions(access); } @Test public void batchChecksEverySourceBeforeReturning() { WorkflowFileRequest second = new WorkflowFileRequest(); second.setFileId("B"); second.setField("child"); second.setModelId("model"); when(access.authorize(argThat(r -> "B".equals(r.getFileId())))).thenReturn(ActionResult.fail("revoked")); denied(() -> service.authorizeDownloads(Arrays.asList(request, second))); verify(access, times(2)).authorize(any()); assertTrue(cache.isEmpty()); } @Test public void batchIssuesNothingOnPartialFailure() { when(access.authorize(any())).thenReturn(ActionResult.fail("denied")); denied(() -> service.authorizeDownloads(Collections.singletonList(request))); assertTrue(cache.isEmpty()); } @Test public void missingUpstreamResponseDenied() { when(access.authorize(any())).thenReturn(null); denied(() -> service.authorizePreview(request)); } @Test public void emptyAndOversizedBatchesDenied() { denied(() -> service.authorizeDownloads(Collections.emptyList())); denied(() -> service.authorizeDownloads(Collections.nCopies(101, request))); } @Test public void fileMismatchInUpstreamResponseDenied() { WorkflowFileGrant grant = new WorkflowFileGrant(); grant.setContext(request); grant.setFile(Collections.singletonMap("fileId", "other")); when(access.authorize(any())).thenReturn(ActionResult.success(grant)); denied(() -> service.authorizePreview(request)); } @Test public void ticketCannotMoveToAnotherRecordOnConsumption() { String ticket = previewTicket(); doAnswer(call -> { WorkflowFileRequest changed = jnpf.util.JsonUtil.getJsonToBean(jnpf.util.JsonUtil.getObjectToString(call.getArgument(0)), WorkflowFileRequest.class); changed.setRecordId("other-record"); WorkflowFileGrant grant = new WorkflowFileGrant(); grant.setContext(changed); grant.setFile(Collections.singletonMap("fileId", "A")); return ActionResult.success(grant); }).when(access).authorize(any()); denied(() -> service.previewAccess(ticket)); } @Test public void samePhysicalFileInAnotherInstanceHasIndependentPermission() { String first = previewTicket(); request.setTaskId("other-task"); request.setRecordId("other-record"); String second = previewTicket(); doAnswer(call -> { WorkflowFileRequest source = call.getArgument(0); if ("task".equals(source.getTaskId())) return ActionResult.fail("revoked"); WorkflowFileGrant grant = new WorkflowFileGrant(); grant.setContext(source); grant.setFile(Collections.singletonMap("fileId", "A")); return ActionResult.success(grant); }).when(access).authorize(any()); denied(() -> service.previewAccess(first)); assertEquals("other-task", service.previewAccess(second).getContext().getTaskId()); } @Test public void editLeaseBindingCannotBeStrippedByAuthorizationResponse() { request.setDraftId("draft"); request.setLeaseId("lease"); String ticket = previewTicket(); assertEquals("lease", service.previewAccess(ticket).getContext().getLeaseId()); doAnswer(call -> { WorkflowFileRequest changed = jnpf.util.JsonUtil.getJsonToBean(jnpf.util.JsonUtil.getObjectToString(call.getArgument(0)), WorkflowFileRequest.class); changed.setLeaseId(null); WorkflowFileGrant grant = new WorkflowFileGrant(); grant.setContext(changed); grant.setFile(Collections.singletonMap("fileId", "A")); return ActionResult.success(grant); }).when(access).authorize(any()); denied(() -> service.previewAccess(ticket)); } }