From 9193a6f93a03c0159a84ba06f1cc44ee49da3c8f Mon Sep 17 00:00:00 2001 From: Bartosz Date: Thu, 11 Jun 2026 22:03:54 +0200 Subject: [PATCH 1/8] Add webhook signature verification --- src/main/java/io/castle/client/Castle.java | 38 +++++++++++++ .../castle/client/internal/utils/Webhook.java | 56 +++++++++++++++++++ .../io/castle/client/CastleWebhookTest.java | 38 +++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 src/main/java/io/castle/client/internal/utils/Webhook.java create mode 100644 src/test/java/io/castle/client/CastleWebhookTest.java diff --git a/src/main/java/io/castle/client/Castle.java b/src/main/java/io/castle/client/Castle.java index 61cc4ee..d5a3ed8 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; @@ -36,6 +37,11 @@ public class Castle { public static final String URL_FILTER = "/v1/filter"; public static final String URL_LOG = "/v1/log"; + /** + * Header used by Castle to sign webhook payloads. + */ + public static final String WEBHOOK_SIGNATURE_HEADER = "X-Castle-Signature"; + public static final String URL_RECOVER = "/v1/users/%s/recover"; public static final String URL_LISTS = "/v1/lists"; @@ -270,6 +276,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/internal/utils/Webhook.java b/src/main/java/io/castle/client/internal/utils/Webhook.java new file mode 100644 index 0000000..9c6ebf3 --- /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/test/java/io/castle/client/CastleWebhookTest.java b/src/test/java/io/castle/client/CastleWebhookTest.java new file mode 100644 index 0000000..c6478a9 --- /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(); + } +} From f4b7c34318de0b6a47f9d22ff22226c9eae9d828 Mon Sep 17 00:00:00 2001 From: Bartosz Date: Thu, 11 Jun 2026 22:06:09 +0200 Subject: [PATCH 2/8] Add Events API (schema, query, group) --- src/main/java/io/castle/client/Castle.java | 1 + .../java/io/castle/client/api/CastleApi.java | 23 ++++++ .../castle/client/internal/CastleApiImpl.java | 20 +++++ .../castle/client/CastleEventsHttpTest.java | 76 +++++++++++++++++++ 4 files changed, 120 insertions(+) create mode 100644 src/test/java/io/castle/client/CastleEventsHttpTest.java diff --git a/src/main/java/io/castle/client/Castle.java b/src/main/java/io/castle/client/Castle.java index d5a3ed8..9c7768b 100644 --- a/src/main/java/io/castle/client/Castle.java +++ b/src/main/java/io/castle/client/Castle.java @@ -36,6 +36,7 @@ public class Castle { 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"; /** * Header used by Castle to sign webhook payloads. diff --git a/src/main/java/io/castle/client/api/CastleApi.java b/src/main/java/io/castle/client/api/CastleApi.java index ae782d8..73678e8 100644 --- a/src/main/java/io/castle/client/api/CastleApi.java +++ b/src/main/java/io/castle/client/api/CastleApi.java @@ -301,6 +301,29 @@ public interface CastleApi { CastleResponse delete(String path, Object payload); + /** + * Makes a sync GET request to the events schema endpoint. + * + * @return a decoded json response + */ + CastleResponse eventsSchema(); + + /** + * Makes a sync POST request to the events query endpoint. + * + * @param payload query parameters + * @return a decoded json response + */ + CastleResponse queryEvents(ImmutableMap payload); + + /** + * Makes a sync POST request to the events group endpoint. + * + * @param payload group parameters + * @return a decoded json response + */ + CastleResponse groupEvents(ImmutableMap payload); + /** * Makes a sync POST request to the risk endpoint. * diff --git a/src/main/java/io/castle/client/internal/CastleApiImpl.java b/src/main/java/io/castle/client/internal/CastleApiImpl.java index aaa9652..2bef5e9 100644 --- a/src/main/java/io/castle/client/internal/CastleApiImpl.java +++ b/src/main/java/io/castle/client/internal/CastleApiImpl.java @@ -324,6 +324,26 @@ public CastleResponse risk(ImmutableMap payload) { return restApi.post(Castle.URL_RISK, payload); } + @Override + public CastleResponse eventsSchema() { + RestApi restApi = configuration.getRestApiFactory().buildBackend(); + return restApi.get(Castle.URL_EVENTS + "/schema"); + } + + @Override + public CastleResponse queryEvents(ImmutableMap payload) { + Preconditions.checkNotNull(payload); + RestApi restApi = configuration.getRestApiFactory().buildBackend(); + return restApi.post(Castle.URL_EVENTS + "/query", payload); + } + + @Override + public CastleResponse groupEvents(ImmutableMap payload) { + Preconditions.checkNotNull(payload); + RestApi restApi = configuration.getRestApiFactory().buildBackend(); + return restApi.post(Castle.URL_EVENTS + "/group", payload); + } + @Override public FilterAndRiskResponse risk(Risk payload) { Preconditions.checkNotNull(payload); 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 0000000..522b706 --- /dev/null +++ b/src/test/java/io/castle/client/CastleEventsHttpTest.java @@ -0,0 +1,76 @@ +package io.castle.client; + +import com.google.common.collect.ImmutableMap; +import io.castle.client.model.AuthenticateAction; +import io.castle.client.model.AuthenticateFailoverStrategy; +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 { + + public CastleEventsHttpTest() { + super(new AuthenticateFailoverStrategy(AuthenticateAction.CHALLENGE)); + } + + @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); + } +} From b971c83e72eb77c22caf5716fb3d0c97d5d1e704 Mon Sep 17 00:00:00 2001 From: Bartosz Date: Thu, 11 Jun 2026 22:07:21 +0200 Subject: [PATCH 3/8] Add privacy requestUserData and deleteUserData --- .../java/io/castle/client/api/CastleApi.java | 16 +++++ .../castle/client/internal/CastleApiImpl.java | 14 +++++ .../castle/client/CastlePrivacyHttpTest.java | 59 +++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 src/test/java/io/castle/client/CastlePrivacyHttpTest.java diff --git a/src/main/java/io/castle/client/api/CastleApi.java b/src/main/java/io/castle/client/api/CastleApi.java index 73678e8..8054e3d 100644 --- a/src/main/java/io/castle/client/api/CastleApi.java +++ b/src/main/java/io/castle/client/api/CastleApi.java @@ -301,6 +301,22 @@ public interface CastleApi { CastleResponse delete(String path, Object payload); + /** + * Makes a sync POST request to the privacy endpoint to request a user's data. + * + * @param payload request parameters + * @return a decoded json response + */ + CastleResponse requestUserData(ImmutableMap payload); + + /** + * Makes a sync DELETE request to the privacy endpoint to delete a user's data. + * + * @param payload request parameters + * @return a decoded json response + */ + CastleResponse deleteUserData(ImmutableMap payload); + /** * Makes a sync GET request to the events schema endpoint. * diff --git a/src/main/java/io/castle/client/internal/CastleApiImpl.java b/src/main/java/io/castle/client/internal/CastleApiImpl.java index 2bef5e9..9a5f257 100644 --- a/src/main/java/io/castle/client/internal/CastleApiImpl.java +++ b/src/main/java/io/castle/client/internal/CastleApiImpl.java @@ -324,6 +324,20 @@ public CastleResponse risk(ImmutableMap payload) { return restApi.post(Castle.URL_RISK, payload); } + @Override + public CastleResponse requestUserData(ImmutableMap payload) { + Preconditions.checkNotNull(payload); + RestApi restApi = configuration.getRestApiFactory().buildBackend(); + return restApi.post(Castle.URL_PRIVACY + "users", payload); + } + + @Override + public CastleResponse deleteUserData(ImmutableMap payload) { + Preconditions.checkNotNull(payload); + RestApi restApi = configuration.getRestApiFactory().buildBackend(); + return restApi.delete(Castle.URL_PRIVACY + "users", payload); + } + @Override public CastleResponse eventsSchema() { RestApi restApi = configuration.getRestApiFactory().buildBackend(); 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 0000000..33780dd --- /dev/null +++ b/src/test/java/io/castle/client/CastlePrivacyHttpTest.java @@ -0,0 +1,59 @@ +package io.castle.client; + +import com.google.common.collect.ImmutableMap; +import io.castle.client.model.AuthenticateAction; +import io.castle.client.model.AuthenticateFailoverStrategy; +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 { + + public CastlePrivacyHttpTest() { + super(new AuthenticateFailoverStrategy(AuthenticateAction.CHALLENGE)); + } + + @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()); + } +} From 5854805eba83f27536030bd2ff604d899ed1fc36 Mon Sep 17 00:00:00 2001 From: Bartosz Date: Thu, 11 Jun 2026 22:28:49 +0200 Subject: [PATCH 4/8] Remove legacy endpoints (authenticate, track, devices, impersonate, recover) --- src/main/java/io/castle/client/Castle.java | 7 - .../java/io/castle/client/api/CastleApi.java | 240 -------- .../castle/client/internal/CastleApiImpl.java | 265 --------- .../internal/backend/OkRestApiBackend.java | 324 ----------- .../client/internal/backend/RestApi.java | 82 --- .../internal/config/CastleConfiguration.java | 13 +- .../config/CastleConfigurationBuilder.java | 39 +- .../internal/config/ConfigurationLoader.java | 20 - .../json/AuthenticateActionDeserializer.java | 16 - .../client/internal/json/CastleGsonModel.java | 4 - .../json/CastleMessageSerializer.java | 29 - .../client/internal/utils/VerdictBuilder.java | 97 ---- .../internal/utils/VerdictTransportModel.java | 50 -- .../client/model/AsyncCallbackHandler.java | 14 - .../client/model/AuthenticateAction.java | 28 - .../model/AuthenticateFailoverStrategy.java | 57 -- .../io/castle/client/model/CastleMessage.java | 198 ------- .../io/castle/client/model/CastleSuccess.java | 13 - .../castle/client/model/CastleUserDevice.java | 89 --- .../client/model/CastleUserDevices.java | 24 - .../client/model/ImpersonatePayload.java | 22 - .../java/io/castle/client/model/Verdict.java | 113 ---- .../client/AbstractCastleHttpLayerTest.java | 8 - .../client/CastleAuthenticateHttpTest.java | 541 ------------------ .../client/CastleDeviceApproveHttpTest.java | 69 --- .../castle/client/CastleDeviceHttpTest.java | 84 --- .../client/CastleDeviceReportHttpTest.java | 69 --- .../castle/client/CastleDevicesHttpTest.java | 86 --- .../castle/client/CastleEventsHttpTest.java | 6 - .../castle/client/CastleFilterHttpTest.java | 6 - .../client/CastleGenericAPIHttpTest.java | 6 - .../client/CastleImpersonateEndHttpTest.java | 56 -- .../CastleImpersonateStartHttpTest.java | 56 -- .../io/castle/client/CastleListItemsTest.java | 6 - .../io/castle/client/CastleListsHttpTest.java | 6 - .../io/castle/client/CastleLogHttpTest.java | 7 - .../io/castle/client/CastleMergeHttpTest.java | 100 ---- .../client/CastleNoTrackOptionTest.java | 170 ------ .../castle/client/CastlePrivacyHttpTest.java | 6 - .../castle/client/CastleRecoverHttpTest.java | 38 -- .../client/CastleRemoveUserHttpTest.java | 76 --- .../io/castle/client/CastleRiskHttpTest.java | 6 - .../client/CastleThrowStrategyTest.java | 92 --- .../io/castle/client/CastleTrackHttpTest.java | 313 ---------- .../config/ConfigurationLoaderTest.java | 5 - .../utils/VerdictTransportModelTest.java | 40 -- .../model/AuthenticationActionTest.java | 46 -- .../model/CastleConfigurationBuilderTest.java | 1 - .../client/model/CastleMessageTest.java | 129 ----- .../model/CastleUserDeviceContextTest.java | 57 -- .../client/model/CastleUserDeviceTest.java | 72 --- .../client/model/CastleUserDevicesTest.java | 78 --- .../io/castle/client/utils/DeviceUtils.java | 48 -- src/test/resources/castle_sdk.properties | 1 - 54 files changed, 2 insertions(+), 4026 deletions(-) delete mode 100644 src/main/java/io/castle/client/internal/json/AuthenticateActionDeserializer.java delete mode 100644 src/main/java/io/castle/client/internal/json/CastleMessageSerializer.java delete mode 100644 src/main/java/io/castle/client/internal/utils/VerdictBuilder.java delete mode 100644 src/main/java/io/castle/client/internal/utils/VerdictTransportModel.java delete mode 100644 src/main/java/io/castle/client/model/AsyncCallbackHandler.java delete mode 100644 src/main/java/io/castle/client/model/AuthenticateAction.java delete mode 100644 src/main/java/io/castle/client/model/AuthenticateFailoverStrategy.java delete mode 100644 src/main/java/io/castle/client/model/CastleMessage.java delete mode 100644 src/main/java/io/castle/client/model/CastleSuccess.java delete mode 100644 src/main/java/io/castle/client/model/CastleUserDevice.java delete mode 100644 src/main/java/io/castle/client/model/CastleUserDevices.java delete mode 100644 src/main/java/io/castle/client/model/ImpersonatePayload.java delete mode 100644 src/main/java/io/castle/client/model/Verdict.java delete mode 100644 src/test/java/io/castle/client/CastleAuthenticateHttpTest.java delete mode 100644 src/test/java/io/castle/client/CastleDeviceApproveHttpTest.java delete mode 100644 src/test/java/io/castle/client/CastleDeviceHttpTest.java delete mode 100644 src/test/java/io/castle/client/CastleDeviceReportHttpTest.java delete mode 100644 src/test/java/io/castle/client/CastleDevicesHttpTest.java delete mode 100644 src/test/java/io/castle/client/CastleImpersonateEndHttpTest.java delete mode 100644 src/test/java/io/castle/client/CastleImpersonateStartHttpTest.java delete mode 100644 src/test/java/io/castle/client/CastleMergeHttpTest.java delete mode 100644 src/test/java/io/castle/client/CastleNoTrackOptionTest.java delete mode 100644 src/test/java/io/castle/client/CastleRecoverHttpTest.java delete mode 100644 src/test/java/io/castle/client/CastleRemoveUserHttpTest.java delete mode 100644 src/test/java/io/castle/client/CastleThrowStrategyTest.java delete mode 100644 src/test/java/io/castle/client/CastleTrackHttpTest.java delete mode 100644 src/test/java/io/castle/client/internal/utils/VerdictTransportModelTest.java delete mode 100644 src/test/java/io/castle/client/model/AuthenticationActionTest.java delete mode 100644 src/test/java/io/castle/client/model/CastleMessageTest.java delete mode 100644 src/test/java/io/castle/client/model/CastleUserDeviceContextTest.java delete mode 100644 src/test/java/io/castle/client/model/CastleUserDeviceTest.java delete mode 100644 src/test/java/io/castle/client/model/CastleUserDevicesTest.java diff --git a/src/main/java/io/castle/client/Castle.java b/src/main/java/io/castle/client/Castle.java index 9c7768b..3b9e06c 100644 --- a/src/main/java/io/castle/client/Castle.java +++ b/src/main/java/io/castle/client/Castle.java @@ -27,11 +27,6 @@ * 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"; @@ -43,8 +38,6 @@ public class Castle { */ public static final String WEBHOOK_SIGNATURE_HEADER = "X-Castle-Signature"; - public static final String URL_RECOVER = "/v1/users/%s/recover"; - public static final String URL_LISTS = "/v1/lists"; public static final String URL_LISTS_ID = "/v1/lists/%s"; public static final String URL_LISTS_SEARCH = "/v1/lists/query"; diff --git a/src/main/java/io/castle/client/api/CastleApi.java b/src/main/java/io/castle/client/api/CastleApi.java index 8054e3d..1131a0d 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,165 +57,6 @@ 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); - - /** - * 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); - - /** - * Makes a DELETE request to the privacy endpoint. - * - * @param userId String representing a user id - * @see The docs - */ - Boolean removeUser(String userId); - - /** - * 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); - - /** - * 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); - - /** - * Makes a sync GET request to the user devices endpoint. - * - * @param userId user unique ID - * @return devices model object - */ - CastleUserDevices userDevices(String userId); - - /** - * Makes a sync GET request to the device endpoint. - * - * @param deviceToken string representing the device to report - * @return device model object - */ - CastleUserDevice device(String deviceToken); - - /** - * Makes a sync POST request to the impersonate endpoint. - * - * @param userId user unique ID - * @return - */ - CastleSuccess impersonateStart(String userId); - - /** - * Makes a sync POST request to the impersonate endpoint. - * - * @param userId user unique ID - * @param impersonator description of impersonator, e.g., email - * @return - */ - CastleSuccess impersonateStart(String userId, String impersonator); - - /** - * Makes a sync DELETE request to the impersonate endpoint. - * - * @param userId user unique ID - * @return - */ - CastleSuccess impersonateEnd(String userId); - - /** - * Makes a sync DELETE request to the impersonate endpoint. - * - * @param userId user unique ID - * @param impersonator description of impersonator, e.g., email - * @return - */ - CastleSuccess impersonateEnd(String userId, String impersonator); - CastleResponse get(String path); CastleResponse post(String path, Object payload); @@ -490,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 9a5f257..46c8be3 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; @@ -69,219 +68,6 @@ public CastleApi doNotTrack(boolean doNotTrack) { return new CastleApiImpl(doNotTrack, configuration, contextJson); } - @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); - RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.sendReportDeviceRequestSync(deviceToken); - } - - @Override - public CastleUserDevices userDevices(String userId) { - Preconditions.checkNotNull(userId); - RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.sendGetUserDevicesRequestSync(userId); - } - - @Override - public CastleUserDevice device(String deviceToken) { - Preconditions.checkNotNull(deviceToken); - RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.sendGetUserDeviceRequestSync(deviceToken); - } - - @Override - public CastleSuccess impersonateStart(String userId) { - Preconditions.checkNotNull(userId); - RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.sendImpersonateStartRequestSync(userId, null, contextJson); - } - - @Override - public CastleSuccess impersonateStart(String userId, String impersonator) { - Preconditions.checkNotNull(userId); - RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.sendImpersonateStartRequestSync(userId, impersonator, contextJson); - } - - @Override - public CastleSuccess impersonateEnd(String userId) { - Preconditions.checkNotNull(userId); - RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.sendImpersonateEndRequestSync(userId, "", contextJson); - } - - @Override - public CastleSuccess impersonateEnd(String userId, String impersonator) { - Preconditions.checkNotNull(userId); - RestApi restApi = configuration.getRestApiFactory().buildBackend(); - return restApi.sendImpersonateEndRequestSync(userId, impersonator, contextJson); - } - @Override public CastleResponse get(String path) { RestApi restApi = configuration.getRestApiFactory().buildBackend(); @@ -516,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 46c77f6..ad55100 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 1c19d08..2c740ca 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 d4b5f73..dd884b9 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 ab177de..4382307 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: *