Skip to content

Commit 8c1168d

Browse files
FirebaseInstallation: send IID checkin data on IID migration. (#4114)
* FIRInstallationsIIDCheckinStore with tests introduced. * FIRInstallationsIIDCheckinStore implementation and tests. * Store IID checkin data. * FIRInstallationsIDControllerTests: IID checkin migration tests. * FIRInstallationsIDController: IID checkin migration logic. * style.sh * FIRInstallationsAPIServiceTests: IID migration header test. * FIRInstallationsAPIService: send IID checkin data in CreateInstallation API request. * FIRInstallationsIIDCheckinStoreTests: fix FIRInstanceIDCheckinStore subDirectoryName. * FIRInstallationsIIDCheckinStoreTests: fix missing sub directory. * Disable FIRInstallationsIntegrationTests for macOS. * FIRInstallationsIntegrationTests: properly disable all tests on macOS. * Fix.
1 parent cd460ec commit 8c1168d

23 files changed

+686
-125
lines changed

FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ void FIRInstallationsItemSetErrorToPointer(NSError *error, NSError **pointer);
3939

4040
+ (NSError *)FIDRegistrationErrorWithResponseMissingField:(NSString *)missingFieldName;
4141

42+
+ (NSError *)corruptedIIDCheckingData;
43+
4244
+ (FIRInstallationsHTTPError *)APIErrorWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse
4345
data:(nullable NSData *)data;
4446
+ (BOOL)isAPIError:(NSError *)error withHTTPCode:(NSInteger)HTTPCode;

FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.m

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ + (NSError *)installationItemNotFoundForAppID:(NSString *)appID appName:(NSStrin
5959
underlyingError:nil];
6060
}
6161

62+
+ (NSError *)corruptedIIDCheckingData {
63+
NSString *failureReason =
64+
@"IID checking data stored in Keychain is corrupted or in an incompatible format.";
65+
return [self installationsErrorWithCode:FIRInstallationsErrorCodeUnknown
66+
failureReason:failureReason
67+
underlyingError:nil];
68+
}
69+
6270
+ (FIRInstallationsHTTPError *)APIErrorWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse
6371
data:(nullable NSData *)data {
6472
return [[FIRInstallationsHTTPError alloc] initWithHTTPResponse:HTTPResponse data:data];

FirebaseInstallations/Source/Library/FIRInstallationsItem.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
@class FIRInstallationsStoredAuthToken;
2323
@class FIRInstallationsStoredRegistrationError;
2424
@class FIRInstallationsStoredRegistrationParameters;
25+
@class FIRInstallationsStoredIIDCheckin;
2526

2627
NS_ASSUME_NONNULL_BEGIN
2728

@@ -47,6 +48,9 @@ NS_ASSUME_NONNULL_BEGIN
4748

4849
@property(nonatomic, nullable) FIRInstallationsStoredRegistrationError *registrationError;
4950

51+
/// Instance ID checkin data imported from IID checkin store as a part of IID migration.
52+
@property(nonatomic, nullable) FIRInstallationsStoredIIDCheckin *IIDCheckin;
53+
5054
- (instancetype)initWithAppID:(NSString *)appID firebaseAppName:(NSString *)firebaseAppName;
5155

5256
/**

FirebaseInstallations/Source/Library/FIRInstallationsItem.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ - (void)updateWithStoredItem:(FIRInstallationsStoredItem *)item {
4848
self.authToken = item.authToken;
4949
self.registrationStatus = item.registrationStatus;
5050
self.registrationError = item.registrationError;
51+
self.IIDCheckin = item.IIDCheckin;
5152
}
5253

5354
- (FIRInstallationsStoredItem *)storedItem {
@@ -57,6 +58,7 @@ - (FIRInstallationsStoredItem *)storedItem {
5758
storedItem.authToken = self.authToken;
5859
storedItem.registrationStatus = self.registrationStatus;
5960
storedItem.registrationError = self.registrationError;
61+
storedItem.IIDCheckin = self.IIDCheckin;
6062
return storedItem;
6163
}
6264

FirebaseInstallations/Source/Library/FIRInstallationsLogger.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,7 @@ extern NSString *const kFIRInstallationsMessageCodeRegistrationErrorFailedToDeco
4949

5050
// FIRInstallationsStoredRegistrationParameters.m
5151
extern NSString *const kFIRInstallationsMessageCodeRegistrationParametersCoderVersionMismatch;
52+
53+
// FIRInstallationsStoredIIDCheckin.m
54+
extern NSString *const kFIRInstallationsMessageCodeIIDCheckinCoderVersionMismatch;
55+
extern NSString *const kFIRInstallationsMessageCodeIIDCheckinFailedToDecode;

FirebaseInstallations/Source/Library/FIRInstallationsLogger.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,7 @@
4848
// FIRInstallationsStoredRegistrationParameters.m
4949
NSString *const kFIRInstallationsMessageCodeRegistrationParametersCoderVersionMismatch =
5050
@"I-FIS006000";
51+
52+
// FIRInstallationsStoredIIDCheckin.m
53+
NSString *const kFIRInstallationsMessageCodeIIDCheckinCoderVersionMismatch = @"I-FIS007000";
54+
NSString *const kFIRInstallationsMessageCodeIIDCheckinFailedToDecode = @"I-FIS007001";
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2019 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import <Foundation/Foundation.h>
18+
19+
@class FIRInstallationsStoredIIDCheckin;
20+
@class FBLPromise<ValueType>;
21+
22+
NS_ASSUME_NONNULL_BEGIN
23+
24+
@interface FIRInstallationsIIDCheckinStore : NSObject
25+
26+
/*
27+
* Tries to read IID checking from the Keychain (see also `FIRInstanceIDCheckinStore`).
28+
* @return Returns a promise that is resolved with the checkin object when all required data found
29+
* in the Keychain. The promise is rejected when the data is missing.
30+
*/
31+
- (FBLPromise<FIRInstallationsStoredIIDCheckin *> *)existingCheckin;
32+
33+
@end
34+
35+
NS_ASSUME_NONNULL_END
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright 2019 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import "FIRInstallationsIIDCheckinStore.h"
18+
19+
#if __has_include(<FBLPromises/FBLPromises.h>)
20+
#import <FBLPromises/FBLPromises.h>
21+
#else
22+
#import "FBLPromises.h"
23+
#endif
24+
25+
#import "FIRInstallationsErrorUtil.h"
26+
#import "FIRInstallationsKeychainUtils.h"
27+
#import "FIRInstallationsStoredIIDCheckin.h"
28+
29+
NSString *const kFIRInstallationsIIDCheckinKeychainGeneric = @"com.google.iid";
30+
NSString *const kFIRFIRInstallationsIIDCheckinKeychainService = @"com.google.iid.checkin";
31+
32+
@implementation FIRInstallationsIIDCheckinStore
33+
34+
- (FBLPromise<FIRInstallationsStoredIIDCheckin *> *)existingCheckin {
35+
return [[FBLPromise onQueue:dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)
36+
do:^id _Nullable {
37+
return [self IIDCheckinData];
38+
}] onQueue:dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)
39+
then:^id _Nullable(NSData *_Nullable keychainData) {
40+
return [self IIDCheckinWithData:keychainData];
41+
}];
42+
}
43+
44+
- (FBLPromise<FIRInstallationsStoredIIDCheckin *> *)IIDCheckinWithData:(NSData *)data {
45+
FBLPromise<FIRInstallationsStoredIIDCheckin *> *resultPromise = [FBLPromise pendingPromise];
46+
47+
NSString *checkinKeychainContent = [[NSString alloc] initWithData:data
48+
encoding:NSUTF8StringEncoding];
49+
NSArray<NSString *> *checkinComponents =
50+
[checkinKeychainContent componentsSeparatedByString:@"|"];
51+
52+
if (checkinComponents.count < 2) {
53+
[resultPromise reject:[FIRInstallationsErrorUtil corruptedIIDCheckingData]];
54+
return resultPromise;
55+
}
56+
57+
NSString *deviceID = checkinComponents[0];
58+
NSString *secret = checkinComponents[1];
59+
60+
if (deviceID.length < 1 || secret.length < 1) {
61+
[resultPromise reject:[FIRInstallationsErrorUtil corruptedIIDCheckingData]];
62+
return resultPromise;
63+
}
64+
65+
__auto_type checkin = [[FIRInstallationsStoredIIDCheckin alloc] initWithDeviceID:deviceID
66+
secretToken:secret];
67+
[resultPromise fulfill:checkin];
68+
69+
return resultPromise;
70+
}
71+
72+
- (FBLPromise<NSData *> *)IIDCheckinData {
73+
FBLPromise<NSData *> *resultPromise = [FBLPromise pendingPromise];
74+
75+
NSMutableDictionary *keychainQuery = [self IIDCheckinDataKeychainQuery];
76+
NSError *error;
77+
NSData *data = [FIRInstallationsKeychainUtils getItemWithQuery:keychainQuery error:&error];
78+
79+
if (data) {
80+
[resultPromise fulfill:data];
81+
return resultPromise;
82+
} else if (error) {
83+
[resultPromise reject:error];
84+
return resultPromise;
85+
} else {
86+
[resultPromise reject:[FIRInstallationsErrorUtil corruptedIIDCheckingData]];
87+
return resultPromise;
88+
}
89+
}
90+
91+
- (NSMutableDictionary *)IIDCheckinDataKeychainQuery {
92+
NSDictionary *query = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword};
93+
94+
NSMutableDictionary *finalQuery = [NSMutableDictionary dictionaryWithDictionary:query];
95+
finalQuery[(__bridge NSString *)kSecAttrGeneric] = kFIRInstallationsIIDCheckinKeychainGeneric;
96+
97+
NSString *account = [self IIDAppIdentifier];
98+
if ([account length]) {
99+
finalQuery[(__bridge NSString *)kSecAttrAccount] = account;
100+
}
101+
102+
finalQuery[(__bridge NSString *)kSecAttrService] = kFIRFIRInstallationsIIDCheckinKeychainService;
103+
return finalQuery;
104+
}
105+
106+
- (NSString *)IIDAppIdentifier {
107+
return [[NSBundle mainBundle] bundleIdentifier] ?: @"";
108+
}
109+
110+
@end

FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.m

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@
2727
#import "FIRInstallationsErrorUtil.h"
2828
#import "FIRInstallationsItem+RegisterInstallationAPI.h"
2929
#import "FIRInstallationsLogger.h"
30+
#import "FIRInstallationsStoredIIDCheckin.h"
3031

3132
NSString *const kFIRInstallationsAPIBaseURL = @"https://firebaseinstallations.googleapis.com";
3233
NSString *const kFIRInstallationsAPIKey = @"X-Goog-Api-Key";
34+
NSString *const kFIRInstallationsIIDMigrationAuthHeader = @"x-goog-fis-ios-iid-migration-auth";
3335

3436
NS_ASSUME_NONNULL_BEGIN
3537

@@ -128,7 +130,20 @@ - (NSURLRequest *)registerRequestWithInstallation:(FIRInstallationsItem *)instal
128130
@"sdkVersion" : [self SDKVersion]
129131
};
130132

131-
return [self requestWithURL:URL HTTPMethod:@"POST" bodyDict:bodyDict refreshToken:nil];
133+
NSDictionary *headers;
134+
if (installation.IIDCheckin && installation.IIDCheckin.deviceID &&
135+
installation.IIDCheckin.secretToken) {
136+
NSString *IIDAuthHeaderValue =
137+
[NSString stringWithFormat:@"%@:%@", installation.IIDCheckin.deviceID,
138+
installation.IIDCheckin.secretToken];
139+
headers = @{kFIRInstallationsIIDMigrationAuthHeader : IIDAuthHeaderValue};
140+
}
141+
142+
return [self requestWithURL:URL
143+
HTTPMethod:@"POST"
144+
bodyDict:bodyDict
145+
refreshToken:nil
146+
additionalHeaders:headers];
132147
}
133148

134149
- (FBLPromise<FIRInstallationsItem *> *)
@@ -215,14 +230,33 @@ - (NSURLRequest *)requestWithURL:(NSURL *)requestURL
215230
HTTPMethod:(NSString *)HTTPMethod
216231
bodyDict:(NSDictionary *)bodyDict
217232
refreshToken:(nullable NSString *)refreshToken {
218-
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestURL];
233+
return [self requestWithURL:requestURL
234+
HTTPMethod:HTTPMethod
235+
bodyDict:bodyDict
236+
refreshToken:refreshToken
237+
additionalHeaders:nil];
238+
}
239+
240+
- (NSURLRequest *)requestWithURL:(NSURL *)requestURL
241+
HTTPMethod:(NSString *)HTTPMethod
242+
bodyDict:(NSDictionary *)bodyDict
243+
refreshToken:(nullable NSString *)refreshToken
244+
additionalHeaders:
245+
(nullable NSDictionary<NSString *, NSString *> *)additionalHeaders {
246+
__block NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestURL];
219247
request.HTTPMethod = HTTPMethod;
220248
[request addValue:self.APIKey forHTTPHeaderField:kFIRInstallationsAPIKey];
221249
[self setJSONHTTPBody:bodyDict forRequest:request];
222250
if (refreshToken) {
223251
NSString *authHeader = [NSString stringWithFormat:@"FIS_v2 %@", refreshToken];
224252
[request setValue:authHeader forHTTPHeaderField:@"Authorization"];
225253
}
254+
255+
[additionalHeaders enumerateKeysAndObjectsUsingBlock:^(
256+
NSString *_Nonnull key, NSString *_Nonnull obj, BOOL *_Nonnull stop) {
257+
[request setValue:obj forHTTPHeaderField:key];
258+
}];
259+
226260
return [request copy];
227261
}
228262

0 commit comments

Comments
 (0)