diff --git a/CHANGELOG.md b/CHANGELOG.md
index 29148c73..80c6ca0d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,11 @@
# DocuSign Java Client Changelog
See [DocuSign Support Center](https://support.docusign.com/en/releasenotes/) for Product Release Notes.
+## [v6.0.0-RC2] - eSignature API v2.1-24.2.00.00 - 2024-07-12
+### Changed
+- Deprecation of OLTU library: The OLTU library for handling OAuth is no longer used.
+- Upgradation of OWASP for vulnerability check of dependencies.
+- Updated the SDK release version.
## [v6.0.0-RC1] - eSignature API v2.1-24.2.00.00 - 2024-06-28
### Changed
- Added support for version v2.1-24.2.00.00 of the DocuSign ESignature API.
diff --git a/README.md b/README.md
index 9d7452ae..bc463321 100644
--- a/README.md
+++ b/README.md
@@ -57,7 +57,7 @@ This client SDK is provided as open source, which enables you to customize its f
com.docusign
docusign-esign-java
- 6.0.0-RC1
+ 6.0.0-RC2
```
8. If your project is still open, restart Eclipse.
@@ -71,7 +71,6 @@ This client has the following external dependencies:
* 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
-* org.apache.oltu.oauth2:org.apache.oltu.oauth2.client:1.0.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
diff --git a/pom.xml b/pom.xml
index 08a5282a..5517e03c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
docusign-esign-java
jar
docusign-esign-java
- 6.0.0-RC1
+ 6.0.0-RC2
https://developers.docusign.com
The official DocuSign eSignature JAVA client is based on version 2.1 of the DocuSign REST API and provides libraries for JAVA application integration. It is recommended that you use this version of the library for new development.
@@ -230,7 +230,7 @@
org.owasp
dependency-check-maven
- 9.2.0
+ 10.0.2
8
@@ -356,11 +356,6 @@
jackson-jakarta-rs-base
${jackson-version}
-
- org.apache.oltu.oauth2
- org.apache.oltu.oauth2.client
- ${oltu-version}
-
com.auth0
@@ -429,6 +424,5 @@
3.1.6
2.14.2
4.13.1
- 1.0.2
diff --git a/src/main/java/com/docusign/esign/client/ApiClient.java b/src/main/java/com/docusign/esign/client/ApiClient.java
index da164351..39e25c66 100644
--- a/src/main/java/com/docusign/esign/client/ApiClient.java
+++ b/src/main/java/com/docusign/esign/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,6 +30,7 @@
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;
@@ -84,6 +81,8 @@ public class ApiClient {
protected DateFormat dateFormat;
private SSLContext sslContext = null;
+ private final String HTTPS = "https://";
+
/**
* ApiClient constructor.
*
@@ -96,11 +95,11 @@ public ApiClient() {
String javaVersion = System.getProperty("java.version");
// Set default User-Agent.
- setUserAgent("Swagger-Codegen/v2.1/6.0.0-RC1/Java/" + javaVersion);
+ setUserAgent("Swagger-Codegen/v2.1/6.0.0-RC2/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();
@@ -138,7 +137,7 @@ public ApiClient(String oAuthBasePath, String[] authNames) {
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,9 +165,11 @@ 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);
+ }
}
/**
@@ -378,7 +379,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 +524,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 +534,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;
}
/**
@@ -677,7 +651,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");
diff --git a/src/main/java/com/docusign/esign/client/auth/AccessTokenListener.java b/src/main/java/com/docusign/esign/client/auth/AccessTokenListener.java
index fa14a861..9fee5a93 100644
--- a/src/main/java/com/docusign/esign/client/auth/AccessTokenListener.java
+++ b/src/main/java/com/docusign/esign/client/auth/AccessTokenListener.java
@@ -1,7 +1,7 @@
package com.docusign.esign.client.auth;
-import org.apache.oltu.oauth2.common.token.BasicOAuthToken;
+import com.docusign.esign.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/esign/client/auth/OAuth.java b/src/main/java/com/docusign/esign/client/auth/OAuth.java
index 4f4afb82..83cff426 100644
--- a/src/main/java/com/docusign/esign/client/auth/OAuth.java
+++ b/src/main/java/com/docusign/esign/client/auth/OAuth.java
@@ -1,26 +1,23 @@
package com.docusign.esign.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.esign.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.esign.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.esign.client.Pair;
import com.fasterxml.jackson.annotation.JsonProperty;
-import jakarta.ws.rs.client.Client;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -57,32 +54,38 @@ public class OAuth implements Authentication {
/** 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 +98,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 +143,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 +177,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,37 +319,175 @@ 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