package jnpf.dmsService; import jnpf.base.UserInfo; import jnpf.dmsEntity.permission.entity.DmsFilePermissionEntity; import jnpf.dmsEntity.permission.enums.DmsPermissionAction; import jnpf.dmsEntity.permission.enums.DmsResourceType; import jnpf.dmsEntity.permission.form.PermissionDecisionForm; import jnpf.dmsEntity.permission.model.DmsResourcePath; import jnpf.dmsEntity.permission.model.DmsSubjectSet; import jnpf.dmsEntity.permission.vo.DmsPermissionDecisionVO; import jnpf.dmsIntegration.audit.DmsAuditAdapter; import jnpf.dmsMapper.DmsFilePermissionMapper; import jnpf.dmsPermission.DmsAccessExclusionResolver; import jnpf.dmsPermission.DmsCurrentIdentity; import jnpf.dmsPermission.DmsPermissionGroupMembershipResolver; import jnpf.dmsPermission.DmsPermissionMatcher; import jnpf.dmsPermission.DmsPermissionProperties; import jnpf.dmsPermission.DmsResourceResolver; import jnpf.dmsPermission.DmsSubjectResolver; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.UUID; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anySet; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; class DmsPermissionDecisionServiceTest { private final DmsCurrentIdentity currentIdentity = mock(DmsCurrentIdentity.class); private final DmsSubjectResolver subjectResolver = mock(DmsSubjectResolver.class); private final DmsResourceResolver resourceResolver = mock(DmsResourceResolver.class); private final DmsFilePermissionMapper permissionMapper = mock(DmsFilePermissionMapper.class); private final DmsPermissionGroupMembershipResolver groupResolver = mock(DmsPermissionGroupMembershipResolver.class); private final DmsPermissionMatcher matcher = mock(DmsPermissionMatcher.class); private final DmsPermissionProperties properties = new DmsPermissionProperties(); private final DmsAuditAdapter auditAdapter = mock(DmsAuditAdapter.class); private final DmsAccessExclusionResolver exclusionResolver = mock(DmsAccessExclusionResolver.class); private final DmsPermissionDecisionService service = new DmsPermissionDecisionService( currentIdentity, subjectResolver, resourceResolver, permissionMapper, groupResolver, matcher, properties, auditAdapter, exclusionResolver); private final UserInfo user = new UserInfo(); @BeforeEach void setUp() { user.setTenantId("tenant-1"); user.setUserId("user-1"); when(currentIdentity.requireUser()).thenReturn(user); when(currentIdentity.tenantId(user)).thenReturn("tenant-1"); when(subjectResolver.resolve(user)).thenReturn(mock(DmsSubjectSet.class)); } @Test void exclusionWinsWithoutLoadingAllowCandidates() { UUID documentId = UUID.randomUUID(); PermissionDecisionForm request = request(DmsResourceType.DOCUMENT, documentId); DmsResourcePath path = document(documentId); when(resourceResolver.resolveAll("tenant-1", Collections.singletonList(request))) .thenReturn(Collections.singletonMap(documentId, path)); when(exclusionResolver.resolve(eq("tenant-1"), eq("user-1"), any())) .thenReturn(new DmsAccessExclusionResolver.Snapshot( new HashSet<>(Collections.singletonList(documentId)), Collections.emptySet())); DmsPermissionDecisionVO result = service.evaluate(request); assertFalse(result.isAllowed()); assertEquals("USER_READ_EXCLUDED", result.getReasonCode()); verify(permissionMapper, never()).selectCandidates(any(), any(), anyList()); verify(subjectResolver, never()).resolve(user); } @Test void fileVersionExclusionOnlyDeniesSpecifiedVersionInBatch() { UUID documentId = UUID.randomUUID(); UUID excludedVersionId = UUID.randomUUID(); UUID visibleVersionId = UUID.randomUUID(); PermissionDecisionForm excludedRequest = request(DmsResourceType.FILE_VERSION, excludedVersionId); PermissionDecisionForm visibleRequest = request(DmsResourceType.FILE_VERSION, visibleVersionId); DmsResourcePath excludedPath = version(documentId, excludedVersionId); DmsResourcePath visiblePath = version(documentId, visibleVersionId); Map paths = new HashMap<>(); paths.put(excludedVersionId, excludedPath); paths.put(visibleVersionId, visiblePath); when(resourceResolver.resolveAll("tenant-1", Arrays.asList(excludedRequest, visibleRequest))) .thenReturn(paths); when(exclusionResolver.resolve(eq("tenant-1"), eq("user-1"), any())) .thenReturn(new DmsAccessExclusionResolver.Snapshot(Collections.emptySet(), new HashSet<>(Collections.singletonList(excludedVersionId)))); when(resourceResolver.candidateResourceIds(visiblePath)) .thenReturn(Arrays.asList(visibleVersionId, documentId)); DmsFilePermissionEntity allow = new DmsFilePermissionEntity(); allow.setId(UUID.randomUUID()); when(permissionMapper.selectCandidates("tenant-1", Collections.singletonList("READ"), Arrays.asList(visibleVersionId, documentId))).thenReturn(Collections.singletonList(allow)); allow.setActionCode("READ"); when(matcher.matches(eq(allow), eq(visiblePath), any(DmsSubjectSet.class))).thenReturn(true); List result = service.evaluate(Arrays.asList(excludedRequest, visibleRequest)); assertFalse(result.get(0).isAllowed()); assertEquals("USER_READ_EXCLUDED", result.get(0).getReasonCode()); assertTrue(result.get(1).isAllowed()); assertEquals(allow.getId(), result.get(1).getMatchedPermissionId()); verify(permissionMapper).selectCandidates("tenant-1", Collections.singletonList("READ"), Arrays.asList(visibleVersionId, documentId)); verify(groupResolver).resolve(anySet(), any(DmsSubjectSet.class)); } @Test void twoHundredMixedDecisionsUseOneBulkLoadPerDependency() { List requests = new java.util.ArrayList<>(); Map paths = new HashMap<>(); for (int index = 0; index < properties.getMaxDecisionBatchSize(); index++) { UUID resourceId = UUID.randomUUID(); DmsPermissionAction action = index % 2 == 0 ? DmsPermissionAction.READ : DmsPermissionAction.UPDATE; PermissionDecisionForm request = request(DmsResourceType.DOCUMENT, resourceId, action); DmsResourcePath path = document(resourceId); requests.add(request); paths.put(resourceId, path); when(resourceResolver.candidateResourceIds(path)).thenReturn(Collections.singletonList(resourceId)); } when(resourceResolver.resolveAll("tenant-1", requests)).thenReturn(paths); when(exclusionResolver.resolve(eq("tenant-1"), eq("user-1"), any())) .thenReturn(DmsAccessExclusionResolver.Snapshot.empty()); when(permissionMapper.selectCandidates(eq("tenant-1"), anyList(), anyList())) .thenReturn(Collections.emptyList()); List result = service.evaluate(requests); assertEquals(properties.getMaxDecisionBatchSize(), result.size()); assertTrue(result.stream().noneMatch(DmsPermissionDecisionVO::isAllowed)); org.mockito.ArgumentCaptor> actions = org.mockito.ArgumentCaptor.forClass(List.class); org.mockito.ArgumentCaptor> resourceIds = org.mockito.ArgumentCaptor.forClass(List.class); verify(resourceResolver, times(1)).resolveAll("tenant-1", requests); verify(subjectResolver, times(1)).resolve(user); verify(exclusionResolver, times(1)).resolve(eq("tenant-1"), eq("user-1"), any()); verify(permissionMapper, times(1)).selectCandidates(eq("tenant-1"), actions.capture(), resourceIds.capture()); verify(groupResolver, times(1)).resolve(anySet(), any(DmsSubjectSet.class)); assertEquals(Arrays.asList("READ", "UPDATE"), actions.getValue()); assertEquals(properties.getMaxDecisionBatchSize(), new HashSet<>(resourceIds.getValue()).size()); } private PermissionDecisionForm request(DmsResourceType type, UUID id) { return request(type, id, DmsPermissionAction.READ); } private PermissionDecisionForm request(DmsResourceType type, UUID id, DmsPermissionAction action) { PermissionDecisionForm result = new PermissionDecisionForm(); result.setResourceType(type); result.setResourceId(id); result.setActionCode(action); return result; } private DmsResourcePath document(UUID documentId) { DmsResourcePath path = new DmsResourcePath(); path.setActualResourceType("DOCUMENT"); path.setActualResourceId(documentId); path.setDocumentId(documentId); return path; } private DmsResourcePath version(UUID documentId, UUID versionId) { DmsResourcePath path = new DmsResourcePath(); path.setActualResourceType("FILE_VERSION"); path.setActualResourceId(versionId); path.setDocumentId(documentId); return path; } }