Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/google_sign_in/google_sign_in/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 4.3.0

* Add support for method introduced in `google_sign_in_platform_interface` 1.1.0.

## 4.2.0

* Migrate to AndroidX.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

import android.accounts.Account;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.VisibleForTesting;
import com.google.android.gms.auth.GoogleAuthUtil;
import com.google.android.gms.auth.UserRecoverableAuthException;
import com.google.android.gms.auth.api.signin.GoogleSignIn;
Expand All @@ -27,6 +29,7 @@
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -46,17 +49,19 @@ public class GoogleSignInPlugin implements MethodCallHandler {
private static final String METHOD_DISCONNECT = "disconnect";
private static final String METHOD_IS_SIGNED_IN = "isSignedIn";
private static final String METHOD_CLEAR_AUTH_CACHE = "clearAuthCache";
private static final String METHOD_REQUEST_SCOPES = "requestScopes";

private final IDelegate delegate;

public static void registerWith(PluginRegistry.Registrar registrar) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME);
final GoogleSignInPlugin instance = new GoogleSignInPlugin(registrar);
final GoogleSignInPlugin instance =
new GoogleSignInPlugin(registrar, new GoogleSignInWrapper());
channel.setMethodCallHandler(instance);
}

private GoogleSignInPlugin(PluginRegistry.Registrar registrar) {
delegate = new Delegate(registrar);
GoogleSignInPlugin(PluginRegistry.Registrar registrar, GoogleSignInWrapper googleSignInWrapper) {
delegate = new Delegate(registrar, googleSignInWrapper);
}

@Override
Expand Down Expand Up @@ -100,6 +105,11 @@ public void onMethodCall(MethodCall call, Result result) {
delegate.isSignedIn(result);
break;

case METHOD_REQUEST_SCOPES:
List<String> scopes = call.argument("scopes");
delegate.requestScopes(result, scopes);
break;

default:
result.notImplemented();
}
Expand Down Expand Up @@ -153,6 +163,9 @@ public void init(

/** Checks if there is a signed in user. */
public void isSignedIn(Result result);

/** Prompts the user to grant an additional Oauth scopes. */
public void requestScopes(final Result result, final List<String> scopes);
}

/**
Expand All @@ -167,6 +180,7 @@ public void init(
public static final class Delegate implements IDelegate, PluginRegistry.ActivityResultListener {
private static final int REQUEST_CODE_SIGNIN = 53293;
private static final int REQUEST_CODE_RECOVER_AUTH = 53294;
@VisibleForTesting static final int REQUEST_CODE_REQUEST_SCOPE = 53295;

private static final String ERROR_REASON_EXCEPTION = "exception";
private static final String ERROR_REASON_STATUS = "status";
Expand All @@ -183,13 +197,15 @@ public static final class Delegate implements IDelegate, PluginRegistry.Activity

private final PluginRegistry.Registrar registrar;
private final BackgroundTaskRunner backgroundTaskRunner = new BackgroundTaskRunner(1);
private final GoogleSignInWrapper googleSignInWrapper;

private GoogleSignInClient signInClient;
private List<String> requestedScopes;
private PendingOperation pendingOperation;

public Delegate(PluginRegistry.Registrar registrar) {
public Delegate(PluginRegistry.Registrar registrar, GoogleSignInWrapper googleSignInWrapper) {
this.registrar = registrar;
this.googleSignInWrapper = googleSignInWrapper;
registrar.addActivityResultListener(this);
}

Expand Down Expand Up @@ -343,6 +359,37 @@ public void isSignedIn(final Result result) {
result.success(value);
}

@Override
public void requestScopes(Result result, List<String> scopes) {
checkAndSetPendingOperation(METHOD_REQUEST_SCOPES, result);

GoogleSignInAccount account = googleSignInWrapper.getLastSignedInAccount(registrar.context());
if (account == null) {
result.error(ERROR_REASON_SIGN_IN_REQUIRED, "No account to grant scopes.", null);
return;
}

List<Scope> wrappedScopes = new ArrayList<>();

for (String scope : scopes) {
Scope wrappedScope = new Scope(scope);
if (!googleSignInWrapper.hasPermissions(account, wrappedScope)) {
wrappedScopes.add(wrappedScope);
}
}

if (wrappedScopes.isEmpty()) {
result.success(true);
return;
}

googleSignInWrapper.requestPermissions(
registrar.activity(),
REQUEST_CODE_REQUEST_SCOPE,
account,
wrappedScopes.toArray(new Scope[0]));
}

private void onSignInResult(Task<GoogleSignInAccount> completedTask) {
try {
GoogleSignInAccount account = completedTask.getResult(ApiException.class);
Expand Down Expand Up @@ -527,9 +574,37 @@ public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
finishWithError(ERROR_REASON_SIGN_IN_FAILED, "Signin failed");
}
return true;
case REQUEST_CODE_REQUEST_SCOPE:
finishWithSuccess(resultCode == Activity.RESULT_OK);
return true;
default:
return false;
}
}
}
}

/**
* A wrapper object that calls static method in GoogleSignIn.
*
* <p>Because GoogleSignIn uses static method mostly, which is hard for unit testing. We use this
* wrapper class to use instance method which calls the corresponding GoogleSignIn static methods.
*
* <p>Warning! This class should stay true that each method calls a GoogleSignIn static method with
* the same name and same parameters.
*/
class GoogleSignInWrapper {

GoogleSignInAccount getLastSignedInAccount(Context context) {
return GoogleSignIn.getLastSignedInAccount(context);
}

boolean hasPermissions(GoogleSignInAccount account, Scope scope) {
return GoogleSignIn.hasPermissions(account, scope);
}

void requestPermissions(
Activity activity, int requestCode, GoogleSignInAccount account, Scope[] scopes) {
GoogleSignIn.requestPermissions(activity, requestCode, account, scopes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,18 @@ android {
signingConfig signingConfigs.debug
}
}

testOptions {
unitTests.returnDefaultValues = true
}
}

flutter {
source '../..'
}

dependencies {
implementation 'com.google.android.gms:play-services-auth:16.0.1'
testImplementation'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.17.0'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.googlesignin;

import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.common.api.Scope;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.PluginRegistry.ActivityResultListener;
import io.flutter.plugins.googlesignin.GoogleSignInPlugin.Delegate;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;

public class GoogleSignInPluginTests {

@Mock Context mockContext;
@Mock Activity mockActivity;
@Mock PluginRegistry.Registrar mockRegistrar;
@Mock BinaryMessenger mockMessenger;
@Spy MethodChannel.Result result;
@Mock GoogleSignInWrapper mockGoogleSignIn;
@Mock GoogleSignInAccount account;
private GoogleSignInPlugin plugin;

@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mockRegistrar.messenger()).thenReturn(mockMessenger);
when(mockRegistrar.context()).thenReturn(mockContext);
when(mockRegistrar.activity()).thenReturn(mockActivity);
plugin = new GoogleSignInPlugin(mockRegistrar, mockGoogleSignIn);
}

@Test
public void requestScopes_ResultErrorIfAccountIsNull() {
MethodCall methodCall = new MethodCall("requestScopes", null);
when(mockGoogleSignIn.getLastSignedInAccount(mockContext)).thenReturn(null);
plugin.onMethodCall(methodCall, result);
verify(result).error("sign_in_required", "No account to grant scopes.", null);
}

@Test
public void requestScopes_ResultTrueIfAlreadyGranted() {
HashMap<String, List<String>> arguments = new HashMap<>();
arguments.put("scopes", Collections.singletonList("requestedScope"));

MethodCall methodCall = new MethodCall("requestScopes", arguments);
Scope requestedScope = new Scope("requestedScope");
when(mockGoogleSignIn.getLastSignedInAccount(mockContext)).thenReturn(account);
when(account.getGrantedScopes()).thenReturn(Collections.singleton(requestedScope));
when(mockGoogleSignIn.hasPermissions(account, requestedScope)).thenReturn(true);

plugin.onMethodCall(methodCall, result);
verify(result).success(true);
}

@Test
public void requestScopes_RequestsPermissionIfNotGranted() {
HashMap<String, List<String>> arguments = new HashMap<>();
arguments.put("scopes", Collections.singletonList("requestedScope"));
MethodCall methodCall = new MethodCall("requestScopes", arguments);
Scope requestedScope = new Scope("requestedScope");

when(mockGoogleSignIn.getLastSignedInAccount(mockContext)).thenReturn(account);
when(account.getGrantedScopes()).thenReturn(Collections.singleton(requestedScope));
when(mockGoogleSignIn.hasPermissions(account, requestedScope)).thenReturn(false);

plugin.onMethodCall(methodCall, result);

verify(mockGoogleSignIn)
.requestPermissions(mockActivity, 53295, account, new Scope[] {requestedScope});
}

@Test
public void requestScopes_ReturnsFalseIfPermissionDenied() {
HashMap<String, List<String>> arguments = new HashMap<>();
arguments.put("scopes", Collections.singletonList("requestedScope"));
MethodCall methodCall = new MethodCall("requestScopes", arguments);
Scope requestedScope = new Scope("requestedScope");

ArgumentCaptor<ActivityResultListener> captor =
ArgumentCaptor.forClass(ActivityResultListener.class);
verify(mockRegistrar).addActivityResultListener(captor.capture());
ActivityResultListener listener = captor.getValue();

when(mockGoogleSignIn.getLastSignedInAccount(mockContext)).thenReturn(account);
when(account.getGrantedScopes()).thenReturn(Collections.singleton(requestedScope));
when(mockGoogleSignIn.hasPermissions(account, requestedScope)).thenReturn(false);

plugin.onMethodCall(methodCall, result);
listener.onActivityResult(
Delegate.REQUEST_CODE_REQUEST_SCOPE, Activity.RESULT_CANCELED, new Intent());

verify(result).success(false);
}

@Test
public void requestScopes_ReturnsTrueIfPermissionGranted() {
HashMap<String, List<String>> arguments = new HashMap<>();
arguments.put("scopes", Collections.singletonList("requestedScope"));
MethodCall methodCall = new MethodCall("requestScopes", arguments);
Scope requestedScope = new Scope("requestedScope");

ArgumentCaptor<ActivityResultListener> captor =
ArgumentCaptor.forClass(ActivityResultListener.class);
verify(mockRegistrar).addActivityResultListener(captor.capture());
ActivityResultListener listener = captor.getValue();

when(mockGoogleSignIn.getLastSignedInAccount(mockContext)).thenReturn(account);
when(account.getGrantedScopes()).thenReturn(Collections.singleton(requestedScope));
when(mockGoogleSignIn.hasPermissions(account, requestedScope)).thenReturn(false);

plugin.onMethodCall(methodCall, result);
listener.onActivityResult(
Delegate.REQUEST_CODE_REQUEST_SCOPE, Activity.RESULT_OK, new Intent());

verify(result).success(true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mock-maker-inline
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>
Loading