diff --git a/.github/workflows/publish-e2e-test-suites-docker-image.yaml b/.github/workflows/publish-e2e-test-suites-docker-image.yaml index 76d0d0d..782e6c9 100644 --- a/.github/workflows/publish-e2e-test-suites-docker-image.yaml +++ b/.github/workflows/publish-e2e-test-suites-docker-image.yaml @@ -1,11 +1,11 @@ -name: Publish E2E Test Suites -run-name: ${{ format('Publish E2E Test Suites{0} {1} Release', ':', inputs.release_type) }} +name: Release UID2 E2E Image +run-name: ${{ inputs.release_type == 'Snapshot' && 'Publish Pre-release' || format('Release {0}', inputs.release_type)}} Docker Image by @${{ github.actor }} on: workflow_dispatch: inputs: release_type: type: choice - description: 'The type of release' + description: The type of release options: - Snapshot - Patch @@ -15,6 +15,13 @@ on: description: If set, the version number will not be incremented and the given number will be used. type: string default: '' + vulnerability_severity: + description: The severity to fail the workflow if such vulnerability is detected. DO NOT override it unless a Jira ticket is raised. + type: choice + options: + - CRITICAL,HIGH + - CRITICAL,HIGH,MEDIUM + - CRITICAL (DO NOT use if JIRA ticket not raised) jobs: Image: @@ -22,7 +29,7 @@ jobs: with: release_type: ${{ inputs.release_type }} version_number_input: ${{ inputs.version_number_input }} - java_version: '21' - force_release: 'yes' + vulnerability_severity: ${{ inputs.vulnerability_severity }} + java_version: 21 skip_tests: true secrets: inherit diff --git a/Dockerfile b/Dockerfile index 4df4d39..7f0e319 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,42 +11,35 @@ COPY ./src ./src ###################### # Configure env vars # ###################### -ENV UID2_E2E_ENV "github-test-pipeline" - -ENV UID2_E2E_SITE_ID "" -ENV UID2_E2E_API_KEY "" -ENV UID2_E2E_API_SECRET "" -ENV UID2_E2E_API_KEY_OLD "" -ENV UID2_E2E_API_SECRET_OLD "" -ENV UID2_E2E_API_KEY_SHARING_RECIPIENT "" -ENV UID2_E2E_API_SECRET_SHARING_RECIPIENT "" -ENV UID2_E2E_API_KEY_NON_SHARING_RECIPIENT "" -ENV UID2_E2E_API_SECRET_NON_SHARING_RECIPIENT "" -ENV UID2_E2E_SUBSCRIPTION_ID "" -ENV UID2_E2E_SERVER_PUBLIC_KEY "" -ENV UID2_E2E_ORIGIN "" -ENV UID2_E2E_INVALID_ORIGIN "" - -ENV UID2_E2E_IDENTITY_SCOPE "" -ENV UID2_E2E_PHONE_SUPPORT "" - -ENV UID2_E2E_PIPELINE_OPERATOR_URL "" -ENV UID2_E2E_PIPELINE_OPERATOR_TYPE "" -ENV UID2_E2E_PIPELINE_OPERATOR_CLOUD_PROVIDER "" - -ENV UID2_E2E_CORE_API_TOKEN "" -ENV UID2_E2E_OPTOUT_TO_CALL_CORE_API_TOKEN "" -ENV UID2_E2E_CORE_URL "" -ENV UID2_E2E_OPTOUT_URL "" - -CMD \ - if [ "$UID2_E2E_PIPELINE_OPERATOR_TYPE" != "PUBLIC" ] && [ "$UID2_E2E_PIPELINE_OPERATOR_TYPE" != "PRIVATE" ] ; \ - then \ - echo "ERROR: Incorrect operator type: $UID2_E2E_PIPELINE_OPERATOR_TYPE. Exiting." ; \ - exit 1 ; \ - elif [ "$UID2_E2E_PIPELINE_OPERATOR_TYPE" = "PUBLIC" ] ; \ - then \ - mvn test -Dtest="E2EPublicOperatorTestSuite" ; \ - else \ - mvn test -Dtest="E2EPrivateOperatorTestSuite" ; \ - fi \ No newline at end of file +ENV E2E_SUITES "" +ENV E2E_ARGS_JSON "" + +ENV E2E_ENV "" +ENV E2E_IDENTITY_SCOPE "" +ENV E2E_PHONE_SUPPORT "" + +ENV UID2_CORE_E2E_OPERATOR_API_KEY "" +ENV UID2_CORE_E2E_OPTOUT_API_KEY "" +ENV UID2_CORE_E2E_CORE_URL "" +ENV UID2_CORE_E2E_OPTOUT_URL "" + +ENV UID2_OPERATOR_E2E_CLIENT_SITE_ID "" +ENV UID2_OPERATOR_E2E_CLIENT_API_KEY "" +ENV UID2_OPERATOR_E2E_CLIENT_API_SECRET "" +ENV UID2_OPERATOR_E2E_CLIENT_API_KEY_BEFORE_OPTOUT_CUTOFF "" +ENV UID2_OPERATOR_E2E_CLIENT_API_SECRET_BEFORE_OPTOUT_CUTOFF "" +ENV UID2_OPERATOR_E2E_CLIENT_API_KEY_SHARING_RECIPIENT "" +ENV UID2_OPERATOR_E2E_CLIENT_API_SECRET_SHARING_RECIPIENT "" +ENV UID2_OPERATOR_E2E_CLIENT_API_KEY_NON_SHARING_RECIPIENT "" +ENV UID2_OPERATOR_E2E_CLIENT_API_SECRET_NON_SHARING_RECIPIENT "" +ENV UID2_OPERATOR_E2E_CSTG_SUBSCRIPTION_ID "" +ENV UID2_OPERATOR_E2E_CSTG_SERVER_PUBLIC_KEY "" +ENV UID2_OPERATOR_E2E_CSTG_ORIGIN "" +ENV UID2_OPERATOR_E2E_CSTG_INVALID_ORIGIN "" + +ENV UID2_PIPELINE_E2E_CORE_URL "" +ENV UID2_PIPELINE_E2E_OPERATOR_URL "" +ENV UID2_PIPELINE_E2E_OPERATOR_TYPE "" +ENV UID2_PIPELINE_E2E_OPERATOR_CLOUD_PROVIDER "" + +CMD mvn test -Dtest="${E2E_SUITES}" diff --git a/README.md b/README.md index fa1d372..065e8f5 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,68 @@ Any changes to [uid2-operator](https://github.com/IABTechLab/uid2-operator) need There are different test suites that can be run depending on the environment and operator type: -| Test Suite | Description | -|---------------------------------------|--------------------------------------------------------------| -| `E2ELocalFullTestSuite` | Used when running both public and private operators locally. | -| `E2EPipelinePrivateOperatorTestSuite` | Used when testing private operators in a pipeline. | -| `E2EPipelinePublicOperatorTestSuite` | Used when testing public operators in a pipeline. | -| `E2EPrivateOperatorTestSuite` | Used when testing real private operators. | -| `E2EPublicOperatorTestSuite` | Used when testing real public operators. | +| Test Suite | Description | +|-------------------------------|-------------------------------------| +| `E2ELocalFullTestSuite` | Used to test all apps locally | +| `E2ECoreTestSuite` | Used when testing Core | +| `E2EPublicOperatorTestSuite` | Used when testing public Operators | +| `E2EPrivateOperatorTestSuite` | Used when testing private Operators | + +## Environment Variables + +* `E2E_SUITES` - **Docker image only** - The test suites to run, comma separated + * e.g. `E2EPrivateOperatorTestSuite,E2ECoreTestSuite` +* `E2E_ARGS_JSON` - The below environment variables can be put into this environment variable as a JSON + * Any environment variables declared explicitly will override args in `E2E_ARGS_JSON` + +### General + +| Name | Value | +|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------| +| `E2E_ENV` | The E2E environment - this determines which apps get instantiated
Certain tests run for `local` environments only
Check `AppsMap` for details | +| `E2E_IDENTITY_SCOPE` | The identity scope - one of [UID2, EUID] | +| `E2E_PHONE_SUPPORT` | True if APIs support phone numbers, false otherwise | + +### Core + +| Name | Value | +|----------------------------------|------------------------------------------------------| +| `UID2_CORE_E2E_OPERATOR_API_KEY` | The API key for an Operator to communicate with Core | +| `UID2_CORE_E2E_OPTOUT_API_KEY` | The API key for Optout to communicate with Core | +| `UID2_CORE_E2E_CORE_URL` | The Core URL to include in attestation requests | +| `UID2_CORE_E2E_OPTOUT_URL` | The Optout URL to include in attestation requests | + +### Operator + +| Name | Value | +|-------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------| +| `UID2_OPERATOR_E2E_CLIENT_SITE_ID` | The site ID of the client communicating with Operator | +| `UID2_OPERATOR_E2E_CLIENT_API_KEY` | The API key for a client to communicate with Operator | +| `UID2_OPERATOR_E2E_CLIENT_API_SECRET` | The API secret for a client to communicate with Operator | +| `UID2_OPERATOR_E2E_CLIENT_API_KEY_BEFORE_OPTOUT_CUTOFF` | **Optout cutoff tests** - The API key before the optout policy cutoff for a client to communicate with Operator | +| `UID2_OPERATOR_E2E_CLIENT_API_SECRET_BEFORE_OPTOUT_CUTOFF` | **Optout cutoff tests** - The API secret before the optout policy cutoff for a client to communicate with Operator | +| `UID2_OPERATOR_E2E_CLIENT_API_KEY_SHARING_RECIPIENT` | **Sharing tests** - The API key with SHARER role for a client to communicate with Operator | +| `UID2_OPERATOR_E2E_CLIENT_API_SECRET_SHARING_RECIPIENT` | **Sharing tests** - The API with SHARER role secret for a client to communicate with Operator | +| `UID2_OPERATOR_E2E_CLIENT_API_KEY_NON_SHARING_RECIPIENT` | **Sharing tests** - The API key without SHARER role for a client to communicate with Operator | +| `UID2_OPERATOR_E2E_CLIENT_API_SECRET_NON_SHARING_RECIPIENT` | **Sharing tests** - The API without SHARER role secret for a client to communicate with Operator | +| `UID2_OPERATOR_E2E_CSTG_SUBSCRIPTION_ID` | **CSTG tests** - The subscription ID | +| `UID2_OPERATOR_E2E_CSTG_SERVER_PUBLIC_KEY` | **CSTG tests** - The server public key | +| `UID2_OPERATOR_E2E_CSTG_ORIGIN` | **CSTG tests** - A valid origin | +| `UID2_OPERATOR_E2E_CSTG_INVALID_ORIGIN` | **CSTG tests** - An invalid origin | + +### Pipeline + +| Name | Value | +|---------------------------------------------|---------------------------------------------------------------------------------------------------------------| +| `UID2_PIPELINE_E2E_CORE_URL` | The Core URL | +| `UID2_PIPELINE_E2E_OPERATOR_URL` | The Operator URL | +| `UID2_PIPELINE_E2E_OPERATOR_TYPE` | The type of Operator - one of [PUBLIC, PRIVATE] | +| `UID2_PIPELINE_E2E_OPERATOR_CLOUD_PROVIDER` | Empty for public Operators, the cloud provider for private Operators - one of [aws-nitro, gcp-oidc, azure-cc] | ## Running the Dockerfile -`docker build -f Dockerfile -t uid2-e2e . && docker run --env = ... uid2-e2e` -* Set each environment variable specified in the Dockerfile as `--env =` +```shell +docker build -f Dockerfile -t uid2-e2e . +docker run --env = ... uid2-e2e + ``` * If running the E2E tests against localhost, include the option `--network=host` diff --git a/src/test/java/app/AppsMap.java b/src/test/java/app/AppsMap.java index 58571ae..30c6306 100644 --- a/src/test/java/app/AppsMap.java +++ b/src/test/java/app/AppsMap.java @@ -1,6 +1,5 @@ package app; -import common.EnvUtil; import app.component.App; import java.lang.reflect.InvocationTargetException; @@ -10,8 +9,6 @@ public final class AppsMap { private static final Map APP_MAP; - private static final String ENV = EnvUtil.getEnv("UID2_E2E_ENV"); - private static final Apps APPS; static { @@ -24,10 +21,11 @@ public final class AppsMap { "uid2-prod", "app.Uid2ProdApps", "euid-integ", "app.EuidIntegApps", "euid-prod", "app.EuidProdApps", + "github-test-pipeline-local", "app.GitHubTestPipelineApps", "github-test-pipeline", "app.GitHubTestPipelineApps" ); - APPS = (Apps) Class.forName(APP_MAP.get(ENV)).getDeclaredConstructor().newInstance(); + APPS = (Apps) Class.forName(APP_MAP.get(App.ENV)).getDeclaredConstructor().newInstance(); } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } diff --git a/src/test/java/app/GitHubTestPipelineApps.java b/src/test/java/app/GitHubTestPipelineApps.java index 816278b..46df6d4 100644 --- a/src/test/java/app/GitHubTestPipelineApps.java +++ b/src/test/java/app/GitHubTestPipelineApps.java @@ -1,5 +1,7 @@ package app; +import common.Const; +import common.EnabledCondition; import common.EnvUtil; import app.component.Core; import app.component.Operator; @@ -7,19 +9,20 @@ import java.util.Set; public class GitHubTestPipelineApps extends Apps { - private static final String OPERATOR_URL = EnvUtil.getEnv("UID2_E2E_PIPELINE_OPERATOR_URL"); - private static final Operator.Type OPERATOR_TYPE = Operator.Type.valueOf(EnvUtil.getEnv("UID2_E2E_PIPELINE_OPERATOR_TYPE")); - private static final Operator.CloudProvider OPERATOR_CLOUD_PROVIDER = Operator.CloudProvider.valueOf(EnvUtil.getEnv("UID2_E2E_PIPELINE_OPERATOR_CLOUD_PROVIDER")); + private static final String CORE_URL = EnvUtil.getEnv(Const.Config.Pipeline.CORE_URL, EnabledCondition.isLocal()); - private static final String CORE_URL = EnvUtil.getEnv("UID2_E2E_CORE_URL"); + private static final String OPERATOR_URL = EnvUtil.getEnv(Const.Config.Pipeline.OPERATOR_URL); + private static final Operator.Type OPERATOR_TYPE = Operator.Type.valueOf(EnvUtil.getEnv(Const.Config.Pipeline.OPERATOR_TYPE)); + private static final Operator.CloudProvider OPERATOR_CLOUD_PROVIDER = Operator.CloudProvider.valueOf(EnvUtil.getEnv(Const.Config.Pipeline.OPERATOR_CLOUD_PROVIDER)); private static final String OPERATOR_NAME = OPERATOR_TYPE == Operator.Type.PUBLIC ? "GitHub Test Pipeline - Public Operator" : "GitHub Test Pipeline - Private %s Operator".formatted(OPERATOR_CLOUD_PROVIDER.toString()); public GitHubTestPipelineApps() { - super(Set.of( - new Operator(OPERATOR_URL, OPERATOR_NAME, OPERATOR_TYPE), - new Core(CORE_URL, "GitHub Test Pipeline - Core") - )); + super(EnabledCondition.isLocal() ? + Set.of( + new Operator(OPERATOR_URL, OPERATOR_NAME, OPERATOR_TYPE), + new Core(CORE_URL, "GitHub Test Pipeline - Core")) : + Set.of(new Operator(OPERATOR_URL, OPERATOR_NAME, OPERATOR_TYPE))); } } diff --git a/src/test/java/app/LocalApps.java b/src/test/java/app/LocalApps.java index 9978fc7..1062daf 100644 --- a/src/test/java/app/LocalApps.java +++ b/src/test/java/app/LocalApps.java @@ -5,9 +5,9 @@ import java.util.Set; public final class LocalApps extends Apps { + // TODO: Add optout public LocalApps() { super(Set.of( - // TODO: Add optout, monitorstack new Localstack("http://localhost", 5001, "Local - Localstack"), new Admin("http://localhost", 8089, "Local - Admin"), new Core("http://localhost", 8088, "Local - Core"), diff --git a/src/test/java/app/component/App.java b/src/test/java/app/component/App.java index b8688dd..3f552d3 100644 --- a/src/test/java/app/component/App.java +++ b/src/test/java/app/component/App.java @@ -1,8 +1,17 @@ package app.component; +import com.uid2.client.IdentityScope; +import common.Const; +import common.EnvUtil; import common.HttpClient; +import lombok.Getter; +@Getter public abstract class App { + public static final String ENV = EnvUtil.getEnv(Const.Config.ENV); + public static final IdentityScope IDENTITY_SCOPE = IdentityScope.valueOf(EnvUtil.getEnv(Const.Config.IDENTITY_SCOPE)); + public static final boolean PHONE_SUPPORT = Boolean.parseBoolean(EnvUtil.getEnv(Const.Config.PHONE_SUPPORT)); + private final String host; private final Integer port; private final String name; @@ -13,18 +22,6 @@ public App(String host, Integer port, String name) { this.name = name; } - public String getHost() { - return host; - } - - public Integer getPort() { - return port; - } - - public String getName() { - return name; - } - public String getBaseUrl() { return getPort() == null ? getHost() : "%s:%d".formatted(getHost(), getPort()); } diff --git a/src/test/java/app/component/Core.java b/src/test/java/app/component/Core.java index 1eff522..eb2299b 100644 --- a/src/test/java/app/component/Core.java +++ b/src/test/java/app/component/Core.java @@ -1,5 +1,6 @@ package app.component; +import common.Const; import common.EnvUtil; import common.HttpClient; import common.Mapper; @@ -9,10 +10,10 @@ import java.util.Map; public class Core extends App { - private static final String CORE_API_TOKEN = EnvUtil.getEnv("UID2_E2E_CORE_API_TOKEN"); - private static final String OPTOUT_TO_CALL_CORE_API_TOKEN = EnvUtil.getEnv("UID2_E2E_OPTOUT_TO_CALL_CORE_API_TOKEN"); - public static final String CORE_URL = EnvUtil.getEnv("UID2_E2E_CORE_URL"); - public static final String OPTOUT_URL = EnvUtil.getEnv("UID2_E2E_OPTOUT_URL"); + private static final String OPERATOR_API_KEY = EnvUtil.getEnv(Const.Config.Core.OPERATOR_API_KEY); + private static final String OPTOUT_API_KEY = EnvUtil.getEnv(Const.Config.Core.OPTOUT_API_KEY); + public static final String CORE_URL = EnvUtil.getEnv(Const.Config.Core.CORE_URL); + public static final String OPTOUT_URL = EnvUtil.getEnv(Const.Config.Core.OPTOUT_URL); public Core(String host, Integer port, String name) { super(host, port, name); @@ -23,7 +24,7 @@ public Core(String host, String name) { } public JsonNode attest(String attestationRequest) throws Exception { - String response = HttpClient.post(getBaseUrl() + "/attest", attestationRequest, CORE_API_TOKEN); + String response = HttpClient.post(getBaseUrl() + "/attest", attestationRequest, OPERATOR_API_KEY); return Mapper.OBJECT_MAPPER.readTree(response); } @@ -35,12 +36,12 @@ public JsonNode getWithCoreApiToken(String path, boolean encrypted) throws Excep Map headers = new HashMap<>(); if (encrypted) headers.put("Encrypted", "true"); - String response = HttpClient.get(getBaseUrl() + path, CORE_API_TOKEN, headers); + String response = HttpClient.get(getBaseUrl() + path, OPERATOR_API_KEY, headers); return Mapper.OBJECT_MAPPER.readTree(response); } public JsonNode getWithOptOutApiToken(String path) throws Exception { - String response = HttpClient.get(getBaseUrl() + path, OPTOUT_TO_CALL_CORE_API_TOKEN); + String response = HttpClient.get(getBaseUrl() + path, OPTOUT_API_KEY); return Mapper.OBJECT_MAPPER.readTree(response); } } diff --git a/src/test/java/app/component/Operator.java b/src/test/java/app/component/Operator.java index d7aa520..0050949 100644 --- a/src/test/java/app/component/Operator.java +++ b/src/test/java/app/component/Operator.java @@ -1,13 +1,12 @@ package app.component; -import common.EnvUtil; -import common.HttpClient; -import common.Mapper; import com.fasterxml.jackson.databind.JsonNode; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.uid2.client.IdentityScope; import com.uid2.client.*; +import common.*; +import lombok.Getter; import okhttp3.Request; import okhttp3.RequestBody; @@ -16,7 +15,6 @@ import javax.crypto.spec.SecretKeySpec; import java.lang.reflect.Constructor; import java.lang.reflect.Method; -import java.net.URLEncoder; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.security.*; @@ -45,9 +43,9 @@ public String toString() { public enum CloudProvider { PUBLIC(""), - AWS("AWS"), - GCP("GCP-OIDC"), - AZURE("Azure-CC"); + AWS("aws-nitro"), + GCP("gcp-oidc"), + AZURE("azure-cc"); private final String name; @@ -64,32 +62,37 @@ public String toString() { private record V2Envelope(String envelope, byte[] nonce) { } - private static final SecureRandom SECURE_RANDOM = new SecureRandom(); - // When running via the pipeline, environment variables are defined in the uid2-shared-actions repo. // When running via IntelliJ, environment variables are defined in the uid2-dev-workspace repo under .idea/runConfigurations. - // // Test data is defined in the uid2-admin repo. - public static final String CLIENT_API_KEY = EnvUtil.getEnv("UID2_E2E_API_KEY"); - public static final String CLIENT_API_SECRET = EnvUtil.getEnv("UID2_E2E_API_SECRET"); - private static final String CLIENT_API_KEY_BEFORE_OPTOUT_CUTOFF = EnvUtil.getEnv("UID2_E2E_API_KEY_OLD"); - private static final String CLIENT_API_SECRET_BEFORE_OPTOUT_CUTOFF = EnvUtil.getEnv("UID2_E2E_API_SECRET_OLD"); - public static final String CLIENT_API_KEY_SHARING_RECIPIENT = EnvUtil.getEnv("UID2_E2E_API_KEY_SHARING_RECIPIENT"); - public static final String CLIENT_API_SECRET_SHARING_RECIPIENT = EnvUtil.getEnv("UID2_E2E_API_SECRET_SHARING_RECIPIENT"); - public static final String CLIENT_API_KEY_NON_SHARING_RECIPIENT = EnvUtil.getEnv("UID2_E2E_API_KEY_NON_SHARING_RECIPIENT"); - public static final String CLIENT_API_SECRET_NON_SHARING_RECIPIENT = EnvUtil.getEnv("UID2_E2E_API_SECRET_NON_SHARING_RECIPIENT"); - private static final String CLIENT_SIDE_TOKEN_GENERATE_SUBSCRIPTION_ID = EnvUtil.getEnv("UID2_E2E_SUBSCRIPTION_ID"); - private static final String CLIENT_SIDE_TOKEN_GENERATE_SERVER_PUBLIC_KEY = EnvUtil.getEnv("UID2_E2E_SERVER_PUBLIC_KEY"); - private static final String CLIENT_SIDE_TOKEN_GENERATE_ORIGIN = EnvUtil.getEnv("UID2_E2E_ORIGIN"); - private static final String CLIENT_SIDE_TOKEN_GENERATE_INVALID_ORIGIN = EnvUtil.getEnv("UID2_E2E_INVALID_ORIGIN"); - public static final IdentityScope IDENTITY_SCOPE = IdentityScope.valueOf(EnvUtil.getEnv("UID2_E2E_IDENTITY_SCOPE")); + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); private static final int TIMESTAMP_LENGTH = 8; private static final int PUBLIC_KEY_PREFIX_LENGTH = 9; private static final int AUTHENTICATION_TAG_LENGTH_BITS = 128; private static final int IV_BYTES = 12; private static final String TC_STRING = "CPhJRpMPhJRpMABAMBFRACBoALAAAEJAAIYgAKwAQAKgArABAAqAAA"; + public static final String CLIENT_API_KEY = EnvUtil.getEnv(Const.Config.Operator.CLIENT_API_KEY); + public static final String CLIENT_API_SECRET = EnvUtil.getEnv(Const.Config.Operator.CLIENT_API_SECRET); + + // Optout cutoff + public static final String CLIENT_API_KEY_BEFORE_OPTOUT_CUTOFF = EnvUtil.getEnv(Const.Config.Operator.CLIENT_API_KEY_BEFORE_OPTOUT_CUTOFF); + public static final String CLIENT_API_SECRET_BEFORE_OPTOUT_CUTOFF = EnvUtil.getEnv(Const.Config.Operator.CLIENT_API_SECRET_BEFORE_OPTOUT_CUTOFF); + + // Local only - Sharing + public static final String CLIENT_API_KEY_SHARING_RECIPIENT = EnvUtil.getEnv(Const.Config.Operator.CLIENT_API_KEY_SHARING_RECIPIENT, EnabledCondition.isLocal()); + public static final String CLIENT_API_SECRET_SHARING_RECIPIENT = EnvUtil.getEnv(Const.Config.Operator.CLIENT_API_SECRET_SHARING_RECIPIENT, EnabledCondition.isLocal()); + public static final String CLIENT_API_KEY_NON_SHARING_RECIPIENT = EnvUtil.getEnv(Const.Config.Operator.CLIENT_API_KEY_NON_SHARING_RECIPIENT, EnabledCondition.isLocal()); + public static final String CLIENT_API_SECRET_NON_SHARING_RECIPIENT = EnvUtil.getEnv(Const.Config.Operator.CLIENT_API_SECRET_NON_SHARING_RECIPIENT, EnabledCondition.isLocal()); + + // Local only - CSTG + public static final String CSTG_SUBSCRIPTION_ID = EnvUtil.getEnv(Const.Config.Operator.CSTG_SUBSCRIPTION_ID, EnabledCondition.isLocal()); + public static final String CSTG_SERVER_PUBLIC_KEY = EnvUtil.getEnv(Const.Config.Operator.CSTG_SERVER_PUBLIC_KEY, EnabledCondition.isLocal()); + public static final String CSTG_ORIGIN = EnvUtil.getEnv(Const.Config.Operator.CSTG_ORIGIN, EnabledCondition.isLocal()); + public static final String CSTG_INVALID_ORIGIN = EnvUtil.getEnv(Const.Config.Operator.CSTG_INVALID_ORIGIN, EnabledCondition.isLocal()); + + @Getter private final Type type; private final PublisherUid2Client publisherClient; private final PublisherUid2Client oldPublisherClient; @@ -122,10 +125,6 @@ public Operator(String host, String name, Type type) { this(host, null, name, type); } - public Type getType() { - return type; - } - public TokenGenerateResponse v2TokenGenerate(String type, String identity, boolean asOldParticipant) { TokenGenerateInput token; @@ -156,7 +155,7 @@ public JsonNode v2TokenGenerateUsingPayload(String payload, boolean asOldPartici } public JsonNode v2ClientSideTokenGenerate(String requestBody, boolean useValidOrigin) throws Exception { - final byte[] serverPublicKeyBytes = base64ToByteArray(CLIENT_SIDE_TOKEN_GENERATE_SERVER_PUBLIC_KEY.substring(PUBLIC_KEY_PREFIX_LENGTH)); + final byte[] serverPublicKeyBytes = base64ToByteArray(CSTG_SERVER_PUBLIC_KEY.substring(PUBLIC_KEY_PREFIX_LENGTH)); final PublicKey serverPublicKey = KeyFactory.getInstance("EC") .generatePublic(new X509EncodedKeySpec(serverPublicKeyBytes)); @@ -164,11 +163,11 @@ public JsonNode v2ClientSideTokenGenerate(String requestBody, boolean useValidOr final KeyPair keyPair = generateKeyPair(); final SecretKey sharedSecret = generateSharedSecret(serverPublicKey, keyPair); - final JsonObject cstgEnvelope = createCstgEnvelope(requestBody, CLIENT_SIDE_TOKEN_GENERATE_SUBSCRIPTION_ID, keyPair.getPublic(), sharedSecret); + final JsonObject cstgEnvelope = createCstgEnvelope(requestBody, keyPair.getPublic(), sharedSecret); final Request.Builder requestBuilder = new Request.Builder() .url(getBaseUrl() + "/v2/token/client-generate") - .addHeader("Origin", useValidOrigin ? CLIENT_SIDE_TOKEN_GENERATE_ORIGIN : CLIENT_SIDE_TOKEN_GENERATE_INVALID_ORIGIN) + .addHeader("Origin", useValidOrigin ? CSTG_ORIGIN : CSTG_INVALID_ORIGIN) .post(RequestBody.create(cstgEnvelope.toString(), HttpClient.JSON)); final String encryptedResponse = HttpClient.execute(requestBuilder.build(), HttpClient.HttpMethod.POST); @@ -202,7 +201,7 @@ private static SecretKey generateSharedSecret(PublicKey serverPublicKey, KeyPair } } - private static JsonObject createCstgEnvelope(String request, String subscriptionId, PublicKey clientPublicKey, SecretKey sharedSecret) { + private static JsonObject createCstgEnvelope(String request, PublicKey clientPublicKey, SecretKey sharedSecret) { final long now = Clock.systemUTC().millis(); final byte[] iv = new byte[IV_BYTES]; @@ -222,7 +221,7 @@ private static JsonObject createCstgEnvelope(String request, String subscription body.addProperty("iv", byteArrayToBase64(iv)); body.addProperty("public_key", byteArrayToBase64(clientPublicKey.getEncoded())); body.addProperty("timestamp", now); - body.addProperty("subscription_id", subscriptionId); + body.addProperty("subscription_id", CSTG_SUBSCRIPTION_ID); return body; } diff --git a/src/test/java/app/component/Optout.java b/src/test/java/app/component/Optout.java deleted file mode 100644 index b749b04..0000000 --- a/src/test/java/app/component/Optout.java +++ /dev/null @@ -1,20 +0,0 @@ -package app.component; - - -import common.EnvUtil; -import common.HttpClient; -import common.Mapper; -import com.fasterxml.jackson.databind.JsonNode; - -public class Optout extends App { - private static final String CORE_API_TOKEN = EnvUtil.getEnv("UID2_E2E_OPTOUT_TO_CALL_CORE_API_TOKEN"); - public Optout(String host, Integer port, String name) { - super(host, port, name); - } - - public JsonNode getPath(String path) throws Exception { - String response = HttpClient.get(getBaseUrl() + path, CORE_API_TOKEN); - return Mapper.OBJECT_MAPPER.readTree(response); - } - -} diff --git a/src/test/java/common/Const.java b/src/test/java/common/Const.java new file mode 100644 index 0000000..11020ea --- /dev/null +++ b/src/test/java/common/Const.java @@ -0,0 +1,53 @@ +package common; + +public final class Const { + public static final class Config { + public static final String SUITES = "E2E_SUITES"; + public static final String ARGS_JSON = "E2E_ARGS_JSON"; + + public static final String ENV = "E2E_ENV"; + public static final String IDENTITY_SCOPE = "E2E_IDENTITY_SCOPE"; + public static final String PHONE_SUPPORT = "E2E_PHONE_SUPPORT"; + + // Local only - Args used for Core E2Es + public static final class Core { + public static final String OPERATOR_API_KEY = "UID2_CORE_E2E_OPERATOR_API_KEY"; + public static final String OPTOUT_API_KEY = "UID2_CORE_E2E_OPTOUT_API_KEY"; + public static final String CORE_URL = "UID2_CORE_E2E_CORE_URL"; + public static final String OPTOUT_URL = "UID2_CORE_E2E_OPTOUT_URL"; + } + + // Args used for Operator E2Es + public static final class Operator { + public static final String CLIENT_SITE_ID = "UID2_OPERATOR_E2E_CLIENT_SITE_ID"; + public static final String CLIENT_API_KEY = "UID2_OPERATOR_E2E_CLIENT_API_KEY"; + public static final String CLIENT_API_SECRET = "UID2_OPERATOR_E2E_CLIENT_API_SECRET"; + + // Optout cutoff + public static final String CLIENT_API_KEY_BEFORE_OPTOUT_CUTOFF = "UID2_OPERATOR_E2E_CLIENT_API_KEY_BEFORE_OPTOUT_CUTOFF"; + public static final String CLIENT_API_SECRET_BEFORE_OPTOUT_CUTOFF = "UID2_OPERATOR_E2E_CLIENT_API_SECRET_BEFORE_OPTOUT_CUTOFF"; + + // Local only - Sharing + public static final String CLIENT_API_KEY_SHARING_RECIPIENT = "UID2_OPERATOR_E2E_CLIENT_API_KEY_SHARING_RECIPIENT"; + public static final String CLIENT_API_SECRET_SHARING_RECIPIENT = "UID2_OPERATOR_E2E_CLIENT_API_SECRET_SHARING_RECIPIENT"; + + public static final String CLIENT_API_KEY_NON_SHARING_RECIPIENT = "UID2_OPERATOR_E2E_CLIENT_API_KEY_NON_SHARING_RECIPIENT"; + public static final String CLIENT_API_SECRET_NON_SHARING_RECIPIENT = "UID2_OPERATOR_E2E_CLIENT_API_SECRET_NON_SHARING_RECIPIENT"; + + // Local only - CSTG + public static final String CSTG_SUBSCRIPTION_ID = "UID2_OPERATOR_E2E_CSTG_SUBSCRIPTION_ID"; + public static final String CSTG_SERVER_PUBLIC_KEY = "UID2_OPERATOR_E2E_CSTG_SERVER_PUBLIC_KEY"; + public static final String CSTG_ORIGIN = "UID2_OPERATOR_E2E_CSTG_ORIGIN"; + public static final String CSTG_INVALID_ORIGIN = "UID2_OPERATOR_E2E_CSTG_INVALID_ORIGIN"; + } + + // Args used for pipeline setup + public static final class Pipeline { + public static final String CORE_URL = "UID2_PIPELINE_E2E_CORE_URL"; + + public static final String OPERATOR_URL = "UID2_PIPELINE_E2E_OPERATOR_URL"; + public static final String OPERATOR_TYPE = "UID2_PIPELINE_E2E_OPERATOR_TYPE"; + public static final String OPERATOR_CLOUD_PROVIDER = "UID2_PIPELINE_E2E_OPERATOR_CLOUD_PROVIDER"; + } + } +} diff --git a/src/test/java/common/EnabledCondition.java b/src/test/java/common/EnabledCondition.java index c14919c..44494d3 100644 --- a/src/test/java/common/EnabledCondition.java +++ b/src/test/java/common/EnabledCondition.java @@ -1,7 +1,7 @@ package common; public final class EnabledCondition { - private static final String ENV = EnvUtil.getEnv("UID2_E2E_ENV"); + private static final String ENV = EnvUtil.getEnv(Const.Config.ENV); private EnabledCondition() { } diff --git a/src/test/java/common/EnvUtil.java b/src/test/java/common/EnvUtil.java index c287607..e950c33 100644 --- a/src/test/java/common/EnvUtil.java +++ b/src/test/java/common/EnvUtil.java @@ -1,21 +1,51 @@ package common; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.lang3.StringUtils; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; +import java.util.HashMap; +import java.util.Map; + public final class EnvUtil { private static final Logger LOGGER = LoggerFactory.getLogger(EnvUtil.class); + private static final Map ARGS = new HashMap<>(); + + static { + try { + String args = getEnv(Const.Config.ARGS_JSON, false); + + if (StringUtils.isNotBlank(args)) { + TypeReference> typeRef = new TypeReference<>() { + }; + ARGS.putAll(Mapper.OBJECT_MAPPER.readValue(args, typeRef)); + } + } catch (JsonProcessingException e) { + LOGGER.error(e::getMessage); + System.exit(1); + } + } private EnvUtil() { } - public static String getEnv(String env) { + public static String getEnv(String env, boolean required) { String value = System.getenv(env); if (StringUtils.isBlank(value)) { + value = ARGS.get(env); + } + + if (StringUtils.isBlank(value) && required) { LOGGER.error(() -> "Missing environment variable: " + env); System.exit(1); } + return value; } + + public static String getEnv(String env) { + return getEnv(env, true); + } } diff --git a/src/test/java/suite/E2ECoreTestSuite.java b/src/test/java/suite/E2ECoreTestSuite.java new file mode 100644 index 0000000..742cbfa --- /dev/null +++ b/src/test/java/suite/E2ECoreTestSuite.java @@ -0,0 +1,16 @@ +package suite; + +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; +import suite.basic.BasicTest; +import suite.core.CoreRefreshTest; +import suite.core.CoreTest; + +@Suite +@SelectClasses({ + BasicTest.class, + CoreTest.class, + CoreRefreshTest.class +}) +public class E2ECoreTestSuite { +} diff --git a/src/test/java/suite/E2ELocalFullTestSuite.java b/src/test/java/suite/E2ELocalFullTestSuite.java index 553171d..cc301d5 100644 --- a/src/test/java/suite/E2ELocalFullTestSuite.java +++ b/src/test/java/suite/E2ELocalFullTestSuite.java @@ -5,8 +5,7 @@ import suite.basic.BasicTest; import suite.core.CoreRefreshTest; import suite.core.CoreTest; -import suite.operator.V2ApiOperatorPublicOnlyTest; -import suite.operator.V2ApiOperatorTest; +import suite.operator.*; import suite.optout.OptoutTest; import suite.validator.V2ApiValidatorTest; @@ -17,6 +16,7 @@ CoreRefreshTest.class, V2ApiOperatorTest.class, V2ApiOperatorPublicOnlyTest.class, + V2ApiOperatorLocalOnlyTest.class, OptoutTest.class, V2ApiValidatorTest.class }) diff --git a/src/test/java/suite/E2EPublicOperatorTestSuite.java b/src/test/java/suite/E2EPublicOperatorTestSuite.java index c125aa0..7a483d5 100644 --- a/src/test/java/suite/E2EPublicOperatorTestSuite.java +++ b/src/test/java/suite/E2EPublicOperatorTestSuite.java @@ -5,8 +5,7 @@ import suite.basic.BasicTest; import suite.core.CoreRefreshTest; import suite.core.CoreTest; -import suite.operator.V2ApiOperatorPublicOnlyTest; -import suite.operator.V2ApiOperatorTest; +import suite.operator.*; import suite.optout.OptoutTest; @Suite @@ -16,6 +15,7 @@ CoreRefreshTest.class, V2ApiOperatorTest.class, V2ApiOperatorPublicOnlyTest.class, + V2ApiOperatorLocalOnlyTest.class, OptoutTest.class }) public class E2EPublicOperatorTestSuite { diff --git a/src/test/java/suite/core/CoreRefreshTest.java b/src/test/java/suite/core/CoreRefreshTest.java index 191d054..02478d6 100644 --- a/src/test/java/suite/core/CoreRefreshTest.java +++ b/src/test/java/suite/core/CoreRefreshTest.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import common.JsonAssert; +import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -11,6 +12,7 @@ import static org.junit.jupiter.api.Assertions.*; +@EnabledIf("common.EnabledCondition#isLocal") public class CoreRefreshTest { @ParameterizedTest(name = "Refresh test - UrlPath: {1} - JsonPath: {2}") @MethodSource({"suite.core.TestData#refreshArgs", "suite.core.TestData#refreshArgsEncrypted"}) diff --git a/src/test/java/suite/core/CoreTest.java b/src/test/java/suite/core/CoreTest.java index 864275c..4ee9c0e 100644 --- a/src/test/java/suite/core/CoreTest.java +++ b/src/test/java/suite/core/CoreTest.java @@ -6,11 +6,13 @@ import com.uid2.shared.attest.JwtService; import com.uid2.shared.attest.JwtValidationResponse; import io.vertx.core.json.JsonObject; +import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import static org.junit.jupiter.api.Assertions.*; +@EnabledIf("common.EnabledCondition#isLocal") public class CoreTest { @ParameterizedTest(name = "/attest - {0}") @MethodSource({ diff --git a/src/test/java/suite/operator/TestData.java b/src/test/java/suite/operator/TestData.java index 08e6df6..38a6df8 100644 --- a/src/test/java/suite/operator/TestData.java +++ b/src/test/java/suite/operator/TestData.java @@ -1,7 +1,7 @@ package suite.operator; import app.AppsMap; -import common.EnvUtil; +import app.component.App; import app.component.Operator; import com.uid2.client.DecryptionStatus; import org.junit.jupiter.params.provider.Arguments; @@ -17,11 +17,6 @@ public final class TestData { public static final int RAW_UID2_LENGTH = 44; - private static final boolean PHONE_SUPPORT = Boolean.parseBoolean(EnvUtil.getEnv("UID2_E2E_PHONE_SUPPORT")); - - private static final Client CLIENT = new Client(Operator.CLIENT_API_KEY, Operator.CLIENT_API_SECRET); - private static final Client SHARING_RECIPIENT = new Client(Operator.CLIENT_API_KEY_SHARING_RECIPIENT, Operator.CLIENT_API_SECRET_SHARING_RECIPIENT); - private static final Client NON_SHARING_RECIPIENT = new Client(Operator.CLIENT_API_KEY_NON_SHARING_RECIPIENT, Operator.CLIENT_API_SECRET_NON_SHARING_RECIPIENT); private TestData() { } @@ -39,50 +34,6 @@ public static Set baseArgs() { return args; } - public static Stream sharingArgs() { - final var privateOperator = named("PRIVATE OPERATOR", getOperator(Operator.Type.PRIVATE).orElse(null)); - final var publicOperator = named("PUBLIC OPERATOR", getOperator(Operator.Type.PUBLIC).orElse(null)); - - final var client = named("CLIENT", CLIENT); - final var sharingRecipient = named("SHARING RECIPIENT", SHARING_RECIPIENT); - final var nonSharingRecipient = named("NON-SHARING RECIPIENT", NON_SHARING_RECIPIENT); - - return Stream.of( - // CLIENT shares with CLIENT. - Arguments.of(client, privateOperator, client, privateOperator, DecryptionStatus.SUCCESS), - Arguments.of(client, privateOperator, client, publicOperator, DecryptionStatus.SUCCESS), - Arguments.of(client, publicOperator, client, privateOperator, DecryptionStatus.SUCCESS), - Arguments.of(client, publicOperator, client, publicOperator, DecryptionStatus.SUCCESS), - - // CLIENT shares with SHARING RECIPIENT. - // Private operator only has site data for CLIENT, so SHARING RECIPIENT must decrypt using public operator. - Arguments.of(client, privateOperator, sharingRecipient, publicOperator, DecryptionStatus.SUCCESS), - Arguments.of(client, publicOperator, sharingRecipient, publicOperator, DecryptionStatus.SUCCESS), - - // CLIENT does not share with NON-SHARING RECIPIENT. - // Private operator only has site data for CLIENT, so NON-SHARING RECIPIENT must decrypt using public operator. - Arguments.of(client, privateOperator, nonSharingRecipient, publicOperator, DecryptionStatus.NOT_AUTHORIZED_FOR_KEY), - Arguments.of(client, publicOperator, nonSharingRecipient, publicOperator, DecryptionStatus.NOT_AUTHORIZED_FOR_KEY), - - // SHARING RECIPIENT shares with CLIENT. - // Private operator only has site data for CLIENT, so SHARING RECIPIENT must encrypt using public operator. - Arguments.of(sharingRecipient, publicOperator, client, privateOperator, DecryptionStatus.SUCCESS), - Arguments.of(sharingRecipient, publicOperator, client, publicOperator, DecryptionStatus.SUCCESS), - - // NON-SHARING RECIPIENT does not share with CLIENT. - // Private operator only has site data for CLIENT, so NON-SHARING RECIPIENT must encrypt using public operator. - Arguments.of(nonSharingRecipient, publicOperator, client, publicOperator, DecryptionStatus.NOT_AUTHORIZED_FOR_KEY), - Arguments.of(nonSharingRecipient, publicOperator, client, privateOperator, DecryptionStatus.NOT_AUTHORIZED_FOR_KEY) - ); - } - - private static Optional getOperator(Operator.Type type) { - return AppsMap.getApps(Operator.class) - .stream() - .filter(operator -> operator.getType() == type) - .findFirst(); - } - public static Set tokenEmailArgs() { Set operators = AppsMap.getApps(Operator.class); Set> inputs = Set.of( @@ -136,7 +87,7 @@ public static Set tokenPhoneArgs() { ); Set args = new HashSet<>(); - if (PHONE_SUPPORT) { + if (App.PHONE_SUPPORT) { for (Operator operator : operators) { for (List input : inputs) { args.add(Arguments.of(input.get(0), operator, operator.getName(), input.get(1), input.get(2))); @@ -150,7 +101,7 @@ public static Set tokenPhoneArgsSpecialOptout() { Set operators = AppsMap.getApps(Operator.class); Set args = new HashSet<>(); - if (PHONE_SUPPORT) { + if (App.PHONE_SUPPORT) { for (Operator operator : operators) { args.add(Arguments.of("optout special phone", operator, operator.getName(), "phone", "+00000000000", true)); args.add(Arguments.of("optout special phone", operator, operator.getName(), "phone", "+00000000000", false)); @@ -163,7 +114,7 @@ public static Set tokenPhoneArgsSpecialRefreshOptout() { Set operators = AppsMap.getApps(Operator.class); Set args = new HashSet<>(); - if (PHONE_SUPPORT) { + if (App.PHONE_SUPPORT) { for (Operator operator : operators) { args.add(Arguments.of("optout special refresh phone", operator, operator.getName(), "phone", "+00000000002")); } @@ -256,7 +207,7 @@ public static Set tokenValidatePhoneArgs() { ); Set args = new HashSet<>(); - if (PHONE_SUPPORT) { + if (App.PHONE_SUPPORT) { for (Operator operator : operators) { for (List input : inputs) { args.add(Arguments.of(input.get(0), operator, operator.getName(), input.get(1), input.get(2))); @@ -273,7 +224,7 @@ public static Set tokenValidatePhoneHashArgs() { ); Set args = new HashSet<>(); - if (PHONE_SUPPORT) { + if (App.PHONE_SUPPORT) { for (Operator operator : operators) { for (List input : inputs) { args.add(Arguments.of(input.get(0), operator, operator.getName(), input.get(1), input.get(2))); @@ -307,7 +258,7 @@ public static Set identityMapPhoneArgs() { ); Set args = new HashSet<>(); - if (PHONE_SUPPORT) { + if (App.PHONE_SUPPORT) { for (Operator operator : operators) { for (List input : inputs) { args.add(Arguments.of(input.get(0), operator, operator.getName(), input.get(1), input.get(2))); @@ -407,7 +358,7 @@ public static Set clientSideTokenGenerateArgs() { inputs.add(List.of("email hash", "{\"email_hash\":\"eVvLS/Vg+YZ6+z3i0NOpSXYyQAfEXqCZ7BTpAjFUBUc=\"}")); - if (PHONE_SUPPORT) { + if (App.PHONE_SUPPORT) { inputs.add(List.of("phone hash", "{\"phone_hash\":\"eVvLS/Vg+YZ6+z3i0NOpSXYyQAfEXqCZ7BTpAjFUBUc=\"}")); } @@ -452,7 +403,7 @@ public static Set tokenGeneratePhoneArgsBadPolicy() { ); Set args = new HashSet<>(); - if (PHONE_SUPPORT) { + if (App.PHONE_SUPPORT) { for (Operator operator : operators) { for (List input : inputs) { args.add(Arguments.of(input.get(0), operator, operator.getName(), input.get(1))); @@ -476,7 +427,7 @@ public static Set identityMapBatchPhoneArgs() { ); Set args = new HashSet<>(); - if (PHONE_SUPPORT) { + if (App.PHONE_SUPPORT) { for (Operator operator : operators) { for (List input : inputs) { args.add(Arguments.of(input.get(0), operator, operator.getName(), input.get(1))); @@ -504,7 +455,7 @@ public static Set identityMapBatchPhoneArgsBadPolicy() { ); Set args = new HashSet<>(); - if (PHONE_SUPPORT) { + if (App.PHONE_SUPPORT) { for (Operator operator : operators) { for (List input : inputs) { args.add(Arguments.of(input.get(0), operator, operator.getName(), input.get(1), true)); @@ -543,7 +494,7 @@ public static Set identityMapBatchBadPhoneArgs() { ); Set args = new HashSet<>(); - if (PHONE_SUPPORT) { + if (App.PHONE_SUPPORT) { for (Operator operator : operators) { for (List input : inputs) { args.add(Arguments.of(input.get(0), operator, operator.getName(), input.get(1))); @@ -568,6 +519,50 @@ public static Set identityBucketsArgs() { return args; } + public static Stream sharingArgs() { + var privateOperator = named("PRIVATE OPERATOR", getOperator(Operator.Type.PRIVATE).orElse(null)); + var publicOperator = named("PUBLIC OPERATOR", getOperator(Operator.Type.PUBLIC).orElse(null)); + + var client = named("CLIENT", new Client(Operator.CLIENT_API_KEY, Operator.CLIENT_API_SECRET)); + var sharingRecipient = named("SHARING RECIPIENT", new Client(Operator.CLIENT_API_KEY_SHARING_RECIPIENT, Operator.CLIENT_API_SECRET_SHARING_RECIPIENT)); + var nonSharingRecipient = named("NON-SHARING RECIPIENT", new Client(Operator.CLIENT_API_KEY_NON_SHARING_RECIPIENT, Operator.CLIENT_API_SECRET_NON_SHARING_RECIPIENT)); + + return Stream.of( + // CLIENT shares with CLIENT. + Arguments.of(client, privateOperator, client, privateOperator, DecryptionStatus.SUCCESS), + Arguments.of(client, privateOperator, client, publicOperator, DecryptionStatus.SUCCESS), + Arguments.of(client, publicOperator, client, privateOperator, DecryptionStatus.SUCCESS), + Arguments.of(client, publicOperator, client, publicOperator, DecryptionStatus.SUCCESS), + + // CLIENT shares with SHARING RECIPIENT. + // Private operator only has site data for CLIENT, so SHARING RECIPIENT must decrypt using public operator. + Arguments.of(client, privateOperator, sharingRecipient, publicOperator, DecryptionStatus.SUCCESS), + Arguments.of(client, publicOperator, sharingRecipient, publicOperator, DecryptionStatus.SUCCESS), + + // CLIENT does not share with NON-SHARING RECIPIENT. + // Private operator only has site data for CLIENT, so NON-SHARING RECIPIENT must decrypt using public operator. + Arguments.of(client, privateOperator, nonSharingRecipient, publicOperator, DecryptionStatus.NOT_AUTHORIZED_FOR_KEY), + Arguments.of(client, publicOperator, nonSharingRecipient, publicOperator, DecryptionStatus.NOT_AUTHORIZED_FOR_KEY), + + // SHARING RECIPIENT shares with CLIENT. + // Private operator only has site data for CLIENT, so SHARING RECIPIENT must encrypt using public operator. + Arguments.of(sharingRecipient, publicOperator, client, privateOperator, DecryptionStatus.SUCCESS), + Arguments.of(sharingRecipient, publicOperator, client, publicOperator, DecryptionStatus.SUCCESS), + + // NON-SHARING RECIPIENT does not share with CLIENT. + // Private operator only has site data for CLIENT, so NON-SHARING RECIPIENT must encrypt using public operator. + Arguments.of(nonSharingRecipient, publicOperator, client, publicOperator, DecryptionStatus.NOT_AUTHORIZED_FOR_KEY), + Arguments.of(nonSharingRecipient, publicOperator, client, privateOperator, DecryptionStatus.NOT_AUTHORIZED_FOR_KEY) + ); + } + + private static Optional getOperator(Operator.Type type) { + return AppsMap.getApps(Operator.class) + .stream() + .filter(operator -> operator.getType() == type) + .findFirst(); + } + private static Set getPublicOperators() { return AppsMap.getApps(Operator.class).stream() .filter(s -> s.getType() != Operator.Type.PRIVATE) diff --git a/src/test/java/suite/operator/V2ApiOperatorLocalOnlyTest.java b/src/test/java/suite/operator/V2ApiOperatorLocalOnlyTest.java new file mode 100644 index 0000000..980cbf7 --- /dev/null +++ b/src/test/java/suite/operator/V2ApiOperatorLocalOnlyTest.java @@ -0,0 +1,74 @@ +package suite.operator; + +import app.component.App; +import app.component.Operator; +import com.uid2.client.*; +import common.Const; +import common.EnvUtil; +import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.security.SecureRandom; +import java.util.Base64; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@EnabledIf("common.EnabledCondition#isLocal") +public class V2ApiOperatorLocalOnlyTest { + @ParameterizedTest(name = "{index} ==> Sender {0} encrypts with {1}, recipient {2} decrypts with {3}, expected result is {4}") + @MethodSource({ + "suite.operator.TestData#sharingArgs", + }) + public void testSharing(TestData.Client sender, Operator senderOperator, TestData.Client recipient, Operator recipientOperator, DecryptionStatus expectedDecryptionStatus) throws Exception { + assumeThat(senderOperator).isNotNull(); + assumeThat(recipientOperator).isNotNull(); + + final var rawUidBytes = new byte[33]; + new SecureRandom().nextBytes(rawUidBytes); + rawUidBytes[0] = 0; + + final var rawUid = Base64.getEncoder().encodeToString(rawUidBytes); + + final var senderClient = new UID2Client( + senderOperator.getBaseUrl(), + sender.apiKey(), + sender.apiSecret(), + App.IDENTITY_SCOPE + ); + + final var recipientClient = new UID2Client( + recipientOperator.getBaseUrl(), + recipient.apiKey(), + recipient.apiSecret(), + App.IDENTITY_SCOPE + ); + + senderClient.refresh(); + final var encrypted = senderClient.encrypt(rawUid); + assertTrue(encrypted.isSuccess()); + + recipientClient.refresh(); + final var decrypted = recipientClient.decrypt(encrypted.getEncryptedData()); + final var expectedRawUid = expectedDecryptionStatus == DecryptionStatus.SUCCESS ? rawUid : null; + + assertAll( + () -> assertThat(decrypted.getStatus()).isEqualTo(expectedDecryptionStatus), + () -> assertThat(decrypted.getUid()).isEqualTo(expectedRawUid) + ); + } + + @ParameterizedTest(name = "/v2/token/generate - LOCAL MOCK OPTOUT - {0} - {2}") + @MethodSource({ + "suite.operator.TestData#tokenEmailArgsLocalMockOptout" + }) + public void testV2TokenGenerateLocalMockOptout(String label, Operator operator, String operatorName, String type, String identity) { + TokenGenerateResponse tokenGenerateResponse = operator.v2TokenGenerate(type, identity, false); + IdentityTokens currentIdentity = tokenGenerateResponse.getIdentity(); + + assertThat(currentIdentity).isNull(); + } +} diff --git a/src/test/java/suite/operator/V2ApiOperatorPublicOnlyTest.java b/src/test/java/suite/operator/V2ApiOperatorPublicOnlyTest.java index 6921e55..95042ae 100644 --- a/src/test/java/suite/operator/V2ApiOperatorPublicOnlyTest.java +++ b/src/test/java/suite/operator/V2ApiOperatorPublicOnlyTest.java @@ -7,6 +7,8 @@ import com.uid2.client.DecryptionResponse; import com.uid2.client.IdentityTokens; import com.uid2.client.TokenRefreshResponse; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -199,6 +201,7 @@ public void testV2IdentityMapBadPolicy(String label, Operator operator, String o assertThat(response.at("/status").asText()).isEqualTo("success"); } + @EnabledIf("common.EnabledCondition#isLocal") @ParameterizedTest(name = "/v2/token/client-generate - {0} - {2}") @MethodSource({ "suite.operator.TestData#clientSideTokenGenerateArgs", @@ -213,6 +216,7 @@ public void testV2ClientSideTokenGenerate(String label, Operator operator, Strin assertThat(response.get("status").asText()).isEqualTo("success"); } + @EnabledIf("common.EnabledCondition#isLocal") @ParameterizedTest(name = "/v2/token/client-generate - INVALID ORIGIN - {0} - {2}") @MethodSource({ "suite.operator.TestData#clientSideTokenGenerateArgs", diff --git a/src/test/java/suite/operator/V2ApiOperatorTest.java b/src/test/java/suite/operator/V2ApiOperatorTest.java index f877db1..e87b190 100644 --- a/src/test/java/suite/operator/V2ApiOperatorTest.java +++ b/src/test/java/suite/operator/V2ApiOperatorTest.java @@ -1,19 +1,15 @@ package suite.operator; +import common.Const; import common.EnvUtil; import common.Mapper; import app.component.Operator; import com.fasterxml.jackson.databind.JsonNode; import com.uid2.client.*; -import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import java.security.SecureRandom; -import java.util.Base64; - import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assumptions.assumeThat; import static org.junit.jupiter.api.Assertions.*; @SuppressWarnings("unused") @@ -29,49 +25,7 @@ public class V2ApiOperatorTest { // The advertiser token will be different on every call due to randomness used in encryption, // so we can't assert on it - private static final String SITE_ID = EnvUtil.getEnv("UID2_E2E_SITE_ID"); - - @ParameterizedTest(name = "{index} ==> Sender {0} encrypts with {1}, recipient {2} decrypts with {3}, expected result is {4}") - @MethodSource({ - "suite.operator.TestData#sharingArgs", - }) - public void testSharing(TestData.Client sender, Operator senderOperator, TestData.Client recipient, Operator recipientOperator, DecryptionStatus expectedDecryptionStatus) throws Exception { - assumeThat(senderOperator).isNotNull(); - assumeThat(recipientOperator).isNotNull(); - - final var rawUidBytes = new byte[33]; - new SecureRandom().nextBytes(rawUidBytes); - rawUidBytes[0] = 0; - - final var rawUid = Base64.getEncoder().encodeToString(rawUidBytes); - - final var senderClient = new UID2Client( - senderOperator.getBaseUrl(), - sender.apiKey(), - sender.apiSecret(), - Operator.IDENTITY_SCOPE - ); - - final var recipientClient = new UID2Client( - recipientOperator.getBaseUrl(), - recipient.apiKey(), - recipient.apiSecret(), - Operator.IDENTITY_SCOPE - ); - - senderClient.refresh(); - final var encrypted = senderClient.encrypt(rawUid); - assertTrue(encrypted.isSuccess()); - - recipientClient.refresh(); - final var decrypted = recipientClient.decrypt(encrypted.getEncryptedData()); - final var expectedRawUid = expectedDecryptionStatus == DecryptionStatus.SUCCESS ? rawUid : null; - - assertAll( - () -> assertThat(decrypted.getStatus()).isEqualTo(expectedDecryptionStatus), - () -> assertThat(decrypted.getUid()).isEqualTo(expectedRawUid) - ); - } + private static final String CLIENT_SITE_ID = EnvUtil.getEnv(Const.Config.Operator.CLIENT_SITE_ID); @ParameterizedTest(name = "/v2/token/generate - {0} - {2}") @MethodSource({ @@ -122,18 +76,6 @@ public void testV2SpecialRefreshOptout(String label, Operator operator, String o assertTrue(refreshed.isOptout()); } - @EnabledIf("common.EnabledCondition#isLocal") - @ParameterizedTest(name = "/v2/token/generate - LOCAL MOCK OPTOUT - {0} - {2}") - @MethodSource({ - "suite.operator.TestData#tokenEmailArgsLocalMockOptout" - }) - public void testV2TokenGenerateLocalMockOptout(String label, Operator operator, String operatorName, String type, String identity) { - TokenGenerateResponse tokenGenerateResponse = operator.v2TokenGenerate(type, identity, false); - IdentityTokens currentIdentity = tokenGenerateResponse.getIdentity(); - - assertThat(currentIdentity).isNull(); - } - @ParameterizedTest(name = "/v2/token/validate - {0} - {2}") @MethodSource({ "suite.operator.TestData#tokenValidateEmailArgs", @@ -191,6 +133,6 @@ public void testV2IdentityBuckets(String label, Operator operator, String operat public void testV2KeySharing(Operator operator, String operatorName) throws Exception { JsonNode response = operator.v2KeySharing(); - assertEquals(SITE_ID, response.at("/body/caller_site_id").asText()); + assertEquals(CLIENT_SITE_ID, response.at("/body/caller_site_id").asText()); } } diff --git a/src/test/java/suite/optout/TestData.java b/src/test/java/suite/optout/TestData.java index f595b15..4d78bb8 100644 --- a/src/test/java/suite/optout/TestData.java +++ b/src/test/java/suite/optout/TestData.java @@ -1,7 +1,7 @@ package suite.optout; import app.AppsMap; -import common.EnvUtil; +import app.component.App; import app.component.Operator; import org.junit.jupiter.params.provider.Arguments; @@ -14,7 +14,6 @@ public final class TestData { public static final String ADVERTISING_ID = "advertising_id"; public static final String OPTED_OUT_SINCE = "opted_out_since"; - private static final boolean PHONE_SUPPORT = Boolean.parseBoolean(EnvUtil.getEnv("UID2_E2E_PHONE_SUPPORT")); private TestData() { } @@ -55,7 +54,7 @@ public static Set tokenPhoneArgs() { ); Set args = new HashSet<>(); - if (PHONE_SUPPORT) { + if (App.PHONE_SUPPORT) { for (Operator operator : operators) { for (List input : inputs) { args.add(Arguments.of(input.get(0), operator, operator.getName(), input.get(1), input.get(2))); @@ -70,7 +69,7 @@ public static Set optoutTokenPhoneArgs() { Set> inputs = generatePhoneSet(1); Set args = new HashSet<>(); - if (PHONE_SUPPORT) { + if (App.PHONE_SUPPORT) { for (Operator operator : operators) { for (List input : inputs) { args.add(Arguments.of(input.get(0), operator, operator.getName(), input.get(1), input.get(2)));