FIRInstanceIDTokenOperationsTest.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  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. #import <XCTest/XCTest.h>
  17. #import <FirebaseInstanceID/FIRInstanceID.h>
  18. #import <OCMock/OCMock.h>
  19. #import "Firebase/InstanceID/FIRInstanceIDAuthService.h"
  20. #import "Firebase/InstanceID/FIRInstanceIDCheckinPreferences+Internal.h"
  21. #import "Firebase/InstanceID/FIRInstanceIDCheckinService.h"
  22. #import "Firebase/InstanceID/FIRInstanceIDConstants.h"
  23. #import "Firebase/InstanceID/FIRInstanceIDKeychain.h"
  24. #import "Firebase/InstanceID/FIRInstanceIDStore.h"
  25. #import "Firebase/InstanceID/FIRInstanceIDTokenDeleteOperation.h"
  26. #import "Firebase/InstanceID/FIRInstanceIDTokenFetchOperation.h"
  27. #import "Firebase/InstanceID/FIRInstanceIDTokenOperation+Private.h"
  28. #import "Firebase/InstanceID/FIRInstanceIDTokenOperation.h"
  29. #import "Firebase/InstanceID/NSError+FIRInstanceID.h"
  30. #import <FirebaseCore/FIRAppInternal.h>
  31. #import <GoogleUtilities/GULHeartbeatDateStorage.h>
  32. static NSString *kDeviceID = @"fakeDeviceID";
  33. static NSString *kSecretToken = @"fakeSecretToken";
  34. static NSString *kDigestString = @"test-digest";
  35. static NSString *kVersionInfoString = @"version_info-1.0.0";
  36. static NSString *kAuthorizedEntity = @"sender-1234567";
  37. static NSString *kScope = @"fcm";
  38. static NSString *kRegistrationToken = @"token-12345";
  39. @interface FIRInstanceIDTokenOperation (ExposedForTest)
  40. - (void)performTokenOperation;
  41. @end
  42. @interface FIRInstanceIDTokenOperationsTest : XCTestCase
  43. @property(strong, readonly, nonatomic) FIRInstanceIDAuthService *authService;
  44. @property(strong, readonly, nonatomic) id mockAuthService;
  45. @property(strong, readonly, nonatomic) id mockStore;
  46. @property(strong, readonly, nonatomic) FIRInstanceIDCheckinService *checkinService;
  47. @property(strong, readonly, nonatomic) id mockCheckinService;
  48. @property(strong, readonly, nonatomic) NSString *instanceID;
  49. @property(nonatomic, readwrite, strong) FIRInstanceIDCheckinPreferences *checkinPreferences;
  50. @end
  51. @implementation FIRInstanceIDTokenOperationsTest
  52. - (void)setUp {
  53. [super setUp];
  54. _mockStore = OCMClassMock([FIRInstanceIDStore class]);
  55. _checkinService = [[FIRInstanceIDCheckinService alloc] init];
  56. _mockCheckinService = OCMPartialMock(_checkinService);
  57. _authService = [[FIRInstanceIDAuthService alloc] initWithCheckinService:_mockCheckinService
  58. store:_mockStore];
  59. _instanceID = @"instanceID";
  60. NSString *const kHeartbeatStorageFile = @"HEARTBEAT_INFO_STORAGE";
  61. GULHeartbeatDateStorage *dataStorage =
  62. [[GULHeartbeatDateStorage alloc] initWithFileName:kHeartbeatStorageFile];
  63. [[NSFileManager defaultManager] removeItemAtURL:[dataStorage fileURL] error:nil];
  64. }
  65. - (void)testThatTokenOperationsAuthHeaderStringMatchesCheckin {
  66. int64_t tenHoursAgo = FIRInstanceIDCurrentTimestampInMilliseconds() - 10 * 60 * 60 * 1000;
  67. FIRInstanceIDCheckinPreferences *checkin =
  68. [self setCheckinPreferencesWithLastCheckinTime:tenHoursAgo];
  69. NSString *expectedAuthHeader = [FIRInstanceIDTokenOperation HTTPAuthHeaderFromCheckin:checkin];
  70. XCTestExpectation *authHeaderMatchesCheckinExpectation =
  71. [self expectationWithDescription:@"Auth header string in request matches checkin info"];
  72. FIRInstanceIDTokenFetchOperation *operation =
  73. [[FIRInstanceIDTokenFetchOperation alloc] initWithAuthorizedEntity:kAuthorizedEntity
  74. scope:kScope
  75. options:nil
  76. checkinPreferences:checkin
  77. instanceID:self.instanceID];
  78. operation.testBlock =
  79. ^(NSURLRequest *request, FIRInstanceIDURLRequestTestResponseBlock response) {
  80. NSDictionary<NSString *, NSString *> *headers = request.allHTTPHeaderFields;
  81. NSString *authHeader = headers[@"Authorization"];
  82. if ([authHeader isEqualToString:expectedAuthHeader]) {
  83. [authHeaderMatchesCheckinExpectation fulfill];
  84. }
  85. // Return a response (doesnt matter what the response is)
  86. NSData *responseBody = [self dataForFetchRequest:request returnValidToken:YES];
  87. NSHTTPURLResponse *responseObject = [[NSHTTPURLResponse alloc] initWithURL:request.URL
  88. statusCode:200
  89. HTTPVersion:@"HTTP/1.1"
  90. headerFields:nil];
  91. response(responseBody, responseObject, nil);
  92. };
  93. [operation start];
  94. [self waitForExpectationsWithTimeout:0.25
  95. handler:^(NSError *_Nullable error) {
  96. XCTAssertNil(error.localizedDescription);
  97. }];
  98. }
  99. - (void)testThatTokenOperationWithoutCheckInFails {
  100. // If asserts are enabled, test for the assert to be thrown, otherwise check for the resulting
  101. // error in the completion handler.
  102. XCTestExpectation *failedExpectation =
  103. [self expectationWithDescription:@"Operation failed without checkin info"];
  104. // This will return hasCheckinInfo == NO
  105. FIRInstanceIDCheckinPreferences *emptyCheckinPreferences =
  106. [[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:@"" secretToken:@""];
  107. FIRInstanceIDTokenOperation *operation =
  108. [[FIRInstanceIDTokenOperation alloc] initWithAction:FIRInstanceIDTokenActionFetch
  109. forAuthorizedEntity:kAuthorizedEntity
  110. scope:kScope
  111. options:nil
  112. checkinPreferences:emptyCheckinPreferences
  113. instanceID:self.instanceID];
  114. [operation addCompletionHandler:^(FIRInstanceIDTokenOperationResult result,
  115. NSString *_Nullable token, NSError *_Nullable error) {
  116. [failedExpectation fulfill];
  117. }];
  118. @try {
  119. [operation start];
  120. } @catch (NSException *exception) {
  121. if (exception.name == NSInternalInconsistencyException) {
  122. [failedExpectation fulfill];
  123. }
  124. } @finally {
  125. }
  126. [self waitForExpectationsWithTimeout:0.25
  127. handler:^(NSError *_Nullable error) {
  128. XCTAssertNil(error.localizedDescription);
  129. }];
  130. }
  131. - (void)testThatAnAlreadyCancelledOperationFinishesWithoutStarting {
  132. XCTestExpectation *cancelledExpectation =
  133. [self expectationWithDescription:@"Operation finished as cancelled"];
  134. XCTestExpectation *didNotCallPerform =
  135. [self expectationWithDescription:@"Did not call performTokenOperation"];
  136. __block BOOL performWasCalled = NO;
  137. int64_t tenHoursAgo = FIRInstanceIDCurrentTimestampInMilliseconds() - 10 * 60 * 60 * 1000;
  138. FIRInstanceIDCheckinPreferences *checkinPreferences =
  139. [self setCheckinPreferencesWithLastCheckinTime:tenHoursAgo];
  140. FIRInstanceIDTokenOperation *operation =
  141. [[FIRInstanceIDTokenOperation alloc] initWithAction:FIRInstanceIDTokenActionFetch
  142. forAuthorizedEntity:kAuthorizedEntity
  143. scope:kScope
  144. options:nil
  145. checkinPreferences:checkinPreferences
  146. instanceID:self.instanceID];
  147. [operation addCompletionHandler:^(FIRInstanceIDTokenOperationResult result,
  148. NSString *_Nullable token, NSError *_Nullable error) {
  149. if (result == FIRInstanceIDTokenOperationCancelled) {
  150. [cancelledExpectation fulfill];
  151. }
  152. if (!performWasCalled) {
  153. [didNotCallPerform fulfill];
  154. }
  155. }];
  156. id mockOperation = OCMPartialMock(operation);
  157. [[[mockOperation stub] andDo:^(NSInvocation *invocation) {
  158. performWasCalled = YES;
  159. }] performTokenOperation];
  160. [operation cancel];
  161. [operation start];
  162. [self waitForExpectationsWithTimeout:0.25
  163. handler:^(NSError *_Nullable error) {
  164. XCTAssertNil(error.localizedDescription);
  165. }];
  166. }
  167. - (void)testThatOptionsDictionaryIsIncludedWithFetchRequest {
  168. XCTestExpectation *optionsIncludedExpectation =
  169. [self expectationWithDescription:@"Options keys were included in token URL request"];
  170. int64_t tenHoursAgo = FIRInstanceIDCurrentTimestampInMilliseconds() - 10 * 60 * 60 * 1000;
  171. FIRInstanceIDCheckinPreferences *checkinPreferences =
  172. [self setCheckinPreferencesWithLastCheckinTime:tenHoursAgo];
  173. NSData *fakeDeviceToken = [@"fakeAPNSToken" dataUsingEncoding:NSUTF8StringEncoding];
  174. BOOL isSandbox = NO;
  175. NSString *apnsTupleString =
  176. FIRInstanceIDAPNSTupleStringForTokenAndServerType(fakeDeviceToken, isSandbox);
  177. NSDictionary *options = @{
  178. kFIRInstanceIDTokenOptionsFirebaseAppIDKey : @"fakeGMPAppID",
  179. kFIRInstanceIDTokenOptionsAPNSKey : fakeDeviceToken,
  180. kFIRInstanceIDTokenOptionsAPNSIsSandboxKey : @(isSandbox),
  181. };
  182. FIRInstanceIDTokenFetchOperation *operation =
  183. [[FIRInstanceIDTokenFetchOperation alloc] initWithAuthorizedEntity:kAuthorizedEntity
  184. scope:kScope
  185. options:options
  186. checkinPreferences:checkinPreferences
  187. instanceID:self.instanceID];
  188. operation.testBlock =
  189. ^(NSURLRequest *request, FIRInstanceIDURLRequestTestResponseBlock response) {
  190. NSString *query = [[NSString alloc] initWithData:request.HTTPBody
  191. encoding:NSUTF8StringEncoding];
  192. NSString *gmpAppIDQueryTuple =
  193. [NSString stringWithFormat:@"%@=%@", kFIRInstanceIDTokenOptionsFirebaseAppIDKey,
  194. options[kFIRInstanceIDTokenOptionsFirebaseAppIDKey]];
  195. NSRange gmpAppIDRange = [query rangeOfString:gmpAppIDQueryTuple];
  196. NSString *apnsQueryTuple = [NSString
  197. stringWithFormat:@"%@=%@", kFIRInstanceIDTokenOptionsAPNSKey, apnsTupleString];
  198. NSRange apnsRange = [query rangeOfString:apnsQueryTuple];
  199. if (gmpAppIDRange.location != NSNotFound && apnsRange.location != NSNotFound) {
  200. [optionsIncludedExpectation fulfill];
  201. }
  202. // Return a response (doesnt matter what the response is)
  203. NSData *responseBody = [self dataForFetchRequest:request returnValidToken:YES];
  204. NSHTTPURLResponse *responseObject = [[NSHTTPURLResponse alloc] initWithURL:request.URL
  205. statusCode:200
  206. HTTPVersion:@"HTTP/1.1"
  207. headerFields:nil];
  208. response(responseBody, responseObject, nil);
  209. };
  210. [operation start];
  211. [self waitForExpectationsWithTimeout:0.25
  212. handler:^(NSError *_Nullable error) {
  213. XCTAssertNil(error.localizedDescription);
  214. }];
  215. }
  216. - (void)testServerResetCommand {
  217. XCTestExpectation *shouldResetIdentityExpectation =
  218. [self expectationWithDescription:
  219. @"When server sends down RST error, clients should return reset identity error."];
  220. int64_t tenHoursAgo = FIRInstanceIDCurrentTimestampInMilliseconds() - 10 * 60 * 60 * 1000;
  221. FIRInstanceIDCheckinPreferences *checkinPreferences =
  222. [self setCheckinPreferencesWithLastCheckinTime:tenHoursAgo];
  223. FIRInstanceIDTokenFetchOperation *operation =
  224. [[FIRInstanceIDTokenFetchOperation alloc] initWithAuthorizedEntity:kAuthorizedEntity
  225. scope:kScope
  226. options:nil
  227. checkinPreferences:checkinPreferences
  228. instanceID:self.instanceID];
  229. operation.testBlock =
  230. ^(NSURLRequest *request, FIRInstanceIDURLRequestTestResponseBlock response) {
  231. // Return a response with Error=RST
  232. NSData *responseBody = [self dataForFetchRequest:request returnValidToken:NO];
  233. NSHTTPURLResponse *responseObject = [[NSHTTPURLResponse alloc] initWithURL:request.URL
  234. statusCode:200
  235. HTTPVersion:@"HTTP/1.1"
  236. headerFields:nil];
  237. response(responseBody, responseObject, nil);
  238. };
  239. [operation addCompletionHandler:^(FIRInstanceIDTokenOperationResult result,
  240. NSString *_Nullable token, NSError *_Nullable error) {
  241. XCTAssertEqual(result, FIRInstanceIDTokenOperationError);
  242. XCTAssertNotNil(error);
  243. XCTAssertEqual(error.code, kFIRInstanceIDErrorCodeInvalidIdentity);
  244. [shouldResetIdentityExpectation fulfill];
  245. }];
  246. [operation start];
  247. [self waitForExpectationsWithTimeout:0.25
  248. handler:^(NSError *_Nullable error) {
  249. XCTAssertNil(error.localizedDescription);
  250. }];
  251. }
  252. - (void)testHTTPAuthHeaderGenerationFromCheckin {
  253. FIRInstanceIDCheckinPreferences *checkinPreferences =
  254. [[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:kDeviceID secretToken:kSecretToken];
  255. NSString *expectedHeader =
  256. [NSString stringWithFormat:@"AidLogin %@:%@", checkinPreferences.deviceID,
  257. checkinPreferences.secretToken];
  258. NSString *generatedHeader =
  259. [FIRInstanceIDTokenOperation HTTPAuthHeaderFromCheckin:checkinPreferences];
  260. XCTAssertEqualObjects(generatedHeader, expectedHeader);
  261. }
  262. - (void)testTokenFetchOperationFirebaseUserAgentAndHeartbeatHeader {
  263. XCTestExpectation *completionExpectation =
  264. [self expectationWithDescription:@"completionExpectation"];
  265. FIRInstanceIDCheckinPreferences *checkinPreferences =
  266. [self setCheckinPreferencesWithLastCheckinTime:0];
  267. FIRInstanceIDTokenFetchOperation *operation =
  268. [[FIRInstanceIDTokenFetchOperation alloc] initWithAuthorizedEntity:kAuthorizedEntity
  269. scope:kScope
  270. options:nil
  271. checkinPreferences:checkinPreferences
  272. instanceID:self.instanceID];
  273. operation.testBlock =
  274. ^(NSURLRequest *request, FIRInstanceIDURLRequestTestResponseBlock response) {
  275. NSString *userAgentValue = request.allHTTPHeaderFields[kFIRInstanceIDFirebaseUserAgentKey];
  276. XCTAssertEqualObjects(userAgentValue, [FIRApp firebaseUserAgent]);
  277. NSString *heartBeatCode = request.allHTTPHeaderFields[kFIRInstanceIDFirebaseHeartbeatKey];
  278. XCTAssertEqualObjects(heartBeatCode, @"3");
  279. // Return a response with Error=RST
  280. NSData *responseBody = [self dataForFetchRequest:request returnValidToken:NO];
  281. NSHTTPURLResponse *responseObject = [[NSHTTPURLResponse alloc] initWithURL:request.URL
  282. statusCode:200
  283. HTTPVersion:@"HTTP/1.1"
  284. headerFields:nil];
  285. response(responseBody, responseObject, nil);
  286. };
  287. [operation addCompletionHandler:^(FIRInstanceIDTokenOperationResult result,
  288. NSString *_Nullable token, NSError *_Nullable error) {
  289. [completionExpectation fulfill];
  290. }];
  291. [operation start];
  292. [self waitForExpectationsWithTimeout:0.25
  293. handler:^(NSError *_Nullable error) {
  294. XCTAssertNil(error.localizedDescription);
  295. }];
  296. }
  297. #pragma mark - Internal Helpers
  298. - (NSData *)dataForFetchRequest:(NSURLRequest *)request returnValidToken:(BOOL)returnValidToken {
  299. NSString *response;
  300. if (returnValidToken) {
  301. response = [NSString stringWithFormat:@"token=%@", kRegistrationToken];
  302. } else {
  303. response = @"Error=RST";
  304. }
  305. return [response dataUsingEncoding:NSUTF8StringEncoding];
  306. }
  307. - (FIRInstanceIDCheckinPreferences *)setCheckinPreferencesWithLastCheckinTime:(int64_t)time {
  308. FIRInstanceIDCheckinPreferences *checkinPreferences =
  309. [[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:kDeviceID secretToken:kSecretToken];
  310. NSDictionary *checkinPlistContents = @{
  311. kFIRInstanceIDDigestStringKey : kDigestString,
  312. kFIRInstanceIDVersionInfoStringKey : kVersionInfoString,
  313. kFIRInstanceIDLastCheckinTimeKey : @(time)
  314. };
  315. [checkinPreferences updateWithCheckinPlistContents:checkinPlistContents];
  316. // manually initialize the checkin preferences
  317. self.checkinPreferences = checkinPreferences;
  318. return checkinPreferences;
  319. }
  320. @end