From 3d3db9e3fb992804e21d9ad7ed7a256d37453b61 Mon Sep 17 00:00:00 2001 From: Sina Madani Date: Wed, 11 Oct 2023 16:39:48 +0100 Subject: [PATCH] fix: Move sandbox field to SilentAuthWorkflow --- .../com/vonage/client/DynamicEndpoint.java | 22 +++++-- .../client/verify2/SilentAuthWorkflow.java | 3 +- .../vonage/client/verify2/SmsWorkflow.java | 3 +- .../vonage/client/verify2/Verify2Client.java | 64 ++++++++++++++----- .../vonage/client/verify2/VoiceWorkflow.java | 3 +- .../verify2/WhatsappCodelessWorkflow.java | 3 +- .../client/verify2/WhatsappWorkflow.java | 3 +- src/test/java/com/vonage/client/BugRepro.java | 2 + .../verify2/VerificationRequestTest.java | 12 ++-- .../client/verify2/Verify2ClientTest.java | 56 +++++++++++----- 10 files changed, 125 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/vonage/client/DynamicEndpoint.java b/src/main/java/com/vonage/client/DynamicEndpoint.java index e8f5085c1..871dfc182 100644 --- a/src/main/java/com/vonage/client/DynamicEndpoint.java +++ b/src/main/java/com/vonage/client/DynamicEndpoint.java @@ -112,6 +112,13 @@ public Builder pathGetter(BiFunction, T, String> pat return this; } + public Builder addAuthMethodIfTrue(boolean condition, Class primary, Class... others) { + if (condition) { + authMethod(primary, others); + } + return this; + } + public Builder authMethod(Class primary, Class... others) { authMethods = new ArrayList<>(2); authMethods.add(Objects.requireNonNull(primary, "Primary auth method cannot be null")); @@ -167,14 +174,21 @@ static RequestBuilder createRequestBuilderFromRequestMethod(HttpMethod requestMe @Override protected Class[] getAcceptableAuthMethods() { - return authMethods.toArray(new Class[0]); + Class[] emptyArray = new Class[0]; + return authMethods != null ? authMethods.toArray(emptyArray) : emptyArray; } @Override protected RequestBuilder applyAuth(RequestBuilder request) throws VonageClientException { - return applyBasicAuth ? - getAuthMethod(getAcceptableAuthMethods()).applyAsBasicAuth(request) : - super.applyAuth(request); + if (authMethods == null || authMethods.isEmpty()) { + return request; + } + else if (applyBasicAuth) { + return getAuthMethod(getAcceptableAuthMethods()).applyAsBasicAuth(request); + } + else { + return super.applyAuth(request); + } } @Override diff --git a/src/main/java/com/vonage/client/verify2/SilentAuthWorkflow.java b/src/main/java/com/vonage/client/verify2/SilentAuthWorkflow.java index 437f89863..31fa9e6bf 100644 --- a/src/main/java/com/vonage/client/verify2/SilentAuthWorkflow.java +++ b/src/main/java/com/vonage/client/verify2/SilentAuthWorkflow.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.vonage.client.common.E164; /** * Defines properties for mobile network-based authentication. See the @@ -33,7 +34,7 @@ public final class SilentAuthWorkflow extends Workflow { * @param to The number to registered to the device on the network to authenticate. */ public SilentAuthWorkflow(String to) { - super(Channel.SILENT_AUTH, to); + super(Channel.SILENT_AUTH, new E164(to).toString()); } /** diff --git a/src/main/java/com/vonage/client/verify2/SmsWorkflow.java b/src/main/java/com/vonage/client/verify2/SmsWorkflow.java index adb9020a7..812aaa462 100644 --- a/src/main/java/com/vonage/client/verify2/SmsWorkflow.java +++ b/src/main/java/com/vonage/client/verify2/SmsWorkflow.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.vonage.client.common.E164; /** * Defines workflow properties for sending a verification code to a user via SMS. @@ -42,7 +43,7 @@ public SmsWorkflow(String to) { * @param appHash Android Application Hash Key for automatic code detection on a user's device. */ public SmsWorkflow(String to, String appHash) { - super(Channel.SMS, to); + super(Channel.SMS, new E164(to).toString()); if ((this.appHash = appHash) != null && appHash.length() != 11) { throw new IllegalArgumentException("Android app hash must be 11 characters."); } diff --git a/src/main/java/com/vonage/client/verify2/Verify2Client.java b/src/main/java/com/vonage/client/verify2/Verify2Client.java index 675fb95eb..207692788 100644 --- a/src/main/java/com/vonage/client/verify2/Verify2Client.java +++ b/src/main/java/com/vonage/client/verify2/Verify2Client.java @@ -21,16 +21,17 @@ import com.vonage.client.auth.JWTAuthMethod; import com.vonage.client.auth.TokenAuthMethod; import com.vonage.client.common.HttpMethod; +import java.net.URI; import java.util.Objects; import java.util.UUID; -import java.util.function.Function; +import java.util.function.BiFunction; public class Verify2Client { final boolean hasJwtAuthMethod; final RestEndpoint verifyUser; final RestEndpoint verifyRequest; final RestEndpoint cancel; - final RestEndpoint silentAuthCheck; + final RestEndpoint silentAuthCheck; /** * Create a new Verify2Client. @@ -42,23 +43,23 @@ public Verify2Client(HttpWrapper wrapper) { @SuppressWarnings("unchecked") final class Endpoint extends DynamicEndpoint { - Endpoint(Function pathGetter, HttpMethod method, R... type) { + Endpoint(BiFunction pathGetter, HttpMethod method, R... type) { super(DynamicEndpoint. builder(type) .responseExceptionType(VerifyResponseException.class) .wrapper(wrapper).requestMethod(method) - .authMethod(JWTAuthMethod.class, TokenAuthMethod.class) + .addAuthMethodIfTrue(method != HttpMethod.GET, JWTAuthMethod.class, TokenAuthMethod.class) .pathGetter((de, req) -> { String base = de.getHttpWrapper().getHttpConfig().getVersionedApiBaseUri("v2") + "/verify"; - return pathGetter != null ? base + "/" + pathGetter.apply(req) : base; + return pathGetter.apply(base, req); }) ); } } - verifyUser = new Endpoint<>(null, HttpMethod.POST); - verifyRequest = new Endpoint<>(req -> req.requestId, HttpMethod.POST); - cancel = new Endpoint<>(UUID::toString, HttpMethod.DELETE); - silentAuthCheck = new Endpoint<>(reqId -> reqId + "/silent-auth/redirect", HttpMethod.GET); + verifyUser = new Endpoint<>((base, req) -> base, HttpMethod.POST); + verifyRequest = new Endpoint<>((base, req) -> base + '/' + req.requestId, HttpMethod.POST); + cancel = new Endpoint<>((base, req) -> base + '/' + req, HttpMethod.DELETE); + silentAuthCheck = new Endpoint<>((base, req) -> req.toString(), HttpMethod.GET); } private UUID validateRequestId(UUID requestId) { @@ -139,21 +140,54 @@ public void cancelVerification(UUID requestId) { /** * Final step of Silent Authentication workflow. Once the {@linkplain #sendVerification(VerificationRequest)} - * has been called, use the response to obtain the request ID and pass it to this method to complete - * the verification workflow. This method uses the {@linkplain #cancelVerification(UUID)} under the hood - * with a code obtained from the API after following the `check_url` redirect. Refer to the + * has been called, pass the response to this method to complete the verification workflow. This method uses + * the {@linkplain #checkVerificationCode(UUID, String)} under the hood with a code obtained from the API + * after following the `check_url` redirect. Refer to the * * Silent Authentication documentation for more details. * - * @param requestId ID of the request, as obtained from {@link VerificationResponse#getRequestId()}. + * @param verifyResponse The VerificationResponse, as obtained from {@link #sendVerification(VerificationRequest)}. * * @throws VerifyResponseException If the Silent Authentication workflow failed due * to a network error (409 HTTP status response). * * @since v7.10.0 */ - public void checkSilentAuth(UUID requestId) { - SilentAuthResponse response = silentAuthCheck.execute(validateRequestId(requestId)); + public void checkSilentAuth(VerificationResponse verifyResponse) { + Objects.requireNonNull(verifyResponse, "Response object cannot be null."); + URI checkUrl = verifyResponse.getCheckUrl(); + if (checkUrl == null) { + throw new IllegalStateException("'check_url' is missing in the response."); + } + SilentAuthResponse response = silentAuthCheck.execute(checkUrl); checkVerificationCode(response.getRequestId(), response.getCode()); } + + /*/** + * A fully declarative, automated utility method for performing Silent Authentication using + * a device's mobile network connection. If the authentication failed due to a network error, + * this method will return {@code false}. If a failure occurs for any other reason, a + * {@linkplain VerifyResponseException} will be thrown. + * + * @param number The device's SIM (phone) number in E.164 format. + * + * @return {@code true} if the authentication was successful. + * + * @throws VerifyResponseException If the workflow fails for any reason other than a 409 Network error. + * + * @since v7.10.0 + + public boolean doSilentAuthWorkflow(String number) { + VerificationRequest request = VerificationRequest.builder() + .addWorkflow(new SilentAuthWorkflow(number)) + .brand("Vonage Java SDK").build(); + VerificationResponse response = sendVerification(request); + try { + checkSilentAuth(response); + return true; + } + catch (VerifyResponseException ex) { + return false; + } + }*/ } diff --git a/src/main/java/com/vonage/client/verify2/VoiceWorkflow.java b/src/main/java/com/vonage/client/verify2/VoiceWorkflow.java index e66fcccd3..447490a1b 100644 --- a/src/main/java/com/vonage/client/verify2/VoiceWorkflow.java +++ b/src/main/java/com/vonage/client/verify2/VoiceWorkflow.java @@ -16,6 +16,7 @@ package com.vonage.client.verify2; import com.fasterxml.jackson.annotation.JsonInclude; +import com.vonage.client.common.E164; /** * Defines workflow properties for sending a verification code to a user over a voice call. @@ -29,6 +30,6 @@ public final class VoiceWorkflow extends Workflow { * @param to The number to call, in E.164 format. */ public VoiceWorkflow(String to) { - super(Channel.VOICE, to); + super(Channel.VOICE, new E164(to).toString()); } } diff --git a/src/main/java/com/vonage/client/verify2/WhatsappCodelessWorkflow.java b/src/main/java/com/vonage/client/verify2/WhatsappCodelessWorkflow.java index 13a95a598..0a98e7773 100644 --- a/src/main/java/com/vonage/client/verify2/WhatsappCodelessWorkflow.java +++ b/src/main/java/com/vonage/client/verify2/WhatsappCodelessWorkflow.java @@ -16,6 +16,7 @@ package com.vonage.client.verify2; import com.fasterxml.jackson.annotation.JsonInclude; +import com.vonage.client.common.E164; /** * Defines properties for sending a verification code to a user over WhatsApp @@ -35,6 +36,6 @@ public final class WhatsappCodelessWorkflow extends Workflow { * @param to The number to send the verification prompt to, in E.164 format. */ public WhatsappCodelessWorkflow(String to) { - super(Channel.WHATSAPP_INTERACTIVE, to); + super(Channel.WHATSAPP_INTERACTIVE, new E164(to).toString()); } } diff --git a/src/main/java/com/vonage/client/verify2/WhatsappWorkflow.java b/src/main/java/com/vonage/client/verify2/WhatsappWorkflow.java index 4a3e9d272..a6b4986d4 100644 --- a/src/main/java/com/vonage/client/verify2/WhatsappWorkflow.java +++ b/src/main/java/com/vonage/client/verify2/WhatsappWorkflow.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.vonage.client.common.E164; /** * Defines properties for sending a verification code to a user over a WhatsApp message. @@ -45,7 +46,7 @@ public WhatsappWorkflow(String to) { * Note that you will need to get in touch with the Vonage sales team to enable use of the field. */ public WhatsappWorkflow(String to, String from) { - super(Channel.WHATSAPP, to, from); + super(Channel.WHATSAPP, new E164(to).toString(), from); } /** diff --git a/src/test/java/com/vonage/client/BugRepro.java b/src/test/java/com/vonage/client/BugRepro.java index 329bb70a9..13f615628 100644 --- a/src/test/java/com/vonage/client/BugRepro.java +++ b/src/test/java/com/vonage/client/BugRepro.java @@ -26,6 +26,8 @@ */ public class BugRepro { public static void main(String[] args) throws Throwable { + String TO_NUMBER = System.getenv("TO_NUMBER"); + VonageClient client = VonageClient.builder() .httpConfig(HttpConfig.builder().timeoutMillis(12_000).build()) .apiKey(System.getenv("VONAGE_API_KEY")) diff --git a/src/test/java/com/vonage/client/verify2/VerificationRequestTest.java b/src/test/java/com/vonage/client/verify2/VerificationRequestTest.java index e93661c56..2555e9fd9 100644 --- a/src/test/java/com/vonage/client/verify2/VerificationRequestTest.java +++ b/src/test/java/com/vonage/client/verify2/VerificationRequestTest.java @@ -178,12 +178,12 @@ public void testWithoutBrand() { @Test public void testAllWorkflowsWithoutRecipient() { for (String invalid : new String[]{"", " ", null}) { - assertThrows(IllegalArgumentException.class, () -> new SilentAuthWorkflow(invalid)); - assertThrows(IllegalArgumentException.class, () -> new SmsWorkflow(invalid)); - assertThrows(IllegalArgumentException.class, () -> new VoiceWorkflow(invalid)); - assertThrows(IllegalArgumentException.class, () -> new WhatsappWorkflow(invalid)); - assertThrows(IllegalArgumentException.class, () -> new WhatsappCodelessWorkflow(invalid)); - assertThrows(IllegalArgumentException.class, () -> new EmailWorkflow(invalid)); + assertThrows(RuntimeException.class, () -> new SilentAuthWorkflow(invalid)); + assertThrows(RuntimeException.class, () -> new SmsWorkflow(invalid)); + assertThrows(RuntimeException.class, () -> new VoiceWorkflow(invalid)); + assertThrows(RuntimeException.class, () -> new WhatsappWorkflow(invalid)); + assertThrows(RuntimeException.class, () -> new WhatsappCodelessWorkflow(invalid)); + assertThrows(RuntimeException.class, () -> new EmailWorkflow(invalid)); } } diff --git a/src/test/java/com/vonage/client/verify2/Verify2ClientTest.java b/src/test/java/com/vonage/client/verify2/Verify2ClientTest.java index bc97fc3b3..5eaa3773e 100644 --- a/src/test/java/com/vonage/client/verify2/Verify2ClientTest.java +++ b/src/test/java/com/vonage/client/verify2/Verify2ClientTest.java @@ -18,14 +18,13 @@ import com.vonage.client.ClientTest; import com.vonage.client.HttpWrapper; import com.vonage.client.RestEndpoint; +import com.vonage.client.auth.AuthMethod; import com.vonage.client.common.HttpMethod; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.*; import org.junit.jupiter.api.function.Executable; import java.net.URI; -import java.util.Arrays; -import java.util.List; -import java.util.UUID; +import java.util.*; public class Verify2ClientTest extends ClientTest { static final UUID REQUEST_ID = UUID.randomUUID(); @@ -367,8 +366,11 @@ void testParse404AllParams() throws Exception { @Test public void testVerifySilentAuth() throws Exception { + VerificationResponse verificationResponse = VerificationResponse.fromJson( + "{\"request_id\":\""+REQUEST_ID+"\",\"check_url\":\"https://example.com/verify2/redirect\"}" + ); String successJson = "{\"request_id\":\""+REQUEST_ID+"\",\"code\":\""+CODE+"\"}"; - stubResponseAndRun(successJson, () -> client.checkSilentAuth(REQUEST_ID)); + stubResponseAndRun(successJson, () -> client.checkSilentAuth(verificationResponse)); stubResponseAndAssertThrows(200, successJson, () -> client.checkSilentAuth(null), NullPointerException.class @@ -379,7 +381,7 @@ public void testVerifySilentAuth() throws Exception { stubResponse(409, "{\"title\":\""+title+"\",\"detail\":\""+detail+"\"}"); try { - client.checkSilentAuth(REQUEST_ID); + client.checkSilentAuth(verificationResponse); fail("Expected " + VerifyResponseException.class.getName()); } catch (VerifyResponseException ex) { @@ -387,14 +389,22 @@ public void testVerifySilentAuth() throws Exception { assertEquals(title, ex.getTitle()); assertEquals(detail, ex.getDetail()); } + + VerificationResponse missingCheckUrlResponse = VerificationResponse.fromJson( + "{\"request_id\":\""+REQUEST_ID+"\"}" + ); + stubResponseAndAssertThrows(200, successJson, + () -> client.checkSilentAuth(missingCheckUrlResponse), + IllegalStateException.class + ); } @Test public void testSilentAuthCheckEndpoint() throws Exception { - new Verify2EndpointTestSpec() { + new Verify2EndpointTestSpec() { @Override - protected RestEndpoint endpoint() { + protected RestEndpoint endpoint() { return client.silentAuthCheck; } @@ -404,13 +414,28 @@ protected HttpMethod expectedHttpMethod() { } @Override - protected String expectedEndpointUri(UUID request) { - return "/v2/verify/"+request+"/silent-auth/redirect"; + protected Collection> expectedAuthMethods() { + return Collections.emptyList(); } @Override - protected UUID sampleRequest() { - return REQUEST_ID; + protected String expectedDefaultBaseUri() { + return ""; + } + + @Override + protected String customBaseUri() { + return expectedDefaultBaseUri(); + } + + @Override + protected String expectedEndpointUri(URI request) { + return request.toString(); + } + + @Override + protected URI sampleRequest() { + return URI.create("https://api-eu-3.vonage.com/v2/verify/"+REQUEST_ID+"/silent-auth/redirect"); } @Override @@ -419,15 +444,14 @@ public void runTests() throws Exception { testParseResponse(); } - void testParseResponse() throws Exception { - UUID requestId = sampleRequest(); + private void testParseResponse() throws Exception { stubResponse(200, "{\n" + - " \"request_id\": \""+requestId+"\",\n" + + " \"request_id\": \""+REQUEST_ID+"\",\n" + " \"code\": \"si9sfG\"\n" + "}" ); - SilentAuthResponse response = endpoint().execute(requestId); - assertEquals(requestId, response.getRequestId()); + SilentAuthResponse response = endpoint().execute(sampleRequest()); + assertEquals(REQUEST_ID, response.getRequestId()); assertEquals("si9sfG", response.getCode()); } }