Skip to content

Commit f7bd6b5

Browse files
Fix race condition where checkin is not reset properly when app first installed (#8513)
1 parent c0d3b63 commit f7bd6b5

File tree

8 files changed

+79
-56
lines changed

8 files changed

+79
-56
lines changed

FirebaseMessaging/Apps/Sample/Sample.xcodeproj/project.pbxproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
archiveVersion = 1;
44
classes = {
55
};
6-
objectVersion = 50;
6+
objectVersion = 51;
77
objects = {
88

99
/* Begin PBXBuildFile section */
1010
5125CCA22437F471006CA5D0 /* Sample.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 5125CCA02437F471006CA5D0 /* Sample.xcdatamodeld */; };
1111
5125CCA92437F472006CA5D0 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5125CCA82437F472006CA5D0 /* Preview Assets.xcassets */; };
12+
515617FB26C20330006AEA6E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 515617FA26C20330006AEA6E /* GoogleService-Info.plist */; };
1213
51944B6E258B12E300297021 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 51944B6D258B12E300297021 /* Assets.xcassets */; };
1314
51A1F3D92588405A0025932B /* Identity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1F3D52588405A0025932B /* Identity.swift */; };
1415
51A1F3DA2588405A0025932B /* UserSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1F3D62588405A0025932B /* UserSettings.swift */; };
@@ -26,6 +27,7 @@
2627
5125CCA82437F472006CA5D0 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
2728
5125CCAD2437F472006CA5D0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
2829
5125CCB32437F9A9006CA5D0 /* Sample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Sample.entitlements; sourceTree = "<group>"; };
30+
515617FA26C20330006AEA6E /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../../Shared/GoogleService-Info.plist"; sourceTree = "<group>"; };
2931
51944B6D258B12E300297021 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = ../../Shared/Assets.xcassets; sourceTree = "<group>"; };
3032
51A1F3D52588405A0025932B /* Identity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Identity.swift; path = ../../Shared/Identity.swift; sourceTree = "<group>"; };
3133
51A1F3D62588405A0025932B /* UserSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UserSettings.swift; path = ../../Shared/UserSettings.swift; sourceTree = "<group>"; };
@@ -75,6 +77,7 @@
7577
5125CC9B2437F471006CA5D0 /* Sample */ = {
7678
isa = PBXGroup;
7779
children = (
80+
515617FA26C20330006AEA6E /* GoogleService-Info.plist */,
7881
51C24C732589614800236F25 /* logo.png */,
7982
51C24C5025894EDB00236F25 /* LaunchScreen.storyboard */,
8083
51A1F3DE2588406A0025932B /* AppDelegate.swift */,
@@ -161,6 +164,7 @@
161164
files = (
162165
51C24C742589614800236F25 /* logo.png in Resources */,
163166
5125CCA92437F472006CA5D0 /* Preview Assets.xcassets in Resources */,
167+
515617FB26C20330006AEA6E /* GoogleService-Info.plist in Resources */,
164168
51C24C5225894EDB00236F25 /* LaunchScreen.storyboard in Resources */,
165169
51944B6E258B12E300297021 /* Assets.xcassets in Resources */,
166170
);

FirebaseMessaging/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# 2021-08 -- v8.6.0
22
- [changed] Removed iOS version check from `FIRMessagingExtensionHelper.h` (#8492).
33
- [added] Added new API `FIRMessagingExtensionHelper exportDeliveryMetricsToBigQuery` that allows developers to enable notification delivery metrics that exports to BigQuery. (#6181)
4+
- [fixed] Fixed an issue that delete token no longer works. (#8491)
45

56
# 2021-06 -- v8.2.0
67
- [fixed] Fixed an issue that local scheduled notification is not set correctly due to sound type. (#8172)

FirebaseMessaging/Sources/Token/FIRMessagingAuthService.h

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
NS_ASSUME_NONNULL_BEGIN
2121

2222
@class FIRMessagingCheckinPreferences;
23-
@class FIRMessagingCheckinStore;
2423
/**
2524
* @related FIRInstanceIDCheckinService
2625
*
@@ -42,14 +41,10 @@ typedef void (^FIRMessagingDeviceCheckinCompletion)(
4241
*/
4342
@interface FIRMessagingAuthService : NSObject
4443

45-
/**
46-
* Initializes the auth service given a store (which provides the local caching of checkin info).
47-
* This initializer will create its own instance of FIRMessagingCheckinService.
48-
*/
49-
- (instancetype)initWithCheckinStore:(FIRMessagingCheckinStore *)store;
50-
5144
#pragma mark - Checkin Service
5245

46+
- (BOOL)hasCheckinPlist;
47+
5348
/**
5449
* Checks if the current deviceID and secret are valid or not.
5550
*

FirebaseMessaging/Sources/Token/FIRMessagingAuthService.m

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ @interface FIRMessagingAuthService ()
5353

5454
@implementation FIRMessagingAuthService
5555

56-
- (instancetype)initWithCheckinStore:(FIRMessagingCheckinStore *)checkinStore {
56+
- (instancetype)init {
5757
self = [super init];
5858
if (self) {
59-
_checkinStore = checkinStore;
59+
_checkinStore = [[FIRMessagingCheckinStore alloc] init];
6060
_checkinPreferences = [_checkinStore cachedCheckinPreferences];
6161
_checkinService = [[FIRMessagingCheckinService alloc] init];
6262
_checkinHandlers = [[NSMutableArray alloc] init];
@@ -70,6 +70,10 @@ - (void)dealloc {
7070

7171
#pragma mark - Schedule Checkin
7272

73+
- (BOOL)hasCheckinPlist {
74+
return [_checkinStore hasCheckinPlist];
75+
}
76+
7377
- (void)scheduleCheckin:(BOOL)immediately {
7478
// Checkin is still valid, so a remote checkin is not required.
7579
if ([self.checkinPreferences hasValidCheckinInfo]) {

FirebaseMessaging/Sources/Token/FIRMessagingTokenManager.m

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,7 @@ - (instancetype)init {
5151
self = [super init];
5252
if (self) {
5353
_tokenStore = [[FIRMessagingTokenStore alloc] init];
54-
_checkinStore = [[FIRMessagingCheckinStore alloc] init];
55-
_authService = [[FIRMessagingAuthService alloc] initWithCheckinStore:_checkinStore];
54+
_authService = [[FIRMessagingAuthService alloc] init];
5655
[self resetCredentialsIfNeeded];
5756
[self configureTokenOperations];
5857
_installations = [FIRInstallations installations];
@@ -458,41 +457,40 @@ - (void)deleteWithHandler:(void (^)(NSError *))handler {
458457
* since the Keychain items are marked with `*BackupThisDeviceOnly`.
459458
*/
460459
- (void)resetCredentialsIfNeeded {
461-
BOOL checkinPlistExists = [_checkinStore hasCheckinPlist];
460+
BOOL checkinPlistExists = [_authService hasCheckinPlist];
462461
// Checkin info existed in backup excluded plist. Should not be a fresh install.
463462
if (checkinPlistExists) {
464463
return;
465464
}
466-
467-
// Resets checkin in keychain if a fresh install.
468465
// Keychain can still exist even if app is uninstalled.
469-
FIRMessagingCheckinPreferences *oldCheckinPreferences = [_checkinStore cachedCheckinPreferences];
470-
471-
if (oldCheckinPreferences) {
472-
[_checkinStore removeCheckinPreferencesWithHandler:^(NSError *error) {
473-
if (!error) {
474-
FIRMessagingLoggerDebug(
475-
kFIRMessagingMessageCodeStore002,
476-
@"Removed cached checkin preferences from Keychain because this is a fresh install.");
477-
} else {
478-
FIRMessagingLoggerError(
479-
kFIRMessagingMessageCodeStore003,
480-
@"Couldn't remove cached checkin preferences for a fresh install. Error: %@", error);
481-
}
482-
if (oldCheckinPreferences.deviceID.length && oldCheckinPreferences.secretToken.length) {
483-
FIRMessagingLoggerDebug(kFIRMessagingMessageCodeStore006,
484-
@"App reset detected. Will delete server registrations.");
485-
// We don't really need to delete old FCM tokens created via IID auth tokens since
486-
// those tokens are already hashed by APNS token as the has so creating a new
487-
// token should automatically delete the old-token.
488-
[self didDeleteFCMScopedTokensForCheckin:oldCheckinPreferences];
489-
} else {
490-
FIRMessagingLoggerDebug(kFIRMessagingMessageCodeStore009,
491-
@"App reset detected but no valid checkin auth preferences found."
492-
@" Will not delete server registrations.");
493-
}
494-
}];
466+
FIRMessagingCheckinPreferences *oldCheckinPreferences = _authService.checkinPreferences;
467+
468+
if (!oldCheckinPreferences) {
469+
FIRMessagingLoggerDebug(kFIRMessagingMessageCodeStore009,
470+
@"App reset detected but no valid checkin auth preferences found."
471+
@" Will not delete server token registrations.");
472+
return;
495473
}
474+
[_authService resetCheckinWithHandler:^(NSError *_Nonnull error) {
475+
if (!error) {
476+
FIRMessagingLoggerDebug(
477+
kFIRMessagingMessageCodeStore002,
478+
@"Removed cached checkin preferences from Keychain because this is a fresh install.");
479+
} else {
480+
FIRMessagingLoggerError(
481+
kFIRMessagingMessageCodeStore003,
482+
@"Couldn't remove cached checkin preferences for a fresh install. Error: %@", error);
483+
}
484+
485+
if (oldCheckinPreferences.deviceID.length && oldCheckinPreferences.secretToken.length) {
486+
FIRMessagingLoggerDebug(kFIRMessagingMessageCodeStore006,
487+
@"Resetting old checkin and deleting server token registrations.");
488+
// We don't really need to delete old FCM tokens created via IID auth tokens since
489+
// those tokens are already hashed by APNS token as the has so creating a new
490+
// token should automatically delete the old-token.
491+
[self didDeleteFCMScopedTokensForCheckin:oldCheckinPreferences];
492+
}
493+
}];
496494
}
497495

498496
- (void)didDeleteFCMScopedTokensForCheckin:(FIRMessagingCheckinPreferences *)checkin {

FirebaseMessaging/Tests/UnitTests/FIRMessagingAuthServiceTest.m

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ @interface FIRMessagingAuthService (ExposedForTest)
3737
@property(nonatomic, readonly, strong)
3838
NSMutableArray<FIRMessagingDeviceCheckinCompletion> *checkinHandlers;
3939
@property(nonatomic, readwrite, strong) FIRMessagingCheckinService *checkinService;
40+
@property(nonatomic, readwrite, strong) FIRMessagingCheckinStore *checkinStore;
4041
@end
4142

4243
@interface FIRMessagingAuthServiceTest : XCTestCase
@@ -53,8 +54,9 @@ @implementation FIRMessagingAuthServiceTest
5354

5455
- (void)setUp {
5556
[super setUp];
56-
_mockStore = OCMClassMock([FIRMessagingCheckinStore class]);
57-
_authService = [[FIRMessagingAuthService alloc] initWithCheckinStore:_mockStore];
57+
_authService = [[FIRMessagingAuthService alloc] init];
58+
_mockStore = OCMPartialMock(_authService.checkinStore);
59+
5860
_mockCheckinService = OCMPartialMock(_authService.checkinService);
5961
// The tests here are to focus on checkin interval not locale change, so always set locale as
6062
// non-changed.

FirebaseMessaging/Tests/UnitTests/FIRMessagingTokenManagerTest.m

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#import <OCMock/OCMock.h>
1818
#import <XCTest/XCTest.h>
1919

20+
#import "FirebaseMessaging/Sources/Token/FIRMessagingAuthService.h"
2021
#import "FirebaseMessaging/Sources/Token/FIRMessagingCheckinPreferences.h"
2122
#import "FirebaseMessaging/Sources/Token/FIRMessagingCheckinStore.h"
2223
#import "FirebaseMessaging/Sources/Token/FIRMessagingTokenManager.h"
@@ -30,19 +31,24 @@ @interface FIRMessaging (ExposedForTest)
3031

3132
@interface FIRMessagingTokenManager (ExposedForTest)
3233

33-
@property(nonatomic, readwrite, strong) FIRMessagingCheckinStore *checkinStore;
34-
3534
- (void)resetCredentialsIfNeeded;
3635

3736
@end
3837

38+
@interface FIRMessagingAuthService (ExposedForTest)
39+
40+
@property(nonatomic, readwrite, strong) FIRMessagingCheckinStore *checkinStore;
41+
42+
@end
43+
3944
@interface FIRMessagingTokenManagerTest : XCTestCase {
4045
FIRMessaging *_messaging;
4146
id _mockMessaging;
4247
id _mockPubSub;
4348
id _mockTokenManager;
4449
id _mockInstallations;
4550
id _mockCheckinStore;
51+
id _mockAuthService;
4652
FIRMessagingTestUtilities *_testUtil;
4753
}
4854

@@ -59,10 +65,13 @@ - (void)setUp {
5965
_mockMessaging = _testUtil.mockMessaging;
6066
_messaging = _testUtil.messaging;
6167
_mockTokenManager = _testUtil.mockTokenManager;
62-
_mockCheckinStore = OCMPartialMock(_messaging.tokenManager.checkinStore);
68+
_mockAuthService = OCMPartialMock(_messaging.tokenManager.authService);
69+
_mockCheckinStore = OCMPartialMock(_messaging.tokenManager.authService.checkinStore);
6370
}
6471

6572
- (void)tearDown {
73+
[_mockCheckinStore stopMocking];
74+
[_mockAuthService stopMocking];
6675
[_testUtil cleanupAfterTest:self];
6776
_messaging = nil;
6877
[[[NSUserDefaults alloc] initWithSuiteName:kFIRMessagingDefaultsTestDomain]
@@ -110,30 +119,41 @@ - (void)testTokenChangeMethod {
110119
}
111120

112121
- (void)testResetCredentialsWithNoCachedCheckin {
113-
id niceMockCheckinStore = [OCMockObject niceMockForClass:[FIRMessagingCheckinStore class]];
114-
[[niceMockCheckinStore reject]
115-
removeCheckinPreferencesWithHandler:[OCMArg invokeBlockWithArgs:[NSNull null], nil]];
122+
id completionArg = [OCMArg invokeBlockWithArgs:[NSNull null], nil];
123+
OCMReject([_mockCheckinStore removeCheckinPreferencesWithHandler:completionArg]);
124+
// Always setting up stub after expect.
125+
OCMStub([_mockAuthService checkinPreferences]).andReturn(nil);
126+
127+
[_messaging.tokenManager resetCredentialsIfNeeded];
128+
129+
OCMVerifyAll(_mockCheckinStore);
130+
}
131+
132+
- (void)testResetCredentialsWithoutFreshInstall {
133+
id completionArg = [OCMArg invokeBlockWithArgs:[NSNull null], nil];
134+
OCMReject([_mockCheckinStore removeCheckinPreferencesWithHandler:completionArg]);
116135
// Always setting up stub after expect.
117-
OCMStub([_mockCheckinStore cachedCheckinPreferences]).andReturn(nil);
136+
OCMStub([_mockAuthService hasCheckinPlist]).andReturn(YES);
118137

119138
[_messaging.tokenManager resetCredentialsIfNeeded];
120139

121-
OCMVerifyAll(niceMockCheckinStore);
140+
OCMVerifyAll(_mockCheckinStore);
122141
}
123142

124143
- (void)testResetCredentialsWithFreshInstall {
125144
FIRMessagingCheckinPreferences *checkinPreferences =
126145
[[FIRMessagingCheckinPreferences alloc] initWithDeviceID:@"test-auth-id"
127146
secretToken:@"test-secret"];
128147
// Expect checkin is removed if it's a fresh install.
129-
[[_mockCheckinStore expect]
130-
removeCheckinPreferencesWithHandler:[OCMArg invokeBlockWithArgs:[NSNull null], nil]];
148+
id completionArg = [OCMArg invokeBlockWithArgs:[NSNull null], nil];
149+
OCMExpect([_mockCheckinStore removeCheckinPreferencesWithHandler:completionArg]);
131150
// Always setting up stub after expect.
132-
OCMStub([_mockCheckinStore cachedCheckinPreferences]).andReturn(checkinPreferences);
151+
OCMStub([_mockAuthService checkinPreferences]).andReturn(checkinPreferences);
133152
// Plist file doesn't exist, meaning this is a fresh install.
134153
OCMStub([_mockCheckinStore hasCheckinPlist]).andReturn(NO);
135154

136155
[_messaging.tokenManager resetCredentialsIfNeeded];
137156
OCMVerifyAll(_mockCheckinStore);
138157
}
158+
139159
@end

FirebaseMessaging/Tests/UnitTests/FIRMessagingTokenOperationsTest.m

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,7 @@ - (void)setUp {
9999
_checkinService = [[FIRMessagingCheckinService alloc] init];
100100
_mockCheckinService = OCMPartialMock(_checkinService);
101101

102-
_authService = [[FIRMessagingAuthService alloc]
103-
initWithCheckinStore:[[FIRMessagingCheckinStore alloc] init]];
102+
_authService = [[FIRMessagingAuthService alloc] init];
104103
_instanceID = @"instanceID";
105104

106105
// `FIRMessagingTokenOperation` uses `FIRInstallations` under the hood to get FIS auth token.

0 commit comments

Comments
 (0)