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

Commit 1be7a6a

Browse files
authored
[google_sign_in] Implement Dart-based configuration and serverClientId (#6034)
1 parent c8c6d9d commit 1be7a6a

File tree

22 files changed

+609
-83
lines changed

22 files changed

+609
-83
lines changed

packages/google_sign_in/google_sign_in_android/CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
## 6.0.0
2+
3+
* Deprecates `clientId` and adds support for `serverClientId` instead.
4+
Historically `clientId` was interpreted as `serverClientId`, but only on Android. On
5+
other platforms it was interpreted as the OAuth `clientId` of the app. For backwards-compatibility
6+
`clientId` will still be used as a server client ID if `serverClientId` is not provided.
7+
* **BREAKING CHANGES**:
8+
* Adds `serverClientId` parameter to `IDelegate.init` (Java).
9+
110
## 5.2.8
211

312
* Suppresses `deprecation` warnings (for using Android V1 embedding).
@@ -13,4 +22,4 @@
1322

1423
## 5.2.5
1524

16-
* Splits from `video_player` as a federated implementation.
25+
* 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: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import android.app.Activity;
99
import android.content.Context;
1010
import android.content.Intent;
11+
import android.util.Log;
1112
import androidx.annotation.NonNull;
1213
import androidx.annotation.VisibleForTesting;
1314
import com.google.android.gms.auth.GoogleAuthUtil;
@@ -138,7 +139,9 @@ public void onMethodCall(MethodCall call, Result result) {
138139
List<String> requestedScopes = call.argument("scopes");
139140
String hostedDomain = call.argument("hostedDomain");
140141
String clientId = call.argument("clientId");
141-
delegate.init(result, signInOption, requestedScopes, hostedDomain, clientId);
142+
String serverClientId = call.argument("serverClientId");
143+
delegate.init(
144+
result, signInOption, requestedScopes, hostedDomain, clientId, serverClientId);
142145
break;
143146

144147
case METHOD_SIGN_IN_SILENTLY:
@@ -194,7 +197,8 @@ public void init(
194197
String signInOption,
195198
List<String> requestedScopes,
196199
String hostedDomain,
197-
String clientId);
200+
String clientId,
201+
String serverClientId);
198202

199203
/**
200204
* Returns the account information for the user who is signed in to this app. If no user is
@@ -321,7 +325,8 @@ public void init(
321325
String signInOption,
322326
List<String> requestedScopes,
323327
String hostedDomain,
324-
String clientId) {
328+
String clientId,
329+
String serverClientId) {
325330
try {
326331
GoogleSignInOptions.Builder optionsBuilder;
327332

@@ -338,20 +343,38 @@ public void init(
338343
throw new IllegalStateException("Unknown signInOption");
339344
}
340345

341-
// Only requests a clientId if google-services.json was present and parsed
342-
// by the google-services Gradle script.
343-
// TODO(jackson): Perhaps we should provide a mechanism to override this
344-
// behavior.
345-
int clientIdIdentifier =
346-
context
347-
.getResources()
348-
.getIdentifier("default_web_client_id", "string", context.getPackageName());
346+
// The clientId parameter is not supported on Android.
347+
// Android apps are identified by their package name and the SHA-1 of their signing key.
348+
// https://developers.google.com/android/guides/client-auth
349+
// https://developers.google.com/identity/sign-in/android/start#configure-a-google-api-project
349350
if (!Strings.isNullOrEmpty(clientId)) {
350-
optionsBuilder.requestIdToken(clientId);
351-
optionsBuilder.requestServerAuthCode(clientId);
352-
} else if (clientIdIdentifier != 0) {
353-
optionsBuilder.requestIdToken(context.getString(clientIdIdentifier));
354-
optionsBuilder.requestServerAuthCode(context.getString(clientIdIdentifier));
351+
if (Strings.isNullOrEmpty(serverClientId)) {
352+
Log.w(
353+
"google_sing_in",
354+
"clientId is not supported on Android and is interpreted as serverClientId."
355+
+ "Use serverClientId instead to suppress this warning.");
356+
serverClientId = clientId;
357+
} else {
358+
Log.w("google_sing_in", "clientId is not supported on Android and is ignored.");
359+
}
360+
}
361+
362+
if (Strings.isNullOrEmpty(serverClientId)) {
363+
// Only requests a clientId if google-services.json was present and parsed
364+
// by the google-services Gradle script.
365+
// TODO(jackson): Perhaps we should provide a mechanism to override this
366+
// behavior.
367+
int webClientIdIdentifier =
368+
context
369+
.getResources()
370+
.getIdentifier("default_web_client_id", "string", context.getPackageName());
371+
if (webClientIdIdentifier != 0) {
372+
serverClientId = context.getString(webClientIdIdentifier);
373+
}
374+
}
375+
if (!Strings.isNullOrEmpty(serverClientId)) {
376+
optionsBuilder.requestIdToken(serverClientId);
377+
optionsBuilder.requestServerAuthCode(serverClientId);
355378
}
356379
for (String scope : requestedScopes) {
357380
optionsBuilder.requestScopes(new Scope(scope));
@@ -361,7 +384,7 @@ public void init(
361384
}
362385

363386
this.requestedScopes = requestedScopes;
364-
signInClient = GoogleSignIn.getClient(context, optionsBuilder.build());
387+
signInClient = googleSignInWrapper.getClient(context, optionsBuilder.build());
365388
result.success(null);
366389
} catch (Exception e) {
367390
result.error(ERROR_REASON_EXCEPTION, e.getMessage(), null);

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import android.content.Context;
99
import com.google.android.gms.auth.api.signin.GoogleSignIn;
1010
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
11+
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
12+
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
1113
import com.google.android.gms.common.api.Scope;
1214

1315
/**
@@ -21,6 +23,10 @@
2123
*/
2224
public class GoogleSignInWrapper {
2325

26+
GoogleSignInClient getClient(Context context, GoogleSignInOptions options) {
27+
return GoogleSignIn.getClient(context, options);
28+
}
29+
2430
GoogleSignInAccount getLastSignedInAccount(Context context) {
2531
return GoogleSignIn.getLastSignedInAccount(context);
2632
}

packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInTest.java

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package io.flutter.plugins.googlesignin;
66

7+
import static org.mockito.ArgumentMatchers.any;
78
import static org.mockito.Mockito.mock;
89
import static org.mockito.Mockito.times;
910
import static org.mockito.Mockito.verify;
@@ -12,7 +13,10 @@
1213
import android.app.Activity;
1314
import android.content.Context;
1415
import android.content.Intent;
16+
import android.content.res.Resources;
1517
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
18+
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
19+
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
1620
import com.google.android.gms.common.api.Scope;
1721
import io.flutter.plugin.common.BinaryMessenger;
1822
import io.flutter.plugin.common.MethodCall;
@@ -21,6 +25,7 @@
2125
import java.util.Collections;
2226
import java.util.HashMap;
2327
import java.util.List;
28+
import org.junit.Assert;
2429
import org.junit.Before;
2530
import org.junit.Test;
2631
import org.mockito.ArgumentCaptor;
@@ -30,12 +35,14 @@
3035

3136
public class GoogleSignInTest {
3237
@Mock Context mockContext;
38+
@Mock Resources mockResources;
3339
@Mock Activity mockActivity;
3440
@Mock PluginRegistry.Registrar mockRegistrar;
3541
@Mock BinaryMessenger mockMessenger;
3642
@Spy MethodChannel.Result result;
3743
@Mock GoogleSignInWrapper mockGoogleSignIn;
3844
@Mock GoogleSignInAccount account;
45+
@Mock GoogleSignInClient mockClient;
3946
private GoogleSignInPlugin plugin;
4047

4148
@Before
@@ -44,6 +51,7 @@ public void setUp() {
4451
when(mockRegistrar.messenger()).thenReturn(mockMessenger);
4552
when(mockRegistrar.context()).thenReturn(mockContext);
4653
when(mockRegistrar.activity()).thenReturn(mockActivity);
54+
when(mockContext.getResources()).thenReturn(mockResources);
4755
plugin = new GoogleSignInPlugin();
4856
plugin.initInstance(mockRegistrar.messenger(), mockRegistrar.context(), mockGoogleSignIn);
4957
plugin.setUpRegistrar(mockRegistrar);
@@ -195,4 +203,68 @@ public void signInThrowsWithoutActivity() {
195203

196204
plugin.onMethodCall(new MethodCall("signIn", null), null);
197205
}
206+
207+
@Test
208+
public void init_LoadsServerClientIdFromResources() {
209+
final String packageName = "fakePackageName";
210+
final String serverClientId = "fakeServerClientId";
211+
final int resourceId = 1;
212+
MethodCall methodCall = buildInitMethodCall(null, null);
213+
when(mockContext.getPackageName()).thenReturn(packageName);
214+
when(mockResources.getIdentifier("default_web_client_id", "string", packageName))
215+
.thenReturn(resourceId);
216+
when(mockContext.getString(resourceId)).thenReturn(serverClientId);
217+
initAndAssertServerClientId(methodCall, serverClientId);
218+
}
219+
220+
@Test
221+
public void init_InterpretsClientIdAsServerClientId() {
222+
final String clientId = "fakeClientId";
223+
MethodCall methodCall = buildInitMethodCall(clientId, null);
224+
initAndAssertServerClientId(methodCall, clientId);
225+
}
226+
227+
@Test
228+
public void init_ForwardsServerClientId() {
229+
final String serverClientId = "fakeServerClientId";
230+
MethodCall methodCall = buildInitMethodCall(null, serverClientId);
231+
initAndAssertServerClientId(methodCall, serverClientId);
232+
}
233+
234+
@Test
235+
public void init_IgnoresClientIdIfServerClientIdIsProvided() {
236+
final String clientId = "fakeClientId";
237+
final String serverClientId = "fakeServerClientId";
238+
MethodCall methodCall = buildInitMethodCall(clientId, serverClientId);
239+
initAndAssertServerClientId(methodCall, serverClientId);
240+
}
241+
242+
public void initAndAssertServerClientId(MethodCall methodCall, String serverClientId) {
243+
ArgumentCaptor<GoogleSignInOptions> optionsCaptor =
244+
ArgumentCaptor.forClass(GoogleSignInOptions.class);
245+
when(mockGoogleSignIn.getClient(any(Context.class), optionsCaptor.capture()))
246+
.thenReturn(mockClient);
247+
plugin.onMethodCall(methodCall, result);
248+
verify(result).success(null);
249+
Assert.assertEquals(serverClientId, optionsCaptor.getValue().getServerClientId());
250+
}
251+
252+
private static MethodCall buildInitMethodCall(String clientId, String serverClientId) {
253+
return buildInitMethodCall(
254+
"SignInOption.standard", Collections.<String>emptyList(), clientId, serverClientId);
255+
}
256+
257+
private static MethodCall buildInitMethodCall(
258+
String signInOption, List<String> scopes, String clientId, String serverClientId) {
259+
HashMap<String, Object> arguments = new HashMap<>();
260+
arguments.put("signInOption", signInOption);
261+
arguments.put("scopes", scopes);
262+
if (clientId != null) {
263+
arguments.put("clientId", clientId);
264+
}
265+
if (serverClientId != null) {
266+
arguments.put("serverClientId", serverClientId);
267+
}
268+
return new MethodCall("init", arguments);
269+
}
198270
}

packages/google_sign_in/google_sign_in_android/example/lib/main.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,16 @@ class SignInDemoState extends State<SignInDemo> {
4141
}
4242

4343
Future<void> _ensureInitialized() {
44-
return _initialization ??= GoogleSignInPlatform.instance.init(
44+
return _initialization ??=
45+
GoogleSignInPlatform.instance.initWithParams(const SignInInitParameters(
4546
scopes: <String>[
4647
'email',
4748
'https://www.googleapis.com/auth/contacts.readonly',
4849
],
49-
)..catchError((dynamic _) {
50-
_initialization = null;
51-
});
50+
))
51+
..catchError((dynamic _) {
52+
_initialization = null;
53+
});
5254
}
5355

5456
void _setUser(GoogleSignInUserData? user) {

packages/google_sign_in/google_sign_in_android/example/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ dependencies:
1616
# The example app is bundled with the plugin so we use a path dependency on
1717
# the parent directory to use the current plugin's version.
1818
path: ../
19-
google_sign_in_platform_interface: ^2.1.0
19+
google_sign_in_platform_interface: ^2.2.0
2020
http: ^0.13.0
2121

2222
dev_dependencies:

packages/google_sign_in/google_sign_in_android/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: google_sign_in_android
22
description: Android implementation of the google_sign_in plugin.
33
repository: https://github.com/flutter/plugins/tree/main/packages/google_sign_in/google_sign_in_android
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22
5-
version: 5.2.8
5+
version: 6.0.0
66

77
environment:
88
sdk: ">=2.14.0 <3.0.0"
@@ -20,7 +20,7 @@ flutter:
2020
dependencies:
2121
flutter:
2222
sdk: flutter
23-
google_sign_in_platform_interface: ^2.1.0
23+
google_sign_in_platform_interface: ^2.2.0
2424

2525
dev_dependencies:
2626
flutter_driver:

packages/google_sign_in/google_sign_in_ios/CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 5.4.0
2+
3+
* Adds support for `serverClientId` configuration option.
4+
* Makes `Google-Services.info` file optional.
5+
16
## 5.3.1
27

38
* Suppresses warnings for pre-iOS-13 codepaths.
@@ -17,4 +22,4 @@
1722

1823
## 5.2.5
1924

20-
* Splits from `video_player` as a federated implementation.
25+
* Splits from `google_sign_in` as a federated implementation.

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

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,25 @@ - (void)testDisconnectIgnoresError {
9696

9797
#pragma mark - Init
9898

99+
- (void)testInitNoClientIdError {
100+
// Init plugin without GoogleService-Info.plist.
101+
self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.mockSignIn
102+
withGoogleServiceProperties:nil];
103+
104+
// init call does not provide a clientId.
105+
FlutterMethodCall *initMethodCall = [FlutterMethodCall methodCallWithMethodName:@"init"
106+
arguments:@{}];
107+
108+
XCTestExpectation *initExpectation =
109+
[self expectationWithDescription:@"expect result returns true"];
110+
[self.plugin handleMethodCall:initMethodCall
111+
result:^(FlutterError *result) {
112+
XCTAssertEqualObjects(result.code, @"missing-config");
113+
[initExpectation fulfill];
114+
}];
115+
[self waitForExpectationsWithTimeout:5.0 handler:nil];
116+
}
117+
99118
- (void)testInitGoogleServiceInfoPlist {
100119
FlutterMethodCall *initMethodCall =
101120
[FlutterMethodCall methodCallWithMethodName:@"init"
@@ -133,6 +152,10 @@ - (void)testInitGoogleServiceInfoPlist {
133152
}
134153

135154
- (void)testInitDynamicClientIdNullDomain {
155+
// Init plugin without GoogleService-Info.plist.
156+
self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.mockSignIn
157+
withGoogleServiceProperties:nil];
158+
136159
FlutterMethodCall *initMethodCall = [FlutterMethodCall
137160
methodCallWithMethodName:@"init"
138161
arguments:@{@"hostedDomain" : [NSNull null], @"clientId" : @"mockClientId"}];
@@ -163,6 +186,40 @@ - (void)testInitDynamicClientIdNullDomain {
163186
callback:OCMOCK_ANY]);
164187
}
165188

189+
- (void)testInitDynamicServerClientIdNullDomain {
190+
FlutterMethodCall *initMethodCall =
191+
[FlutterMethodCall methodCallWithMethodName:@"init"
192+
arguments:@{
193+
@"hostedDomain" : [NSNull null],
194+
@"serverClientId" : @"mockServerClientId"
195+
}];
196+
197+
XCTestExpectation *initExpectation =
198+
[self expectationWithDescription:@"expect result returns true"];
199+
[self.plugin handleMethodCall:initMethodCall
200+
result:^(id result) {
201+
XCTAssertNil(result);
202+
[initExpectation fulfill];
203+
}];
204+
[self waitForExpectationsWithTimeout:5.0 handler:nil];
205+
206+
// Initialization values used in the next sign in request.
207+
FlutterMethodCall *signInMethodCall = [FlutterMethodCall methodCallWithMethodName:@"signIn"
208+
arguments:nil];
209+
[self.plugin handleMethodCall:signInMethodCall
210+
result:^(id r){
211+
}];
212+
OCMVerify([self.mockSignIn
213+
signInWithConfiguration:[OCMArg checkWithBlock:^BOOL(GIDConfiguration *configuration) {
214+
return configuration.hostedDomain == nil &&
215+
[configuration.serverClientID isEqualToString:@"mockServerClientId"];
216+
}]
217+
presentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]]
218+
hint:nil
219+
additionalScopes:OCMOCK_ANY
220+
callback:OCMOCK_ANY]);
221+
}
222+
166223
#pragma mark - Is signed in
167224

168225
- (void)testIsNotSignedIn {

0 commit comments

Comments
 (0)