diff --git a/.github/ISSUE_TEMPLATE/release-checklist.md b/.github/ISSUE_TEMPLATE/release-checklist.md new file mode 100644 index 000000000..912285221 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/release-checklist.md @@ -0,0 +1,43 @@ +--- +name: Release checklist +about: Použi tento checklist na testovanie artefaktov pred public releaseom +title: "Release " +labels: release +assignees: "@slovensko-digital/autogram-release-team" +--- + +## Windows + +- [ ] funguje inštalácia na Windows cez stiahnutý .msi +- [ ] funguje spustenie v GUI móde +- [ ] funguje URL handler [autogram://go](autogram://go) +- [ ] funguje GUI otvoriť jeden súbor, ten sa zobrazí, viem ho podpísať, vytvorí sa podpísaný súbor +- [ ] funguje CLI `autogram --help` + +## Linux +- [ ] funguje inštalácia na Linux (Debian-based) cez stiahnutý .deb +- [ ] funguje inštalácia na Linux cez stiahnutý .rpm +- [ ] funguje spustenie v GUI móde +- [ ] funguje URL handler [autogram://go](autogram://go) +- [ ] funguje GUI otvoriť jeden súbor, ten sa zobrazí, viem ho podpísať, vytvorí sa podpísaný súbor +- [ ] funguje CLI `autogram --help` + +## MacOS +- [ ] funguje inštalácia na MacOS cez stiahnutý .pkg +- [ ] funguje spustenie v GUI móde +- [ ] funguje URL handler [autogram://go](autogram://go) +- [ ] funguje GUI otvoriť jeden súbor, ten sa zobrazí, viem ho podpísať, vytvorí sa podpísaný súbor +- [ ] funguje CLI `/Applications/Autogram.app/Contents/MacOS/AutogramApp --help` + + +## Na aspoň jednom systéme + +- [ ] fungujú všetky smoke testy `./mvnw test -Psmoke` +- [ ] funguje spustenie v GUI serverovom móde `autogram --url=autogram://listen?protocol=http&port=37201` na inom porte +- [ ] funguje CLI `autogram --cli --source source.pdf` +- [ ] funguje CLI `autogram --cli --source source.pdf --target target.pdf` +- [ ] funguje CLI `autogram --cli --source source-dir --target target-dir` +- [ ] funguje API info request +- [ ] funguje API docs request +- [ ] funguje API sign request +- [ ] funguje s [extension](https://github.com/slovensko-digital/autogram-extension) \ No newline at end of file diff --git a/.github/workflows/prerelease.yaml b/.github/workflows/prerelease.yaml new file mode 100644 index 000000000..43abc50af --- /dev/null +++ b/.github/workflows/prerelease.yaml @@ -0,0 +1,22 @@ +name: Pre-release +on: + release: + types: [prereleased] +jobs: + checklist: + runs-on: ubuntu-latest + steps: + - uses: imjohnbo/extract-issue-template-fields@f5232d9bc6ad9aff26c462e60a2c8ef0b17aa3fa + id: issueTemplate + with: + path: .github/ISSUE_TEMPLATE/release-checklist.md + + - name: Create release checklist issue + uses: imjohnbo/issue-bot@3d96848fb5e9a4a473bb81ae62b4b4866a56e93a + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + assignees: ${{ steps.issueTemplate.outputs.assignees }} + labels: ${{ steps.issueTemplate.outputs.labels }} + title: ${{ steps.issueTemplate.outputs.title }} ${{ github.event.release.tag_name }} + body: ${{ steps.issueTemplate.outputs.body }} diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 000000000..f1c5790f4 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,33 @@ +Release robí slovensko.digital, ale checklist je verejný aby mohol každý contributor samostatne +pretestovať zmeny pred tým než urobí PR. + +# Release Checklist + +## Kontrola nového kódu + +Je potrebné skontrolovať či nepribudol škodlivý kód, exfiltrácia secrets cez GH actions, či je nová core funkcionalita pokrytá testami. + +## Testing + +- [ ] zelené všetky automatizované testy +- [ ] funguje inštalácia na Windows cez stiahnutý .msi +- [ ] funguje inštalácia na MacOS cez stiahnutý .pkg +- [ ] funguje inštalácia na Linux (Debian-based) cez stiahnutý .deb +- [ ] funguje inštalácia na Linux cez stiahnutý .rpm +- [ ] funguje spustenie v GUI móde +- [ ] funguje spustenie v GUI serverovom móde `autogram --url=autogram://listen?protocol=http&port=37200` +- [ ] funguje URL handler na Windows [autogram://go](autogram://go) +- [ ] funguje URL handler na MacOS [autogram://go](autogram://go) +- [ ] funguje URL handler na Linux [autogram://go](autogram://go) +- [ ] funguje GUI otvoriť jeden súbor, ten sa zobrazí, viem ho podpísať, vytvorí sa podpísaný súbor +- [ ] funguje CLI `autogram --cli --source source.pdf` +- [ ] funguje CLI `autogram --cli --source source.pdf --target target.pdf` +- [ ] funguje CLI `autogram --cli --source source-dir --target target-dir` +- [ ] funguje API info request +- [ ] funguje API docs request +- [ ] funguje API sign request +- [ ] funguje s [extension](https://github.com/slovensko-digital/autogram-extension) + +## Pripraviť popis releasu + +vid. existujúce [Releases](https://github.com/slovensko-digital/autogram/releases) diff --git a/pom.xml b/pom.xml index a72ff3c8c..e1856fcfe 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,9 @@ 5.4.0 1.5.0 2.9.1 + 2.0 1.3.0 + HttpSmokeTest @@ -133,6 +135,26 @@ ${xmlunit.version} test + + org.yaml + snakeyaml + ${snakeyml.version} + test + + + org.junit.jupiter + junit-jupiter-params + ${junit.version} + test + + + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + + com.google.jimfs jimfs @@ -150,10 +172,9 @@ 3.1.2 ${jlink.jdk.path}${file.separator}bin${file.separator}java - - - **/BatchSigningHttpSmokeTests.java - + + ${testExcludedGroups} @@ -519,5 +540,25 @@ Bearer ${env.GITHUB_TOKEN} + + + smoke + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + ${jlink.jdk.path}${file.separator}bin${file.separator}java + HttpSmokeTest + + + + + + + + \ No newline at end of file diff --git a/src/main/java/digital/slovensko/autogram/server/dto/ServerSigningParameters.java b/src/main/java/digital/slovensko/autogram/server/dto/ServerSigningParameters.java index f50099e02..5570024b1 100644 --- a/src/main/java/digital/slovensko/autogram/server/dto/ServerSigningParameters.java +++ b/src/main/java/digital/slovensko/autogram/server/dto/ServerSigningParameters.java @@ -47,12 +47,12 @@ public enum VisualizationWidthEnum { private final VisualizationWidthEnum visualizationWidth; public ServerSigningParameters(SignatureLevel level, ASiCContainerType container, - String containerFilename, String containerXmlns, SignaturePackaging packaging, - DigestAlgorithm digestAlgorithm, - Boolean en319132, LocalCanonicalizationMethod infoCanonicalization, - LocalCanonicalizationMethod propertiesCanonicalization, LocalCanonicalizationMethod keyInfoCanonicalization, - String schema, String transformation, - String Identifier, boolean checkPDFACompliance, VisualizationWidthEnum preferredPreviewWidth) { + String containerFilename, String containerXmlns, SignaturePackaging packaging, + DigestAlgorithm digestAlgorithm, + Boolean en319132, LocalCanonicalizationMethod infoCanonicalization, + LocalCanonicalizationMethod propertiesCanonicalization, LocalCanonicalizationMethod keyInfoCanonicalization, + String schema, String transformation, + String Identifier, boolean checkPDFACompliance, VisualizationWidthEnum preferredPreviewWidth) { this.level = level; this.container = container; this.containerXmlns = containerXmlns; diff --git a/src/test/java/digital/slovensko/autogram/BatchSigningHttpSmokeTests.java b/src/test/java/digital/slovensko/autogram/BatchSigningHttpSmokeTests.java index 18440dec5..6d66b1317 100644 --- a/src/test/java/digital/slovensko/autogram/BatchSigningHttpSmokeTests.java +++ b/src/test/java/digital/slovensko/autogram/BatchSigningHttpSmokeTests.java @@ -11,6 +11,7 @@ import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.util.EntityUtils; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Tag; import com.google.gson.Gson; @@ -18,6 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +@Tag("HttpSmokeTest") public class BatchSigningHttpSmokeTests { @Test void testBatchSigningHappyScenario() throws URISyntaxException, ClientProtocolException, IOException { diff --git a/src/test/java/digital/slovensko/autogram/SignHttpSmokeTest.java b/src/test/java/digital/slovensko/autogram/SignHttpSmokeTest.java new file mode 100644 index 000000000..97e1150ee --- /dev/null +++ b/src/test/java/digital/slovensko/autogram/SignHttpSmokeTest.java @@ -0,0 +1,214 @@ +package digital.slovensko.autogram; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Map; + +import org.apache.http.HttpStatus; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.junit.platform.commons.util.ReflectionUtils; +import org.yaml.snakeyaml.Yaml; + +import digital.slovensko.autogram.server.dto.Document; +import digital.slovensko.autogram.server.dto.ServerSigningParameters; +import digital.slovensko.autogram.server.dto.ServerSigningParameters.LocalCanonicalizationMethod; +import digital.slovensko.autogram.server.dto.ServerSigningParameters.VisualizationWidthEnum; +import eu.europa.esig.dss.enumerations.ASiCContainerType; +import eu.europa.esig.dss.enumerations.DigestAlgorithm; +import eu.europa.esig.dss.enumerations.SignatureLevel; +import eu.europa.esig.dss.enumerations.SignaturePackaging; +import digital.slovensko.autogram.server.dto.SignRequestBody; +import digital.slovensko.autogram.server.dto.SignResponse; + +// TODO mvn test -Psmoke +@TestInstance(Lifecycle.PER_CLASS) +@Tag("HttpSmokeTest") +public class SignHttpSmokeTest { + + Map data; + + URI baseUri; + HttpClientBuilder clientBuilder; + + public static Object getNested(Object obj, String... keys) { + for (String key : keys) { + if (obj instanceof Map) { + obj = ((Map) obj).get(key); + } else { + return null; + } + } + return obj; + } + + public Object getNested(String... keys) { + return getNested(data, keys); + } + + public String getDecoded(Object obj) { + return new String(Base64.getDecoder().decode(((String) obj))); + } + + public String getDecoded(String... keys) { + return getDecoded(getNested(keys)); + } + + @BeforeAll + public void initAll() throws FileNotFoundException, URISyntaxException { + Yaml yaml = new Yaml(); + var inputStream = new FileInputStream( + new File("src/main/resources/digital/slovensko/autogram/server/server.yml")); + + data = (Map) yaml.load(inputStream); + baseUri = new URI("http://localhost:37200"); + clientBuilder = HttpClientBuilder.create(); + } + + @Test + @Tag("HttpSmokeTest") + void testSigningHappyScenario() throws URISyntaxException, ClientProtocolException, IOException { + + var signRequest = new HttpPost(baseUri.resolve("/sign")); + signRequest.setHeader("Content-Type", "application/json"); + var signRequestBody = """ + {"document": { + "content": "Testovací dokument", + "filename": "TextDocument.txt" + }, + "parameters": { + "level": "XAdES_BASELINE_B", + "container": "ASiC_E" + }, + "payloadMimeType": "text/plain" + } + """; + System.out.println("Sign request body: " + signRequestBody); + + signRequest.setEntity(new StringEntity(signRequestBody, "UTF-8")); + var signResponse = clientBuilder.build().execute(signRequest); + assertEquals(HttpStatus.SC_OK, signResponse.getStatusLine().getStatusCode()); + // System.out.println("Sign Response: " + signResponse.getStatusLine()); + // System.out.println("Sign Response: " + new String( + // signResponse.getEntity().getContent().readAllBytes(), StandardCharsets.UTF_8)); + } + + @ParameterizedTest + @ValueSource(strings = { + "XAdES-XML-Base64-HTML_md", "XAdES-XML-Base64-TXT", "XAdES-XML-TXT-HTML", + "XAdES-XML-TXT-TXT_md", "XAdES-ASiC_E-Base64-HTML", "XAdES-ASiC_E-Base64-TXT", + "XAdES-ASiC_E-TXT-HTML_md", "XAdES-ASiC_E-TXT-TXT", "XAdES-ASiC_E-SKXDC-Base64-HTML", + "XAdES-ASiC_E-SKXDC-TXT-HTML", "PAdES-PDF_lg", "XAdES-PDF", + "XAdES-ASiC_E-PDF", "XAdES-ASiC_E-TXT", "XAdES-ASiC_E-DOCX", + "CAdES-ASiC_E-DOCX", "CAdES-ASiC_E-PNG_md", "CAdES-PNG_lg", + }) + public void testFromYaml(String exampleName) throws ClientProtocolException, IOException, IllegalAccessException, + NoSuchFieldException, SecurityException { + var example = getNested("components", "examples", exampleName, "value"); + var document = (Map) getNested(example, "document"); + var parameters = getNested(example, "parameters"); + var payloadMimeType = getNested(example, "payloadMimeType"); + + var rDocument = new Document(document.get("filename"), document.get("content")); + var rParameters = fromMap((Map) parameters); + var rPayloadMimeType = (String) payloadMimeType; + + var body = new SignRequestBody( + rDocument, + rParameters, + rPayloadMimeType); + var gson = new com.google.gson.Gson(); + + var signRequest = new HttpPost(baseUri.resolve("/sign")); + signRequest.setHeader("Content-Type", "application/json"); + var entity = new StringEntity(gson.toJson(body), "UTF-8"); + signRequest.setEntity(entity); + + System.out.println("Sign request body: " + EntityUtils.toString(entity)); + var signResponse = clientBuilder.build().execute(signRequest); + System.out.println("Sign Response: " + signResponse.getStatusLine()); + + var responseStr = new String( + signResponse.getEntity().getContent().readAllBytes(), StandardCharsets.UTF_8); + System.out.println("Sign Response: " + responseStr); + assertEquals(HttpStatus.SC_OK, signResponse.getStatusLine().getStatusCode()); + var response = gson.fromJson(responseStr, SignResponse.class); + + ReflectionUtils + .findFields(SignResponse.class, (e) -> true, + ReflectionUtils.HierarchyTraversalMode.TOP_DOWN) + .forEach(f -> { + f.setAccessible(true); + try { + var o = f.get(response); + assertNotNull(o); + System.out.println(f.getName() + ": " + o); + } catch (IllegalArgumentException | IllegalAccessException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + }); + } + + + private static ServerSigningParameters fromMap(Map map) { + var level = SignatureLevel.valueByName((String) map.get("level")); + var container = fromMapToEnum(ASiCContainerType.class, map.get("container")); + var containerFilename = (String) map.get("containerFilename"); + var containerXmlns = (String) map.get("containerXmlns"); + var packaging = fromMapToEnum(SignaturePackaging.class, map.get("packaging")); + var digestAlgorithm = fromMapToEnum(DigestAlgorithm.class, map.get("digestAlgorithm")); + var en319132 = (Boolean) map.get("en319132"); + var infoCanonicalization = fromMapToEnum(LocalCanonicalizationMethod.class, map.get("infoCanonicalization")); + var propertiesCanonicalization = fromMapToEnum(LocalCanonicalizationMethod.class, + map.get("propertiesCanonicalization")); + var keyInfoCanonicalization = fromMapToEnum(LocalCanonicalizationMethod.class, + map.get("keyInfoCanonicalization")); + var schema = (String) map.get("schema"); + var transformation = (String) map.get("transformation"); + var identifier = (String) map.get("identifier"); + var checkPDFACompliance = map.getOrDefault("checkPDFACompliance", "false") == "true"; + var visualizationWidth = fromMapToEnum(VisualizationWidthEnum.class, map.get("visualizationWidth")); + + return new ServerSigningParameters( + level, + container, + containerFilename, + containerXmlns, + packaging, + digestAlgorithm, + en319132, + infoCanonicalization, + propertiesCanonicalization, + keyInfoCanonicalization, + schema, + transformation, + identifier, checkPDFACompliance, visualizationWidth); + } + + private static > T fromMapToEnum(Class clazz, Object obj) { + var visualizationWidthStr = (String) obj; + if (visualizationWidthStr == null) + return null; + return T.valueOf(clazz, visualizationWidthStr); + } +} \ No newline at end of file