فهرست منبع

Merge branch 'main' into mdmathias/async-support

Peter Andrews 3 سال پیش
والد
کامیت
bd642fd475
35فایلهای تغییر یافته به همراه594 افزوده شده و 322 حذف شده
  1. 31 0
      .github/ISSUE_TEMPLATE/bug_report.md
  2. 1 0
      .github/ISSUE_TEMPLATE/config.yml
  3. 19 0
      .github/ISSUE_TEMPLATE/feature_request.md
  4. 1 1
      .github/workflows/builds.yml
  5. 42 0
      .github/workflows/integration_tests.yml
  6. 7 3
      .github/workflows/pr_notification.yml
  7. 3 1
      .github/workflows/push_notification.yml
  8. 62 0
      .github/workflows/scorecards.yml
  9. 2 34
      .github/workflows/tests.yml
  10. 7 0
      CHANGELOG.md
  11. 2 2
      GoogleSignIn.podspec
  12. 9 0
      GoogleSignIn/Sources/GIDAuthentication.m
  13. 94 96
      GoogleSignIn/Sources/GIDSignIn.m
  14. 3 0
      GoogleSignIn/Sources/GIDSignIn_Private.h
  15. 3 0
      GoogleSignIn/Sources/Public/GoogleSignIn/GIDAuthentication.h
  16. 3 3
      GoogleSignIn/Sources/Public/GoogleSignIn/GIDConfiguration.h
  17. 27 36
      GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h
  18. 3 0
      GoogleSignIn/Tests/Unit/GIDAuthenticationTest.m
  19. 16 4
      GoogleSignIn/Tests/Unit/GIDFakeMainBundle.h
  20. 42 33
      GoogleSignIn/Tests/Unit/GIDFakeMainBundle.m
  21. 1 2
      GoogleSignIn/Tests/Unit/GIDSignInCallbackSchemesTest.m
  22. 71 67
      GoogleSignIn/Tests/Unit/GIDSignInTest.m
  23. 2 1
      GoogleSignInSwift/Sources/GoogleSignInButtonBundleExtensions.swift
  24. 34 0
      GoogleSignInSwift/Tests/Unit/GoogleSignInButtonExtensionsTests.swift
  25. 1 1
      GoogleSignInSwiftSupport.podspec
  26. 2 2
      Package.swift
  27. 55 0
      README.md
  28. 3 0
      Samples/ObjC/SignInSample/SignInSample-Info.plist
  29. 2 2
      Samples/ObjC/SignInSample/Source/AppDelegate.m
  30. 5 17
      Samples/ObjC/SignInSample/Source/SignInViewController.m
  31. 15 4
      Samples/Swift/DaysUntilBirthday/DaysUntilBirthday.xcodeproj/project.pbxproj
  32. 19 0
      Samples/Swift/DaysUntilBirthday/README.md
  33. 1 13
      Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift
  34. 3 0
      Samples/Swift/DaysUntilBirthday/iOS/Info.plist
  35. 3 0
      Samples/Swift/DaysUntilBirthday/macOS/Info.plist

+ 31 - 0
.github/ISSUE_TEMPLATE/bug_report.md

@@ -0,0 +1,31 @@
+---
+name: Bug Report
+about: Submit a bug report if something isn't working as expected.
+title: ""
+labels: bug, triage
+assignees: ""
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Tap on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Environment**
+- Device: [ e.g. iPhone 13, MacBook Pro, etc ]
+- OS: [ e.g. iOS 15, macOS 11, etc ]
+- Browser: [ e.g. Safari, Chrome, etc ]
+
+**Additional context**
+Add any other context about the problem here.

+ 1 - 0
.github/ISSUE_TEMPLATE/config.yml

@@ -0,0 +1 @@
+blank_issues_enabled: false

+ 19 - 0
.github/ISSUE_TEMPLATE/feature_request.md

@@ -0,0 +1,19 @@
+---
+name: Feature Request
+about: Make a feature request if you have a suggestion for something new.
+title: ""
+labels: enhancement, triage
+assignees: ""
+---
+
+**Is your feature request related to a problem you're having? Please describe.**
+A clear and concise description of what the problem is.
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.

+ 1 - 1
.github/workflows/builds.yml

@@ -13,7 +13,7 @@ jobs:
         os: [macos-11, macos-12]
   
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
     - name: Archive for iOS
       run: |
         xcodebuild \

+ 42 - 0
.github/workflows/integration_tests.yml

@@ -0,0 +1,42 @@
+name: integration_tests
+
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+  workflow_dispatch:
+
+jobs:
+
+  swift-button-functional-test:
+    runs-on: macOS-12
+    # Don't run if triggered by a PR from a fork since our Secrets won't be provided to the runner.
+    if: "!github.event.pull_request.head.repo.fork"
+    defaults:
+      run:
+        working-directory: Samples/Swift/DaysUntilBirthday
+    steps:
+    - name: Checkout
+      uses: actions/checkout@v3
+    - name: Build test target for Google Sign-in button for Swift
+      run: |
+        xcodebuild \
+          -project DaysUntilBirthday.xcodeproj \
+          build-for-testing \
+          -scheme DaysUntilBirthday\ \(iOS\) \
+          -sdk iphonesimulator \
+          -destination 'platform=iOS Simulator,name=iPhone 11'
+    - name: Run test target for Google Sign-in button for Swift
+      env:
+        EMAIL_SECRET : ${{ secrets.EMAIL_SECRET }}
+        PASSWORD_SECRET : ${{ secrets.PASSWORD_SECRET }}
+      run: |
+        xcodebuild \
+          -project DaysUntilBirthday.xcodeproj \
+          test-without-building \
+          -scheme DaysUntilBirthday\ \(iOS\) \
+          -sdk iphonesimulator \
+          -destination 'platform=iOS Simulator,name=iPhone 11' \
+          EMAIL_SECRET=$EMAIL_SECRET \
+          PASSWORD_SECRET=$PASSWORD_SECRET

+ 7 - 3
.github/workflows/pr_notification.yml

@@ -10,10 +10,14 @@ jobs:
     steps:
     - name: Pull Request Details
       run: |
-        echo "Pull Request: ${{ github.event.pull_request.title }}"
+        echo "Pull Request: ${{ github.event.pull_request.number }}"
         echo "Author: ${{ github.event.pull_request.user.login }}"
 
     - name: Google Chat Notification
+      shell: bash
+      env:
+        TITLE: ${{ github.event.pull_request.title }}
+        LABELS: ${{ join(github.event.pull_request.labels.*.name, ', ') }}
       run: |
         curl --location --request POST '${{ secrets.WEBHOOK_URL }}' \
         --header 'Content-Type: application/json' \
@@ -36,7 +40,7 @@ jobs:
                     {
                       "keyValue": {
                         "topLabel": "Title",
-                        "content": "${{ github.event.pull_request.title }}"
+                        "content": "'"$TITLE"'"
                       }
                     },
                     {
@@ -66,7 +70,7 @@ jobs:
                     {
                       "keyValue": {
                         "topLabel": "Labels",
-                        "content": "- ${{ join(github.event.pull_request.labels.*.name, ', ') }}"
+                        "content": "- '"$LABELS"'"
                       }
                     },
                     {

+ 3 - 1
.github/workflows/push_notification.yml

@@ -8,6 +8,8 @@ on:
 jobs:
   notify-push-main:
     runs-on: ubuntu-latest
+    env:
+      COMMIT: ${{ github.event.head_commit.message }}
     steps:
     - name: Main Branch Push
       run: |
@@ -24,7 +26,7 @@ jobs:
             {
               "header": {
                 "title": "Push to main branch",
-                "subtitle": "${{ github.event.head_commit.message }}"
+                "subtitle": "'"$COMMIT"'"
               },
               "sections": [
                 {

+ 62 - 0
.github/workflows/scorecards.yml

@@ -0,0 +1,62 @@
+name: Scorecards supply-chain security
+on:
+  # Only the default branch is supported.
+  branch_protection_rule:
+  schedule:
+    - cron: '36 4 * * 3'
+  push:
+    branches: [ "main" ]
+
+# Declare default permissions as read only.
+permissions: read-all
+
+jobs:
+  analysis:
+    name: Scorecards analysis
+    runs-on: ubuntu-latest
+    permissions:
+      # Needed to upload the results to code-scanning dashboard.
+      security-events: write
+      # Used to receive a badge. (Upcoming feature)
+      id-token: write
+      # Needs for private repositories.
+      contents: read
+      actions: read
+    
+    steps:
+      - name: "Checkout code"
+        uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 # tag=v3.0.0
+        with:
+          persist-credentials: false
+
+      - name: "Run analysis"
+        uses: ossf/scorecard-action@3e15ea8318eee9b333819ec77a36aca8d39df13e # tag=v1.1.1
+        with:
+          results_file: results.sarif
+          results_format: sarif
+          # (Optional) Read-only PAT token. Uncomment the `repo_token` line below if:
+          # - you want to enable the Branch-Protection check on a *public* repository, or
+          # - you are installing Scorecards on a *private* repository
+          # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
+          # repo_token: ${{ secrets.SCORECARD_READ_TOKEN }}
+
+          # Publish the results for public repositories to enable scorecard badges. For more details, see
+          # https://github.com/ossf/scorecard-action#publishing-results. 
+          # For private repositories, `publish_results` will automatically be set to `false`, regardless 
+          # of the value entered here.
+          publish_results: true
+
+      # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
+      # format to the repository Actions tab.
+      - name: "Upload artifact"
+        uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # tag=v3.0.0
+        with:
+          name: SARIF file
+          path: results.sarif
+          retention-days: 5
+      
+      # Upload the results to GitHub's code scanning dashboard.
+      - name: "Upload to code-scanning"
+        uses: github/codeql-action/upload-sarif@5f532563584d71fdef14ee64d17bafb34f751ce5 # tag=v1.0.26
+        with:
+          sarif_file: results.sarif

+ 2 - 34
.github/workflows/tests.yml

@@ -5,8 +5,6 @@ on:
     branches:
       - main
   pull_request:
-    branches:
-      - main
   workflow_dispatch:
 
 jobs:
@@ -27,7 +25,7 @@ jobs:
           - podspec: GoogleSignInSwiftSupport.podspec
             includePodspecFlag: "--include-podspecs='GoogleSignIn.podspec'"
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
     - name: Update Bundler
       run: bundle update --bundler
     - name: Install Ruby gems with Bundler
@@ -50,7 +48,7 @@ jobs:
           - sdk: 'iphonesimulator'
             destination: '"platform=iOS Simulator,name=iPhone 11"'
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
     - name: Build unit test target
       run: |
         xcodebuild \
@@ -66,33 +64,3 @@ jobs:
           -destination ${{ matrix.destination }} \
           test-without-building
 
-  swift-button-functional-test:
-    if: ${{ false }} # Disable integration tests while we figure out OTAs
-    runs-on: macOS-latest
-    defaults:
-      run:
-        working-directory: Samples/Swift/DaysUntilBirthday
-    steps:
-    - name: Checkout
-      uses: actions/checkout@v2
-    - name: Build test target for Google Sign-in button for Swift
-      run: |
-        xcodebuild \
-          -project DaysUntilBirthday.xcodeproj \
-          build-for-testing \
-          -scheme DaysUntilBirthday\ \(iOS\) \
-          -sdk iphonesimulator \
-          -destination 'platform=iOS Simulator,name=iPhone 11'
-    - name: Run test target for Google Sign-in button for Swift
-      env:
-        EMAIL_SECRET : ${{ secrets.EMAIL_SECRET }}
-        PASSWORD_SECRET : ${{ secrets.PASSWORD_SECRET }}
-      run: |
-        xcodebuild \
-          -project DaysUntilBirthday.xcodeproj \
-          test-without-building \
-          -scheme DaysUntilBirthday\ \(iOS\) \
-          -sdk iphonesimulator \
-          -destination 'platform=iOS Simulator,name=iPhone 11' \
-          EMAIL_SECRET=$EMAIL_SECRET \
-          PASSWORD_SECRET=$PASSWORD_SECRET

+ 7 - 0
CHANGELOG.md

@@ -1,3 +1,10 @@
+# 6.2.4 (2022-9-13)
+- Updated the GTMSessionFetcher dependency to allow 2.x versions. ([#207](https://github.com/google/GoogleSignIn-iOS/pull/207))
+
+# 6.2.3 (2022-8-18)
+- Fix resource loading in GoogleSignInSwift with CocoaPods use_frameworks! ([#197](https://github.com/google/GoogleSignIn-iOS/pull/197))
+- Prevent build errors for GoogleSignInSwift in certain scenarios when using Swift Package Manager. ([#166](https://github.com/google/GoogleSignIn-iOS/pull/166))
+
 # 6.2.2 (2022-5-27)
 - Prevent build errors for GoogleSignInSwift when using Swift Package Manager. ([#157](https://github.com/google/GoogleSignIn-iOS/pull/157))
 - Prevent a build error on Xcode 12 and earlier. ([#158](https://github.com/google/GoogleSignIn-iOS/pull/158))

+ 2 - 2
GoogleSignIn.podspec

@@ -1,6 +1,6 @@
 Pod::Spec.new do |s|
   s.name             = 'GoogleSignIn'
-  s.version          = '6.2.2'
+  s.version          = '6.2.4'
   s.summary          = 'Enables iOS apps to sign in with Google.'
   s.description      = <<-DESC
 The Google Sign-In SDK allows users to sign in with their Google account from third-party apps.
@@ -34,7 +34,7 @@ The Google Sign-In SDK allows users to sign in with their Google account from th
   s.osx.framework = 'AppKit'
   s.dependency 'AppAuth', '~> 1.5'
   s.dependency 'GTMAppAuth', '~> 1.3'
-  s.dependency 'GTMSessionFetcher/Core', '~> 1.1'
+  s.dependency 'GTMSessionFetcher/Core', '>= 1.1', '< 3.0'
   s.resource_bundle = {
     'GoogleSignIn' => ['GoogleSignIn/Sources/{Resources,Strings}/*']
   }

+ 9 - 0
GoogleSignIn/Sources/GIDAuthentication.m

@@ -127,15 +127,21 @@ static NSString *const kNewIOSSystemName = @"iOS";
 
 @implementation GTMAppAuthFetcherAuthorizationWithEMMSupport
 
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-implementations"
 - (void)authorizeRequest:(nullable NSMutableURLRequest *)request
                 delegate:(id)delegate
        didFinishSelector:(SEL)sel {
+#pragma clang diagnostic pop
   GTMAppAuthFetcherAuthorizationEMMChainedDelegate *chainedDelegate =
       [[GTMAppAuthFetcherAuthorizationEMMChainedDelegate alloc] initWithDelegate:delegate
                                                                         selector:sel];
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
   [super authorizeRequest:request
                  delegate:chainedDelegate
         didFinishSelector:@selector(authentication:request:finishedWithError:)];
+#pragma clang diagnostic pop
 }
 
 - (void)authorizeRequest:(nullable NSMutableURLRequest *)request
@@ -206,7 +212,10 @@ static NSString *const kNewIOSSystemName = @"iOS";
 
 #pragma mark - Public methods
 
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
 - (id<GTMFetcherAuthorizationProtocol>)fetcherAuthorizer {
+#pragma clang diagnostic pop
 #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
   GTMAppAuthFetcherAuthorization *authorization = self.emmSupport ?
       [[GTMAppAuthFetcherAuthorizationWithEMMSupport alloc] initWithAuthState:_authState] :

+ 94 - 96
GoogleSignIn/Sources/GIDSignIn.m

@@ -135,6 +135,12 @@ static NSString *const kHostedDomainParameter = @"hd";
 // Minimum time to expiration for a restored access token.
 static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
 
+// Info.plist config keys
+static NSString *const kConfigClientIDKey = @"GIDClientID";
+static NSString *const kConfigServerClientIDKey = @"GIDServerClientID";
+static NSString *const kConfigHostedDomainKey = @"GIDHostedDomain";
+static NSString *const kConfigOpenIDRealmKey = @"GIDOpenIDRealm";
+
 // The callback queue used for authentication flow.
 @interface GIDAuthFlow : GIDCallbackQueue
 
@@ -208,18 +214,17 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
   GIDProfileData *profileData = [self profileDataWithIDToken:idToken];
 
   GIDGoogleUser *user = [[GIDGoogleUser alloc] initWithAuthState:authState profileData:profileData];
-  [self setCurrentUserWithKVO:user];
+  self.currentUser = user;
   return YES;
 }
 
 #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
 
-- (void)signInWithConfiguration:(GIDConfiguration *)configuration
-       presentingViewController:(UIViewController *)presentingViewController
-                           hint:(nullable NSString *)hint
-                     completion:(nullable GIDSignInCompletion)completion {
+- (void)signInWithPresentingViewController:(UIViewController *)presentingViewController
+                                      hint:(nullable NSString *)hint
+                                completion:(nullable GIDSignInCompletion)completion {
   GIDSignInInternalOptions *options =
-      [GIDSignInInternalOptions defaultOptionsWithConfiguration:configuration
+      [GIDSignInInternalOptions defaultOptionsWithConfiguration:_configuration
                                        presentingViewController:presentingViewController
                                                       loginHint:hint
                                                   addScopesFlow:NO
@@ -227,13 +232,12 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
   [self signInWithOptions:options];
 }
 
-- (void)signInWithConfiguration:(GIDConfiguration *)configuration
-       presentingViewController:(UIViewController *)presentingViewController
-                           hint:(nullable NSString *)hint
-               additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
-                     completion:(nullable GIDSignInCompletion)completion {
+- (void)signInWithPresentingViewController:(UIViewController *)presentingViewController
+                                      hint:(nullable NSString *)hint
+                          additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
+                                completion:(nullable GIDSignInCompletion)completion {
   GIDSignInInternalOptions *options =
-    [GIDSignInInternalOptions defaultOptionsWithConfiguration:configuration
+    [GIDSignInInternalOptions defaultOptionsWithConfiguration:_configuration
                                      presentingViewController:presentingViewController
                                                     loginHint:hint
                                                 addScopesFlow:NO
@@ -242,13 +246,11 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
   [self signInWithOptions:options];
 }
 
-- (void)signInWithConfiguration:(GIDConfiguration *)configuration
-       presentingViewController:(UIViewController *)presentingViewController
-                     completion:(nullable GIDSignInCompletion)completion {
-  [self signInWithConfiguration:configuration
-       presentingViewController:presentingViewController
-                           hint:nil
-                     completion:completion];
+- (void)signInWithPresentingViewController:(UIViewController *)presentingViewController
+                                completion:(nullable GIDSignInCompletion)completion {
+  [self signInWithPresentingViewController:presentingViewController
+                                      hint:nil
+                                completion:completion];
 }
 
 - (void)addScopes:(NSArray<NSString *> *)scopes
@@ -307,12 +309,11 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
 
 #elif TARGET_OS_OSX
 
-- (void)signInWithConfiguration:(GIDConfiguration *)configuration
-               presentingWindow:(NSWindow *)presentingWindow
-                           hint:(nullable NSString *)hint
-                     completion:(nullable GIDSignInCompletion)completion {
+- (void)signInWithPresentingWindow:(NSWindow *)presentingWindow
+                              hint:(nullable NSString *)hint
+                        completion:(nullable GIDSignInCompletion)completion {
   GIDSignInInternalOptions *options =
-      [GIDSignInInternalOptions defaultOptionsWithConfiguration:configuration
+      [GIDSignInInternalOptions defaultOptionsWithConfiguration:_configuration
                                                presentingWindow:presentingWindow
                                                       loginHint:hint
                                                   addScopesFlow:NO
@@ -320,22 +321,19 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
   [self signInWithOptions:options];
 }
 
-- (void)signInWithConfiguration:(GIDConfiguration *)configuration
-               presentingWindow:(NSWindow *)presentingWindow
-                     completion:(nullable GIDSignInCompletion)completion {
-  [self signInWithConfiguration:configuration
-               presentingWindow:presentingWindow
-                           hint:nil
-                     completion:completion];
+- (void)signInWithPresentingWindow:(NSWindow *)presentingWindow
+                        completion:(nullable GIDSignInCompletion)completion {
+  [self signInWithPresentingWindow:presentingWindow
+                              hint:nil
+                        completion:completion];
 }
 
-- (void)signInWithConfiguration:(GIDConfiguration *)configuration
-               presentingWindow:(NSWindow *)presentingWindow
-                           hint:(nullable NSString *)hint
-               additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
-                     completion:(nullable GIDSignInCompletion)completion {
+- (void)signInWithPresentingWindow:(NSWindow *)presentingWindow
+                              hint:(nullable NSString *)hint
+                  additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
+                        completion:(nullable GIDSignInCompletion)completion {
   GIDSignInInternalOptions *options =
-    [GIDSignInInternalOptions defaultOptionsWithConfiguration:configuration
+    [GIDSignInInternalOptions defaultOptionsWithConfiguration:_configuration
                                              presentingWindow:presentingWindow
                                                     loginHint:hint
                                                 addScopesFlow:NO
@@ -403,9 +401,7 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
 - (void)signOut {
   // Clear the current user if there is one.
   if (_currentUser) {
-    [self willChangeValueForKey:NSStringFromSelector(@selector(currentUser))];
-    _currentUser = nil;
-    [self didChangeValueForKey:NSStringFromSelector(@selector(currentUser))];
+    self.currentUser = nil;
   }
   // Remove all state from the keychain.
   [self removeAllKeychainEntries];
@@ -477,6 +473,14 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
 - (id)initPrivate {
   self = [super init];
   if (self) {
+    // Get the bundle of the current executable.
+    NSBundle *bundle = NSBundle.mainBundle;
+
+    // If we have a bundle, try to set the active configuration from the bundle's Info.plist.
+    if (bundle) {
+      _configuration = [GIDSignIn configurationFromBundle:bundle];
+    }
+    
     // Check to see if the 3P app is being run for the first time after a fresh install.
     BOOL isFreshInstall = [self isFreshInstall];
 
@@ -514,6 +518,14 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
   }
 
   if (options.interactive) {
+    // Ensure that a configuration has been provided.
+    if (!_configuration) {
+      // NOLINTNEXTLINE(google-objc-avoid-throwing-exception)
+      [NSException raise:NSInvalidArgumentException
+                  format:@"No active configuration.  Make sure GIDClientID is set in Info.plist."];
+      return;
+    }
+
     // Explicitly throw exception for missing client ID here. This must come before
     // scheme check because schemes rely on reverse client IDs.
     [self assertValidParameters];
@@ -589,7 +601,6 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
   additionalParameters[kSDKVersionLoggingParameter] = GIDVersion();
   additionalParameters[kEnvironmentLoggingParameter] = GIDEnvironment();
 
-#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
   OIDAuthorizationRequest *request =
       [[OIDAuthorizationRequest alloc] initWithConfiguration:_appAuthConfiguration
                                                     clientId:options.configuration.clientID
@@ -600,34 +611,17 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
 
   _currentAuthorizationFlow = [OIDAuthorizationService
       presentAuthorizationRequest:request
+#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
          presentingViewController:options.presentingViewController
-                        callback:^(OIDAuthorizationResponse *_Nullable authorizationResponse,
-                                   NSError *_Nullable error) {
-    [self processAuthorizationResponse:authorizationResponse
-                                 error:error
-                            emmSupport:emmSupport];
-  }];
 #elif TARGET_OS_OSX
-  OIDAuthorizationRequest *request =
-      [[OIDAuthorizationRequest alloc] initWithConfiguration:_appAuthConfiguration
-                                                    clientId:options.configuration.clientID
-                                                clientSecret:@""
-                                                      scopes:options.scopes
-                                                 redirectURL:redirectURL
-                                                responseType:OIDResponseTypeCode
-                                        additionalParameters:additionalParameters];
-
-  _currentAuthorizationFlow = [OIDAuthorizationService
-      presentAuthorizationRequest:request
                  presentingWindow:options.presentingWindow
-                         callback:^(OIDAuthorizationResponse *_Nullable authorizationResponse,
-                                    NSError *_Nullable error) {
+#endif // TARGET_OS_OSX
+                        callback:^(OIDAuthorizationResponse *_Nullable authorizationResponse,
+                                   NSError *_Nullable error) {
     [self processAuthorizationResponse:authorizationResponse
                                  error:error
                             emmSupport:emmSupport];
   }];
-#endif // TARGET_OS_OSX
-
 }
 
 - (void)processAuthorizationResponse:(OIDAuthorizationResponse *)authorizationResponse
@@ -647,7 +641,7 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
       authFlow.authState = [[OIDAuthState alloc]
           initWithAuthorizationResponse:authorizationResponse];
       // perform auth code exchange
-      [self maybeFetchToken:authFlow fallback:nil];
+      [self maybeFetchToken:authFlow];
     } else {
       // There was a failure, convert to appropriate error code.
       NSString *errorString;
@@ -719,17 +713,14 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
   // Complete the auth flow using saved auth in keychain.
   GIDAuthFlow *authFlow = [[GIDAuthFlow alloc] init];
   authFlow.authState = authState;
-  [self maybeFetchToken:authFlow fallback:options.interactive ? ^() {
-    [self authenticateInteractivelyWithOptions:options];
-  } : nil];
+  [self maybeFetchToken:authFlow];
   [self addDecodeIdTokenCallback:authFlow];
   [self addSaveAuthCallback:authFlow];
   [self addCompletionCallback:authFlow];
 }
 
-// Fetches the access token if necessary as part of the auth flow. If |fallback|
-// is provided, call it instead of continuing the auth flow in case of error.
-- (void)maybeFetchToken:(GIDAuthFlow *)authFlow fallback:(nullable void (^)(void))fallback {
+// Fetches the access token if necessary as part of the auth flow.
+- (void)maybeFetchToken:(GIDAuthFlow *)authFlow {
   OIDAuthState *authState = authFlow.authState;
   // Do nothing if we have an auth flow error or a restored access token that isn't near expiration.
   if (authFlow.error ||
@@ -776,14 +767,6 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
     [authState updateWithTokenResponse:tokenResponse error:error];
     authFlow.error = error;
 
-    if (!tokenResponse.accessToken || error) {
-      if (fallback) {
-        [authFlow reset];
-        fallback();
-        return;
-      }
-    }
-
 #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
     if (authFlow.emmSupport) {
       [GIDAuthentication handleTokenFetchEMMError:error completion:^(NSError *error) {
@@ -818,7 +801,7 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
       } else {
         GIDGoogleUser *user = [[GIDGoogleUser alloc] initWithAuthState:authState
                                                            profileData:handlerAuthFlow.profileData];
-        [self setCurrentUserWithKVO:user];
+        self.currentUser = user;
       }
     }
   }];
@@ -950,17 +933,6 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
   return YES;
 }
 
-#pragma mark - Key-Value Observing
-
-// Override |NSObject(NSKeyValueObservingCustomization)| method in order to provide custom KVO
-// notifications for the |currentUser| property.
-+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
-  if ([key isEqual:NSStringFromSelector(@selector(currentUser))]) {
-    return NO;
-  }
-  return [super automaticallyNotifiesObserversForKey:key];
-}
-
 #pragma mark - Helpers
 
 - (NSError *)errorWithString:(NSString *)errorString code:(GIDSignInErrorCode)code {
@@ -991,10 +963,11 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
 // Assert that the presenting view controller has been set.
 - (void)assertValidPresentingViewController {
 #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
-  if (!_currentOptions.presentingViewController) {
+  if (!_currentOptions.presentingViewController)
 #elif TARGET_OS_OSX
-  if (!_currentOptions.presentingWindow) {
+  if (!_currentOptions.presentingWindow)
 #endif // TARGET_OS_OSX
+  {
     // NOLINTNEXTLINE(google-objc-avoid-throwing-exception)
     [NSException raise:NSInvalidArgumentException
                 format:@"|presentingViewController| must be set."];
@@ -1049,11 +1022,36 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
             imageURL:[NSURL URLWithString:idToken.claims[kBasicProfilePictureKey]]];
 }
 
-// Set currentUser making appropriate KVO calls.
-- (void)setCurrentUserWithKVO:(GIDGoogleUser *_Nullable)user {
-  [self willChangeValueForKey:NSStringFromSelector(@selector(currentUser))];
-  _currentUser = user;
-  [self didChangeValueForKey:NSStringFromSelector(@selector(currentUser))];
+// Try to retrieve a configuration value from an |NSBundle|'s Info.plist for a given key.
++ (nullable NSString *)configValueFromBundle:(NSBundle *)bundle forKey:(NSString *)key {
+  NSString *value;
+  id configValue = [bundle objectForInfoDictionaryKey:key];
+  if ([configValue isKindOfClass:[NSString class]]) {
+    value = configValue;
+  }
+  return value;
+}
+
+// Try to generate a |GIDConfiguration| from an |NSBundle|'s Info.plist.
++ (nullable GIDConfiguration *)configurationFromBundle:(NSBundle *)bundle {
+  GIDConfiguration *configuration;
+
+  // Retrieve any valid config parameters from the bundle's Info.plist.
+  NSString *clientID = [GIDSignIn configValueFromBundle:bundle forKey:kConfigClientIDKey];
+  NSString *serverClientID = [GIDSignIn configValueFromBundle:bundle
+                                                       forKey:kConfigServerClientIDKey];
+  NSString *hostedDomain = [GIDSignIn configValueFromBundle:bundle forKey:kConfigHostedDomainKey];
+  NSString *openIDRealm = [GIDSignIn configValueFromBundle:bundle forKey:kConfigOpenIDRealmKey];
+    
+  // If we have at least a client ID, try to construct a configuration.
+  if (clientID) {
+    configuration = [[GIDConfiguration alloc] initWithClientID:clientID
+                                                 serverClientID:serverClientID
+                                                   hostedDomain:hostedDomain
+                                                    openIDRealm:openIDRealm];
+  }
+  
+  return configuration;
 }
 
 @end

+ 3 - 0
GoogleSignIn/Sources/GIDSignIn_Private.h

@@ -24,6 +24,9 @@ NS_ASSUME_NONNULL_BEGIN
 // Private |GIDSignIn| methods that are used internally in this SDK and other Google SDKs.
 @interface GIDSignIn ()
 
+// Redeclare |currentUser| as readwrite for internal use.
+@property(nonatomic, readwrite, nullable) GIDGoogleUser *currentUser;
+
 // Private initializer for |GIDSignIn|.
 - (instancetype)initPrivate;
 

+ 3 - 0
GoogleSignIn/Sources/Public/GoogleSignIn/GIDAuthentication.h

@@ -59,7 +59,10 @@ typedef void (^GIDAuthenticationCompletion)(GIDAuthentication *_Nullable authent
 /// Gets a new authorizer for `GTLService`, `GTMSessionFetcher`, or `GTMHTTPFetcher`.
 ///
 /// @return A new authorizer
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
 - (id<GTMFetcherAuthorizationProtocol>)fetcherAuthorizer;
+#pragma clang diagnostic pop
 
 /// Get a valid access token and a valid ID token, refreshing them first if they have expired or are
 /// about to expire.

+ 3 - 3
GoogleSignIn/Sources/Public/GoogleSignIn/GIDConfiguration.h

@@ -48,14 +48,14 @@ NS_ASSUME_NONNULL_BEGIN
 /// Initialize a `GIDConfiguration` object with a client ID.
 ///
 /// @param clientID The client ID of the app.
-/// @return An initilized `GIDConfiguration` instance.
+/// @return An initialized `GIDConfiguration` instance.
 - (instancetype)initWithClientID:(NSString *)clientID;
 
 /// Initialize a `GIDConfiguration` object with a client ID and server client ID.
 ///
 /// @param clientID The client ID of the app.
 /// @param serverClientID The server's client ID.
-/// @return An initilized `GIDConfiguration` instance.
+/// @return An initialized `GIDConfiguration` instance.
 - (instancetype)initWithClientID:(NSString *)clientID
                   serverClientID:(nullable NSString *)serverClientID;
 
@@ -65,7 +65,7 @@ NS_ASSUME_NONNULL_BEGIN
 /// @param serverClientID The server's client ID.
 /// @param hostedDomain The Google Apps domain to be used.
 /// @param openIDRealm The OpenID realm to be used.
-/// @return An initilized `GIDConfiguration` instance.
+/// @return An initialized `GIDConfiguration` instance.
 - (instancetype)initWithClientID:(NSString *)clientID
                   serverClientID:(nullable NSString *)serverClientID
                     hostedDomain:(nullable NSString *)hostedDomain

+ 27 - 36
GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h

@@ -69,6 +69,9 @@ typedef void (^GIDDisconnectCompletion)(NSError *_Nullable error);
 /// The `GIDGoogleUser` object representing the current user or `nil` if there is no signed-in user.
 @property(nonatomic, readonly, nullable) GIDGoogleUser *currentUser;
 
+/// The active configuration for this instance of `GIDSignIn`.
+@property(nonatomic, nullable) GIDConfiguration *configuration;
+
 /// Unavailable. Use the `sharedInstance` property to instantiate `GIDSignIn`.
 /// :nodoc:
 + (instancetype)new NS_UNAVAILABLE;
@@ -106,32 +109,29 @@ typedef void (^GIDDisconnectCompletion)(NSError *_Nullable error);
 - (void)disconnectWithCompletion:(nullable GIDDisconnectCompletion)completion;
 
 #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
-/// Starts an interactive sign-in flow on iOS using the provided configuration.
+/// Starts an interactive sign-in flow on iOS.
 ///
 /// The completion will be called at the end of this process.  Any saved sign-in state will be
 /// replaced by the result of this flow.  Note that this method should not be called when the app is
 /// starting up, (e.g in `application:didFinishLaunchingWithOptions:`); instead use the
 /// `restorePreviousSignInWithCompletion:` method to restore a previous sign-in.
 ///
-/// @param configuration The configuration properties to be used for this flow.
 /// @param presentingViewController The view controller used to present `SFSafariViewContoller` on
 ///     iOS 9 and 10 and to supply `presentationContextProvider` for `ASWebAuthenticationSession` on
 ///     iOS 13+.
 /// @param completion The `GIDSignInCompletion` block that is called on completion.  This block will
 ///     be called asynchronously on the main queue.
-- (void)signInWithConfiguration:(GIDConfiguration *)configuration
-       presentingViewController:(UIViewController *)presentingViewController
-                     completion:(nullable GIDSignInCompletion)completion
+- (void)signInWithPresentingViewController:(UIViewController *)presentingViewController
+                                completion:(nullable GIDSignInCompletion)completion
     NS_EXTENSION_UNAVAILABLE("The sign-in flow is not supported in App Extensions.");
 
-/// Starts an interactive sign-in flow on iOS using the provided configuration and a login hint.
+/// Starts an interactive sign-in flow on iOS using the provided hint.
 ///
 /// The completion will be called at the end of this process.  Any saved sign-in state will be
 /// replaced by the result of this flow.  Note that this method should not be called when the app is
 /// starting up, (e.g in `application:didFinishLaunchingWithOptions:`); instead use the
 /// `restorePreviousSignInWithCompletion:` method to restore a previous sign-in.
 ///
-/// @param configuration The configuration properties to be used for this flow.
 /// @param presentingViewController The view controller used to present `SFSafariViewContoller` on
 ///     iOS 9 and 10 and to supply `presentationContextProvider` for `ASWebAuthenticationSession` on
 ///     iOS 13+.
@@ -139,20 +139,18 @@ typedef void (^GIDDisconnectCompletion)(NSError *_Nullable error);
 ///     address, to be prefilled if possible.
 /// @param completion The `GIDSignInCompletion` block that is called on completion.  This block will
 ///     be called asynchronously on the main queue.
-- (void)signInWithConfiguration:(GIDConfiguration *)configuration
-       presentingViewController:(UIViewController *)presentingViewController
-                           hint:(nullable NSString *)hint
-                     completion:(nullable GIDSignInCompletion)completion
+- (void)signInWithPresentingViewController:(UIViewController *)presentingViewController
+                                      hint:(nullable NSString *)hint
+                                completion:(nullable GIDSignInCompletion)completion
     NS_EXTENSION_UNAVAILABLE("The sign-in flow is not supported in App Extensions.");
 
-/// Starts an interactive sign-in flow on iOS using the provided configuration and a login hint.
+/// Starts an interactive sign-in flow on iOS using the provided hint and additional scopes.
 ///
 /// The completion will be called at the end of this process.  Any saved sign-in state will be
 /// replaced by the result of this flow.  Note that this method should not be called when the app is
 /// starting up, (e.g in `application:didFinishLaunchingWithOptions:`); instead use the
 /// `restorePreviousSignInWithCompletion:` method to restore a previous sign-in.
 ///
-/// @param configuration The configuration properties to be used for this flow.
 /// @param presentingViewController The view controller used to present `SFSafariViewContoller` on
 ///     iOS 9 and 10.
 /// @param hint An optional hint for the authorization server, for example the user's ID or email
@@ -161,11 +159,10 @@ typedef void (^GIDDisconnectCompletion)(NSError *_Nullable error);
 /// @param completion The `GIDSignInCompletion` block that is called on completion.  This block will
 ///     be called asynchronously on the main queue.
 
-- (void)signInWithConfiguration:(GIDConfiguration *)configuration
-       presentingViewController:(UIViewController *)presentingViewController
-                           hint:(nullable NSString *)hint
-               additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
-                     completion:(nullable GIDSignInCompletion)completion;
+- (void)signInWithPresentingViewController:(UIViewController *)presentingViewController
+                                      hint:(nullable NSString *)hint
+                          additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
+                                completion:(nullable GIDSignInCompletion)completion;
 
 /// Starts an interactive consent flow on iOS to add scopes to the current user's grants.
 ///
@@ -184,47 +181,42 @@ typedef void (^GIDDisconnectCompletion)(NSError *_Nullable error);
     NS_EXTENSION_UNAVAILABLE("The add scopes flow is not supported in App Extensions."); 
 
 #elif TARGET_OS_OSX
-/// Starts an interactive sign-in flow on macOS using the provided configuration.
+/// Starts an interactive sign-in flow on macOS.
 ///
 /// The completion will be called at the end of this process.  Any saved sign-in state will be
 /// replaced by the result of this flow.  Note that this method should not be called when the app is
 /// starting up, (e.g in `application:didFinishLaunchingWithOptions:`); instead use the
 /// `restorePreviousSignInWithCompletion:` method to restore a previous sign-in.
 ///
-/// @param configuration The configuration properties to be used for this flow.
 /// @param presentingWindow The window used to supply `presentationContextProvider` for `ASWebAuthenticationSession`.
 /// @param completion The `GIDSignInCompletion` block that is called on completion.  This block will
 ///     be called asynchronously on the main queue.
-- (void)signInWithConfiguration:(GIDConfiguration *)configuration
-               presentingWindow:(NSWindow *)presentingWindow
-                     completion:(nullable GIDSignInCompletion)completion;
+- (void)signInWithPresentingWindow:(NSWindow *)presentingWindow
+                        completion:(nullable GIDSignInCompletion)completion;
 
-/// Starts an interactive sign-in flow on macOS using the provided configuration and a login hint.
+/// Starts an interactive sign-in flow on macOS using the provided hint.
 ///
 /// The completion will be called at the end of this process.  Any saved sign-in state will be
 /// replaced by the result of this flow.  Note that this method should not be called when the app is
 /// starting up, (e.g in `application:didFinishLaunchingWithOptions:`); instead use the
 /// `restorePreviousSignInWithCompletion:` method to restore a previous sign-in.
 ///
-/// @param configuration The configuration properties to be used for this flow.
 /// @param presentingWindow The window used to supply `presentationContextProvider` for `ASWebAuthenticationSession`.
 /// @param hint An optional hint for the authorization server, for example the user's ID or email
 ///     address, to be prefilled if possible.
 /// @param completion The `GIDSignInCompletion` block that is called on completion.  This block will
 ///     be called asynchronously on the main queue.
-- (void)signInWithConfiguration:(GIDConfiguration *)configuration
-               presentingWindow:(NSWindow *)presentingWindow
-                           hint:(nullable NSString *)hint
-                     completion:(nullable GIDSignInCompletion)completion;
+- (void)signInWithPresentingWindow:(NSWindow *)presentingWindow
+                              hint:(nullable NSString *)hint
+                        completion:(nullable GIDSignInCompletion)completion;
 
-/// Starts an interactive sign-in flow on macOS using the provided configuration and a login hint.
+/// Starts an interactive sign-in flow on macOS using the provided hint.
 ///
 /// The completion will be called at the end of this process.  Any saved sign-in state will be
 /// replaced by the result of this flow.  Note that this method should not be called when the app is
 /// starting up, (e.g in `application:didFinishLaunchingWithOptions:`); instead use the
 /// `restorePreviousSignInWithCompletion:` method to restore a previous sign-in.
 ///
-/// @param configuration The configuration properties to be used for this flow.
 /// @param presentingWindow The window used to supply `presentationContextProvider` for `ASWebAuthenticationSession`.
 /// @param hint An optional hint for the authorization server, for example the user's ID or email
 ///     address, to be prefilled if possible.
@@ -232,11 +224,10 @@ typedef void (^GIDDisconnectCompletion)(NSError *_Nullable error);
 /// @param completion The `GIDSignInCompletion` block that is called on completion.  This block will
 ///     be called asynchronously on the main queue.
 
-- (void)signInWithConfiguration:(GIDConfiguration *)configuration
-               presentingWindow:(NSWindow *)presentingWindow
-                           hint:(nullable NSString *)hint
-               additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
-                     completion:(nullable GIDSignInCompletion)completion;
+- (void)signInWithPresentingWindow:(NSWindow *)presentingWindow
+                              hint:(nullable NSString *)hint
+                  additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
+                        completion:(nullable GIDSignInCompletion)completion;
 
 /// Starts an interactive consent flow on macOS to add scopes to the current user's grants.
 ///

+ 3 - 0
GoogleSignIn/Tests/Unit/GIDAuthenticationTest.m

@@ -245,7 +245,10 @@ _Static_assert(kChangeTypeEnd == (sizeof(kObservedProperties) / sizeof(*kObserve
   // internally, so let's just take the shortcut here by asserting we get a
   // GTMAppAuthFetcherAuthorization object.
   GIDAuthentication *auth = [self auth];
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
   id<GTMFetcherAuthorizationProtocol> fetcherAuthroizer = auth.fetcherAuthorizer;
+#pragma clang diagnostic pop
   XCTAssertTrue([fetcherAuthroizer isKindOfClass:[GTMAppAuthFetcherAuthorization class]]);
   XCTAssertTrue([fetcherAuthroizer canAuthorize]);
 }

+ 16 - 4
GoogleSignIn/Tests/Unit/GIDFakeMainBundle.h

@@ -23,12 +23,11 @@
 @interface GIDFakeMainBundle : NSObject
 
 /**
- * @fn startFakingWithBundleId:clientId:
+ * @fn startFakingWithClientID:
  * @brief Starts faking [NSBundle mainBundle]
- * @param bundleId The fake bundle idenfitier for the app.
- * @param clientId The fake client idenfitier for the app.
+ * @param clientID The fake client idenfitier for the app.
  */
-- (void)startFakingWithBundleId:(NSString *)bundleId clientId:(NSString *)clientId;
+- (void)startFakingWithClientID:(NSString *)clientID;
 
 /**
  * @fn stopFaking
@@ -80,4 +79,17 @@
  */
 - (void)fakeOtherSchemesAndAllSchemes;
 
+/**
+ * @fn fakeWithClientID:serverClientID:hostedDomain:openIDRealm:
+ * @brief Sets values for faked Info.plist params.
+ * @param clientID The fake client idenfitier for the app.
+ * @param serverClientID The fake server client idenfitier for the app.
+ * @param hostedDomain The fake hosted domain for the app.
+ * @param openIDRealm The fake OpenID realm for the app.
+ */
+- (void)fakeWithClientID:(id)clientID
+          serverClientID:(id)serverClientID
+            hostedDomain:(id)hostedDomain
+             openIDRealm:(id)openIDRealm;
+
 @end

+ 42 - 33
GoogleSignIn/Tests/Unit/GIDFakeMainBundle.m

@@ -21,24 +21,39 @@ static NSString *const kCFBundleURLTypesKey = @"CFBundleURLTypes";
 
 static NSString *const kCFBundleURLSchemesKey = @"CFBundleURLSchemes";
 
-@implementation GIDFakeMainBundle {
-  // Represents the CFBundleURLTypes of the mocked app bundle's info.plist.
-  __block NSArray *_fakeSupportedSchemes;
+// Info.plist config keys
+static NSString *const kConfigClientIDKey = @"GIDClientID";
+static NSString *const kConfigServerClientIDKey = @"GIDServerClientID";
+static NSString *const kConfigHostedDomainKey = @"GIDHostedDomain";
+static NSString *const kConfigOpenIDRealmKey = @"GIDOpenIDRealm";
 
+@implementation GIDFakeMainBundle {
   NSString *_clientId;
-  NSString *_bundleId;
+
+  // Represents the Info.plist keys to fake.
+  NSArray *_fakedKeys;
+
+  // Represents the values for any Info.plist keys to be faked.
+  NSMutableDictionary *_fakeConfig;
 }
 
-- (void)startFakingWithBundleId:(NSString *)bundleId clientId:(NSString *)clientId {
-  _bundleId = bundleId;
+- (void)startFakingWithClientID:(NSString *)clientId {
   _clientId = clientId;
 
+  _fakedKeys = @[ kCFBundleURLTypesKey,
+                  kConfigClientIDKey,
+                  kConfigServerClientIDKey,
+                  kConfigHostedDomainKey,
+                  kConfigOpenIDRealmKey ];
+  
+  _fakeConfig = [@{ @"GIDClientID" : clientId } mutableCopy];
+
   [GULSwizzler swizzleClass:[NSBundle class]
                    selector:@selector(objectForInfoDictionaryKey:)
             isClassSelector:NO
-                  withBlock:^(id _self, NSString *key) {
-    if ([key isEqual:kCFBundleURLTypesKey]) {
-      return self->_fakeSupportedSchemes;
+                  withBlock:^id(id _self, NSString *key) {
+    if ([self->_fakedKeys containsObject:key]) {
+      return self->_fakeConfig[key];
     } else {
       @throw [NSException exceptionWithName:@"Requested unexpected info.plist key."
                                      reason:nil
@@ -51,7 +66,7 @@ static NSString *const kCFBundleURLSchemesKey = @"CFBundleURLSchemes";
   [GULSwizzler unswizzleClass:[NSBundle class]
                      selector:@selector(objectForInfoDictionaryKey:)
               isClassSelector:NO];
-  _fakeSupportedSchemes = nil;
+  _fakeConfig = nil;
 }
 
 #pragma mark - Utilities
@@ -93,10 +108,7 @@ static NSString *const kCFBundleURLSchemesKey = @"CFBundleURLSchemes";
 #pragma mark - URL Schemes
 
 - (void)fakeAllSchemesSupported {
-  _fakeSupportedSchemes = @[
-    @{
-      kCFBundleURLSchemesKey : @[ _bundleId ]
-    },
+  _fakeConfig[kCFBundleURLTypesKey] = @[
     @{
       kCFBundleURLSchemesKey : @[ [self reversedClientId] ]
     }
@@ -104,10 +116,9 @@ static NSString *const kCFBundleURLSchemesKey = @"CFBundleURLSchemes";
 }
 
 - (void)fakeAllSchemesSupportedAndMerged {
-  _fakeSupportedSchemes = @[
+  _fakeConfig[kCFBundleURLTypesKey] = @[
     @{
       kCFBundleURLSchemesKey : @[
-        _bundleId,
         [self reversedClientId]
       ]
     },
@@ -115,14 +126,9 @@ static NSString *const kCFBundleURLSchemesKey = @"CFBundleURLSchemes";
 }
 
 - (void)fakeAllSchemesSupportedWithCasesMangled {
-  NSString *caseFlippedBundleId =
-      [self stringByFlippingCasesInString:_bundleId];
   NSString *caseFlippedReverseClientId =
       [self stringByFlippingCasesInString:[self reversedClientId]];
-  _fakeSupportedSchemes = @[
-    @{
-      kCFBundleURLSchemesKey : @[ caseFlippedBundleId ]
-    },
+  _fakeConfig[kCFBundleURLTypesKey] = @[
     @{
       kCFBundleURLSchemesKey : @[ caseFlippedReverseClientId ]
     }
@@ -130,19 +136,15 @@ static NSString *const kCFBundleURLSchemesKey = @"CFBundleURLSchemes";
 }
 
 - (void)fakeMissingClientIdScheme {
-  _fakeSupportedSchemes = @[
-    @{
-      kCFBundleURLSchemesKey : @[ _bundleId ]
-    }
-  ];
+  [self fakeMissingAllSchemes];
 }
 
 - (void)fakeMissingAllSchemes {
-  _fakeSupportedSchemes = nil;
+  _fakeConfig[kCFBundleURLTypesKey] = nil;
 }
 
 - (void)fakeOtherSchemes {
-  _fakeSupportedSchemes = @[
+  _fakeConfig[kCFBundleURLTypesKey] = @[
     @{
       kCFBundleURLSchemesKey : @[ @"junk" ]
     }
@@ -150,10 +152,7 @@ static NSString *const kCFBundleURLSchemesKey = @"CFBundleURLSchemes";
 }
 
 - (void)fakeOtherSchemesAndAllSchemes {
-  _fakeSupportedSchemes = @[
-    @{
-      kCFBundleURLSchemesKey : @[ _bundleId ]
-    },
+  _fakeConfig[kCFBundleURLTypesKey] = @[
     @{
       kCFBundleURLSchemesKey : @[ @"junk" ]
     },
@@ -163,4 +162,14 @@ static NSString *const kCFBundleURLSchemesKey = @"CFBundleURLSchemes";
   ];
 }
 
+- (void)fakeWithClientID:(id)clientID
+          serverClientID:(id)serverClientID
+            hostedDomain:(id)hostedDomain
+             openIDRealm:(id)openIDRealm {
+  _fakeConfig[kConfigClientIDKey] = clientID;
+  _fakeConfig[kConfigServerClientIDKey] = serverClientID;
+  _fakeConfig[kConfigHostedDomainKey] = hostedDomain;
+  _fakeConfig[kConfigOpenIDRealmKey] = openIDRealm;
+}
+
 @end

+ 1 - 2
GoogleSignIn/Tests/Unit/GIDSignInCallbackSchemesTest.m

@@ -18,7 +18,6 @@
 #import "GoogleSignIn/Tests/Unit/GIDFakeMainBundle.h"
 
 static NSString *const kClientId = @"FakeClientID";
-static NSString *const kBundleId = @"FakeBundleID";
 
 @interface GIDSignInCallbackSchemesTest : XCTestCase
 @end
@@ -29,7 +28,7 @@ static NSString *const kBundleId = @"FakeBundleID";
 
 - (void)setUp {
   _fakeMainBundle = [[GIDFakeMainBundle alloc] init];
-  [_fakeMainBundle startFakingWithBundleId:kBundleId clientId:kClientId];
+  [_fakeMainBundle startFakingWithClientID:kClientId];
 }
 
 - (void)tearDown {

+ 71 - 67
GoogleSignIn/Tests/Unit/GIDSignInTest.m

@@ -85,7 +85,7 @@ static NSString * const kFakeIDToken = @"FakeIDToken";
 static NSString * const kClientId = @"FakeClientID";
 static NSString * const kDotReversedClientId = @"FakeClientID";
 static NSString * const kClientId2 = @"FakeClientID2";
-static NSString * const kAppBundleId = @"FakeBundleID";
+static NSString * const kServerClientId = @"FakeServerClientID";
 static NSString * const kLanguage = @"FakeLanguage";
 static NSString * const kScope = @"FakeScope";
 static NSString * const kScope2 = @"FakeScope2";
@@ -153,9 +153,6 @@ static NSString *const kEMMSupport = @"1";
 static NSString *const kGrantedScope = @"grantedScope";
 static NSString *const kNewScope = @"newScope";
 
-/// Unique pointer value for KVO tests.
-static void *kTestObserverContext = &kTestObserverContext;
-
 #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
 // This category is used to allow the test to swizzle a private method.
 @interface UIViewController (Testing)
@@ -263,9 +260,6 @@ static void *kTestObserverContext = &kTestObserverContext;
   // The saved token request callback.
   OIDTokenCallback _savedTokenCallback;
 
-  // Set of all |GIDSignIn| key paths which were observed to change.
-  NSMutableSet *_changedKeyPaths;
-
   // Status returned by saveAuthorization:toKeychainForName:
   BOOL _saveAuthorizationReturnValue;
 }
@@ -286,7 +280,6 @@ static void *kTestObserverContext = &kTestObserverContext;
   _completionCalled = NO;
   _keychainSaved = NO;
   _keychainRemoved = NO;
-  _changedKeyPaths = [[NSMutableSet alloc] init];
 
   // Mocks
 #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
@@ -334,7 +327,7 @@ static void *kTestObserverContext = &kTestObserverContext;
   // Fakes
   _fetcherService = [[GIDFakeFetcherService alloc] init];
   _fakeMainBundle = [[GIDFakeMainBundle alloc] init];
-  [_fakeMainBundle startFakingWithBundleId:kAppBundleId clientId:kClientId];
+  [_fakeMainBundle startFakingWithClientID:kClientId];
   [_fakeMainBundle fakeAllSchemesSupported];
 
   // Object under test
@@ -342,7 +335,6 @@ static void *kTestObserverContext = &kTestObserverContext;
                                           forKey:kAppHasRunBeforeKey];
 
   _signIn = [[GIDSignIn alloc] initPrivate];
-  _configuration = [[GIDConfiguration alloc] initWithClientID:kClientId];
   _hint = nil;
 
   __weak GIDSignInTest *weakSelf = self;
@@ -355,11 +347,6 @@ static void *kTestObserverContext = &kTestObserverContext;
     strongSelf->_completionCalled = YES;
     strongSelf->_authError = error;
   };
-
-  [_signIn addObserver:self
-            forKeyPath:NSStringFromSelector(@selector(currentUser))
-               options:0
-               context:kTestObserverContext];
 }
 
 - (void)tearDown {
@@ -380,10 +367,6 @@ static void *kTestObserverContext = &kTestObserverContext;
 
   [_fakeMainBundle stopFaking];
   [super tearDown];
-
-  [_signIn removeObserver:self
-               forKeyPath:NSStringFromSelector(@selector(currentUser))
-                  context:kTestObserverContext];
 }
 
 #pragma mark - Tests
@@ -394,6 +377,46 @@ static void *kTestObserverContext = &kTestObserverContext;
   XCTAssertTrue(signIn1 == signIn2, @"shared instance must be singleton");
 }
 
+- (void)testInitPrivate {
+  GIDSignIn *signIn = [[GIDSignIn alloc] initPrivate];
+  XCTAssertNotNil(signIn.configuration);
+  XCTAssertEqual(signIn.configuration.clientID, kClientId);
+  XCTAssertNil(signIn.configuration.serverClientID);
+  XCTAssertNil(signIn.configuration.hostedDomain);
+  XCTAssertNil(signIn.configuration.openIDRealm);
+}
+
+- (void)testInitPrivate_noConfig {
+  [_fakeMainBundle fakeWithClientID:nil
+                     serverClientID:nil
+                       hostedDomain:nil
+                        openIDRealm:nil];
+  GIDSignIn *signIn = [[GIDSignIn alloc] initPrivate];
+  XCTAssertNil(signIn.configuration);
+}
+
+- (void)testInitPrivate_fullConfig {
+  [_fakeMainBundle fakeWithClientID:kClientId
+                     serverClientID:kServerClientId
+                       hostedDomain:kFakeHostedDomain
+                        openIDRealm:kOpenIDRealm];
+  GIDSignIn *signIn = [[GIDSignIn alloc] initPrivate];
+  XCTAssertNotNil(signIn.configuration);
+  XCTAssertEqual(signIn.configuration.clientID, kClientId);
+  XCTAssertEqual(signIn.configuration.serverClientID, kServerClientId);
+  XCTAssertEqual(signIn.configuration.hostedDomain, kFakeHostedDomain);
+  XCTAssertEqual(signIn.configuration.openIDRealm, kOpenIDRealm);
+}
+
+- (void)testInitPrivate_invalidConfig {
+  [_fakeMainBundle fakeWithClientID:@[ @"bad", @"config", @"values" ]
+                     serverClientID:nil
+                       hostedDomain:nil
+                        openIDRealm:nil];
+  GIDSignIn *signIn = [[GIDSignIn alloc] initPrivate];
+  XCTAssertNil(signIn.configuration);
+}
+
 - (void)testRestorePreviousSignInNoRefresh_hasPreviousUser {
   [[[_authorization expect] andReturn:_authState] authState];
   OCMStub([_authState lastTokenResponse]).andReturn(_tokenResponse);
@@ -607,10 +630,10 @@ static void *kTestObserverContext = &kTestObserverContext;
 }
 
 - (void)testOpenIDRealm {
-  _configuration = [[GIDConfiguration alloc] initWithClientID:kClientId
-                                               serverClientID:nil
-                                                 hostedDomain:nil
-                                                  openIDRealm:kOpenIDRealm];
+  _signIn.configuration = [[GIDConfiguration alloc] initWithClientID:kClientId
+                                                      serverClientID:nil
+                                                        hostedDomain:nil
+                                                         openIDRealm:kOpenIDRealm];
 
   [self OAuthLoginWithAddScopesFlow:NO
                           authError:nil
@@ -642,10 +665,10 @@ static void *kTestObserverContext = &kTestObserverContext;
 }
 
 - (void)testOAuthLogin_HostedDomain {
-  _configuration = [[GIDConfiguration alloc] initWithClientID:kClientId
-                                               serverClientID:nil
-                                                 hostedDomain:kHostedDomain
-                                                  openIDRealm:nil];
+  _signIn.configuration = [[GIDConfiguration alloc] initWithClientID:kClientId
+                                                      serverClientID:nil
+                                                        hostedDomain:kHostedDomain
+                                                         openIDRealm:nil];
 
   [self OAuthLoginWithAddScopesFlow:NO
                           authError:nil
@@ -720,8 +743,6 @@ static void *kTestObserverContext = &kTestObserverContext;
   [_signIn signOut];
   XCTAssertNil(_signIn.currentUser, @"should not have a current user");
   XCTAssertTrue(_keychainRemoved, @"should remove keychain");
-  XCTAssertTrue([_changedKeyPaths containsObject:NSStringFromSelector(@selector(currentUser))],
-                @"should notify observers that signed in user changed");
 
   OCMVerify([_authorization removeAuthorizationFromKeychainForName:kKeychainName
                                          useDataProtectionKeychain:YES]);
@@ -880,30 +901,28 @@ static void *kTestObserverContext = &kTestObserverContext;
 #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
 
 
-  XCTAssertThrows([_signIn signInWithConfiguration:_configuration
 #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
-                          presentingViewController:_presentingViewController
+  XCTAssertThrows([_signIn signInWithPresentingViewController:_presentingViewController
 #elif TARGET_OS_OSX
-                                  presentingWindow:_presentingWindow
+  XCTAssertThrows([_signIn signInWithPresentingWindow:_presentingWindow
 #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
-                                              hint:_hint
-                                        completion:_completion]);
+                                                 hint:_hint
+                                           completion:_completion]);
 }
 
 - (void)testClientIDMissingException {
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wnonnull"
-  GIDConfiguration *configuration = [[GIDConfiguration alloc] initWithClientID:nil];
+  _signIn.configuration = [[GIDConfiguration alloc] initWithClientID:nil];
 #pragma GCC diagnostic pop
   BOOL threw = NO;
   @try {
-    [_signIn signInWithConfiguration:configuration
 #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
-            presentingViewController:_presentingViewController
+    [_signIn signInWithPresentingViewController:_presentingViewController
 #elif TARGET_OS_OSX
-                    presentingWindow:_presentingWindow
+    [_signIn signInWithPresentingWindow:_presentingWindow
 #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
-                          completion:nil];
+                             completion:nil];
   } @catch (NSException *exception) {
     threw = YES;
     XCTAssertEqualObjects(exception.description,
@@ -917,14 +936,13 @@ static void *kTestObserverContext = &kTestObserverContext;
   [_fakeMainBundle fakeMissingAllSchemes];
   BOOL threw = NO;
   @try {
-    [_signIn signInWithConfiguration:_configuration
 #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
-            presentingViewController:_presentingViewController
+    [_signIn signInWithPresentingViewController:_presentingViewController
 #elif TARGET_OS_OSX
-                    presentingWindow:_presentingWindow
+    [_signIn signInWithPresentingWindow:_presentingWindow
 #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
-                                hint:_hint
-                          completion:_completion];
+                                   hint:_hint
+                             completion:_completion];
   } @catch (NSException *exception) {
     threw = YES;
     XCTAssertEqualObjects(exception.description,
@@ -1239,24 +1257,22 @@ static void *kTestObserverContext = &kTestObserverContext;
               completion:completion];
     } else {
       if (useAdditionalScopes) {
-        [_signIn signInWithConfiguration:_configuration
 #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
-                presentingViewController:_presentingViewController
+        [_signIn signInWithPresentingViewController:_presentingViewController
 #elif TARGET_OS_OSX
-                        presentingWindow:_presentingWindow
+        [_signIn signInWithPresentingWindow:_presentingWindow
 #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
-                                    hint:_hint
-                        additionalScopes:additionalScopes
-                              completion:completion];
+                                       hint:_hint
+                           additionalScopes:additionalScopes
+                                 completion:completion];
       } else {
-        [_signIn signInWithConfiguration:_configuration
 #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
-                presentingViewController:_presentingViewController
+        [_signIn signInWithPresentingViewController:_presentingViewController
 #elif TARGET_OS_OSX
-                        presentingWindow:_presentingWindow
+        [_signIn signInWithPresentingWindow:_presentingWindow
 #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
-                                    hint:_hint
-                              completion:completion];
+                                       hint:_hint
+                                 completion:completion];
       }
     }
 
@@ -1411,16 +1427,4 @@ static void *kTestObserverContext = &kTestObserverContext;
   }
 }
 
-
-#pragma mark - Key Value Observing
-
-- (void)observeValueForKeyPath:(NSString *)keyPath
-                      ofObject:(id)object
-                        change:(NSDictionary<NSKeyValueChangeKey, id> *)change
-                       context:(void *)context {
-  if (context == kTestObserverContext && object == _signIn) {
-    [_changedKeyPaths addObject:keyPath];
-  }
-}
-
 @end

+ 2 - 1
GoogleSignInSwift/Sources/GoogleSignInButtonBundleExtensions.swift

@@ -17,6 +17,7 @@
 #if !arch(arm) && !arch(i386)
 
 import Foundation
+import GoogleSignIn
 
 // MARK: - Bundle Extensions
 
@@ -40,7 +41,7 @@ extension Bundle {
       return Bundle(path: mainPath)
     }
 
-    let classBundle = Bundle(for: GoogleSignInButtonViewModel.self)
+    let classBundle = Bundle(for: GIDSignIn.self)
 
     if let classPath = classBundle.path(
       forResource: GoogleSignInBundleName,

+ 34 - 0
GoogleSignInSwift/Tests/Unit/GoogleSignInButtonExtensionsTests.swift

@@ -0,0 +1,34 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import XCTest
+import SwiftUI
+@testable import GoogleSignInSwift
+
+@available(iOS 13.0, macOS 10.15, *)
+class GoogleSignInButtonExtensionsTests: XCTestCase {
+
+  func testThatBundleExtensionReturnsImageIconURL() {
+    let googleIcon = Bundle.urlForGoogleResource(name: googleImageName, withExtension: "png")
+    XCTAssertNotNil(googleIcon)
+  }
+
+  func testThatBundleExtensionReturnsFontURL() {
+    let googleFont = Bundle.urlForGoogleResource(name: fontNameRobotoBold, withExtension: "ttf")
+    XCTAssertNotNil(googleFont)
+  }
+
+}

+ 1 - 1
GoogleSignInSwiftSupport.podspec

@@ -1,6 +1,6 @@
 Pod::Spec.new do |s|
   s.name = 'GoogleSignInSwiftSupport'
-  s.version = '6.2.2'
+  s.version = '6.2.4'
   s.swift_version = '4.0'
   s.summary = 'Adds Swift-focused support for Google Sign-In.'
   s.description = 'Additional Swift support for the Google Sign-In SDK.'

+ 2 - 2
Package.swift

@@ -17,7 +17,7 @@
 
 import PackageDescription
 
-let googleSignInVersion = "6.2.2"
+let googleSignInVersion = "6.2.4"
 
 let package = Package(
   name: "GoogleSignIn",
@@ -52,7 +52,7 @@ let package = Package(
     .package(
       name: "GTMSessionFetcher",
       url: "https://github.com/google/gtm-session-fetcher.git",
-      "1.5.0" ..< "2.0.0"),
+      "1.5.0" ..< "3.0.0"),
     .package(
       name: "OCMock",
       url: "https://github.com/firebase/ocmock.git",

+ 55 - 0
README.md

@@ -63,3 +63,58 @@ Google Sign-In also supports iOS apps that are built for macOS via
 [Mac Catalyst](https://developer.apple.com/mac-catalyst/).  In order for your Mac Catalyst app
 to store credientials via the Keychain on macOS, you will need to
 [sign your app](https://developer.apple.com/support/code-signing/).
+
+## Using the Google Sign-In Button
+
+There are several ways to add a 'Sign in with Google' button to your app, which
+path you choose will depend on your UI framework and target platform.
+
+### SwiftUI (iOS and macOS)
+
+Creating a 'Sign in with Google' button in SwiftUI can be as simple as this:
+
+```
+GoogleSignInButton {
+  GIDSignIn.sharedInstance.signIn(
+    with: configuration, 
+    presenting: yourViewController) { user, error in
+      // check `error`; do something with `user`
+  }
+}
+```
+
+This example takes advantage of the initializer's [default argument for the
+view model](GoogleSignInSwift/Sources/GoogleSignInButton.swift#L39).
+The default arguments for the view model will use the light scheme, the
+standard button style, and the normal button state.
+You can supply an instance of [`GoogleSignInButtonViewModel`](GoogleSignInSwift/Sources/GoogleSignInButtonViewModel.swift)
+with different values for these properties to customize the button.
+[This convenience initializer](GoogleSignInSwift/Sources/GoogleSignInButton.swift#L56) 
+provides parameters that you can use to set these values as needed.
+
+### UIKit (iOS)
+
+If you are not using SwiftUI to build your user interfaces, you can either
+create `GIDSignInButton` programmatically, or in a Xib/Storyboard. 
+If you are writing programmatic UI code, it will look something like this:
+
+`let button = GIDSignInButton(frame: CGRect(<YOUR_RECT>))`
+
+### AppKit (macOS)
+
+Given that `GIDSignInButton` is implemented as a subclass of `UIControl`, it
+will not be available on macOS. 
+You can instead use the SwiftUI Google sign-in button.
+Doing so will require that you wrap the SwiftUI button in a hosting view so
+that it will be available for use in AppKit.
+
+```
+let signInButton = GoogleSignInButton {
+  GIDSignIn.sharedInstance.signIn(
+    with: configuration, 
+    presenting: yourViewController) { user, error in
+      // check `error`; do something with `user`
+  }
+}
+let hostedButton = NSHostingView(rootView: signInButton)
+```

+ 3 - 0
Samples/ObjC/SignInSample/SignInSample-Info.plist

@@ -67,5 +67,8 @@
 		<string>UIInterfaceOrientationLandscapeLeft</string>
 		<string>UIInterfaceOrientationLandscapeRight</string>
 	</array>
+  <!-- Do not use this client ID in your own app, it will only work for this sample. -->
+  <key>GIDClientID</key>
+  <string>589453917038-qaoga89fitj2ukrsq27ko56fimmojac6.apps.googleusercontent.com</string>
 </dict>
 </plist>

+ 2 - 2
Samples/ObjC/SignInSample/Source/AppDelegate.m

@@ -30,8 +30,8 @@
   // succeeds, we'll have a currentUser and the view will be able to draw its UI for the signed-in
   // state.  If the restore fails, currentUser will be nil and we'll draw the signed-out state
   // prompting the user to sign in.
-  [GIDSignIn.sharedInstance restorePreviousSignInWithCallback:^(GIDGoogleUser * _Nullable user,
-                                                                NSError * _Nullable error) {
+  [GIDSignIn.sharedInstance restorePreviousSignInWithCompletion:^(GIDGoogleUser *user,
+                                                                  NSError *error) {
     self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
     SignInViewController *masterViewController =
         [[SignInViewController alloc] initWithNibName:@"SignInViewController" bundle:nil];

+ 5 - 17
Samples/ObjC/SignInSample/Source/SignInViewController.m

@@ -37,11 +37,6 @@ static NSString *const kStyleCellLabel = @"Style";
 // Accessibility Identifiers.
 static NSString *const kCredentialsButtonAccessibilityIdentifier = @"Credentials";
 
-// DO NOT USE THIS CLIENT ID. IT WILL NOT WORK FOR YOUR APP.
-// Please use the client ID created for you by Google.
-static NSString * const kClientID =
-    @"589453917038-qaoga89fitj2ukrsq27ko56fimmojac6.apps.googleusercontent.com";
-
 @implementation SignInViewController {
   // This is an array of arrays, each one corresponding to the cell
   // labels for its respective section.
@@ -58,9 +53,6 @@ static NSString * const kClientID =
 
   // Map that keeps track of which cell corresponds to which DataPickerState.
   NSDictionary *_drilldownCellState;
-
-  // Configuration options for GIDSignIn.
-  GIDConfiguration *_configuration;
 }
 
 #pragma mark - View lifecycle
@@ -97,8 +89,6 @@ static NSString * const kClientID =
   // Make sure the GIDSignInButton class is linked in because references from
   // xib file doesn't count.
   [GIDSignInButton class];
-
-  _configuration = [[GIDConfiguration alloc] initWithClientID:kClientID];
 }
 
 - (id)initWithNibName:(NSString *)nibNameOrNil
@@ -257,10 +247,9 @@ static NSString * const kClientID =
 #pragma mark - IBActions
 
 - (IBAction)signIn:(id)sender {
-  [GIDSignIn.sharedInstance signInWithConfiguration:_configuration
-                           presentingViewController:self
-                                           callback:^(GIDGoogleUser * _Nullable user,
-                                                      NSError * _Nullable error) {
+  [GIDSignIn.sharedInstance signInWithPresentingViewController:self
+                                                    completion:^(GIDGoogleUser *user,
+                                                                 NSError *error) {
     if (error) {
       self->_signInAuthStatus.text =
           [NSString stringWithFormat:@"Status: Authentication error: %@", error];
@@ -278,7 +267,7 @@ static NSString * const kClientID =
 }
 
 - (IBAction)disconnect:(id)sender {
-  [GIDSignIn.sharedInstance disconnectWithCallback:^(NSError * _Nullable error) {
+  [GIDSignIn.sharedInstance disconnectWithCompletion:^(NSError *error) {
     if (error) {
       self->_signInAuthStatus.text = [NSString stringWithFormat:@"Status: Failed to disconnect: %@",
                                       error];
@@ -293,8 +282,7 @@ static NSString * const kClientID =
 - (IBAction)addScopes:(id)sender {
   [GIDSignIn.sharedInstance addScopes:@[ @"https://www.googleapis.com/auth/user.birthday.read" ]
              presentingViewController:self
-                             callback:^(GIDGoogleUser * _Nullable user,
-                                        NSError * _Nullable error) {
+                           completion:^(GIDGoogleUser *user, NSError *error) {
     if (error) {
       self->_signInAuthStatus.text = [NSString stringWithFormat:@"Status: Failed to add scopes: %@",
                                       error];

+ 15 - 4
Samples/Swift/DaysUntilBirthday/DaysUntilBirthday.xcodeproj/project.pbxproj

@@ -553,8 +553,10 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
-				CODE_SIGN_STYLE = Automatic;
+				CODE_SIGN_IDENTITY = "iPhone Developer";
+				CODE_SIGN_STYLE = Manual;
 				DEVELOPMENT_ASSET_PATHS = "\"iOS/Preview Content\"";
+				DEVELOPMENT_TEAM = EQHXZ8M8AV;
 				ENABLE_PREVIEWS = YES;
 				INFOPLIST_FILE = iOS/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -564,6 +566,7 @@
 				);
 				PRODUCT_BUNDLE_IDENTIFIER = com.google.DaysUntilBirthday;
 				PRODUCT_NAME = "$(TARGET_NAME)";
+				PROVISIONING_PROFILE_SPECIFIER = "Google Development";
 				SWIFT_VERSION = 5.0;
 				TARGETED_DEVICE_FAMILY = "1,2";
 			};
@@ -574,8 +577,10 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
-				CODE_SIGN_STYLE = Automatic;
+				CODE_SIGN_IDENTITY = "iPhone Developer";
+				CODE_SIGN_STYLE = Manual;
 				DEVELOPMENT_ASSET_PATHS = "\"iOS/Preview Content\"";
+				DEVELOPMENT_TEAM = EQHXZ8M8AV;
 				ENABLE_PREVIEWS = YES;
 				INFOPLIST_FILE = iOS/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -585,6 +590,7 @@
 				);
 				PRODUCT_BUNDLE_IDENTIFIER = com.google.DaysUntilBirthday;
 				PRODUCT_NAME = "$(TARGET_NAME)";
+				PROVISIONING_PROFILE_SPECIFIER = "Google Development";
 				SWIFT_VERSION = 5.0;
 				TARGETED_DEVICE_FAMILY = "1,2";
 			};
@@ -652,10 +658,12 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_ENTITLEMENTS = macOS/DaysUntilBirthdayOnMac.entitlements;
-				CODE_SIGN_STYLE = Automatic;
+				CODE_SIGN_IDENTITY = "Mac Developer";
+				CODE_SIGN_STYLE = Manual;
 				COMBINE_HIDPI_IMAGES = YES;
 				CURRENT_PROJECT_VERSION = 1;
 				DEVELOPMENT_ASSET_PATHS = "\"macOS/Preview Content\"";
+				DEVELOPMENT_TEAM = EQHXZ8M8AV;
 				ENABLE_PREVIEWS = YES;
 				GENERATE_INFOPLIST_FILE = YES;
 				INFOPLIST_FILE = macOS/Info.plist;
@@ -668,6 +676,7 @@
 				MARKETING_VERSION = 1.0;
 				PRODUCT_BUNDLE_IDENTIFIER = Google.DaysUntilBirthdayOnMac;
 				PRODUCT_NAME = "$(TARGET_NAME)";
+				PROVISIONING_PROFILE_SPECIFIER = "Google Development macOS";
 				SDKROOT = macosx;
 				SWIFT_EMIT_LOC_STRINGS = YES;
 				SWIFT_VERSION = 5.0;
@@ -681,10 +690,11 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
 				CODE_SIGN_ENTITLEMENTS = macOS/DaysUntilBirthdayOnMac.entitlements;
-				CODE_SIGN_STYLE = Automatic;
+				CODE_SIGN_STYLE = Manual;
 				COMBINE_HIDPI_IMAGES = YES;
 				CURRENT_PROJECT_VERSION = 1;
 				DEVELOPMENT_ASSET_PATHS = "\"macOS/Preview Content\"";
+				DEVELOPMENT_TEAM = EQHXZ8M8AV;
 				ENABLE_PREVIEWS = YES;
 				GENERATE_INFOPLIST_FILE = YES;
 				INFOPLIST_FILE = macOS/Info.plist;
@@ -697,6 +707,7 @@
 				MARKETING_VERSION = 1.0;
 				PRODUCT_BUNDLE_IDENTIFIER = Google.DaysUntilBirthdayOnMac;
 				PRODUCT_NAME = "$(TARGET_NAME)";
+				PROVISIONING_PROFILE_SPECIFIER = "Google Development macOS";
 				SDKROOT = macosx;
 				SWIFT_EMIT_LOC_STRINGS = YES;
 				SWIFT_VERSION = 5.0;

+ 19 - 0
Samples/Swift/DaysUntilBirthday/README.md

@@ -26,6 +26,25 @@ open DaysUntilBirthday.xcodeproj
 ```
 2. Run the `DaysUntilBirthday (iOS)` or `DaysUntilBirthday (macOS)` target.
 
+## A Note on running the macOS Sample
+
+If you are running the `DaysUntilBirthday` sample app on macOS, you may see an
+error like the below:
+
+```
+Error! Optional(Error Domain=com.google.GIDSignIn Code=-2 "keychain error" UserInfo={NSLocalizedDescription=keychain error})
+```
+
+This error is related to the signing of the sample app.
+You have two choices to remedy the problem.
+
+1. We suggest that you manually manage the signing of the macOS sample app so
+that you can provide a provisioning profile. Make sure to select a profile
+that is able to sign macOS apps.
+2. If you do not have the correct provisioning profile, and are unable to
+generate one, then you can add the ["Keychain Sharing" capability](https://developer.apple.com/documentation/xcode/configuring-keychain-sharing)
+to the `DaysUntilBirthday (macOS)` target as a workaround.
+
 ## Integration Tests
 
 We run integration tests on the `DaysUntilBirthday(iOS)` sample app.

+ 1 - 13
Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift

@@ -23,18 +23,7 @@ import AppKit
 #endif
 
 /// An observable class for authenticating via Google.
-final class GoogleSignInAuthenticator {
-  // TODO: Replace this with your own ID.
-  #if os(iOS)
-  private let clientID = "687389107077-8qr6dh8fr4uaja89sdr5ieqb7mep04qv.apps.googleusercontent.com"
-  #elseif os(macOS)
-  private let clientID = "687389107077-8qr6dh8fr4uaja89sdr5ieqb7mep04qv.apps.googleusercontent.com"
-  #endif
-
-  private lazy var configuration: GIDConfiguration = {
-    return GIDConfiguration(clientID: clientID)
-  }()
-
+final class GoogleSignInAuthenticator: ObservableObject {
   private var authViewModel: AuthenticationViewModel
 
   /// Creates an instance of this authenticator.
@@ -43,7 +32,6 @@ final class GoogleSignInAuthenticator {
     self.authViewModel = authViewModel
   }
 
-
   #if os(iOS)
   /// Signs in the user based upon the selected account.
   /// - parameter rootViewController: The `UIViewController` to use during the sign in flow.

+ 3 - 0
Samples/Swift/DaysUntilBirthday/iOS/Info.plist

@@ -61,5 +61,8 @@
 		<string>UIInterfaceOrientationLandscapeLeft</string>
 		<string>UIInterfaceOrientationLandscapeRight</string>
 	</array>
+  <!-- Do not use this client ID in your own app, it will only work for this sample. -->
+  <key>GIDClientID</key>
+  <string>687389107077-8qr6dh8fr4uaja89sdr5ieqb7mep04qv.apps.googleusercontent.com</string>
 </dict>
 </plist>

+ 3 - 0
Samples/Swift/DaysUntilBirthday/macOS/Info.plist

@@ -15,5 +15,8 @@
 			</array>
 		</dict>
 	</array>
+  <!-- Do not use this client ID in your own app, it will only work for this sample. -->
+  <key>GIDClientID</key>
+  <string>687389107077-8qr6dh8fr4uaja89sdr5ieqb7mep04qv.apps.googleusercontent.com</string>
 </dict>
 </plist>