Skip to content

Commit

Permalink
enable MFA support for oidc conformant clients
Browse files Browse the repository at this point in the history
  • Loading branch information
lbalmaceda committed Jul 18, 2018
1 parent f3d0836 commit 81ef8bd
Show file tree
Hide file tree
Showing 10 changed files with 93 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ private void showClassicLock() {
builder.closable(checkboxClosable.isChecked());
builder.useLabeledSubmitButton(groupSubmitMode.getCheckedRadioButtonId() == R.id.radio_use_label);
builder.loginAfterSignUp(checkboxLoginAfterSignUp.isChecked());
builder.withAudience("https://lbalmaceda.auth0.com/api/v2/");
builder.withScope("openid profile");

if (groupSocialStyle.getCheckedRadioButtonId() == R.id.radio_social_style_big) {
builder.withAuthButtonSize(AuthButtonSize.BIG);
Expand Down
31 changes: 24 additions & 7 deletions lib/src/main/java/com/auth0/android/lock/LockActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,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;
Expand Down Expand Up @@ -381,17 +382,25 @@ public void onDatabaseAuthenticationRequest(DatabaseLoginEvent event) {
lockView.showProgress(true);
lastDatabaseLogin = event;
AuthenticationAPIClient apiClient = options.getAuthenticationAPIClient();
final HashMap<String, Object> parameters = new HashMap<>(options.getAuthenticationParameters());
if (event.getVerificationCode() != null) {
parameters.put(KEY_VERIFICATION_CODE, event.getVerificationCode());
AuthenticationRequest request;
HashMap<String, Object> parameters = new HashMap<>(options.getAuthenticationParameters());
if (TextUtils.isEmpty(event.getMFAToken()) || TextUtils.isEmpty(event.getVerificationCode())) {
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 {
//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());
}
if (options.getAudience() != null && options.getAccount().isOIDCConformant()) {
//Check if this is required on MFA 2nd step
request.setAudience(options.getAudience());
}
request.start(authCallback);
Expand Down Expand Up @@ -516,12 +525,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.isMultifactorTokenInvalid()) {
//The MFA Token has expired. The user needs to log in again. Show the username/password form
onBackPressed();
}
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public class LoginErrorMessageBuilder implements ErrorMessageBuilder<Authenticat
private static final int userExistsResource = R.string.com_auth0_lock_db_signup_user_already_exists_error_message;
private static final int unauthorizedResource = R.string.com_auth0_lock_db_login_error_unauthorized_message;
private static final int invalidMFACodeResource = R.string.com_auth0_lock_db_login_error_invalid_mfa_code_message;
private static final int mfaEnrollRequiredResource = R.string.com_auth0_lock_db_login_error_mfa_enroll_required;
private static final int tooManyAttemptsResource = R.string.com_auth0_lock_db_too_many_attempts_error_message;

private int invalidCredentialsResource;
Expand All @@ -63,8 +64,10 @@ public AuthenticationError buildFrom(AuthenticationException exception) {

if (exception.isInvalidCredentials()) {
messageRes = invalidCredentialsResource;
} else if (exception.isMultifactorCodeInvalid()) {
} else if (exception.isMultifactorCodeInvalid() || exception.isMultifactorTokenInvalid()) {
messageRes = invalidMFACodeResource;
} else if (exception.isMultifactorEnrollRequired()) {
messageRes = mfaEnrollRequiredResource;
} else if (USER_EXISTS_ERROR.equals(exception.getCode()) || USERNAME_EXISTS_ERROR.equals(exception.getCode())) {
messageRes = userExistsResource;
} else if (exception.isRuleError()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class DatabaseLoginEvent extends DatabaseEvent {

private String password;
private String verificationCode;
private String mfaToken;

public DatabaseLoginEvent(@NonNull String usernameOrEmail, @NonNull String password) {
super(usernameOrEmail);
Expand All @@ -56,4 +57,13 @@ public void setVerificationCode(@NonNull String code) {
public String getVerificationCode() {
return verificationCode;
}

public void setMFAToken(@NonNull String mfaToken) {
this.mfaToken = mfaToken;
}

@Nullable
public String getMFAToken() {
return mfaToken;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ public void showCustomFieldsForm(DatabaseSignUpEvent event) {
}

public void showMFACodeForm(DatabaseLoginEvent event) {
MFACodeFormView form = new MFACodeFormView(this, event.getUsernameOrEmail(), event.getPassword());
MFACodeFormView form = new MFACodeFormView(this, event.getUsernameOrEmail(), event.getPassword(), event.getMFAToken());
updateHeaderTitle(R.string.com_auth0_lock_title_mfa_input_code);
addSubForm(form);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@

package com.auth0.android.lock.views;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.TextView;

Expand All @@ -38,16 +38,18 @@ public class MFACodeFormView extends FormView implements TextView.OnEditorAction

private final String usernameOrEmail;
private final String password;
private final String mfaToken;

private LockWidget lockWidget;
private ValidatedInputView codeInput;


public MFACodeFormView(LockWidget lockWidget, String usernameOrEmail, String password) {
public MFACodeFormView(@NonNull LockWidget lockWidget, @Nullable String usernameOrEmail, @Nullable String password, @Nullable String mfaToken) {
super(lockWidget.getContext());
this.lockWidget = lockWidget;
this.usernameOrEmail = usernameOrEmail;
this.password = password;
this.mfaToken = mfaToken;
init();
}

Expand All @@ -62,6 +64,7 @@ private void init() {
public Object getActionEvent() {
DatabaseLoginEvent event = new DatabaseLoginEvent(usernameOrEmail, password);
event.setVerificationCode(getInputText());
event.setMFAToken(mfaToken);
return event;
}

Expand Down
3 changes: 2 additions & 1 deletion lib/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@
<string name="com_auth0_lock_enterprise_no_connection_message">THERE WAS NO CONNECTION CONFIGURED FOR THE DOMAIN</string>
<string name="com_auth0_lock_db_login_error_message">THERE WAS AN ERROR PROCESSING THE SIGN IN</string>
<string name="com_auth0_lock_db_login_error_invalid_credentials_message">WRONG EMAIL OR PASSWORD</string>
<string name="com_auth0_lock_db_login_error_mfa_enroll_required">THE USER HAS NOT ENROLLED MULTIFACTOR AUTHENTICATION</string>
<string name="com_auth0_lock_db_login_error_invalid_mfa_code_message">THE CODE IS INVALID OR HAS EXPIRED</string>
<string name="com_auth0_lock_db_login_error_unauthorized_message">USER IS BLOCKED</string>
<string name="com_auth0_lock_db_sign_up_error_message">THERE WAS AN ERROR PROCESSING THE SIGN UP</string>
<string name="com_auth0_lock_db_change_password_message_success">WE\'VE JUST SENT YOU AN EMAIL TO RESET YOUR PASSWORD</string>
Expand All @@ -142,7 +144,6 @@
<string name="com_auth0_lock_passwordless_code_request_error_message">THERE WAS AN ERROR SENDING THE CODE</string>
<string name="com_auth0_lock_passwordless_link_request_error_message">THERE WAS AN ERROR SENDING THE LINK</string>
<string name="com_auth0_lock_passwordless_login_error_invalid_credentials_message">WRONG IDENTITY OR PASSCODE</string>
<string name="com_auth0_lock_db_login_error_invalid_mfa_code_message">THE CODE IS INVALID OR HAS EXPIRED</string>
<string name="com_auth0_lock_title_mfa_input_code">Two Step Verification</string>
<string name="com_auth0_lock_description_mfa_input_code">Please enter a verification code from\n your code generator application.</string>

Expand Down
33 changes: 33 additions & 0 deletions lib/src/test/java/com/auth0/android/lock/LockActivityTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public void setUp() throws Exception {

when(options.getAuthenticationParameters()).thenReturn(basicParameters);
when(client.login(anyString(), anyString(), anyString())).thenReturn(authRequest);
when(client.loginWithOTP(anyString(), anyString())).thenReturn(authRequest);
when(client.createUser(anyString(), anyString(), anyString())).thenReturn(dbRequest);
when(client.createUser(anyString(), anyString(), anyString(), anyString())).thenReturn(dbRequest);
when(client.signUp(anyString(), anyString(), anyString())).thenReturn(signUpRequest);
Expand Down Expand Up @@ -169,6 +170,38 @@ public void shouldCallLegacyDatabaseLoginWithVerificationCode() throws Exception
assertThat(reqParams, hasEntry("mfa_code", "123456"));
}

@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).loginWithOTP("mfaToken", "123456");
verify(authRequest).addAuthenticationParameters(mapCaptor.capture());
verify(authRequest).start(any(BaseCallback.class));
verify(authRequest).setScope("openid user photos");
verify(authRequest).setAudience("aud");
verify(configuration, atLeastOnce()).getDatabaseConnection();

Map<String, String> reqParams = mapCaptor.getValue();
assertThat(reqParams, is(notNullValue()));
assertThat(reqParams, hasEntry("extra", "value"));
assertThat(reqParams, not(hasKey("mfa_code")));
}

@Test
public void shouldCallOIDCDatabaseLoginWithCustomAudience() throws Exception {
Auth0 account = new Auth0("cliendId", "domain");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public void shouldHaveDefaultMessageIfMultifactorRequired() throws Exception {
public void shouldHaveDefaultMessageIfMultifactorEnrollRequired() throws Exception {
Mockito.when(exception.isMultifactorEnrollRequired()).thenReturn(true);
final AuthenticationError error = builder.buildFrom(exception);
assertThat(error.getMessageRes(), is(equalTo(R.string.com_auth0_lock_db_login_error_message)));
assertThat(error.getMessageRes(), is(equalTo(R.string.com_auth0_lock_db_login_error_mfa_enroll_required)));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
}
}

0 comments on commit 81ef8bd

Please sign in to comment.