diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index ee5fd660..3db1f824 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - os: [macos-13] + os: [macos-15] podspec: [GoogleSignIn.podspec, GoogleSignInSwiftSupport.podspec] flag: [ "", diff --git a/GoogleSignIn/Sources/GIDSignIn.m b/GoogleSignIn/Sources/GIDSignIn.m index 1c043735..015c68c5 100644 --- a/GoogleSignIn/Sources/GIDSignIn.m +++ b/GoogleSignIn/Sources/GIDSignIn.m @@ -119,8 +119,7 @@ // Error string for user cancelations. static NSString *const kUserCanceledError = @"The user canceled the sign-in flow."; -// User preference key to detect fresh install of the app. -static NSString *const kAppHasRunBeforeKey = @"GID_AppHasRunBefore"; +NSString *const kAppHasRunBeforeKey = @"GID_AppHasRunBefore"; // Maximum retry interval in seconds for the fetcher. static const NSTimeInterval kFetcherMaxRetryInterval = 15.0; @@ -553,6 +552,11 @@ - (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore // Check to see if the 3P app is being run for the first time after a fresh install. BOOL isFreshInstall = [self isFreshInstall]; + + // If this is a fresh install, ensure that any pre-existing keychain data is purged. + if (isFreshInstall) { + [self removeAllKeychainEntries]; + } NSString *authorizationEnpointURL = [NSString stringWithFormat:kAuthorizationURLTemplate, [GIDSignInPreferences googleAuthorizationServer]]; diff --git a/GoogleSignIn/Sources/GIDSignIn_Private.h b/GoogleSignIn/Sources/GIDSignIn_Private.h index bb642cb7..803cc10e 100644 --- a/GoogleSignIn/Sources/GIDSignIn_Private.h +++ b/GoogleSignIn/Sources/GIDSignIn_Private.h @@ -32,6 +32,9 @@ NS_ASSUME_NONNULL_BEGIN @class GIDAppCheck; @class GIDAuthStateMigration; +/// User preference key to detect fresh install of the app. +extern NSString *const kAppHasRunBeforeKey; + /// Represents a completion block that takes a `GIDSignInResult` on success or an error if the /// operation was unsuccessful. typedef void (^GIDSignInCompletion)(GIDSignInResult *_Nullable signInResult, diff --git a/GoogleSignIn/Tests/Unit/GIDSignInTest.m b/GoogleSignIn/Tests/Unit/GIDSignInTest.m index b36e197a..8db96b41 100644 --- a/GoogleSignIn/Tests/Unit/GIDSignInTest.m +++ b/GoogleSignIn/Tests/Unit/GIDSignInTest.m @@ -119,8 +119,6 @@ @"com.google.UnitTests:///emmcallback?action=unrecognized"; static NSString * const kDevicePolicyAppBundleID = @"com.google.DevicePolicy"; -static NSString * const kAppHasRunBeforeKey = @"GPP_AppHasRunBefore"; - static NSString * const kFingerprintKeychainName = @"fingerprint"; static NSString * const kVerifierKeychainName = @"verifier"; static NSString * const kVerifierKey = @"verifier"; @@ -993,6 +991,19 @@ - (void)testNotHandleWrongPath { XCTAssertFalse(_completionCalled, @"should not call delegate"); } +#pragma mark - Test Fresh Install + +- (void)testFreshInstall_removesKeychainEntries { + // Simulate that the app has been deleted and user defaults removed. + [NSUserDefaults.standardUserDefaults removeObjectForKey:kAppHasRunBeforeKey]; + // Initialization should check `isFreshInstall`. + GIDSignIn *signIn = [[GIDSignIn alloc] initWithKeychainStore:_keychainStore + authStateMigrationService:_authStateMigrationService]; + // If `isFreshInstall`, keychain entries should be removed. + XCTAssertNotNil(signIn); + XCTAssertTrue(self->_keychainRemoved); +} + #pragma mark - Tests - disconnectWithCallback: // Verifies disconnect calls callback with no errors if access token is present.