Skip to content

Commit

Permalink
fix: Move sandbox field to SilentAuthWorkflow
Browse files Browse the repository at this point in the history
  • Loading branch information
SMadani committed Oct 11, 2023
1 parent f3efd56 commit 3d3db9e
Show file tree
Hide file tree
Showing 10 changed files with 125 additions and 46 deletions.
22 changes: 18 additions & 4 deletions src/main/java/com/vonage/client/DynamicEndpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,13 @@ public Builder<T, R> pathGetter(BiFunction<DynamicEndpoint<T, R>, T, String> pat
return this;
}

public Builder<T, R> addAuthMethodIfTrue(boolean condition, Class<? extends AuthMethod> primary, Class<? extends AuthMethod>... others) {
if (condition) {
authMethod(primary, others);
}
return this;
}

public Builder<T, R> authMethod(Class<? extends AuthMethod> primary, Class<? extends AuthMethod>... others) {
authMethods = new ArrayList<>(2);
authMethods.add(Objects.requireNonNull(primary, "Primary auth method cannot be null"));
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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());
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/vonage/client/verify2/SmsWorkflow.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.");
}
Expand Down
64 changes: 49 additions & 15 deletions src/main/java/com/vonage/client/verify2/Verify2Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<VerificationRequest, VerificationResponse> verifyUser;
final RestEndpoint<VerifyCodeRequestWrapper, Void> verifyRequest;
final RestEndpoint<UUID, Void> cancel;
final RestEndpoint<UUID, SilentAuthResponse> silentAuthCheck;
final RestEndpoint<URI, SilentAuthResponse> silentAuthCheck;

/**
* Create a new Verify2Client.
Expand All @@ -42,23 +43,23 @@ public Verify2Client(HttpWrapper wrapper) {

@SuppressWarnings("unchecked")
final class Endpoint<T, R> extends DynamicEndpoint<T, R> {
Endpoint(Function<T, String> pathGetter, HttpMethod method, R... type) {
Endpoint(BiFunction<String, T, String> pathGetter, HttpMethod method, R... type) {
super(DynamicEndpoint.<T, R> 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) {
Expand Down Expand Up @@ -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
* <a href=https://developer.vonage.com/en/verify/guides/silent-authentication>
* Silent Authentication documentation</a> 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;
}
}*/
}
3 changes: 2 additions & 1 deletion src/main/java/com/vonage/client/verify2/VoiceWorkflow.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/test/java/com/vonage/client/BugRepro.java
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}

Expand Down
56 changes: 40 additions & 16 deletions src/test/java/com/vonage/client/verify2/Verify2ClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Verify2Client> {
static final UUID REQUEST_ID = UUID.randomUUID();
Expand Down Expand Up @@ -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
Expand All @@ -379,22 +381,30 @@ 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) {
assertEquals(409, ex.getStatusCode());
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<UUID, SilentAuthResponse>() {
new Verify2EndpointTestSpec<URI, SilentAuthResponse>() {

@Override
protected RestEndpoint<UUID, SilentAuthResponse> endpoint() {
protected RestEndpoint<URI, SilentAuthResponse> endpoint() {
return client.silentAuthCheck;
}

Expand All @@ -404,13 +414,28 @@ protected HttpMethod expectedHttpMethod() {
}

@Override
protected String expectedEndpointUri(UUID request) {
return "/v2/verify/"+request+"/silent-auth/redirect";
protected Collection<Class<? extends AuthMethod>> 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
Expand All @@ -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());
}
}
Expand Down

0 comments on commit 3d3db9e

Please sign in to comment.