diff --git a/src/main/java/com/auth0/client/auth/AuthAPI.java b/src/main/java/com/auth0/client/auth/AuthAPI.java index fefe5abb5..6e310b356 100644 --- a/src/main/java/com/auth0/client/auth/AuthAPI.java +++ b/src/main/java/com/auth0/client/auth/AuthAPI.java @@ -1395,6 +1395,27 @@ public Request addOtpAuthenticator(String mfaToken) { return request; } + + private BaseRequest createBaseOobRequest(String mfaToken, List oobChannels) { + String url = baseUrl + .newBuilder() + .addPathSegment("mfa") + .addPathSegment("associate") + .build() + .toString(); + + BaseRequest request = new BaseRequest<>(client, null, url, HttpMethod.POST, new TypeReference() { + }); + + request.addParameter("authenticator_types", Collections.singletonList("oob")); + request.addParameter("oob_channels", oobChannels); + request.addParameter(KEY_CLIENT_ID, clientId); + addClientAuthentication(request, false); + request.addHeader("Authorization", "Bearer " + mfaToken); + + return request; + } + /** * Associates or adds a new OOB authenticator for multi-factor authentication (MFA). * Confidential clients (Regular Web Apps) must have a client secret configured on this {@code AuthAPI} instance. @@ -1415,32 +1436,67 @@ public Request addOtpAuthenticator(String mfaToken) { * @param phoneNumber The phone number for "sms" or "voice" channels. May be null if not using "sms" or "voice". * @return a Request to execute. * @see Add an Authenticator API documentation + * @deprecated Use {@linkplain #addOobAuthenticator(String, List, String, String)} instead. */ + @Deprecated public Request addOobAuthenticator(String mfaToken, List oobChannels, String phoneNumber) { Asserts.assertNotNull(mfaToken, "mfa token"); Asserts.assertNotNull(oobChannels, "OOB channels"); + if (oobChannels.contains("sms") || oobChannels.contains("voice")) { + Asserts.assertNotNull(phoneNumber, "phone number"); + } - String url = baseUrl - .newBuilder() - .addPathSegment("mfa") - .addPathSegment("associate") - .build() - .toString(); - - BaseRequest request = new BaseRequest<>(client, null, url, HttpMethod.POST, new TypeReference() { - }); + BaseRequest request = createBaseOobRequest(mfaToken, oobChannels); - request.addParameter("authenticator_types", Collections.singletonList("oob")); - request.addParameter("oob_channels", oobChannels); - request.addParameter(KEY_CLIENT_ID, clientId); if (phoneNumber != null) { request.addParameter("phone_number", phoneNumber); } - addClientAuthentication(request, false); - request.addHeader("Authorization", "Bearer " + mfaToken); + return request; } + /** + * Associates or adds a new OOB authenticator for multi-factor authentication (MFA). + * Confidential clients (Regular Web Apps) must have a client secret configured on this {@code AuthAPI} instance. + *
+     * {@code
+     * try {
+     *      CreatedOobResponse result = authAPI.addOobAuthenticator("the-mfa-token", Arrays.asList("sms", "email"), "phone-number", "email-address")
+     *          .execute()
+     *          .getBody();
+     * } catch (Auth0Exception e) {
+     *      //Something happened
+     * }
+     * }
+     * 
+ * + * @param mfaToken The token received from mfa_required error. Must not be null. + * @param oobChannels The type of OOB channels supported by the client. Must not be null. + * @param phoneNumber The phone number for "sms" or "voice" channels. May be null if not using "sms" or "voice". + * @param emailAddress The email address for "email" channel. May be null if not using "email". + * @return a Request to execute. + * @see Enroll with SMS or voice + */ + public Request addOobAuthenticator(String mfaToken, List oobChannels, String phoneNumber, String emailAddress) { + Asserts.assertNotNull(mfaToken, "mfa token"); + Asserts.assertNotNull(oobChannels, "OOB channels"); + if (oobChannels.contains("sms") || oobChannels.contains("voice")) { + Asserts.assertNotNull(phoneNumber, "phone number"); + } + if (oobChannels.contains("email")) { + Asserts.assertNotNull(emailAddress, "email address"); + } + + BaseRequest request = createBaseOobRequest(mfaToken, oobChannels); + if (phoneNumber != null) { + request.addParameter("phone_number", phoneNumber); + } + if (emailAddress != null) { + request.addParameter("email", emailAddress); + } + + return request; + } /** * Returns a list of authenticators associated with your application. diff --git a/src/test/java/com/auth0/client/auth/AuthAPITest.java b/src/test/java/com/auth0/client/auth/AuthAPITest.java index 800f4c202..b89154a65 100644 --- a/src/test/java/com/auth0/client/auth/AuthAPITest.java +++ b/src/test/java/com/auth0/client/auth/AuthAPITest.java @@ -1541,22 +1541,41 @@ public void addOtpAuthenticatorRequest() throws Exception { assertThat(response.getRecoveryCodes(), notNullValue()); } + @SuppressWarnings("deprecation") @Test - public void addOobAuthenticatorThrowsWhenTokenNull() { + public void addOobAuthenticatorDeprecatedThrowsWhenTokenNull() { verifyThrows(IllegalArgumentException.class, - () -> api.addOobAuthenticator(null, Collections.singletonList("otp"), null), + () -> api.addOobAuthenticator(null, Collections.singletonList("auth0"), null), "'mfa token' cannot be null!"); } + @SuppressWarnings("deprecation") @Test - public void addOobAuthenticatorThrowsWhenChannelsNull() { + public void addOobAuthenticatorDeprecatedThrowsWhenChannelsNull() { verifyThrows(IllegalArgumentException.class, () -> api.addOobAuthenticator("mfaToken", null, null), "'OOB channels' cannot be null!"); } + @SuppressWarnings("deprecation") + @Test + public void addOobAuthenticatorDeprecatedThrowsWhenPhoneNumberNull() { + verifyThrows(IllegalArgumentException.class, + () -> api.addOobAuthenticator("mfaToken", Collections.singletonList("sms"), null), + "'phone number' cannot be null!"); + } + + @SuppressWarnings("deprecation") @Test - public void addOobAuthenticatorRequest() throws Exception { + public void addOobAuthenticatorDeprecatedThrowsWhenPhoneNumberNullVoiceChannel() { + verifyThrows(IllegalArgumentException.class, + () -> api.addOobAuthenticator("mfaToken", Collections.singletonList("voice"), null), + "'phone number' cannot be null!"); + } + + @SuppressWarnings("deprecation") + @Test + public void addOobAuthenticatorDeprecatedRequest() throws Exception { Request request = api.addOobAuthenticator("mfaToken", Collections.singletonList("sms"), "phone-number"); server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); @@ -1579,6 +1598,199 @@ public void addOobAuthenticatorRequest() throws Exception { assertThat(response.getRecoveryCodes(), notNullValue()); } + @SuppressWarnings("deprecation") + @Test + public void addOobAuthenticatorDeprecatedRequestWithNoPhoneNumber() throws Exception { + Request request = api.addOobAuthenticator("mfaToken", Collections.singletonList("auth0"), null); + + server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); + CreatedOobResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/mfa/associate")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("authenticator_types", Collections.singletonList("oob"))); + assertThat(body, hasEntry("oob_channels", Collections.singletonList("auth0"))); + + assertThat(response, is(notNullValue())); + assertThat(response.getAuthenticatorType(), not(emptyOrNullString())); + assertThat(response.getOobChannel(), not(emptyOrNullString())); + assertThat(response.getOobCode(), not(emptyOrNullString())); + assertThat(response.getBarcodeUri(), not(emptyOrNullString())); + assertThat(response.getRecoveryCodes(), notNullValue()); + } + + @Test + public void addOobAuthenticatorThrowsWhenTokenNull() { + verifyThrows(IllegalArgumentException.class, + () -> api.addOobAuthenticator(null, Collections.singletonList("auth0"), null, null), + "'mfa token' cannot be null!"); + } + + + @Test + public void addOobAuthenticatorThrowsWhenChannelsNull() { + verifyThrows(IllegalArgumentException.class, + () -> api.addOobAuthenticator("mfaToken", null, null, null), + "'OOB channels' cannot be null!"); + } + + @Test + public void addOobAuthenticatorThrowsWhenChannelsNullWithPhoneNumber() { + verifyThrows(IllegalArgumentException.class, + () -> api.addOobAuthenticator("mfaToken", null, "phone-number", null), + "'OOB channels' cannot be null!"); + } + + @Test + public void addOobAuthenticatorThrowsWhenChannelsNullWithEmail() { + verifyThrows(IllegalArgumentException.class, + () -> api.addOobAuthenticator("mfaToken", null, null, "email-address"), + "'OOB channels' cannot be null!"); + } + + @Test + public void addOobAuthenticatorThrowsWhenPhoneNumberNull() { + verifyThrows(IllegalArgumentException.class, + () -> api.addOobAuthenticator("mfaToken", Collections.singletonList("sms"), null, null), + "'phone number' cannot be null!"); + } + + @Test + public void addOobAuthenticatorThrowsWhenPhoneNumberNullWithVoiceChannel() { + verifyThrows(IllegalArgumentException.class, + () -> api.addOobAuthenticator("mfaToken", Collections.singletonList("voice"), null, null), + "'phone number' cannot be null!"); + } + + @Test + public void addOobAuthenticatorThrowsWhenEmailNull() { + verifyThrows(IllegalArgumentException.class, + () -> api.addOobAuthenticator("mfaToken", Collections.singletonList("email"), null, null), + "'email address' cannot be null!"); + } + + @Test + public void addOobAuthenticatorRequestWithPhoneNumber() throws Exception { + Request request = api.addOobAuthenticator("mfaToken", Collections.singletonList("sms"), "phone-number", null); + + server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); + CreatedOobResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/mfa/associate")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("authenticator_types", Collections.singletonList("oob"))); + assertThat(body, hasEntry("oob_channels", Collections.singletonList("sms"))); + assertThat(body, hasEntry("phone_number", "phone-number")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAuthenticatorType(), not(emptyOrNullString())); + assertThat(response.getOobChannel(), not(emptyOrNullString())); + assertThat(response.getOobCode(), not(emptyOrNullString())); + assertThat(response.getBarcodeUri(), not(emptyOrNullString())); + assertThat(response.getRecoveryCodes(), notNullValue()); + } + + @Test + public void addOobAuthenticatorRequestWithEmail() throws Exception { + Request request = api.addOobAuthenticator("mfaToken", Collections.singletonList("email"), null, "email-address"); + + server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); + CreatedOobResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/mfa/associate")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("authenticator_types", Collections.singletonList("oob"))); + assertThat(body, hasEntry("oob_channels", Collections.singletonList("email"))); + assertThat(body, hasEntry("email", "email-address")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAuthenticatorType(), not(emptyOrNullString())); + assertThat(response.getOobChannel(), not(emptyOrNullString())); + assertThat(response.getOobCode(), not(emptyOrNullString())); + assertThat(response.getBarcodeUri(), not(emptyOrNullString())); + assertThat(response.getRecoveryCodes(), notNullValue()); + } + + @Test + public void addOobAuthenticatorRequestWithNoContactInfo() throws Exception { + Request request = api.addOobAuthenticator("mfaToken", Collections.singletonList("auth0"), null, null); + + server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); + CreatedOobResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/mfa/associate")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("authenticator_types", Collections.singletonList("oob"))); + assertThat(body, hasEntry("oob_channels", Collections.singletonList("auth0"))); + + assertThat(response, is(notNullValue())); + assertThat(response.getAuthenticatorType(), not(emptyOrNullString())); + assertThat(response.getOobChannel(), not(emptyOrNullString())); + assertThat(response.getOobCode(), not(emptyOrNullString())); + assertThat(response.getBarcodeUri(), not(emptyOrNullString())); + assertThat(response.getRecoveryCodes(), notNullValue()); + } + + @Test + public void addOobAuthenticatorRequestWithMultipleChannels() throws Exception { + Request request = api.addOobAuthenticator("mfaToken", Arrays.asList("sms", "email", "auth0"), "phone-number", "email-address"); + + server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); + CreatedOobResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/mfa/associate")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("authenticator_types", Collections.singletonList("oob"))); + assertThat(body, hasEntry("oob_channels", Arrays.asList("sms", "email", "auth0"))); + assertThat(body, hasEntry("phone_number", "phone-number")); + assertThat(body, hasEntry("email", "email-address")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAuthenticatorType(), not(emptyOrNullString())); + assertThat(response.getOobChannel(), not(emptyOrNullString())); + assertThat(response.getOobCode(), not(emptyOrNullString())); + assertThat(response.getBarcodeUri(), not(emptyOrNullString())); + assertThat(response.getRecoveryCodes(), notNullValue()); + } + + @Test + public void addOobAuthenticatorRequestWithAuth0Channel() throws Exception { + Request request = api.addOobAuthenticator("mfaToken", Collections.singletonList("auth0"), null, "email-address"); + + server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); + CreatedOobResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/mfa/associate")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("authenticator_types", Collections.singletonList("oob"))); + assertThat(body, hasEntry("oob_channels", Collections.singletonList("auth0"))); + + assertThat(response, is(notNullValue())); + assertThat(response.getAuthenticatorType(), not(emptyOrNullString())); + assertThat(response.getOobChannel(), not(emptyOrNullString())); + assertThat(response.getOobCode(), not(emptyOrNullString())); + assertThat(response.getBarcodeUri(), not(emptyOrNullString())); + assertThat(response.getRecoveryCodes(), notNullValue()); + } + @Test public void listAuthenticatorsThrowsWhenTokenNull() { verifyThrows(IllegalArgumentException.class,