FIRAppAttestAPIServiceTests.m 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. /*
  2. * Copyright 2021 Google LLC
  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 <OCMock/OCMock.h>
  18. #import "FBLPromise+Testing.h"
  19. #import <GoogleUtilities/GULURLSessionDataResponse.h>
  20. #import "FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAPIService.h"
  21. #import "FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAttestationResponse.h"
  22. #import "FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckAPIService.h"
  23. #import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h"
  24. #import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckHTTPError.h"
  25. #import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h"
  26. #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckErrors.h"
  27. #import "FirebaseAppCheck/Tests/Unit/Utils/FIRFixtureLoader.h"
  28. #import "SharedTestUtilities/Date/FIRDateTestUtils.h"
  29. #import "SharedTestUtilities/URLSession/FIRURLSessionOCMockStub.h"
  30. @interface FIRAppAttestAPIServiceTests : XCTestCase
  31. @property(nonatomic) FIRAppAttestAPIService *appAttestAPIService;
  32. @property(nonatomic) id mockAPIService;
  33. @property(nonatomic) NSString *projectID;
  34. @property(nonatomic) NSString *appID;
  35. @end
  36. @implementation FIRAppAttestAPIServiceTests
  37. - (void)setUp {
  38. [super setUp];
  39. self.projectID = @"project_id";
  40. self.appID = @"app_id";
  41. self.mockAPIService = OCMProtocolMock(@protocol(FIRAppCheckAPIServiceProtocol));
  42. OCMStub([self.mockAPIService baseURL]).andReturn(@"https://test.appcheck.url.com/beta");
  43. self.appAttestAPIService = [[FIRAppAttestAPIService alloc] initWithAPIService:self.mockAPIService
  44. projectID:self.projectID
  45. appID:self.appID];
  46. }
  47. - (void)tearDown {
  48. [super tearDown];
  49. self.appAttestAPIService = nil;
  50. [self.mockAPIService stopMocking];
  51. self.mockAPIService = nil;
  52. }
  53. #pragma mark - Random challenge request
  54. - (void)testGetRandomChallengeWhenAPIResponseValid {
  55. // 1. Prepare API response.
  56. NSData *responseBody = [FIRFixtureLoader loadFixtureNamed:@"AppAttestResponseSuccess.json"];
  57. GULURLSessionDataResponse *validAPIResponse = [self APIResponseWithCode:200
  58. responseBody:responseBody];
  59. // 2. Stub API Service Request to return prepared API response.
  60. [self stubMockAPIServiceRequestForChallengeRequestWithResponse:validAPIResponse];
  61. // 3. Request the random challenge and verify results.
  62. __auto_type *promise = [self.appAttestAPIService getRandomChallenge];
  63. XCTAssert(FBLWaitForPromisesWithTimeout(1));
  64. XCTAssert(promise.isFulfilled);
  65. XCTAssertNotNil(promise.value);
  66. XCTAssertNil(promise.error);
  67. NSString *challengeString = [[NSString alloc] initWithData:promise.value
  68. encoding:NSUTF8StringEncoding];
  69. // The challenge stored in `AppAttestResponseSuccess.json` is a valid base64 encoding of
  70. // the string "random_challenge".
  71. XCTAssert([challengeString isEqualToString:@"random_challenge"]);
  72. OCMVerifyAll(self.mockAPIService);
  73. }
  74. - (void)testGetRandomChallengeWhenAPIError {
  75. // 1. Prepare API response.
  76. NSString *responseBodyString = @"Generate challenge failed with invalid format.";
  77. NSData *responseBody = [responseBodyString dataUsingEncoding:NSUTF8StringEncoding];
  78. GULURLSessionDataResponse *invalidAPIResponse = [self APIResponseWithCode:300
  79. responseBody:responseBody];
  80. FIRAppCheckHTTPError *APIError =
  81. [FIRAppCheckErrorUtil APIErrorWithHTTPResponse:invalidAPIResponse.HTTPResponse
  82. data:invalidAPIResponse.HTTPBody];
  83. // 2. Stub API Service Request to return prepared API response.
  84. [self stubMockAPIServiceRequestForChallengeRequestWithResponse:APIError];
  85. // 3. Request the random challenge and verify results.
  86. __auto_type *promise = [self.appAttestAPIService getRandomChallenge];
  87. XCTAssert(FBLWaitForPromisesWithTimeout(1));
  88. XCTAssert(promise.isRejected);
  89. XCTAssertNotNil(promise.error);
  90. XCTAssertNil(promise.value);
  91. // Assert error is as expected.
  92. XCTAssertEqualObjects(promise.error.domain, FIRAppCheckErrorDomain);
  93. XCTAssertEqual(promise.error.code, FIRAppCheckErrorCodeUnknown);
  94. // Expect response body and HTTP status code to be included in the error.
  95. NSString *failureReason = promise.error.userInfo[NSLocalizedFailureReasonErrorKey];
  96. XCTAssertTrue([failureReason containsString:@"300"]);
  97. XCTAssertTrue([failureReason containsString:responseBodyString]);
  98. OCMVerifyAll(self.mockAPIService);
  99. }
  100. - (void)testGetRandomChallengeWhenAPIResponseEmpty {
  101. // 1. Prepare API response.
  102. NSData *responseBody = [NSData data];
  103. GULURLSessionDataResponse *emptyAPIResponse = [self APIResponseWithCode:200
  104. responseBody:responseBody];
  105. // 2. Stub API Service Request to return prepared API response.
  106. [self stubMockAPIServiceRequestForChallengeRequestWithResponse:emptyAPIResponse];
  107. // 3. Request the random challenge and verify results.
  108. __auto_type *promise = [self.appAttestAPIService getRandomChallenge];
  109. XCTAssert(FBLWaitForPromisesWithTimeout(1));
  110. XCTAssert(promise.isRejected);
  111. XCTAssertNotNil(promise.error);
  112. XCTAssertNil(promise.value);
  113. // Expect response body and HTTP status code to be included in the error.
  114. NSString *failureReason = promise.error.userInfo[NSLocalizedFailureReasonErrorKey];
  115. XCTAssertEqualObjects(failureReason, @"Empty server response body.");
  116. OCMVerifyAll(self.mockAPIService);
  117. }
  118. - (void)testGetRandomChallengeWhenAPIResponseInvalidFormat {
  119. // 1. Prepare API response.
  120. NSString *responseBodyString = @"Generate challenge failed with invalid format.";
  121. NSData *responseBody = [responseBodyString dataUsingEncoding:NSUTF8StringEncoding];
  122. GULURLSessionDataResponse *validAPIResponse = [self APIResponseWithCode:200
  123. responseBody:responseBody];
  124. // 2. Stub API Service Request to return prepared API response.
  125. [self stubMockAPIServiceRequestForChallengeRequestWithResponse:validAPIResponse];
  126. // 3. Request the random challenge and verify results.
  127. __auto_type *promise = [self.appAttestAPIService getRandomChallenge];
  128. XCTAssert(FBLWaitForPromisesWithTimeout(1));
  129. XCTAssert(promise.isRejected);
  130. XCTAssertNotNil(promise.error);
  131. XCTAssertNil(promise.value);
  132. // Expect response body and HTTP status code to be included in the error.
  133. NSString *failureReason = promise.error.userInfo[NSLocalizedFailureReasonErrorKey];
  134. XCTAssertEqualObjects(failureReason, @"JSON serialization error.");
  135. OCMVerifyAll(self.mockAPIService);
  136. }
  137. - (void)testGetRandomChallengeWhenResponseMissingField {
  138. [self assertMissingFieldErrorWithFixture:@"AppAttestResponseMissingChallenge.json"
  139. missingField:@"challenge"];
  140. }
  141. - (void)assertMissingFieldErrorWithFixture:(NSString *)fixtureName
  142. missingField:(NSString *)fieldName {
  143. // 1. Prepare API response.
  144. NSData *missingFieldBody = [FIRFixtureLoader loadFixtureNamed:fixtureName];
  145. GULURLSessionDataResponse *incompleteAPIResponse = [self APIResponseWithCode:200
  146. responseBody:missingFieldBody];
  147. // 2. Stub API Service Request to return prepared API response.
  148. [self stubMockAPIServiceRequestForChallengeRequestWithResponse:incompleteAPIResponse];
  149. // 3. Request the random challenge and verify results.
  150. __auto_type *promise = [self.appAttestAPIService getRandomChallenge];
  151. XCTAssert(FBLWaitForPromisesWithTimeout(1));
  152. XCTAssert(promise.isRejected);
  153. XCTAssertNotNil(promise.error);
  154. XCTAssertNil(promise.value);
  155. // Assert error is as expected.
  156. XCTAssertEqualObjects(promise.error.domain, FIRAppCheckErrorDomain);
  157. XCTAssertEqual(promise.error.code, FIRAppCheckErrorCodeUnknown);
  158. // Expect missing field name to be included in the error.
  159. NSString *failureReason = promise.error.userInfo[NSLocalizedFailureReasonErrorKey];
  160. NSString *fieldNameString = [NSString stringWithFormat:@"`%@`", fieldName];
  161. XCTAssertTrue([failureReason containsString:fieldNameString],
  162. @"Fixture `%@`: expected missing field %@ error not found", fixtureName,
  163. fieldNameString);
  164. }
  165. #pragma mark - Assertion request
  166. - (void)testGetAppCheckTokenSuccess {
  167. NSData *artifact = [self generateRandomData];
  168. NSData *challenge = [self generateRandomData];
  169. NSData *assertion = [self generateRandomData];
  170. // 1. Prepare response.
  171. NSData *responseBody =
  172. [FIRFixtureLoader loadFixtureNamed:@"FACTokenExchangeResponseSuccess.json"];
  173. GULURLSessionDataResponse *validAPIResponse = [self APIResponseWithCode:200
  174. responseBody:responseBody];
  175. // 2. Stub API Service
  176. // 2.1. Return prepared response.
  177. [self expectTokenAPIRequestWithArtifact:artifact
  178. challenge:challenge
  179. assertion:assertion
  180. response:validAPIResponse
  181. error:nil];
  182. // 2.2. Return token from parsed response.
  183. FIRAppCheckToken *expectedToken = [[FIRAppCheckToken alloc] initWithToken:@"app_check_token"
  184. expirationDate:[NSDate date]
  185. receivedAtDate:[NSDate date]];
  186. [self expectTokenWithAPIReponse:validAPIResponse toReturnToken:expectedToken];
  187. // 3. Send request.
  188. __auto_type promise = [self.appAttestAPIService getAppCheckTokenWithArtifact:artifact
  189. challenge:challenge
  190. assertion:assertion];
  191. // 4. Verify.
  192. XCTAssert(FBLWaitForPromisesWithTimeout(1));
  193. XCTAssertTrue(promise.isFulfilled);
  194. XCTAssertNil(promise.error);
  195. XCTAssertEqualObjects(promise.value, expectedToken);
  196. XCTAssertEqualObjects(promise.value.token, expectedToken.token);
  197. XCTAssertEqualObjects(promise.value.expirationDate, expectedToken.expirationDate);
  198. XCTAssertEqualObjects(promise.value.receivedAtDate, expectedToken.receivedAtDate);
  199. OCMVerifyAll(self.mockAPIService);
  200. }
  201. - (void)testGetAppCheckTokenNetworkError {
  202. NSData *artifact = [self generateRandomData];
  203. NSData *challenge = [self generateRandomData];
  204. NSData *assertion = [self generateRandomData];
  205. // 1. Prepare response.
  206. NSData *responseBody =
  207. [FIRFixtureLoader loadFixtureNamed:@"FACTokenExchangeResponseSuccess.json"];
  208. GULURLSessionDataResponse *validAPIResponse = [self APIResponseWithCode:200
  209. responseBody:responseBody];
  210. // 2. Stub API Service
  211. // 2.1. Return prepared response.
  212. NSError *networkError = [NSError errorWithDomain:self.name code:0 userInfo:nil];
  213. [self expectTokenAPIRequestWithArtifact:artifact
  214. challenge:challenge
  215. assertion:assertion
  216. response:validAPIResponse
  217. error:networkError];
  218. // 3. Send request.
  219. __auto_type promise = [self.appAttestAPIService getAppCheckTokenWithArtifact:artifact
  220. challenge:challenge
  221. assertion:assertion];
  222. // 4. Verify.
  223. XCTAssert(FBLWaitForPromisesWithTimeout(1));
  224. XCTAssertTrue(promise.isRejected);
  225. XCTAssertNil(promise.value);
  226. XCTAssertEqualObjects(promise.error, networkError);
  227. OCMVerifyAll(self.mockAPIService);
  228. }
  229. - (void)testGetAppCheckTokenUnexpectedResponse {
  230. NSData *artifact = [self generateRandomData];
  231. NSData *challenge = [self generateRandomData];
  232. NSData *assertion = [self generateRandomData];
  233. // 1. Prepare response.
  234. NSData *responseBody =
  235. [FIRFixtureLoader loadFixtureNamed:@"DeviceCheckResponseMissingToken.json"];
  236. GULURLSessionDataResponse *validAPIResponse = [self APIResponseWithCode:200
  237. responseBody:responseBody];
  238. // 2. Stub API Service
  239. // 2.1. Return prepared response.
  240. [self expectTokenAPIRequestWithArtifact:artifact
  241. challenge:challenge
  242. assertion:assertion
  243. response:validAPIResponse
  244. error:nil];
  245. // 2.2. Return token from parsed response.
  246. [self expectTokenWithAPIReponse:validAPIResponse toReturnToken:nil];
  247. // 3. Send request.
  248. __auto_type promise = [self.appAttestAPIService getAppCheckTokenWithArtifact:artifact
  249. challenge:challenge
  250. assertion:assertion];
  251. // 4. Verify.
  252. XCTAssert(FBLWaitForPromisesWithTimeout(1));
  253. XCTAssertTrue(promise.isRejected);
  254. XCTAssertNil(promise.value);
  255. XCTAssertNotNil(promise.error);
  256. OCMVerifyAll(self.mockAPIService);
  257. }
  258. #pragma mark - Attestation request
  259. - (void)testAttestKeySuccess {
  260. NSData *attestation = [self generateRandomData];
  261. NSData *challenge = [self generateRandomData];
  262. NSString *keyID = [NSUUID UUID].UUIDString;
  263. // 1. Prepare response.
  264. NSData *responseBody =
  265. [FIRFixtureLoader loadFixtureNamed:@"AppAttestAttestationResponseSuccess.json"];
  266. GULURLSessionDataResponse *validAPIResponse = [self APIResponseWithCode:200
  267. responseBody:responseBody];
  268. // 2. Stub API Service
  269. // 2.1. Return prepared response.
  270. [self expectAttestAPIRequestWithAttestation:attestation
  271. keyID:keyID
  272. challenge:challenge
  273. response:validAPIResponse
  274. error:nil];
  275. // 3. Send request.
  276. __auto_type promise = [self.appAttestAPIService attestKeyWithAttestation:attestation
  277. keyID:keyID
  278. challenge:challenge];
  279. // 4. Verify.
  280. XCTAssert(FBLWaitForPromisesWithTimeout(1));
  281. XCTAssertTrue(promise.isFulfilled);
  282. XCTAssertNil(promise.error);
  283. NSData *expectedArtifact =
  284. [@"valid Firebase app attest artifact" dataUsingEncoding:NSUTF8StringEncoding];
  285. XCTAssertEqualObjects(promise.value.artifact, expectedArtifact);
  286. XCTAssertEqualObjects(promise.value.token.token, @"valid_app_check_token");
  287. XCTAssertTrue([FIRDateTestUtils isDate:promise.value.token.expirationDate
  288. approximatelyEqualCurrentPlusTimeInterval:1800
  289. precision:10]);
  290. OCMVerifyAll(self.mockAPIService);
  291. }
  292. - (void)testAttestKeyNetworkError {
  293. NSData *attestation = [self generateRandomData];
  294. NSData *challenge = [self generateRandomData];
  295. NSString *keyID = [NSUUID UUID].UUIDString;
  296. // 1. Stub API Service
  297. // 1.1. Return prepared response.
  298. NSError *networkError = [NSError errorWithDomain:self.name code:0 userInfo:nil];
  299. [self expectAttestAPIRequestWithAttestation:attestation
  300. keyID:keyID
  301. challenge:challenge
  302. response:nil
  303. error:networkError];
  304. // 2. Send request.
  305. __auto_type promise = [self.appAttestAPIService attestKeyWithAttestation:attestation
  306. keyID:keyID
  307. challenge:challenge];
  308. // 3. Verify.
  309. XCTAssert(FBLWaitForPromisesWithTimeout(1));
  310. XCTAssertTrue(promise.isRejected);
  311. XCTAssertNil(promise.value);
  312. XCTAssertEqualObjects(promise.error, networkError);
  313. OCMVerifyAll(self.mockAPIService);
  314. }
  315. - (void)testAttestKeyUnexpectedResponse {
  316. NSData *attestation = [self generateRandomData];
  317. NSData *challenge = [self generateRandomData];
  318. NSString *keyID = [NSUUID UUID].UUIDString;
  319. // 1. Prepare unexpected response.
  320. NSData *responseBody =
  321. [FIRFixtureLoader loadFixtureNamed:@"FACTokenExchangeResponseSuccess.json"];
  322. GULURLSessionDataResponse *validAPIResponse = [self APIResponseWithCode:200
  323. responseBody:responseBody];
  324. // 2. Stub API Service
  325. // 2.1. Return prepared response.
  326. [self expectAttestAPIRequestWithAttestation:attestation
  327. keyID:keyID
  328. challenge:challenge
  329. response:validAPIResponse
  330. error:nil];
  331. // 3. Send request.
  332. __auto_type promise = [self.appAttestAPIService attestKeyWithAttestation:attestation
  333. keyID:keyID
  334. challenge:challenge];
  335. // 4. Verify.
  336. XCTAssert(FBLWaitForPromisesWithTimeout(1));
  337. XCTAssertTrue(promise.isRejected);
  338. XCTAssertNil(promise.value);
  339. XCTAssertNotNil(promise.error);
  340. OCMVerifyAll(self.mockAPIService);
  341. }
  342. #pragma mark - Helpers
  343. - (GULURLSessionDataResponse *)APIResponseWithCode:(NSInteger)code
  344. responseBody:(NSData *)responseBody {
  345. XCTAssertNotNil(responseBody);
  346. NSHTTPURLResponse *HTTPResponse = [FIRURLSessionOCMockStub HTTPResponseWithCode:code];
  347. GULURLSessionDataResponse *APIResponse =
  348. [[GULURLSessionDataResponse alloc] initWithResponse:HTTPResponse HTTPBody:responseBody];
  349. return APIResponse;
  350. }
  351. - (void)stubMockAPIServiceRequestForChallengeRequestWithResponse:(id)response {
  352. id URLValidationArg = [self URLValidationArgumentWithResource:@"generateAppAttestChallenge"];
  353. OCMStub([self.mockAPIService sendRequestWithURL:URLValidationArg
  354. HTTPMethod:@"POST"
  355. body:nil
  356. additionalHeaders:nil])
  357. .andDo(^(NSInvocation *invocation) {
  358. XCTAssertFalse([NSThread isMainThread]);
  359. })
  360. .andReturn([FBLPromise resolvedWith:response]);
  361. }
  362. - (id)URLValidationArgumentWithResource:(NSString *)resource {
  363. NSString *expectedRequestURL =
  364. [NSString stringWithFormat:@"%@/projects/%@/apps/%@:%@", [self.mockAPIService baseURL],
  365. self.projectID, self.appID, resource];
  366. id URLValidationArg = [OCMArg checkWithBlock:^BOOL(NSURL *URL) {
  367. XCTAssertEqualObjects(URL.absoluteString, expectedRequestURL);
  368. return YES;
  369. }];
  370. return URLValidationArg;
  371. }
  372. - (void)expectTokenAPIRequestWithArtifact:(NSData *)attestation
  373. challenge:(NSData *)challenge
  374. assertion:(NSData *)assertion
  375. response:(nullable GULURLSessionDataResponse *)response
  376. error:(nullable NSError *)error {
  377. id URLValidationArg = [self URLValidationArgumentWithResource:@"exchangeAppAttestAssertion"];
  378. id bodyValidationArg = [OCMArg checkWithBlock:^BOOL(NSData *requestBody) {
  379. NSDictionary<NSString *, id> *decodedData = [NSJSONSerialization JSONObjectWithData:requestBody
  380. options:0
  381. error:nil];
  382. XCTAssert([decodedData isKindOfClass:[NSDictionary class]]);
  383. // Validate artifact field.
  384. NSString *base64EncodedArtifact = decodedData[@"artifact"];
  385. XCTAssert([base64EncodedArtifact isKindOfClass:[NSString class]]);
  386. NSData *decodedAttestation = [[NSData alloc] initWithBase64EncodedString:base64EncodedArtifact
  387. options:0];
  388. XCTAssertEqualObjects(decodedAttestation, attestation);
  389. // Validate challenge field.
  390. NSString *base64EncodedChallenge = decodedData[@"challenge"];
  391. XCTAssert([base64EncodedChallenge isKindOfClass:[NSString class]]);
  392. NSData *decodedChallenge = [[NSData alloc] initWithBase64EncodedString:base64EncodedChallenge
  393. options:0];
  394. XCTAssertEqualObjects(decodedChallenge, challenge);
  395. // Validate assertion field.
  396. NSString *base64EncodedAssertion = decodedData[@"assertion"];
  397. XCTAssert([base64EncodedAssertion isKindOfClass:[NSString class]]);
  398. NSData *decodedAssertion = [[NSData alloc] initWithBase64EncodedString:base64EncodedAssertion
  399. options:0];
  400. XCTAssertEqualObjects(decodedAssertion, assertion);
  401. return YES;
  402. }];
  403. FBLPromise *responsePromise = [FBLPromise pendingPromise];
  404. if (error) {
  405. [responsePromise reject:error];
  406. } else {
  407. [responsePromise fulfill:response];
  408. }
  409. OCMExpect([self.mockAPIService sendRequestWithURL:URLValidationArg
  410. HTTPMethod:@"POST"
  411. body:bodyValidationArg
  412. additionalHeaders:@{@"Content-Type" : @"application/json"}])
  413. .andReturn(responsePromise);
  414. }
  415. - (void)expectTokenWithAPIReponse:(nonnull GULURLSessionDataResponse *)response
  416. toReturnToken:(nullable FIRAppCheckToken *)token {
  417. FBLPromise *tokenPromise = [FBLPromise pendingPromise];
  418. if (token) {
  419. [tokenPromise fulfill:token];
  420. } else {
  421. NSError *tokenError = [NSError errorWithDomain:self.name code:0 userInfo:nil];
  422. [tokenPromise reject:tokenError];
  423. }
  424. OCMExpect([self.mockAPIService appCheckTokenWithAPIResponse:response]).andReturn(tokenPromise);
  425. }
  426. - (void)expectAttestAPIRequestWithAttestation:(NSData *)attestation
  427. keyID:(NSString *)keyID
  428. challenge:(NSData *)challenge
  429. response:(nullable GULURLSessionDataResponse *)response
  430. error:(nullable NSError *)error {
  431. id URLValidationArg = [self URLValidationArgumentWithResource:@"exchangeAppAttestAttestation"];
  432. id bodyValidationArg = [OCMArg checkWithBlock:^BOOL(NSData *requestBody) {
  433. NSDictionary<NSString *, id> *decodedData = [NSJSONSerialization JSONObjectWithData:requestBody
  434. options:0
  435. error:nil];
  436. XCTAssert([decodedData isKindOfClass:[NSDictionary class]]);
  437. // Validate attestation field.
  438. NSString *base64EncodedAttestation = decodedData[@"attestation_statement"];
  439. XCTAssert([base64EncodedAttestation isKindOfClass:[NSString class]]);
  440. NSData *decodedAttestation =
  441. [[NSData alloc] initWithBase64EncodedString:base64EncodedAttestation options:0];
  442. XCTAssertEqualObjects(decodedAttestation, attestation);
  443. // Validate challenge field.
  444. NSString *base64EncodedChallenge = decodedData[@"challenge"];
  445. XCTAssert([base64EncodedAttestation isKindOfClass:[NSString class]]);
  446. NSData *decodedChallenge = [[NSData alloc] initWithBase64EncodedString:base64EncodedChallenge
  447. options:0];
  448. XCTAssertEqualObjects(decodedChallenge, challenge);
  449. // Validate key ID field.
  450. NSString *keyIDField = decodedData[@"key_id"];
  451. XCTAssert([base64EncodedAttestation isKindOfClass:[NSString class]]);
  452. XCTAssertEqualObjects(keyIDField, keyID);
  453. return YES;
  454. }];
  455. FBLPromise *resultPromise = [FBLPromise pendingPromise];
  456. if (error) {
  457. [resultPromise reject:error];
  458. } else {
  459. [resultPromise fulfill:response];
  460. }
  461. OCMExpect([self.mockAPIService sendRequestWithURL:URLValidationArg
  462. HTTPMethod:@"POST"
  463. body:bodyValidationArg
  464. additionalHeaders:@{@"Content-Type" : @"application/json"}])
  465. .andReturn(resultPromise);
  466. }
  467. - (NSData *)generateRandomData {
  468. return [[NSUUID UUID].UUIDString dataUsingEncoding:NSUTF8StringEncoding];
  469. }
  470. @end