-
Notifications
You must be signed in to change notification settings - Fork 9.8k
Add support for handling UserRecoverableAuthException #771
Conversation
/// Is thrown only on android. | ||
/// | ||
/// Dart equivalent of android `UserRecoverableAuthException`. | ||
class AndroidUserRecoverableAuthException implements Exception { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this 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."); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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())) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: [shouldRecoverAuth] ?
There was a problem hiding this 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) { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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().
There was a problem hiding this 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); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Add support for handling UserRecoverableAuthException (flutter#771)
Fixes flutter/flutter#21123