diff options
Diffstat (limited to 'pdf-as-web/src/test/java')
5 files changed, 437 insertions, 10 deletions
diff --git a/pdf-as-web/src/test/java/at/gv/egiz/pdfas/web/test/JsonApiTest.java b/pdf-as-web/src/test/java/at/gv/egiz/pdfas/web/test/JsonApiTest.java new file mode 100644 index 00000000..71761e1d --- /dev/null +++ b/pdf-as-web/src/test/java/at/gv/egiz/pdfas/web/test/JsonApiTest.java @@ -0,0 +1,130 @@ +package at.gv.egiz.pdfas.web.test; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.junit.Assert.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.jayway.jsonpath.JsonPath; +import lombok.Lombok; +import lombok.SneakyThrows; +import lombok.val; +import org.apache.commons.io.IOUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import java.io.File; +import java.util.Arrays; +import java.util.Base64; +import java.util.Map; +import java.util.UUID; + +@RunWith(SpringRunner.class) +@SpringBootTest(properties = { + "management.endpoint.metrics.enabled=true", + "management.endpoints.web.exposure.include=metrics" +}) +@AutoConfigureMockMvc +public class JsonApiTest { + @Autowired MockMvc mvc; + @Autowired ObjectMapper om; + + static { + try { + System.setProperty("pdf-as-web.conf", + (new File(".").getCanonicalPath()) + "/src/test/resources/config/pdfas/pdf-as-web.properties"); + } catch (Throwable t) { + throw Lombok.sneakyThrow(t); + } + } + + @Test + @SneakyThrows + public void sign_single_jks() { + try (val watcher = TestUtils.OperationCountWatcher(mvc, "operation:sign", "status:ok")) { + final String pdf = Base64.getEncoder().encodeToString( + IOUtils.toByteArray(JsonApiTest.class.getResourceAsStream("/data/enc_own.pdf"))); + + final String signRequestID = UUID.randomUUID().toString(); + final String signRequest = om.writeValueAsString( + Map.of( + "requestID", signRequestID, + "inputData", pdf, + "parameters", Map.of( + "connector", "jks", + "transactionId", UUID.randomUUID().toString() + ) + ) + ); + + final String signResponse = mvc.perform( + post("/api/v2/sign/single") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(signRequest) + ) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.requestID").value(signRequestID)) + .andExpect(jsonPath("$.signedPDF").isNotEmpty()) + .andExpect(jsonPath("$.verificationResponse").exists()) + .andReturn().getResponse().getContentAsString(); + + final byte[] signedPDF = Base64.getDecoder().decode(JsonPath.<String>read(signResponse, "$.signedPDF")); + assertArrayEquals("Signed data looks PDF-ish (%PDF- header)", + new byte[]{'%', 'P', 'D', 'F', '-'}, Arrays.copyOfRange(signedPDF, 0, 5)); + } + } + + @Test + @SneakyThrows + public void verify_single() { + try (val watcher = TestUtils.OperationCountWatcher(mvc, "operation:verify", "status:ok")) { + final String pdf = Base64.getEncoder().encodeToString( + IOUtils.toByteArray(JsonApiTest.class.getResourceAsStream("/data/dummy-pdf-signed.pdf"))); + + final String verifyRequestID = UUID.randomUUID().toString(); + final String verifyRequest = om.writeValueAsString( + Map.of( + "requestID", verifyRequestID, + "inputData", pdf, + "verificationLevel", "intOnly" + ) + ); + + mvc.perform( + post("/api/v2/verify") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(verifyRequest) + ) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.verifyResults").isArray()) + .andExpect(jsonPath("$.verifyResults.length()").value(1)) + .andExpect(jsonPath("$.verifyResults[0].requestID").value(verifyRequestID)) + .andExpect(jsonPath("$.verifyResults[0].error").isEmpty()) + .andExpect(jsonPath("$.verifyResults[0].signatureIndex").value(0)) + .andExpect(jsonPath("$.verifyResults[0].signedBy").value("CN=MOA-ID IDP (Test-Version),O=EGIZ,L=Graz,C=AT")); + } + } + + @Test + @SneakyThrows + public void openapi_docs_test() { + mvc.perform(get("/v3/api-docs")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.paths['/api/v2/sign/single']").exists()) + .andExpect(jsonPath("$.paths['/api/v2/sign/bulk']").exists()) + .andExpect(jsonPath("$.paths['/api/v2/sign/multiple']").exists()) + .andExpect(jsonPath("$.paths['/api/v2/sign/multiple/get-result']").exists()) + .andExpect(jsonPath("$.paths['/api/v2/verify']").exists()); + } +} diff --git a/pdf-as-web/src/test/java/at/gv/egiz/pdfas/web/test/MockMoaSigningTest.java b/pdf-as-web/src/test/java/at/gv/egiz/pdfas/web/test/MockMoaSigningTest.java new file mode 100644 index 00000000..466cfcca --- /dev/null +++ b/pdf-as-web/src/test/java/at/gv/egiz/pdfas/web/test/MockMoaSigningTest.java @@ -0,0 +1,267 @@ +package at.gv.egiz.pdfas.web.test; + +import at.gv.e_government.reference.namespace.moa._20020822_.*; +import at.gv.egiz.pdfas.common.exceptions.PdfAsException; +import at.gv.egiz.pdfas.lib.api.Configuration; +import at.gv.egiz.pdfas.lib.api.IConfigurationConstants; +import at.gv.egiz.pdfas.lib.api.sign.IPlainSigner; +import at.gv.egiz.pdfas.lib.impl.configuration.ConfigurationImpl; +import at.gv.egiz.pdfas.moa.MOAConnector; +import at.gv.egiz.pdfas.sigs.pades.PAdESSignerKeystore; +import at.gv.egiz.pdfas.sigs.pkcs7detached.PKCS7DetachedSigner; +import at.gv.egiz.pdfas.web.config.WebConfiguration; +import at.gv.egiz.pdfas.web.helper.PdfAsHelper; +import at.gv.egiz.pdfas.web.servlets.ExternSignServlet; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.jayway.jsonpath.JsonPath; +import iaik.x509.X509Certificate; +import jakarta.jws.WebService; +import jakarta.xml.ws.Endpoint; +import lombok.Lombok; +import lombok.SneakyThrows; +import lombok.val; +import org.apache.commons.io.IOUtils; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.event.annotation.BeforeTestClass; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import java.io.*; +import java.net.ServerSocket; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.util.*; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + +@RunWith(SpringRunner.class) +@SpringBootTest(properties = { + "management.endpoint.metrics.enabled=true", + "management.endpoints.web.exposure.include=metrics" +}) +@AutoConfigureMockMvc +public class MockMoaSigningTest { + @Autowired MockMvc mvc; + @Autowired ObjectMapper om; + + static { + try { + System.setProperty("pdf-as-web.conf", + (new File(".").getCanonicalPath()) + "/src/test/resources/config/pdfas/pdf-as-web.properties"); + } catch (Throwable t) { + throw Lombok.sneakyThrow(t); + } + } + + @BeforeClass + public static void jceWorkaround() { + System.setProperty("javax.net.ssl.trustStoreType", "JKS"); + } + + @WebService( + serviceName = "SignatureCreationService", + portName = "SignatureCreationPort", + targetNamespace = "http://reference.e-government.gv.at/namespace/moa/20020822#", + endpointInterface = + "at.gv.e_government.reference.namespace.moa._20020822_.SignatureCreationPortType") + static class MockMoa implements AutoCloseable, SignatureCreationPortType { + @SneakyThrows + private static int freePort() { + try (ServerSocket socket = new ServerSocket(0)) { + return socket.getLocalPort(); + } + } + private static String azstring(int length) { + return + new Random().ints(97,123).limit(length) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString(); + } + public final int port = freePort(); + public final String endpointURL = "http://127.0.0.1:"+port+"/moa-spss/services/SignatureCreation"; + public final Endpoint endpoint = + Endpoint.publish(endpointURL, this); + public final String keyIdentifier = azstring(16); + + public final IPlainSigner signer; + + @SneakyThrows + private static Properties getBaseProperties() { + try (InputStream in = new FileInputStream(System.getProperty(ExternSignServlet.PDF_AS_WEB_CONF))) { + val props = new Properties(); + props.load(in); + return props; + } + } + + @SneakyThrows + private static void injectProperties(Map<String, String> overlay) { + val props = getBaseProperties(); + if (overlay != null) overlay.forEach(props::setProperty); + try (val out = new ByteArrayOutputStream()) { + props.store(out, "test config"); + try (val in = new ByteArrayInputStream(out.toByteArray())) { + WebConfiguration.configure(in); + PdfAsHelper.reloadConfig(); + PdfAsHelper.init(); + } + } + } + + @SneakyThrows + public MockMoa() { + try { + KeyStore ks = KeyStore.getInstance("PKCS12"); + try (InputStream is = MockMoaSigningTest.class.getResourceAsStream("/config/pdfas/test.p12")) { + ks.load(is, "123456".toCharArray()); + } + val alias = ks.aliases().nextElement(); + val privateKey = (PrivateKey) ks.getKey(alias, "123456".toCharArray()); + val certificate = new X509Certificate(ks.getCertificate(alias).getEncoded()); + signer = new PAdESSignerKeystore(privateKey, certificate); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + + // inject ourselves into the configuration + injectProperties(Map.of( + "moal."+keyIdentifier+".enabled", "true", + "moal."+keyIdentifier+".url", endpointURL, + "moal."+keyIdentifier+".KeyIdentifier", "KG_TEST", + "moal."+keyIdentifier+".Certificate", + "base64:"+Base64.getEncoder().encodeToString(signer.getCertificate(null).getEncoded()) + )); + } + + @Override + public CreateCMSSignatureResponseType createCMSSignature(CreateCMSSignatureRequest body) throws MOAFault { + val signatureInfoList = body.getSingleSignatureInfo(); + Assertions.assertEquals(1, signatureInfoList.size()); + val signatureInfo = signatureInfoList.get(0); + val dataObjectInfo = signatureInfo.getDataObjectInfo(); + Assertions.assertEquals("detached", dataObjectInfo.getStructure()); + val dataObject = dataObjectInfo.getDataObject(); + Assertions.assertEquals("application/pdf", dataObject.getMetaInfo().getMimeType()); + val content = dataObject.getContent().getBase64Content(); + Assertions.assertNotEquals(0, content.length); + Assertions.assertEquals("KG_TEST", body.getKeyIdentifier()); + try { + val cms = signer.sign(content, null, null, null); + val response = new CreateCMSSignatureResponseType(); + response.getCMSSignatureOrErrorResponse().add(cms); + return response; + } catch (PdfAsException e) { + throw new MOAFault("Failed to create detached CMS in fake MOA", e); + } + } + + @Override + public CreateXMLSignatureResponseType createXMLSignature(CreateXMLSignatureRequest body) throws MOAFault { + throw new IllegalStateException("We do not create XML signatures in this house."); + } + + public void close() { + endpoint.stop(); + // remove the injected overlay + injectProperties(null); + } + } + + @Test + @SneakyThrows + public void signWithMockMOA() { + try (val watcher = TestUtils.OperationCountWatcher(mvc, "operation:sign", "status:ok")) { + try (MockMoa moa = new MockMoa()) { + + final String pdf = Base64.getEncoder().encodeToString( + IOUtils.toByteArray(JsonApiTest.class.getResourceAsStream("/data/enc_own.pdf"))); + + final String signRequestID = UUID.randomUUID().toString(); + final String signRequest = om.writeValueAsString( + Map.of( + "requestID", signRequestID, + "inputData", pdf, + "parameters", Map.of( + "connector", "moa", + "keyIdentifier", moa.keyIdentifier, + "transactionId", UUID.randomUUID().toString() + ) + ) + ); + + final String signResponse = mvc.perform( + post("/api/v2/sign/single") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(signRequest) + ) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.requestID").value(signRequestID)) + .andExpect(jsonPath("$.signedPDF").isNotEmpty()) + .andExpect(jsonPath("$.verificationResponse").exists()) + .andReturn().getResponse().getContentAsString(); + + final byte[] signedPDF = Base64.getDecoder().decode(JsonPath.<String>read(signResponse, "$.signedPDF")); + assertArrayEquals("Signed data looks PDF-ish (%PDF- header)", + new byte[]{'%', 'P', 'D', 'F', '-'}, Arrays.copyOfRange(signedPDF, 0, 5)); + } + } + } + + @Test + @SneakyThrows + public void moaTimeout() { + try (MockMoa moa = new MockMoa() { + @Override + @SneakyThrows + public CreateCMSSignatureResponseType createCMSSignature(CreateCMSSignatureRequest body) throws MOAFault { + // this will cause a timeout + Thread.sleep(300 * 1000); + throw new RuntimeException("unreachable"); + } + }) { + final String pdf = Base64.getEncoder().encodeToString( + IOUtils.toByteArray(JsonApiTest.class.getResourceAsStream("/data/enc_own.pdf"))); + + final String signRequestID = UUID.randomUUID().toString(); + final String signRequest = om.writeValueAsString( + Map.of( + "requestID", signRequestID, + "inputData", pdf, + "parameters", Map.of( + "connector", "moa", + "keyIdentifier", moa.keyIdentifier, + "transactionId", UUID.randomUUID().toString() + ) + ) + ); + + mvc.perform( + post("/api/v2/sign/single") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(signRequest) + ) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.requestID").value(signRequestID)) + .andExpect(jsonPath("$.signedPDF").isEmpty()) + .andExpect(jsonPath("$.errorCode").value(11022)); + } + } +} diff --git a/pdf-as-web/src/test/java/at/gv/egiz/pdfas/web/test/SimpleSignServletTest.java b/pdf-as-web/src/test/java/at/gv/egiz/pdfas/web/test/SimpleSignServletTest.java index 7c020b17..8ab9cfaf 100644 --- a/pdf-as-web/src/test/java/at/gv/egiz/pdfas/web/test/SimpleSignServletTest.java +++ b/pdf-as-web/src/test/java/at/gv/egiz/pdfas/web/test/SimpleSignServletTest.java @@ -6,11 +6,11 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Enumeration; -import javax.servlet.ServletConfig; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletConfig; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; import org.junit.BeforeClass; import org.junit.Ignore; diff --git a/pdf-as-web/src/test/java/at/gv/egiz/pdfas/web/test/SimpleVerifyServletTest.java b/pdf-as-web/src/test/java/at/gv/egiz/pdfas/web/test/SimpleVerifyServletTest.java index 046a1203..e0075940 100644 --- a/pdf-as-web/src/test/java/at/gv/egiz/pdfas/web/test/SimpleVerifyServletTest.java +++ b/pdf-as-web/src/test/java/at/gv/egiz/pdfas/web/test/SimpleVerifyServletTest.java @@ -9,11 +9,11 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Enumeration; -import javax.servlet.ServletConfig; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletConfig; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; diff --git a/pdf-as-web/src/test/java/at/gv/egiz/pdfas/web/test/TestUtils.java b/pdf-as-web/src/test/java/at/gv/egiz/pdfas/web/test/TestUtils.java new file mode 100644 index 00000000..4ee606bb --- /dev/null +++ b/pdf-as-web/src/test/java/at/gv/egiz/pdfas/web/test/TestUtils.java @@ -0,0 +1,30 @@ +package at.gv.egiz.pdfas.web.test; + +import com.jayway.jsonpath.JsonPath; +import lombok.val; +import org.junit.Assert; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import java.util.Arrays; +import java.util.List; + +public class TestUtils { + public static double getOperationCount(MockMvc mvc, String... tags) throws Exception { + val builder = MockMvcRequestBuilders.get("/actuator/metrics/pdfas_requests"); + Arrays.stream(tags).forEach(tag -> builder.param("tag", tag)); + val result = + mvc.perform(builder).andReturn().getResponse(); + if (result.getStatus() == 404) return 0.0; + Assert.assertEquals(200, result.getStatus()); + return JsonPath.<List<Double>>read( + result.getContentAsString(), + "$.measurements[?(@.statistic == 'COUNT')].value") + .get(0); + } + + public static AutoCloseable OperationCountWatcher(MockMvc mvc, String... tags) throws Exception { + val initialCount = TestUtils.getOperationCount(mvc, tags); + return () -> Assert.assertEquals(initialCount+1.0, TestUtils.getOperationCount(mvc, tags), 0.0001); + } +} |
