From 4751045e72d2356635cbdeda6ef74b53bd944b49 Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Tue, 20 Jun 2023 01:24:33 +0200 Subject: [PATCH 01/18] Updated Gradle wrapper version --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 45003759..3301c79d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https://services.gradle.org/distributions/gradle-7.6.1-all.zip From 7d3aad2acf937b7412032e584c8877b8e738db42 Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Tue, 20 Jun 2023 01:25:17 +0200 Subject: [PATCH 02/18] Updated dependency version --- build.gradle | 56 ++++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/build.gradle b/build.gradle index d318fa64..75d8c790 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,4 @@ -plugins{ +plugins { id 'java-library' id 'signing' id 'checkstyle' @@ -22,7 +22,7 @@ dependencies { testImplementation('junit:junit:4.13.2') testImplementation('org.mockito:mockito-core:4.8.0') testImplementation('org.testcontainers:testcontainers:1.17.5') - testImplementation('org.eclipse.jetty:jetty-server:11.0.12') + testImplementation('org.eclipse.jetty:jetty-server:11.0.14') testImplementation('org.slf4j:slf4j-api:2.0.3') testImplementation('org.bouncycastle:bcprov-jdk15on:1.70') testImplementation('org.bouncycastle:bcpkix-jdk15on:1.70') @@ -180,31 +180,31 @@ if (hasProperty("publish")) { developers { [ - developer { - id = 'steve-perkins' - name = 'Steve Perkins' - email = 'steve@steveperkins.com' - }, - developer { - id = 'steve-perkins-bc' - name = 'Steve Perkins' - email = 'steve.perkins@bettercloud.com' - }, - developer { - id = 'jarrodcodes' - name = 'Jarrod Young' - email = 'jarrodsy@gmail.com' - }, - developer { - id = 'tledkov' - name = 'Taras Ledkov' - email = 'tledkov@apache.org' - }, - developer { - id = 'henryx' - name = 'Enrico Bianchi' - email = 'enrico.bianchi@gmail.com' - } + developer { + id = 'steve-perkins' + name = 'Steve Perkins' + email = 'steve@steveperkins.com' + }, + developer { + id = 'steve-perkins-bc' + name = 'Steve Perkins' + email = 'steve.perkins@bettercloud.com' + }, + developer { + id = 'jarrodcodes' + name = 'Jarrod Young' + email = 'jarrodsy@gmail.com' + }, + developer { + id = 'tledkov' + name = 'Taras Ledkov' + email = 'tledkov@apache.org' + }, + developer { + id = 'henryx' + name = 'Enrico Bianchi' + email = 'enrico.bianchi@gmail.com' + } ] } } @@ -228,7 +228,7 @@ if (hasProperty("publish")) { } javadoc { - if(JavaVersion.current().isJava9Compatible()) { + if (JavaVersion.current().isJava9Compatible()) { options.addBooleanOption('html5', true) } } From fe1b05527fe3c2de4ff79c49ecf42e348ee99f06 Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Tue, 20 Jun 2023 01:25:46 +0200 Subject: [PATCH 03/18] Updated CI tests --- .github/workflows/gradle.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index d56610fc..5384dccc 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -26,11 +26,17 @@ jobs: - 1.11.6 - 1.11.7 - 1.11.8 + - 1.11.9 + - 1.11.10 + - 1.11.11 - 1.12.0 - 1.12.1 - 1.12.2 - 1.12.3 - 1.12.4 + - 1.12.5 + - 1.12.6 + - 1.12.7 - 1.13.0 - 1.13.1 - latest From ab8663422d7119d4d6966bd1420974d740a9ceb0 Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Tue, 20 Jun 2023 01:41:37 +0200 Subject: [PATCH 04/18] Removed missing images --- .github/workflows/gradle.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 5384dccc..e07e1385 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -28,7 +28,6 @@ jobs: - 1.11.8 - 1.11.9 - 1.11.10 - - 1.11.11 - 1.12.0 - 1.12.1 - 1.12.2 @@ -36,7 +35,6 @@ jobs: - 1.12.4 - 1.12.5 - 1.12.6 - - 1.12.7 - 1.13.0 - 1.13.1 - latest From f633f0d4e6c1fe206b9f268e9a2475af07d5bdab Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Tue, 27 Jun 2023 00:29:42 +0200 Subject: [PATCH 05/18] Updated tests according to new location of images --- .../io/github/jopenlibs/vault/util/VaultAgentContainer.java | 2 +- .../java/io/github/jopenlibs/vault/util/VaultContainer.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test-integration/java/io/github/jopenlibs/vault/util/VaultAgentContainer.java b/src/test-integration/java/io/github/jopenlibs/vault/util/VaultAgentContainer.java index e42dea59..73b1414e 100644 --- a/src/test-integration/java/io/github/jopenlibs/vault/util/VaultAgentContainer.java +++ b/src/test-integration/java/io/github/jopenlibs/vault/util/VaultAgentContainer.java @@ -21,7 +21,7 @@ public class VaultAgentContainer extends GenericContainer implements TestConstants, TestLifecycleAware { - public static final String VAULT_DEFAULT_IMAGE = "vault"; + public static final String VAULT_DEFAULT_IMAGE = "hashicorp/vault"; public static final String VAULT_DEFAULT_TAG = "latest"; private static final Logger LOGGER = LoggerFactory.getLogger(VaultAgentContainer.class); diff --git a/src/test-integration/java/io/github/jopenlibs/vault/util/VaultContainer.java b/src/test-integration/java/io/github/jopenlibs/vault/util/VaultContainer.java index de63fcec..bfec1f2c 100644 --- a/src/test-integration/java/io/github/jopenlibs/vault/util/VaultContainer.java +++ b/src/test-integration/java/io/github/jopenlibs/vault/util/VaultContainer.java @@ -29,7 +29,7 @@ public class VaultContainer extends GenericContainer implements TestConstants, TestLifecycleAware { - public static final String VAULT_DEFAULT_IMAGE = "vault"; + public static final String VAULT_DEFAULT_IMAGE = "hashicorp/vault"; public static final String VAULT_DEFAULT_TAG = "latest"; private static final Logger LOGGER = LoggerFactory.getLogger(VaultContainer.class); private String rootToken; From 8d2af28884e65071a7eb737a6f0b34e1e644b996 Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Tue, 27 Jun 2023 00:30:03 +0200 Subject: [PATCH 06/18] Added in CI tests new releases of Vault --- .github/workflows/gradle.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index d56610fc..8423b6aa 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -17,22 +17,22 @@ jobs: java: - 11 vault: - - 1.11.0 - - 1.11.1 - - 1.11.2 - - 1.11.3 - - 1.11.4 - - 1.11.5 - - 1.11.6 - - 1.11.7 - - 1.11.8 + - 1.11.12 - 1.12.0 - 1.12.1 - 1.12.2 - 1.12.3 - 1.12.4 + - 1.12.5 + - 1.12.6 + - 1.12.7 + - 1.12.8 - 1.13.0 - 1.13.1 + - 1.13.2 + - 1.13.3 + - 1.13.4 + - 1.14.0 - latest os: - ubuntu-latest From bd9bef2e3713f9098004b04d4734e12256e34308 Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Tue, 27 Jun 2023 00:45:12 +0200 Subject: [PATCH 07/18] Updated build.gradle to compile to Java 11 --- build.gradle | 95 +++++++++++++++++----------------------------------- 1 file changed, 31 insertions(+), 64 deletions(-) diff --git a/build.gradle b/build.gradle index d318fa64..cdd9a194 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,4 @@ -plugins{ +plugins { id 'java-library' id 'signing' id 'checkstyle' @@ -10,9 +10,8 @@ group 'io.github.jopenlibs' archivesBaseName = 'vault-java-driver' version '5.4.0' -// This project is actually limited to Java 8 compatibility. See below. -sourceCompatibility = 9 -targetCompatibility = 9 +sourceCompatibility = 11 +targetCompatibility = 11 repositories { mavenCentral() @@ -22,7 +21,7 @@ dependencies { testImplementation('junit:junit:4.13.2') testImplementation('org.mockito:mockito-core:4.8.0') testImplementation('org.testcontainers:testcontainers:1.17.5') - testImplementation('org.eclipse.jetty:jetty-server:11.0.12') + testImplementation('org.eclipse.jetty:jetty-server:11.0.14') testImplementation('org.slf4j:slf4j-api:2.0.3') testImplementation('org.bouncycastle:bcprov-jdk15on:1.70') testImplementation('org.bouncycastle:bcpkix-jdk15on:1.70') @@ -31,46 +30,14 @@ dependencies { testRuntimeOnly('org.slf4j:slf4j-simple:2.0.3') } -// Beginning of Java 9 compatibility config -// -// Allowing a library to support Java 9+ modularity, while also maintaining backwards-compatibility for Java 8 users, is WAY more -// tricky than expected! The lines below are adapted from a blog article (https://dzone.com/articles/building-java-6-8-libraries-for-jpms-in-gradle), -// and cause the built classes to support a Java 8 JRE, while also including a module definition suitable for use with Java 9. There are a -// few considerations that come with this: -// -// * You now need JDK 9 or higher to BUILD this library. You can still USE the built artifact as a dependency in a Java 8 project. -// * Although "sourceCompatibility" and "targetCompatability" above are set for Java 9, the "compileJava" settings below will not -// allow you to build with any code changes that are not Java 8 compatible. -// * Unfortunately, IntelliJ (and perhaps other IDE's?) will show syntax highlighting, code completion tips, etc for Java 9. Sorry for -// the inconvenience. In any case, you should not commit changes to source control without confirming that the project builds. compileJava { - exclude 'module-info.java' - - options.compilerArgs = ['--release', '8'] + options.compilerArgs = ['--release', targetCompatibility.toString()] } tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' } -tasks.register('compileModuleInfoJava', JavaCompile) { - classpath = files() - source = 'src/main/java/module-info.java' - destinationDir = compileJava.destinationDir - options.encoding = compileJava.options.encoding - - doFirst { - options.compilerArgs = [ - '--release', '9', - '--module-path', compileJava.classpath.asPath, - ] - } -} - -compileModuleInfoJava.dependsOn compileJava -classes.dependsOn compileModuleInfoJava -// End of Java 9 compatibility config - tasks.register('javadocJar', Jar) { dependsOn tasks.named("javadoc") archiveClassifier.set('javadoc') @@ -180,31 +147,31 @@ if (hasProperty("publish")) { developers { [ - developer { - id = 'steve-perkins' - name = 'Steve Perkins' - email = 'steve@steveperkins.com' - }, - developer { - id = 'steve-perkins-bc' - name = 'Steve Perkins' - email = 'steve.perkins@bettercloud.com' - }, - developer { - id = 'jarrodcodes' - name = 'Jarrod Young' - email = 'jarrodsy@gmail.com' - }, - developer { - id = 'tledkov' - name = 'Taras Ledkov' - email = 'tledkov@apache.org' - }, - developer { - id = 'henryx' - name = 'Enrico Bianchi' - email = 'enrico.bianchi@gmail.com' - } + developer { + id = 'steve-perkins' + name = 'Steve Perkins' + email = 'steve@steveperkins.com' + }, + developer { + id = 'steve-perkins-bc' + name = 'Steve Perkins' + email = 'steve.perkins@bettercloud.com' + }, + developer { + id = 'jarrodcodes' + name = 'Jarrod Young' + email = 'jarrodsy@gmail.com' + }, + developer { + id = 'tledkov' + name = 'Taras Ledkov' + email = 'tledkov@apache.org' + }, + developer { + id = 'henryx' + name = 'Enrico Bianchi' + email = 'enrico.bianchi@gmail.com' + } ] } } @@ -228,7 +195,7 @@ if (hasProperty("publish")) { } javadoc { - if(JavaVersion.current().isJava9Compatible()) { + if (JavaVersion.current().isJava9Compatible()) { options.addBooleanOption('html5', true) } } From 534bbd7b652a5f7e61d854c5f631751ac7ae2eac Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Tue, 27 Jun 2023 00:45:31 +0200 Subject: [PATCH 08/18] Extended module-info.java --- src/main/java/module-info.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 512656e5..478d5b5d 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,5 +1,6 @@ module vault.java.driver { requires java.logging; + requires java.net.http; exports io.github.jopenlibs.vault; exports io.github.jopenlibs.vault.api; exports io.github.jopenlibs.vault.api.database; From c89da1deb562e5df7081802a1d1be2670f6c31a3 Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Tue, 27 Jun 2023 00:46:36 +0200 Subject: [PATCH 09/18] Rewritten methods using HttpClient package --- .../io/github/jopenlibs/vault/rest/Rest.java | 317 ++++++------------ 1 file changed, 97 insertions(+), 220 deletions(-) diff --git a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java index 70ec257c..63c0c0c8 100644 --- a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java +++ b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java @@ -2,26 +2,29 @@ import io.github.jopenlibs.vault.SslConfig; import io.github.jopenlibs.vault.VaultConfig; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLConnection; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpClient.Version; +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.BodyPublisher; +import java.net.http.HttpRequest.BodyPublishers; +import java.net.http.HttpResponse.BodyHandlers; import java.nio.charset.StandardCharsets; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; -import java.util.ArrayList; +import java.time.Duration; +import java.time.temporal.ChronoUnit; import java.util.Arrays; -import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.StringJoiner; import java.util.TreeMap; -import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; @@ -284,30 +287,27 @@ public Rest sslContext(final SSLContext sslContext) { * @throws RestException If an error occurs, or an unexpected response received */ public RestResponse get() throws RestException { - if (urlString == null) { - throw new RestException("No URL is set"); - } + Optional.ofNullable(urlString).orElseThrow(() -> new RestException("No URL is set")); + try { - if (!parameters.isEmpty()) { - // Append parameters to existing query string, or create one - if (urlString.indexOf('?') == -1) { - urlString = urlString + "?" + parametersToQueryString(); - } else { - urlString = urlString + "&" + parametersToQueryString(); - } - } + var uri = new URI(urlString); + var params = parametersToQueryString(); + var query = uri.getQuery() == null ? params + : !params.isEmpty() ? uri.getQuery() + "&" + params : uri.getQuery(); + uri = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), + uri.getPath(), query, uri.getFragment()); // Initialize HTTP(S) connection, and set any header values - final URLConnection connection = initURLConnection(urlString, "GET"); - for (final Map.Entry header : headers.entrySet()) { - connection.setRequestProperty(header.getKey(), header.getValue()); + var request = HttpRequest.newBuilder() + .version(Version.HTTP_1_1) + .uri(uri); + + headers.forEach(request::header); + + if (readTimeoutSeconds != null) { + request.timeout(Duration.of(readTimeoutSeconds, ChronoUnit.SECONDS)); } - // Get the resulting status code - final int statusCode = connectionStatus(connection); - // Download and parse response - final String mimeType = connection.getContentType(); - final byte[] body = responseBodyBytes(connection); - return new RestResponse(statusCode, mimeType, body); + return send(request.GET().build()); } catch (Exception e) { throw new RestException(e); } @@ -361,30 +361,28 @@ public RestResponse put() throws RestException { * @throws RestException If an error occurs, or an unexpected response received */ public RestResponse delete() throws RestException { - if (urlString == null) { - throw new RestException("No URL is set"); - } + Optional.ofNullable(urlString).orElseThrow(() -> new RestException("No URL is set")); + try { - if (!parameters.isEmpty()) { - // Append parameters to existing query string, or create one - if (urlString.indexOf('?') == -1) { - urlString = urlString + "?" + parametersToQueryString(); - } else { - urlString = urlString + "&" + parametersToQueryString(); - } - } + var uri = new URI(urlString); + var params = parametersToQueryString(); + var query = uri.getQuery() == null ? params + : !params.isEmpty() ? uri.getQuery() + "&" + params : uri.getQuery(); + uri = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), + uri.getPath(), query, uri.getFragment()); + // Initialize HTTP(S) connection, and set any header values - final URLConnection connection = initURLConnection(urlString, "DELETE"); - for (final Map.Entry header : headers.entrySet()) { - connection.setRequestProperty(header.getKey(), header.getValue()); + var request = HttpRequest.newBuilder() + .version(Version.HTTP_1_1) + .uri(uri); + + headers.forEach(request::header); + + if (readTimeoutSeconds != null) { + request.timeout(Duration.of(readTimeoutSeconds, ChronoUnit.SECONDS)); } - // Get the resulting status code - final int statusCode = connectionStatus(connection); - // Download and parse response - final String mimeType = connection.getContentType(); - final byte[] body = responseBodyBytes(connection); - return new RestResponse(statusCode, mimeType, body); + return send(request.DELETE().build()); } catch (Exception e) { throw new RestException(e); } @@ -401,104 +399,41 @@ public RestResponse delete() throws RestException { * @return The result of the HTTP operation */ private RestResponse postOrPutImpl(final boolean doPost) throws RestException { - if (urlString == null) { - throw new RestException("No URL is set"); - } + Optional.ofNullable(urlString).orElseThrow(() -> new RestException("No URL is set")); + try { - // Initialize HTTP connection, and set any header values - URLConnection connection; - if (doPost) { - connection = initURLConnection(urlString, "POST"); - } else { - connection = initURLConnection(urlString, "PUT"); - } - for (final Map.Entry header : headers.entrySet()) { - connection.setRequestProperty(header.getKey(), header.getValue()); - } + // Initialize HTTP(S) connection, and set any header values + var request = HttpRequest.newBuilder() + .version(Version.HTTP_1_1) + .uri(new URI(urlString)); + + headers.forEach(request::header); + request.header("Accept-Charset", "UTF-8"); - connection.setDoOutput(true); - connection.setRequestProperty("Accept-Charset", "UTF-8"); + if (readTimeoutSeconds != null) { + request.timeout(Duration.of(readTimeoutSeconds, ChronoUnit.SECONDS)); + } - // If a body payload has been provided, then it takes precedence. Otherwise, look for any additional - // parameters to send as form field values. Parameters sent via the base URL query string are left - // as-is regardless. + BodyPublisher payload; if (body != null) { - final OutputStream outputStream = connection.getOutputStream(); - outputStream.write(body); - outputStream.close(); + payload = BodyPublishers.ofByteArray(body); } else if (!parameters.isEmpty()) { - connection.setRequestProperty("Content-Type", + request.header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); - final OutputStream outputStream = connection.getOutputStream(); - outputStream.write(parametersToQueryString().getBytes(StandardCharsets.UTF_8)); - outputStream.close(); - } - - // Get the resulting status code - final int statusCode = connectionStatus(connection); - // Download and parse response - final String mimeType = connection.getContentType(); - final byte[] body = responseBodyBytes(connection); - return new RestResponse(statusCode, mimeType, body); - } catch (IOException e) { - throw new RestException(e); - } - } - - /** - *

This helper method constructs a new HttpURLConnection or - * HttpsURLConnection, configured with all of the settings that were passed in - * when first initializing this Rest instance (e.g. timeout thresholds, SSL - * verification, SSL certificate data).

- * - * @param urlString The URL to which this connection will be made - * @param method The applicable request method (e.g. "GET", "POST", etc) - * @throws RestException If the URL cannot be successfully parsed, or if there are errors - * processing an SSL cert, etc. - */ - private URLConnection initURLConnection(final String urlString, final String method) - throws RestException { - URLConnection connection = null; - try { - final URL url = new URL(urlString); - connection = url.openConnection(); - - // Timeout settings, if applicable - if (connectTimeoutSeconds != null) { - connection.setConnectTimeout(connectTimeoutSeconds * 1000); - } - if (readTimeoutSeconds != null) { - connection.setReadTimeout(readTimeoutSeconds * 1000); + payload = BodyPublishers.ofByteArray( + parametersToQueryString().getBytes(StandardCharsets.UTF_8)); + } else { + payload = BodyPublishers.noBody(); } - // SSL settings, if applicable - if (connection instanceof HttpsURLConnection) { - final HttpsURLConnection httpsURLConnection = (HttpsURLConnection) connection; - if (sslVerification != null && !sslVerification) { - // SSL verification disabled - httpsURLConnection.setSSLSocketFactory(DISABLED_SSL_CONTEXT.getSocketFactory()); - httpsURLConnection.setHostnameVerifier((s, sslSession) -> true); - } else if (sslContext != null) { - // Cert file supplied - httpsURLConnection.setSSLSocketFactory(sslContext.getSocketFactory()); - } - httpsURLConnection.setRequestMethod(method); - } else if (connection instanceof HttpURLConnection) { - final HttpURLConnection httpURLConnection = (HttpURLConnection) connection; - httpURLConnection.setRequestMethod(method); + if (doPost) { + return send(request.POST(payload).build()); } else { - final String message = "URL string " + urlString - + " cannot be parsed as an instance of HttpURLConnection or HttpsURLConnection"; - throw new RestException(message); + return send(request.PUT(payload).build()); } - return connection; - } catch (Exception e) { + } catch (IOException | InterruptedException | URISyntaxException e) { throw new RestException(e); - } finally { - if (connection instanceof HttpURLConnection) { - ((HttpURLConnection) connection).disconnect(); - } } } @@ -510,100 +445,42 @@ private URLConnection initURLConnection(final String urlString, final String met * @return A url-encoded URL query string */ private String parametersToQueryString() { - final StringBuilder queryString = new StringBuilder(); - final List> params = new ArrayList<>(parameters.entrySet()); - for (int index = 0; index < params.size(); index++) { - if (index > 0) { - queryString.append('&'); - } - final String name = params.get(index).getKey(); - final String value = params.get(index).getValue(); - queryString.append(name).append('=').append(value); - } - return queryString.toString(); + final var sj = new StringJoiner("&"); + parameters.forEach((name, value) -> sj.add(name + "=" + value)); + + return sj.toString(); } /** - *

This helper method downloads the body of an HTTP response (e.g. a clob of JSON text) as - * binary data.

+ * This helper method initialize {@link HttpClient} and send {@link HttpRequest} to remote + * resource * - * @param connection An active HTTP(S) connection - * @return The body payload, downloaded from the HTTP connection response + * @param req an {@link HttpRequest} request + * @return A {@link RestResponse} instance + * @throws IOException if connection fails + * @throws InterruptedException if connection is interrupted */ - private byte[] responseBodyBytes(final URLConnection connection) throws RestException { - try { - final InputStream inputStream; - final int responseCode = this.connectionStatus(connection); - if (200 <= responseCode && responseCode <= 299) { - inputStream = connection.getInputStream(); - } else { - if (connection instanceof HttpsURLConnection) { - final HttpsURLConnection httpsURLConnection = (HttpsURLConnection) connection; - inputStream = httpsURLConnection.getErrorStream(); - } else { - final HttpURLConnection httpURLConnection = (HttpURLConnection) connection; - inputStream = httpURLConnection.getErrorStream(); - } - } - return handleResponseInputStream(inputStream); - } catch (IOException e) { - return new byte[0]; + private RestResponse send(HttpRequest req) throws IOException, InterruptedException { + final var client = HttpClient.newBuilder(); + if (connectTimeoutSeconds != null) { + client.connectTimeout(Duration.of(connectTimeoutSeconds, ChronoUnit.SECONDS)); } - } - - /** - *

This helper method extracts the HTTP(S) status code from a URLConnection, - * provided that it is an HttpURLConnection or a - * HttpsUrlConnection.

- * - * @param connection An active HTTP(S) connection - */ - private int connectionStatus(final URLConnection connection) throws IOException, RestException { - int statusCode; - if (connection instanceof HttpsURLConnection) { - final HttpsURLConnection httpsURLConnection = (HttpsURLConnection) connection; - statusCode = httpsURLConnection.getResponseCode(); - } else if (connection instanceof HttpURLConnection) { - final HttpURLConnection httpURLConnection = (HttpURLConnection) connection; - statusCode = httpURLConnection.getResponseCode(); - } else { - final String className = connection != null ? connection.getClass().getName() : "null"; - throw new RestException("Expecting a URLConnection of type " - + HttpURLConnection.class.getName() - + " or " - + HttpsURLConnection.class.getName() - + ", found " - + className); + if (sslVerification != null && !sslVerification) { + client.sslContext(DISABLED_SSL_CONTEXT); + } else if (sslContext != null) { + client.sslContext(sslContext); } - return statusCode; - } - /** - *

This method handles the response stream from the connection.

- * - * @param inputStream The input stream from the connection. - * @return The body payload, downloaded from the HTTP connection response - */ - protected byte[] handleResponseInputStream(final InputStream inputStream) { - try { - // getErrorStream() can return null so handle it. - if (inputStream != null) { - final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - - int bytesRead; - final byte[] bytes = new byte[16384]; - while ((bytesRead = inputStream.read(bytes, 0, bytes.length)) != -1) { - byteArrayOutputStream.write(bytes, 0, bytesRead); - } + var response = client.build().send(req, BodyHandlers.ofString()); - byteArrayOutputStream.flush(); - return byteArrayOutputStream.toByteArray(); - } else { - return new byte[0]; - } - } catch (IOException e) { - return new byte[0]; - } + // Get the resulting status code + final var statusCode = response.statusCode(); + + // Download and parse response + final var mimeType = response.headers().firstValue("Content-Type").orElse(""); + final var body = response.body().getBytes(); + + return new RestResponse(statusCode, mimeType, body); } } From 010bcd162bdd2c5a3a9a4ead3e638332fb8e578a Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Tue, 27 Jun 2023 00:46:48 +0200 Subject: [PATCH 10/18] Removed unnecessary test --- .../io/github/jopenlibs/vault/rest/GetTests.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/test/java/io/github/jopenlibs/vault/rest/GetTests.java b/src/test/java/io/github/jopenlibs/vault/rest/GetTests.java index cc95f61d..8605e857 100644 --- a/src/test/java/io/github/jopenlibs/vault/rest/GetTests.java +++ b/src/test/java/io/github/jopenlibs/vault/rest/GetTests.java @@ -191,16 +191,4 @@ public void testGet_RetrievesResponseBodyWhenStatusIs418() throws RestException assertTrue("Response body doesn't contain word User-Agent", responseBody.contains("User-Agent")); } - - - /** - *

Verify that response body does not cause NPE when input stream is null.

- */ - @Test - public void test_handleResponseInputStream() { - final Rest rest = new Rest(); - final byte[] result = rest.handleResponseInputStream(null); - assertEquals(0, result.length); - } - } From 5df42c009f973389d5c98ed45d1f99f850fcfc88 Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Tue, 27 Jun 2023 00:29:42 +0200 Subject: [PATCH 11/18] Updated tests according to new location of images --- .../io/github/jopenlibs/vault/util/VaultAgentContainer.java | 2 +- .../java/io/github/jopenlibs/vault/util/VaultContainer.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test-integration/java/io/github/jopenlibs/vault/util/VaultAgentContainer.java b/src/test-integration/java/io/github/jopenlibs/vault/util/VaultAgentContainer.java index e42dea59..73b1414e 100644 --- a/src/test-integration/java/io/github/jopenlibs/vault/util/VaultAgentContainer.java +++ b/src/test-integration/java/io/github/jopenlibs/vault/util/VaultAgentContainer.java @@ -21,7 +21,7 @@ public class VaultAgentContainer extends GenericContainer implements TestConstants, TestLifecycleAware { - public static final String VAULT_DEFAULT_IMAGE = "vault"; + public static final String VAULT_DEFAULT_IMAGE = "hashicorp/vault"; public static final String VAULT_DEFAULT_TAG = "latest"; private static final Logger LOGGER = LoggerFactory.getLogger(VaultAgentContainer.class); diff --git a/src/test-integration/java/io/github/jopenlibs/vault/util/VaultContainer.java b/src/test-integration/java/io/github/jopenlibs/vault/util/VaultContainer.java index de63fcec..bfec1f2c 100644 --- a/src/test-integration/java/io/github/jopenlibs/vault/util/VaultContainer.java +++ b/src/test-integration/java/io/github/jopenlibs/vault/util/VaultContainer.java @@ -29,7 +29,7 @@ public class VaultContainer extends GenericContainer implements TestConstants, TestLifecycleAware { - public static final String VAULT_DEFAULT_IMAGE = "vault"; + public static final String VAULT_DEFAULT_IMAGE = "hashicorp/vault"; public static final String VAULT_DEFAULT_TAG = "latest"; private static final Logger LOGGER = LoggerFactory.getLogger(VaultContainer.class); private String rootToken; From 43aec73683ea6648ae7d422c71f50f111ec1a725 Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Tue, 27 Jun 2023 18:15:46 +0200 Subject: [PATCH 12/18] Removed deprecated method --- .../io/github/jopenlibs/vault/api/Auth.java | 2 +- .../io/github/jopenlibs/vault/rest/Rest.java | 20 ------------------- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/src/main/java/io/github/jopenlibs/vault/api/Auth.java b/src/main/java/io/github/jopenlibs/vault/api/Auth.java index c25a20bb..f05e7cf8 100644 --- a/src/main/java/io/github/jopenlibs/vault/api/Auth.java +++ b/src/main/java/io/github/jopenlibs/vault/api/Auth.java @@ -439,7 +439,7 @@ public AuthResponse loginByAppID(final String path, final String appId, final St .toString(); final RestResponse restResponse = new Rest()//NOPMD .url(config.getAddress() + "/v1/auth/" + path) - .optionalHeader("X-Vault-Namespace", this.nameSpace) + .header("X-Vault-Namespace", this.nameSpace) .body(requestJson.getBytes(StandardCharsets.UTF_8)) .connectTimeoutSeconds(config.getOpenTimeout()) .readTimeoutSeconds(config.getReadTimeout()) diff --git a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java index 63c0c0c8..155bfbb1 100644 --- a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java +++ b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java @@ -191,26 +191,6 @@ public Rest header(final String name, final String value) { return this; } - /** - *

Adds an optional header to be sent with the HTTP request.

- * * - *

The value, if null, will skip adding this header to the request.

- * - *

This method may be chained together repeatedly, to pass multiple headers with a request. - * When the request is ultimately sent, the headers will be sorted by their names.

- * - * @param name The raw header name - * @param value The raw header value - * @return This object, with a header added, ready for other builder-pattern config methods or - * an HTTP verb method - * @deprecated use {@link #header(String, String)} instead. - */ - @Deprecated - public Rest optionalHeader(final String name, final String value) { - header(name, value); - return this; - } - /** *

The number of seconds to wait before giving up on establishing an HTTP(S) connection.

* From cf09a40ba413e1ee43c545af06c800192d1cd059 Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Tue, 27 Jun 2023 20:48:40 +0200 Subject: [PATCH 13/18] Moved duplicated code in dedicated method --- .../io/github/jopenlibs/vault/rest/Rest.java | 86 ++++++++----------- 1 file changed, 37 insertions(+), 49 deletions(-) diff --git a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java index 155bfbb1..ef0d75b1 100644 --- a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java +++ b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java @@ -12,6 +12,7 @@ import java.net.http.HttpRequest; import java.net.http.HttpRequest.BodyPublisher; import java.net.http.HttpRequest.BodyPublishers; +import java.net.http.HttpRequest.Builder; import java.net.http.HttpResponse.BodyHandlers; import java.nio.charset.StandardCharsets; import java.security.KeyManagementException; @@ -267,25 +268,8 @@ public Rest sslContext(final SSLContext sslContext) { * @throws RestException If an error occurs, or an unexpected response received */ public RestResponse get() throws RestException { - Optional.ofNullable(urlString).orElseThrow(() -> new RestException("No URL is set")); - try { - var uri = new URI(urlString); - var params = parametersToQueryString(); - var query = uri.getQuery() == null ? params - : !params.isEmpty() ? uri.getQuery() + "&" + params : uri.getQuery(); - uri = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), - uri.getPath(), query, uri.getFragment()); - // Initialize HTTP(S) connection, and set any header values - var request = HttpRequest.newBuilder() - .version(Version.HTTP_1_1) - .uri(uri); - - headers.forEach(request::header); - - if (readTimeoutSeconds != null) { - request.timeout(Duration.of(readTimeoutSeconds, ChronoUnit.SECONDS)); - } + var request = buildRequest(); return send(request.GET().build()); } catch (Exception e) { @@ -341,27 +325,8 @@ public RestResponse put() throws RestException { * @throws RestException If an error occurs, or an unexpected response received */ public RestResponse delete() throws RestException { - Optional.ofNullable(urlString).orElseThrow(() -> new RestException("No URL is set")); - try { - var uri = new URI(urlString); - var params = parametersToQueryString(); - var query = uri.getQuery() == null ? params - : !params.isEmpty() ? uri.getQuery() + "&" + params : uri.getQuery(); - uri = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), - uri.getPath(), query, uri.getFragment()); - - // Initialize HTTP(S) connection, and set any header values - var request = HttpRequest.newBuilder() - .version(Version.HTTP_1_1) - .uri(uri); - - headers.forEach(request::header); - - if (readTimeoutSeconds != null) { - request.timeout(Duration.of(readTimeoutSeconds, ChronoUnit.SECONDS)); - } - + var request = this.buildRequest(); return send(request.DELETE().build()); } catch (Exception e) { throw new RestException(e); @@ -379,21 +344,11 @@ public RestResponse delete() throws RestException { * @return The result of the HTTP operation */ private RestResponse postOrPutImpl(final boolean doPost) throws RestException { - Optional.ofNullable(urlString).orElseThrow(() -> new RestException("No URL is set")); - try { // Initialize HTTP(S) connection, and set any header values - var request = HttpRequest.newBuilder() - .version(Version.HTTP_1_1) - .uri(new URI(urlString)); - - headers.forEach(request::header); + var request = this.buildRequest(); request.header("Accept-Charset", "UTF-8"); - if (readTimeoutSeconds != null) { - request.timeout(Duration.of(readTimeoutSeconds, ChronoUnit.SECONDS)); - } - BodyPublisher payload; if (body != null) { payload = BodyPublishers.ofByteArray(body); @@ -463,4 +418,37 @@ private RestResponse send(HttpRequest req) throws IOException, InterruptedExcept return new RestResponse(statusCode, mimeType, body); } + + + /** + * This helper method build an {@link HttpRequest.Builder} object used to send requests to + * remote resource + * + * @return a {@link HttpRequest.Builder} bojnect + * @throws URISyntaxException if passed URL isn't valid + * @throws RestException if isn't passed an URL + */ + private Builder buildRequest() throws URISyntaxException, RestException { + Optional.ofNullable(urlString).orElseThrow(() -> new RestException("No URL is set")); + + var uri = new URI(urlString); + var params = parametersToQueryString(); + var query = uri.getQuery() == null ? params + : !params.isEmpty() ? uri.getQuery() + "&" + params : uri.getQuery(); + uri = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), + uri.getPath(), query, uri.getFragment()); + + // Initialize HTTP(S) connection, and set any header values + var request = HttpRequest.newBuilder() + .version(Version.HTTP_1_1) + .uri(uri); + + headers.forEach(request::header); + + if (readTimeoutSeconds != null) { + request.timeout(Duration.of(readTimeoutSeconds, ChronoUnit.SECONDS)); + } + + return request; + } } From 11e82d82a54d21d07dea2a43cd1b547e74940d8e Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Thu, 29 Jun 2023 17:52:22 +0200 Subject: [PATCH 14/18] Fixed tests --- src/main/java/io/github/jopenlibs/vault/rest/Rest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java index ef0d75b1..10d7cc20 100644 --- a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java +++ b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java @@ -269,7 +269,7 @@ public Rest sslContext(final SSLContext sslContext) { */ public RestResponse get() throws RestException { try { - var request = buildRequest(); + var request = buildRequest(true); return send(request.GET().build()); } catch (Exception e) { @@ -326,7 +326,7 @@ public RestResponse put() throws RestException { */ public RestResponse delete() throws RestException { try { - var request = this.buildRequest(); + var request = this.buildRequest(true); return send(request.DELETE().build()); } catch (Exception e) { throw new RestException(e); @@ -346,7 +346,7 @@ public RestResponse delete() throws RestException { private RestResponse postOrPutImpl(final boolean doPost) throws RestException { try { // Initialize HTTP(S) connection, and set any header values - var request = this.buildRequest(); + var request = this.buildRequest(false); request.header("Accept-Charset", "UTF-8"); BodyPublisher payload; @@ -428,11 +428,11 @@ private RestResponse send(HttpRequest req) throws IOException, InterruptedExcept * @throws URISyntaxException if passed URL isn't valid * @throws RestException if isn't passed an URL */ - private Builder buildRequest() throws URISyntaxException, RestException { + private Builder buildRequest(Boolean isGetOrDelete) throws URISyntaxException, RestException { Optional.ofNullable(urlString).orElseThrow(() -> new RestException("No URL is set")); var uri = new URI(urlString); - var params = parametersToQueryString(); + var params = isGetOrDelete ? parametersToQueryString() : ""; var query = uri.getQuery() == null ? params : !params.isEmpty() ? uri.getQuery() + "&" + params : uri.getQuery(); uri = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), From ea3960019aa4f24b45be5e20159bf5e640062b93 Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Thu, 29 Jun 2023 17:59:57 +0200 Subject: [PATCH 15/18] Added missing javadoc parameter --- src/main/java/io/github/jopenlibs/vault/rest/Rest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java index 10d7cc20..3a586ddc 100644 --- a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java +++ b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java @@ -424,6 +424,8 @@ private RestResponse send(HttpRequest req) throws IOException, InterruptedExcept * This helper method build an {@link HttpRequest.Builder} object used to send requests to * remote resource * + * @param isGetOrDelete sets if request is called for a GET or DELETE request instead of POST or + * PUT request * @return a {@link HttpRequest.Builder} bojnect * @throws URISyntaxException if passed URL isn't valid * @throws RestException if isn't passed an URL From 11cba473eb98ba0221dc8e5e536e7aca531ec1a4 Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Thu, 29 Jun 2023 23:39:25 +0200 Subject: [PATCH 16/18] Simplified ternary operator --- src/main/java/io/github/jopenlibs/vault/rest/Rest.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java index 3a586ddc..efa9fc33 100644 --- a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java +++ b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java @@ -435,8 +435,14 @@ private Builder buildRequest(Boolean isGetOrDelete) throws URISyntaxException, R var uri = new URI(urlString); var params = isGetOrDelete ? parametersToQueryString() : ""; - var query = uri.getQuery() == null ? params - : !params.isEmpty() ? uri.getQuery() + "&" + params : uri.getQuery(); + var query = params; + + if (uri.getQuery() != null) { + query = uri.getQuery(); + if (!params.isEmpty()) { + query = uri.getQuery() + "&" + params; + } + } uri = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), query, uri.getFragment()); From 291bd6a1b4e7e3b6613d7279e3897245af75e1fd Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Sat, 1 Jul 2023 19:12:59 +0200 Subject: [PATCH 17/18] Removed in build.gradle deprecated code --- build.gradle | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index cdd9a194..dd0c74b0 100644 --- a/build.gradle +++ b/build.gradle @@ -82,8 +82,14 @@ tasks.named('test') { events "passed", "skipped", "failed" } - reports.html.enabled = false - reports.junitXml.enabled = true + reports { + html { + required = false + } + junitXml { + required = true + } + } } def integrationTestTask = tasks.register('integrationTest', Test) { @@ -94,8 +100,14 @@ def integrationTestTask = tasks.register('integrationTest', Test) { events "passed", "skipped", "failed" } - reports.html.enabled = false - reports.junitXml.enabled = true + reports { + html { + required = false + } + junitXml { + required = true + } + } } // From f4211864c69a62002503021a139f6b64c56051d0 Mon Sep 17 00:00:00 2001 From: Enrico Bianchi Date: Sat, 1 Jul 2023 19:13:18 +0200 Subject: [PATCH 18/18] Upgraded Gradle --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3301c79d..4d52a502 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https://services.gradle.org/distributions/gradle-7.6.1-all.zip +distributionUrl=https://services.gradle.org/distributions/gradle-8.2-all.zip