diff --git a/NOTICE b/NOTICE index 53e7cfef..4fa4feb8 100644 --- a/NOTICE +++ b/NOTICE @@ -12,13 +12,12 @@ This software includes third party software subject to the following licenses: Apache Commons Lang under Apache License, Version 2.0 Apache HttpClient under Apache License, Version 2.0 Apache HttpCore under Apache License, Version 2.0 - Extended StAX API under Eclipse Distribution License - v 1.0 - fastinfoset under Apache License, Version 2.0 or Eclipse Distribution License - v 1.0 istack common utility code runtime under Eclipse Distribution License - v 1.0 + Jakarta Activation under EDL 1.0 + Jakarta Activation API jar under EDL 1.0 Jakarta Annotations API under EPL 2.0 or GPL2 w/ CPE + Jakarta XML Binding API under Eclipse Distribution License - v 1.0 jakarta.ws.rs-api under EPL 2.0 or GPL2 w/ CPE - jakarta.xml.bind-api under Eclipse Distribution License - v 1.0 - JavaBeans Activation Framework API jar under EDL 1.0 javax.inject:1 as OSGi bundle under EPL 2.0 or GPL2 w/ CPE JAXB Runtime under Eclipse Distribution License - v 1.0 JAXB2 Basics - Runtime under BSD-Style License diff --git a/pom.xml b/pom.xml index 5a16c8ca..a78e4f4a 100644 --- a/pom.xml +++ b/pom.xml @@ -17,8 +17,8 @@ 1.8 1.8 set_with_-Dproject.previousVersion=X.Y - 2.7 - 1.7.30 + 2.8 + 1.7.31 @@ -26,21 +26,21 @@ org.springframework spring-framework-bom - 5.3.3 + 5.3.8 pom import org.glassfish.jersey jersey-bom - 2.33 + 2.34 pom import org.junit junit-bom - 5.7.0 + 5.8.0-M1 pom import @@ -88,7 +88,7 @@ org.glassfish.jaxb jaxb-runtime - 2.3.2 + 2.3.4 runtime @@ -106,7 +106,7 @@ commons-io commons-io - 2.8.0 + 2.10.0 test @@ -125,12 +125,12 @@ org.springframework.ws spring-xml - 3.0.10.RELEASE + 3.1.1 org.apache.commons commons-lang3 - 3.11 + 3.12.0 @@ -170,7 +170,7 @@ nl.jqno.equalsverifier equalsverifier - 3.5.2 + 3.6.1 test @@ -194,7 +194,7 @@ no.digipost digg - 0.25 + 0.30 test @@ -227,12 +227,12 @@ org.glassfish.jaxb jaxb-runtime - 2.3.2 + 2.3.4 jakarta.xml.bind jakarta.xml.bind-api - 2.3.2 + 2.3.3 @@ -243,7 +243,7 @@ com.github.siom79.japicmp japicmp-maven-plugin - 0.15.2 + 0.15.3 @@ -271,7 +271,7 @@ maven-surefire-plugin - 3.0.0-M4 + 3.0.0-M5 maven-deploy-plugin @@ -283,7 +283,7 @@ maven-dependency-plugin - 3.1.1 + 3.1.2 maven-install-plugin @@ -291,15 +291,15 @@ maven-resources-plugin - 3.1.0 + 3.2.0 maven-site-plugin - 3.8.2 + 3.9.1 maven-javadoc-plugin - 3.1.1 + 3.2.0 maven-jar-plugin @@ -331,7 +331,7 @@ org.codehaus.mojo versions-maven-plugin - 2.7 + 2.8.1 diff --git a/src/main/java/no/digipost/signature/client/asice/ASiCEAttachable.java b/src/main/java/no/digipost/signature/client/asice/ASiCEAttachable.java index bbd80ac3..844ca482 100644 --- a/src/main/java/no/digipost/signature/client/asice/ASiCEAttachable.java +++ b/src/main/java/no/digipost/signature/client/asice/ASiCEAttachable.java @@ -5,15 +5,19 @@ import static org.apache.commons.codec.digest.DigestUtils.sha256; public interface ASiCEAttachable extends SignableFileReference { + + public static final String XML_MEDIATYPE = "application/xml"; + @Override String getFileName(); - byte[] getBytes(); + byte[] getContent(); - String getMimeType(); + @Override + String getMediaType(); @Override default byte[] getSha256() { - return sha256(getBytes()); + return sha256(getContent()); } } diff --git a/src/main/java/no/digipost/signature/client/asice/CreateASiCE.java b/src/main/java/no/digipost/signature/client/asice/CreateASiCE.java index 17d6daaa..c4c6683d 100644 --- a/src/main/java/no/digipost/signature/client/asice/CreateASiCE.java +++ b/src/main/java/no/digipost/signature/client/asice/CreateASiCE.java @@ -43,8 +43,7 @@ public DocumentBundle createASiCE(JOB job) { Manifest manifest = manifestCreator.createManifest(job, sender); - List files = new ArrayList<>(); - files.add(job.getDocument()); + List files = new ArrayList<>(job.getDocuments()); files.add(manifest); Signature signature = createSignature.createSignature(files, keyStoreConfig); diff --git a/src/main/java/no/digipost/signature/client/asice/archive/CreateZip.java b/src/main/java/no/digipost/signature/client/asice/archive/CreateZip.java index e7a10795..0ad6a2be 100644 --- a/src/main/java/no/digipost/signature/client/asice/archive/CreateZip.java +++ b/src/main/java/no/digipost/signature/client/asice/archive/CreateZip.java @@ -16,9 +16,9 @@ public byte[] zipIt(final List files) { try (ZipOutputStream zipOutputStream = new ZipOutputStream(archive)) { for (ASiCEAttachable file : files) { ZipEntry zipEntry = new ZipEntry(file.getFileName()); - zipEntry.setSize(file.getBytes().length); + zipEntry.setSize(file.getContent().length); zipOutputStream.putNextEntry(zipEntry); - zipOutputStream.write(file.getBytes()); + zipOutputStream.write(file.getContent()); zipOutputStream.closeEntry(); } } diff --git a/src/main/java/no/digipost/signature/client/asice/manifest/CreateDirectManifest.java b/src/main/java/no/digipost/signature/client/asice/manifest/CreateDirectManifest.java index dc703d4c..75b022f2 100644 --- a/src/main/java/no/digipost/signature/client/asice/manifest/CreateDirectManifest.java +++ b/src/main/java/no/digipost/signature/client/asice/manifest/CreateDirectManifest.java @@ -3,25 +3,25 @@ import no.digipost.signature.api.xml.XMLDirectDocument; import no.digipost.signature.api.xml.XMLDirectSignatureJobManifest; import no.digipost.signature.api.xml.XMLDirectSigner; +import no.digipost.signature.api.xml.XMLHref; import no.digipost.signature.api.xml.XMLSender; import no.digipost.signature.client.core.AuthenticationLevel; import no.digipost.signature.client.core.IdentifierInSignedDocuments; import no.digipost.signature.client.core.OnBehalfOf; import no.digipost.signature.client.core.Sender; import no.digipost.signature.client.core.SignatureType; -import no.digipost.signature.client.direct.DirectDocument; import no.digipost.signature.client.direct.DirectJob; import no.digipost.signature.client.direct.DirectSigner; import java.util.ArrayList; import java.util.List; +import static java.util.stream.Collectors.toList; + public class CreateDirectManifest extends ManifestCreator { @Override Object buildXmlManifest(DirectJob job, Sender sender) { - DirectDocument document = job.getDocument(); - List signers = new ArrayList<>(); for (DirectSigner signer : job.getSigners()) { XMLDirectSigner xmlSigner = new XMLDirectSigner() @@ -39,13 +39,14 @@ Object buildXmlManifest(DirectJob job, Sender sender) { .withSigners(signers) .withRequiredAuthentication(job.getRequiredAuthentication().map(AuthenticationLevel::getXmlEnumValue).orElse(null)) .withSender(new XMLSender().withOrganizationNumber(sender.getOrganizationNumber())) - .withDocument(new XMLDirectDocument() - .withTitle(document.getTitle()) - .withDescription(document.getMessage()) - .withHref(document.getFileName()) - .withMime(document.getMimeType()) - ) - .withIdentifierInSignedDocuments(job.getIdentifierInSignedDocuments().map(IdentifierInSignedDocuments::getXmlEnumValue).orElse(null)) - ; + .withTitle(job.getTitle()) + .withDescription(job.getDescription().orElse(null)) + .withDocuments(job.getDocuments().stream() + .map(document -> new XMLDirectDocument() + .withTitle(document.getTitle()) + .withHref(XMLHref.of(document.getFileName())) + .withMediaType(document.getMediaType())) + .collect(toList())) + .withIdentifierInSignedDocuments(job.getIdentifierInSignedDocuments().map(IdentifierInSignedDocuments::getXmlEnumValue).orElse(null)); } } diff --git a/src/main/java/no/digipost/signature/client/asice/manifest/CreatePortalManifest.java b/src/main/java/no/digipost/signature/client/asice/manifest/CreatePortalManifest.java index 5bcb9cca..574fde06 100644 --- a/src/main/java/no/digipost/signature/client/asice/manifest/CreatePortalManifest.java +++ b/src/main/java/no/digipost/signature/client/asice/manifest/CreatePortalManifest.java @@ -3,6 +3,7 @@ import no.digipost.signature.api.xml.XMLAvailability; import no.digipost.signature.api.xml.XMLEmail; import no.digipost.signature.api.xml.XMLEnabled; +import no.digipost.signature.api.xml.XMLHref; import no.digipost.signature.api.xml.XMLNotifications; import no.digipost.signature.api.xml.XMLNotificationsUsingLookup; import no.digipost.signature.api.xml.XMLPortalDocument; @@ -17,15 +18,16 @@ import no.digipost.signature.client.core.SignatureType; import no.digipost.signature.client.portal.Notifications; import no.digipost.signature.client.portal.NotificationsUsingLookup; -import no.digipost.signature.client.portal.PortalDocument; import no.digipost.signature.client.portal.PortalJob; import no.digipost.signature.client.portal.PortalSigner; import java.time.Clock; +import java.time.Duration; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; +import static java.util.stream.Collectors.toList; import static no.digipost.signature.client.core.exceptions.SignerNotSpecifiedException.SIGNER_NOT_SPECIFIED; public class CreatePortalManifest extends ManifestCreator { @@ -41,7 +43,6 @@ Object buildXmlManifest(PortalJob job, Sender sender) { List xmlSigners = new ArrayList<>(); for (PortalSigner signer : job.getSigners()) { XMLPortalSigner xmlPortalSigner = generateSigner(signer); - if (signer.getNotifications() != null) { xmlPortalSigner.setNotifications(generateNotifications(signer.getNotifications())); } else if (signer.getNotificationsUsingLookup() != null) { @@ -50,27 +51,25 @@ Object buildXmlManifest(PortalJob job, Sender sender) { xmlSigners.add(xmlPortalSigner); } - PortalDocument document = job.getDocument(); - ZonedDateTime activationTime = job.getActivationTime().map(activation -> activation.atZone(clock.getZone())).orElse(null); return new XMLPortalSignatureJobManifest() .withSigners(xmlSigners) .withRequiredAuthentication(job.getRequiredAuthentication().map(AuthenticationLevel::getXmlEnumValue).orElse(null)) .withSender(new XMLSender().withOrganizationNumber(sender.getOrganizationNumber())) - .withDocument(new XMLPortalDocument() - .withTitle(document.getTitle()) - .withNonsensitiveTitle(document.getNonsensitiveTitle()) - .withDescription(document.getMessage()) - .withHref(document.getFileName()) - .withMime(document.getMimeType()) - ) + .withTitle(job.getTitle()) + .withNonsensitiveTitle(job.getNonsensitiveTitle().orElse(null)) + .withDescription(job.getDescription().orElse(null)) + .withDocuments(job.getDocuments().stream() + .map(document -> new XMLPortalDocument() + .withTitle(document.getTitle()) + .withHref(XMLHref.of(document.getFileName())) + .withMediaType(document.getMediaType())) + .collect(toList())) .withAvailability(new XMLAvailability() .withActivationTime(activationTime) - .withAvailableSeconds(job.getAvailableSeconds()) - ) - .withIdentifierInSignedDocuments(job.getIdentifierInSignedDocuments().map(IdentifierInSignedDocuments::getXmlEnumValue).orElse(null)) - ; + .withAvailableSeconds(job.getAvailable().map(Duration::getSeconds).orElse(null))) + .withIdentifierInSignedDocuments(job.getIdentifierInSignedDocuments().map(IdentifierInSignedDocuments::getXmlEnumValue).orElse(null)); } private XMLPortalSigner generateSigner(PortalSigner signer) { diff --git a/src/main/java/no/digipost/signature/client/asice/manifest/Manifest.java b/src/main/java/no/digipost/signature/client/asice/manifest/Manifest.java index 0a57c9d3..e79823e7 100644 --- a/src/main/java/no/digipost/signature/client/asice/manifest/Manifest.java +++ b/src/main/java/no/digipost/signature/client/asice/manifest/Manifest.java @@ -16,12 +16,13 @@ public String getFileName() { } @Override - public byte[] getBytes() { + public byte[] getContent() { return xmlBytes; } @Override - public String getMimeType() { - return "application/xml"; + public String getMediaType() { + return XML_MEDIATYPE; } + } diff --git a/src/main/java/no/digipost/signature/client/asice/signature/CreateSignature.java b/src/main/java/no/digipost/signature/client/asice/signature/CreateSignature.java index e236277d..f20c4fc9 100644 --- a/src/main/java/no/digipost/signature/client/asice/signature/CreateSignature.java +++ b/src/main/java/no/digipost/signature/client/asice/signature/CreateSignature.java @@ -169,8 +169,9 @@ private List references(final XMLSignatureFactory xmlSignatureFactory for (int i = 0; i < files.size(); i++) { try { String signatureElementId = "ID_" + i; - String uri = URLEncoder.encode(files.get(i).getFileName(), "UTF-8"); - Reference reference = xmlSignatureFactory.newReference(uri, sha256DigestMethod, null, null, signatureElementId, files.get(i).getSha256()); + SignableFileReference file = files.get(i); + String uri = URLEncoder.encode(file.getFileName(), "UTF-8"); + Reference reference = xmlSignatureFactory.newReference(uri, sha256DigestMethod, null, null, signatureElementId, file.getSha256()); result.add(reference); } catch(UnsupportedEncodingException e) { throw new RuntimeException(e); diff --git a/src/main/java/no/digipost/signature/client/asice/signature/CreateXAdESArtifacts.java b/src/main/java/no/digipost/signature/client/asice/signature/CreateXAdESArtifacts.java index 228c9fc8..2ea05a09 100644 --- a/src/main/java/no/digipost/signature/client/asice/signature/CreateXAdESArtifacts.java +++ b/src/main/java/no/digipost/signature/client/asice/signature/CreateXAdESArtifacts.java @@ -60,7 +60,7 @@ private List dataObjectFormats(List result = new ArrayList<>(); for (int i = 0; i < files.size(); i++) { String signatureElementIdReference = format("#ID_%s", i); - result.add(new DataObjectFormat(null, null, files.get(i).getMimeType(), null, signatureElementIdReference)); + result.add(new DataObjectFormat(null, null, files.get(i).getMediaType(), null, signatureElementIdReference)); } return result; } diff --git a/src/main/java/no/digipost/signature/client/asice/signature/SignableFileReference.java b/src/main/java/no/digipost/signature/client/asice/signature/SignableFileReference.java index 3c720f8a..508a1584 100644 --- a/src/main/java/no/digipost/signature/client/asice/signature/SignableFileReference.java +++ b/src/main/java/no/digipost/signature/client/asice/signature/SignableFileReference.java @@ -6,6 +6,6 @@ public interface SignableFileReference { byte[] getSha256(); - String getMimeType(); + String getMediaType(); } diff --git a/src/main/java/no/digipost/signature/client/asice/signature/Signature.java b/src/main/java/no/digipost/signature/client/asice/signature/Signature.java index 34933f08..5335e154 100644 --- a/src/main/java/no/digipost/signature/client/asice/signature/Signature.java +++ b/src/main/java/no/digipost/signature/client/asice/signature/Signature.java @@ -16,13 +16,13 @@ public String getFileName() { } @Override - public byte[] getBytes() { + public byte[] getContent() { return xmlBytes; } @Override - public String getMimeType() { - return "application/xml"; + public String getMediaType() { + return XML_MEDIATYPE; } } diff --git a/src/main/java/no/digipost/signature/client/core/Document.java b/src/main/java/no/digipost/signature/client/core/Document.java index 3bdaf02d..bb6884fe 100644 --- a/src/main/java/no/digipost/signature/client/core/Document.java +++ b/src/main/java/no/digipost/signature/client/core/Document.java @@ -2,20 +2,22 @@ import no.digipost.signature.client.asice.ASiCEAttachable; -public abstract class Document implements ASiCEAttachable { +public class Document implements ASiCEAttachable { - private String title; - private String message; - private String fileName; - private byte[] document; - private FileType fileType; + private final String title; + private final String mediaType; + private final String fileName; + private final byte[] content; - protected Document(final String title, final String message, final String fileName, final FileType fileType, final byte[] document) { + public Document(String title, String mediaType, String fileName, byte[] content) { this.title = title; - this.message = message; + this.mediaType = mediaType; this.fileName = fileName; - this.fileType = fileType; - this.document = document; + this.content = content; + } + + public String getTitle() { + return title; } @Override @@ -24,31 +26,13 @@ public String getFileName() { } @Override - public byte[] getBytes() { - return document; + public String getMediaType() { + return mediaType; } @Override - public String getMimeType() { - return fileType.mimeType; + public byte[] getContent() { + return content; } - public String getTitle() { - return title; - } - - public String getMessage() { - return message; - } - - public enum FileType { - PDF("application/pdf"), - TXT("text/plain"); - - public final String mimeType; - - FileType(final String mimeType) { - this.mimeType = mimeType; - } - } } diff --git a/src/main/java/no/digipost/signature/client/core/DocumentType.java b/src/main/java/no/digipost/signature/client/core/DocumentType.java new file mode 100644 index 00000000..17dd509e --- /dev/null +++ b/src/main/java/no/digipost/signature/client/core/DocumentType.java @@ -0,0 +1,24 @@ +package no.digipost.signature.client.core; + +public enum DocumentType { + + PDF("application/pdf", "pdf"), + TXT("text/plain", "txt"); + + private final String mediaType; + private final String fileExtension; + + DocumentType(String mediaType, String fileExtension) { + this.mediaType = mediaType; + this.fileExtension = fileExtension; + } + + public String getMediaType() { + return mediaType; + } + + public String getFileExtension() { + return fileExtension; + } + +} diff --git a/src/main/java/no/digipost/signature/client/core/SignatureJob.java b/src/main/java/no/digipost/signature/client/core/SignatureJob.java index bd66e6cc..a9211f84 100644 --- a/src/main/java/no/digipost/signature/client/core/SignatureJob.java +++ b/src/main/java/no/digipost/signature/client/core/SignatureJob.java @@ -1,10 +1,11 @@ package no.digipost.signature.client.core; +import java.util.List; import java.util.Optional; public interface SignatureJob { - Document getDocument(); + List getDocuments(); Optional getSender(); diff --git a/src/main/java/no/digipost/signature/client/core/internal/FileName.java b/src/main/java/no/digipost/signature/client/core/internal/FileName.java new file mode 100644 index 00000000..110353f6 --- /dev/null +++ b/src/main/java/no/digipost/signature/client/core/internal/FileName.java @@ -0,0 +1,89 @@ +package no.digipost.signature.client.core.internal; + +import java.text.Normalizer; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Pattern; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static java.text.Normalizer.Form.NFD; +import static java.util.Arrays.binarySearch; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.joining; +import static java.util.stream.IntStream.rangeClosed; + +public final class FileName { + + private static final Map knownReplacements; static { + knownReplacements = new LinkedHashMap<>(); + knownReplacements.put(Pattern.compile("[\\s:;&%\\$\\*\\?\\+=@,\\(\\)\\[\\]\\{\\}#!\"“”\\^`´<>]+"), "_"); //various punctuation + knownReplacements.put(Pattern.compile("[æÆ]"), "ae"); + knownReplacements.put(Pattern.compile("[øØ]"), "oe"); + knownReplacements.put(Pattern.compile("[åÅ]"), "aa"); + } + + private static final char[] allowedChars = Stream.of( + rangeClosed('0', '9'), + rangeClosed('a', 'z'), + IntStream.of('-', '_', '.')) + .flatMapToInt(identity()) + .sorted() + .mapToObj(c -> String.valueOf((char) c)) + .collect(joining()) + .toCharArray(); + + + public static String reduceToFileNameSafeChars(String text) { + if (text == null || text.isEmpty()) { + throw new IllegalArgumentException(text); + } + String knownReplacements = text; + for (Entry replacement : FileName.knownReplacements.entrySet()) { + knownReplacements = replacement.getKey().matcher(knownReplacements).replaceAll(replacement.getValue()); + } + String accentsRemoved = removeAccents(knownReplacements); + return reduceToAlphabet(accentsRemoved.toLowerCase(), allowedChars); + } + + + /** + * Reduce a text to only contain characters of a given alphabet + * using the following algorithm: + *
    + *
  • characters already part of the alphabet are kept as-is
  • + *
  • characters not part of the alphabet is replaced with a + * character from the alphabet in a non-defined, but repeatable manner.
  • + *
+ * + * @param text the source text + * @param alphabet the allowed characters in the resulting String + * @return the resulting string + */ + private static String reduceToAlphabet(String text, char[] alphabet) { + StringBuilder reducedToAllowedChars = new StringBuilder(text.length()); + for (int c : text.toCharArray()) { + if (binarySearch(allowedChars, (char) c) >= 0) { + reducedToAllowedChars.append((char) c); + } else { + reducedToAllowedChars.append(allowedChars[c % allowedChars.length]); + } + } + return reducedToAllowedChars.toString(); + } + + + private static final Pattern UNICODE_ACCENT = Pattern.compile("\\p{M}"); + + /** + * Replaces accented characters (è, ü, etc) with their base glyphs (e, u, etc). + * @see https://stackoverflow.com/questions/3322152/is-there-a-way-to-get-rid-of-accents-and-convert-a-whole-string-to-regular-lette + */ + private static String removeAccents(String text) { + return UNICODE_ACCENT.matcher(Normalizer.normalize(text, NFD)).replaceAll(""); + } + + private FileName() { + } +} diff --git a/src/main/java/no/digipost/signature/client/direct/DirectDocument.java b/src/main/java/no/digipost/signature/client/direct/DirectDocument.java index 2595e012..6f03f01d 100644 --- a/src/main/java/no/digipost/signature/client/direct/DirectDocument.java +++ b/src/main/java/no/digipost/signature/client/direct/DirectDocument.java @@ -1,44 +1,47 @@ package no.digipost.signature.client.direct; -import no.digipost.signature.client.core.Document; +import no.digipost.signature.client.core.DocumentType; -import static no.digipost.signature.client.core.Document.FileType.PDF; +import static java.util.Objects.requireNonNull; +import static no.digipost.signature.client.core.DocumentType.PDF; -public class DirectDocument extends Document { - private DirectDocument(String title, String message, String fileName, FileType fileType, byte[] document) { - super(title, message, fileName, fileType, document); + +public class DirectDocument { + + public static Builder builder(String title, byte[] documentContent) { + return new Builder(title, documentContent); } - public static Builder builder(final String title, final String fileName, final byte[] document) { - return new Builder(title, fileName, document); + + public final String title; + public final DocumentType type; + public final byte[] content; + + private DirectDocument(String title, DocumentType type, byte[] content) { + this.title = title; + this.type = type; + this.content = content; } + public static class Builder { private String title; - private String fileName; - private byte[] document; - private String message; - private FileType fileType = PDF; - - public Builder(final String title, final String fileName, final byte[] document) { - this.title = title; - this.fileName = fileName; - this.document = document; - } + private DocumentType type = PDF; + private byte[] content; - public Builder message(String message) { - this.message = message; - return this; + private Builder(String title, byte[] content) { + this.title = requireNonNull(title, "title"); + this.content = requireNonNull(content, "document content"); } - public Builder fileType(final FileType fileType) { - this.fileType = fileType; + public Builder type(DocumentType type) { + this.type = requireNonNull(type, "document type"); return this; } public DirectDocument build() { - return new DirectDocument(title, message, fileName, fileType, document); + return new DirectDocument(title, type, content); } } } diff --git a/src/main/java/no/digipost/signature/client/direct/DirectJob.java b/src/main/java/no/digipost/signature/client/direct/DirectJob.java index 1b33a2ab..1299ef5c 100644 --- a/src/main/java/no/digipost/signature/client/direct/DirectJob.java +++ b/src/main/java/no/digipost/signature/client/direct/DirectJob.java @@ -1,6 +1,7 @@ package no.digipost.signature.client.direct; import no.digipost.signature.client.core.AuthenticationLevel; +import no.digipost.signature.client.core.Document; import no.digipost.signature.client.core.IdentifierInSignedDocuments; import no.digipost.signature.client.core.Sender; import no.digipost.signature.client.core.SignatureJob; @@ -8,29 +9,39 @@ import java.net.URI; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.UUID; +import static java.lang.String.format; +import static java.util.Collections.singletonList; import static java.util.Collections.unmodifiableList; +import static no.digipost.signature.client.core.internal.FileName.reduceToFileNameSafeChars; + +/** + * Signature job with document(s) to be signed by + * one or more signers in direct flow. + */ public class DirectJob implements SignatureJob, WithExitUrls { + private final List documents; + private final List signers; + private final String title; + private final URI completionUrl; + private final URI rejectionUrl; + private final URI errorUrl; + private Optional description = Optional.empty(); private String reference; - private List signers; - private DirectDocument document; - private URI completionUrl; - private URI rejectionUrl; - private URI errorUrl; private Optional sender = Optional.empty(); private Optional statusRetrievalMethod = Optional.empty(); private Optional requiredAuthentication = Optional.empty(); private Optional identifierInSignedDocuments = Optional.empty(); - private DirectJob(List signers, DirectDocument document, URI completionUrl, URI rejectionUrl, URI errorUrl) { + private DirectJob(String title, List documents, List signers, URI completionUrl, URI rejectionUrl, URI errorUrl) { + this.title = title; + this.documents = unmodifiableList(new ArrayList<>(documents)); this.signers = unmodifiableList(new ArrayList<>(signers)); - this.document = document; this.completionUrl = completionUrl; this.rejectionUrl = rejectionUrl; this.errorUrl = errorUrl; @@ -42,8 +53,15 @@ public String getReference() { } @Override - public DirectDocument getDocument() { - return document; + public List getDocuments() { + List documents = new ArrayList<>(); + for (int i = 0; i < this.documents.size(); i++) { + DirectDocument document = this.documents.get(i); + documents.add(new Document(document.title, document.type.getMediaType(), + format("%04d", i) + "_" + reduceToFileNameSafeChars(document.title) + "." + document.type.getFileExtension(), + document.content)); + } + return documents; } @Override @@ -84,38 +102,43 @@ public Optional getStatusRetrievalMethod() { return statusRetrievalMethod; } + public String getTitle() { + return title; + } + + public Optional getDescription() { + return description; + } + /** - * Create a new DirectJob. + * Create a new signature job for direct flow. * - * @param document The {@link DirectDocument} that should be signed. - * @param hasExitUrls specifies the urls the user will be redirected back to upon completing/rejecting/failing - * the signing ceremony. See {@link ExitUrls#of(URI, URI, URI)}, and alternatively + * @param document The {@link DirectDocument document} that should be signed. + * @param hasExitUrls specifies the URLs the user will be redirected back to upon completing/rejecting/failing + * to sign the document. See {@link ExitUrls#of(URI, URI, URI)}, and alternatively * {@link ExitUrls#singleExitUrl(URI)}. - * @param signers The {@link DirectSigner DirectSigners} of the document. + * @param signer The {@link DirectSigner signer} of the document. * * @return a builder to further customize the job - * @see DirectJob#builder(DirectDocument, WithExitUrls, List) */ - public static Builder builder(DirectDocument document, WithExitUrls hasExitUrls, DirectSigner... signers) { - return builder(document, hasExitUrls, Arrays.asList(signers)); + public static Builder builder(String title, DirectDocument document, DirectSigner signer, WithExitUrls hasExitUrls) { + return builder(title, singletonList(document), singletonList(signer), hasExitUrls); } - /** - * Create a new DirectJob. + * Create a new signature job for direct flow. * - * @param document The {@link DirectDocument} that should be signed. - * @param hasExitUrls specifies the urls the user will be redirected back to upon completing/rejecting/failing - * the signing ceremony. See {@link ExitUrls#of(URI, URI, URI)}, and alternatively + * @param documents The {@link DirectDocument document} that should be signed. + * @param hasExitUrls specifies the URLs the user will be redirected back to upon completing/rejecting/failing + * to sign the documents. See {@link ExitUrls#of(URI, URI, URI)}, and alternatively * {@link ExitUrls#singleExitUrl(URI)}. - * @param signers The {@link DirectSigner DirectSigners} of the document. + * @param signers The {@link DirectSigner signers} of the document. * * @return a builder to further customize the job - * @see DirectJob#builder(DirectDocument, WithExitUrls, DirectSigner...) */ - public static Builder builder(DirectDocument document, WithExitUrls hasExitUrls, List signers) { - return new Builder(signers, document, hasExitUrls.getCompletionUrl(), hasExitUrls.getRejectionUrl(), hasExitUrls.getErrorUrl()); + public static Builder builder(String title, List documents, List signers, WithExitUrls hasExitUrls) { + return new Builder(title, documents, signers, hasExitUrls.getCompletionUrl(), hasExitUrls.getRejectionUrl(), hasExitUrls.getErrorUrl()); } public static class Builder implements JobCustomizations { @@ -123,8 +146,8 @@ public static class Builder implements JobCustomizations { private final DirectJob target; private boolean built = false; - private Builder(List signers, DirectDocument document, URI completionUrl, URI rejectionUrl, URI errorUrl) { - target = new DirectJob(signers, document, completionUrl, rejectionUrl, errorUrl); + private Builder(String title, List documents, List signers, URI completionUrl, URI rejectionUrl, URI errorUrl) { + target = new DirectJob(title, documents, signers, completionUrl, rejectionUrl, errorUrl); } @Override @@ -132,6 +155,11 @@ public Builder withReference(UUID uuid) { return withReference(uuid.toString()); } + public Builder withDescription(String description) { + target.description = Optional.of(description); + return this; + } + @Override public Builder withReference(String reference) { target.reference = reference; diff --git a/src/main/java/no/digipost/signature/client/portal/PortalDocument.java b/src/main/java/no/digipost/signature/client/portal/PortalDocument.java index e7dfae3e..c15b3c6a 100644 --- a/src/main/java/no/digipost/signature/client/portal/PortalDocument.java +++ b/src/main/java/no/digipost/signature/client/portal/PortalDocument.java @@ -1,57 +1,47 @@ package no.digipost.signature.client.portal; -import no.digipost.signature.client.core.Document; +import no.digipost.signature.client.core.DocumentType; -import static no.digipost.signature.client.core.Document.FileType.PDF; +import static java.util.Objects.requireNonNull; +import static no.digipost.signature.client.core.DocumentType.PDF; -public class PortalDocument extends Document { - private final String nonsensitiveTitle; - private PortalDocument(String title, String nonsensitiveTitle, String message, String fileName, FileType fileType, byte[] document) { - super(title, message, fileName, fileType, document); - this.nonsensitiveTitle = nonsensitiveTitle; - } +public class PortalDocument { - public String getNonsensitiveTitle() { - return nonsensitiveTitle; + public static Builder builder(String title, byte[] documentContent) { + return new Builder(title, documentContent); } - public static Builder builder(final String title, final String fileName, final byte[] document) { - return new Builder(title, fileName, document); + + public final String title; + public final DocumentType type; + public final byte[] content; + + private PortalDocument(String title, DocumentType type, byte[] content) { + this.title = title; + this.type = type; + this.content = content; } + public static class Builder { private String title; - private String nonsensitiveTitle; - private String fileName; - private byte[] document; - private String message; - private FileType fileType = PDF; - - public Builder(final String title, final String fileName, final byte[] document) { - this.title = title; - this.fileName = fileName; - this.document = document; - } + private DocumentType type = PDF; + private byte[] content; - public Builder message(String message) { - this.message = message; - return this; - } - - public Builder nonsensitiveTitle(String nonsensitiveTitle) { - this.nonsensitiveTitle = nonsensitiveTitle; - return this; + public Builder(String title, byte[] content) { + this.title = requireNonNull(title, "title"); + this.content = requireNonNull(content, "document content"); } - public Builder fileType(final FileType fileType) { - this.fileType = fileType; + public Builder type(DocumentType type) { + this.type = requireNonNull(type, "document type"); return this; } public PortalDocument build() { - return new PortalDocument(title, nonsensitiveTitle, message, fileName, fileType, document); + return new PortalDocument(title, type, content); } } } diff --git a/src/main/java/no/digipost/signature/client/portal/PortalJob.java b/src/main/java/no/digipost/signature/client/portal/PortalJob.java index 5fb4ef2f..7b0a6a73 100644 --- a/src/main/java/no/digipost/signature/client/portal/PortalJob.java +++ b/src/main/java/no/digipost/signature/client/portal/PortalJob.java @@ -1,36 +1,47 @@ package no.digipost.signature.client.portal; import no.digipost.signature.client.core.AuthenticationLevel; +import no.digipost.signature.client.core.Document; import no.digipost.signature.client.core.IdentifierInSignedDocuments; import no.digipost.signature.client.core.Sender; import no.digipost.signature.client.core.SignatureJob; import no.digipost.signature.client.core.internal.JobCustomizations; +import java.time.Duration; import java.time.Instant; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.UUID; -import java.util.concurrent.TimeUnit; +import static java.lang.String.format; +import static java.util.Collections.singletonList; import static java.util.Collections.unmodifiableList; +import static no.digipost.signature.client.core.internal.FileName.reduceToFileNameSafeChars; +/** + * Signature job with document(s) to be signed by + * one or more signers in portal flow. + */ public class PortalJob implements SignatureJob { + private final List documents; private final List signers; - private final PortalDocument document; + private final String title; + private Optional nonsensitiveTitle = Optional.empty(); + private Optional description = Optional.empty(); private String reference; private Optional activationTime = Optional.empty(); - private Long availableSeconds; + private Optional available = Optional.empty(); private Optional sender = Optional.empty(); private Optional requiredAuthentication = Optional.empty(); private Optional identifierInSignedDocuments = Optional.empty(); - private PortalJob(List signers, PortalDocument document) { + private PortalJob(String title, List documents, List signers) { + this.title = title; + this.documents = unmodifiableList(new ArrayList<>(documents)); this.signers = unmodifiableList(new ArrayList<>(signers)); - this.document = document; } @Override @@ -39,8 +50,15 @@ public String getReference() { } @Override - public PortalDocument getDocument() { - return document; + public List getDocuments() { + List documents = new ArrayList<>(); + for (int i = 0; i < this.documents.size(); i++) { + PortalDocument document = this.documents.get(i); + documents.add(new Document(document.title, document.type.getMediaType(), + format("%04d", i) + "_" + reduceToFileNameSafeChars(document.title) + "." + document.type.getFileExtension(), + document.content)); + } + return documents; } @Override @@ -66,17 +84,45 @@ public Optional getActivationTime() { return activationTime; } - public Long getAvailableSeconds() { - return availableSeconds; + public Optional getAvailable() { + return available; + } + + public String getTitle() { + return title; + } + + public Optional getNonsensitiveTitle() { + return nonsensitiveTitle; + } + + public Optional getDescription() { + return description; } - public static Builder builder(PortalDocument document, PortalSigner... signers) { - return builder(document, Arrays.asList(signers)); + /** + * Create a new signature job for portal flow. + * + * @param document The {@link PortalDocument document} that should be signed. + * @param signer The {@link PortalSigner signer} of the document. + * + * @return a builder to further customize the job + */ + public static Builder builder(String title, PortalDocument document, PortalSigner signer) { + return builder(title, singletonList(document), singletonList(signer)); } - public static Builder builder(PortalDocument document, List signers) { - return new Builder(signers, document); + /** + * Create a new signature job for portal flow. + * + * @param documents The {@link PortalDocument documents} that should be signed. + * @param signers The {@link PortalSigner signers} of the document. + * + * @return a builder to further customize the job + */ + public static Builder builder(String title, List documents, List signers) { + return new Builder(title, documents, signers); } public static class Builder implements JobCustomizations { @@ -84,8 +130,8 @@ public static class Builder implements JobCustomizations { private final PortalJob target; private boolean built = false; - private Builder(List signers, PortalDocument document) { - target = new PortalJob(signers, document); + private Builder(String title, List documents, List signers) { + target = new PortalJob(title, documents, signers); } @Override @@ -122,8 +168,18 @@ public Builder withActivationTime(Instant activationTime) { return this; } - public Builder availableFor(long duration, TimeUnit unit) { - target.availableSeconds = unit.toSeconds(duration); + public Builder availableFor(Duration duration) { + target.available = Optional.of(duration); + return this; + } + + public Builder withNonsensitiveTitle(String nonsensitiveTitle) { + target.nonsensitiveTitle = Optional.of(nonsensitiveTitle); + return this; + } + + public Builder withDescription(String description) { + target.description = Optional.of(description); return this; } diff --git a/src/test/java/no/digipost/signature/client/asice/CreateASiCETest.java b/src/test/java/no/digipost/signature/client/asice/CreateASiCETest.java index 92dafb3c..05f6073e 100644 --- a/src/test/java/no/digipost/signature/client/asice/CreateASiCETest.java +++ b/src/test/java/no/digipost/signature/client/asice/CreateASiCETest.java @@ -5,6 +5,7 @@ import no.digipost.signature.client.asice.manifest.CreatePortalManifest; import no.digipost.signature.client.asice.manifest.ManifestCreator; import no.digipost.signature.client.core.Document; +import no.digipost.signature.client.core.DocumentType; import no.digipost.signature.client.core.Sender; import no.digipost.signature.client.core.SignatureJob; import no.digipost.signature.client.direct.DirectDocument; @@ -26,21 +27,24 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.time.Clock; +import java.time.Duration; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; +import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import static java.nio.file.Files.newDirectoryStream; -import static java.util.concurrent.TimeUnit.DAYS; +import static java.util.Arrays.asList; +import static java.util.stream.Stream.concat; import static no.digipost.signature.client.TestKonfigurasjon.CLIENT_KEYSTORE; import static no.digipost.signature.client.asice.DumpDocumentBundleToDisk.TIMESTAMP_PATTERN; import static no.digipost.signature.client.asice.DumpDocumentBundleToDisk.referenceFilenamePart; import static no.digipost.signature.client.direct.ExitUrls.singleExitUrl; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.containsInAnyOrder; public class CreateASiCETest { @@ -56,20 +60,21 @@ public static void initTempFolder() throws URISyntaxException, IOException { private static Path dumpFolder; - private static final DirectDocument DIRECT_DOCUMENT = DirectDocument.builder("Title", "file.txt", "hello".getBytes()) - .message("Message") - .fileType(Document.FileType.TXT) + private static final DirectDocument DIRECT_DOCUMENT = DirectDocument.builder("Document title", "hello".getBytes()) + .type(DocumentType.TXT) .build(); - private static final PortalDocument PORTAL_DOCUMENT = PortalDocument.builder("Title", "file.txt", "hello".getBytes()) - .message("Message") - .fileType(Document.FileType.TXT) + private static final PortalDocument PORTAL_DOCUMENT = PortalDocument.builder("Document title", "hello".getBytes()) + .type(DocumentType.TXT) .build(); @Test public void create_direct_asice_and_write_to_disk() throws IOException { - DirectJob job = DirectJob.builder(DIRECT_DOCUMENT, singleExitUrl(URI.create("https://job.well.done.org")), DirectSigner.withPersonalIdentificationNumber("12345678910").build()) + DirectJob job = DirectJob.builder("Job title", + asList(DIRECT_DOCUMENT, DIRECT_DOCUMENT), + asList(DirectSigner.withPersonalIdentificationNumber("12345678910").build()), + singleExitUrl(URI.create("https://job.well.done.org"))) .withReference("direct job") .build(); @@ -78,16 +83,19 @@ public void create_direct_asice_and_write_to_disk() throws IOException { @Test public void create_portal_asice_and_write_to_disk() throws IOException { - PortalJob job = PortalJob.builder(PORTAL_DOCUMENT, PortalSigner.identifiedByPersonalIdentificationNumber("12345678910", NotificationsUsingLookup.EMAIL_ONLY).build()) + PortalJob job = PortalJob.builder("Job title", + asList(PORTAL_DOCUMENT, PORTAL_DOCUMENT, PORTAL_DOCUMENT), + asList(PortalSigner.identifiedByPersonalIdentificationNumber("12345678910", NotificationsUsingLookup.EMAIL_ONLY).build())) .withReference("portal job") + .withDescription("Message") .withActivationTime(clock.instant()) - .availableFor(30, DAYS) + .availableFor(Duration.ofDays(30)) .build(); create_document_bundle_and_dump_to_disk(new CreatePortalManifest(clock), job); } - private void create_document_bundle_and_dump_to_disk(ManifestCreator manifestCreator, JOB job) throws IOException { + private static void create_document_bundle_and_dump_to_disk(ManifestCreator manifestCreator, JOB job) throws IOException { CreateASiCE aSiCECreator = new CreateASiCE<>(manifestCreator, ClientConfiguration.builder(CLIENT_KEYSTORE) .globalSender(new Sender("123456789")) .enableDocumentBundleDiskDump(dumpFolder) @@ -105,9 +113,10 @@ private void create_document_bundle_and_dump_to_disk( fileNames.add(entry.getName()); } } - assertThat(fileNames, hasItem(job.getDocument().getFileName())); - assertThat(fileNames, hasItem("manifest.xml")); - assertThat(fileNames, hasItem("META-INF/signatures.xml")); + assertThat(fileNames, containsInAnyOrder(concat( + job.getDocuments().stream().map(Document::getFileName), + Stream.of("manifest.xml", "META-INF/signatures.xml")) + .toArray(String[]::new))); } } diff --git a/src/test/java/no/digipost/signature/client/asice/archive/CreateZipTest.java b/src/test/java/no/digipost/signature/client/asice/archive/CreateZipTest.java index a3f79684..7cd80db2 100644 --- a/src/test/java/no/digipost/signature/client/asice/archive/CreateZipTest.java +++ b/src/test/java/no/digipost/signature/client/asice/archive/CreateZipTest.java @@ -1,6 +1,7 @@ package no.digipost.signature.client.asice.archive; import no.digipost.signature.client.asice.ASiCEAttachable; +import no.digipost.signature.client.core.DocumentType; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.Test; @@ -41,7 +42,7 @@ private static void verifyZipFile(ZipInputStream zipInputStream, String fileName assertArrayEquals(IOUtils.toByteArray(zipInputStream), contents.getBytes()); } - private ASiCEAttachable file(final String fileName, final String contents) { + private ASiCEAttachable file(String fileName, String contents) { return new ASiCEAttachable() { @Override public String getFileName() { @@ -49,13 +50,13 @@ public String getFileName() { } @Override - public byte[] getBytes() { + public byte[] getContent() { return contents.getBytes(); } @Override - public String getMimeType() { - return "text/plain"; + public String getMediaType() { + return DocumentType.TXT.getMediaType(); } }; } diff --git a/src/test/java/no/digipost/signature/client/asice/manifest/CreateDirectManifestTest.java b/src/test/java/no/digipost/signature/client/asice/manifest/CreateDirectManifestTest.java index c701c7c6..9412fbb0 100644 --- a/src/test/java/no/digipost/signature/client/asice/manifest/CreateDirectManifestTest.java +++ b/src/test/java/no/digipost/signature/client/asice/manifest/CreateDirectManifestTest.java @@ -1,6 +1,5 @@ package no.digipost.signature.client.asice.manifest; -import no.digipost.signature.client.core.Document; import no.digipost.signature.client.core.IdentifierInSignedDocuments; import no.digipost.signature.client.core.Sender; import no.digipost.signature.client.direct.DirectDocument; @@ -11,9 +10,10 @@ import java.net.URI; -import static uk.co.probablyfine.matchers.Java8Matchers.where; +import static no.digipost.signature.client.core.DocumentType.TXT; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; +import static uk.co.probablyfine.matchers.Java8Matchers.where; class CreateDirectManifestTest { @@ -21,15 +21,13 @@ class CreateDirectManifestTest { void accept_valid_manifest() { CreateDirectManifest createManifest = new CreateDirectManifest(); - DirectDocument document = DirectDocument.builder("Title", "file.txt", "hello".getBytes()) - .message("Message") - .fileType(Document.FileType.TXT) - .build(); + DirectDocument document = DirectDocument.builder("Title", "hello".getBytes()).type(TXT).build(); DirectJob job = DirectJob.builder( + "Job title", document, - ExitUrls.of(URI.create("http://localhost/signed"), URI.create("http://localhost/canceled"), URI.create("http://localhost/failed")), - DirectSigner.withPersonalIdentificationNumber("12345678910").build()) + DirectSigner.withPersonalIdentificationNumber("12345678910").build(), + ExitUrls.of(URI.create("http://localhost/signed"), URI.create("http://localhost/canceled"), URI.create("http://localhost/failed"))) .withIdentifierInSignedDocuments(IdentifierInSignedDocuments.NAME) .build(); assertThat(createManifest, where(__ -> __.createManifest(job, new Sender("123456789")), instanceOf(Manifest.class))); diff --git a/src/test/java/no/digipost/signature/client/asice/manifest/CreatePortalManifestTest.java b/src/test/java/no/digipost/signature/client/asice/manifest/CreatePortalManifestTest.java index 0af280a3..4544663e 100644 --- a/src/test/java/no/digipost/signature/client/asice/manifest/CreatePortalManifestTest.java +++ b/src/test/java/no/digipost/signature/client/asice/manifest/CreatePortalManifestTest.java @@ -1,6 +1,5 @@ package no.digipost.signature.client.asice.manifest; -import no.digipost.signature.client.core.Document; import no.digipost.signature.client.core.IdentifierInSignedDocuments; import no.digipost.signature.client.core.Sender; import no.digipost.signature.client.portal.NotificationsUsingLookup; @@ -10,12 +9,12 @@ import org.junit.jupiter.api.Test; import java.time.Clock; -import java.util.Collections; +import java.time.Duration; -import static uk.co.probablyfine.matchers.Java8Matchers.where; -import static java.util.concurrent.TimeUnit.DAYS; +import static no.digipost.signature.client.core.DocumentType.TXT; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; +import static uk.co.probablyfine.matchers.Java8Matchers.where; class CreatePortalManifestTest { @@ -25,14 +24,12 @@ class CreatePortalManifestTest { void accept_valid_manifest() { CreatePortalManifest createManifest = new CreatePortalManifest(clock); - PortalDocument document = PortalDocument.builder("Title", "file.txt", "hello".getBytes()) - .message("Message") - .fileType(Document.FileType.TXT) - .build(); + PortalDocument document = PortalDocument.builder("Title", "hello".getBytes()).type(TXT).build(); - PortalJob job = PortalJob.builder(document, Collections.singletonList(PortalSigner.identifiedByPersonalIdentificationNumber("12345678910", NotificationsUsingLookup.EMAIL_ONLY).build())) + PortalJob job = PortalJob.builder("Job title", document, PortalSigner.identifiedByPersonalIdentificationNumber("12345678910", NotificationsUsingLookup.EMAIL_ONLY).build()) .withActivationTime(clock.instant()) - .availableFor(30, DAYS) + .availableFor(Duration.ofDays(30)) + .withDescription("Message") .withIdentifierInSignedDocuments(IdentifierInSignedDocuments.PERSONAL_IDENTIFICATION_NUMBER_AND_NAME) .build(); assertThat(createManifest, where(__ -> __.createManifest(job, new Sender("123456789")), instanceOf(Manifest.class))); diff --git a/src/test/java/no/digipost/signature/client/asice/signature/CreateSignatureTest.java b/src/test/java/no/digipost/signature/client/asice/signature/CreateSignatureTest.java index b00ed53b..918d6842 100644 --- a/src/test/java/no/digipost/signature/client/asice/signature/CreateSignatureTest.java +++ b/src/test/java/no/digipost/signature/client/asice/signature/CreateSignatureTest.java @@ -11,6 +11,7 @@ import no.digipost.signature.api.xml.thirdparty.xmldsig.X509IssuerSerialType; import no.digipost.signature.client.TestKonfigurasjon; import no.digipost.signature.client.asice.ASiCEAttachable; +import no.digipost.signature.client.core.DocumentType; import no.digipost.signature.client.security.KeyStoreConfig; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -26,12 +27,12 @@ import java.util.Base64; import java.util.List; -import static uk.co.probablyfine.matchers.Java8Matchers.where; import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; +import static uk.co.probablyfine.matchers.Java8Matchers.where; public class CreateSignatureTest { @@ -54,8 +55,8 @@ public class CreateSignatureTest { public void setUp() { noekkelpar = TestKonfigurasjon.CLIENT_KEYSTORE; files = asList( - file("dokument.pdf", "hoveddokument-innhold".getBytes(), "application/pdf"), - file("manifest.xml", "manifest-innhold".getBytes(), "application/xml") + file("dokument.pdf", "hoveddokument-innhold".getBytes(), DocumentType.PDF.getMediaType()), + file("manifest.xml", "manifest-innhold".getBytes(), ASiCEAttachable.XML_MEDIATYPE) ); ZonedDateTime signingTime = ZonedDateTime.of(2018, 11, 29, 9, 15, 0, 0, ZoneId.of("Europe/Oslo")); @@ -75,7 +76,7 @@ public void test_generated_signatures() { "PAdNndCACJiWrkHNQ5gTJ+UWx8y2kuzZHEGGTJ+ip9KpCRohDfLapQAMTh0zMLrUNbYpq6kiYWrlxTNfdcVm4skBY0j9Q=="; Signature signature = createSignature.createSignature(files, noekkelpar); - XAdESSignatures xAdESSignatures = (XAdESSignatures) marshaller.unmarshal(new StreamSource(new ByteArrayInputStream(signature.getBytes()))); + XAdESSignatures xAdESSignatures = (XAdESSignatures) marshaller.unmarshal(new StreamSource(new ByteArrayInputStream(signature.getContent()))); assertThat(xAdESSignatures, where(XAdESSignatures::getSignatures, hasSize(1))); no.digipost.signature.api.xml.thirdparty.xmldsig.Signature dSignature = xAdESSignatures.getSignatures().get(0); @@ -88,7 +89,7 @@ public void test_generated_signatures() { public void test_xades_signed_properties() { Signature signature = createSignature.createSignature(files, noekkelpar); - XAdESSignatures xAdESSignatures = (XAdESSignatures) marshaller.unmarshal(new StreamSource(new ByteArrayInputStream(signature.getBytes()))); + XAdESSignatures xAdESSignatures = (XAdESSignatures) marshaller.unmarshal(new StreamSource(new ByteArrayInputStream(signature.getContent()))); no.digipost.signature.api.xml.thirdparty.xmldsig.Object object = xAdESSignatures.getSignatures().get(0).getObjects().get(0); QualifyingProperties xadesProperties = (QualifyingProperties) object.getContent().get(0); @@ -102,12 +103,12 @@ public void test_xades_signed_properties() { @Test public void should_support_filenames_with_spaces_and_other_characters() { List otherFiles = asList( - file("dokument (2).pdf", "hoveddokument-innhold".getBytes(), "application/pdf"), - file("manifest.xml", "manifest-innhold".getBytes(), "application/xml") + file("dokument (2).pdf", "hoveddokument-innhold".getBytes(), DocumentType.PDF.getMediaType()), + file("manifest.xml", "manifest-innhold".getBytes(), ASiCEAttachable.XML_MEDIATYPE) ); Signature signature = createSignature.createSignature(otherFiles, noekkelpar); - XAdESSignatures xAdESSignatures = (XAdESSignatures) marshaller.unmarshal(new StreamSource(new ByteArrayInputStream(signature.getBytes()))); + XAdESSignatures xAdESSignatures = (XAdESSignatures) marshaller.unmarshal(new StreamSource(new ByteArrayInputStream(signature.getContent()))); String uri = xAdESSignatures.getSignatures().get(0).getSignedInfo().getReferences().get(0).getURI(); assertThat(uri, is("dokument+%282%29.pdf")); } @@ -160,14 +161,14 @@ private void assert_dokument_reference(final Reference dokumentReference) { assertThat(dokumentReference.getDigestMethod().getAlgorithm(), is("http://www.w3.org/2001/04/xmlenc#sha256")); } - private ASiCEAttachable file(final String fileName, final byte[] contents, final String mimeType) { + private ASiCEAttachable file(String fileName, byte[] contents, String mediaType) { return new ASiCEAttachable() { @Override public String getFileName() { return fileName; } @Override - public byte[] getBytes() { return contents; } + public byte[] getContent() { return contents; } @Override - public String getMimeType() { return mimeType; } + public String getMediaType() { return mediaType; } }; } diff --git a/src/test/java/no/digipost/signature/client/core/internal/FileNameTest.java b/src/test/java/no/digipost/signature/client/core/internal/FileNameTest.java new file mode 100644 index 00000000..641810b2 --- /dev/null +++ b/src/test/java/no/digipost/signature/client/core/internal/FileNameTest.java @@ -0,0 +1,71 @@ +package no.digipost.signature.client.core.internal; + +import org.junit.jupiter.api.Test; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +import static no.digipost.signature.client.core.internal.FileName.reduceToFileNameSafeChars; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.quicktheories.QuickTheory.qt; +import static org.quicktheories.generators.SourceDSL.strings; + +class FileNameTest { + + @Test + void convertsToLowerCase() { + assertThat(reduceToFileNameSafeChars("Title"), is("title")); + } + + @Test + void replacesNorwegianCharacters() { + assertThat(reduceToFileNameSafeChars("ÆØÅ"), is("aeoeaa")); + assertThat(reduceToFileNameSafeChars("æøå"), is("aeoeaa")); + } + + @Test + void successiveSpacesIsReplacedWithAnUnderscore() { + assertThat(reduceToFileNameSafeChars("Hei på \t Deg"), is("hei_paa_deg")); + } + + @Test + void successivePunctuationIsReplacedWithAnUnderscore() { + assertThat(reduceToFileNameSafeChars("*@,:;&%$^?+=()[]{}#\"“”!`´<>"), is("_")); + } + + @Test + void emptyNameIsAnError() { + assertThrows(IllegalArgumentException.class, () -> reduceToFileNameSafeChars("")); + assertThrows(IllegalArgumentException.class, () -> reduceToFileNameSafeChars(null)); + } + + @Test + void accentsAndOtherDiacriticalMarksAreRemoved() { + assertThat(reduceToFileNameSafeChars("àáëêÈÉüÜïñÑ"), is("aaeeeeuuinn")); + } + + @Test + void periodAndDashAreKeptAsIs() { + assertThat(reduceToFileNameSafeChars("Morse ...---..."), is("morse_...---...")); + } + + @Test + void urlEncodingWillAlwaysProduceSameString() { + qt() + .forAll(strings().allPossible().ofLengthBetween(1, 100)) + .as(FileName::reduceToFileNameSafeChars) + .checkAssert(safeFileName -> assertThat(urlEncode(safeFileName), equalTo(safeFileName))); + } + + private static String urlEncode(String s) { + try { + return URLEncoder.encode(s, "UTF-8"); + } catch (UnsupportedEncodingException utf8NotSupported) { + throw new RuntimeException(utf8NotSupported.getMessage(), utf8NotSupported); + } + } + +} diff --git a/src/test/java/no/digipost/signature/client/docs/DirectClientUseCases.java b/src/test/java/no/digipost/signature/client/docs/DirectClientUseCases.java index 3a48f113..8ceb5142 100644 --- a/src/test/java/no/digipost/signature/client/docs/DirectClientUseCases.java +++ b/src/test/java/no/digipost/signature/client/docs/DirectClientUseCases.java @@ -32,7 +32,7 @@ static void create_and_send_signature_job() { DirectClient client = new DirectClient(clientConfiguration); byte[] documentBytes = null; // Loaded document bytes - DirectDocument document = DirectDocument.builder("Subject", "document.pdf", documentBytes).build(); + DirectDocument document = DirectDocument.builder("Document title", documentBytes).build(); ExitUrls exitUrls = ExitUrls.of( URI.create("http://sender.org/onCompletion"), @@ -41,7 +41,7 @@ static void create_and_send_signature_job() { ); DirectSigner signer = DirectSigner.withPersonalIdentificationNumber("12345678910").build(); - DirectJob directJob = DirectJob.builder(document, exitUrls, signer).build(); + DirectJob directJob = DirectJob.builder("Job title", document, signer, exitUrls).build(); DirectJobResponse directJobResponse = client.create(directJob); } @@ -102,7 +102,7 @@ static void get_signature_job_status() { static void create_job_and_status_by_polling() { DirectClient client = null; // As initialized earlier - DirectJob directJob = DirectJob.builder(document, exitUrls, signer) + DirectJob directJob = DirectJob.builder("Job title", document, signer, exitUrls) .retrieveStatusBy(StatusRetrievalMethod.POLLING) .build(); @@ -148,7 +148,7 @@ static void specifying_queues() { DirectClient client = null; // As initialized earlier Sender sender = new Sender("000000000", PollingQueue.of("CustomPollingQueue")); - DirectJob directJob = DirectJob.builder(document, exitUrls, signer) + DirectJob directJob = DirectJob.builder("Job title", document, signer, exitUrls) .retrieveStatusBy(StatusRetrievalMethod.POLLING).withSender(sender) .build(); diff --git a/src/test/java/no/digipost/signature/client/docs/PortalClientUseCases.java b/src/test/java/no/digipost/signature/client/docs/PortalClientUseCases.java index 089f0500..e94cbb1e 100644 --- a/src/test/java/no/digipost/signature/client/docs/PortalClientUseCases.java +++ b/src/test/java/no/digipost/signature/client/docs/PortalClientUseCases.java @@ -19,6 +19,8 @@ import java.io.InputStream; import java.time.Instant; +import static java.util.Arrays.asList; + @SuppressWarnings({"unused", "ConstantConditions", "StatementWithEmptyBody", "null"}) class PortalClientUseCases { @@ -27,15 +29,17 @@ static void create_and_send_signature_job() { PortalClient client = new PortalClient(clientConfiguration); byte[] documentBytes = null; // Loaded document bytes - PortalDocument document = PortalDocument.builder("Subject", "document.pdf", documentBytes).build(); + PortalDocument document = PortalDocument.builder("Document title", documentBytes).build(); PortalJob portalJob = PortalJob.builder( - document, - PortalSigner.identifiedByPersonalIdentificationNumber("12345678910", - NotificationsUsingLookup.EMAIL_ONLY).build(), - PortalSigner.identifiedByPersonalIdentificationNumber("12345678911", - Notifications.builder().withEmailTo("email@example.com").build()).build(), - PortalSigner.identifiedByEmail("email@example.com").build() + "Job title", + asList(document), + asList( + PortalSigner.identifiedByPersonalIdentificationNumber("12345678910", + NotificationsUsingLookup.EMAIL_ONLY).build(), + PortalSigner.identifiedByPersonalIdentificationNumber("12345678911", + Notifications.builder().withEmailTo("email@example.com").build()).build(), + PortalSigner.identifiedByEmail("email@example.com").build()) ).build(); PortalJobResponse portalJobResponse = client.create(portalJob); @@ -95,15 +99,17 @@ static void specifying_queues() { Sender sender = new Sender("000000000", PollingQueue.of("CustomPollingQueue")); byte[] documentBytes = null; // Loaded document bytes - PortalDocument document = PortalDocument.builder("Subject", "document.pdf", documentBytes).build(); + PortalDocument document = PortalDocument.builder("Document title", documentBytes).build(); PortalJob portalJob = PortalJob.builder( - document, - PortalSigner.identifiedByPersonalIdentificationNumber("12345678910", - NotificationsUsingLookup.EMAIL_ONLY).build(), - PortalSigner.identifiedByPersonalIdentificationNumber("12345678911", - Notifications.builder().withEmailTo("email@example.com").build()).build(), - PortalSigner.identifiedByEmail("email@example.com").build() + "Job title", + asList(document), + asList( + PortalSigner.identifiedByPersonalIdentificationNumber("12345678910", + NotificationsUsingLookup.EMAIL_ONLY).build(), + PortalSigner.identifiedByPersonalIdentificationNumber("12345678911", + Notifications.builder().withEmailTo("email@example.com").build()).build(), + PortalSigner.identifiedByEmail("email@example.com").build()) ).withSender(sender).build(); PortalJobResponse portalJobResponse = client.create(portalJob);