From 1603fbd378d9e89f6dc151a88032876bd77241b6 Mon Sep 17 00:00:00 2001 From: Luciano Balmaceda Date: Tue, 20 Feb 2018 17:46:03 -0300 Subject: [PATCH] enable MFA support for oidc conformant clients --- build.gradle | 1 + lib/build.gradle | 2 +- .../com/auth0/android/lock/LockActivity.java | 30 ++++++++++++---- .../lock/errors/LoginErrorMessageBuilder.java | 5 ++- .../lock/events/DatabaseLoginEvent.java | 10 ++++++ .../android/lock/views/ClassicLockView.java | 2 +- .../android/lock/views/MFACodeFormView.java | 7 ++-- lib/src/main/res/values/strings.xml | 3 +- .../auth0/android/lock/LockActivityTest.java | 36 +++++++++++++++++++ .../lock/events/DatabaseLoginEventTest.java | 11 ++++++ 10 files changed, 94 insertions(+), 13 deletions(-) diff --git a/build.gradle b/build.gradle index 1a23cee3d..595804437 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,7 @@ allprojects { group = 'com.auth0.android' repositories { + mavenLocal() jcenter() } } \ No newline at end of file diff --git a/lib/build.gradle b/lib/build.gradle index 49a0b5c42..1eb572eeb 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -41,7 +41,7 @@ dependencies { compile 'com.android.support:design:25.3.1' compile 'com.google.code.gson:gson:2.8.2' compile 'com.squareup:otto:1.3.8' - compile 'com.auth0.android:auth0:1.11.0' + compile 'com.auth0.android:auth0:1.12.1-SNAPSHOT' testCompile 'junit:junit:4.12' testCompile 'org.hamcrest:hamcrest-library:1.3' testCompile 'org.robolectric:robolectric:3.1.2' diff --git a/lib/src/main/java/com/auth0/android/lock/LockActivity.java b/lib/src/main/java/com/auth0/android/lock/LockActivity.java index 3db874ce2..46332400c 100644 --- a/lib/src/main/java/com/auth0/android/lock/LockActivity.java +++ b/lib/src/main/java/com/auth0/android/lock/LockActivity.java @@ -91,6 +91,7 @@ public class LockActivity extends AppCompatActivity implements ActivityCompat.On private static final int WEB_AUTH_REQUEST_CODE = 200; private static final int CUSTOM_AUTH_REQUEST_CODE = 201; private static final int PERMISSION_REQUEST_CODE = 202; + public static final String KEY_MFA_TOKEN = "mfa_token"; private ApplicationFetcher applicationFetcher; private Configuration configuration; @@ -377,13 +378,20 @@ public void onDatabaseAuthenticationRequest(DatabaseLoginEvent event) { lockView.showProgress(true); lastDatabaseLogin = event; AuthenticationAPIClient apiClient = options.getAuthenticationAPIClient(); - final HashMap parameters = new HashMap<>(options.getAuthenticationParameters()); - if (event.getVerificationCode() != null) { - parameters.put(KEY_VERIFICATION_CODE, event.getVerificationCode()); + AuthenticationRequest request; + HashMap parameters = new HashMap<>(options.getAuthenticationParameters()); + if (TextUtils.isEmpty(event.getMFAToken())) { + String connection = configuration.getDatabaseConnection().getName(); + request = apiClient.login(event.getUsernameOrEmail(), event.getPassword(), connection); + if (!TextUtils.isEmpty(event.getVerificationCode())) { + parameters.put(KEY_VERIFICATION_CODE, event.getVerificationCode()); + } + } else { + //noinspection ConstantConditions / OTP code is present along with MFA token + request = apiClient.loginWithOTP(event.getMFAToken(), event.getVerificationCode()); } - final String connection = configuration.getDatabaseConnection().getName(); - AuthenticationRequest request = apiClient.login(event.getUsernameOrEmail(), event.getPassword(), connection) - .addAuthenticationParameters(parameters); + + request.addAuthenticationParameters(parameters); if (options.getScope() != null) { request.setScope(options.getScope()); } @@ -512,12 +520,20 @@ public void run() { lockView.showProgress(false); final AuthenticationError authError = loginErrorBuilder.buildFrom(error); - if (error.isMultifactorRequired() || error.isMultifactorEnrollRequired()) { + if (error.isMultifactorRequired()) { + String mfaToken = (String) error.getValue(KEY_MFA_TOKEN); + if (!TextUtils.isEmpty(mfaToken)) { + lastDatabaseLogin.setMFAToken(mfaToken); + } lockView.showMFACodeForm(lastDatabaseLogin); return; } String message = authError.getMessage(LockActivity.this); showErrorMessage(message); + if (error.isMultifactorTokenExpired()) { + //The MFA Token has expired. The user needs to log in again. Show the username/password form + onBackPressed(); + } } }); } diff --git a/lib/src/main/java/com/auth0/android/lock/errors/LoginErrorMessageBuilder.java b/lib/src/main/java/com/auth0/android/lock/errors/LoginErrorMessageBuilder.java index eed3c51d9..cd2849fd9 100644 --- a/lib/src/main/java/com/auth0/android/lock/errors/LoginErrorMessageBuilder.java +++ b/lib/src/main/java/com/auth0/android/lock/errors/LoginErrorMessageBuilder.java @@ -42,6 +42,7 @@ public class LoginErrorMessageBuilder implements ErrorMessageBuilderTHERE WAS NO CONNECTION CONFIGURED FOR THE DOMAIN THERE WAS AN ERROR PROCESSING THE SIGN IN WRONG EMAIL OR PASSWORD + THE USER HAS NOT ENROLLED MULTIFACTOR AUTHENTICATION + THE CODE IS INVALID OR HAS EXPIRED USER IS BLOCKED THERE WAS AN ERROR PROCESSING THE SIGN UP WE\'VE JUST SENT YOU AN EMAIL TO RESET YOUR PASSWORD @@ -142,7 +144,6 @@ THERE WAS AN ERROR SENDING THE CODE THERE WAS AN ERROR SENDING THE LINK WRONG IDENTITY OR PASSCODE - THE CODE IS INVALID OR HAS EXPIRED Two Step Verification Please enter a verification code from\n your code generator application. diff --git a/lib/src/test/java/com/auth0/android/lock/LockActivityTest.java b/lib/src/test/java/com/auth0/android/lock/LockActivityTest.java index 3cba9675a..1c6dc5b4b 100644 --- a/lib/src/test/java/com/auth0/android/lock/LockActivityTest.java +++ b/lib/src/test/java/com/auth0/android/lock/LockActivityTest.java @@ -167,6 +167,42 @@ public void shouldCallLegacyDatabaseLoginWithVerificationCode() throws Exception assertThat(reqParams, is(notNullValue())); assertThat(reqParams, hasEntry("extra", "value")); assertThat(reqParams, hasEntry("mfa_code", "123456")); + assertThat(reqParams, not(hasKey("mfa_token"))); + assertThat(reqParams, not(hasKey("otp"))); + } + + @Test + public void shouldCallOIDCDatabaseLoginWithOTPCodeAndMFAToken() throws Exception { + Auth0 account = new Auth0("cliendId", "domain"); + account.setOIDCConformant(true); + Options options = mock(Options.class); + when(options.getAccount()).thenReturn(account); + when(options.getAuthenticationAPIClient()).thenReturn(client); + when(options.getScope()).thenReturn("openid user photos"); + when(options.getAudience()).thenReturn("aud"); + when(options.getAuthenticationParameters()).thenReturn(basicParameters); + LockActivity activity = new LockActivity(configuration, options, lockView, webProvider); + + DatabaseLoginEvent event = new DatabaseLoginEvent("username", "password"); + event.setVerificationCode("123456"); + event.setMFAToken("mfaToken"); + activity.onDatabaseAuthenticationRequest(event); + + verify(lockView).showProgress(true); + verify(options).getAuthenticationAPIClient(); + verify(client).login("username", "password", "connection"); + verify(authRequest).addAuthenticationParameters(mapCaptor.capture()); + verify(authRequest).start(any(BaseCallback.class)); + verify(authRequest).setScope("openid user photos"); + verify(authRequest, never()).setAudience("aud"); + verify(configuration, atLeastOnce()).getDatabaseConnection(); + + Map reqParams = mapCaptor.getValue(); + assertThat(reqParams, is(notNullValue())); + assertThat(reqParams, hasEntry("extra", "value")); + assertThat(reqParams, hasEntry("otp", "123456")); + assertThat(reqParams, hasEntry("mfa_token", "mfaToken")); + assertThat(reqParams, not(hasKey("mfa_code"))); } @Test diff --git a/lib/src/test/java/com/auth0/android/lock/events/DatabaseLoginEventTest.java b/lib/src/test/java/com/auth0/android/lock/events/DatabaseLoginEventTest.java index 3980b1eeb..4c18ce27a 100644 --- a/lib/src/test/java/com/auth0/android/lock/events/DatabaseLoginEventTest.java +++ b/lib/src/test/java/com/auth0/android/lock/events/DatabaseLoginEventTest.java @@ -41,4 +41,15 @@ public void shouldSetVerificationCode() throws Exception { event.setVerificationCode("code"); assertThat(event.getVerificationCode(), is("code")); } + + @Test + public void shouldNotHaveMFAToken() throws Exception { + assertThat(event.getMFAToken(), is(nullValue())); + } + + @Test + public void shouldSetMFAToken() throws Exception { + event.setMFAToken("mfatoken"); + assertThat(event.getMFAToken(), is("mfatoken")); + } } \ No newline at end of file