Camden King преди 9 месеца
родител
ревизия
e3ff2c111a

+ 26 - 0
GoogleSignIn/Sources/GIDAuthStateMigration/Fake/GIDFakeAuthStateMigration.h

@@ -0,0 +1,26 @@
+/*
+ * Copyright 2025 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 "GoogleSignIn/Sources/GIDAuthStateMigration/GIDAuthStateMigration.h"
+
+/// A fake |GIDAuthStateMigration| for testing.
+@interface GIDFakeAuthStateMigration : GIDAuthStateMigration
+
+/// Callback that is called when `migrateIfNeededWithTokenURL` is invoked.
+@property (nonatomic, nullable) void (^migrationInvokedCallback)
+  (NSURL * _Nullable tokenURL, NSString * _Nullable callbackPath, BOOL isFreshInstall);
+
+@end

+ 41 - 0
GoogleSignIn/Sources/GIDAuthStateMigration/Fake/GIDFakeAuthStateMigration.m

@@ -0,0 +1,41 @@
+/*
+ * Copyright 2025 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 "GoogleSignIn/Sources/GIDAuthStateMigration/Fake/GIDFakeAuthStateMigration.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@implementation GIDFakeAuthStateMigration
+
+@synthesize migrationInvokedCallback = _migrationInvokedCallback;
+
+- (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore {
+  self = [super initWithKeychainStore:keychainStore];
+  return self;
+}
+
+- (void)migrateIfNeededWithTokenURL:(NSURL *)tokenURL
+                       callbackPath:(NSString *)callbackPath
+                     isFreshInstall:(BOOL)isFreshInstall {
+  if (_migrationInvokedCallback) {
+    _migrationInvokedCallback(tokenURL, callbackPath, isFreshInstall);
+  }
+  return;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 0 - 1
GoogleSignIn/Sources/GIDAuthStateMigration.h → GoogleSignIn/Sources/GIDAuthStateMigration/GIDAuthStateMigration.h

@@ -30,7 +30,6 @@ NS_ASSUME_NONNULL_BEGIN
 /// Perform necessary migrations from legacy auth state storage to most recent GTMAppAuth version.
 - (void)migrateIfNeededWithTokenURL:(NSURL *)tokenURL
                        callbackPath:(NSString *)callbackPath
-                       keychainName:(NSString *)keychainName
                      isFreshInstall:(BOOL)isFreshInstall;
 
 @end

+ 12 - 24
GoogleSignIn/Sources/GIDAuthStateMigration.m → GoogleSignIn/Sources/GIDAuthStateMigration/Implementation/GIDAuthStateMigration.m

@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#import "GoogleSignIn/Sources/GIDAuthStateMigration.h"
-
+#import "GoogleSignIn/Sources/GIDAuthStateMigration/GIDAuthStateMigration.h"
 #import "GoogleSignIn/Sources/GIDSignInCallbackSchemes.h"
 
 @import GTMAppAuth;
@@ -65,30 +64,28 @@ static NSString *const kFingerprintService = @"fingerprint";
 
 - (void)migrateIfNeededWithTokenURL:(NSURL *)tokenURL
                        callbackPath:(NSString *)callbackPath
-                       keychainName:(NSString *)keychainName
                      isFreshInstall:(BOOL)isFreshInstall {
   // If this is a fresh install, take no action and mark the migration checks as having been
   // performed.
   if (isFreshInstall) {
     NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
-#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
+#if TARGET_OS_OSX
     [defaults setBool:YES forKey:kDataProtectedMigrationCheckPerformedKey];
-#elif TARGET_OS_IOS
+#elif TARGET_OS_IOS && !TARGET_OS_MACCATALYST
     [defaults setBool:YES forKey:kGTMAppAuthMigrationCheckPerformedKey];
-#endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
+#endif // TARGET_OS_OSX
     return;
   }
 
-#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
+#if TARGET_OS_OSX
   [self performDataProtectedMigrationIfNeeded];
-#elif TARGET_OS_IOS
+#elif TARGET_OS_IOS && !TARGET_OS_MACCATALYST
   [self performGIDMigrationIfNeededWithTokenURL:tokenURL
-                                   callbackPath:callbackPath
-                                   keychainName:keychainName];
-#endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
+                                   callbackPath:callbackPath];
+#endif // TARGET_OS_OSX
 }
 
-#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
+#if TARGET_OS_OSX
 // Migrate from the fileBasedKeychain to dataProtectedKeychain with GTMAppAuth 5.0.
 - (void)performDataProtectedMigrationIfNeeded {
   // See if we've performed the migration check previously.
@@ -107,10 +104,6 @@ static NSString *const kFingerprintService = @"fingerprint";
   if (authSession) {
     NSError *err;
     [self.keychainStore saveAuthSession:authSession error:&err];
-    // If we're unable to save to the keychain, return without marking migration performed.
-    if (err) {
-      return;
-    };
     [keychainStoreLegacy removeAuthSessionWithError:nil];
   }
 
@@ -118,12 +111,11 @@ static NSString *const kFingerprintService = @"fingerprint";
   [defaults setBool:YES forKey:kDataProtectedMigrationCheckPerformedKey];
 }
 
-#elif TARGET_OS_IOS
+#elif TARGET_OS_IOS && !TARGET_OS_MACCATALYST
 // Migrate from GPPSignIn 1.x or GIDSignIn 1.0 - 4.x to the GTMAppAuth storage introduced in
 // GIDSignIn 5.0.
 - (void)performGIDMigrationIfNeededWithTokenURL:(NSURL *)tokenURL
-                                   callbackPath:(NSString *)callbackPath
-                                   keychainName:(NSString *)keychainName {
+                                   callbackPath:(NSString *)callbackPath {
   // See if we've performed the migration check previously.
   NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
   if ([defaults boolForKey:kGTMAppAuthMigrationCheckPerformedKey]) {
@@ -138,10 +130,6 @@ static NSString *const kFingerprintService = @"fingerprint";
   if (authSession) {
     NSError *err;
     [self.keychainStore saveAuthSession:authSession error:&err];
-    // If we're unable to save to the keychain, return without marking migration performed.
-    if (err) {
-      return;
-    };
   }
 
   // Mark the migration check as having been performed.
@@ -246,7 +234,7 @@ static NSString *const kFingerprintService = @"fingerprint";
   NSString *password = [[NSString alloc] initWithData:passwordData encoding:NSUTF8StringEncoding];
   return password;
 }
-#endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
+#endif // TARGET_OS_OSX
 
 @end
 

+ 14 - 10
GoogleSignIn/Sources/GIDSignIn.m

@@ -21,7 +21,7 @@
 #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDProfileData.h"
 #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignInResult.h"
 
-#import "GoogleSignIn/Sources/GIDAuthStateMigration.h"
+#import "GoogleSignIn/Sources/GIDAuthStateMigration/GIDAuthStateMigration.h"
 #import "GoogleSignIn/Sources/GIDEMMSupport.h"
 #import "GoogleSignIn/Sources/GIDSignInInternalOptions.h"
 #import "GoogleSignIn/Sources/GIDSignInPreferences.h"
@@ -490,15 +490,19 @@ static NSString *const kConfigOpenIDRealmKey = @"GIDOpenIDRealm";
   dispatch_once(&once, ^{
     GTMKeychainStore *keychainStore =
         [[GTMKeychainStore alloc] initWithItemName:kGTMAppAuthKeychainName];
+    GIDAuthStateMigration *authStateMigrationService =
+        [[GIDAuthStateMigration alloc] initWithKeychainStore:keychainStore];
 #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
     if (@available(iOS 14.0, *)) {
       GIDAppCheck *appCheck = [GIDAppCheck appCheckUsingAppAttestProvider];
       sharedInstance = [[self alloc] initWithKeychainStore:keychainStore
+                                 authStateMigrationService:authStateMigrationService
                                                   appCheck:appCheck];
     }
 #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
     if (!sharedInstance) {
-      sharedInstance = [[self alloc] initWithKeychainStore:keychainStore];
+      sharedInstance = [[self alloc] initWithKeychainStore:keychainStore
+                                 authStateMigrationService:authStateMigrationService];
     }
   });
   return sharedInstance;
@@ -533,7 +537,8 @@ static NSString *const kConfigOpenIDRealmKey = @"GIDOpenIDRealm";
 
 #pragma mark - Private methods
 
-- (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore {
+- (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore
+            authStateMigrationService:(GIDAuthStateMigration *)authStateMigrationService {
   self = [super init];
   if (self) {
     // Get the bundle of the current executable.
@@ -561,20 +566,19 @@ static NSString *const kConfigOpenIDRealmKey = @"GIDOpenIDRealm";
                              tokenEndpoint:[NSURL URLWithString:tokenEndpointURL]];
     _keychainStore = keychainStore;
     // Perform migration of auth state from old versions of the SDK if needed.
-    GIDAuthStateMigration *migration =
-        [[GIDAuthStateMigration alloc] initWithKeychainStore:_keychainStore];
-    [migration migrateIfNeededWithTokenURL:_appAuthConfiguration.tokenEndpoint
-                              callbackPath:kBrowserCallbackPath
-                              keychainName:kGTMAppAuthKeychainName
-                            isFreshInstall:isFreshInstall];
+    [authStateMigrationService migrateIfNeededWithTokenURL:_appAuthConfiguration.tokenEndpoint
+                                              callbackPath:kBrowserCallbackPath
+                                            isFreshInstall:isFreshInstall];
   }
   return self;
 }
 
 #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
 - (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore
+            authStateMigrationService:(GIDAuthStateMigration *)authStateMigrationService
                              appCheck:(GIDAppCheck *)appCheck {
-  self = [self initWithKeychainStore:keychainStore];
+  self = [self initWithKeychainStore:keychainStore
+           authStateMigrationService:authStateMigrationService];
   if (self) {
     _appCheck = appCheck;
     _configureAppCheckCalled = NO;

+ 4 - 1
GoogleSignIn/Sources/GIDSignIn_Private.h

@@ -30,6 +30,7 @@ NS_ASSUME_NONNULL_BEGIN
 @class GIDSignInInternalOptions;
 @class GTMKeychainStore;
 @class GIDAppCheck;
+@class GIDAuthStateMigration;
 
 /// Represents a completion block that takes a `GIDSignInResult` on success or an error if the
 /// operation was unsuccessful.
@@ -46,11 +47,13 @@ typedef void (^GIDDisconnectCompletion)(NSError *_Nullable error);
 @property(nonatomic, readwrite, nullable) GIDGoogleUser *currentUser;
 
 /// Private initializer taking a `GTMKeychainStore`.
-- (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore;
+- (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore
+            authStateMigrationService:(GIDAuthStateMigration *)authStateMigrationService;
 
 #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
 /// Private initializer taking a `GTMKeychainStore` and `GIDAppCheckProvider`.
 - (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore
+            authStateMigrationService:(GIDAuthStateMigration *)authStateMigrationService
                              appCheck:(GIDAppCheck *)appCheck
 API_AVAILABLE(ios(14));
 #endif // TARGET_OS_IOS || !TARGET_OS_MACCATALYST

+ 19 - 25
GoogleSignIn/Tests/Unit/GIDAuthStateMigrationTest.m

@@ -14,11 +14,11 @@
 
 #import <XCTest/XCTest.h>
 
-#import "GoogleSignIn/Sources/GIDAuthStateMigration.h"
+#import "GoogleSignIn/Sources/GIDAuthStateMigration/GIDAuthStateMigration.h"
 #import "GoogleSignIn/Sources/GIDSignInCallbackSchemes.h"
-#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
+#if TARGET_OS_OSX
 #import "GoogleSignIn/Tests/Unit/OIDAuthState+Testing.h"
-#endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
+#endif // TARGET_OS_OSX
 
 @import GTMAppAuth;
 
@@ -84,9 +84,9 @@ NS_ASSUME_NONNULL_BEGIN
   id _mockNSBundle;
   id _mockGIDSignInCallbackSchemes;
   id _mockGTMOAuth2Compatibility;
-#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
+#if TARGET_OS_OSX
   id _realLegacyGTMKeychainStore;
-#endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
+#endif // TARGET_OS_OSX
 }
 
 - (void)setUp {
@@ -100,12 +100,12 @@ NS_ASSUME_NONNULL_BEGIN
   _mockNSBundle = OCMStrictClassMock([NSBundle class]);
   _mockGIDSignInCallbackSchemes = OCMStrictClassMock([GIDSignInCallbackSchemes class]);
   _mockGTMOAuth2Compatibility = OCMStrictClassMock([GTMOAuth2Compatibility class]);
-#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
+#if TARGET_OS_OSX
   GTMKeychainAttribute *fileBasedKeychain = [GTMKeychainAttribute useFileBasedKeychain];
   NSSet *attributes = [NSSet setWithArray:@[fileBasedKeychain]];
   _realLegacyGTMKeychainStore = [[GTMKeychainStore alloc] initWithItemName:kKeychainName
                                                         keychainAttributes:attributes];
-#endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
+#endif // TARGET_OS_OSX
 }
 
 - (void)tearDown {
@@ -125,16 +125,16 @@ NS_ASSUME_NONNULL_BEGIN
   [_mockGIDSignInCallbackSchemes stopMocking];
   [_mockGTMOAuth2Compatibility verify];
   [_mockGTMOAuth2Compatibility stopMocking];
-#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
+#if TARGET_OS_OSX
   [_realLegacyGTMKeychainStore removeAuthSessionWithError:nil];
-#endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
+#endif // TARGET_OS_OSX
 
   [super tearDown];
 }
 
 #pragma mark - Tests
 
-#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
+#if TARGET_OS_OSX
 - (void)testMigrateIfNeeded_NoPreviousMigration_DataProtectedMigration {
   [[[_mockUserDefaults stub] andReturn:_mockUserDefaults] standardUserDefaults];
   [[[_mockUserDefaults expect] andReturnValue:@NO] boolForKey:kDataProtectedMigrationCheckPerformedKey];
@@ -154,7 +154,6 @@ NS_ASSUME_NONNULL_BEGIN
       [[GIDAuthStateMigration alloc] initWithKeychainStore:_mockGTMKeychainStore];
   [migration migrateIfNeededWithTokenURL:[NSURL URLWithString:kTokenURL]
                             callbackPath:kCallbackPath
-                            keychainName:kKeychainName
                           isFreshInstall:NO];
 
   // verify that the auth session was removed during migration
@@ -179,12 +178,11 @@ NS_ASSUME_NONNULL_BEGIN
       [[GIDAuthStateMigration alloc] initWithKeychainStore:_mockGTMKeychainStore];
   [migration migrateIfNeededWithTokenURL:[NSURL URLWithString:kTokenURL]
                             callbackPath:kCallbackPath
-                            keychainName:kKeychainName
                           isFreshInstall:NO];
-  XCTAssertNotNil([_realLegacyGTMKeychainStore retrieveAuthSessionWithError:nil]);
+  XCTAssertNil([_realLegacyGTMKeychainStore retrieveAuthSessionWithError:nil]);
 }
 
-#else
+#elif TARGET_OS_IOS && !TARGET_OS_MACCATALYST
 - (void)testMigrateIfNeeded_NoPreviousMigration_GTMAppAuthMigration {
   [[[_mockUserDefaults stub] andReturn:_mockUserDefaults] standardUserDefaults];
   [[[_mockUserDefaults expect] andReturnValue:@NO] boolForKey:kGTMAppAuthMigrationCheckPerformedKey];
@@ -198,7 +196,6 @@ NS_ASSUME_NONNULL_BEGIN
       [[GIDAuthStateMigration alloc] initWithKeychainStore:_mockGTMKeychainStore];
   [migration migrateIfNeededWithTokenURL:[NSURL URLWithString:kTokenURL]
                             callbackPath:kCallbackPath
-                            keychainName:kKeychainName
                           isFreshInstall:NO];
 }
 
@@ -215,7 +212,6 @@ NS_ASSUME_NONNULL_BEGIN
       [[GIDAuthStateMigration alloc] initWithKeychainStore:_mockGTMKeychainStore];
   [migration migrateIfNeededWithTokenURL:[NSURL URLWithString:kTokenURL]
                             callbackPath:kCallbackPath
-                            keychainName:kKeychainName
                           isFreshInstall:NO];
 }
 
@@ -242,39 +238,37 @@ NS_ASSUME_NONNULL_BEGIN
 
   XCTAssertNotNil(authorization);
 }
-#endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
+#endif // TARGET_OS_OSX
 
 - (void)testMigrateIfNeeded_HasPreviousMigration {
   [[[_mockUserDefaults stub] andReturn:_mockUserDefaults] standardUserDefaults];
-#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
+#if TARGET_OS_OSX
   [[[_mockUserDefaults expect] andReturnValue:@YES] boolForKey:kDataProtectedMigrationCheckPerformedKey];
   [[_mockUserDefaults reject] setBool:YES forKey:kDataProtectedMigrationCheckPerformedKey];
-#else
+#elif TARGET_OS_IOS && !TARGET_OS_MACCATALYST
   [[[_mockUserDefaults expect] andReturnValue:@YES] boolForKey:kGTMAppAuthMigrationCheckPerformedKey];
   [[_mockUserDefaults reject] setBool:YES forKey:kGTMAppAuthMigrationCheckPerformedKey];
-#endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
+#endif // TARGET_OS_OSX
 
   GIDAuthStateMigration *migration =
       [[GIDAuthStateMigration alloc] initWithKeychainStore:_mockGTMKeychainStore];
   [migration migrateIfNeededWithTokenURL:[NSURL URLWithString:kTokenURL]
                             callbackPath:kCallbackPath
-                            keychainName:kKeychainName
                           isFreshInstall:NO];
 }
 
 - (void)testMigrateIfNeeded_isFreshInstall {
   [[[_mockUserDefaults stub] andReturn:_mockUserDefaults] standardUserDefaults];
-#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
+#if TARGET_OS_OSX
   [[_mockUserDefaults expect] setBool:YES forKey:kDataProtectedMigrationCheckPerformedKey];
-#else
+#elif TARGET_OS_IOS && !TARGET_OS_MACCATALYST
   [[_mockUserDefaults expect] setBool:YES forKey:kGTMAppAuthMigrationCheckPerformedKey];
-#endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
+#endif // TARGET_OS_OSX
 
   GIDAuthStateMigration *migration =
       [[GIDAuthStateMigration alloc] initWithKeychainStore:_mockGTMKeychainStore];
   [migration migrateIfNeededWithTokenURL:[NSURL URLWithString:kTokenURL]
                             callbackPath:kCallbackPath
-                            keychainName:kKeychainName
                           isFreshInstall:YES];
 }
 

+ 33 - 5
GoogleSignIn/Tests/Unit/GIDSignInTest.m

@@ -40,6 +40,7 @@
 #import "GoogleSignIn/Sources/GIDEMMErrorHandler.h"
 #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
 
+#import "GoogleSignIn/Sources/GIDAuthStateMigration/Fake/GIDFakeAuthStateMigration.h"
 #import "GoogleSignIn/Tests/Unit/GIDFakeFetcher.h"
 #import "GoogleSignIn/Tests/Unit/GIDFakeFetcherService.h"
 #import "GoogleSignIn/Tests/Unit/GIDFakeMainBundle.h"
@@ -221,6 +222,9 @@ static NSString *const kNewScope = @"newScope";
   // Whether callback block has been called.
   BOOL _completionCalled;
 
+  // Fake for |GIDAuthStateMigration|.
+  GIDFakeAuthStateMigration *_authStateMigrationService;
+
   // Fake fetcher service to emulate network requests.
   GIDFakeFetcherService *_fetcherService;
 
@@ -329,6 +333,7 @@ static NSString *const kNewScope = @"newScope";
       callback:COPY_TO_ARG_BLOCK(self->_savedTokenCallback)]);
 
   // Fakes
+  _authStateMigrationService = [[GIDFakeAuthStateMigration alloc] init];
   _fetcherService = [[GIDFakeFetcherService alloc] init];
   _fakeMainBundle = [[GIDFakeMainBundle alloc] init];
   [_fakeMainBundle startFakingWithClientID:kClientId];
@@ -338,7 +343,8 @@ static NSString *const kNewScope = @"newScope";
   _testUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:kUserDefaultsSuiteName];
   [_testUserDefaults setBool:YES forKey:kAppHasRunBeforeKey];
 
-  _signIn = [[GIDSignIn alloc] initWithKeychainStore:_keychainStore];
+  _signIn = [[GIDSignIn alloc] initWithKeychainStore:_keychainStore
+                           authStateMigrationService:_authStateMigrationService];
   _hint = nil;
 
   __weak GIDSignInTest *weakSelf = self;
@@ -389,6 +395,7 @@ static NSString *const kNewScope = @"newScope";
                                                              userDefaults:_testUserDefaults];
 
     GIDSignIn *signIn = [[GIDSignIn alloc] initWithKeychainStore:_keychainStore
+                                       authStateMigrationService:_authStateMigrationService
                                                         appCheck:appCheck];
     [signIn configureWithCompletion:^(NSError * _Nullable error) {
       XCTAssertNil(error);
@@ -412,6 +419,7 @@ static NSString *const kNewScope = @"newScope";
                                          userDefaults:_testUserDefaults];
 
     GIDSignIn *signIn = [[GIDSignIn alloc] initWithKeychainStore:_keychainStore
+                                       authStateMigrationService:_authStateMigrationService
                                                         appCheck:appCheck];
 
     // Should fail if missing both token and error
@@ -430,7 +438,8 @@ static NSString *const kNewScope = @"newScope";
 - (void)testInitWithKeychainStore {
   GTMKeychainStore *store = [[GTMKeychainStore alloc] initWithItemName:@"foo"];
   GIDSignIn *signIn;
-  signIn = [[GIDSignIn alloc] initWithKeychainStore:store];
+  signIn = [[GIDSignIn alloc] initWithKeychainStore:store
+                          authStateMigrationService:_authStateMigrationService];
   XCTAssertNotNil(signIn.configuration);
   XCTAssertEqual(signIn.configuration.clientID, kClientId);
   XCTAssertNil(signIn.configuration.serverClientID);
@@ -445,7 +454,8 @@ static NSString *const kNewScope = @"newScope";
                         openIDRealm:nil];
   GTMKeychainStore *store = [[GTMKeychainStore alloc] initWithItemName:@"foo"];
   GIDSignIn *signIn;
-  signIn = [[GIDSignIn alloc] initWithKeychainStore:store];
+  signIn = [[GIDSignIn alloc] initWithKeychainStore:store
+                          authStateMigrationService:_authStateMigrationService];
   XCTAssertNil(signIn.configuration);
 }
 
@@ -457,7 +467,8 @@ static NSString *const kNewScope = @"newScope";
 
   GTMKeychainStore *store = [[GTMKeychainStore alloc] initWithItemName:@"foo"];
   GIDSignIn *signIn;
-  signIn = [[GIDSignIn alloc] initWithKeychainStore:store];
+  signIn = [[GIDSignIn alloc] initWithKeychainStore:store
+                          authStateMigrationService:_authStateMigrationService];
   XCTAssertNotNil(signIn.configuration);
   XCTAssertEqual(signIn.configuration.clientID, kClientId);
   XCTAssertEqual(signIn.configuration.serverClientID, kServerClientId);
@@ -472,10 +483,27 @@ static NSString *const kNewScope = @"newScope";
                         openIDRealm:nil];
   GTMKeychainStore *store = [[GTMKeychainStore alloc] initWithItemName:@"foo"];
   GIDSignIn *signIn;
-  signIn = [[GIDSignIn alloc] initWithKeychainStore:store];
+  signIn = [[GIDSignIn alloc] initWithKeychainStore:store
+                          authStateMigrationService:_authStateMigrationService];
   XCTAssertNil(signIn.configuration);
 }
 
+- (void)testInitWithKeychainStore_attemptsMigration {
+  XCTestExpectation *expectation = [self expectationWithDescription:@"Callback should be called."];
+  _authStateMigrationService.migrationInvokedCallback =
+    ^(NSURL *tokenURL, NSString *callbackPath, BOOL isFreshInstall) {
+      XCTAssertFalse(isFreshInstall);
+      [expectation fulfill];
+    };
+
+  GTMKeychainStore *store = [[GTMKeychainStore alloc] initWithItemName:kKeychainName];
+  GIDSignIn *signIn = [[GIDSignIn alloc] initWithKeychainStore:store
+                                     authStateMigrationService:_authStateMigrationService];
+
+  XCTAssertNotNil(signIn.configuration);
+  [self waitForExpectationsWithTimeout:1 handler:nil];
+}
+
 - (void)testRestorePreviousSignInNoRefresh_hasPreviousUser {
   [[[_authorization stub] andReturn:_authState] authState];
 #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST