Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Add support for handling UserRecoverableAuthException #771

Merged
merged 22 commits into from
Sep 21, 2018

Conversation

bparrishMines
Copy link
Contributor

/// Is thrown only on android.
///
/// Dart equivalent of android `UserRecoverableAuthException`.
class AndroidUserRecoverableAuthException implements Exception {
Copy link
Contributor

Choose a reason for hiding this comment

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

This doesn't help mulligan, but is there an equivalent to this for iOS? Just wondering if it's necessary to put "Android" in the name here I guess if iOS support is ever added.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh sorry. I think I misunderstood your request then. Did you want a functionality similar to this, but for iOS?

Copy link
Contributor

Choose a reason for hiding this comment

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

My team doesn't need it for iOS; however, I assume the same problem exists on iOS, and I'm asking if it might be a good idea to not scope this API specifically to Android to avoid a breaking change in the future if support for iOS were added. To be clear, this pull request probably does what we need (still have to look over it more thoroughly).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed. I removed Android. Also, I was unable to find a similar API for iOS so far.

Copy link
Contributor

@alanrussian alanrussian left a comment

Choose a reason for hiding this comment

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

I'll review this more thoroughly tomorrow. Thanks for the quick turnaround!

if (userRecoverableAuthIntent == null) {
throw new IllegalStateException("No recoverable auth intent available.");
} else if (currentAccount != null && !accountId.equals(currentAccount.getId())) {
throw new IllegalStateException("No recoverable auth intent for this account.");
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of throwing, can we make the result a failure? I would prefer to not cause the app to crash.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed it to throw a PlatformException instead. This check is to just make sure the developer doesn't sign in -> get UserRecoverAuthException -> log out -> try to start intent and recover Auth.

Also, recoverAuth() now returns a boolean to indicate if the User took successful actions to recover auth.

@@ -55,16 +81,35 @@ class GoogleSignInAccount implements GoogleIdentity {
final String _idToken;
final GoogleSignIn _googleSignIn;

/// Retrieve [GoogleSignInAuthentication] for this account.
///
/// Throws a [AndroidUserRecoverableAuthException] on Android to signal that a
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Throws an

public void recoverAuth(Result result, String accountId) {
if (currentAccount == null
|| userRecoverableAuthIntent == null
|| !accountId.equals(currentAccount.getId())) {
Copy link
Contributor

@alanrussian alanrussian Sep 10, 2018

Choose a reason for hiding this comment

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

I don't think this will work for us. We have use cases where we try to get the token when no account is selected. We try to get the token for every account before any account is selected.

Some ideas:

  • Store every Intent. While this leaks memory, I think intents are pretty tiny and don't hold any resources.

  • Map of account to Intent. Slightly better than above.

  • Have an option in the get token method to automatically launch the recovery flow if necessary.

Copy link
Contributor

Choose a reason for hiding this comment

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

cc @jacobklintg who thought about this for iOS

Choose a reason for hiding this comment

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

Alan covered the cases pretty well. We also want to be able to support silent sign-in attempts, which explicitly do not show any UI.

I think an optional boolean parameter at the dart level (defaulting to true) to control whether or not to attempt reauth when needed will work for us on both platforms.

It might be simplest on the app developer if the plugin just passes this boolean along to the native code and lets it perform reauth if desired, without making the developer explicitly perform reauth. Either the boolean is true so the developer wants reauth (which I imagine is the common case), or else it's false and the call will fail if reauth is needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@alanrussian @jklint-g I added an option to attempt to recover auth automatically once. If it doesn't work, authentication will return null.

@@ -135,7 +137,7 @@ public void init(
* Gets an OAuth access token with the scopes that were specified during initialization for the
* user with the specified email address.
*/
public void getTokens(final Result result, final String email);
public void getTokens(final Result result, final String email, final boolean shouldRecoverAuth);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you document shouldRecoverAuth?

@@ -342,7 +346,26 @@ public void run(Future<String> tokenFuture) {
// instead of the value we cached during sign in. At least, that's
// how it works on iOS.
result.success(tokenResult);
} catch (ExecutionException e) {
} catch (final ExecutionException e) {
if (shouldRecoverAuth && e.getCause() instanceof UserRecoverableAuthException) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If there's a UserRecoverableAuthException but shouldRecoverAuth is false, it might still be useful to report the exception to the Dart side (without the recover method that you had before).

getTokens(pendingOperation.result, (String) pendingOperation.data, false);
pendingOperation = null;
} else {
finishWithSuccess(null);
Copy link
Contributor

Choose a reason for hiding this comment

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

My instinct here is that it would be better to make the result a failure. Is there a reason why you prefer a success result with null?

Future<GoogleSignInAuthentication> get authentication async {
/// Retrieve [GoogleSignInAuthentication] for this account.
///
/// `shouldRecoverAuth` sets whether to attempt to recover authentication if
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: [shouldRecoverAuth] ?

Copy link
Contributor

@mehmetf mehmetf left a comment

Choose a reason for hiding this comment

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

I would like to review this more carefully later today. Will post my updates.

@@ -198,12 +206,12 @@ public GoogleSignInAccount getCurrentAccount() {
return currentAccount;
}

private void checkAndSetPendingOperation(String method, Result result) {
private void checkAndSetPendingOperation(String method, Result result, Object data) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Keep the existing signature and just call checkAndSetPendingOperation with null data. That way you don't need to update all callers.

return true;
} else if (requestCode == REQUEST_CODE_RECOVER_AUTH) {
if (resultCode == Activity.RESULT_OK) {
getTokens(pendingOperation.result, (String) pendingOperation.data, false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we know the pendingOperation type?

I would assert that pendingOperation was getTokens().

Copy link
Contributor

@mehmetf mehmetf left a comment

Choose a reason for hiding this comment

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

Given @alanrussian 's use case, I think this PR is good. Thanks Maurice.

public void run() {
UserRecoverableAuthException exception =
(UserRecoverableAuthException) e.getCause();
checkAndSetPendingOperation(METHOD_GET_TOKENS, result, email);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why set this here instead of at the very beginning?

@@ -464,6 +506,19 @@ public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
} else if (pendingOperation != null && pendingOperation.method.equals(METHOD_INIT)) {
finishWithError(ERROR_REASON_CONNECTION_FAILED, String.valueOf(resultCode));
}
return true;
} else if (requestCode == REQUEST_CODE_RECOVER_AUTH) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps this is big enough now that we should make it a switch statement:

switch (requestCode) {
  case REQUEST_CODE_RESOLVE_ERROR:

  case REQUEST_CODE_RECOVER_AUTH:

  case REQUEST_CODE:
     break;
  default:
     return false;
}

@@ -161,6 +166,7 @@ public void init(
*/
public static final class Delegate implements IDelegate {
private static final int REQUEST_CODE = 53293;
private static final int REQUEST_CODE_RECOVER_AUTH = 12345;
Copy link
Contributor

Choose a reason for hiding this comment

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

Just increment REQUEST_CODE by 1.

/// recovered by user action a [PlatformException] is thrown with error code
/// [kUserRecoverableAuthError].
Future<GoogleSignInAuthentication> getAuthentication({
bool shouldRecoverAuth = true,
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we set this as a property of the Account (or even a property of the GoogleSignIn API itself?)

That way it is not a breaking change. Otherwise, I assume you also need to make authHeaders a function that takes this parameter. Most apps do not deal with tokens directly. They just fetch the headers.

Copy link
Contributor

Choose a reason for hiding this comment

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

I confirmed that Mulligan is OK with this. They don't care about this Dart interface since they don't use it internally.

I now propose that we don't make a change to the Dart interface at all. We just pass in true as shouldRecoverAuth in here.

@bparrishMines bparrishMines merged commit b889b40 into flutter:master Sep 21, 2018
@bparrishMines bparrishMines deleted the google_sign branch September 21, 2018 18:26
mridul-sahu added a commit to mridul-sahu/plugins that referenced this pull request Sep 22, 2018
Add support for handling UserRecoverableAuthException (flutter#771)
andreidiaconu pushed a commit to andreidiaconu/plugins that referenced this pull request Feb 17, 2019
andreidiaconu added a commit to andreidiaconu/plugins that referenced this pull request Feb 17, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
6 participants