diff --git a/FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckProvider.m b/FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckProvider.m index 418cb223eb4..812f44faf67 100644 --- a/FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckProvider.m +++ b/FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckProvider.m @@ -30,6 +30,7 @@ #import "FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckAPIService.h" #import "FirebaseAppCheck/Sources/Core/Backoff/FIRAppCheckBackoffWrapper.h" +#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" #import "FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h" #import "FirebaseAppCheck/Sources/Core/FIRAppCheckValidator.h" #import "FirebaseAppCheck/Sources/DeviceCheckProvider/API/FIRDeviceCheckAPIService.h" @@ -133,10 +134,26 @@ - (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable token, #pragma mark - DeviceCheck - (FBLPromise *)deviceToken { - return [FBLPromise - wrapObjectOrErrorCompletion:^(FBLPromiseObjectOrErrorCompletion _Nonnull handler) { - [self.deviceTokenGenerator generateTokenWithCompletionHandler:handler]; - }]; + return [self isDeviceCheckSupported].then(^FBLPromise *(NSNull *ignored) { + return [FBLPromise + wrapObjectOrErrorCompletion:^(FBLPromiseObjectOrErrorCompletion _Nonnull handler) { + [self.deviceTokenGenerator generateTokenWithCompletionHandler:handler]; + }]; + }); +} + +#pragma mark - Helpers + +/// Returns a resolved promise if DeviceCheck is supported and a rejected promise if it is not. +- (FBLPromise *)isDeviceCheckSupported { + if (self.deviceTokenGenerator.isSupported) { + return [FBLPromise resolvedWith:[NSNull null]]; + } else { + NSError *error = [FIRAppCheckErrorUtil unsupportedAttestationProvider:@"DeviceCheckProvider"]; + FBLPromise *rejectedPromise = [FBLPromise pendingPromise]; + [rejectedPromise reject:error]; + return rejectedPromise; + } } @end diff --git a/FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckTokenGenerator.h b/FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckTokenGenerator.h index 417dd078738..f1daebbd742 100644 --- a/FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckTokenGenerator.h +++ b/FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckTokenGenerator.h @@ -20,6 +20,8 @@ NS_ASSUME_NONNULL_BEGIN @protocol FIRDeviceCheckTokenGenerator +@property(getter=isSupported, readonly) BOOL supported; + - (void)generateTokenWithCompletionHandler:(void (^)(NSData* _Nullable token, NSError* _Nullable error))completion; diff --git a/FirebaseAppCheck/Tests/Unit/DeviceCheckProvider/FIRDeviceCheckProviderTests.m b/FirebaseAppCheck/Tests/Unit/DeviceCheckProvider/FIRDeviceCheckProviderTests.m index 2fff62867c5..a9ed180d66e 100644 --- a/FirebaseAppCheck/Tests/Unit/DeviceCheckProvider/FIRDeviceCheckProviderTests.m +++ b/FirebaseAppCheck/Tests/Unit/DeviceCheckProvider/FIRDeviceCheckProviderTests.m @@ -19,6 +19,7 @@ #import #import "FBLPromise+Testing.h" +#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" #import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h" #import "FirebaseAppCheck/Sources/DeviceCheckProvider/API/FIRDeviceCheckAPIService.h" #import "FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckTokenGenerator.h" @@ -98,22 +99,25 @@ - (void)testInitWithIncompleteApp { } - (void)testGetTokenSuccess { - // 1. Expect device token to be generated. + // 1. Expect FIRDeviceCheckTokenGenerator.isSupported. + OCMExpect([self.fakeTokenGenerator isSupported]).andReturn(YES); + + // 2. Expect device token to be generated. NSData *deviceToken = [NSData data]; id generateTokenArg = [OCMArg invokeBlockWithArgs:deviceToken, [NSNull null], nil]; OCMExpect([self.fakeTokenGenerator generateTokenWithCompletionHandler:generateTokenArg]); - // 2. Expect FAA token to be requested. + // 3. Expect FAA token to be requested. FIRAppCheckToken *validToken = [[FIRAppCheckToken alloc] initWithToken:@"valid_token" expirationDate:[NSDate distantFuture] receivedAtDate:[NSDate date]]; OCMExpect([self.fakeAPIService appCheckTokenWithDeviceToken:deviceToken]) .andReturn([FBLPromise resolvedWith:validToken]); - // 3. Expect backoff wrapper to be used. + // 4. Expect backoff wrapper to be used. self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"]; - // 4. Call getToken and validate the result. + // 5. Call getToken and validate the result. XCTestExpectation *completionExpectation = [self expectationWithDescription:@"completionExpectation"]; [self.provider @@ -129,7 +133,7 @@ - (void)testGetTokenSuccess { timeout:0.5 enforceOrder:YES]; - // 5. Verify. + // 6. Verify. XCTAssertNil(self.fakeBackoffWrapper.operationError); FIRAppCheckToken *wrapperResult = [self.fakeBackoffWrapper.operationResult isKindOfClass:[FIRAppCheckToken class]] @@ -141,6 +145,52 @@ - (void)testGetTokenSuccess { OCMVerifyAll(self.fakeTokenGenerator); } +- (void)testGetTokenWhenDeviceCheckIsNotSupported { + NSError *expectedError = + [FIRAppCheckErrorUtil unsupportedAttestationProvider:@"DeviceCheckProvider"]; + + // 0.1. Expect backoff wrapper to be used. + self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"]; + + // 0.2. Expect default error handler to be used. + XCTestExpectation *errorHandlerExpectation = [self expectationWithDescription:@"Error handler"]; + self.fakeBackoffWrapper.defaultErrorHandler = ^FIRAppCheckBackoffType(NSError *_Nonnull error) { + XCTAssertEqualObjects(error, expectedError); + [errorHandlerExpectation fulfill]; + return FIRAppCheckBackoffType1Day; + }; + + // 1. Expect FIRDeviceCheckTokenGenerator.isSupported. + OCMExpect([self.fakeTokenGenerator isSupported]).andReturn(NO); + + // 2. Don't expect DeviceCheck token to be generated or FAA token to be requested. + OCMReject([self.fakeTokenGenerator generateTokenWithCompletionHandler:OCMOCK_ANY]); + OCMReject([self.fakeAPIService appCheckTokenWithDeviceToken:OCMOCK_ANY]); + + // 3. Call getToken and validate the result. + XCTestExpectation *completionExpectation = + [self expectationWithDescription:@"completionExpectation"]; + [self.provider + getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { + [completionExpectation fulfill]; + XCTAssertNil(token); + XCTAssertEqualObjects(error, expectedError); + }]; + + [self waitForExpectations:@[ + self.fakeBackoffWrapper.backoffExpectation, errorHandlerExpectation, completionExpectation + ] + timeout:0.5 + enforceOrder:YES]; + + // 4. Verify. + OCMVerifyAll(self.fakeAPIService); + OCMVerifyAll(self.fakeTokenGenerator); + + XCTAssertEqualObjects(self.fakeBackoffWrapper.operationError, expectedError); + XCTAssertNil(self.fakeBackoffWrapper.operationResult); +} + - (void)testGetTokenWhenDeviceTokenFails { NSError *deviceTokenError = [NSError errorWithDomain:@"FIRDeviceCheckProviderTests" code:-1 @@ -157,14 +207,17 @@ - (void)testGetTokenWhenDeviceTokenFails { return FIRAppCheckBackoffType1Day; }; - // 1. Expect device token to be generated. + // 1. Expect FIRDeviceCheckTokenGenerator.isSupported. + OCMExpect([self.fakeTokenGenerator isSupported]).andReturn(YES); + + // 2. Expect device token to be generated. id generateTokenArg = [OCMArg invokeBlockWithArgs:[NSNull null], deviceTokenError, nil]; OCMExpect([self.fakeTokenGenerator generateTokenWithCompletionHandler:generateTokenArg]); - // 2. Don't expect FAA token to be requested. + // 3. Don't expect FAA token to be requested. OCMReject([self.fakeAPIService appCheckTokenWithDeviceToken:[OCMArg any]]); - // 3. Call getToken and validate the result. + // 4. Call getToken and validate the result. XCTestExpectation *completionExpectation = [self expectationWithDescription:@"completionExpectation"]; [self.provider @@ -180,7 +233,7 @@ - (void)testGetTokenWhenDeviceTokenFails { timeout:0.5 enforceOrder:YES]; - // 4. Verify. + // 5. Verify. OCMVerifyAll(self.fakeAPIService); OCMVerifyAll(self.fakeTokenGenerator); @@ -204,18 +257,21 @@ - (void)testGetTokenWhenAPIServiceFails { return FIRAppCheckBackoffType1Day; }; - // 1. Expect device token to be generated. + // 1. Expect FIRDeviceCheckTokenGenerator.isSupported. + OCMExpect([self.fakeTokenGenerator isSupported]).andReturn(YES); + + // 2. Expect device token to be generated. NSData *deviceToken = [NSData data]; id generateTokenArg = [OCMArg invokeBlockWithArgs:deviceToken, [NSNull null], nil]; OCMExpect([self.fakeTokenGenerator generateTokenWithCompletionHandler:generateTokenArg]); - // 2. Expect FAA token to be requested. + // 3. Expect FAA token to be requested. FBLPromise *rejectedPromise = [FBLPromise pendingPromise]; [rejectedPromise reject:APIServiceError]; OCMExpect([self.fakeAPIService appCheckTokenWithDeviceToken:deviceToken]) .andReturn(rejectedPromise); - // 3. Call getToken and validate the result. + // 4. Call getToken and validate the result. XCTestExpectation *completionExpectation = [self expectationWithDescription:@"completionExpectation"]; [self.provider @@ -231,7 +287,7 @@ - (void)testGetTokenWhenAPIServiceFails { timeout:0.5 enforceOrder:YES]; - // 4. Verify. + // 5. Verify. OCMVerifyAll(self.fakeAPIService); OCMVerifyAll(self.fakeTokenGenerator);