-
Notifications
You must be signed in to change notification settings - Fork 3.3k
[google_sign_in] Redesign API for current identity SDKs #9267
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
base: main
Are you sure you want to change the base?
[google_sign_in] Redesign API for current identity SDKs #9267
Conversation
Also reworks app-facing package example app for testing.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
Looks like this will need either #9168 or a localized SDK 35 change. |
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 I have anything blocking. I took a look at google_sign_in
, google_sign_in_platform_interface
and google_sign_in_web
.
isAuthorized = await _googleSignIn.canAccessScopes(scopes); | ||
} | ||
const List<String> scopes = <String>[ | ||
'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.
'email'
seems to me like an "old school" scope docs, maybe don't show it in this example, or replace by 'https://www.googleapis.com/auth/userinfo.email'
?
|
||
Once your app determines that the current user `isAuthorized` to access the | ||
services for which you need `scopes`, it can proceed normally. | ||
|
||
### Authorization expiration | ||
|
||
In the web, **the `accessToken` is no longer refreshed**. It expires after 3600 |
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.
Upon re-reading this... is this paragraph is a little bit incomplete? IIRC The authentication token (idToken) is also not refreshed, so both authorization and authentication end up expiring, right?
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.
Looks that way. I can adjust the README accordingly, but maybe you just didn't mention it because normally (IIUC) clients don't need to keep using ID tokens they way they do access tokens.
user = event.user; | ||
case GoogleSignInAuthenticationEventSignOut(): | ||
user = null; | ||
case GoogleSignInAuthenticationEventException(): |
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 is not specifically related to this example, but... why are we adding something that looks like an "error" event through the onData
callback, instead of adding them as an error to the stream so they can be dealt with by a separate onError
handler?
Adding an error to the stream also makes the case where the user is doing await stream.first
throwy.
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 think I just got carried away with structured error returns on iOS and Android. I'll revisit this for both layers of streams.
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.
IMO I would only change the user-facing stream. In the implementation of the plugin we're already looking at the continuous stream of events coming from the platform code, so maybe the platform -> plugin stream can continue be a single one, with error events ("it never crashes") but the plugin -> app stream can emit errors normally? But yeah, if there's a benefit to making the platform -> plugin also emit errors, I won't oppose it changing :)
|
||
/// The scopes required by this application. | ||
// #docregion Initialize | ||
// #docregion CheckAuthorization | ||
const List<String> scopes = <String>[ | ||
'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.
(Mentioned this in the readme above, but again, this 'email'
scope might not be needed.)
await user.authorizationClient.authorizationHeaders(scopes); | ||
if (headers == null) { | ||
setState(() { | ||
_contactText = 'Failed to construct authorization 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.
This should be _errorMessage
, probably?
@@ -27,134 +24,129 @@ abstract class GoogleSignInPlatform extends PlatformInterface { | |||
|
|||
static final Object _token = Object(); |
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 use this opportunity to clean up this _token
and the set instance
setter tricks and make the abstract class GoogleSignInPlatform
an abstract base class
? Should this be a separate technical debt cleanup issue? (Are there any downsides to this that I'm not seeing? :S)
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 did seriously consider doing this, since it can only be done during a breaking change to the platform interface.
I may still tackle it here for that reason, but the downside is that I would have to replace the Mockito-generated mocks with manual mocks, and rewrite the tests accordingly.
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.
the downside is that I would have to replace the Mockito-generated mocks with manual mocks, and rewrite the tests accordingly.
Ah yes...
Why is this change considered breaking, though? Is it just in case people are using "implements" for their own federated versions of the plugin, or mocks? If you're using the class "as intended" (with extends), your code shouldn't need to change by us moving it to base
class. Would it?
One upside of the extra work, should you want to tackle it, is that the mocks that you create here can be distributed as a testing
library from the package, so those users who were creating their own mocks can use officially maintained mock classes that we vend. Similar to: https://pub.dev/documentation/http/1.4.0/testing
.thenAnswer((_) => Future<GoogleSignInUserData>.value(someUser)); | ||
group('clientAuthorizationTokensForScopes', () { | ||
const String someAccessToken = '50m3_4cc35_70k3n'; | ||
const List<String> scopes = <String>['scope1', 'scope2']; |
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.
Would it make sense to retain the 'asserts no scopes have any spaces'
functionality and test here?
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 was confused by the fact that the web implementation was asserting this in the first place. Is there some web-specific extra requirement on scopes? Is having spaces that shouldn't be there different from just typoing the scope?
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 code comes from the first version of the plugin, and this is why I added this, when asked about "why not put this in the app-facing package":
flutter/plugins#2280 (comment)
[Spaces are] a problem here because we use the space as a "join" character to pass the scopes to JS. In the mobile versions, the List is preserved all the way through.
I just checked the API docs again, here: https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow#redirecting and scope
is still "A space-delimited list of scopes that identify the resources that your application could access on the user's behalf."
IMO we should still assert (and test the assert) that scopes don't contain spaces, but it doesn't look like a super big source of problems.
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.
Interesting; I'll put it back with a comment explaining it. I wonder if the mobile SDKs fail under the hood, or if they escape the spaces.
@@ -52,7 +52,6 @@ void main() { | |||
expect(user.id, expectedPersonId); | |||
expect(user.displayName, expectedPersonName); | |||
expect(user.photoUrl, expectedPersonPhoto); | |||
expect(user.idToken, isNull); |
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 thought we no longer did the People API fallback on the web? Maybe this whole test can be removed as well?
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 think I did indeed just forget to delete these files.
@@ -12,7 +12,6 @@ import 'package:web/web.dart' as web; | |||
|
|||
import 'button_configuration.dart' | |||
show GSIButtonConfiguration, convertButtonConfiguration; | |||
import 'people.dart' as people; |
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 think this people.dart
file is now dead code, you can maybe delete it and its tests?
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.
Reviewed the Android implementation!
Future<PlatformGoogleIdTokenCredential?> _authenticate({ | ||
required bool filterToAuthorized, | ||
required bool autoSelectEnabled, | ||
required bool useButtonFlow, |
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.
So if useButtonFlow
is true, then basically the other options are ignored? Would it be helpful to warn users here about that?
null, | ||
0, | ||
0, | ||
0, | ||
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.
Nit: can you leave a comment for each of these params about what they are since it's not obvious?
} | ||
Future<void> disconnect(DisconnectParams params) async { | ||
// TODO(stuartmorgan): Implement this once Credential Manager adds the | ||
// necessary API (or temporarily implement it with the deprecated SDK). |
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.
Should we file an issue to track this since this is sorta a regression?
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.
What do you think about adding a test for attachToActivity
since it seems that the call to addActivityResultListener
is critical? Maybe disposeActivity
for the same reason.
ResultCompat.asCompatCallback( | ||
reply -> { | ||
// This is never called, since this test doesn't trigger the getCredentialsAsync callback. | ||
return 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.
Nit: could throw an exception to ensure it's not called.
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.
What do you think about adding a test for the various parameter options for authorize
?
@@ -13,3 +13,28 @@ should add it to your `pubspec.yaml` as usual. | |||
|
|||
[1]: https://pub.dev/packages/google_sign_in | |||
[2]: https://flutter.dev/to/endorsed-federated-plugin | |||
|
|||
## Integration |
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 consider linking to the credentials
docs on this? https://developer.android.com/identity/sign-in/credential-manager-siwg#set-google for the non-firebase option? If it's more confusing than helpful because some steps don't apply, maybe not though.
This is a full overhaul of the
google_sign_in
API, with breaking changes for all component packages—including the platform interface. The usual model of adding the new approach while keeping the old one is not viable here, as the underlying SDKs have changed significantly since the original API was designed. Web already had some only-partially-compatible shims for this reason, and Android would have had to do something similar; see flutter/flutter#119300 and flutter/flutter#154205, and the design doc for more background.google_sign_in
to reflect changes in underlying SDKs flutter#119300play-services-auth
flutter#150365signIn
method. flutter#137727canAccessScopes
on mobile flutter#124206Pre-Review Checklist
[shared_preferences]
pubspec.yaml
with an appropriate new version according to the pub versioning philosophy, or I have commented below to indicate which version change exemption this PR falls under1.CHANGELOG.md
to add a description of the change, following repository CHANGELOG style, or I have commented below to indicate which CHANGELOG exemption this PR falls under1.///
).Footnotes
Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. ↩ ↩2 ↩3