Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Raise Unauthorized and Access Denied errors through callback [SDK-2480] #618

Merged
merged 3 commits into from
Apr 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,7 @@ Continue reading for the detail of classes and methods that were impacted.
### Changes to the underlying SDK

The core SDK has been updated to the version 2+. Since this is exposed as an API scoped dependency, if you were using any of the classes or methods that changed in the new major release (e.g. the `WebAuthProvider` class), you might need to update your code. Follow the [Auth0.Android Migration Guide](https://github.com/auth0/Auth0.Android/blob/main/V2_MIGRATION_GUIDE.md) to assess the impact.

## Changes in behavior

The `LockCallback` will get its `onError` method invoked when an [Auth0 Rule](https://auth0.com/docs/rules) returns an `Error` or `UnauthorizedError`. This was previously handled internally by Lock, causing it to display an orange toast with a generic failure message. From this release on, if you are using Auth0 Rules and throwing custom errors, you should obtain the _cause_ of the exception and read the code or description values to understand what went wrong.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@

import androidx.annotation.NonNull;

import com.auth0.android.authentication.AuthenticationException;
import com.auth0.android.lock.utils.LockException;
import com.auth0.android.result.Credentials;

import java.util.Date;
Expand Down Expand Up @@ -75,6 +77,11 @@ public void onEvent(@LockEvent int event, @NonNull Intent data) {
* @param data the intent received at the end of the login process.
*/
private void parseAuthentication(Intent data) {
if (data.hasExtra(Constants.EXCEPTION_EXTRA)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if an exception is present after an authentication event, raise it through the onError method

AuthenticationException error = (AuthenticationException) data.getSerializableExtra(Constants.EXCEPTION_EXTRA);
onError(new LockException(error));
return;
}
String idToken = data.getStringExtra(Constants.ID_TOKEN_EXTRA);
String accessToken = data.getStringExtra(Constants.ACCESS_TOKEN_EXTRA);
String tokenType = data.getStringExtra(Constants.TOKEN_TYPE_EXTRA);
Expand Down
1 change: 1 addition & 0 deletions lib/src/main/java/com/auth0/android/lock/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ abstract class Constants {
static final String CANCELED_ACTION = "com.auth0.android.lock.action.Canceled";
static final String INVALID_CONFIGURATION_ACTION = "com.auth0.android.lock.action.InvalidConfiguration";

static final String EXCEPTION_EXTRA = "com.auth0.android.lock.extra.Exception";
static final String ERROR_EXTRA = "com.auth0.android.lock.extra.Error";
static final String ID_TOKEN_EXTRA = "com.auth0.android.lock.extra.IdToken";
static final String ACCESS_TOKEN_EXTRA = "com.auth0.android.lock.extra.AccessToken";
Expand Down
5 changes: 3 additions & 2 deletions lib/src/main/java/com/auth0/android/lock/Lock.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import androidx.localbroadcastmanager.content.LocalBroadcastManager;

import com.auth0.android.Auth0;
import com.auth0.android.authentication.AuthenticationException;
import com.auth0.android.lock.LockCallback.LockEvent;
import com.auth0.android.lock.internal.configuration.Options;
import com.auth0.android.lock.internal.configuration.Theme;
Expand Down Expand Up @@ -150,8 +151,8 @@ private void processEvent(@NonNull Context context, @NonNull Intent data) {
switch (action) {
case Constants.AUTHENTICATION_ACTION:
Log.v(TAG, "AUTHENTICATION action received in our BroadcastReceiver");
if (data.hasExtra(Constants.ERROR_EXTRA)) {
callback.onError(new LockException(data.getStringExtra(Constants.ERROR_EXTRA)));
if (data.hasExtra(Constants.EXCEPTION_EXTRA)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The former was not used from the code. Replaced with a more meaningful extra that can hold the Exception instance with all the details.

callback.onError(new LockException((AuthenticationException) data.getSerializableExtra(Constants.EXCEPTION_EXTRA)));
} else {
callback.onEvent(LockEvent.AUTHENTICATION, data);
}
Expand Down
26 changes: 21 additions & 5 deletions lib/src/main/java/com/auth0/android/lock/LockActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,14 @@ private void deliverAuthenticationResult(Credentials credentials) {
finish();
}

private void deliverAuthenticationError(AuthenticationException exception) {
Intent intent = new Intent(Constants.AUTHENTICATION_ACTION);
intent.putExtra(Constants.EXCEPTION_EXTRA, exception);

LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
finish();
}

private void deliverSignUpResult(DatabaseUser result) {
Intent intent = new Intent(Constants.SIGN_UP_ACTION);
intent.putExtra(Constants.EMAIL_EXTRA, result.getEmail());
Expand Down Expand Up @@ -484,9 +492,13 @@ public void onFailure(@NonNull final Dialog dialog) {

@Override
public void onFailure(@NonNull final AuthenticationException exception) {
Log.e(TAG, "Failed to authenticate the user: " + exception.getCode(), exception);
if (exception.isRuleError() || exception.isAccessDenied()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

covers the webauth flow for Lock

deliverAuthenticationError(exception);
return;
}
final AuthenticationError authError = loginErrorBuilder.buildFrom(exception);
final String message = authError.getMessage(LockActivity.this);
Log.e(TAG, "Failed to authenticate the user: " + message, exception);
handler.post(() -> showErrorMessage(message));
}

Expand All @@ -506,12 +518,16 @@ public void onSuccess(@Nullable Credentials credentials) {

@Override
public void onFailure(@NonNull final AuthenticationException error) {
Log.e(TAG, "Failed to authenticate the user: " + error.getMessage(), error);
final AuthenticationError authError = loginErrorBuilder.buildFrom(error);
Log.e(TAG, "Failed to authenticate the user: " + error.getCode(), error);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

covers the database/enterprise flow for Lock

if (error.isRuleError() || error.isAccessDenied()) {
deliverAuthenticationError(error);
return;
}
if (error.isVerificationRequired()) {
completeDatabaseAuthenticationOnBrowser();
return;
}
final AuthenticationError authError = loginErrorBuilder.buildFrom(error);

handler.post(() -> {
lockView.showProgress(false);
Expand Down Expand Up @@ -542,7 +558,7 @@ public void onSuccess(@Nullable final DatabaseUser user) {

@Override
public void onFailure(@NonNull final AuthenticationException error) {
Log.e(TAG, "Failed to create the user: " + error.getMessage(), error);
Log.e(TAG, "Failed to create the user: " + error.getCode(), error);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed a few log lines, moved them up in the code and used the error code instead. These might be removed before the release.

if (error.isVerificationRequired()) {
completeDatabaseAuthenticationOnBrowser();
return;
Expand All @@ -568,7 +584,7 @@ public void onSuccess(@Nullable Void payload) {

@Override
public void onFailure(@NonNull AuthenticationException error) {
Log.e(TAG, "Failed to reset the user password: " + error.getMessage(), error);
Log.e(TAG, "Failed to reset the user password: " + error.getCode(), error);
handler.post(() -> {
String message = new AuthenticationError(R.string.com_auth0_lock_db_message_change_password_error).getMessage(LockActivity.this);
showErrorMessage(message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import androidx.localbroadcastmanager.content.LocalBroadcastManager;

import com.auth0.android.Auth0;
import com.auth0.android.authentication.AuthenticationException;
import com.auth0.android.lock.LockCallback.LockEvent;
import com.auth0.android.lock.internal.configuration.Options;
import com.auth0.android.lock.internal.configuration.Theme;
Expand Down Expand Up @@ -149,8 +150,8 @@ private void processEvent(Context context, Intent data) {
switch (action) {
case Constants.AUTHENTICATION_ACTION:
Log.v(TAG, "AUTHENTICATION action received in our BroadcastReceiver");
if (data.getExtras().containsKey(Constants.ERROR_EXTRA)) {
callback.onError(new LockException(data.getStringExtra(Constants.ERROR_EXTRA)));
if (data.hasExtra(Constants.EXCEPTION_EXTRA)) {
callback.onError(new LockException((AuthenticationException) data.getSerializableExtra(Constants.EXCEPTION_EXTRA)));
} else {
callback.onEvent(LockEvent.AUTHENTICATION, data);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,14 @@ private void deliverAuthenticationResult(Credentials credentials) {
finish();
}

private void deliverAuthenticationError(AuthenticationException exception) {
Intent intent = new Intent(Constants.AUTHENTICATION_ACTION);
intent.putExtra(Constants.EXCEPTION_EXTRA, exception);

LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
finish();
}

private void showErrorMessage(String message) {
resultMessage.setBackgroundColor(ContextCompat.getColor(this, R.color.com_auth0_lock_result_message_error_background));
resultMessage.setVisibility(View.VISIBLE);
Expand Down Expand Up @@ -494,6 +502,10 @@ public void onSuccess(@Nullable Credentials credentials) {
@Override
public void onFailure(@NonNull final AuthenticationException error) {
Log.e(TAG, "Failed to authenticate the user: " + error.getMessage(), error);
if (error.isRuleError() || error.isAccessDenied()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

covers the passwordless flow for PasswordlessLock

deliverAuthenticationError(error);
return;
}
handler.post(() -> showErrorMessage(loginErrorBuilder.buildFrom(error).getMessage(PasswordlessLockActivity.this)));
}
};
Expand All @@ -507,9 +519,13 @@ public void onFailure(@NonNull final Dialog dialog) {

@Override
public void onFailure(@NonNull final AuthenticationException exception) {
Log.e(TAG, "Failed to authenticate the user: " + exception.getCode(), exception);
if (exception.isRuleError() || exception.isAccessDenied()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

covers the webauth flow for PasswordlessLock

deliverAuthenticationError(exception);
return;
}
final AuthenticationError authError = loginErrorBuilder.buildFrom(exception);
final String message = authError.getMessage(PasswordlessLockActivity.this);
Log.e(TAG, "Failed to authenticate the user: " + message, exception);
handler.post(() -> showErrorMessage(message));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,17 @@

import androidx.annotation.NonNull;

import com.auth0.android.authentication.AuthenticationException;


public class LockException extends Exception {

public LockException(@NonNull String message) {
super(message);
}

public LockException(@NonNull AuthenticationException exception) {
super(exception);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

import android.content.Intent;

import com.auth0.android.authentication.AuthenticationException;
import com.auth0.android.lock.LockCallback.LockEvent;
import com.auth0.android.lock.utils.MockLockCallback;
import com.auth0.android.result.Credentials;
Expand All @@ -39,6 +40,7 @@
import java.util.Date;

import static com.auth0.android.lock.utils.AuthenticationCallbackMatcher.hasAuthentication;
import static com.auth0.android.lock.utils.AuthenticationCallbackMatcher.hasError;
import static com.auth0.android.lock.utils.AuthenticationCallbackMatcher.hasNoError;
import static com.auth0.android.lock.utils.AuthenticationCallbackMatcher.isCanceled;
import static org.hamcrest.CoreMatchers.equalTo;
Expand Down Expand Up @@ -80,6 +82,16 @@ public void shouldReturnAuthentication() {
assertThat(callback, hasNoError());
}

@Test
public void shouldReturnAuthenticationError() {
Intent data = new Intent();
AuthenticationException error = new AuthenticationException("err_code", "err description");
data.putExtra(Constants.EXCEPTION_EXTRA, error);
callback.onEvent(LockEvent.AUTHENTICATION, data);

assertThat(callback, hasError());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checks that when an "exception extra" is present, the onError is invoked with a non-null value for the exception

}

@Test
public void shouldCallOnCanceled() {
Intent data = new Intent();
Expand Down