package jnpf.elnService.template;
|
|
import jnpf.elnEntity.template.ElnTemplateVersionEntity;
|
import jnpf.util.JsonUtil;
|
import org.junit.Test;
|
|
import java.lang.reflect.Field;
|
import java.lang.reflect.Modifier;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicReference;
|
|
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.fail;
|
|
public class TemplateAnnotationAuthorizerTest {
|
|
@Test
|
public void successfulAuthorizationKeepsTenantUntilOuterExecutionCompletes() {
|
List<String> events = new ArrayList<>();
|
RecordingTenantRoute tenants = new RecordingTenantRoute(events);
|
TemplateAnnotationAuthorizer authorizer = authorizer(validPayload("tenant-a", "template-1", "file-1"),
|
template("tenant-a", "template-1", "file-1", "DRAFT", "enabled"), true, tenants, events);
|
|
TemplateAnnotationAuthorization result = authorizer.authorize("ticket-1", "template-1");
|
|
assertEquals(Arrays.asList("access:tenant-a:user-1:template-1:file-1",
|
"switch:tenant-a", "lookup:tenant-a:template-1"), events);
|
assertEquals("tenant-a", tenants.currentTenant);
|
assertEquals("tenant-a", result.getTenantId());
|
assertEquals("user-1", result.getUserId());
|
assertEquals("template-1", result.getTemplateId());
|
assertEquals("file-1", result.getFileId());
|
for (Field field : TemplateAnnotationAuthorization.class.getDeclaredFields()) {
|
if (!field.isSynthetic()) {
|
assertTrue(Modifier.isFinal(field.getModifiers()));
|
}
|
}
|
|
events.add("service:" + tenants.currentTenant);
|
tenants.clear();
|
assertEquals(Arrays.asList("access:tenant-a:user-1:template-1:file-1",
|
"switch:tenant-a", "lookup:tenant-a:template-1", "service:tenant-a", "clear"), events);
|
assertNull(tenants.currentTenant);
|
}
|
|
@Test
|
public void requestCannotOverrideSignedTenantAndSequentialTenantsDoNotLeak() {
|
List<String> events = new ArrayList<>();
|
RecordingTenantRoute tenants = new RecordingTenantRoute(events);
|
AtomicReference<String> routedTenant = new AtomicReference<>();
|
TemplateAnnotationTicketService tickets = new TemplateAnnotationTicketService(ticket ->
|
JsonUtil.getObjectToString(validPayload(ticket, "template-1", "file-1")));
|
TemplateAnnotationAuthorizer authorizer = new TemplateAnnotationAuthorizer(tickets,
|
templateId -> {
|
routedTenant.set(tenants.currentTenant);
|
return template(tenants.currentTenant, templateId, "file-1", "DRAFT", "enabled");
|
}, (tenantId, userId, templateId, fileId) -> true, tenants);
|
|
authorizer.authorize("tenant-a", "template-1");
|
assertEquals("tenant-a", routedTenant.get());
|
assertEquals("tenant-a", tenants.currentTenant);
|
tenants.clear();
|
assertNull(tenants.currentTenant);
|
authorizer.authorize("tenant-b", "template-1");
|
assertEquals("tenant-b", routedTenant.get());
|
assertEquals("tenant-b", tenants.currentTenant);
|
tenants.clear();
|
assertNull(tenants.currentTenant);
|
assertEquals(Arrays.asList("tenant-a", "tenant-b"), tenants.switchedTenants);
|
assertEquals(2, tenants.clearCount);
|
}
|
|
@Test
|
public void tenantSwitchFailureDoesNotQueryTemplateAndStillClears() {
|
List<String> events = new ArrayList<>();
|
RecordingTenantRoute tenants = new RecordingTenantRoute(events);
|
tenants.failSwitch = true;
|
AtomicInteger queries = new AtomicInteger();
|
TemplateAnnotationAuthorizer authorizer = new TemplateAnnotationAuthorizer(ticketService(validPayload()),
|
templateId -> {
|
queries.incrementAndGet();
|
return template("tenant-a", templateId, "file-1", "DRAFT", "enabled");
|
}, (tenantId, userId, templateId, fileId) -> true, tenants);
|
|
assertStatus(401, () -> authorizer.authorize("ticket-1", "template-1"));
|
|
assertEquals(0, queries.get());
|
assertEquals(Arrays.asList("switch:tenant-a", "clear"), events);
|
assertNull(tenants.currentTenant);
|
}
|
|
@Test
|
public void templateAndFileMustMatchSignedTicket() {
|
RecordingTenantRoute tenants = new RecordingTenantRoute(new ArrayList<>());
|
TemplateAnnotationAuthorizer authorizer = authorizer(validPayload(),
|
template("tenant-a", "template-1", "other-file", "DRAFT", "enabled"), true, tenants,
|
new ArrayList<>());
|
|
assertStatus(401, () -> authorizer.authorize("ticket-1", "template-2"));
|
assertStatus(401, () -> authorizer.authorize("ticket-1", "template-1"));
|
assertNull(tenants.currentTenant);
|
}
|
|
@Test
|
public void attachmentMustContainExactlyOneDocx() {
|
RecordingTenantRoute tenants = new RecordingTenantRoute(new ArrayList<>());
|
ElnTemplateVersionEntity template = template("tenant-a", "template-1", "file-1", "DRAFT", "enabled");
|
template.setMubanFile("[{\"fileId\":\"file-1\",\"fileExtension\":\"docx\"},"
|
+ "{\"fileId\":\"file-2\",\"fileExtension\":\"DOCX\"}]");
|
TemplateAnnotationAuthorizer authorizer = authorizer(validPayload(), template, true, tenants,
|
new ArrayList<>());
|
|
assertStatus(409, () -> authorizer.authorize("ticket-1", "template-1"));
|
assertNull(tenants.currentTenant);
|
}
|
|
@Test
|
public void templateMustRemainDraftAndEnabled() {
|
RecordingTenantRoute tenants = new RecordingTenantRoute(new ArrayList<>());
|
assertStatus(409, () -> authorizer(validPayload(),
|
template("tenant-a", "template-1", "file-1", "PUBLISHED", "enabled"), true, tenants,
|
new ArrayList<>()).authorize("ticket-1", "template-1"));
|
assertStatus(409, () -> authorizer(validPayload(),
|
template("tenant-a", "template-1", "file-1", "DRAFT", "disabled"), true, tenants,
|
new ArrayList<>()).authorize("ticket-1", "template-1"));
|
assertNull(tenants.currentTenant);
|
}
|
|
@Test
|
public void deletedTemplateCannotBeAnnotated() {
|
RecordingTenantRoute tenants = new RecordingTenantRoute(new ArrayList<>());
|
ElnTemplateVersionEntity template = template("tenant-a", "template-1", "file-1", "DRAFT", "enabled");
|
template.setFDeleteMark(1);
|
|
assertStatus(409, () -> authorizer(validPayload(), template, true, tenants,
|
new ArrayList<>()).authorize("ticket-1", "template-1"));
|
assertNull(tenants.currentTenant);
|
}
|
|
@Test
|
public void revokedAnnotationAccessIsForbidden() {
|
RecordingTenantRoute tenants = new RecordingTenantRoute(new ArrayList<>());
|
ElnTemplateVersionEntity template = template("tenant-a", "template-1", "file-1", "DRAFT", "enabled");
|
assertStatus(403, () -> authorizer(validPayload(), template, false, tenants,
|
new ArrayList<>()).authorize("ticket-1", "template-1"));
|
assertNull(tenants.currentTenant);
|
}
|
|
@Test
|
public void malformedTicketAlsoClearsExistingTenantContext() {
|
List<String> events = new ArrayList<>();
|
RecordingTenantRoute tenants = new RecordingTenantRoute(events);
|
tenants.currentTenant = "stale-tenant";
|
TemplateAnnotationAuthorizer authorizer = new TemplateAnnotationAuthorizer(
|
new TemplateAnnotationTicketService(ticket -> null), templateId -> null,
|
(tenantId, userId, templateId, fileId) -> true, tenants);
|
|
assertStatus(401, () -> authorizer.authorize("expired", "template-1"));
|
|
assertNull(tenants.currentTenant);
|
assertEquals(Arrays.asList("clear"), events);
|
}
|
|
private TemplateAnnotationAuthorizer authorizer(Map<String, String> payload,
|
ElnTemplateVersionEntity template,
|
boolean allowed,
|
RecordingTenantRoute tenants,
|
List<String> events) {
|
return new TemplateAnnotationAuthorizer(ticketService(payload), templateId -> {
|
events.add("lookup:" + tenants.currentTenant + ":" + templateId);
|
return template;
|
}, (tenantId, userId, templateId, fileId) -> {
|
events.add("access:" + tenantId + ":" + userId + ":" + templateId + ":" + fileId);
|
return allowed;
|
}, tenants);
|
}
|
|
private TemplateAnnotationTicketService ticketService(Map<String, String> payload) {
|
return new TemplateAnnotationTicketService(ticket -> JsonUtil.getObjectToString(payload));
|
}
|
|
private ElnTemplateVersionEntity template(String tenantId, String templateId, String fileId,
|
String status, String enabled) {
|
ElnTemplateVersionEntity template = new ElnTemplateVersionEntity();
|
template.setFTenantId(tenantId);
|
template.setId(templateId);
|
template.setMubanFile("[{\"fileId\":\"" + fileId + "\",\"fileExtension\":\"docx\"}]");
|
template.setBizStatus(status);
|
template.setBizEnabled(enabled);
|
return template;
|
}
|
|
private Map<String, String> validPayload() {
|
return validPayload("tenant-a", "template-1", "file-1");
|
}
|
|
private Map<String, String> validPayload(String tenantId, String templateId, String fileId) {
|
Map<String, String> payload = new LinkedHashMap<>();
|
payload.put("scope", "onlyoffice:eln-template-annotate");
|
payload.put("tenantId", tenantId);
|
payload.put("userId", "user-1");
|
payload.put("fileId", fileId);
|
payload.put("bizDataId", templateId);
|
payload.put("bizModule", "eln.template");
|
payload.put("bizScene", "eln.template.annotate");
|
payload.put("pluginGuid", "asc.{2D6D6CB6-F6FC-45B7-8EC7-0CC1940F1A6F}");
|
return payload;
|
}
|
|
private void assertStatus(int expected, ThrowingRunnable operation) {
|
try {
|
operation.run();
|
fail("expected authorization failure");
|
} catch (TemplateAnnotationTicketService.InvalidAnnotationTicketException e) {
|
assertEquals(expected, e.getStatus());
|
} catch (TemplateAnnotationAuthorizer.AnnotationAuthorizationException e) {
|
assertEquals(expected, e.getStatus());
|
assertFalse(e.getMessage().contains("module-template"));
|
assertFalse(e.getMessage().contains("tenant-a"));
|
}
|
}
|
|
private interface ThrowingRunnable {
|
void run();
|
}
|
|
private static class RecordingTenantRoute implements TemplateAnnotationAuthorizer.TenantRoute {
|
private final List<String> events;
|
private final List<String> switchedTenants = new ArrayList<>();
|
private String currentTenant;
|
private boolean failSwitch;
|
private int clearCount;
|
|
private RecordingTenantRoute(List<String> events) {
|
this.events = events;
|
}
|
|
@Override
|
public void switchTo(String tenantId) {
|
events.add("switch:" + tenantId);
|
switchedTenants.add(tenantId);
|
if (failSwitch) {
|
throw new IllegalStateException("switch failed");
|
}
|
currentTenant = tenantId;
|
}
|
|
@Override
|
public void clear() {
|
events.add("clear");
|
clearCount++;
|
currentTenant = null;
|
}
|
}
|
}
|