diff --git a/packages/google_sign_in/google_sign_in/CHANGELOG.md b/packages/google_sign_in/google_sign_in/CHANGELOG.md index 9668392da3e..7e5b8665a62 100644 --- a/packages/google_sign_in/google_sign_in/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.1.6 + +* Updates README to direct to google_sign_in_ios README for iOS integration instructions. + ## 6.1.5 * Adds pub topics to package metadata. diff --git a/packages/google_sign_in/google_sign_in/README.md b/packages/google_sign_in/google_sign_in/README.md index 154e683c376..87f28571805 100644 --- a/packages/google_sign_in/google_sign_in/README.md +++ b/packages/google_sign_in/google_sign_in/README.md @@ -27,59 +27,7 @@ Otherwise, you may encounter `APIException` errors. ### iOS integration -1. [First register your application](https://firebase.google.com/docs/ios/setup). -2. Make sure the file you download in step 1 is named - `GoogleService-Info.plist`. -3. Move or copy `GoogleService-Info.plist` into the `[my_project]/ios/Runner` - directory. -4. Open Xcode, then right-click on `Runner` directory and select - `Add Files to "Runner"`. -5. Select `GoogleService-Info.plist` from the file manager. -6. A dialog will show up and ask you to select the targets, select the `Runner` - target. -7. If you need to authenticate to a backend server you can add a - `SERVER_CLIENT_ID` key value pair in your `GoogleService-Info.plist`. - ```xml - SERVER_CLIENT_ID - [YOUR SERVER CLIENT ID] - ``` -8. Then add the `CFBundleURLTypes` attributes below into the - `[my_project]/ios/Runner/Info.plist` file. - -```xml - - -CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLSchemes - - - - com.googleusercontent.apps.861823949799-vc35cprkp249096uujjn0vvnmcvjppkn - - - - -``` - -As an alternative to adding `GoogleService-Info.plist` to your Xcode project, -you can instead configure your app in Dart code. In this case, skip steps 3 to 7 - and pass `clientId` and `serverClientId` to the `GoogleSignIn` constructor: - -```dart -GoogleSignIn _googleSignIn = GoogleSignIn( - ... - // The OAuth client id of your app. This is required. - clientId: ..., - // If you need to authenticate to a backend server, specify its OAuth client. This is optional. - serverClientId: ..., -); -``` - -Note that step 8 is still required. +Please see [instructions on integrating Google Sign-In for iOS](https://pub.dev/packages/google_sign_in_ios#ios-integration). #### iOS additional requirement @@ -212,12 +160,12 @@ For more details, take a look at the ### Does an app always need to check `canAccessScopes`? -The new web SDK implicitly grant access to the `email`, `profile` and `openid` +The new web SDK implicitly grant access to the `email`, `profile` and `openid` scopes when users complete the sign-in process (either via the One Tap UX or the Google Sign In button). If an app only needs an `idToken`, or only requests permissions to any/all of -the three scopes mentioned above +the three scopes mentioned above ([OpenID Connect scopes](https://developers.google.com/identity/protocols/oauth2/scopes#openid-connect)), it won't need to implement any additional scope handling. diff --git a/packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/project.pbxproj b/packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/project.pbxproj index 45b4e802664..5af6f7cec13 100644 --- a/packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ 5C6F5A6E1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C6F5A6D1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m */; }; - 7A303C2E1E89D76400B1F19E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */; }; 7ACDFB0E1E8944C400BE2D00 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7ACDFB0D1E8944C400BE2D00 /* AppFrameworkInfo.plist */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; @@ -39,7 +38,6 @@ 5A76713E622F06379AEDEBFA /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 5C6F5A6C1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 5C6F5A6D1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 7ACDFB0D1E8944C400BE2D00 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; @@ -118,7 +116,6 @@ 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, ); @@ -173,7 +170,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -204,7 +201,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7A303C2E1E89D76400B1F19E /* GoogleService-Info.plist in Resources */, 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 7ACDFB0E1E8944C400BE2D00 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, diff --git a/packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index f4569c48ce1..8e83ef7194e 100644 --- a/packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ UIApplicationSupportsIndirectInputEvents + GIDClientID + 479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com + GIDServerClientID + YOUR_SERVER_CLIENT_ID diff --git a/packages/google_sign_in/google_sign_in/pubspec.yaml b/packages/google_sign_in/google_sign_in/pubspec.yaml index 2f3767f0cdb..05d8bfbc6de 100644 --- a/packages/google_sign_in/google_sign_in/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for Google Sign-In, a secure authentication system for signing in with a Google account on Android and iOS. 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: 6.1.5 +version: 6.1.6 environment: sdk: ">=2.19.0 <4.0.0" @@ -43,5 +43,6 @@ topics: # The example deliberately includes limited-use secrets. false_secrets: - /example/android/app/google-services.json - - /example/ios/Runner/GoogleService-Info.plist + - /example/ios/Runner/Info.plist + - /example/ios/RunnerTests/GoogleService-Info.plist - /example/ios/RunnerTests/GoogleSignInTests.m diff --git a/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart b/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart index 86b83cf4a09..94fabc58aec 100644 --- a/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart +++ b/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart @@ -81,6 +81,28 @@ void main() { verify(mockPlatform.signIn()); }); + test( + 'clientId and serverClientId parameters is forwarded to implementation', + () async { + // #docregion GoogleSignIn + final GoogleSignIn googleSignIn = GoogleSignIn( + // The OAuth client id of your app. This is required. + clientId: 'Your Client ID', + // If you need to authenticate to a backend server, specify its OAuth client. This is optional. + serverClientId: 'Your Server ID', + ); + // #enddocregion GoogleSignIn + + await googleSignIn.signIn(); + + _verifyInit( + mockPlatform, + clientId: 'Your Client ID', + serverClientId: 'Your Server ID', + ); + verify(mockPlatform.signIn()); + }); + test('forceCodeForRefreshToken sent with init method call', () async { final GoogleSignIn googleSignIn = GoogleSignIn(forceCodeForRefreshToken: true); diff --git a/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md b/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md index 4f67a64510f..28641320706 100644 --- a/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.6.5 + +* Upgrades GoogleSignIn iOS SDK to 7.0. + ## 5.6.4 * Converts platform communication to Pigeon. diff --git a/packages/google_sign_in/google_sign_in_ios/README.md b/packages/google_sign_in/google_sign_in_ios/README.md index 6a5d862d77e..29bcc25fb18 100644 --- a/packages/google_sign_in/google_sign_in_ios/README.md +++ b/packages/google_sign_in/google_sign_in_ios/README.md @@ -13,3 +13,62 @@ should add it to your `pubspec.yaml` as usual. [1]: https://pub.dev/packages/google_sign_in [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin + +### iOS integration + +1. [Create a Firebase project](https://firebase.google.com/docs/ios/setup#create-firebase-project) + and [register your application](https://firebase.google.com/docs/ios/setup#register-app). +2. [Enable Google Sign-In for your Firebase project](https://firebase.google.com/docs/auth/ios/google-signin#enable_google_sign-in_for_your_firebase_project). +3. Make sure to download a new copy of your project's + `GoogleService-Info.plist` from step 2. Do not put this file in your project. +4. Add the client ID from the `GoogleService-Info.plist` into your app's + `[my_project]/ios/Runner/Info.plist` file. + ```xml + GIDClientID + + + [YOUR IOS CLIENT ID] + ``` +5. If you need to authenticate to a backend server you can add a + `GIDServerClientID` key value pair in your `[my_project]/ios/Runner/Info.plist` file. + ```xml + GIDServerClientID + [YOUR SERVER CLIENT ID] + ``` +6. Then add the `CFBundleURLTypes` attributes below into the + `[my_project]/ios/Runner/Info.plist` file. + +```xml + + +CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + + + com.googleusercontent.apps.861823949799-vc35cprkp249096uujjn0vvnmcvjppkn + + + + +``` + +As an alternative to editing the `Info.plist` in your Xcode project, +you can instead configure your app in Dart code. In this case, skip steps 4 to 5 + and pass `clientId` and `serverClientId` to the `GoogleSignIn` constructor: + + +```dart +final GoogleSignIn googleSignIn = GoogleSignIn( + // The OAuth client id of your app. This is required. + clientId: 'Your Client ID', + // If you need to authenticate to a backend server, specify its OAuth client. This is optional. + serverClientId: 'Your Server ID', +); +``` + +Note that step 6 is still required. diff --git a/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.pbxproj index abbb5ef47ca..ca1508eba8c 100644 --- a/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.pbxproj @@ -8,7 +8,7 @@ /* Begin PBXBuildFile section */ 5C6F5A6E1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C6F5A6D1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m */; }; - 7A303C2E1E89D76400B1F19E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */; }; + 78A36DA12AF5761E00CBFD43 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */; }; 7ACDFB0E1E8944C400BE2D00 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7ACDFB0D1E8944C400BE2D00 /* AppFrameworkInfo.plist */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; @@ -163,7 +163,6 @@ 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, ); @@ -191,6 +190,7 @@ isa = PBXGroup; children = ( F76AC1A42666D0540040C8BC /* GoogleSignInTests.m */, + 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */, F76AC1A62666D0540040C8BC /* Info.plist */, ); path = RunnerTests; @@ -316,7 +316,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7A303C2E1E89D76400B1F19E /* GoogleService-Info.plist in Resources */, 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 7ACDFB0E1E8944C400BE2D00 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, @@ -328,6 +327,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 78A36DA12AF5761E00CBFD43 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/packages/google_sign_in/google_sign_in_ios/example/ios/Runner/GoogleService-Info.plist b/packages/google_sign_in/google_sign_in_ios/example/ios/Runner/GoogleService-Info.plist deleted file mode 100644 index 6042aab908a..00000000000 --- a/packages/google_sign_in/google_sign_in_ios/example/ios/Runner/GoogleService-Info.plist +++ /dev/null @@ -1,44 +0,0 @@ - - - - - AD_UNIT_ID_FOR_BANNER_TEST - ca-app-pub-3940256099942544/2934735716 - AD_UNIT_ID_FOR_INTERSTITIAL_TEST - ca-app-pub-3940256099942544/4411468910 - CLIENT_ID - 479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com - REVERSED_CLIENT_ID - com.googleusercontent.apps.479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u - ANDROID_CLIENT_ID - 479882132969-jie8r1me6dsra60pal6ejaj8dgme3tg0.apps.googleusercontent.com - API_KEY - AIzaSyBECOwLTAN6PU4Aet1b2QLGIb3kRK8Xjew - GCM_SENDER_ID - 479882132969 - PLIST_VERSION - 1 - BUNDLE_ID - io.flutter.plugins.googleSignInExample - PROJECT_ID - my-flutter-proj - STORAGE_BUCKET - my-flutter-proj.appspot.com - IS_ADS_ENABLED - - IS_ANALYTICS_ENABLED - - IS_APPINVITE_ENABLED - - IS_GCM_ENABLED - - IS_SIGNIN_ENABLED - - GOOGLE_APP_ID - 1:479882132969:ios:2643f950e0a0da08 - DATABASE_URL - https://my-flutter-proj.firebaseio.com - SERVER_CLIENT_ID - YOUR_SERVER_CLIENT_ID - - \ No newline at end of file diff --git a/packages/google_sign_in/google_sign_in_ios/example/ios/Runner/Info.plist b/packages/google_sign_in/google_sign_in_ios/example/ios/Runner/Info.plist index 08fef9a9fe4..5c3953aef76 100644 --- a/packages/google_sign_in/google_sign_in_ios/example/ios/Runner/Info.plist +++ b/packages/google_sign_in/google_sign_in_ios/example/ios/Runner/Info.plist @@ -60,5 +60,9 @@ UIApplicationSupportsIndirectInputEvents + GIDClientID + 479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com + GIDServerClientID + YOUR_SERVER_CLIENT_ID diff --git a/packages/google_sign_in/google_sign_in/example/ios/Runner/GoogleService-Info.plist b/packages/google_sign_in/google_sign_in_ios/example/ios/RunnerTests/GoogleService-Info.plist similarity index 100% rename from packages/google_sign_in/google_sign_in/example/ios/Runner/GoogleService-Info.plist rename to packages/google_sign_in/google_sign_in_ios/example/ios/RunnerTests/GoogleService-Info.plist diff --git a/packages/google_sign_in/google_sign_in_ios/example/ios/RunnerTests/GoogleSignInTests.m b/packages/google_sign_in/google_sign_in_ios/example/ios/RunnerTests/GoogleSignInTests.m index 8e27c39a841..de504636e65 100644 --- a/packages/google_sign_in/google_sign_in_ios/example/ios/RunnerTests/GoogleSignInTests.m +++ b/packages/google_sign_in/google_sign_in_ios/example/ios/RunnerTests/GoogleSignInTests.m @@ -18,6 +18,7 @@ @interface FLTGoogleSignInPluginTest : XCTestCase @property(strong, nonatomic) NSObject *mockPluginRegistrar; @property(strong, nonatomic) FLTGoogleSignInPlugin *plugin; @property(strong, nonatomic) id mockSignIn; +@property(strong, nonatomic) NSDictionary *googleServiceInfo; @end @@ -34,6 +35,13 @@ - (void)setUp { OCMStub(self.mockPluginRegistrar.messenger).andReturn(self.mockBinaryMessenger); self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:mockSignIn]; [FLTGoogleSignInPlugin registerWithRegistrar:self.mockPluginRegistrar]; + + NSString *plistPath = + [[NSBundle bundleForClass:[self class]] pathForResource:@"GoogleService-Info" + ofType:@"plist"]; + if (plistPath) { + self.googleServiceInfo = [[NSDictionary alloc] initWithContentsOfFile:plistPath]; + } } - (void)testSignOut { @@ -44,7 +52,7 @@ - (void)testSignOut { } - (void)testDisconnect { - [[self.mockSignIn stub] disconnectWithCallback:[OCMArg invokeBlockWithArgs:[NSNull null], nil]]; + [[self.mockSignIn stub] disconnectWithCompletion:[OCMArg invokeBlockWithArgs:[NSNull null], nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin disconnectWithCompletion:^(FlutterError *error) { @@ -58,7 +66,7 @@ - (void)testDisconnectIgnoresError { NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain code:kGIDSignInErrorCodeHasNoAuthInKeychain userInfo:nil]; - [[self.mockSignIn stub] disconnectWithCallback:[OCMArg invokeBlockWithArgs:error, nil]]; + [[self.mockSignIn stub] disconnectWithCompletion:[OCMArg invokeBlockWithArgs:error, nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin disconnectWithCompletion:^(FlutterError *error) { @@ -70,7 +78,7 @@ - (void)testDisconnectIgnoresError { #pragma mark - Init -- (void)testInitNoClientIdError { +- (void)testInitNoClientIdNoError { // Init plugin without GoogleService-Info.plist. self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.mockSignIn withGoogleServiceProperties:nil]; @@ -83,10 +91,12 @@ - (void)testInitNoClientIdError { FlutterError *error; [self.plugin initializeSignInWithParameters:params error:&error]; - XCTAssertEqualObjects(error.code, @"missing-config"); + XCTAssertNil(error); } - (void)testInitGoogleServiceInfoPlist { + self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.mockSignIn + withGoogleServiceProperties:self.googleServiceInfo]; FSIInitParams *params = [FSIInitParams makeWithScopes:@[] hostedDomain:@"example.com" clientId:nil @@ -100,19 +110,17 @@ - (void)testInitGoogleServiceInfoPlist { [self.plugin signInWithCompletion:^(FSIUserData *user, FlutterError *error){ }]; OCMVerify([self.mockSignIn - signInWithConfiguration:[OCMArg checkWithBlock:^BOOL(GIDConfiguration *configuration) { - // Set in example app GoogleService-Info.plist. - return - [configuration.hostedDomain isEqualToString:@"example.com"] && - [configuration.clientID - isEqualToString: - @"479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com"] && - [configuration.serverClientID isEqualToString:@"YOUR_SERVER_CLIENT_ID"]; - }] - presentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]] - hint:nil - additionalScopes:OCMOCK_ANY - callback:OCMOCK_ANY]); + signInWithPresentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]] + hint:nil + additionalScopes:OCMOCK_ANY + completion:OCMOCK_ANY]); + + XCTAssertEqualObjects(self.plugin.configuration.hostedDomain, @"example.com"); + // Set in example app GoogleService-Info.plist. + XCTAssertEqualObjects( + self.plugin.configuration.clientID, + @"479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com"); + XCTAssertEqualObjects(self.plugin.configuration.serverClientID, @"YOUR_SERVER_CLIENT_ID"); } - (void)testInitDynamicClientIdNullDomain { @@ -133,17 +141,19 @@ - (void)testInitDynamicClientIdNullDomain { [self.plugin signInWithCompletion:^(FSIUserData *user, FlutterError *error){ }]; OCMVerify([self.mockSignIn - signInWithConfiguration:[OCMArg checkWithBlock:^BOOL(GIDConfiguration *configuration) { - return configuration.hostedDomain == nil && - [configuration.clientID isEqualToString:@"mockClientId"]; - }] - presentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]] - hint:nil - additionalScopes:OCMOCK_ANY - callback:OCMOCK_ANY]); + signInWithPresentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]] + hint:nil + additionalScopes:OCMOCK_ANY + completion:OCMOCK_ANY]); + + XCTAssertEqualObjects(self.plugin.configuration.hostedDomain, nil); + XCTAssertEqualObjects(self.plugin.configuration.clientID, @"mockClientId"); + XCTAssertEqualObjects(self.plugin.configuration.serverClientID, nil); } - (void)testInitDynamicServerClientIdNullDomain { + self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.mockSignIn + withGoogleServiceProperties:self.googleServiceInfo]; FSIInitParams *params = [FSIInitParams makeWithScopes:@[] hostedDomain:nil clientId:nil @@ -156,14 +166,36 @@ - (void)testInitDynamicServerClientIdNullDomain { [self.plugin signInWithCompletion:^(FSIUserData *user, FlutterError *error){ }]; 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]); + signInWithPresentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]] + hint:nil + additionalScopes:OCMOCK_ANY + completion:OCMOCK_ANY]); + + XCTAssertEqualObjects(self.plugin.configuration.hostedDomain, nil); + // Set in example app GoogleService-Info.plist. + XCTAssertEqualObjects( + self.plugin.configuration.clientID, + @"479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com"); + XCTAssertEqualObjects(self.plugin.configuration.serverClientID, @"mockServerClientId"); +} + +- (void)testInitInfoPlist { + FSIInitParams *params = [FSIInitParams makeWithScopes:@[ @"scope1" ] + hostedDomain:@"example.com" + clientId:nil + serverClientId:nil]; + + FlutterError *error; + self.plugin = [[FLTGoogleSignInPlugin alloc] init]; + [self.plugin initializeSignInWithParameters:params error:&error]; + XCTAssertNil(error); + XCTAssertNil(self.plugin.configuration); + XCTAssertNotNil(self.plugin.requestedScopes); + // Set in example app Info.plist. + XCTAssertEqualObjects( + self.plugin.signIn.configuration.clientID, + @"479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com"); + XCTAssertEqualObjects(self.plugin.signIn.configuration.serverClientID, @"YOUR_SERVER_CLIENT_ID"); } #pragma mark - Is signed in @@ -193,7 +225,8 @@ - (void)testSignInSilently { OCMStub([mockUser userID]).andReturn(@"mockID"); [[self.mockSignIn stub] - restorePreviousSignInWithCallback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; + restorePreviousSignInWithCompletion:[OCMArg + invokeBlockWithArgs:mockUser, [NSNull null], nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin signInSilentlyWithCompletion:^(FSIUserData *user, FlutterError *error) { @@ -215,7 +248,7 @@ - (void)testSignInSilentlyWithError { userInfo:nil]; [[self.mockSignIn stub] - restorePreviousSignInWithCallback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; + restorePreviousSignInWithCompletion:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin signInSilentlyWithCompletion:^(FSIUserData *user, FlutterError *error) { @@ -229,6 +262,8 @@ - (void)testSignInSilentlyWithError { #pragma mark - Sign in - (void)testSignIn { + self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.mockSignIn + withGoogleServiceProperties:self.googleServiceInfo]; id mockUser = OCMClassMock([GIDGoogleUser class]); id mockUserProfile = OCMClassMock([GIDProfileData class]); OCMStub([mockUserProfile name]).andReturn(@"mockDisplay"); @@ -239,18 +274,17 @@ - (void)testSignIn { OCMStub([mockUser profile]).andReturn(mockUserProfile); OCMStub([mockUser userID]).andReturn(@"mockID"); - OCMStub([mockUser serverAuthCode]).andReturn(@"mockAuthCode"); + + id mockSignInResult = OCMClassMock([GIDSignInResult class]); + OCMStub([mockSignInResult user]).andReturn(mockUser); + OCMStub([mockSignInResult serverAuthCode]).andReturn(@"mockAuthCode"); [[self.mockSignIn expect] - signInWithConfiguration:[OCMArg checkWithBlock:^BOOL(GIDConfiguration *configuration) { - return [configuration.clientID - isEqualToString: - @"479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com"]; - }] - presentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]] - hint:nil - additionalScopes:@[] - callback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; + signInWithPresentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]] + hint:nil + additionalScopes:@[] + completion:[OCMArg invokeBlockWithArgs:mockSignInResult, + [NSNull null], nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin signInWithCompletion:^(FSIUserData *user, FlutterError *error) { @@ -264,6 +298,11 @@ - (void)testSignIn { }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; + // Set in example app GoogleService-Info.plist. + XCTAssertEqualObjects( + self.plugin.configuration.clientID, + @"479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com"); + OCMVerifyAll(self.mockSignIn); } @@ -278,16 +317,18 @@ - (void)testSignInWithInitializedScopes { id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([mockUser userID]).andReturn(@"mockID"); + id mockSignInResult = OCMClassMock([GIDSignInResult class]); + OCMStub([mockSignInResult user]).andReturn(mockUser); [[self.mockSignIn expect] - signInWithConfiguration:OCMOCK_ANY - presentingViewController:OCMOCK_ANY - hint:nil - additionalScopes:[OCMArg checkWithBlock:^BOOL(NSArray *scopes) { - return [[NSSet setWithArray:scopes] - isEqualToSet:[NSSet setWithObjects:@"initial1", @"initial2", nil]]; - }] - callback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; + signInWithPresentingViewController:OCMOCK_ANY + hint:nil + additionalScopes:[OCMArg checkWithBlock:^BOOL(NSArray *scopes) { + return [[NSSet setWithArray:scopes] + isEqualToSet:[NSSet setWithObjects:@"initial1", @"initial2", nil]]; + }] + completion:[OCMArg invokeBlockWithArgs:mockSignInResult, + [NSNull null], nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin signInWithCompletion:^(FSIUserData *user, FlutterError *error) { @@ -303,20 +344,22 @@ - (void)testSignInWithInitializedScopes { - (void)testSignInAlreadyGranted { id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([mockUser userID]).andReturn(@"mockID"); + id mockSignInResult = OCMClassMock([GIDSignInResult class]); + OCMStub([mockSignInResult user]).andReturn(mockUser); [[self.mockSignIn stub] - signInWithConfiguration:OCMOCK_ANY - presentingViewController:OCMOCK_ANY - hint:nil - additionalScopes:OCMOCK_ANY - callback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; + signInWithPresentingViewController:OCMOCK_ANY + hint:nil + additionalScopes:OCMOCK_ANY + completion:[OCMArg invokeBlockWithArgs:mockSignInResult, + [NSNull null], nil]]; NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain code:kGIDSignInErrorCodeScopesAlreadyGranted userInfo:nil]; - [[self.mockSignIn stub] addScopes:OCMOCK_ANY - presentingViewController:OCMOCK_ANY - callback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; + [[self.mockSignIn currentUser] addScopes:OCMOCK_ANY + presentingViewController:OCMOCK_ANY + completion:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin signInWithCompletion:^(FSIUserData *user, FlutterError *error) { @@ -332,11 +375,10 @@ - (void)testSignInError { code:kGIDSignInErrorCodeCanceled userInfo:nil]; [[self.mockSignIn stub] - signInWithConfiguration:OCMOCK_ANY - presentingViewController:OCMOCK_ANY - hint:nil - additionalScopes:OCMOCK_ANY - callback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; + signInWithPresentingViewController:OCMOCK_ANY + hint:nil + additionalScopes:OCMOCK_ANY + completion:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin signInWithCompletion:^(FSIUserData *user, FlutterError *error) { @@ -348,11 +390,10 @@ - (void)testSignInError { } - (void)testSignInException { - OCMExpect([self.mockSignIn signInWithConfiguration:OCMOCK_ANY - presentingViewController:OCMOCK_ANY - hint:OCMOCK_ANY - additionalScopes:OCMOCK_ANY - callback:OCMOCK_ANY]) + OCMExpect([self.mockSignIn signInWithPresentingViewController:OCMOCK_ANY + hint:OCMOCK_ANY + additionalScopes:OCMOCK_ANY + completion:OCMOCK_ANY]) .andThrow([NSException exceptionWithName:@"MockName" reason:@"MockReason" userInfo:nil]); __block FlutterError *error; @@ -371,14 +412,20 @@ - (void)testSignInException { - (void)testGetTokens { id mockUser = OCMClassMock([GIDGoogleUser class]); - OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); + id mockUserResponse = OCMClassMock([GIDGoogleUser class]); + + id mockIdToken = OCMClassMock([GIDToken class]); + OCMStub([mockIdToken tokenString]).andReturn(@"mockIdToken"); + OCMStub([mockUserResponse idToken]).andReturn(mockIdToken); - id mockAuthentication = OCMClassMock([GIDAuthentication class]); - OCMStub([mockAuthentication idToken]).andReturn(@"mockIdToken"); - OCMStub([mockAuthentication accessToken]).andReturn(@"mockAccessToken"); - [[mockAuthentication stub] - doWithFreshTokens:[OCMArg invokeBlockWithArgs:mockAuthentication, [NSNull null], nil]]; - OCMStub([mockUser authentication]).andReturn(mockAuthentication); + id mockAccessToken = OCMClassMock([GIDToken class]); + OCMStub([mockAccessToken tokenString]).andReturn(@"mockAccessToken"); + OCMStub([mockUserResponse accessToken]).andReturn(mockAccessToken); + + [[mockUser stub] + refreshTokensIfNeededWithCompletion:[OCMArg invokeBlockWithArgs:mockUserResponse, + [NSNull null], nil]]; + OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin getAccessTokenWithCompletion:^(FSITokenData *token, FlutterError *error) { @@ -394,13 +441,11 @@ - (void)testGetTokensNoAuthKeychainError { id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); - id mockAuthentication = OCMClassMock([GIDAuthentication class]); NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain code:kGIDSignInErrorCodeHasNoAuthInKeychain userInfo:nil]; - [[mockAuthentication stub] - doWithFreshTokens:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; - OCMStub([mockUser authentication]).andReturn(mockAuthentication); + [[mockUser stub] + refreshTokensIfNeededWithCompletion:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin getAccessTokenWithCompletion:^(FSITokenData *token, FlutterError *error) { @@ -416,13 +461,11 @@ - (void)testGetTokensCancelledError { id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); - id mockAuthentication = OCMClassMock([GIDAuthentication class]); NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain code:kGIDSignInErrorCodeCanceled userInfo:nil]; - [[mockAuthentication stub] - doWithFreshTokens:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; - OCMStub([mockUser authentication]).andReturn(mockAuthentication); + [[mockUser stub] + refreshTokensIfNeededWithCompletion:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin getAccessTokenWithCompletion:^(FSITokenData *token, FlutterError *error) { @@ -438,11 +481,9 @@ - (void)testGetTokensURLError { id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); - id mockAuthentication = OCMClassMock([GIDAuthentication class]); NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:nil]; - [[mockAuthentication stub] - doWithFreshTokens:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; - OCMStub([mockUser authentication]).andReturn(mockAuthentication); + [[mockUser stub] + refreshTokensIfNeededWithCompletion:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin getAccessTokenWithCompletion:^(FSITokenData *token, FlutterError *error) { @@ -458,11 +499,9 @@ - (void)testGetTokensUnknownError { id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); - id mockAuthentication = OCMClassMock([GIDAuthentication class]); NSError *error = [NSError errorWithDomain:@"BogusDomain" code:42 userInfo:nil]; - [[mockAuthentication stub] - doWithFreshTokens:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; - OCMStub([mockUser authentication]).andReturn(mockAuthentication); + [[mockUser stub] + refreshTokensIfNeededWithCompletion:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin getAccessTokenWithCompletion:^(FSITokenData *token, FlutterError *error) { @@ -477,13 +516,6 @@ - (void)testGetTokensUnknownError { #pragma mark - Request scopes - (void)testRequestScopesResultErrorIfNotSignedIn { - NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain - code:kGIDSignInErrorCodeNoCurrentUser - userInfo:nil]; - [[self.mockSignIn stub] addScopes:@[ @"mockScope1" ] - presentingViewController:OCMOCK_ANY - callback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; - XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin requestScopes:@[ @"mockScope1" ] completion:^(NSNumber *success, FlutterError *error) { @@ -495,12 +527,15 @@ - (void)testRequestScopesResultErrorIfNotSignedIn { } - (void)testRequestScopesIfNoMissingScope { + id mockUser = OCMClassMock([GIDGoogleUser class]); + OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); + NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain code:kGIDSignInErrorCodeScopesAlreadyGranted userInfo:nil]; - [[self.mockSignIn stub] addScopes:@[ @"mockScope1" ] - presentingViewController:OCMOCK_ANY - callback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; + [[mockUser stub] addScopes:@[ @"mockScope1" ] + presentingViewController:OCMOCK_ANY + completion:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin requestScopes:@[ @"mockScope1" ] @@ -512,11 +547,35 @@ - (void)testRequestScopesIfNoMissingScope { [self waitForExpectationsWithTimeout:5.0 handler:nil]; } +- (void)testRequestScopesResultErrorIfMismatchingUser { + id mockUser = OCMClassMock([GIDGoogleUser class]); + OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); + + NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain + code:kGIDSignInErrorCodeMismatchWithCurrentUser + userInfo:nil]; + [[mockUser stub] addScopes:@[ @"mockScope1" ] + presentingViewController:OCMOCK_ANY + completion:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; + [self.plugin requestScopes:@[ @"mockScope1" ] + completion:^(NSNumber *success, FlutterError *error) { + XCTAssertNil(success); + XCTAssertEqualObjects(error.code, @"mismatch_user"); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + - (void)testRequestScopesWithUnknownError { + id mockUser = OCMClassMock([GIDGoogleUser class]); + OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); + NSError *error = [NSError errorWithDomain:@"BogusDomain" code:42 userInfo:nil]; - [[self.mockSignIn stub] addScopes:@[ @"mockScope1" ] - presentingViewController:OCMOCK_ANY - callback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; + [[mockUser stub] addScopes:@[ @"mockScope1" ] + presentingViewController:OCMOCK_ANY + completion:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin requestScopes:@[ @"mockScope1" ] @@ -529,7 +588,10 @@ - (void)testRequestScopesWithUnknownError { } - (void)testRequestScopesException { - OCMExpect([self.mockSignIn addScopes:@[] presentingViewController:OCMOCK_ANY callback:OCMOCK_ANY]) + id mockUser = OCMClassMock([GIDGoogleUser class]); + OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); + + OCMExpect([mockUser addScopes:@[] presentingViewController:OCMOCK_ANY completion:OCMOCK_ANY]) .andThrow([NSException exceptionWithName:@"MockName" reason:@"MockReason" userInfo:nil]); [self.plugin requestScopes:@[] @@ -542,16 +604,18 @@ - (void)testRequestScopesException { } - (void)testRequestScopesReturnsFalseIfOnlySubsetGranted { - GIDGoogleUser *mockUser = OCMClassMock([GIDGoogleUser class]); + id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); NSArray *requestedScopes = @[ @"mockScope1", @"mockScope2" ]; // Only grant one of the two requested scopes. - OCMStub(mockUser.grantedScopes).andReturn(@[ @"mockScope1" ]); + id mockSignInResult = OCMClassMock([GIDSignInResult class]); + OCMStub([mockUser grantedScopes]).andReturn(@[ @"mockScope1" ]); + OCMStub([mockSignInResult user]).andReturn(mockUser); - [[self.mockSignIn stub] addScopes:requestedScopes - presentingViewController:OCMOCK_ANY - callback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; + [[mockUser stub] addScopes:requestedScopes + presentingViewController:OCMOCK_ANY + completion:[OCMArg invokeBlockWithArgs:mockSignInResult, [NSNull null], nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin requestScopes:requestedScopes @@ -564,6 +628,9 @@ - (void)testRequestScopesReturnsFalseIfOnlySubsetGranted { } - (void)testRequestsInitializedScopes { + id mockUser = OCMClassMock([GIDGoogleUser class]); + OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); + FSIInitParams *params = [FSIInitParams makeWithScopes:@[ @"initial1", @"initial2" ] hostedDomain:nil clientId:nil @@ -580,27 +647,28 @@ - (void)testRequestsInitializedScopes { }]; // All four scopes are requested. - [[self.mockSignIn verify] - addScopes:[OCMArg checkWithBlock:^BOOL(NSArray *scopes) { + [[mockUser verify] addScopes:[OCMArg checkWithBlock:^BOOL(NSArray *scopes) { return [[NSSet setWithArray:scopes] isEqualToSet:[NSSet setWithObjects:@"initial1", @"initial2", @"addScope1", @"addScope2", nil]]; }] presentingViewController:OCMOCK_ANY - callback:OCMOCK_ANY]; + completion:OCMOCK_ANY]; } - (void)testRequestScopesReturnsTrueIfGranted { - GIDGoogleUser *mockUser = OCMClassMock([GIDGoogleUser class]); + id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); NSArray *requestedScopes = @[ @"mockScope1", @"mockScope2" ]; // Grant both of the requested scopes. - OCMStub(mockUser.grantedScopes).andReturn(requestedScopes); + id mockSignInResult = OCMClassMock([GIDSignInResult class]); + OCMStub([mockUser grantedScopes]).andReturn(requestedScopes); + OCMStub([mockSignInResult user]).andReturn(mockUser); - [[self.mockSignIn stub] addScopes:requestedScopes - presentingViewController:OCMOCK_ANY - callback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; + [[mockUser stub] addScopes:requestedScopes + presentingViewController:OCMOCK_ANY + completion:[OCMArg invokeBlockWithArgs:mockSignInResult, [NSNull null], nil]]; XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"]; [self.plugin requestScopes:requestedScopes diff --git a/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.h b/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.h index ec5403f0d3d..503b6a4a32e 100644 --- a/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.h +++ b/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.h @@ -7,4 +7,5 @@ #import "messages.g.h" @interface FLTGoogleSignInPlugin : NSObject + @end diff --git a/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.m b/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.m index 193c15e1ee3..2ccc4670c5f 100644 --- a/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.m +++ b/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.m @@ -47,20 +47,6 @@ @interface FLTGoogleSignInPlugin () -// Configuration wrapping Google Cloud Console, Google Apps, OpenID, -// and other initialization metadata. -@property(strong) GIDConfiguration *configuration; - -// Permissions requested during at sign in "init" method call -// unioned with scopes requested later with incremental authorization -// "requestScopes" method call. -// The "email" and "profile" base scopes are always implicitly requested. -@property(copy) NSSet *requestedScopes; - -// Instance used to manage Google Sign In authentication including -// sign in, sign out, and requesting additional scopes. -@property(strong, readonly) GIDSignIn *signIn; - // The contents of GoogleService-Info.plist, if it exists. @property(strong, nullable) NSDictionary *googleServiceProperties; @@ -113,21 +99,17 @@ - (void)initializeSignInWithParameters:(nonnull FSIInitParams *)params GIDConfiguration *configuration = [self configurationWithClientIdArgument:params.clientId serverClientIdArgument:params.serverClientId hostedDomainArgument:params.hostedDomain]; + self.requestedScopes = [NSSet setWithArray:params.scopes]; if (configuration != nil) { - self.requestedScopes = [NSSet setWithArray:params.scopes]; self.configuration = configuration; - } else { - *error = [FlutterError errorWithCode:@"missing-config" - message:@"GoogleService-Info.plist file not found and clientId " - @"was not provided programmatically." - details:nil]; } } - (void)signInSilentlyWithCompletion:(nonnull void (^)(FSIUserData *_Nullable, FlutterError *_Nullable))completion { - [self.signIn restorePreviousSignInWithCallback:^(GIDGoogleUser *user, NSError *error) { - [self didSignInForUser:user withCompletion:completion error:error]; + [self.signIn restorePreviousSignInWithCompletion:^(GIDGoogleUser *_Nullable user, + NSError *_Nullable error) { + [self didSignInForUser:user withServerAuthCode:nil completion:completion error:error]; }]; } @@ -139,19 +121,36 @@ - (nullable NSNumber *)isSignedInWithError: - (void)signInWithCompletion:(nonnull void (^)(FSIUserData *_Nullable, FlutterError *_Nullable))completion { @try { - GIDConfiguration *configuration = self.configuration - ?: [self configurationWithClientIdArgument:nil - serverClientIdArgument:nil - hostedDomainArgument:nil]; - [self.signIn signInWithConfiguration:configuration - presentingViewController:[self topViewController] - hint:nil - additionalScopes:self.requestedScopes.allObjects - callback:^(GIDGoogleUser *user, NSError *error) { - [self didSignInForUser:user - withCompletion:completion - error:error]; - }]; + // If the configuration settings are passed from the Dart API, use those. + // Otherwise, use settings from the GoogleService-Info.plist if available. + // If neither are available, do not set the configuration - GIDSignIn will automatically use + // settings from the Info.plist (which is the recommended method). + if (!self.configuration && self.googleServiceProperties) { + self.configuration = [self configurationWithClientIdArgument:nil + serverClientIdArgument:nil + hostedDomainArgument:nil]; + } + if (self.configuration) { + self.signIn.configuration = self.configuration; + } + + [self.signIn signInWithPresentingViewController:[self topViewController] + hint:nil + additionalScopes:self.requestedScopes.allObjects + completion:^(GIDSignInResult *_Nullable signInResult, + NSError *_Nullable error) { + GIDGoogleUser *user; + NSString *serverAuthCode; + if (signInResult) { + user = signInResult.user; + serverAuthCode = signInResult.serverAuthCode; + } + + [self didSignInForUser:user + withServerAuthCode:serverAuthCode + completion:completion + error:error]; + }]; } @catch (NSException *e) { completion(nil, [FlutterError errorWithCode:@"google_sign_in" message:e.reason details:e.name]); [e raise]; @@ -161,13 +160,13 @@ - (void)signInWithCompletion:(nonnull void (^)(FSIUserData *_Nullable, - (void)getAccessTokenWithCompletion:(nonnull void (^)(FSITokenData *_Nullable, FlutterError *_Nullable))completion { GIDGoogleUser *currentUser = self.signIn.currentUser; - GIDAuthentication *auth = currentUser.authentication; - [auth doWithFreshTokens:^void(GIDAuthentication *authentication, NSError *error) { + [currentUser refreshTokensIfNeededWithCompletion:^(GIDGoogleUser *_Nullable user, + NSError *_Nullable error) { if (error) { completion(nil, getFlutterError(error)); } else { - completion([FSITokenData makeWithIdToken:authentication.idToken - accessToken:authentication.accessToken], + completion([FSITokenData makeWithIdToken:user.idToken.tokenString + accessToken:user.accessToken.tokenString], nil); } }]; @@ -177,7 +176,7 @@ - (void)signOutWithError:(FlutterError *_Nullable *_Nonnull)error; { [self.signIn signOut]; } - (void)disconnectWithCompletion:(nonnull void (^)(FlutterError *_Nullable))completion { - [self.signIn disconnectWithCallback:^(NSError *error) { + [self.signIn disconnectWithCompletion:^(NSError *_Nullable error) { // TODO(stuartmorgan): This preserves the pre-Pigeon-migration behavior, but it's unclear why // 'error' is being ignored here. completion(nil); @@ -190,31 +189,38 @@ - (void)requestScopes:(nonnull NSArray *)scopes NSSet *requestedScopes = self.requestedScopes; @try { - [self.signIn addScopes:requestedScopes.allObjects + GIDGoogleUser *currentUser = self.signIn.currentUser; + if (currentUser == nil) { + completion(nil, [FlutterError errorWithCode:@"sign_in_required" + message:@"No account to grant scopes." + details:nil]); + } + [currentUser addScopes:requestedScopes.allObjects presentingViewController:[self topViewController] - callback:^(GIDGoogleUser *addedScopeUser, NSError *addedScopeError) { - BOOL granted = NO; - FlutterError *error = nil; - if ([addedScopeError.domain isEqualToString:kGIDSignInErrorDomain] && - addedScopeError.code == kGIDSignInErrorCodeNoCurrentUser) { - error = [FlutterError errorWithCode:@"sign_in_required" - message:@"No account to grant scopes." - details:nil]; - } else if ([addedScopeError.domain - isEqualToString:kGIDSignInErrorDomain] && - addedScopeError.code == - kGIDSignInErrorCodeScopesAlreadyGranted) { - // Scopes already granted, report success. - granted = YES; - } else if (addedScopeUser == nil) { - granted = NO; - } else { - NSSet *grantedScopes = - [NSSet setWithArray:addedScopeUser.grantedScopes]; - granted = [requestedScopes isSubsetOfSet:grantedScopes]; - } - completion(error == nil ? @(granted) : nil, error); - }]; + completion:^(GIDSignInResult *_Nullable signInResult, + NSError *_Nullable addedScopeError) { + BOOL granted = NO; + FlutterError *error = nil; + + if ([addedScopeError.domain isEqualToString:kGIDSignInErrorDomain] && + addedScopeError.code == kGIDSignInErrorCodeMismatchWithCurrentUser) { + error = + [FlutterError errorWithCode:@"mismatch_user" + message:@"There is an operation on a previous " + @"user. Try signing in again." + details:nil]; + } else if ([addedScopeError.domain isEqualToString:kGIDSignInErrorDomain] && + addedScopeError.code == + kGIDSignInErrorCodeScopesAlreadyGranted) { + // Scopes already granted, report success. + granted = YES; + } else if (signInResult.user) { + NSSet *grantedScopes = + [NSSet setWithArray:signInResult.user.grantedScopes]; + granted = [requestedScopes isSubsetOfSet:grantedScopes]; + } + completion(error == nil ? @(granted) : nil, error); + }]; } @catch (NSException *e) { completion(nil, [FlutterError errorWithCode:@"request_scopes" message:e.reason details:e.name]); } @@ -265,7 +271,8 @@ - (GIDConfiguration *)configurationWithClientIdArgument:(id)clientIDArg } - (void)didSignInForUser:(GIDGoogleUser *)user - withCompletion:(nonnull void (^)(FSIUserData *_Nullable, + withServerAuthCode:(NSString *_Nullable)serverAuthCode + completion:(nonnull void (^)(FSIUserData *_Nullable, FlutterError *_Nullable))completion error:(NSError *)error { if (error != nil) { @@ -277,11 +284,16 @@ - (void)didSignInForUser:(GIDGoogleUser *)user // Placeholder that will be replaced by on the Dart side based on screen size. photoUrl = [user.profile imageURLWithDimension:1337]; } + NSString *idToken; + if (user.idToken) { + idToken = user.idToken.tokenString; + } completion([FSIUserData makeWithDisplayName:user.profile.name email:user.profile.email userId:user.userID photoUrl:[photoUrl absoluteString] - serverAuthCode:user.serverAuthCode], + serverAuthCode:serverAuthCode + idToken:idToken], nil); } } diff --git a/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin_Test.h b/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin_Test.h index 17ddb7f616b..fc18c9b9e51 100644 --- a/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin_Test.h +++ b/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin_Test.h @@ -6,6 +6,8 @@ #import +#import + NS_ASSUME_NONNULL_BEGIN @class GIDSignIn; @@ -13,6 +15,20 @@ NS_ASSUME_NONNULL_BEGIN /// Methods exposed for unit testing. @interface FLTGoogleSignInPlugin () +// Configuration wrapping Google Cloud Console, Google Apps, OpenID, +// and other initialization metadata. +@property(strong) GIDConfiguration *configuration; + +// Permissions requested during at sign in "init" method call +// unioned with scopes requested later with incremental authorization +// "requestScopes" method call. +// The "email" and "profile" base scopes are always implicitly requested. +@property(copy) NSSet *requestedScopes; + +// Instance used to manage Google Sign In authentication including +// sign in, sign out, and requesting additional scopes. +@property(strong, readonly) GIDSignIn *signIn; + /// Inject @c GIDSignIn for testing. - (instancetype)initWithSignIn:(GIDSignIn *)signIn; diff --git a/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.h b/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.h index 37493aa26c6..745c1ec9180 100644 --- a/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.h +++ b/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.h @@ -43,12 +43,14 @@ NS_ASSUME_NONNULL_BEGIN email:(NSString *)email userId:(NSString *)userId photoUrl:(nullable NSString *)photoUrl - serverAuthCode:(nullable NSString *)serverAuthCode; + serverAuthCode:(nullable NSString *)serverAuthCode + idToken:(nullable NSString *)idToken; @property(nonatomic, copy, nullable) NSString *displayName; @property(nonatomic, copy) NSString *email; @property(nonatomic, copy) NSString *userId; @property(nonatomic, copy, nullable) NSString *photoUrl; @property(nonatomic, copy, nullable) NSString *serverAuthCode; +@property(nonatomic, copy, nullable) NSString *idToken; @end /// Pigeon version of GoogleSignInTokenData. diff --git a/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.m b/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.m index 4d617ca81f0..96d6b54232a 100644 --- a/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.m +++ b/packages/google_sign_in/google_sign_in_ios/ios/Classes/messages.g.m @@ -86,13 +86,15 @@ + (instancetype)makeWithDisplayName:(nullable NSString *)displayName email:(NSString *)email userId:(NSString *)userId photoUrl:(nullable NSString *)photoUrl - serverAuthCode:(nullable NSString *)serverAuthCode { + serverAuthCode:(nullable NSString *)serverAuthCode + idToken:(nullable NSString *)idToken { FSIUserData *pigeonResult = [[FSIUserData alloc] init]; pigeonResult.displayName = displayName; pigeonResult.email = email; pigeonResult.userId = userId; pigeonResult.photoUrl = photoUrl; pigeonResult.serverAuthCode = serverAuthCode; + pigeonResult.idToken = idToken; return pigeonResult; } + (FSIUserData *)fromList:(NSArray *)list { @@ -104,6 +106,7 @@ + (FSIUserData *)fromList:(NSArray *)list { NSAssert(pigeonResult.userId != nil, @""); pigeonResult.photoUrl = GetNullableObjectAtIndex(list, 3); pigeonResult.serverAuthCode = GetNullableObjectAtIndex(list, 4); + pigeonResult.idToken = GetNullableObjectAtIndex(list, 5); return pigeonResult; } + (nullable FSIUserData *)nullableFromList:(NSArray *)list { @@ -116,6 +119,7 @@ - (NSArray *)toList { (self.userId ?: [NSNull null]), (self.photoUrl ?: [NSNull null]), (self.serverAuthCode ?: [NSNull null]), + (self.idToken ?: [NSNull null]), ]; } @end diff --git a/packages/google_sign_in/google_sign_in_ios/ios/google_sign_in_ios.podspec b/packages/google_sign_in/google_sign_in_ios/ios/google_sign_in_ios.podspec index 7739005bdd4..9658184c883 100644 --- a/packages/google_sign_in/google_sign_in_ios/ios/google_sign_in_ios.podspec +++ b/packages/google_sign_in/google_sign_in_ios/ios/google_sign_in_ios.podspec @@ -16,7 +16,7 @@ Enables Google Sign-In in Flutter apps. s.public_header_files = 'Classes/**/*.h' s.module_map = 'Classes/FLTGoogleSignInPlugin.modulemap' s.dependency 'Flutter' - s.dependency 'GoogleSignIn', '~> 6.2' + s.dependency 'GoogleSignIn', '~> 7.0' s.static_framework = true s.platform = :ios, '11.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } diff --git a/packages/google_sign_in/google_sign_in_ios/lib/google_sign_in_ios.dart b/packages/google_sign_in/google_sign_in_ios/lib/google_sign_in_ios.dart index 73113abb49a..9c589501114 100644 --- a/packages/google_sign_in/google_sign_in_ios/lib/google_sign_in_ios.dart +++ b/packages/google_sign_in/google_sign_in_ios/lib/google_sign_in_ios.dart @@ -103,6 +103,7 @@ class GoogleSignInIOS extends GoogleSignInPlatform { displayName: data.displayName, photoUrl: data.photoUrl, serverAuthCode: data.serverAuthCode, + idToken: data.idToken, ); } diff --git a/packages/google_sign_in/google_sign_in_ios/lib/src/messages.g.dart b/packages/google_sign_in/google_sign_in_ios/lib/src/messages.g.dart index 78fa37259be..21dd2ebf8e2 100644 --- a/packages/google_sign_in/google_sign_in_ios/lib/src/messages.g.dart +++ b/packages/google_sign_in/google_sign_in_ios/lib/src/messages.g.dart @@ -60,6 +60,7 @@ class UserData { required this.userId, this.photoUrl, this.serverAuthCode, + this.idToken, }); String? displayName; @@ -72,6 +73,8 @@ class UserData { String? serverAuthCode; + String? idToken; + Object encode() { return [ displayName, @@ -79,6 +82,7 @@ class UserData { userId, photoUrl, serverAuthCode, + idToken, ]; } @@ -90,6 +94,7 @@ class UserData { userId: result[2]! as String, photoUrl: result[3] as String?, serverAuthCode: result[4] as String?, + idToken: result[5] as String?, ); } } diff --git a/packages/google_sign_in/google_sign_in_ios/pigeons/messages.dart b/packages/google_sign_in/google_sign_in_ios/pigeons/messages.dart index f92f1d06c63..2baf9af82ad 100644 --- a/packages/google_sign_in/google_sign_in_ios/pigeons/messages.dart +++ b/packages/google_sign_in/google_sign_in_ios/pigeons/messages.dart @@ -43,6 +43,7 @@ class UserData { this.displayName, this.photoUrl, this.serverAuthCode, + this.idToken, }); final String? displayName; @@ -50,6 +51,7 @@ class UserData { final String userId; final String? photoUrl; final String? serverAuthCode; + final String? idToken; } /// Pigeon version of GoogleSignInTokenData. diff --git a/packages/google_sign_in/google_sign_in_ios/pubspec.yaml b/packages/google_sign_in/google_sign_in_ios/pubspec.yaml index c2e780e4ac4..d6a9e975fa9 100644 --- a/packages/google_sign_in/google_sign_in_ios/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: google_sign_in_ios description: iOS implementation of the google_sign_in plugin. repository: https://github.com/flutter/packages/tree/main/packages/google_sign_in/google_sign_in_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22 -version: 5.6.4 +version: 5.6.5 environment: sdk: ">=2.19.0 <4.0.0" @@ -36,6 +36,7 @@ topics: # The example deliberately includes limited-use secrets. false_secrets: - - /example/ios/Runner/GoogleService-Info.plist + - /example/ios/Runner/Info.plist + - /example/ios/RunnerTests/GoogleService-Info.plist - /example/ios/RunnerTests/GoogleSignInTests.m - /example/lib/main.dart diff --git a/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart b/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart index ace1a0fec30..d76bc978fc7 100644 --- a/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart +++ b/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart @@ -13,12 +13,12 @@ import 'package:mockito/mockito.dart'; import 'google_sign_in_ios_test.mocks.dart'; final GoogleSignInUserData _user = GoogleSignInUserData( - email: 'john.doe@gmail.com', - id: '8162538176523816253123', - photoUrl: 'https://lh5.googleusercontent.com/photo.jpg', - displayName: 'John Doe', - serverAuthCode: '789', -); + email: 'john.doe@gmail.com', + id: '8162538176523816253123', + photoUrl: 'https://lh5.googleusercontent.com/photo.jpg', + displayName: 'John Doe', + serverAuthCode: '789', + idToken: '123'); final GoogleSignInTokenData _token = GoogleSignInTokenData( idToken: '123', @@ -60,6 +60,7 @@ void main() { photoUrl: _user.photoUrl, displayName: _user.displayName, serverAuthCode: _user.serverAuthCode, + idToken: _user.idToken, )); final dynamic response = await googleSignIn.signInSilently(); @@ -82,6 +83,7 @@ void main() { photoUrl: _user.photoUrl, displayName: _user.displayName, serverAuthCode: _user.serverAuthCode, + idToken: _user.idToken, )); final dynamic response = await googleSignIn.signIn();