Skip to content
Open
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: 3 additions & 1 deletion packages/google_sign_in/google_sign_in/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## NEXT
## 7.2.0

* Adds a `clearAuthorizationToken` method to remove an access token from the
cache.
* Updates minimum supported SDK version to Flutter 3.29/Dart 3.7.

## 7.1.1
Expand Down
1 change: 1 addition & 0 deletions packages/google_sign_in/google_sign_in/MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ include:
publishing, the only platform that does not support `authenticate` is web,
where `google_sign_in_web`'s `renderButton` is used to create a sign-in
button.
* `clearAuthCache` has been replaced by `clearAuthorizationToken`.
* Outcomes other than successful authentication or authorization will throw
`GoogleSignInException`s in most cases, allowing a clear way to distinguish
different sign in failure outcomes. This includes the user canceling
Expand Down
7 changes: 7 additions & 0 deletions packages/google_sign_in/google_sign_in/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,10 @@ dev_dependencies:

flutter:
uses-material-design: true
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
google_sign_in_android: {path: ../../../../packages/google_sign_in/google_sign_in_android}
google_sign_in_ios: {path: ../../../../packages/google_sign_in/google_sign_in_ios}
google_sign_in_platform_interface: {path: ../../../../packages/google_sign_in/google_sign_in_platform_interface}
google_sign_in_web: {path: ../../../../packages/google_sign_in/google_sign_in_web}
29 changes: 27 additions & 2 deletions packages/google_sign_in/google_sign_in/lib/google_sign_in.dart
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ class GoogleSignInAuthorizationClient {
///
/// If authorization would require user interaction, this returns null, in
/// which case [authorizeScopes] should be used instead.
///
/// In rare cases, this can return tokens that are no longer valid. See
/// [clearAuthorizationToken] for details.
Future<GoogleSignInClientAuthorization?> authorizationForScopes(
List<String> scopes,
) async {
Expand All @@ -150,6 +153,9 @@ class GoogleSignInAuthorizationClient {
/// allowed (for example, while the app is foregrounded on mobile), and if
/// [GoogleSignIn.authorizationRequiresUserInteraction] returns true this
/// should only be called from an user interaction handler.
///
/// In rare cases, this can return tokens that are no longer valid. See
/// [clearAuthorizationToken] for details.
Future<GoogleSignInClientAuthorization> authorizeScopes(
List<String> scopes,
) async {
Expand All @@ -173,8 +179,10 @@ class GoogleSignInAuthorizationClient {
/// authorization headers, containing the access token for the given scopes.
///
/// Returns null if the given scopes are not authorized, or there is no
/// currently valid authorization token available, and
/// [promptIfNecessary] is false.
/// unexpired authorization token available, and [promptIfNecessary] is false.
///
/// In rare cases, this can return tokens that are no longer valid. See
/// [clearAuthorizationToken] for details.
///
/// See also https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization.
Future<Map<String, String>?> authorizationHeaders(
Expand Down Expand Up @@ -207,6 +215,9 @@ class GoogleSignInAuthorizationClient {
/// allowed (for example, while the app is foregrounded on mobile), and if
/// [GoogleSignIn.authorizationRequiresUserInteraction] returns true this
/// should only be called from an user interaction handler.
///
/// In rare cases, this can return tokens that are no longer valid. See
/// [clearAuthorizationToken] for details.
Future<GoogleSignInServerAuthorization?> authorizeServer(
List<String> scopes,
) async {
Expand All @@ -229,6 +240,20 @@ class GoogleSignInAuthorizationClient {
);
}

/// Removes the given [accessToken] from any local authorization caches.
///
/// This should be called if using an access token results in an invalid token
/// response from the target API, followed by re-requsting authorization.
///
/// A token can be invalidated by, for example, a user removing an
/// application's authorization from outside of the application:
/// https://support.google.com/accounts/answer/13533235.
Future<void> clearAuthorizationToken({required String accessToken}) {
return GoogleSignInPlatform.instance.clearAuthorizationToken(
ClearAuthorizationTokensParams(accessToken: accessToken),
);
}

Future<GoogleSignInClientAuthorization?> _authorizeClient(
List<String> scopes, {
required bool promptIfUnauthorized,
Expand Down
9 changes: 8 additions & 1 deletion packages/google_sign_in/google_sign_in/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: Flutter plugin for Google Sign-In, a secure authentication system
for signing in with a Google account.
repository: https://github.com/flutter/packages/tree/main/packages/google_sign_in/google_sign_in
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22
version: 7.1.1
version: 7.2.0

environment:
sdk: ^3.7.0
Expand Down Expand Up @@ -48,3 +48,10 @@ false_secrets:
- /example/ios/RunnerTests/GoogleService-Info.plist
- /example/ios/RunnerTests/GoogleSignInTests.m
- /example/macos/Runner/Info.plist
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
google_sign_in_android: {path: ../../../packages/google_sign_in/google_sign_in_android}
google_sign_in_ios: {path: ../../../packages/google_sign_in/google_sign_in_ios}
google_sign_in_platform_interface: {path: ../../../packages/google_sign_in/google_sign_in_platform_interface}
google_sign_in_web: {path: ../../../packages/google_sign_in/google_sign_in_web}
Original file line number Diff line number Diff line change
Expand Up @@ -760,4 +760,22 @@ void main() {
);
});
});

group('clearAuthorizationToken', () {
test('passes expected paramaters', () async {
final GoogleSignIn googleSignIn = GoogleSignIn.instance;

const String token = 'someAccessToken';
await googleSignIn.authorizationClient.clearAuthorizationToken(
accessToken: token,
);

final VerificationResult verification = verify(
mockPlatform.clearAuthorizationToken(captureAny),
);
final ClearAuthorizationTokensParams params =
verification.captured[0] as ClearAuthorizationTokensParams;
expect(params.accessToken, token);
});
});
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Mocks generated by Mockito 5.4.5 from annotations
// Mocks generated by Mockito 5.4.6 from annotations
// in google_sign_in/test/google_sign_in_test.dart.
// Do not manually edit this file.

Expand Down Expand Up @@ -57,6 +57,14 @@ class MockGoogleSignInPlatform extends _i1.Mock
)
as _i4.Future<_i2.AuthenticationResults?>?);

@override
bool supportsAuthenticate() =>
(super.noSuchMethod(
Invocation.method(#supportsAuthenticate, []),
returnValue: false,
)
as bool);

@override
_i4.Future<_i2.AuthenticationResults> authenticate(
_i2.AuthenticateParameters? params,
Expand All @@ -72,14 +80,6 @@ class MockGoogleSignInPlatform extends _i1.Mock
)
as _i4.Future<_i2.AuthenticationResults>);

@override
bool supportsAuthenticate() =>
(super.noSuchMethod(
Invocation.method(#supportsAuthenticate, []),
returnValue: false,
)
as bool);

@override
bool authorizationRequiresUserInteraction() =>
(super.noSuchMethod(
Expand Down Expand Up @@ -110,6 +110,17 @@ class MockGoogleSignInPlatform extends _i1.Mock
)
as _i4.Future<_i2.ServerAuthorizationTokenData?>);

@override
_i4.Future<void> clearAuthorizationToken(
_i2.ClearAuthorizationTokensParams? params,
) =>
(super.noSuchMethod(
Invocation.method(#clearAuthorizationToken, [params]),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
)
as _i4.Future<void>);

@override
_i4.Future<void> signOut(_i2.SignOutParams? params) =>
(super.noSuchMethod(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## NEXT
## 7.1.0

* Adds support for the `clearAuthorizationToken` method.
* Updates minimum supported SDK version to Flutter 3.29/Dart 3.7.

## 7.0.3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ dependencies {
implementation 'androidx.credentials:credentials:1.5.0'
implementation 'androidx.credentials:credentials-play-services-auth:1.5.0'
implementation 'com.google.android.libraries.identity.googleid:googleid:1.1.1'
implementation 'com.google.android.gms:play-services-auth:21.3.0'
implementation 'com.google.android.gms:play-services-auth:21.4.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-inline:5.2.0'
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.google.android.gms.auth.api.identity.AuthorizationClient;
import com.google.android.gms.auth.api.identity.AuthorizationRequest;
import com.google.android.gms.auth.api.identity.AuthorizationResult;
import com.google.android.gms.auth.api.identity.ClearTokenRequest;
import com.google.android.gms.auth.api.identity.Identity;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.Scope;
Expand Down Expand Up @@ -219,7 +220,7 @@ public void getCredential(
return;
}

// getCredentialAsync requires an acitivity context, not an application context, per
// getCredentialAsync requires an activity context, not an application context, per
// the API docs.
Activity activity = getActivity();
if (activity == null) {
Expand Down Expand Up @@ -331,17 +332,31 @@ public void clearCredentialState(@NonNull Function1<? super Result<Unit>, Unit>
new CredentialManagerCallback<>() {
@Override
public void onResult(Void result) {
ResultUtilsKt.completeWithClearCredentialStateSuccess(callback);
ResultUtilsKt.completeWithUnitSuccess(callback);
}

@Override
public void onError(@NonNull ClearCredentialException e) {
ResultUtilsKt.completeWithClearCredentialStateError(
ResultUtilsKt.completeWithUnitError(
callback, new FlutterError("Clear Failed", e.getMessage(), null));
}
});
}

@Override
public void clearAuthorizationToken(
@NonNull String token, @NonNull Function1<? super Result<Unit>, Unit> callback) {
authorizationClientFactory
.create(context)
.clearToken(ClearTokenRequest.builder().setToken(token).build())
.addOnSuccessListener(unused -> ResultUtilsKt.completeWithUnitSuccess(callback))
.addOnFailureListener(
e ->
ResultUtilsKt.completeWithUnitError(
callback,
new FlutterError("clearAuthorizationToken failed", e.getMessage(), null)));
}

@Override
public void authorize(
@NonNull PlatformAuthorizationRequest params,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,8 @@ interface GoogleSignInApi {
)
/** Clears CredentialManager credential state. */
fun clearCredentialState(callback: (Result<Unit>) -> Unit)
/** Clears the authorization cache for the given token. */
fun clearAuthorizationToken(token: String, callback: (Result<Unit>) -> Unit)
/** Requests authorization tokens via AuthorizationClient. */
fun authorize(
params: PlatformAuthorizationRequest,
Expand Down Expand Up @@ -563,6 +565,29 @@ interface GoogleSignInApi {
channel.setMessageHandler(null)
}
}
run {
val channel =
BasicMessageChannel<Any?>(
binaryMessenger,
"dev.flutter.pigeon.google_sign_in_android.GoogleSignInApi.clearAuthorizationToken$separatedMessageChannelSuffix",
codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val tokenArg = args[0] as String
api.clearAuthorizationToken(tokenArg) { result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(wrapError(error))
} else {
reply.reply(wrapResult(null))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel =
BasicMessageChannel<Any?>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ fun completeWithGetCredentialFailure(
callback(Result.success(failure))
}

fun completeWithClearCredentialStateSuccess(callback: (Result<Unit>) -> Unit) {
fun completeWithUnitSuccess(callback: (Result<Unit>) -> Unit) {
callback(Result.success(Unit))
}

fun completeWithClearCredentialStateError(callback: (Result<Unit>) -> Unit, failure: FlutterError) {
fun completeWithUnitError(callback: (Result<Unit>) -> Unit, failure: FlutterError) {
callback(Result.failure(failure))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import com.google.android.gms.auth.api.identity.AuthorizationClient;
import com.google.android.gms.auth.api.identity.AuthorizationRequest;
import com.google.android.gms.auth.api.identity.AuthorizationResult;
import com.google.android.gms.auth.api.identity.ClearTokenRequest;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.tasks.OnSuccessListener;
Expand Down Expand Up @@ -71,6 +72,7 @@ public class GoogleSignInTest {
@Mock CustomCredential mockGenericCredential;
@Mock GoogleIdTokenCredential mockGoogleCredential;
@Mock Task<AuthorizationResult> mockAuthorizationTask;
@Mock Task<Void> mockClearTokenTask;

private GoogleSignInPlugin flutterPlugin;
// Technically this is not the plugin, but in practice almost all of the functionality is in this
Expand All @@ -88,6 +90,8 @@ public void setUp() {
.thenReturn(GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL);
when(mockAuthorizationTask.addOnSuccessListener(any())).thenReturn(mockAuthorizationTask);
when(mockAuthorizationTask.addOnFailureListener(any())).thenReturn(mockAuthorizationTask);
when(mockClearTokenTask.addOnSuccessListener(any())).thenReturn(mockClearTokenTask);
when(mockClearTokenTask.addOnFailureListener(any())).thenReturn(mockClearTokenTask);
when(mockAuthorizationIntent.getIntentSender()).thenReturn(mockAuthorizationIntentSender);
when(mockActivityPluginBinding.getActivity()).thenReturn(mockActivity);

Expand Down Expand Up @@ -1097,4 +1101,29 @@ public void clearCredentialState_reportsFailure() {

callbackCaptor.getValue().onError(mock(ClearCredentialException.class));
}

@Test
public void clearAuthorizationToken_callsClient() {
final String testToken = "testToken";
when(mockAuthorizationClient.clearToken(any())).thenReturn(mockClearTokenTask);
plugin.clearAuthorizationToken(
testToken,
ResultCompat.asCompatCallback(
reply -> {
return null;
}));

ArgumentCaptor<ClearTokenRequest> authRequestCaptor =
ArgumentCaptor.forClass(ClearTokenRequest.class);
verify(mockAuthorizationClient).clearToken(authRequestCaptor.capture());

@SuppressWarnings("unchecked")
ArgumentCaptor<OnSuccessListener<Void>> callbackCaptor =
ArgumentCaptor.forClass(OnSuccessListener.class);
verify(mockClearTokenTask).addOnSuccessListener(callbackCaptor.capture());
callbackCaptor.getValue().onSuccess(null);

ClearTokenRequest request = authRequestCaptor.getValue();
assertEquals(testToken, request.getToken());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@ dev_dependencies:

flutter:
uses-material-design: true
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
google_sign_in_platform_interface: {path: ../../../../packages/google_sign_in/google_sign_in_platform_interface}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ class GoogleSignInAndroid extends GoogleSignInPlatform {
GoogleSignInPlatform.instance = GoogleSignInAndroid();
}

@override
Future<void> clearAuthorizationToken(ClearAuthorizationTokensParams params) {
return _hostApi.clearAuthorizationToken(params.accessToken);
}

@override
Future<void> init(InitParameters params) async {
_hostedDomain = params.hostedDomain;
Expand Down
Loading