GACAppCheckAPIServiceTests.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. /*
  2. * Copyright 2020 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 <GoogleUtilities/NSURLSession+GULPromises.h>
  21. #import "AppCheckCore/Sources/Core/APIService/GACAppCheckAPIService.h"
  22. #import "AppCheckCore/Sources/Core/Errors/GACAppCheckErrorUtil.h"
  23. #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckErrors.h"
  24. #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckToken.h"
  25. #import "AppCheckCore/Tests/Unit/Utils/GACFixtureLoader.h"
  26. #import "AppCheckCore/Tests/Utils/Date/GACDateTestUtils.h"
  27. #import "AppCheckCore/Tests/Utils/URLSession/GACURLSessionOCMockStub.h"
  28. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  29. static NSString *const kAPIKeyHeaderKey = @"X-Goog-Api-Key";
  30. static NSString *const kAPIKeyHeaderValue = @"Test-API-Key";
  31. static NSString *const kBundleIDHeaderKey = @"X-Ios-Bundle-Identifier";
  32. static NSString *const kTestHeaderKey = @"X-test-header";
  33. static NSString *const kTestHeaderValue = @"TEST_HEADER_VALUE";
  34. #pragma mark - GACAppCheckAPIServiceTests
  35. @interface GACAppCheckAPIServiceTests : XCTestCase
  36. @property(nonatomic) GACAppCheckAPIService *APIService;
  37. @property(nonatomic) id mockURLSession;
  38. @property(nonatomic) NSMutableDictionary<NSString *, NSString *> *expectedHTTPHeaderFields;
  39. @end
  40. @implementation GACAppCheckAPIServiceTests
  41. - (void)setUp {
  42. [super setUp];
  43. self.mockURLSession = OCMStrictClassMock([NSURLSession class]);
  44. self.expectedHTTPHeaderFields = [NSMutableDictionary
  45. dictionaryWithDictionary:@{kBundleIDHeaderKey : [[NSBundle mainBundle] bundleIdentifier]}];
  46. self.APIService = [[GACAppCheckAPIService alloc] initWithURLSession:self.mockURLSession
  47. baseURL:nil
  48. APIKey:nil
  49. requestHooks:nil];
  50. }
  51. - (void)tearDown {
  52. [super tearDown];
  53. self.APIService = nil;
  54. [self.mockURLSession stopMocking];
  55. self.mockURLSession = nil;
  56. }
  57. #pragma mark - Init
  58. - (void)testInitDefaultBaseURL {
  59. GACAppCheckAPIService *APIService =
  60. [[GACAppCheckAPIService alloc] initWithURLSession:self.mockURLSession
  61. baseURL:nil
  62. APIKey:nil
  63. requestHooks:nil];
  64. XCTAssertNotNil(APIService);
  65. XCTAssertEqualObjects(APIService.baseURL, @"https://firebaseappcheck.googleapis.com/v1");
  66. }
  67. - (void)testInitCustomBaseURL {
  68. NSString *customBaseURL = @"https://custom.example.com/v1beta";
  69. GACAppCheckAPIService *APIService =
  70. [[GACAppCheckAPIService alloc] initWithURLSession:self.mockURLSession
  71. baseURL:customBaseURL
  72. APIKey:nil
  73. requestHooks:nil];
  74. XCTAssertNotNil(APIService);
  75. XCTAssertEqualObjects(APIService.baseURL, customBaseURL);
  76. }
  77. #pragma mark - Send Requests
  78. - (void)testDataRequestNetworkError {
  79. NSURL *URL = [NSURL URLWithString:@"https://some.url.com"];
  80. NSDictionary *additionalHeaders = @{@"header1" : @"value1"};
  81. NSData *requestBody = [@"Request body" dataUsingEncoding:NSUTF8StringEncoding];
  82. // 1. Stub URL session.
  83. NSError *networkError = [NSError errorWithDomain:self.name code:-1 userInfo:nil];
  84. [self stubURLSessionDataTaskPromiseWithResponse:nil
  85. body:nil
  86. error:networkError
  87. URLSessionMock:self.mockURLSession
  88. requestValidationBlock:nil];
  89. // 2. Send request.
  90. __auto_type requestPromise = [self.APIService sendRequestWithURL:URL
  91. HTTPMethod:@"POST"
  92. body:requestBody
  93. additionalHeaders:additionalHeaders];
  94. // 3. Verify.
  95. XCTAssert(FBLWaitForPromisesWithTimeout(1));
  96. XCTAssertTrue(requestPromise.isRejected);
  97. XCTAssertNotNil(requestPromise.error);
  98. XCTAssertEqualObjects(requestPromise.error.domain, GACAppCheckErrorDomain);
  99. XCTAssertEqual(requestPromise.error.code, GACAppCheckErrorCodeServerUnreachable);
  100. XCTAssertEqualObjects(requestPromise.error.userInfo[NSUnderlyingErrorKey], networkError);
  101. OCMVerifyAll(self.mockURLSession);
  102. }
  103. - (void)testDataRequestNot2xxHTTPStatusCode {
  104. NSURL *URL = [NSURL URLWithString:@"https://some.url.com"];
  105. NSData *requestBody = [@"Request body" dataUsingEncoding:NSUTF8StringEncoding];
  106. NSString *responseBodyString = @"Token verification failed.";
  107. NSData *HTTPResponseBody = [responseBodyString dataUsingEncoding:NSUTF8StringEncoding];
  108. NSHTTPURLResponse *HTTPResponse = [GACURLSessionOCMockStub HTTPResponseWithCode:300];
  109. [self stubURLSessionDataTaskPromiseWithResponse:HTTPResponse
  110. body:HTTPResponseBody
  111. error:nil
  112. URLSessionMock:self.mockURLSession
  113. requestValidationBlock:nil];
  114. // 2. Send request.
  115. __auto_type requestPromise = [self.APIService sendRequestWithURL:URL
  116. HTTPMethod:@"POST"
  117. body:requestBody
  118. additionalHeaders:nil];
  119. // 3. Verify.
  120. XCTAssert(FBLWaitForPromisesWithTimeout(1));
  121. XCTAssertTrue(requestPromise.isRejected);
  122. XCTAssertNil(requestPromise.value);
  123. XCTAssertNotNil(requestPromise.error);
  124. XCTAssertEqualObjects(requestPromise.error.domain, GACAppCheckErrorDomain);
  125. XCTAssertEqual(requestPromise.error.code, GACAppCheckErrorCodeUnknown);
  126. // Expect response body and HTTP status code to be included in the error.
  127. NSString *failureReason = requestPromise.error.userInfo[NSLocalizedFailureReasonErrorKey];
  128. XCTAssertNotNil(failureReason);
  129. XCTAssertTrue([failureReason containsString:@"300"]);
  130. XCTAssertTrue([failureReason containsString:responseBodyString]);
  131. OCMVerifyAll(self.mockURLSession);
  132. }
  133. - (void)testDataRequestWithRequestHooks {
  134. NSURL *URL = [NSURL URLWithString:@"https://some.url.com"];
  135. NSString *HTTPMethod = @"POST";
  136. NSData *requestBody = [@"Request body" dataUsingEncoding:NSUTF8StringEncoding];
  137. NSTimeInterval requestTimeout = 5.0;
  138. [self.expectedHTTPHeaderFields setObject:kTestHeaderValue forKey:kTestHeaderKey];
  139. GACAppCheckAPIRequestHook headerRequestHook = ^(NSMutableURLRequest *request) {
  140. [request addValue:kTestHeaderValue forHTTPHeaderField:kTestHeaderKey];
  141. };
  142. GACAppCheckAPIRequestHook timeoutRequestHook = ^(NSMutableURLRequest *request) {
  143. request.timeoutInterval = requestTimeout;
  144. };
  145. GACAppCheckAPIRequestHook cellularAccessRequestHook = ^(NSMutableURLRequest *request) {
  146. request.allowsCellularAccess = NO;
  147. };
  148. self.APIService = [[GACAppCheckAPIService alloc]
  149. initWithURLSession:self.mockURLSession
  150. baseURL:nil
  151. APIKey:nil
  152. requestHooks:@[ headerRequestHook, timeoutRequestHook, cellularAccessRequestHook ]];
  153. // 1. Stub URL session.
  154. FIRRequestValidationBlock requestValidation = ^BOOL(NSURLRequest *request) {
  155. XCTAssertEqualObjects(request.URL, URL);
  156. XCTAssertEqualObjects(request.HTTPMethod, HTTPMethod);
  157. XCTAssertEqualObjects(request.HTTPBody, requestBody);
  158. XCTAssertEqualObjects(request.allHTTPHeaderFields, self.expectedHTTPHeaderFields);
  159. XCTAssertEqual(request.timeoutInterval, requestTimeout);
  160. XCTAssertEqual(request.allowsCellularAccess, NO);
  161. return YES;
  162. };
  163. NSData *HTTPResponseBody = [@"A response" dataUsingEncoding:NSUTF8StringEncoding];
  164. NSHTTPURLResponse *HTTPResponse = [GACURLSessionOCMockStub HTTPResponseWithCode:200];
  165. [self stubURLSessionDataTaskPromiseWithResponse:HTTPResponse
  166. body:HTTPResponseBody
  167. error:nil
  168. URLSessionMock:self.mockURLSession
  169. requestValidationBlock:requestValidation];
  170. // 2. Send request.
  171. __auto_type requestPromise = [self.APIService sendRequestWithURL:URL
  172. HTTPMethod:HTTPMethod
  173. body:requestBody
  174. additionalHeaders:nil];
  175. // 3. Verify.
  176. XCTAssert(FBLWaitForPromisesWithTimeout(1));
  177. XCTAssertTrue(requestPromise.isFulfilled);
  178. XCTAssertNil(requestPromise.error);
  179. XCTAssertEqualObjects(requestPromise.value.HTTPResponse, HTTPResponse);
  180. XCTAssertEqualObjects(requestPromise.value.HTTPBody, HTTPResponseBody);
  181. OCMVerifyAll(self.mockURLSession);
  182. }
  183. - (void)testDataRequestWithAdditionalHeaders {
  184. NSURL *URL = [NSURL URLWithString:@"https://some.url.com"];
  185. NSString *HTTPMethod = @"POST";
  186. NSData *requestBody = [@"Request body" dataUsingEncoding:NSUTF8StringEncoding];
  187. NSDictionary<NSString *, NSString *> *additionalHeaders = @{kTestHeaderKey : kTestHeaderValue};
  188. [self.expectedHTTPHeaderFields addEntriesFromDictionary:additionalHeaders];
  189. // 1. Stub URL session.
  190. FIRRequestValidationBlock requestValidation = ^BOOL(NSURLRequest *request) {
  191. XCTAssertEqualObjects(request.URL, URL);
  192. XCTAssertEqualObjects(request.HTTPMethod, HTTPMethod);
  193. XCTAssertEqualObjects(request.HTTPBody, requestBody);
  194. XCTAssertEqualObjects(request.allHTTPHeaderFields, self.expectedHTTPHeaderFields);
  195. return YES;
  196. };
  197. NSData *HTTPResponseBody = [@"A response" dataUsingEncoding:NSUTF8StringEncoding];
  198. NSHTTPURLResponse *HTTPResponse = [GACURLSessionOCMockStub HTTPResponseWithCode:200];
  199. [self stubURLSessionDataTaskPromiseWithResponse:HTTPResponse
  200. body:HTTPResponseBody
  201. error:nil
  202. URLSessionMock:self.mockURLSession
  203. requestValidationBlock:requestValidation];
  204. // 2. Send request.
  205. __auto_type requestPromise = [self.APIService sendRequestWithURL:URL
  206. HTTPMethod:HTTPMethod
  207. body:requestBody
  208. additionalHeaders:additionalHeaders];
  209. // 3. Verify.
  210. XCTAssert(FBLWaitForPromisesWithTimeout(1));
  211. XCTAssertTrue(requestPromise.isFulfilled);
  212. XCTAssertNil(requestPromise.error);
  213. XCTAssertEqualObjects(requestPromise.value.HTTPResponse, HTTPResponse);
  214. XCTAssertEqualObjects(requestPromise.value.HTTPBody, HTTPResponseBody);
  215. OCMVerifyAll(self.mockURLSession);
  216. }
  217. - (void)testDataRequestWithAPIKey {
  218. NSURL *URL = [NSURL URLWithString:@"https://some.url.com"];
  219. NSString *HTTPMethod = @"POST";
  220. NSData *requestBody = [@"Request body" dataUsingEncoding:NSUTF8StringEncoding];
  221. [self.expectedHTTPHeaderFields setObject:kAPIKeyHeaderValue forKey:kAPIKeyHeaderKey];
  222. self.APIService = [[GACAppCheckAPIService alloc] initWithURLSession:self.mockURLSession
  223. baseURL:nil
  224. APIKey:kAPIKeyHeaderValue
  225. requestHooks:nil];
  226. // 1. Stub URL session.
  227. FIRRequestValidationBlock requestValidation = ^BOOL(NSURLRequest *request) {
  228. XCTAssertEqualObjects(request.URL, URL);
  229. XCTAssertEqualObjects(request.HTTPMethod, HTTPMethod);
  230. XCTAssertEqualObjects(request.HTTPBody, requestBody);
  231. XCTAssertEqualObjects(request.allHTTPHeaderFields, self.expectedHTTPHeaderFields);
  232. return YES;
  233. };
  234. NSData *HTTPResponseBody = [@"A response" dataUsingEncoding:NSUTF8StringEncoding];
  235. NSHTTPURLResponse *HTTPResponse = [GACURLSessionOCMockStub HTTPResponseWithCode:200];
  236. [self stubURLSessionDataTaskPromiseWithResponse:HTTPResponse
  237. body:HTTPResponseBody
  238. error:nil
  239. URLSessionMock:self.mockURLSession
  240. requestValidationBlock:requestValidation];
  241. // 2. Send request.
  242. __auto_type requestPromise = [self.APIService sendRequestWithURL:URL
  243. HTTPMethod:HTTPMethod
  244. body:requestBody
  245. additionalHeaders:nil];
  246. // 3. Verify.
  247. XCTAssert(FBLWaitForPromisesWithTimeout(1));
  248. XCTAssertTrue(requestPromise.isFulfilled);
  249. XCTAssertNil(requestPromise.error);
  250. XCTAssertEqualObjects(requestPromise.value.HTTPResponse, HTTPResponse);
  251. XCTAssertEqualObjects(requestPromise.value.HTTPBody, HTTPResponseBody);
  252. OCMVerifyAll(self.mockURLSession);
  253. }
  254. #pragma mark - Token Exchange API response
  255. - (void)testAppCheckTokenWithAPIResponseValidResponse {
  256. // 1. Prepare input parameters.
  257. NSData *responseBody =
  258. [GACFixtureLoader loadFixtureNamed:@"FACTokenExchangeResponseSuccess.json"];
  259. XCTAssertNotNil(responseBody);
  260. NSHTTPURLResponse *HTTPResponse = [GACURLSessionOCMockStub HTTPResponseWithCode:200];
  261. GULURLSessionDataResponse *APIResponse =
  262. [[GULURLSessionDataResponse alloc] initWithResponse:HTTPResponse HTTPBody:responseBody];
  263. // 2. Expected result.
  264. NSString *expectedFACToken = @"valid_app_check_token";
  265. // 3. Parse API response.
  266. __auto_type tokenPromise = [self.APIService appCheckTokenWithAPIResponse:APIResponse];
  267. // 4. Verify.
  268. XCTAssert(FBLWaitForPromisesWithTimeout(1));
  269. XCTAssertTrue(tokenPromise.isFulfilled);
  270. XCTAssertNil(tokenPromise.error);
  271. XCTAssertEqualObjects(tokenPromise.value.token, expectedFACToken);
  272. XCTAssertTrue([GACDateTestUtils isDate:tokenPromise.value.expirationDate
  273. approximatelyEqualCurrentPlusTimeInterval:1800
  274. precision:10]);
  275. }
  276. - (void)testAppCheckTokenWithAPIResponseInvalidFormat {
  277. // 1. Prepare input parameters.
  278. NSString *responseBodyString = @"Token verification failed.";
  279. NSData *responseBody = [responseBodyString dataUsingEncoding:NSUTF8StringEncoding];
  280. NSHTTPURLResponse *HTTPResponse = [GACURLSessionOCMockStub HTTPResponseWithCode:200];
  281. GULURLSessionDataResponse *APIResponse =
  282. [[GULURLSessionDataResponse alloc] initWithResponse:HTTPResponse HTTPBody:responseBody];
  283. // 2. Parse API response.
  284. __auto_type tokenPromise = [self.APIService appCheckTokenWithAPIResponse:APIResponse];
  285. // 3. Verify.
  286. XCTAssert(FBLWaitForPromisesWithTimeout(1));
  287. XCTAssertTrue(tokenPromise.isRejected);
  288. XCTAssertNil(tokenPromise.value);
  289. XCTAssertNotNil(tokenPromise.error);
  290. XCTAssertEqualObjects(tokenPromise.error.domain, GACAppCheckErrorDomain);
  291. XCTAssertEqual(tokenPromise.error.code, GACAppCheckErrorCodeUnknown);
  292. // Expect response body and HTTP status code to be included in the error.
  293. NSString *failureReason = tokenPromise.error.userInfo[NSLocalizedFailureReasonErrorKey];
  294. XCTAssertEqualObjects(failureReason, @"JSON serialization error.");
  295. }
  296. - (void)testAppCheckTokenResponseMissingFields {
  297. [self assertMissingFieldErrorWithFixture:@"DeviceCheckResponseMissingToken.json"
  298. missingField:@"token"];
  299. [self assertMissingFieldErrorWithFixture:@"DeviceCheckResponseMissingTimeToLive.json"
  300. missingField:@"ttl"];
  301. }
  302. - (void)assertMissingFieldErrorWithFixture:(NSString *)fixtureName
  303. missingField:(NSString *)fieldName {
  304. // 1. Parse API response.
  305. NSData *missingFiledBody = [GACFixtureLoader loadFixtureNamed:fixtureName];
  306. XCTAssertNotNil(missingFiledBody);
  307. NSHTTPURLResponse *HTTPResponse = [GACURLSessionOCMockStub HTTPResponseWithCode:200];
  308. GULURLSessionDataResponse *APIResponse =
  309. [[GULURLSessionDataResponse alloc] initWithResponse:HTTPResponse HTTPBody:missingFiledBody];
  310. // 2. Parse API response.
  311. __auto_type tokenPromise = [self.APIService appCheckTokenWithAPIResponse:APIResponse];
  312. // 3. Verify.
  313. XCTAssert(FBLWaitForPromisesWithTimeout(1));
  314. XCTAssertTrue(tokenPromise.isRejected);
  315. XCTAssertNil(tokenPromise.value);
  316. XCTAssertNotNil(tokenPromise.error);
  317. XCTAssertEqualObjects(tokenPromise.error.domain, GACAppCheckErrorDomain);
  318. XCTAssertEqual(tokenPromise.error.code, GACAppCheckErrorCodeUnknown);
  319. // Expect missing field name to be included in the error.
  320. NSString *failureReason = tokenPromise.error.userInfo[NSLocalizedFailureReasonErrorKey];
  321. NSString *fieldNameString = [NSString stringWithFormat:@"`%@`", fieldName];
  322. XCTAssertTrue([failureReason containsString:fieldNameString],
  323. @"Fixture `%@`: expected missing field %@ error not found", fixtureName,
  324. fieldNameString);
  325. }
  326. #pragma mark - Helpers
  327. - (void)stubURLSessionDataTaskPromiseWithResponse:(NSHTTPURLResponse *)HTTPResponse
  328. body:(NSData *)body
  329. error:(NSError *)error
  330. URLSessionMock:(id)URLSessionMock
  331. requestValidationBlock:
  332. (FIRRequestValidationBlock)requestValidationBlock {
  333. // Validate request content.
  334. FIRRequestValidationBlock nonOptionalRequestValidationBlock =
  335. requestValidationBlock ?: ^BOOL(id request) {
  336. return YES;
  337. };
  338. id URLRequestValidationArg = [OCMArg checkWithBlock:nonOptionalRequestValidationBlock];
  339. // Result promise.
  340. FBLPromise<GULURLSessionDataResponse *> *result = [FBLPromise pendingPromise];
  341. if (error == nil) {
  342. GULURLSessionDataResponse *response =
  343. [[GULURLSessionDataResponse alloc] initWithResponse:HTTPResponse HTTPBody:body];
  344. [result fulfill:response];
  345. } else {
  346. [result reject:error];
  347. }
  348. // Stub the method.
  349. OCMExpect([URLSessionMock gul_dataTaskPromiseWithRequest:URLRequestValidationArg])
  350. .andReturn(result);
  351. }
  352. @end