diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f6fe4e73..d1d69a8f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,17 +7,19 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [ '1.8', '11' ] + java: [ '8', '11' ] name: build java ${{ matrix.java }} steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Set up java - uses: actions/setup-java@v1 + uses: actions/setup-java@v3.6.0 with: java-version: ${{ matrix.java }} + distribution: temurin + cache: "maven" - name: Build with Maven - run: mvn -B package --no-transfer-progress --file pom.xml + run: mvn --settings .mvn/settings.xml -B package --no-transfer-progress makeversion: if: github.ref != 'refs/heads/main' @@ -35,7 +37,7 @@ jobs: else TAG=${GITHUB_REF#refs/heads/}-SNAPSHOT fi - echo ::set-output name=version::${TAG//\//-} + echo "version=${TAG//\//-}" >> $GITHUB_OUTPUT deploy_snapshot: if: startsWith(github.ref, 'refs/heads/') @@ -44,8 +46,8 @@ jobs: name: Deploy snapshot steps: - - uses: actions/checkout@v1 - - uses: digipost/action-maven-publish@1.1.0 + - uses: actions/checkout@v3 + - uses: digipost/action-maven-publish@1.3.2 with: sonatype_secrets: ${{ secrets.sonatype_secrets }} release_version: ${{ needs.makeversion.outputs.version }} @@ -58,9 +60,9 @@ jobs: name: Release to Sonatype steps: - name: Check out Git repository - uses: actions/checkout@v1 + uses: actions/checkout@v3 - name: Release to Central Repository - uses: digipost/action-maven-publish@1.1.0 + uses: digipost/action-maven-publish@1.3.2 with: sonatype_secrets: ${{ secrets.sonatype_secrets }} release_version: ${{ needs.makeversion.outputs.version }} diff --git a/.mvn/settings.xml b/.mvn/settings.xml new file mode 100644 index 00000000..cf700ad6 --- /dev/null +++ b/.mvn/settings.xml @@ -0,0 +1,28 @@ + + + + + sonatype_snapshots + + + oss.sonatype.org-snapshot + https://oss.sonatype.org/content/repositories/snapshots + + false + + + true + + + + + + + + sonatype_snapshots + + + diff --git a/NOTICE b/NOTICE index 6c844b4e..4c242e6c 100644 --- a/NOTICE +++ b/NOTICE @@ -8,44 +8,24 @@ Licensed under Apache 2 - http://www.apache.org/licenses/LICENSE-2.0.html This software includes third party software subject to the following licenses: - aopalliance version 1.0 repackaged as a module under EPL 2.0 or GPL2 w/ CPE Apache Commons Codec under Apache License, Version 2.0 Apache Commons Lang under Apache License, Version 2.0 Apache HttpClient under Apache License, Version 2.0 + Apache HttpComponents Core HTTP/1.1 under Apache License, Version 2.0 + Apache HttpComponents Core HTTP/2 under Apache License, Version 2.0 Apache HttpCore under Apache License, Version 2.0 Digipost Certificate Validator under The Apache Software License, Version 2.0 - HK2 API module under EPL 2.0 or GPL2 w/ CPE - HK2 Implementation Utilities under EPL 2.0 or GPL2 w/ CPE 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 - Javassist under MPL 1.1 or LGPL 2.1 or Apache License 2.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 JCL 1.2 implemented over SLF4J under Apache License, Version 2.0 - jersey-core-client under EPL 2.0 or GPL2 w/ CPE or EDL 1.0 or BSD 2-Clause or Apache License, 2.0 or Public Domain or Modified BSD or jQuery license or MIT license or W3C license - jersey-core-common under EPL 2.0 or The GNU General Public License (GPL), Version 2, With Classpath Exception or Apache License, 2.0 or Public Domain - jersey-inject-hk2 under EPL 2.0 or GPL2 w/ CPE or EDL 1.0 or BSD 2-Clause or Apache License, 2.0 or Public Domain or Modified BSD or jQuery license or MIT license or W3C license - jersey-media-multipart under EPL 2.0 or GPL2 w/ CPE or EDL 1.0 or BSD 2-Clause or Apache License, 2.0 or Public Domain or Modified BSD or jQuery license or MIT license or W3C license - MIME streaming extension under Eclipse Distribution License - v 1.0 - OSGi resource locator under EPL 2.0 or GPL2 w/ CPE Posten signering - API JAXB Classes under The Apache Software License, Version 2.0 Posten signering - API Schema under The Apache Software License, Version 2.0 Posten signering - Java API Client Library under The Apache Software License, Version 2.0 - ServiceLocator Default Implementation under EPL 2.0 or GPL2 w/ CPE SLF4J API Module under MIT License - Spring AOP under Apache License, Version 2.0 - Spring Beans under Apache License, Version 2.0 - Spring Commons Logging Bridge under Apache License, Version 2.0 - Spring Context under Apache License, Version 2.0 - Spring Core under Apache License, Version 2.0 - Spring Expression Language (SpEL) under Apache License, Version 2.0 - Spring Object/XML Marshalling under Apache License, Version 2.0 - spring-xml under Apache License, Version 2.0 TXW2 Runtime under Eclipse Distribution License - v 1.0 diff --git a/pom.xml b/pom.xml index f4dbebd7..ded32f4c 100644 --- a/pom.xml +++ b/pom.xml @@ -10,37 +10,23 @@ no.digipost digipost-open-super-pom - 7 + 8 1.8 1.8 set_with_-Dproject.previousVersion=X.Y - 2.8 + 3.0.0-RC3 1.7.36 - - org.springframework - spring-framework-bom - 5.3.21 - pom - import - - - org.glassfish.jersey - jersey-bom - 2.36 - pom - import - org.junit junit-bom - 5.9.0-M1 + 5.9.2 pom import @@ -62,7 +48,7 @@ no.digipost certificate-validator - 2.5 + 2.6 org.bouncycastle @@ -70,55 +56,30 @@ + - org.apache.httpcomponents - httpclient - 4.5.13 - - - commons-logging - commons-logging - - - - - org.apache.httpcomponents - httpcore - 4.4.15 + org.apache.httpcomponents.client5 + httpclient5 + 5.2.1 - jakarta.annotation - jakarta.annotation-api - 1.3.5 + org.apache.httpcomponents.core5 + httpcore5 + 5.2.1 + - jakarta.ws.rs - jakarta.ws.rs-api - 2.1.6 + jakarta.xml.bind + jakarta.xml.bind-api + 2.3.3 org.glassfish.jaxb jaxb-runtime - 2.3.4 - runtime - - - org.glassfish.jersey.core - jersey-client - - - org.glassfish.jersey.core - jersey-common - - - org.glassfish.jersey.media - jersey-media-multipart - - - org.glassfish.jersey.inject - jersey-hk2 + 2.3.8 runtime + commons-io commons-io @@ -130,19 +91,6 @@ commons-codec 1.15 - - org.springframework - spring-oxm - - - org.springframework - spring-core - - - org.springframework.ws - spring-xml - 3.1.3 - org.apache.commons commons-lang3 @@ -154,12 +102,6 @@ slf4j-api ${slf4j.version} - - org.slf4j - jcl-over-slf4j - ${slf4j.version} - runtime - org.slf4j slf4j-simple @@ -186,7 +128,7 @@ nl.jqno.equalsverifier equalsverifier - 3.10 + 3.13.2 test @@ -210,7 +152,7 @@ no.digipost digg - 0.30 + 0.33 test @@ -238,7 +180,7 @@ org.glassfish.jaxb jaxb-runtime - 2.3.4 + 2.3.6 jakarta.xml.bind @@ -254,7 +196,7 @@ com.github.siom79.japicmp japicmp-maven-plugin - 0.15.7 + 0.17.1 @@ -313,11 +255,11 @@ maven-surefire-plugin - 3.0.0-M7 + 3.0.0-M9 maven-deploy-plugin - 3.0.0-M2 + 3.1.0 maven-clean-plugin @@ -325,27 +267,27 @@ maven-dependency-plugin - 3.3.0 + 3.5.0 maven-install-plugin - 3.0.0-M1 + 3.1.0 maven-resources-plugin - 3.2.0 + 3.3.0 maven-javadoc-plugin - 3.4.0 + 3.4.1 maven-jar-plugin - 3.2.2 + 3.3.0 maven-enforcer-plugin - 3.1.0 + 3.2.1 @@ -357,11 +299,7 @@ true - - 3.3.1 + 3.6.3 @@ -369,7 +307,7 @@ org.codehaus.mojo versions-maven-plugin - 2.11.0 + 2.14.2 @@ -414,25 +352,6 @@ - - - jdk11+ - - [11,) - - - - true - - - - scm:git:git@github.com:digipost/signature-api-client-java.git scm:git:git@github.com:digipost/signature-api-client-java.git diff --git a/src/main/java/no/digipost/signature/client/ClientConfiguration.java b/src/main/java/no/digipost/signature/client/ClientConfiguration.java index 796b59f6..ba125e43 100644 --- a/src/main/java/no/digipost/signature/client/ClientConfiguration.java +++ b/src/main/java/no/digipost/signature/client/ClientConfiguration.java @@ -6,28 +6,29 @@ import no.digipost.signature.client.core.Sender; import no.digipost.signature.client.core.SignatureJob; import no.digipost.signature.client.core.exceptions.KeyException; -import no.digipost.signature.client.core.internal.http.AddRequestHeaderFilter; import no.digipost.signature.client.core.internal.http.HttpIntegrationConfiguration; import no.digipost.signature.client.core.internal.http.SignatureApiTrustStrategy; import no.digipost.signature.client.core.internal.security.ProvidesCertificateResourcePaths; import no.digipost.signature.client.core.internal.security.TrustStoreLoader; -import no.digipost.signature.client.core.internal.xml.JaxbMessageReaderWriterProvider; import no.digipost.signature.client.security.CertificateChainValidation; import no.digipost.signature.client.security.KeyStoreConfig; import no.digipost.signature.client.security.OrganizationNumberValidation; import org.apache.commons.lang3.StringUtils; -import org.apache.http.ssl.SSLContexts; -import org.glassfish.jersey.client.ClientConfig; -import org.glassfish.jersey.client.ClientProperties; -import org.glassfish.jersey.logging.LoggingFeature; -import org.glassfish.jersey.media.multipart.MultiPartFeature; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.io.SocketConfig; +import org.apache.hc.core5.ssl.SSLContexts; +import org.apache.hc.core5.util.Timeout; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.net.ssl.SSLContext; -import javax.ws.rs.core.Configurable; -import javax.ws.rs.core.Configuration; -import javax.ws.rs.core.HttpHeaders; import java.io.InputStream; import java.net.URI; @@ -37,6 +38,7 @@ import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.time.Clock; +import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -44,7 +46,6 @@ import java.util.function.Consumer; import static java.util.Arrays.asList; -import static javax.ws.rs.core.HttpHeaders.USER_AGENT; import static no.digipost.signature.client.Certificates.TEST; import static no.digipost.signature.client.ClientMetadata.VERSION; @@ -71,39 +72,36 @@ public final class ClientConfiguration implements ProvidesCertificateResourcePat /** * Socket timeout is used for both requests and, if any, * underlying layered sockets (typically for - * secure sockets). The default value is {@value #DEFAULT_SOCKET_TIMEOUT_MS} ms. + * secure sockets). */ - public static final int DEFAULT_SOCKET_TIMEOUT_MS = 10_000; + public static final Duration DEFAULT_SOCKET_TIMEOUT = Duration.ofSeconds(10); /** - * The default connect timeout for requests: {@value #DEFAULT_CONNECT_TIMEOUT_MS} ms. + * The default connect timeout for requests. */ - public static final int DEFAULT_CONNECT_TIMEOUT_MS = 10_000; + public static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofSeconds(10); - private final Configurable jaxrsConfig; private final KeyStoreConfig keyStoreConfig; - private final Iterable certificatePaths; + private final org.apache.hc.client5.http.classic.HttpClient httpClient; private final Optional sender; private final URI signatureServiceRoot; + private final Iterable certificatePaths; private final Iterable documentBundleProcessors; - private final CertificateChainValidation serverCertificateValidation; private final Clock clock; private ClientConfiguration( - KeyStoreConfig keyStoreConfig, Configurable jaxrsConfig, - Optional sender, URI serviceRoot, Iterable certificatePaths, - Iterable documentBundleProcessors, CertificateChainValidation serverCertificateValidation, Clock clock) { + KeyStoreConfig keyStoreConfig, org.apache.hc.client5.http.classic.HttpClient httpClient, Optional sender, + URI serviceRoot, Iterable certificatePaths, Iterable documentBundleProcessors, Clock clock) { - this.jaxrsConfig = jaxrsConfig; this.keyStoreConfig = keyStoreConfig; - this.certificatePaths = certificatePaths; + this.httpClient = httpClient; this.sender = sender; this.signatureServiceRoot = serviceRoot; + this.certificatePaths = certificatePaths; this.documentBundleProcessors = documentBundleProcessors; - this.serverCertificateValidation = serverCertificateValidation; this.clock = clock; } @@ -134,70 +132,47 @@ public URI getServiceRoot() { } @Override - public Iterable getCertificatePaths() { - return certificatePaths; + public HttpClient httpClient() { + return httpClient; } - /** - * Get the JAX-RS {@link Configuration} based on the current state of this {@link ClientConfiguration}. - * - * @return the JAX-RS {@link Configuration} + * Build a new {@link ClientConfiguration}. */ - @Override - public Configuration getJaxrsConfiguration() { - return jaxrsConfig.getConfiguration(); + public static Builder builder(KeyStoreConfig keystore) { + return new Builder(keystore); } - @Override - public SSLContext getSSLContext() { - try { - return SSLContexts.custom() - .loadKeyMaterial(keyStoreConfig.keyStore, keyStoreConfig.privatekeyPassword.toCharArray(), (aliases, socket) -> keyStoreConfig.alias) - .loadTrustMaterial(TrustStoreLoader.build(this), new SignatureApiTrustStrategy(serverCertificateValidation)) - .build(); - } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | UnrecoverableKeyException e) { - if (e instanceof UnrecoverableKeyException && "Given final block not properly padded".equals(e.getMessage())) { - throw new KeyException( - "Unable to load key from keystore, because " + e.getClass().getSimpleName() + ": '" + e.getMessage() + "'. Possible causes:\n" + - "* Wrong password for private key (the password for the keystore and the private key may not be the same)\n" + - "* Multiple private keys in the keystore with different passwords (private keys in the same key store must have the same password)", e); - } else { - throw new KeyException("Unable to create the SSLContext, because " + e.getClass().getSimpleName() + ": '" + e.getMessage() + "'", e); - } - } + public Iterable getCertificatePaths() { + return certificatePaths; } - - /** - * Build a new {@link ClientConfiguration}. - */ - public static Builder builder(KeyStoreConfig keystore) { - return new Builder(keystore); - } - public static class Builder { - private final Configurable jaxrsConfig; + private final HttpClientBuilder httpClientBuilder; private final KeyStoreConfig keyStoreConfig; - private int socketTimeoutMs = DEFAULT_SOCKET_TIMEOUT_MS; - private int connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MS; + private Duration socketTimeout = DEFAULT_SOCKET_TIMEOUT; + private Duration connectTimeout = DEFAULT_CONNECT_TIMEOUT; + private Duration connectionRequestTimeout = connectTimeout; + private Duration responseArrivalTimeout = connectTimeout; + private Optional customUserAgentPart = Optional.empty(); private URI serviceRoot = ServiceUri.PRODUCTION.uri; private Optional globalSender = Optional.empty(); private Iterable certificatePaths = Certificates.PRODUCTION.certificatePaths; private CertificateChainValidation serverCertificateTrustStrategy = new OrganizationNumberValidation("984661185"); // Posten Norge AS organization number - private Optional loggingFeature = Optional.empty(); private List documentBundleProcessors = new ArrayList<>(); private Clock clock = Clock.systemDefaultZone(); + private Optional proxy = Optional.empty(); + private Optional> httpClientCustomizer = Optional.empty(); private Builder(KeyStoreConfig keyStoreConfig) { this.keyStoreConfig = keyStoreConfig; - this.jaxrsConfig = new ClientConfig(); + this.httpClientBuilder = HttpClientBuilder.create(); } /** @@ -217,22 +192,29 @@ public Builder serviceUri(URI uri) { /** * Override the - * {@link ClientConfiguration#DEFAULT_SOCKET_TIMEOUT_MS default socket timeout value}. + * {@link ClientConfiguration#DEFAULT_SOCKET_TIMEOUT default socket timeout value}. */ - public Builder socketTimeoutMillis(int millis) { - this.socketTimeoutMs = millis; + public Builder socketTimeout(Duration timeout) { + this.socketTimeout = timeout; return this; } /** * Override the - * {@link ClientConfiguration#DEFAULT_CONNECT_TIMEOUT_MS default connect timeout value}. + * {@link ClientConfiguration#DEFAULT_CONNECT_TIMEOUT default connect timeout value}. */ - public Builder connectTimeoutMillis(int millis) { - this.connectTimeoutMs = millis; + public Builder connectTimeout(Duration timeout) { + this.connectTimeout = timeout; return this; } + /** + * Set proxy to be used by {@link HttpClient}. + */ + public void proxy(HttpHost proxy) { + this.proxy = Optional.of(proxy); + } + public Builder trustStore(Certificates certificates) { if (certificates == TEST) { LOG.warn("Using test certificates in trust store. This should never be done for production environments."); @@ -240,7 +222,6 @@ public Builder trustStore(Certificates certificates) { return trustStore(certificates.certificatePaths); } - /** * Override the trust store configuration to load DER-encoded certificates from the given folder(s). * @@ -288,7 +269,7 @@ public Builder includeInUserAgent(String userAgentCustomPart) { * {@link ClientConfiguration#HTTP_REQUEST_RESPONSE_LOGGER_NAME}. */ public Builder enableRequestAndResponseLogging() { - loggingFeature = Optional.of(new LoggingFeature(java.util.logging.Logger.getLogger(HTTP_REQUEST_RESPONSE_LOGGER_NAME), 16 * 1024)); + //loggingFeature = Optional.of(new LoggingFeature(java.util.logging.Logger.getLogger(HTTP_REQUEST_RESPONSE_LOGGER_NAME), 16 * 1024)); return this; } @@ -317,7 +298,7 @@ public Builder enableDocumentBundleDiskDump(Path directory) { * together with the {@link SignatureJob job} it was created for. The processor is not responsible for closing * the stream, as this is handled by the library itself. * - *

A note on performance

+ *

A note on performance: * The processor is free to do what it want with the passed stream, but bear in mind that the time * used by a processor adds to the processing time to create signature jobs. * @@ -330,7 +311,7 @@ public Builder addDocumentBundleProcessor(DocumentBundleProcessor processor) { } /** - * This methods allows for custom configuration of JAX-RS (i.e. Jersey) if anything is + * This methods allows for custom configuration of the {@link HttpClient} if anything is * needed that is not already supported by the {@link ClientConfiguration.Builder}. * This method should not be used to configure anything that is already directly supported by the * {@code ClientConfiguration.Builder} API. @@ -338,11 +319,10 @@ public Builder addDocumentBundleProcessor(DocumentBundleProcessor processor) { * If you still need to use this method, consider requesting first-class support for your requirement * on the library's web site on GitHub. * - * @param customizer The operations to do on the JAX-RS {@link Configurable}, e.g. - * {@link Configurable#register(Object) registering components}. + * @param customizer The operations to do on the {@link HttpClientBuilder}. */ - public Builder customizeJaxRs(Consumer> customizer) { - customizer.accept(jaxrsConfig); + public Builder customizeHttpClient(Consumer customizer) { + this.httpClientCustomizer = Optional.of(customizer); return this; } @@ -386,34 +366,53 @@ public Builder usingClock(Clock clock) { return this; } - - /** - * Disable the pre-initialization step of the internal HTTP client (Jersey Client) when - * instantiating the Signature API Client. - * - * @see org.glassfish.jersey.client.JerseyClient#preInitialize() - */ - public Builder disablePreInitializingHttpClient() { - this.jaxrsConfig.property(PRE_INIT_CLIENT, false); - return this; - } - public ClientConfiguration build() { - jaxrsConfig.property(ClientProperties.READ_TIMEOUT, socketTimeoutMs); - jaxrsConfig.property(ClientProperties.CONNECT_TIMEOUT, connectTimeoutMs); - jaxrsConfig.register(MultiPartFeature.class); - jaxrsConfig.register(JaxbMessageReaderWriterProvider.class); - jaxrsConfig.register(new AddRequestHeaderFilter(USER_AGENT, createUserAgentString())); - this.loggingFeature.ifPresent(jaxrsConfig::register); - return new ClientConfiguration( - keyStoreConfig, jaxrsConfig, globalSender, serviceRoot, certificatePaths, - documentBundleProcessors, serverCertificateTrustStrategy, clock); + // TODO: Add possibility to add logger for http client + + SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(Timeout.ofMilliseconds(socketTimeout.toMillis())).build(); + PoolingHttpClientConnectionManagerBuilder poolingHttpClientConnectionManager = PoolingHttpClientConnectionManagerBuilder.create() + .setDefaultSocketConfig(socketConfig) + .setSSLSocketFactory(SSLConnectionSocketFactoryBuilder.create() + .setSslContext(sslContext()) + .setHostnameVerifier(NoopHostnameVerifier.INSTANCE) + .build()); + + RequestConfig.Builder requestConfigBuilder = RequestConfig.custom() + .setConnectionRequestTimeout(Timeout.ofMilliseconds(connectionRequestTimeout.toMillis())) + .setConnectTimeout(Timeout.ofMilliseconds(connectTimeout.toMillis())) + .setResponseTimeout(Timeout.ofMilliseconds(responseArrivalTimeout.toMillis())); + + httpClientBuilder + .setConnectionManager(poolingHttpClientConnectionManager.build()) + .setDefaultRequestConfig(requestConfigBuilder.build()) + .setUserAgent(createUserAgentString()); + proxy.ifPresent(httpClientBuilder::setProxy); + httpClientCustomizer.ifPresent(customizer -> customizer.accept(httpClientBuilder)); + + return new ClientConfiguration(keyStoreConfig, httpClientBuilder.build(), globalSender, serviceRoot, certificatePaths, documentBundleProcessors, clock); } String createUserAgentString() { return MANDATORY_USER_AGENT + customUserAgentPart.map(ua -> String.format(" (%s)", ua)).orElse(""); } + private SSLContext sslContext() { + try { + return SSLContexts.custom() + .loadKeyMaterial(keyStoreConfig.keyStore, keyStoreConfig.privatekeyPassword.toCharArray(), (aliases, socket) -> keyStoreConfig.alias) + .loadTrustMaterial(TrustStoreLoader.build(() -> certificatePaths), new SignatureApiTrustStrategy(serverCertificateTrustStrategy)) + .build(); + } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | UnrecoverableKeyException e) { + if (e instanceof UnrecoverableKeyException && "Given final block not properly padded".equals(e.getMessage())) { + throw new KeyException( + "Unable to load key from keystore, because " + e.getClass().getSimpleName() + ": '" + e.getMessage() + "'. Possible causes:\n" + + "* Wrong password for private key (the password for the keystore and the private key may not be the same)\n" + + "* Multiple private keys in the keystore with different passwords (private keys in the same key store must have the same password)", e); + } else { + throw new KeyException("Unable to create the SSLContext, because " + e.getClass().getSimpleName() + ": '" + e.getMessage() + "'", e); + } + } + } } } diff --git a/src/main/java/no/digipost/signature/client/archive/ArchiveClient.java b/src/main/java/no/digipost/signature/client/archive/ArchiveClient.java index b3075bbe..1f345c9a 100644 --- a/src/main/java/no/digipost/signature/client/archive/ArchiveClient.java +++ b/src/main/java/no/digipost/signature/client/archive/ArchiveClient.java @@ -16,7 +16,7 @@ public ArchiveClient(HttpIntegrationConfiguration httpIntegrationConfig) { } public ResponseInputStream getPAdES(ArchiveOwner owner, String id) { - return client.getDataStream(root -> root.path(owner.getOrganizationNumber()).path("archive/documents/").path(id).path("pades")); + return client.getDataStream(owner.getOrganizationNumber() + "/archive/documents/" + id + "/pades"); } } diff --git a/src/main/java/no/digipost/signature/client/asice/manifest/ManifestCreator.java b/src/main/java/no/digipost/signature/client/asice/manifest/ManifestCreator.java index 230f32fd..05ba8aed 100644 --- a/src/main/java/no/digipost/signature/client/asice/manifest/ManifestCreator.java +++ b/src/main/java/no/digipost/signature/client/asice/manifest/ManifestCreator.java @@ -3,9 +3,6 @@ import no.digipost.signature.client.core.Sender; import no.digipost.signature.client.core.SignatureJob; import no.digipost.signature.client.core.exceptions.RuntimeIOException; -import no.digipost.signature.client.core.exceptions.XmlValidationException; -import org.springframework.oxm.MarshallingFailureException; -import org.xml.sax.SAXParseException; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -20,13 +17,6 @@ public Manifest createManifest(JOB job, Sender sender) { try (ByteArrayOutputStream manifestStream = new ByteArrayOutputStream()) { marshal(xmlManifest, manifestStream); return new Manifest(manifestStream.toByteArray()); - } catch (MarshallingFailureException e) { - if (e.getMostSpecificCause() instanceof SAXParseException) { - throw new XmlValidationException("Unable to validate generated Manifest XML. " + - "This typically happens if one or more values are not in accordance with the XSD. " + - "You may inspect the cause (by calling getCause()) to see which constraint has been violated.", (SAXParseException) e.getMostSpecificCause()); - } - throw e; } catch (IOException e) { throw new RuntimeIOException(e); } 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 f20c4fc9..1d0096d8 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 @@ -4,15 +4,15 @@ import no.digipost.signature.client.core.exceptions.XmlConfigurationException; import no.digipost.signature.client.core.exceptions.XmlValidationException; import no.digipost.signature.client.security.KeyStoreConfig; +import no.digipost.signature.jaxb.JaxbMarshaller; import no.digipost.signature.xsd.SignatureApiSchemas; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -import org.springframework.xml.validation.SchemaLoaderUtils; -import org.springframework.xml.validation.XmlValidatorFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import javax.xml.XMLConstants; import javax.xml.crypto.MarshalException; import javax.xml.crypto.NodeSetData; import javax.xml.crypto.URIDereferencer; @@ -33,21 +33,34 @@ import javax.xml.crypto.dsig.keyinfo.X509Data; import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec; import javax.xml.crypto.dsig.spec.TransformParameterSpec; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.Source; import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXSource; import javax.xml.validation.Schema; - +import javax.xml.validation.SchemaFactory; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; import java.io.UnsupportedEncodingException; +import java.net.URL; import java.net.URLEncoder; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; import java.time.Clock; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; import java.util.List; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; +import static java.util.Objects.requireNonNull; public class CreateSignature { @@ -82,14 +95,50 @@ public CreateSignature(Clock clock) { schema = loadSchema(); } - private Schema loadSchema() { + private static InputSource createInputSource(String resource) { + URL resourceUrl = requireNonNull(JaxbMarshaller.class.getResource(resource), resource); + try (InputStream inputStream = resourceUrl.openStream()) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + final int bufLen = 1024; + byte[] buf = new byte[bufLen]; + int readLen; + while ((readLen = inputStream.read(buf, 0, bufLen)) != -1) + outputStream.write(buf, 0, readLen); + + InputSource source = new InputSource(new ByteArrayInputStream(outputStream.toByteArray())); + source.setSystemId(resourceUrl.toString()); + return source; + } catch (IOException e) { + throw new UncheckedIOException( + "Unable to resolve " + resource + " from " + resourceUrl + ", " + + "because " + e.getClass().getSimpleName() + " '" + e.getMessage() + "'", e); + } + } + + private static Schema createSchema(Collection resources) { try { - return SchemaLoaderUtils.loadSchema(new Resource[]{new ClassPathResource(SignatureApiSchemas.XMLDSIG_SCHEMA), new ClassPathResource(SignatureApiSchemas.ASICE_SCHEMA)}, XmlValidatorFactory.SCHEMA_W3C_XML); - } catch (IOException | SAXException e) { - throw new ConfigurationException("Failed to load schemas for validating signatures", e); + SAXParserFactory parserFactory = SAXParserFactory.newInstance(); + parserFactory.setNamespaceAware(true); + parserFactory.setFeature("http://xml.org/sax/features/namespace-prefixes", true); + + SAXParser saxParser = parserFactory.newSAXParser(); + XMLReader xmlReader = saxParser.getXMLReader(); + Source[] schemaSources = resources.stream() + .map(resource -> new SAXSource(xmlReader, createInputSource(resource))) + .toArray(Source[]::new); + + SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + return schemaFactory.newSchema(schemaSources); + } catch (Exception e) { + throw new RuntimeException("Could not create schema from resources [" + String.join(", ", resources) + "]", e); } } + private Schema loadSchema() { + return createSchema(new HashSet<>(Arrays.asList(SignatureApiSchemas.XMLDSIG_SCHEMA, SignatureApiSchemas.ASICE_SCHEMA))); + } + public Signature createSignature(final List attachedFiles, final KeyStoreConfig keyStoreConfig) { return new Signature(domUtils.serializeToXml(createXmlSignature(attachedFiles, keyStoreConfig))); } 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 2ea05a09..810e9f1b 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 @@ -44,7 +44,7 @@ XAdESArtifacts createArtifactsToSign(List files } DigestAlgAndValueType certificateDigest = new DigestAlgAndValueType(sha1DigestMethod, certificateDigestValue); - X509IssuerSerialType certificateIssuer = new X509IssuerSerialType(certificate.getIssuerDN().getName(), certificate.getSerialNumber()); + X509IssuerSerialType certificateIssuer = new X509IssuerSerialType(certificate.getIssuerX500Principal().getName(), certificate.getSerialNumber()); SigningCertificate signingCertificate = new SigningCertificate(singletonList(new CertIDType(certificateDigest, certificateIssuer, null))); ZonedDateTime now = ZonedDateTime.now(clock); diff --git a/src/main/java/no/digipost/signature/client/asice/signature/XAdESArtifacts.java b/src/main/java/no/digipost/signature/client/asice/signature/XAdESArtifacts.java index 54cfb94a..2ec0f40f 100644 --- a/src/main/java/no/digipost/signature/client/asice/signature/XAdESArtifacts.java +++ b/src/main/java/no/digipost/signature/client/asice/signature/XAdESArtifacts.java @@ -1,29 +1,40 @@ package no.digipost.signature.client.asice.signature; import no.digipost.signature.api.xml.thirdparty.xades.QualifyingProperties; -import org.springframework.oxm.jaxb.Jaxb2Marshaller; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; import javax.xml.transform.dom.DOMResult; import static java.util.stream.IntStream.range; final class XAdESArtifacts { - private static Jaxb2Marshaller marshaller; + private static Marshaller marshaller; static { - marshaller = new Jaxb2Marshaller(); - marshaller.setClassesToBeBound(QualifyingProperties.class); + try { + marshaller = JAXBContext.newInstance(QualifyingProperties.class).createMarshaller(); + } catch (JAXBException e) { + throw new RuntimeException(e); + } } public static XAdESArtifacts from(QualifyingProperties qualifyingProperties) { DOMResult domResult = new DOMResult(); - marshaller.marshal(qualifyingProperties, domResult); + try { + marshaller.marshal(qualifyingProperties, domResult); + } catch (JAXBException e) { + throw new RuntimeException( + "Failed to marshal qualifying properties, because " + + e.getClass().getSimpleName() + " '" + e.getMessage() + "'", e); + } return from((Document) domResult.getNode()); } diff --git a/src/main/java/no/digipost/signature/client/core/PollingQueue.java b/src/main/java/no/digipost/signature/client/core/PollingQueue.java index 1141cbd9..196aeed3 100644 --- a/src/main/java/no/digipost/signature/client/core/PollingQueue.java +++ b/src/main/java/no/digipost/signature/client/core/PollingQueue.java @@ -30,4 +30,9 @@ public int hashCode() { return Objects.hash(value); } + @Override + public String toString() { + return value != null ? "polling-queue '" + value + "'" : "no specified polling-queue (default)"; + } + } diff --git a/src/main/java/no/digipost/signature/client/core/ResponseInputStream.java b/src/main/java/no/digipost/signature/client/core/ResponseInputStream.java index 85427c4a..4f203256 100644 --- a/src/main/java/no/digipost/signature/client/core/ResponseInputStream.java +++ b/src/main/java/no/digipost/signature/client/core/ResponseInputStream.java @@ -5,14 +5,14 @@ public class ResponseInputStream extends FilterInputStream { - private final int contentLength; + private final long contentLength; - public ResponseInputStream(InputStream in, int contentLength) { + public ResponseInputStream(InputStream in, long contentLength) { super(in); this.contentLength = contentLength; } - public int getContentLength() { + public long getContentLength() { return contentLength; } } diff --git a/src/main/java/no/digipost/signature/client/core/Sender.java b/src/main/java/no/digipost/signature/client/core/Sender.java index 3bf061ac..684fbfb6 100644 --- a/src/main/java/no/digipost/signature/client/core/Sender.java +++ b/src/main/java/no/digipost/signature/client/core/Sender.java @@ -41,4 +41,9 @@ public int hashCode() { return Objects.hash(organizationNumber, pollingQueue); } + @Override + public String toString() { + return "sender " + organizationNumber + ", " + pollingQueue; + } + } diff --git a/src/main/java/no/digipost/signature/client/core/exceptions/CantQueryStatusException.java b/src/main/java/no/digipost/signature/client/core/exceptions/CantQueryStatusException.java index c0e4bce5..0d85b01e 100644 --- a/src/main/java/no/digipost/signature/client/core/exceptions/CantQueryStatusException.java +++ b/src/main/java/no/digipost/signature/client/core/exceptions/CantQueryStatusException.java @@ -1,13 +1,13 @@ package no.digipost.signature.client.core.exceptions; -import javax.ws.rs.core.Response.StatusType; +import no.digipost.signature.client.core.internal.http.StatusCode; public class CantQueryStatusException extends SignatureException { - public CantQueryStatusException(StatusType status, String errorMessageFromServer) { + public CantQueryStatusException(StatusCode status, String errorMessageFromServer) { super("The service refused to process the status request. This happens when the job has not been completed " + "(i.e. the signer haven't signed or rejected). Please wait until the signer have been redirected to " + "one of the exit URLs provided in the initial request before querying the job's status. The server response was " + - status.getStatusCode() + " " + status.getReasonPhrase() + " '" + errorMessageFromServer + "'"); + status.value() + " '" + errorMessageFromServer + "'"); } } diff --git a/src/main/java/no/digipost/signature/client/core/exceptions/JobCannotBeCancelledException.java b/src/main/java/no/digipost/signature/client/core/exceptions/JobCannotBeCancelledException.java index 13f24acd..891dc0c9 100644 --- a/src/main/java/no/digipost/signature/client/core/exceptions/JobCannotBeCancelledException.java +++ b/src/main/java/no/digipost/signature/client/core/exceptions/JobCannotBeCancelledException.java @@ -1,20 +1,19 @@ package no.digipost.signature.client.core.exceptions; import no.digipost.signature.api.xml.XMLError; - -import javax.ws.rs.core.Response.StatusType; +import no.digipost.signature.client.core.internal.http.StatusCode; public class JobCannotBeCancelledException extends SignatureException { - public JobCannotBeCancelledException(StatusType status, XMLError errorEntity) { + public JobCannotBeCancelledException(StatusCode status, XMLError errorEntity) { this(status, errorEntity.getErrorCode(), errorEntity.getErrorMessage()); } - public JobCannotBeCancelledException(StatusType status, String errorCode, String errorMessageFromServer) { + public JobCannotBeCancelledException(StatusCode status, String errorCode, String errorMessageFromServer) { super("The service refused to process the cancellation. This happens when the job has been completed " + "(i.e. all signers have signed or rejected, the job has expired, etc.) since receiving last update. " + "Please ask the service for status changes to get the latest changes. The server response was " + - status.getStatusCode() + " " + status.getReasonPhrase() + " '" + errorCode + ": " + errorMessageFromServer + "'"); + status.value() + " '" + errorCode + ": " + errorMessageFromServer + "'"); } } diff --git a/src/main/java/no/digipost/signature/client/core/exceptions/UnexpectedResponseException.java b/src/main/java/no/digipost/signature/client/core/exceptions/UnexpectedResponseException.java index b8456ead..7dc2b9f5 100644 --- a/src/main/java/no/digipost/signature/client/core/exceptions/UnexpectedResponseException.java +++ b/src/main/java/no/digipost/signature/client/core/exceptions/UnexpectedResponseException.java @@ -1,49 +1,39 @@ package no.digipost.signature.client.core.exceptions; import no.digipost.signature.api.xml.XMLError; - -import javax.ws.rs.core.Response.Status; -import javax.ws.rs.core.Response.StatusType; +import no.digipost.signature.client.core.internal.http.StatusCode; import java.util.Objects; public class UnexpectedResponseException extends SignatureException { private final XMLError error; - private final StatusType actualStatus; + private final int httpStatusCode; - public UnexpectedResponseException(StatusType actual) { + public UnexpectedResponseException(StatusCode actual) { this(null, actual); } - public UnexpectedResponseException(Object errorEntity, StatusType actual, StatusType ... expected) { + public UnexpectedResponseException(Object errorEntity, StatusCode actual, StatusCode ... expected) { this(errorEntity, null, actual, expected); } - public UnexpectedResponseException(Object errorEntity, Throwable cause, StatusType actual, StatusType ... expected) { + public UnexpectedResponseException(Object errorEntity, Throwable cause, StatusCode actual, StatusCode ... expected) { super("Expected " + prettyprintExpectedStatuses(expected) + - ", but got " + actual.getStatusCode() + " " + actual.getReasonPhrase() + + ", but got " + actual.value() + (errorEntity != null ? " [" + errorEntity + "]" : "") + (cause != null ? " - " + cause.getClass().getSimpleName() + ": '" + cause.getMessage() + "'.": ""), cause); this.error = errorEntity instanceof XMLError ? (XMLError) errorEntity : null; - this.actualStatus = actual; - } - - public StatusType getActualStatus() { - return actualStatus; - } - - public boolean is(Status.Family family) { - return actualStatus != null && actualStatus.getFamily() == family; + this.httpStatusCode = actual.value(); } - public boolean isStatusCode(int statusCode) { - return actualStatus != null && actualStatus.getStatusCode() == statusCode; + public boolean isHttpStatusCode(int statusCode) { + return this.httpStatusCode == statusCode; } - public boolean isStatusCodeOf(StatusType status) { - return isStatusCode(status.getStatusCode()); + public int httpStatusCode() { + return httpStatusCode; } public String getErrorCode() { @@ -59,10 +49,10 @@ public String getErrorType() { } public boolean isErrorCode(String errorCode) { - return error != null & Objects.equals(error.getErrorCode(), errorCode); + return error != null && Objects.equals(error.getErrorCode(), errorCode); } - private static String prettyprintExpectedStatuses(StatusType ... statuses) { + private static String prettyprintExpectedStatuses(StatusCode ... statuses) { if (statuses == null || statuses.length == 0) { return "status not specified"; } @@ -73,8 +63,8 @@ private static String prettyprintExpectedStatuses(StatusType ... statuses) { return message + "]"; } - private static String prettyprintSingleStatus(StatusType status) { - return status.getStatusCode() + " " + status.getReasonPhrase(); + private static String prettyprintSingleStatus(StatusCode status) { + return status.value() +""; } } diff --git a/src/main/java/no/digipost/signature/client/core/internal/ClientExceptionMapper.java b/src/main/java/no/digipost/signature/client/core/internal/ClientExceptionMapper.java index 62c221a2..d852d90e 100644 --- a/src/main/java/no/digipost/signature/client/core/internal/ClientExceptionMapper.java +++ b/src/main/java/no/digipost/signature/client/core/internal/ClientExceptionMapper.java @@ -1,11 +1,8 @@ package no.digipost.signature.client.core.internal; -import no.digipost.signature.client.core.exceptions.ConfigurationException; import no.digipost.signature.client.core.exceptions.SignatureException; -import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; -import javax.ws.rs.ProcessingException; import java.util.function.Supplier; class ClientExceptionMapper { @@ -20,23 +17,13 @@ void doWithMappedClientException(Runnable action) { T doWithMappedClientException(Supplier produceResult) { try { return produceResult.get(); - } catch (ProcessingException e) { + } catch (RuntimeException e) { throw map(e); } } - private RuntimeException map(ProcessingException e) { - if (e.getCause() instanceof SSLException) { - String sslExceptionMessage = e.getCause().getMessage(); - if (sslExceptionMessage != null && sslExceptionMessage.contains("protocol_version")) { - return new ConfigurationException( - "Invalid TLS protocol version. This will typically happen if you're running on an older Java version, which doesn't support TLS 1.2. " + - "Java 7 needs to be explicitly configured to support TLS 1.2. See 'JSSE tuning parameters' at " + - "https://blogs.oracle.com/java-platform-group/entry/diagnosing_tls_ssl_and_https.", e); - } - } - + private RuntimeException map(RuntimeException e) { if (e.getCause() instanceof SSLHandshakeException) { return new SignatureException( "Unable to perform SSL handshake with remote server. Some possible causes (could be others, see underlying error): \n" + diff --git a/src/main/java/no/digipost/signature/client/core/internal/ClientHelper.java b/src/main/java/no/digipost/signature/client/core/internal/ClientHelper.java index ead50461..af960b2b 100644 --- a/src/main/java/no/digipost/signature/client/core/internal/ClientHelper.java +++ b/src/main/java/no/digipost/signature/client/core/internal/ClientHelper.java @@ -20,55 +20,59 @@ import no.digipost.signature.client.core.exceptions.InvalidStatusQueryTokenException; import no.digipost.signature.client.core.exceptions.JobCannotBeCancelledException; import no.digipost.signature.client.core.exceptions.NotCancellableException; -import no.digipost.signature.client.core.exceptions.RuntimeIOException; import no.digipost.signature.client.core.exceptions.SignatureException; import no.digipost.signature.client.core.exceptions.TooEagerPollingException; import no.digipost.signature.client.core.exceptions.UnexpectedResponseException; import no.digipost.signature.client.core.internal.http.ResponseStatus; import no.digipost.signature.client.core.internal.http.SignatureHttpClient; +import no.digipost.signature.client.core.internal.http.StatusCode; +import no.digipost.signature.client.core.internal.http.StatusCodeFamily; +import no.digipost.signature.client.core.internal.xml.Marshalling; import no.digipost.signature.client.direct.WithSignerUrl; import org.apache.commons.lang3.StringUtils; -import org.glassfish.jersey.media.multipart.BodyPart; -import org.glassfish.jersey.media.multipart.MultiPart; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.entity.mime.ByteArrayBody; +import org.apache.hc.client5.http.entity.mime.InputStreamBody; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.entity.mime.MultipartPartBuilder; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.NameValuePair; +import org.apache.hc.core5.http.ProtocolException; +import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; +import org.apache.hc.core5.http.message.BasicHeader; +import org.apache.hc.core5.http.message.HeaderGroup; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.ws.rs.client.Entity; -import javax.ws.rs.client.Invocation; -import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.StatusType; - +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.UncheckedIOException; import java.net.URI; +import java.nio.charset.StandardCharsets; import java.time.Instant; import java.time.ZonedDateTime; -import java.util.Arrays; -import java.util.List; import java.util.Optional; import java.util.function.Supplier; -import java.util.function.UnaryOperator; import static java.time.format.DateTimeFormatter.ISO_DATE_TIME; -import static javax.ws.rs.core.HttpHeaders.CONTENT_LENGTH; -import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE; -import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE; -import static javax.ws.rs.core.MediaType.APPLICATION_XML_TYPE; -import static javax.ws.rs.core.Response.Status.CONFLICT; -import static javax.ws.rs.core.Response.Status.FORBIDDEN; -import static javax.ws.rs.core.Response.Status.NOT_FOUND; -import static javax.ws.rs.core.Response.Status.NO_CONTENT; -import static javax.ws.rs.core.Response.Status.OK; -import static javax.ws.rs.core.Response.Status.TOO_MANY_REQUESTS; -import static javax.ws.rs.core.Response.Status.Family.SUCCESSFUL; import static no.digipost.signature.client.core.internal.ActualSender.getActualSender; import static no.digipost.signature.client.core.internal.ErrorCodes.BROKER_NOT_AUTHORIZED; import static no.digipost.signature.client.core.internal.ErrorCodes.SIGNING_CEREMONY_NOT_COMPLETED; import static no.digipost.signature.client.core.internal.Target.DIRECT; import static no.digipost.signature.client.core.internal.Target.PORTAL; +import static no.digipost.signature.client.core.internal.http.StatusCodeFamily.SUCCESSFUL; +import static org.apache.hc.core5.http.ContentType.APPLICATION_OCTET_STREAM; +import static org.apache.hc.core5.http.ContentType.APPLICATION_XML; +import static org.apache.hc.core5.http.ContentType.MULTIPART_MIXED; +import static org.apache.hc.core5.http.HttpHeaders.ACCEPT; +import static org.apache.hc.core5.http.HttpHeaders.CONTENT_TYPE; public class ClientHelper { @@ -90,74 +94,140 @@ public ClientHelper(SignatureHttpClient httpClient, Optional globalSende public XMLDirectSignatureJobResponse sendSignatureJobRequest(XMLDirectSignatureJobRequest signatureJobRequest, DocumentBundle documentBundle, Optional sender) { final Sender actualSender = getActualSender(sender, globalSender); - final BodyPart signatureJobBodyPart = new BodyPart(signatureJobRequest, APPLICATION_XML_TYPE); - final BodyPart documentBundleBodyPart = new BodyPart(documentBundle.getInputStream(), APPLICATION_OCTET_STREAM_TYPE); - - return call(() -> new UsingBodyParts(signatureJobBodyPart, documentBundleBodyPart) - .postAsMultiPart(DIRECT.path(actualSender), XMLDirectSignatureJobResponse.class)); - } - - public XMLDirectSignerResponse requestNewRedirectUrl(WithSignerUrl url) { - try (Response response = postEntity(url.getSignerUrl(), new XMLDirectSignerUpdateRequest().withRedirectUrl(new XMLEmptyElement()))) { - return parseResponse(response, XMLDirectSignerResponse.class); - } + return multipartSignatureJobRequest(signatureJobRequest, documentBundle, actualSender, DIRECT, XMLDirectSignatureJobResponse.class); } public XMLPortalSignatureJobResponse sendPortalSignatureJobRequest(XMLPortalSignatureJobRequest signatureJobRequest, DocumentBundle documentBundle, Optional sender) { final Sender actualSender = getActualSender(sender, globalSender); - final BodyPart signatureJobBodyPart = new BodyPart(signatureJobRequest, APPLICATION_XML_TYPE); - final BodyPart documentBundleBodyPart = new BodyPart(documentBundle.getInputStream(), APPLICATION_OCTET_STREAM_TYPE); + return multipartSignatureJobRequest(signatureJobRequest, documentBundle, actualSender, PORTAL, XMLPortalSignatureJobResponse.class); + } + + private RESPONSE multipartSignatureJobRequest(REQUEST signatureJobRequest, DocumentBundle documentBundle, Sender actualSender, Target target, Class responseClass) { + return call(() -> { + try { + MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create(); + multipartEntityBuilder.setContentType(MULTIPART_MIXED); + + try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { + Marshalling.marshal(signatureJobRequest, os); + multipartEntityBuilder.addPart(MultipartPartBuilder.create() + .setBody(new ByteArrayBody(os.toByteArray(), APPLICATION_XML, "")) + .addHeader(CONTENT_TYPE, APPLICATION_XML.getMimeType()) + .build()); + } + multipartEntityBuilder.addPart(MultipartPartBuilder.create() + .setBody(new InputStreamBody(documentBundle.getInputStream(), APPLICATION_OCTET_STREAM, "")) + .addHeader(CONTENT_TYPE, APPLICATION_OCTET_STREAM.getMimeType()).build()); + + try (HttpEntity multiPart = multipartEntityBuilder.build()) { + ClassicHttpRequest request = ClassicRequestBuilder + .post(httpClient.constructUrl(uri -> uri.appendPath(target.path(actualSender)))) + .addHeader(ACCEPT, APPLICATION_XML.getMimeType()) + .build(); + + request.setEntity(multiPart); + + return httpClient.httpClient().execute(request, response -> parseResponse(response, responseClass)); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } - return call(() -> new UsingBodyParts(signatureJobBodyPart, documentBundleBodyPart) - .postAsMultiPart(PORTAL.path(actualSender), XMLPortalSignatureJobResponse.class)); + public XMLDirectSignerResponse requestNewRedirectUrl(WithSignerUrl url) { + try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { + Marshalling.marshal(new XMLDirectSignerUpdateRequest().withRedirectUrl(new XMLEmptyElement()), os); + ClassicHttpRequest request = new HttpPost(url.getSignerUrl()); + request.addHeader(ACCEPT, APPLICATION_XML.getMimeType()); + + return httpClient.httpClient().execute(request, response -> parseResponse(response, XMLDirectSignerResponse.class)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } public XMLDirectSignatureJobStatusResponse sendSignatureJobStatusRequest(final URI statusUrl) { return call(() -> { - Invocation.Builder request = httpClient.target(statusUrl).request().accept(APPLICATION_XML_TYPE); - - try (Response response = request.get()) { - ResponseStatus.resolve(response.getStatus()).expect(SUCCESSFUL).orThrow(status -> { - if (status == FORBIDDEN) { - XMLError error = extractError(response); - if (ErrorCodes.INVALID_STATUS_QUERY_TOKEN.sameAs(error.getErrorCode())) { - return new InvalidStatusQueryTokenException(statusUrl, error.getErrorMessage()); - } - } else if (status == NOT_FOUND) { - XMLError error = extractError(response); - if (SIGNING_CEREMONY_NOT_COMPLETED.sameAs(error.getErrorCode())) { - return new CantQueryStatusException(status, error.getErrorMessage()); + ClassicHttpRequest request = new HttpGet(statusUrl); + request.addHeader(ACCEPT, APPLICATION_XML.getMimeType()); + + try { + return httpClient.httpClient().execute(request, response -> { + ResponseStatus.resolve(response.getCode()).expect(StatusCodeFamily.SUCCESSFUL).orThrow(status -> { + if (status.value() == HttpStatus.SC_FORBIDDEN) { + XMLError error = extractError(response); + if (ErrorCodes.INVALID_STATUS_QUERY_TOKEN.sameAs(error.getErrorCode())) { + return new InvalidStatusQueryTokenException(statusUrl, error.getErrorMessage()); + } + } else if (status.value() == HttpStatus.SC_NOT_FOUND) { + XMLError error = extractError(response); + if (SIGNING_CEREMONY_NOT_COMPLETED.sameAs(error.getErrorCode())) { + return new CantQueryStatusException(status, error.getErrorMessage()); + } } - } - return exceptionForGeneralError(response); + return exceptionForGeneralError(response); + }); + return parseResponse(response, XMLDirectSignatureJobStatusResponse.class); }); - return response.readEntity(XMLDirectSignatureJobStatusResponse.class); + } catch (IOException e) { + throw new UncheckedIOException(e); } }); } - public ResponseInputStream getDataStream(URI uri, MediaType ... acceptedResponses) { - return getDataStream(ignoredRoot -> httpClient.target(uri), acceptedResponses); + public ResponseInputStream getDataStream(String path, ContentType ... acceptedResponses) { + return getDataStream(httpClient.constructUrl(uri -> uri.appendPath(path))); } - public ResponseInputStream getDataStream(UnaryOperator targetResolver, MediaType ... acceptedResponses) { + public ResponseInputStream getDataStream(URI absoluteUri, ContentType ... acceptedResponses) { + if (!absoluteUri.isAbsolute()) { + throw new IllegalArgumentException("'" + absoluteUri + "' is not an absolute URL"); + } return call(() -> { - Response response = targetResolver.apply(httpClient.signatureServiceRoot()).request().accept(acceptedResponses).get(); - InputStream inputStream = parseResponse(response, InputStream.class); - return new ResponseInputStream(inputStream, response.getLength()); + HeaderGroup acceptHeader = new HeaderGroup(); + for (ContentType acceptedType : acceptedResponses) { + acceptHeader.addHeader(new BasicHeader(ACCEPT, acceptedType.getMimeType())); + } + + ClassicHttpRequest request = ClassicRequestBuilder.get(absoluteUri) + .addHeader(acceptHeader.getCondensedHeader(ACCEPT)) + .build(); + + ClassicHttpResponse response = null; + try { + response = httpClient.httpClient().execute(null, request); + StatusCode statusCode = StatusCode.from(response.getCode()); + if (!statusCode.is(SUCCESSFUL)) { + throw exceptionForGeneralError(response); + } + return new ResponseInputStream(response.getEntity().getContent(), response.getEntity().getContentLength()); + } catch (Exception e) { + if (response != null) { + try { + response.close(); + } catch (IOException closingException) { + e.addSuppressed(closingException); + } + } + throw e instanceof RuntimeException + ? (RuntimeException) e + : new RuntimeException(request + ": " + e.getClass().getSimpleName() + " '" + e.getMessage() + "'", e); + } }); } public void cancel(final Cancellable cancellable) { call(() -> { if (cancellable.getCancellationUrl() != null) { - URI url = cancellable.getCancellationUrl().getUrl(); - try (Response response = postEmptyEntity(url)) { - ResponseStatus.resolve(response.getStatus()) - .throwIf(CONFLICT, status -> new JobCannotBeCancelledException(status, extractError(response))) - .expect(SUCCESSFUL) - .orThrow(status -> exceptionForGeneralError(response)); + try(ClassicHttpResponse response = postEmptyEntity(cancellable.getCancellationUrl().getUrl())) { + ResponseStatus.resolve(response.getCode()) + .throwIf(HttpStatus.SC_CONFLICT, status -> new JobCannotBeCancelledException(status, extractError(response))) + .expect(StatusCodeFamily.SUCCESSFUL) + .orThrow(status -> exceptionForGeneralError(response)); + } catch (IOException e) { + throw new UncheckedIOException(e); } } else { throw new NotCancellableException(); @@ -175,22 +245,28 @@ public JobStatusResponse getDirectStatusCha private JobStatusResponse getStatusChange(final Optional sender, final Target target, final Class responseClass) { return call(() -> { + Sender actualSender = getActualSender(sender, globalSender); - Invocation.Builder request = httpClient.signatureServiceRoot().path(target.path(actualSender)) - .queryParam(POLLING_QUEUE_QUERY_PARAMETER, actualSender.getPollingQueue().value) - .request() - .accept(APPLICATION_XML_TYPE); - try (Response response = request.get()) { - StatusType status = ResponseStatus.resolve(response.getStatus()) - .throwIf(TOO_MANY_REQUESTS, s -> new TooEagerPollingException()) - .expect(SUCCESSFUL).orThrow(unexpectedStatus -> exceptionForGeneralError(response)); - return new JobStatusResponse<>(status == NO_CONTENT ? null : response.readEntity(responseClass), getNextPermittedPollTime(response)); + URI jobStatusUrl = httpClient.constructUrl(uri -> uri + .appendPath(target.path(actualSender)) + .addParameter(POLLING_QUEUE_QUERY_PARAMETER, actualSender.getPollingQueue().value)); + + try { + ClassicHttpRequest get = ClassicRequestBuilder.get(jobStatusUrl).addHeader(ACCEPT, APPLICATION_XML.getMimeType()).build(); + return httpClient.httpClient().execute(get, response -> { + StatusCode status = ResponseStatus.resolve(response.getCode()) + .throwIf(HttpStatus.SC_TOO_MANY_REQUESTS, s -> new TooEagerPollingException()) + .expect(StatusCodeFamily.SUCCESSFUL).orThrow(unexpectedStatus -> exceptionForGeneralError(response)); + return new JobStatusResponse<>(status.value() == HttpStatus.SC_NO_CONTENT ? null : Marshalling.unmarshal(response.getEntity().getContent(), responseClass), getNextPermittedPollTime(response)); + }); + } catch (IOException e) { + throw new UncheckedIOException(e); } }); } - private static Instant getNextPermittedPollTime(Response response) { - return ZonedDateTime.parse(response.getHeaderString(NEXT_PERMITTED_POLL_TIME_HEADER), ISO_DATE_TIME).toInstant(); + private static Instant getNextPermittedPollTime(ClassicHttpResponse response) throws ProtocolException { + return ZonedDateTime.parse(response.getHeader(NEXT_PERMITTED_POLL_TIME_HEADER).getValue(), ISO_DATE_TIME).toInstant(); } public void confirm(final Confirmable confirmable) { @@ -198,8 +274,10 @@ public void confirm(final Confirmable confirmable) { if (confirmable.getConfirmationReference() != null) { URI url = confirmable.getConfirmationReference().getConfirmationUrl(); LOG.info("Sends confirmation for '{}' to URL {}", confirmable, url); - try (Response response = postEmptyEntity(url)) { - ResponseStatus.resolve(response.getStatus()).expect(SUCCESSFUL).orThrow(status -> exceptionForGeneralError(response)); + try (ClassicHttpResponse response = postEmptyEntity(url)) { + ResponseStatus.resolve(response.getCode()).expect(StatusCodeFamily.SUCCESSFUL).orThrow(status -> exceptionForGeneralError(response)); + } catch (IOException e) { + throw new RuntimeException(e); } } else { LOG.info("Does not need to send confirmation for '{}'", confirmable); @@ -219,8 +297,10 @@ public void deleteDocuments(DeleteDocumentsUrl deleteDocumentsUrl) { call(() -> { if (deleteDocumentsUrl != null) { URI url = deleteDocumentsUrl.getUrl(); - try (Response response = delete(url)) { - ResponseStatus.resolve(response.getStatus()).expect(SUCCESSFUL).orThrow(status -> exceptionForGeneralError(response)); + try (ClassicHttpResponse response = delete(url)) { + ResponseStatus.resolve(response.getCode()).expect(StatusCodeFamily.SUCCESSFUL).orThrow(status -> exceptionForGeneralError(response)); + } catch (IOException e) { + throw new UncheckedIOException(e); } } else { throw new DocumentsNotDeletableException(); @@ -228,90 +308,80 @@ public void deleteDocuments(DeleteDocumentsUrl deleteDocumentsUrl) { }); } + private ClassicHttpResponse postEmptyEntity(URI uri) { + try { + ClassicHttpRequest request = ClassicRequestBuilder + .post(uri) + .addHeader(ACCEPT, APPLICATION_XML.getMimeType()) + .build(); - private class UsingBodyParts { - - private final List parts; - - UsingBodyParts(BodyPart... parts) { - this.parts = Arrays.asList(parts); + return httpClient.httpClient().execute(null, request); + } catch (IOException e) { + throw new UncheckedIOException(e); } - - T postAsMultiPart(String path, Class responseType) { - try (MultiPart multiPart = new MultiPart()) { - for (BodyPart bodyPart : parts) { - multiPart.bodyPart(bodyPart); - } - - Invocation.Builder request = httpClient.signatureServiceRoot().path(path) - .request() - .header(CONTENT_TYPE, multiPart.getMediaType()) - .accept(APPLICATION_XML_TYPE); - try (Response response = request.post(Entity.entity(multiPart, multiPart.getMediaType()))) { - return parseResponse(response, responseType); - } - } catch (IOException e) { - throw new RuntimeIOException(e); - } - } - } - - private Response postEmptyEntity(URI uri) { - return postEntity(uri, null); - } - - private Response postEntity(URI uri, Object entity) { - Invocation.Builder requestBuilder = httpClient.target(uri) - .request() - .accept(APPLICATION_XML_TYPE); - return (entity == null ? requestBuilder.header(CONTENT_LENGTH, 0) : requestBuilder) - .post(Entity.entity(entity, APPLICATION_XML_TYPE)); } + private ClassicHttpResponse delete(URI uri) { + try { + ClassicHttpRequest request = ClassicRequestBuilder + .delete(uri) + .addHeader(ACCEPT, APPLICATION_XML.getMimeType()) + .build(); - private Response delete(URI uri) { - return httpClient.target(uri) - .request() - .accept(APPLICATION_XML_TYPE) - .delete(); + return httpClient.httpClient().execute(null, request); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } - private static T parseResponse(Response response, Class responseType) { - ResponseStatus.resolve(response.getStatus()).expect(SUCCESSFUL).orThrow(unexpectedStatus -> exceptionForGeneralError(response)); - return response.readEntity(responseType); + private static T parseResponse(ClassicHttpResponse response, Class responseType) { + ResponseStatus.resolve(response.getCode()).expect(StatusCodeFamily.SUCCESSFUL).orThrow(unexpectedStatus -> exceptionForGeneralError(response)); + try(InputStream body = response.getEntity().getContent()) { + return Marshalling.unmarshal(body, responseType); + } catch (IOException e) { + throw new UncheckedIOException("Could not parse response.", e); + } } - private static SignatureException exceptionForGeneralError(Response response) { + private static SignatureException exceptionForGeneralError(ClassicHttpResponse response) { XMLError error = extractError(response); if (BROKER_NOT_AUTHORIZED.sameAs(error.getErrorCode())) { return new BrokerNotAuthorizedException(error); } - return new UnexpectedResponseException(error, ResponseStatus.resolve(response.getStatus()).get(), OK); + return new UnexpectedResponseException(error, ResponseStatus.resolve(response.getCode()).get(), StatusCode.from(HttpStatus.SC_OK)); } - private static XMLError extractError(Response response) { - XMLError error; - Optional responseContentType = Optional.ofNullable(response.getHeaderString(HttpHeaders.CONTENT_TYPE)); - if (responseContentType.isPresent() && MediaType.valueOf(responseContentType.get()).equals(APPLICATION_XML_TYPE)) { - try { - response.bufferEntity(); - error = response.readEntity(XMLError.class); - } catch (Exception e) { + private static XMLError extractError(ClassicHttpResponse response) { + try { + XMLError error; + Optional contentType = Optional.ofNullable(response.getHeader(CONTENT_TYPE)).map(NameValuePair::getValue).map(ContentType::parse); + if (contentType.filter(APPLICATION_XML::isSameMimeType).isPresent()) { + try(InputStream body = response.getEntity().getContent()) { + error = Marshalling.unmarshal(body, XMLError.class); + } catch (IOException e) { + throw new UncheckedIOException("Could not extract error from body.", e); + } + } else { + String errorAsString; + try(InputStream body = response.getEntity().getContent()) { + ByteArrayOutputStream result = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + for (int length; (length = body.read(buffer)) != -1; ) { + result.write(buffer, 0, length); + } + errorAsString = result.toString(StandardCharsets.UTF_8.name()); + } catch (IOException e) { + throw new UncheckedIOException("Could not read body as string.", e); + } throw new UnexpectedResponseException( - HttpHeaders.CONTENT_TYPE + " " + responseContentType.orElse("unknown") + ": " + - Optional.ofNullable(response.readEntity(String.class)).filter(StringUtils::isNoneBlank).orElse(""), - e, ResponseStatus.resolve(response.getStatus()).get(), OK); + HttpHeaders.CONTENT_TYPE + " " + contentType.map(ContentType::getMimeType).orElse("unknown") + ": " + + Optional.ofNullable(errorAsString).filter(StringUtils::isNoneBlank).orElse(""), + ResponseStatus.resolve(response.getCode()).get(), StatusCode.from(HttpStatus.SC_OK)); } - } else { - throw new UnexpectedResponseException( - HttpHeaders.CONTENT_TYPE + " " + responseContentType.orElse("unknown") + ": " + - Optional.ofNullable(response.readEntity(String.class)).filter(StringUtils::isNoneBlank).orElse(""), - ResponseStatus.resolve(response.getStatus()).get(), OK); - } - if (error == null) { - throw new UnexpectedResponseException(null, ResponseStatus.resolve(response.getStatus()).get(), OK); + return error; + } catch (ProtocolException e) { + throw new RuntimeException(e); } - return error; } } diff --git a/src/main/java/no/digipost/signature/client/core/internal/http/AddRequestHeaderFilter.java b/src/main/java/no/digipost/signature/client/core/internal/http/AddRequestHeaderFilter.java deleted file mode 100644 index 8132be46..00000000 --- a/src/main/java/no/digipost/signature/client/core/internal/http/AddRequestHeaderFilter.java +++ /dev/null @@ -1,27 +0,0 @@ -package no.digipost.signature.client.core.internal.http; - -import javax.annotation.Priority; -import javax.ws.rs.client.ClientRequestContext; -import javax.ws.rs.client.ClientRequestFilter; -import java.io.IOException; - -import static javax.ws.rs.Priorities.HEADER_DECORATOR; - - -@Priority(HEADER_DECORATOR) -public class AddRequestHeaderFilter implements ClientRequestFilter { - - private final String headerName; - private final String value; - - public AddRequestHeaderFilter(String headerName, String value) { - this.headerName = headerName; - this.value = value; - } - - @Override - public void filter(ClientRequestContext clientRequestContext) throws IOException { - clientRequestContext.getHeaders().add(headerName, value); - } - -} diff --git a/src/main/java/no/digipost/signature/client/core/internal/http/HttpIntegrationConfiguration.java b/src/main/java/no/digipost/signature/client/core/internal/http/HttpIntegrationConfiguration.java index e13a9af7..cca0b88d 100644 --- a/src/main/java/no/digipost/signature/client/core/internal/http/HttpIntegrationConfiguration.java +++ b/src/main/java/no/digipost/signature/client/core/internal/http/HttpIntegrationConfiguration.java @@ -1,17 +1,12 @@ package no.digipost.signature.client.core.internal.http; -import javax.net.ssl.SSLContext; -import javax.ws.rs.core.Configuration; +import org.apache.hc.client5.http.classic.HttpClient; import java.net.URI; public interface HttpIntegrationConfiguration { - String PRE_INIT_CLIENT = "no.posten.signering.client.preInit"; - - Configuration getJaxrsConfiguration(); - - SSLContext getSSLContext(); + HttpClient httpClient(); URI getServiceRoot(); diff --git a/src/main/java/no/digipost/signature/client/core/internal/http/ResponseStatus.java b/src/main/java/no/digipost/signature/client/core/internal/http/ResponseStatus.java index fb30e0ce..95d9c1f9 100644 --- a/src/main/java/no/digipost/signature/client/core/internal/http/ResponseStatus.java +++ b/src/main/java/no/digipost/signature/client/core/internal/http/ResponseStatus.java @@ -2,11 +2,6 @@ import no.digipost.signature.client.core.exceptions.UnexpectedResponseException; -import javax.ws.rs.core.Response.Status; -import javax.ws.rs.core.Response.Status.Family; -import javax.ws.rs.core.Response.StatusType; - -import java.util.Objects; import java.util.function.BiPredicate; import java.util.function.Function; import java.util.function.Predicate; @@ -14,37 +9,30 @@ public final class ResponseStatus { - public static ResponseStatus resolve(int code) { - StatusType status = Status.fromStatusCode(code); - if (status == null) { - status = Custom.fromStatusCode(code); - } - if (status == null) { - status = unknown(code); - } - return new ResponseStatus(status, s -> true); + public static ResponseStatus resolve(int statusCode) { + return new ResponseStatus(new StatusCode(statusCode), s -> true); } - private final StatusType status; - private final Predicate statusExpectation; + private final StatusCode statusCode; + private final Predicate statusExpectation; - private ResponseStatus(StatusType status, Predicate expectation) { - this.status = status; + private ResponseStatus(StatusCode statusCode, Predicate expectation) { + this.statusCode = statusCode; this.statusExpectation = expectation; } - public ResponseStatus expect(Status.Family expectedStatusFamily) { - return expect(s -> s.getFamily() == expectedStatusFamily); + public ResponseStatus expect(StatusCodeFamily expectedStatusFamily) { + return expect(s -> s.is(expectedStatusFamily)); } - public ResponseStatus expectOneOf(Status.Family ... expectedStatusFamilies) { - return expectOneOf(Stream.of(expectedStatusFamilies), (family, status) -> status.getFamily() == family); + public ResponseStatus expectOneOf(StatusCodeFamily ... expectedStatusFamilies) { + return expectOneOf(Stream.of(expectedStatusFamilies), (family, statusCode) -> statusCode.is(family)); } - private ResponseStatus expectOneOf(Stream expecteds, BiPredicate expectedEvaluator) { - Predicate oneOfExpectedsAndAlsoExistingExpectation = + private ResponseStatus expectOneOf(Stream expecteds, BiPredicate expectedEvaluator) { + Predicate oneOfExpectedsAndAlsoExistingExpectation = expecteds - .map(expected -> (Predicate) status -> expectedEvaluator.test(expected, status)) + .map(expected -> (Predicate) status -> expectedEvaluator.test(expected, status)) .reduce(Predicate::or) .map(statusExpectation::and) .orElse(statusExpectation); @@ -52,153 +40,35 @@ private ResponseStatus expectOneOf(Stream expecteds, BiPredicate expectation) { - return new ResponseStatus(status, this.statusExpectation.and(expectation)); + public ResponseStatus expect(Predicate expectation) { + return new ResponseStatus(statusCode, this.statusExpectation.and(expectation)); } - public ResponseStatus throwIf(Status status, Function exceptionSupplier) throws X { - return throwIf(s -> s.equals(status), exceptionSupplier); + public ResponseStatus throwIf(int status, Function exceptionSupplier) throws X { + return throwIf(s -> s.equals(StatusCode.from(status)), exceptionSupplier); } - public ResponseStatus throwIf(Predicate illegalStatus, Function exceptionSupplier) throws X { - if (illegalStatus.test(status)) { - throw exceptionSupplier.apply(status); + public ResponseStatus throwIf(Predicate illegalStatus, Function exceptionSupplier) throws X { + if (illegalStatus.test(statusCode)) { + throw exceptionSupplier.apply(statusCode); } else { return this; } } - public StatusType get() { - return orThrow(s -> new UnexpectedResponseException(status)); + public StatusCode get() { + return orThrow(s -> new UnexpectedResponseException(statusCode)); } - public StatusType orThrow(Function exceptionSuppplier) throws X { - return throwIf(statusExpectation.negate(), exceptionSuppplier).status; + public StatusCode orThrow(Function exceptionSupplier) throws X { + return throwIf(statusExpectation.negate(), exceptionSupplier).statusCode; } @Override public String toString() { - return status.toString() + (statusExpectation.test(status) ? "" : " (unexpected)"); - } - - - - - /** - * Status codes not part of the JAX-RS {@link Status} enum. - */ - public enum Custom implements StatusType { - - /** - * 422 Unprocessable Entity, see - * https://tools.ietf.org/html/rfc4918#section-11.2 - */ - UNPROCESSABLE_ENTITY(422, "Unprocessable Entity"), - - ; - - /** - * Convert a numerical status code into the corresponding CustomStatus. - * - * @param code the numerical status code. - * @return the matching Status or null is no matching Status is defined. - */ - public static ResponseStatus.Custom fromStatusCode(int code) { - for (Custom s : Custom.values()) { - if (s.code == code) { - return s; - } - } - return null; - } - - - - private int code; - private String reason; - private Family family; - - Custom(int code, String reasonPhrase) { - this.code = code; - this.reason = reasonPhrase; - this.family = Family.familyOf(code); - } - - @Override - public int getStatusCode() { - return code; - } - - @Override - public Family getFamily() { - return family; - } - - @Override - public String getReasonPhrase() { - return reason; - } - - @Override - public String toString() { - return code + " " + reason; - } - } - - - - static StatusType unknown(int code) { - return new Unknown(code); - } - - public static final class Unknown implements StatusType { - - final int code; - final Family family; - final String reason; - - private Unknown(int code) { - this.code = code; - this.family = Family.familyOf(code); - this.reason = "(" + family + ", unrecognized status code)"; - } - - - @Override - public int getStatusCode() { - return code; - } - - @Override - public String getReasonPhrase() { - return reason; - } - - @Override - public Family getFamily() { - return family; - } - - @Override - public String toString() { - return code + " " + reason; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof ResponseStatus.Unknown) { - ResponseStatus.Unknown that = (ResponseStatus.Unknown) obj; - return Objects.equals(this.code, that.code); - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(code); - } + return statusCode.toString() + (statusExpectation.test(statusCode) ? "" : " (unexpected)"); } } diff --git a/src/main/java/no/digipost/signature/client/core/internal/http/SignatureApiTrustStrategy.java b/src/main/java/no/digipost/signature/client/core/internal/http/SignatureApiTrustStrategy.java index a3af6747..3ce6501a 100644 --- a/src/main/java/no/digipost/signature/client/core/internal/http/SignatureApiTrustStrategy.java +++ b/src/main/java/no/digipost/signature/client/core/internal/http/SignatureApiTrustStrategy.java @@ -3,7 +3,7 @@ import no.digipost.signature.client.core.exceptions.SecurityException; import no.digipost.signature.client.security.CertificateChainValidation; import no.digipost.signature.client.security.CertificateChainValidation.Result; -import org.apache.http.conn.ssl.TrustStrategy; +import org.apache.hc.core5.ssl.TrustStrategy; import java.security.cert.X509Certificate; @@ -35,7 +35,7 @@ public boolean isTrusted(X509Certificate[] chain, String authType) { case TRUSTED_AND_SKIP_FURTHER_VALIDATION: return true; case TRUSTED: return false; case UNTRUSTED: default: - String subjectDN = chain[0].getSubjectDN().getName(); + String subjectDN = chain[0].getSubjectX500Principal().getName(); throw new SecurityException( "Untrusted server certificate, according to " + certificateChainValidation + ". " + "Make sure the server URI is correct. Actual certificate: " + subjectDN + ". " + diff --git a/src/main/java/no/digipost/signature/client/core/internal/http/SignatureHttpClient.java b/src/main/java/no/digipost/signature/client/core/internal/http/SignatureHttpClient.java index 3a6761cc..618e8836 100644 --- a/src/main/java/no/digipost/signature/client/core/internal/http/SignatureHttpClient.java +++ b/src/main/java/no/digipost/signature/client/core/internal/http/SignatureHttpClient.java @@ -1,13 +1,28 @@ package no.digipost.signature.client.core.internal.http; -import javax.ws.rs.client.WebTarget; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.core5.net.URIBuilder; import java.net.URI; +import java.net.URISyntaxException; +import java.util.function.UnaryOperator; public interface SignatureHttpClient { - WebTarget signatureServiceRoot(); + URI signatureServiceRoot(); - WebTarget target(URI uri); + HttpClient httpClient(); + + default URI constructUrl(UnaryOperator uri) { + URI serviceRoot = signatureServiceRoot(); + URIBuilder uriBuilder = uri.apply(new URIBuilder()); + try { + return uriBuilder.build(); + } catch (URISyntaxException e) { + throw new IllegalStateException( + "Invalid URL constructed for service at " + serviceRoot + ": " + uriBuilder + ". " + + "Reason: " + e.getClass().getSimpleName() + " '" + e.getMessage() + "'", e); + } + } } diff --git a/src/main/java/no/digipost/signature/client/core/internal/http/SignatureHttpClientFactory.java b/src/main/java/no/digipost/signature/client/core/internal/http/SignatureHttpClientFactory.java index e0a638ae..7b017caa 100644 --- a/src/main/java/no/digipost/signature/client/core/internal/http/SignatureHttpClientFactory.java +++ b/src/main/java/no/digipost/signature/client/core/internal/http/SignatureHttpClientFactory.java @@ -1,76 +1,34 @@ package no.digipost.signature.client.core.internal.http; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.glassfish.jersey.client.JerseyClient; -import org.glassfish.jersey.client.JerseyClientBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.ws.rs.client.Client; -import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.Configuration; +import org.apache.hc.client5.http.classic.HttpClient; import java.net.URI; -import static java.lang.Boolean.parseBoolean; -import static no.digipost.signature.client.core.internal.http.HttpIntegrationConfiguration.PRE_INIT_CLIENT; - public class SignatureHttpClientFactory { - private static final Logger LOG = LoggerFactory.getLogger(SignatureHttpClientFactory.class); - public static SignatureHttpClient create(HttpIntegrationConfiguration config) { - Configuration jaxrsConfiguration = config.getJaxrsConfiguration(); - JerseyClientBuilder jerseyBuilder = (JerseyClientBuilder) JerseyClientBuilder.newBuilder(); - JerseyClient jerseyClient = jerseyBuilder - .withConfig(jaxrsConfiguration) - .sslContext(config.getSSLContext()) - .hostnameVerifier(NoopHostnameVerifier.INSTANCE) - .build(); - - Object configuredPreInit = jaxrsConfiguration.getProperty(PRE_INIT_CLIENT); - if (configuredPreInit == null || parseBoolean(String.valueOf(configuredPreInit))) { - try { - jerseyClient.preInitialize(); - } catch (Exception e) { - throw new IllegalStateException( - "Unable to pre-initialize Jersey Client, because " + e.getClass().getSimpleName() + " '" + e.getMessage() + "'. " + - "This step is taken to ensure everything the client needs is available already on instantiation, " + - "in particular the InjectionManager facilities used internally by Jersey. By default, the Signature API Client " + - "should include the Jersey HK2 implementation, but if you need to control this yourself, consider excluding " + - "org.glassfish.jersey.inject:jersey-hk2 when depending on the signature-api-client-java, and make sure to make the " + - "InjectionManagerFactory of your choice discoverable by Jersey.", e); - } - } else { - LOG.warn( - "Pre-initializing the Signature API Jersey Client is disabled, because of the property " + - PRE_INIT_CLIENT + "=" + configuredPreInit + " is set in the JAX-RS configuration. " + - "There is a chance that requests done by Jersey Client will break, and we don't yet know about it. " + - "If this is intended configured behavior, this warning may be ignored."); - } - return new DefaultClient(jerseyClient, config.getServiceRoot()); + return new DefaultClient(config.httpClient(), config.getServiceRoot()); } private static final class DefaultClient implements SignatureHttpClient { - private final Client jerseyClient; - private final WebTarget signatureServiceRoot; + private final HttpClient httpClient; + private final URI signatureServiceRoot; - DefaultClient(Client jerseyClient, URI root) { - this.jerseyClient = jerseyClient; - this.signatureServiceRoot = jerseyClient.target(root); + DefaultClient(HttpClient httpClient, URI root) { + this.httpClient = httpClient; + this.signatureServiceRoot = root; } @Override - public WebTarget target(URI uri) { - return jerseyClient.target(uri); + public URI signatureServiceRoot() { + return signatureServiceRoot; } - @Override - public WebTarget signatureServiceRoot() { - return signatureServiceRoot; + public HttpClient httpClient() { + return httpClient; } } diff --git a/src/main/java/no/digipost/signature/client/core/internal/http/StatusCode.java b/src/main/java/no/digipost/signature/client/core/internal/http/StatusCode.java new file mode 100644 index 00000000..d7d4409c --- /dev/null +++ b/src/main/java/no/digipost/signature/client/core/internal/http/StatusCode.java @@ -0,0 +1,53 @@ +package no.digipost.signature.client.core.internal.http; + +import java.util.EnumSet; +import java.util.Set; + +public final class StatusCode { + + private final int value; + + public static StatusCode from(int value) { + return new StatusCode(value); + } + + public StatusCode(int value) { + this.value = value; + } + + public boolean is(StatusCodeFamily family) { + return this.family() == family; + } + + public boolean isOneOf(StatusCodeFamily first, StatusCodeFamily ... rest) { + return isOneOf(EnumSet.of(first, rest)); + } + + public boolean isOneOf(Set families) { + return families.contains(this.family()); + } + + public StatusCodeFamily family() { + return StatusCodeFamily.of(value); + } + + public int value() { + return this.value; + } + + @Override + public String toString() { + return "status code " + value + " (" + family() + ")"; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof StatusCode && this.value == ((StatusCode) obj).value; + } + + @Override + public int hashCode() { + return Integer.hashCode(value); + } + +} diff --git a/src/main/java/no/digipost/signature/client/core/internal/http/StatusCodeFamily.java b/src/main/java/no/digipost/signature/client/core/internal/http/StatusCodeFamily.java new file mode 100644 index 00000000..be3dc0e5 --- /dev/null +++ b/src/main/java/no/digipost/signature/client/core/internal/http/StatusCodeFamily.java @@ -0,0 +1,22 @@ +package no.digipost.signature.client.core.internal.http; + +import org.apache.hc.core5.http.message.StatusLine; + +public enum StatusCodeFamily { + INFORMATIONAL, SUCCESSFUL, REDIRECTION, CLIENT_ERROR, SERVER_ERROR, OTHER; + + public static StatusCodeFamily of(StatusLine apacheHttpClientStatus) { + return of(apacheHttpClientStatus.getStatusCode()); + } + + public static StatusCodeFamily of(int statusCode) { + switch (statusCode / 100) { + case 1: return INFORMATIONAL; + case 2: return SUCCESSFUL; + case 3: return REDIRECTION; + case 4: return CLIENT_ERROR; + case 5: return SERVER_ERROR; + default: return OTHER; + } + } +} diff --git a/src/main/java/no/digipost/signature/client/core/internal/xml/JaxbMessageReaderWriterProvider.java b/src/main/java/no/digipost/signature/client/core/internal/xml/JaxbMessageReaderWriterProvider.java deleted file mode 100644 index 10f7dce6..00000000 --- a/src/main/java/no/digipost/signature/client/core/internal/xml/JaxbMessageReaderWriterProvider.java +++ /dev/null @@ -1,44 +0,0 @@ -package no.digipost.signature.client.core.internal.xml; - -import org.glassfish.jersey.message.internal.AbstractMessageReaderWriterProvider; - -import javax.ws.rs.Consumes; -import javax.ws.rs.Produces; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.ext.Provider; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.annotation.Annotation; -import java.lang.reflect.Type; - -import static no.digipost.signature.client.core.internal.xml.Marshalling.marshal; -import static no.digipost.signature.client.core.internal.xml.Marshalling.unmarshal; - -@Provider -@Produces(MediaType.APPLICATION_XML) -@Consumes(MediaType.APPLICATION_XML) -public class JaxbMessageReaderWriterProvider extends AbstractMessageReaderWriterProvider { - - @Override - public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { - return mediaType.isCompatible(MediaType.APPLICATION_XML_TYPE); - } - - @Override - public Object readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { - return unmarshal(entityStream); - } - - @Override - public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { - return mediaType.isCompatible(MediaType.APPLICATION_XML_TYPE); - } - - @Override - public void writeTo(Object o, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { - marshal(o, entityStream); - } -} diff --git a/src/main/java/no/digipost/signature/client/core/internal/xml/Marshalling.java b/src/main/java/no/digipost/signature/client/core/internal/xml/Marshalling.java index 81d33bc2..4ece53f4 100644 --- a/src/main/java/no/digipost/signature/client/core/internal/xml/Marshalling.java +++ b/src/main/java/no/digipost/signature/client/core/internal/xml/Marshalling.java @@ -1,20 +1,18 @@ package no.digipost.signature.client.core.internal.xml; -import no.digipost.signature.jaxb.spring.SignatureJaxb2Marshaller; +import no.digipost.signature.jaxb.JaxbMarshaller; -import javax.xml.transform.stream.StreamResult; -import javax.xml.transform.stream.StreamSource; import java.io.InputStream; import java.io.OutputStream; public final class Marshalling { public static void marshal(Object object, OutputStream entityStream) { - SignatureJaxb2Marshaller.ForRequestsOfAllApis.singleton().marshal(object, new StreamResult(entityStream)); + JaxbMarshaller.ForRequestsOfAllApis.singleton().marshal(object, entityStream); } - public static Object unmarshal(InputStream entityStream) { - return SignatureJaxb2Marshaller.ForResponsesOfAllApis.singleton().unmarshal(new StreamSource(entityStream)); + public static T unmarshal(InputStream entityStream, Class type) { + return JaxbMarshaller.ForResponsesOfAllApis.singleton().unmarshal(entityStream, type); } private Marshalling() { } diff --git a/src/main/java/no/digipost/signature/client/direct/DirectClient.java b/src/main/java/no/digipost/signature/client/direct/DirectClient.java index fef8dae7..c42f4a5f 100644 --- a/src/main/java/no/digipost/signature/client/direct/DirectClient.java +++ b/src/main/java/no/digipost/signature/client/direct/DirectClient.java @@ -20,11 +20,11 @@ import java.util.Optional; -import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE; -import static javax.ws.rs.core.MediaType.APPLICATION_XML_TYPE; import static no.digipost.signature.client.direct.DirectJobStatusResponse.noUpdatedStatus; import static no.digipost.signature.client.direct.JaxbEntityMapping.fromJaxb; import static no.digipost.signature.client.direct.JaxbEntityMapping.toJaxb; +import static org.apache.hc.core5.http.ContentType.APPLICATION_OCTET_STREAM; +import static org.apache.hc.core5.http.ContentType.APPLICATION_XML; public class DirectClient { @@ -132,11 +132,11 @@ public void confirm(DirectJobStatusResponse receivedStatusResponse) { public ResponseInputStream getXAdES(XAdESReference xAdESReference) { - return client.getDataStream(xAdESReference.getxAdESUrl(), APPLICATION_XML_TYPE); + return client.getDataStream(xAdESReference.getxAdESUrl(), APPLICATION_XML); } public ResponseInputStream getPAdES(PAdESReference pAdESReference) { - return client.getDataStream(pAdESReference.getpAdESUrl(), APPLICATION_OCTET_STREAM_TYPE, APPLICATION_XML_TYPE); + return client.getDataStream(pAdESReference.getpAdESUrl(), APPLICATION_OCTET_STREAM, APPLICATION_XML); } public void deleteDocuments(DeleteDocumentsUrl deleteDocumentsUrl) { diff --git a/src/main/java/no/digipost/signature/client/direct/StatusReference.java b/src/main/java/no/digipost/signature/client/direct/StatusReference.java index ccb78ede..4574432e 100644 --- a/src/main/java/no/digipost/signature/client/direct/StatusReference.java +++ b/src/main/java/no/digipost/signature/client/direct/StatusReference.java @@ -1,8 +1,9 @@ package no.digipost.signature.client.direct; -import javax.ws.rs.core.UriBuilder; +import org.apache.hc.core5.net.URIBuilder; import java.net.URI; +import java.net.URISyntaxException; import java.util.Objects; /** * A {@code StatusReference} is constructed from the url acquired from @@ -54,7 +55,12 @@ private StatusReference(URI statusUrl, String statusQueryToken) { } public URI getStatusUrl() { - return UriBuilder.fromUri(statusUrl).queryParam(STATUS_QUERY_TOKEN_PARAM_NAME, statusQueryToken).build(); + URIBuilder uriBuilder = new URIBuilder(statusUrl).addParameter(STATUS_QUERY_TOKEN_PARAM_NAME, statusQueryToken); + try { + return uriBuilder.build(); + } catch (URISyntaxException e) { + throw new IllegalStateException("Unable to create valid URL from " + uriBuilder, e); + } } public static abstract class StatusUrlContruction { diff --git a/src/main/java/no/digipost/signature/client/portal/PortalClient.java b/src/main/java/no/digipost/signature/client/portal/PortalClient.java index 84202baa..5fd0eca5 100644 --- a/src/main/java/no/digipost/signature/client/portal/PortalClient.java +++ b/src/main/java/no/digipost/signature/client/portal/PortalClient.java @@ -20,11 +20,11 @@ import java.util.Optional; -import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE; -import static javax.ws.rs.core.MediaType.APPLICATION_XML_TYPE; import static no.digipost.signature.client.portal.JaxbEntityMapping.fromJaxb; import static no.digipost.signature.client.portal.JaxbEntityMapping.toJaxb; import static no.digipost.signature.client.portal.PortalJobStatusChanged.noUpdatedStatus; +import static org.apache.hc.core5.http.ContentType.APPLICATION_OCTET_STREAM; +import static org.apache.hc.core5.http.ContentType.APPLICATION_XML; public class PortalClient { @@ -104,12 +104,12 @@ public void cancel(Cancellable cancellable) { public ResponseInputStream getXAdES(XAdESReference xAdESReference) { - return client.getDataStream(xAdESReference.getxAdESUrl(), APPLICATION_XML_TYPE); + return client.getDataStream(xAdESReference.getxAdESUrl(), APPLICATION_XML); } public ResponseInputStream getPAdES(PAdESReference pAdESReference) { - return client.getDataStream(pAdESReference.getpAdESUrl(), APPLICATION_OCTET_STREAM_TYPE, APPLICATION_XML_TYPE); + return client.getDataStream(pAdESReference.getpAdESUrl(), APPLICATION_OCTET_STREAM, APPLICATION_XML); } public void deleteDocuments(DeleteDocumentsUrl deleteDocumentsUrl) { diff --git a/src/main/java/no/digipost/signature/client/portal/PortalJobStatusChanged.java b/src/main/java/no/digipost/signature/client/portal/PortalJobStatusChanged.java index 90d6c674..4552ab00 100644 --- a/src/main/java/no/digipost/signature/client/portal/PortalJobStatusChanged.java +++ b/src/main/java/no/digipost/signature/client/portal/PortalJobStatusChanged.java @@ -16,7 +16,7 @@ * Indicates a job which has got a new {@link PortalJobStatus status} * since the last time its status was queried. * - *

Confirmation

+ *

Confirmation

* * When the client {@link Confirmable confirms} this, the job and its associated * resources will become unavailable through the Signature API. 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 918d6842..69c8713b 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 @@ -15,8 +15,10 @@ import no.digipost.signature.client.security.KeyStoreConfig; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.oxm.jaxb.Jaxb2Marshaller; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; import javax.xml.transform.stream.StreamSource; import java.io.ByteArrayInputStream; @@ -46,9 +48,12 @@ public class CreateSignatureTest { private KeyStoreConfig noekkelpar; private List files; - private static final Jaxb2Marshaller marshaller; static { - marshaller = new Jaxb2Marshaller(); - marshaller.setClassesToBeBound(XAdESSignatures.class, QualifyingProperties.class); + private static final Unmarshaller unmarshaller; static { + try { + unmarshaller = JAXBContext.newInstance(XAdESSignatures.class, QualifyingProperties.class).createUnmarshaller(); + } catch (JAXBException e) { + throw new RuntimeException(e); + } } @BeforeEach @@ -64,19 +69,19 @@ public void setUp() { } @Test - public void test_generated_signatures() { + public void test_generated_signatures() throws JAXBException { /* * Expected signature value (Base-64 encoded) from the given keys, files, and time of the signing. * If this changes, something has changed in the signature implementation, and must be investigated! */ final String expectedBase64EncodedSignatureValue = - "GELdAv4Dc+w5RnN3Hd2Fk9KjRh8LyNaiLM8PZLvFriuahHoZcb3OBTdCJhaxoup1tgmOUspMGgJ7Zgl6/hh" + - "G+zuZ0UjT521mqH4PrupH464B9ztBFnPScedvJpSAwROqSn7eCG78J+ittKBhKxPkBmfStaGPxnvqm2reug" + - "I3vbFem+KDiFU+Q4T26OWXapLQC2fhEttH/pUYND1PZN8pNaMiKgzaG7aHurMQCoB5qxfKEL9YEe4pF7H0T" + - "PAdNndCACJiWrkHNQ5gTJ+UWx8y2kuzZHEGGTJ+ip9KpCRohDfLapQAMTh0zMLrUNbYpq6kiYWrlxTNfdcVm4skBY0j9Q=="; + "L3HFN44OGUEbK5p9zxbnDZrey+UnQ9fYQX0k7gv8hfxouRfXFvNHXtUJEI00/BOlhcyGRu8wEIpKYEkDIzj" + + "WZyKXjV8Tz6PkHMJedgVGFDPlKwkx7gufntbjH2xqhBOsJzobDQ44rqOlK1YiXNQCPAMFwN/CpOTQTRFuf9" + + "/37BN2QG5cmgz+ZNqcKPwQnjrVaQBOrQEc5D2/n05aPsRdc6OUzu2TIftoRLRH1peRDLCAo7MjPNhYo1CCi" + + "nBa0FMipG5jtqUPYJJMTt56wIJlwQ95PhGIEYtdRYwxlgSau9Bw+wYmD4NU0K0hw6FgBQ/UDF87T5Zr7HTPWPMwpngkWg=="; Signature signature = createSignature.createSignature(files, noekkelpar); - XAdESSignatures xAdESSignatures = (XAdESSignatures) marshaller.unmarshal(new StreamSource(new ByteArrayInputStream(signature.getContent()))); + XAdESSignatures xAdESSignatures = (XAdESSignatures) unmarshaller.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); @@ -86,10 +91,10 @@ public void test_generated_signatures() { } @Test - public void test_xades_signed_properties() { + public void test_xades_signed_properties() throws JAXBException { Signature signature = createSignature.createSignature(files, noekkelpar); - XAdESSignatures xAdESSignatures = (XAdESSignatures) marshaller.unmarshal(new StreamSource(new ByteArrayInputStream(signature.getContent()))); + XAdESSignatures xAdESSignatures = (XAdESSignatures) unmarshaller.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); @@ -101,14 +106,14 @@ public void test_xades_signed_properties() { } @Test - public void should_support_filenames_with_spaces_and_other_characters() { + public void should_support_filenames_with_spaces_and_other_characters() throws JAXBException { List otherFiles = asList( 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.getContent()))); + XAdESSignatures xAdESSignatures = (XAdESSignatures) unmarshaller.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")); } @@ -132,7 +137,7 @@ private void verify_signing_certificate(final SigningCertificate signingCertific assertThat(certDigest.getDigestValue().length, is(20)); // SHA1 is 160 bits => 20 bytes X509IssuerSerialType issuerSerial = signingCertificate.getCerts().get(0).getIssuerSerial(); - assertThat(issuerSerial.getX509IssuerName(), is("CN=Avsender, OU=Avsender, O=Avsender, L=Oslo, ST=NO, C=NO")); + assertThat(issuerSerial.getX509IssuerName(), is("CN=Avsender,OU=Avsender,O=Avsender,L=Oslo,ST=NO,C=NO")); assertThat(issuerSerial.getX509SerialNumber(), is(new BigInteger("589725471"))); } diff --git a/src/test/java/no/digipost/signature/client/core/internal/http/ResponseStatusTest.java b/src/test/java/no/digipost/signature/client/core/internal/http/ResponseStatusTest.java index 3c9faf83..9cc2368b 100644 --- a/src/test/java/no/digipost/signature/client/core/internal/http/ResponseStatusTest.java +++ b/src/test/java/no/digipost/signature/client/core/internal/http/ResponseStatusTest.java @@ -1,48 +1,24 @@ package no.digipost.signature.client.core.internal.http; -import nl.jqno.equalsverifier.EqualsVerifier; -import nl.jqno.equalsverifier.Warning; -import no.digipost.signature.client.core.internal.http.ResponseStatus.Custom; -import no.digipost.signature.client.core.internal.http.ResponseStatus.Unknown; import org.junit.jupiter.api.Test; -import javax.ws.rs.core.Response.Status; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.quicktheories.QuickTheory.qt; -import static org.quicktheories.generators.SourceDSL.integers; public class ResponseStatusTest { @Test - public void resolveStandardHttpStatus() { - assertThat(ResponseStatus.resolve(200).get(), is(Status.OK)); + public void resolveSuccessfulHttpStatus() { + assertThat(ResponseStatus.resolve(200).get().family(), is(StatusCodeFamily.SUCCESSFUL)); + assertThat(ResponseStatus.resolve(204).get().family(), is(StatusCodeFamily.SUCCESSFUL)); } @Test - public void resolveCustomHttpStatus() { - assertThat(ResponseStatus.resolve(422).get(), is(Custom.UNPROCESSABLE_ENTITY)); + public void resolveClientError() { + assertThat(ResponseStatus.resolve(400).get().family(), is(StatusCodeFamily.CLIENT_ERROR)); + assertThat(ResponseStatus.resolve(404).get().family(), is(StatusCodeFamily.CLIENT_ERROR)); + assertThat(ResponseStatus.resolve(409).get().family(), is(StatusCodeFamily.CLIENT_ERROR)); + assertThat(ResponseStatus.resolve(422).get().family(), is(StatusCodeFamily.CLIENT_ERROR)); } - @Test - public void resolveUnknownHttpStatus() { - assertThat(ResponseStatus.resolve(478).get(), is(ResponseStatus.unknown(478))); - } - - @Test - public void correctEqualsHashCodeForAnyResolvedStatus() { - qt() - .forAll(integers().between(0, 1000)) - .checkAssert(anyStatusCode -> { - assertThat(ResponseStatus.resolve(anyStatusCode).get(), is(ResponseStatus.resolve(anyStatusCode).get())); - assertThat(ResponseStatus.resolve(anyStatusCode).get(), not(ResponseStatus.resolve(anyStatusCode + 1).get())); - }); - } - - @Test - public void correctEqualsHashCodeForUnknownStatus() { - EqualsVerifier.forClass(Unknown.class).suppress(Warning.ALL_FIELDS_SHOULD_BE_USED).verify(); - } } diff --git a/src/test/java/no/digipost/signature/client/core/internal/http/SignatureHttpClientFactoryTest.java b/src/test/java/no/digipost/signature/client/core/internal/http/SignatureHttpClientFactoryTest.java index d6ef4f8d..ae01aaf8 100644 --- a/src/test/java/no/digipost/signature/client/core/internal/http/SignatureHttpClientFactoryTest.java +++ b/src/test/java/no/digipost/signature/client/core/internal/http/SignatureHttpClientFactoryTest.java @@ -1,41 +1,26 @@ package no.digipost.signature.client.core.internal.http; -import org.glassfish.jersey.client.ClientConfig; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.junit.jupiter.api.Test; -import javax.net.ssl.SSLContext; -import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.Configuration; - import java.net.URI; -import java.security.NoSuchAlgorithmException; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static uk.co.probablyfine.matchers.Java8Matchers.where; public class SignatureHttpClientFactoryTest implements HttpIntegrationConfiguration { @Test void instantiatesSignatureHttpClient() { SignatureHttpClient signatureHttpClient = SignatureHttpClientFactory.create(this); - WebTarget target = signatureHttpClient.signatureServiceRoot(); - assertThat(target, where(WebTarget::getUri, is(this.getServiceRoot()))); - } - - - @Override - public Configuration getJaxrsConfiguration() { - return new ClientConfig(); + URI root = signatureHttpClient.signatureServiceRoot(); + assertThat(root, is(this.getServiceRoot())); } @Override - public SSLContext getSSLContext() { - try { - return SSLContext.getDefault(); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e.getMessage(), e); - } + public HttpClient httpClient() { + return HttpClientBuilder.create().build(); } @Override diff --git a/src/test/java/no/digipost/signature/client/core/internal/http/StatusCodeTest.java b/src/test/java/no/digipost/signature/client/core/internal/http/StatusCodeTest.java new file mode 100644 index 00000000..d8948081 --- /dev/null +++ b/src/test/java/no/digipost/signature/client/core/internal/http/StatusCodeTest.java @@ -0,0 +1,13 @@ +package no.digipost.signature.client.core.internal.http; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +class StatusCodeTest { + + @Test + void correctEqualsAndHashCode() { + EqualsVerifier.forClass(StatusCode.class).verify(); + } + +}