diff --git a/.gitignore b/.gitignore
index ba762d031..1d3d69dd4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,6 +42,8 @@ Thumbs.db
.nx
.vscode
+# Java
+**/target
# Created by `dart pub`
.dart_tool/
diff --git a/packages/java/auth.provider/.env.example b/packages/java/auth.provider/.env.example
new file mode 100644
index 000000000..fc80469d9
--- /dev/null
+++ b/packages/java/auth.provider/.env.example
@@ -0,0 +1,7 @@
+# Optional values needed to generate auth token. These values can also be directly passsed to the AuthProvider build fuunction
+PROJECT_ID=
+KEY_ID=
+TOKEN_ID=
+PASSPHRASE=
+PRIVATE_KEY=
+
diff --git a/packages/java/auth.provider/README.md b/packages/java/auth.provider/README.md
new file mode 100644
index 000000000..afc8d2281
--- /dev/null
+++ b/packages/java/auth.provider/README.md
@@ -0,0 +1,90 @@
+# auth.provider
+
+AuthProvider package enables you to generate the required access token like Project Scoped Token to authenticate and access Affinidi Trust Network services.
+
+For more information, please visit [https://github.com/affinidi/affinidi-tdk](https://github.com/affinidi/affinidi-tdk)
+
+## Requirements
+
+Building the package requires:
+
+1. Java 1.8+
+2. Maven (3.8.3+)
+
+## Installation
+
+To install the API client library to your local Maven repository, simply execute:
+
+```shell
+mvn clean install
+```
+
+To deploy it to a remote Maven repository instead, configure the settings of the repository and execute:
+
+```shell
+mvn clean deploy
+```
+
+### Maven users
+
+Add this dependency to your project's POM:
+
+```xml
+
+ com.affinidi.tdk
+ auth.provider
+ 1.0.0
+
+```
+
+### Others
+
+At first generate the JAR by executing:
+
+```shell
+mvn clean package
+```
+
+## Usage
+
+Sample usage to generate Project Scoped Token to call Affinidi TDK clients.
+
+> You can store the required parameters like the Token details into an environment file.
+
+```java
+
+// Import classes:
+import com.affinidi.tdk.authProvider.helper.JwtUtil;
+import com.affinidi.tdk.authProvider.helper.AuthProvider;
+
+public class AuthProviderConsumer {
+ public static void main(String arg[]) {
+ try{
+ // Create an authprovider from the values configured in the environment file
+ AuthProvider authProviderFromEnvFile = new AuthProvider.Configurations().buildWithEnv();
+ String projectToken = authProviderFromEnvFile.fetchProjectScopedToken();
+ System.out.println(projectToken);
+
+ boolean isExistingProjectScopeTokenValid = JwtUtil.validProjectTokenPresent(projectToken, authProviderFromEnvFile.apiGatewayUrl);
+ System.out.println(isExistingProjectScopeTokenValid);
+
+
+ // Alternatively you can create an auth provider by explicitly passing the configurations
+ AuthProvider authProviderWithPassedValues = new AuthProvider.Configurations()
+ .keyId("")
+ .projectId("")
+ .passphrase("")
+ .projectId("")
+ .tokenId("")
+ .build();
+ String projectToken = authProvider.fetchProjectScopedToken();
+ System.out.println(projectToken);
+
+
+ }catch(Exception e){
+ e.printStackTrace();
+ }
+ }
+}
+
+```
diff --git a/packages/java/auth.provider/pom.xml b/packages/java/auth.provider/pom.xml
new file mode 100644
index 000000000..12c74acd1
--- /dev/null
+++ b/packages/java/auth.provider/pom.xml
@@ -0,0 +1,175 @@
+
+
+
+ 4.0.0
+ jar
+ com.affinidi.tdk
+ auth.provider
+ 1.0
+ auth.provider
+ https://github.com/affinidi/affinidi-tdk
+
+
+ Apache-2.0
+ https://github.com/affinidi/affinidi-tdk/blob/main/LICENSE
+ repo
+
+
+
+
+
+ Affinidi
+ ...
+ Affinidi
+ https://affinidi.com
+
+
+
+
+ UTF-8
+ 1.8
+ ${java.version}
+ ${java.version}
+ 5.4.1
+ 5.11.0
+ 2.2.0
+ 3.1.7
+ 1.0
+ 0.12.6
+ 3.12.4
+ 3.10.0
+ 2.10.1
+ 2.0.16
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit-version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit-version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ ${junit-version}
+ test
+
+
+ io.github.cdimascio
+ dotenv-java
+ ${cdimascio-version}
+
+
+ org.glassfish.jersey.core
+ jersey-common
+ ${jersey-version}
+
+
+ org.mockito
+ mockito-inline
+ ${mockito-version}
+ test
+
+
+ org.wiremock
+ wiremock
+ ${wiremock-version}
+ test
+
+
+ com.affinidi.tdk
+ common
+ ${affinidi-common-version}
+
+
+ io.jsonwebtoken
+ jjwt-api
+ ${jsonwebtoken-version}
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ ${jsonwebtoken-version}
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ ${jsonwebtoken-version}
+
+
+ org.apache.httpcomponents.client5
+ httpclient5
+ ${apache-http-version}
+
+
+ com.google.code.gson
+ gson
+ ${gson-version}
+
+
+
+
+
+
+
+
+
+ maven-clean-plugin
+ 3.1.0
+
+
+
+ maven-resources-plugin
+ 3.0.2
+
+
+ maven-compiler-plugin
+ 3.8.0
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ maven-failsafe-plugin
+ 3.5.0
+
+
+ maven-jar-plugin
+ 3.0.2
+
+
+ maven-install-plugin
+ 2.5.2
+
+
+ maven-deploy-plugin
+ 2.8.2
+
+
+
+ maven-site-plugin
+ 3.7.1
+
+
+ maven-project-info-reports-plugin
+ 3.0.0
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/AuthProvider.java b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/AuthProvider.java
new file mode 100644
index 000000000..ce5dc0c75
--- /dev/null
+++ b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/AuthProvider.java
@@ -0,0 +1,375 @@
+package com.affinidi.tdk.authProvider;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.message.BasicNameValuePair;
+
+import com.affinidi.tdk.authProvider.exception.AccessTokenGenerationException;
+import com.affinidi.tdk.authProvider.exception.ConfigurationException;
+import com.affinidi.tdk.authProvider.exception.JwtGenerationException;
+import com.affinidi.tdk.authProvider.exception.PSTGenerationException;
+import com.affinidi.tdk.authProvider.helper.AuthProviderConstants;
+import com.affinidi.tdk.authProvider.helper.JwtUtil;
+import com.affinidi.tdk.authProvider.types.IotaJwtOutput;
+import com.affinidi.tdk.common.EnvironmentUtil;
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+/**
+ * This class provides utility functions in order to generate projectScopeToken
+ * required to call Affinidi Services.
+ *
+ *
+ * @author Priyanka
+ *
+ */
+public class AuthProvider {
+
+ /**
+ * Gson is thread safe.
+ */
+ private static final Gson GSON = new Gson();
+
+ private final String projectId;
+ private final String tokenId;
+ private final String privateKey;
+ private final String keyId;
+ private final String passphrase;
+ private String tokenEndPoint;
+ private String apiGatewayUrl;
+ private String publicKey;
+ private String projectScopeToken;
+
+ private AuthProvider(Configurations configurations) {
+ this.projectId = configurations.projectId;
+ this.tokenId = configurations.tokenId;
+ this.privateKey = configurations.privateKey;
+ this.keyId = configurations.keyId;
+ this.passphrase = configurations.passphrase;
+ this.apiGatewayUrl = EnvironmentUtil.getApiGatewayUrlForEnvironment();
+ this.tokenEndPoint = EnvironmentUtil.getElementAuthTokenUrlForEnvironment();
+ }
+
+ /**
+ * This method identifies if the current AuthProvider has a valid existing
+ * projectScopeToken or not. This helps to reuse the valid tokens without
+ * always generating a new.
+ *
+ * The validation involves verifying token's signature against the public
+ * (verification) key; validating token's expiration or malformation.
+ *
+ * @return boolean
+ */
+ public boolean shouldRefreshToken() {
+ return (this.projectScopeToken == null) || !(JwtUtil.validProjectTokenPresent(this.projectScopeToken, this.apiGatewayUrl));
+ }
+
+ /**
+ * This method generates a projectScopeToken required to call Affinidi
+ * services.
+ *
+ * In case there is an existing projectScopeToken in the authProvider
+ * instance; it is first validated and a new one is generated only if
+ * needed.
+ *
+ * Refer {@link JwtUtil#validProjectTokenPresent(String, String)} for
+ * validation details
+ *
+ * @return String
+ * @throws PSTGenerationException incase access_token generation has issues
+ * or projectScopeToken end point
+ */
+ public String fetchProjectScopedToken() throws PSTGenerationException {
+ if (shouldRefreshToken()) {
+ this.projectScopeToken = getProjectScopedToken();
+ }
+ return this.projectScopeToken;
+ }
+
+ /**
+ * This method generates a user-access-token which is required as an API
+ * authorization token.
+ *
+ * @return String
+ * @throws AccessTokenGenerationException in case the access token could not
+ * be generated
+ */
+ public String getUserAccessToken() throws AccessTokenGenerationException {
+ try {
+ final String signedToken = JwtUtil.signPayload(this.tokenId, this.tokenEndPoint, this.privateKey, this.passphrase,
+ this.keyId);
+ if (signedToken == null) {
+ throw new JwtGenerationException("Could not generate signed JWT from the configurations ");
+ }
+ final HttpPost httpPost = new HttpPost(this.getTokenEndPoint());
+ httpPost.setHeader(AuthProviderConstants.CONTENT_TYPE_HEADER,
+ AuthProviderConstants.APPLICATION_URL_ENCODED_CONTENT_TYPE);
+
+ final List params = Arrays.asList(
+ new BasicNameValuePair("grant_type", "client_credentials"),
+ new BasicNameValuePair("scope", "openid"),
+ new BasicNameValuePair("client_assertion_type",
+ "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"),
+ new BasicNameValuePair("client_assertion", signedToken),
+ new BasicNameValuePair("client_id", this.tokenId));
+ httpPost.setEntity(new UrlEncodedFormEntity(params));
+
+ String userAccessToken = executeHttp(httpPost, "access_token");
+ if (userAccessToken == null) {
+ throw new AccessTokenGenerationException(
+ "getUserAccessToken : Could not retrieve access_token from the token end point");
+ }
+ return userAccessToken;
+ } catch (JwtGenerationException | AccessTokenGenerationException | IOException jwtGenerationException) {
+ throw new AccessTokenGenerationException(jwtGenerationException.getMessage());
+ }
+ }
+
+ /**
+ * This method generates a projectScopeToken for the configuration values
+ * associated to the AuthProvider.
+ *
+ * @return String
+ * @throws PSTGenerationException
+ */
+ private String getProjectScopedToken() throws PSTGenerationException {
+ try {
+ String userAccessToken = getUserAccessToken();
+
+ final HttpPost httpPost = new HttpPost(apiGatewayUrl + AuthProviderConstants.PROJECT_SCOPE_TOKEN_API_PATH);
+
+ final List params = Arrays.asList(
+ new BasicNameValuePair("projectId", projectId));
+ httpPost.setEntity(new UrlEncodedFormEntity(params));
+
+ httpPost.setHeader("Authorization", "Bearer " + userAccessToken);
+ httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded");
+
+ String token = executeHttp(httpPost, "accessToken");
+ if (token == null) {
+ throw new PSTGenerationException("getProjectScopedToken : Could not retrieve accessToken from "
+ + (apiGatewayUrl + AuthProviderConstants.PROJECT_SCOPE_TOKEN_API_PATH));
+ }
+ return token;
+ } catch (AccessTokenGenerationException | PSTGenerationException | IOException ex) {
+ throw new PSTGenerationException(ex.getMessage());
+ }
+ }
+
+ /**
+ * This method generates a signed jwt for an iota session.
+ *
+ * @param iotaConfigId
+ * @param did
+ * @param iotaSessionId
+ * @return IotaJwtOutput
+ * @throws Exception
+ */
+ public IotaJwtOutput signIotaJwt(String iotaConfigId, String did, String iotaSessionId) throws Exception {
+
+ String iotaTokenId = "token/" + tokenId;
+ String iotaSessionID = (iotaSessionId != null) ? iotaSessionId : UUID.randomUUID().toString();
+
+ String iotaJwt = JwtUtil.signIotaPayload(iotaTokenId, did, privateKey, passphrase, keyId, projectId,
+ iotaConfigId, iotaSessionID);
+
+ return new IotaJwtOutput(iotaSessionID, iotaJwt);
+ }
+
+ private String executeHttp(final HttpPost httpPost, String memberName) throws IOException {
+ try (CloseableHttpClient client = HttpClients.createSystem()) {
+ return client.execute(httpPost,
+ response -> {
+ if (response.getCode() >= 200 && response.getCode() < 300) {
+ final HttpEntity responseEntity = response.getEntity();
+ final JsonElement responseAsJson = GSON.fromJson(EntityUtils.toString(responseEntity),
+ JsonElement.class);
+ final JsonObject responseObject = responseAsJson.isJsonObject() ? responseAsJson.getAsJsonObject()
+ : null;
+ if (responseObject != null && responseObject.get(memberName) != null) {
+ return responseObject.get(memberName).getAsString();
+ }
+ }
+ return null;
+ });
+ }
+ }
+
+ /**
+ * This class provides a way to pass configurations to the AuthProvider It
+ * also helps to build an instance of AuthProvider which uses these
+ * configurations.
+ */
+ public static class Configurations {
+
+ private String projectId;
+ private String tokenId;
+ private String privateKey;
+ private String keyId;
+ private String passphrase;
+
+ public Configurations projectId(String projectId) {
+ this.projectId = projectId;
+ return this;
+ }
+
+ public Configurations tokenId(String tokenId) {
+ this.tokenId = tokenId;
+ return this;
+ }
+
+ public Configurations privateKey(String privateKey) {
+ this.privateKey = privateKey;
+ return this;
+ }
+
+ public Configurations keyId(String keyId) {
+ this.keyId = keyId;
+ return this;
+ }
+
+ public Configurations passphrase(String passphrase) {
+ this.passphrase = passphrase;
+ return this;
+ }
+
+ /**
+ * This method builds an instance of AuthProvider with the values passed
+ * through {@link Configuration}.
+ *
+ * @return
+ * @throws ConfigurationException
+ */
+ public AuthProvider build() throws ConfigurationException {
+ if (this.projectId == null || this.privateKey == null || this.tokenId == null) {
+ throw new ConfigurationException(
+ "Cannot create Auth provider without projectId, privateKey and toeknId");
+ }
+ return new AuthProvider(this);
+ }
+
+ /**
+ * This method builds an instance of AuthProvider with the configuration
+ * values present in the .env file.
+ *
+ * @return
+ * @throws ConfigurationException
+ */
+ public AuthProvider buildWithEnv() throws ConfigurationException {
+
+ if (this.projectId != null || this.privateKey != null || this.tokenId != null || this.passphrase != null
+ || this.privateKey != null) {
+ throw new ConfigurationException("Please do not pass configurations values while using buildWithEnv. "
+ + " These values will picked from .env. Alternatively you may use build() in order to explicitly pass values");
+ }
+
+ this.keyId = EnvironmentUtil.getValueFromEnvConfig(AuthProviderConstants.KEY_ID_PROPERTY_NAME_IN_ENV);
+ this.projectId = EnvironmentUtil.getValueFromEnvConfig(AuthProviderConstants.PROJECT_ID_PROPERTY_NAME_IN_ENV);
+ this.passphrase = EnvironmentUtil.getValueFromEnvConfig(AuthProviderConstants.PASSPHRASE_PROPERTY_NAME_IN_ENV);
+ this.tokenId = EnvironmentUtil.getValueFromEnvConfig(AuthProviderConstants.TOKEN_ID_PROPERTY_NAME_IN_ENV);
+ this.privateKey = EnvironmentUtil.getValueFromEnvConfig(AuthProviderConstants.PRIVATE_KEY_PROPERTY_NAME_IN_ENV);
+
+ if (this.projectId == null || this.privateKey == null || this.tokenId == null) {
+ throw new ConfigurationException(
+ "Cannot create Auth provider without projectId, privateKey and tokenId. Please ensure these values are configured in .env");
+ }
+ return new AuthProvider(this);
+ }
+ }
+
+ /**
+ * @return String
+ */
+ public String getProjectId() {
+ return projectId;
+ }
+
+ /**
+ * @return String
+ */
+ public String getTokenId() {
+ return tokenId;
+ }
+
+ /**
+ * @return String
+ */
+ public String getPrivateKey() {
+ return privateKey;
+ }
+
+ /**
+ * @return String
+ */
+ public String getKeyId() {
+ return keyId;
+ }
+
+ /**
+ * @return String
+ */
+ public String getPassphrase() {
+ return passphrase;
+ }
+
+ /**
+ * @return String
+ */
+ public String getApiGatewayUrl() {
+ return apiGatewayUrl;
+ }
+
+ public void setApiGatewayUrl(String apiGatewayUrl) {
+ this.apiGatewayUrl = apiGatewayUrl;
+ }
+
+ /**
+ * @return String
+ */
+ public String getTokenEndPoint() {
+ return tokenEndPoint;
+ }
+
+ public void setTokenEndPoint(String tokenEndPoint) {
+ this.tokenEndPoint = tokenEndPoint;
+ }
+
+ /**
+ * @return String
+ */
+ public String getPublicKey() {
+ return publicKey;
+ }
+
+ /**
+ * @return String
+ */
+ public void setPublicKey(String publicKey) {
+ this.publicKey = publicKey;
+ }
+
+ /**
+ * @return String
+ */
+ public String getProjectScopeToken() {
+ return projectScopeToken;
+ }
+
+ /**
+ * @return String
+ */
+ public void setProjectScopeToken(String projectScopeToken) {
+ this.projectScopeToken = projectScopeToken;
+ }
+}
diff --git a/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/exception/AccessTokenGenerationException.java b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/exception/AccessTokenGenerationException.java
new file mode 100644
index 000000000..c05dfe247
--- /dev/null
+++ b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/exception/AccessTokenGenerationException.java
@@ -0,0 +1,8 @@
+package com.affinidi.tdk.authProvider.exception;
+
+public class AccessTokenGenerationException extends GenericAuthProviderException {
+
+ public AccessTokenGenerationException(String errorMessage) {
+ super(errorMessage);
+ }
+}
diff --git a/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/exception/ConfigurationException.java b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/exception/ConfigurationException.java
new file mode 100644
index 000000000..e1f0f7d00
--- /dev/null
+++ b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/exception/ConfigurationException.java
@@ -0,0 +1,8 @@
+package com.affinidi.tdk.authProvider.exception;
+
+public class ConfigurationException extends GenericAuthProviderException {
+
+ public ConfigurationException(String errorMessage) {
+ super(errorMessage);
+ }
+}
diff --git a/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/exception/GenericAuthProviderException.java b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/exception/GenericAuthProviderException.java
new file mode 100644
index 000000000..d10400ab6
--- /dev/null
+++ b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/exception/GenericAuthProviderException.java
@@ -0,0 +1,8 @@
+package com.affinidi.tdk.authProvider.exception;
+
+public class GenericAuthProviderException extends Exception {
+
+ public GenericAuthProviderException(String errorMessage) {
+ super(errorMessage);
+ }
+}
diff --git a/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/exception/InvalidPrivateKeyException.java b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/exception/InvalidPrivateKeyException.java
new file mode 100644
index 000000000..5664fbde2
--- /dev/null
+++ b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/exception/InvalidPrivateKeyException.java
@@ -0,0 +1,8 @@
+package com.affinidi.tdk.authProvider.exception;
+
+public class InvalidPrivateKeyException extends GenericAuthProviderException {
+
+ public InvalidPrivateKeyException(String errorMessage) {
+ super(errorMessage);
+ }
+}
diff --git a/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/exception/InvalidPublicKeyException.java b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/exception/InvalidPublicKeyException.java
new file mode 100644
index 000000000..a0a0120ae
--- /dev/null
+++ b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/exception/InvalidPublicKeyException.java
@@ -0,0 +1,8 @@
+package com.affinidi.tdk.authProvider.exception;
+
+public class InvalidPublicKeyException extends GenericAuthProviderException {
+
+ public InvalidPublicKeyException(String errorMessage) {
+ super(errorMessage);
+ }
+}
diff --git a/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/exception/JwtGenerationException.java b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/exception/JwtGenerationException.java
new file mode 100644
index 000000000..8ee1a57e8
--- /dev/null
+++ b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/exception/JwtGenerationException.java
@@ -0,0 +1,8 @@
+package com.affinidi.tdk.authProvider.exception;
+
+public class JwtGenerationException extends GenericAuthProviderException {
+
+ public JwtGenerationException(String errorMessage) {
+ super(errorMessage);
+ }
+}
diff --git a/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/exception/PSTGenerationException.java b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/exception/PSTGenerationException.java
new file mode 100644
index 000000000..5958dde90
--- /dev/null
+++ b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/exception/PSTGenerationException.java
@@ -0,0 +1,8 @@
+package com.affinidi.tdk.authProvider.exception;
+
+public class PSTGenerationException extends GenericAuthProviderException {
+
+ public PSTGenerationException(String errorMessage) {
+ super(errorMessage);
+ }
+}
diff --git a/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/helper/AuthProviderConstants.java b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/helper/AuthProviderConstants.java
new file mode 100644
index 000000000..6bdcf4fa1
--- /dev/null
+++ b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/helper/AuthProviderConstants.java
@@ -0,0 +1,19 @@
+package com.affinidi.tdk.authProvider.helper;
+
+public interface AuthProviderConstants {
+
+ public static final String PUBLIC_KEY_PATH = "/iam/.well-known/jwks.json";
+ public static final String PROJECT_SCOPE_TOKEN_API_PATH = "/iam/v1/sts/create-project-scoped-token";
+ public static final String CONTENT_TYPE_HEADER = "Content-Type";
+ public static final String APPLICATION_JSON_CONTENT_TYPE = "application/json";
+ public static final String APPLICATION_URL_ENCODED_CONTENT_TYPE = "application/x-www-form-urlencoded";
+
+ public static final String KEY_ID_PROPERTY_NAME_IN_ENV = "KEY_ID";
+ public static final String TOKEN_ID_PROPERTY_NAME_IN_ENV = "TOKEN_ID";
+ public static final String PASSPHRASE_PROPERTY_NAME_IN_ENV = "PASSPHRASE";
+ public static final String PRIVATE_KEY_PROPERTY_NAME_IN_ENV = "PRIVATE_KEY";
+ public static final String PROJECT_ID_PROPERTY_NAME_IN_ENV = "PROJECT_ID";
+
+ // Error messages:
+ public static final String COULD_NOT_DERIVE_PRIVATE_KEY_ERROR_MSG = "Could not derive private key out of the configurations.";
+}
diff --git a/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/helper/JwtUtil.java b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/helper/JwtUtil.java
new file mode 100644
index 000000000..6d85f689d
--- /dev/null
+++ b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/helper/JwtUtil.java
@@ -0,0 +1,341 @@
+package com.affinidi.tdk.authProvider.helper;
+
+import java.io.IOException;
+import java.security.AlgorithmParameters;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.crypto.Cipher;
+import javax.crypto.EncryptedPrivateKeyInfo;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.ParseException;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+
+import com.affinidi.tdk.authProvider.exception.InvalidPrivateKeyException;
+import com.affinidi.tdk.authProvider.exception.InvalidPublicKeyException;
+import com.affinidi.tdk.authProvider.exception.JwtGenerationException;
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSyntaxException;
+
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.security.InvalidKeyException;
+import io.jsonwebtoken.security.Jwk;
+import io.jsonwebtoken.security.Jwks;
+
+/**
+ * This class provides utility functions required by {@link AuthProvider}
+ * related for processing(creating, signing, validation) of JWT.
+ *
+ * @author Priyanka
+ */
+public class JwtUtil {
+
+ private static final Logger LOGGER = Logger.getLogger(JwtUtil.class.getName());
+ /**
+ * Gson is thread safe.
+ */
+ private static final Gson GSON = new Gson();
+
+ /**
+ * This method builds a JSON web token for the provided claims. It then
+ * signs the token with the provided privatekey using RS256.
+ *
+ * In case of an encrypted private key; this method expects to receive the
+ * passphrase which was used during generation of the key pair.
+ *
+ * @param tokenId
+ * @param audience
+ * @param privateKeyString
+ * @param passphrase
+ * @param keyId
+ * @return String
+ * @throws JwtGenerationException in case the private key could not be
+ * processed or jwt generation failed
+ */
+ public static String signPayload(String tokenId, String audience, String privateKeyString, String passphrase,
+ String keyId) throws JwtGenerationException {
+
+ final long issueTimeInSeconds = System.currentTimeMillis() / 1000;
+ final Map claims = new HashMap<>();
+ claims.put("iss", tokenId);
+ claims.put("sub", tokenId);
+ claims.put("aud", audience);
+ claims.put("jti", UUID.randomUUID().toString());
+ claims.put("exp", issueTimeInSeconds + (5 * 60));
+ claims.put("iat", issueTimeInSeconds);
+
+ try {
+ return generateJwt(derivePrivateKey(privateKeyString, passphrase), claims);
+ } catch (InvalidPrivateKeyException ipke) {
+ throw new JwtGenerationException(ipke.getMessage());
+ }
+ }
+
+ /**
+ * This method builds a JSON web token for the provided claims specific to
+ * an iota request. It then signs the token with the provided privatekey
+ * using RS256.
+ *
+ * In case of an encrypted private key; this method expects to receive the
+ * passphrase which was used during generation of the key pair.
+ *
+ * @param tokenId
+ * @param audience
+ * @param privateKeyString
+ * @param passphrase
+ * @param keyId
+ * @param projectId
+ * @param iotaConfigId
+ * @param iotaSessionId
+ * @return String
+ * @throws JwtGenerationException in case the private key could not be
+ * processed or jwt generation failed
+ */
+ public static String signIotaPayload(String tokenId, String audience, String privateKeyString, String passphrase,
+ String keyId, String projectId, String iotaConfigId, String iotaSessionId) throws JwtGenerationException {
+
+ final long issueTimeInSeconds = System.currentTimeMillis() / 1000;
+ final Map claims = new HashMap<>();
+ claims.put("iss", tokenId);
+ claims.put("sub", tokenId);
+ claims.put("kid", tokenId);
+ claims.put("aud", audience);
+ claims.put("jti", UUID.randomUUID().toString());
+ claims.put("exp", issueTimeInSeconds + (5 * 60));
+ claims.put("iat", issueTimeInSeconds);
+ claims.put("project_id", projectId);
+ claims.put("iota_configuration_id", iotaConfigId);
+ claims.put("iota_session_id", iotaSessionId);
+ claims.put("scope", "iota_channel");
+
+ try {
+ return generateJwt(derivePrivateKey(privateKeyString, passphrase), claims);
+ } catch (JwtGenerationException jge) {
+ throw jge;
+ } catch (InvalidPrivateKeyException ipke) {
+ throw new JwtGenerationException(ipke.getMessage());
+ }
+ }
+
+ /**
+ * This method fetches the signature verification key, required to validate
+ * the jws for a projectScopeToken and converts it to
+ * {@link java.security.PublicKey}.
+ *
+ * @param apiGatewayUrl
+ * @return PublicKey
+ * @throws InvalidPublicKeyException in case the public key could not be
+ * retrieved/extracted
+ */
+ public static PublicKey fetchPublicKey(String apiGatewayUrl) throws InvalidPublicKeyException {
+ try {
+ final HttpGet httpGet = new HttpGet(apiGatewayUrl + AuthProviderConstants.PUBLIC_KEY_PATH);
+ httpGet.setHeader(AuthProviderConstants.CONTENT_TYPE_HEADER,
+ AuthProviderConstants.APPLICATION_JSON_CONTENT_TYPE);
+
+ try (final CloseableHttpClient client = HttpClients.createSystem()) {
+ return client.execute(httpGet,
+ response -> {
+ if (response.getCode() >= 200 && response.getCode() < 300) {
+ final HttpEntity entity = response.getEntity();
+ final String jwkFromResponse = decodeResponse(entity);
+ final Jwk> jwk = Jwks.parser().build()
+ .parse(jwkFromResponse);
+ if (jwk != null) {
+ return (PublicKey) jwk.toKey();
+ }
+ } else {
+ String resp = decodeResponse(response.getEntity());
+ LOGGER.log(Level.INFO, "fetchPublicKey entity: {0}", resp);
+ }
+ return null;
+ });
+ }
+ } catch (IOException exception) {
+ throw new InvalidPublicKeyException("Could not retrieve/ extract the public "
+ + "key required to validate projectScopeToken " + exception.getMessage());
+ }
+ }
+
+ /**
+ * This method validates the projectScopeToken. Along with validating if the
+ * token is malformed or expired; it also verifies the token signature using
+ * the signature verification public key.
+ *
+ * If the public key could not be retrieved for any reason; the method would
+ * consider the token as invalid.
+ *
+ * @param token
+ * @param apiGatewayUrl
+ * @return boolean
+ */
+ public static boolean validProjectTokenPresent(String token, String apiGatewayUrl) {
+ try {
+ final PublicKey publicKey = fetchPublicKey(apiGatewayUrl);
+ if (publicKey == null) {
+ throw new Exception("Could not retrieve public key for token validation");
+ }
+ Jwts.parser().verifyWith(publicKey).build().parse(token);
+ return true;
+ } catch (Exception e) {
+ // ignore
+ }
+ return false;
+ }
+
+ /**
+ * This method converts the private key string passed to a
+ * {@link java.security.PrivateKey}. In case a passphrase is passed, the
+ * private key is treated as encrypted and processed accordingly. This
+ * passphrase should be same as the one used to create the public-private
+ * key pair.
+ *
+ * @param privateKeyString
+ * @param passphrase
+ * @return PrivateKey
+ * @throws InvalidPrivateKeyException
+ */
+ private static PrivateKey derivePrivateKey(String privateKeyString, String passphrase)
+ throws InvalidPrivateKeyException {
+ try {
+ if (passphrase.isEmpty()) {
+ return getPrivateKeyFromString(privateKeyString);
+ }
+ return getEncryptedPrivateKeyFromString(privateKeyString, passphrase);
+ } catch (Exception exception) {
+ throw new InvalidPrivateKeyException(
+ AuthProviderConstants.COULD_NOT_DERIVE_PRIVATE_KEY_ERROR_MSG
+ + " Exception : " + exception.toString());
+ }
+ }
+
+ /**
+ * This method generates a JSON payload representing all the claims passed.
+ * It then signs this payload with the {@link java.security.PrivateKey},
+ * creating a json web signature (JWS) and then builds the JWT as a URL-safe
+ * string.
+ *
+ * @param privateKey
+ * @param claims
+ * @return String
+ * @throws JwtGenerationException when the jwt generation fails. For
+ * instance if the private key is insufficient
+ */
+ private static String generateJwt(PrivateKey privateKey, Map claims) throws JwtGenerationException {
+ try {
+ return Jwts.builder()
+ .claims(claims)
+ .signWith(privateKey, Jwts.SIG.RS256)
+ .compact();
+
+ } catch (InvalidKeyException ike) {
+ throw new JwtGenerationException(
+ " Could not generate the JWT representing the claims. Exception" + ike.getMessage());
+ }
+ }
+
+ /**
+ * This method converts a private key string to
+ * {@link java.security.PrivateKey} object.
+ *
+ * @param privateKeyPEM
+ * @return PrivateKey
+ * @throws Exception
+ */
+ private static PrivateKey getPrivateKeyFromString(String privateKeyPEM) throws Exception {
+ final String privateKeyContent = extractPrivateKeyContent(privateKeyPEM);
+ final byte[] encoded = Base64.getDecoder().decode(privateKeyContent);
+ final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
+ return keyFactory.generatePrivate(keySpec);
+ }
+
+ /**
+ * This method converts an encrypted private key string to
+ * {@link java.security.PrivateKey} object The passphrase which was used to
+ * generate the public-private keypair should be passed to this method for
+ * processing.
+ *
+ * @param encryptedPrivateKeyPEM
+ * @param password
+ * @return PrivateKey
+ * @throws InvalidKeySpecException
+ */
+ private static PrivateKey getEncryptedPrivateKeyFromString(String encryptedPrivateKeyPEM, String password)
+ throws Exception {
+ final String privateKeyContent = extractPrivateKeyContent(encryptedPrivateKeyPEM);
+ final byte[] encodedKey = Base64.getDecoder().decode(privateKeyContent);
+ final EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(encodedKey);
+ final Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName());
+ final PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
+ final SecretKeyFactory secFac = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName());
+ final Key pbeKey = secFac.generateSecret(pbeKeySpec);
+ final AlgorithmParameters algParams = encryptedPrivateKeyInfo.getAlgParameters();
+ cipher.init(Cipher.DECRYPT_MODE, pbeKey, algParams);
+ final PKCS8EncodedKeySpec pkcs8KeySpec = encryptedPrivateKeyInfo.getKeySpec(cipher);
+ final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+
+ return keyFactory.generatePrivate(pkcs8KeySpec);
+ }
+
+ /**
+ * This method cleans up the private key string which is required to decode
+ * the key material.
+ *
+ * @param privateKey
+ * @return String
+ */
+ private static String extractPrivateKeyContent(String privateKey) {
+ return privateKey
+ .replaceAll("-----BEGIN (ENCRYPTED )?PRIVATE KEY-----|-----END (ENCRYPTED )?PRIVATE KEY-----", "")
+ .replace("\\n", "").trim();
+ }
+
+ /**
+ * This method processes the response from the public key API call and
+ * extracts the first key listed from the response.
+ *
+ * @param responseEntity
+ * @return String
+ * @throws IOException
+ * @throws JsonSyntaxException
+ * @throws ParseException
+ */
+ private static String decodeResponse(HttpEntity responseEntity)
+ throws IOException, JsonSyntaxException, ParseException {
+ if (responseEntity == null) {
+ return null;
+ }
+ final JsonElement responseAsJson = GSON.fromJson(EntityUtils.toString(responseEntity), JsonElement.class);
+ final JsonObject responseObject = responseAsJson.isJsonObject() ? responseAsJson.getAsJsonObject() : null;
+ if (responseObject == null) {
+ return null;
+ }
+ final JsonArray setOfKeys = responseObject.getAsJsonArray("keys");
+ if (setOfKeys == null) {
+ return null;
+ }
+ final JsonObject firstKeysAsObject = setOfKeys.size() > 0
+ ? setOfKeys.get(0).getAsJsonObject()
+ : null;
+ return (firstKeysAsObject != null) ? firstKeysAsObject.toString() : null;
+ }
+}
diff --git a/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/types/IotaJwtOutput.java b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/types/IotaJwtOutput.java
new file mode 100644
index 000000000..2f0da14b8
--- /dev/null
+++ b/packages/java/auth.provider/src/main/java/com/affinidi/tdk/authProvider/types/IotaJwtOutput.java
@@ -0,0 +1,27 @@
+package com.affinidi.tdk.authProvider.types;
+
+/**
+ * This class represents the output returned when
+ * {@link AuthProvider#signIotaJwt()} is invoked
+ *
+ * @author Priyanka
+ *
+ */
+public class IotaJwtOutput {
+
+ private final String iotaSessionId;
+ private final String iotaJwt;
+
+ public IotaJwtOutput(String iotaSessionId, String iotaJwt) {
+ this.iotaJwt = iotaJwt;
+ this.iotaSessionId = iotaSessionId;
+ }
+
+ public String getIotaSessionId() {
+ return iotaSessionId;
+ }
+
+ public String getIotaJwt() {
+ return iotaJwt;
+ }
+}
diff --git a/packages/java/auth.provider/src/test/java/com/affinidi/tdk/authProvider/AuthProviderTest.java b/packages/java/auth.provider/src/test/java/com/affinidi/tdk/authProvider/AuthProviderTest.java
new file mode 100644
index 000000000..ff0eb4dea
--- /dev/null
+++ b/packages/java/auth.provider/src/test/java/com/affinidi/tdk/authProvider/AuthProviderTest.java
@@ -0,0 +1,261 @@
+package com.affinidi.tdk.authProvider;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EmptySource;
+import org.junit.jupiter.params.provider.ValueSource;
+import static org.mockito.ArgumentMatchers.any;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+import com.affinidi.tdk.authProvider.exception.ConfigurationException;
+import com.affinidi.tdk.authProvider.exception.InvalidPublicKeyException;
+import com.affinidi.tdk.authProvider.exception.JwtGenerationException;
+import com.affinidi.tdk.authProvider.exception.PSTGenerationException;
+import com.affinidi.tdk.authProvider.helper.AuthProviderConstants;
+import com.affinidi.tdk.authProvider.helper.JwtUtil;
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.givenThat;
+import static com.github.tomakehurst.wiremock.client.WireMock.okJson;
+import static com.github.tomakehurst.wiremock.client.WireMock.post;
+import com.github.tomakehurst.wiremock.http.Fault;
+import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
+import com.github.tomakehurst.wiremock.junit5.WireMockTest;
+
+public class AuthProviderTest {
+ @Nested
+ @DisplayName("setting values to the authProvider configurations")
+ class ConfigurationsTest {
+ @Test
+ void testAuthProviderConfiguration() throws Exception {
+ AuthProvider provider = new AuthProvider.Configurations()
+ .projectId("test-project")
+ .tokenId("test-token")
+ .privateKey("test-key")
+ .build();
+
+ assertNotNull(provider);
+ assertEquals("test-project", provider.getProjectId());
+ assertEquals("test-token", provider.getTokenId());
+ assertEquals("test-key", provider.getPrivateKey());
+ }
+ }
+
+ @WireMockTest(proxyMode = true)
+ @Nested
+ @DisplayName("fetchProjectScopedToken method")
+ class FetchProjectScopedTokenTest {
+ @ParameterizedTest
+ @DisplayName("given an invalid private-key and a empty or non-empty passphrase, the it throws")
+ @EmptySource
+ @ValueSource(strings = { "complicated-word" })
+ void givenInvalidPrivateKey_thenThrows(String phrase) {
+ Exception exception = assertThrows(PSTGenerationException.class, () -> {
+ AuthProvider provider = new AuthProvider.Configurations()
+ .projectId("test-project")
+ .tokenId("test-token")
+ .privateKey("invalid-key")
+ .passphrase(phrase)
+ .build();
+ provider.fetchProjectScopedToken();
+ });
+
+ assertTrue(exception.getMessage()
+ .startsWith("Could not derive private key out of the configurations."));
+ }
+
+ @Test
+ @DisplayName("given the failing api-key endpoint call and failing to sign the payload, then it throws")
+ void givenInvalidApiGatewayUrl_thenThrows() {
+ // arrange
+ String mockErrorMessage = "mock-exception-message";
+ try (MockedStatic utilsMock = Mockito.mockStatic(JwtUtil.class)) {
+ utilsMock.when(() -> JwtUtil.fetchPublicKey(any()))
+ .thenThrow(InvalidPublicKeyException.class);
+ utilsMock.when(() -> JwtUtil.signPayload(any(), any(), any(), any(), any()))
+ .thenThrow(new JwtGenerationException(mockErrorMessage));
+
+ // act
+ Exception exception = assertThrows(PSTGenerationException.class, () -> {
+ AuthProvider provider = new AuthProvider.Configurations()
+ .projectId("test-project")
+ .tokenId("test-token")
+ .privateKey("test-key")
+ .build();
+ provider.fetchProjectScopedToken();
+ });
+
+ // assert
+ assertEquals(mockErrorMessage, exception.getMessage());
+ }
+ }
+
+ @Test
+ @DisplayName("given an invalid private-key, when the api-key endpoint returns a response and it fails to sign the payload, then it throws")
+ void givenInvalidPrivateKey_AndApiKeyResponse_thenThrows(WireMockRuntimeInfo wmRuntimeInfo)
+ throws IOException, URISyntaxException {
+ // arrange
+ String apiUrl = wmRuntimeInfo.getHttpBaseUrl();
+ URI uri = new URI(apiUrl);
+ String host = uri.getHost();
+ String apiKeyJson = new String(
+ Files.readAllBytes(Paths.get(
+ "src/test/java/com/affinidi/tdk/authProvider/resources/api-key-response.json")));
+ givenThat(get(AuthProviderConstants.PUBLIC_KEY_PATH)
+ .withHost(equalTo(host))
+ .willReturn(okJson(apiKeyJson)));
+
+ // act
+ Exception exception = assertThrows(PSTGenerationException.class, () -> {
+ AuthProvider provider = new AuthProvider.Configurations()
+ .projectId("test-project")
+ .tokenId("test-token")
+ .privateKey("test-key")
+ .passphrase("")
+ .build();
+ provider.setApiGatewayUrl(apiUrl);
+ provider.setProjectScopeToken("test-project-scope-token");
+ provider.fetchProjectScopedToken();
+ });
+
+ // assert
+ assertTrue(exception.getMessage()
+ .startsWith(AuthProviderConstants.COULD_NOT_DERIVE_PRIVATE_KEY_ERROR_MSG
+ + " Exception : "));
+ }
+
+ @Test
+ @DisplayName("happy path: given a valid private-key, when all the endponts succesfully returns 200, then it returns a JWT")
+ void givenValidApiKeyResponse_AndSuccessfulApiCalls_thenReturnsAJWT(WireMockRuntimeInfo wmRuntimeInfo)
+ throws IOException, URISyntaxException, ConfigurationException {
+ // arrange
+ String apiUrl = wmRuntimeInfo.getHttpBaseUrl();
+ URI uri = new URI(apiUrl);
+ String host = uri.getHost();
+ String fakeTokenUrl = apiUrl + "/auth-token";
+ String apiKeyJson = new String(
+ Files.readAllBytes(Paths.get(
+ "src/test/java/com/affinidi/tdk/authProvider/resources/api-key-response.json")));
+ String testPrivateKey = new String(
+ Files.readAllBytes(Paths.get(
+ "src/test/java/com/affinidi/tdk/authProvider/resources/test-private-key.txt")));
+ AuthProvider provider = new AuthProvider.Configurations()
+ .projectId("test-project")
+ .tokenId("test-token")
+ .privateKey(testPrivateKey)
+ .passphrase("")
+ .build();
+ provider.setApiGatewayUrl(apiUrl);
+ provider.setTokenEndPoint(fakeTokenUrl);
+ givenThat(get(AuthProviderConstants.PUBLIC_KEY_PATH)
+ .withHost(equalTo(host))
+ .willReturn(okJson(apiKeyJson)));
+ givenThat(post("/auth-token")
+ .withHost(equalTo(host))
+ .willReturn(okJson("{access_token: \"some-access-token\"}")));
+ givenThat(post(AuthProviderConstants.PROJECT_SCOPE_TOKEN_API_PATH).withHost(equalTo(host))
+ .willReturn(okJson("{accessToken: \"some-project-scope-token\"}")));
+
+ // act and assert
+ String token = assertDoesNotThrow(() -> provider.fetchProjectScopedToken());
+ assertEquals("some-project-scope-token", token);
+ }
+ }
+
+ @WireMockTest(proxyMode = true)
+ @Nested
+ @DisplayName("shouldRefreshToken method")
+ class ShouldRefreshTokenTest {
+ @Test
+ @DisplayName("given no project-token, then it returns true")
+ void givenNoProjectToken_thenReturnsTrue() throws ConfigurationException {
+ // arrange
+ AuthProvider provider = new AuthProvider.Configurations()
+ .projectId("test-project")
+ .tokenId("test-token")
+ .privateKey("test-key")
+ .build();
+
+ // act and assert
+ assertTrue(provider.shouldRefreshToken());
+ }
+
+ @Test
+ @DisplayName("given a project-token, when the api-key endpoint call fails, then it returns true")
+ void givenProjectToken_whenTheApiKeyEndpointCallFails_thenReturnsTrue(WireMockRuntimeInfo wmRuntimeInfo)
+ throws ConfigurationException, URISyntaxException {
+ // arrange
+ String apiUrl = wmRuntimeInfo.getHttpBaseUrl();
+ URI uri = new URI(apiUrl);
+ String host = uri.getHost();
+ givenThat(get(AuthProviderConstants.PUBLIC_KEY_PATH)
+ .withHost(equalTo(host))
+ .willReturn(aResponse().withFault(Fault.MALFORMED_RESPONSE_CHUNK)));
+
+ // act
+ AuthProvider provider = new AuthProvider.Configurations()
+ .projectId("test-project")
+ .tokenId("test-token")
+ .privateKey("test-key")
+ .passphrase("")
+ .build();
+ provider.setApiGatewayUrl(apiUrl);
+ provider.setProjectScopeToken("test-project-scope-token");
+
+ // assert
+ assertTrue(provider.shouldRefreshToken());
+ }
+
+ @Test
+ @DisplayName("given an invalid project-token, when the api-key endpoint call succeeds, then it returns true")
+ void givenInvalidPrivateKey_WhenTheApiKeyEndpointCallSucceeds_thenReturnsTrue(
+ WireMockRuntimeInfo wmRuntimeInfo) throws URISyntaxException, IOException, ConfigurationException {
+ // arrange
+ String apiUrl = wmRuntimeInfo.getHttpBaseUrl();
+ URI uri = new URI(apiUrl);
+ String host = uri.getHost();
+ String apiKeyJson = new String(
+ Files.readAllBytes(Paths.get(
+ "src/test/java/com/affinidi/tdk/authProvider/resources/api-key-response.json")));
+ givenThat(get(AuthProviderConstants.PUBLIC_KEY_PATH)
+ .withHost(equalTo(host))
+ .willReturn(okJson(apiKeyJson)));
+ AuthProvider provider = new AuthProvider.Configurations()
+ .projectId("test-project")
+ .tokenId("test-token")
+ .privateKey("test-key")
+ .passphrase("")
+ .build();
+ provider.setApiGatewayUrl(apiUrl);
+ provider.setProjectScopeToken("test-project-scope-token");
+
+ // act and assert
+ assertTrue(provider.shouldRefreshToken());
+ }
+ }
+
+ @Test
+ void testGetUserAccessToken() {
+
+ }
+
+ @Test
+ void testSignIotaJwt() {
+
+ }
+}
diff --git a/packages/java/auth.provider/src/test/java/com/affinidi/tdk/authProvider/helper/JwtUtilTest.java b/packages/java/auth.provider/src/test/java/com/affinidi/tdk/authProvider/helper/JwtUtilTest.java
new file mode 100644
index 000000000..41d8fa850
--- /dev/null
+++ b/packages/java/auth.provider/src/test/java/com/affinidi/tdk/authProvider/helper/JwtUtilTest.java
@@ -0,0 +1,33 @@
+package com.affinidi.tdk.authProvider.helper;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class JwtUtilTest {
+
+ @BeforeEach
+ void setUp() {
+
+ }
+
+ @Test
+ void testFetchPublicKey() {
+
+ }
+
+ @Test
+ void testSignIotaPayload() {
+
+ }
+
+ @Test
+ void testSignPayload() {
+
+ }
+
+ @Test
+ void testValidProjectTokenPresent() {
+
+ }
+
+}
diff --git a/packages/java/auth.provider/src/test/java/com/affinidi/tdk/authProvider/resources/api-key-response.json b/packages/java/auth.provider/src/test/java/com/affinidi/tdk/authProvider/resources/api-key-response.json
new file mode 100644
index 000000000..8ccca4310
--- /dev/null
+++ b/packages/java/auth.provider/src/test/java/com/affinidi/tdk/authProvider/resources/api-key-response.json
@@ -0,0 +1,49 @@
+{
+ "keys": [
+ {
+ "kid": "a622a999-9846-48cf-a470-22759e1f435a",
+ "alg": "ES256",
+ "use": "sig",
+ "kty": "EC",
+ "crv": "P-256",
+ "x": "b3kdYEBrlWjQwY55F8MhXC97pwkjTpcQZZ09oDDBK4c",
+ "y": "wlopQwIPWuT55M3ZfCDZdoBs1nh2kwEvzPjnkakf96U"
+ },
+ {
+ "use": "sig",
+ "alg": "ES256",
+ "kty": "EC",
+ "x": "-ayYp8f4Yx47G-Fet4Yuib8rdqZ_XGJZpT60GPocpWw",
+ "y": "TAkQCuTywyl7hwmNN_qHJWKqFE0MIPOf2JAl2o2gPOI",
+ "crv": "P-256",
+ "kid": "03f8de27-d281-47a0-bf8a-affdff3492d6"
+ },
+ {
+ "kid": "9e79e4bc-d93b-48d3-934f-c878ee83d683",
+ "kty": "EC",
+ "x": "fEJH7KRt2tjtAF4pdJEJX0dKXhCNfKtwswfJN11uQZk",
+ "y": "dROiiihrMI-jeCTWbcG6IMJM2oGQuP4gjh0oiCi0KgA",
+ "crv": "P-256",
+ "use": "sig",
+ "alg": "ES256"
+ },
+ {
+ "kty": "EC",
+ "x": "9fajsaREKgJxugEZkms44nR4mJSvVrdYdqFe_-_eGxI",
+ "y": "2ZIdxAoW5NeHUmqLhAwGpKuikm62dFLE1lIhrt5TdxQ",
+ "crv": "P-256",
+ "kid": "12f6aa3d-e692-4d20-a629-52bb14ced005",
+ "alg": "ES256",
+ "use": "sig"
+ },
+ {
+ "use": "sig",
+ "alg": "ES256",
+ "kty": "EC",
+ "x": "tvpex4bxW4K35o_eEqKEf2-KjnzvVn2kFAp6j7kBxZ0",
+ "y": "tRKgJ3faS92y3dxK97COcaLvNiS1VHyonaeRyHj2XHA",
+ "crv": "P-256",
+ "kid": "576b97f6-1ae3-495f-8157-4877cd3036b0"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/java/auth.provider/src/test/java/com/affinidi/tdk/authProvider/resources/test-private-key.txt b/packages/java/auth.provider/src/test/java/com/affinidi/tdk/authProvider/resources/test-private-key.txt
new file mode 100644
index 000000000..4c6181bc4
--- /dev/null
+++ b/packages/java/auth.provider/src/test/java/com/affinidi/tdk/authProvider/resources/test-private-key.txt
@@ -0,0 +1 @@
+-----BEGIN PRIVATE KEY-----\n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\n2SXF2Id7dMeTOTULKZnKC+AH1OGzwN0=\n-----END PRIVATE KEY-----\n
\ No newline at end of file
diff --git a/packages/java/common/.env.example b/packages/java/common/.env.example
new file mode 100644
index 000000000..4c79f59c8
--- /dev/null
+++ b/packages/java/common/.env.example
@@ -0,0 +1,2 @@
+AFFINIDI_TDK_ENVIRONMENT=
+NEXT_PUBLIC_AFFINIDI_TDK_ENVIRONMENT=
diff --git a/packages/java/common/pom.xml b/packages/java/common/pom.xml
new file mode 100644
index 000000000..d9ea60422
--- /dev/null
+++ b/packages/java/common/pom.xml
@@ -0,0 +1,124 @@
+
+
+
+ 4.0.0
+ jar
+ com.affinidi.tdk
+ common
+ 1.0
+ common
+ https://github.com/affinidi/affinidi-tdk
+
+
+ Apache-2.0
+ https://github.com/affinidi/affinidi-tdk/blob/main/LICENSE
+ repo
+
+
+
+
+
+ Affinidi
+ ...
+ Affinidi
+ https://affinidi.com
+
+
+
+
+ UTF-8
+ 1.8
+ ${java.version}
+ ${java.version}
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.11.0
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.11.0
+ test
+
+
+ io.github.cdimascio
+ dotenv-java
+ 2.2.0
+
+
+ org.glassfish.jersey.core
+ jersey-common
+ 3.1.7
+
+
+ jakarta.ws.rs
+ jakarta.ws.rs-api
+ 3.1.0
+
+
+ org.mockito
+
+ mockito-inline
+ 3.12.4
+ test
+
+
+
+
+
+
+
+
+
+ maven-clean-plugin
+ 3.1.0
+
+
+
+ maven-resources-plugin
+ 3.0.2
+
+
+ maven-compiler-plugin
+ 3.8.0
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ maven-failsafe-plugin
+ 3.5.0
+
+
+ maven-jar-plugin
+ 3.0.2
+
+
+ maven-install-plugin
+ 2.5.2
+
+
+ maven-deploy-plugin
+ 2.8.2
+
+
+
+ maven-site-plugin
+ 3.7.1
+
+
+ maven-project-info-reports-plugin
+ 3.0.0
+
+
+
+
+
diff --git a/packages/java/common/src/main/java/com/affinidi/tdk/common/EnvironmentUtil.java b/packages/java/common/src/main/java/com/affinidi/tdk/common/EnvironmentUtil.java
new file mode 100644
index 000000000..57604afd2
--- /dev/null
+++ b/packages/java/common/src/main/java/com/affinidi/tdk/common/EnvironmentUtil.java
@@ -0,0 +1,185 @@
+package com.affinidi.tdk.common;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import io.github.cdimascio.dotenv.Dotenv;
+import io.github.cdimascio.dotenv.DotenvBuilder;
+
+/**
+ * This class provides utility functions required to access environment specific
+ * configurations. The environment can be specified in the .env file at the
+ * project base using AFFINIDI_TDK_PROPERTY_NAME or
+ * NEXT_PUBLIC_AFFINIDI_TDK_ENVIRONMENT as dev, local or prod.
+ *
+ * @author Priyanka
+ */
+public class EnvironmentUtil {
+
+ private static final Dotenv PROPERTIES = new DotenvBuilder().ignoreIfMissing().load();
+ private static final Logger LOGGER = Logger.getLogger(EnvironmentUtil.class.getName());
+
+ public static Dotenv getProperties() {
+ return PROPERTIES;
+ }
+
+ /**
+ * Returns the environment name as configured in the .env file In case no
+ * configuration is found, the default return is production environment.
+ *
+ * @return String
+ */
+ public static String getConfiguredEnvironment() {
+ String configuredEnvironment = null;
+ try {
+ if (getProperties() != null) {
+ configuredEnvironment = getProperties().get(Environment.AFFINIDI_TDK_PROPERTY_NAME);
+ if (configuredEnvironment == null) {
+ configuredEnvironment = getProperties().get(Environment.NEXT_AFFINIDI_TDK_PROPERTY_NAME);
+ }
+ }
+ } catch (Exception exception) {
+ LOGGER.severe("Could not read .env file for TDK environment configuration");
+ }
+
+ if (configuredEnvironment == null) {
+ LOGGER.log(Level.SEVERE, "Could not find environment details for {0}. Defaulting to production",
+ configuredEnvironment);
+ configuredEnvironment = Environment.PRODUCTION.environmentName;
+ }
+ return configuredEnvironment;
+ }
+
+ /**
+ * Return the default region name.
+ *
+ * @return String
+ */
+ public static String getDefaultRegion() {
+ return Environment.DEFAULT_REGION;
+ }
+
+ /**
+ * Returns the IOT url string for the configured environment.
+ *
+ * @return String
+ */
+ public static String getIotUrlForEnvironment() {
+ return getEnvironmentDetail().iotUrl;
+ }
+
+ /**
+ * Returns the elements auth token url string for the configured
+ * environment.
+ *
+ * @return String
+ */
+ public static String getElementAuthTokenUrlForEnvironment() {
+ return getEnvironmentDetail().elementsAuthTokenUrl;
+ }
+
+ /**
+ * Returns the vault URL for the configured environment.
+ *
+ * @return String
+ */
+ public static String getVaultUrlForEnvironment() {
+ return getEnvironmentDetail().vaultUrl;
+ }
+
+ /**
+ * @return String
+ */
+ public static String getApiGatewayUrlForEnvironment() {
+ return getEnvironmentDetail().apiGatewayUrl;
+ }
+
+ /**
+ * @return String
+ */
+ public static String getValueFromEnvConfig(String propertyName) {
+ try {
+ return getProperties().get(propertyName);
+ } catch (Exception exception) {
+ LOGGER.log(Level.SEVERE, "Could not read .env file for {0}", propertyName);
+ }
+ return null;
+ }
+
+ /**
+ * Returns the APi gateway URL for the configured environment
+ *
+ * @return String
+ */
+ static Environment getEnvironmentDetail() {
+ final String envName = getConfiguredEnvironment();
+ final Environment envDetail = (envName != null) ? Environment.getEnvSpecificDetails(envName) : null;
+
+ if (envDetail != null) {
+ return envDetail;
+ }
+ LOGGER.log(Level.SEVERE,
+ "Could not find environment details for the specified name {0}. Hence defaulting to production",
+ envName);
+ return Environment.getEnvSpecificDetails(Environment.PRODUCTION.environmentName);
+ }
+}
+
+enum Environment {
+
+ LOCAL("LOCAL", Environment.LOCAL_IOT_URL, Environment.LOCAL_APIGATEWAY_URL,
+ Environment.LOCAL_ELEMENTS_AUTH_TOKEN_URL, Environment.LOCAL_VAULT_URL),
+ DEVELOPMENT("DEV", Environment.DEV_IOT_URL, Environment.DEV_APIGATEWAY_URL, Environment.DEV_ELEMENTS_AUTH_TOKEN_URL,
+ Environment.DEV_VAULT_URL),
+ PRODUCTION("PROD", Environment.PROD_IOT_URL, Environment.PROD_APIGATEWAY_URL,
+ Environment.PROD_ELEMENTS_AUTH_TOKEN_URL, Environment.PROD_VAULT_URL),;
+
+ static Environment getEnvSpecificDetails(String environmentName) {
+ return EnvironmentMap.get(environmentName.toUpperCase());
+ }
+
+ public final String environmentName;
+ public final String iotUrl;
+ public final String elementsAuthTokenUrl;
+ public final String vaultUrl;
+ public final String apiGatewayUrl;
+
+ private Environment(String environmentName, String iotUrl, String apiGatewayUrl, String elementsAuthTokenUrl,
+ String vaultUrl) {
+ this.environmentName = environmentName;
+ this.iotUrl = iotUrl;
+ this.apiGatewayUrl = apiGatewayUrl;
+ this.elementsAuthTokenUrl = elementsAuthTokenUrl;
+ this.vaultUrl = vaultUrl;
+ }
+
+ private static final Map EnvironmentMap = new HashMap<>();
+
+ static {
+ for (Environment env : values()) {
+ EnvironmentMap.put(env.environmentName, env);
+ }
+ }
+
+ static final String AFFINIDI_TDK_PROPERTY_NAME = "AFFINIDI_TDK_ENVIRONMENT";
+ static final String NEXT_AFFINIDI_TDK_PROPERTY_NAME = "NEXT_PUBLIC_AFFINIDI_TDK_ENVIRONMENT";
+ static final String DEFAULT_REGION = "ap-southeast-1";
+
+ static final String LOCAL_IOT_URL = "a3sq1vuw0cw9an-ats.iot.ap-southeast-1.amazonaws.com";
+ static final String LOCAL_APIGATEWAY_URL = "https://apse1.dev.api.affinidi.io";
+ static final String LOCAL_ELEMENTS_AUTH_TOKEN_URL = "https://apse1.dev.auth.developer.affinidi.io/auth/oauth2/token";
+ static final String LOCAL_VAULT_URL = "http://localhost:3001";
+
+ static final String DEV_IOT_URL = "a3sq1vuw0cw9an-ats.iot.ap-southeast-1.amazonaws.com";
+ static final String DEV_APIGATEWAY_URL = "https://apse1.dev.api.affinidi.io";
+ static final String DEV_ELEMENTS_AUTH_TOKEN_URL = "https://apse1.dev.auth.developer.affinidi.io/auth/oauth2/token";
+ static final String DEV_VAULT_URL = "https://vault.dev.affinidi.com";
+
+ static final String PROD_IOT_URL = "a13pfgsvt8xhx-ats.iot.ap-southeast-1.amazonaws.com";
+ static final String PROD_APIGATEWAY_URL = "https://apse1.api.affinidi.io";
+ static final String PROD_ELEMENTS_AUTH_TOKEN_URL = "https://apse1.auth.developer.affinidi.io/auth/oauth2/token";
+ static final String PROD_VAULT_URL = "https://vault.affinidi.com";
+
+}
diff --git a/packages/java/common/src/main/java/com/affinidi/tdk/common/VaultUtil.java b/packages/java/common/src/main/java/com/affinidi/tdk/common/VaultUtil.java
new file mode 100644
index 000000000..72d872f15
--- /dev/null
+++ b/packages/java/common/src/main/java/com/affinidi/tdk/common/VaultUtil.java
@@ -0,0 +1,63 @@
+package com.affinidi.tdk.common;
+
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import jakarta.ws.rs.core.UriBuilder;
+
+/**
+ * This class provides utility functions required to create vault specific
+ * links. Refer {@EnvironmentUtil} for more information.
+ *
+ *
+ * @author Priyanka
+ */
+public final class VaultUtil {
+
+ static final String VAULT_SHARE_PATH = "/login";
+ static final String VAULT_CLAIM_PATH = "/claim";
+ static final String SHARE_REQUEST_PARAM = "request";
+ static final String SHARE_CLIENT_PARAM = "clientId";
+ static final String CLAIM_CREDENTIAL_URI_PARAM = "credential_offer_uri";
+
+ private static final Logger LOGGER = Logger.getLogger(VaultUtil.class.getName());
+
+ /**
+ * Returns the credential offer claim link specific to the configured
+ * environment.
+ *
+ * @param credentialOfferUri
+ * @return String
+ */
+ public static String buildClaimLink(String credentialOfferUri) {
+ String webVaultUrl = EnvironmentUtil.getVaultUrlForEnvironment();
+
+ if (credentialOfferUri == null || credentialOfferUri.equals("")) {
+ LOGGER.log(Level.SEVERE, "Invalid Credential Offer URI passed to utility {0}. Returning vault url", credentialOfferUri);
+ return webVaultUrl;
+ }
+
+ webVaultUrl = String.join("", webVaultUrl, VAULT_CLAIM_PATH);
+ URI claimUri = UriBuilder.fromUri(webVaultUrl).queryParam(CLAIM_CREDENTIAL_URI_PARAM, credentialOfferUri).build();
+ return claimUri.toString();
+ }
+
+ /**
+ * Returns the vault share link given request and a client id, specific to
+ * the configured environment.
+ *
+ * @param request
+ * @param clientId
+ * @return String
+ */
+ public static String buildShareLink(String request, String clientId) {
+
+ String webVaultUrl = EnvironmentUtil.getVaultUrlForEnvironment();
+ webVaultUrl = String.join("", webVaultUrl, VAULT_SHARE_PATH);
+
+ URI shareUri = UriBuilder.fromUri(webVaultUrl).
+ queryParam(SHARE_REQUEST_PARAM, request).queryParam(SHARE_CLIENT_PARAM, clientId).build();
+ return shareUri.toString();
+ }
+}
diff --git a/packages/java/common/src/test/java/com/affinidi/tdk/common/EnvironmentUtilTest.java b/packages/java/common/src/test/java/com/affinidi/tdk/common/EnvironmentUtilTest.java
new file mode 100644
index 000000000..678a9a7ca
--- /dev/null
+++ b/packages/java/common/src/test/java/com/affinidi/tdk/common/EnvironmentUtilTest.java
@@ -0,0 +1,107 @@
+package com.affinidi.tdk.common;
+
+import java.util.concurrent.Callable;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import static org.mockito.Mockito.mockStatic;
+import org.opentest4j.TestAbortedException;
+
+public class EnvironmentUtilTest {
+
+ private void test_getUrlForEnvironment(String configuredEnvironment, Callable getUrlForEnvironment, String expectedUrlForEnvironment) {
+ try (MockedStatic mocked = mockStatic(EnvironmentUtil.class)) {
+ // Test the function call for local environment without reading the configured property
+ mocked.when(EnvironmentUtil::getConfiguredEnvironment).thenReturn(configuredEnvironment);
+ mocked.when(EnvironmentUtil::getEnvironmentDetail).thenCallRealMethod();
+ mocked.when(EnvironmentUtil::getVaultUrlForEnvironment).thenCallRealMethod();
+ mocked.when(EnvironmentUtil::getIotUrlForEnvironment).thenCallRealMethod();
+ mocked.when(EnvironmentUtil::getElementAuthTokenUrlForEnvironment).thenCallRealMethod();
+ mocked.when(EnvironmentUtil::getApiGatewayUrlForEnvironment).thenCallRealMethod();
+ try {
+ // assertEquals(expectedUrlForEnvironment, EnvironmentUtil.getVaultUrlForEnvironment());
+ assertEquals(expectedUrlForEnvironment, getUrlForEnvironment.call());
+ } catch (Exception ex) {
+ throw new TestAbortedException(ex.getMessage());
+ }
+
+ }
+ }
+
+ private void test_getVaultUrlForEnvironment(String configuredEnvironment, String expectedVaultUrlForEnvironment) {
+ test_getUrlForEnvironment(configuredEnvironment, () -> EnvironmentUtil.getVaultUrlForEnvironment(), expectedVaultUrlForEnvironment);
+ }
+
+ private void test_getIotUrlForEnvironment(String configuredEnvironment, String expectedIotUrlForEnvironment) {
+ test_getUrlForEnvironment(configuredEnvironment, () -> EnvironmentUtil.getIotUrlForEnvironment(), expectedIotUrlForEnvironment);
+ }
+
+ private void test_getElementAuthTokenUrlForEnvironment(String configuredEnvironment, String expectedElementAuthTokenUrlForEnvironment) {
+ test_getUrlForEnvironment(configuredEnvironment, () -> EnvironmentUtil.getElementAuthTokenUrlForEnvironment(), expectedElementAuthTokenUrlForEnvironment);
+ }
+
+ private void test_getApiGatewayUrlForEnvironment(String configuredEnvironment, String expectedApiGatewayUrlForEnvironment) {
+ test_getUrlForEnvironment(configuredEnvironment, () -> EnvironmentUtil.getApiGatewayUrlForEnvironment(), expectedApiGatewayUrlForEnvironment);
+ }
+
+ @Test
+ void testGetVaultUrlForEnvironment_InLocal() {
+ test_getVaultUrlForEnvironment("LOCAL", Environment.LOCAL_VAULT_URL);
+ }
+
+ @Test
+ void testGetVaultUrlForEnvironment_InDev() {
+ test_getVaultUrlForEnvironment("DEV", Environment.DEV_VAULT_URL);
+ }
+
+ @Test
+ void testGetVaultUrlForEnvironment_InProd() {
+ test_getVaultUrlForEnvironment("PROD", Environment.PROD_VAULT_URL);
+ }
+
+ @Test
+ void testGetIotaUrlForEnvironment_InLocal() {
+ test_getIotUrlForEnvironment("LOCAL", Environment.LOCAL_IOT_URL);
+ }
+
+ @Test
+ void testGetIotaUrlForEnvironment_InDev() {
+ test_getIotUrlForEnvironment("DEV", Environment.DEV_IOT_URL);
+ }
+
+ @Test
+ void testGetIotaUrlForEnvironment_InProd() {
+ test_getIotUrlForEnvironment("PROD", Environment.PROD_IOT_URL);
+ }
+
+ @Test
+ void testGetElementAuthTokenUrlForEnvironment_InLocal() {
+ test_getElementAuthTokenUrlForEnvironment("LOCAL", Environment.LOCAL_ELEMENTS_AUTH_TOKEN_URL);
+ }
+
+ @Test
+ void testGetElementAuthTokenUrlForEnvironment_InDev() {
+ test_getElementAuthTokenUrlForEnvironment("DEV", Environment.DEV_ELEMENTS_AUTH_TOKEN_URL);
+ }
+
+ @Test
+ void testGetElementAuthTokenUrlForEnvironment_InProd() {
+ test_getElementAuthTokenUrlForEnvironment("PROD", Environment.PROD_ELEMENTS_AUTH_TOKEN_URL);
+ }
+
+ @Test
+ void testGetApiGatewayUrlForEnvironment_InLocal() {
+ test_getApiGatewayUrlForEnvironment("LOCAL", Environment.LOCAL_APIGATEWAY_URL);
+ }
+
+ @Test
+ void testGetApiGatewayUrlForEnvironment_InDev() {
+ test_getApiGatewayUrlForEnvironment("DEV", Environment.DEV_APIGATEWAY_URL);
+ }
+
+ @Test
+ void testGetApiGatewayUrlForEnvironment_InProd() {
+ test_getApiGatewayUrlForEnvironment("PROD", Environment.PROD_APIGATEWAY_URL);
+ }
+}
diff --git a/packages/java/common/src/test/java/com/affinidi/tdk/common/VaultUtilTest.java b/packages/java/common/src/test/java/com/affinidi/tdk/common/VaultUtilTest.java
new file mode 100644
index 000000000..17642b336
--- /dev/null
+++ b/packages/java/common/src/test/java/com/affinidi/tdk/common/VaultUtilTest.java
@@ -0,0 +1,97 @@
+package com.affinidi.tdk.common;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import static org.mockito.Mockito.mockStatic;
+import org.mockito.MockitoAnnotations;
+
+public class VaultUtilTest {
+
+ @BeforeEach
+ void setUp() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ @Test
+ void testBuildShareLink_InLocal() {
+ try (MockedStatic mocked = mockStatic(EnvironmentUtil.class)) {
+ mocked.when(EnvironmentUtil::getVaultUrlForEnvironment).thenReturn(Environment.LOCAL_VAULT_URL);
+ String testRequestId = "request1234";
+ String testClientId = "clientabcde";
+ String shareLink = VaultUtil.buildShareLink(testRequestId, testClientId);
+ String expectedValue = Environment.LOCAL_VAULT_URL + VaultUtil.VAULT_SHARE_PATH
+ + "?" + VaultUtil.SHARE_REQUEST_PARAM + "=" + testRequestId
+ + "&" + VaultUtil.SHARE_CLIENT_PARAM + "=" + testClientId;
+ assertEquals(expectedValue, shareLink);
+ }
+ }
+
+ @Test
+ void testBuildShareLink_InDev() {
+ try (MockedStatic mocked = mockStatic(EnvironmentUtil.class)) {
+ mocked.when(EnvironmentUtil::getVaultUrlForEnvironment).thenReturn(Environment.DEV_VAULT_URL);
+ String testRequestId = "request1234";
+ String testClientId = "clientabcde";
+ String shareLink = VaultUtil.buildShareLink(testRequestId, testClientId);
+ String expectedValue = Environment.DEV_VAULT_URL + VaultUtil.VAULT_SHARE_PATH
+ + "?" + VaultUtil.SHARE_REQUEST_PARAM + "=" + testRequestId
+ + "&" + VaultUtil.SHARE_CLIENT_PARAM + "=" + testClientId;
+ assertEquals(expectedValue, shareLink);
+ }
+ }
+
+ @Test
+ void testBuildShareLink_InProd() {
+ try (MockedStatic mocked = mockStatic(EnvironmentUtil.class)) {
+ mocked.when(EnvironmentUtil::getVaultUrlForEnvironment).thenReturn(Environment.PROD_VAULT_URL);
+ String testRequestId = "request1234";
+ String testClientId = "clientabcde";
+ String shareLink = VaultUtil.buildShareLink(testRequestId, testClientId);
+ String expectedValue = Environment.PROD_VAULT_URL + VaultUtil.VAULT_SHARE_PATH
+ + "?" + VaultUtil.SHARE_REQUEST_PARAM + "=" + testRequestId
+ + "&" + VaultUtil.SHARE_CLIENT_PARAM + "=" + testClientId;
+ assertEquals(expectedValue, shareLink);
+ }
+ }
+
+ @Test
+ void testBuildClaimLink_InLocal() {
+ try (MockedStatic mocked = mockStatic(EnvironmentUtil.class)) {
+ mocked.when(EnvironmentUtil::getVaultUrlForEnvironment).thenReturn(Environment.LOCAL_VAULT_URL);
+ String testCredentialOfferUri = "https://00112233-xxx-yyyy-aaaaa-abcd2345678.apse1.issuance.affinidi.io/offers/cb3817e6-4b16-4f19-a658-8eb096541bcc";
+ String claimLink = VaultUtil.buildClaimLink(testCredentialOfferUri);
+ String expectedValue = Environment.LOCAL_VAULT_URL + VaultUtil.VAULT_CLAIM_PATH
+ + "?" + VaultUtil.CLAIM_CREDENTIAL_URI_PARAM + "=" + URLEncoder.encode(testCredentialOfferUri, StandardCharsets.UTF_8);
+ assertEquals(expectedValue, claimLink);
+ }
+ }
+
+ @Test
+ void testBuildClaimLink_InDev() {
+ try (MockedStatic mocked = mockStatic(EnvironmentUtil.class)) {
+ mocked.when(EnvironmentUtil::getVaultUrlForEnvironment).thenReturn(Environment.DEV_VAULT_URL);
+ String testCredentialOfferUri = "https://00112233-xxx-yyyy-aaaaa-abcd2345678.apse1.issuance.affinidi.io/offers/cb3817e6-4b16-4f19-a658-8eb096541bcc";
+ String claimLink = VaultUtil.buildClaimLink(testCredentialOfferUri);
+ String expectedValue = Environment.DEV_VAULT_URL + VaultUtil.VAULT_CLAIM_PATH
+ + "?" + VaultUtil.CLAIM_CREDENTIAL_URI_PARAM + "=" + URLEncoder.encode(testCredentialOfferUri, StandardCharsets.UTF_8);
+ assertEquals(expectedValue, claimLink);
+ }
+ }
+
+ @Test
+ void testBuildClaimLink_InProd() {
+ try (MockedStatic mocked = mockStatic(EnvironmentUtil.class)) {
+ mocked.when(EnvironmentUtil::getVaultUrlForEnvironment).thenReturn(Environment.PROD_VAULT_URL);
+ String testCredentialOfferUri = "https://00112233-xxx-yyyy-aaaaa-abcd2345678.apse1.issuance.affinidi.io/offers/cb3817e6-4b16-4f19-a658-8eb096541bcc";
+ String claimLink = VaultUtil.buildClaimLink(testCredentialOfferUri);
+ String expectedValue = Environment.PROD_VAULT_URL + VaultUtil.VAULT_CLAIM_PATH
+ + "?" + VaultUtil.CLAIM_CREDENTIAL_URI_PARAM + "=" + URLEncoder.encode(testCredentialOfferUri, StandardCharsets.UTF_8);
+ assertEquals(expectedValue, claimLink);
+ }
+ }
+}
diff --git a/tests/integration/java/reference/.env.example b/tests/integration/java/reference/.env.example
new file mode 100644
index 000000000..b87060e4d
--- /dev/null
+++ b/tests/integration/java/reference/.env.example
@@ -0,0 +1,7 @@
+AFFINIDI_TDK_ENVIRONMENT=prod
+
+PROJECT_ID=""
+KEY_ID=
+TOKEN_ID=""
+PASSPHRASE=""
+PRIVATE_KEY=""
\ No newline at end of file
diff --git a/tests/integration/java/reference/pom.xml b/tests/integration/java/reference/pom.xml
new file mode 100644
index 000000000..2e30b9a65
--- /dev/null
+++ b/tests/integration/java/reference/pom.xml
@@ -0,0 +1,114 @@
+
+
+
+ 4.0.0
+ jar
+ com.affinidi.tdk
+ reference
+ 1.0
+ reference
+ https://github.com/affinidi/affinidi-tdk
+
+
+ Apache-2.0
+ https://github.com/affinidi/affinidi-tdk/blob/main/LICENSE
+ repo
+
+
+
+
+
+ Affinidi
+ ...
+ Affinidi
+ https://affinidi.com
+
+
+
+
+ UTF-8
+ 1.8
+ ${java.version}
+ ${java.version}
+ 1.0
+ 1.0
+
+
+
+
+
+ com.affinidi.tdk
+ common
+ ${affinidi-common-version}
+
+
+ com.affinidi.tdk
+ auth.provider
+ ${affinidi-auth-version}
+
+
+ com.affinidi.tdk
+ wallets.client
+ 1.0.0
+ compile
+
+
+ com.affinidi.tdk
+ credential.issuance.client
+ 1.0.0
+ compile
+
+
+
+
+
+
+
+
+ maven-clean-plugin
+ 3.1.0
+
+
+
+ maven-resources-plugin
+ 3.0.2
+
+
+ maven-compiler-plugin
+ 3.8.0
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ maven-failsafe-plugin
+ 3.5.0
+
+
+ maven-jar-plugin
+ 3.0.2
+
+
+ maven-install-plugin
+ 2.5.2
+
+
+ maven-deploy-plugin
+ 2.8.2
+
+
+
+ maven-site-plugin
+ 3.7.1
+
+
+ maven-project-info-reports-plugin
+ 3.0.0
+
+
+
+
+
diff --git a/tests/integration/java/reference/src/main/java/com/affinidi/reference/TdkTestUtil.java b/tests/integration/java/reference/src/main/java/com/affinidi/reference/TdkTestUtil.java
new file mode 100644
index 000000000..631606699
--- /dev/null
+++ b/tests/integration/java/reference/src/main/java/com/affinidi/reference/TdkTestUtil.java
@@ -0,0 +1,116 @@
+
+package com.affinidi.reference;
+
+
+
+
+import com.affinidi.tdk.authProvider.AuthProvider;
+
+import com.affinidi.tdk.credential.issuance.client.apis.IssuanceApi;
+
+import com.affinidi.tdk.wallets.client.ApiClient;
+import com.affinidi.tdk.wallets.client.Configuration;
+import com.affinidi.tdk.wallets.client.apis.WalletApi;
+import com.affinidi.tdk.wallets.client.auth.ApiKeyAuth;
+import com.affinidi.tdk.wallets.client.models.WalletsListDto;
+
+
+/**
+* To execute this class,
+1. Ensure that base package of the working directory contains a valid .env file with values for each property
+as present in .env.example
+2. Ensure that auth-provider and common package jar is present in either the central maven respository
+3. Or you can go to the resective pakage and run mvn clean install : in order to deply these jars in local
+maven respository
+*
+* @author Priyanka
+*
+*/
+
+public class TdkTestUtil {
+
+ public static void main(String arg[]){
+ try{
+ System.out.println("\n\n=============================");
+ System.out.println("01. Testing Wallet Client using TDK java packages");
+ System.out.println("=============================\n\n");
+ testWalletClient();
+ System.out.println("\n\n=============================");
+ System.out.println("Wallet Client testing using TDK java packages completed");
+ System.out.println("=============================\n\n");
+
+
+ System.out.println("\n\n=============================");
+ System.out.println("02. Testing Issuance Client using TDK java packages");
+ System.out.println("=============================\n\n");
+ testIssuanceClient();
+ System.out.println("\n\n=============================");
+ System.out.println("Issuance Client testing using TDK java packages completed");
+ System.out.println("=============================\n\n");
+ }catch(Exception exception){
+ exception.printStackTrace();
+ }
+ }
+
+ public static void testWalletClient() throws Exception{
+
+ System.out.println("Getting projectScopenToken before calling wallet API");
+
+ AuthProvider authProvider = new AuthProvider.Configurations().buildWithEnv();
+ String projectToken = authProvider.fetchProjectScopedToken();
+ System.out.println("ProjectScopeToken > > > > > : "+projectToken);
+
+ // Creating an API Client using the above token
+ ApiClient defaultClient = Configuration.getDefaultApiClient();
+ ApiKeyAuth ProjectTokenAuth = (ApiKeyAuth) defaultClient.getAuthentication("ProjectTokenAuth");
+ ProjectTokenAuth.setApiKey(projectToken);
+
+ WalletApi apiInstance = new WalletApi(defaultClient);
+ String didType = null;
+
+ System.out.println("Calling listWallets ");
+ WalletsListDto response = apiInstance.listWallets(didType);
+ System.out.println("Total wallets : "+response.getWallets().size());
+
+
+ System.out.println("Should AuthProvider refresh the token before any further call ? "+authProvider.shouldRefreshToken());
+ }
+
+ public static void testIssuanceClient() throws Exception{
+
+ System.out.println("Getting projectScopenToken before calling issuance API");
+ AuthProvider authProvider = new AuthProvider.Configurations().buildWithEnv();
+ String projectToken = authProvider.fetchProjectScopedToken();
+
+ // Creating an API Client using the above token
+ com.affinidi.tdk.credential.issuance.client.ApiClient issuanceClient = com.affinidi.tdk.credential.issuance.client.Configuration.getDefaultApiClient();
+ com.affinidi.tdk.credential.issuance.client.auth.ApiKeyAuth issueTokenAuth = (com.affinidi.tdk.credential.issuance.client.auth.ApiKeyAuth) issuanceClient.getAuthentication("ProjectTokenAuth");
+ issueTokenAuth.setApiKey(projectToken);
+
+ IssuanceApi issuanceApi = new IssuanceApi(issuanceClient);
+ String projectIdForTesting = "084036a6-a775-478a-9a97-a1323738897f";
+ System.out.println(issuanceApi.listIssuance(projectIdForTesting));
+
+ /* Uncomment to test startIssuance. Provide the relevant projectId and crdential data
+ StartIssuanceInput startIssuanceInput = new StartIssuanceInput();
+ startIssuanceInput.setHolderDid("did:key:zQ3shNb7dEAa7z4LY8eAbPafNM4iSxppwuvndoHkTUUp8Hbt6");
+ startIssuanceInput.setClaimMode(ClaimModeEnum.NORMAL);
+
+ StartIssuanceInputDataInner innerData = new StartIssuanceInputDataInner();
+ innerData.setCredentialTypeId("AlumniIdentityCard");
+ List list = new ArrayList();
+ list.add(innerData);
+
+ startIssuanceInput.setData(list);
+
+ System.out.println(issuanceApi.startIssuance("", startIssuanceInput));
+ */
+
+
+
+
+ }
+
+
+
+}