package jnpf.dmsPermission;
|
|
import jnpf.base.UserInfo;
|
import jnpf.dmsEntity.permission.entity.DmsFilePermissionEntity;
|
import jnpf.dmsEntity.permission.entity.DmsPermissionUsageEntity;
|
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.dmsMapper.DmsFilePermissionMapper;
|
import jnpf.dmsMapper.DmsPermissionUsageMapper;
|
import jnpf.dmsService.DmsPermissionDecisionService;
|
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.Test;
|
|
import java.util.Date;
|
import java.util.UUID;
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.when;
|
|
class DmsPermissionGuardTest {
|
private final DmsCurrentIdentity currentIdentity = mock(DmsCurrentIdentity.class);
|
private final DmsPermissionDecisionService decisionService = mock(DmsPermissionDecisionService.class);
|
private final DmsFilePermissionMapper permissionMapper = mock(DmsFilePermissionMapper.class);
|
private final DmsPermissionUsageMapper usageMapper = mock(DmsPermissionUsageMapper.class);
|
private final DmsResourceResolver resourceResolver = mock(DmsResourceResolver.class);
|
private final DmsSubjectResolver subjectResolver = mock(DmsSubjectResolver.class);
|
private final DmsPermissionGroupMembershipResolver groupResolver = mock(DmsPermissionGroupMembershipResolver.class);
|
private final DmsPermissionMatcher matcher = mock(DmsPermissionMatcher.class);
|
private final DmsAccessExclusionResolver exclusionResolver = mock(DmsAccessExclusionResolver.class);
|
private final DmsPermissionGuard guard = new DmsPermissionGuard(currentIdentity, decisionService,
|
permissionMapper, usageMapper, resourceResolver, subjectResolver, groupResolver, matcher,
|
exclusionResolver);
|
private final UUID permissionId = UUID.randomUUID();
|
private final UUID resourceId = UUID.randomUUID();
|
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));
|
when(resourceResolver.resolve(any(), any(), any())).thenReturn(mock(DmsResourcePath.class));
|
when(exclusionResolver.isExcluded(any(), any(), any())).thenReturn(false);
|
when(matcher.matches(any(), any(), any())).thenReturn(true);
|
DmsPermissionDecisionVO decision = new DmsPermissionDecisionVO();
|
decision.setAllowed(true);
|
decision.setMatchedPermissionId(permissionId);
|
when(decisionService.evaluate(any(PermissionDecisionForm.class))).thenReturn(decision);
|
}
|
|
@Test
|
void rejectsPermissionRevokedWhileWaitingForLock() {
|
DmsFilePermissionEntity permission = activePermission();
|
permission.setRevokedAt(new Date());
|
when(permissionMapper.selectByTenantAndIdForUpdate("tenant-1", permissionId)).thenReturn(permission);
|
DmsPermissionOperation<String> operation = mockOperation();
|
|
DmsPermissionException error = assertThrows(DmsPermissionException.class,
|
() -> guard.execute(context(), operation));
|
|
assertEquals(DmsPermissionError.PERMISSION_DENIED, error.getError());
|
verify(operation, never()).execute();
|
}
|
|
@Test
|
void rejectsPermissionExhaustedWhileWaitingForLock() {
|
DmsFilePermissionEntity permission = activePermission();
|
permission.setMaxUseCount(1L);
|
permission.setUsedCount(1L);
|
when(permissionMapper.selectByTenantAndIdForUpdate("tenant-1", permissionId)).thenReturn(permission);
|
DmsPermissionOperation<String> operation = mockOperation();
|
|
DmsPermissionException error = assertThrows(DmsPermissionException.class,
|
() -> guard.execute(context(), operation));
|
|
assertEquals(DmsPermissionError.PERMISSION_EXHAUSTED, error.getError());
|
verify(operation, never()).execute();
|
}
|
|
@Test
|
void recordsLimitedUseAfterBusinessOperationSucceeds() {
|
DmsFilePermissionEntity permission = activePermission();
|
permission.setMaxUseCount(2L);
|
when(permissionMapper.selectByTenantAndIdForUpdate("tenant-1", permissionId)).thenReturn(permission);
|
DmsPermissionOperation<String> operation = mockOperation();
|
when(operation.execute()).thenReturn("created");
|
DmsPermissionExecutionContext context = context();
|
|
String result = guard.execute(context, operation);
|
|
assertEquals("created", result);
|
org.mockito.ArgumentCaptor<DmsPermissionUsageEntity> captor =
|
org.mockito.ArgumentCaptor.forClass(DmsPermissionUsageEntity.class);
|
verify(usageMapper).insert(captor.capture());
|
DmsPermissionUsageEntity usage = captor.getValue();
|
assertEquals(permissionId, usage.getPermissionId());
|
assertEquals(resourceId, usage.getActualResourceId());
|
assertEquals(context.getOperationId(), usage.getOperationId());
|
assertEquals("READ", usage.getActionCode());
|
}
|
|
@Test
|
void replaysPriorUseWithoutExecutingBusinessOperationAgain() {
|
DmsPermissionExecutionContext context = context();
|
DmsPermissionUsageEntity prior = new DmsPermissionUsageEntity();
|
prior.setPermissionId(permissionId);
|
prior.setUserId("user-1");
|
prior.setActualResourceType("DOCUMENT");
|
prior.setActualResourceId(resourceId);
|
prior.setActionCode("READ");
|
prior.setOperationId(context.getOperationId());
|
when(usageMapper.selectByOperation("tenant-1", "user-1", context.getOperationId())).thenReturn(prior);
|
when(permissionMapper.selectByTenantAndId("tenant-1", permissionId)).thenReturn(activePermission());
|
DmsPermissionOperation<String> operation = mockOperation();
|
when(operation.replay()).thenReturn("replayed");
|
|
String result = guard.execute(context, operation);
|
|
assertEquals("replayed", result);
|
verify(operation, never()).execute();
|
verify(operation).replay();
|
verify(usageMapper, never()).insert(any(DmsPermissionUsageEntity.class));
|
}
|
|
@Test
|
void rejectsReadExcludedLimitedPermissionWithoutUsingPermission() {
|
DmsFilePermissionEntity permission = activePermission();
|
permission.setMaxUseCount(2L);
|
when(permissionMapper.selectByTenantAndIdForUpdate("tenant-1", permissionId)).thenReturn(permission);
|
when(exclusionResolver.isExcluded(any(), any(), any())).thenReturn(true);
|
DmsPermissionOperation<String> operation = mockOperation();
|
|
DmsPermissionException error = assertThrows(DmsPermissionException.class,
|
() -> guard.execute(context(), operation));
|
|
assertEquals(DmsPermissionError.PERMISSION_DENIED, error.getError());
|
verify(operation, never()).execute();
|
verify(usageMapper, never()).insert(any(DmsPermissionUsageEntity.class));
|
}
|
|
@Test
|
void rejectsIdempotentReplayAfterReadExclusionIsAdded() {
|
DmsPermissionExecutionContext context = context();
|
DmsPermissionUsageEntity prior = new DmsPermissionUsageEntity();
|
prior.setPermissionId(permissionId);
|
prior.setUserId("user-1");
|
prior.setActualResourceType("DOCUMENT");
|
prior.setActualResourceId(resourceId);
|
prior.setActionCode("READ");
|
prior.setOperationId(context.getOperationId());
|
when(usageMapper.selectByOperation("tenant-1", "user-1", context.getOperationId())).thenReturn(prior);
|
when(exclusionResolver.isExcluded(any(), any(), any())).thenReturn(true);
|
DmsPermissionOperation<String> operation = mockOperation();
|
|
DmsPermissionException error = assertThrows(DmsPermissionException.class,
|
() -> guard.execute(context, operation));
|
|
assertEquals(DmsPermissionError.PERMISSION_DENIED, error.getError());
|
verify(operation, never()).replay();
|
verify(permissionMapper, never()).selectByTenantAndId(any(), any());
|
}
|
|
@SuppressWarnings("unchecked")
|
private DmsPermissionOperation<String> mockOperation() {
|
return mock(DmsPermissionOperation.class);
|
}
|
|
private DmsPermissionExecutionContext context() {
|
DmsPermissionExecutionContext context = new DmsPermissionExecutionContext();
|
context.setResourceType(DmsResourceType.DOCUMENT);
|
context.setResourceId(resourceId);
|
context.setActionCode(DmsPermissionAction.READ);
|
context.setOperationId(UUID.randomUUID());
|
return context;
|
}
|
|
private DmsFilePermissionEntity activePermission() {
|
DmsFilePermissionEntity permission = new DmsFilePermissionEntity();
|
permission.setId(permissionId);
|
permission.setTenantId("tenant-1");
|
permission.setActionCode("READ");
|
permission.setValidFrom(new Date(System.currentTimeMillis() - 60_000));
|
permission.setUsedCount(0L);
|
return permission;
|
}
|
}
|