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

Commit b9134b1

Browse files
committed
[google_sign_in] Support Dart-only configuration
1 parent d945080 commit b9134b1

File tree

22 files changed

+273
-38
lines changed

22 files changed

+273
-38
lines changed

packages/google_sign_in/google_sign_in/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## Next
2+
3+
* Add support for configuring `serverClientId` through `GoogleSignIn` constructor.
4+
15
## 5.3.0
26

37
* Moves Android and iOS implementations to federated packages.

packages/google_sign_in/google_sign_in/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,20 @@ This plugin requires iOS 9.0 or higher.
6565
<!-- End of the Google Sign-in Section -->
6666
```
6767

68+
Instead of adding `GoogleService-Info.plist` to your Xcode project, you can configure your app in
69+
Dart code. In this case, skip steps 3-6 and pass `clientId` and `serverClientId` to the
70+
`GoogleSignIn` constructor:
71+
72+
```dart
73+
GoogleSignIn _googleSignIn = GoogleSignIn(
74+
...
75+
// Copied from GoogleService-Info.plist key CLIENT_ID
76+
clientId: '861823949799-vc35cprkp249096uujjn0vvnmcvjppkn.apps.googleusercontent.com',
77+
// Copied from GoogleService-Info.plist key SERVER_CLIENT_ID
78+
serverClientId: 'YOUR_SERVER_CLIENT_ID',
79+
);
80+
```
81+
6882
#### iOS additional requirement
6983

7084
Note that according to

packages/google_sign_in/google_sign_in/lib/google_sign_in.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ class GoogleSignIn {
183183
this.scopes = const <String>[],
184184
this.hostedDomain,
185185
this.clientId,
186+
this.serverClientId,
186187
});
187188

188189
/// Factory for creating default sign in user experience.
@@ -228,9 +229,14 @@ class GoogleSignIn {
228229
/// Domain to restrict sign-in to.
229230
final String? hostedDomain;
230231

231-
/// Client ID being used to connect to google sign-in. Only supported on web.
232+
/// Client ID being used to connect to google sign-in. Only supported on Web
233+
/// and iOS.
232234
final String? clientId;
233235

236+
/// Client ID of the backend server to which ID tokens will be sent. Only
237+
/// supported on Android and iOS.
238+
final String? serverClientId;
239+
234240
final StreamController<GoogleSignInAccount?> _currentUserController =
235241
StreamController<GoogleSignInAccount?>.broadcast();
236242

@@ -260,11 +266,12 @@ class GoogleSignIn {
260266
}
261267

262268
Future<void> _ensureInitialized() {
263-
return _initialization ??= GoogleSignInPlatform.instance.init(
269+
return _initialization ??= GoogleSignInPlatform.instance.init2(
264270
signInOption: signInOption,
265271
scopes: scopes,
266272
hostedDomain: hostedDomain,
267273
clientId: clientId,
274+
serverClientId: serverClientId,
268275
)..catchError((dynamic _) {
269276
// Invalidate initialization if it errors out.
270277
_initialization = null;

packages/google_sign_in/google_sign_in/pubspec.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,14 @@ false_secrets:
4444
- /example/ios/RunnerTests/GoogleSignInTests.m
4545
- /example/lib/main.dart
4646
- /example/web/index.html
47+
48+
# FOR TESTING ONLY. DO NOT MERGE.
49+
dependency_overrides:
50+
google_sign_in_android:
51+
path: ../../google_sign_in/google_sign_in_android
52+
google_sign_in_ios:
53+
path: ../../google_sign_in/google_sign_in_ios
54+
google_sign_in_platform_interface:
55+
path: ../../google_sign_in/google_sign_in_platform_interface
56+
google_sign_in_web:
57+
path: ../../google_sign_in/google_sign_in_web

packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,27 @@ void main() {
9696
'scopes': <String>[],
9797
'hostedDomain': null,
9898
'clientId': fakeClientId,
99+
'serverClientId': null,
100+
}),
101+
isMethodCall('signIn', arguments: null),
102+
],
103+
);
104+
});
105+
106+
test('signIn prioritize serverClientId parameter when available', () async {
107+
const String fakeServerClientId = 'fakeServerClientId';
108+
googleSignIn = GoogleSignIn(serverClientId: fakeServerClientId);
109+
await googleSignIn.signIn();
110+
expect(googleSignIn.currentUser, isNotNull);
111+
expect(
112+
log,
113+
<Matcher>[
114+
isMethodCall('init', arguments: <String, dynamic>{
115+
'signInOption': 'SignInOption.standard',
116+
'scopes': <String>[],
117+
'hostedDomain': null,
118+
'clientId': null,
119+
'serverClientId': fakeServerClientId,
99120
}),
100121
isMethodCall('signIn', arguments: null),
101122
],
@@ -431,5 +452,6 @@ Matcher _isSignInMethodCall({String signInOption = 'SignInOption.standard'}) {
431452
'scopes': <String>[],
432453
'hostedDomain': null,
433454
'clientId': null,
455+
'serverClientId': null,
434456
});
435457
}
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## Next
2+
3+
* **BREAKING CHANGES**:
4+
* Add `serverClientId` parameter to `IDelegate.init`.
5+
* Make `clientId` unsupported and support `serverClientId` instead.
6+
Historically `clientId` was interpreted as `serverClientId`, but only on Android. On
7+
other platforms it was interpreted as the OAuth `clientId` of the app.
8+
19
## 5.2.5
210

3-
* Splits from `video_player` as a federated implementation.
11+
* Splits from `google_sign_in` as a federated implementation.

packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,9 @@ public void onMethodCall(MethodCall call, Result result) {
137137
List<String> requestedScopes = call.argument("scopes");
138138
String hostedDomain = call.argument("hostedDomain");
139139
String clientId = call.argument("clientId");
140-
delegate.init(result, signInOption, requestedScopes, hostedDomain, clientId);
140+
String serverClientId = call.argument("serverClientId");
141+
delegate.init(
142+
result, signInOption, requestedScopes, hostedDomain, clientId, serverClientId);
141143
break;
142144

143145
case METHOD_SIGN_IN_SILENTLY:
@@ -193,7 +195,8 @@ public void init(
193195
String signInOption,
194196
List<String> requestedScopes,
195197
String hostedDomain,
196-
String clientId);
198+
String clientId,
199+
String serverClientId);
197200

198201
/**
199202
* Returns the account information for the user who is signed in to this app. If no user is
@@ -318,7 +321,8 @@ public void init(
318321
String signInOption,
319322
List<String> requestedScopes,
320323
String hostedDomain,
321-
String clientId) {
324+
String clientId,
325+
String serverClientId) {
322326
try {
323327
GoogleSignInOptions.Builder optionsBuilder;
324328

@@ -335,20 +339,28 @@ public void init(
335339
throw new IllegalStateException("Unknown signInOption");
336340
}
337341

338-
// Only requests a clientId if google-services.json was present and parsed
339-
// by the google-services Gradle script.
340-
// TODO(jackson): Perhaps we should provide a mechanism to override this
341-
// behavior.
342-
int clientIdIdentifier =
343-
context
344-
.getResources()
345-
.getIdentifier("default_web_client_id", "string", context.getPackageName());
346342
if (!Strings.isNullOrEmpty(clientId)) {
347-
optionsBuilder.requestIdToken(clientId);
348-
optionsBuilder.requestServerAuthCode(clientId);
349-
} else if (clientIdIdentifier != 0) {
350-
optionsBuilder.requestIdToken(context.getString(clientIdIdentifier));
351-
optionsBuilder.requestServerAuthCode(context.getString(clientIdIdentifier));
343+
throw new IllegalArgumentException(
344+
"clientId is not supported on Android. "
345+
+ "To set a server client ID use serverClientId instead.");
346+
}
347+
348+
if (Strings.isNullOrEmpty(serverClientId)) {
349+
// Only requests a clientId if google-services.json was present and parsed
350+
// by the google-services Gradle script.
351+
// TODO(jackson): Perhaps we should provide a mechanism to override this
352+
// behavior.
353+
int webClientIdIdentifier =
354+
context
355+
.getResources()
356+
.getIdentifier("default_web_client_id", "string", context.getPackageName());
357+
if (webClientIdIdentifier != 0) {
358+
serverClientId = context.getString(webClientIdIdentifier);
359+
}
360+
}
361+
if (!Strings.isNullOrEmpty(serverClientId)) {
362+
optionsBuilder.requestIdToken(serverClientId);
363+
optionsBuilder.requestServerAuthCode(serverClientId);
352364
}
353365
for (String scope : requestedScopes) {
354366
optionsBuilder.requestScopes(new Scope(scope));

packages/google_sign_in/google_sign_in_android/example/pubspec.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,10 @@ dev_dependencies:
2828

2929
flutter:
3030
uses-material-design: true
31+
32+
# FOR TESTING ONLY. DO NOT MERGE.
33+
dependency_overrides:
34+
google_sign_in_android:
35+
path: ../../../google_sign_in/google_sign_in_android
36+
google_sign_in_platform_interface:
37+
path: ../../../google_sign_in/google_sign_in_platform_interface

packages/google_sign_in/google_sign_in_android/pubspec.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,8 @@ dev_dependencies:
3333
false_secrets:
3434
- /example/android/app/google-services.json
3535
- /example/lib/main.dart
36+
37+
# FOR TESTING ONLY. DO NOT MERGE.
38+
dependency_overrides:
39+
google_sign_in_platform_interface:
40+
path: ../../google_sign_in/google_sign_in_platform_interface
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## Next
2+
3+
* Remove the requirement for a `GoogleService-Info` file.
4+
* Add support for `serverClientId` configuration option.
5+
16
## 5.2.5
27

3-
* Splits from `video_player` as a federated implementation.
8+
* Splits from `google_sign_in` as a federated implementation.

packages/google_sign_in/google_sign_in_ios/example/ios/RunnerTests/GoogleSignInTests.m

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,20 @@ - (void)testInitDynamicClientId {
153153
OCMVerify([self.mockSignIn setClientID:@"mockClientId"]);
154154
}
155155

156+
- (void)testInitDynamicServerClientId {
157+
FlutterMethodCall *methodCall =
158+
[FlutterMethodCall methodCallWithMethodName:@"init"
159+
arguments:@{@"serverClientId" : @"mockServerClientId"}];
160+
161+
XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"];
162+
[self.plugin handleMethodCall:methodCall
163+
result:^(id r) {
164+
[expectation fulfill];
165+
}];
166+
[self waitForExpectationsWithTimeout:5.0 handler:nil];
167+
OCMVerify([self.mockSignIn setServerClientID:@"mockServerClientId"]);
168+
}
169+
156170
#pragma mark - Is signed in
157171

158172
- (void)testIsNotSignedIn {

packages/google_sign_in/google_sign_in_ios/example/pubspec.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,10 @@ dev_dependencies:
2727

2828
flutter:
2929
uses-material-design: true
30+
31+
# FOR TESTING ONLY. DO NOT MERGE.
32+
dependency_overrides:
33+
google_sign_in_ios:
34+
path: ../../../google_sign_in/google_sign_in_ios
35+
google_sign_in_platform_interface:
36+
path: ../../../google_sign_in/google_sign_in_platform_interface

packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.m

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -84,32 +84,45 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result
8484
message:@"Games sign in is not supported on iOS"
8585
details:nil]);
8686
} else {
87+
NSMutableDictionary<NSString *, NSString *> *plist = nil;
8788
NSString *path = [[NSBundle mainBundle] pathForResource:@"GoogleService-Info"
8889
ofType:@"plist"];
8990
if (path) {
90-
NSMutableDictionary<NSString *, NSString *> *plist =
91-
[[NSMutableDictionary alloc] initWithContentsOfFile:path];
92-
BOOL hasDynamicClientId = [call.arguments[@"clientId"] isKindOfClass:[NSString class]];
93-
94-
if (hasDynamicClientId) {
95-
self.signIn.clientID = call.arguments[@"clientId"];
96-
} else {
97-
self.signIn.clientID = plist[kClientIdKey];
91+
plist = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
92+
}
93+
94+
BOOL hasDynamicClientId = [call.arguments[@"clientId"] isKindOfClass:[NSString class]];
95+
if (hasDynamicClientId) {
96+
self.signIn.clientID = call.arguments[@"clientId"];
97+
} else {
98+
if (!plist) {
99+
result([FlutterError errorWithCode:@"missing-config"
100+
message:@"GoogleService-Info.plist file not found"
101+
details:nil]);
102+
return;
98103
}
104+
self.signIn.clientID = plist[kClientIdKey];
105+
}
99106

107+
BOOL hasDynamicServerClientId =
108+
[call.arguments[@"serverClientId"] isKindOfClass:[NSString class]];
109+
if (hasDynamicServerClientId) {
110+
self.signIn.serverClientID = call.arguments[@"serverClientId"];
111+
} else if (plist) {
100112
self.signIn.serverClientID = plist[kServerClientIdKey];
101-
self.signIn.scopes = call.arguments[@"scopes"];
102-
if (call.arguments[@"hostedDomain"] == [NSNull null]) {
103-
self.signIn.hostedDomain = nil;
104-
} else {
105-
self.signIn.hostedDomain = call.arguments[@"hostedDomain"];
106-
}
107-
result(nil);
108113
} else {
109-
result([FlutterError errorWithCode:@"missing-config"
110-
message:@"GoogleService-Info.plist file not found"
111-
details:nil]);
114+
self.signIn.serverClientID = nil;
112115
}
116+
117+
self.signIn.scopes = call.arguments[@"scopes"];
118+
119+
if (call.arguments[@"hostedDomain"] == [NSNull null]) {
120+
self.signIn.hostedDomain = nil;
121+
} else {
122+
self.signIn.hostedDomain = call.arguments[@"hostedDomain"];
123+
}
124+
125+
result(nil);
113126
}
114127
} else if ([call.method isEqualToString:@"signInSilently"]) {
115128
if ([self setAccountRequest:result]) {

packages/google_sign_in/google_sign_in_ios/pubspec.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,8 @@ false_secrets:
3333
- /example/ios/Runner/GoogleService-Info.plist
3434
- /example/ios/RunnerTests/GoogleSignInTests.m
3535
- /example/lib/main.dart
36+
37+
# FOR TESTING ONLY. DO NOT MERGE.
38+
dependency_overrides:
39+
google_sign_in_platform_interface:
40+
path: ../../google_sign_in/google_sign_in_platform_interface

packages/google_sign_in/google_sign_in_platform_interface/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## Next
2+
3+
* Add `GoogleSignInPlatform.init2` and deprecate `GoogleSignInPlatform.init` to support the
4+
`serverClientId` parameter.
5+
16
## 2.1.2
27

38
* Internal code cleanup for stricter analysis options.

packages/google_sign_in/google_sign_in_platform_interface/lib/google_sign_in_platform_interface.dart

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ abstract class GoogleSignInPlatform {
6666
/// Initializes the plugin. You must call this method before calling other
6767
/// methods.
6868
///
69+
/// This method is deprecated and will be removed in the future.
70+
/// Use [init2] instead.
71+
///
6972
/// The [hostedDomain] argument specifies a hosted domain restriction. By
7073
/// setting this, sign in will be restricted to accounts of the user in the
7174
/// specified domain. By default, the list of accounts will not be restricted.
@@ -89,6 +92,38 @@ abstract class GoogleSignInPlatform {
8992
throw UnimplementedError('init() has not been implemented.');
9093
}
9194

95+
/// Initializes the plugin. You must call this method before calling other
96+
/// methods.
97+
///
98+
/// The [hostedDomain] argument specifies a hosted domain restriction. By
99+
/// setting this, sign in will be restricted to accounts of the user in the
100+
/// specified domain. By default, the list of accounts will not be restricted.
101+
///
102+
/// The list of [scopes] are OAuth scope codes to request when signing in.
103+
/// These scope codes will determine the level of data access that is granted
104+
/// to your application by the user. The full list of available scopes can be
105+
/// found here: <https://developers.google.com/identity/protocols/googlescopes>
106+
///
107+
/// The [signInOption] determines the user experience. [SigninOption.games] is
108+
/// only supported on Android.
109+
///
110+
/// See:
111+
/// https://developers.google.com/identity/sign-in/web/reference#gapiauth2initparams
112+
Future<void> init2({
113+
List<String> scopes = const <String>[],
114+
SignInOption signInOption = SignInOption.standard,
115+
String? hostedDomain,
116+
String? clientId,
117+
String? serverClientId,
118+
}) async {
119+
return init(
120+
scopes: scopes,
121+
signInOption: signInOption,
122+
hostedDomain: hostedDomain,
123+
clientId: clientId,
124+
);
125+
}
126+
92127
/// Attempts to reuse pre-existing credentials to sign in again, without user interaction.
93128
Future<GoogleSignInUserData?> signInSilently() async {
94129
throw UnimplementedError('signInSilently() has not been implemented.');

0 commit comments

Comments
 (0)