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)); + */ + + + + + } + + + +}