diff --git a/.swagger-codegen/VERSION b/.swagger-codegen/VERSION
index 580ce10..19244e8 100644
--- a/.swagger-codegen/VERSION
+++ b/.swagger-codegen/VERSION
@@ -1 +1 @@
-2.4.21-SNAPSHOT
\ No newline at end of file
+2.4.21
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9d9e67e..357e887 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,11 @@
# DocuSign Monitor Java Client Changelog
See [DocuSign Support Center](https://support.docusign.com/en/releasenotes/) for Product Release Notes.
+## [v2.0.0] - Monitor API v2.0-3.0.1 - 2026-07-29
+### Changed
+- Added support for version v2.0-3.0.1 of the DocuSign Monitor API.
+- Updated the SDK release version.
+
## [v1.4.0] - Monitor API v2.0-2.0.0 - 2023-06-14
### Changed
- Added support for version v2.0-2.0.0 of the DocuSign Monitor API.
diff --git a/README.md b/README.md
index 0c63483..df7b1cb 100644
--- a/README.md
+++ b/README.md
@@ -1,88 +1,167 @@
-# The Official DocuSign Monitor Java Client
+# The Official Docusign Monitor Java Client SDK
[![Build status][travis-image]][travis-url]
[![Maven Central status][maven-image]][maven-url]
-## Requirements
+The Docusign SDK makes integrating Docusign into your apps and websites a seamless experience.
+
+## Table of Contents
+- [Introduction](#introduction)
+- [Installation](#installation)
+ * [Version Information](#versionInformation)
+ * [Requirements](#requirements)
+ * [Compatibility](#compatibility)
+ * [Maven](#maven)
+- [Dependencies](#dependencies)
+- [API Reference](#apiReference)
+- [Code Examples](#codeExamples)
+- [OAuth Implementations](#oauthImplementations)
+- [Security](#security)
+- [Changelog](#changeLog)
+- [Support](#support)
+- [License](#license)
+- [Additional Resources](#additionalResources)
+
+
+## Introduction
+Docusign Monitor helps organizations protect their agreements with round-the-clock activity tracking. The Monitor API delivers this activity tracking information directly to existing security stacks or data visualization tools—enabling teams to detect unauthorized activity, investigate incidents, and quickly respond to verified threats. It also provides the flexibility security teams need to customize dashboards and alerts to meet specific business needs.
+
+
+## Installation
+This client SDK is provided as open source, which enables you to customize its functionality to suit your particular use case. To do so, download or clone the repository. If the SDK’s given functionality meets your integration needs, or if you’re working through our [code examples](https://developers.docusign.com/docs/monitor-api/how-to/) from the [Docusign Developer Center](https://developers.docusign.com/), you merely need to install it by following the instructions below.
-- Java 1.9+
-- Free [Developer Sandbox](https://go.docusign.com/sandbox/productshot/?elqCampaignId=16531)
+
+### Version Information
+- **API version**: v3.0
+- **Latest SDK version (Including prerelease)**:
+
+
+## Requirements
+* Java 1.9+
+* Free [developer account](https://go.docusign.com/o/sandbox/?postActivateUrl=https://developers.docusign.com/)
+
## Compatibility
+* Java 1.9+
-- Java 1.9+
+
+**Note:** Docusign uses Eclipse with Maven for testing purposes.
+### Maven:
+1. In Eclipse, create a new project by selecting **File > New > Project**.
+2. In the New Project Wizard, expand **Maven,** then select **Maven Project.**
+3. Leave **Create a simple project** unchecked.
+4. Select **Next,** then provide a unique **groupId** and **artifactId.**
+5. In the directory where you've saved your project, open the pom.xml file.
+6. In the pom.xml file, locate the `dependencies` node.
+7. Add:
+ ```
+
+ com.docusign
+ docusign-monitor-java
+ 2.0.0
+
+ ```
+8. If your project is still open, restart Eclipse.
+
+
+## SDK Dependencies
+This client has the following external dependencies:
+* io.swagger.core.v3:swagger-annotations:2.2.8
+* org.glassfish.jersey.core:jersey-client:3.1.6
+* org.glassfish.jersey.media:jersey-media-multipart:3.1.6
+* org.glassfish.jersey.media:jersey-media-json-jackson:3.1.6
+* org.glassfish.jersey.inject:jersey-hk2:3.1.6
+* com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-base:2.14.2
+* com.auth0:java-jwt:3.4.1
+* org.bouncycastle:bcprov-jdk18on:1.78.1
+* com.fasterxml.jackson.dataformat:jackson-dataformat-csv:2.14.2
+* com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.2
+* com.brsanthu:migbase64:2.2
+* com.fasterxml.jackson.core:jackson-core:2.17.1
+* org.json:json:20240303
-## Note
-This open-source SDK is provided for cases where you would like to make additional changes that the SDK does not
- provide out-of-the-box. If you simply want to use the SDK with any of the examples shown in the [Developer Center ](https://developers.docusign.com/monitor-api/code-examples), follow the installation instructions below.
+
+## API Reference
+You can refer to the API reference [here](https://developers.docusign.com/docs/monitor-api/reference/).
-## Installation
+
+## Code examples
+Explore our GitHub repository for the [Launcher](https://github.com/docusign/code-examples-java/), a self-executing package housing code examples for the Monitor Java SDK. This package showcases several common use cases and their respective source files. Additionally, you can download a version preconfigured for your Docusign developer account from [Quickstart](https://developers.docusign.com/docs/esign-rest-api/quickstart/). These examples support both the [Authorization Code Grant](https://developers.docusign.com/platform/auth/authcode/) and [JSON Web Token (JWT)](https://developers.docusign.com/platform/auth/jwt/) authentication workflows.
-Note: DocuSign uses **Eclipse** with **Maven** for testing purposes.
+
+## OAuth implementations
+For details regarding which type of OAuth grant will work best for your Docusign integration, see [Choose OAuth Type](https://developers.docusign.com/platform/auth/choose/) in the [Docusign Developer Center](https://developers.docusign.com/).
-### Maven:
+For security purposes, Docusign recommends using the [Authorization Code Grant](https://developers.docusign.com/platform/auth/authcode/) flow.
-1. In Eclipse, create a new project by selecting **File** -> **New** -> **Project**.
-2. In the **New Project Wizard** , expand **Maven** , then select **Maven Project.**
-3. Leave **Create a simple project** unchecked.
-4. Select **Next** , then provide a unique **Group** and **Artifact Id**.
-5. In the directory where you've saved your project, open the _pom.xml_ file.
-6. In the _pom.xml_ file, locate the **dependencies** node.
-7. Add:
+
+## Security
-```
-
- com.docusign
- docusign-monitor-java
- 1.1.0
-
-```
+This SDK enforces secure-by-default transport behavior:
-8. If your project is still open, restart **Eclipse**.
+* **TLS certificate validation** is enforced by default using the system's trust store and default `HostnameVerifier`.
+* **HTTPS-only base paths** — `setBasePath()` and the `ApiClient(String basePath)` constructor reject `http://` URLs by default.
+* **OAuth base path validation** — `setOAuthBasePath()` also enforces HTTPS.
+* **TLS 1.2 required** — The SDK fails fast if TLSv1.2 is not available on the JVM.
-## Dependencies
+### Testing with self-signed certificates or HTTP endpoints
-This client has the following external dependencies:
+For local development or testing scenarios that require HTTP or self-signed certificates, use the explicit `ApiClient.insecure()` factory:
-- io.swagger:swagger-annotations:jar:1.5.17
-- org.glassfish.jersey.core:jersey-client:jar:2.29.1
-- org.glassfish.jersey.media:jersey-media-multipart:jar:2.29.1
-- org.glassfish.jersey.media:jersey-media-json-jackson:2.29.1
-- com.fasterxml.jackson.core:jackson-core:jar:2.10.1
-- com.fasterxml.jackson.core:jackson-annotations:jar:2.10.1
-- com.fasterxml.jackson.core:jackson-databind:2.10.1
-- com.fasterxml.jackson.datatype:jackson-datatype-joda:jar:2.10.1
-- com.brsanthu:migbase64:2.2
-- junit:junit:jar:4.12
-- com.apache.oltu.oauth2:org.apache.oltu.oauth2.client:1.0.2
-- com.auth0:java-jwt:3.4.1
-- org.bouncycastle:bcprov-jdk15on:1.60
+```java
+// HTTP endpoint for local testing
+ApiClient client = ApiClient.insecure("http://localhost:8080/restapi");
-## OAuth Implementations
+// Self-signed certificate
+ApiClient client = ApiClient.insecure("https://dev-server.local/restapi");
-For details regarding which type of OAuth grant will work best for your DocuSign integration, see the [REST API
- Authentication Overview](https://developers.docusign.com/monitor-api/guides/auth) guide located on the [DocuSign Developer Center](https://developers.docusign.com/).
+// Insecure client with default base path
+ApiClient client = ApiClient.insecure();
+client.setBasePath("http://localhost:8080/restapi");
+```
-For security purposes, DocuSign recommends using the Authorization Code Grant flow.
+> **Warning:** `ApiClient.insecure()` disables TLS certificate validation and hostname verification. Never use in production.
-## Support
+### Proxy authentication
-Log issues against this client through GitHub. We also have an [active developer community on Stack Overflow](https://stackoverflow.com/questions/tagged/docusignapi).
+By default, when proxy credentials are set via system properties (`https.proxyUser` / `https.proxyPassword`), the SDK configures a JVM-wide `Authenticator` scoped to the proxy host and port. This preserves backward compatibility with existing enterprise configurations.
-## License
+If you prefer per-connection proxy authentication (which avoids JVM-global side effects), enable it via the setter before the first request:
-The DocuSign Java Client is licensed under the [MIT License](https://github.com/docusign/docusign-monitor-java-client/blob
-/master/LICENSE).
+```java
+ApiClient client = new ApiClient();
+client.setPerConnectionProxyAuth(true);
+```
+Or use the convenience constructor:
-[travis-image]: https://img.shields.io/travis/docusign/docusign-monitor-java-client.svg?style=flat
-[travis-url]: https://travis-ci.org/docusign/docusign-monitor-java-client
-[maven-image]: https://img.shields.io/maven-central/v/com.docusign/docusign-monitor-java.svg?style=flat
-[maven-url]: https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.docusign%22
+```java
+ApiClient client = new ApiClient(true);
+```
-### Additional Resources
-* [DocuSign Developer Center](https://developers.docusign.com)
-* [DocuSign API on Twitter](https://twitter.com/docusignapi)
-* [DocuSign For Developers on LinkedIn](https://www.linkedin.com/showcase/docusign-for-developers/)
-* [DocuSign For Developers on YouTube](https://www.youtube.com/channel/UCJSJ2kMs_qeQotmw4-lX2NQ)
+> **Note:** Per-connection proxy auth will become the default in a future major version.
+
+
+## Changelog
+You can refer to the complete changelog [here](https://github.com/docusign/docusign-monitor-java-client/blob/master/CHANGELOG.md).
+
+
+## Support
+Log issues against this client SDK through GitHub. You can also reach out to us through [Docusign Community](https://community.docusign.com/developer-59) and [Stack Overflow](https://stackoverflow.com/questions/tagged/docusignapi).
+
+
+## License
+The Docusign Monitor Java Client SDK is licensed under the [MIT License](https://github.com/docusign/docusign-monitor-java-client/blob/master/LICENSE).
+
+
+### Additional resources
+* [Docusign Developer Center](https://developers.docusign.com/)
+* [Docusign API on Twitter](https://twitter.com/docusignapi)
+* [Docusign For Developers on LinkedIn](https://www.linkedin.com/showcase/docusign-for-developers/)
+* [Docusign For Developers on YouTube](https://www.youtube.com/channel/UCJSJ2kMs_qeQotmw4-lX2NQ)
+
+[travis-image]: https://api.travis-ci.com/docusign/docusign-monitor-java-client.svg?branch=master
+[travis-url]: https://app.travis-ci.com/github/docusign/docusign-monitor-java-client
+[maven-image]: https://img.shields.io/maven-central/v/com.docusign/.svg?style=flat
+[maven-url]: https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.docusign%22
diff --git a/pom.xml b/pom.xml
index d400f8c..76d9621 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,9 +5,9 @@
docusign-monitor-javajardocusign-monitor-java
- 1.4.0
+ 2.0.0https://developers.docusign.com
- The official DocuSign Monitor Java client provides libraries for Java application integration with DocuSign Monitor for real estate and mortgage workflows. It is recommended that you use this version of the library for new development.
+ The official Docusign Monitor Java client provides libraries for Java application integration with Docusign Monitor for real estate and mortgage workflows. It is recommended that you use this version of the library for new development.2.2.0
@@ -15,12 +15,12 @@
- ossrh
- https://s01.oss.sonatype.org/content/repositories/snapshots
+ central
+ https://central.sonatype.com/repository/maven-snapshots/
- ossrh2
- https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/
+ central
+ https://central.sonatype.com/repository/maven-releases/
@@ -33,7 +33,7 @@
DocuSign Monitor Java Client License
- https://raw.githubusercontent.com/docusign/docusign-monitor-java-client/master/LICENSE
+ https://www.docusign.com/company/terms-and-conditions/webrepo
@@ -52,7 +52,7 @@
com.diffplug.spotlessspotless-maven-plugin
- 2.12.1
+ 2.43.0
@@ -66,7 +66,7 @@
- 1.7
+ 1.21.0
@@ -227,6 +227,21 @@
+
+ org.owasp
+ dependency-check-maven
+ 12.1.1
+
+ 8
+
+
+
+
+ check
+
+
+
+ org.apache.maven.pluginsmaven-source-plugin
@@ -294,13 +309,13 @@
- org.sonatype.plugins
- nexus-staging-maven-plugin
- 1.6.8
+ org.sonatype.central
+ central-publishing-maven-plugin
+ 0.8.0
+ true
- ossrh
- https://s01.oss.sonatype.org/
- true
+ central
+ true
@@ -329,6 +344,7 @@
jersey-media-json-jackson${jersey-version}
+
org.glassfish.jersey.injectjersey-hk2
@@ -340,11 +356,6 @@
jackson-jakarta-rs-base${jackson-version}
-
- org.apache.oltu.oauth2
- org.apache.oltu.oauth2.client
- ${oltu-version}
- com.auth0
@@ -353,8 +364,8 @@
org.bouncycastle
- bcprov-jdk15on
- 1.69
+ bcprov-jdk18on
+ 1.78.1com.fasterxml.jackson.dataformat
@@ -390,13 +401,28 @@
migbase642.2
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.10.2
+ test
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ 2.17.1
+
+
+ org.json
+ json
+ 20240303
+ UTF-82.2.8
- 3.0.9
+ 3.1.62.14.24.13.1
- 1.0.2
diff --git a/src/main/java/com/docusign/monitor/api/DocuMonitorApi.java b/src/main/java/com/docusign/monitor/api/DocuMonitorApi.java
new file mode 100644
index 0000000..e768779
--- /dev/null
+++ b/src/main/java/com/docusign/monitor/api/DocuMonitorApi.java
@@ -0,0 +1,177 @@
+
+package com.docusign.monitor.api;
+
+import jakarta.ws.rs.core.GenericType;
+
+import com.docusign.monitor.client.ApiException;
+import com.docusign.monitor.client.ApiClient;
+import com.docusign.monitor.client.Configuration;
+import com.docusign.monitor.model.*;
+import com.docusign.monitor.client.Pair;
+import com.docusign.monitor.client.ApiResponse;
+
+
+
+
+/**
+ * DocuMonitorApi class.
+ *
+ **/
+public class DocuMonitorApi {
+ private ApiClient apiClient;
+
+ /**
+ * DocuMonitorApi.
+ *
+ **/
+ public DocuMonitorApi() {
+ this(Configuration.getDefaultApiClient());
+ }
+
+ /**
+ * DocuMonitorApi.
+ *
+ **/
+ public DocuMonitorApi(ApiClient apiClient) {
+ this.apiClient = apiClient;
+ }
+
+ /**
+ * getApiClient Method.
+ *
+ * @return ApiClient
+ **/
+ public ApiClient getApiClient() {
+ return apiClient;
+ }
+
+ /**
+ * setApiClient Method.
+ *
+ **/
+ public void setApiClient(ApiClient apiClient) {
+ this.apiClient = apiClient;
+ }
+
+ ///
+ /// Gets customer event data for an organization. Gets customer event data for the organization that owns the integration key. The results for this endpoint are paginated by their event ingestion time, which can be up to five minutes after the event originally occurred. Use the `cursor` parameter to specify where the query begins in the dataset. Use the `limit` parameter to set the number of records returned. For a detailed example of how to use `getStream` and retrieve monitoring data, see our guides [How to get monitoring data](/docs/monitor-api/how-to/get-monitoring-data/) and [Tracking data and handling alerts](/docs/monitor-api/monitor101/tracking-data-handling-alerts/). **Note**: data is not guaranteed to be in the order of the timestamp of the events. It is returned in the order it was inserted into our database. ### Related topics - [How to get monitoring data](/docs/monitor-api/how-to/get-monitoring-data/) - [Tracking data and handling alerts](/docs/monitor-api/monitor101/tracking-data-handling-alerts/)
+ ///
+
+ /**
+ * StreamOptions Class.
+ *
+ **/
+ public class StreamOptions
+ {
+ private String cursor = null;
+ private Integer limit = null;
+
+ /**
+ * setCursor method.
+ */
+ public void setCursor(String cursor) {
+ this.cursor = cursor;
+ }
+
+ /**
+ * getCursor method.
+ *
+ * @return String
+ */
+ public String getCursor() {
+ return this.cursor;
+ }
+
+ /**
+ * setLimit method.
+ */
+ public void setLimit(Integer limit) {
+ this.limit = limit;
+ }
+
+ /**
+ * getLimit method.
+ *
+ * @return Integer
+ */
+ public Integer getLimit() {
+ return this.limit;
+ }
+ }
+
+ /**
+ * Gets customer event data for an organization..
+ * Gets customer event data for the organization that owns the integration key. The results for this endpoint are paginated by their event ingestion time, which can be up to five minutes after the event originally occurred. Use the `cursor` parameter to specify where the query begins in the dataset. Use the `limit` parameter to set the number of records returned. For a detailed example of how to use `getStream` and retrieve monitoring data, see our guides [How to get monitoring data](/docs/monitor-api/how-to/get-monitoring-data/) and [Tracking data and handling alerts](/docs/monitor-api/monitor101/tracking-data-handling-alerts/). **Note**: data is not guaranteed to be in the order of the timestamp of the events. It is returned in the order it was inserted into our database. ### Related topics - [How to get monitoring data](/docs/monitor-api/how-to/get-monitoring-data/) - [Tracking data and handling alerts](/docs/monitor-api/monitor101/tracking-data-handling-alerts/)
+ * @param organizationId (required)
+ * @return StreamResponse
+ */
+ public StreamResponse stream(java.util.UUID organizationId) throws ApiException {
+ return stream(organizationId, null);
+ }
+
+ /**
+ * Gets customer event data for an organization..
+ * Gets customer event data for the organization that owns the integration key. The results for this endpoint are paginated by their event ingestion time, which can be up to five minutes after the event originally occurred. Use the `cursor` parameter to specify where the query begins in the dataset. Use the `limit` parameter to set the number of records returned. For a detailed example of how to use `getStream` and retrieve monitoring data, see our guides [How to get monitoring data](/docs/monitor-api/how-to/get-monitoring-data/) and [Tracking data and handling alerts](/docs/monitor-api/monitor101/tracking-data-handling-alerts/). **Note**: data is not guaranteed to be in the order of the timestamp of the events. It is returned in the order it was inserted into our database. ### Related topics - [How to get monitoring data](/docs/monitor-api/how-to/get-monitoring-data/) - [Tracking data and handling alerts](/docs/monitor-api/monitor101/tracking-data-handling-alerts/)
+ * @param organizationId (required)
+ * @param options for modifying the method behavior.
+ * @return StreamResponse
+ * @throws ApiException if fails to make API call
+ */
+ public StreamResponse stream(java.util.UUID organizationId, DocuMonitorApi.StreamOptions options) throws ApiException {
+ ApiResponse localVarResponse = streamWithHttpInfo(organizationId, options);
+ return localVarResponse.getData();
+ }
+
+ /**
+ * Gets customer event data for an organization.
+ * Gets customer event data for the organization that owns the integration key. The results for this endpoint are paginated by their event ingestion time, which can be up to five minutes after the event originally occurred. Use the `cursor` parameter to specify where the query begins in the dataset. Use the `limit` parameter to set the number of records returned. For a detailed example of how to use `getStream` and retrieve monitoring data, see our guides [How to get monitoring data](/docs/monitor-api/how-to/get-monitoring-data/) and [Tracking data and handling alerts](/docs/monitor-api/monitor101/tracking-data-handling-alerts/). **Note**: data is not guaranteed to be in the order of the timestamp of the events. It is returned in the order it was inserted into our database. ### Related topics - [How to get monitoring data](/docs/monitor-api/how-to/get-monitoring-data/) - [Tracking data and handling alerts](/docs/monitor-api/monitor101/tracking-data-handling-alerts/)
+ * @param organizationId (required)
+ * @param options for modifying the method behavior.
+ * @return StreamResponse
+ * @throws ApiException if fails to make API call
+ */
+ public ApiResponse streamWithHttpInfo(java.util.UUID organizationId, DocuMonitorApi.StreamOptions options) throws ApiException {
+ Object localVarPostBody = "{}";
+
+ // verify the required parameter 'organizationId' is set
+ if (organizationId == null) {
+ throw new ApiException(400, "Missing the required parameter 'organizationId' when calling stream");
+ }
+
+ // create path and map variables
+ String localVarPath = "/v1/organizations/{organizationId}/stream"
+ .replaceAll("\\{" + "organizationId" + "\\}", apiClient.escapeString(organizationId.toString()));
+
+ // query params
+ java.util.List localVarQueryParams = new java.util.ArrayList();
+ java.util.List localVarCollectionQueryParams = new java.util.ArrayList();
+ java.util.Map localVarHeaderParams = new java.util.HashMap();
+ java.util.Map localVarFormParams = new java.util.HashMap();
+
+ if (options != null) {
+ localVarQueryParams.addAll(apiClient.parameterToPair("cursor", options.cursor));
+ }if (options != null) {
+ localVarQueryParams.addAll(apiClient.parameterToPair("limit", options.limit));
+ }
+
+
+
+
+
+ final String[] localVarAccepts = {
+ "application/json"
+ };
+ final String localVarAccept = apiClient.selectHeaderAccept(localVarAccepts);
+
+ final String[] localVarContentTypes = {
+ "application/json"
+ };
+ final String localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes);
+
+ String[] localVarAuthNames = new String[] { "docusignAccessCode" };
+
+ GenericType localVarReturnType = new GenericType() {};
+ StreamResponse localVarResponse = apiClient.invokeAPI(localVarPath, "GET", localVarQueryParams, localVarCollectionQueryParams, localVarPostBody, localVarHeaderParams, localVarFormParams, localVarAccept, localVarContentType, localVarAuthNames, localVarReturnType);
+ return new ApiResponse(apiClient.getStatusCode(), apiClient.getResponseHeaders(), localVarResponse);
+ }
+}
diff --git a/src/main/java/com/docusign/monitor/client/ApiClient.java b/src/main/java/com/docusign/monitor/client/ApiClient.java
index acf535e..bea36fc 100644
--- a/src/main/java/com/docusign/monitor/client/ApiClient.java
+++ b/src/main/java/com/docusign/monitor/client/ApiClient.java
@@ -9,10 +9,6 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
-import com.migcomponents.migbase64.Base64;
-import org.apache.oltu.oauth2.client.request.OAuthClientRequest.AuthenticationRequestBuilder;
-import org.apache.oltu.oauth2.client.request.OAuthClientRequest.TokenRequestBuilder;
-import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.HttpUrlConnectorProvider;
@@ -34,14 +30,19 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.*;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import org.glassfish.jersey.logging.LoggingFeature;
+import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.NoSuchAlgorithmException;
+import javax.net.ssl.TrustManagerFactory;
+import java.util.Arrays;
+import java.util.logging.Logger;
import java.text.DateFormat;
import java.util.*;
import java.util.Map.Entry;
@@ -57,16 +58,16 @@
**/
public class ApiClient {
+ private static final Logger logger = Logger.getLogger(ApiClient.class.getName());
+
protected Map defaultHeaderMap = new HashMap();
// Rest API base path constants
/** live/production base path. */
public final static String PRODUCTION_REST_BASEPATH = "https://www.docusign.net/restapi";
/** sandbox/demo base path. */
public final static String DEMO_REST_BASEPATH = "https://demo.docusign.net/restapi";
- /** stage base path. */
- public final static String STAGE_REST_BASEPATH = "https://stage.docusign.net/restapi";
- private String basePath = PRODUCTION_REST_BASEPATH;
+ private String basePath = DEMO_REST_BASEPATH;
private String oAuthBasePath = OAuth.PRODUCTION_OAUTH_BASEPATH;
protected boolean debugging = false;
protected int connectionTimeout = 0;
@@ -84,11 +85,32 @@ public class ApiClient {
protected DateFormat dateFormat;
private SSLContext sslContext = null;
- /**
- * ApiClient constructor.
- *
- **/
- public ApiClient() {
+ private final String HTTPS = "https://";
+
+ /**
+ * When false (the default), the SDK uses the system's default TrustManager and
+ * HostnameVerifier for secure HTTPS connections and enforces HTTPS-only base paths.
+ * When true, allows insecure connections: trusts all certificates, skips hostname
+ * verification, and allows HTTP base paths. Only enable for testing purposes.
+ */
+ private boolean allowInsecureConnections = false;
+
+ /**
+ * When true, proxy credentials from system properties are applied per-connection
+ * using the Proxy-Authorization header instead of the JVM-wide Authenticator.setDefault().
+ * This avoids leaking credentials to unrelated HTTP clients in the same JVM.
+ * Default is false (JVM-wide Authenticator) for backward compatibility.
+ */
+ private boolean perConnectionProxyAuth = false;
+
+ private ApiClient(String basePath, boolean allowInsecureConnections, boolean perConnectionProxyAuth) {
+ this.allowInsecureConnections = allowInsecureConnections;
+ this.perConnectionProxyAuth = perConnectionProxyAuth;
+ if (basePath != null) {
+ validateBasePath(basePath);
+ this.basePath = basePath;
+ }
+
json = new JSON();
httpClient = buildHttpClient(debugging);
@@ -96,16 +118,33 @@ public ApiClient() {
String javaVersion = System.getProperty("java.version");
// Set default User-Agent.
- setUserAgent("/SDK/1.4.0/Java/");
+ setUserAgent("Swagger-Codegen/v3.0/2.0.0/Java/" + javaVersion);
// Setup authentications (key: authentication name, value: authentication).
authentications = new HashMap();
- authentications.put("docusignAccessCode", new OAuth());
+ authentications.put("docusignAccessCode", new OAuth(httpClient));
- // Derive the OAuth base path from the Rest API base url
this.deriveOAuthBasePathFromRestBasePath();
}
+ /**
+ * ApiClient constructor.
+ *
+ **/
+ public ApiClient() {
+ this(null, false, false);
+ }
+
+ /**
+ * ApiClient constructor.
+ *
+ * @param perConnectionProxyAuth true to use per-connection proxy authentication
+ * from construction time instead of the JVM-wide Authenticator
+ **/
+ public ApiClient(boolean perConnectionProxyAuth) {
+ this(null, false, perConnectionProxyAuth);
+ }
+
/**
* buildDefaultDateFormat method.
*
@@ -118,12 +157,11 @@ public static DateFormat buildDefaultDateFormat() {
/**
* ApiClient constructor.
*
- * @param basePath The base path to create the client with
+ * @param basePath The base path to create the client with. Must use HTTPS.
+ * @throws IllegalArgumentException if basePath uses HTTP
**/
public ApiClient(String basePath) {
- this();
- this.basePath = basePath;
- this.deriveOAuthBasePathFromRestBasePath();
+ this(basePath, false, false);
}
/**
@@ -133,12 +171,12 @@ public ApiClient(String basePath) {
* @param authNames The authentication names
**/
public ApiClient(String oAuthBasePath, String[] authNames) {
- this();
+ this(null, false, false);
this.setOAuthBasePath(oAuthBasePath);
for(String authName : authNames) {
Authentication auth;
if ("docusignAccessCode".equals(authName)) {
- auth = new OAuth(httpClient, OAuthFlow.accessCode, oAuthBasePath + "/oauth/auth", oAuthBasePath + "/oauth/token", "all");
+ auth = new OAuth(httpClient, OAuthFlow.accessCode, HTTPS + oAuthBasePath + "/oauth/auth", HTTPS + oAuthBasePath + "/oauth/token", "all");
} else if ("docusignApiKey".equals(authName)) {
auth = new ApiKeyAuth("header", "docusignApiKey");
} else {
@@ -166,15 +204,52 @@ public ApiClient(String oAuthBasePath, String authName) {
*/
public ApiClient(String oAuthBasePath, String authName, String clientId, String secret) {
this(oAuthBasePath, authName);
- this.getTokenEndPoint()
- .setClientId(clientId)
- .setClientSecret(secret);
+ Authentication auth = authentications.get(authName);
+ if (auth instanceof OAuth) {
+ ((OAuth) auth).setClientId(clientId);
+ ((OAuth) auth).setClientSecret(secret);
+ }
+ }
+
+ /**
+ * Creates an ApiClient with insecure connections enabled.
+ * This disables TLS certificate validation, hostname verification, and allows HTTP base paths.
+ *
+ * WARNING: Only use for local testing with self-signed certificates or HTTP endpoints.
+ * Never use in production.
+ *
+ *
+ * @return an ApiClient with insecure connections enabled
+ */
+ public static ApiClient insecure() {
+ logger.warning("Insecure mode enabled - TLS certificate validation and hostname verification are disabled. Do not use in production.");
+ return new ApiClient(null, true, false);
+ }
+
+ /**
+ * Creates an ApiClient with insecure connections enabled and the given base path.
+ * This disables TLS certificate validation, hostname verification, and allows HTTP base paths.
+ *
+ * WARNING: Only use for local testing with self-signed certificates or HTTP endpoints.
+ * Never use in production.
+ *
+ *
+ * @param basePath The base path (HTTP or HTTPS) to create the client with.
+ * @return an ApiClient with insecure connections enabled and the given base path
+ */
+ public static ApiClient insecure(String basePath) {
+ logger.warning("Insecure mode enabled - TLS certificate validation and hostname verification are disabled. Do not use in production.");
+ return new ApiClient(basePath, true, false);
}
/**
* Build the Client used to make HTTP requests with the latest settings,
* i.e. objectMapper and debugging.
- * TODO: better to use the Builder Pattern?
* @return API client
*/
public ApiClient rebuildHttpClient() {
@@ -247,11 +322,30 @@ public String getBasePath() {
* @return ApiClient
*/
public ApiClient setBasePath(String basePath) {
+ validateBasePath(basePath);
this.basePath = basePath;
this.deriveOAuthBasePathFromRestBasePath();
return this;
}
+ /**
+ * Validates that the base path uses HTTPS protocol.
+ * HTTP connections are not allowed unless allowInsecureConnections is enabled.
+ *
+ * @param basePath The base path to validate
+ * @throws IllegalArgumentException if the base path uses HTTP and insecure connections are not allowed
+ */
+ private void validateBasePath(String basePath) {
+ if (basePath != null && !allowInsecureConnections) {
+ String lowerCasePath = basePath.toLowerCase();
+ if (lowerCasePath.startsWith("http://")) {
+ throw new IllegalArgumentException(
+ "HTTP connections are not allowed. Use HTTPS for secure connections. " +
+ "If you need HTTP for testing, use ApiClient.insecure(url).");
+ }
+ }
+ }
+
/**
* Gets the status code of the previous request.
* @return Status code
@@ -268,6 +362,48 @@ public Map> getResponseHeaders() {
return responseHeaders;
}
+ /**
+ * Returns whether insecure connections are allowed.
+ * @return true if insecure connections are allowed, false otherwise
+ */
+ public boolean isAllowInsecureConnections() {
+ return allowInsecureConnections;
+ }
+
+ /**
+ * Returns whether per-connection proxy authentication is enabled.
+ * When true, proxy credentials are applied per-connection using the Proxy-Authorization
+ * header instead of the JVM-wide Authenticator.setDefault().
+ * @return true if per-connection proxy auth is enabled, false otherwise
+ */
+ public boolean isPerConnectionProxyAuth() {
+ return perConnectionProxyAuth;
+ }
+
+ /**
+ * Sets whether to use per-connection proxy authentication.
+ *
+ * By default (when false), proxy credentials from system properties (e.g. https.proxyUser)
+ * are configured JVM-wide via Authenticator.setDefault() with host/port scoping for
+ * backward compatibility.
+ *
+ * When set to true, proxy credentials are applied per-connection using the
+ * Proxy-Authorization header, which is safer and scoped to this client's connections only.
+ * This avoids leaking credentials to other HTTP clients running in the same JVM.
+ *
+ * Prefer the constructor overloads when this must be fixed at client construction time.
+ * If you use this setter, call it before the first request.
+ *
+ * Note: In a future major version, per-connection proxy auth will become the default.
+ *
+ * @param perConnectionProxyAuth true to use per-connection proxy auth instead of JVM-wide
+ * @return this ApiClient for method chaining
+ */
+ public ApiClient setPerConnectionProxyAuth(boolean perConnectionProxyAuth) {
+ this.perConnectionProxyAuth = perConnectionProxyAuth;
+ return this;
+ }
+
/**
* Get authentications (key: authentication name, value: authentication).
* @return Map of authentication object
@@ -378,7 +514,7 @@ public void setAccessToken(final String accessToken, final Long expiresIn) {
return;
}
}
- OAuth oAuth = new OAuth(null, null, null);
+ OAuth oAuth = new OAuth();
oAuth.setAccessToken(accessToken, expiresIn);
addAuthorization("docusignAccessCode", oAuth);
}
@@ -523,35 +659,6 @@ public ApiClient setDateFormat(DateFormat dateFormat) {
return this;
}
- /**
- * Helper method to configure the token endpoint of the first oauth found in the authentications (there should be only one).
- * @return
- */
- public TokenRequestBuilder getTokenEndPoint() {
- for(Authentication auth : getAuthentications().values()) {
- if (auth instanceof OAuth) {
- OAuth oauth = (OAuth) auth;
- return oauth.getTokenRequestBuilder();
- }
- }
- return null;
- }
-
-
- /**
- * Helper method to configure authorization endpoint of the first oauth found in the authentications (there should be only one).
- * @return
- */
- public AuthenticationRequestBuilder getAuthorizationEndPoint() {
- for(Authentication auth : authentications.values()) {
- if (auth instanceof OAuth) {
- OAuth oauth = (OAuth) auth;
- return oauth.getAuthenticationRequestBuilder();
- }
- }
- return null;
- }
-
/**
* Helper method to configure the OAuth accessCode/implicit flow parameters.
* @param clientId OAuth2 client ID
@@ -562,20 +669,22 @@ public void configureAuthorizationFlow(String clientId, String clientSecret, Str
for(Authentication auth : authentications.values()) {
if (auth instanceof OAuth) {
OAuth oauth = (OAuth) auth;
- oauth.getTokenRequestBuilder()
- .setClientId(clientId)
- .setClientSecret(clientSecret)
- .setRedirectURI(redirectURI);
- oauth.getAuthenticationRequestBuilder()
- .setClientId(clientId)
- .setRedirectURI(redirectURI);
+ ((OAuth) auth).setClientId(clientId);
+ ((OAuth) auth).setClientSecret(clientSecret);
+ ((OAuth) auth).setRedirectURI(redirectURI);
return;
}
}
}
- public String getAuthorizationUri() throws OAuthSystemException {
- return getAuthorizationEndPoint().buildQueryMessage().getLocationUri();
+ public String getAuthorizationUri() {
+ for(Authentication auth : authentications.values()) {
+ if (auth instanceof OAuth) {
+ OAuth oauth = (OAuth) auth;
+ return oauth.getAuthorizationUrl();
+ }
+ }
+ return null;
}
/**
@@ -630,11 +739,9 @@ public URI getAuthorizationUri(String clientId, java.util.List scopes, S
private void deriveOAuthBasePathFromRestBasePath() {
if (this.basePath == null) { // this case should not happen but just in case
- this.oAuthBasePath = OAuth.PRODUCTION_OAUTH_BASEPATH;
- } else if (this.basePath.startsWith("https://demo") || this.basePath.startsWith("http://demo")) {
this.oAuthBasePath = OAuth.DEMO_OAUTH_BASEPATH;
- } else if (this.basePath.startsWith("https://stage") || this.basePath.startsWith("http://stage")) {
- this.oAuthBasePath = OAuth.STAGE_OAUTH_BASEPATH;
+ } else if (this.basePath.startsWith("https://demo") || this.basePath.startsWith("http://demo") || this.basePath.startsWith("https://apps-d") || this.basePath.startsWith("http://apps-d")) {
+ this.oAuthBasePath = OAuth.DEMO_OAUTH_BASEPATH;
} else {
this.oAuthBasePath = OAuth.PRODUCTION_OAUTH_BASEPATH;
}
@@ -645,11 +752,18 @@ private String getOAuthBasePath() {
}
/**
- * Sets the OAuth base path. Values include {@link OAuth#PRODUCTION_OAUTH_BASEPATH}, {@link OAuth#DEMO_OAUTH_BASEPATH} and custom (e.g. "account-s.docusign.com").
+ * Sets the OAuth base path. Values include {@link OAuth#PRODUCTION_OAUTH_BASEPATH}, {@link OAuth#DEMO_OAUTH_BASEPATH} and custom.
* @param oAuthBasePath the new value for the OAuth base path
* @return this instance of the ApiClient updated with the new OAuth base path
+ * @throws IllegalArgumentException if the OAuth base path uses HTTP and insecure connections are not allowed
*/
public ApiClient setOAuthBasePath(String oAuthBasePath) {
+ if (oAuthBasePath != null && !allowInsecureConnections
+ && oAuthBasePath.toLowerCase().startsWith("http://")) {
+ throw new IllegalArgumentException(
+ "HTTP connections are not allowed for OAuth base path. Use HTTPS for secure connections. " +
+ "If you need HTTP for testing, use ApiClient.insecure().");
+ }
this.oAuthBasePath = oAuthBasePath;
return this;
}
@@ -659,7 +773,7 @@ public ApiClient setOAuthBasePath(String oAuthBasePath) {
*
* @param clientId OAuth2 client ID: Identifies the client making the request.
* Client applications may be scoped to a limited set of system access.
- * @param clientSecret the secret key you generated when you set up the integration in DocuSign Admin console.
+ * @param clientSecret the secret key you generated when you set up the integration in Docusign Admin console.
* @param code The authorization code that you received from the getAuthorizationUri callback.
* @return OAuth.OAuthToken object.
* @throws ApiException if the HTTP call status is different than 2xx.
@@ -677,7 +791,7 @@ public OAuth.OAuthToken generateAccessToken(String clientId, String clientSecret
Invocation.Builder invocationBuilder = target.request();
invocationBuilder = invocationBuilder
- .header("Authorization", "Basic " + Base64.encodeToString(clientStr.getBytes("UTF-8"), false))
+ .header("Authorization", "Basic " + Base64.getEncoder().encodeToString(clientStr.getBytes(StandardCharsets.UTF_8)))
.header("Cache-Control", "no-store")
.header("Pragma", "no-cache");
@@ -808,13 +922,13 @@ public String getJWTUri(String clientId, String redirectURI, String oAuthBasePat
}
/**
- * Configures the current instance of ApiClient with a fresh OAuth JWT access token from DocuSign.
+ * Configures the current instance of ApiClient with a fresh OAuth JWT access token from Docusign.
* @param publicKeyFilename the filename of the RSA public key
* @param privateKeyFilename the filename of the RSA private key
- * @param oAuthBasePath DocuSign OAuth base path (account-d.docusign.com for the developer sandbox
+ * @param oAuthBasePath Docusign OAuth base path (account-d.docusign.com for the developer sandbox
and account.docusign.com for the production platform)
- * @param clientId DocuSign OAuth Client Id (AKA Integrator Key)
- * @param userId DocuSign user Id to be impersonated (This is a UUID)
+ * @param clientId Docusign OAuth Client Id (AKA Integrator Key)
+ * @param userId Docusign user Id to be impersonated (This is a UUID)
* @param expiresIn number of seconds remaining before the JWT assertion is considered as invalid
* @throws ApiException if there is an error while exchanging the JWT with an access token
* @throws IOException if there is an issue with either the public or private file
@@ -878,9 +992,9 @@ public String getJWTUri(String clientId, String redirectURI, String oAuthBasePat
}
/**
- * Configures the current instance of ApiClient with a fresh OAuth JWT access token from DocuSign.
- * @param clientId DocuSign OAuth Client Id (AKA Integrator Key)
- * @param userId DocuSign user Id to be impersonated (This is a UUID)
+ * Configures the current instance of ApiClient with a fresh OAuth JWT access token from Docusign.
+ * @param clientId Docusign OAuth Client Id (AKA Integrator Key)
+ * @param userId Docusign user Id to be impersonated (This is a UUID)
* @param scopes the list of requested scopes. Values include {@link OAuth#Scope_SIGNATURE}, {@link OAuth#Scope_EXTENDED}, {@link OAuth#Scope_IMPERSONATION}. You can also pass any advanced scope.
* @param rsaPrivateKey the byte contents of the RSA private key
* @param expiresIn number of seconds remaining before the JWT assertion is considered as invalid
@@ -952,8 +1066,8 @@ public OAuth.OAuthToken requestJWTUserToken(String clientId, String userId, java
/**
* RESERVED FOR PARTNERS Request JWT Application Token.
- * Configures the current instance of ApiClient with a fresh OAuth JWT access token from DocuSign
- * @param clientId DocuSign OAuth Client Id (AKA Integrator Key)
+ * Configures the current instance of ApiClient with a fresh OAuth JWT access token from Docusign
+ * @param clientId Docusign OAuth Client Id (AKA Integrator Key)
* @param scopes the list of requested scopes. Values include {@link OAuth#Scope_SIGNATURE}, {@link OAuth#Scope_EXTENDED}, {@link OAuth#Scope_IMPERSONATION}. You can also pass any advanced scope.
* @param rsaPrivateKey the byte contents of the RSA private key
* @param expiresIn number of seconds remaining before the JWT assertion is considered as invalid
@@ -1388,6 +1502,12 @@ public File prepareDownloadFile(Response response) throws IOException {
*/
public T invokeAPI(String path, String method, List queryParams, List collectionQueryParams, Object
body, Map headerParams, Map formParams, String accept, String contentType, String[] authNames, GenericType returnType) throws ApiException {
+ // Defense-in-depth: ensure HTTPS is used unless insecure mode is explicitly enabled
+ if (!allowInsecureConnections && basePath != null && basePath.toLowerCase().startsWith("http://")) {
+ throw new ApiException(0, "HTTPS is required. The current basePath ('" + basePath + "') uses HTTP. " +
+ "Use an HTTPS URL or create the client via ApiClient.insecure() for testing.");
+ }
+
updateParamsForAuth(authNames, queryParams, headerParams);
// Not using `.target(this.basePath).path(path)` below,
@@ -1449,7 +1569,7 @@ public T invokeAPI(String path, String method, List queryParams, List<
}
}
- // Add DocuSign Tracking Header
+ // Add Docusign Tracking Header
invocationBuilder = invocationBuilder.header("X-DocuSign-SDK", "Java");
if (body == null && formParams.isEmpty()) {
@@ -1643,24 +1763,33 @@ protected Client buildHttpClient(boolean debugging) {
throw new SecurityException("Docusign Java SDK requires TLSv1.2 Protocol");
}
} catch (SecurityException se) {
- System.err.println(se.getMessage());
+ throw new RuntimeException("TLS requirement check failed: " + se.getMessage(), se);
} catch (NoSuchAlgorithmException nsae) {
- System.err.println(nsae.getMessage());
+ throw new RuntimeException("TLS requirement check failed: " + nsae.getMessage(), nsae);
}
// Setup the SSLContext object to use for HTTPS connections to the API
if (sslContext == null) {
try {
sslContext = SSLContext.getInstance("TLSv1.2");
- sslContext.init(null, new TrustManager[] { new SecureTrustManager() }, new SecureRandom());
+ if (allowInsecureConnections) {
+ // Warning: This trusts all certificates - only use for testing
+ logger.warning("Building HTTP client with insecure SSL context - all certificates will be trusted.");
+ sslContext.init(null, new TrustManager[] { new InsecureTrustManager() }, new SecureRandom());
+ } else {
+ // Use the system's default TrustManager for secure certificate validation
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init((KeyStore) null); // Use default system trust store
+ sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
+ }
} catch (final Exception ex) {
- System.err.println("failed to initialize SSL context");
+ throw new RuntimeException("Failed to initialize SSL context: " + ex.getMessage(), ex);
}
- HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
}
clientConfig.connectorProvider(new ConnectorProvider() {
Proxy p = null;
+ String proxyAuthHeader = null;
/*
* Returns whether the host is part of the list of hosts that should be accessed without going through the proxy
@@ -1694,6 +1823,56 @@ private boolean isNonProxyHost(String host, String nonProxyHosts) {
return false;
}
+ /**
+ * Configures SSL and proxy authentication on the given connection.
+ */
+ private HttpURLConnection configureConnection(HttpURLConnection connection, Proxy usedProxy) {
+ if ("https".equalsIgnoreCase(connection.getURL().getProtocol())) {
+ HttpsURLConnection httpsConn = (HttpsURLConnection) connection;
+ httpsConn.setSSLSocketFactory(sslContext.getSocketFactory());
+ if (allowInsecureConnections) {
+ httpsConn.setHostnameVerifier(new InsecureHostnameVerifier());
+ }
+ }
+ // Apply proxy credentials per-connection only when explicitly opted in
+ if (perConnectionProxyAuth && proxyAuthHeader != null && usedProxy != null && usedProxy != Proxy.NO_PROXY) {
+ connection.setRequestProperty("Proxy-Authorization", proxyAuthHeader);
+ }
+ return connection;
+ }
+
+ /**
+ * Initializes proxy credentials from system properties.
+ * By default, configures JVM-wide Authenticator.setDefault() with host/port scoping
+ * for backward compatibility. When perConnectionProxyAuth is true, only uses
+ * per-connection Proxy-Authorization header.
+ */
+ private void initProxyAuth(final String host, final Integer port, String user, String password) {
+ proxyAuthHeader = null;
+ if (user != null && password != null) {
+ if (!perConnectionProxyAuth) {
+ if (host != null && port != null) {
+ final String proxyHost = host;
+ final int proxyPort = port;
+ Authenticator.setDefault(new Authenticator() {
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ if (getRequestorType() == RequestorType.PROXY
+ && getRequestingHost().equalsIgnoreCase(proxyHost)
+ && proxyPort == getRequestingPort()) {
+ return new PasswordAuthentication(user, password.toCharArray());
+ }
+ return null;
+ }
+ });
+ }
+ } else {
+ proxyAuthHeader = "Basic " + Base64.getEncoder().encodeToString(
+ (user + ":" + password).getBytes(StandardCharsets.UTF_8));
+ }
+ }
+ }
+
@Override
public Connector getConnector(Client client, jakarta.ws.rs.core.Configuration configuration) {
HttpUrlConnectorProvider customConnProv = new HttpUrlConnectorProvider();
@@ -1705,55 +1884,34 @@ public HttpURLConnection getConnection(java.net.URL url) throws IOException {
}
if (isNonProxyHost(url.getHost(), System.getProperty("http.nonProxyHosts"))) {
- HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(Proxy.NO_PROXY);
- connection.setSSLSocketFactory(sslContext.getSocketFactory());
- return connection;
+ return configureConnection(
+ (HttpURLConnection) url.openConnection(Proxy.NO_PROXY), Proxy.NO_PROXY);
}
// set up the proxy/no-proxy settings
if (p == null) {
if (System.getProperty("https.proxyHost") != null) {
// set up the proxy host and port
- final String host = System.getProperty("https.proxyHost");
- final Integer port = Integer.getInteger("https.proxyPort");
- if (host != null && port != null) {
- p = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port));
- }
- // set up optional proxy authentication credentials
- final String user = System.getProperty("https.proxyUser");
- final String password = System.getProperty("https.proxyPassword");
- if (user != null && password != null) {
- Authenticator.setDefault(new Authenticator() {
- @Override
- protected PasswordAuthentication getPasswordAuthentication() {
- if (getRequestorType() == RequestorType.PROXY && getRequestingHost().equalsIgnoreCase(host) && port == getRequestingPort()) {
- return new PasswordAuthentication(user, password.toCharArray());
- }
- return null;
- }
- });
- }
+ final String host = System.getProperty("https.proxyHost");
+ final Integer port = Integer.getInteger("https.proxyPort");
+ if (host != null && port != null) {
+ p = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port));
+ }
+ // set up proxy authentication credentials using the configured mode
+ initProxyAuth(host, port,
+ System.getProperty("https.proxyUser"),
+ System.getProperty("https.proxyPassword"));
} else if (System.getProperty("http.proxyHost") != null) {
// set up the proxy host and port
- final String host = System.getProperty("http.proxyHost");
- final Integer port = Integer.getInteger("http.proxyPort");
- if (host != null && port != null) {
- p = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port));
- }
- // set up optional proxy authentication credentials
- final String user = System.getProperty("http.proxyUser");
- final String password = System.getProperty("http.proxyPassword");
- if (user != null && password != null) {
- Authenticator.setDefault(new Authenticator() {
- @Override
- protected PasswordAuthentication getPasswordAuthentication() {
- if (getRequestorType() == RequestorType.PROXY && getRequestingHost().equalsIgnoreCase(host) && port == getRequestingPort()) {
- return new PasswordAuthentication(user, password.toCharArray());
- }
- return null;
- }
- });
- }
+ final String host = System.getProperty("http.proxyHost");
+ final Integer port = Integer.getInteger("http.proxyPort");
+ if (host != null && port != null) {
+ p = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port));
+ }
+ // set up proxy authentication credentials using the configured mode
+ initProxyAuth(host, port,
+ System.getProperty("http.proxyUser"),
+ System.getProperty("http.proxyPassword"));
}
// no-proxy fallback if the proxy settings are misconfigured in the system properties
if (p == null) {
@@ -1761,11 +1919,8 @@ protected PasswordAuthentication getPasswordAuthentication() {
}
}
- HostnameVerifier allHostsValid = new InsecureHostnameVerifier();
- HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(p);
- connection.setSSLSocketFactory(sslContext.getSocketFactory());
- connection.setHostnameVerifier(allHostsValid);
- return connection;
+ return configureConnection(
+ (HttpURLConnection) url.openConnection(p), p);
}
});
return customConnProv.getConnector(client, configuration);
@@ -1788,16 +1943,23 @@ public boolean verify(String hostname, SSLSession session) {
}
}
- class SecureTrustManager implements X509TrustManager {
+ /**
+ * WARNING: This TrustManager trusts ALL certificates without validation.
+ * It should ONLY be used when allowInsecureConnections is explicitly enabled for testing.
+ * Using this in production is a critical security vulnerability.
+ */
+ class InsecureTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
+ // WARNING: No validation - trusts all client certificates
}
@Override
public void checkServerTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
+ // WARNING: No validation - trusts all server certificates
}
@Override
diff --git a/src/main/java/com/docusign/monitor/client/auth/AccessTokenListener.java b/src/main/java/com/docusign/monitor/client/auth/AccessTokenListener.java
index bc79753..57d5f3f 100644
--- a/src/main/java/com/docusign/monitor/client/auth/AccessTokenListener.java
+++ b/src/main/java/com/docusign/monitor/client/auth/AccessTokenListener.java
@@ -1,7 +1,7 @@
package com.docusign.monitor.client.auth;
-import org.apache.oltu.oauth2.common.token.BasicOAuthToken;
+import com.docusign.monitor.client.auth.OAuth.OAuthToken;
public interface AccessTokenListener {
- void notify(BasicOAuthToken token);
-}
+ void notify(OAuthToken token);
+}
\ No newline at end of file
diff --git a/src/main/java/com/docusign/monitor/client/auth/JWTUtils.java b/src/main/java/com/docusign/monitor/client/auth/JWTUtils.java
index 596b348..c0b250c 100644
--- a/src/main/java/com/docusign/monitor/client/auth/JWTUtils.java
+++ b/src/main/java/com/docusign/monitor/client/auth/JWTUtils.java
@@ -34,10 +34,10 @@ public class JWTUtils {
* Helper method to create a JWT token for the JWT flow.
*
* @param rsaPrivateKey the byte contents of the RSA private key
- * @param oAuthBasePath DocuSign OAuth base path (account-d.docusign.com for the developer sandbox
+ * @param oAuthBasePath Docusign OAuth base path (account-d.docusign.com for the developer sandbox
* and account.docusign.com for the production platform)
- * @param clientId DocuSign OAuth Client Id (AKA Integrator Key)
- * @param userId DocuSign user Id to be impersonated (This is a UUID)
+ * @param clientId Docusign OAuth Client Id (AKA Integrator Key)
+ * @param userId Docusign user Id to be impersonated (This is a UUID)
* @param expiresIn number of seconds remaining before the JWT assertion is considered as invalid
* @param scopes space-separated string that represents the list of scopes to grant to the OAuth
* token.
@@ -88,10 +88,10 @@ public static String generateJWTAssertionFromByteArray(
*
* @param publicKeyFilename the filename of the RSA public key
* @param privateKeyFilename the filename of the RSA private key
- * @param oAuthBasePath DocuSign OAuth base path (account-d.docusign.com for the developer sandbox
+ * @param oAuthBasePath Docusign OAuth base path (account-d.docusign.com for the developer sandbox
* and account.docusign.com for the production platform)
- * @param clientId DocuSign OAuth Client Id (AKA Integrator Key)
- * @param userId DocuSign user Id to be impersonated (This is a UUID)
+ * @param clientId Docusign OAuth Client Id (AKA Integrator Key)
+ * @param userId Docusign user Id to be impersonated (This is a UUID)
* @param expiresIn number of seconds remaining before the JWT assertion is considered as invalid
* @return a fresh JWT token
* @throws JWTCreationException if not able to create a JWT token from the input parameters
@@ -113,10 +113,10 @@ public static String generateJWTAssertion(
*
* @param publicKeyFilename the filename of the RSA public key
* @param privateKeyFilename the filename of the RSA private key
- * @param oAuthBasePath DocuSign OAuth base path (account-d.docusign.com for the developer sandbox
+ * @param oAuthBasePath Docusign OAuth base path (account-d.docusign.com for the developer sandbox
* and account.docusign.com for the production platform)
- * @param clientId DocuSign OAuth Client Id (AKA Integrator Key)
- * @param userId DocuSign user Id to be impersonated (This is a UUID)
+ * @param clientId Docusign OAuth Client Id (AKA Integrator Key)
+ * @param userId Docusign user Id to be impersonated (This is a UUID)
* @param expiresIn number of seconds remaining before the JWT assertion is considered as invalid
* @param scopes space-separated string that represents the list of scopes to grant to the OAuth
* token.
diff --git a/src/main/java/com/docusign/monitor/client/auth/OAuth.java b/src/main/java/com/docusign/monitor/client/auth/OAuth.java
index 4fcae99..f5397e4 100644
--- a/src/main/java/com/docusign/monitor/client/auth/OAuth.java
+++ b/src/main/java/com/docusign/monitor/client/auth/OAuth.java
@@ -1,26 +1,23 @@
package com.docusign.monitor.client.auth;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.text.DateFormat;
+import java.util.*;
-import jakarta.ws.rs.client.ClientBuilder;
+import com.docusign.monitor.client.RFC3339DateFormat;
+
+import jakarta.ws.rs.client.*;
+import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.GenericType;
+import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import com.docusign.monitor.client.ApiException;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import org.apache.oltu.oauth2.client.OAuthClient;
-import org.apache.oltu.oauth2.client.URLConnectionClient;
-import org.apache.oltu.oauth2.client.request.OAuthClientRequest;
-import org.apache.oltu.oauth2.client.response.OAuthJSONAccessTokenResponse;
-import org.apache.oltu.oauth2.client.request.OAuthClientRequest.AuthenticationRequestBuilder;
-import org.apache.oltu.oauth2.client.request.OAuthClientRequest.TokenRequestBuilder;
-import org.apache.oltu.oauth2.common.message.types.GrantType;
-import org.apache.oltu.oauth2.common.token.BasicOAuthToken;
import com.docusign.monitor.client.Pair;
import com.fasterxml.jackson.annotation.JsonProperty;
-import jakarta.ws.rs.client.Client;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -50,39 +47,43 @@ public class OAuth implements Authentication {
public final static String PRODUCTION_OAUTH_BASEPATH = "account.docusign.com";
/** sandbox/demo base path. */
public final static String DEMO_OAUTH_BASEPATH = "account-d.docusign.com";
- /** stage base path. */
- public final static String STAGE_OAUTH_BASEPATH = "account-s.docusign.com";
// OAuth grant types
/** JWT grant type. */
public final static String GRANT_TYPE_JWT = "urn:ietf:params:oauth:grant-type:jwt-bearer";
+ // Client ID and secret for oauth
+ private String clientId = null;
+ private String clientSecret = null;
+
+ // auth, token and redirect urls
+ private String authorizationUrl = null;
+ private String tokenUrl = null;
+ private String redirectURI = null;
+
+ // scopes and grant type
+ private String scope = null;
+ private OAuthFlow grantType = null;
+
private volatile String accessToken;
+ private volatile String refreshToken;
+ private volatile String authCode;
+ private volatile String jwtAssertion;
+
private Long expirationTimeMillis;
- private OAuthClient oauthClient;
- private TokenRequestBuilder tokenRequestBuilder;
- private AuthenticationRequestBuilder authenticationRequestBuilder;
private AccessTokenListener accessTokenListener;
- /**
+ private Client httpClient;
+
+
+ /**
* OAuth constructor.
*
*/
- public OAuth() {
- this(null, null, null);
- }
+ public OAuth() { }
- /**
- * OAuth constructor.
- *
- * @param client The client to use
- * @param tokenRequestBuilder The request builder
- * @param authenticationRequestBuilder The auth request builder
- */
- public OAuth(Client client, TokenRequestBuilder tokenRequestBuilder, AuthenticationRequestBuilder authenticationRequestBuilder) {
- this.oauthClient = new OAuthClient(new URLConnectionClient());
- this.tokenRequestBuilder = tokenRequestBuilder;
- this.authenticationRequestBuilder = authenticationRequestBuilder;
+ public OAuth(Client httpClient) {
+ this.httpClient = httpClient;
}
/**
@@ -95,26 +96,11 @@ public OAuth(Client client, TokenRequestBuilder tokenRequestBuilder, Authenticat
* @param scopes The scopes to use
*/
public OAuth(Client client, OAuthFlow flow, String authorizationUrl, String tokenUrl, String scopes) {
- this(client, OAuthClientRequest.tokenLocation(tokenUrl).setScope(scopes), OAuthClientRequest.authorizationLocation(authorizationUrl).setScope(scopes));
-
- switch (flow) {
- case accessCode:
- tokenRequestBuilder.setGrantType(GrantType.AUTHORIZATION_CODE);
- authenticationRequestBuilder.setResponseType(OAuth.CODE);
- break;
- case implicit:
- tokenRequestBuilder.setGrantType(GrantType.IMPLICIT);
- authenticationRequestBuilder.setResponseType(OAuth.TOKEN);
- break;
- case password:
- tokenRequestBuilder.setGrantType(GrantType.PASSWORD);
- break;
- case application:
- tokenRequestBuilder.setGrantType(GrantType.CLIENT_CREDENTIALS);
- break;
- default:
- break;
- }
+ this.httpClient = client;
+ setScope(scopes);
+ setGrantType(flow);
+ setAuthorizationUrl(authorizationUrl);
+ setTokenUrl(tokenUrl);
}
/**
@@ -155,30 +141,33 @@ public void applyToParams(List queryParams, Map headerPara
*
*/
public synchronized void updateAccessToken() throws ApiException {
- OAuthJSONAccessTokenResponse accessTokenResponse;
+ OAuthToken oauthToken = null;
try {
- accessTokenResponse = oauthClient.accessToken(tokenRequestBuilder.buildBodyMessage());
+ switch (getGrantType()) {
+ case accessCode: // Authorization Grant
+ oauthToken = generateAccessToken();
+ break;
+ case jwt: // JWT Grant
+ oauthToken = requestJWTUserToken();
+ break;
+ default: // Implicit Grant - token will already be set by consuming app using setAccessToken()
+ break;
+ }
} catch (Exception e) {
throw new ApiException(e.getMessage());
}
- if (accessTokenResponse != null)
+ if (oauthToken != null)
{
- // FIXME: This does not work in case of non HTTP 200 :-( oauthClient needs to return the plain HTTP resonse
- if (accessTokenResponse.getResponseCode() != Response.Status.OK.getStatusCode())
- {
- throw new ApiException("Error while requesting an access token, received HTTP code: " + accessTokenResponse.getResponseCode());
- }
-
- if (accessTokenResponse.getAccessToken() == null) {
+ if (oauthToken.getAccessToken() == null) {
throw new ApiException("Error while requesting an access token. No 'access_token' found.");
}
- if (accessTokenResponse.getExpiresIn() == null) {
+ if (oauthToken.getExpiresIn() == null) {
throw new ApiException("Error while requesting an access token. No 'expires_in' found.");
}
- setAccessToken(accessTokenResponse.getAccessToken(), accessTokenResponse.getExpiresIn());
+ setAccessToken(oauthToken.getAccessToken(), Long.valueOf(oauthToken.getExpiresIn()));
if (this.accessTokenListener != null) {
- this.accessTokenListener.notify((BasicOAuthToken)accessTokenResponse.getOAuthToken());
+ this.accessTokenListener.notify(oauthToken);
}
} else {
// in case of HTTP error codes accessTokenResponse is null, thus no check of accessTokenResponse.getResponseCode() possible :-(
@@ -186,6 +175,130 @@ public synchronized void updateAccessToken() throws ApiException {
}
}
+
+ /**
+ * Configures the current instance of ApiClient with a fresh OAuth JWT access token from DocuSign.
+ * @return OAuth.OAuthToken object.
+ * @throws IllegalArgumentException if one of the arguments is invalid
+ * @throws ApiException if there is an error while exchanging the JWT with an access token
+ * @throws IOException if there is an issue with either the public or private file
+ */
+ public OAuth.OAuthToken requestJWTUserToken() throws IllegalArgumentException, ApiException, IOException {
+ java.util.Map form = new java.util.HashMap<>();
+ form.put("assertion", getJwtAssertion());
+ form.put("grant_type", OAuth.GRANT_TYPE_JWT);
+
+ WebTarget target = httpClient.target(getTokenUrl());
+ Invocation.Builder invocationBuilder = target.request();
+ invocationBuilder = invocationBuilder
+ .header("Cache-Control", "no-store")
+ .header("Pragma", "no-cache");
+
+ Entity> entity = serialize(null, form, MediaType.APPLICATION_FORM_URLENCODED);
+
+ Response response = null;
+
+ try {
+ response = invocationBuilder.post(entity);
+
+ if (response.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL) {
+ String message = "error";
+ String respBody = null;
+ if (response.hasEntity()) {
+ try {
+ respBody = String.valueOf(response.readEntity(String.class));
+ message = "Error while requesting server, received a non successful HTTP code " + response.getStatusInfo().getStatusCode() + " with response Body: '" + respBody + "'";
+ } catch (RuntimeException e) {
+ // e.printStackTrace();
+ }
+ }
+ throw new ApiException(
+ response.getStatusInfo().getStatusCode(),
+ message,
+ buildResponseHeaders(response),
+ respBody);
+ }
+
+ GenericType returnType = new GenericType() {};
+ OAuth.OAuthToken oAuthToken = deserialize(response, returnType);
+ if (oAuthToken.getAccessToken() == null || "".equals(oAuthToken.getAccessToken()) || oAuthToken.getExpiresIn() <= 0) {
+ throw new ApiException("Error while requesting an access token: " + response.toString());
+ }
+ return oAuthToken;
+ } finally {
+ try {
+ if (response != null) {
+ response.close();
+ }
+ } catch (Exception e) {
+ // it's not critical, since the response object is local in method invokeAPI; that's fine, just continue
+ }
+ }
+ }
+
+ /**
+ * Helper method to configure the OAuth accessCode/implicit flow parameters.
+ *
+ * @return OAuth.OAuthToken object.
+ * @throws ApiException if the HTTP call status is different than 2xx.
+ * @throws IOException if there is a problem while parsing the reponse object.
+ * @see OAuth.OAuthToken
+ */
+ public OAuth.OAuthToken generateAccessToken() throws ApiException, IOException {
+ String clientId = getClientId();
+ String clientSecret = getClientSecret();
+ String code = getAuthCode();
+ String clientStr = (clientId == null ? "" : clientId) + ":" + (clientSecret == null ? "" : clientSecret);
+ java.util.Map form = new java.util.HashMap<>();
+ form.put("code", code);
+ form.put("grant_type", "authorization_code");
+
+ WebTarget target = httpClient.target(getTokenUrl());
+
+ Invocation.Builder invocationBuilder = target.request();
+ invocationBuilder = invocationBuilder
+ .header("Authorization", "Basic " + Base64.getEncoder().encodeToString(clientStr.getBytes(StandardCharsets.UTF_8)))
+ .header("Cache-Control", "no-store")
+ .header("Pragma", "no-cache");
+
+ Entity> entity = serialize(null, form, MediaType.APPLICATION_FORM_URLENCODED);
+
+ Response response = null;
+
+ try {
+ response = invocationBuilder.post(entity);
+
+ if (response.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL) {
+ String message = "error";
+ String respBody = null;
+ if (response.hasEntity()) {
+ try {
+ respBody = String.valueOf(response.readEntity(String.class));
+ message = "Error while requesting server, received a non successful HTTP code " + response.getStatusInfo().getStatusCode() + " with response Body: '" + respBody + "'";
+ } catch (RuntimeException e) {
+ // e.printStackTrace();
+ }
+ }
+ throw new ApiException(
+ response.getStatusInfo().getStatusCode(),
+ message,
+ buildResponseHeaders(response),
+ respBody);
+ }
+
+ GenericType returnType = new GenericType() {};
+ return deserialize(response, returnType);
+ } finally {
+ try {
+ if (response != null) {
+ response.close();
+ }
+ } catch (Exception e) {
+ // it's not critical, since the response object is local in method invokeAPI; that's fine, just continue
+ }
+ }
+ }
+
/**
* registerAccessTokenListener method.
*
@@ -204,43 +317,181 @@ public synchronized String getAccessToken() {
return accessToken;
}
+ public Long getExpirationTimeMillis() {
+ return expirationTimeMillis;
+ }
+
+ public void setExpirationTimeMillis(Long expirationTimeMillis) {
+ this.expirationTimeMillis = expirationTimeMillis;
+ }
+
public synchronized void setAccessToken(String accessToken, Long expiresIn) {
this.accessToken = accessToken;
this.expirationTimeMillis = System.currentTimeMillis() + expiresIn * MILLIS_PER_SECOND;
}
- public TokenRequestBuilder getTokenRequestBuilder() {
- return tokenRequestBuilder;
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
}
- public void setTokenRequestBuilder(TokenRequestBuilder tokenRequestBuilder) {
- this.tokenRequestBuilder = tokenRequestBuilder;
+ public String getClientSecret() {
+ return clientSecret;
}
- public AuthenticationRequestBuilder getAuthenticationRequestBuilder() {
- return authenticationRequestBuilder;
+ public void setClientSecret(String clientSecret) {
+ this.clientSecret = clientSecret;
}
- public void setAuthenticationRequestBuilder(AuthenticationRequestBuilder authenticationRequestBuilder) {
- this.authenticationRequestBuilder = authenticationRequestBuilder;
+ public String getAuthorizationUrl() {
+ return authorizationUrl;
}
- public OAuthClient getOauthClient() {
- return oauthClient;
+ public void setAuthorizationUrl(String authorizationUrl) {
+ this.authorizationUrl = authorizationUrl;
}
- public void setOauthClient(OAuthClient oauthClient) {
- this.oauthClient = oauthClient;
+ public String getTokenUrl() {
+ return tokenUrl;
}
- public void setOauthClient(Client client) {
- this.oauthClient = new OAuthClient(new URLConnectionClient());
+ public void setTokenUrl(String tokenUrl) {
+ this.tokenUrl = tokenUrl;
+ }
+
+ public String getRedirectURI() {
+ return redirectURI;
+ }
+
+ public void setRedirectURI(String redirectURI) {
+ this.redirectURI = redirectURI;
+ }
+
+ public String getScope() {
+ return scope;
+ }
+
+ public void setScope(String scope) {
+ this.scope = scope;
+ }
+
+ public OAuthFlow getGrantType() {
+ return grantType;
+ }
+
+ public void setGrantType(OAuthFlow grantType) {
+ this.grantType = grantType;
+ }
+
+ public String getJwtAssertion() {
+ return jwtAssertion;
+ }
+
+ public void setJwtAssertion(String jwtAssertion) {
+ this.jwtAssertion = jwtAssertion;
+ }
+
+ public String getAuthCode() {
+ return authCode;
+ }
+
+ public void setAuthCode(String authCode) {
+ this.authCode = authCode;
+ }
+
+
+ protected Map> buildResponseHeaders(Response response) {
+ Map> responseHeaders = new HashMap>();
+ for (Map.Entry> entry: response.getHeaders().entrySet()) {
+ List