Skip to content

Commit

Permalink
[App Check] Add DeviceCheck isSupported check (#11663)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
andrewheard authored Aug 9, 2023
1 parent 4b9cbf6 commit 77fa63d
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -133,10 +134,26 @@ - (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable token,
#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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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]]
Expand All @@ -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
Expand All @@ -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
Expand All @@ -180,7 +233,7 @@ - (void)testGetTokenWhenDeviceTokenFails {
timeout:0.5
enforceOrder:YES];

// 4. Verify.
// 5. Verify.
OCMVerifyAll(self.fakeAPIService);
OCMVerifyAll(self.fakeTokenGenerator);

Expand All @@ -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
Expand All @@ -231,7 +287,7 @@ - (void)testGetTokenWhenAPIServiceFails {
timeout:0.5
enforceOrder:YES];

// 4. Verify.
// 5. Verify.
OCMVerifyAll(self.fakeAPIService);
OCMVerifyAll(self.fakeTokenGenerator);

Expand Down

0 comments on commit 77fa63d

Please sign in to comment.