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

Commit

Permalink
[google_sign_in] Implement Dart-based configuration and `serverClient…
Browse files Browse the repository at this point in the history
…Id` (#6034)
  • Loading branch information
blaugold authored Jul 7, 2022
1 parent c8c6d9d commit 1be7a6a
Show file tree
Hide file tree
Showing 22 changed files with 609 additions and 83 deletions.
11 changes: 10 additions & 1 deletion packages/google_sign_in/google_sign_in_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## 6.0.0

* Deprecates `clientId` and adds support for `serverClientId` instead.
Historically `clientId` was interpreted as `serverClientId`, but only on Android. On
other platforms it was interpreted as the OAuth `clientId` of the app. For backwards-compatibility
`clientId` will still be used as a server client ID if `serverClientId` is not provided.
* **BREAKING CHANGES**:
* Adds `serverClientId` parameter to `IDelegate.init` (Java).

## 5.2.8

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

## 5.2.5

* Splits from `video_player` as a federated implementation.
* Splits from `google_sign_in` as a federated implementation.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.google.android.gms.auth.GoogleAuthUtil;
Expand Down Expand Up @@ -138,7 +139,9 @@ public void onMethodCall(MethodCall call, Result result) {
List<String> requestedScopes = call.argument("scopes");
String hostedDomain = call.argument("hostedDomain");
String clientId = call.argument("clientId");
delegate.init(result, signInOption, requestedScopes, hostedDomain, clientId);
String serverClientId = call.argument("serverClientId");
delegate.init(
result, signInOption, requestedScopes, hostedDomain, clientId, serverClientId);
break;

case METHOD_SIGN_IN_SILENTLY:
Expand Down Expand Up @@ -194,7 +197,8 @@ public void init(
String signInOption,
List<String> requestedScopes,
String hostedDomain,
String clientId);
String clientId,
String serverClientId);

/**
* Returns the account information for the user who is signed in to this app. If no user is
Expand Down Expand Up @@ -321,7 +325,8 @@ public void init(
String signInOption,
List<String> requestedScopes,
String hostedDomain,
String clientId) {
String clientId,
String serverClientId) {
try {
GoogleSignInOptions.Builder optionsBuilder;

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

// Only requests a clientId if google-services.json was present and parsed
// by the google-services Gradle script.
// TODO(jackson): Perhaps we should provide a mechanism to override this
// behavior.
int clientIdIdentifier =
context
.getResources()
.getIdentifier("default_web_client_id", "string", context.getPackageName());
// The clientId parameter is not supported on Android.
// Android apps are identified by their package name and the SHA-1 of their signing key.
// https://developers.google.com/android/guides/client-auth
// https://developers.google.com/identity/sign-in/android/start#configure-a-google-api-project
if (!Strings.isNullOrEmpty(clientId)) {
optionsBuilder.requestIdToken(clientId);
optionsBuilder.requestServerAuthCode(clientId);
} else if (clientIdIdentifier != 0) {
optionsBuilder.requestIdToken(context.getString(clientIdIdentifier));
optionsBuilder.requestServerAuthCode(context.getString(clientIdIdentifier));
if (Strings.isNullOrEmpty(serverClientId)) {
Log.w(
"google_sing_in",
"clientId is not supported on Android and is interpreted as serverClientId."
+ "Use serverClientId instead to suppress this warning.");
serverClientId = clientId;
} else {
Log.w("google_sing_in", "clientId is not supported on Android and is ignored.");
}
}

if (Strings.isNullOrEmpty(serverClientId)) {
// Only requests a clientId if google-services.json was present and parsed
// by the google-services Gradle script.
// TODO(jackson): Perhaps we should provide a mechanism to override this
// behavior.
int webClientIdIdentifier =
context
.getResources()
.getIdentifier("default_web_client_id", "string", context.getPackageName());
if (webClientIdIdentifier != 0) {
serverClientId = context.getString(webClientIdIdentifier);
}
}
if (!Strings.isNullOrEmpty(serverClientId)) {
optionsBuilder.requestIdToken(serverClientId);
optionsBuilder.requestServerAuthCode(serverClientId);
}
for (String scope : requestedScopes) {
optionsBuilder.requestScopes(new Scope(scope));
Expand All @@ -361,7 +384,7 @@ public void init(
}

this.requestedScopes = requestedScopes;
signInClient = GoogleSignIn.getClient(context, optionsBuilder.build());
signInClient = googleSignInWrapper.getClient(context, optionsBuilder.build());
result.success(null);
} catch (Exception e) {
result.error(ERROR_REASON_EXCEPTION, e.getMessage(), null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import android.content.Context;
import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.common.api.Scope;

/**
Expand All @@ -21,6 +23,10 @@
*/
public class GoogleSignInWrapper {

GoogleSignInClient getClient(Context context, GoogleSignInOptions options) {
return GoogleSignIn.getClient(context, options);
}

GoogleSignInAccount getLastSignedInAccount(Context context) {
return GoogleSignIn.getLastSignedInAccount(context);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package io.flutter.plugins.googlesignin;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
Expand All @@ -12,7 +13,10 @@
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.common.api.Scope;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
Expand All @@ -21,6 +25,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
Expand All @@ -30,12 +35,14 @@

public class GoogleSignInTest {
@Mock Context mockContext;
@Mock Resources mockResources;
@Mock Activity mockActivity;
@Mock PluginRegistry.Registrar mockRegistrar;
@Mock BinaryMessenger mockMessenger;
@Spy MethodChannel.Result result;
@Mock GoogleSignInWrapper mockGoogleSignIn;
@Mock GoogleSignInAccount account;
@Mock GoogleSignInClient mockClient;
private GoogleSignInPlugin plugin;

@Before
Expand All @@ -44,6 +51,7 @@ public void setUp() {
when(mockRegistrar.messenger()).thenReturn(mockMessenger);
when(mockRegistrar.context()).thenReturn(mockContext);
when(mockRegistrar.activity()).thenReturn(mockActivity);
when(mockContext.getResources()).thenReturn(mockResources);
plugin = new GoogleSignInPlugin();
plugin.initInstance(mockRegistrar.messenger(), mockRegistrar.context(), mockGoogleSignIn);
plugin.setUpRegistrar(mockRegistrar);
Expand Down Expand Up @@ -195,4 +203,68 @@ public void signInThrowsWithoutActivity() {

plugin.onMethodCall(new MethodCall("signIn", null), null);
}

@Test
public void init_LoadsServerClientIdFromResources() {
final String packageName = "fakePackageName";
final String serverClientId = "fakeServerClientId";
final int resourceId = 1;
MethodCall methodCall = buildInitMethodCall(null, null);
when(mockContext.getPackageName()).thenReturn(packageName);
when(mockResources.getIdentifier("default_web_client_id", "string", packageName))
.thenReturn(resourceId);
when(mockContext.getString(resourceId)).thenReturn(serverClientId);
initAndAssertServerClientId(methodCall, serverClientId);
}

@Test
public void init_InterpretsClientIdAsServerClientId() {
final String clientId = "fakeClientId";
MethodCall methodCall = buildInitMethodCall(clientId, null);
initAndAssertServerClientId(methodCall, clientId);
}

@Test
public void init_ForwardsServerClientId() {
final String serverClientId = "fakeServerClientId";
MethodCall methodCall = buildInitMethodCall(null, serverClientId);
initAndAssertServerClientId(methodCall, serverClientId);
}

@Test
public void init_IgnoresClientIdIfServerClientIdIsProvided() {
final String clientId = "fakeClientId";
final String serverClientId = "fakeServerClientId";
MethodCall methodCall = buildInitMethodCall(clientId, serverClientId);
initAndAssertServerClientId(methodCall, serverClientId);
}

public void initAndAssertServerClientId(MethodCall methodCall, String serverClientId) {
ArgumentCaptor<GoogleSignInOptions> optionsCaptor =
ArgumentCaptor.forClass(GoogleSignInOptions.class);
when(mockGoogleSignIn.getClient(any(Context.class), optionsCaptor.capture()))
.thenReturn(mockClient);
plugin.onMethodCall(methodCall, result);
verify(result).success(null);
Assert.assertEquals(serverClientId, optionsCaptor.getValue().getServerClientId());
}

private static MethodCall buildInitMethodCall(String clientId, String serverClientId) {
return buildInitMethodCall(
"SignInOption.standard", Collections.<String>emptyList(), clientId, serverClientId);
}

private static MethodCall buildInitMethodCall(
String signInOption, List<String> scopes, String clientId, String serverClientId) {
HashMap<String, Object> arguments = new HashMap<>();
arguments.put("signInOption", signInOption);
arguments.put("scopes", scopes);
if (clientId != null) {
arguments.put("clientId", clientId);
}
if (serverClientId != null) {
arguments.put("serverClientId", serverClientId);
}
return new MethodCall("init", arguments);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,16 @@ class SignInDemoState extends State<SignInDemo> {
}

Future<void> _ensureInitialized() {
return _initialization ??= GoogleSignInPlatform.instance.init(
return _initialization ??=
GoogleSignInPlatform.instance.initWithParams(const SignInInitParameters(
scopes: <String>[
'email',
'https://www.googleapis.com/auth/contacts.readonly',
],
)..catchError((dynamic _) {
_initialization = null;
});
))
..catchError((dynamic _) {
_initialization = null;
});
}

void _setUser(GoogleSignInUserData? user) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ dependencies:
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
google_sign_in_platform_interface: ^2.1.0
google_sign_in_platform_interface: ^2.2.0
http: ^0.13.0

dev_dependencies:
Expand Down
4 changes: 2 additions & 2 deletions packages/google_sign_in/google_sign_in_android/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: google_sign_in_android
description: Android implementation of the google_sign_in plugin.
repository: https://github.com/flutter/plugins/tree/main/packages/google_sign_in/google_sign_in_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22
version: 5.2.8
version: 6.0.0

environment:
sdk: ">=2.14.0 <3.0.0"
Expand All @@ -20,7 +20,7 @@ flutter:
dependencies:
flutter:
sdk: flutter
google_sign_in_platform_interface: ^2.1.0
google_sign_in_platform_interface: ^2.2.0

dev_dependencies:
flutter_driver:
Expand Down
7 changes: 6 additions & 1 deletion packages/google_sign_in/google_sign_in_ios/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 5.4.0

* Adds support for `serverClientId` configuration option.
* Makes `Google-Services.info` file optional.

## 5.3.1

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

## 5.2.5

* Splits from `video_player` as a federated implementation.
* Splits from `google_sign_in` as a federated implementation.
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,25 @@ - (void)testDisconnectIgnoresError {

#pragma mark - Init

- (void)testInitNoClientIdError {
// Init plugin without GoogleService-Info.plist.
self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.mockSignIn
withGoogleServiceProperties:nil];

// init call does not provide a clientId.
FlutterMethodCall *initMethodCall = [FlutterMethodCall methodCallWithMethodName:@"init"
arguments:@{}];

XCTestExpectation *initExpectation =
[self expectationWithDescription:@"expect result returns true"];
[self.plugin handleMethodCall:initMethodCall
result:^(FlutterError *result) {
XCTAssertEqualObjects(result.code, @"missing-config");
[initExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}

- (void)testInitGoogleServiceInfoPlist {
FlutterMethodCall *initMethodCall =
[FlutterMethodCall methodCallWithMethodName:@"init"
Expand Down Expand Up @@ -133,6 +152,10 @@ - (void)testInitGoogleServiceInfoPlist {
}

- (void)testInitDynamicClientIdNullDomain {
// Init plugin without GoogleService-Info.plist.
self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.mockSignIn
withGoogleServiceProperties:nil];

FlutterMethodCall *initMethodCall = [FlutterMethodCall
methodCallWithMethodName:@"init"
arguments:@{@"hostedDomain" : [NSNull null], @"clientId" : @"mockClientId"}];
Expand Down Expand Up @@ -163,6 +186,40 @@ - (void)testInitDynamicClientIdNullDomain {
callback:OCMOCK_ANY]);
}

- (void)testInitDynamicServerClientIdNullDomain {
FlutterMethodCall *initMethodCall =
[FlutterMethodCall methodCallWithMethodName:@"init"
arguments:@{
@"hostedDomain" : [NSNull null],
@"serverClientId" : @"mockServerClientId"
}];

XCTestExpectation *initExpectation =
[self expectationWithDescription:@"expect result returns true"];
[self.plugin handleMethodCall:initMethodCall
result:^(id result) {
XCTAssertNil(result);
[initExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:5.0 handler:nil];

// Initialization values used in the next sign in request.
FlutterMethodCall *signInMethodCall = [FlutterMethodCall methodCallWithMethodName:@"signIn"
arguments:nil];
[self.plugin handleMethodCall:signInMethodCall
result:^(id r){
}];
OCMVerify([self.mockSignIn
signInWithConfiguration:[OCMArg checkWithBlock:^BOOL(GIDConfiguration *configuration) {
return configuration.hostedDomain == nil &&
[configuration.serverClientID isEqualToString:@"mockServerClientId"];
}]
presentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]]
hint:nil
additionalScopes:OCMOCK_ANY
callback:OCMOCK_ANY]);
}

#pragma mark - Is signed in

- (void)testIsNotSignedIn {
Expand Down
Loading

0 comments on commit 1be7a6a

Please sign in to comment.