FIRAuthAppCredentialManagerTests.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. /*
  2. * Copyright 2017 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 <TargetConditionals.h>
  17. #if !TARGET_OS_OSX
  18. #import <OCMock/OCMock.h>
  19. #import <XCTest/XCTest.h>
  20. #import "FirebaseAuth/Sources/Storage/FIRAuthKeychainServices.h"
  21. #import "FirebaseAuth/Sources/SystemService/FIRAuthAppCredential.h"
  22. #import "FirebaseAuth/Sources/SystemService/FIRAuthAppCredentialManager.h"
  23. #define ANY_ERROR_POINTER ((NSError * __autoreleasing *_Nullable)[OCMArg anyPointer])
  24. #define SAVE_TO(var) \
  25. [OCMArg checkWithBlock:^BOOL(id arg) { \
  26. var = arg; \
  27. return YES; \
  28. }]
  29. /** @var kReceipt
  30. @brief A fake receipt used for testing.
  31. */
  32. static NSString *const kReceipt = @"FAKE_RECEIPT";
  33. /** @var kAnotherReceipt
  34. @brief Another fake receipt used for testing.
  35. */
  36. static NSString *const kAnotherReceipt = @"OTHER_RECEIPT";
  37. /** @var kSecret
  38. @brief A fake secret used for testing.
  39. */
  40. static NSString *const kSecret = @"FAKE_SECRET";
  41. /** @var kAnotherSecret
  42. @brief Another fake secret used for testing.
  43. */
  44. static NSString *const kAnotherSecret = @"OTHER_SECRET";
  45. /** @var kVerificationTimeout
  46. @brief The verification timeout used for testing.
  47. */
  48. static const NSTimeInterval kVerificationTimeout = 1;
  49. /** @var kExpectationTimeout
  50. @brief The test expectation timeout.
  51. @remarks This must be considerably greater than @c kVerificationTimeout .
  52. */
  53. static const NSTimeInterval kExpectationTimeout = 2;
  54. NS_ASSUME_NONNULL_BEGIN
  55. /** @class FIRAuthAppCredentialManagerTests
  56. @brief Unit tests for @c FIRAuthAppCredentialManager .
  57. */
  58. @interface FIRAuthAppCredentialManagerTests : XCTestCase
  59. @end
  60. @implementation FIRAuthAppCredentialManagerTests {
  61. /** @var _mockKeychain
  62. @brief The mock keychain for testing.
  63. */
  64. id _mockKeychain;
  65. }
  66. - (void)setUp {
  67. _mockKeychain = OCMClassMock([FIRAuthKeychainServices class]);
  68. }
  69. /** @fn testCompletion
  70. @brief Tests a successfully completed verification flow.
  71. */
  72. - (void)testCompletion {
  73. // Initial empty state.
  74. OCMExpect([_mockKeychain dataForKey:OCMOCK_ANY error:ANY_ERROR_POINTER]).andReturn(nil);
  75. FIRAuthAppCredentialManager *manager =
  76. [[FIRAuthAppCredentialManager alloc] initWithKeychain:_mockKeychain];
  77. XCTAssertNil(manager.credential);
  78. OCMVerifyAll(_mockKeychain);
  79. // Start verification.
  80. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  81. OCMExpect([_mockKeychain setData:OCMOCK_ANY forKey:OCMOCK_ANY error:ANY_ERROR_POINTER])
  82. .andReturn(YES);
  83. [manager didStartVerificationWithReceipt:kReceipt
  84. timeout:kVerificationTimeout
  85. callback:^(FIRAuthAppCredential *credential) {
  86. XCTAssertEqualObjects(credential.receipt, kReceipt);
  87. XCTAssertEqualObjects(credential.secret, kSecret);
  88. [expectation fulfill];
  89. }];
  90. XCTAssertNil(manager.credential);
  91. OCMVerifyAll(_mockKeychain);
  92. // Mismatched receipt shouldn't finish verification.
  93. XCTAssertFalse([manager canFinishVerificationWithReceipt:kAnotherReceipt secret:kAnotherSecret]);
  94. XCTAssertNil(manager.credential);
  95. // Finish verification.
  96. OCMExpect([_mockKeychain setData:OCMOCK_ANY forKey:OCMOCK_ANY error:ANY_ERROR_POINTER])
  97. .andReturn(YES);
  98. XCTAssertTrue([manager canFinishVerificationWithReceipt:kReceipt secret:kSecret]);
  99. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  100. XCTAssertNotNil(manager.credential);
  101. XCTAssertEqualObjects(manager.credential.receipt, kReceipt);
  102. XCTAssertEqualObjects(manager.credential.secret, kSecret);
  103. OCMVerifyAll(_mockKeychain);
  104. // Repeated receipt should have no effect.
  105. XCTAssertFalse([manager canFinishVerificationWithReceipt:kReceipt secret:kAnotherSecret]);
  106. XCTAssertEqualObjects(manager.credential.secret, kSecret);
  107. }
  108. /** @fn testTimeout
  109. @brief Tests a verification flow that times out.
  110. */
  111. - (void)testTimeout {
  112. // Initial empty state.
  113. OCMExpect([_mockKeychain dataForKey:OCMOCK_ANY error:ANY_ERROR_POINTER]).andReturn(nil);
  114. FIRAuthAppCredentialManager *manager =
  115. [[FIRAuthAppCredentialManager alloc] initWithKeychain:_mockKeychain];
  116. XCTAssertNil(manager.credential);
  117. OCMVerifyAll(_mockKeychain);
  118. // Start verification.
  119. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  120. OCMExpect([_mockKeychain setData:OCMOCK_ANY forKey:OCMOCK_ANY error:ANY_ERROR_POINTER])
  121. .andReturn(YES);
  122. [manager didStartVerificationWithReceipt:kReceipt
  123. timeout:kVerificationTimeout
  124. callback:^(FIRAuthAppCredential *credential) {
  125. XCTAssertEqualObjects(credential.receipt, kReceipt);
  126. XCTAssertNil(credential.secret);
  127. [expectation fulfill];
  128. }];
  129. XCTAssertNil(manager.credential);
  130. OCMVerifyAll(_mockKeychain);
  131. // Time-out.
  132. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  133. XCTAssertNil(manager.credential);
  134. // Completion after timeout.
  135. OCMExpect([_mockKeychain setData:OCMOCK_ANY forKey:OCMOCK_ANY error:ANY_ERROR_POINTER])
  136. .andReturn(YES);
  137. XCTAssertTrue([manager canFinishVerificationWithReceipt:kReceipt secret:kSecret]);
  138. XCTAssertNotNil(manager.credential);
  139. XCTAssertEqualObjects(manager.credential.receipt, kReceipt);
  140. XCTAssertEqualObjects(manager.credential.secret, kSecret);
  141. OCMVerifyAll(_mockKeychain);
  142. }
  143. /** @fn testMaximumPendingReceipt
  144. @brief Tests the maximum allowed number of pending receipt.
  145. */
  146. - (void)testMaximumPendingReceipt {
  147. // Initial empty state.
  148. OCMExpect([_mockKeychain dataForKey:OCMOCK_ANY error:ANY_ERROR_POINTER]).andReturn(nil);
  149. FIRAuthAppCredentialManager *manager =
  150. [[FIRAuthAppCredentialManager alloc] initWithKeychain:_mockKeychain];
  151. XCTAssertNil(manager.credential);
  152. OCMVerifyAll(_mockKeychain);
  153. // Start verification of the target receipt.
  154. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  155. OCMExpect([_mockKeychain setData:OCMOCK_ANY forKey:OCMOCK_ANY error:ANY_ERROR_POINTER])
  156. .andReturn(YES);
  157. [manager didStartVerificationWithReceipt:kReceipt
  158. timeout:kVerificationTimeout
  159. callback:^(FIRAuthAppCredential *credential) {
  160. XCTAssertEqualObjects(credential.receipt, kReceipt);
  161. XCTAssertEqualObjects(credential.secret, kSecret);
  162. [expectation fulfill];
  163. }];
  164. XCTAssertNil(manager.credential);
  165. OCMVerifyAll(_mockKeychain);
  166. // Start verification of a number of random receipts without overflowing.
  167. for (NSUInteger i = 1; i < manager.maximumNumberOfPendingReceipts; i++) {
  168. OCMExpect([_mockKeychain setData:OCMOCK_ANY forKey:OCMOCK_ANY error:ANY_ERROR_POINTER])
  169. .andReturn(YES);
  170. NSString *randomReceipt = [NSString stringWithFormat:@"RANDOM_%lu", (unsigned long)i];
  171. XCTestExpectation *randomExpectation = [self expectationWithDescription:randomReceipt];
  172. [manager didStartVerificationWithReceipt:randomReceipt
  173. timeout:kVerificationTimeout
  174. callback:^(FIRAuthAppCredential *credential) {
  175. // They all should get full credential because one is
  176. // available at this point.
  177. XCTAssertEqualObjects(credential.receipt, kReceipt);
  178. XCTAssertEqualObjects(credential.secret, kSecret);
  179. [randomExpectation fulfill];
  180. }];
  181. }
  182. // Finish verification of target receipt.
  183. OCMExpect([_mockKeychain setData:OCMOCK_ANY forKey:OCMOCK_ANY error:ANY_ERROR_POINTER])
  184. .andReturn(YES);
  185. XCTAssertTrue([manager canFinishVerificationWithReceipt:kReceipt secret:kSecret]);
  186. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  187. XCTAssertNotNil(manager.credential);
  188. XCTAssertEqualObjects(manager.credential.receipt, kReceipt);
  189. XCTAssertEqualObjects(manager.credential.secret, kSecret);
  190. OCMVerifyAll(_mockKeychain);
  191. // Clear credential to prepare for next round.
  192. [manager clearCredential];
  193. XCTAssertNil(manager.credential);
  194. // Start verification of another target receipt.
  195. expectation = [self expectationWithDescription:@"another callback"];
  196. OCMExpect([_mockKeychain setData:OCMOCK_ANY forKey:OCMOCK_ANY error:ANY_ERROR_POINTER])
  197. .andReturn(YES);
  198. [manager didStartVerificationWithReceipt:kAnotherReceipt
  199. timeout:kVerificationTimeout
  200. callback:^(FIRAuthAppCredential *credential) {
  201. XCTAssertEqualObjects(credential.receipt, kAnotherReceipt);
  202. XCTAssertNil(credential.secret);
  203. [expectation fulfill];
  204. }];
  205. XCTAssertNil(manager.credential);
  206. OCMVerifyAll(_mockKeychain);
  207. // Start verification of a number of random receipts to overflow.
  208. for (NSUInteger i = 0; i < manager.maximumNumberOfPendingReceipts; i++) {
  209. OCMExpect([_mockKeychain setData:OCMOCK_ANY forKey:OCMOCK_ANY error:ANY_ERROR_POINTER])
  210. .andReturn(YES);
  211. NSString *randomReceipt = [NSString stringWithFormat:@"RANDOM_%lu", (unsigned long)i];
  212. XCTestExpectation *randomExpectation = [self expectationWithDescription:randomReceipt];
  213. [manager didStartVerificationWithReceipt:randomReceipt
  214. timeout:kVerificationTimeout
  215. callback:^(FIRAuthAppCredential *credential) {
  216. // They all should get partial credential because verification
  217. // has never completed.
  218. XCTAssertEqualObjects(credential.receipt, randomReceipt);
  219. XCTAssertNil(credential.secret);
  220. [randomExpectation fulfill];
  221. }];
  222. }
  223. // Finish verification of the other target receipt.
  224. XCTAssertFalse([manager canFinishVerificationWithReceipt:kAnotherReceipt secret:kAnotherSecret]);
  225. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  226. XCTAssertNil(manager.credential);
  227. }
  228. /** @fn testKeychain
  229. @brief Tests state preservation in the keychain.
  230. */
  231. - (void)testKeychain {
  232. // Initial empty state.
  233. OCMExpect([_mockKeychain dataForKey:OCMOCK_ANY error:ANY_ERROR_POINTER]).andReturn(nil);
  234. FIRAuthAppCredentialManager *manager =
  235. [[FIRAuthAppCredentialManager alloc] initWithKeychain:_mockKeychain];
  236. XCTAssertNil(manager.credential);
  237. OCMVerifyAll(_mockKeychain);
  238. // Start verification.
  239. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  240. __block NSString *key;
  241. __block NSString *data;
  242. OCMExpect([_mockKeychain setData:SAVE_TO(data) forKey:SAVE_TO(key) error:ANY_ERROR_POINTER])
  243. .andReturn(YES);
  244. [manager didStartVerificationWithReceipt:kReceipt
  245. timeout:kVerificationTimeout
  246. callback:^(FIRAuthAppCredential *credential) {
  247. XCTAssertEqualObjects(credential.receipt, kReceipt);
  248. XCTAssertNil(credential.secret);
  249. [expectation fulfill];
  250. }];
  251. XCTAssertNil(manager.credential);
  252. OCMVerifyAll(_mockKeychain);
  253. // Time-out.
  254. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  255. XCTAssertNil(manager.credential);
  256. // Start a new manager with saved data in keychain.
  257. OCMExpect([_mockKeychain dataForKey:key error:ANY_ERROR_POINTER]).andReturn(data);
  258. manager = [[FIRAuthAppCredentialManager alloc] initWithKeychain:_mockKeychain];
  259. XCTAssertNil(manager.credential);
  260. OCMVerifyAll(_mockKeychain);
  261. // Finish verification.
  262. OCMExpect([_mockKeychain setData:SAVE_TO(data) forKey:SAVE_TO(key) error:ANY_ERROR_POINTER])
  263. .andReturn(YES);
  264. XCTAssertTrue([manager canFinishVerificationWithReceipt:kReceipt secret:kSecret]);
  265. XCTAssertNotNil(manager.credential);
  266. XCTAssertEqualObjects(manager.credential.receipt, kReceipt);
  267. XCTAssertEqualObjects(manager.credential.secret, kSecret);
  268. OCMVerifyAll(_mockKeychain);
  269. // Start yet another new manager with saved data in keychain.
  270. OCMExpect([_mockKeychain dataForKey:key error:ANY_ERROR_POINTER]).andReturn(data);
  271. manager = [[FIRAuthAppCredentialManager alloc] initWithKeychain:_mockKeychain];
  272. XCTAssertNotNil(manager.credential);
  273. XCTAssertEqualObjects(manager.credential.receipt, kReceipt);
  274. XCTAssertEqualObjects(manager.credential.secret, kSecret);
  275. OCMVerifyAll(_mockKeychain);
  276. }
  277. @end
  278. NS_ASSUME_NONNULL_END
  279. #endif