Browse Source

[App Check] Add DeviceCheck `isSupported` check (#11663)

Added a DeviceCheck isSupported check and return a FIRAppCheckErrorCodeUnsupported error when not supported (e.g., when running on the simulator or older Macs). This was done to match the behaviour of the FIRAppAttestProvider.
Andrew Heard 2 năm trước cách đây
mục cha
commit
77fa63da55

+ 21 - 4
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 @@ NS_ASSUME_NONNULL_BEGIN
 #pragma mark - DeviceCheck
 
 - (FBLPromise<NSData *> *)deviceToken {
-  return [FBLPromise
-      wrapObjectOrErrorCompletion:^(FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
-        [self.deviceTokenGenerator generateTokenWithCompletionHandler:handler];
-      }];
+  return [self isDeviceCheckSupported].then(^FBLPromise<NSData *> *(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<NSNull *> *)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

+ 2 - 0
FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckTokenGenerator.h

@@ -20,6 +20,8 @@ NS_ASSUME_NONNULL_BEGIN
 
 @protocol FIRDeviceCheckTokenGenerator <NSObject>
 
+@property(getter=isSupported, readonly) BOOL supported;
+
 - (void)generateTokenWithCompletionHandler:(void (^)(NSData* _Nullable token,
                                                      NSError* _Nullable error))completion;
 

+ 69 - 13
FirebaseAppCheck/Tests/Unit/DeviceCheckProvider/FIRDeviceCheckProviderTests.m

@@ -19,6 +19,7 @@
 #import <OCMock/OCMock.h>
 #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 @@ FIR_DEVICE_CHECK_PROVIDER_AVAILABILITY
 }
 
 - (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 @@ FIR_DEVICE_CHECK_PROVIDER_AVAILABILITY
                     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 @@ FIR_DEVICE_CHECK_PROVIDER_AVAILABILITY
   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 @@ FIR_DEVICE_CHECK_PROVIDER_AVAILABILITY
     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 @@ FIR_DEVICE_CHECK_PROVIDER_AVAILABILITY
                     timeout:0.5
                enforceOrder:YES];
 
-  // 4. Verify.
+  // 5. Verify.
   OCMVerifyAll(self.fakeAPIService);
   OCMVerifyAll(self.fakeTokenGenerator);
 
@@ -204,18 +257,21 @@ FIR_DEVICE_CHECK_PROVIDER_AVAILABILITY
     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 @@ FIR_DEVICE_CHECK_PROVIDER_AVAILABILITY
                     timeout:0.5
                enforceOrder:YES];
 
-  // 4. Verify.
+  // 5. Verify.
   OCMVerifyAll(self.fakeAPIService);
   OCMVerifyAll(self.fakeTokenGenerator);