diff --git a/.github/workflows/maven-itest.yml b/.github/workflows/maven-itest.yml index 5bce429a..2068225b 100644 --- a/.github/workflows/maven-itest.yml +++ b/.github/workflows/maven-itest.yml @@ -56,6 +56,41 @@ jobs: aio_api_url: ${{ secrets.aio_api_url }} aio_publish_url: ${{ secrets.aio_publish_url }} + stage_oauth_itest: + runs-on: ubuntu-latest + environment: aio_stage_oauth + strategy: + fail-fast: true + + steps: + + # Check out Git repository + - name: Checkout code + uses: actions/checkout@v3 + + # Set up environment with Java and Maven + - name: Setup JDK + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: 11 + cache: maven + + # Build & Test + - name: Stage Integration Test with Maven + run: mvn -Daio_publish_url=$aio_publish_url -Daio_api_url=$aio_api_url -Daio_ims_url=$aio_ims_url -Daio_oauth_scopes=$aio_oauth_scopes -Daio_ims_org_id=$aio_ims_org_id -Daio_consumer_org_id=$aio_consumer_org_id -Daio_project_id=$aio_project_id -Daio_workspace_id=$aio_workspace_id -Daio_api_key=$aio_api_key -Daio_client_secret=$aio_client_secret verify -Pitest + env: + aio_ims_url: ${{ secrets.aio_ims_url }} + aio_client_secret: ${{ secrets.aio_client_secret }} + aio_oauth_scopes: ${{ secrets.aio_oauth_scopes }} + aio_ims_org_id: ${{ secrets.aio_ims_org_id }} + aio_consumer_org_id: ${{ secrets.aio_consumer_org_id }} + aio_project_id: ${{ secrets.aio_project_id }} + aio_workspace_id: ${{ secrets.aio_workspace_id }} + aio_api_key: ${{ secrets.aio_api_key }} + aio_api_url: ${{ secrets.aio_api_url }} + aio_publish_url: ${{ secrets.aio_publish_url }} + prod_itest: runs-on: ubuntu-latest environment: aio_prod @@ -93,8 +128,3 @@ jobs: aio_encoded_pkcs8: ${{ secrets.aio_encoded_pkcs8 }} aio_api_url: ${{ secrets.aio_api_url }} aio_publish_url: ${{ secrets.aio_publish_url }} - - - - - diff --git a/aem/aio_aem_events/pom.xml b/aem/aio_aem_events/pom.xml index e8241808..c58de5da 100644 --- a/aem/aio_aem_events/pom.xml +++ b/aem/aio_aem_events/pom.xml @@ -22,7 +22,7 @@ com.adobe.aio.aem aio-aem ../pom.xml - 1.1.29-SNAPSHOT + 2.0.0-SNAPSHOT aio-aem-events diff --git a/aem/core_aem/README.md b/aem/core_aem/README.md index 2fea19e0..d7fbd03e 100644 --- a/aem/core_aem/README.md +++ b/aem/core_aem/README.md @@ -5,7 +5,7 @@ wrapping [`aio-lib-java-core`](../../core) and [`aio-lib-java-ims`](../../ims) It hosts the services to * get the Adobe Developer Console Workspace -* get Access token (from JWT exchange token flow) from Adobe Identity Management System (IMS) +* get Access token (using either JWT or OAuth token flow) from Adobe Identity Management System (IMS) ## `Workspace` Configuration @@ -18,28 +18,22 @@ service looks up the following OSGI configuration keys: * `aio.consumer.org.id` your Adobe Developer Console consumer orgnaization id (`project.org.id`) * `aio.ims.org.id` your Adobe Developer Console IMS Organization ID (`project.org.ims_org_id`) * `aio.workspace.id` your Adobe Developer Console workspace Id (`project.workspace.id`) -* `aio.credential.id` your Adobe Developer Console jwt credential id (`project.workspace.details.credentials[i].id`) -* `aio.api.key` your Adobe Developer Console jwt credential API Key (or Client ID) (`project.workspace.details.credentials[i].jwt.client_id`) -* `aio.client.secret` your Adobe Developer Console jwt credential client secret (`project.workspace.details.credentials[i].jwt.client_secret`) -* `aio.meta.scopes` a comma separated list of metascopes associated with your API, see your Adobe Developer Console jwt credential metascopes (`project.workspace.details.credentials[i].jwt.meta_scopes`) -* `aio.technical.account.id` your Adobe Developer Console jwt credential technical account id (`project.workspace.details.credentials[i].jwt.technical_account_id`) -* `aio.encoded.pkcs8` your private key (in a base64 encoded pkcs8 format) see below +* `aio.api.key` your Adobe Developer Console credential API Key (or Client ID) +When using JWT credentials also set +* `aio.credential.id` your Adobe Developer Console jwt credential id +* `aio.client.secret` your Adobe Developer Console jwt credential client secret +* `aio.meta.scopes` a comma separated list of metascopes associated with your API, see your Adobe Developer Console jwt credential metascopes +* `aio.technical.account.id` your Adobe Developer Console jwt credential technical account id +* `aio.encoded.pkcs8` your private key (in a base64 encoded pkcs8 format) -### `aio.encoded.pkcs8` +When using OAuth credentials also set +* `aio.client.secret` your Adobe Developer Console oAuth credential client secret +* `aio_oauth_scopes` a comma separated list of OAuth scopes associated with your API, see your Adobe Developer Console OAuth scopes (project.workspace.details.credentials[i].oauth_server_to_server.scopes) -`aio.encoded.pkcs8` configuration value is associated with your Adobe Developer Console private key. -It is a string: your private key in a pkcs8 format, base64 encoded, here is how to generate it: - -First, convert your private key to a PKCS8 format, use the following command: - - openssl pkcs8 -topk8 -inform PEM -outform DER -in private.key -nocrypt > private.pkcs8.key - -Then, base 64 encode it, use the following command: - - base64 private.pkcs8.key - -For more details check our [`aio-lib-java-ims` documentation](../../ims/README.md) +For more details on the above please refer to +* [`aio-lib-java-core` docs](../../core/README.md) for more details +* [`aio-lib-java-ims` docs](../../ims/README.md) for more details ### `on premise` AEM configuration: When running AEM on premise: @@ -66,19 +60,7 @@ The response json payload should like this: "workspace": { "imsUrl": "https://ims-na1.adobelogin.com", "imsOrgId": "...@AdobeOrg", - "apiKey": "...", - "credentialId": "...", - "technicalAccountId": "...@techacct.adobe.com", - "metascopes": [ - "...", - "/s/ent_adobeio_sdk" - ], - "consumerOrgId": "...", - "projectId": "...", - "workspaceId": "...", - "projectUrl": "https://developer.adobe.com/console/projects/.../.../overview", - "clientSecretDefined": true, - "privateKeyDefined": true + "apiKey": "..." } }, "error": null diff --git a/aem/core_aem/pom.xml b/aem/core_aem/pom.xml index a6d62933..9ad0fb4b 100644 --- a/aem/core_aem/pom.xml +++ b/aem/core_aem/pom.xml @@ -19,7 +19,7 @@ com.adobe.aio.aem aio-aem ../pom.xml - 1.1.29-SNAPSHOT + 2.0.0-SNAPSHOT aio-aem-core diff --git a/aem/core_aem/src/main/java/com/adobe/aio/aem/workspace/internal/WorkspaceSupplierImpl.java b/aem/core_aem/src/main/java/com/adobe/aio/aem/workspace/internal/WorkspaceSupplierImpl.java index aeaf2b93..eb4e8c97 100644 --- a/aem/core_aem/src/main/java/com/adobe/aio/aem/workspace/internal/WorkspaceSupplierImpl.java +++ b/aem/core_aem/src/main/java/com/adobe/aio/aem/workspace/internal/WorkspaceSupplierImpl.java @@ -14,9 +14,12 @@ import com.adobe.aio.aem.status.Status; import com.adobe.aio.aem.workspace.WorkspaceSupplier; import com.adobe.aio.aem.workspace.ocd.WorkspaceConfig; +import com.adobe.aio.auth.Context; +import com.adobe.aio.auth.JwtContext; +import com.adobe.aio.auth.OAuthContext; import com.adobe.aio.ims.util.PrivateKeyBuilder; +import com.adobe.aio.util.WorkspaceUtil; import com.adobe.aio.workspace.Workspace; -import java.security.PrivateKey; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang3.StringUtils; @@ -48,8 +51,6 @@ protected void activate(final WorkspaceConfig config) { public Status getStatus() { Map details = new HashMap<>(); try { - details.put("workspace", Workspace.builder() - .configMap(getAuthConfigMap(workspaceConfig)).build()); Workspace workspace = getWorkspace(); details.put("workspace", workspace); workspace.validateAll(); @@ -67,33 +68,35 @@ public Status getStatus() { */ @Override public Workspace getWorkspace() { - if (!StringUtils.isEmpty(workspaceConfig.aio_encoded_pkcs8())) { - PrivateKey privateKey = new PrivateKeyBuilder() - .encodedPkcs8Key(workspaceConfig.aio_encoded_pkcs8()).build(); - return Workspace.builder() - .configMap(getAuthConfigMap(workspaceConfig)) - .privateKey(privateKey).build(); - } else { - return Workspace.builder() - .configMap(getAuthConfigMap(workspaceConfig)).build(); - } + return WorkspaceUtil.getWorkspaceBuilder(getAuthConfigMap(workspaceConfig)).build(); } private Map getAuthConfigMap( WorkspaceConfig config) { Map map = new HashMap(); - map.put(Workspace.API_KEY, config.aio_api_key()); - map.put(Workspace.CLIENT_SECRET, config.aio_client_secret()); - map.put(Workspace.CONSUMER_ORG_ID, config.aio_consumer_org_id()); - map.put(Workspace.CREDENTIAL_ID, config.aio_credential_id()); - map.put(Workspace.IMS_ORG_ID, config.aio_ims_org_id()); - map.put(Workspace.IMS_URL, config.aio_ims_url()); - map.put(Workspace.PROJECT_ID, config.aio_project_id()); - map.put(Workspace.TECHNICAL_ACCOUNT_ID, config.aio_technical_account_id()); - map.put(Workspace.WORKSPACE_ID, config.aio_workspace_id()); - map.put(Workspace.META_SCOPES, config.aio_meta_scopes()); + putIfNotBlank(map, Workspace.API_KEY, config.aio_api_key()); + putIfNotBlank(map, Workspace.CONSUMER_ORG_ID, config.aio_consumer_org_id()); + putIfNotBlank(map, Workspace.IMS_ORG_ID, config.aio_ims_org_id()); + putIfNotBlank(map, Workspace.IMS_URL, config.aio_ims_url()); + putIfNotBlank(map, Workspace.PROJECT_ID, config.aio_project_id()); + putIfNotBlank(map, Workspace.WORKSPACE_ID, config.aio_workspace_id()); + putIfNotBlank(map, Workspace.CREDENTIAL_ID, config.aio_credential_id()); + + putIfNotBlank(map, Context.CLIENT_SECRET, config.aio_client_secret()); + + + putIfNotBlank(map, JwtContext.TECHNICAL_ACCOUNT_ID, config.aio_technical_account_id()); + putIfNotBlank(map, JwtContext.META_SCOPES, config.aio_meta_scopes()); + putIfNotBlank(map, PrivateKeyBuilder.AIO_ENCODED_PKCS_8, config.aio_encoded_pkcs8()); + + putIfNotBlank(map, OAuthContext.SCOPES, config.aio_oauth_scopes()); return map; } + private void putIfNotBlank(Map map, String key, String value) { + if (StringUtils.isNotBlank(value)) { + map.put(key, value); + } + } } diff --git a/aem/core_aem/src/main/java/com/adobe/aio/aem/workspace/ocd/WorkspaceConfig.java b/aem/core_aem/src/main/java/com/adobe/aio/aem/workspace/ocd/WorkspaceConfig.java index d54ff780..9b8d662f 100644 --- a/aem/core_aem/src/main/java/com/adobe/aio/aem/workspace/ocd/WorkspaceConfig.java +++ b/aem/core_aem/src/main/java/com/adobe/aio/aem/workspace/ocd/WorkspaceConfig.java @@ -22,10 +22,14 @@ description = "Adobe IMS URL: prod: https://ims-na1.adobelogin.com | stage: https://ims-na1-stg1.adobelogin.com") String aio_ims_url() default "https://ims-na1.adobelogin.com"; - @AttributeDefinition(name = "Meta Scopes", - description = "Comma separated list of metascopes associated with your API (`/s/event_receiver_api,/s/ent_adobeio_sdk` for instance) (project.workspace.details.credentials.jwt.meta_scopes)") + @AttributeDefinition(name = "JWT Meta Scopes (For deprecated JWT Auth only)", + description = "Comma separated list of metascopes associated with your API (`/s/event_receiver_api,/s/ent_adobeio_sdk` for instance) (project.workspace.details.credentials.jwt.meta_scopes), to be used for deprecated JWT Auth only.") String aio_meta_scopes() default "/s/ent_adobeio_sdk"; + @AttributeDefinition(name = "OAuth Scopes", + description = "Comma separated String. list of oauth scopes associated with your API (project.workspace.details.credentials.oauth_server_to_server.scopes)") + String aio_oauth_scopes(); + @AttributeDefinition(name = "IMS ORG ID", description = "Adobe IMS Organization ID as shown in your Adobe Developer Console workspace (project.org.ims_org_id)") String aio_ims_org_id(); @@ -43,23 +47,23 @@ String aio_workspace_id(); @AttributeDefinition(name = "API Key (Client ID)", - description = "Adobe I/O API Key (Client ID) as shown in in your Adobe Developer Console workspace (project.workspace.details.credentials.jwt.client_id)") + description = "Adobe I/O API Key (Client ID) as shown in in your Adobe Developer Console workspace") String aio_api_key(); @AttributeDefinition(name = "Credential ID", description = "Adobe I/O Credential ID as shown in your Adobe Developer Console workspace (project.workspace.details.credentials.id)") String aio_credential_id(); - @AttributeDefinition(name = "Technical Account ID", + @AttributeDefinition(name = "Technical Account ID (For deprecated JWT Auth only)", description = "Technical account ID as shown in your Adobe Developer Console workspace (project.workspace.details.credentials.jwt.technical_account_id)") String aio_technical_account_id(); @AttributeDefinition(name = "Client Secret", - description = "Adobe I/O Client Secret as shown in your Adobe Developer Console workspace (project.workspace.details.credentials.jwt.client_secret)") + description = "Adobe I/O Client Secret as shown in your Adobe Developer Console workspace") String aio_client_secret(); - @AttributeDefinition(name = "Private Key", - description = "Base64 encoded pkcs8 Private Key.") + @AttributeDefinition(name = "Private Key (For deprecated JWT Auth only)", + description = "Base64 encoded pkcs8 Private Key (For deprecated JWT Auth only).") String aio_encoded_pkcs8(); } diff --git a/aem/events_ingress_aem/pom.xml b/aem/events_ingress_aem/pom.xml index d91efe8a..6163f101 100644 --- a/aem/events_ingress_aem/pom.xml +++ b/aem/events_ingress_aem/pom.xml @@ -19,7 +19,7 @@ com.adobe.aio.aem aio-aem ../pom.xml - 1.1.29-SNAPSHOT + 2.0.0-SNAPSHOT aio-aem-events-publish diff --git a/aem/events_mgmt_aem/pom.xml b/aem/events_mgmt_aem/pom.xml index ae15dd31..e812ed67 100644 --- a/aem/events_mgmt_aem/pom.xml +++ b/aem/events_mgmt_aem/pom.xml @@ -19,7 +19,7 @@ com.adobe.aio.aem aio-aem ../pom.xml - 1.1.29-SNAPSHOT + 2.0.0-SNAPSHOT aio-aem-events-mgmt diff --git a/aem/events_osgi_mapping/pom.xml b/aem/events_osgi_mapping/pom.xml index 10d575b7..3476042d 100644 --- a/aem/events_osgi_mapping/pom.xml +++ b/aem/events_osgi_mapping/pom.xml @@ -19,7 +19,7 @@ com.adobe.aio.aem aio-aem ../pom.xml - 1.1.29-SNAPSHOT + 2.0.0-SNAPSHOT aio-aem-events-osgi-mapping diff --git a/aem/lib_osgi/pom.xml b/aem/lib_osgi/pom.xml index dea994f9..54c425af 100644 --- a/aem/lib_osgi/pom.xml +++ b/aem/lib_osgi/pom.xml @@ -17,7 +17,7 @@ com.adobe.aio.aem aio-aem ../pom.xml - 1.1.29-SNAPSHOT + 2.0.0-SNAPSHOT aio-lib-osgi diff --git a/aem/pom.xml b/aem/pom.xml index 91dbee62..b559657b 100644 --- a/aem/pom.xml +++ b/aem/pom.xml @@ -19,7 +19,7 @@ com.adobe.aio aio-lib-java ../pom.xml - 1.1.29-SNAPSHOT + 2.0.0-SNAPSHOT com.adobe.aio.aem diff --git a/core/README.md b/core/README.md index 3f51976a..578f636e 100644 --- a/core/README.md +++ b/core/README.md @@ -10,7 +10,9 @@ This library holds a [`Workspace`](./src/main/java/com/adobe/aio/workspace/Works your [Adobe Developer Console Project Workspace](https://www.adobe.io/apis/experienceplatform/console/docs.html#!AdobeDocs/adobeio-console/master/projects.md), To get you started quickly use a `.properties` file, -* see our [sample config file](./src/test/resources/workspace.properties) in our jUnit Test. +* see our sample config files: + * [workspace.jwt.properties](./src/test/resources/workspace.jwt.properties) + * [workspace.oauth.properties](./src/test/resources/workspace.oauth.properties) * download your `project` configurations file from your Adobe Developer Console Project overview page * map your `project` configurations with this properties @@ -23,9 +25,11 @@ The `Workspace` POJO holds your Adobe Developer Console Project configurations * `aio_consumer_org_id` your Adobe Developer Console consumer orgnaization id (`project.org.id`) * `aio_ims_org_id` your Adobe Developer Console IMS Organization ID (`project.org.ims_org_id`) * `aio_workspace_id` your Adobe Developer Console workspace Id (`project.workspace.id`) +* `aio_credential_id` your Adobe Developer Console credential id (`project.workspace.details.credentials[i].id`) + * this is optional, but it might be handy to have it in your `Workspace` POJO, to avoid confusion when you have multiple credentials, and to eventually in some Adobe API calls ### Workspace Authentication Context -The `Workspace` POJO must also hold your Adobe Developer Auth configurations, pick one of the following authentication methods: +The `Workspace` POJO must also hold your Adobe Developer Auth configurations, pick one of the following authentication methods (see [aio-lib-java-ims](../ims/README.md) docs for more details): #### OAuth2 authentication For [OAuth2 authentication](https://developer.adobe.com/developer-console/docs/guides/authentication/ServerToServerAuthentication/#oauth-server-to-server-credential), you will need to provide the following properties: @@ -35,20 +39,13 @@ For [OAuth2 authentication](https://developer.adobe.com/developer-console/docs/g #### JWT authentication For [JWT authentication](https://developer.adobe.com/developer-console/docs/guides/authentication/ServerToServerAuthentication/#service-account-jwt-credential-deprecated), you will need to provide the following properties: -* `aio_credential_id` your Adobe Developer Console jwt credential id (`project.workspace.details.credentials[i].id`) * `aio_client_secret` your Adobe Developer Console jwt credential client secret (`project.workspace.details.credentials[i].jwt.client_secret`) * `aio_api_key` your Adobe Developer Console jwt credential API Key (or Client ID) (`project.workspace.details.credentials[i].jwt.client_id`) * `aio_meta_scopes` a comma separated list of metascopes associated with your API, see your Adobe Developer Console jwt credential metascopes (`project.workspace.details.credentials[i].jwt.meta_scopes`) * `aio_technical_account_id` your Adobe Developer Console jwt credential technical account id (`project.workspace.details.credentials[i].jwt.technical_account_id`) +* `aio_encoded_pkcs8` your privateKey (associated with the public key set in your Adobe Developer Console workspace) in a base64 encoded pkcs8 format -On top of these, the [`Workspace`](./src/main/java/com/adobe/aio/workspace/Workspace.java) POJO can also hold your JWT private Key -(associated with the public key you uploaded in your Adobe Developer Console Workspace) -this will help to power Adobe JWT authentication flow and transparently add the proper `Bearer` -authentication token to all your request, -Note the easiest way is to stuff your Private Key as a `pkcs8` base64 encoded String -using the `aio_encoded_pkcs8` property key. -confer [aio-lib-java-ims](../ims/README.md#Create-and-configure-your-public-and-private-key) documentation for more details. ## Builds diff --git a/core/pom.xml b/core/pom.xml index 72e0ec38..683bc706 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -17,7 +17,7 @@ com.adobe.aio aio-lib-java - 1.1.29-SNAPSHOT + 2.0.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/core/src/main/java/com/adobe/aio/auth/Context.java b/core/src/main/java/com/adobe/aio/auth/Context.java index f0bd8025..48c026d3 100644 --- a/core/src/main/java/com/adobe/aio/auth/Context.java +++ b/core/src/main/java/com/adobe/aio/auth/Context.java @@ -19,13 +19,10 @@ public interface Context { /** - * Property name used in maps and config files for setting the AIO IMS URL. + * Property name for looking up Authentication Client Secret in various contexts. + * Reference: AIO Developer Documentation */ - public static final String IMS_URL = "aio_ims_url"; - /** - * Property name used in maps and config files for setting the AIO IMS Org Id. - */ - public static final String IMS_ORG_ID = "aio_ims_org_id"; + String CLIENT_SECRET = "aio_client_secret"; /** * Validates this context is minimally populated and able to function. diff --git a/core/src/main/java/com/adobe/aio/auth/JwtContext.java b/core/src/main/java/com/adobe/aio/auth/JwtContext.java index 4f4ca233..fed94d4e 100644 --- a/core/src/main/java/com/adobe/aio/auth/JwtContext.java +++ b/core/src/main/java/com/adobe/aio/auth/JwtContext.java @@ -13,37 +13,28 @@ import java.security.PrivateKey; import java.util.HashSet; -import java.util.Map; import java.util.Objects; -import java.util.Properties; import java.util.Set; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; import com.fasterxml.jackson.annotation.JsonIgnore; -import static com.adobe.aio.util.FileUtil.*; - /** * JWT Authentication context. */ public class JwtContext implements Context { - public static final String CREDENTIAL_ID = "aio_credential_id"; - public static final String CLIENT_SECRET = "aio_client_secret"; + public static final String TECHNICAL_ACCOUNT_ID = "aio_technical_account_id"; public static final String META_SCOPES = "aio_meta_scopes"; - private final String credentialId; private final String technicalAccountId; private final Set metascopes; private final String clientSecret; private final PrivateKey privateKey; - public JwtContext(final String credentialId, final String clientSecret, final String technicalAccountId, + public JwtContext(final String clientSecret, final String technicalAccountId, final Set metascopes, final PrivateKey privateKey) { - this.credentialId = credentialId; this.clientSecret = clientSecret; this.technicalAccountId = technicalAccountId; this.metascopes = metascopes; @@ -69,10 +60,6 @@ public void validate() { } } - public String getCredentialId() { - return credentialId; - } - public String getTechnicalAccountId() { return technicalAccountId; } @@ -108,7 +95,6 @@ public boolean equals(Object o) { JwtContext that = (JwtContext) o; - if (!Objects.equals(credentialId, that.credentialId)) return false; if (!Objects.equals(technicalAccountId, that.technicalAccountId)) return false; if (!Objects.equals(metascopes, that.metascopes)) return false; @@ -118,8 +104,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = credentialId != null ? credentialId.hashCode() : 0; - result = 31 * result + (technicalAccountId != null ? technicalAccountId.hashCode() : 0); + int result = technicalAccountId != null ? technicalAccountId.hashCode() : 0; result = 31 * result + (metascopes != null ? metascopes.hashCode() : 0); result = 31 * result + (clientSecret != null ? clientSecret.hashCode() : 0); result = 31 * result + (privateKey != null ? privateKey.hashCode() : 0); @@ -129,25 +114,18 @@ public int hashCode() { @Override public String toString() { return "JwtContext{" + - "credentialId='" + credentialId + '\'' + - ", technicalAccountId='" + technicalAccountId + '\'' + + "technicalAccountId='" + technicalAccountId + '\'' + ", metascopes=" + metascopes + '}'; } public static class Builder { - private String credentialId; private String clientSecret; private String technicalAccountId; private PrivateKey privateKey; private final Set metascopes = new HashSet<>(); - public Builder credentialId(final String credentialId) { - this.credentialId = credentialId; - return this; - } - public Builder clientSecret(final String clientSecret) { this.clientSecret = clientSecret; return this; @@ -168,36 +146,8 @@ public Builder privateKey(final PrivateKey privateKey) { return this; } - public Builder configMap(final Map configMap) { - this - .credentialId(configMap.get(CREDENTIAL_ID)) - .clientSecret(configMap.get(CLIENT_SECRET)) - .technicalAccountId(configMap.get(TECHNICAL_ACCOUNT_ID)); - if (!StringUtils.isEmpty(configMap.get(META_SCOPES))) { - String[] metascopeArray = configMap.get(META_SCOPES).split(","); - for (String metascope : metascopeArray) { - this.addMetascope(metascope); - } - } - return this; - } - - public Builder systemEnv() { - return configMap(System.getenv()); - } - - public Builder propertiesPath(final String propertiesPath) { - return properties( - readPropertiesFromFile(propertiesPath) - .orElse(readPropertiesFromClassPath(propertiesPath))); - } - - public Builder properties(final Properties properties) { - return configMap(getMapFromProperties(properties)); - } - public JwtContext build() { - return new JwtContext(credentialId, clientSecret, technicalAccountId, metascopes, privateKey); + return new JwtContext(clientSecret, technicalAccountId, metascopes, privateKey); } } } diff --git a/core/src/main/java/com/adobe/aio/auth/OAuthContext.java b/core/src/main/java/com/adobe/aio/auth/OAuthContext.java index f361caa6..b818464a 100644 --- a/core/src/main/java/com/adobe/aio/auth/OAuthContext.java +++ b/core/src/main/java/com/adobe/aio/auth/OAuthContext.java @@ -29,11 +29,6 @@ * Reference: OAuth Client Credentials */ public class OAuthContext implements Context { - /** - * Property name for looking up Authentication Client Secret in various contexts. - * Reference: AIO Developer Documentation - */ - public static final String CLIENT_SECRET = "aio_client_secret"; /** * Property name for looking up Authentication Scopes in various contexts. * Reference: AIO Developer Documentation @@ -112,26 +107,6 @@ public Builder addScope(final String scope) { return this; } - public Builder configMap(final Map configMap) { - this.clientSecret(configMap.get(CLIENT_SECRET)); - if (!StringUtils.isEmpty(configMap.get(SCOPES))) { - Arrays.stream(configMap.get(SCOPES).split(",")).forEach(this::addScope); - } - return this; - } - - public Builder systemEvn() { - return configMap(System.getenv()); - } - - public Builder propertiesPath(final String propertiesPath) { - return properties(readPropertiesFromFile(propertiesPath).orElse(readPropertiesFromClassPath(propertiesPath))); - } - - public Builder properties(final Properties properties) { - return configMap(getMapFromProperties(properties)); - } - public OAuthContext build() { return new OAuthContext(clientSecret, scopes); } diff --git a/core/src/main/java/com/adobe/aio/util/Constants.java b/core/src/main/java/com/adobe/aio/util/Constants.java index 88e5783b..f24924fc 100644 --- a/core/src/main/java/com/adobe/aio/util/Constants.java +++ b/core/src/main/java/com/adobe/aio/util/Constants.java @@ -17,7 +17,7 @@ public class Constants { public static final String BEARER_PREFIX = "Bearer "; public static final String API_KEY_HEADER = "x-api-key"; public static final String IMS_ORG_HEADER = "x-gw-ims-org-id"; - public static final String IMS_URL = "https://ims-na1.adobelogin.com"; + public static final String PROD_IMS_URL = "https://ims-na1.adobelogin.com"; public static final String API_MANAGEMENT_URL = "https://api.adobe.io"; public static final String CUSTOM_EVENTS_PROVIDER_METADATA_ID = "3rd_party_custom_events"; diff --git a/core/src/main/java/com/adobe/aio/util/FileUtil.java b/core/src/main/java/com/adobe/aio/util/FileUtil.java index 837be638..153e869c 100644 --- a/core/src/main/java/com/adobe/aio/util/FileUtil.java +++ b/core/src/main/java/com/adobe/aio/util/FileUtil.java @@ -11,23 +11,23 @@ */ package com.adobe.aio.util; -import com.adobe.aio.exception.AIOException; -import java.io.FileInputStream; -import java.io.FileNotFoundException; + import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; -import java.util.Optional; import java.util.Properties; -import org.apache.commons.lang3.StringUtils; public class FileUtil { private FileUtil() { } - public static Map getMapFromProperties(final Properties properties) { + public static Map getMap(final String propertiesClassPath) { + return getMap(getProperties(propertiesClassPath)); + } + + public static Map getMap(final Properties properties) { Map map = new HashMap<>(); for (final String name : properties.stringPropertyNames()) { map.put(name, properties.getProperty(name)); @@ -35,29 +35,18 @@ public static Map getMapFromProperties(final Properties properti return map; } - public static Optional readPropertiesFromFile(final String configFilePath) { - if (StringUtils.isEmpty(configFilePath)) { - return Optional.empty(); - } else { - try (InputStream in = new FileInputStream(configFilePath)) { - return Optional.of(read(in)); - } catch (FileNotFoundException e) { - return Optional.empty(); - } catch (IOException e) { - throw new AIOException("Unable to load your Properties from File " + configFilePath, e); - } - } - } - - public static Properties readPropertiesFromClassPath(final String configClassPath) { - try (InputStream in = FileUtil.class.getClassLoader().getResourceAsStream(configClassPath)) { + public static Properties getProperties(final String propertiesClassPath) { + try (InputStream in = FileUtil.class.getClassLoader().getResourceAsStream(propertiesClassPath)) { return read(in); - } catch (IOException e) { - throw new AIOException("Unable to load your Properties from class path " + configClassPath, e); + } catch (Exception e) { + throw new IllegalArgumentException("Unable to load your Properties from class path " + propertiesClassPath, e); } } private static Properties read(InputStream in) throws IOException { + if (in == null) { + throw new IllegalArgumentException("InputStream cannot be null"); + } Properties prop = new Properties(); prop.load(in); in.close(); diff --git a/core/src/main/java/com/adobe/aio/workspace/Workspace.java b/core/src/main/java/com/adobe/aio/workspace/Workspace.java index cd6986d5..28e02579 100644 --- a/core/src/main/java/com/adobe/aio/workspace/Workspace.java +++ b/core/src/main/java/com/adobe/aio/workspace/Workspace.java @@ -11,17 +11,13 @@ */ package com.adobe.aio.workspace; -import static com.adobe.aio.util.FileUtil.getMapFromProperties; -import static com.adobe.aio.util.FileUtil.readPropertiesFromClassPath; -import static com.adobe.aio.util.FileUtil.readPropertiesFromFile; import com.adobe.aio.auth.Context; import com.adobe.aio.auth.JwtContext; +import com.adobe.aio.auth.OAuthContext; import com.adobe.aio.util.Constants; -import java.security.PrivateKey; import java.util.Map; import java.util.Objects; -import java.util.Properties; import org.apache.commons.lang3.StringUtils; public class Workspace { @@ -32,46 +28,27 @@ public class Workspace { public static final String PROJECT_ID = "aio_project_id"; public static final String WORKSPACE_ID = "aio_workspace_id"; public static final String API_KEY = "aio_api_key"; - /** - * @deprecated This will be removed in v2.0 of the library. - */ - @Deprecated public static final String CREDENTIAL_ID = "aio_credential_id"; - /** - * @deprecated This will be removed in v2.0 of the library. - */ - @Deprecated - public static final String CLIENT_SECRET = "aio_client_secret"; - /** - * @deprecated This will be removed in v2.0 of the library. - */ - @Deprecated - public static final String TECHNICAL_ACCOUNT_ID = "aio_technical_account_id"; - /** - * @deprecated This will be removed in v2.0 of the library. - */ - @Deprecated - public static final String META_SCOPES = "aio_meta_scopes"; - // workspace context related: private final String imsUrl; private final String imsOrgId; private final String apiKey; private final String consumerOrgId; private final String projectId; private final String workspaceId; + private final String credentialId; private final Context authContext; private Workspace(final String imsUrl, final String imsOrgId, final String apiKey, final String consumerOrgId, final String projectId, final String workspaceId, - Context authContext) { - this.imsUrl = StringUtils.isEmpty(imsUrl) ? Constants.IMS_URL : imsUrl; + final String credentialId, Context authContext) { + this.imsUrl = StringUtils.isEmpty(imsUrl) ? Constants.PROD_IMS_URL : imsUrl; this.imsOrgId = imsOrgId; - this.apiKey = apiKey; this.consumerOrgId = consumerOrgId; this.projectId = projectId; this.workspaceId = workspaceId; + this.credentialId = credentialId; this.authContext = authContext; } @@ -80,8 +57,11 @@ public static Builder builder() { } public void validateAll() { - authContext.validate(); validateWorkspaceContext(); + if (!isAuthOAuth() && !isAuthJWT()) { + throw new IllegalStateException("Missing auth configuration, set either jwt or oauth..."); + } + authContext.validate(); } /** @@ -105,6 +85,14 @@ public void validateWorkspaceContext() throws IllegalStateException { if (StringUtils.isEmpty(this.getWorkspaceId())) { throw new IllegalStateException("Your `Workspace` is missing a workspaceId"); } + // note that the credentialId is optional + // but it might be handy to have it in your `Workspace` POJO, + // to avoid confusion when you have multiple credentials, + // and to eventually in some Adobe API calls + + if (authContext == null) { + throw new IllegalStateException("Missing auth configuration ..."); + } } public String getProjectUrl() { @@ -141,22 +129,21 @@ public String getWorkspaceId() { return workspaceId; } + public String getCredentialId() { return credentialId;} + public Context getAuthContext() { return authContext; } - /** - * @deprecated This will be removed in v2.0 of the library. - */ - @Deprecated - public String getCredentialId() { - if (authContext instanceof JwtContext) { - return ((JwtContext) authContext).getCredentialId(); - } else { - return null; - } + public boolean isAuthOAuth() { + return authContext!=null && authContext instanceof OAuthContext; + } + + public boolean isAuthJWT() { + return authContext!=null && authContext instanceof JwtContext; } + @Override public boolean equals(Object o) { if (this == o) @@ -197,10 +184,10 @@ public static class Builder { private String consumerOrgId; private String projectId; private String workspaceId; + private String credentialId; private Map workspaceProperties; - private JwtContext.Builder jwtbuilder; private Context authContext; private Builder() { @@ -236,88 +223,19 @@ public Builder workspaceId(final String workspaceId) { return this; } - public Builder authContext(final Context authContext) { - this.authContext = authContext; - return this; - } - public Builder credentialId(final String credentialId) { - if (jwtbuilder == null) { - jwtbuilder = JwtContext.builder(); - } - jwtbuilder.credentialId(credentialId); - return this; - } - - public Builder clientSecret(final String clientSecret) { - if (jwtbuilder == null) { - jwtbuilder = JwtContext.builder(); - } - jwtbuilder.clientSecret(clientSecret); - return this; - } - - public Builder technicalAccountId(final String technicalAccountId) { - if (jwtbuilder == null) { - jwtbuilder = JwtContext.builder(); - } - jwtbuilder.technicalAccountId(technicalAccountId); - return this; - } - - public Builder addMetascope(final String metascope) { - if (jwtbuilder == null) { - jwtbuilder = JwtContext.builder(); - } - jwtbuilder.addMetascope(metascope); + this.credentialId = credentialId; return this; } - public Builder privateKey(final PrivateKey privateKey) { - if (jwtbuilder == null) { - jwtbuilder = JwtContext.builder(); - } - jwtbuilder.privateKey(privateKey); - return this; - } - - public Builder configMap(final Map configMap) { - this - .imsUrl(configMap.get(IMS_URL)) - .imsOrgId(configMap.get(IMS_ORG_ID)) - .apiKey(configMap.get(API_KEY)) - .consumerOrgId(configMap.get(CONSUMER_ORG_ID)) - .projectId(configMap.get(PROJECT_ID)) - .workspaceId(configMap.get(WORKSPACE_ID)); - - // For backwards compatibility - should this be kept? - jwtbuilder = JwtContext.builder(); - jwtbuilder.configMap(configMap); + public Builder authContext(final Context authContext) { + this.authContext = authContext; return this; } - public Builder systemEnv() { - return configMap(System.getenv()); - } - - public Builder propertiesPath(final String propertiesPath) { - return properties( - readPropertiesFromFile(propertiesPath) - .orElse(readPropertiesFromClassPath(propertiesPath))); - } - - public Builder properties(final Properties properties) { - return configMap(getMapFromProperties(properties)); - } - public Workspace build() { - if (authContext != null) { - return new Workspace(imsUrl, imsOrgId, apiKey, consumerOrgId, projectId, workspaceId, authContext); - } - if (jwtbuilder == null) { - jwtbuilder = JwtContext.builder(); - } - return new Workspace(imsUrl, imsOrgId, apiKey, consumerOrgId, projectId, workspaceId, jwtbuilder.build()); + return new Workspace(imsUrl, imsOrgId, apiKey, consumerOrgId, projectId, workspaceId, credentialId, authContext); } + } } diff --git a/core/src/test/java/com/adobe/aio/auth/JwtContextTest.java b/core/src/test/java/com/adobe/aio/auth/JwtContextTest.java index 60259f93..52613ca7 100644 --- a/core/src/test/java/com/adobe/aio/auth/JwtContextTest.java +++ b/core/src/test/java/com/adobe/aio/auth/JwtContextTest.java @@ -11,11 +11,8 @@ import org.junit.jupiter.api.Test; public class JwtContextTest { - private static final String TEST_PROPERTIES = "workspace.properties"; private static final String TEST_VALUE = "_changeMe"; - private static JwtContext expected; - private static PrivateKey privateKey; @BeforeAll @@ -24,23 +21,6 @@ public static void beforeClass() throws Exception { kpg.initialize(2048); KeyPair kp = kpg.generateKeyPair(); privateKey = kp.getPrivate(); - expected = JwtContext.builder().propertiesPath(TEST_PROPERTIES).privateKey(privateKey).build(); - } - - @Test - void properties() { - JwtContext actual = JwtContext.builder() - .credentialId(CREDENTIAL_ID + TEST_VALUE) - .technicalAccountId(TECHNICAL_ACCOUNT_ID + TEST_VALUE) - .addMetascope(META_SCOPES + TEST_VALUE) - .clientSecret(CLIENT_SECRET + TEST_VALUE) - .privateKey(privateKey) - .build(); - - assertEquals(actual, expected); - assertEquals(actual.hashCode(), expected.hashCode()); - assertEquals(actual.toString(), expected.toString()); - actual.validate(); } @Test diff --git a/core/src/test/java/com/adobe/aio/auth/OAuthContextTest.java b/core/src/test/java/com/adobe/aio/auth/OAuthContextTest.java index 778a3762..c75b388d 100644 --- a/core/src/test/java/com/adobe/aio/auth/OAuthContextTest.java +++ b/core/src/test/java/com/adobe/aio/auth/OAuthContextTest.java @@ -8,28 +8,8 @@ public class OAuthContextTest { - private static final String TEST_PROPERTIES = "workspace.properties"; private static final String TEST_VALUE = "_changeMe"; - private static OAuthContext expected; - - @BeforeAll - public static void beforeClass() throws Exception { - expected = OAuthContext.builder().propertiesPath(TEST_PROPERTIES).build(); - } - - @Test - void properties() { - OAuthContext actual = OAuthContext.builder() - .clientSecret(CLIENT_SECRET + TEST_VALUE) - .addScope(SCOPES + TEST_VALUE) - .build(); - assertEquals(actual, expected); - assertEquals(actual.hashCode(), expected.hashCode()); - assertEquals(actual.toString(), expected.toString()); - actual.validate(); - } - @Test void missingClientSecret() { OAuthContext actual = OAuthContext.builder().build(); diff --git a/core/src/test/java/com/adobe/aio/util/FileUtilTest.java b/core/src/test/java/com/adobe/aio/util/FileUtilTest.java index f4bd618d..0f42096d 100644 --- a/core/src/test/java/com/adobe/aio/util/FileUtilTest.java +++ b/core/src/test/java/com/adobe/aio/util/FileUtilTest.java @@ -15,6 +15,7 @@ import java.util.Map; import java.util.Properties; +import com.adobe.aio.exception.AIOException; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -23,30 +24,37 @@ public class FileUtilTest { private static final String KEY = "key"; private static final String VALUE = "value"; - private static final String TEST_PROPERTIES_FILE = "test.properties"; + private static final String TEST_PROPERTIES_CLASS_PATH = "test.properties"; private Properties getTestProperties() { Properties properties = new Properties(); properties.put(KEY, VALUE); return properties; } + @Test + public void testGetMapFromMissingPropertiesClassPath() { + Map map = new HashMap<>(); + map.put(KEY, VALUE); + assertThrows(IllegalArgumentException.class, () -> FileUtil.getMap("missing.properties")); + } @Test - public void testGetMapFromProperties() { + public void testGetMapFromPropertiesClassPath() { Map map = new HashMap<>(); map.put(KEY, VALUE); - assertEquals(map, FileUtil.getMapFromProperties(getTestProperties())); + assertEquals(map, FileUtil.getMap(TEST_PROPERTIES_CLASS_PATH)); } @Test - public void testReadPropertiesFromFile() { - assertFalse(FileUtil.readPropertiesFromFile("").isPresent()); - assertFalse(FileUtil.readPropertiesFromFile(null).isPresent()); + public void testGetMapFromProperties() { + Map map = new HashMap<>(); + map.put(KEY, VALUE); + assertEquals(map, FileUtil.getMap(getTestProperties())); } @Test public void testReadPropertiesFromClassPath() { - assertEquals(getTestProperties(), FileUtil.readPropertiesFromClassPath(TEST_PROPERTIES_FILE)); + assertEquals(getTestProperties(), FileUtil.getProperties(TEST_PROPERTIES_CLASS_PATH)); } } diff --git a/core/src/test/java/com/adobe/aio/workspace/WorkspaceTest.java b/core/src/test/java/com/adobe/aio/workspace/WorkspaceTest.java index 1661f6f7..a8295792 100644 --- a/core/src/test/java/com/adobe/aio/workspace/WorkspaceTest.java +++ b/core/src/test/java/com/adobe/aio/workspace/WorkspaceTest.java @@ -17,7 +17,6 @@ import java.security.PrivateKey; import com.adobe.aio.auth.Context; -import com.adobe.aio.auth.JwtContext; import com.adobe.aio.util.Constants; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -26,8 +25,6 @@ public class WorkspaceTest { - private static Workspace expected; - private static final String TEST_PROPERTIES = "workspace.properties"; private static final String TEST_VALUE = "_changeMe"; private static PrivateKey privateKey; @@ -37,11 +34,10 @@ public static void beforeClass() throws Exception { kpg.initialize(2048); KeyPair kp = kpg.generateKeyPair(); privateKey = kp.getPrivate(); - expected = Workspace.builder().propertiesPath(TEST_PROPERTIES).privateKey(privateKey).build(); } @Test - public void properties() throws IOException { + public void successFullBuilder() throws IOException { class MockContext implements Context { @Override @@ -51,7 +47,7 @@ public void validate() { } Workspace actual = Workspace.builder() - .imsUrl(Constants.IMS_URL) + .imsUrl(Constants.PROD_IMS_URL) .imsOrgId(Workspace.IMS_ORG_ID + TEST_VALUE) .apiKey(Workspace.API_KEY + TEST_VALUE) .consumerOrgId(Workspace.CONSUMER_ORG_ID + TEST_VALUE) @@ -65,8 +61,8 @@ public void validate() { assertEquals(Workspace.CONSUMER_ORG_ID + TEST_VALUE, actual.getConsumerOrgId()); assertEquals(Workspace.PROJECT_ID + TEST_VALUE, actual.getProjectId()); assertEquals(Workspace.WORKSPACE_ID + TEST_VALUE, actual.getWorkspaceId()); - assertEquals(Constants.IMS_URL, actual.getImsUrl()); - actual.validateAll(); + assertEquals(Constants.PROD_IMS_URL, actual.getImsUrl()); + actual.validateWorkspaceContext(); } @Test @@ -128,24 +124,4 @@ public void projectUrl() { assertEquals("https://developer.adobe.com/console/projects/aio_consumer_org_id_changeMe/aio_project_id_changeMe/overview", actual.getProjectUrl()); } - @Test - public void jwtBackwardsCompat() throws Exception { - Workspace actual = Workspace.builder() - .imsUrl(Constants.IMS_URL) - .imsOrgId(Workspace.IMS_ORG_ID + TEST_VALUE) - .apiKey(Workspace.API_KEY + TEST_VALUE) - .consumerOrgId(Workspace.CONSUMER_ORG_ID + TEST_VALUE) - .projectId(Workspace.PROJECT_ID + TEST_VALUE) - .workspaceId(Workspace.WORKSPACE_ID + TEST_VALUE) - .clientSecret(JwtContext.CLIENT_SECRET + TEST_VALUE) - .credentialId(JwtContext.CREDENTIAL_ID + TEST_VALUE) - .technicalAccountId(JwtContext.TECHNICAL_ACCOUNT_ID + TEST_VALUE) - .privateKey(privateKey) - .addMetascope(JwtContext.META_SCOPES + TEST_VALUE) - .build(); - assertEquals(actual, expected); - assertEquals(actual.hashCode(), expected.hashCode()); - assertEquals(actual.toString(), expected.toString()); - actual.validateAll(); - } } diff --git a/core/src/test/resources/workspace.properties b/core/src/test/resources/workspace.jwt.properties similarity index 85% rename from core/src/test/resources/workspace.properties rename to core/src/test/resources/workspace.jwt.properties index bd16d06d..27eee20f 100644 --- a/core/src/test/resources/workspace.properties +++ b/core/src/test/resources/workspace.jwt.properties @@ -18,20 +18,18 @@ aio_consumer_org_id=aio_consumer_org_id_changeMe aio_ims_org_id=aio_ims_org_id_changeMe # aio_workspace_id = your Adobe Developer Console workspace Id (project.workspace.id) aio_workspace_id=aio_workspace_id_changeMe -# aio_credential_id = your Adobe Developer Console jwt credential id (project.workspace.details.credentials[i].id) +# aio_credential_id = your Adobe Developer Console credential id (project.workspace.details.credentials[i].id) aio_credential_id=aio_credential_id_changeMe -# aio_client_secret = your Adobe Developer Console jwt or OAuth credential client secret (project.workspace.details.credentials[i].jwt.client_secret) -aio_client_secret=aio_client_secret_changeMe + # aio_api_key = your Adobe Developer Console jwt credential API Key (or Client ID) (project.workspace.details.credentials[i].jwt.client_id aio_api_key=aio_api_key_changeMe +# aio_client_secret = your Adobe Developer Console jwt or OAuth credential client secret (project.workspace.details.credentials[i].jwt.client_secret) +aio_client_secret=aio_client_secret_changeMe # aio_meta_scopes : comma separated list of metascopes associated with your API, see your Adobe Developer Console jwt credential metascopes (project.workspace.details.credentials[i].jwt.meta_scopes) # sample aio_meta_scopes: /s/ent_user_sdk,/s/ent_marketing_sdk,/s/creative_sdk (project.workspace.details.credentials[i].jwt.meta_scopes) aio_meta_scopes=aio_meta_scopes_changeMe # aio_technical_account_id = your Adobe Developer Console jwt credential technical account id (project.workspace.details.credentials[i].jwt.technical_account_id) aio_technical_account_id=aio_technical_account_id_changeMe -# aio_oauth_scopes : comma separated list of scopes associated with your API, see your Adobe Developer Console OAuth credential scopes -# sample aio_meta_scopes: openid, AdobeID, read_organizations -aio_oauth_scopes=aio_oauth_scopes_changeMe diff --git a/core/src/test/resources/workspace.oauth.properties b/core/src/test/resources/workspace.oauth.properties new file mode 100644 index 00000000..f847f386 --- /dev/null +++ b/core/src/test/resources/workspace.oauth.properties @@ -0,0 +1,32 @@ +# +# Copyright 2017 Adobe. All rights reserved. +# This file is licensed to you under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. You may obtain a copy +# of the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under +# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +# OF ANY KIND, either express or implied. See the License for the specific language +# governing permissions and limitations under the License. +# + +# aio_project_id = your Adobe Developer Console project id (project.id) +aio_project_id=aio_project_id_changeMe +# aio_consumer_org_id = your Adobe Developer Console consumer orgnaization id (project.org.id) +aio_consumer_org_id=aio_consumer_org_id_changeMe +# aio_ims_org_id = your Adobe Developer Console IMS Organization ID (project.org.ims_org_id) +aio_ims_org_id=aio_ims_org_id_changeMe +# aio_workspace_id = your Adobe Developer Console workspace Id (project.workspace.id) +aio_workspace_id=aio_workspace_id_changeMe +# aio_credential_id = your Adobe Developer Console credential id (project.workspace.details.credentials[i].id) +aio_credential_id=aio_credential_id_changeMe + +# aio_api_key = your Adobe Developer Console credential API Key (or Client ID) (project.workspace.details.credentials[i].oauth_server_to_server.client_id) +aio_api_key=aio_api_key_changeMe +# aio_client_secret = your Adobe Developer Console jwt credential client secret (project.workspace.details.credentials[i].oauth_server_to_server.client_secrets[0]) +aio_client_secret=aio_client_secret_changeMe +# aio_oauth_scopes : comma separated list of oauth associated with your API, see your Adobe Developer Console oauth scopes (project.workspace.details.credentials[i].oauth_server_to_server.scopes) +# sample aio_oauth_scopes: aio_oauth_scopes= AdobeID,openid,read_organizations,additional_info.projectedProductContext,additional_info.roles,adobeio_api,read_client_secret,manage_client_secrets +aio_oauth_scopes=aio_oauth_scopes_changeMe + + diff --git a/events_ingress/pom.xml b/events_ingress/pom.xml index de7382b7..ca2e0031 100644 --- a/events_ingress/pom.xml +++ b/events_ingress/pom.xml @@ -17,7 +17,7 @@ com.adobe.aio aio-lib-java - 1.1.29-SNAPSHOT + 2.0.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/events_ingress/src/test/resources/workspace.properties b/events_ingress/src/test/resources/workspace.properties deleted file mode 100644 index bc5b692e..00000000 --- a/events_ingress/src/test/resources/workspace.properties +++ /dev/null @@ -1,45 +0,0 @@ -# -# Copyright 2017 Adobe. All rights reserved. -# This file is licensed to you under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under -# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -# OF ANY KIND, either express or implied. See the License for the specific language -# governing permissions and limitations under the License. -# - -# aio_meta_scopes : comma separated list of metascopes associated with your API -# sample aio_meta_scopes: /s/ent_user_sdk,/s/ent_marketing_sdk,/s/creative_sdk -aio_meta_scopes=aio_meta_scopes_changeMe -# aio_ims_org_id = your IMS Organization ID as shown in your Adobe Developer Console workspace -aio_ims_org_id=aio_ims_org_id_changeMe -# aio_consumer_org_id = your consumer Org Id as shown in your Adobe Developer Console workspace (project.org.id) -aio_consumer_org_id=aio_consumer_org_id_changeMe -# aio_project_id = your project Id as shown in your Adobe Developer Console workspace (project.id) -aio_project_id=aio_project_id_changeMe -# aio_workspace_id = your workspace Id as shown in your Adobe Developer Console workspace (project.workspace.id) -aio_workspace_id=aio_workspace_id_changeMe -# aio_api_key = your credential API Key (Client ID) as shown in in your Adobe Developer Console workspace -aio_api_key=aio_api_key_changeMe -# aio_credential_id = your credential id as shown in your Adobe Developer Console workspace -aio_credential_id=aio_credential_id_changeMe -# aio_client_secret = your credential Client secret as shown in in your Adobe Developer Console workspace -aio_client_secret=aio_client_secret_changeMe -# aio_technical_account_id = your credential Technical account ID as shown in your Adobe Developer Console workspace -aio_technical_account_id=aio_technical_account_id_changeMe - -# You then have 3 options to configure the privateKey (associated with the public key set in your Adobe Developer Console workspace): -# * Option 1: use a pcks8 file -# * Option 2: use base 64 encoded pcks8 key -# * Option 3: use a keystore -aio_encoded_pkcs8=aio_encoded_pkcs8_changeMe - -###################################### -# Management API test drive properties -###################################### -aio_provider_id=aio_provider_id_changeMe -aio_event_code=aio_event_code_changeMe - - diff --git a/events_journal/pom.xml b/events_journal/pom.xml index 9f8b0879..538ec5e9 100644 --- a/events_journal/pom.xml +++ b/events_journal/pom.xml @@ -17,7 +17,7 @@ com.adobe.aio aio-lib-java - 1.1.29-SNAPSHOT + 2.0.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/events_journal/src/main/java/com/adobe/aio/event/journal/feign/FeignJournalService.java b/events_journal/src/main/java/com/adobe/aio/event/journal/feign/FeignJournalService.java index 3de112e1..1e868b2a 100644 --- a/events_journal/src/main/java/com/adobe/aio/event/journal/feign/FeignJournalService.java +++ b/events_journal/src/main/java/com/adobe/aio/event/journal/feign/FeignJournalService.java @@ -36,7 +36,7 @@ public class FeignJournalService implements JournalService { public FeignJournalService(final Workspace workspace, final String journalUrl) { if (StringUtils.isEmpty(journalUrl)) { throw new IllegalArgumentException( - "JournalService is missing aj ournalUrl"); + "JournalService is missing a journalUrl"); } if (workspace == null) { throw new IllegalArgumentException("RegistrationService is missing a workspace context"); diff --git a/events_journal/src/test/resources/workspace.properties b/events_journal/src/test/resources/workspace.properties deleted file mode 100644 index 896fb845..00000000 --- a/events_journal/src/test/resources/workspace.properties +++ /dev/null @@ -1,36 +0,0 @@ -# -# Copyright 2017 Adobe. All rights reserved. -# This file is licensed to you under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under -# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -# OF ANY KIND, either express or implied. See the License for the specific language -# governing permissions and limitations under the License. -# - -aio_meta_scopes=aio_meta_scopes_changeMe -# ims_org_id = your IMS Organization ID as shown in your Adobe Developer Console workspace -aio_ims_org_id=aio_ims_org_id_changeMe -# consumer_org_id = your consumer Org Id as shown in your Adobe Developer Console workspace -aio_consumer_org_id=aio_consumer_org_id_changeMe -# api_key = your credential API Key (Client ID) as shown in in your Adobe Developer Console workspace -aio_api_key=aio_api_key_changeMe -# credential_id = your credential id as shown in your Adobe Developer Console workspace -aio_credential_id=aio_credential_id_changeMe -# client_secret = your credential Client secret as shown in in your Adobe Developer Console workspace -aio_client_secret=aio_client_secret_changeMe -# technical_account_id = your credential Technical account ID as shown in your Adobe Developer Console workspace -aio_technical_account_id=aio_technical_account_id_changeMe -# You then have 3 options to configure the privateKey (associated with the public key set in your Adobe Developer Console workspace): -# * Option 1: use a pcks8 file (using the `pkcs8_file_path` config) -# * Option 2: use base 64 encoded pcks8 key (using the `encoded_pkcs8` config) -# * Option 3: use a keystore (using the `pkcs12_file_path`, `pkcs12_alias` and `pkcs12_password` config) -aio_encoded_pkcs8=changeMe -###################################### -# test drive properties -###################################### -aio_journal_url=changeMe - - diff --git a/events_mgmt/pom.xml b/events_mgmt/pom.xml index 54d5d569..85a71fd6 100644 --- a/events_mgmt/pom.xml +++ b/events_mgmt/pom.xml @@ -17,7 +17,7 @@ com.adobe.aio aio-lib-java - 1.1.29-SNAPSHOT + 2.0.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/events_mgmt/src/test/resources/workspace.properties b/events_mgmt/src/test/resources/workspace.properties deleted file mode 100644 index bc5b692e..00000000 --- a/events_mgmt/src/test/resources/workspace.properties +++ /dev/null @@ -1,45 +0,0 @@ -# -# Copyright 2017 Adobe. All rights reserved. -# This file is licensed to you under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under -# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -# OF ANY KIND, either express or implied. See the License for the specific language -# governing permissions and limitations under the License. -# - -# aio_meta_scopes : comma separated list of metascopes associated with your API -# sample aio_meta_scopes: /s/ent_user_sdk,/s/ent_marketing_sdk,/s/creative_sdk -aio_meta_scopes=aio_meta_scopes_changeMe -# aio_ims_org_id = your IMS Organization ID as shown in your Adobe Developer Console workspace -aio_ims_org_id=aio_ims_org_id_changeMe -# aio_consumer_org_id = your consumer Org Id as shown in your Adobe Developer Console workspace (project.org.id) -aio_consumer_org_id=aio_consumer_org_id_changeMe -# aio_project_id = your project Id as shown in your Adobe Developer Console workspace (project.id) -aio_project_id=aio_project_id_changeMe -# aio_workspace_id = your workspace Id as shown in your Adobe Developer Console workspace (project.workspace.id) -aio_workspace_id=aio_workspace_id_changeMe -# aio_api_key = your credential API Key (Client ID) as shown in in your Adobe Developer Console workspace -aio_api_key=aio_api_key_changeMe -# aio_credential_id = your credential id as shown in your Adobe Developer Console workspace -aio_credential_id=aio_credential_id_changeMe -# aio_client_secret = your credential Client secret as shown in in your Adobe Developer Console workspace -aio_client_secret=aio_client_secret_changeMe -# aio_technical_account_id = your credential Technical account ID as shown in your Adobe Developer Console workspace -aio_technical_account_id=aio_technical_account_id_changeMe - -# You then have 3 options to configure the privateKey (associated with the public key set in your Adobe Developer Console workspace): -# * Option 1: use a pcks8 file -# * Option 2: use base 64 encoded pcks8 key -# * Option 3: use a keystore -aio_encoded_pkcs8=aio_encoded_pkcs8_changeMe - -###################################### -# Management API test drive properties -###################################### -aio_provider_id=aio_provider_id_changeMe -aio_event_code=aio_event_code_changeMe - - diff --git a/events_test/pom.xml b/events_test/pom.xml index 84b1769e..1df5f674 100644 --- a/events_test/pom.xml +++ b/events_test/pom.xml @@ -16,7 +16,7 @@ com.adobe.aio aio-lib-java - 1.1.29-SNAPSHOT + 2.0.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/events_test/src/main/java/com/adobe/aio/event/journal/JournalServiceTester.java b/events_test/src/main/java/com/adobe/aio/event/journal/JournalServiceTester.java index f6982491..fa0cec27 100644 --- a/events_test/src/main/java/com/adobe/aio/event/journal/JournalServiceTester.java +++ b/events_test/src/main/java/com/adobe/aio/event/journal/JournalServiceTester.java @@ -17,9 +17,8 @@ import com.adobe.aio.event.journal.model.JournalEntry; import com.adobe.aio.util.WorkspaceUtil; import com.adobe.aio.workspace.Workspace; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; + +import java.util.*; import java.util.function.BiPredicate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,16 +48,63 @@ public JournalServiceTester() { workspace = WorkspaceUtil.getSystemWorkspaceBuilder().build(); } + /** + * + * @param journalUrl + * @return the latest journal entry, we do retry the journaling API, up to pollingTimeOutInMs, + * if it fails (due to temporary failure, or cache propagation latencies). + */ + public JournalEntry getLatestEntry(String journalUrl) { + long pollingDuration = 0; + long sleeptime = 2000L; + JournalEntry entry = null; + while (pollingDuration < JOURNAL_POLLING_TIME_OUT_IN_MILLISECONDS && entry == null) + { + JournalService journalService = + JournalService.builder().workspace(workspace).url(journalUrl).build(); + entry = journalService.getLatest(); + if (entry == null) { + logger.warn("Latest Journal entry is null... retrying in 2 seconds... "); + pollingDuration += sleeptime; + try { + Thread.sleep(sleeptime); + } catch (InterruptedException e) { + logger.error("Interrupted while sleeping", e); + } + } else { + entry = journalService.get(entry.getNextLink()); + } + } + + if (entry == null) { + logger.error("We polled the journal for " + JOURNAL_POLLING_TIME_OUT_IN_MILLISECONDS + " milliseconds and could NOT GET the latest Journal Entry."); + throw new RuntimeException("We polled the journal for " + JOURNAL_POLLING_TIME_OUT_IN_MILLISECONDS + " milliseconds and could NOT GET the latest Journal Entry."); + } else { + logger.info("Successfully polled the latest Journal Entry before publishing a new test event..."); + } + return entry; + } + public boolean pollJournalForEvent(String journalUrl, String eventId, + BiPredicate isEventIdInEvent) + throws InterruptedException { + JournalService journalService = JournalService.builder() + .workspace(workspace) + .url(journalUrl) + .build(); + JournalEntry entry = journalService.getOldest(); + return pollJournalForEvent(journalUrl, entry, eventId, isEventIdInEvent); + } + + + public boolean pollJournalForEvent(String journalUrl, JournalEntry fromEntry, String eventId, BiPredicate isEventIdInEvent) throws InterruptedException { - JournalService journalService = JournalService.builder() - .workspace(workspace) - .url(journalUrl) - .build(); + JournalService journalService = + JournalService.builder().workspace(workspace).url(journalUrl).build(); long pollingDuration = 0; - JournalEntry entry = journalService.getOldest(); + JournalEntry entry = fromEntry; while (!isEventIdInJournalEntry(entry, eventId, isEventIdInEvent)) { if (entry.isEmpty()) { logger.info("Empty journal entry, we will retry-after {} seconds.", diff --git a/events_test/src/test/java/com/adobe/aio/event/journal/JournalServiceIntegrationTest.java b/events_test/src/test/java/com/adobe/aio/event/journal/JournalServiceIntegrationTest.java index 221a8ba4..5ca711d5 100644 --- a/events_test/src/test/java/com/adobe/aio/event/journal/JournalServiceIntegrationTest.java +++ b/events_test/src/test/java/com/adobe/aio/event/journal/JournalServiceIntegrationTest.java @@ -17,6 +17,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.adobe.aio.event.journal.model.JournalEntry; import com.adobe.aio.event.management.ProviderServiceIntegrationTest; import com.adobe.aio.event.management.ProviderServiceTester; import com.adobe.aio.event.management.RegistrationServiceTester; @@ -37,6 +38,7 @@ public JournalServiceIntegrationTest() { providerServiceTester = new ProviderServiceTester(); registrationServiceTester = new RegistrationServiceTester(); publishServiceTester = new PublishServiceTester(); + } @Test @@ -70,12 +72,15 @@ public void testJournalPolling() TEST_REGISTRATION_NAME, providerId, TEST_EVENT_CODE); registrationId = registration.getRegistrationId(); + JournalEntry fromEntry = getLatestEntry(registration.getJournalUrl().getHref()); + logger.info("polled latest Journal Entry before publishing a new test event..."); + String cloudEventId = publishServiceTester.publishCloudEvent(false, providerId, TEST_EVENT_CODE); boolean wasCloudEventPolled = pollJournalForEvent( - registration.getJournalUrl().getHref(), cloudEventId, isEventIdTheCloudEventId); + registration.getJournalUrl().getHref(), fromEntry, cloudEventId, isEventIdTheCloudEventId); String rawEventId = publishServiceTester.publishRawEvent(providerId, TEST_EVENT_CODE); - boolean wasRawEventPolled = pollJournalForEvent(registration.getJournalUrl().getHref(), rawEventId, + boolean wasRawEventPolled = pollJournalForEvent(registration.getJournalUrl().getHref(), fromEntry, rawEventId, isEventIdInTheCloudEventData); assertTrue(wasCloudEventPolled, "The published CloudEvent was not retrieved in the Journal"); diff --git a/events_test/src/test/resources/workspace.properties b/events_test/src/test/resources/workspace.properties deleted file mode 100644 index 671ddcaf..00000000 --- a/events_test/src/test/resources/workspace.properties +++ /dev/null @@ -1,39 +0,0 @@ -# -# Copyright 2017 Adobe. All rights reserved. -# This file is licensed to you under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under -# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -# OF ANY KIND, either express or implied. See the License for the specific language -# governing permissions and limitations under the License. -# - -aio_meta_scopes=aio_meta_scopes_changeMe -# ims_org_id = your IMS Organization ID as shown in your Adobe Developer Console workspace -aio_ims_org_id=aio_ims_org_id_changeMe -# consumer_org_id = your consumer Org Id as shown in your Adobe Developer Console workspace -aio_consumer_org_id=aio_consumer_org_id_changeMe -# api_key = your credential API Key (Client ID) as shown in in your Adobe Developer Console workspace -aio_api_key=aio_api_key_changeMe -# credential_id = your credential id as shown in your Adobe Developer Console workspace -aio_credential_id=aio_credential_id_changeMe -# client_secret = your credential Client secret as shown in in your Adobe Developer Console workspace -aio_client_secret=aio_client_secret_changeMe -# technical_account_id = your credential Technical account ID as shown in your Adobe Developer Console workspace -aio_technical_account_id=aio_technical_account_id_changeMe -# You then have 3 options to configure the privateKey (associated with the public key set in your Adobe Developer Console workspace): -# * Option 1: use a pcks8 file (using the `pkcs8_file_path` config) -# * Option 2: use base 64 encoded pcks8 key (using the `encoded_pkcs8` config) -# * Option 3: use a keystore (using the `pkcs12_file_path`, `pkcs12_alias` and `pkcs12_password` config) -aio_encoded_pkcs8=changeMe - -###################################### -# I/O Events url -###################################### -#aio_api_url=https://api.adobe.io -#aio_publish_url=https://eventsingress.adobe.io - - - diff --git a/events_webhook/pom.xml b/events_webhook/pom.xml index 18256a85..c163b403 100644 --- a/events_webhook/pom.xml +++ b/events_webhook/pom.xml @@ -16,7 +16,7 @@ com.adobe.aio aio-lib-java - 1.1.29-SNAPSHOT + 2.0.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/events_xdm/pom.xml b/events_xdm/pom.xml index adb40dde..ac52923a 100644 --- a/events_xdm/pom.xml +++ b/events_xdm/pom.xml @@ -17,7 +17,7 @@ com.adobe.aio aio-lib-java - 1.1.29-SNAPSHOT + 2.0.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/ims/README.md b/ims/README.md index 032def58..78c48066 100644 --- a/ims/README.md +++ b/ims/README.md @@ -1,111 +1,80 @@ # `aio-lib-java-ims` -`aio-lib-java-ims` is an Adobe I/O - Java SDK - IMS Library. -This Java library wraps http API endpoints exposed by -[Adobe Identity Management System (IMS)](https://developer.adobe.com/developer-console/docs/guides/#!AdobeDocs/adobeio-auth/master/AuthenticationOverview/ServiceAccountIntegration.md) +`aio-lib-java-ims` is the Adobe I/O - Java SDK - IMS Library. -## Service Account Integration (JWT authentication flow) +* wrapping http API endpoints exposed by [Adobe Identity Management System (IMS)](https://developer.adobe.com/developer-console/docs/guides/#!AdobeDocs/adobeio-auth/master/AuthenticationOverview/ServiceAccountIntegration.md) +* providing an Authentication [Open Feign RequestInterceptor](https://github.com/OpenFeign/feign#request-interceptors) that can be leveraged to transparently add the authentication headers expected by many Adobe APIs, it will add + * an `Authorization` header with a `Bearer` access token + * renewing it only when expired or when not present in memory yet + * a `x-api-key` header matching your token -A Service Account connection allows your application to call Adobe services on behalf of -the application itself or on behalf of an enterprise organization. - -For this type of connection, you will create a JSON Web Token (JWT) that encapsulates -your credentials and begin each API session by exchanging the JWT for an access token. - -The JWT encodes all of the identity and security information required to obtain an access -token and must be signed with the private key that is associated with a public key certificate specified on your integration. - -Browse our [JWT authentication documentation](https://developer.adobe.com/developer-console/docs/guides/authentication/JWT/) -for more details. - -This Java library will help you implement this JWT exchange token flow, to get a valid access token -and start interacting with the many Adobe I/O API that support such authentication. +## Test Drive -### Configurations - -This library fluent workspace builder API offers many ways to have your `Workspace` (a Java POJO representation of your `Adobe Developer Console` Workspace) configured. - -To get you started quickly you could use a `.properties` file, -see our [sample config file](./src/test/resources/workspace.properties) - -#### Create and configure your public and private key - -As introduced above the authentication flow signs the JWT request and therefore requires private-public keys configurations -, therefore you will need to - -* First, create this RSA private/public certificate pair, using openssl: - - `openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout private.key -out certificate_pub.crt` +It only takes a few lines of code: -* Then, upload the public key in your Adobe Developer Workspace, see our [JWT authentication documentation](https://developer.adobe.com/developer-console/docs/guides/authentication/JWT/) -* Finally, configure this library (and its [`PrivateKeyBuilder`](./src/main/java/com/adobe/util/PrivateKeyBuilder.java)) with your privateKey, you may either - * use a pcks8 file - * use a base 64 encoded pcks8 key - * use a keystore file + Workspace workspace = WorkspaceUtil.getSystemWorkspaceBuilder().build(); // [1] + ImsService imsService = ImsService.builder().workspace(workspace).build(); // [2] + logger.info("accessToken: {}", imsService.getAccessToken()); // [3] -##### Option 1: Use a pcks8 file + // [1] build your `Workspace` (a Java POJO representation of your `Adobe Developer Console` Workspace) + // looking up other System Environment variables. + // Note that our fluent workspace and private Key builders offers many ways to have your workspace configured, + // we are showing here the most concise + // [2] build the Ims Service wrapper and have it use this workspace context + // [3] use this service to retrieve an OAuth access token -First, convert your private key to a PKCS8 format, use the following command: + // here is one way you can build the related Adobe IMS Auth Interceptor + RequestInterceptor authInterceptor = AuthInterceptor.builder().workspace(workspace).build(); - openssl pkcs8 -topk8 -inform PEM -outform DER -in private.key -nocrypt > private.pkcs8.key - -Then, set your workspace `aio_pkcs8_file_path` properties -to match the `private.pkcs8.key` file path (you generated using the previous command) - - -##### Option 2: use a base 64 encoded pcks8 key - -First, convert your private key to a PKCS8 format, use the following command: - - openssl pkcs8 -topk8 -inform PEM -outform DER -in private.key -nocrypt > private.pkcs8.key - -Then, base 64 encode it, use the following command: - - base64 private.pkcs8.key +Have a look at our [ImsService `main()` Test Drive](./src/test/java/com/adobe/aio/ims/ImsServiceTestDrive.java) -Finally, set your workspace `aio_encoded_pkcs8` properties value using the string you generated with the above command -##### Option 3: use a keystore +## Configurations -First, use the following commands to set the alias (as `myalias` here) and a non-empty keystore password. +Note that this library is built on top of `aio-lib-java-core` which holds a fluent workspace builder API that offers many ways to build your `Workspace` (a Java POJO representation of your `Adobe Developer Console` Workspace). +See more details on the [Workspace](../aio-lib-java-core/README.md#Workspace) configurations in the `aio-lib-java-core` [README](../core/README.md#Workspace) - cat private.key certificate_pub.crt > private-key-crt - openssl pkcs12 -export -in private-key-crt -out keystore.p12 -name myalias -noiter -nomaciter +It allows you to integrate with the two server to server authentication credentials that Adobe supports. +* OAuth Server-to-Server credentials +* Service Account (JWT) credentials (deprecated) -Then fill the associated `aio_pkcs12_file_path`, `aio_pkcs12_alias` and `aio_pkcs12_password` workspace properties. +These credentials only differ in the way your application generates the access token, the rest of their functioning is similar. +### OAuth Server-to-Server credentials Configurations +The OAuth Server-to-Server credential relies on the OAuth 2.0 `client_credentials` grant type to generate access tokens. +To generate an access token, your application can make a single HTTP request with your `client_id` and `client_secret` and `scopes`. -### Our reusable `OpenFeign` JWT (exchange token flow) Authentication `RequestInterceptor` +Browse our [OAuth authentication documentation](https://developer.adobe.com/developer-console/docs/guides/authentication/ServerToServerAuthentication/#oauth-server-to-server-credential) for more details, +and to get you started quickly, have a look at our [sample oauth config file: `workspace.oauth.properties`](./src/test/resources/workspace.oauth.properties) -This lib also contains JWT (exchange token flow) Authentication `RequestInterceptor`: [JWTAuthInterceptor](src/main/java/com/adobe/aio/ims/api/JWTAuthInterceptor.java) -It is a [Open Feign RequestInterceptor](https://github.com/OpenFeign/feign#request-interceptors). -It can be leverage to add the authentication headers expected by many Adobe APIs, it will add -* an `Authorization` header with a `Bearer` access token (generated from a JWT exchange flow) - * renewing it only when expired (after 24 hours) or when not present in memory yet -* a `x-api-key` header matching your JWT token -### Test Drive +### Service Account (JWT) credential (deprecated) +A Service Account connection allows your application to call Adobe services on behalf of +the application itself or on behalf of an enterprise organization. - PrivateKey privateKey = new PrivateKeyBuilder().systemEnv().build(); // [1] - Workspace workspace = Workspace.builder() - .systemEnv() - .privateKey(privateKey) - .build(); // [2] - ImsService imsService = ImsService.builder().workspace(workspace).build(); // [3] +For this type of connection, you will create a JSON Web Token (JWT) that encapsulates +your credentials and begin each API session by exchanging the JWT for an access token. - AccessToken accessToken = imsService.getJwtExchangeAccessToken(); // [4] +The JWT encodes all of the identity and security information required to obtain an access +token and must be signed with the private key that is associated with a public key certificate specified on your integration. - // [1] Build your PrivateKey looking up the key indicated by you System Environment variables - // [2] build your `Workspace` (a Java POJO representation of your `Adobe Developer Console` Workspace) - // looking up other System Environment variables. - // Note that our fluent workspace and private Key builders offers many ways to have your workspace configured, - // we are showing here the most concise - // [3] build the Ims Service wrapper and have it use this workspace context - // [4] use this service to retrieve an access token using a jwt exchange token flow +Browse our [JWT authentication documentation](https://developer.adobe.com/developer-console/docs/guides/authentication/JWT/) for more details, +and to get you started quickly, look at our [sample jwt config file: `workspace.jwt.properties`](./src/test/resources/workspace.jwt.properties) +#### Create and configure your public and private key +As introduced above the authentication flow signs the JWT request and therefore requires private-public keys configurations +, therefore you will need to -Have a look at our [ImsService `main()` Test Drive](./src/test/java/com/adobe/aio/ims/ImsServiceTestDrive.java) +* create this RSA private/public certificate pair, using openssl: + `openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout private.key -out certificate_pub.crt` +* upload the public key in your Adobe Developer Workspace, see our [JWT authentication documentation](https://developer.adobe.com/developer-console/docs/guides/authentication/JWT/) +* configure this library (and its [`PrivateKeyBuilder`](./src/main/java/com/adobe/util/PrivateKeyBuilder.java)) with your privateKey using a base 64 encoded pcks8 key + * convert your private key to a PKCS8 format, use the following command: + `openssl pkcs8 -topk8 -inform PEM -outform DER -in private.key -nocrypt > private.pkcs8.key` + * base 64 encode it (and stuff it in a `private.pkcs8.key.base64` file), use the following command: + `base64 -i private.pkcs8.key -o private.pkcs8.key.base64` +* set your workspace `aio_encoded_pkcs8` properties value using the string you generated with the above command ## Builds diff --git a/ims/pom.xml b/ims/pom.xml index 9988d48f..a6843e80 100644 --- a/ims/pom.xml +++ b/ims/pom.xml @@ -17,7 +17,7 @@ com.adobe.aio aio-lib-java - 1.1.29-SNAPSHOT + 2.0.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/ims/src/main/java/com/adobe/aio/ims/ImsService.java b/ims/src/main/java/com/adobe/aio/ims/ImsService.java index 954a17dd..58134b18 100644 --- a/ims/src/main/java/com/adobe/aio/ims/ImsService.java +++ b/ims/src/main/java/com/adobe/aio/ims/ImsService.java @@ -17,26 +17,23 @@ public interface ImsService { - /** - * Returns an {@link AccessToken} that can be used for other AIO API Calls. - * - * @deprecated this will be removed in v2.0 - * @return AccessToken a valid API authentication token - */ - @Deprecated() - AccessToken getJwtExchangeAccessToken(); /** - * Checks that the access token is still valid. + * Checks that the jwt access token is still valid. * - * @deprecated this will be removed in v2.0 - * @param accessToken the token to check + * @deprecated this will be removed as JWT token exchange is deprecated + * @param jwtAccessToken the jwt token to check * @return true if the provided access token is still valid, false otherwise */ @Deprecated() - boolean validateAccessToken(String accessToken); + boolean validateJwtAccessToken(String jwtAccessToken); - AccessToken getOAuthAccessToken(); + /** + * Looking up the contextual Workspace, it will use + * either the OAuth or JWT authentication context to fetch a valid access token. + * @return AccessToken a valid API authentication token + */ + AccessToken getAccessToken(); static Builder builder() { return new Builder(); @@ -67,7 +64,10 @@ public Builder workspace(Workspace workspace) { * @throws IllegalStateException if the Workspace authentication context is not valid. */ public ImsService build() throws IllegalStateException { - this.workspace.getAuthContext().validate(); + if (workspace == null) { + throw new IllegalStateException("Workspace is required to build ImsService"); + } + workspace.validateAll(); return new FeignImsService(this.workspace); } } diff --git a/ims/src/main/java/com/adobe/aio/ims/JwtTokenBuilder.java b/ims/src/main/java/com/adobe/aio/ims/JwtTokenBuilder.java index b4eabe1c..11053c8e 100644 --- a/ims/src/main/java/com/adobe/aio/ims/JwtTokenBuilder.java +++ b/ims/src/main/java/com/adobe/aio/ims/JwtTokenBuilder.java @@ -53,7 +53,7 @@ public class JwtTokenBuilder { private static final String AUD_SUFFIX = "/c/"; public JwtTokenBuilder(final Workspace workspace) { - if (!(workspace.getAuthContext() instanceof JwtContext)) { + if (!workspace.isAuthJWT()) { throw new IllegalStateException("AuthContext in workspace not of type `JwtContext`."); } diff --git a/ims/src/main/java/com/adobe/aio/ims/feign/AuthInterceptor.java b/ims/src/main/java/com/adobe/aio/ims/feign/AuthInterceptor.java index 62826e35..3a86a44e 100644 --- a/ims/src/main/java/com/adobe/aio/ims/feign/AuthInterceptor.java +++ b/ims/src/main/java/com/adobe/aio/ims/feign/AuthInterceptor.java @@ -1,8 +1,5 @@ package com.adobe.aio.ims.feign; -import com.adobe.aio.auth.Context; -import com.adobe.aio.auth.JwtContext; -import com.adobe.aio.auth.OAuthContext; import com.adobe.aio.ims.ImsService; import com.adobe.aio.ims.model.AccessToken; import com.adobe.aio.workspace.Workspace; @@ -11,14 +8,14 @@ import static com.adobe.aio.util.Constants.*; -public abstract class AuthInterceptor implements RequestInterceptor { +public class AuthInterceptor implements RequestInterceptor { private volatile Long expirationTimeMillis; private volatile AccessToken accessToken; private final ImsService imsService; - protected AuthInterceptor(final ImsService imsService) { - this.imsService = imsService; + protected AuthInterceptor (final Workspace workspace) { + this.imsService = ImsService.builder().workspace(workspace).build(); } @Override @@ -30,7 +27,9 @@ ImsService getImsService() { return this.imsService; } - abstract AccessToken fetchAccessToken(); + AccessToken fetchAccessToken() { + return getImsService().getAccessToken(); + } synchronized String getAccessToken() { if (expirationTimeMillis == null || System.currentTimeMillis() >= expirationTimeMillis) { @@ -62,25 +61,18 @@ public static Builder builder() { public static class Builder { - private Context authContext; - private ImsService imsService; + private Workspace workspace; private Builder() { } public Builder workspace(Workspace workspace) { - this.authContext = workspace.getAuthContext(); - this.imsService = ImsService.builder().workspace(workspace).build(); + this.workspace = workspace; return this; } public AuthInterceptor build() { - if (authContext instanceof JwtContext) { - return new JWTAuthInterceptor(imsService); - } else if (authContext instanceof OAuthContext) { - return new OAuthInterceptor(imsService); - } - throw new IllegalStateException("Unable to find interceptor for AuthContext"); + return new AuthInterceptor(workspace); } } diff --git a/ims/src/main/java/com/adobe/aio/ims/feign/FeignImsService.java b/ims/src/main/java/com/adobe/aio/ims/feign/FeignImsService.java index c293194a..634adca9 100644 --- a/ims/src/main/java/com/adobe/aio/ims/feign/FeignImsService.java +++ b/ims/src/main/java/com/adobe/aio/ims/feign/FeignImsService.java @@ -23,9 +23,13 @@ import com.adobe.aio.ims.api.ImsApi; import com.adobe.aio.ims.model.AccessToken; import com.adobe.aio.util.feign.FeignUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class FeignImsService implements ImsService { + private Logger logger = LoggerFactory.getLogger(this.getClass()); + public static final String ACCESS_TOKEN = "access_token"; private final ImsApi imsApi; private final Workspace workspace; @@ -36,11 +40,29 @@ public FeignImsService(final Workspace workspace) { } @Override - public AccessToken getJwtExchangeAccessToken() { - if (!(workspace.getAuthContext() instanceof JwtContext)) { - throw new IllegalStateException("AuthContext in workspace not of type `JwtContext`."); + public AccessToken getAccessToken() { + if (workspace.isAuthJWT()) { + return getJwtExchangeAccessToken(); + } else if (workspace.isAuthOAuth()) { + return getOAuthAccessToken(); + } else { + throw new IllegalStateException("AuthContext in workspace not of type `OAuthContext` or `JwtContext`."); } + } + @Override + public boolean validateJwtAccessToken(String jwtAccessToken) { + if (!workspace.isAuthJWT()) { + logger.error("AuthContext in workspace not of type `JwtContext`... this only validates JWT Token"); + return false; + } + return imsApi.validateJwtToken(ACCESS_TOKEN, workspace.getApiKey(), jwtAccessToken).getValid(); + } + + private AccessToken getJwtExchangeAccessToken() { + if (!workspace.isAuthJWT()) { + throw new IllegalStateException("AuthContext in workspace not of type `JwtContext`."); + } JwtContext context = (JwtContext) workspace.getAuthContext(); context.validate(); @@ -49,24 +71,14 @@ public AccessToken getJwtExchangeAccessToken() { return imsApi.getJwtAccessToken(workspace.getApiKey(), context.getClientSecret(), token); } - @Override - public boolean validateAccessToken(String accessToken) { - if (!(workspace.getAuthContext() instanceof JwtContext)) { - throw new IllegalStateException("AuthContext in workspace not of type `JwtContext`."); - } - - return imsApi.validateJwtToken(ACCESS_TOKEN, workspace.getApiKey(), accessToken).getValid(); - } - - @Override - public AccessToken getOAuthAccessToken() { - if (!(workspace.getAuthContext() instanceof OAuthContext)) { + private AccessToken getOAuthAccessToken() { + if (!workspace.isAuthOAuth()) { throw new IllegalStateException("AuthContext in workspace not of type `OAuthContext`."); } OAuthContext context = (OAuthContext) workspace.getAuthContext(); String scopes = context.getScopes().stream().filter(StringUtils::isNotBlank).map(String::trim).collect(Collectors.joining(",")); - return imsApi.getOAuthAccessToken(workspace.getApiKey(), context.getClientSecret(), scopes); } + } diff --git a/ims/src/main/java/com/adobe/aio/ims/feign/JWTAuthInterceptor.java b/ims/src/main/java/com/adobe/aio/ims/feign/JWTAuthInterceptor.java deleted file mode 100644 index ceecf3df..00000000 --- a/ims/src/main/java/com/adobe/aio/ims/feign/JWTAuthInterceptor.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2017 Adobe. All rights reserved. - * This file is licensed to you under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. You may obtain a copy - * of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under - * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - * OF ANY KIND, either express or implied. See the License for the specific language - * governing permissions and limitations under the License. - */ -package com.adobe.aio.ims.feign; - -import com.adobe.aio.ims.ImsService; -import com.adobe.aio.ims.model.AccessToken; - -@Deprecated -public class JWTAuthInterceptor extends AuthInterceptor { - - protected JWTAuthInterceptor(ImsService imsService) { - super(imsService); - } - - public boolean isUp() { - return getImsService().validateAccessToken(this.getAccessToken()); - } - - AccessToken fetchAccessToken() { - return getImsService().getJwtExchangeAccessToken(); - } -} diff --git a/ims/src/main/java/com/adobe/aio/ims/feign/OAuthInterceptor.java b/ims/src/main/java/com/adobe/aio/ims/feign/OAuthInterceptor.java deleted file mode 100644 index beade471..00000000 --- a/ims/src/main/java/com/adobe/aio/ims/feign/OAuthInterceptor.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2017 Adobe. All rights reserved. - * This file is licensed to you under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. You may obtain a copy - * of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under - * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - * OF ANY KIND, either express or implied. See the License for the specific language - * governing permissions and limitations under the License. - */ -package com.adobe.aio.ims.feign; - -import com.adobe.aio.ims.ImsService; -import com.adobe.aio.ims.model.AccessToken; - -public class OAuthInterceptor extends AuthInterceptor { - protected OAuthInterceptor(ImsService imsService) { - super(imsService); - } - - @Override - AccessToken fetchAccessToken() { - return getImsService().getOAuthAccessToken(); - } -} diff --git a/ims/src/main/java/com/adobe/aio/ims/util/PrivateKeyBuilder.java b/ims/src/main/java/com/adobe/aio/ims/util/PrivateKeyBuilder.java index ce28b32b..1cb70c2a 100644 --- a/ims/src/main/java/com/adobe/aio/ims/util/PrivateKeyBuilder.java +++ b/ims/src/main/java/com/adobe/aio/ims/util/PrivateKeyBuilder.java @@ -11,24 +11,14 @@ */ package com.adobe.aio.ims.util; -import static com.adobe.aio.util.FileUtil.getMapFromProperties; -import static com.adobe.aio.util.FileUtil.readPropertiesFromClassPath; -import static com.adobe.aio.util.FileUtil.readPropertiesFromFile; - -import java.io.IOException; import java.security.PrivateKey; import java.util.Map; -import java.util.Properties; + import org.apache.commons.lang3.StringUtils; public class PrivateKeyBuilder { public static final String AIO_ENCODED_PKCS_8 = "aio_encoded_pkcs8"; - private static final String AIO_PKCS_8_FILE_PATH = "aio_pkcs8_file_path"; - - private static final String AIO_PKCS_12_FILE_PATH = "aio_pkcs12_file_path"; - private static final String AIO_PKCS_12_PASSWORD = "aio_pkcs12_password"; - private static final String AIO_PKCS_12_ALIAS = "aio_pkcs12_alias"; private Map configMap; private String encodedPkcs8Key; @@ -36,26 +26,6 @@ public class PrivateKeyBuilder { public PrivateKeyBuilder() { } - public PrivateKeyBuilder systemEnv() { - this.configMap = System.getenv(); - return this; - } - - /** - * @param configPath: will first look on the file system, if not found, in the classpath - * @return a PrivateKeyBuilder loaded with the provided config - */ - public PrivateKeyBuilder configPath(String configPath) { - this.configMap = getMapFromProperties( - readPropertiesFromFile(configPath) - .orElse(readPropertiesFromClassPath(configPath))); - return this; - } - - public PrivateKeyBuilder properties(Properties properties) { - this.configMap = getMapFromProperties(properties); - return this; - } public PrivateKeyBuilder encodedPkcs8Key(String encodedPkcs8Key) { this.encodedPkcs8Key = encodedPkcs8Key; @@ -72,30 +42,9 @@ public PrivateKey build() { + "" + e.getMessage(), e); } } else { - return getPrivateKey(this.configMap); + return null; } } - private static PrivateKey getPrivateKey(final Map imsConfig) { - try { - if (imsConfig.containsKey(AIO_ENCODED_PKCS_8)) { - return KeyStoreUtil.getPrivateKeyFromEncodedPkcs8(imsConfig.get(AIO_ENCODED_PKCS_8)); - } else if (imsConfig.containsKey(AIO_PKCS_8_FILE_PATH)) { - return KeyStoreUtil.getPrivateKeyFromPkcs8File(imsConfig.get(AIO_PKCS_8_FILE_PATH)); - } else if (imsConfig.containsKey(AIO_PKCS_12_FILE_PATH) && imsConfig.containsKey( - AIO_PKCS_12_PASSWORD) - && imsConfig.containsKey(AIO_PKCS_12_ALIAS)) { - return KeyStoreUtil.getPrivateKeyFromPkcs12File( - imsConfig.get(AIO_PKCS_12_FILE_PATH), imsConfig.get(AIO_PKCS_12_ALIAS), - imsConfig.get(AIO_PKCS_12_PASSWORD)); - } else { - throw new IllegalArgumentException( - "AIO is missing a valid (pkcs8 or pkcs12) Private Key configuration"); - } - } catch (Exception e) { - throw new IllegalArgumentException( - "AIO holds an invalid (pkcs8 or pkcs12) Private Key configuration. " - + "" + e.getMessage(), e); - } - } + } diff --git a/ims/src/main/java/com/adobe/aio/util/WorkspaceUtil.java b/ims/src/main/java/com/adobe/aio/util/WorkspaceUtil.java index 7462525f..55cd68c9 100644 --- a/ims/src/main/java/com/adobe/aio/util/WorkspaceUtil.java +++ b/ims/src/main/java/com/adobe/aio/util/WorkspaceUtil.java @@ -11,115 +11,191 @@ */ package com.adobe.aio.util; +import com.adobe.aio.auth.Context; +import com.adobe.aio.auth.JwtContext; +import com.adobe.aio.auth.OAuthContext; import com.adobe.aio.ims.util.PrivateKeyBuilder; import com.adobe.aio.workspace.Workspace; -import java.security.PrivateKey; -import java.util.Properties; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.security.PrivateKey; +import java.util.*; + +import static com.adobe.aio.auth.Context.CLIENT_SECRET; +import static com.adobe.aio.auth.JwtContext.*; +import static com.adobe.aio.auth.OAuthContext.SCOPES; +import static com.adobe.aio.util.FileUtil.getMap; +import static com.adobe.aio.workspace.Workspace.*; + public class WorkspaceUtil { - public static final String API_URL = "aio_api_url"; - public static final String PUBLISH_URL = "aio_publish_url"; - public static final String DEFAULT_TEST_PROPERTIES = "workspace.secret.properties"; - private static final Logger logger = LoggerFactory.getLogger(WorkspaceUtil.class); - - private WorkspaceUtil() { - } - - /** - * Loads configurations for a Workspace from either one and only one of the following - * sources, probing them first to check that all the required properties are given, - * in order: - *
    - *
  1. System Properties
  2. - *
  3. Environment Variables
  4. - *
  5. classpath:{@link WorkspaceUtil#DEFAULT_TEST_PROPERTIES}
  6. - *
- * @return a Workspace.Builder loaded with the provided config - */ - public static Workspace.Builder getSystemWorkspaceBuilder() { - if (StringUtils.isNoneBlank( - System.getProperty(PrivateKeyBuilder.AIO_ENCODED_PKCS_8), - System.getProperty(Workspace.API_KEY), - System.getProperty(Workspace.WORKSPACE_ID), - System.getProperty(Workspace.CLIENT_SECRET), - System.getProperty(Workspace.CONSUMER_ORG_ID), - System.getProperty(Workspace.CREDENTIAL_ID), - System.getProperty(Workspace.IMS_ORG_ID), - System.getProperty(Workspace.META_SCOPES), - System.getProperty(Workspace.PROJECT_ID), - System.getProperty(Workspace.TECHNICAL_ACCOUNT_ID))) { - logger.debug("loading test Workspace from JVM System Properties"); - PrivateKey privateKey = new PrivateKeyBuilder().encodedPkcs8Key( - System.getProperty(PrivateKeyBuilder.AIO_ENCODED_PKCS_8)).build(); - return Workspace.builder() - .properties(System.getProperties()) - .privateKey(privateKey); - } else if (StringUtils.isNoneBlank( - System.getenv(PrivateKeyBuilder.AIO_ENCODED_PKCS_8), - System.getenv(Workspace.API_KEY), - System.getenv(Workspace.WORKSPACE_ID), - System.getenv(Workspace.CLIENT_SECRET), - System.getenv(Workspace.CONSUMER_ORG_ID), - System.getenv(Workspace.CREDENTIAL_ID), - System.getenv(Workspace.IMS_ORG_ID), - System.getenv(Workspace.META_SCOPES), - System.getenv(Workspace.PROJECT_ID), - System.getenv(Workspace.TECHNICAL_ACCOUNT_ID))) { - logger.debug("loading test Workspace from JVM System Properties"); - PrivateKey privateKey = - new PrivateKeyBuilder() - .encodedPkcs8Key(System.getenv(PrivateKeyBuilder.AIO_ENCODED_PKCS_8)) - .build(); - return Workspace.builder().systemEnv().privateKey(privateKey); - } else { - /** - * WARNING: don't push back your workspace secrets to github - */ - logger.debug("loading test Workspace from classpath {}", DEFAULT_TEST_PROPERTIES); - return getWorkspaceBuilder(DEFAULT_TEST_PROPERTIES); + public static final String API_URL = "aio_api_url"; + public static final String PUBLISH_URL = "aio_publish_url"; + + /** + * Default workspace configuration file class path + * WARNING: don't push back this file to github as it contains many secrets. + * We do provide a sample properties files in the + * ./src/test/resources folder + */ + public static final String DEFAULT_TEST_PROPERTIES = "workspace.secret.properties"; + + private static final Logger logger = LoggerFactory.getLogger(WorkspaceUtil.class); + + private WorkspaceUtil() { + } + + /** + * Loads Workspace from either one and only one of the following + * sources, probing them first to check that all the required properties are given, + * in order: + *
    + *
  1. System Properties
  2. + *
  3. Environment Variables
  4. + *
  5. classpath:{@link WorkspaceUtil#DEFAULT_TEST_PROPERTIES}
  6. + *
+ * + * @return a Workspace.Builder loaded with the provided config + */ + public static Workspace.Builder getSystemWorkspaceBuilder() { + return getWorkspaceBuilder(getSystemWorkspaceConfig(DEFAULT_TEST_PROPERTIES)); + } + + public static Workspace.Builder getWorkspaceBuilder(Map configMap) { + Workspace.Builder builder = + Workspace.builder().imsUrl(configMap.get(IMS_URL)) + .imsOrgId(configMap.get(IMS_ORG_ID)) + .apiKey(configMap.get(API_KEY)) + .consumerOrgId(configMap.get(CONSUMER_ORG_ID)) + .projectId(configMap.get(PROJECT_ID)) + .workspaceId(configMap.get(WORKSPACE_ID)) + .credentialId(configMap.get(CREDENTIAL_ID)); + builder.authContext(getAuthContext(configMap)); + return builder; + } + + public static boolean isOAuthConfig(Map configMap) { + return configMap.containsKey(SCOPES); + } + + public static Context getAuthContext(Map configMap) { + if (isOAuthConfig(configMap)) { + return getOAuthContextBuilder(configMap).build(); + } else { + return getJwtContextBuilder(configMap).build(); + } } - } - - public static String getSystemProperty(String key) { - return getSystemProperty(key,DEFAULT_TEST_PROPERTIES); - } - - /** - * Loads a property from either one of the following sources, probing it first to - * check that the required property is given, in order: - *
    - *
  1. System Properties
  2. - *
  3. Environment Variables
  4. - *
  5. classpath:{@code propertyClassPath}
  6. - *
- * - * @param key the property name - * @param propertyClassPath the classpath of the property file - * @return the value of the property - */ - public static String getSystemProperty(String key, String propertyClassPath) { - if (StringUtils.isNotBlank(System.getProperty(key))) { - logger.debug("loading property `{}`from JVM System Properties", key); - return System.getProperty(key); - } if (StringUtils.isNotBlank(System.getenv(key))) { - logger.debug("loading property `{}` from Environment Variables", key); - return System.getenv(key); - } else { - logger.debug("loading property `{}` from classpath `{}`", key, propertyClassPath); - return FileUtil.readPropertiesFromClassPath(propertyClassPath).getProperty(key); + + public static OAuthContext.Builder getOAuthContextBuilder(Map configMap) { + OAuthContext.Builder builder = new OAuthContext.Builder(); + builder.clientSecret(configMap.get(CLIENT_SECRET)); + if (!StringUtils.isEmpty(configMap.get(SCOPES))) { + Arrays.stream(configMap.get(SCOPES).split(",")).forEach(builder::addScope); + } + return builder; + } + + + public static JwtContext.Builder getJwtContextBuilder(Map configMap) { + JwtContext.Builder builder = new JwtContext.Builder() + .clientSecret(configMap.get(CLIENT_SECRET)) + .technicalAccountId(configMap.get(TECHNICAL_ACCOUNT_ID)); + if (!StringUtils.isEmpty(configMap.get(META_SCOPES))) { + String[] metascopeArray = configMap.get(META_SCOPES).split(","); + for (String metascope : metascopeArray) { + builder.addMetascope(metascope); + } + } + getPrivateKey(configMap).ifPresent(builder::privateKey); + return builder; + } + + public static Optional getPrivateKey(Map configMap) { + String encodedPkcs8Key = configMap.get(PrivateKeyBuilder.AIO_ENCODED_PKCS_8); + if (encodedPkcs8Key != null) { + logger.debug("loading test JWT Private Key from JVM System Properties"); + try { + return Optional.of(new PrivateKeyBuilder().encodedPkcs8Key(encodedPkcs8Key).build()); + } catch (Exception e) { + logger.error("Error {} loading test Private Key from configMap", e.getMessage()); + return Optional.empty(); + } + } else { + return Optional.empty(); + } + } + + public static String getSystemProperty(String key) { + return getSystemProperty(key, DEFAULT_TEST_PROPERTIES); } - } - - private static Workspace.Builder getWorkspaceBuilder(String propertyFileClassPath) { - Properties prop = FileUtil.readPropertiesFromClassPath(propertyFileClassPath); - PrivateKey privateKey = new PrivateKeyBuilder().properties(prop).build(); - return Workspace.builder() - .properties(prop) - .privateKey(privateKey); - } + + /** + * Loads a property from either one of the following sources, probing it first to + * check that the required property is given, in order: + *
    + *
  1. System Properties
  2. + *
  3. Environment Variables
  4. + *
  5. classpath:{@code propertyClassPath}
  6. + *
+ * + * @param key the property name + * @param propertyClassPath the classpath of the property file + * @return the value of the property + */ + public static String getSystemProperty(String key, String propertyClassPath) { + return getSystemProperties(Arrays.asList(key), propertyClassPath).get(key); + } + + public static Map getSystemWorkspaceConfig(String propertiesClassPath) { + return getSystemProperties(Arrays.asList(API_KEY, + Workspace.WORKSPACE_ID, + CLIENT_SECRET, + Workspace.CONSUMER_ORG_ID, + IMS_ORG_ID, + Workspace.PROJECT_ID), propertiesClassPath); + } + + /** + * Loads configurations from either one and only one of the following + * sources, probing them first to check that all the required properties are given, + * in order: + *
    + *
  1. JVM System Properties
  2. + *
  3. Environment Variables
  4. + *
  5. the provided propertiesClassPath
  6. + *
+ * + * @param propertiesClassPath the classpath of the properties file + * @param keys the list of keys to look up + * @return Map of all the properties + */ + private static Map getSystemProperties(List keys, String propertiesClassPath) { + if (StringUtils.isNoneBlank(keys.stream().map(System::getProperty).toArray(String[]::new))) { + logger.debug("loading `{}` from JVM System Properties", keys); + return getMap(System.getProperties()); + } + if (StringUtils.isNoneBlank(keys.stream().map(System::getenv).toArray(String[]::new))) { + logger.debug("loading `{}` from Environment Variables", keys); + return System.getenv(); + } else { + if (WorkspaceUtil.class.getClassLoader().getResourceAsStream(propertiesClassPath) == null) { + logger.error("No system configuration found for keys `{}`, no properties file either at `{}`", + keys, propertiesClassPath); + return Collections.emptyMap(); + } else { + logger.debug("loading `{}` from classpath `{}`", keys, propertiesClassPath); + Map map = getMap(propertiesClassPath); + if (StringUtils.isNoneBlank(keys.stream().map(map::get).toArray(String[]::new))) { + return map; + } else { + logger.error("Missing configurations: keys: `{}` classpath: `{}`", keys, propertiesClassPath); + return Collections.emptyMap(); + } + } + } + } + } diff --git a/ims/src/test/java/com/adobe/aio/ims/ImsServiceTestDrive.java b/ims/src/test/java/com/adobe/aio/ims/ImsServiceTestDrive.java new file mode 100644 index 00000000..938aeb26 --- /dev/null +++ b/ims/src/test/java/com/adobe/aio/ims/ImsServiceTestDrive.java @@ -0,0 +1,50 @@ +/* + * Copyright 2017 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package com.adobe.aio.ims; + +import com.adobe.aio.ims.feign.AuthInterceptor; +import com.adobe.aio.util.WorkspaceUtil; +import com.adobe.aio.workspace.Workspace; +import feign.RequestInterceptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ImsServiceTestDrive { + + private static final Logger logger = LoggerFactory.getLogger(ImsServiceTestDrive.class); + + public static void main(String[] args) { + try { + Workspace workspace = WorkspaceUtil.getSystemWorkspaceBuilder().build(); // [1] + ImsService imsService = ImsService.builder().workspace(workspace).build(); // [2] + logger.info("accessToken: {}", imsService.getAccessToken()); // [3] + + // [1] build your `Workspace` (a Java POJO representation of your `Adobe Developer Console` Workspace) + // looking up other System Environment variables. + // Note that our fluent workspace and private Key builders offers many ways to have your workspace configured, + // we are showing here the most concise + // [2] build the Ims Service wrapper and have it use this workspace context + // [3] use this service to retrieve an OAuth access token + + // here is one way you can build the related Adobe IMS Auth Interceptor + RequestInterceptor authInterceptor = AuthInterceptor.builder() + .workspace(workspace) + .build(); + + System.exit(0); + } catch (Exception e) { + logger.error(e.getMessage(), e); + System.exit(-1); + } + } + +} diff --git a/ims/src/test/java/com/adobe/aio/ims/JwtTokenBuilderTest.java b/ims/src/test/java/com/adobe/aio/ims/JwtTokenBuilderTest.java index c750704d..4a91b29f 100644 --- a/ims/src/test/java/com/adobe/aio/ims/JwtTokenBuilderTest.java +++ b/ims/src/test/java/com/adobe/aio/ims/JwtTokenBuilderTest.java @@ -4,12 +4,14 @@ import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; +import java.util.Map; import com.adobe.aio.auth.Context; import com.adobe.aio.auth.JwtContext; +import com.adobe.aio.util.FileUtil; +import com.adobe.aio.util.WorkspaceUtil; import com.adobe.aio.workspace.Workspace; import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Header; import io.jsonwebtoken.Jwt; import io.jsonwebtoken.Jwts; import org.junit.jupiter.api.Test; @@ -40,8 +42,11 @@ void build() throws Exception{ PrivateKey privateKey = kp.getPrivate(); PublicKey publicKey = kp.getPublic(); - Workspace.Builder builder = Workspace.builder(); - Workspace workspace = builder.propertiesPath("workspace.properties").privateKey(privateKey).build(); + Map testConfigs = FileUtil.getMap("workspace.jwt.properties"); + JwtContext authContext = WorkspaceUtil.getJwtContextBuilder(testConfigs).privateKey(privateKey).build(); + Workspace.Builder builder = WorkspaceUtil.getWorkspaceBuilder(testConfigs); + Workspace workspace = builder.authContext(authContext).build(); + String actual = new JwtTokenBuilder(workspace).build(); Jwt jwt = Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(actual); diff --git a/ims/src/test/java/com/adobe/aio/ims/feign/AuthInterceptorTest.java b/ims/src/test/java/com/adobe/aio/ims/feign/AuthInterceptorTest.java index f538ae8c..9d01f1d3 100644 --- a/ims/src/test/java/com/adobe/aio/ims/feign/AuthInterceptorTest.java +++ b/ims/src/test/java/com/adobe/aio/ims/feign/AuthInterceptorTest.java @@ -1,131 +1,151 @@ package com.adobe.aio.ims.feign; -import java.lang.reflect.Field; -import java.util.Calendar; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import com.adobe.aio.auth.Context; import com.adobe.aio.ims.ImsService; import com.adobe.aio.ims.model.AccessToken; import com.adobe.aio.workspace.Workspace; import feign.RequestTemplate; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockedConstruction; -import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; -import static com.adobe.aio.util.Constants.*; -import static org.junit.jupiter.api.Assertions.*; +import java.lang.reflect.Field; +import java.util.*; + +import static com.adobe.aio.util.Constants.AUTHORIZATION_HEADER; +import static com.adobe.aio.util.Constants.BEARER_PREFIX; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class AuthInterceptorTest { - private static final String ACCESS_TOKEN = "ACCESS_TOKEN"; + private static final String ACCESS_TOKEN = "ACCESS_TOKEN"; + + @Mock + private RequestTemplate template; + + @Mock + private Workspace workspace; + + @Mock + private ImsService imsService; + + + @Test + void applyAlreadySet() throws Exception { + Calendar expires = Calendar.getInstance(); + expires.add(Calendar.HOUR, 1); + AccessToken token = new AccessToken(ACCESS_TOKEN, 3600000); + + Map> headers = new HashMap<>(); + headers.put(AUTHORIZATION_HEADER, Collections.EMPTY_LIST); - @Mock - private RequestTemplate template; + when(template.headers()).thenReturn(headers); - @Mock - private Context authContext; + try (MockedConstruction ignored = mockConstruction(ImsService.Builder.class, + (mock, mockContext) -> { + when(mock.workspace(workspace)).thenReturn(mock); + when(mock.build()).thenReturn(imsService); + } + )) { + AuthInterceptor interceptor = AuthInterceptor.builder().workspace(workspace).build(); + Field expiresField = AuthInterceptor.class.getDeclaredField("expirationTimeMillis"); + expiresField.setAccessible(true); + expiresField.set(interceptor, expires.getTimeInMillis()); - @Mock - private Workspace workspace; + Field tokenField = AuthInterceptor.class.getDeclaredField("accessToken"); + tokenField.setAccessible(true); + tokenField.set(interceptor, token); + interceptor.apply(template); + } + + } + + @Test + void apply() throws Exception { + Calendar expires = Calendar.getInstance(); + expires.add(Calendar.HOUR, 1); + AccessToken token = new AccessToken(ACCESS_TOKEN, 3600000); + + when(template.headers()).thenReturn(Collections.emptyMap()); + + try (MockedConstruction ignored = mockConstruction(ImsService.Builder.class, + (mock, mockContext) -> { + when(mock.workspace(workspace)).thenReturn(mock); + when(mock.build()).thenReturn(imsService); + } + )) { + AuthInterceptor interceptor = AuthInterceptor.builder().workspace(workspace).build(); + Field expiresField = AuthInterceptor.class.getDeclaredField("expirationTimeMillis"); + expiresField.setAccessible(true); + expiresField.set(interceptor, expires.getTimeInMillis()); + + Field tokenField = AuthInterceptor.class.getDeclaredField("accessToken"); + tokenField.setAccessible(true); + tokenField.set(interceptor, token); + interceptor.apply(template); + + verify(template).header(AUTHORIZATION_HEADER, BEARER_PREFIX + ACCESS_TOKEN); + } + } + + @Test + void getAccessTokenNotSet() { + Calendar expires = Calendar.getInstance(); + expires.add(Calendar.HOUR, 1); + AccessToken token = new AccessToken(ACCESS_TOKEN, 3600000); + + try (MockedConstruction ignored = mockConstruction(ImsService.Builder.class, + (mock, mockContext) -> { + when(mock.workspace(workspace)).thenReturn(mock); + when(mock.build()).thenReturn(imsService); + } + )) { + AuthInterceptor interceptor = AuthInterceptor.builder().workspace(workspace).build(); + doReturn(token).when(imsService).getAccessToken(); + assertEquals(ACCESS_TOKEN, interceptor.getAccessToken()); + } + } + + @Test + void getAccessTokenExpired() throws Exception { + Calendar expires = Calendar.getInstance(); + expires.add(Calendar.HOUR, 1); + AccessToken token = new AccessToken(ACCESS_TOKEN, 3600000); + + try (MockedConstruction ignored = mockConstruction(ImsService.Builder.class, + (mock, mockContext) -> { + when(mock.workspace(workspace)).thenReturn(mock); + when(mock.build()).thenReturn(imsService); + } + )) { + AuthInterceptor interceptor = AuthInterceptor.builder().workspace(workspace).build(); + + Field expiresField = AuthInterceptor.class.getDeclaredField("expirationTimeMillis"); + expiresField.setAccessible(true); + expiresField.set(interceptor, 1L); + + doReturn(token).when(imsService).getAccessToken(); + assertEquals(ACCESS_TOKEN, interceptor.getAccessToken()); + } + } - @Mock - private ImsService imsService; + @Test + void fetchAccessToken() { - @Test - void invalidContext() { - when(workspace.getAuthContext()).thenReturn(authContext); + when(imsService.getAccessToken()).thenReturn(new AccessToken(ACCESS_TOKEN, 0)); - try (MockedConstruction ignored = mockConstruction(ImsService.Builder.class, - (mock, mockContext) -> { - when(mock.workspace(workspace)).thenReturn(mock); - when(mock.build()).thenReturn(imsService); + try (MockedConstruction ignored = mockConstruction(ImsService.Builder.class, + (mock, mockContext) -> { + when(mock.workspace(workspace)).thenReturn(mock); + when(mock.build()).thenReturn(imsService); + } + )) { + AuthInterceptor interceptor = AuthInterceptor.builder().workspace(workspace).build(); + assertNotNull(interceptor.fetchAccessToken()); } - )) { - assertThrows(IllegalStateException.class, () -> AuthInterceptor.builder().workspace(workspace).build()); } - } - - @Test - void applyAlreadySet() throws Exception { - Calendar expires = Calendar.getInstance(); - expires.add(Calendar.HOUR, 1); - AccessToken token = new AccessToken(ACCESS_TOKEN, 3600000); - - Map> headers = new HashMap<>(); - headers.put(AUTHORIZATION_HEADER, Collections.EMPTY_LIST); - - when(template.headers()).thenReturn(headers); - - AuthInterceptor interceptor = mock(AuthInterceptor.class, - withSettings().useConstructor(imsService).defaultAnswer(CALLS_REAL_METHODS)); - Field expiresField = AuthInterceptor.class.getDeclaredField("expirationTimeMillis"); - expiresField.setAccessible(true); - expiresField.set(interceptor, expires.getTimeInMillis()); - - Field tokenField = AuthInterceptor.class.getDeclaredField("accessToken"); - tokenField.setAccessible(true); - tokenField.set(interceptor, token); - interceptor.apply(template); - - } - - @Test - void apply() throws Exception { - Calendar expires = Calendar.getInstance(); - expires.add(Calendar.HOUR, 1); - AccessToken token = new AccessToken(ACCESS_TOKEN, 3600000); - - when(template.headers()).thenReturn(Collections.emptyMap()); - - AuthInterceptor interceptor = mock(AuthInterceptor.class, - withSettings().useConstructor(imsService).defaultAnswer(CALLS_REAL_METHODS)); - Field expiresField = AuthInterceptor.class.getDeclaredField("expirationTimeMillis"); - expiresField.setAccessible(true); - expiresField.set(interceptor, expires.getTimeInMillis()); - - Field tokenField = AuthInterceptor.class.getDeclaredField("accessToken"); - tokenField.setAccessible(true); - tokenField.set(interceptor, token); - interceptor.apply(template); - - verify(template).header(AUTHORIZATION_HEADER, BEARER_PREFIX + ACCESS_TOKEN); - } - - @Test - void getAccessTokenNotSet() { - Calendar expires = Calendar.getInstance(); - expires.add(Calendar.HOUR, 1); - AccessToken token = new AccessToken(ACCESS_TOKEN, 3600000); - - AuthInterceptor interceptor = mock(AuthInterceptor.class, - withSettings().useConstructor(imsService).defaultAnswer(CALLS_REAL_METHODS)); - doReturn(token).when(interceptor).fetchAccessToken(); - assertEquals(ACCESS_TOKEN, interceptor.getAccessToken()); - } - - @Test - void getAccessTokenExpired() throws Exception { - Calendar expires = Calendar.getInstance(); - expires.add(Calendar.HOUR, 1); - AccessToken token = new AccessToken(ACCESS_TOKEN, 3600000); - - AuthInterceptor interceptor = mock(AuthInterceptor.class, - withSettings().useConstructor(imsService).defaultAnswer(CALLS_REAL_METHODS)); - Field expiresField = AuthInterceptor.class.getDeclaredField("expirationTimeMillis"); - expiresField.setAccessible(true); - expiresField.set(interceptor, 1L); - - doReturn(token).when(interceptor).fetchAccessToken(); - assertEquals(ACCESS_TOKEN, interceptor.getAccessToken()); - } } diff --git a/ims/src/test/java/com/adobe/aio/ims/feign/FeignImsServiceIntegrationTest.java b/ims/src/test/java/com/adobe/aio/ims/feign/FeignImsServiceIntegrationTest.java index 1c1b770b..31a712a9 100644 --- a/ims/src/test/java/com/adobe/aio/ims/feign/FeignImsServiceIntegrationTest.java +++ b/ims/src/test/java/com/adobe/aio/ims/feign/FeignImsServiceIntegrationTest.java @@ -11,6 +11,7 @@ */ package com.adobe.aio.ims.feign; +import com.adobe.aio.auth.Context; import com.adobe.aio.ims.ImsService; import com.adobe.aio.ims.model.AccessToken; import com.adobe.aio.util.WorkspaceUtil; @@ -20,6 +21,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Map; + +import static com.adobe.aio.util.WorkspaceUtil.DEFAULT_TEST_PROPERTIES; +import static com.adobe.aio.workspace.Workspace.API_KEY; import static org.junit.jupiter.api.Assertions.*; public class FeignImsServiceIntegrationTest { @@ -27,42 +32,42 @@ public class FeignImsServiceIntegrationTest { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Test - public void getAndValidateJwtExchangeAccessToken() { + public void getAccessToken() { Workspace workspace = WorkspaceUtil.getSystemWorkspaceBuilder().build(); ImsService imsService = ImsService.builder().workspace(workspace).build(); - AccessToken accessToken = imsService.getJwtExchangeAccessToken(); - logger.info("JWT Exchange token flow complete"); + AccessToken accessToken = imsService.getAccessToken(); assertNotNull(accessToken); assertNotNull(accessToken.getAccessToken()); - assertTrue(accessToken.getExpiresIn()>0); - assertTrue(imsService.validateAccessToken(accessToken.getAccessToken())); - logger.info("JWT Exchange access token validated"); - } - - @Test - public void getAndValidateJwtExchangeAccessTokenWithBadApiKey() { - Workspace workspace = WorkspaceUtil.getSystemWorkspaceBuilder().apiKey("bad_api_key").build(); - ImsService imsService = ImsService.builder().workspace(workspace).build(); - assertThrows(FeignException.BadRequest.class, imsService::getJwtExchangeAccessToken); + assertTrue(accessToken.getExpiresIn() > 0); + logger.info("retrieved an access Token"); + if (workspace.isAuthJWT()) { + assertTrue(imsService.validateJwtAccessToken(accessToken.getAccessToken())); + logger.info("JWT Exchange access token validated"); + } } @Test - public void getAndValidateJwtExchangeAccessTokenWithBadSecret() { - Workspace workspace = WorkspaceUtil.getSystemWorkspaceBuilder().clientSecret("bad_secret").build(); + public void getAccessTokenWithBadApiKey() { + Map workspaceConfig = WorkspaceUtil.getSystemWorkspaceConfig(DEFAULT_TEST_PROPERTIES); + workspaceConfig.put(API_KEY, "bad_api_key"); + Workspace workspace = WorkspaceUtil.getWorkspaceBuilder(workspaceConfig).build(); ImsService imsService = ImsService.builder().workspace(workspace).build(); - assertThrows(FeignException.BadRequest.class, imsService::getJwtExchangeAccessToken); + assertThrows(FeignException.BadRequest.class, imsService::getAccessToken); } @Test - public void getAndValidateJwtExchangeAccessTokenWithBadTechAccount() { - Workspace workspace = WorkspaceUtil.getSystemWorkspaceBuilder().technicalAccountId("bad_tech_account_id@techacct.adobe.com").build(); + public void getAccessTokenWithBadSecret() { + Map workspaceConfig = WorkspaceUtil.getSystemWorkspaceConfig(DEFAULT_TEST_PROPERTIES); + workspaceConfig.put(Context.CLIENT_SECRET, "bad_secret"); + Workspace workspace = WorkspaceUtil.getWorkspaceBuilder(workspaceConfig).build(); ImsService imsService = ImsService.builder().workspace(workspace).build(); - assertThrows(FeignException.BadRequest.class, imsService::getJwtExchangeAccessToken); + assertThrows(FeignException.BadRequest.class, imsService::getAccessToken); } @Test - public void getAndValidateJwtExchangeAccessTokenWithMissingPrivateKey() { - Workspace workspace = WorkspaceUtil.getSystemWorkspaceBuilder().privateKey(null).build(); + public void buildImsServiceWithMissingAuth() { + Workspace workspace = WorkspaceUtil.getSystemWorkspaceBuilder().authContext(null).build(); assertThrows(IllegalStateException.class, () -> ImsService.builder().workspace(workspace).build()); } + } diff --git a/ims/src/test/java/com/adobe/aio/ims/feign/FeignImsServiceTest.java b/ims/src/test/java/com/adobe/aio/ims/feign/FeignImsServiceTest.java index ea0060ae..2c150f28 100644 --- a/ims/src/test/java/com/adobe/aio/ims/feign/FeignImsServiceTest.java +++ b/ims/src/test/java/com/adobe/aio/ims/feign/FeignImsServiceTest.java @@ -38,15 +38,15 @@ public class FeignImsServiceTest { @BeforeEach void before() { - when(workspace.getImsUrl()).thenReturn(Constants.IMS_URL); + when(workspace.getImsUrl()).thenReturn(Constants.PROD_IMS_URL); } @Test - void getOauthInvalidAuthContext() { + void getInvalidAuthContext() { when(workspace.getAuthContext()).thenReturn(mock(Context.class)); ImsService service = new FeignImsService(workspace); - Exception ex = assertThrows(IllegalStateException.class, service::getOAuthAccessToken); - assertEquals("AuthContext in workspace not of type `OAuthContext`.", ex.getMessage()); + Exception ex = assertThrows(IllegalStateException.class, service::getAccessToken); + assertEquals("AuthContext in workspace not of type `OAuthContext` or `JwtContext`.", ex.getMessage()); } @Test @@ -58,6 +58,7 @@ void getOAuthAccessToken(MockServerClient client) { when(workspace.getImsUrl()).thenReturn(imsUrl); when(workspace.getAuthContext()).thenReturn(context); when(workspace.getApiKey()).thenReturn(apiKey); + when(workspace.isAuthOAuth()).thenReturn(true); when(context.getClientSecret()).thenReturn(clientSecret); Set scopes = new HashSet<>(); @@ -86,26 +87,18 @@ void getOAuthAccessToken(MockServerClient client) { .withBody("{ \"access_token\": \"ACCESS_TOKEN\", \"token_type\": \"bearer\", \"expires_in\": \"1000\" }") ); ImsService service = new FeignImsService(workspace); - AccessToken token = service.getOAuthAccessToken(); + AccessToken token = service.getAccessToken(); assertNotNull(token); assertEquals("ACCESS_TOKEN", token.getAccessToken()); } - @Test - void getJwtInvalidAuthContext() { - when(workspace.getAuthContext()).thenReturn(mock(Context.class)); - ImsService service = new FeignImsService(workspace); - Exception ex = assertThrows(IllegalStateException.class, service::getJwtExchangeAccessToken); - assertEquals("AuthContext in workspace not of type `JwtContext`.", ex.getMessage()); - } - @Test void getJwtInvalidJwtAuthContext() { Context context = JwtContext.builder().build(); when(workspace.getAuthContext()).thenReturn(context); ImsService service = new FeignImsService(workspace); - assertThrows(IllegalStateException.class, service::getJwtExchangeAccessToken); + assertThrows(IllegalStateException.class, service::getAccessToken); } @Test @@ -118,6 +111,7 @@ void getJwtExchangeAccessTokenError(MockServerClient client) { when(workspace.getImsUrl()).thenReturn(imsUrl); when(workspace.getAuthContext()).thenReturn(context); when(workspace.getApiKey()).thenReturn(apiKey); + when(workspace.isAuthJWT()).thenReturn(true); when(context.getClientSecret()).thenReturn(clientSecret); client.when( @@ -137,7 +131,7 @@ void getJwtExchangeAccessTokenError(MockServerClient client) { } )) { ImsService service = new FeignImsService(workspace); - assertThrows(FeignException.class, service::getJwtExchangeAccessToken); + assertThrows(FeignException.class, service::getAccessToken); } verify(context).validate(); } @@ -152,6 +146,7 @@ void getJwtExchangeAccessTokenSuccess(MockServerClient client) { when(workspace.getImsUrl()).thenReturn(imsUrl); when(workspace.getAuthContext()).thenReturn(context); when(workspace.getApiKey()).thenReturn(apiKey); + when(workspace.isAuthJWT()).thenReturn(true); when(context.getClientSecret()).thenReturn(clientSecret); client.when( @@ -174,24 +169,15 @@ void getJwtExchangeAccessTokenSuccess(MockServerClient client) { } )) { ImsService service = new FeignImsService(workspace); - AccessToken token = service.getJwtExchangeAccessToken(); + AccessToken token = service.getAccessToken(); assertNotNull(token); assertEquals("ACCESS_TOKEN", token.getAccessToken()); } verify(context).validate(); } - - - @Test - void validateInvalidJwtAuthContext() { - when(workspace.getAuthContext()).thenReturn(mock(Context.class)); - ImsService service = new FeignImsService(workspace); - assertThrows(IllegalStateException.class, service::getJwtExchangeAccessToken); - } - @Test - void validateAccessToken(MockServerClient client) { + void validateJwtAccessToken(MockServerClient client) { final String imsUrl = "http://localhost:" + client.getPort(); final String apiKey = "API_KEY"; final String accessToken = "ACCESS_TOKEN"; @@ -211,7 +197,8 @@ void validateAccessToken(MockServerClient client) { when(workspace.getAuthContext()).thenReturn(context); when(workspace.getImsUrl()).thenReturn(imsUrl); when(workspace.getApiKey()).thenReturn(apiKey); + when(workspace.isAuthJWT()).thenReturn(true); ImsService service = new FeignImsService(workspace); - assertTrue(service.validateAccessToken(accessToken)); + assertTrue(service.validateJwtAccessToken(accessToken)); } } diff --git a/ims/src/test/java/com/adobe/aio/ims/feign/FeignImsServiceTestDrive.java b/ims/src/test/java/com/adobe/aio/ims/feign/FeignImsServiceTestDrive.java deleted file mode 100644 index 4dcead17..00000000 --- a/ims/src/test/java/com/adobe/aio/ims/feign/FeignImsServiceTestDrive.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2017 Adobe. All rights reserved. - * This file is licensed to you under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. You may obtain a copy - * of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under - * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - * OF ANY KIND, either express or implied. See the License for the specific language - * governing permissions and limitations under the License. - */ -package com.adobe.aio.ims.feign; - -import com.adobe.aio.ims.ImsService; -import com.adobe.aio.util.WorkspaceUtil; -import com.adobe.aio.workspace.Workspace; -import com.adobe.aio.ims.model.AccessToken; -import com.adobe.aio.ims.util.PrivateKeyBuilder; -import feign.RequestInterceptor; -import java.security.PrivateKey; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class FeignImsServiceTestDrive { - - private static final Logger logger = LoggerFactory.getLogger(FeignImsServiceTestDrive.class); - - public static void runTheReadmeFile() { - - PrivateKey privateKey = new PrivateKeyBuilder().systemEnv().build(); // [1] - Workspace workspace = Workspace.builder() - .systemEnv() - .privateKey(privateKey) - .build(); // [2] - ImsService imsService = ImsService.builder().workspace(workspace).build(); // [3] - - AccessToken accessToken = imsService.getJwtExchangeAccessToken(); // [4] - - // [1] Build your PrivateKey looking up the key indicated by you System Environment variables - // [2] build your `Workspace` (a Java POJO representation of your `Adobe Developer Console` Workspace) - // looking up other System Environment variables. - // Note that our fluent workspace and private Key builders offers many ways to have your workspace configured, - // we are showing here the most concise - // [3] build the Ims Service wrapper and have it use this workspace context - // [4] use this service to retrieve an access token using a jwt exchange token flow - - // here is one way you can build the related IMS Feign JWT Auth Interceptor - RequestInterceptor authInterceptor = JWTAuthInterceptor.builder() - .workspace(workspace) - .build(); - } - - /** - * use your own property file filePath or classpath. WARNING: don't push back to github as it - * contains many secrets. We do provide a sample/template workspace.properties file in the - * ./src/test/resources folder - */ - private static final String DEFAULT_TEST_PROPERTIES = "workspace.secret.properties"; - - public static void main(String[] args) { - try { - Workspace workspace = WorkspaceUtil.getSystemWorkspaceBuilder().build(); - ImsService imsService = ImsService.builder().workspace(workspace).build(); - - AccessToken accessToken = imsService.getJwtExchangeAccessToken(); - logger.info("accessToken: {}", accessToken); - logger.info("accessToken validated: {}:",imsService.validateAccessToken(accessToken.getAccessToken())); - System.exit(0); - } catch (Exception e) { - logger.error(e.getMessage(), e); - System.exit(-1); - } - } - -} diff --git a/ims/src/test/java/com/adobe/aio/ims/feign/JwtAuthInterceptorTest.java b/ims/src/test/java/com/adobe/aio/ims/feign/JwtAuthInterceptorTest.java deleted file mode 100644 index b99577e2..00000000 --- a/ims/src/test/java/com/adobe/aio/ims/feign/JwtAuthInterceptorTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.adobe.aio.ims.feign; - -import java.lang.reflect.Field; -import java.util.Calendar; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -import com.adobe.aio.auth.JwtContext; -import com.adobe.aio.ims.ImsService; -import com.adobe.aio.ims.model.AccessToken; -import com.adobe.aio.workspace.Workspace; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.MockedConstruction; -import org.mockito.junit.jupiter.MockitoExtension; -import shaded_package.org.checkerframework.checker.units.qual.A; - -import static com.adobe.aio.util.Constants.*; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -public class JwtAuthInterceptorTest { - private static final String ACCESS_TOKEN = "ACCESS_TOKEN"; - - @Mock - private Workspace workspace; - - @Mock - private JwtContext authContext; - - @Mock - private ImsService imsService; - - @Test - void isUp() { - when(workspace.getAuthContext()).thenReturn(authContext); - when(imsService.validateAccessToken(ACCESS_TOKEN)).thenReturn(true); - - try (MockedConstruction ignored = mockConstruction(ImsService.Builder.class, - (mock, mockContext) -> { - when(mock.workspace(workspace)).thenReturn(mock); - when(mock.build()).thenReturn(imsService); - } - )) { - JWTAuthInterceptor spy = spy((JWTAuthInterceptor) AuthInterceptor.builder().workspace(workspace).build()); - doReturn(ACCESS_TOKEN).when(spy).getAccessToken(); - assertTrue(spy.isUp()); - } - } - - @Test - void fetchAccessToken() { - when(workspace.getAuthContext()).thenReturn(authContext); - when(imsService.getJwtExchangeAccessToken()).thenReturn(new AccessToken(ACCESS_TOKEN, 0)); - - try (MockedConstruction ignored = mockConstruction(ImsService.Builder.class, - (mock, mockContext) -> { - when(mock.workspace(workspace)).thenReturn(mock); - when(mock.build()).thenReturn(imsService); - } - )) { - JWTAuthInterceptor interceptor = (JWTAuthInterceptor) AuthInterceptor.builder().workspace(workspace).build(); - assertNotNull(interceptor.fetchAccessToken()); - } - } -} diff --git a/ims/src/test/java/com/adobe/aio/ims/feign/OAuthAuthInterceptorTest.java b/ims/src/test/java/com/adobe/aio/ims/feign/OAuthAuthInterceptorTest.java deleted file mode 100644 index de93714d..00000000 --- a/ims/src/test/java/com/adobe/aio/ims/feign/OAuthAuthInterceptorTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.adobe.aio.ims.feign; - -import com.adobe.aio.auth.JwtContext; -import com.adobe.aio.auth.OAuthContext; -import com.adobe.aio.ims.ImsService; -import com.adobe.aio.ims.model.AccessToken; -import com.adobe.aio.workspace.Workspace; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.MockedConstruction; -import org.mockito.junit.jupiter.MockitoExtension; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -public class OAuthAuthInterceptorTest { - private static final String ACCESS_TOKEN = "ACCESS_TOKEN"; - - @Mock - private Workspace workspace; - - @Mock - private OAuthContext authContext; - - @Mock - private ImsService imsService; - - @Test - void fetchAccessToken() { - when(workspace.getAuthContext()).thenReturn(authContext); - when(imsService.getOAuthAccessToken()).thenReturn(new AccessToken(ACCESS_TOKEN, 0)); - - try (MockedConstruction ignored = mockConstruction(ImsService.Builder.class, - (mock, mockContext) -> { - when(mock.workspace(workspace)).thenReturn(mock); - when(mock.build()).thenReturn(imsService); - } - )) { - OAuthInterceptor interceptor = (OAuthInterceptor) AuthInterceptor.builder().workspace(workspace).build(); - assertNotNull(interceptor.fetchAccessToken()); - } - } -} diff --git a/ims/src/test/java/com/adobe/aio/util/WorkspaceUtilTest.java b/ims/src/test/java/com/adobe/aio/util/WorkspaceUtilTest.java new file mode 100644 index 00000000..08fa6e46 --- /dev/null +++ b/ims/src/test/java/com/adobe/aio/util/WorkspaceUtilTest.java @@ -0,0 +1,79 @@ +/* + * Copyright 2017 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package com.adobe.aio.util; + +import com.adobe.aio.auth.JwtContext; +import com.adobe.aio.auth.OAuthContext; +import com.adobe.aio.workspace.Workspace; +import org.junit.jupiter.api.Test; + +import static com.adobe.aio.auth.Context.CLIENT_SECRET; +import static com.adobe.aio.auth.JwtContext.*; +import static com.adobe.aio.auth.OAuthContext.SCOPES; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class WorkspaceUtilTest { + + private static final String TEST_JWT_WORKSPACE_PROPERTIES = "workspace.jwt.properties"; + private static final String TEST_OAUTH_WORKSPACE_PROPERTIES = "workspace.oauth.properties"; + private static final String TEST_VALUE = "_changeMe"; + + @Test + public void getWorkspaceBuilderFromJwtProperties() { + Workspace workspaceFromProperties = WorkspaceUtil.getWorkspaceBuilder(FileUtil.getMap(TEST_JWT_WORKSPACE_PROPERTIES)).build(); + JwtContext expectedAuthContext = JwtContext.builder() + .clientSecret(CLIENT_SECRET + TEST_VALUE) + .technicalAccountId(TECHNICAL_ACCOUNT_ID + TEST_VALUE) + .addMetascope(META_SCOPES + TEST_VALUE) + .build(); + Workspace expected = Workspace.builder() + .imsUrl(Constants.PROD_IMS_URL) + .imsOrgId(Workspace.IMS_ORG_ID + TEST_VALUE) + .apiKey(Workspace.API_KEY + TEST_VALUE) + .consumerOrgId(Workspace.CONSUMER_ORG_ID + TEST_VALUE) + .projectId(Workspace.PROJECT_ID + TEST_VALUE) + .workspaceId(Workspace.WORKSPACE_ID + TEST_VALUE) + .credentialId(Workspace.CREDENTIAL_ID + TEST_VALUE) + .authContext(expectedAuthContext) + .build(); + + assertEquals(expected, workspaceFromProperties); + assertEquals(expected.hashCode(), workspaceFromProperties.hashCode()); + assertEquals(expected.toString(), workspaceFromProperties.toString()); + assertEquals(expectedAuthContext, workspaceFromProperties.getAuthContext()); + assertTrue(workspaceFromProperties.isAuthJWT()); + workspaceFromProperties.validateWorkspaceContext(); + } + + @Test + public void getWorkspaceBuilderFromOAuthProperties() { + Workspace workspaceFromProperties = WorkspaceUtil.getWorkspaceBuilder(FileUtil.getMap(TEST_OAUTH_WORKSPACE_PROPERTIES)).build(); + OAuthContext expectedAuthContext = new OAuthContext.Builder().clientSecret(CLIENT_SECRET + TEST_VALUE).addScope(SCOPES + TEST_VALUE).build(); + Workspace expected = Workspace.builder() + .imsUrl(Constants.PROD_IMS_URL) + .imsOrgId(Workspace.IMS_ORG_ID + TEST_VALUE) + .apiKey(Workspace.API_KEY + TEST_VALUE) + .consumerOrgId(Workspace.CONSUMER_ORG_ID + TEST_VALUE) + .projectId(Workspace.PROJECT_ID + TEST_VALUE) + .workspaceId(Workspace.WORKSPACE_ID + TEST_VALUE) + .authContext(expectedAuthContext) + .build(); + assertEquals(expected, workspaceFromProperties); + assertEquals(expected.hashCode(), workspaceFromProperties.hashCode()); + assertEquals(expected.toString(), workspaceFromProperties.toString()); + assertEquals(expectedAuthContext, workspaceFromProperties.getAuthContext()); + assertTrue(workspaceFromProperties.isAuthOAuth()); + workspaceFromProperties.validateAll(); + } + +} diff --git a/ims/src/test/resources/workspace.properties b/ims/src/test/resources/workspace.jwt.properties similarity index 89% rename from ims/src/test/resources/workspace.properties rename to ims/src/test/resources/workspace.jwt.properties index 23b3eb9f..8ecdf9c0 100644 --- a/ims/src/test/resources/workspace.properties +++ b/ims/src/test/resources/workspace.jwt.properties @@ -18,7 +18,7 @@ aio_consumer_org_id=aio_consumer_org_id_changeMe aio_ims_org_id=aio_ims_org_id_changeMe # aio_workspace_id = your Adobe Developer Console workspace Id (project.workspace.id) aio_workspace_id=aio_workspace_id_changeMe -# aio_credential_id = your Adobe Developer Console jwt credential id (project.workspace.details.credentials[i].id) +# aio_credential_id = your Adobe Developer Console credential id (project.workspace.details.credentials[i].id) aio_credential_id=aio_credential_id_changeMe # aio_client_secret = your Adobe Developer Console jwt credential client secret (project.workspace.details.credentials[i].jwt.client_secret) aio_client_secret=aio_client_secret_changeMe @@ -29,5 +29,6 @@ aio_api_key=aio_api_key_changeMe aio_meta_scopes=aio_meta_scopes_changeMe # aio_technical_account_id = your Adobe Developer Console jwt credential technical account id (project.workspace.details.credentials[i].jwt.technical_account_id) aio_technical_account_id=aio_technical_account_id_changeMe +# aio_encoded_pkcs8 = your privateKey (associated with the public key set in your Adobe Developer Console workspace) in a base64 encoded pkcs8 format aio_encoded_pkcs8=changeMe diff --git a/ims/src/test/resources/workspace.oauth.properties b/ims/src/test/resources/workspace.oauth.properties new file mode 100644 index 00000000..591df34d --- /dev/null +++ b/ims/src/test/resources/workspace.oauth.properties @@ -0,0 +1,28 @@ +# +# Copyright 2017 Adobe. All rights reserved. +# This file is licensed to you under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. You may obtain a copy +# of the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under +# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +# OF ANY KIND, either express or implied. See the License for the specific language +# governing permissions and limitations under the License. +# + +# aio_project_id = your Adobe Developer Console project id (project.id) +aio_project_id=aio_project_id_changeMe +# aio_consumer_org_id = your Adobe Developer Console consumer orgnaization id (project.org.id) +aio_consumer_org_id=aio_consumer_org_id_changeMe +# aio_ims_org_id = your Adobe Developer Console IMS Organization ID (project.org.ims_org_id) +aio_ims_org_id=aio_ims_org_id_changeMe +# aio_workspace_id = your Adobe Developer Console workspace Id (project.workspace.id) +aio_workspace_id=aio_workspace_id_changeMe +# aio_client_secret = your Adobe Developer Console jwt credential client secret (project.workspace.details.credentials[i].oauth_server_to_server.client_secrets[0]) +aio_client_secret=aio_client_secret_changeMe +# aio_api_key = your Adobe Developer Console credential API Key (or Client ID) (project.workspace.details.credentials[i].oauth_server_to_server.client_id) +aio_api_key=aio_api_key_changeMe +# aio_oauth_scopes : comma separated list of oauth associated with your API, see your Adobe Developer Console oauth scopes (project.workspace.details.credentials[i].oauth_server_to_server.scopes) +# sample aio_oauth_scopes: aio_oauth_scopes= AdobeID,openid,read_organizations,additional_info.projectedProductContext,additional_info.roles,adobeio_api,read_client_secret,manage_client_secrets +aio_oauth_scopes=aio_oauth_scopes_changeMe + diff --git a/pom.xml b/pom.xml index 5707a8e6..2decc576 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ com.adobe.aio aio-lib-java - 1.1.29-SNAPSHOT + 2.0.0-SNAPSHOT pom