diff --git a/.circleci/config.yml b/.circleci/config.yml index 1d526ca0..b11bf633 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,13 +1,16 @@ -# Java Maven CircleCI 2.0 configuration file +# Java Maven CircleCI 2.1 configuration file # # Check https://circleci.com/docs/2.0/language-java/ for more details # -version: 2 +version: 2.1 + jobs: build: + parameters: + jdk-version: + type: string docker: - # specify the version you desire here - - image: cimg/openjdk:17.0.8 + - image: cimg/openjdk:<< parameters.jdk-version >> working_directory: ~/repo @@ -46,3 +49,13 @@ jobs: - store_test_results: path: target/surefire-reports +workflows: + build-and-test: + jobs: + - build: + matrix: + parameters: + jdk-version: + - "17.0" + - "21.0" + - "25.0" diff --git a/CHANGELOG.md b/CHANGELOG.md index ffadffe6..7985252b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ ## master +## 3.0.0 (2026-06-11) + +- Add webhook signature verification (`Castle#verifyWebhookSignature`) and the `X-Castle-Signature` header constant +- Add the Events API: `eventsSchema`, `queryEvents` and `groupEvents` +- Add the privacy methods `requestUserData` and `deleteUserData` +- Remove the legacy `authenticate` and `track` endpoints +- Remove device management (`approve`, `report`, `userDevices`, `device`) and impersonation (`impersonateStart`, `impersonateEnd`) +- Remove the `recover` endpoint and the `removeUser` privacy method +- Remove the authenticate failover configuration (`AuthenticateFailoverStrategy`, `withAuthenticateFailoverStrategy`, the `failover_strategy` setting) and the `Verdict`/`CastleMessage` models +- Build and test against Java 17, 21 and 25 (CI matrix); bump JaCoCo to 0.8.15, Mockito to 5.23.0, System Stubs to 2.1.8 and AssertJ to 3.27.7 +- Upgrade runtime dependencies: Gson 2.14.0, Guava 33.6.0-jre, SLF4J 2.0.18, jackson-databind-nullable 0.2.10, swagger-annotations 1.6.16, ThreeTen-Backport 1.7.3; declare an explicit `jsr305` dependency for the JSR-305 annotations +- Upgrade test dependencies: Logback 1.5.34, JSONassert 1.5.3, spring-test 6.1.21; remove the unused System Rules dependency +- Upgrade build plugins: maven-compiler-plugin 3.14.0, maven-surefire-plugin 3.5.6, maven-source-plugin 3.4.0, maven-javadoc-plugin 3.12.0, versions-maven-plugin 2.18.0, maven-gpg-plugin 3.2.8 and central-publishing-maven-plugin 0.10.0 + ## 2.6.1 (2025-07-28) - [#153](https://github.com/castle/castle-java/pull/153) Remove string json serialization cap diff --git a/README.md b/README.md index c041f36d..f41068e9 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,22 @@ # Requirements -- **Java Version:** This SDK requires **Java 17** or higher. +- **Java Version:** This SDK requires **Java 17** or higher, and is built and tested against Java 17, 21 and 25. + +# Supported APIs + +The SDK exposes the modern Castle API surface: + +| Group | Methods | +| --- | --- | +| Scoring | `risk`, `filter`, `log` | +| Lists | `createList`, `list`, `updateList`, `deleteList` | +| List items | `createListItem`, `createOrUpdateListItems`, `searchListItems`, `countListItems`, `getListItem`, `updateListItem`, `archiveListItem`, `unarchiveListItem` | +| Privacy | `requestUserData`, `deleteUserData` | +| Events | `eventsSchema`, `queryEvents`, `groupEvents` | +| Webhooks | `verifyWebhookSignature` | +| Secure mode | `secureUserID` | +| Generic | `get`, `post`, `put`, `delete` | # Usage See the [documentation](https://docs.castle.io) for how to use this SDK with the Castle APIs @@ -16,7 +31,7 @@ When using Maven, add the following dependency to your `pom.xml` file: io.castle castle-java - 2.6.1 + 3.0.0 ``` diff --git a/pom.xml b/pom.xml index a3561f0c..58643156 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.castle castle-java jar - 2.6.1 + 3.0.0 ${project.groupId}:${project.artifactId}:${project.version} Castle adds real-time monitoring of your authentication stack, instantly notifying you and your users @@ -51,7 +51,7 @@ UTF-8 17 17 - 0.8.10 + 0.8.15 @@ -62,7 +62,7 @@ org.sonatype.central central-publishing-maven-plugin - 0.8.0 + 0.10.0 true ossrh @@ -71,7 +71,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.6 + 3.2.8 sign-artifacts @@ -91,22 +91,27 @@ org.openapitools jackson-databind-nullable - 0.2.6 + 0.2.10 javax.annotation javax.annotation-api 1.3.2 + + com.google.code.findbugs + jsr305 + 3.0.2 + org.threeten threetenbp - 1.6.8 + 1.7.3 io.swagger swagger-annotations - 1.6.11 + 1.6.16 jakarta.servlet @@ -127,23 +132,23 @@ org.slf4j slf4j-api - 2.0.7 + 2.0.18 ch.qos.logback logback-classic - 1.4.12 + 1.5.34 test com.google.guava guava - 32.1.2-jre + 33.6.0-jre com.google.code.gson gson - 2.10.1 + 2.14.0 org.junit.jupiter @@ -160,18 +165,18 @@ uk.org.webcompere system-stubs-jupiter - 2.0.2 + 2.1.8 org.mockito mockito-core - 5.4.0 + 5.23.0 test org.assertj assertj-core - 3.24.2 + 3.27.7 test @@ -183,19 +188,13 @@ org.springframework spring-test - 6.0.11 + 6.1.21 test org.skyscreamer jsonassert - 1.5.1 - test - - - com.github.stefanbirkner - system-rules - 1.19.0 + 1.5.3 test @@ -218,7 +217,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.0 + 3.14.0 17 17 @@ -229,7 +228,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.0.1 + 3.12.0 private true @@ -246,7 +245,7 @@ org.apache.maven.plugins maven-source-plugin - 3.0.1 + 3.4.0 attach-sources @@ -259,9 +258,10 @@ org.apache.maven.plugins maven-surefire-plugin - 3.1.2 + 3.5.6 false + @{argLine} --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED @@ -297,7 +297,7 @@ org.codehaus.mojo versions-maven-plugin - 2.7 + 2.18.0 diff --git a/src/main/java/io/castle/client/Castle.java b/src/main/java/io/castle/client/Castle.java index 61cc4ee9..3b9e06cc 100644 --- a/src/main/java/io/castle/client/Castle.java +++ b/src/main/java/io/castle/client/Castle.java @@ -9,6 +9,7 @@ import io.castle.client.internal.config.CastleSdkInternalConfiguration; import io.castle.client.internal.json.CastleGsonModel; import io.castle.client.internal.utils.CastleContextBuilder; +import io.castle.client.internal.utils.Webhook; import io.castle.client.model.CastleResponse; import io.castle.client.model.CastleSdkConfigurationException; import org.slf4j.Logger; @@ -26,17 +27,16 @@ * Once set the {@code this#instance()} method will return that instance */ public class Castle { - public static final String URL_TRACK = "/v1/track"; - public static final String URL_AUTHENTICATE = "/v1/authenticate"; - public static final String URL_DEVICES = "/v1/devices/"; - public static final String URL_USERS = "/v1/users/"; - public static final String URL_IMPERSONATE = "/v1/impersonate"; public static final String URL_PRIVACY = "/v1/privacy/"; public static final String URL_RISK = "/v1/risk"; public static final String URL_FILTER = "/v1/filter"; public static final String URL_LOG = "/v1/log"; + public static final String URL_EVENTS = "/v1/events"; - public static final String URL_RECOVER = "/v1/users/%s/recover"; + /** + * Header used by Castle to sign webhook payloads. + */ + public static final String WEBHOOK_SIGNATURE_HEADER = "X-Castle-Signature"; public static final String URL_LISTS = "/v1/lists"; public static final String URL_LISTS_ID = "/v1/lists/%s"; @@ -270,6 +270,38 @@ public String secureUserID(String userId) { return hashFunction.hashString(userId,com.google.common.base.Charsets.UTF_8).toString(); } + /** + * Verifies a Castle webhook signature against the raw request body. + *

+ * Castle signs every webhook with an HMAC-SHA256 of the raw request body using + * the account API secret, base64 encoded and sent in the + * {@code X-Castle-Signature} header. + * + * @param signature the value of the {@code X-Castle-Signature} header + * @param body the raw request body bytes + * @return {@code true} when the signature matches the computed signature + */ + public boolean verifyWebhookSignature(String signature, byte[] body) { + return Webhook.verifySignature(internalConfiguration.getConfiguration().getApiSecret(), body, signature); + } + + /** + * Verifies a Castle webhook signature for the given servlet request. + *

+ * The signature is read from the {@code X-Castle-Signature} header and verified + * against the supplied raw request body bytes. + * + * @param request the incoming webhook request + * @param body the raw request body bytes + * @return {@code true} when the signature matches the computed signature + */ + public boolean verifyWebhookSignature(HttpServletRequest request, byte[] body) { + if (request == null) { + return false; + } + return verifyWebhookSignature(request.getHeader(WEBHOOK_SIGNATURE_HEADER), body); + } + /** * Make a GET request to a Castle API endpoint such as /v1/{userId}/devices * diff --git a/src/main/java/io/castle/client/api/CastleApi.java b/src/main/java/io/castle/client/api/CastleApi.java index ae782d8d..1131a0d5 100644 --- a/src/main/java/io/castle/client/api/CastleApi.java +++ b/src/main/java/io/castle/client/api/CastleApi.java @@ -48,79 +48,6 @@ public interface CastleApi { */ CastleApi mergeContext(Object additionalContext); - /** - * Makes a sync POST request to the authenticate endpoint containing all required parameters. - *

- * Optional parameters (that is, traits and properties) are set to null. - * - * @param event a String representing an event understood by the Castle API - * @param userId a String representing a user ID associated to an authentication attempt - * @return a verdict that might result from a successful call to the Castle API or from the client's - * {@link io.castle.client.model.AuthenticateFailoverStrategy}, in case of a failed call - * @see The docs - */ - Verdict authenticate(String event, String userId); - - /** - * Makes a sync POST request to the authenticate endpoint containing required and optional parameters. - * - * @param event a String representing an event understood by the Castle API - * @param userId a String representing a user ID associated to an authentication attempt - * @param properties object for recording additional information connected to the event, takes null - * @param traits object for recording additional information connected to the user, takes null - * @return a verdict that might result from a successful call to the Castle API or from the client's - * {@link io.castle.client.model.AuthenticateFailoverStrategy}, in case of a failed call - * @see The docs - */ - Verdict authenticate(String event, String userId, @Nullable Object properties, @Nullable Object traits); - - /** - * Makes a sync POST request to the authenticate endpoint containing required and optional parameters. - * @param message Event parameters - * @return a verdict that might result from a successful call to the Castle API or from the client's - */ - Verdict authenticate(CastleMessage message); - - JsonElement buildAuthenticateRequest(CastleMessage request); - - Verdict sendAuthenticateRequest(JsonElement request); - - void sendAuthenticateRequest(JsonElement request, AsyncCallbackHandler asyncCallbackHandler); - - /** - * Makes an async POST request to the authenticate endpoint containing required and optional parameters. - * - * @param event a String representing an event understood by the Castle API - * @param userId a String representing a user ID associated to an authentication attempt - * @param properties object for recording additional information connected to the event, takes null - * @param traits object for recording additional information connected to the user, takes null - * @param asyncCallbackHandler a user-implemented instance of {@code AsyncCallbackHandler} which specifies - * how to handle success of failure of authenticate API calls - * @see The docs - */ - void authenticateAsync(String event, String userId, @Nullable Object properties, @Nullable Object traits, AsyncCallbackHandler asyncCallbackHandler); - - /** - * Makes an async POST request to the authenticate endpoint containing all required parameters. - *

- * Optional parameters (that is, traits and properties) are set to null. - * - * @param event a String representing an event understood by the Castle API - * @param userId a String representing a user ID associated to an authentication attempt - * @param asyncCallbackHandler a user-implemented instance of {@code AsyncCallbackHandler} which specifies - * how to handle success of failure of authenticate API calls - * @see The docs - */ - void authenticateAsync(String event, String userId, AsyncCallbackHandler asyncCallbackHandler); - - /** - * Makes an async POST request to the authenticate endpoint containing required and optional parameters. - * @param message Event parameters - * @param asyncCallbackHandler a user-implemented instance of {@code AsyncCallbackHandler} which specifies - * how to handle success of failure of authenticate API calls - */ - void authenticateAsync(CastleMessage message, AsyncCallbackHandler asyncCallbackHandler); - /** * Sets the doNotTrack boolean of a new instance of {@code CastleApi} * @@ -130,176 +57,56 @@ public interface CastleApi { */ CastleApi doNotTrack(boolean doNotTrack); - /** - * Makes an async POST request to the track endpoint containing all required parameters. - * - * @param event a String representing an event understood by the Castle API - * @see The docs - */ - void track(String event); - - /** - * Makes an async POST request to the track endpoint containing all required parameters and a user ID. - * - * @param event a String representing an event understood by the Castle API - * @param userId a String representing a user ID - * @see The docs - */ - void track(String event, @Nullable String userId); - - /** - * Makes an async POST request to the track endpoint containing all required parameters and a user ID. - * - * @param event a String representing an event understood by the Castle API - * @param userId a String representing a user ID - * @param reviewId a String representing a reference to review ID - * @see The docs - */ - void track(String event, @Nullable String userId, @Nullable String reviewId); - - /** - * Makes an async POST request to the track endpoint containing required and optional parameters. - * - * @param event a String representing an event understood by the Castle API - * @param userId a String representing a user ID - * @param reviewId a String representing a reference to review ID - * @param properties object for recording additional information connected to the event, takes null - * @see The docs - */ - void track(String event, @Nullable String userId, @Nullable String reviewId, @Nullable Object properties); - - /** - * Makes an async POST request to the track endpoint containing required and optional parameters. - * - * @param event a String representing an event understood by the Castle API - * @param userId a String representing a user ID - * @param reviewId a String representing a reference to review ID - * @param properties object for recording additional information connected to the event, takes null - * @param traits object for recording additional information about the user like email or name, takes null - * @see The docs - */ - void track(String event, @Nullable String userId, @Nullable String reviewId, @Nullable Object properties, @Nullable Object traits); - - /** - * Makes an async POST request to the track endpoint containing required and optional parameters and a custom handler - * for the async call's success and failure cases. - * - * @param event a String representing an event understood by the Castle API - * @param userId a String representing a user ID - * @param reviewId a String representing a reference to review ID - * @param properties object for recording additional information connected to the event, takes null - * @param traits object for recording additional information about the user like email or name, takes null - * @param asyncCallbackHandler a user-implemented instance of {@code AsyncCallbackHandler} which specifies - * how to handle success of failure of authenticate API calls - * @see The docs - */ - void track(String event, @Nullable String userId, @Nullable String reviewId, @Nullable Object properties, @Nullable Object traits, AsyncCallbackHandler asyncCallbackHandler); - - /** - * Makes an async POST request to the track endpoint containing required and optional parameters. - * @param message Event parameters - */ - void track(CastleMessage message); - - JsonElement buildTrackRequest(CastleMessage request); - - void sendTrackRequest(JsonElement request); - - void sendTrackRequest(JsonElement request, AsyncCallbackHandler asyncCallbackHandler); + CastleResponse get(String path); - /** - * Makes an async POST request to the track endpoint containing required and optional parameters. - * @param message Event parameters - * @param asyncCallbackHandler a user-implemented instance of {@code AsyncCallbackHandler} which specifies - * how to handle success of failure of authenticate API calls - */ - void track(CastleMessage message, AsyncCallbackHandler asyncCallbackHandler); + CastleResponse post(String path, Object payload); - /** - * Makes a DELETE request to the privacy endpoint. - * - * @param userId String representing a user id - * @see The docs - */ - Boolean removeUser(String userId); + CastleResponse put(String path); - /** - * Makes a sync POST request to the approve device endpoint. - * - * @param deviceToken string representing the device to approve - * @return device model object - */ - CastleUserDevice approve(String deviceToken); + CastleResponse put(String path, Object payload); - /** - * Makes a sync POST request to the report device endpoint. - * - * @param deviceToken string representing the device to report - * @return device model object - */ - CastleUserDevice report(String deviceToken); + CastleResponse delete(String path); - /** - * Makes a sync GET request to the user devices endpoint. - * - * @param userId user unique ID - * @return devices model object - */ - CastleUserDevices userDevices(String userId); + CastleResponse delete(String path, Object payload); /** - * Makes a sync GET request to the device endpoint. + * Makes a sync POST request to the privacy endpoint to request a user's data. * - * @param deviceToken string representing the device to report - * @return device model object + * @param payload request parameters + * @return a decoded json response */ - CastleUserDevice device(String deviceToken); + CastleResponse requestUserData(ImmutableMap payload); /** - * Makes a sync POST request to the impersonate endpoint. + * Makes a sync DELETE request to the privacy endpoint to delete a user's data. * - * @param userId user unique ID - * @return + * @param payload request parameters + * @return a decoded json response */ - CastleSuccess impersonateStart(String userId); + CastleResponse deleteUserData(ImmutableMap payload); /** - * Makes a sync POST request to the impersonate endpoint. + * Makes a sync GET request to the events schema endpoint. * - * @param userId user unique ID - * @param impersonator description of impersonator, e.g., email - * @return + * @return a decoded json response */ - CastleSuccess impersonateStart(String userId, String impersonator); + CastleResponse eventsSchema(); /** - * Makes a sync DELETE request to the impersonate endpoint. + * Makes a sync POST request to the events query endpoint. * - * @param userId user unique ID - * @return + * @param payload query parameters + * @return a decoded json response */ - CastleSuccess impersonateEnd(String userId); + CastleResponse queryEvents(ImmutableMap payload); /** - * Makes a sync DELETE request to the impersonate endpoint. + * Makes a sync POST request to the events group endpoint. * - * @param userId user unique ID - * @param impersonator description of impersonator, e.g., email - * @return + * @param payload group parameters + * @return a decoded json response */ - CastleSuccess impersonateEnd(String userId, String impersonator); - - CastleResponse get(String path); - - CastleResponse post(String path, Object payload); - - CastleResponse put(String path); - - CastleResponse put(String path, Object payload); - - CastleResponse delete(String path); - - CastleResponse delete(String path, Object payload); + CastleResponse groupEvents(ImmutableMap payload); /** * Makes a sync POST request to the risk endpoint. @@ -451,12 +258,4 @@ public interface CastleApi { * @return */ CastleResponse log(Log payload); - - /** - * Makes a sync PUT request to the recover endpoint. - * - * @param userId User ID - * @return - */ - CastleResponse recover(String userId); } diff --git a/src/main/java/io/castle/client/internal/CastleApiImpl.java b/src/main/java/io/castle/client/internal/CastleApiImpl.java index aaa96527..46c8be31 100644 --- a/src/main/java/io/castle/client/internal/CastleApiImpl.java +++ b/src/main/java/io/castle/client/internal/CastleApiImpl.java @@ -12,7 +12,6 @@ import io.castle.client.internal.utils.CastleContextBuilder; import io.castle.client.internal.utils.ContextMerge; import io.castle.client.internal.utils.Timestamp; -import io.castle.client.internal.utils.VerdictBuilder; import io.castle.client.model.*; import io.castle.client.model.generated.*; import jakarta.servlet.http.HttpServletRequest; @@ -70,258 +69,79 @@ public CastleApi doNotTrack(boolean doNotTrack) { } @Override - public Verdict authenticate(String event, String userId) { - return authenticate(event, userId, null, null); - } - - @Override - public Verdict authenticate(String event, String userId, @Nullable Object properties, @Nullable Object traits) { - return authenticate(buildMessage(event, userId, properties, traits)); - } - - @Override - public Verdict authenticate(CastleMessage message) { - JsonElement request = buildAuthenticateRequest(message); - return sendAuthenticateRequest(request); - } - - @Override - public JsonElement buildAuthenticateRequest(CastleMessage message) { - return buildJson(message); - } - - @Override - public Verdict sendAuthenticateRequest(JsonElement request) { - Preconditions.checkNotNull(request, "Request json can not be null"); - - if (doNotTrack) { - return buildVerdictForDoNotTrack(request.getAsJsonObject().get("user_id").getAsString()); - } - - RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.sendAuthenticateSync(request); - } - - @Override - public void sendAuthenticateRequest(JsonElement request, AsyncCallbackHandler asyncCallbackHandler) { - Preconditions.checkNotNull(request, "Request json can not be null"); - - if (doNotTrack) { - asyncCallbackHandler.onResponse(buildVerdictForDoNotTrack(request.getAsJsonObject().get("user_id").getAsString())); - } else { - Preconditions.checkNotNull(asyncCallbackHandler, "The async handler can not be null"); - RestApi restApi = configuration.getRestApiFactory().buildBackend(); - restApi.sendAuthenticateAsync(request, asyncCallbackHandler); - } - } - - private Verdict buildVerdictForDoNotTrack(String userId) { - return VerdictBuilder.failover("Castle set to do not track.") - .withAction(AuthenticateAction.ALLOW) - .withUserId(userId) - .build(); - } - - @Override - public void authenticateAsync(String event, String userId, @Nullable Object properties, @Nullable Object traits, AsyncCallbackHandler asyncCallbackHandler) { - authenticateAsync( - buildMessage(event, userId, properties, traits), - asyncCallbackHandler - ); - } - - @Override - public void authenticateAsync(String event, String userId, AsyncCallbackHandler asyncCallbackHandler) { - authenticateAsync( - CastleMessage.builder(event).userId(userId).build(), - asyncCallbackHandler - ); - } - - @Override - public void authenticateAsync(CastleMessage message, AsyncCallbackHandler asyncCallbackHandler) { - JsonElement request = buildAuthenticateRequest(message); - sendAuthenticateRequest(request, asyncCallbackHandler); - } - - @Override - public void track(String event) { - track(event, null, null, null, null); - } - - @Override - public void track(String event, String userId) { - track(event, userId, null, null, null); - } - - @Override - public void track(String event, @Nullable String userId, @Nullable String reviewId) { - track(event, userId, reviewId, null, null, null); - } - - @Override - public void track(String event, String userId, String reviewId, Object properties) { - track(event, userId, reviewId, properties, null, null); - } - - @Override - public void track(String event, @Nullable String userId, @Nullable String reviewId, @Nullable Object properties, @Nullable Object traits) { - track(event, userId, reviewId, properties, traits, null); - } - - @Override - public void track(String event, @Nullable String userId, @Nullable String reviewId, @Nullable Object properties, @Nullable Object traits, AsyncCallbackHandler asyncCallbackHandler) { - - CastleMessage message = buildMessage(event, userId, properties, traits); - - if (reviewId != null) { - message.setReviewId(reviewId); - } - - track(message, asyncCallbackHandler); - } - - @Override - public void track(CastleMessage message) { - track(message, null); - } - - @Override - public JsonElement buildTrackRequest(CastleMessage message) { - Preconditions.checkNotNull(message.getEvent()); - return buildJson(message); - } - - @Override - public void sendTrackRequest(JsonElement request) { - sendTrackRequest(request, null); - } - - @Override - public void sendTrackRequest(JsonElement request, AsyncCallbackHandler asyncCallbackHandler) { - Preconditions.checkNotNull(request, "Request json can not be null"); - - if (doNotTrack) { - if (asyncCallbackHandler != null) { - asyncCallbackHandler.onResponse(true); - } - return; - } - - RestApi restApi = configuration.getRestApiFactory().buildBackend(); - restApi.sendTrackRequest(request, asyncCallbackHandler); - } - - @Override - public void track(CastleMessage message, @Nullable AsyncCallbackHandler asyncCallbackHandler) { - JsonElement messageJson = buildTrackRequest(message); - - sendTrackRequest(messageJson, asyncCallbackHandler); - } - - @Override - public Boolean removeUser(String userId) { - Preconditions.checkNotNull(userId); - RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.sendPrivacyRemoveUser(userId); - } - - @Override - public CastleUserDevice approve(String deviceToken) { - Preconditions.checkNotNull(deviceToken); - RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.sendApproveDeviceRequestSync(deviceToken); - } - - @Override - public CastleUserDevice report(String deviceToken) { - Preconditions.checkNotNull(deviceToken); + public CastleResponse get(String path) { RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.sendReportDeviceRequestSync(deviceToken); + return restApi.get(path); } @Override - public CastleUserDevices userDevices(String userId) { - Preconditions.checkNotNull(userId); + public CastleResponse post(String path, Object payload) { RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.sendGetUserDevicesRequestSync(userId); + return restApi.post(path, payload); } @Override - public CastleUserDevice device(String deviceToken) { - Preconditions.checkNotNull(deviceToken); + public CastleResponse put(String path) { RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.sendGetUserDeviceRequestSync(deviceToken); + return restApi.put(path); } @Override - public CastleSuccess impersonateStart(String userId) { - Preconditions.checkNotNull(userId); + public CastleResponse put(String path, Object payload) { RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.sendImpersonateStartRequestSync(userId, null, contextJson); + return restApi.put(path, payload); } @Override - public CastleSuccess impersonateStart(String userId, String impersonator) { - Preconditions.checkNotNull(userId); + public CastleResponse delete(String path) { RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.sendImpersonateStartRequestSync(userId, impersonator, contextJson); + return restApi.delete(path); } @Override - public CastleSuccess impersonateEnd(String userId) { - Preconditions.checkNotNull(userId); + public CastleResponse delete(String path, Object payload) { RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.sendImpersonateEndRequestSync(userId, "", contextJson); + return restApi.delete(path, payload); } - @Override - public CastleSuccess impersonateEnd(String userId, String impersonator) { - Preconditions.checkNotNull(userId); + public CastleResponse risk(ImmutableMap payload) { + Preconditions.checkNotNull(payload); RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.sendImpersonateEndRequestSync(userId, impersonator, contextJson); + return restApi.post(Castle.URL_RISK, payload); } @Override - public CastleResponse get(String path) { + public CastleResponse requestUserData(ImmutableMap payload) { + Preconditions.checkNotNull(payload); RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.get(path); + return restApi.post(Castle.URL_PRIVACY + "users", payload); } @Override - public CastleResponse post(String path, Object payload) { - RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.post(path, payload); - } - - @Override - public CastleResponse put(String path) { + public CastleResponse deleteUserData(ImmutableMap payload) { + Preconditions.checkNotNull(payload); RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.put(path); + return restApi.delete(Castle.URL_PRIVACY + "users", payload); } @Override - public CastleResponse put(String path, Object payload) { + public CastleResponse eventsSchema() { RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.put(path, payload); + return restApi.get(Castle.URL_EVENTS + "/schema"); } @Override - public CastleResponse delete(String path) { + public CastleResponse queryEvents(ImmutableMap payload) { + Preconditions.checkNotNull(payload); RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.delete(path); + return restApi.post(Castle.URL_EVENTS + "/query", payload); } @Override - public CastleResponse delete(String path, Object payload) { - RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.delete(path, payload); - } - - public CastleResponse risk(ImmutableMap payload) { + public CastleResponse groupEvents(ImmutableMap payload) { Preconditions.checkNotNull(payload); RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.post(Castle.URL_RISK, payload); + return restApi.post(Castle.URL_EVENTS + "/group", payload); } @Override @@ -482,55 +302,4 @@ public CastleResponse log(Log payload) { return restApi.post(Castle.URL_LOG, payload); } - @Override - public CastleResponse recover(String userId) { - Preconditions.checkNotNull(userId, "UserId can not be null"); - Preconditions.checkArgument(!userId.isEmpty()); - - RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.put(String.format(Castle.URL_RECOVER, userId)); - } - - private CastleMessage buildMessage(String event, String userId, @Nullable Object properties, @Nullable Object traits) { - CastleMessage message = new CastleMessage(event); - - message.setUserId(userId); - - return setTraitsAndProperties(message, properties, traits); - } - - private CastleMessage setTraitsAndProperties(CastleMessage message, @Nullable Object properties, @Nullable Object traits) { - if (properties != null) { - JsonElement propertiesJson = configuration.getModel().getGson().toJsonTree(properties); - message.setProperties(propertiesJson); - } - - if (traits != null) { - JsonElement traitsJson = configuration.getModel().getGson().toJsonTree(traits); - message.setUserTraits(traitsJson); - } - - return message; - } - - private JsonElement buildJson(CastleMessage message) throws CastleRuntimeException { - JsonObject contextJson; - // Context can be either from the message or from the instance of this - // class. Make sure we have one - CastleContext context = message.getContext(); - if (context == null) { - contextJson = this.contextJson; - } else { - contextJson = configuration.getModel().getGson().toJsonTree(context).getAsJsonObject(); - } - - JsonElement messageJson = configuration.getModel().getGson().toJsonTree(message); - JsonObject messageObj = messageJson.getAsJsonObject(); - messageObj.add("context", contextJson); - - // Add sent_at to json - messageObj.addProperty("sent_at", Timestamp.timestamp()); - - return messageObj; - } } diff --git a/src/main/java/io/castle/client/internal/backend/OkRestApiBackend.java b/src/main/java/io/castle/client/internal/backend/OkRestApiBackend.java index 46c77f6c..ad551005 100644 --- a/src/main/java/io/castle/client/internal/backend/OkRestApiBackend.java +++ b/src/main/java/io/castle/client/internal/backend/OkRestApiBackend.java @@ -1,12 +1,9 @@ package io.castle.client.internal.backend; import com.google.gson.*; -import io.castle.client.Castle; import io.castle.client.internal.config.CastleConfiguration; import io.castle.client.internal.json.CastleGsonModel; import io.castle.client.internal.utils.OkHttpExceptionUtil; -import io.castle.client.internal.utils.VerdictBuilder; -import io.castle.client.internal.utils.VerdictTransportModel; import io.castle.client.model.*; import okhttp3.*; @@ -25,224 +22,15 @@ public class OkRestApiBackend implements RestApi { private final CastleConfiguration configuration; private final HttpUrl baseUrl; - private final HttpUrl track; - private final HttpUrl authenticate; - private final HttpUrl deviceBase; - private final HttpUrl userBase; - private final HttpUrl impersonateBase; - private final HttpUrl privacyBase; public OkRestApiBackend(OkHttpClient client, CastleGsonModel model, CastleConfiguration configuration) { this.baseUrl = HttpUrl.parse(configuration.getApiBaseUrl()); this.client = client; this.model = model; this.configuration = configuration; - this.track = baseUrl.resolve(Castle.URL_TRACK); - this.authenticate = baseUrl.resolve(Castle.URL_AUTHENTICATE); - this.deviceBase = baseUrl.resolve(Castle.URL_DEVICES); - this.userBase = baseUrl.resolve(Castle.URL_USERS); - this.impersonateBase = baseUrl.resolve(Castle.URL_IMPERSONATE); - this.privacyBase = baseUrl.resolve(Castle.URL_PRIVACY); } @Override - public void sendTrackRequest(JsonElement payload, final AsyncCallbackHandler asyncCallbackHandler) { - RequestBody body = buildRequestBody(payload); - Request request = new Request.Builder() - .url(track) - .post(body) - .build(); - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(Call call, IOException e) { - Castle.logger.error("HTTP layer. Error sending track request.", e); - if (asyncCallbackHandler != null) { - asyncCallbackHandler.onException(e); - } - } - - @Override - public void onResponse(Call call, Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - if (asyncCallbackHandler != null) { - asyncCallbackHandler.onResponse(response.isSuccessful()); - } - } - } - }); - } - - @Override - public Verdict sendAuthenticateSync(JsonElement payloadJson) { - final String userId = getUserIdFromPayload(payloadJson); - - RequestBody body = buildRequestBody(payloadJson); - Request request = new Request.Builder() - .url(authenticate) - .post(body) - .build(); - try (Response response = client.newCall(request).execute()) { - return extractAuthenticationAction(response, userId); - } catch (IOException e) { - Castle.logger.error("HTTP layer. Error sending request.", e); - if (configuration.getAuthenticateFailoverStrategy().isThrowTimeoutException()) { - throw OkHttpExceptionUtil.handle(e); - } else { - return VerdictBuilder.failover(e.getMessage()) - .withAction(configuration.getAuthenticateFailoverStrategy().getDefaultAction()) - .withUserId(userId) - .build(); - } - } - } - - @Override - public void sendAuthenticateAsync(JsonElement payloadJson, final AsyncCallbackHandler asyncCallbackHandler) { - final String userId = getUserIdFromPayload(payloadJson); - - RequestBody body = buildRequestBody(payloadJson); - Request request = new Request.Builder() - .url(authenticate) - .post(body) - .build(); - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(Call call, IOException e) { - if (configuration.getAuthenticateFailoverStrategy().isThrowTimeoutException()) { - asyncCallbackHandler.onException(OkHttpExceptionUtil.handle(e)); - } else { - asyncCallbackHandler.onResponse( - VerdictBuilder.failover(e.getMessage()) - .withAction(configuration.getAuthenticateFailoverStrategy().getDefaultAction()) - .withUserId(userId) - .build() - ); - } - } - - @Override - public void onResponse(Call call, Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - asyncCallbackHandler.onResponse(extractAuthenticationAction(response, userId)); - } - } - }); - } - - private String getUserIdFromPayload(JsonElement payloadJson) { - final String userId = ((JsonObject) payloadJson).has("user_id") ? ((JsonObject) payloadJson).get("user_id").getAsString() : null; - if (userId == null) { - Castle.logger.warn("Authenticate called with user_id null. Is this correct?"); - } - return userId; - } - - private RequestBody buildRequestBody(JsonElement payloadJson) { - JsonObject json = payloadJson.getAsJsonObject(); - return RequestBody.create(json.toString(), JSON); - } - - private Verdict extractAuthenticationAction(Response response, String userId) throws IOException { - String errorReason = response.message(); - String jsonResponse = response.body().string(); - - if (response.isSuccessful()) { - Gson gson = model.getGson(); - VerdictTransportModel transport = gson.fromJson(jsonResponse, VerdictTransportModel.class); - if (transport != null && transport.getAction() != null) { - return VerdictBuilder.fromTransport(transport, JsonParser.parseString(jsonResponse)); - } else { - errorReason = "Invalid JSON in response"; - } - } - - if (response.code() >= 500) { - //Use failover for error backends calls. - if (!configuration.getAuthenticateFailoverStrategy().isThrowTimeoutException()) { - Verdict verdict = VerdictBuilder.failover(errorReason) - .withAction(configuration.getAuthenticateFailoverStrategy().getDefaultAction()) - .withUserId(userId) - .build(); - return verdict; - } else { - throw new CastleApiInternalServerErrorException(response); - } - } - - // Could not extract Verdict, so fail for client logic space. - throw new CastleRuntimeException(response); - } - - @Override - public Boolean sendPrivacyRemoveUser(String userId) { - Request request = createPrivacyRemoveRequest(userId); - try (Response response = client.newCall(request).execute()) { - return extractResponse(response); - } catch (IOException e) { - throw OkHttpExceptionUtil.handle(e); - } - } - - @Override - public CastleUserDevice sendApproveDeviceRequestSync(String deviceToken) { - Request request = createApproveDeviceRequest(deviceToken); - try (Response response = client.newCall(request).execute()) { - return extractDevice(response); - } catch (IOException e) { - throw OkHttpExceptionUtil.handle(e); - } - } - - @Override - public CastleUserDevice sendReportDeviceRequestSync(String deviceToken) { - Request request = createReportDeviceRequest(deviceToken); - try (Response response = client.newCall(request).execute()) { - return extractDevice(response); - } catch (IOException e) { - throw OkHttpExceptionUtil.handle(e); - } - } - - @Override - public CastleUserDevices sendGetUserDevicesRequestSync(String userId) { - Request request = createGetUserDevicesRequest(userId); - try (Response response = client.newCall(request).execute()) { - return extractDevices(response); - } catch (IOException e) { - throw OkHttpExceptionUtil.handle(e); - } - } - - @Override - public CastleUserDevice sendGetUserDeviceRequestSync(String deviceToken) { - Request request = createGetUserDeviceRequest(deviceToken); - try (Response response = client.newCall(request).execute()) { - return extractDevice(response); - } catch (IOException e) { - throw OkHttpExceptionUtil.handle(e); - } - } - - @Override - public CastleSuccess sendImpersonateStartRequestSync(String userId, String impersonator, JsonObject contextJson) { - Request request = createImpersonateStartRequest(userId, impersonator, contextJson); - try (Response response = client.newCall(request).execute()) { - return extractSuccess(response); - } catch (IOException e) { - throw OkHttpExceptionUtil.handle(e); - } - } - - @Override - public CastleSuccess sendImpersonateEndRequestSync(String userId, String impersonator, JsonObject contextJson) { - Request request = createImpersonateEndRequest(userId, impersonator, contextJson); - try (Response response = client.newCall(request).execute()) { - return extractSuccess(response); - } catch (IOException e) { - throw OkHttpExceptionUtil.handle(e); - } - } - public CastleResponse get(String path) { return makeRequest(path, null, METHOD_GET); } @@ -302,118 +90,6 @@ private CastleResponse makeRequest(String path, JsonElement payload, String meth } } - private CastleUserDevice extractDevice(Response response) throws IOException { - return (CastleUserDevice) extract(response, CastleUserDevice.class); - } - - private CastleUserDevices extractDevices(Response response) throws IOException { - return (CastleUserDevices) extract(response, CastleUserDevices.class); - } - - private Object extract(Response response, Class clazz) throws IOException { - if (response.isSuccessful()) { - String jsonResponse = response.body().string(); - Gson gson = model.getGson(); - return gson.fromJson(jsonResponse, clazz); - } else if (response.code() == 404) { - return null; - } - OkHttpExceptionUtil.handle(response); - return null; - } - - private CastleSuccess extractSuccess(Response response) throws IOException { - if (response.isSuccessful()) { - if (response.body() != null) { - String jsonResponse = response.body().string(); - Gson gson = model.getGson(); - return gson.fromJson(jsonResponse, CastleSuccess.class); - } - } - OkHttpExceptionUtil.handle(response); - return null; - } - - private Boolean extractResponse(Response response) { - if (response.isSuccessful()) { - return true; - } else if (response.code() == 404) { - return null; - } - OkHttpExceptionUtil.handle(response); - return false; - } - - private CastleUser extractUser(Response response) throws IOException { - return (CastleUser) extract(response, CastleUser.class); - } - - private Request createApproveDeviceRequest(String deviceToken) { - HttpUrl approveDeviceUrl = deviceBase.resolve(deviceToken + "/approve"); - return new Request.Builder() - .url(approveDeviceUrl) - .put(createEmptyRequestBody()) - .build(); - } - - private Request createReportDeviceRequest(String deviceToken) { - HttpUrl reportDeviceUrl = deviceBase.resolve(deviceToken + "/report"); - return new Request.Builder() - .url(reportDeviceUrl) - .put(createEmptyRequestBody()) - .build(); - } - - private Request createGetUserDevicesRequest(String userId) { - HttpUrl getUserDevicesUrl = userBase.resolve(userId + "/devices"); - return new Request.Builder() - .url(getUserDevicesUrl) - .get() - .build(); - } - - private Request createGetUserDeviceRequest(String deviceToken) { - HttpUrl getUserDeviceUrl = deviceBase.resolve(deviceToken); - return new Request.Builder() - .url(getUserDeviceUrl) - .get() - .build(); - } - - private Request createImpersonateStartRequest(String userId, String impersonator, JsonObject contextJson) { - HttpUrl impersonateUrl = impersonateBase; - - ImpersonatePayload payload = new ImpersonatePayload(userId, impersonator, contextJson); - - RequestBody body = RequestBody.create(model.getGson().toJson(payload), JSON); - - return new Request.Builder() - .url(impersonateUrl) - .post(body) - .build(); - } - - private Request createImpersonateEndRequest(String userId, String impersonator, JsonObject contextJson) { - HttpUrl impersonateUrl = impersonateBase; - - ImpersonatePayload payload = new ImpersonatePayload(userId, impersonator, contextJson); - - RequestBody body = RequestBody.create(model.getGson().toJson(payload), JSON); - - return new Request.Builder() - .url(impersonateUrl) - .delete(body) - .build(); - } - - private Request createPrivacyRemoveRequest(String userId) { - HttpUrl privacyRemoveUrl = privacyBase.resolve("users/" + userId); - return new Request.Builder() - .url(privacyRemoveUrl) - .delete() - .build(); - } - private RequestBody createEmptyRequestBody() { return RequestBody.create(new byte[0], null); } diff --git a/src/main/java/io/castle/client/internal/backend/RestApi.java b/src/main/java/io/castle/client/internal/backend/RestApi.java index 1c19d081..2c740caa 100644 --- a/src/main/java/io/castle/client/internal/backend/RestApi.java +++ b/src/main/java/io/castle/client/internal/backend/RestApi.java @@ -1,91 +1,9 @@ package io.castle.client.internal.backend; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import io.castle.client.model.*; public interface RestApi { - /** - * - * @param payloadJson JSON object containing the event properties - * @param asyncCallbackHandler callback to inform if request was correctly sent - */ - void sendTrackRequest(JsonElement payloadJson, AsyncCallbackHandler asyncCallbackHandler); - - /** - * - * @param payloadJson JSON object containing the event properties - * @return Verdict to be used in login logic - */ - Verdict sendAuthenticateSync(JsonElement payloadJson); - - /** - * - * @param payloadJson JSON object containing the event properties - * @param asyncCallbackHandler callback to inform if request was correctly sent - */ - void sendAuthenticateAsync(JsonElement payloadJson, AsyncCallbackHandler asyncCallbackHandler); - - /** - * Remove user from Castle (GDPR reasons) - * @see The docs - * @param userId user id to be removed - */ - Boolean sendPrivacyRemoveUser(String userId); - - /** - * Sync call to the approve device endpoint. - * - * @param deviceToken string representing the token for the device to get - * @return a {@code device} with metadata contained in the body of the response - */ - CastleUserDevice sendApproveDeviceRequestSync(String deviceToken); - - /** - * Sync call to the report device endpoint. - * - * @param deviceToken string representing the token for the device to get - * @return a {@code device} with metadata contained in the body of the response - */ - CastleUserDevice sendReportDeviceRequestSync(String deviceToken); - - /** - * Sync call to the devices endpoint. - * - * @param userId string representing the user to get devices for - * @return a {@code devices} with metadata contained in the body of the response - */ - CastleUserDevices sendGetUserDevicesRequestSync(String userId); - - /** - * Sync call to the device endpoint. - * - * @param deviceToken string representing the token for the device to get - * @return a {@code device} with metadata contained in the body of the response - */ - CastleUserDevice sendGetUserDeviceRequestSync(String deviceToken); - - /** - * Sync call to the impersonate endpoint. - * - * @param userId id of the user to impersonate - * @param impersonator id of the user doing the impersonation - * @param contextJson context json - * @return a success message - */ - CastleSuccess sendImpersonateStartRequestSync(String userId, String impersonator, JsonObject contextJson); - - /** - * Sync call to the impersonate endpoint. - * - * @param userId id of the user to stop impersonating - * @param impersonator id of the user doing the impersonation - * @param contextJson context json - * @return a success message - */ - CastleSuccess sendImpersonateEndRequestSync(String userId, String impersonator, JsonObject contextJson); - /** * Make a GET request to a Castle API endpoint such as /v1/{userId}/devices * diff --git a/src/main/java/io/castle/client/internal/config/CastleConfiguration.java b/src/main/java/io/castle/client/internal/config/CastleConfiguration.java index d4b5f73d..dd884b9e 100644 --- a/src/main/java/io/castle/client/internal/config/CastleConfiguration.java +++ b/src/main/java/io/castle/client/internal/config/CastleConfiguration.java @@ -1,7 +1,6 @@ package io.castle.client.internal.config; import io.castle.client.internal.backend.CastleBackendProvider; -import io.castle.client.model.AuthenticateFailoverStrategy; import io.castle.client.model.CastleRuntimeException; import java.util.List; @@ -24,11 +23,6 @@ public class CastleConfiguration { */ private final int timeout; - /** - * Strategy for returning a {@code verdict} when an authenticate call fails. - */ - private final AuthenticateFailoverStrategy authenticateFailoverStrategy; - /** * List of headers that will get passed to the {@code CastleContext} unless they are denyListed. */ @@ -66,10 +60,9 @@ public class CastleConfiguration { */ private final int maxRequests; - public CastleConfiguration(String apiBaseUrl, int timeout, AuthenticateFailoverStrategy authenticateFailoverStrategy, List allowListHeaders, List denyListHeaders, String apiSecret, String castleAppId, CastleBackendProvider backendProvider, boolean logHttpRequests, List ipHeaders, Integer maxRequests) { + public CastleConfiguration(String apiBaseUrl, int timeout, List allowListHeaders, List denyListHeaders, String apiSecret, String castleAppId, CastleBackendProvider backendProvider, boolean logHttpRequests, List ipHeaders, Integer maxRequests) { this.apiBaseUrl = apiBaseUrl; this.timeout = timeout; - this.authenticateFailoverStrategy = authenticateFailoverStrategy; this.allowListHeaders = allowListHeaders; this.denyListHeaders = denyListHeaders; this.apiSecret = apiSecret; @@ -88,10 +81,6 @@ public int getTimeout() { return timeout; } - public AuthenticateFailoverStrategy getAuthenticateFailoverStrategy() { - return authenticateFailoverStrategy; - } - public List getAllowListHeaders() { return allowListHeaders; } diff --git a/src/main/java/io/castle/client/internal/config/CastleConfigurationBuilder.java b/src/main/java/io/castle/client/internal/config/CastleConfigurationBuilder.java index ab177de1..43823078 100644 --- a/src/main/java/io/castle/client/internal/config/CastleConfigurationBuilder.java +++ b/src/main/java/io/castle/client/internal/config/CastleConfigurationBuilder.java @@ -4,8 +4,6 @@ import com.google.common.collect.ImmutableList; import io.castle.client.internal.backend.CastleBackendProvider; import io.castle.client.internal.utils.HeaderNormalizer; -import io.castle.client.model.AuthenticateAction; -import io.castle.client.model.AuthenticateFailoverStrategy; import io.castle.client.model.CastleSdkConfigurationException; import java.util.LinkedList; @@ -19,7 +17,6 @@ * The fields that can be set in a CastleConfiguration: *

    *
  • timeout - *
  • failoverStrategy *
  • allowListHeaders *
  • denyListHeaders *
  • apiSecret @@ -42,11 +39,6 @@ public class CastleConfigurationBuilder { */ private int timeout = 500; - /** - * Strategy used when an authenticate call to the Castle API fails. - */ - private AuthenticateFailoverStrategy failoverStrategy; - /** * Strings representing headers that should be passed to the context object unless they are also denyListed. */ @@ -111,7 +103,6 @@ public static CastleConfigurationBuilder defaultConfigBuilder() { .withDefaultDenyList() .withDefaultApiBaseUrl() .withTimeout(500) - .withDefaultAuthenticateFailoverStrategy() .withDefaultBackendProvider() .withMaxRequests(5); return builder; @@ -163,29 +154,6 @@ public CastleConfigurationBuilder withTimeout(int timeout) { return this; } - /** - * Establishes the authentication strategy that will be used in case of a timeout when performing a - * {@code CastleApi#authenticate} call. - * - * @param failoverStrategy strategy to use for failed authenticate API calls; not null. - * @return a castleConfigurationBuilder with the chosen AuthenticationStrategy set - */ - public CastleConfigurationBuilder withAuthenticateFailoverStrategy(AuthenticateFailoverStrategy failoverStrategy) { - this.failoverStrategy = failoverStrategy; - return this; - } - - /** - * Sets the failover strategy for the authenticate Castle API call to allow. - *

    - * The authenticate failover strategy for the default configuration is to return {@link AuthenticateAction#ALLOW}. - * - * @return a castleConfigurationBuilder with allow as the authenticate failover strategy - */ - public CastleConfigurationBuilder withDefaultAuthenticateFailoverStrategy() { - return this.withAuthenticateFailoverStrategy(new AuthenticateFailoverStrategy(AuthenticateAction.ALLOW)); - } - /** * Sets the endpoint of the Castle API to its default value. *

    @@ -292,7 +260,7 @@ public CastleConfigurationBuilder apiSecret(String apiSecret) { * * @return a castleConfiguration with all fields set to some meaningful value * @throws CastleSdkConfigurationException if at least one of castleAppId, apiSecret, allowListHeaders, - * denyListHeaders, failoverStrategy, backendProvider is not provided + * denyListHeaders, backendProvider is not provided * during the building stage of the * CastleConfiguration instance. */ @@ -310,10 +278,6 @@ public CastleConfiguration build() throws CastleSdkConfigurationException { builder.add("A denyList of headers must be provided. If not sure, then use the default values provided " + "by method withDefaultDenyList. Read documentation for further details."); } - if (failoverStrategy == null) { - builder.add("A failover strategy must be provided. If not sure, then use the default values provided " + - "by method withDefaultAuthenticateFailoverStrategy. Read documentation for further details."); - } if (backendProvider == null) { builder.add("A backend provider must be selected. If not sure, then use the default values provided " + "by method withDefaultBackendProvider. Read documentation for further details."); @@ -328,7 +292,6 @@ public CastleConfiguration build() throws CastleSdkConfigurationException { HeaderNormalizer normalizer = new HeaderNormalizer(); return new CastleConfiguration(apiBaseUrl, timeout, - failoverStrategy, normalizer.normalizeList(allowListHeaders), normalizer.normalizeList(denyListHeaders), apiSecret, diff --git a/src/main/java/io/castle/client/internal/config/ConfigurationLoader.java b/src/main/java/io/castle/client/internal/config/ConfigurationLoader.java index 30513ab0..e279c51a 100644 --- a/src/main/java/io/castle/client/internal/config/ConfigurationLoader.java +++ b/src/main/java/io/castle/client/internal/config/ConfigurationLoader.java @@ -3,8 +3,6 @@ import com.google.common.base.Splitter; import io.castle.client.Castle; import io.castle.client.internal.backend.CastleBackendProvider; -import io.castle.client.model.AuthenticateAction; -import io.castle.client.model.AuthenticateFailoverStrategy; import io.castle.client.model.CastleSdkConfigurationException; import java.io.InputStream; @@ -130,11 +128,6 @@ public CastleConfigurationBuilder loadConfigurationBuilder() { "backend_provider", "CASTLE_SDK_BACKEND_PROVIDER" ); - String authenticateFailoverStrategyValue = loadConfigurationValue( - castleConfigurationProperties, - "failover_strategy", - "CASTLE_SDK_AUTHENTICATE_FAILOVER_STRATEGY" - ); String apiBaseUrl = loadConfigurationValue( castleConfigurationProperties, "base_url", @@ -170,19 +163,6 @@ public CastleConfigurationBuilder loadConfigurationBuilder() { int timeout = Integer.parseInt(timeoutValue); builder.withTimeout(timeout); } - if (authenticateFailoverStrategyValue != null) { - if (authenticateFailoverStrategyValue.compareTo("throw") == 0) { - builder.withAuthenticateFailoverStrategy(new AuthenticateFailoverStrategy()); - } else { - builder.withAuthenticateFailoverStrategy( - new AuthenticateFailoverStrategy( - AuthenticateAction.fromAction(authenticateFailoverStrategyValue) - ) - ); - } - } else { - builder.withDefaultAuthenticateFailoverStrategy(); - } if (backendProviderValue != null) { builder.withBackendProvider( CastleBackendProvider.valueOf(backendProviderValue) diff --git a/src/main/java/io/castle/client/internal/json/AuthenticateActionDeserializer.java b/src/main/java/io/castle/client/internal/json/AuthenticateActionDeserializer.java deleted file mode 100644 index 05837ac4..00000000 --- a/src/main/java/io/castle/client/internal/json/AuthenticateActionDeserializer.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.castle.client.internal.json; - -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; -import io.castle.client.model.AuthenticateAction; - -import java.lang.reflect.Type; - -public class AuthenticateActionDeserializer implements JsonDeserializer { - @Override - public AuthenticateAction deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - return AuthenticateAction.fromAction(json.getAsString()); - } -} diff --git a/src/main/java/io/castle/client/internal/json/CastleGsonModel.java b/src/main/java/io/castle/client/internal/json/CastleGsonModel.java index ff0b65a0..b25a5967 100644 --- a/src/main/java/io/castle/client/internal/json/CastleGsonModel.java +++ b/src/main/java/io/castle/client/internal/json/CastleGsonModel.java @@ -4,10 +4,8 @@ import com.google.gson.internal.bind.util.ISO8601Utils; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; -import io.castle.client.model.AuthenticateAction; import io.castle.client.model.RiskPolicyType; import io.castle.client.model.CastleHeaders; -import io.castle.client.model.CastleMessage; import io.castle.client.model.generated.BaseChangesetEntry; import okio.ByteString; import org.threeten.bp.LocalDate; @@ -27,9 +25,7 @@ public class CastleGsonModel { public CastleGsonModel() { GsonBuilder builder = createGsonBuilder(); builder.registerTypeAdapter(CastleHeaders.class, new CastleHeadersSerializer()); - builder.registerTypeAdapter(CastleMessage.class, new CastleMessageSerializer()); builder.registerTypeAdapter(CastleHeaders.class, new CastleHeadersDeserializer()); - builder.registerTypeAdapter(AuthenticateAction.class, new AuthenticateActionDeserializer()); builder.registerTypeAdapter(RiskPolicyType.class, new RiskPolicyTypeDeserializer()); builder.registerTypeAdapterFactory(ChangesetEntryTypeAdapter.FACTORY); builder.registerTypeAdapter(BaseChangesetEntry.class, new BaseChangesetEntryDeserializer()); diff --git a/src/main/java/io/castle/client/internal/json/CastleMessageSerializer.java b/src/main/java/io/castle/client/internal/json/CastleMessageSerializer.java deleted file mode 100644 index 51d819b3..00000000 --- a/src/main/java/io/castle/client/internal/json/CastleMessageSerializer.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.castle.client.internal.json; - -import com.google.gson.JsonElement; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; -import com.google.gson.Gson; - -import io.castle.client.internal.utils.ContextMerge; -import io.castle.client.model.CastleMessage; - -import java.lang.reflect.Type; -import java.util.HashMap; - -public class CastleMessageSerializer implements JsonSerializer { - - private final Gson gson = CastleGsonModel.createGsonBuilder().create(); - - @Override - public JsonElement serialize(CastleMessage message, Type typeOfSrc, JsonSerializationContext context) { - JsonElement root = gson.toJsonTree(message); - HashMap other = message.getOther(); - if (other == null) { - return root; - } - JsonElement otherJson = context.serialize(message.getOther()); - ContextMerge merger = new ContextMerge(); - return merger.merge(root.getAsJsonObject(), otherJson.getAsJsonObject()); - } -} diff --git a/src/main/java/io/castle/client/internal/utils/VerdictBuilder.java b/src/main/java/io/castle/client/internal/utils/VerdictBuilder.java deleted file mode 100644 index df775030..00000000 --- a/src/main/java/io/castle/client/internal/utils/VerdictBuilder.java +++ /dev/null @@ -1,97 +0,0 @@ -package io.castle.client.internal.utils; - -import com.google.gson.JsonElement; -import io.castle.client.model.AuthenticateAction; -import io.castle.client.model.RiskPolicyResult; -import io.castle.client.model.Verdict; - -public class VerdictBuilder { - private AuthenticateAction action; - private RiskPolicyResult riskPolicy; - private String userId; - private boolean failover; - private String failoverReason; - private String deviceToken; - private JsonElement internal; - private float risk; - - private VerdictBuilder() { - } - - public static VerdictBuilder success() { - return new VerdictBuilder() - .withFailover(false); - } - - public static VerdictBuilder failover(String failoverReason) { - return new VerdictBuilder() - .withFailover(true) - .withFailoverReason(failoverReason); - } - - public VerdictBuilder withAction(AuthenticateAction action) { - this.action = action; - return this; - } - - public VerdictBuilder withUserId(String userId) { - this.userId = userId; - return this; - } - - - public VerdictBuilder withRiskPolicy(RiskPolicyResult riskPolicy) { - this.riskPolicy = riskPolicy; - return this; - } - - public VerdictBuilder withDeviceToken(final String deviceToken) { - this.deviceToken = deviceToken; - return this; - } - - public VerdictBuilder withRisk(final float risk) { - this.risk = risk; - return this; - } - - public Verdict build() { - Verdict verdict = new Verdict(); - verdict.setAction(action); - verdict.setUserId(userId); - verdict.setFailover(failover); - verdict.setFailoverReason(failoverReason); - verdict.setDeviceToken(deviceToken); - verdict.setRiskPolicy(riskPolicy); - verdict.setInternal(internal); - verdict.setRisk(risk); - return verdict; - } - - public VerdictBuilder withFailover(boolean failover) { - this.failover = failover; - return this; - } - - public VerdictBuilder withFailoverReason(String failoverReason) { - this.failoverReason = failoverReason; - return this; - } - - public VerdictBuilder withInternal(JsonElement internal) { - this.internal = internal; - return this; - } - - public static Verdict fromTransport(VerdictTransportModel transport, JsonElement internal) { - internal.getAsJsonObject().get("action").getAsString(); - return success() - .withAction(transport.getAction()) - .withUserId(transport.getUserId()) - .withDeviceToken(transport.getDeviceToken()) - .withRiskPolicy(transport.getRiskPolicy()) - .withRisk(transport.getRisk()) - .withInternal(internal) - .build(); - } -} diff --git a/src/main/java/io/castle/client/internal/utils/VerdictTransportModel.java b/src/main/java/io/castle/client/internal/utils/VerdictTransportModel.java deleted file mode 100644 index 07dab519..00000000 --- a/src/main/java/io/castle/client/internal/utils/VerdictTransportModel.java +++ /dev/null @@ -1,50 +0,0 @@ -package io.castle.client.internal.utils; - -import io.castle.client.model.AuthenticateAction; -import io.castle.client.model.RiskPolicyResult; - -public class VerdictTransportModel { - - private AuthenticateAction action; - private RiskPolicyResult riskPolicy; - private String userId; - private String deviceToken; - private float risk; - - public AuthenticateAction getAction() { - return action; - } - - public void setAction(AuthenticateAction action) { - this.action = action; - } - - public RiskPolicyResult getRiskPolicy() { - return riskPolicy; - } - - public void setRiskPolicy(RiskPolicyResult riskPolicy) { - this.riskPolicy = riskPolicy; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public String getDeviceToken() { - return deviceToken; - } - - public float getRisk() { - return risk; - } - - public void setRisk(float risk) { - this.risk = risk; - } - -} diff --git a/src/main/java/io/castle/client/internal/utils/Webhook.java b/src/main/java/io/castle/client/internal/utils/Webhook.java new file mode 100644 index 00000000..9c6ebf3c --- /dev/null +++ b/src/main/java/io/castle/client/internal/utils/Webhook.java @@ -0,0 +1,56 @@ +package io.castle.client.internal.utils; + +import com.google.common.base.Charsets; +import com.google.common.hash.Hashing; +import com.google.common.io.BaseEncoding; + +import java.security.MessageDigest; + +/** + * Helper for verifying Castle webhook signatures. + *

    + * Castle signs every webhook with an HMAC-SHA256 of the raw request body using the + * account API secret. The signature is base64 encoded and delivered in the + * {@code X-Castle-Signature} header. + */ +public final class Webhook { + + private Webhook() { + } + + /** + * Computes the base64 encoded HMAC-SHA256 signature for the given body. + * + * @param secret the account API secret + * @param body the raw request body bytes + * @return the base64 encoded signature + */ + public static String computeSignature(String secret, byte[] body) { + byte[] safeBody = body != null ? body : new byte[0]; + byte[] hmac = Hashing.hmacSha256(secret.getBytes(Charsets.UTF_8)) + .hashBytes(safeBody) + .asBytes(); + return BaseEncoding.base64().encode(hmac); + } + + /** + * Verifies that the supplied signature matches the computed signature for the body. + *

    + * The comparison is done in constant time to avoid timing attacks. + * + * @param secret the account API secret + * @param body the raw request body bytes + * @param signature the signature received in the {@code X-Castle-Signature} header + * @return {@code true} when the signature matches + */ + public static boolean verifySignature(String secret, byte[] body, String signature) { + if (secret == null || signature == null) { + return false; + } + String expected = computeSignature(secret, body); + return MessageDigest.isEqual( + expected.getBytes(Charsets.UTF_8), + signature.getBytes(Charsets.UTF_8) + ); + } +} diff --git a/src/main/java/io/castle/client/model/AsyncCallbackHandler.java b/src/main/java/io/castle/client/model/AsyncCallbackHandler.java deleted file mode 100644 index 0286db15..00000000 --- a/src/main/java/io/castle/client/model/AsyncCallbackHandler.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.castle.client.model; - -/** - * Callback interface used to handle async requests responses. - *

    - * An implementation of this interface allows the user to handle async request success and failure cases in a custom manner. - * - * @param The type of the internal response after execution. - */ -public interface AsyncCallbackHandler { - void onResponse(T response); - - void onException(Exception exception); -} diff --git a/src/main/java/io/castle/client/model/AuthenticateAction.java b/src/main/java/io/castle/client/model/AuthenticateAction.java deleted file mode 100644 index 7839c701..00000000 --- a/src/main/java/io/castle/client/model/AuthenticateAction.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.castle.client.model; - -/** - * Action that needs to be taken after a login attempt. - *

    - * See the documentation for the semantics of each case. - * It can be null. - * - * @see Adaptive authentication - */ -public enum AuthenticateAction { - ALLOW, DENY, CHALLENGE; - - /** - * Returns an AuthenticateAction from a string representing its name. - * - * @param action string representing the name of the AuthenticateAction, case-insensitive - * @return the enum value matching the name, or null if it does not match any enum - */ - public static AuthenticateAction fromAction(String action) { - for (AuthenticateAction kind : AuthenticateAction.class.getEnumConstants()) { - if (kind.name().equalsIgnoreCase(action)) { - return kind; - } - } - return null; - } -} diff --git a/src/main/java/io/castle/client/model/AuthenticateFailoverStrategy.java b/src/main/java/io/castle/client/model/AuthenticateFailoverStrategy.java deleted file mode 100644 index 294b4600..00000000 --- a/src/main/java/io/castle/client/model/AuthenticateFailoverStrategy.java +++ /dev/null @@ -1,57 +0,0 @@ -package io.castle.client.model; - -import io.castle.client.api.CastleApi; - -/** - * Stores settings for either using an AuthenticationAction or throwing a TimeoutException, - * if a Castle API authenticate call fails. - *

    - * If a {@link CastleApi#authenticate} call fails during an authentication attempt, the failover strategy specifies - * which {@link AuthenticateAction} should be used. - * Alternatively, a TimeoutException instance could be configured. - */ -public class AuthenticateFailoverStrategy { - - private final AuthenticateAction defaultAction; - private final boolean throwTimeoutException; - - private AuthenticateFailoverStrategy(AuthenticateAction defaultAction, boolean throwTimeoutException) { - this.defaultAction = defaultAction; - this.throwTimeoutException = throwTimeoutException; - } - - /** - * Sets the default authenticationAction for failed requests. - * - * @param action the authenticateAction that will be used as a default for failed requests - */ - public AuthenticateFailoverStrategy(AuthenticateAction action) { - this(action, false); - } - - /** - * Creates an authenticationStrategy whose policy is to throw a timeoutException. - */ - public AuthenticateFailoverStrategy() { - this(null, true); - } - - - /** - * Gets the authenticateAction that this strategy uses for failover. - * - * @return the authenticateAction configured as default; null if there is no default AuthenticateAction - */ - public AuthenticateAction getDefaultAction() { - return defaultAction; - } - - /** - * Checks whether the failover strategy is configured to throw a timeoutException. - * - * @return true if the failover strategy is set to throw a timeoutException, false otherwise - */ - public boolean isThrowTimeoutException() { - return throwTimeoutException; - } -} diff --git a/src/main/java/io/castle/client/model/CastleMessage.java b/src/main/java/io/castle/client/model/CastleMessage.java deleted file mode 100644 index ec7ba32d..00000000 --- a/src/main/java/io/castle/client/model/CastleMessage.java +++ /dev/null @@ -1,198 +0,0 @@ -package io.castle.client.model; - -import java.util.HashMap; - -import javax.annotation.Nonnull; - -public class CastleMessage { - private transient CastleContext context; - private String createdAt; - private String timestamp; - private String deviceToken; - @Nonnull private String event; - /** - * Collect other attributes that haven't not explicit setters to accommodate for - * future parameters - */ - private transient HashMap other; - private Object properties; - private String reviewId; - private String userId; - private Object userTraits; - - /** - * Initialize a new request payload message - * @param event Name of the event to send - */ - public CastleMessage(String event) { - setEvent(event); - } - - public CastleContext getContext() { - return context; - } - - public void setContext(CastleContext context) { - this.context = context; - } - - /** - * @deprecated use {@link #getTimestamp()} instead. - */ - @Deprecated - public String getCreatedAt() { - return createdAt; - } - - /** - * @deprecated use {@link #setTimestamp(String)} instead. - */ - @Deprecated - public void setCreatedAt(String createdAt) { - this.createdAt = createdAt; - } - - public String getTimestamp() { - return timestamp; - } - - public void setTimestamp(String timestamp) { - this.timestamp = timestamp; - } - - public String getDeviceToken() { - return deviceToken; - } - - public void setDeviceToken(String deviceToken) { - this.deviceToken = deviceToken; - } - - @Nonnull - public String getEvent() { - return event; - } - - public void setEvent(@Nonnull String event) { - this.event = event; - } - - public HashMap getOther() { - if (other == null) { - this.other = new HashMap(); - } - return other; - } - - public void setOther(HashMap other) { - this.other = other; - } - - public Object getProperties() { - return properties; - } - - public void setProperties(Object properties) { - this.properties = properties; - } - - public String getReviewId() { - return reviewId; - } - - public void setReviewId(String reviewId) { - this.reviewId = reviewId; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public Object getUserTraits() { - return userTraits; - } - - public void setUserTraits(Object userTraits) { - this.userTraits = userTraits; - } - - public static Builder builder(String event) { - return new Builder(new CastleMessage(event)); - } - - public static class Builder { - private CastleMessage payload; - - public Builder(CastleMessage payload) { - this.payload = payload; - } - - public CastleMessage build() { - return payload; - } - - public Builder context(CastleContext context) { - payload.setContext(context); - return this; - } - - /** - * @deprecated use {@link #timestamp(String)} instead. - */ - @Deprecated - public Builder createdAt(String createdAt) { - payload.setCreatedAt(createdAt); - return this; - } - - public Builder timestamp(String timestamp) { - payload.setTimestamp(timestamp); - return this; - } - - public Builder deviceToken(String deviceToken) { - payload.setDeviceToken(deviceToken); - return this; - } - - public Builder reviewId(String reviewId) { - payload.setReviewId(reviewId); - return this; - } - - public Builder properties(Object properties) { - if (properties == null) { - throw new NullPointerException("Null properties"); - } - payload.setProperties(properties); - return this; - } - - public Builder other(HashMap other) { - payload.setOther(other); - return this; - } - - public Builder put(String key, Object value) { - payload.getOther().put(key, value); - return this; - } - - public Builder userId(String userId) { - payload.setUserId(userId); - return this; - } - - public Builder userTraits(Object userTraits) { - if (userTraits == null) { - throw new NullPointerException("Null userTraits"); - } - payload.setUserTraits(userTraits); - return this; - } - } -} diff --git a/src/main/java/io/castle/client/model/CastleSuccess.java b/src/main/java/io/castle/client/model/CastleSuccess.java deleted file mode 100644 index 060999d0..00000000 --- a/src/main/java/io/castle/client/model/CastleSuccess.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.castle.client.model; - -public class CastleSuccess { - boolean success; - - public boolean isSuccess() { - return success; - } - - public void setSuccess(boolean success) { - this.success = success; - } -} diff --git a/src/main/java/io/castle/client/model/CastleUserDevice.java b/src/main/java/io/castle/client/model/CastleUserDevice.java deleted file mode 100644 index 6e751272..00000000 --- a/src/main/java/io/castle/client/model/CastleUserDevice.java +++ /dev/null @@ -1,89 +0,0 @@ -package io.castle.client.model; - -/** - * Model of the device object returned in the body of the response of a device call to the Castle Api. - */ -public class CastleUserDevice { - private String token; - private double risk; - private String createdAt; - private String lastSeenAt; - private String approvedAt; - private String escalatedAt; - private String mitigatedAt; - private CastleUserDeviceContext context; - - boolean isCurrentDevice; - - public String getToken() { - return token; - } - - public void setToken(String token) { - this.token = token; - } - - public double getRisk() { - return risk; - } - - public void setRisk(double risk) { - this.risk = risk; - } - - public String getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(String createdAt) { - this.createdAt = createdAt; - } - - public String getLastSeenAt() { - return lastSeenAt; - } - - public void setLastSeenAt(String lastSeenAt) { - this.lastSeenAt = lastSeenAt; - } - - public String getApprovedAt() { - return approvedAt; - } - - public void setApprovedAt(String approvedAt) { - this.approvedAt = approvedAt; - } - - public String getEscalatedAt() { - return escalatedAt; - } - - public void setEscalatedAt(String escalatedAt) { - this.escalatedAt = escalatedAt; - } - - public String getMitigatedAt() { - return mitigatedAt; - } - - public void setMitigatedAt(String mitigatedAt) { - this.mitigatedAt = mitigatedAt; - } - - public CastleUserDeviceContext getContext() { - return context; - } - - public void setContext(CastleUserDeviceContext context) { - this.context = context; - } - - public boolean isCurrentDevice() { - return isCurrentDevice; - } - - public void setCurrentDevice(boolean currentDevice) { - isCurrentDevice = currentDevice; - } -} diff --git a/src/main/java/io/castle/client/model/CastleUserDevices.java b/src/main/java/io/castle/client/model/CastleUserDevices.java deleted file mode 100644 index fbc9399d..00000000 --- a/src/main/java/io/castle/client/model/CastleUserDevices.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.castle.client.model; - -import com.google.gson.annotations.SerializedName; - -import java.util.List; - -public class CastleUserDevices { - private int totalCount; - @SerializedName("data") - private List devices; - - public int getTotalCount() { - return totalCount; - } - - public List getDevices() { - return devices; - } - - public void setDevices(List devices) { - this.devices = devices; - this.totalCount = devices != null ? devices.size() : 0; - } -} diff --git a/src/main/java/io/castle/client/model/ImpersonatePayload.java b/src/main/java/io/castle/client/model/ImpersonatePayload.java deleted file mode 100644 index 161fe525..00000000 --- a/src/main/java/io/castle/client/model/ImpersonatePayload.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.castle.client.model; - -import com.google.gson.JsonObject; - -public class ImpersonatePayload { - private final String userId; - private final JsonObject context; - private final JsonObject properties; - - public ImpersonatePayload(String userId, String impersonator, JsonObject contextJson) { - this.userId = userId; - this.properties = new JsonObject(); - this.properties.addProperty("impersonator", impersonator); - this.context = contextJson; - } - - public ImpersonatePayload(String userId, JsonObject contextJson) { - this.userId = userId; - this.properties = new JsonObject(); - this.context = contextJson; - } -} diff --git a/src/main/java/io/castle/client/model/Verdict.java b/src/main/java/io/castle/client/model/Verdict.java deleted file mode 100644 index 8e29681e..00000000 --- a/src/main/java/io/castle/client/model/Verdict.java +++ /dev/null @@ -1,113 +0,0 @@ -package io.castle.client.model; - -import com.google.gson.JsonElement; - -/** - * Model of the outcome of an authenticate call to the Castle API. - */ -public class Verdict { - - /** - * AuthenticateAction returned by a call to the CastleAPI, or configured by a failover strategy. - */ - private AuthenticateAction action; - - /** - * String representing a user ID associated with an authenticate call. - */ - private String userId; - - /** - * True if the SDK resorted the {@code AuthenticateFailoverStrategy} configured. - */ - private boolean failover; - - /** - * Explains the reason why the {@code AuthenticateFailoverStrategy} was used. - */ - private String failoverReason; - - /** - * String representing a device ID associated with an authenticate call. - */ - private String deviceToken; - - /** - * Float representing risk value associated with an authenticate call. - */ - private float risk; - - /** - * RiskPolicyResult representing risk policy used for generating this verdict. - */ - private RiskPolicyResult riskPolicy; - - /** - * JsonElement representing the full response of the server request - */ - private JsonElement internal; - - public AuthenticateAction getAction() { - return action; - } - - public void setAction(AuthenticateAction action) { - this.action = action; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public boolean isFailover() { - return failover; - } - - public void setFailover(boolean failover) { - this.failover = failover; - } - - public String getFailoverReason() { - return failoverReason; - } - - public void setFailoverReason(String failoverReason) { - this.failoverReason = failoverReason; - } - - public String getDeviceToken() { - return deviceToken; - } - - public void setRisk(float risk) { - this.risk = risk; - } - - public float getRisk() { - return risk; - } - - public void setDeviceToken(String deviceToken) { - this.deviceToken = deviceToken; - } - - public void setInternal(JsonElement internal) { - this.internal = internal; - } - - public RiskPolicyResult getRiskPolicy() { - return riskPolicy; - } - - public void setRiskPolicy(RiskPolicyResult riskPolicy) { - this.riskPolicy = riskPolicy; - } - - public JsonElement getInternal() { - return internal; - } -} diff --git a/src/test/java/io/castle/client/AbstractCastleHttpLayerTest.java b/src/test/java/io/castle/client/AbstractCastleHttpLayerTest.java index 23303633..338e5094 100644 --- a/src/test/java/io/castle/client/AbstractCastleHttpLayerTest.java +++ b/src/test/java/io/castle/client/AbstractCastleHttpLayerTest.java @@ -4,7 +4,6 @@ import io.castle.client.internal.config.CastleConfiguration; import io.castle.client.internal.config.CastleConfigurationBuilder; import io.castle.client.internal.config.CastleSdkInternalConfiguration; -import io.castle.client.model.AuthenticateFailoverStrategy; import io.castle.client.model.CastleSdkConfigurationException; import okhttp3.HttpUrl; import okhttp3.mockwebserver.MockWebServer; @@ -18,16 +17,10 @@ public abstract class AbstractCastleHttpLayerTest { - private final AuthenticateFailoverStrategy testAuthenticateFailoverStrategy; - Castle sdk; MockWebServer server; HttpUrl testServerBaseUrl; - protected AbstractCastleHttpLayerTest(AuthenticateFailoverStrategy testAuthenticateFailoverStrategy) { - this.testAuthenticateFailoverStrategy = testAuthenticateFailoverStrategy; - } - @Before public void prepare() throws NoSuchFieldException, IllegalAccessException, CastleSdkConfigurationException, IOException { //Given a mocked API server @@ -45,7 +38,6 @@ public void prepare() throws NoSuchFieldException, IllegalAccessException, Castl .withAllowListHeaders(configuration.getAllowListHeaders()) .withCastleAppId(configuration.getCastleAppId()) .withBackendProvider(configuration.getBackendProvider()) - .withAuthenticateFailoverStrategy(testAuthenticateFailoverStrategy) .withTimeout(configuration.getTimeout()) .build(); diff --git a/src/test/java/io/castle/client/CastleAuthenticateHttpTest.java b/src/test/java/io/castle/client/CastleAuthenticateHttpTest.java deleted file mode 100644 index 4e57ce44..00000000 --- a/src/test/java/io/castle/client/CastleAuthenticateHttpTest.java +++ /dev/null @@ -1,541 +0,0 @@ -package io.castle.client; - -import com.google.gson.JsonParser; -import io.castle.client.internal.utils.VerdictBuilder; -import io.castle.client.model.*; -import io.castle.client.internal.json.CastleGsonModel; -import io.castle.client.utils.SDKVersion; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.RecordedRequest; -import org.assertj.core.api.Assertions; -import org.json.JSONException; -import org.json.JSONObject; -import org.junit.Assert; -import org.junit.Test; -import org.skyscreamer.jsonassert.JSONAssert; -import org.springframework.mock.web.MockHttpServletRequest; - -import com.google.common.collect.ImmutableMap; - -import jakarta.servlet.http.HttpServletRequest; -import java.util.concurrent.atomic.AtomicReference; - -import static okhttp3.mockwebserver.SocketPolicy.NO_RESPONSE; - -public class CastleAuthenticateHttpTest extends AbstractCastleHttpLayerTest { - - private static final String DENY_RESPONSE = "{\n" + - " \"action\": \"deny\",\n" + - " \"user_id\": \"12345\",\n" + - " \"device_token\": \"abcdefg1234\",\n" + - " \"risk_policy\": {\n" + - " \"id\": \"q-rbeMzBTdW2Fd09sbz55A\",\n" + - " \"revision_id\": \"pke4zqO2TnqVr-NHJOAHEg\",\n" + - " \"name\": \"Block Users from X\",\n" + - " \"type\": \"bot\"\n" + - " }\n" + - "}"; - - private static final String DENY_RESPONSE_NO_POLICY = "{\n" + - " \"action\": \"deny\",\n" + - " \"user_id\": \"12345\",\n" + - " \"device_token\": \"abcdefg1234\"\n" + - "}"; - - public CastleAuthenticateHttpTest() { - super(new AuthenticateFailoverStrategy(AuthenticateAction.CHALLENGE)); - } - - @Test - public void authenticateAsyncEndpointWithNullUserIdPayload() throws InterruptedException, JSONException { - // Given - server.enqueue(new MockResponse().setBody(DENY_RESPONSE)); - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - final AtomicReference result = new AtomicReference<>(); - AsyncCallbackHandler handler = new AsyncCallbackHandler() { - @Override - public void onResponse(Verdict response) { - result.set(response); - } - - @Override - public void onException(Exception exception) { - Assertions.fail("error on request", exception); - } - }; - // and an authenticate request is made - sdk.onRequest(request).authenticateAsync( - CastleMessage.builder("$login.succeeded").build(), - handler - ); - - // then - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - JSONAssert.assertEquals("{\"event\":\"$login.succeeded\",\"context\":{\"active\":true,\"ip\":\"127.0.0.1\",\"headers\":{\"REMOTE_ADDR\":\"127.0.0.1\"}," + SDKVersion.getLibraryString() +"}}", - body, false); - - Assert.assertTrue(new JSONObject(body).has("sent_at")); - } - - public void authenticateAsyncEndpontWithPayload() throws InterruptedException, JSONException { - // Given - server.enqueue(new MockResponse().setBody(DENY_RESPONSE)); - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - final AtomicReference result = new AtomicReference<>(); - AsyncCallbackHandler handler = new AsyncCallbackHandler() { - @Override - public void onResponse(Verdict response) { - result.set(response); - } - - @Override - public void onException(Exception exception) { - Assertions.fail("error on request", exception); - } - }; - // and an authenticate request is made - sdk.onRequest(request).authenticateAsync( - CastleMessage.builder("$login.succeeded").userId("12345").build(), - handler - ); - - // then - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - JSONAssert.assertEquals("{\"event\":\"$login.succeeded\",\"user_id\":\"12345\",\"context\":{\"active\":true,\"ip\":\"127.0.0.1\",\"headers\":{\"REMOTE_ADDR\":\"127.0.0.1\"}," + SDKVersion.getLibraryString() +"}}", - body, false); - - Assert.assertTrue(new JSONObject(body).has("sent_at")); - } - - @Test - public void authenticationAsyncEndpointTest() throws InterruptedException, JSONException { - //given - server.enqueue(new MockResponse().setBody(DENY_RESPONSE)); - String id = "12345"; - String event = "$login.succeeded"; - String deviceToken = "abcdefg1234"; - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - final AtomicReference result = new AtomicReference<>(); - AsyncCallbackHandler handler = new AsyncCallbackHandler() { - @Override - public void onResponse(Verdict response) { - result.set(response); - } - - @Override - public void onException(Exception exception) { - Assertions.fail("error on request", exception); - } - }; - // and an authenticate request is made - sdk.onRequest(request).authenticateAsync(event, id, handler); - - // then - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - String json = "{\"event\":\"$login.succeeded\",\"user_id\":\"12345\",\"context\":{\"active\":true,\"ip\":\"127.0.0.1\",\"headers\":{\"REMOTE_ADDR\":\"127.0.0.1\"}," + SDKVersion.getLibraryString() +"}}"; - - JSONAssert.assertEquals(json, body, false); - - Assert.assertTrue(new JSONObject(body).has("sent_at")); - - RiskPolicyResult riskPolicyResult = new CastleGsonModel().getGson().fromJson("{\"id\": \"q-rbeMzBTdW2Fd09sbz55A\", \"revision_id\": \"pke4zqO2TnqVr-NHJOAHEg\",\"name\": \"Block Users from X\",\"type\": \"bot\"}", RiskPolicyResult.class); - - // and - Verdict expected = VerdictBuilder.success() - .withAction(AuthenticateAction.DENY) - .withUserId("12345") - .withDeviceToken(deviceToken) - .withRiskPolicy(riskPolicyResult) - .withInternal(JsonParser.parseString("{\"action\":\"deny\",\"user_id\":\"12345\",\"device_token\":\"abcdefg1234\", \"risk_policy\": {\"id\": \"q-rbeMzBTdW2Fd09sbz55A\", \"revision_id\": \"pke4zqO2TnqVr-NHJOAHEg\",\"name\": \"Block Users from X\",\"type\": \"bot\"}}")) - .build(); - waitForValueAndVerify(result, expected); - } - - @Test - public void authenticationAsyncEndpointNoRiskPolicyTest() throws InterruptedException, JSONException { - //given - server.enqueue(new MockResponse().setBody(DENY_RESPONSE_NO_POLICY)); - String id = "12345"; - String event = "$login.succeeded"; - String deviceToken = "abcdefg1234"; - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - final AtomicReference result = new AtomicReference<>(); - AsyncCallbackHandler handler = new AsyncCallbackHandler() { - @Override - public void onResponse(Verdict response) { - result.set(response); - } - - @Override - public void onException(Exception exception) { - Assertions.fail("error on request", exception); - } - }; - // and an authenticate request is made - sdk.onRequest(request).authenticateAsync(event, id, handler); - - // then - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - String json = "{\"event\":\"$login.succeeded\",\"user_id\":\"12345\",\"context\":{\"active\":true,\"ip\":\"127.0.0.1\",\"headers\":{\"REMOTE_ADDR\":\"127.0.0.1\"}," + SDKVersion.getLibraryString() +"}}"; - - JSONAssert.assertEquals(json, body, false); - - Assert.assertTrue(new JSONObject(body).has("sent_at")); - - // and - Verdict expected = VerdictBuilder.success() - .withAction(AuthenticateAction.DENY) - .withUserId("12345") - .withDeviceToken(deviceToken) - .withInternal(JsonParser.parseString("{\"action\":\"deny\",\"user_id\":\"12345\",\"device_token\":\"abcdefg1234\"}")) - .build(); - waitForValueAndVerify(result, expected); - } - - @Test - public void authenticationAsyncEndpointWithPropertiesAndTraitsTest() throws InterruptedException, JSONException { - //given - server.enqueue(new MockResponse().setBody(DENY_RESPONSE)); - String id = "12345"; - String event = "$login.succeeded"; - String deviceToken = "abcdefg1234"; - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - final AtomicReference result = new AtomicReference<>(); - AsyncCallbackHandler handler = new AsyncCallbackHandler() { - @Override - public void onResponse(Verdict response) { - result.set(response); - } - - @Override - public void onException(Exception exception) { - Assertions.fail("error on request", exception); - } - }; - // and an authenticate request is made - sdk.onRequest(request).authenticateAsync(event, id, ImmutableMap.builder() - .put("b",0) - .build(), ImmutableMap.builder() - .put("y",0) - .build(), handler); - - // then - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - JSONAssert.assertEquals("{\"event\":\"$login.succeeded\",\"properties\":{\"b\":0},\"user_id\":\"12345\",\"user_traits\":{\"y\":0},\"context\":{\"active\":true,\"ip\":\"127.0.0.1\",\"headers\":{\"REMOTE_ADDR\":\"127.0.0.1\"}," + SDKVersion.getLibraryString() +"}}", - body, false); - - Assert.assertTrue(new JSONObject(body).has("sent_at")); - - RiskPolicyResult riskPolicyResult = new CastleGsonModel().getGson().fromJson("{\"id\": \"q-rbeMzBTdW2Fd09sbz55A\", \"revision_id\": \"pke4zqO2TnqVr-NHJOAHEg\",\"name\": \"Block Users from X\",\"type\": \"bot\"}", RiskPolicyResult.class); - - // and - Verdict expected = VerdictBuilder.success() - .withAction(AuthenticateAction.DENY) - .withUserId("12345") - .withDeviceToken(deviceToken) - .withRiskPolicy(riskPolicyResult) - .withInternal(JsonParser.parseString("{\"action\":\"deny\",\"user_id\":\"12345\",\"device_token\":\"abcdefg1234\", \"risk_policy\": {\"id\": \"q-rbeMzBTdW2Fd09sbz55A\", \"revision_id\": \"pke4zqO2TnqVr-NHJOAHEg\",\"name\": \"Block Users from X\",\"type\": \"bot\"}}")) - .build(); - waitForValueAndVerify(result, expected); - } - - - @Test - public void authenticationAsyncEndpointProvideDefaultValueOnTimeoutErrorTest() throws InterruptedException, JSONException { - //given - server.enqueue(new MockResponse().setSocketPolicy(NO_RESPONSE)); - String id = "12345"; - String event = "$login.succeeded"; - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - final AtomicReference result = new AtomicReference<>(); - AsyncCallbackHandler handler = new AsyncCallbackHandler() { - @Override - public void onResponse(Verdict response) { - result.set(response); - } - - @Override - public void onException(Exception exception) { - Assertions.fail("error on request", exception); - } - }; - // and an authenticate request is made - sdk.onRequest(request).authenticateAsync(event, id, handler); - - // then - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - JSONAssert.assertEquals("{\"event\":\"$login.succeeded\",\"user_id\":\"12345\",\"context\":{\"active\":true,\"ip\":\"127.0.0.1\",\"headers\":{\"REMOTE_ADDR\":\"127.0.0.1\"}," + SDKVersion.getLibraryString() +"}}", - body, false); - - Assert.assertTrue(new JSONObject(body).has("sent_at")); - - // and - Verdict expected = VerdictBuilder.failover("timeout") - .withAction(AuthenticateAction.CHALLENGE) - .withUserId(id) - .build(); - - verifyFailoverResponse(result, expected, false); - } - - - @Test - public void authenticationEndpointDefaultValueOnTimeoutErrorTest() throws InterruptedException, JSONException { - //given the backed will timeout - server.enqueue(new MockResponse().setSocketPolicy(NO_RESPONSE)); - String id = "12345"; - String event = "$login.succeeded"; - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // and an authenticate request is made - Verdict verdict = sdk.onRequest(request).authenticate(event, id); - - // then - Verdict expected = VerdictBuilder.failover("timeout") - .withAction(AuthenticateAction.CHALLENGE) - .withUserId(id) - .build(); - verifyFailoverResponse(verdict, expected, false); - - // and - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - JSONAssert.assertEquals("{\"event\":\"$login.succeeded\",\"user_id\":\"12345\",\"context\":{\"active\":true,\"ip\":\"127.0.0.1\",\"headers\":{\"REMOTE_ADDR\":\"127.0.0.1\"}," + SDKVersion.getLibraryString() +"}}", - body, false); - - Assert.assertTrue(new JSONObject(body).has("sent_at")); - } - - @Test - public void authenticationEndpointWithPayload() throws InterruptedException, JSONException { - // Given - server.enqueue(new MockResponse().setBody(DENY_RESPONSE)); - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // And an authenticate request is made - Verdict verdict = sdk.onRequest(request).authenticate( - CastleMessage.builder("$login.succeeded").userId("12345").build() - ); - - // Then - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - JSONAssert.assertEquals("{\"event\":\"$login.succeeded\",\"user_id\":\"12345\",\"context\":{\"active\":true,\"ip\":\"127.0.0.1\",\"headers\":{\"REMOTE_ADDR\":\"127.0.0.1\"}," + SDKVersion.getLibraryString() +"}}", - body, false); - - Assert.assertTrue(new JSONObject(body).has("sent_at")); - } - - @Test - public void authenticationEndpointTest() throws InterruptedException, JSONException { - //given - server.enqueue(new MockResponse().setBody(DENY_RESPONSE)); - String id = "12345"; - String event = "$login.succeeded"; - String deviceToken = "abcdefg1234"; - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // and an authenticate request is made - Verdict verdict = sdk.onRequest(request).authenticate(event, id); - - RiskPolicyResult riskPolicyResult = new CastleGsonModel().getGson().fromJson("{\"id\": \"q-rbeMzBTdW2Fd09sbz55A\", \"revision_id\": \"pke4zqO2TnqVr-NHJOAHEg\",\"name\": \"Block Users from X\",\"type\": \"bot\"}", RiskPolicyResult.class); - - // then - Verdict expected = VerdictBuilder.success() - .withAction(AuthenticateAction.DENY) - .withUserId(id) - .withDeviceToken(deviceToken) - .withRiskPolicy(riskPolicyResult) - .withInternal(JsonParser.parseString("{\"action\":\"deny\",\"user_id\":\"12345\",\"device_token\":\"abcdefg1234\", \"risk_policy\": {\"id\": \"q-rbeMzBTdW2Fd09sbz55A\", \"revision_id\": \"pke4zqO2TnqVr-NHJOAHEg\",\"name\": \"Block Users from X\",\"type\": \"bot\"}}")) - .build(); - Assertions.assertThat(verdict).usingRecursiveComparison().isEqualTo(expected); - - // and - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - JSONAssert.assertEquals("{\"event\":\"$login.succeeded\",\"user_id\":\"12345\",\"context\":{\"active\":true,\"ip\":\"127.0.0.1\",\"headers\":{\"REMOTE_ADDR\":\"127.0.0.1\"}," + SDKVersion.getLibraryString() +"}}", - body, false); - - Assert.assertTrue(new JSONObject(body).has("sent_at")); - } - - @Test - public void authenticationEndpointWithPropertiesAndTraitsTest() throws InterruptedException, JSONException { - //given - server.enqueue(new MockResponse().setBody(DENY_RESPONSE)); - String id = "12345"; - String event = "$login.succeeded"; - String deviceToken = "abcdefg1234"; - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // and an authenticate request is made - Verdict verdict = sdk.onRequest(request).authenticate(event, id, ImmutableMap.builder() - .put("a","valueA") - .put("b",123456) - .build(), ImmutableMap.builder() - .put("x","valueX") - .put("y",654321) - .build()); - - RiskPolicyResult riskPolicyResult = new CastleGsonModel().getGson().fromJson("{\"id\": \"q-rbeMzBTdW2Fd09sbz55A\", \"revision_id\": \"pke4zqO2TnqVr-NHJOAHEg\",\"name\": \"Block Users from X\",\"type\": \"bot\"}", RiskPolicyResult.class); - - // then - Verdict expected = VerdictBuilder.success() - .withAction(AuthenticateAction.DENY) - .withUserId(id) - .withDeviceToken(deviceToken) - .withRiskPolicy(riskPolicyResult) - .withInternal(JsonParser.parseString("{\"action\":\"deny\",\"user_id\":\"12345\",\"device_token\":\"abcdefg1234\", \"risk_policy\": {\"id\": \"q-rbeMzBTdW2Fd09sbz55A\", \"revision_id\": \"pke4zqO2TnqVr-NHJOAHEg\",\"name\": \"Block Users from X\",\"type\": \"bot\"}}")) - .build(); - Assertions.assertThat(verdict).usingRecursiveComparison().isEqualTo(expected); - - // and - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - JSONAssert.assertEquals("{\"event\":\"$login.succeeded\",\"properties\":{\"a\":\"valueA\",\"b\":123456},\"user_id\":\"12345\",\"user_traits\":{\"x\":\"valueX\",\"y\":654321},\"context\":{\"active\":true,\"ip\":\"127.0.0.1\",\"headers\":{\"REMOTE_ADDR\":\"127.0.0.1\"}," + SDKVersion.getLibraryString() +"}}", - body, false); - - Assert.assertTrue(new JSONObject(body).has("sent_at")); - } - - @Test(expected = CastleRuntimeException.class) - public void authenticationThrowExceptionWhenBackendReturnErrors() throws InterruptedException { - //given a 403 response from backend - server.enqueue(new MockResponse().setResponseCode(403)); - String id = "12345"; - String event = "$login.succeeded"; - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // and an authenticate request is made - Verdict verdict = sdk.onRequest(request).authenticate(event, id, ImmutableMap.builder() - .put("a","valueA") - .put("b",123456) - .build(), null); - - // then a exception is send to the client code because of bad response from the castle backend - } - - @Test - public void authenticationUseDefaultOnBackendErrorTest() throws InterruptedException, JSONException { - //given a 403 response from backend - server.enqueue(new MockResponse().setResponseCode(500)); - String id = "12345"; - String event = "$login.succeeded"; - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // and an authenticate request is made - Verdict verdict = sdk.onRequest(request).authenticate(event, id, ImmutableMap.builder() - .put("a","valueA") - .put("b",123456) - .build(), null); - - // then - Verdict expected = VerdictBuilder.failover("Client Error") - .withAction(AuthenticateAction.CHALLENGE) - .withUserId(id) - .build(); - verifyFailoverResponse(verdict, expected, false); - - // and - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - JSONAssert.assertEquals("{\"event\":\"$login.succeeded\",\"properties\":{\"a\":\"valueA\",\"b\":123456},\"user_id\":\"12345\",\"context\":{\"active\":true,\"ip\":\"127.0.0.1\",\"headers\":{\"REMOTE_ADDR\":\"127.0.0.1\"}," + SDKVersion.getLibraryString() +"}}", - body, false); - - Assert.assertTrue(new JSONObject(body).has("sent_at")); - } - - @Test(expected = CastleRuntimeException.class) - public void authenticationUseDefaultOnBadResponseFormatTestEmptyCase() throws InterruptedException { - //given a response do not match the transport contract - testIlegalJsonForAuthenticate("{}"); - - } - @Test(expected = CastleRuntimeException.class) - public void authenticationUseDefaultOnBadResponseFormatTestInvalidActionUseCase() throws InterruptedException { - //given a response do not match the transport contract - testIlegalJsonForAuthenticate("{\"action\":\"action\"}"); - - } - - private void testIlegalJsonForAuthenticate(String illegalJsonBody) { - server.enqueue(new MockResponse().setBody(illegalJsonBody)); - String id = "12345"; - String event = "$login.succeeded"; - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // and an authenticate request is made - Verdict verdict = sdk.onRequest(request).authenticate(event, id, ImmutableMap.builder() - .put("a","valueA") - .put("b",123456) - .build(), null); - - // then a illegal json failover is provided - Verdict expected = VerdictBuilder.failover("Illegal json format") - .withAction(AuthenticateAction.CHALLENGE) - .withUserId(id) - .build(); - verifyFailoverResponse(verdict, expected, true); - } - - /** - * Verify that the async Verdict value match the expected values. - * The Failover reason depend from the JVM implementation, so we only check that is not null. - * - * @param result - * @param expected - */ - private void verifyFailoverResponse(AtomicReference result, Verdict expected, boolean expectedExactReasonMatch) { - Verdict extractedVerdict = waitForValue(result); - verifyFailoverResponse(extractedVerdict, expected, expectedExactReasonMatch); - } - - /** - * Sync version of the verification for Verdict - * - * @param extractedVerdict - * @param expected - */ - private void verifyFailoverResponse(Verdict extractedVerdict, Verdict expected, boolean expectedExactReasonMatch) { - Assertions.assertThat(extractedVerdict.getAction()).isEqualTo(expected.getAction()); - Assertions.assertThat(extractedVerdict.getUserId()).isEqualTo(expected.getUserId()); - Assertions.assertThat(extractedVerdict.isFailover()).isTrue(); - if (expectedExactReasonMatch) { - Assertions.assertThat(extractedVerdict.getFailoverReason()).isEqualTo(expected.getFailoverReason()); - } else { - Assertions.assertThat(extractedVerdict.getFailoverReason()).isNotEmpty(); - } - } -} diff --git a/src/test/java/io/castle/client/CastleDeviceApproveHttpTest.java b/src/test/java/io/castle/client/CastleDeviceApproveHttpTest.java deleted file mode 100644 index 6809d281..00000000 --- a/src/test/java/io/castle/client/CastleDeviceApproveHttpTest.java +++ /dev/null @@ -1,69 +0,0 @@ -package io.castle.client; - -import io.castle.client.model.*; -import io.castle.client.utils.DeviceUtils; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.RecordedRequest; -import org.assertj.core.api.Assertions; -import org.junit.Assert; -import org.junit.Test; -import org.springframework.mock.web.MockHttpServletRequest; - -import jakarta.servlet.http.HttpServletRequest; - -import static okhttp3.mockwebserver.SocketPolicy.NO_RESPONSE; - -public class CastleDeviceApproveHttpTest extends AbstractCastleHttpLayerTest { - - public CastleDeviceApproveHttpTest() { - super(new AuthenticateFailoverStrategy(AuthenticateAction.CHALLENGE)); - } - - @Test public void approveDevice() throws InterruptedException { - MockResponse mockResponse = new MockResponse(); - mockResponse.setBody(DeviceUtils.getDefaultDeviceJSON()); - server.enqueue(mockResponse); - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // And an report device request is made - CastleUserDevice device = sdk.onRequest(request).approve("deviceToken"); - - // Then - RecordedRequest recordedRequest = server.takeRequest(); - Assert.assertEquals(testServerBaseUrl.resolve("v1/devices/deviceToken/approve"), recordedRequest.getRequestUrl()); - Assert.assertEquals("PUT", recordedRequest.getMethod()); - - // and the correct CastleUserDevice model object is extracted - CastleUserDevice expected = DeviceUtils.createExpectedDevice(); - - Assertions.assertThat(device).usingRecursiveComparison().isEqualTo(expected); - } - - @Test(expected = CastleApiTimeoutException.class) - public void approveDeviceTimeoutTest() { - - // given backend request timeouts - server.enqueue(new MockResponse().setSocketPolicy(NO_RESPONSE)); - HttpServletRequest request = new MockHttpServletRequest(); - String deviceToken = "deviceToken"; - - //when a v1/devices/:device_token/approve request is made - sdk.onRequest(request).approve(deviceToken); - } - - @Test(expected = CastleRuntimeException.class) - public void testExceptionWithServerError () { - - // given a server failure - server.enqueue(new MockResponse().setResponseCode(500)); - // And a request - HttpServletRequest request = new MockHttpServletRequest(); - String deviceToken = "deviceToken"; - - //when a v1/devices/:device_token/approve request is made - sdk.onRequest(request).approve(deviceToken); - - } -} diff --git a/src/test/java/io/castle/client/CastleDeviceHttpTest.java b/src/test/java/io/castle/client/CastleDeviceHttpTest.java deleted file mode 100644 index 8ac91f25..00000000 --- a/src/test/java/io/castle/client/CastleDeviceHttpTest.java +++ /dev/null @@ -1,84 +0,0 @@ -package io.castle.client; - -import io.castle.client.internal.json.CastleGsonModel; -import io.castle.client.model.*; -import io.castle.client.utils.DeviceUtils; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.RecordedRequest; -import org.assertj.core.api.Assertions; -import org.junit.Assert; -import org.junit.Test; -import org.springframework.mock.web.MockHttpServletRequest; - -import jakarta.servlet.http.HttpServletRequest; - -import static okhttp3.mockwebserver.SocketPolicy.NO_RESPONSE; - -public class CastleDeviceHttpTest extends AbstractCastleHttpLayerTest { - - public CastleDeviceHttpTest() { - super(new AuthenticateFailoverStrategy(AuthenticateAction.CHALLENGE)); - } - - @Test public void getDevice() throws Exception { - MockResponse mockResponse = new MockResponse(); - mockResponse.setBody(DeviceUtils.getDefaultDeviceJSON()); - server.enqueue(mockResponse); - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // And an devices request is made - CastleUserDevice device = sdk.onRequest(request).device("deviceToken"); - - // Then - RecordedRequest recordedRequest = server.takeRequest(); - Assert.assertEquals(testServerBaseUrl.resolve("v1/devices/deviceToken"), recordedRequest.getRequestUrl()); - - // and the correct CastleUserDevice model object is extracted - CastleUserDevice expected = DeviceUtils.createExpectedDevice(); - - Assertions.assertThat(device).usingRecursiveComparison().isEqualTo(expected); - } - - @Test(expected = CastleApiTimeoutException.class) - public void getDeviceTimeoutTest() { - - // given backend request timeouts - server.enqueue(new MockResponse().setSocketPolicy(NO_RESPONSE)); - HttpServletRequest request = new MockHttpServletRequest(); - String deviceToken = "deviceToken"; - - //when a v1/devices/:device_token request is made - sdk.onRequest(request).device(deviceToken); - } - - @Test(expected = CastleRuntimeException.class) - public void testExceptionWithServerError () { - - // given a server failure - server.enqueue(new MockResponse().setResponseCode(500)); - // And a request - HttpServletRequest request = new MockHttpServletRequest(); - String deviceToken = "deviceToken"; - - //when a v1/devices/:device_token request is made - sdk.onRequest(request).device(deviceToken); - - } - - @Test - public void getDeviceNotFoundTest () { - - // given a server failure - server.enqueue(new MockResponse().setResponseCode(404)); - // And a request - HttpServletRequest request = new MockHttpServletRequest(); - String deviceToken = "deviceToken"; - - //when a v1/devices/:device_token request is made - CastleUserDevice userDevice = sdk.onRequest(request).device(deviceToken); - - Assert.assertNull(userDevice); - } -} diff --git a/src/test/java/io/castle/client/CastleDeviceReportHttpTest.java b/src/test/java/io/castle/client/CastleDeviceReportHttpTest.java deleted file mode 100644 index d927a4cb..00000000 --- a/src/test/java/io/castle/client/CastleDeviceReportHttpTest.java +++ /dev/null @@ -1,69 +0,0 @@ -package io.castle.client; - -import io.castle.client.model.*; -import io.castle.client.utils.DeviceUtils; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.RecordedRequest; -import org.assertj.core.api.Assertions; -import org.junit.Assert; -import org.junit.Test; -import org.springframework.mock.web.MockHttpServletRequest; - -import jakarta.servlet.http.HttpServletRequest; - -import static okhttp3.mockwebserver.SocketPolicy.NO_RESPONSE; - -public class CastleDeviceReportHttpTest extends AbstractCastleHttpLayerTest { - - public CastleDeviceReportHttpTest() { - super(new AuthenticateFailoverStrategy(AuthenticateAction.CHALLENGE)); - } - - @Test public void reportDevice() throws InterruptedException { - MockResponse mockResponse = new MockResponse(); - mockResponse.setBody(DeviceUtils.getDefaultDeviceJSON()); - server.enqueue(mockResponse); - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // And an report device request is made - CastleUserDevice device = sdk.onRequest(request).report("deviceToken"); - - // Then - RecordedRequest recordedRequest = server.takeRequest(); - Assert.assertEquals(testServerBaseUrl.resolve("v1/devices/deviceToken/report"), recordedRequest.getRequestUrl()); - Assert.assertEquals("PUT", recordedRequest.getMethod()); - - // and the correct CastleUserDevice model object is extracted - CastleUserDevice expected = DeviceUtils.createExpectedDevice(); - - Assertions.assertThat(device).usingRecursiveComparison().isEqualTo(expected); - } - - @Test(expected = CastleApiTimeoutException.class) - public void reportDeviceTimeoutTest() { - - // given backend request timeouts - server.enqueue(new MockResponse().setSocketPolicy(NO_RESPONSE)); - HttpServletRequest request = new MockHttpServletRequest(); - String deviceToken = "deviceToken"; - - //when a v1/devices/:device_token/report request is made - sdk.onRequest(request).report(deviceToken); - } - - @Test(expected = CastleRuntimeException.class) - public void testExceptionWithServerError () { - - // given a server failure - server.enqueue(new MockResponse().setResponseCode(500)); - // And a request - HttpServletRequest request = new MockHttpServletRequest(); - String deviceToken = "deviceToken"; - - //when a v1/devices/:device_token/report request is made - sdk.onRequest(request).report(deviceToken); - - } -} diff --git a/src/test/java/io/castle/client/CastleDevicesHttpTest.java b/src/test/java/io/castle/client/CastleDevicesHttpTest.java deleted file mode 100644 index 68be962d..00000000 --- a/src/test/java/io/castle/client/CastleDevicesHttpTest.java +++ /dev/null @@ -1,86 +0,0 @@ -package io.castle.client; - -import io.castle.client.internal.json.CastleGsonModel; -import io.castle.client.model.*; -import io.castle.client.utils.DeviceUtils; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.RecordedRequest; -import org.assertj.core.api.Assertions; -import org.junit.Assert; -import org.junit.Test; -import org.springframework.mock.web.MockHttpServletRequest; - -import jakarta.servlet.http.HttpServletRequest; - -import static okhttp3.mockwebserver.SocketPolicy.NO_RESPONSE; - -public class CastleDevicesHttpTest extends AbstractCastleHttpLayerTest { - - private CastleGsonModel model = new CastleGsonModel(); - - public CastleDevicesHttpTest() { - super(new AuthenticateFailoverStrategy(AuthenticateAction.CHALLENGE)); - } - - @Test public void getDevices() throws Exception { - MockResponse mockResponse = new MockResponse(); - mockResponse.setBody("{\"total_count\":1,\"data\":[" + DeviceUtils.getDefaultDeviceJSON() + "]}"); - server.enqueue(mockResponse); - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // And an devices request is made - CastleUserDevices devices = sdk.onRequest(request).userDevices("userId"); - - // Then - RecordedRequest recordedRequest = server.takeRequest(); - Assert.assertEquals(testServerBaseUrl.resolve("v1/users/userId/devices"), recordedRequest.getRequestUrl()); - - // and the correct CastleUserDevice model object is extracted - CastleUserDevices expected = DeviceUtils.createExpectedDevices(); - - Assertions.assertThat(devices).usingRecursiveComparison().isEqualTo(expected); - } - - @Test(expected = CastleApiTimeoutException.class) - public void getDevicesTimeoutTest() { - - // given backend request timeouts - server.enqueue(new MockResponse().setSocketPolicy(NO_RESPONSE)); - HttpServletRequest request = new MockHttpServletRequest(); - String userId = "userId"; - - //when a v1/users/:user_id/devices request is made - sdk.onRequest(request).userDevices(userId); - } - - @Test(expected = CastleRuntimeException.class) - public void testExceptionWithServerError () { - - // given a server failure - server.enqueue(new MockResponse().setResponseCode(500)); - // And a request - HttpServletRequest request = new MockHttpServletRequest(); - String userId = "userId"; - - //when a v1/users/:user_id/devices request is made - sdk.onRequest(request).userDevices(userId); - - } - - @Test - public void getDevicesNotFoundTest () { - - // given a server failure - server.enqueue(new MockResponse().setResponseCode(404)); - // And a request - HttpServletRequest request = new MockHttpServletRequest(); - String userId = "userId"; - - //when a v1/users/:user_id/devices request is made - CastleUserDevices userDevices = sdk.onRequest(request).userDevices(userId); - - Assert.assertNull(userDevices); - } -} diff --git a/src/test/java/io/castle/client/CastleEventsHttpTest.java b/src/test/java/io/castle/client/CastleEventsHttpTest.java new file mode 100644 index 00000000..5c3894e0 --- /dev/null +++ b/src/test/java/io/castle/client/CastleEventsHttpTest.java @@ -0,0 +1,70 @@ +package io.castle.client; + +import com.google.common.collect.ImmutableMap; +import io.castle.client.model.CastleResponse; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.RecordedRequest; +import org.assertj.core.api.Assertions; +import org.json.JSONException; +import org.junit.Assert; +import org.junit.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.mock.web.MockHttpServletRequest; + +import jakarta.servlet.http.HttpServletRequest; + +public class CastleEventsHttpTest extends AbstractCastleHttpLayerTest { + + @Test + public void eventsSchema() throws InterruptedException { + server.enqueue(new MockResponse().setBody("{}")); + HttpServletRequest request = new MockHttpServletRequest(); + + CastleResponse response = sdk.onRequest(request).eventsSchema(); + if (response == null) { + Assertions.fail("error on request"); + } + + RecordedRequest recordedRequest = server.takeRequest(); + Assert.assertEquals(testServerBaseUrl.resolve("v1/events/schema"), recordedRequest.getRequestUrl()); + Assert.assertEquals("GET", recordedRequest.getMethod()); + } + + @Test + public void queryEvents() throws InterruptedException, JSONException { + server.enqueue(new MockResponse().setBody("{}")); + HttpServletRequest request = new MockHttpServletRequest(); + + CastleResponse response = sdk.onRequest(request).queryEvents(ImmutableMap.builder() + .put("type", "$login") + .build()); + if (response == null) { + Assertions.fail("error on request"); + } + + RecordedRequest recordedRequest = server.takeRequest(); + String body = recordedRequest.getBody().readUtf8(); + Assert.assertEquals(testServerBaseUrl.resolve("v1/events/query"), recordedRequest.getRequestUrl()); + Assert.assertEquals("POST", recordedRequest.getMethod()); + JSONAssert.assertEquals("{\"type\":\"$login\"}", body, false); + } + + @Test + public void groupEvents() throws InterruptedException, JSONException { + server.enqueue(new MockResponse().setBody("{}")); + HttpServletRequest request = new MockHttpServletRequest(); + + CastleResponse response = sdk.onRequest(request).groupEvents(ImmutableMap.builder() + .put("field", "name") + .build()); + if (response == null) { + Assertions.fail("error on request"); + } + + RecordedRequest recordedRequest = server.takeRequest(); + String body = recordedRequest.getBody().readUtf8(); + Assert.assertEquals(testServerBaseUrl.resolve("v1/events/group"), recordedRequest.getRequestUrl()); + Assert.assertEquals("POST", recordedRequest.getMethod()); + JSONAssert.assertEquals("{\"field\":\"name\"}", body, false); + } +} diff --git a/src/test/java/io/castle/client/CastleFilterHttpTest.java b/src/test/java/io/castle/client/CastleFilterHttpTest.java index 8c2fd85e..04f23a2e 100644 --- a/src/test/java/io/castle/client/CastleFilterHttpTest.java +++ b/src/test/java/io/castle/client/CastleFilterHttpTest.java @@ -2,8 +2,6 @@ import com.google.gson.JsonParser; import io.castle.client.internal.json.CastleGsonModel; -import io.castle.client.model.AuthenticateAction; -import io.castle.client.model.AuthenticateFailoverStrategy; import io.castle.client.model.generated.*; import jakarta.servlet.http.HttpServletRequest; import okhttp3.mockwebserver.MockResponse; @@ -18,10 +16,6 @@ public class CastleFilterHttpTest extends AbstractCastleHttpLayerTest { - public CastleFilterHttpTest() { - super(new AuthenticateFailoverStrategy(AuthenticateAction.CHALLENGE)); - } - @Test public void filter() throws InterruptedException { MockResponse mockResponse = new MockResponse(); mockResponse.setBody("{\n" + diff --git a/src/test/java/io/castle/client/CastleGenericAPIHttpTest.java b/src/test/java/io/castle/client/CastleGenericAPIHttpTest.java index a510c887..47f15042 100644 --- a/src/test/java/io/castle/client/CastleGenericAPIHttpTest.java +++ b/src/test/java/io/castle/client/CastleGenericAPIHttpTest.java @@ -6,9 +6,7 @@ import io.castle.client.internal.json.CastleGsonModel; import io.castle.client.internal.utils.CastleContextBuilder; import io.castle.client.internal.utils.Timestamp; -import io.castle.client.internal.utils.VerdictBuilder; import io.castle.client.model.*; -import io.castle.client.utils.DeviceUtils; import io.castle.client.utils.SDKVersion; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.RecordedRequest; @@ -39,10 +37,6 @@ public class CastleGenericAPIHttpTest extends AbstractCastleHttpLayerTest { " }\n" + "}"; - public CastleGenericAPIHttpTest() { - super(new AuthenticateFailoverStrategy(AuthenticateAction.CHALLENGE)); - } - @Test public void postRequest() throws InterruptedException, JSONException { // Given diff --git a/src/test/java/io/castle/client/CastleImpersonateEndHttpTest.java b/src/test/java/io/castle/client/CastleImpersonateEndHttpTest.java deleted file mode 100644 index 8474d69d..00000000 --- a/src/test/java/io/castle/client/CastleImpersonateEndHttpTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.castle.client; - -import io.castle.client.model.*; -import io.castle.client.utils.DeviceUtils; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.RecordedRequest; -import org.assertj.core.api.Assertions; -import org.junit.Assert; -import org.junit.Test; -import org.springframework.mock.web.MockHttpServletRequest; - -import jakarta.servlet.http.HttpServletRequest; - -import static okhttp3.mockwebserver.SocketPolicy.NO_RESPONSE; - -public class CastleImpersonateEndHttpTest extends AbstractCastleHttpLayerTest { - - public CastleImpersonateEndHttpTest() { - super(new AuthenticateFailoverStrategy(AuthenticateAction.CHALLENGE)); - } - - @Test public void impersonateEnd() throws Exception { - MockResponse mockResponse = new MockResponse(); - mockResponse.setBody("{\"success\":true}"); - server.enqueue(mockResponse); - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // And an devices request is made - CastleSuccess result = sdk.onRequest(request).impersonateEnd("userId"); - - // Then - RecordedRequest recordedRequest = server.takeRequest(); - Assert.assertEquals(testServerBaseUrl.resolve("v1/impersonate"), recordedRequest.getRequestUrl()); - Assert.assertEquals("DELETE", recordedRequest.getMethod()); - - // and the correct Success model object is extracted - CastleSuccess expected = new CastleSuccess(); - expected.setSuccess(true); - - Assertions.assertThat(result).usingRecursiveComparison().isEqualTo(expected); - } - - @Test(expected = CastleApiTimeoutException.class) - public void impersonateEndTimeoutTest() { - - // given backend request timeouts - server.enqueue(new MockResponse().setSocketPolicy(NO_RESPONSE)); - HttpServletRequest request = new MockHttpServletRequest(); - String userId = "userId"; - - //when a v1/impersonate request is made - sdk.onRequest(request).impersonateStart(userId); - } -} diff --git a/src/test/java/io/castle/client/CastleImpersonateStartHttpTest.java b/src/test/java/io/castle/client/CastleImpersonateStartHttpTest.java deleted file mode 100644 index f2cb0eb6..00000000 --- a/src/test/java/io/castle/client/CastleImpersonateStartHttpTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.castle.client; - -import io.castle.client.model.*; -import io.castle.client.utils.DeviceUtils; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.RecordedRequest; -import org.assertj.core.api.Assertions; -import org.junit.Assert; -import org.junit.Test; -import org.springframework.mock.web.MockHttpServletRequest; - -import jakarta.servlet.http.HttpServletRequest; - -import static okhttp3.mockwebserver.SocketPolicy.NO_RESPONSE; - -public class CastleImpersonateStartHttpTest extends AbstractCastleHttpLayerTest { - - public CastleImpersonateStartHttpTest() { - super(new AuthenticateFailoverStrategy(AuthenticateAction.CHALLENGE)); - } - - @Test public void impersonateStart() throws Exception { - MockResponse mockResponse = new MockResponse(); - mockResponse.setBody("{\"success\":true}"); - server.enqueue(mockResponse); - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // And an devices request is made - CastleSuccess result = sdk.onRequest(request).impersonateStart("userId"); - - // Then - RecordedRequest recordedRequest = server.takeRequest(); - Assert.assertEquals(testServerBaseUrl.resolve("v1/impersonate"), recordedRequest.getRequestUrl()); - Assert.assertEquals("POST", recordedRequest.getMethod()); - - // and the correct Success model object is extracted - CastleSuccess expected = new CastleSuccess(); - expected.setSuccess(true); - - Assertions.assertThat(result).usingRecursiveComparison().isEqualTo(expected); - } - - @Test(expected = CastleApiTimeoutException.class) - public void impersonateStartTimeoutTest() { - - // given backend request timeouts - server.enqueue(new MockResponse().setSocketPolicy(NO_RESPONSE)); - HttpServletRequest request = new MockHttpServletRequest(); - String userId = "userId"; - - //when a review request is made - sdk.onRequest(request).impersonateStart(userId); - } -} \ No newline at end of file diff --git a/src/test/java/io/castle/client/CastleListItemsTest.java b/src/test/java/io/castle/client/CastleListItemsTest.java index a3c91e91..08537085 100644 --- a/src/test/java/io/castle/client/CastleListItemsTest.java +++ b/src/test/java/io/castle/client/CastleListItemsTest.java @@ -1,7 +1,5 @@ package io.castle.client; -import io.castle.client.model.AuthenticateAction; -import io.castle.client.model.AuthenticateFailoverStrategy; import io.castle.client.model.CastleResponse; import io.castle.client.model.generated.*; import jakarta.servlet.http.HttpServletRequest; @@ -18,10 +16,6 @@ public class CastleListItemsTest extends AbstractCastleHttpLayerTest { - public CastleListItemsTest() { - super(new AuthenticateFailoverStrategy(AuthenticateAction.CHALLENGE)); - } - @Test public void createListItem() throws InterruptedException { MockResponse mockResponse = new MockResponse(); diff --git a/src/test/java/io/castle/client/CastleListsHttpTest.java b/src/test/java/io/castle/client/CastleListsHttpTest.java index 548be6f8..3275fb2a 100644 --- a/src/test/java/io/castle/client/CastleListsHttpTest.java +++ b/src/test/java/io/castle/client/CastleListsHttpTest.java @@ -2,8 +2,6 @@ import com.google.gson.JsonParser; import io.castle.client.internal.json.CastleGsonModel; -import io.castle.client.model.AuthenticateAction; -import io.castle.client.model.AuthenticateFailoverStrategy; import io.castle.client.model.generated.*; import jakarta.servlet.http.HttpServletRequest; import okhttp3.mockwebserver.MockResponse; @@ -19,10 +17,6 @@ public class CastleListsHttpTest extends AbstractCastleHttpLayerTest { - public CastleListsHttpTest() { - super(new AuthenticateFailoverStrategy(AuthenticateAction.CHALLENGE)); - } - @Test public void updateList() throws InterruptedException { MockResponse mockResponse = new MockResponse(); diff --git a/src/test/java/io/castle/client/CastleLogHttpTest.java b/src/test/java/io/castle/client/CastleLogHttpTest.java index d3d1a579..534adadc 100644 --- a/src/test/java/io/castle/client/CastleLogHttpTest.java +++ b/src/test/java/io/castle/client/CastleLogHttpTest.java @@ -1,11 +1,8 @@ package io.castle.client; import com.google.gson.JsonParser; -import io.castle.client.model.AuthenticateAction; -import io.castle.client.model.AuthenticateFailoverStrategy; import io.castle.client.model.CastleResponse; import io.castle.client.model.generated.*; -import io.castle.client.utils.DeviceUtils; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.RecordedRequest; import org.assertj.core.api.Assertions; @@ -20,10 +17,6 @@ public class CastleLogHttpTest extends AbstractCastleHttpLayerTest { - public CastleLogHttpTest() { - super(new AuthenticateFailoverStrategy(AuthenticateAction.CHALLENGE)); - } - @Test public void log() throws InterruptedException { MockResponse mockResponse = new MockResponse(); mockResponse.setResponseCode(204); diff --git a/src/test/java/io/castle/client/CastleMergeHttpTest.java b/src/test/java/io/castle/client/CastleMergeHttpTest.java deleted file mode 100644 index 8323ad55..00000000 --- a/src/test/java/io/castle/client/CastleMergeHttpTest.java +++ /dev/null @@ -1,100 +0,0 @@ -package io.castle.client; - -import io.castle.client.model.AuthenticateAction; -import io.castle.client.model.AuthenticateFailoverStrategy; -import io.castle.client.utils.SDKVersion; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.RecordedRequest; -import org.json.JSONException; -import org.json.JSONObject; -import org.junit.Assert; -import org.junit.Test; -import org.skyscreamer.jsonassert.JSONAssert; -import org.springframework.mock.web.MockHttpServletRequest; - -import com.google.common.collect.ImmutableMap; - -import jakarta.servlet.http.HttpServletRequest; -import java.util.concurrent.atomic.AtomicReference; -import io.castle.client.model.AsyncCallbackHandler; -import org.assertj.core.api.Assertions; - -public class CastleMergeHttpTest extends AbstractCastleHttpLayerTest { - - public CastleMergeHttpTest() { - super(new AuthenticateFailoverStrategy(AuthenticateAction.CHALLENGE)); - } - - @Test - public void mergeContextIsSendOnHttp() throws InterruptedException, JSONException { - // Given - server.enqueue(new MockResponse().setResponseCode(200)); - String event = "any.valid.event"; - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // And a extra context - CustomExtraContext customExtraContext = new CustomExtraContext(); - customExtraContext.setAddString("String value"); - customExtraContext.setCondition(true); - customExtraContext.setValue(10L); - sdk.onRequest(request).mergeContext(customExtraContext).track(event, null, null, null, ImmutableMap.builder() - .put("x", "valueX") - .put("y", 234567) - .build()); - - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - - JSONAssert.assertEquals("{\"event\":\"any.valid.event\",\"context\":{\"active\":true,\"ip\":\"127.0.0.1\",\"headers\":{\"REMOTE_ADDR\":\"127.0.0.1\"}," + SDKVersion.getLibraryString() +",\"add_string\":\"String value\",\"condition\":true,\"value\":10},\"user_traits\":{\"x\":\"valueX\",\"y\":234567}}", body, false); - } - - @Test - public void mergeContextIsNull() throws InterruptedException, JSONException { - // Given - server.enqueue(new MockResponse().setResponseCode(200)); - String event = "any.valid.event"; - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // And null context - sdk.onRequest(request).mergeContext(null).track(event, null, null, null); - - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - - JSONAssert.assertEquals("{\"event\":\"any.valid.event\"}", body, false); - } - - private static class CustomExtraContext { - private String addString; - private Boolean condition; - private Long value; - - public String getAddString() { - return addString; - } - - public void setAddString(String addString) { - this.addString = addString; - } - - public Boolean getCondition() { - return condition; - } - - public void setCondition(Boolean condition) { - this.condition = condition; - } - - public Long getValue() { - return value; - } - - public void setValue(Long value) { - this.value = value; - } - } -} diff --git a/src/test/java/io/castle/client/CastleNoTrackOptionTest.java b/src/test/java/io/castle/client/CastleNoTrackOptionTest.java deleted file mode 100644 index 144a03f7..00000000 --- a/src/test/java/io/castle/client/CastleNoTrackOptionTest.java +++ /dev/null @@ -1,170 +0,0 @@ -package io.castle.client; - -import io.castle.client.api.CastleApi; -import io.castle.client.model.AsyncCallbackHandler; -import io.castle.client.model.AuthenticateAction; -import io.castle.client.model.AuthenticateFailoverStrategy; -import io.castle.client.model.Verdict; -import okhttp3.mockwebserver.MockResponse; -import org.assertj.core.api.Assertions; -import org.junit.Assert; -import org.junit.Test; -import org.springframework.mock.web.MockHttpServletRequest; - -import jakarta.servlet.http.HttpServletRequest; -import java.util.concurrent.atomic.AtomicReference; - -public class CastleNoTrackOptionTest extends AbstractCastleHttpLayerTest { - - public CastleNoTrackOptionTest() { - super(new AuthenticateFailoverStrategy(AuthenticateAction.CHALLENGE)); - } - - @Test - public void noTrackOptionDisableRequests() throws InterruptedException { - - //Given a mock HTTP request is provided - HttpServletRequest request = new MockHttpServletRequest(); - //And a CastleApi is created with doNotTrack option - CastleApi castleApi = sdk.onRequest(request).doNotTrack(true); - // And a custom handler for the async authenticate call - final AtomicReference authenticateAsyncResult = new AtomicReference<>(); - AsyncCallbackHandler handler = new AsyncCallbackHandler() { - @Override - public void onResponse(Verdict response) { - authenticateAsyncResult.set(response); - } - - @Override - public void onException(Exception exception) { - Assertions.fail("error on request", exception); - } - }; - - //When all API call are executed - castleApi.track("testEvent"); - Verdict verdict = castleApi.authenticate("testEvent", "userId"); - castleApi.authenticateAsync("testEvent", "userId", handler); - - //and we await the timeout period to avoid concurrency races - Thread.sleep(120); - - //Then no calls are made to the backend - Assertions.assertThat(server.getRequestCount()).isEqualTo(0); - //And the Verdict is the default ALLOW value - Assert.assertEquals(AuthenticateAction.ALLOW, verdict.getAction()); - Assert.assertEquals("userId", verdict.getUserId()); - Assert.assertEquals(AuthenticateAction.ALLOW, authenticateAsyncResult.get().getAction()); - Assertions.assertThat(authenticateAsyncResult.get().isFailover()).isTrue(); - } - - @Test - public void useCustomTrackHandlerWithNoTrackOptionToDisableRequests() { - //Given a mock HTTP request is provided - HttpServletRequest request = new MockHttpServletRequest(); - //And a CastleApi is created with doNotTrack option - CastleApi castleApi = sdk.onRequest(request).doNotTrack(true); - // And a custom handler for the async track call - final AtomicReference trackAsyncResult = new AtomicReference<>(); - - AsyncCallbackHandler trackHandler = new AsyncCallbackHandler() { - @Override - public void onResponse(Boolean response) { - trackAsyncResult.set(response); - } - - @Override - public void onException(Exception exception) { - Assertions.fail("error on request", exception); - - } - }; - - //When an API call is executed - castleApi.track( - "testEvent", - null, - null, - null, - null, - trackHandler - ); - - //Then no calls are made to the backend - Assertions.assertThat(server.getRequestCount()).isEqualTo(0); - - //And the track call returns and the resulting boolean is true - Assert.assertTrue(trackAsyncResult.get()); - } - - @Test - public void noTrackOptionCreateAllowFailoverVerdict() { - - //Given a mock HTTP request is provided - HttpServletRequest request = new MockHttpServletRequest(); - //And a CastleApi is created with doNotTrack option - CastleApi castleApi = sdk.onRequest(request).doNotTrack(true); - //When authenticate is called - - Verdict verdict = castleApi.authenticate("testEvent", "userId"); - - //Then no calls are made to the backend - Assertions.assertThat(server.getRequestCount()).isEqualTo(0); - //And the Verdict is the correct failover value - Assert.assertEquals(AuthenticateAction.ALLOW, verdict.getAction()); - - Assertions.assertThat(verdict.getUserId()).isEqualTo("userId"); - Assertions.assertThat(verdict.getAction()).isEqualTo(AuthenticateAction.ALLOW); - Assertions.assertThat(verdict.getFailoverReason()).isEqualTo("Castle set to do not track."); - Assertions.assertThat(verdict.isFailover()).isTrue(); - } - - - @Test - public void trackOptionAllowRequests() throws InterruptedException { - - //Given a mock HTTP request is provided - HttpServletRequest request = new MockHttpServletRequest(); - //And responses are setup on mock server - server.enqueue(new MockResponse().setResponseCode(200)); - server.enqueue(new MockResponse().setBody( - "{\n" + - " \"action\": \"deny\",\n" + - " \"user_id\": \"12345\"\n" + - "}")); - server.enqueue(new MockResponse().setBody( - "{\n" + - " \"action\": \"deny\",\n" + - " \"user_id\": \"12345\"\n" + - "}")); - //And a CastleApi is created with doNotTrack option - CastleApi castleApi = sdk.onRequest(request).doNotTrack(false); - // And a custom handler for the async authenticate call - final AtomicReference result = new AtomicReference<>(); - AsyncCallbackHandler handler = new AsyncCallbackHandler() { - @Override - public void onResponse(Verdict response) { - result.set(response); - } - - @Override - public void onException(Exception exception) { - Assertions.fail("error on request", exception); - } - }; - - //When all API call are executed - castleApi.track("testEvent"); - server.takeRequest(); - castleApi.authenticate("testEvent", "userId"); - server.takeRequest(); - castleApi.authenticateAsync("testEvent", "userId", handler); - server.takeRequest(); - - //Then all the calls are executed - waitForValue(result); - Assertions.assertThat(server.getRequestCount()).isEqualTo(3); - Assert.assertEquals(AuthenticateAction.DENY, result.get().getAction()); - } - -} diff --git a/src/test/java/io/castle/client/CastlePrivacyHttpTest.java b/src/test/java/io/castle/client/CastlePrivacyHttpTest.java new file mode 100644 index 00000000..25956c2f --- /dev/null +++ b/src/test/java/io/castle/client/CastlePrivacyHttpTest.java @@ -0,0 +1,53 @@ +package io.castle.client; + +import com.google.common.collect.ImmutableMap; +import io.castle.client.model.CastleResponse; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.RecordedRequest; +import org.assertj.core.api.Assertions; +import org.json.JSONException; +import org.junit.Assert; +import org.junit.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.mock.web.MockHttpServletRequest; + +import jakarta.servlet.http.HttpServletRequest; + +public class CastlePrivacyHttpTest extends AbstractCastleHttpLayerTest { + + @Test + public void requestUserData() throws InterruptedException, JSONException { + server.enqueue(new MockResponse().setBody("{}")); + HttpServletRequest request = new MockHttpServletRequest(); + + CastleResponse response = sdk.onRequest(request).requestUserData(ImmutableMap.builder() + .put("user_id", "12345") + .build()); + if (response == null) { + Assertions.fail("error on request"); + } + + RecordedRequest recordedRequest = server.takeRequest(); + String body = recordedRequest.getBody().readUtf8(); + Assert.assertEquals(testServerBaseUrl.resolve("v1/privacy/users"), recordedRequest.getRequestUrl()); + Assert.assertEquals("POST", recordedRequest.getMethod()); + JSONAssert.assertEquals("{\"user_id\":\"12345\"}", body, false); + } + + @Test + public void deleteUserData() throws InterruptedException, JSONException { + server.enqueue(new MockResponse().setBody("{}")); + HttpServletRequest request = new MockHttpServletRequest(); + + CastleResponse response = sdk.onRequest(request).deleteUserData(ImmutableMap.builder() + .put("user_id", "12345") + .build()); + if (response == null) { + Assertions.fail("error on request"); + } + + RecordedRequest recordedRequest = server.takeRequest(); + Assert.assertEquals(testServerBaseUrl.resolve("v1/privacy/users"), recordedRequest.getRequestUrl()); + Assert.assertEquals("DELETE", recordedRequest.getMethod()); + } +} diff --git a/src/test/java/io/castle/client/CastleRecoverHttpTest.java b/src/test/java/io/castle/client/CastleRecoverHttpTest.java deleted file mode 100644 index cdc76bd0..00000000 --- a/src/test/java/io/castle/client/CastleRecoverHttpTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.castle.client; - -import io.castle.client.model.*; -import io.castle.client.utils.DeviceUtils; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.RecordedRequest; -import org.assertj.core.api.Assertions; -import org.junit.Assert; -import org.junit.Test; -import org.springframework.mock.web.MockHttpServletRequest; - -import jakarta.servlet.http.HttpServletRequest; - -import static okhttp3.mockwebserver.SocketPolicy.NO_RESPONSE; - -public class CastleRecoverHttpTest extends AbstractCastleHttpLayerTest { - - public CastleRecoverHttpTest() { - super(new AuthenticateFailoverStrategy(AuthenticateAction.CHALLENGE)); - } - - @Test public void recoverDevices() throws InterruptedException { - MockResponse mockResponse = new MockResponse(); - mockResponse.setBody(DeviceUtils.getDefaultDeviceJSON()); - server.enqueue(mockResponse); - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // And an report device request is made - CastleResponse response = sdk.onRequest(request).recover("1234"); - - // Then - RecordedRequest recordedRequest = server.takeRequest(); - Assert.assertEquals(testServerBaseUrl.resolve("v1/users/1234/recover"), recordedRequest.getRequestUrl()); - Assert.assertEquals("PUT", recordedRequest.getMethod()); - } -} diff --git a/src/test/java/io/castle/client/CastleRemoveUserHttpTest.java b/src/test/java/io/castle/client/CastleRemoveUserHttpTest.java deleted file mode 100644 index 06233e8d..00000000 --- a/src/test/java/io/castle/client/CastleRemoveUserHttpTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package io.castle.client; - -import io.castle.client.model.AuthenticateAction; -import io.castle.client.model.AuthenticateFailoverStrategy; -import io.castle.client.model.CastleApiInternalServerErrorException; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.RecordedRequest; -import org.junit.Assert; -import org.junit.Test; -import org.springframework.mock.web.MockHttpServletRequest; - -import jakarta.servlet.http.HttpServletRequest; - -public class CastleRemoveUserHttpTest extends AbstractCastleHttpLayerTest { - - public CastleRemoveUserHttpTest() { - super(new AuthenticateFailoverStrategy(AuthenticateAction.CHALLENGE)); - } - - @Test - public void removeUser() throws Exception { - // Mock a response - MockResponse mockResponse = new MockResponse(); - mockResponse.setResponseCode(202); - server.enqueue(mockResponse); - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // And an privacy remove user request is made - Boolean response = sdk.onRequest(request).removeUser("user-test-id"); - - // Then - RecordedRequest recordedRequest = server.takeRequest(); - Assert.assertEquals(testServerBaseUrl.resolve("v1/privacy/users/user-test-id"), recordedRequest.getRequestUrl()); - Assert.assertEquals("DELETE", recordedRequest.getMethod()); - Assert.assertTrue(response); - } - - @Test - public void removeUserNotFoundTest() throws Exception { - // Mock a response - MockResponse mockResponse = new MockResponse(); - mockResponse.setResponseCode(404); - server.enqueue(mockResponse); - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // And an privacy remove user request is made - Boolean response = sdk.onRequest(request).removeUser("user-test-id"); - - // Then it doesn't throw exception - RecordedRequest recordedRequest = server.takeRequest(); - Assert.assertEquals(testServerBaseUrl.resolve("v1/privacy/users/user-test-id"), recordedRequest.getRequestUrl()); - Assert.assertEquals("DELETE", recordedRequest.getMethod()); - - Assert.assertNull(response); - } - - @Test(expected = CastleApiInternalServerErrorException.class) - public void removeUserServerError() { - // Mock a response - MockResponse mockResponse = new MockResponse(); - mockResponse.setResponseCode(500); - server.enqueue(mockResponse); - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // And a privacy remove user request is made, exception throw - Boolean response = sdk.onRequest(request).removeUser("user-test-id"); - - Assert.assertNull(response); - } -} \ No newline at end of file diff --git a/src/test/java/io/castle/client/CastleRiskHttpTest.java b/src/test/java/io/castle/client/CastleRiskHttpTest.java index a3bb7a26..59bd2957 100644 --- a/src/test/java/io/castle/client/CastleRiskHttpTest.java +++ b/src/test/java/io/castle/client/CastleRiskHttpTest.java @@ -2,8 +2,6 @@ import com.google.gson.JsonParser; import io.castle.client.internal.json.CastleGsonModel; -import io.castle.client.model.AuthenticateAction; -import io.castle.client.model.AuthenticateFailoverStrategy; import io.castle.client.model.generated.*; import jakarta.servlet.http.HttpServletRequest; import okhttp3.mockwebserver.MockResponse; @@ -18,10 +16,6 @@ public class CastleRiskHttpTest extends AbstractCastleHttpLayerTest { - public CastleRiskHttpTest() { - super(new AuthenticateFailoverStrategy(AuthenticateAction.CHALLENGE)); - } - @Test public void risk() throws InterruptedException { MockResponse mockResponse = new MockResponse(); mockResponse.setBody("{\n" + diff --git a/src/test/java/io/castle/client/CastleThrowStrategyTest.java b/src/test/java/io/castle/client/CastleThrowStrategyTest.java deleted file mode 100644 index c5c7df0e..00000000 --- a/src/test/java/io/castle/client/CastleThrowStrategyTest.java +++ /dev/null @@ -1,92 +0,0 @@ -package io.castle.client; - -import io.castle.client.model.*; -import okhttp3.mockwebserver.MockResponse; -import org.assertj.core.api.Assertions; -import org.junit.Test; -import org.springframework.mock.web.MockHttpServletRequest; - -import jakarta.servlet.http.HttpServletRequest; -import java.util.concurrent.atomic.AtomicReference; - -import static okhttp3.mockwebserver.SocketPolicy.NO_RESPONSE; - -public class CastleThrowStrategyTest extends AbstractCastleHttpLayerTest { - - public CastleThrowStrategyTest() { - super(new AuthenticateFailoverStrategy()); - } - - @Test(expected = CastleApiTimeoutException.class) - public void onThrowStrategyAuthenticateThrowCastleRuntimeExceptionWhenNoResponse() { - // given the throw strategy is setup (see constructor) - // and backend request timeouts - server.enqueue(new MockResponse().setSocketPolicy(NO_RESPONSE)); - // and - String id = "12345"; - String event = "$login.succeeded"; - - // and a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // when authenticate request is made - sdk.onRequest(request).authenticate(event, id); - - // then the exception is throw - } - - @Test(expected = CastleApiInternalServerErrorException.class) - public void onThrowStrategyAuthenticateThrowCastleRuntimeExceptionWhenResponseIs500() { - // given the throw strategy is setup (see constructor) - // and backend request timeouts - server.enqueue(new MockResponse().setResponseCode(500)); - // and - String id = "12345"; - String event = "$login.succeeded"; - - // and a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // when authenticate request is made - Verdict authenticate = sdk.onRequest(request).authenticate(event, id); - - // then the exception is throw - Assertions.fail("A exception should be throw"); - } - - @Test - public void onThrowStrategyAuthenticateAsyncThrowCastleRuntimeException() { - // given the throw strategy is setup (see constructor) - // and backend request timeouts - server.enqueue(new MockResponse().setSocketPolicy(NO_RESPONSE)); - // and - String id = "12345"; - String event = "$login.succeeded"; - - // and a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - // and a async callback is prepared - final AtomicReference exceptionAtomicReference = new AtomicReference<>(); - AsyncCallbackHandler callback = new AsyncCallbackHandler() { - @Override - public void onResponse(Verdict response) { - Assertions.fail("A exceptions was expected"); - } - - @Override - public void onException(Exception exception) { - exceptionAtomicReference.set(exception); - } - }; - // when authenticate request is made - sdk.onRequest(request).authenticateAsync(event, id, callback); - - // then the exception is throw in the callback - Exception exception = waitForValue(exceptionAtomicReference); - Assertions.assertThat(exception) - .isNotNull() - .isInstanceOf(CastleRuntimeException.class); - } - - -} diff --git a/src/test/java/io/castle/client/CastleTrackHttpTest.java b/src/test/java/io/castle/client/CastleTrackHttpTest.java deleted file mode 100644 index 9fb036db..00000000 --- a/src/test/java/io/castle/client/CastleTrackHttpTest.java +++ /dev/null @@ -1,313 +0,0 @@ -package io.castle.client; - -import io.castle.client.model.AsyncCallbackHandler; -import io.castle.client.model.AuthenticateAction; -import io.castle.client.model.AuthenticateFailoverStrategy; -import io.castle.client.model.CastleContext; -import io.castle.client.model.CastleHeaders; -import io.castle.client.utils.SDKVersion; -import io.castle.client.model.CastleMessage; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.RecordedRequest; -import okhttp3.mockwebserver.SocketPolicy; -import org.assertj.core.api.Assertions; -import org.json.JSONException; -import org.json.JSONObject; -import org.junit.Assert; -import org.junit.Test; -import org.skyscreamer.jsonassert.JSONAssert; -import org.springframework.mock.web.MockHttpServletRequest; - -import com.google.common.collect.ImmutableMap; - -import jakarta.servlet.http.HttpServletRequest; -import java.util.concurrent.atomic.AtomicReference; - -public class CastleTrackHttpTest extends AbstractCastleHttpLayerTest { - - public CastleTrackHttpTest() { - super(new AuthenticateFailoverStrategy(AuthenticateAction.CHALLENGE)); - } - - @Test public void trackWithManualContext() throws Exception { - server.enqueue(new MockResponse()); - - - // When - CastleContext context = sdk.contextBuilder() - .ip("1.1.1.1") - .userAgent("Mozilla/5.0") - .clientId("") - .headers(CastleHeaders.builder() - .add("User-Agent", "Mozilla/5.0") - .add("Accept-Language", "sv-se") - .build() - ) - .build(); - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // And a track request is made - sdk.onRequest(request).track(CastleMessage.builder("$login.succeeded") - .userId("12345") - .context(context) - .build() - ); - - - // Then - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - JSONAssert.assertEquals("{\"event\":\"$login.succeeded\",\"user_id\":\"12345\",\"context\":{\"active\":true,\"client_id\":\"\",\"ip\":\"1.1.1.1\",\"headers\":{\"User-Agent\":\"Mozilla/5.0\",\"Accept-Language\":\"sv-se\"}," + SDKVersion.getLibraryString() +",\"user_agent\":\"Mozilla/5.0\"}}", - body, false); - - Assert.assertTrue(new JSONObject(body).has("sent_at")); - } - - @Test - public void trackEndpointHttpAuthorize() throws Exception { - // Given - Castle.verifySdkConfigurationAndInitialize(); - - server.enqueue(new MockResponse()); - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // And an track request is made - sdk.onRequest(request).track("$login.successful", "1234"); - - // Then - RecordedRequest recordedRequest = server.takeRequest(); - Assert.assertEquals("Basic OnRlc3RfYXBpX3NlY3JldA==", recordedRequest.getHeader("Authorization")); - } - - @Test - public void trackEndpointWithPaylod() throws InterruptedException, JSONException { - // Given - server.enqueue(new MockResponse()); - CastleMessage payload = CastleMessage.builder("$login.succeeded") - .userId("12345") - .build(); - HttpServletRequest request = new MockHttpServletRequest(); - - // When - sdk.onRequest(request).track(payload); - - // Then - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - JSONAssert.assertEquals("{\"event\":\"$login.succeeded\",\"user_id\":\"12345\",\"context\":{\"active\":true,\"ip\":\"127.0.0.1\",\"headers\":{\"REMOTE_ADDR\":\"127.0.0.1\"}," + SDKVersion.getLibraryString() +"}}", - body, false); - - Assert.assertTrue(new JSONObject(body).has("sent_at")); - } - - @Test - public void trackEndpointWithUserIDTest() throws InterruptedException, JSONException { - //given - server.enqueue(new MockResponse()); - String id = "12345"; - String event = "$login.succeeded"; - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // and an track request is made - sdk.onRequest(request).track(event, id); - - // then - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - JSONAssert.assertEquals("{\"event\":\"$login.succeeded\",\"user_id\":\"12345\",\"context\":{\"active\":true,\"ip\":\"127.0.0.1\",\"headers\":{\"REMOTE_ADDR\":\"127.0.0.1\"}," + SDKVersion.getLibraryString() +"}}", - body, false); - - Assert.assertTrue(new JSONObject(body).has("sent_at")); - } - - @Test - public void trackEndpointWithUserIDAndReviewIdTest() throws InterruptedException, JSONException { - //given - server.enqueue(new MockResponse()); - String userId = "12345"; - String reviewId = "r45677"; - String event = "$login.succeeded"; - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // and an track request is made - sdk.onRequest(request).track(event, userId,reviewId); - - // then - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - JSONAssert.assertEquals("{\"event\":\"$login.succeeded\",\"review_id\":\"r45677\",\"user_id\":\"12345\",\"context\":{\"active\":true,\"ip\":\"127.0.0.1\",\"headers\":{\"REMOTE_ADDR\":\"127.0.0.1\"}," + SDKVersion.getLibraryString() +"}}", - body, false); - - Assert.assertTrue(new JSONObject(body).has("sent_at")); - } - - @Test - public void trackEndpointWithUserIDAndPropertiesTest() throws InterruptedException, JSONException { - //given - server.enqueue(new MockResponse()); - String userId = "12345"; - String reviewId = "r987"; - String event = "$login.succeeded"; - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // and an track request is made - sdk.onRequest(request).track(event, userId, reviewId, ImmutableMap.builder() - .put("a","valueA") - .put("b",123456) - .build()); - - // then - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - JSONAssert.assertEquals("{\"event\":\"$login.succeeded\",\"properties\":{\"a\":\"valueA\",\"b\":123456},\"review_id\":\"r987\",\"user_id\":\"12345\",\"context\":{\"active\":true,\"ip\":\"127.0.0.1\",\"headers\":{\"REMOTE_ADDR\":\"127.0.0.1\"}," + SDKVersion.getLibraryString() +"}}", - body, false); - - Assert.assertTrue(new JSONObject(body).has("sent_at")); - } - - @Test - public void trackEndpointWithUserIDAndPropertiesAndTraitTest() throws InterruptedException, JSONException { - //given - server.enqueue(new MockResponse()); - String userId = "23456"; - String reviewId = "r987"; - String event = "$login.succeeded"; - - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // and an track request is made - sdk.onRequest(request).track(event, userId, reviewId, ImmutableMap.builder() - .put("a","valueA") - .put("b",123456) - .build(), ImmutableMap.builder() - .put("x", "x value") - .put("y", 2342) - .build()); - - // then - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - JSONAssert.assertEquals("{\"event\":\"$login.succeeded\",\"properties\":{\"a\":\"valueA\",\"b\":123456},\"review_id\":\"r987\",\"user_id\":\"23456\",\"user_traits\":{\"x\":\"x value\",\"y\":2342},\"context\":{\"active\":true,\"ip\":\"127.0.0.1\",\"headers\":{\"REMOTE_ADDR\":\"127.0.0.1\"}," + SDKVersion.getLibraryString() +"}}", - body, false); - - Assert.assertTrue(new JSONObject(body).has("sent_at")); - } - - @Test - public void trackEndpointWithMinimalInformationTest() throws InterruptedException, JSONException { - //given - server.enqueue(new MockResponse()); - String event = "any.valid.event"; - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // and an track request is made - sdk.onRequest(request).track(event); - - // then - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - JSONAssert.assertEquals("{\"event\":\"any.valid.event\",\"context\":{\"active\":true,\"ip\":\"127.0.0.1\",\"headers\":{\"REMOTE_ADDR\":\"127.0.0.1\"}," + SDKVersion.getLibraryString() +"}}", - body, false); - - Assert.assertTrue(new JSONObject(body).has("sent_at")); - } - - @Test - public void trackEndpointAsyncTest() throws InterruptedException { - //given - server.enqueue(new MockResponse().setResponseCode(200)); - String event = "any.valid.event"; - // And a mock Request - HttpServletRequest request = new MockHttpServletRequest(); - - // and an async track request is made - final AtomicReference result = new AtomicReference<>(); - AsyncCallbackHandler callback = new AsyncCallbackHandler() { - @Override - public void onResponse(Boolean response) { - result.set(response); - } - - @Override - public void onException(Exception exception) { - Assertions.fail("No exception expected here"); - } - }; - sdk.onRequest(request).track(event, null, null, null,null,callback); - - // then the callback response is called with true - Boolean extracted = waitForValue(result); - Assertions.assertThat(extracted).isTrue(); - } - - @Test - public void trackEndpointTimeoutTest() throws InterruptedException, JSONException { - // given the backend will timeout - server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.NO_RESPONSE)); - String event = "any.valid.event"; - // and a mock Request - final HttpServletRequest request = new MockHttpServletRequest(); - - // and a async handler is prepared - final AtomicReference result = new AtomicReference<>(); - AsyncCallbackHandler callback = new AsyncCallbackHandler() { - @Override - public void onResponse(Boolean response) { - Assertions.fail("Request should end with an timeout exception and not result"); - } - - @Override - public void onException(Exception exception) { - result.set(false); - } - }; - // when an track request is made - sdk.onRequest(request).track(event, null, null, null,null,callback); - - // then the track request must be send - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - JSONAssert.assertEquals("{\"event\":\"any.valid.event\",\"context\":{\"active\":true,\"ip\":\"127.0.0.1\",\"headers\":{\"REMOTE_ADDR\":\"127.0.0.1\"}," + SDKVersion.getLibraryString() +"}}", - body, false); - - Assert.assertTrue(new JSONObject(body).has("sent_at")); - - // and the onException method must be called - waitForValueAndVerify(result, Boolean.FALSE); - } - - @Test - public void trackEndpointTimeoutAreIgnoreWhenNoCallbackIsProvidedTest() throws InterruptedException, JSONException { - // given the backend will timeout - server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.NO_RESPONSE)); - String event = "any.valid.event"; - // and a mock Request - final HttpServletRequest request = new MockHttpServletRequest(); - - // when an track request is made - sdk.onRequest(request).track(event, null, null, null); - - // then the track request must be send - RecordedRequest recordedRequest = server.takeRequest(); - String body = recordedRequest.getBody().readUtf8(); - JSONAssert.assertEquals("{\"event\":\"any.valid.event\",\"context\":{\"active\":true,\"ip\":\"127.0.0.1\",\"headers\":{\"REMOTE_ADDR\":\"127.0.0.1\"}," + SDKVersion.getLibraryString() +"}}", - body, false); - - Assert.assertTrue(new JSONObject(body).has("sent_at")); - - // and no exceptions are thrown in any thread - } - -} \ No newline at end of file diff --git a/src/test/java/io/castle/client/CastleWebhookTest.java b/src/test/java/io/castle/client/CastleWebhookTest.java new file mode 100644 index 00000000..c6478a97 --- /dev/null +++ b/src/test/java/io/castle/client/CastleWebhookTest.java @@ -0,0 +1,38 @@ +package io.castle.client; + +import io.castle.client.internal.utils.Webhook; +import io.castle.client.model.CastleSdkConfigurationException; +import org.assertj.core.api.Assertions; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class CastleWebhookTest { + + private static final String SECRET = "test_api_secret"; + + @Test + public void verifiesValidSignature() throws CastleSdkConfigurationException { + Castle sdk = Castle.verifySdkConfigurationAndInitialize(); + byte[] body = "{\"type\":\"$review.opened\"}".getBytes(StandardCharsets.UTF_8); + String signature = Webhook.computeSignature(SECRET, body); + + Assertions.assertThat(sdk.verifyWebhookSignature(signature, body)).isTrue(); + } + + @Test + public void rejectsInvalidSignature() throws CastleSdkConfigurationException { + Castle sdk = Castle.verifySdkConfigurationAndInitialize(); + byte[] body = "{\"type\":\"$review.opened\"}".getBytes(StandardCharsets.UTF_8); + + Assertions.assertThat(sdk.verifyWebhookSignature("not-the-signature", body)).isFalse(); + } + + @Test + public void rejectsNullSignature() throws CastleSdkConfigurationException { + Castle sdk = Castle.verifySdkConfigurationAndInitialize(); + byte[] body = "{}".getBytes(StandardCharsets.UTF_8); + + Assertions.assertThat(sdk.verifyWebhookSignature((String) null, body)).isFalse(); + } +} diff --git a/src/test/java/io/castle/client/internal/config/ConfigurationLoaderTest.java b/src/test/java/io/castle/client/internal/config/ConfigurationLoaderTest.java index 25d0ac7d..890a5c1c 100644 --- a/src/test/java/io/castle/client/internal/config/ConfigurationLoaderTest.java +++ b/src/test/java/io/castle/client/internal/config/ConfigurationLoaderTest.java @@ -1,7 +1,5 @@ package io.castle.client.internal.config; -import io.castle.client.model.AuthenticateAction; -import io.castle.client.model.AuthenticateFailoverStrategy; import io.castle.client.model.CastleRuntimeException; import io.castle.client.model.CastleSdkConfigurationException; import org.assertj.core.api.Assertions; @@ -88,7 +86,6 @@ public void loadFromProperties() throws CastleSdkConfigurationException { ) .withDefaultBackendProvider() .withTimeout(100) - .withAuthenticateFailoverStrategy(new AuthenticateFailoverStrategy(AuthenticateAction.CHALLENGE)) .build(); //Then the value of the timeout should be the one in the properties file castle_sdk.properties @@ -145,7 +142,6 @@ public void loadFromEnv() throws CastleSdkConfigurationException { .withApiBaseUrl("https://api.dev.castle.io/v1/") .withTimeout(700) .withLogHttpRequests(true) - .withAuthenticateFailoverStrategy(new AuthenticateFailoverStrategy(AuthenticateAction.DENY)) .build(); testLoad(expectedConfiguration); @@ -193,7 +189,6 @@ public void loadFailoverThrowStrategyFromEnv() throws CastleSdkConfigurationExce .defaultConfigBuilder() .withApiSecret("1234") .withCastleAppId("test_app_id_env") - .withAuthenticateFailoverStrategy(new AuthenticateFailoverStrategy()) .build(); // when then diff --git a/src/test/java/io/castle/client/internal/utils/VerdictTransportModelTest.java b/src/test/java/io/castle/client/internal/utils/VerdictTransportModelTest.java deleted file mode 100644 index 057c1279..00000000 --- a/src/test/java/io/castle/client/internal/utils/VerdictTransportModelTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.castle.client.internal.utils; - -import io.castle.client.internal.json.CastleGsonModel; -import io.castle.client.model.AuthenticateAction; -import io.castle.client.model.RiskPolicyType; -import org.assertj.core.api.Assertions; -import org.junit.Test; - -public class VerdictTransportModelTest { - - @Test - public void transportProtocolIsWellDeserialized() { - // given a json input value - String json = "{\n" + - " \"action\": \"deny\",\n" + - " \"user_id\": \"12345\",\n" + - " \"device_token\": \"abcdefg1234\",\n" + - " \"risk_policy\": {\n" + - " \"id\": \"q-rbeMzBTdW2Fd09sbz55A\",\n" + - " \"revision_id\": \"pke4zqO2TnqVr-NHJOAHEg\",\n" + - " \"name\": \"Block Users from X\",\n" + - " \"type\": \"bot\"\n" + - " }\n" + - "}"; - // and castle gson model - CastleGsonModel model = new CastleGsonModel(); - - - // when the Verdict transport object is deserialized - VerdictTransportModel loaded = model.getGson().fromJson(json, VerdictTransportModel.class); - - Assertions.assertThat(loaded.getAction()).isEqualTo(AuthenticateAction.DENY); - Assertions.assertThat(loaded.getDeviceToken()).isEqualTo("abcdefg1234"); - Assertions.assertThat(loaded.getUserId()).isEqualTo("12345"); - Assertions.assertThat(loaded.getRiskPolicy().getId()).isEqualTo("q-rbeMzBTdW2Fd09sbz55A"); - Assertions.assertThat(loaded.getRiskPolicy().getRevisionId()).isEqualTo("pke4zqO2TnqVr-NHJOAHEg"); - Assertions.assertThat(loaded.getRiskPolicy().getName()).isEqualTo("Block Users from X"); - Assertions.assertThat(loaded.getRiskPolicy().getType()).isEqualTo(RiskPolicyType.BOT); - } -} diff --git a/src/test/java/io/castle/client/model/AuthenticationActionTest.java b/src/test/java/io/castle/client/model/AuthenticationActionTest.java deleted file mode 100644 index 57c1e5d0..00000000 --- a/src/test/java/io/castle/client/model/AuthenticationActionTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.castle.client.model; - -import org.junit.Assert; -import org.junit.Test; - -public class AuthenticationActionTest { - - @Test - public void testFromAllow() { - testFromActionMethod("AllOw", AuthenticateAction.ALLOW); - } - - @Test - public void testFromDeny() { - testFromActionMethod("Deny", AuthenticateAction.DENY); - } - - @Test - public void testFromChallenge() { - testFromActionMethod("cHallenge", AuthenticateAction.CHALLENGE); - } - - @Test - public void testFromNull() { - testFromActionMethod(null, null); - } - - @Test - public void testFromEmptyString() { - testFromActionMethod("", null); - } - - @Test - public void testFromWrongString() { - testFromActionMethod("getMeANull", null); - } - - private void testFromActionMethod(String from, AuthenticateAction expected) { - - AuthenticateAction authenticateAction = AuthenticateAction.fromAction(from); - Assert.assertEquals(expected, authenticateAction); - - } - - -} diff --git a/src/test/java/io/castle/client/model/CastleConfigurationBuilderTest.java b/src/test/java/io/castle/client/model/CastleConfigurationBuilderTest.java index 7805885c..053dbb33 100644 --- a/src/test/java/io/castle/client/model/CastleConfigurationBuilderTest.java +++ b/src/test/java/io/castle/client/model/CastleConfigurationBuilderTest.java @@ -23,7 +23,6 @@ public void buildDefaultConfiguration() throws CastleSdkConfigurationException { Assertions.assertThat(config.getDenyListHeaders()).contains("cookie"); Assertions.assertThat(config.getDenyListHeaders()).contains("authorization"); Assertions.assertThat(config.getAllowListHeaders()).isEmpty(); - Assertions.assertThat(config.getAuthenticateFailoverStrategy().getDefaultAction()).isEqualTo(AuthenticateAction.ALLOW); Assertions.assertThat(config.getTimeout()).isEqualTo(500); diff --git a/src/test/java/io/castle/client/model/CastleMessageTest.java b/src/test/java/io/castle/client/model/CastleMessageTest.java deleted file mode 100644 index 1cbbce0e..00000000 --- a/src/test/java/io/castle/client/model/CastleMessageTest.java +++ /dev/null @@ -1,129 +0,0 @@ -package io.castle.client.model; - -import com.google.common.collect.ImmutableMap; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import io.castle.client.internal.json.CastleGsonModel; -import org.assertj.core.api.Assertions; -import org.junit.Assert; -import org.junit.Test; - -import java.util.HashMap; - -public class CastleMessageTest { - - private CastleGsonModel model = new CastleGsonModel(); - - @Test - public void jsonSerialized() { - // Given - CastleMessage message = new CastleMessage("event"); - message.setCreatedAt("2018-01-01"); - message.setTimestamp("2018-01-01"); - message.setDeviceToken("1234"); - message.setReviewId("2345"); - message.setProperties(ImmutableMap.builder() - .put("key", "val") - .build()); - message.setUserId("3456"); - message.setUserTraits(ImmutableMap.builder() - .put("key", "val") - .build()); - - // When - String payloadJson = model.getGson().toJson(message); - String expected = "{\"created_at\":\"2018-01-01\",\"timestamp\":\"2018-01-01\",\"device_token\":\"1234\",\"event\":\"event\",\"properties\":{\"key\":\"val\"},\"review_id\":\"2345\",\"user_id\":\"3456\",\"user_traits\":{\"key\":\"val\"}}"; - - // Then - Assertions.assertThat(JsonParser.parseString(payloadJson)).isEqualTo(JsonParser.parseString(expected)); - - } - - @Test - public void fullBuilderJson() { - // Given - CastleMessage message = CastleMessage.builder("event") - .createdAt("2018-01-01") - .timestamp("2018-01-01") - .deviceToken("1234") - .reviewId("2345") - .properties(ImmutableMap.builder() - .put("key", "val") - .build()) - .userId("3456") - .userTraits(ImmutableMap.builder() - .put("key", "val") - .build()) - .build(); - - Assert.assertEquals(message.getCreatedAt(), "2018-01-01"); - Assert.assertEquals(message.getDeviceToken(), "1234"); - Assert.assertEquals(message.getReviewId(), "2345"); - Assert.assertEquals(message.getUserId(), "3456"); - Assert.assertEquals(message.getEvent(), "event"); - Assert.assertEquals(message.getUserTraits(), ImmutableMap.builder() - .put("key", "val") - .build()); - Assert.assertEquals(message.getProperties(), ImmutableMap.builder() - .put("key", "val") - .build()); - - // When - String payloadJson = model.getGson().toJson(message); - String expected = "{\"created_at\":\"2018-01-01\",\"timestamp\":\"2018-01-01\",\"device_token\":\"1234\",\"event\":\"event\",\"properties\":{\"key\":\"val\"},\"review_id\":\"2345\",\"user_id\":\"3456\",\"user_traits\":{\"key\":\"val\"}}"; - - // Then - Assertions.assertThat(JsonParser.parseString(payloadJson)).isEqualTo(JsonParser.parseString(expected)); - } - - @Test - public void properties() { - JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("key", "value"); - - CastleMessage message = CastleMessage.builder("event") - .properties(jsonObject) - .build(); - - String payloadJson = model.getGson().toJson(message); - String expected = "{\"event\":\"event\",\"properties\":{\"key\":\"value\"}}"; - - Assertions.assertThat(JsonParser.parseString(payloadJson)).isEqualTo(JsonParser.parseString(expected)); - } - - @Test - public void otherProperties() { - CastleMessage message = CastleMessage.builder("event") - .put("key", "value") - .build(); - - String payloadJson = model.getGson().toJson(message); - - Assertions.assertThat(payloadJson).isEqualTo("{\"event\":\"event\",\"key\":\"value\"}"); - - HashMap other = new HashMap(); - other.put("key", "value"); - - message = CastleMessage.builder("event") - .other(other) - .build(); - - payloadJson = model.getGson().toJson(message); - - Assertions.assertThat(payloadJson).isEqualTo("{\"event\":\"event\",\"key\":\"value\"}"); - } - - @Test(expected = NullPointerException.class) - public void propertiesNull() { - CastleMessage message = CastleMessage.builder("event") - .properties(null) - .build(); - } - - @Test(expected = NullPointerException.class) - public void userTraitsNull() { - CastleMessage message = CastleMessage.builder("event") - .userTraits(null) - .build(); - } -} diff --git a/src/test/java/io/castle/client/model/CastleUserDeviceContextTest.java b/src/test/java/io/castle/client/model/CastleUserDeviceContextTest.java deleted file mode 100644 index b46563bf..00000000 --- a/src/test/java/io/castle/client/model/CastleUserDeviceContextTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package io.castle.client.model; - -import com.google.gson.JsonParser; -import io.castle.client.internal.json.CastleGsonModel; -import io.castle.client.utils.DeviceUtils; -import org.assertj.core.api.Assertions; -import org.junit.Assert; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.List; - -public class CastleUserDeviceContextTest { - private CastleGsonModel model = new CastleGsonModel(); - - @Test - public void jsonSerialized() { - // Given - CastleUserDeviceContext deviceContext = new CastleUserDeviceContext(); - - // When - String payloadJson = model.getGson().toJson(deviceContext); - - // Then - Assertions.assertThat(payloadJson).isEqualTo("{}"); - } - - @Test - public void fullBuilderJson() { - // Given - CastleUserDeviceContext deviceContext = new CastleUserDeviceContext(); - deviceContext.setIp(DeviceUtils.CONTEXT_IP); - deviceContext.setType(DeviceUtils.CONTEXT_TYPE); - - DeviceUserAgent userAgent = new DeviceUserAgent(); - userAgent.setBrowser(DeviceUtils.USER_AGENT_BROWSER); - userAgent.setDevice(DeviceUtils.USER_AGENT_DEVICE); - userAgent.setFamily(DeviceUtils.USER_AGENT_FAMILY); - userAgent.setMobile(DeviceUtils.USER_AGENT_MOBILE); - userAgent.setOs(DeviceUtils.USER_AGENT_OS); - userAgent.setPlatform(DeviceUtils.USER_AGENT_PLATFORM); - userAgent.setRaw(DeviceUtils.USER_AGENT_RAW); - userAgent.setVersion(DeviceUtils.USER_AGENT_VERSION); - deviceContext.setUserAgent(userAgent); - - // When - String payloadJson = model.getGson().toJson(deviceContext); - String expected = "{\"ip\":\"1.1.1.1\",\"user_agent\":{\"raw\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36 OPR/54.0.2952.51\",\"browser\":\"Opera\",\"version\":\"54.0.2952\",\"os\":\"Mac OS X 10.13.6\",\"mobile\":false,\"platform\":\"Mac OS X\",\"device\":\"Unknown\",\"family\":\"Opera\"},\"type\":\"desktop\"}"; - - // Then - Assertions.assertThat(JsonParser.parseString(payloadJson)).isEqualTo(JsonParser.parseString(expected)); - - Assert.assertEquals(deviceContext.getIp(), DeviceUtils.CONTEXT_IP); - Assert.assertEquals(deviceContext.getType(), DeviceUtils.CONTEXT_TYPE); - Assert.assertEquals(deviceContext.getUserAgent(), userAgent); - } -} diff --git a/src/test/java/io/castle/client/model/CastleUserDeviceTest.java b/src/test/java/io/castle/client/model/CastleUserDeviceTest.java deleted file mode 100644 index 8011231c..00000000 --- a/src/test/java/io/castle/client/model/CastleUserDeviceTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package io.castle.client.model; - -import com.google.gson.JsonParser; -import io.castle.client.internal.json.CastleGsonModel; -import io.castle.client.utils.DeviceUtils; -import org.assertj.core.api.Assertions; -import org.junit.Assert; -import org.junit.Test; - -public class CastleUserDeviceTest { - - private final CastleGsonModel model = new CastleGsonModel(); - - @Test - public void jsonSerialized() { - // Given - CastleUserDevice device = new CastleUserDevice(); - - // When - String payloadJson = model.getGson().toJson(device); - String expected = "{\"risk\":0.0,\"is_current_device\":false}"; - - // Then - Assert.assertEquals(JsonParser.parseString(payloadJson), JsonParser.parseString(expected)); - } - - @Test - public void fullBuilderJson() { - // Given - CastleUserDevice device = new CastleUserDevice(); - device.setToken(DeviceUtils.DEVICE_TOKEN); - device.setCreatedAt(DeviceUtils.DEVICE_CREATED_AT); - device.setLastSeenAt(DeviceUtils.DEVICE_LAST_SEEN_AT); - device.setApprovedAt(DeviceUtils.DEVICE_APPROVED_AT); - device.setEscalatedAt(DeviceUtils.DEVICE_ESCALATED_AT); - device.setMitigatedAt(DeviceUtils.DEVICE_MITIGATED_AT); - device.setCurrentDevice(true); - - CastleUserDeviceContext deviceContext = new CastleUserDeviceContext(); - deviceContext.setIp(DeviceUtils.CONTEXT_IP); - deviceContext.setType(DeviceUtils.CONTEXT_TYPE); - - DeviceUserAgent userAgent = new DeviceUserAgent(); - userAgent.setBrowser(DeviceUtils.USER_AGENT_BROWSER); - userAgent.setDevice(DeviceUtils.USER_AGENT_DEVICE); - userAgent.setFamily(DeviceUtils.USER_AGENT_FAMILY); - userAgent.setMobile(DeviceUtils.USER_AGENT_MOBILE); - userAgent.setOs(DeviceUtils.USER_AGENT_OS); - userAgent.setPlatform(DeviceUtils.USER_AGENT_PLATFORM); - userAgent.setRaw(DeviceUtils.USER_AGENT_RAW); - userAgent.setVersion(DeviceUtils.USER_AGENT_VERSION); - deviceContext.setUserAgent(userAgent); - - device.setContext(deviceContext); - - // When - String payloadJson = model.getGson().toJson(device); - String expected = "{\"token\":\"abcdefg12345\",\"risk\":0.0,\"created_at\":\"2018-06-15T16:36:22.916Z\",\"last_seen_at\":\"2018-07-19T23:09:29.681Z\",\"context\":{\"ip\":\"1.1.1.1\",\"user_agent\":{\"raw\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36 OPR/54.0.2952.51\",\"browser\":\"Opera\",\"version\":\"54.0.2952\",\"os\":\"Mac OS X 10.13.6\",\"mobile\":false,\"platform\":\"Mac OS X\",\"device\":\"Unknown\",\"family\":\"Opera\"},\"type\":\"desktop\"},\"is_current_device\":true}"; - - // Then - Assertions.assertThat(JsonParser.parseString(payloadJson)).isEqualTo(JsonParser.parseString(expected)); - - Assert.assertEquals(device.getToken(), DeviceUtils.DEVICE_TOKEN); - Assert.assertEquals(device.getCreatedAt(), DeviceUtils.DEVICE_CREATED_AT); - Assert.assertEquals(device.getLastSeenAt(), DeviceUtils.DEVICE_LAST_SEEN_AT); - Assert.assertEquals(device.getApprovedAt(), DeviceUtils.DEVICE_APPROVED_AT); - Assert.assertEquals(device.getEscalatedAt(), DeviceUtils.DEVICE_ESCALATED_AT); - Assert.assertEquals(device.getMitigatedAt(), DeviceUtils.DEVICE_MITIGATED_AT); - Assert.assertTrue(device.isCurrentDevice()); - Assert.assertEquals(device.getContext(), deviceContext); - } -} diff --git a/src/test/java/io/castle/client/model/CastleUserDevicesTest.java b/src/test/java/io/castle/client/model/CastleUserDevicesTest.java deleted file mode 100644 index a78050b0..00000000 --- a/src/test/java/io/castle/client/model/CastleUserDevicesTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package io.castle.client.model; - -import com.google.gson.JsonParser; -import io.castle.client.internal.json.CastleGsonModel; -import io.castle.client.utils.DeviceUtils; -import org.assertj.core.api.Assertions; -import org.junit.Assert; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.List; - -public class CastleUserDevicesTest { - private CastleGsonModel model = new CastleGsonModel(); - - @Test - public void jsonSerialized() { - // Given - CastleUserDevices devices = new CastleUserDevices(); - - // When - String payloadJson = model.getGson().toJson(devices); - - // Then - Assertions.assertThat(payloadJson).isEqualTo("{\"total_count\":0}"); - } - - @Test - public void fullBuilderJson() { - // Given - CastleUserDevices devices = new CastleUserDevices(); - - List devicesList = new ArrayList<>(); - - CastleUserDevice device = new CastleUserDevice(); - device.setToken(DeviceUtils.DEVICE_TOKEN); - device.setCreatedAt(DeviceUtils.DEVICE_CREATED_AT); - device.setLastSeenAt(DeviceUtils.DEVICE_LAST_SEEN_AT); - device.setApprovedAt(DeviceUtils.DEVICE_APPROVED_AT); - device.setEscalatedAt(DeviceUtils.DEVICE_ESCALATED_AT); - device.setMitigatedAt(DeviceUtils.DEVICE_MITIGATED_AT); - device.setCurrentDevice(true); - - CastleUserDeviceContext deviceContext = new CastleUserDeviceContext(); - deviceContext.setIp(DeviceUtils.CONTEXT_IP); - deviceContext.setType(DeviceUtils.CONTEXT_TYPE); - - DeviceUserAgent userAgent = new DeviceUserAgent(); - userAgent.setBrowser(DeviceUtils.USER_AGENT_BROWSER); - userAgent.setDevice(DeviceUtils.USER_AGENT_DEVICE); - userAgent.setFamily(DeviceUtils.USER_AGENT_FAMILY); - userAgent.setMobile(DeviceUtils.USER_AGENT_MOBILE); - userAgent.setOs(DeviceUtils.USER_AGENT_OS); - userAgent.setPlatform(DeviceUtils.USER_AGENT_PLATFORM); - userAgent.setRaw(DeviceUtils.USER_AGENT_RAW); - userAgent.setVersion(DeviceUtils.USER_AGENT_VERSION); - deviceContext.setUserAgent(userAgent); - - device.setContext(deviceContext); - - devicesList.add(device); - - devices.setDevices(devicesList); - - // When - String payloadJson = model.getGson().toJson(devices); - String expected = "{\"total_count\":1,\"data\":[{\"token\":\"abcdefg12345\",\"risk\":0.0,\"created_at\":\"2018-06-15T16:36:22.916Z\",\"last_seen_at\":\"2018-07-19T23:09:29.681Z\",\"context\":{\"ip\":\"1.1.1.1\",\"user_agent\":{\"raw\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36 OPR/54.0.2952.51\",\"browser\":\"Opera\",\"version\":\"54.0.2952\",\"os\":\"Mac OS X 10.13.6\",\"mobile\":false,\"platform\":\"Mac OS X\",\"device\":\"Unknown\",\"family\":\"Opera\"},\"type\":\"desktop\"},\"is_current_device\":true}]}"; - - // Then - Assertions.assertThat(JsonParser.parseString(payloadJson)).isEqualTo(JsonParser.parseString(expected)); - - Assert.assertEquals(devices.getTotalCount(), devicesList.size()); - Assert.assertEquals(devices.getDevices(), devicesList); - - devices.setDevices(null); - Assert.assertEquals(0, devices.getTotalCount()); - } -} diff --git a/src/test/java/io/castle/client/utils/DeviceUtils.java b/src/test/java/io/castle/client/utils/DeviceUtils.java index b1692e68..693be728 100644 --- a/src/test/java/io/castle/client/utils/DeviceUtils.java +++ b/src/test/java/io/castle/client/utils/DeviceUtils.java @@ -1,12 +1,5 @@ package io.castle.client.utils; -import io.castle.client.model.CastleUserDevice; -import io.castle.client.model.CastleUserDeviceContext; -import io.castle.client.model.CastleUserDevices; -import io.castle.client.model.DeviceUserAgent; - -import java.util.Collections; - public class DeviceUtils { public static final String DEVICE_TOKEN = "abcdefg12345"; @@ -28,45 +21,4 @@ public class DeviceUtils { public static final String USER_AGENT_BROWSER = "Opera"; public static final String USER_AGENT_RAW = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36 OPR/54.0.2952.51"; public static final boolean USER_AGENT_MOBILE = false; - - public static CastleUserDevice createExpectedDevice() { - CastleUserDevice device = new CastleUserDevice(); - device.setToken(DEVICE_TOKEN); - device.setRisk(DEVICE_RISK); - device.setCreatedAt(DEVICE_CREATED_AT); - device.setLastSeenAt(DEVICE_LAST_SEEN_AT); - device.setApprovedAt(null); - device.setEscalatedAt(null); - device.setMitigatedAt(null); - - CastleUserDeviceContext deviceContext = new CastleUserDeviceContext(); - deviceContext.setIp(CONTEXT_IP); - deviceContext.setType(CONTEXT_TYPE); - - DeviceUserAgent userAgent = new DeviceUserAgent(); - userAgent.setFamily(USER_AGENT_FAMILY); - userAgent.setDevice(USER_AGENT_DEVICE); - userAgent.setPlatform(USER_AGENT_PLATFORM); - userAgent.setOs(USER_AGENT_OS); - userAgent.setVersion(USER_AGENT_VERSION); - userAgent.setBrowser(USER_AGENT_BROWSER); - userAgent.setRaw(USER_AGENT_RAW); - userAgent.setMobile(USER_AGENT_MOBILE); - - deviceContext.setUserAgent(userAgent); - - device.setContext(deviceContext); - - return device; - } - - public static CastleUserDevices createExpectedDevices() { - CastleUserDevices devices = new CastleUserDevices(); - devices.setDevices(Collections.singletonList(createExpectedDevice())); - return devices; - } - - public static String getDefaultDeviceJSON() { - return "{\"token\":\"abcdefg12345\",\"risk\":0.000154,\"created_at\":\"2018-06-15T16:36:22.916Z\",\"last_seen_at\":\"2018-07-19T23:09:29.681Z\",\"approved_at\":null,\"escalated_at\":null,\"mitigated_at\":null,\"context\":{\"ip\":\"1.1.1.1\",\"location\":{\"country_code\":\"US\",\"country\":\"United States\",\"region\":\"New Jersey\",\"region_code\":\"NJ\",\"city\":\"Lyndhurst\",\"lat\":40.7923,\"lon\":-74.1001},\"user_agent\":{\"raw\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36 OPR/54.0.2952.51\",\"browser\":\"Opera\",\"version\":\"54.0.2952\",\"os\":\"Mac OS X 10.13.6\",\"mobile\":false,\"platform\":\"Mac OS X\",\"device\":\"Unknown\",\"family\":\"Opera\"},\"type\":\"desktop\"}}"; - } } diff --git a/src/test/resources/castle_sdk.properties b/src/test/resources/castle_sdk.properties index c22bb141..2b8d1b56 100644 --- a/src/test/resources/castle_sdk.properties +++ b/src/test/resources/castle_sdk.properties @@ -4,5 +4,4 @@ allow_list=TestAllow,User-Agent,Accept-Language,Accept-Encoding,Accept-Charset,A deny_list=TestDeny,Cookie timeout=100 backend_provider=OKHTTP -failover_strategy=CHALLENGE base_url=https://testing.api.dev.castle/v1/