diff --git a/FirebaseAppCheck.podspec b/FirebaseAppCheck.podspec index a113cfb0de7..2c1e894cce0 100644 --- a/FirebaseAppCheck.podspec +++ b/FirebaseAppCheck.podspec @@ -44,6 +44,7 @@ Pod::Spec.new do |s| s.osx.weak_framework = 'DeviceCheck' s.tvos.weak_framework = 'DeviceCheck' + s.dependency 'AppCheckCore', '~> 10.18' s.dependency 'FirebaseAppCheckInterop', '~> 10.17' s.dependency 'FirebaseCore', '~> 10.0' s.dependency 'PromisesObjC', '~> 2.1' @@ -93,7 +94,6 @@ Pod::Spec.new do |s| } swift_unit_tests.source_files = [ base_dir + 'Tests/Unit/Swift/**/*.swift', - base_dir + 'Tests/Unit/Swift/**/*.h', ] end diff --git a/FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAPIService.h b/FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAPIService.h deleted file mode 100644 index a3ae83f4879..00000000000 --- a/FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAPIService.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -@class FBLPromise; -@class FIRAppAttestAttestationResponse; -@class FIRAppCheckToken; -@protocol FIRAppCheckAPIServiceProtocol; - -NS_ASSUME_NONNULL_BEGIN - -/// Methods to send API requests required for App Attest based attestation sequence. -@protocol FIRAppAttestAPIServiceProtocol - -/// Request a random challenge from server. -- (FBLPromise *)getRandomChallenge; - -/// Sends attestation data to Firebase backend for validation. -/// @param attestation The App Attest key attestation data obtained from the method -/// `-[DCAppAttestService attestKey:clientDataHash:completionHandler:]` using the random challenge -/// received from Firebase backend. -/// @param keyID The key ID used to generate the attestation. -/// @param challenge The challenge used to generate the attestation. -/// @return A promise that is fulfilled with a response object with an encrypted attestation -/// artifact and an Firebase App Check token or rejected with an error. -- (FBLPromise *)attestKeyWithAttestation:(NSData *)attestation - keyID:(NSString *)keyID - challenge:(NSData *)challenge; - -/// Exchanges attestation data (artifact & assertion) and a challenge for a FAC token. -- (FBLPromise *)getAppCheckTokenWithArtifact:(NSData *)artifact - challenge:(NSData *)challenge - assertion:(NSData *)assertion; - -@end - -/// A default implementation of `FIRAppAttestAPIServiceProtocol`. -@interface FIRAppAttestAPIService : NSObject - -/// Default initializer. -/// @param APIService An instance implementing `FIRAppCheckAPIServiceProtocol` to be used to send -/// network requests to Firebase App Check backend. -/// @param projectID A Firebase project ID for the requests (`FIRApp.options.projectID`). -/// @param appID A Firebase app ID for the requests (`FIRApp.options.googleAppID`). -- (instancetype)initWithAPIService:(id)APIService - projectID:(NSString *)projectID - appID:(NSString *)appID; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAPIService.m b/FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAPIService.m deleted file mode 100644 index 1c1c09f7b0d..00000000000 --- a/FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAPIService.m +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAPIService.h" - -#if __has_include() -#import -#else -#import "FBLPromises.h" -#endif - -#import "FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAttestationResponse.h" -#import "FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckAPIService.h" - -#import -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" - -NS_ASSUME_NONNULL_BEGIN - -static NSString *const kRequestFieldArtifact = @"artifact"; -static NSString *const kRequestFieldAssertion = @"assertion"; -static NSString *const kRequestFieldAttestation = @"attestation_statement"; -static NSString *const kRequestFieldChallenge = @"challenge"; -static NSString *const kRequestFieldKeyID = @"key_id"; - -static NSString *const kExchangeAppAttestAssertionEndpoint = @"exchangeAppAttestAssertion"; -static NSString *const kExchangeAppAttestAttestationEndpoint = @"exchangeAppAttestAttestation"; -static NSString *const kGenerateAppAttestChallengeEndpoint = @"generateAppAttestChallenge"; - -static NSString *const kContentTypeKey = @"Content-Type"; -static NSString *const kJSONContentType = @"application/json"; -static NSString *const kHTTPMethodPost = @"POST"; - -@interface FIRAppAttestAPIService () - -@property(nonatomic, readonly) id APIService; - -@property(nonatomic, readonly) NSString *projectID; -@property(nonatomic, readonly) NSString *appID; - -@end - -@implementation FIRAppAttestAPIService - -- (instancetype)initWithAPIService:(id)APIService - projectID:(NSString *)projectID - appID:(NSString *)appID { - self = [super init]; - if (self) { - _APIService = APIService; - _projectID = projectID; - _appID = appID; - } - return self; -} - -#pragma mark - Assertion request - -- (FBLPromise *)getAppCheckTokenWithArtifact:(NSData *)artifact - challenge:(NSData *)challenge - assertion:(NSData *)assertion { - NSURL *URL = [self URLForEndpoint:kExchangeAppAttestAssertionEndpoint]; - - return [self HTTPBodyWithArtifact:artifact challenge:challenge assertion:assertion] - .then(^FBLPromise *(NSData *HTTPBody) { - return [self.APIService sendRequestWithURL:URL - HTTPMethod:kHTTPMethodPost - body:HTTPBody - additionalHeaders:@{kContentTypeKey : kJSONContentType}]; - }) - .then(^id _Nullable(GULURLSessionDataResponse *_Nullable response) { - return [self.APIService appCheckTokenWithAPIResponse:response]; - }); -} - -#pragma mark - Random Challenge - -- (nonnull FBLPromise *)getRandomChallenge { - NSURL *URL = [self URLForEndpoint:kGenerateAppAttestChallengeEndpoint]; - - return [FBLPromise onQueue:[self backgroundQueue] - do:^id _Nullable { - return [self.APIService sendRequestWithURL:URL - HTTPMethod:kHTTPMethodPost - body:nil - additionalHeaders:nil]; - }] - .then(^id _Nullable(GULURLSessionDataResponse *_Nullable response) { - return [self randomChallengeWithAPIResponse:response]; - }); -} - -#pragma mark - Challenge response parsing - -- (FBLPromise *)randomChallengeWithAPIResponse:(GULURLSessionDataResponse *)response { - return [FBLPromise onQueue:[self backgroundQueue] - do:^id _Nullable { - NSError *error; - - NSData *randomChallenge = - [self randomChallengeFromResponseBody:response.HTTPBody - error:&error]; - - return randomChallenge ?: error; - }]; -} - -- (nullable NSData *)randomChallengeFromResponseBody:(NSData *)response error:(NSError **)outError { - if (response.length <= 0) { - FIRAppCheckSetErrorToPointer( - [FIRAppCheckErrorUtil errorWithFailureReason:@"Empty server response body."], outError); - return nil; - } - - NSError *JSONError; - NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:response - options:0 - error:&JSONError]; - - if (![responseDict isKindOfClass:[NSDictionary class]]) { - FIRAppCheckSetErrorToPointer([FIRAppCheckErrorUtil JSONSerializationError:JSONError], outError); - return nil; - } - - NSString *challenge = responseDict[@"challenge"]; - if (![challenge isKindOfClass:[NSString class]]) { - FIRAppCheckSetErrorToPointer( - [FIRAppCheckErrorUtil appCheckTokenResponseErrorWithMissingField:@"challenge"], outError); - return nil; - } - - NSData *randomChallenge = [[NSData alloc] initWithBase64EncodedString:challenge options:0]; - return randomChallenge; -} - -#pragma mark - Attestation request - -- (FBLPromise *)attestKeyWithAttestation:(NSData *)attestation - keyID:(NSString *)keyID - challenge:(NSData *)challenge { - NSURL *URL = [self URLForEndpoint:kExchangeAppAttestAttestationEndpoint]; - - return [self HTTPBodyWithAttestation:attestation keyID:keyID challenge:challenge] - .then(^FBLPromise *(NSData *HTTPBody) { - return [self.APIService sendRequestWithURL:URL - HTTPMethod:kHTTPMethodPost - body:HTTPBody - additionalHeaders:@{kContentTypeKey : kJSONContentType}]; - }) - .thenOn( - [self backgroundQueue], ^id _Nullable(GULURLSessionDataResponse *_Nullable URLResponse) { - NSError *error; - - __auto_type response = - [[FIRAppAttestAttestationResponse alloc] initWithResponseData:URLResponse.HTTPBody - requestDate:[NSDate date] - error:&error]; - - return response ?: error; - }); -} - -#pragma mark - Request HTTP Body - -- (FBLPromise *)HTTPBodyWithArtifact:(NSData *)artifact - challenge:(NSData *)challenge - assertion:(NSData *)assertion { - if (artifact.length <= 0 || challenge.length <= 0 || assertion.length <= 0) { - FBLPromise *rejectedPromise = [FBLPromise pendingPromise]; - [rejectedPromise reject:[FIRAppCheckErrorUtil - errorWithFailureReason:@"Missing or empty request parameter."]]; - return rejectedPromise; - } - - return [FBLPromise onQueue:[self backgroundQueue] - do:^id { - id JSONObject = @{ - kRequestFieldArtifact : [self base64StringWithData:artifact], - kRequestFieldChallenge : [self base64StringWithData:challenge], - kRequestFieldAssertion : [self base64StringWithData:assertion] - }; - - return [self HTTPBodyWithJSONObject:JSONObject]; - }]; -} - -- (FBLPromise *)HTTPBodyWithAttestation:(NSData *)attestation - keyID:(NSString *)keyID - challenge:(NSData *)challenge { - if (attestation.length <= 0 || keyID.length <= 0 || challenge.length <= 0) { - FBLPromise *rejectedPromise = [FBLPromise pendingPromise]; - [rejectedPromise reject:[FIRAppCheckErrorUtil - errorWithFailureReason:@"Missing or empty request parameter."]]; - return rejectedPromise; - } - - return [FBLPromise onQueue:[self backgroundQueue] - do:^id { - id JSONObject = @{ - kRequestFieldKeyID : keyID, - kRequestFieldAttestation : [self base64StringWithData:attestation], - kRequestFieldChallenge : [self base64StringWithData:challenge] - }; - - return [self HTTPBodyWithJSONObject:JSONObject]; - }]; -} - -- (FBLPromise *)HTTPBodyWithJSONObject:(nonnull id)JSONObject { - NSError *encodingError; - NSData *payloadJSON = [NSJSONSerialization dataWithJSONObject:JSONObject - options:0 - error:&encodingError]; - FBLPromise *HTTPBodyPromise = [FBLPromise pendingPromise]; - if (payloadJSON) { - [HTTPBodyPromise fulfill:payloadJSON]; - } else { - [HTTPBodyPromise reject:[FIRAppCheckErrorUtil JSONSerializationError:encodingError]]; - } - return HTTPBodyPromise; -} - -#pragma mark - Helpers - -- (NSString *)base64StringWithData:(NSData *)data { - return [data base64EncodedStringWithOptions:0]; -} - -- (NSURL *)URLForEndpoint:(NSString *)endpoint { - NSString *URL = [[self class] URLWithBaseURL:self.APIService.baseURL - projectID:self.projectID - appID:self.appID]; - return [NSURL URLWithString:[NSString stringWithFormat:@"%@:%@", URL, endpoint]]; -} - -+ (NSString *)URLWithBaseURL:(NSString *)baseURL - projectID:(NSString *)projectID - appID:(NSString *)appID { - return [NSString stringWithFormat:@"%@/projects/%@/apps/%@", baseURL, projectID, appID]; -} - -- (dispatch_queue_t)backgroundQueue { - return dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAttestationResponse.h b/FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAttestationResponse.h deleted file mode 100644 index c17088e0a6b..00000000000 --- a/FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAttestationResponse.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -@class FIRAppCheckToken; - -NS_ASSUME_NONNULL_BEGIN - -@interface FIRAppAttestAttestationResponse : NSObject - -/// App Attest attestation artifact required to refresh Firebase App Check token. -@property(nonatomic, readonly) NSData *artifact; - -/// Firebase App Check token. -@property(nonatomic, readonly) FIRAppCheckToken *token; - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithArtifact:(NSData *)artifact - token:(FIRAppCheckToken *)token NS_DESIGNATED_INITIALIZER; - -/// Init with the server response. -- (nullable instancetype)initWithResponseData:(NSData *)response - requestDate:(NSDate *)requestDate - error:(NSError **)outError; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAttestationResponse.m b/FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAttestationResponse.m deleted file mode 100644 index ec12e23629c..00000000000 --- a/FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAttestationResponse.m +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAttestationResponse.h" - -#import "FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckToken+APIResponse.h" -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" - -static NSString *const kResponseFieldAppCheckTokenDict = @"appCheckToken"; -static NSString *const kResponseFieldArtifact = @"artifact"; - -@implementation FIRAppAttestAttestationResponse - -- (instancetype)initWithArtifact:(NSData *)artifact token:(FIRAppCheckToken *)token { - self = [super init]; - if (self) { - _artifact = artifact; - _token = token; - } - return self; -} - -- (nullable instancetype)initWithResponseData:(NSData *)response - requestDate:(NSDate *)requestDate - error:(NSError **)outError { - if (response.length <= 0) { - FIRAppCheckSetErrorToPointer( - [FIRAppCheckErrorUtil - errorWithFailureReason: - @"Failed to parse the initial handshake response. Empty server response body."], - outError); - return nil; - } - - NSError *JSONError; - NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:response - options:0 - error:&JSONError]; - - if (![responseDict isKindOfClass:[NSDictionary class]]) { - FIRAppCheckSetErrorToPointer([FIRAppCheckErrorUtil JSONSerializationError:JSONError], outError); - return nil; - } - - NSString *artifactBase64String = responseDict[kResponseFieldArtifact]; - if (![artifactBase64String isKindOfClass:[NSString class]]) { - FIRAppCheckSetErrorToPointer( - [FIRAppCheckErrorUtil - appAttestAttestationResponseErrorWithMissingField:kResponseFieldArtifact], - outError); - return nil; - } - NSData *artifactData = [[NSData alloc] initWithBase64EncodedString:artifactBase64String - options:0]; - if (artifactData == nil) { - FIRAppCheckSetErrorToPointer( - [FIRAppCheckErrorUtil - appAttestAttestationResponseErrorWithMissingField:kResponseFieldArtifact], - outError); - return nil; - } - - NSDictionary *appCheckTokenDict = responseDict[kResponseFieldAppCheckTokenDict]; - if (![appCheckTokenDict isKindOfClass:[NSDictionary class]]) { - FIRAppCheckSetErrorToPointer( - [FIRAppCheckErrorUtil - appAttestAttestationResponseErrorWithMissingField:kResponseFieldAppCheckTokenDict], - outError); - return nil; - } - - FIRAppCheckToken *appCheckToken = [[FIRAppCheckToken alloc] initWithResponseDict:appCheckTokenDict - requestDate:requestDate - error:outError]; - - if (appCheckToken == nil) { - return nil; - } - - return [self initWithArtifact:artifactData token:appCheckToken]; -} - -@end diff --git a/FirebaseAppCheck/Sources/AppAttestProvider/DCAppAttestService+FIRAppAttestService.m b/FirebaseAppCheck/Sources/AppAttestProvider/DCAppAttestService+FIRAppAttestService.m deleted file mode 100644 index ac4feae689a..00000000000 --- a/FirebaseAppCheck/Sources/AppAttestProvider/DCAppAttestService+FIRAppAttestService.m +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/AppAttestProvider/DCAppAttestService+FIRAppAttestService.h" - -@implementation DCAppAttestService (FIRAppAttestService) - -@end diff --git a/FirebaseAppCheck/Sources/AppAttestProvider/Errors/FIRAppAttestRejectionError.m b/FirebaseAppCheck/Sources/AppAttestProvider/Errors/FIRAppAttestRejectionError.m deleted file mode 100644 index fcef6356d28..00000000000 --- a/FirebaseAppCheck/Sources/AppAttestProvider/Errors/FIRAppAttestRejectionError.m +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckErrors.h" - -#import "FirebaseAppCheck/Sources/AppAttestProvider/Errors/FIRAppAttestRejectionError.h" - -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" - -@implementation FIRAppAttestRejectionError - -- (instancetype)init { - return [self initWithDomain:FIRAppCheckErrorDomain code:FIRAppCheckErrorCodeUnknown userInfo:nil]; -} - -@end diff --git a/FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestProvider.m b/FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestProvider.m index 26d6d2a6091..9ed9232fdd9 100644 --- a/FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestProvider.m +++ b/FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestProvider.m @@ -16,507 +16,70 @@ #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppAttestProvider.h" -#import "FirebaseAppCheck/Sources/AppAttestProvider/DCAppAttestService+FIRAppAttestService.h" +#import -#if __has_include() -#import -#else -#import "FBLPromises.h" -#endif - -#import "FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAPIService.h" -#import "FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAttestationResponse.h" -#import "FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestProviderState.h" -#import "FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestService.h" -#import "FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestArtifactStorage.h" -#import "FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestKeyIDStorage.h" -#import "FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckAPIService.h" -#import "FirebaseAppCheck/Sources/Core/Backoff/FIRAppCheckBackoffWrapper.h" +#import "FirebaseAppCheck/Sources/Core/FIRApp+AppCheck.h" #import "FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h" - -#import "FirebaseAppCheck/Sources/Core/Utils/FIRAppCheckCryptoUtils.h" - -#import "FirebaseAppCheck/Sources/AppAttestProvider/Errors/FIRAppAttestRejectionError.h" -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckHTTPError.h" +#import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h" +#import "FirebaseAppCheck/Sources/Core/FIRHeartbeatLogger+AppCheck.h" #import "FirebaseCore/Extension/FirebaseCoreInternal.h" NS_ASSUME_NONNULL_BEGIN -/// A data object that contains all key attest data required for FAC token exchange. -@interface FIRAppAttestKeyAttestationResult : NSObject - -@property(nonatomic, readonly) NSString *keyID; -@property(nonatomic, readonly) NSData *challenge; -@property(nonatomic, readonly) NSData *attestation; - -- (instancetype)initWithKeyID:(NSString *)keyID - challenge:(NSData *)challenge - attestation:(NSData *)attestation; - -@end - -@implementation FIRAppAttestKeyAttestationResult - -- (instancetype)initWithKeyID:(NSString *)keyID - challenge:(NSData *)challenge - attestation:(NSData *)attestation { - self = [super init]; - if (self) { - _keyID = keyID; - _challenge = challenge; - _attestation = attestation; - } - return self; -} - -@end - -/// A data object that contains information required for assertion request. -@interface FIRAppAttestAssertionData : NSObject - -@property(nonatomic, readonly) NSData *challenge; -@property(nonatomic, readonly) NSData *artifact; -@property(nonatomic, readonly) NSData *assertion; - -- (instancetype)initWithChallenge:(NSData *)challenge - artifact:(NSData *)artifact - assertion:(NSData *)assertion; - -@end - -@implementation FIRAppAttestAssertionData - -- (instancetype)initWithChallenge:(NSData *)challenge - artifact:(NSData *)artifact - assertion:(NSData *)assertion { - self = [super init]; - if (self) { - _challenge = challenge; - _artifact = artifact; - _assertion = assertion; - } - return self; -} - -@end - @interface FIRAppAttestProvider () -@property(nonatomic, readonly) id APIService; -@property(nonatomic, readonly) id appAttestService; -@property(nonatomic, readonly) id keyIDStorage; -@property(nonatomic, readonly) id artifactStorage; -@property(nonatomic, readonly) id backoffWrapper; - -@property(nonatomic, nullable) FBLPromise *ongoingGetTokenOperation; - -@property(nonatomic, readonly) dispatch_queue_t queue; +@property(nonatomic, readonly) GACAppAttestProvider *appAttestProvider; @end @implementation FIRAppAttestProvider -- (instancetype)initWithAppAttestService:(id)appAttestService - APIService:(id)APIService - keyIDStorage:(id)keyIDStorage - artifactStorage:(id)artifactStorage - backoffWrapper:(id)backoffWrapper { +- (instancetype)initWithAppAttestProvider:(GACAppAttestProvider *)appAttestProvider { self = [super init]; if (self) { - _appAttestService = appAttestService; - _APIService = APIService; - _keyIDStorage = keyIDStorage; - _artifactStorage = artifactStorage; - _backoffWrapper = backoffWrapper; - _queue = dispatch_queue_create("com.firebase.FIRAppAttestProvider", DISPATCH_QUEUE_SERIAL); + _appAttestProvider = appAttestProvider; } return self; } - (nullable instancetype)initWithApp:(FIRApp *)app { - NSURLSession *URLSession = [NSURLSession - sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; - - FIRAppAttestKeyIDStorage *keyIDStorage = - [[FIRAppAttestKeyIDStorage alloc] initWithAppName:app.name appID:app.options.googleAppID]; - - FIRAppCheckAPIService *APIService = - [[FIRAppCheckAPIService alloc] initWithURLSession:URLSession + GACAppAttestProvider *appAttestProvider = + [[GACAppAttestProvider alloc] initWithServiceName:app.name + resourceName:app.resourceName + baseURL:nil APIKey:app.options.APIKey - appID:app.options.googleAppID - heartbeatLogger:app.heartbeatLogger]; - - FIRAppAttestAPIService *appAttestAPIService = - [[FIRAppAttestAPIService alloc] initWithAPIService:APIService - projectID:app.options.projectID - appID:app.options.googleAppID]; - - FIRAppAttestArtifactStorage *artifactStorage = - [[FIRAppAttestArtifactStorage alloc] initWithAppName:app.name - appID:app.options.googleAppID - accessGroup:app.options.appGroupID]; - - FIRAppCheckBackoffWrapper *backoffWrapper = [[FIRAppCheckBackoffWrapper alloc] init]; + keychainAccessGroup:app.options.appGroupID + requestHooks:@[ [app.heartbeatLogger requestHook] ]]; - return [self initWithAppAttestService:DCAppAttestService.sharedService - APIService:appAttestAPIService - keyIDStorage:keyIDStorage - artifactStorage:artifactStorage - backoffWrapper:backoffWrapper]; + return [self initWithAppAttestProvider:appAttestProvider]; } #pragma mark - FIRAppCheckProvider - (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable, NSError *_Nullable))handler { - [self getToken] - // Call the handler with the result. - .then(^FBLPromise *(FIRAppCheckToken *token) { - handler(token, nil); - return nil; - }) - .catch(^(NSError *error) { - handler(nil, error); - }); -} - -- (FBLPromise *)getToken { - return [FBLPromise onQueue:self.queue - do:^id _Nullable { - if (self.ongoingGetTokenOperation == nil) { - // Kick off a new handshake sequence only when there is not an ongoing - // handshake to avoid race conditions. - self.ongoingGetTokenOperation = - [self createGetTokenSequenceWithBackoffPromise] - - // Release the ongoing operation promise on completion. - .then(^FIRAppCheckToken *(FIRAppCheckToken *token) { - self.ongoingGetTokenOperation = nil; - return token; - }) - .recover(^NSError *(NSError *error) { - self.ongoingGetTokenOperation = nil; - return error; - }); - } - return self.ongoingGetTokenOperation; - }]; -} - -- (FBLPromise *)createGetTokenSequenceWithBackoffPromise { - return [self.backoffWrapper - applyBackoffToOperation:^FBLPromise *_Nonnull { - return [self createGetTokenSequencePromise]; - } - errorHandler:[self.backoffWrapper defaultAppCheckProviderErrorHandler]]; -} - -- (FBLPromise *)createGetTokenSequencePromise { - // Check attestation state to decide on the next steps. - return [self attestationState].thenOn(self.queue, ^id(FIRAppAttestProviderState *attestState) { - switch (attestState.state) { - case FIRAppAttestAttestationStateUnsupported: - FIRAppCheckDebugLog(kFIRLoggerAppCheckMessageCodeAppAttestNotSupported, - @"App Attest is not supported."); - return attestState.appAttestUnsupportedError; - break; - - case FIRAppAttestAttestationStateSupportedInitial: - case FIRAppAttestAttestationStateKeyGenerated: - // Initial handshake is required for both the "initial" and the "key generated" states. - return [self initialHandshakeWithKeyID:attestState.appAttestKeyID]; - break; - - case FIRAppAttestAttestationStateKeyRegistered: - // Refresh FAC token using the existing registered App Attest key pair. - return [self refreshTokenWithKeyID:attestState.appAttestKeyID - artifact:attestState.attestationArtifact]; - break; + [self.appAttestProvider getTokenWithCompletion:^(GACAppCheckToken *_Nullable internalToken, + NSError *_Nullable error) { + if (error) { + handler(nil, error); + return; } - }); -} - -#pragma mark - Initial handshake sequence (attestation) - -- (FBLPromise *)initialHandshakeWithKeyID:(nullable NSString *)keyID { - // 1. Attest the device. Retry once on 403 from Firebase backend (attestation rejection error). - __block NSString *keyIDForAttempt = keyID; - return [FBLPromise onQueue:self.queue - attempts:1 - delay:0 - condition:^BOOL(NSInteger attemptCount, NSError *_Nonnull error) { - // Reset keyID before retrying. - keyIDForAttempt = nil; - return [error isKindOfClass:[FIRAppAttestRejectionError class]]; - } - retry:^FBLPromise *_Nullable { - return [self attestKeyGenerateIfNeededWithID:keyIDForAttempt]; - }] - .thenOn(self.queue, ^FBLPromise *(NSArray *attestationResults) { - // 4. Save the artifact and return the received FAC token. - - FIRAppAttestKeyAttestationResult *attestation = attestationResults.firstObject; - FIRAppAttestAttestationResponse *firebaseAttestationResponse = - attestationResults.lastObject; - - return [self saveArtifactAndGetAppCheckTokenFromResponse:firebaseAttestationResponse - keyID:attestation.keyID]; - }); -} - -- (FBLPromise *)saveArtifactAndGetAppCheckTokenFromResponse: - (FIRAppAttestAttestationResponse *)response - keyID:(NSString *)keyID { - return [self.artifactStorage setArtifact:response.artifact forKey:keyID].thenOn( - self.queue, ^FIRAppCheckToken *(id result) { - return response.token; - }); -} - -- (FBLPromise *)attestKey:(NSString *)keyID - challenge:(NSData *)challenge { - return [FBLPromise onQueue:self.queue - do:^NSData *_Nullable { - return [FIRAppCheckCryptoUtils sha256HashFromData:challenge]; - }] - .thenOn(self.queue, - ^FBLPromise *(NSData *challengeHash) { - return [FBLPromise onQueue:self.queue - wrapObjectOrErrorCompletion:^( - FBLPromiseObjectOrErrorCompletion _Nonnull handler) { - [self.appAttestService attestKey:keyID - clientDataHash:challengeHash - completionHandler:handler]; - }] - .recoverOn(self.queue, ^id(NSError *error) { - return [FIRAppCheckErrorUtil appAttestAttestKeyFailedWithError:error - keyId:keyID - clientDataHash:challengeHash]; - }); - }) - .thenOn(self.queue, ^FBLPromise *(NSData *attestation) { - FIRAppAttestKeyAttestationResult *result = - [[FIRAppAttestKeyAttestationResult alloc] initWithKeyID:keyID - challenge:challenge - attestation:attestation]; - return [FBLPromise resolvedWith:result]; - }); -} - -- (FBLPromise *)attestKeyGenerateIfNeededWithID: - (nullable NSString *)keyID { - // 1. Request a random challenge and get App Attest key ID concurrently. - return [FBLPromise onQueue:self.queue - all:@[ - // 1.1. Request random challenge. - [self.APIService getRandomChallenge], - // 1.2. Get App Attest key ID. - [self generateAppAttestKeyIDIfNeeded:keyID] - ]] - .thenOn(self.queue, - ^FBLPromise *(NSArray *challengeAndKeyID) { - // 2. Attest the key. - NSData *challenge = challengeAndKeyID.firstObject; - NSString *keyID = challengeAndKeyID.lastObject; - - return [self attestKey:keyID challenge:challenge]; - }) - .recoverOn(self.queue, - ^id(NSError *error) { - // If Apple rejected the key (DCErrorInvalidKey) then reset the attestation and - // throw a specific error to signal retry (FIRAppAttestRejectionError). - NSError *underlyingError = error.userInfo[NSUnderlyingErrorKey]; - if (underlyingError && [underlyingError.domain isEqualToString:DCErrorDomain] && - underlyingError.code == DCErrorInvalidKey) { - FIRAppCheckDebugLog( - kFIRLoggerAppCheckMessageCodeAttestationRejected, - @"App Attest invalid key; the existing attestation will be reset."); - - // Reset the attestation. - return [self resetAttestation].thenOn(self.queue, ^NSError *(id result) { - // Throw the rejection error. - return [[FIRAppAttestRejectionError alloc] init]; - }); - } - - // Otherwise just re-throw the error. - return error; - }) - .thenOn(self.queue, - ^FBLPromise *(FIRAppAttestKeyAttestationResult *result) { - // 3. Exchange the attestation to FAC token and pass the results to the next step. - NSArray *attestationResults = @[ - // 3.1. Just pass the attestation result to the next step. - [FBLPromise resolvedWith:result], - // 3.2. Exchange the attestation to FAC token. - [self.APIService attestKeyWithAttestation:result.attestation - keyID:result.keyID - challenge:result.challenge] - ]; - - return [FBLPromise onQueue:self.queue all:attestationResults]; - }) - .recoverOn(self.queue, ^id(NSError *error) { - // If App Attest attestation was rejected then reset the attestation and throw a specific - // error. - FIRAppCheckHTTPError *HTTPError = (FIRAppCheckHTTPError *)error; - if ([HTTPError isKindOfClass:[FIRAppCheckHTTPError class]] && - HTTPError.HTTPResponse.statusCode == 403) { - FIRAppCheckDebugLog(kFIRLoggerAppCheckMessageCodeAttestationRejected, - @"App Attest attestation was rejected by backend. The existing " - @"attestation will be reset."); - // Reset the attestation. - return [self resetAttestation].thenOn(self.queue, ^NSError *(id result) { - // Throw the rejection error. - return [[FIRAppAttestRejectionError alloc] init]; - }); - } - - // Otherwise just re-throw the error. - return error; - }); -} -/// Resets stored key ID and attestation artifact. -- (FBLPromise *)resetAttestation { - return [self.keyIDStorage setAppAttestKeyID:nil].thenOn(self.queue, ^id(id result) { - return [self.artifactStorage setArtifact:nil forKey:@""]; - }); + handler([[FIRAppCheckToken alloc] initWithInternalToken:internalToken], nil); + }]; } -#pragma mark - Token refresh sequence (assertion) - -- (FBLPromise *)refreshTokenWithKeyID:(NSString *)keyID - artifact:(NSData *)artifact { - return [self.APIService getRandomChallenge] - .thenOn(self.queue, - ^FBLPromise *(NSData *challenge) { - return [self generateAssertionWithKeyID:keyID - artifact:artifact - challenge:challenge]; - }) - .thenOn(self.queue, ^id(FIRAppAttestAssertionData *assertion) { - return [self.APIService getAppCheckTokenWithArtifact:assertion.artifact - challenge:assertion.challenge - assertion:assertion.assertion]; - }); -} - -- (FBLPromise *)generateAssertionWithKeyID:(NSString *)keyID - artifact:(NSData *)artifact - challenge:(NSData *)challenge { - // 1. Calculate the statement and its hash for assertion. - return [FBLPromise - onQueue:self.queue - do:^NSData *_Nullable { - // 1.1. Compose statement to generate assertion for. - NSMutableData *statementForAssertion = [artifact mutableCopy]; - [statementForAssertion appendData:challenge]; - - // 1.2. Get the statement SHA256 hash. - return [FIRAppCheckCryptoUtils sha256HashFromData:[statementForAssertion copy]]; - }] - .thenOn(self.queue, - ^FBLPromise *(NSData *statementHash) { - return [FBLPromise onQueue:self.queue - wrapObjectOrErrorCompletion:^( - FBLPromiseObjectOrErrorCompletion _Nonnull handler) { - [self.appAttestService generateAssertion:keyID - clientDataHash:statementHash - completionHandler:handler]; - }] - .recoverOn(self.queue, ^id(NSError *error) { - return [FIRAppCheckErrorUtil - appAttestGenerateAssertionFailedWithError:error - keyId:keyID - clientDataHash:statementHash]; - }); - }) - // 3. Compose the result object. - .thenOn(self.queue, ^FIRAppAttestAssertionData *(NSData *assertion) { - return [[FIRAppAttestAssertionData alloc] initWithChallenge:challenge - artifact:artifact - assertion:assertion]; - }); -} - -#pragma mark - State handling - -- (FBLPromise *)attestationState { - dispatch_queue_t stateQueue = - dispatch_queue_create("FIRAppAttestProvider.state", DISPATCH_QUEUE_SERIAL); - - return [FBLPromise - onQueue:stateQueue - do:^id _Nullable { - NSError *error; - - // 1. Check if App Attest is supported. - id isSupportedResult = FBLPromiseAwait([self isAppAttestSupported], &error); - if (isSupportedResult == nil) { - return [[FIRAppAttestProviderState alloc] initUnsupportedWithError:error]; - } - - // 2. Check for stored key ID of the generated App Attest key pair. - NSString *appAttestKeyID = - FBLPromiseAwait([self.keyIDStorage getAppAttestKeyID], &error); - if (appAttestKeyID == nil) { - return [[FIRAppAttestProviderState alloc] initWithSupportedInitialState]; - } - - // 3. Check for stored attestation artifact received from Firebase backend. - NSData *attestationArtifact = - FBLPromiseAwait([self.artifactStorage getArtifactForKey:appAttestKeyID], &error); - if (attestationArtifact == nil) { - return [[FIRAppAttestProviderState alloc] initWithGeneratedKeyID:appAttestKeyID]; - } - - // 4. A valid App Attest key pair was generated and registered with Firebase - // backend. Return the corresponding state. - return [[FIRAppAttestProviderState alloc] initWithRegisteredKeyID:appAttestKeyID - artifact:attestationArtifact]; - }]; -} - -#pragma mark - Helpers - -/// Returns a resolved promise if App Attest is supported and a rejected promise if it is not. -- (FBLPromise *)isAppAttestSupported { - if (self.appAttestService.isSupported) { - return [FBLPromise resolvedWith:[NSNull null]]; - } else { - NSError *error = [FIRAppCheckErrorUtil unsupportedAttestationProvider:@"AppAttestProvider"]; - FBLPromise *rejectedPromise = [FBLPromise pendingPromise]; - [rejectedPromise reject:error]; - return rejectedPromise; - } -} - -/// Generates a new App Attest key associated with the Firebase app if `storedKeyID == nil`. -- (FBLPromise *)generateAppAttestKeyIDIfNeeded:(nullable NSString *)storedKeyID { - if (storedKeyID) { - // The key ID has been fetched already, just return it. - return [FBLPromise resolvedWith:storedKeyID]; - } else { - // Generate and save a new key otherwise. - return [self generateAppAttestKey]; - } -} +- (void)getLimitedUseTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable, + NSError *_Nullable))handler { + [self.appAttestProvider getLimitedUseTokenWithCompletion:^( + GACAppCheckToken *_Nullable internalToken, NSError *_Nullable error) { + if (error) { + handler(nil, error); + return; + } -/// Generates and stores App Attest key associated with the Firebase app. -- (FBLPromise *)generateAppAttestKey { - return [FBLPromise onQueue:self.queue - wrapObjectOrErrorCompletion:^(FBLPromiseObjectOrErrorCompletion _Nonnull handler) { - [self.appAttestService generateKeyWithCompletionHandler:handler]; - }] - .recoverOn(self.queue, - ^id(NSError *error) { - return [FIRAppCheckErrorUtil appAttestGenerateKeyFailedWithError:error]; - }) - .thenOn(self.queue, ^FBLPromise *(NSString *keyID) { - return [self.keyIDStorage setAppAttestKeyID:keyID]; - }); + handler([[FIRAppCheckToken alloc] initWithInternalToken:internalToken], nil); + }]; } @end diff --git a/FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestProviderState.h b/FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestProviderState.h deleted file mode 100644 index 4d82b02b9d2..00000000000 --- a/FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestProviderState.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -NS_ASSUME_NONNULL_BEGIN - -/// Represents different stages of App Attest attestation. -typedef NS_ENUM(NSInteger, FIRAppAttestAttestationState) { - /// App Attest is not supported on the current device. - FIRAppAttestAttestationStateUnsupported, - - /// App Attest is supported, the App Attest key pair has been generated. - FIRAppAttestAttestationStateSupportedInitial, - - /// App Attest key pair has been generated but has not been attested and registered with Firebase - /// backend. - FIRAppAttestAttestationStateKeyGenerated, - - /// App Attest key has been generated, attested with Apple backend and registered with Firebase - /// backend. An encrypted artifact required to refresh FAC token is stored on the device. - FIRAppAttestAttestationStateKeyRegistered, -}; - -/// Represents attestation stages of App Attest. The class is designed to be used exclusively by -/// `FIRAppAttestProvider`. -@interface FIRAppAttestProviderState : NSObject - -/// App Attest attestation state. -@property(nonatomic, readonly) FIRAppAttestAttestationState state; - -/// An error object when state is FIRAppAttestAttestationStateUnsupported. -@property(nonatomic, nullable, readonly) NSError *appAttestUnsupportedError; - -/// An App Attest key ID when state is FIRAppAttestAttestationStateKeyGenerated or -/// FIRAppAttestAttestationStateKeyRegistered. -@property(nonatomic, nullable, readonly) NSString *appAttestKeyID; - -/// An attestation artifact received from Firebase backend when state is -/// FIRAppAttestAttestationStateKeyRegistered. -@property(nonatomic, nullable, readonly) NSData *attestationArtifact; - -- (instancetype)init NS_UNAVAILABLE; - -/// Init with FIRAppAttestAttestationStateUnsupported and an error describing issue. -- (instancetype)initUnsupportedWithError:(NSError *)error; - -/// Init with FIRAppAttestAttestationStateSupportedInitial. -- (instancetype)initWithSupportedInitialState; - -/// Init with FIRAppAttestAttestationStateKeyGenerated and the key ID. -- (instancetype)initWithGeneratedKeyID:(NSString *)keyID; - -/// Init with FIRAppAttestAttestationStateKeyRegistered, the key ID and the attestation artifact -/// received from Firebase backend. -- (instancetype)initWithRegisteredKeyID:(NSString *)keyID artifact:(NSData *)artifact; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestProviderState.m b/FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestProviderState.m deleted file mode 100644 index c71be31c00d..00000000000 --- a/FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestProviderState.m +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestProviderState.h" - -@implementation FIRAppAttestProviderState - -- (instancetype)initUnsupportedWithError:(NSError *)error { - self = [super init]; - if (self) { - _state = FIRAppAttestAttestationStateUnsupported; - _appAttestUnsupportedError = error; - } - return self; -} - -- (instancetype)initWithSupportedInitialState { - self = [super init]; - if (self) { - _state = FIRAppAttestAttestationStateSupportedInitial; - } - return self; -} - -- (instancetype)initWithGeneratedKeyID:(NSString *)keyID { - self = [super init]; - if (self) { - _state = FIRAppAttestAttestationStateKeyGenerated; - _appAttestKeyID = keyID; - } - return self; -} - -- (instancetype)initWithRegisteredKeyID:(NSString *)keyID artifact:(NSData *)artifact { - self = [super init]; - if (self) { - _state = FIRAppAttestAttestationStateKeyRegistered; - _appAttestKeyID = keyID; - _attestationArtifact = artifact; - } - return self; -} - -@end diff --git a/FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestService.h b/FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestService.h deleted file mode 100644 index 27409a82d91..00000000000 --- a/FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestService.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -@class FBLPromise; - -NS_ASSUME_NONNULL_BEGIN - -/// See `DCAppAttestService` -/// https://developer.apple.com/documentation/devicecheck/dcappattestservice?language=objc -@protocol FIRAppAttestService - -@property(getter=isSupported, readonly) BOOL supported; - -- (void)generateKeyWithCompletionHandler:(void (^)(NSString *keyId, - NSError *error))completionHandler; - -- (void)attestKey:(NSString *)keyId - clientDataHash:(NSData *)clientDataHash - completionHandler:(void (^)(NSData *attestationObject, NSError *error))completionHandler; - -- (void)generateAssertion:(NSString *)keyId - clientDataHash:(NSData *)clientDataHash - completionHandler:(void (^)(NSData *assertionObject, NSError *error))completionHandler; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestArtifactStorage.h b/FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestArtifactStorage.h deleted file mode 100644 index 16a51d16344..00000000000 --- a/FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestArtifactStorage.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -@class FBLPromise; -@class GULKeychainStorage; - -NS_ASSUME_NONNULL_BEGIN - -/// Defines API of a storage capable to store an encrypted artifact required to refresh Firebase App -/// Check token obtained with App Attest provider. -@protocol FIRAppAttestArtifactStorageProtocol - -/// Set the artifact. An artifact previously set for *any* key ID will be replaced by the new one -/// with the new key ID. The storage always stores a single artifact. -/// @param artifact The artifact data to store. Pass `nil` to remove the stored artifact. -/// @param keyID The App Attest key ID used to generate the artifact. -/// @return An artifact that is resolved with the artifact data passed into the method in case of -/// success or is rejected with an error. -- (FBLPromise *)setArtifact:(nullable NSData *)artifact forKey:(NSString *)keyID; - -/// Get the artifact. -/// @param keyID The App Attest key ID used to generate the artifact. -/// @return A promise that is resolved with the artifact data if artifact exists, is resolved with -/// `nil` if no artifact found (or the existing artifact was set for a different key ID) or is -/// rejected with an error. -- (FBLPromise *)getArtifactForKey:(NSString *)keyID; - -@end - -/// An implementation of FIRAppAttestArtifactStorageProtocol. -@interface FIRAppAttestArtifactStorage : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -/// Default convenience initializer. -/// @param appName A Firebase App name (`FirebaseApp.name`). The app name will be used as a part of -/// the key to store the token for the storage instance. -/// @param appID A Firebase App identifier (`FirebaseOptions.googleAppID`). The app ID will be used -/// as a part of the key to store the token for the storage instance. -/// @param accessGroup The Keychain Access Group. -- (instancetype)initWithAppName:(NSString *)appName - appID:(NSString *)appID - accessGroup:(nullable NSString *)accessGroup; - -/// Designated initializer. -/// @param appName A Firebase App name (`FirebaseApp.name`). The app name will be used as a part of -/// the key to store the token for the storage instance. -/// @param appID A Firebase App identifier (`FirebaseOptions.googleAppID`). The app ID will be used -/// as a part of the key to store the token for the storage instance. -/// @param keychainStorage An instance of `GULKeychainStorage` used as an underlying secure storage. -/// @param accessGroup The Keychain Access Group. -- (instancetype)initWithAppName:(NSString *)appName - appID:(NSString *)appID - keychainStorage:(GULKeychainStorage *)keychainStorage - accessGroup:(nullable NSString *)accessGroup NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestArtifactStorage.m b/FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestArtifactStorage.m deleted file mode 100644 index 5ffd2886e10..00000000000 --- a/FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestArtifactStorage.m +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestArtifactStorage.h" - -#if __has_include() -#import -#else -#import "FBLPromises.h" -#endif - -#import - -#import "FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestStoredArtifact.h" -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" - -NS_ASSUME_NONNULL_BEGIN - -static NSString *const kKeychainService = @"com.firebase.app_check.app_attest_artifact_storage"; - -@interface FIRAppAttestArtifactStorage () - -@property(nonatomic, readonly) NSString *appName; -@property(nonatomic, readonly) NSString *appID; -@property(nonatomic, readonly) GULKeychainStorage *keychainStorage; -@property(nonatomic, readonly, nullable) NSString *accessGroup; - -@end - -@implementation FIRAppAttestArtifactStorage - -- (instancetype)initWithAppName:(NSString *)appName - appID:(NSString *)appID - keychainStorage:(GULKeychainStorage *)keychainStorage - accessGroup:(nullable NSString *)accessGroup { - self = [super init]; - if (self) { - _appName = [appName copy]; - _appID = [appID copy]; - _keychainStorage = keychainStorage; - _accessGroup = [accessGroup copy]; - } - return self; -} - -- (instancetype)initWithAppName:(NSString *)appName - appID:(NSString *)appID - accessGroup:(nullable NSString *)accessGroup { - GULKeychainStorage *keychainStorage = - [[GULKeychainStorage alloc] initWithService:kKeychainService]; - return [self initWithAppName:appName - appID:appID - keychainStorage:keychainStorage - accessGroup:accessGroup]; -} - -- (FBLPromise *)getArtifactForKey:(NSString *)keyID { - return [self.keychainStorage getObjectForKey:[self artifactKey] - objectClass:[FIRAppAttestStoredArtifact class] - accessGroup:self.accessGroup] - .then(^NSData *(id storedArtifact) { - FIRAppAttestStoredArtifact *artifact = (FIRAppAttestStoredArtifact *)storedArtifact; - if ([artifact isKindOfClass:[FIRAppAttestStoredArtifact class]] && - [artifact.keyID isEqualToString:keyID]) { - return artifact.artifact; - } else { - return nil; - } - }) - .recover(^NSError *(NSError *error) { - return [FIRAppCheckErrorUtil keychainErrorWithError:error]; - }); -} - -- (FBLPromise *)setArtifact:(nullable NSData *)artifact forKey:(nonnull NSString *)keyID { - if (artifact) { - return [self storeArtifact:artifact forKey:keyID].recover(^NSError *(NSError *error) { - return [FIRAppCheckErrorUtil keychainErrorWithError:error]; - }); - } else { - return [self.keychainStorage removeObjectForKey:[self artifactKey] accessGroup:self.accessGroup] - .then(^id _Nullable(NSNull *_Nullable value) { - return nil; - }) - .recover(^NSError *(NSError *error) { - return [FIRAppCheckErrorUtil keychainErrorWithError:error]; - }); - } -} - -#pragma mark - Helpers - -- (FBLPromise *)storeArtifact:(nullable NSData *)artifact - forKey:(nonnull NSString *)keyID { - FIRAppAttestStoredArtifact *storedArtifact = - [[FIRAppAttestStoredArtifact alloc] initWithKeyID:keyID artifact:artifact]; - return [self.keychainStorage setObject:storedArtifact - forKey:[self artifactKey] - accessGroup:self.accessGroup] - .then(^id _Nullable(NSNull *_Nullable value) { - return artifact; - }); -} - -- (NSString *)artifactKey { - return - [NSString stringWithFormat:@"app_check_app_attest_artifact.%@.%@", self.appName, self.appID]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestKeyIDStorage.h b/FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestKeyIDStorage.h deleted file mode 100644 index c0ff9ebd7d1..00000000000 --- a/FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestKeyIDStorage.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -@class FBLPromise; - -NS_ASSUME_NONNULL_BEGIN - -/// The protocol defines methods to store App Attest key IDs per Firebase app. -@protocol FIRAppAttestKeyIDStorageProtocol - -/** Manages storage of an app attest key ID. - * @param keyID The app attest key ID to store or `nil` to remove the existing app attest key ID. - * @return A promise that is resolved with a stored app attest key ID or `nil` if the existing app - * attest key ID has been removed. - */ -- (FBLPromise *)setAppAttestKeyID:(nullable NSString *)keyID; - -/** Reads a stored app attest key ID. - * @return A promise that is resolved with a stored app attest key ID or `nil` if there is not a - * stored app attest key ID. The promise is rejected with an error in the case of a missing app - * attest key ID . - */ -- (FBLPromise *)getAppAttestKeyID; - -@end - -/// The App Attest key ID storage implementation. -/// This class is designed for use by `FIRAppAttestProvider`. It's operations are managed by -/// `FIRAppAttestProvider`'s internal serial queue. It is not considered thread safe and should not -/// be used by other classes at this time. -@interface FIRAppAttestKeyIDStorage : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -/** Default convenience initializer. - * @param appName A Firebase App name (`FirebaseApp.name`). The app name will be used as a part of - * the key to store the token for the storage instance. - * @param appID A Firebase App identifier (`FirebaseOptions.googleAppID`). The app ID will be used - * as a part of the key to store the token for the storage instance. - */ -- (instancetype)initWithAppName:(NSString *)appName appID:(NSString *)appID; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestKeyIDStorage.m b/FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestKeyIDStorage.m deleted file mode 100644 index 5eb8ffd88f1..00000000000 --- a/FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestKeyIDStorage.m +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestKeyIDStorage.h" - -#if __has_include() -#import -#else -#import "FBLPromises.h" -#endif - -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" - -/// The `NSUserDefaults` suite name for the storage location of the app attest key ID. -static NSString *const kKeyIDStorageDefaultsSuiteName = @"com.firebase.FIRAppAttestKeyIDStorage"; - -@interface FIRAppAttestKeyIDStorage () - -@property(nonatomic, readonly) NSString *appName; -@property(nonatomic, readonly) NSString *appID; - -/// The app attest key ID is stored using `NSUserDefaults` . -@property(nonatomic, readonly) NSUserDefaults *userDefaults; - -@end - -@implementation FIRAppAttestKeyIDStorage - -- (instancetype)initWithAppName:(NSString *)appName appID:(NSString *)appID { - self = [super init]; - if (self) { - _appName = [appName copy]; - _appID = [appID copy]; - _userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kKeyIDStorageDefaultsSuiteName]; - } - return self; -} - -- (nonnull FBLPromise *)setAppAttestKeyID:(nullable NSString *)keyID { - [self storeAppAttestKeyID:keyID]; - return [FBLPromise resolvedWith:keyID]; -} - -- (nonnull FBLPromise *)getAppAttestKeyID { - NSString *appAttestKeyID = [self appAttestKeyIDFromStorage]; - if (appAttestKeyID) { - return [FBLPromise resolvedWith:appAttestKeyID]; - } else { - NSError *error = [FIRAppCheckErrorUtil appAttestKeyIDNotFound]; - FBLPromise *rejectedPromise = [FBLPromise pendingPromise]; - [rejectedPromise reject:error]; - return rejectedPromise; - } -} - -#pragma mark - Helpers - -- (void)storeAppAttestKeyID:(nullable NSString *)keyID { - if (keyID) { - [self.userDefaults setObject:keyID forKey:[self keyIDStorageKey]]; - } else { - [self.userDefaults removeObjectForKey:[self keyIDStorageKey]]; - } -} - -- (nullable NSString *)appAttestKeyIDFromStorage { - NSString *appAttestKeyID = nil; - appAttestKeyID = [self.userDefaults objectForKey:[self keyIDStorageKey]]; - return appAttestKeyID; -} - -- (NSString *)keyIDStorageKey { - return [[self class] keyIDStorageKeyForAppName:self.appName appID:self.appID]; -} - -+ (NSString *)keyIDStorageKeyForAppName:(NSString *)appName appID:(NSString *)appID { - return [NSString stringWithFormat:@"app_attest_keyID.%@.%@", appName, appID]; -} - -@end diff --git a/FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestStoredArtifact.h b/FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestStoredArtifact.h deleted file mode 100644 index c78a0058d7d..00000000000 --- a/FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestStoredArtifact.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface FIRAppAttestStoredArtifact : NSObject - -/// The App Attest key ID used to generate the artifact. -@property(nonatomic, readonly) NSString *keyID; - -/// The Firebase App Attest artifact generated by the backend. -@property(nonatomic, readonly) NSData *artifact; - -/// The object version. -/// WARNING: The version must be incremented if properties are added, removed or modified. Migration -/// must be handled accordingly in `initWithCoder:` method. -@property(nonatomic, readonly) NSInteger storageVersion; - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithKeyID:(NSString *)keyID - artifact:(NSData *)artifact NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestStoredArtifact.m b/FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestStoredArtifact.m deleted file mode 100644 index c0272f1c919..00000000000 --- a/FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestStoredArtifact.m +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestStoredArtifact.h" - -static NSString *const kKeyIDKey = @"keyID"; -static NSString *const kArtifactKey = @"artifact"; -static NSString *const kStorageVersionKey = @"storageVersion"; - -static NSInteger const kStorageVersion = 1; - -@implementation FIRAppAttestStoredArtifact - -- (instancetype)initWithKeyID:(NSString *)keyID artifact:(NSData *)artifact { - self = [super init]; - if (self) { - _keyID = keyID; - _artifact = artifact; - } - return self; -} - -- (NSInteger)storageVersion { - return kStorageVersion; -} - -#pragma mark - NSSecureCoding - -+ (BOOL)supportsSecureCoding { - return YES; -} - -- (void)encodeWithCoder:(nonnull NSCoder *)coder { - [coder encodeObject:self.keyID forKey:kKeyIDKey]; - [coder encodeObject:self.artifact forKey:kArtifactKey]; - [coder encodeInteger:self.storageVersion forKey:kStorageVersionKey]; -} - -- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { - NSInteger storageVersion = [coder decodeIntegerForKey:kStorageVersionKey]; - - if (storageVersion < kStorageVersion) { - // Handle migration here when new versions are added - } - - // If the version of the stored object is equal or higher than the current version then try the - // best to get enough data to initialize the object. - NSString *keyID = [coder decodeObjectOfClass:[NSString class] forKey:kKeyIDKey]; - if (keyID.length < 1) { - return nil; - } - - NSData *artifact = [coder decodeObjectOfClass:[NSData class] forKey:kArtifactKey]; - if (artifact.length < 1) { - return nil; - } - - return [self initWithKeyID:keyID artifact:artifact]; -} - -@end diff --git a/FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckAPIService.h b/FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckAPIService.h deleted file mode 100644 index 1ac20c3af14..00000000000 --- a/FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckAPIService.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -@class FBLPromise; -@class GULURLSessionDataResponse; -@class FIRAppCheckToken; - -@protocol FIRHeartbeatLoggerProtocol; - -NS_ASSUME_NONNULL_BEGIN - -@protocol FIRAppCheckAPIServiceProtocol - -@property(nonatomic, readonly) NSString *baseURL; - -- (FBLPromise *) - sendRequestWithURL:(NSURL *)requestURL - HTTPMethod:(NSString *)HTTPMethod - body:(nullable NSData *)body - additionalHeaders:(nullable NSDictionary *)additionalHeaders; - -- (FBLPromise *)appCheckTokenWithAPIResponse: - (GULURLSessionDataResponse *)response; - -@end - -@interface FIRAppCheckAPIService : NSObject - -/** - * The default initializer. - * @param session The URL session used to make network requests. - * @param APIKey The Firebase project API key (see `FIROptions.APIKey`). - * @param appID The Firebase app ID (see `FIROptions.googleAppID`). - * @param heartbeatLogger The heartbeat logger used to populate heartbeat data in request headers. - */ -- (instancetype)initWithURLSession:(NSURLSession *)session - APIKey:(NSString *)APIKey - appID:(NSString *)appID - heartbeatLogger:(id)heartbeatLogger; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckAPIService.m b/FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckAPIService.m deleted file mode 100644 index c24a52fe409..00000000000 --- a/FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckAPIService.m +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#import "FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckAPIService.h" - -#if __has_include() -#import -#else -#import "FBLPromises.h" -#endif - -#import "FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckToken+APIResponse.h" -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" -#import "FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h" - -#import "FirebaseCore/Extension/FirebaseCoreInternal.h" - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -static NSString *const kAPIKeyHeaderKey = @"X-Goog-Api-Key"; -static NSString *const kHeartbeatKey = @"X-firebase-client"; -static NSString *const kBundleIdKey = @"X-Ios-Bundle-Identifier"; - -static NSString *const kDefaultBaseURL = @"https://firebaseappcheck.googleapis.com/v1"; - -@interface FIRAppCheckAPIService () - -@property(nonatomic, readonly) NSURLSession *URLSession; -@property(nonatomic, readonly) NSString *APIKey; -@property(nonatomic, readonly) NSString *appID; -@property(nonatomic, readonly) id heartbeatLogger; - -@end - -@implementation FIRAppCheckAPIService - -// Synthesize properties declared in a protocol. -@synthesize baseURL = _baseURL; - -- (instancetype)initWithURLSession:(NSURLSession *)session - APIKey:(NSString *)APIKey - appID:(NSString *)appID - heartbeatLogger:(id)heartbeatLogger { - return [self initWithURLSession:session - APIKey:APIKey - appID:appID - heartbeatLogger:heartbeatLogger - baseURL:kDefaultBaseURL]; -} - -- (instancetype)initWithURLSession:(NSURLSession *)session - APIKey:(NSString *)APIKey - appID:(NSString *)appID - heartbeatLogger:(id)heartbeatLogger - baseURL:(NSString *)baseURL { - self = [super init]; - if (self) { - _URLSession = session; - _APIKey = APIKey; - _appID = appID; - _heartbeatLogger = heartbeatLogger; - _baseURL = baseURL; - } - return self; -} - -- (FBLPromise *) - sendRequestWithURL:(NSURL *)requestURL - HTTPMethod:(NSString *)HTTPMethod - body:(nullable NSData *)body - additionalHeaders:(nullable NSDictionary *)additionalHeaders { - return [self requestWithURL:requestURL - HTTPMethod:HTTPMethod - body:body - additionalHeaders:additionalHeaders] - .then(^id _Nullable(NSURLRequest *_Nullable request) { - return [self sendURLRequest:request]; - }) - .then(^id _Nullable(GULURLSessionDataResponse *_Nullable response) { - return [self validateHTTPResponseStatusCode:response]; - }); -} - -- (FBLPromise *)requestWithURL:(NSURL *)requestURL - HTTPMethod:(NSString *)HTTPMethod - body:(NSData *)body - additionalHeaders:(nullable NSDictionary *) - additionalHeaders { - return [FBLPromise - onQueue:[self defaultQueue] - do:^id _Nullable { - __block NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestURL]; - request.HTTPMethod = HTTPMethod; - request.HTTPBody = body; - - [request setValue:self.APIKey forHTTPHeaderField:kAPIKeyHeaderKey]; - - [request setValue:FIRHeaderValueFromHeartbeatsPayload( - [self.heartbeatLogger flushHeartbeatsIntoPayload]) - forHTTPHeaderField:kHeartbeatKey]; - - [request setValue:[[NSBundle mainBundle] bundleIdentifier] - forHTTPHeaderField:kBundleIdKey]; - - [additionalHeaders - enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, NSString *_Nonnull obj, - BOOL *_Nonnull stop) { - [request setValue:obj forHTTPHeaderField:key]; - }]; - - return [request copy]; - }]; -} - -- (FBLPromise *)sendURLRequest:(NSURLRequest *)request { - return [self.URLSession gul_dataTaskPromiseWithRequest:request] - .recover(^id(NSError *networkError) { - // Wrap raw network error into App Check domain error. - return [FIRAppCheckErrorUtil APIErrorWithNetworkError:networkError]; - }) - .then(^id _Nullable(GULURLSessionDataResponse *response) { - return [self validateHTTPResponseStatusCode:response]; - }); -} - -- (FBLPromise *)validateHTTPResponseStatusCode: - (GULURLSessionDataResponse *)response { - NSInteger statusCode = response.HTTPResponse.statusCode; - return [FBLPromise do:^id _Nullable { - if (statusCode < 200 || statusCode >= 300) { - FIRAppCheckDebugLog(kFIRLoggerAppCheckMessageCodeUnexpectedHTTPCode, - @"Unexpected API response: %@, body: %@.", response.HTTPResponse, - [[NSString alloc] initWithData:response.HTTPBody - encoding:NSUTF8StringEncoding]); - return [FIRAppCheckErrorUtil APIErrorWithHTTPResponse:response.HTTPResponse - data:response.HTTPBody]; - } - return response; - }]; -} - -- (FBLPromise *)appCheckTokenWithAPIResponse: - (GULURLSessionDataResponse *)response { - return [FBLPromise onQueue:[self defaultQueue] - do:^id _Nullable { - NSError *error; - - FIRAppCheckToken *token = [[FIRAppCheckToken alloc] - initWithTokenExchangeResponse:response.HTTPBody - requestDate:[NSDate date] - error:&error]; - return token ?: error; - }]; -} - -- (dispatch_queue_t)defaultQueue { - return dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckToken+APIResponse.h b/FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckToken+APIResponse.h deleted file mode 100644 index bbea948eeab..00000000000 --- a/FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckToken+APIResponse.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckToken.h" - -@class FBLPromise; -@class GULURLSessionDataResponse; - -NS_ASSUME_NONNULL_BEGIN - -@interface FIRAppCheckToken (APIResponse) - -- (nullable instancetype)initWithTokenExchangeResponse:(NSData *)response - requestDate:(NSDate *)requestDate - error:(NSError **)outError; - -- (nullable instancetype)initWithResponseDict:(NSDictionary *)responseDict - requestDate:(NSDate *)requestDate - error:(NSError **)outError; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckToken+APIResponse.m b/FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckToken+APIResponse.m deleted file mode 100644 index 17006b38578..00000000000 --- a/FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckToken+APIResponse.m +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckToken+APIResponse.h" -#import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h" - -#if __has_include() -#import -#else -#import "FBLPromises.h" -#endif - -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" - -#import - -static NSString *const kResponseFieldToken = @"token"; -static NSString *const kResponseFieldTTL = @"ttl"; - -@implementation FIRAppCheckToken (APIResponse) - -- (nullable instancetype)initWithTokenExchangeResponse:(NSData *)response - requestDate:(NSDate *)requestDate - error:(NSError **)outError { - if (response.length <= 0) { - FIRAppCheckSetErrorToPointer( - [FIRAppCheckErrorUtil errorWithFailureReason:@"Empty server response body."], outError); - return nil; - } - - NSError *JSONError; - NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:response - options:0 - error:&JSONError]; - - if (![responseDict isKindOfClass:[NSDictionary class]]) { - FIRAppCheckSetErrorToPointer([FIRAppCheckErrorUtil JSONSerializationError:JSONError], outError); - return nil; - } - - return [self initWithResponseDict:responseDict requestDate:requestDate error:outError]; -} - -- (nullable instancetype)initWithResponseDict:(NSDictionary *)responseDict - requestDate:(NSDate *)requestDate - error:(NSError **)outError { - NSString *token = responseDict[kResponseFieldToken]; - if (![token isKindOfClass:[NSString class]]) { - FIRAppCheckSetErrorToPointer( - [FIRAppCheckErrorUtil appCheckTokenResponseErrorWithMissingField:kResponseFieldToken], - outError); - return nil; - } - - NSString *timeToLiveString = responseDict[kResponseFieldTTL]; - if (![token isKindOfClass:[NSString class]] || token.length <= 0) { - FIRAppCheckSetErrorToPointer( - [FIRAppCheckErrorUtil appCheckTokenResponseErrorWithMissingField:kResponseFieldTTL], - outError); - return nil; - } - - // Expect a string like "3600s" representing a time interval in seconds. - NSString *timeToLiveValueString = [timeToLiveString stringByReplacingOccurrencesOfString:@"s" - withString:@""]; - NSTimeInterval secondsToLive = timeToLiveValueString.doubleValue; - - if (secondsToLive == 0) { - FIRAppCheckSetErrorToPointer( - [FIRAppCheckErrorUtil appCheckTokenResponseErrorWithMissingField:kResponseFieldTTL], - outError); - return nil; - } - - NSDate *expirationDate = [requestDate dateByAddingTimeInterval:secondsToLive]; - - return [self initWithToken:token expirationDate:expirationDate receivedAtDate:requestDate]; -} - -@end diff --git a/FirebaseAppCheck/Sources/Core/Backoff/FIRAppCheckBackoffWrapper.h b/FirebaseAppCheck/Sources/Core/Backoff/FIRAppCheckBackoffWrapper.h deleted file mode 100644 index 73dfa97f05d..00000000000 --- a/FirebaseAppCheck/Sources/Core/Backoff/FIRAppCheckBackoffWrapper.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -@class FBLPromise; - -NS_ASSUME_NONNULL_BEGIN - -/// Backoff type. Backoff interval calculation depends on the type. -typedef NS_ENUM(NSUInteger, FIRAppCheckBackoffType) { - /// No backoff. Another retry is allowed straight away. - FIRAppCheckBackoffTypeNone, - - /// Next retry will be allowed in 1 day (24 hours) after the failure. - FIRAppCheckBackoffType1Day, - - /// A small backoff interval that exponentially increases after each consequent failure. - FIRAppCheckBackoffTypeExponential -}; - -/// Creates a promise for an operation to apply the backoff to. -typedef FBLPromise *_Nonnull (^FIRAppCheckBackoffOperationProvider)(void); - -/// Converts an error to a backoff type. -typedef FIRAppCheckBackoffType (^FIRAppCheckBackoffErrorHandler)(NSError *error); - -/// A block returning a date. Is used instead of `+[NSDate date]` for better testability of logic -/// dependent on the current time. -typedef NSDate *_Nonnull (^FIRAppCheckDateProvider)(void); - -/// Defines API for an object that conditionally applies backoff to a given operation based on the -/// history of previous operation failures. -@protocol FIRAppCheckBackoffWrapperProtocol - -/// Conditionally applies backoff to the given operation. -/// @param operationProvider A block that returns a new promise. The block will be called only when -/// the operation is allowed. -/// NOTE: We cannot accept just a promise because the operation will be started once the -/// promise has been instantiated, so we need to have a way to instantiate the promise only -/// when the operation is good to go. The provider block is the way we use. -/// @param errorHandler A block that receives an operation error as an input and returns the -/// appropriate backoff type. `defaultErrorHandler` provides a default implementation for Firebase -/// services. -/// @return A promise that is either: -/// - a promise returned by the promise provider if no backoff is required -/// - rejected if the backoff is needed -- (FBLPromise *)applyBackoffToOperation:(FIRAppCheckBackoffOperationProvider)operationProvider - errorHandler:(FIRAppCheckBackoffErrorHandler)errorHandler; - -/// The default Firebase services error handler. It keeps track of network errors and -/// `FIRAppCheckHTTPError.HTTPResponse.statusCode.statusCode` value to return the appropriate -/// backoff type for the standard Firebase App Check backend response codes. -- (FIRAppCheckBackoffErrorHandler)defaultAppCheckProviderErrorHandler; - -@end - -/// Provides a backoff implementation. Keeps track of the operation successes and failures to either -/// create and perform the operation promise or fails with a backoff error when the backoff is -/// needed. -@interface FIRAppCheckBackoffWrapper : NSObject - -/// Initializes the wrapper with `+[FIRAppCheckBackoffWrapper currentDateProvider]`. -- (instancetype)init; - -- (instancetype)initWithDateProvider:(FIRAppCheckDateProvider)dateProvider - NS_DESIGNATED_INITIALIZER; - -/// A date provider that returns `+[NSDate date]`. -+ (FIRAppCheckDateProvider)currentDateProvider; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/Backoff/FIRAppCheckBackoffWrapper.m b/FirebaseAppCheck/Sources/Core/Backoff/FIRAppCheckBackoffWrapper.m deleted file mode 100644 index e89fe54a5d3..00000000000 --- a/FirebaseAppCheck/Sources/Core/Backoff/FIRAppCheckBackoffWrapper.m +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/Core/Backoff/FIRAppCheckBackoffWrapper.h" - -#if __has_include() -#import -#else -#import "FBLPromises.h" -#endif - -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckHTTPError.h" - -NS_ASSUME_NONNULL_BEGIN - -static NSTimeInterval const k24Hours = 24 * 60 * 60; - -/// Jitter coefficient 0.5 means that the backoff interval can be up to 50% longer. -static double const kMaxJitterCoefficient = 0.5; - -/// Maximum exponential backoff interval. -static double const kMaxExponentialBackoffInterval = 4 * 60 * 60; // 4 hours. - -/// A class representing an operation result with data required for the backoff calculation. -@interface FIRAppCheckBackoffOperationFailure : NSObject - -/// The operation finish date. -@property(nonatomic, readonly) NSDate *finishDate; - -/// The operation error. -@property(nonatomic, readonly) NSError *error; - -/// A backoff type calculated based on the error. -@property(nonatomic, readonly) FIRAppCheckBackoffType backoffType; - -/// Number of retries. Is 0 for the first attempt and incremented with each error. Is reset back to -/// 0 on success. -@property(nonatomic, readonly) NSInteger retryCount; - -/// Designated initializer. -- (instancetype)initWithFinishDate:(NSDate *)finishDate - error:(NSError *)error - backoffType:(FIRAppCheckBackoffType)backoffType - retryCount:(NSInteger)retryCount NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; - -/// Creates a new result with incremented retryCount and specified error and backoff type. -+ (instancetype)nextRetryFailureWithFailure: - (nullable FIRAppCheckBackoffOperationFailure *)previousFailure - finishDate:(NSDate *)finishDate - error:(NSError *)error - backoffType:(FIRAppCheckBackoffType)backoffType; - -@end - -@implementation FIRAppCheckBackoffOperationFailure - -- (instancetype)initWithFinishDate:(NSDate *)finishDate - error:(NSError *)error - backoffType:(FIRAppCheckBackoffType)backoffType - retryCount:(NSInteger)retryCount { - self = [super init]; - if (self) { - _finishDate = finishDate; - _error = error; - _retryCount = retryCount; - _backoffType = backoffType; - } - return self; -} - -+ (instancetype)nextRetryFailureWithFailure: - (nullable FIRAppCheckBackoffOperationFailure *)previousFailure - finishDate:(NSDate *)finishDate - error:(NSError *)error - backoffType:(FIRAppCheckBackoffType)backoffType { - NSInteger newRetryCount = previousFailure ? previousFailure.retryCount + 1 : 0; - - return [[self alloc] initWithFinishDate:finishDate - error:error - backoffType:backoffType - retryCount:newRetryCount]; -} - -@end - -@interface FIRAppCheckBackoffWrapper () - -/// Current date provider. Is used instead of `+[NSDate date]` for testability. -@property(nonatomic, readonly) FIRAppCheckDateProvider dateProvider; - -/// Last operation result. -@property(nonatomic, nullable) FIRAppCheckBackoffOperationFailure *lastFailure; - -@end - -@implementation FIRAppCheckBackoffWrapper - -- (instancetype)init { - return [self initWithDateProvider:[FIRAppCheckBackoffWrapper currentDateProvider]]; -} - -- (instancetype)initWithDateProvider:(FIRAppCheckDateProvider)dateProvider { - self = [super init]; - if (self) { - _dateProvider = [dateProvider copy]; - } - return self; -} - -+ (FIRAppCheckDateProvider)currentDateProvider { - return ^NSDate *(void) { - return [NSDate date]; - }; -} - -- (FBLPromise *)applyBackoffToOperation:(FIRAppCheckBackoffOperationProvider)operationProvider - errorHandler:(FIRAppCheckBackoffErrorHandler)errorHandler { - if (![self isNextOperationAllowed]) { - // Backing off - skip the operation and return an error straight away. - return [self promiseWithRetryDisallowedError:self.lastFailure.error]; - } - - __auto_type operationPromise = operationProvider(); - return operationPromise - .thenOn([self queue], - ^id(id result) { - @synchronized(self) { - // Reset failure on success. - self.lastFailure = nil; - } - - // Return the result. - return result; - }) - .recoverOn([self queue], ^NSError *(NSError *error) { - @synchronized(self) { - // Update the last failure to calculate the backoff. - self.lastFailure = - [FIRAppCheckBackoffOperationFailure nextRetryFailureWithFailure:self.lastFailure - finishDate:self.dateProvider() - error:error - backoffType:errorHandler(error)]; - } - - // Re-throw the error. - return error; - }); -} - -#pragma mark - Private - -- (BOOL)isNextOperationAllowed { - @synchronized(self) { - if (self.lastFailure == nil) { - // It is first attempt. Always allow it. - return YES; - } - - switch (self.lastFailure.backoffType) { - case FIRAppCheckBackoffTypeNone: - return YES; - break; - - case FIRAppCheckBackoffType1Day: - return [self hasTimeIntervalPassedSinceLastFailure:k24Hours]; - break; - - case FIRAppCheckBackoffTypeExponential: - return [self hasTimeIntervalPassedSinceLastFailure: - [self exponentialBackoffIntervalForFailure:self.lastFailure]]; - break; - } - } -} - -- (BOOL)hasTimeIntervalPassedSinceLastFailure:(NSTimeInterval)timeInterval { - NSDate *failureDate = self.lastFailure.finishDate; - // Return YES if there has not been a failure yet. - if (failureDate == nil) return YES; - - NSTimeInterval timeSinceFailure = [self.dateProvider() timeIntervalSinceDate:failureDate]; - return timeSinceFailure >= timeInterval; -} - -- (FBLPromise *)promiseWithRetryDisallowedError:(NSError *)error { - NSString *reason = - [NSString stringWithFormat:@"Too many attempts. Underlying error: %@", - error.localizedDescription ?: error.localizedFailureReason]; - NSError *retryDisallowedError = [FIRAppCheckErrorUtil errorWithFailureReason:reason]; - FBLPromise *rejectedPromise = [FBLPromise pendingPromise]; - [rejectedPromise reject:retryDisallowedError]; - return rejectedPromise; -} - -- (dispatch_queue_t)queue { - return dispatch_get_global_queue(QOS_CLASS_UTILITY, 0); -} - -#pragma mark - Exponential backoff - -/// @return Exponential backoff interval with jitter. Jitter is needed to avoid all clients to retry -/// at the same time after e.g. a backend outage. -- (NSTimeInterval)exponentialBackoffIntervalForFailure: - (FIRAppCheckBackoffOperationFailure *)failure { - // Base exponential backoff interval. - NSTimeInterval baseBackoff = pow(2, failure.retryCount); - - // Get a random number from 0 to 1. - double maxRandom = 1000; - double randomNumber = (double)arc4random_uniform((int32_t)maxRandom) / maxRandom; - - // A number from 1 to 1 + kMaxJitterCoefficient, e.g. from 1 to 1.5. Indicates how much the - // backoff can be extended. - double jitterCoefficient = 1 + randomNumber * kMaxJitterCoefficient; - - // Exponential backoff interval with jitter. - NSTimeInterval backoffIntervalWithJitter = baseBackoff * jitterCoefficient; - - // Apply limit to the backoff interval. - return MIN(backoffIntervalWithJitter, kMaxExponentialBackoffInterval); -} - -#pragma mark - Error handling - -- (FIRAppCheckBackoffErrorHandler)defaultAppCheckProviderErrorHandler { - return ^FIRAppCheckBackoffType(NSError *error) { - FIRAppCheckHTTPError *HTTPError = - [error isKindOfClass:[FIRAppCheckHTTPError class]] ? (FIRAppCheckHTTPError *)error : nil; - - if (HTTPError == nil) { - // No backoff for attestation providers for non-backend (e.g. network) errors. - return FIRAppCheckBackoffTypeNone; - } - - NSInteger statusCode = HTTPError.HTTPResponse.statusCode; - - if (statusCode < 400) { - // No backoff for codes before 400. - return FIRAppCheckBackoffTypeNone; - } - - if (statusCode == 400 || statusCode == 404) { - // Firebase project misconfiguration. It will unlikely be fixed soon and often requires - // another version of the app. Try again in 1 day. - return FIRAppCheckBackoffType1Day; - } - - if (statusCode == 403) { - // Project may have been soft-deleted accidentally. There is a chance of timely recovery, so - // try again later. - return FIRAppCheckBackoffTypeExponential; - } - - if (statusCode == 429) { - // Too many requests. Try again in a while. - return FIRAppCheckBackoffTypeExponential; - } - - if (statusCode == 503) { - // Server is overloaded. Try again in a while. - return FIRAppCheckBackoffTypeExponential; - } - - // For all other server error cases default to the exponential backoff. - return FIRAppCheckBackoffTypeExponential; - }; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h b/FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h index 00cf891c036..b5d54574d10 100644 --- a/FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h +++ b/FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h @@ -16,53 +16,14 @@ #import -@class FIRAppCheckHTTPError; +#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckErrors.h" NS_ASSUME_NONNULL_BEGIN -void FIRAppCheckSetErrorToPointer(NSError *error, NSError **pointer); - @interface FIRAppCheckErrorUtil : NSObject + (NSError *)publicDomainErrorWithError:(NSError *)error; -// MARK: - Internal errors - -+ (NSError *)cachedTokenNotFound; - -+ (NSError *)cachedTokenExpired; - -+ (NSError *)keychainErrorWithError:(NSError *)error; - -+ (FIRAppCheckHTTPError *)APIErrorWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse - data:(nullable NSData *)data; - -+ (NSError *)APIErrorWithNetworkError:(NSError *)networkError; - -+ (NSError *)appCheckTokenResponseErrorWithMissingField:(NSString *)fieldName; - -+ (NSError *)appAttestAttestationResponseErrorWithMissingField:(NSString *)fieldName; - -+ (NSError *)JSONSerializationError:(NSError *)error; - -+ (NSError *)errorWithFailureReason:(NSString *)failureReason; - -+ (NSError *)unsupportedAttestationProvider:(NSString *)providerName; - -+ (NSError *)appAttestKeyIDNotFound; - -// MARK: - App Attest Errors - -+ (NSError *)appAttestGenerateKeyFailedWithError:(NSError *)error; - -+ (NSError *)appAttestAttestKeyFailedWithError:(NSError *)error - keyId:(NSString *)keyId - clientDataHash:(NSData *)clientDataHash; - -+ (NSError *)appAttestGenerateAssertionFailedWithError:(NSError *)error - keyId:(NSString *)keyId - clientDataHash:(NSData *)clientDataHash; - @end NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.m b/FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.m index bd65bb0869f..67ad05133ae 100644 --- a/FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.m +++ b/FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.m @@ -16,145 +16,47 @@ #import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" -#import - -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckHTTPError.h" -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckErrors.h" +#import @implementation FIRAppCheckErrorUtil + (NSError *)publicDomainErrorWithError:(NSError *)error { - if ([error.domain isEqualToString:FIRAppCheckErrorDomain]) { + if ([error.domain isEqualToString:GACAppCheckErrorDomain]) { + return [self publicDomainErrorWithGACError:error]; + } else if ([error.domain isEqualToString:FIRAppCheckErrorDomain]) { return error; } return [self unknownErrorWithError:error]; } -#pragma mark - Internal errors - -+ (NSError *)cachedTokenNotFound { - NSString *failureReason = [NSString stringWithFormat:@"Cached token not found."]; - return [self appCheckErrorWithCode:FIRAppCheckErrorCodeUnknown - failureReason:failureReason - underlyingError:nil]; -} - -+ (NSError *)cachedTokenExpired { - NSString *failureReason = [NSString stringWithFormat:@"Cached token expired."]; - return [self appCheckErrorWithCode:FIRAppCheckErrorCodeUnknown - failureReason:failureReason - underlyingError:nil]; -} - -+ (NSError *)keychainErrorWithError:(NSError *)error { - if ([error.domain isEqualToString:kGULKeychainUtilsErrorDomain]) { - NSString *failureReason = [NSString stringWithFormat:@"Keychain access error."]; - return [self appCheckErrorWithCode:FIRAppCheckErrorCodeKeychain - failureReason:failureReason - underlyingError:error]; +/// Converts an App Check Core error (`GACAppCheckErrorDomain`) to a public error +/// (`FIRAppCheckErrorDomain`). ++ (NSError *)publicDomainErrorWithGACError:(NSError *)appCheckCoreError { + FIRAppCheckErrorCode errorCode; + switch ((GACAppCheckErrorCode)appCheckCoreError.code) { + case GACAppCheckErrorCodeUnknown: + errorCode = FIRAppCheckErrorCodeUnknown; + break; + case GACAppCheckErrorCodeServerUnreachable: + errorCode = FIRAppCheckErrorCodeServerUnreachable; + break; + case GACAppCheckErrorCodeInvalidConfiguration: + errorCode = FIRAppCheckErrorCodeInvalidConfiguration; + break; + case GACAppCheckErrorCodeKeychain: + errorCode = FIRAppCheckErrorCodeKeychain; + break; + case GACAppCheckErrorCodeUnsupported: + errorCode = FIRAppCheckErrorCodeUnsupported; + break; + default: + return [self unknownErrorWithError:appCheckCoreError]; } - return [self unknownErrorWithError:error]; -} - -+ (FIRAppCheckHTTPError *)APIErrorWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse - data:(nullable NSData *)data { - return [[FIRAppCheckHTTPError alloc] initWithHTTPResponse:HTTPResponse data:data]; -} - -+ (NSError *)APIErrorWithNetworkError:(NSError *)networkError { - NSString *failureReason = [NSString stringWithFormat:@"API request error."]; - return [self appCheckErrorWithCode:FIRAppCheckErrorCodeServerUnreachable - failureReason:failureReason - underlyingError:networkError]; -} - -+ (NSError *)appCheckTokenResponseErrorWithMissingField:(NSString *)fieldName { - NSString *failureReason = [NSString - stringWithFormat:@"Unexpected app check token response format. Field `%@` is missing.", - fieldName]; - return [self appCheckErrorWithCode:FIRAppCheckErrorCodeUnknown - failureReason:failureReason - underlyingError:nil]; -} - -+ (NSError *)appAttestAttestationResponseErrorWithMissingField:(NSString *)fieldName { - NSString *failureReason = - [NSString stringWithFormat:@"Unexpected attestation response format. Field `%@` is missing.", - fieldName]; - return [self appCheckErrorWithCode:FIRAppCheckErrorCodeUnknown - failureReason:failureReason - underlyingError:nil]; -} - -+ (NSError *)JSONSerializationError:(NSError *)error { - NSString *failureReason = [NSString stringWithFormat:@"JSON serialization error."]; - return [self appCheckErrorWithCode:FIRAppCheckErrorCodeUnknown - failureReason:failureReason - underlyingError:error]; -} - -+ (NSError *)unsupportedAttestationProvider:(NSString *)providerName { - NSString *failureReason = [NSString - stringWithFormat: - @"The attestation provider %@ is not supported on current platform and OS version.", - providerName]; - return [self appCheckErrorWithCode:FIRAppCheckErrorCodeUnsupported - failureReason:failureReason - underlyingError:nil]; -} - -+ (NSError *)appAttestKeyIDNotFound { - NSString *failureReason = [NSString stringWithFormat:@"App attest key ID not found."]; - return [self appCheckErrorWithCode:FIRAppCheckErrorCodeUnknown - failureReason:failureReason - underlyingError:nil]; -} - -+ (NSError *)errorWithFailureReason:(NSString *)failureReason { - return [self appCheckErrorWithCode:FIRAppCheckErrorCodeUnknown - failureReason:failureReason - underlyingError:nil]; -} - -#pragma mark - App Attest - -+ (NSError *)appAttestGenerateKeyFailedWithError:(NSError *)error { - NSString *failureReason = @"Failed to generate a new cryptographic key for use with the App " - @"Attest service (`generateKeyWithCompletionHandler:`)."; - // TODO(#11967): Add a new error code for this case (e.g., FIRAppCheckAppAttestGenerateKeyFailed). - return [self appCheckErrorWithCode:FIRAppCheckErrorCodeUnknown - failureReason:failureReason - underlyingError:error]; -} - -+ (NSError *)appAttestAttestKeyFailedWithError:(NSError *)error - keyId:(NSString *)keyId - clientDataHash:(NSData *)clientDataHash { - NSString *failureReason = - [NSString stringWithFormat:@"Failed to attest the validity of the generated cryptographic " - @"key (`attestKey:clientDataHash:completionHandler:`); " - @"keyId.length = %lu, clientDataHash.length = %lu", - (unsigned long)keyId.length, (unsigned long)clientDataHash.length]; - // TODO(#11967): Add a new error code for this case (e.g., FIRAppCheckAppAttestAttestKeyFailed). - return [self appCheckErrorWithCode:FIRAppCheckErrorCodeUnknown - failureReason:failureReason - underlyingError:error]; -} - -+ (NSError *)appAttestGenerateAssertionFailedWithError:(NSError *)error - keyId:(NSString *)keyId - clientDataHash:(NSData *)clientDataHash { - NSString *failureReason = [NSString - stringWithFormat:@"Failed to create a block of data that demonstrates the legitimacy of the " - @"app instance (`generateAssertion:clientDataHash:completionHandler:`); " - @"keyId.length = %lu, clientDataHash.length = %lu.", - (unsigned long)keyId.length, (unsigned long)clientDataHash.length]; - // TODO(#11967): Add error code for this case (e.g., FIRAppCheckAppAttestGenerateAssertionFailed). - return [self appCheckErrorWithCode:FIRAppCheckErrorCodeUnknown - failureReason:failureReason - underlyingError:error]; + return [NSError errorWithDomain:FIRAppCheckErrorDomain + code:errorCode + userInfo:appCheckCoreError.userInfo]; } #pragma mark - Helpers @@ -177,9 +79,3 @@ + (NSError *)appCheckErrorWithCode:(FIRAppCheckErrorCode)code } @end - -void FIRAppCheckSetErrorToPointer(NSError *error, NSError **pointer) { - if (pointer != NULL) { - *pointer = error; - } -} diff --git a/FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckHTTPError.h b/FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckHTTPError.h deleted file mode 100644 index b224b69ff34..00000000000 --- a/FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckHTTPError.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface FIRAppCheckHTTPError : NSError - -@property(nonatomic, readonly) NSHTTPURLResponse *HTTPResponse; -@property(nonatomic, readonly, nonnull) NSData *data; - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse data:(nullable NSData *)data; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckHTTPError.m b/FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckHTTPError.m deleted file mode 100644 index 33bddbc149d..00000000000 --- a/FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckHTTPError.m +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckHTTPError.h" - -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckErrors.h" - -@implementation FIRAppCheckHTTPError - -- (instancetype)initWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse - data:(nullable NSData *)data { - NSDictionary *userInfo = [[self class] userInfoWithHTTPResponse:HTTPResponse data:data]; - self = [super initWithDomain:FIRAppCheckErrorDomain - code:FIRAppCheckErrorCodeUnknown - userInfo:userInfo]; - if (self) { - _HTTPResponse = HTTPResponse; - _data = data; - } - return self; -} - -+ (NSDictionary *)userInfoWithHTTPResponse:(NSHTTPURLResponse *)HTTPResponse - data:(nullable NSData *)data { - NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - NSString *failureReason = - [NSString stringWithFormat:@"The server responded with an error: \n - URL: %@ \n - HTTP " - @"status code: %ld \n - Response body: %@", - HTTPResponse.URL, (long)HTTPResponse.statusCode, responseString]; - return @{NSLocalizedFailureReasonErrorKey : failureReason}; -} - -#pragma mark - NSCopying - -- (id)copyWithZone:(NSZone *)zone { - return [[[self class] alloc] initWithHTTPResponse:self.HTTPResponse data:self.data]; -} - -#pragma mark - NSSecureCoding - -- (nullable instancetype)initWithCoder:(NSCoder *)coder { - NSHTTPURLResponse *HTTPResponse = [coder decodeObjectOfClass:[NSHTTPURLResponse class] - forKey:@"HTTPResponse"]; - if (!HTTPResponse) { - return nil; - } - NSData *data = [coder decodeObjectOfClass:[NSData class] forKey:@"data"]; - - return [self initWithHTTPResponse:HTTPResponse data:data]; -} - -- (void)encodeWithCoder:(NSCoder *)coder { - [coder encodeObject:self.HTTPResponse forKey:@"HTTPResponse"]; - [coder encodeObject:self.data forKey:@"data"]; -} - -+ (BOOL)supportsSecureCoding { - return YES; -} - -@end diff --git a/FirebaseAppCheck/Sources/AppAttestProvider/DCAppAttestService+FIRAppAttestService.h b/FirebaseAppCheck/Sources/Core/FIRApp+AppCheck.h similarity index 64% rename from FirebaseAppCheck/Sources/AppAttestProvider/DCAppAttestService+FIRAppAttestService.h rename to FirebaseAppCheck/Sources/Core/FIRApp+AppCheck.h index be0cbddb67d..4a6761fa4d2 100644 --- a/FirebaseAppCheck/Sources/AppAttestProvider/DCAppAttestService+FIRAppAttestService.h +++ b/FirebaseAppCheck/Sources/Core/FIRApp+AppCheck.h @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,16 +14,15 @@ * limitations under the License. */ -#import - -#import "FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestService.h" - -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckAvailability.h" +#import "FirebaseCore/Extension/FirebaseCoreInternal.h" NS_ASSUME_NONNULL_BEGIN -FIR_APP_ATTEST_PROVIDER_AVAILABILITY -@interface DCAppAttestService (FIRAppAttestService) +@interface FIRApp (AppCheck) + +/// The resource name for the Firebase App in the format "projects/{project_id}/apps/{app_id}". +/// See https://google.aip.dev/122 for more details about resource names. +@property(nonatomic, readonly, copy) NSString *resourceName; @end diff --git a/FirebaseAppCheck/Sources/AppAttestProvider/Errors/FIRAppAttestRejectionError.h b/FirebaseAppCheck/Sources/Core/FIRApp+AppCheck.m similarity index 68% rename from FirebaseAppCheck/Sources/AppAttestProvider/Errors/FIRAppAttestRejectionError.h rename to FirebaseAppCheck/Sources/Core/FIRApp+AppCheck.m index 3718f1c8718..6777b4f0bca 100644 --- a/FirebaseAppCheck/Sources/AppAttestProvider/Errors/FIRAppAttestRejectionError.h +++ b/FirebaseAppCheck/Sources/Core/FIRApp+AppCheck.m @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,13 @@ * limitations under the License. */ -#import +#import "FirebaseAppCheck/Sources/Core/FIRApp+AppCheck.h" -NS_ASSUME_NONNULL_BEGIN +@implementation FIRApp (AppCheck) -@interface FIRAppAttestRejectionError : NSError - -- (instancetype)init; +- (NSString *)resourceName { + return [NSString + stringWithFormat:@"projects/%@/apps/%@", self.options.projectID, self.options.googleAppID]; +} @end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/FIRAppCheck.m b/FirebaseAppCheck/Sources/Core/FIRAppCheck.m index 7c1b4f5cbe7..f1ccdab2b6d 100644 --- a/FirebaseAppCheck/Sources/Core/FIRAppCheck.m +++ b/FirebaseAppCheck/Sources/Core/FIRAppCheck.m @@ -16,14 +16,9 @@ #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheck.h" +#import #import -#if __has_include() -#import -#else -#import "FBLPromises.h" -#endif - #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckErrors.h" #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckProvider.h" #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckProviderFactory.h" @@ -34,9 +29,7 @@ #import "FirebaseAppCheck/Sources/Core/FIRAppCheckSettings.h" #import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h" #import "FirebaseAppCheck/Sources/Core/FIRAppCheckTokenResult.h" -#import "FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStorage.h" -#import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefreshResult.h" -#import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefresher.h" +#import "FirebaseAppCheck/Sources/Core/FIRInternalAppCheckProvider.h" NS_ASSUME_NONNULL_BEGIN @@ -55,22 +48,16 @@ static id _providerFactory; -static const NSTimeInterval kTokenExpirationThreshold = 5 * 60; // 5 min. - static NSString *const kDummyFACTokenValue = @"eyJlcnJvciI6IlVOS05PV05fRVJST1IifQ=="; -@interface FIRAppCheck () +@interface FIRAppCheck () @property(class, nullable) id providerFactory; @property(nonatomic, readonly) NSString *appName; -@property(nonatomic, readonly) id appCheckProvider; -@property(nonatomic, readonly) id storage; @property(nonatomic, readonly) NSNotificationCenter *notificationCenter; -@property(nonatomic, readonly) id settings; +@property(nonatomic, readonly) FIRAppCheckSettings *settings; +@property(nonatomic, readonly) GACAppCheck *appCheckCore; -@property(nonatomic, readonly, nullable) id tokenRefresher; - -@property(nonatomic, nullable) FBLPromise *ongoingRetrieveOrRefreshTokenPromise; @end @implementation FIRAppCheck @@ -78,6 +65,9 @@ @implementation FIRAppCheck #pragma mark - Internal - (nullable instancetype)initWithApp:(FIRApp *)app { + // Set the App Check Core logging level to the current equivalent Firebase logging level. + GACAppCheckLogger.logLevel = FIRGetGACAppCheckLogLevel(); + id providerFactory = [FIRAppCheck providerFactory]; if (providerFactory == nil) { @@ -98,47 +88,40 @@ - (nullable instancetype)initWithApp:(FIRApp *)app { return nil; } + NSString *serviceName = [self serviceNameForApp:app]; + NSString *resourceName = [self resourceNameForApp:app]; + id appCheckCoreProvider = + [[FIRInternalAppCheckProvider alloc] initWithAppCheckProvider:appCheckProvider]; FIRAppCheckSettings *settings = [[FIRAppCheckSettings alloc] initWithApp:app userDefault:[NSUserDefaults standardUserDefaults] mainBundle:[NSBundle mainBundle]]; - FIRAppCheckTokenRefreshResult *refreshResult = - [[FIRAppCheckTokenRefreshResult alloc] initWithStatusNever]; - FIRAppCheckTokenRefresher *tokenRefresher = - [[FIRAppCheckTokenRefresher alloc] initWithRefreshResult:refreshResult settings:settings]; - FIRAppCheckStorage *storage = [[FIRAppCheckStorage alloc] initWithAppName:app.name - appID:app.options.googleAppID - accessGroup:app.options.appGroupID]; + GACAppCheck *appCheckCore = [[GACAppCheck alloc] initWithServiceName:serviceName + resourceName:resourceName + appCheckProvider:appCheckCoreProvider + settings:settings + tokenDelegate:self + keychainAccessGroup:app.options.appGroupID]; return [self initWithAppName:app.name + appCheckCore:appCheckCore appCheckProvider:appCheckProvider - storage:storage - tokenRefresher:tokenRefresher notificationCenter:NSNotificationCenter.defaultCenter settings:settings]; } - (instancetype)initWithAppName:(NSString *)appName + appCheckCore:(GACAppCheck *)appCheckCore appCheckProvider:(id)appCheckProvider - storage:(id)storage - tokenRefresher:(id)tokenRefresher notificationCenter:(NSNotificationCenter *)notificationCenter - settings:(id)settings { + settings:(FIRAppCheckSettings *)settings { self = [super init]; if (self) { _appName = appName; - _appCheckProvider = appCheckProvider; - _storage = storage; - _tokenRefresher = tokenRefresher; + _appCheckCore = appCheckCore; _notificationCenter = notificationCenter; _settings = settings; - - __auto_type __weak weakSelf = self; - tokenRefresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) { - __auto_type strongSelf = weakSelf; - [strongSelf periodicTokenRefreshWithCompletion:completion]; - }; } return self; } @@ -166,26 +149,28 @@ + (nullable instancetype)appCheckWithApp:(FIRApp *)firebaseApp { - (void)tokenForcingRefresh:(BOOL)forcingRefresh completion:(void (^)(FIRAppCheckToken *_Nullable token, NSError *_Nullable error))handler { - [self retrieveOrRefreshTokenForcingRefresh:forcingRefresh] - .then(^id _Nullable(FIRAppCheckToken *token) { - handler(token, nil); - return token; - }) - .catch(^(NSError *_Nonnull error) { - handler(nil, [FIRAppCheckErrorUtil publicDomainErrorWithError:error]); - }); + [self.appCheckCore + tokenForcingRefresh:forcingRefresh + completion:^(GACAppCheckTokenResult *result) { + if (result.error) { + handler(nil, [FIRAppCheckErrorUtil publicDomainErrorWithError:result.error]); + return; + } + + handler([[FIRAppCheckToken alloc] initWithInternalToken:result.token], nil); + }]; } - (void)limitedUseTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable token, NSError *_Nullable error))handler { - [self limitedUseToken] - .then(^id _Nullable(FIRAppCheckToken *token) { - handler(token, nil); - return token; - }) - .catch(^(NSError *_Nonnull error) { - handler(nil, [FIRAppCheckErrorUtil publicDomainErrorWithError:error]); - }); + [self.appCheckCore limitedUseTokenWithCompletion:^(GACAppCheckTokenResult *result) { + if (result.error) { + handler(nil, [FIRAppCheckErrorUtil publicDomainErrorWithError:result.error]); + return; + } + + handler([[FIRAppCheckToken alloc] initWithInternalToken:result.token], nil); + }]; } + (void)setAppCheckProviderFactory:(nullable id)factory { @@ -218,33 +203,27 @@ + (void)setProviderFactory:(nullable id)providerFact - (void)getTokenForcingRefresh:(BOOL)forcingRefresh completion:(FIRAppCheckTokenHandlerInterop)handler { - [self retrieveOrRefreshTokenForcingRefresh:forcingRefresh] - .then(^id _Nullable(FIRAppCheckToken *token) { - FIRAppCheckTokenResult *result = [[FIRAppCheckTokenResult alloc] initWithToken:token.token - error:nil]; - handler(result); - return result; - }) - .catch(^(NSError *_Nonnull error) { - FIRAppCheckTokenResult *result = - [[FIRAppCheckTokenResult alloc] initWithToken:kDummyFACTokenValue error:error]; - handler(result); - }); + [self.appCheckCore + tokenForcingRefresh:forcingRefresh + completion:^(GACAppCheckTokenResult *internalResult) { + FIRAppCheckToken *token = + [[FIRAppCheckToken alloc] initWithInternalToken:internalResult.token]; + FIRAppCheckTokenResult *tokenResult = + [[FIRAppCheckTokenResult alloc] initWithToken:token.token + error:internalResult.error]; + + handler(tokenResult); + }]; } - (void)getLimitedUseTokenWithCompletion:(FIRAppCheckTokenHandlerInterop)handler { - [self limitedUseToken] - .then(^id _Nullable(FIRAppCheckToken *token) { - FIRAppCheckTokenResult *result = [[FIRAppCheckTokenResult alloc] initWithToken:token.token - error:nil]; - handler(result); - return result; - }) - .catch(^(NSError *_Nonnull error) { - FIRAppCheckTokenResult *result = - [[FIRAppCheckTokenResult alloc] initWithToken:kDummyFACTokenValue error:error]; - handler(result); - }); + [self.appCheckCore limitedUseTokenWithCompletion:^(GACAppCheckTokenResult *internalResult) { + FIRAppCheckToken *token = [[FIRAppCheckToken alloc] initWithInternalToken:internalResult.token]; + FIRAppCheckTokenResult *tokenResult = + [[FIRAppCheckTokenResult alloc] initWithToken:token.token error:internalResult.error]; + + handler(tokenResult); + }]; } - (nonnull NSString *)tokenDidChangeNotificationName { @@ -259,105 +238,12 @@ - (nonnull NSString *)notificationTokenKey { return kFIRAppCheckTokenNotificationKey; } -#pragma mark - FAA token cache - -- (FBLPromise *)retrieveOrRefreshTokenForcingRefresh:(BOOL)forcingRefresh { - return [FBLPromise do:^id _Nullable { - if (self.ongoingRetrieveOrRefreshTokenPromise == nil) { - // Kick off a new operation only when there is not an ongoing one. - self.ongoingRetrieveOrRefreshTokenPromise = - [self createRetrieveOrRefreshTokenPromiseForcingRefresh:forcingRefresh] - - // Release the ongoing operation promise on completion. - .then(^FIRAppCheckToken *(FIRAppCheckToken *token) { - self.ongoingRetrieveOrRefreshTokenPromise = nil; - return token; - }) - .recover(^NSError *(NSError *error) { - self.ongoingRetrieveOrRefreshTokenPromise = nil; - return error; - }); - } - return self.ongoingRetrieveOrRefreshTokenPromise; - }]; -} - -- (FBLPromise *)createRetrieveOrRefreshTokenPromiseForcingRefresh: - (BOOL)forcingRefresh { - return [self getCachedValidTokenForcingRefresh:forcingRefresh].recover( - ^id _Nullable(NSError *_Nonnull error) { - return [self refreshToken]; - }); -} - -- (FBLPromise *)getCachedValidTokenForcingRefresh:(BOOL)forcingRefresh { - if (forcingRefresh) { - FBLPromise *rejectedPromise = [FBLPromise pendingPromise]; - [rejectedPromise reject:[FIRAppCheckErrorUtil cachedTokenNotFound]]; - return rejectedPromise; - } - - return [self.storage getToken].then(^id(FIRAppCheckToken *_Nullable token) { - if (token == nil) { - return [FIRAppCheckErrorUtil cachedTokenNotFound]; - } - - BOOL isTokenExpiredOrExpiresSoon = - [token.expirationDate timeIntervalSinceNow] < kTokenExpirationThreshold; - if (isTokenExpiredOrExpiresSoon) { - return [FIRAppCheckErrorUtil cachedTokenExpired]; - } - - return token; - }); -} - -- (FBLPromise *)refreshToken { - return [FBLPromise - wrapObjectOrErrorCompletion:^(FBLPromiseObjectOrErrorCompletion _Nonnull handler) { - [self.appCheckProvider getTokenWithCompletion:handler]; - }] - .then(^id _Nullable(FIRAppCheckToken *_Nullable token) { - return [self.storage setToken:token]; - }) - .then(^id _Nullable(FIRAppCheckToken *_Nullable token) { - // TODO: Make sure the self.tokenRefresher is updated only once. Currently the timer will be - // updated twice in the case when the refresh triggered by self.tokenRefresher, but it - // should be fine for now as it is a relatively cheap operation. - __auto_type refreshResult = [[FIRAppCheckTokenRefreshResult alloc] - initWithStatusSuccessAndExpirationDate:token.expirationDate - receivedAtDate:token.receivedAtDate]; - [self.tokenRefresher updateWithRefreshResult:refreshResult]; - [self postTokenUpdateNotificationWithToken:token]; - return token; - }); -} - -- (FBLPromise *)limitedUseToken { - return - [FBLPromise wrapObjectOrErrorCompletion:^( - FBLPromiseObjectOrErrorCompletion _Nonnull handler) { - [self.appCheckProvider getTokenWithCompletion:handler]; - }].then(^id _Nullable(FIRAppCheckToken *_Nullable token) { - return token; - }); -} +#pragma mark - GACAppCheckTokenDelegate -#pragma mark - Token auto refresh - -- (void)periodicTokenRefreshWithCompletion:(FIRAppCheckTokenRefreshCompletion)completion { - [self retrieveOrRefreshTokenForcingRefresh:NO] - .then(^id _Nullable(FIRAppCheckToken *_Nullable token) { - __auto_type refreshResult = [[FIRAppCheckTokenRefreshResult alloc] - initWithStatusSuccessAndExpirationDate:token.expirationDate - receivedAtDate:token.receivedAtDate]; - completion(refreshResult); - return nil; - }) - .catch(^(NSError *error) { - __auto_type refreshResult = [[FIRAppCheckTokenRefreshResult alloc] initWithStatusFailure]; - completion(refreshResult); - }); +- (void)tokenDidUpdate:(nonnull GACAppCheckToken *)token + serviceName:(nonnull NSString *)serviceName { + FIRAppCheckToken *appCheckToken = [[FIRAppCheckToken alloc] initWithInternalToken:token]; + [self postTokenUpdateNotificationWithToken:appCheckToken]; } #pragma mark - Token update notification @@ -371,6 +257,17 @@ - (void)postTokenUpdateNotificationWithToken:(FIRAppCheckToken *)token { }]; } +#pragma mark - Helpers + +- (NSString *)serviceNameForApp:(FIRApp *)app { + return [NSString stringWithFormat:@"FirebaseApp:%@", app.name]; +} + +- (NSString *)resourceNameForApp:(FIRApp *)app { + return [NSString + stringWithFormat:@"projects/%@/apps/%@", app.options.projectID, app.options.googleAppID]; +} + @end NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h b/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h index 28ee60d7c67..3f2fce59da5 100644 --- a/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h +++ b/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h @@ -16,22 +16,18 @@ #import +#import + #import "FirebaseCore/Extension/FirebaseCoreInternal.h" extern FIRLoggerService kFIRLoggerAppCheck; -FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageCodeUnknown; - // FIRAppCheck.m FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageCodeProviderFactoryIsMissing; FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageCodeProviderIsMissing; -// FIRAppCheckAPIService.m -FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageCodeUnexpectedHTTPCode; - // FIRAppCheckDebugProvider.m FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageDebugProviderIncompleteFIROptions; -FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageDebugProviderFailedExchange; // FIRAppCheckDebugProviderFactory.m FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageCodeDebugToken; @@ -39,8 +35,6 @@ FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageCodeDebugToken; // FIRDeviceCheckProvider.m FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageDeviceCheckProviderIncompleteFIROptions; -// FIRAppAttestProvider.m -FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageCodeAppAttestNotSupported; -FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageCodeAttestationRejected; - void FIRAppCheckDebugLog(NSString *messageCode, NSString *message, ...); + +GACAppCheckLogLevel FIRGetGACAppCheckLogLevel(void); diff --git a/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.m b/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.m index 3e7f566a45e..3c001b22326 100644 --- a/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.m +++ b/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.m @@ -22,18 +22,12 @@ FIRLoggerService kFIRLoggerAppCheck = @"[FirebaseAppCheck]"; -NSString *const kFIRLoggerAppCheckMessageCodeUnknown = @"I-FAA001001"; - // FIRAppCheck.m NSString *const kFIRLoggerAppCheckMessageCodeProviderFactoryIsMissing = @"I-FAA002001"; NSString *const kFIRLoggerAppCheckMessageCodeProviderIsMissing = @"I-FAA002002"; -// FIRAppCheckAPIService.m -NSString *const kFIRLoggerAppCheckMessageCodeUnexpectedHTTPCode = @"I-FAA003001"; - // FIRAppCheckDebugProvider.m NSString *const kFIRLoggerAppCheckMessageDebugProviderIncompleteFIROptions = @"I-FAA004001"; -NSString *const kFIRLoggerAppCheckMessageDebugProviderFailedExchange = @"I-FAA004002"; // FIRAppCheckDebugProviderFactory.m NSString *const kFIRLoggerAppCheckMessageCodeDebugToken = @"I-FAA005001"; @@ -41,11 +35,8 @@ // FIRDeviceCheckProvider.m NSString *const kFIRLoggerAppCheckMessageDeviceCheckProviderIncompleteFIROptions = @"I-FAA006001"; -// FIRAppAttestProvider.m -NSString *const kFIRLoggerAppCheckMessageCodeAppAttestNotSupported = @"I-FAA007001"; -NSString *const kFIRLoggerAppCheckMessageCodeAttestationRejected = @"I-FAA007002"; - #pragma mark - Log functions + void FIRAppCheckDebugLog(NSString *messageCode, NSString *message, ...) { va_list args_ptr; va_start(args_ptr, message); @@ -53,4 +44,21 @@ void FIRAppCheckDebugLog(NSString *messageCode, NSString *message, ...) { va_end(args_ptr); } +#pragma mark - Helper functions + +GACAppCheckLogLevel FIRGetGACAppCheckLogLevel(void) { + FIRLoggerLevel loggerLevel = FIRGetLoggerLevel(); + switch (loggerLevel) { + case FIRLoggerLevelError: + return GACAppCheckLogLevelError; + case FIRLoggerLevelWarning: + case FIRLoggerLevelNotice: + return GACAppCheckLogLevelWarning; + case FIRLoggerLevelInfo: + return GACAppCheckLogLevelInfo; + case FIRLoggerLevelDebug: + return GACAppCheckLogLevelDebug; + } +} + NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/FIRAppCheckSettings.h b/FirebaseAppCheck/Sources/Core/FIRAppCheckSettings.h index bd264fb58bb..1a6ab960145 100644 --- a/FirebaseAppCheck/Sources/Core/FIRAppCheckSettings.h +++ b/FirebaseAppCheck/Sources/Core/FIRAppCheckSettings.h @@ -16,6 +16,8 @@ #import +#import + @class FIRApp; NS_ASSUME_NONNULL_BEGIN @@ -23,20 +25,17 @@ NS_ASSUME_NONNULL_BEGIN FOUNDATION_EXPORT NSString *const kFIRAppCheckTokenAutoRefreshEnabledUserDefaultsPrefix; FOUNDATION_EXPORT NSString *const kFIRAppCheckTokenAutoRefreshEnabledInfoPlistKey; -/// A collection of Firebase app check wide settings and parameters. -@protocol FIRAppCheckSettingsProtocol +/// Handles storing and updating the Firebase app check wide settings and parameters. +@interface FIRAppCheckSettings : GACAppCheckSettings /// If Firebase app check token auto-refresh is allowed. @property(nonatomic, assign) BOOL isTokenAutoRefreshEnabled; -@end - -/// Handles storing and updating the Firebase app check wide settings and parameters. -@interface FIRAppCheckSettings : NSObject - - (instancetype)initWithApp:(FIRApp *)firebaseApp userDefault:(NSUserDefaults *)userDefaults - mainBundle:(NSBundle *)mainBundle; + mainBundle:(NSBundle *)mainBundle NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; @end diff --git a/FirebaseAppCheck/Sources/Core/FIRAppCheckSettings.m b/FirebaseAppCheck/Sources/Core/FIRAppCheckSettings.m index 32b22eaef0b..178dba1a063 100644 --- a/FirebaseAppCheck/Sources/Core/FIRAppCheckSettings.m +++ b/FirebaseAppCheck/Sources/Core/FIRAppCheckSettings.m @@ -31,14 +31,12 @@ @interface FIRAppCheckSettings () @property(nonatomic, readonly) NSUserDefaults *userDefaults; @property(nonatomic, readonly) NSBundle *mainBundle; @property(nonatomic, readonly) NSString *userDefaultKey; -@property(nonatomic, nullable) NSNumber *isTokenAutoRefreshEnabledNumber; +@property(nonatomic, assign) BOOL isTokenAutoRefreshConfigured; @end @implementation FIRAppCheckSettings -@dynamic isTokenAutoRefreshEnabled; - - (instancetype)initWithApp:(FIRApp *)firebaseApp userDefault:(NSUserDefaults *)userDefaults mainBundle:(NSBundle *)mainBundle { @@ -49,16 +47,18 @@ - (instancetype)initWithApp:(FIRApp *)firebaseApp _mainBundle = mainBundle; _userDefaultKey = [kFIRAppCheckTokenAutoRefreshEnabledUserDefaultsPrefix stringByAppendingString:firebaseApp.name]; + [super setIsTokenAutoRefreshEnabled:NO]; + _isTokenAutoRefreshConfigured = NO; } return self; } - (BOOL)isTokenAutoRefreshEnabled { @synchronized(self) { - if (self.isTokenAutoRefreshEnabledNumber != nil) { + if (self.isTokenAutoRefreshConfigured) { // Return value form the in-memory cache to avoid accessing the user default or bundle when // not required. - return self.isTokenAutoRefreshEnabledNumber.boolValue; + return [super isTokenAutoRefreshEnabled]; } // Check user defaults for a value set during the previous launch. @@ -73,9 +73,10 @@ - (BOOL)isTokenAutoRefreshEnabled { if (isTokenAutoRefreshEnabledNumber != nil) { // Update in-memory cache. - self.isTokenAutoRefreshEnabledNumber = isTokenAutoRefreshEnabledNumber; + self.isTokenAutoRefreshConfigured = YES; + self.isTokenAutoRefreshEnabled = isTokenAutoRefreshEnabledNumber.boolValue; // Return the value. - return isTokenAutoRefreshEnabledNumber.boolValue; + return [super isTokenAutoRefreshEnabled]; } // Fallback to the global data collection flag. @@ -91,8 +92,9 @@ - (BOOL)isTokenAutoRefreshEnabled { - (void)setIsTokenAutoRefreshEnabled:(BOOL)isTokenAutoRefreshEnabled { @synchronized(self) { - self.isTokenAutoRefreshEnabledNumber = @(isTokenAutoRefreshEnabled); - [self.userDefaults setObject:self.isTokenAutoRefreshEnabledNumber forKey:self.userDefaultKey]; + self.isTokenAutoRefreshConfigured = YES; + [super setIsTokenAutoRefreshEnabled:isTokenAutoRefreshEnabled]; + [self.userDefaults setBool:isTokenAutoRefreshEnabled forKey:self.userDefaultKey]; } } diff --git a/FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h b/FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h index 48c55d2b6fc..b9237a51b7e 100644 --- a/FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h +++ b/FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h @@ -14,9 +14,11 @@ * limitations under the License. */ +#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckToken.h" + #import -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckToken.h" +#import NS_ASSUME_NONNULL_BEGIN @@ -34,6 +36,12 @@ NS_ASSUME_NONNULL_BEGIN expirationDate:(NSDate *)expirationDate receivedAtDate:(NSDate *)receivedAtDate NS_DESIGNATED_INITIALIZER; +/// Instantiates a `FIRAppCheckToken` token from a `GACAppCheckToken`. +/// @param token The internal App Check token to be converted into a Firebase App Check token. +- (instancetype)initWithInternalToken:(GACAppCheckToken *)token; + +- (GACAppCheckToken *)internalToken; + @end NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/FIRAppCheckToken.m b/FirebaseAppCheck/Sources/Core/FIRAppCheckToken.m index 35af5adde09..1067a2dcc16 100644 --- a/FirebaseAppCheck/Sources/Core/FIRAppCheckToken.m +++ b/FirebaseAppCheck/Sources/Core/FIRAppCheckToken.m @@ -36,6 +36,18 @@ - (instancetype)initWithToken:(NSString *)token expirationDate:(NSDate *)expirat return [self initWithToken:token expirationDate:expirationDate receivedAtDate:[NSDate date]]; } +- (instancetype)initWithInternalToken:(GACAppCheckToken *)token { + return [self initWithToken:token.token + expirationDate:token.expirationDate + receivedAtDate:token.receivedAtDate]; +} + +- (GACAppCheckToken *)internalToken { + return [[GACAppCheckToken alloc] initWithToken:self.token + expirationDate:self.expirationDate + receivedAtDate:self.receivedAtDate]; +} + @end NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken+FIRAppCheckToken.h b/FirebaseAppCheck/Sources/Core/FIRHeartbeatLogger+AppCheck.h similarity index 68% rename from FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken+FIRAppCheckToken.h rename to FirebaseAppCheck/Sources/Core/FIRHeartbeatLogger+AppCheck.h index 4ba3862755d..bc90f7d2620 100644 --- a/FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken+FIRAppCheckToken.h +++ b/FirebaseAppCheck/Sources/Core/FIRHeartbeatLogger+AppCheck.h @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +14,16 @@ * limitations under the License. */ -#import "FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken.h" +#import "FirebaseCore/Extension/FIRHeartbeatLogger.h" -@class FIRAppCheckToken; +#import NS_ASSUME_NONNULL_BEGIN -@interface FIRAppCheckStoredToken (FIRAppCheckToken) +@interface FIRHeartbeatLogger (AppCheck) -- (void)updateWithToken:(FIRAppCheckToken *)token; - -- (FIRAppCheckToken *)appCheckToken; +/// Returns a closure that adds a heartbeat logging header to a `URLRequest`. +- (GACAppCheckAPIRequestHook)requestHook; @end diff --git a/FirebaseAppCheck/Sources/Core/FIRHeartbeatLogger+AppCheck.m b/FirebaseAppCheck/Sources/Core/FIRHeartbeatLogger+AppCheck.m new file mode 100644 index 00000000000..78b0628f38a --- /dev/null +++ b/FirebaseAppCheck/Sources/Core/FIRHeartbeatLogger+AppCheck.m @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FirebaseAppCheck/Sources/Core/FIRHeartbeatLogger+AppCheck.h" + +/// The HTTP request header key for a heartbeat logging payload. +static NSString *const kFIRHeartbeatLoggerPayloadHeaderKey = @"X-firebase-client"; + +@implementation FIRHeartbeatLogger (AppCheck) + +- (GACAppCheckAPIRequestHook)requestHook { + return ^(NSMutableURLRequest *request) { + NSString *heartbeatsValue = + FIRHeaderValueFromHeartbeatsPayload([self flushHeartbeatsIntoPayload]); + if (heartbeatsValue) { + [request setValue:heartbeatsValue forHTTPHeaderField:kFIRHeartbeatLoggerPayloadHeaderKey]; + } + }; +} + +@end diff --git a/FirebaseAppCheck/Sources/Core/Utils/FIRAppCheckCryptoUtils.h b/FirebaseAppCheck/Sources/Core/FIRInternalAppCheckProvider.h similarity index 71% rename from FirebaseAppCheck/Sources/Core/Utils/FIRAppCheckCryptoUtils.h rename to FirebaseAppCheck/Sources/Core/FIRInternalAppCheckProvider.h index 1e1ac28e3e5..594fe3d36dc 100644 --- a/FirebaseAppCheck/Sources/Core/Utils/FIRAppCheckCryptoUtils.h +++ b/FirebaseAppCheck/Sources/Core/FIRInternalAppCheckProvider.h @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,15 @@ #import +#import + NS_ASSUME_NONNULL_BEGIN -@interface FIRAppCheckCryptoUtils : NSObject +@protocol FIRAppCheckProvider; + +@interface FIRInternalAppCheckProvider : NSObject -+ (NSData *)sha256HashFromData:(NSData *)dataToHash; +- (instancetype)initWithAppCheckProvider:(id)appCheckProvider; @end diff --git a/FirebaseAppCheck/Sources/Core/FIRInternalAppCheckProvider.m b/FirebaseAppCheck/Sources/Core/FIRInternalAppCheckProvider.m new file mode 100644 index 00000000000..5e41f0c2add --- /dev/null +++ b/FirebaseAppCheck/Sources/Core/FIRInternalAppCheckProvider.m @@ -0,0 +1,57 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FirebaseAppCheck/Sources/Core/FIRInternalAppCheckProvider.h" + +#import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h" +#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckProvider.h" + +@interface FIRInternalAppCheckProvider () + +@property(nonatomic, readonly) id appCheckProvider; + +@end + +@implementation FIRInternalAppCheckProvider + +- (instancetype)initWithAppCheckProvider:(id)appCheckProvider { + if (self = [super init]) { + _appCheckProvider = appCheckProvider; + } + + return self; +} + +- (void)getTokenWithCompletion:(void (^)(GACAppCheckToken *_Nullable, NSError *_Nullable))handler { + [self.appCheckProvider + getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { + handler([token internalToken], error); + }]; +} + +- (void)getLimitedUseTokenWithCompletion:(nonnull void (^)(GACAppCheckToken *_Nullable, + NSError *_Nullable))handler { + if ([self.appCheckProvider respondsToSelector:@selector(getLimitedUseTokenWithCompletion:)]) { + [self.appCheckProvider getLimitedUseTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, + NSError *_Nullable error) { + handler([token internalToken], error); + }]; + } else { + [self getTokenWithCompletion:handler]; + } +} + +@end diff --git a/FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStorage.h b/FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStorage.h deleted file mode 100644 index c20a2595dd3..00000000000 --- a/FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStorage.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -@class FIRAppCheckToken; -@class FBLPromise; -@class GULKeychainStorage; - -NS_ASSUME_NONNULL_BEGIN - -@protocol FIRAppCheckStorageProtocol - -/** Manages storage of the FAA token. - * @param token A token object to store or `nil` to remove existing token. - * @return A promise that is resolved with the stored object in the case of success or is rejected - * with a specific error otherwise. - */ -- (FBLPromise *)setToken:(nullable FIRAppCheckToken *)token; - -/** Reads a stored FAA token. - * @return A promise that is resolved with a stored token or `nil` if there is not a stored token. - * The promise is rejected with an error in the case of a failure. - */ -- (FBLPromise *)getToken; - -@end - -/// The class provides an implementation of persistent storage to store data like FAA token, etc. -@interface FIRAppCheckStorage : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -/** Default convenience initializer. - * @param appName A Firebase App name (`FirebaseApp.name`). The app name will be used as a part of - * the key to store the token for the storage instance. - * @param appID A Firebase App identifier (`FirebaseOptions.googleAppID`). The app ID will be used - * as a part of the key to store the token for the storage instance. - * @param accessGroup The Keychain Access Group. - */ -- (instancetype)initWithAppName:(NSString *)appName - appID:(NSString *)appID - accessGroup:(nullable NSString *)accessGroup; - -/** Designated initializer. - * @param appName A Firebase App name (`FirebaseApp.name`). The app name will be used as a part of - * the key to store the token for the storage instance. - * @param appID A Firebase App identifier (`FirebaseOptions.googleAppID`). The app ID will be used - * as a part of the key to store the token for the storage instance. - * @param keychainStorage An instance of `GULKeychainStorage` used as an underlying secure storage. - * @param accessGroup The Keychain Access Group. - */ -- (instancetype)initWithAppName:(NSString *)appName - appID:(NSString *)appID - keychainStorage:(GULKeychainStorage *)keychainStorage - accessGroup:(nullable NSString *)accessGroup NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStorage.m b/FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStorage.m deleted file mode 100644 index 066d02a1cfc..00000000000 --- a/FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStorage.m +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStorage.h" - -#if __has_include() -#import -#else -#import "FBLPromises.h" -#endif - -#import - -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" -#import "FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken+FIRAppCheckToken.h" - -NS_ASSUME_NONNULL_BEGIN - -static NSString *const kKeychainService = @"com.firebase.app_check.token_storage"; - -@interface FIRAppCheckStorage () - -@property(nonatomic, readonly) NSString *appName; -@property(nonatomic, readonly) NSString *appID; -@property(nonatomic, readonly) GULKeychainStorage *keychainStorage; -@property(nonatomic, readonly, nullable) NSString *accessGroup; - -@end - -@implementation FIRAppCheckStorage - -- (instancetype)initWithAppName:(NSString *)appName - appID:(NSString *)appID - keychainStorage:(GULKeychainStorage *)keychainStorage - accessGroup:(nullable NSString *)accessGroup { - self = [super init]; - if (self) { - _appName = [appName copy]; - _appID = [appID copy]; - _keychainStorage = keychainStorage; - _accessGroup = [accessGroup copy]; - } - return self; -} - -- (instancetype)initWithAppName:(NSString *)appName - appID:(NSString *)appID - accessGroup:(nullable NSString *)accessGroup { - GULKeychainStorage *keychainStorage = - [[GULKeychainStorage alloc] initWithService:kKeychainService]; - return [self initWithAppName:appName - appID:appID - keychainStorage:keychainStorage - accessGroup:accessGroup]; -} - -- (FBLPromise *)getToken { - return [self.keychainStorage getObjectForKey:[self tokenKey] - objectClass:[FIRAppCheckStoredToken class] - accessGroup:self.accessGroup] - .then(^FIRAppCheckToken *(id storedToken) { - if ([(NSObject *)storedToken isKindOfClass:[FIRAppCheckStoredToken class]]) { - return [(FIRAppCheckStoredToken *)storedToken appCheckToken]; - } else { - return nil; - } - }) - .recover(^NSError *(NSError *error) { - return [FIRAppCheckErrorUtil keychainErrorWithError:error]; - }); -} - -- (FBLPromise *)setToken:(nullable FIRAppCheckToken *)token { - if (token) { - return [self storeToken:token].recover(^NSError *(NSError *error) { - return [FIRAppCheckErrorUtil keychainErrorWithError:error]; - }); - } else { - return [self.keychainStorage removeObjectForKey:[self tokenKey] accessGroup:self.accessGroup] - .then(^id _Nullable(NSNull *_Nullable value) { - return token; - }) - .recover(^NSError *(NSError *error) { - return [FIRAppCheckErrorUtil keychainErrorWithError:error]; - }); - } -} - -#pragma mark - Helpers - -- (FBLPromise *)storeToken:(nullable FIRAppCheckToken *)token { - FIRAppCheckStoredToken *storedToken = [[FIRAppCheckStoredToken alloc] init]; - [storedToken updateWithToken:token]; - return [self.keychainStorage setObject:storedToken - forKey:[self tokenKey] - accessGroup:self.accessGroup] - .then(^id _Nullable(NSNull *_Nullable value) { - return token; - }); -} - -- (NSString *)tokenKey { - return [[self class] tokenKeyForAppName:self.appName appID:self.appID]; -} - -+ (NSString *)tokenKeyForAppName:(NSString *)appName appID:(NSString *)appID { - return [NSString stringWithFormat:@"app_check_token.%@.%@", appName, appID]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken+FIRAppCheckToken.m b/FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken+FIRAppCheckToken.m deleted file mode 100644 index 9135854709e..00000000000 --- a/FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken+FIRAppCheckToken.m +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken+FIRAppCheckToken.h" - -#import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h" - -@implementation FIRAppCheckStoredToken (FIRAppCheckToken) - -- (void)updateWithToken:(FIRAppCheckToken *)token { - self.token = token.token; - self.expirationDate = token.expirationDate; - self.receivedAtDate = token.receivedAtDate; -} - -- (FIRAppCheckToken *)appCheckToken { - return [[FIRAppCheckToken alloc] initWithToken:self.token - expirationDate:self.expirationDate - receivedAtDate:self.receivedAtDate]; -} - -@end diff --git a/FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken.h b/FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken.h deleted file mode 100644 index 63f46764009..00000000000 --- a/FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -@class FIRApp; - -NS_ASSUME_NONNULL_BEGIN - -@interface FIRAppCheckStoredToken : NSObject - -/// The Firebase App Check token. -@property(nonatomic, copy, nullable) NSString *token; - -/// The Firebase App Check token expiration date in the device local time. -@property(nonatomic, strong, nullable) NSDate *expirationDate; - -/// The date when the Firebase App Check token was received in the device's local time. -@property(nonatomic, strong, nullable) NSDate *receivedAtDate; - -/// The version of local storage. -@property(nonatomic, readonly) NSInteger storageVersion; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken.m b/FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken.m deleted file mode 100644 index 217f2c77da3..00000000000 --- a/FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken.m +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken.h" - -static NSString *const kTokenKey = @"token"; -static NSString *const kExpirationDateKey = @"expirationDate"; -static NSString *const kReceivedAtDateKey = @"receivedAtDate"; -static NSString *const kStorageVersionKey = @"storageVersion"; - -static const NSInteger kStorageVersion = 2; - -NS_ASSUME_NONNULL_BEGIN - -@implementation FIRAppCheckStoredToken - -- (NSInteger)storageVersion { - return kStorageVersion; -} - -+ (BOOL)supportsSecureCoding { - return YES; -} - -- (void)encodeWithCoder:(NSCoder *)coder { - [coder encodeObject:self.token forKey:kTokenKey]; - [coder encodeObject:self.expirationDate forKey:kExpirationDateKey]; - [coder encodeObject:self.receivedAtDate forKey:kReceivedAtDateKey]; - [coder encodeInteger:self.storageVersion forKey:kStorageVersionKey]; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder { - self = [super init]; - if (self) { - NSInteger decodedStorageVersion = [coder decodeIntegerForKey:kStorageVersionKey]; - if (decodedStorageVersion > kStorageVersion) { - // TODO: Log a message. - } - - _token = [coder decodeObjectOfClass:[NSString class] forKey:kTokenKey]; - _expirationDate = [coder decodeObjectOfClass:[NSDate class] forKey:kExpirationDateKey]; - _receivedAtDate = [coder decodeObjectOfClass:[NSDate class] forKey:kReceivedAtDateKey]; - } - return self; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTimer.h b/FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTimer.h deleted file mode 100644 index f4038ab9b7c..00000000000 --- a/FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTimer.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -NS_ASSUME_NONNULL_BEGIN - -@protocol FIRAppCheckTimerProtocol - -- (void)invalidate; - -@end - -typedef id _Nullable (^FIRTimerProvider)(NSDate *fireDate, - dispatch_queue_t queue, - dispatch_block_t handler); - -@interface FIRAppCheckTimer : NSObject - -+ (FIRTimerProvider)timerProvider; - -- (nullable instancetype)initWithFireDate:(NSDate *)date - dispatchQueue:(dispatch_queue_t)dispatchQueue - block:(dispatch_block_t)block; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTimer.m b/FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTimer.m deleted file mode 100644 index c5151aff21e..00000000000 --- a/FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTimer.m +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTimer.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface FIRAppCheckTimer () -@property(nonatomic, readonly) dispatch_queue_t dispatchQueue; -@property(atomic, readonly) dispatch_source_t timer; -@end - -@implementation FIRAppCheckTimer - -+ (FIRTimerProvider)timerProvider { - return ^id _Nullable(NSDate *fireDate, dispatch_queue_t queue, - dispatch_block_t handler) { - return [[FIRAppCheckTimer alloc] initWithFireDate:fireDate dispatchQueue:queue block:handler]; - }; -} - -+ (nullable instancetype)timerFireDate:(NSDate *)fireDate - dispatchQueue:(dispatch_queue_t)dispatchQueue - block:(dispatch_block_t)block { - return [[FIRAppCheckTimer alloc] initWithFireDate:fireDate - dispatchQueue:dispatchQueue - block:block]; -} - -- (nullable instancetype)initWithFireDate:(NSDate *)date - dispatchQueue:(dispatch_queue_t)dispatchQueue - block:(dispatch_block_t)block { - self = [super init]; - if (self == nil) { - return nil; - } - - if (block == nil) { - return nil; - } - - NSTimeInterval scheduleInSec = [date timeIntervalSinceNow]; - if (scheduleInSec <= 0) { - return nil; - } - - dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, scheduleInSec * NSEC_PER_SEC); - _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.dispatchQueue); - dispatch_source_set_timer(_timer, startTime, UINT64_MAX * NSEC_PER_SEC, 0); - - __auto_type __weak weakSelf = self; - dispatch_source_set_event_handler(_timer, ^{ - __auto_type strongSelf = weakSelf; - - // The initializer returns a one-off timer, so we need to invalidate the dispatch timer to - // prevent firing again. - [strongSelf invalidate]; - block(); - }); - - dispatch_resume(_timer); - - return self; -} - -- (void)dealloc { - [self invalidate]; -} - -- (void)invalidate { - if (self.timer != nil) { - dispatch_source_cancel(self.timer); - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefreshResult.h b/FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefreshResult.h deleted file mode 100644 index 8d899efa61f..00000000000 --- a/FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefreshResult.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -NS_ASSUME_NONNULL_BEGIN - -/// Represents possible results of a Firebase App Check token refresh attempt that matter for -/// `FIRAppCheckTokenRefresher`. -typedef NS_ENUM(NSInteger, FIRAppCheckTokenRefreshStatus) { - // The token has not been refreshed. - FIRAppCheckTokenRefreshStatusNever, - - // The token was successfully refreshed. - FIRAppCheckTokenRefreshStatusSuccess, - - // The token refresh failed. - FIRAppCheckTokenRefreshStatusFailure -}; - -/// An object to pass the possible results of a Firebase App Check token refresh attempt and -/// supplementary data. -@interface FIRAppCheckTokenRefreshResult : NSObject - -/// Status of the refresh. -@property(nonatomic, readonly) FIRAppCheckTokenRefreshStatus status; - -/// A date when the new Firebase App Check token is expiring. -@property(nonatomic, readonly, nullable) NSDate *tokenExpirationDate; - -/// A date when the new Firebase App Check token was received from the server. -@property(nonatomic, readonly, nullable) NSDate *tokenReceivedAtDate; - -- (instancetype)init NS_UNAVAILABLE; - -/// Initializes the instance with `FIRAppCheckTokenRefreshStatusNever`. -- (instancetype)initWithStatusNever; - -/// Initializes the instance with `FIRAppCheckTokenRefreshStatusFailure`. -- (instancetype)initWithStatusFailure; - -/// Initializes the instance with `FIRAppCheckTokenRefreshStatusSuccess`. -/// @param tokenExpirationDate See `tokenExpirationDate` property. -/// @param tokenReceivedAtDate See `tokenReceivedAtDate` property. -- (instancetype)initWithStatusSuccessAndExpirationDate:(NSDate *)tokenExpirationDate - receivedAtDate:(NSDate *)tokenReceivedAtDate; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefreshResult.m b/FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefreshResult.m deleted file mode 100644 index edaf242dd47..00000000000 --- a/FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefreshResult.m +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefreshResult.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface FIRAppCheckTokenRefreshResult () - -- (instancetype)initWithStatus:(FIRAppCheckTokenRefreshStatus)status - expirationDate:(nullable NSDate *)tokenExpirationDate - receivedAtDate:(nullable NSDate *)tokenReceivedAtDate NS_DESIGNATED_INITIALIZER; - -@end - -@implementation FIRAppCheckTokenRefreshResult - -- (instancetype)initWithStatus:(FIRAppCheckTokenRefreshStatus)status - expirationDate:(nullable NSDate *)tokenExpirationDate - receivedAtDate:(nullable NSDate *)tokenReceivedAtDate { - self = [super init]; - if (self) { - _status = status; - _tokenExpirationDate = tokenExpirationDate; - _tokenReceivedAtDate = tokenReceivedAtDate; - } - return self; -} - -- (instancetype)initWithStatusNever { - return [self initWithStatus:FIRAppCheckTokenRefreshStatusNever - expirationDate:nil - receivedAtDate:nil]; -} - -- (instancetype)initWithStatusFailure { - return [self initWithStatus:FIRAppCheckTokenRefreshStatusFailure - expirationDate:nil - receivedAtDate:nil]; -} - -- (instancetype)initWithStatusSuccessAndExpirationDate:(NSDate *)tokenExpirationDate - receivedAtDate:(NSDate *)tokenReceivedAtDate { - return [self initWithStatus:FIRAppCheckTokenRefreshStatusSuccess - expirationDate:tokenExpirationDate - receivedAtDate:tokenReceivedAtDate]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefresher.h b/FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefresher.h deleted file mode 100644 index 26bdca9acfc..00000000000 --- a/FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefresher.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTimer.h" - -@protocol FIRAppCheckSettingsProtocol; -@class FIRAppCheckTokenRefreshResult; - -NS_ASSUME_NONNULL_BEGIN - -/** The block to be called on the token refresh completion. - * @param refreshResult The refresh result. - */ -typedef void (^FIRAppCheckTokenRefreshCompletion)(FIRAppCheckTokenRefreshResult *refreshResult); - -/** The block that will be called by `FIRAppCheckTokenRefresher` to trigger the token refresh. - * @param completion The block that the client must call when the token refresh was completed. - */ -typedef void (^FIRAppCheckTokenRefreshBlock)(FIRAppCheckTokenRefreshCompletion completion); - -@protocol FIRAppCheckTokenRefresherProtocol - -/// The block to be called when refresh is needed. The client is responsible for actual token -/// refresh in the block. -@property(nonatomic, copy) FIRAppCheckTokenRefreshBlock tokenRefreshHandler; - -/// Updates the next refresh date based on the new token expiration date. This method should be -/// called when the token update was initiated not by the refresher. -/// @param refreshResult A result of a refresh attempt. -- (void)updateWithRefreshResult:(FIRAppCheckTokenRefreshResult *)refreshResult; - -@end - -/// The class calls `tokenRefreshHandler` periodically to keep FAC token fresh to reduce FAC token -/// exchange overhead for product requests. -@interface FIRAppCheckTokenRefresher : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -/// The designated initializer. -/// @param refreshResult A previous token refresh attempt result. -/// @param settings An object that handles Firebase app check settings. -- (instancetype)initWithRefreshResult:(FIRAppCheckTokenRefreshResult *)refreshResult - timerProvider:(FIRTimerProvider)timerProvider - settings:(id)settings - NS_DESIGNATED_INITIALIZER; - -/// A convenience initializer with a timer provider returning an instance of `FIRAppCheckTimer`. -- (instancetype)initWithRefreshResult:(FIRAppCheckTokenRefreshResult *)refreshResult - settings:(id)settings; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefresher.m b/FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefresher.m deleted file mode 100644 index be52ace97e5..00000000000 --- a/FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefresher.m +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefresher.h" - -#import "FirebaseAppCheck/Sources/Core/FIRAppCheckSettings.h" -#import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTimer.h" -#import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefreshResult.h" - -NS_ASSUME_NONNULL_BEGIN - -static const NSTimeInterval kInitialBackoffTimeInterval = 30; -static const NSTimeInterval kMaximumBackoffTimeInterval = 16 * 60; - -static const NSTimeInterval kMinimumAutoRefreshTimeInterval = 60; // 1 min. - -/// How much time in advance to auto-refresh token before it's expiration. E.g. 0.5 means that the -/// token will be refreshed half way through it's intended time to live. -static const double kAutoRefreshFraction = 0.5; - -@interface FIRAppCheckTokenRefresher () - -@property(nonatomic, readonly) dispatch_queue_t refreshQueue; - -@property(nonatomic, readonly) id settings; - -@property(nonatomic, readonly) FIRTimerProvider timerProvider; -@property(atomic, nullable) id timer; -@property(atomic) NSUInteger retryCount; - -/// Initial refresh result to be used when `tokenRefreshHandler` has been sent. -@property(nonatomic, nullable) FIRAppCheckTokenRefreshResult *initialRefreshResult; - -@end - -@implementation FIRAppCheckTokenRefresher - -@synthesize tokenRefreshHandler = _tokenRefreshHandler; - -- (instancetype)initWithRefreshResult:(FIRAppCheckTokenRefreshResult *)refreshResult - timerProvider:(FIRTimerProvider)timerProvider - settings:(id)settings { - self = [super init]; - if (self) { - _refreshQueue = - dispatch_queue_create("com.firebase.FIRAppCheckTokenRefresher", DISPATCH_QUEUE_SERIAL); - _initialRefreshResult = refreshResult; - _timerProvider = timerProvider; - _settings = settings; - } - return self; -} - -- (instancetype)initWithRefreshResult:(FIRAppCheckTokenRefreshResult *)refreshResult - settings:(id)settings { - return [self initWithRefreshResult:refreshResult - timerProvider:[FIRAppCheckTimer timerProvider] - settings:settings]; -} - -- (void)dealloc { - [self cancelTimer]; -} - -- (void)setTokenRefreshHandler:(FIRAppCheckTokenRefreshBlock)tokenRefreshHandler { - @synchronized(self) { - _tokenRefreshHandler = tokenRefreshHandler; - - // Check if handler is being set for the first time and if yes then schedule first refresh. - if (tokenRefreshHandler && self.initialRefreshResult) { - FIRAppCheckTokenRefreshResult *initialTokenRefreshResult = self.initialRefreshResult; - self.initialRefreshResult = nil; - [self scheduleWithTokenRefreshResult:initialTokenRefreshResult]; - } - } -} - -- (FIRAppCheckTokenRefreshBlock)tokenRefreshHandler { - @synchronized(self) { - return _tokenRefreshHandler; - } -} - -- (void)updateWithRefreshResult:(FIRAppCheckTokenRefreshResult *)refreshResult { - switch (refreshResult.status) { - case FIRAppCheckTokenRefreshStatusNever: - case FIRAppCheckTokenRefreshStatusSuccess: - self.retryCount = 0; - break; - - case FIRAppCheckTokenRefreshStatusFailure: - self.retryCount += 1; - break; - } - - [self scheduleWithTokenRefreshResult:refreshResult]; -} - -- (void)refresh { - if (self.tokenRefreshHandler == nil) { - return; - } - - if (!self.settings.isTokenAutoRefreshEnabled) { - return; - } - - __auto_type __weak weakSelf = self; - self.tokenRefreshHandler(^(FIRAppCheckTokenRefreshResult *refreshResult) { - __auto_type strongSelf = weakSelf; - [strongSelf updateWithRefreshResult:refreshResult]; - }); -} - -- (void)scheduleWithTokenRefreshResult:(FIRAppCheckTokenRefreshResult *)refreshResult { - // Schedule the refresh only when allowed. - if (self.settings.isTokenAutoRefreshEnabled) { - NSDate *refreshDate = [self nextRefreshDateWithTokenRefreshResult:refreshResult]; - [self scheduleRefreshAtDate:refreshDate]; - } -} - -- (void)scheduleRefreshAtDate:(NSDate *)refreshDate { - [self cancelTimer]; - - NSTimeInterval scheduleInSec = [refreshDate timeIntervalSinceNow]; - - __auto_type __weak weakSelf = self; - dispatch_block_t refreshHandler = ^{ - __auto_type strongSelf = weakSelf; - [strongSelf refresh]; - }; - - // Refresh straight away if the refresh time is too close. - if (scheduleInSec <= 0) { - dispatch_async(self.refreshQueue, refreshHandler); - return; - } - - self.timer = self.timerProvider(refreshDate, self.refreshQueue, refreshHandler); -} - -- (void)cancelTimer { - [self.timer invalidate]; -} - -- (NSDate *)nextRefreshDateWithTokenRefreshResult:(FIRAppCheckTokenRefreshResult *)refreshResult { - switch (refreshResult.status) { - case FIRAppCheckTokenRefreshStatusSuccess: { - NSTimeInterval timeToLive = [refreshResult.tokenExpirationDate - timeIntervalSinceDate:refreshResult.tokenReceivedAtDate]; - timeToLive = MAX(timeToLive, 0); - - // Refresh in 50% of TTL + 5 min. - NSTimeInterval targetRefreshSinceReceivedDate = timeToLive * kAutoRefreshFraction + 5 * 60; - NSDate *targetRefreshDate = [refreshResult.tokenReceivedAtDate - dateByAddingTimeInterval:targetRefreshSinceReceivedDate]; - - // Don't schedule later than expiration date. - NSDate *refreshDate = [targetRefreshDate earlierDate:refreshResult.tokenExpirationDate]; - - // Don't schedule a refresh earlier than in 1 min from now. - if ([refreshDate timeIntervalSinceNow] < kMinimumAutoRefreshTimeInterval) { - refreshDate = [NSDate dateWithTimeIntervalSinceNow:kMinimumAutoRefreshTimeInterval]; - } - return refreshDate; - } break; - - case FIRAppCheckTokenRefreshStatusFailure: { - // Repeat refresh attempt later. - NSTimeInterval backoffTime = [[self class] backoffTimeForRetryCount:self.retryCount]; - return [NSDate dateWithTimeIntervalSinceNow:backoffTime]; - } break; - - case FIRAppCheckTokenRefreshStatusNever: - // Refresh ASAP. - return [NSDate date]; - break; - } -} - -#pragma mark - Backoff - -+ (NSTimeInterval)backoffTimeForRetryCount:(NSInteger)retryCount { - if (retryCount == 0) { - // No backoff for the first attempt. - return 0; - } - - NSTimeInterval exponentialInterval = - kInitialBackoffTimeInterval * pow(2, retryCount - 1) + [self randomMilliseconds]; - return MIN(exponentialInterval, kMaximumBackoffTimeInterval); -} - -+ (NSTimeInterval)randomMilliseconds { - int32_t random_millis = ABS(arc4random() % 1000); - return (double)random_millis * 0.001; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/Utils/FIRAppCheckCryptoUtils.m b/FirebaseAppCheck/Sources/Core/Utils/FIRAppCheckCryptoUtils.m deleted file mode 100644 index 9e2807b5007..00000000000 --- a/FirebaseAppCheck/Sources/Core/Utils/FIRAppCheckCryptoUtils.m +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/Core/Utils/FIRAppCheckCryptoUtils.h" - -#import - -@implementation FIRAppCheckCryptoUtils - -+ (NSData *)sha256HashFromData:(NSData *)dataToHash { - NSMutableData *digest = [[NSMutableData alloc] initWithLength:CC_SHA256_DIGEST_LENGTH]; - CC_SHA256(dataToHash.bytes, (CC_LONG)dataToHash.length, digest.mutableBytes); - return [digest copy]; -} - -@end diff --git a/FirebaseAppCheck/Sources/DebugProvider/API/FIRAppCheckDebugProviderAPIService.h b/FirebaseAppCheck/Sources/DebugProvider/API/FIRAppCheckDebugProviderAPIService.h deleted file mode 100644 index 4384fb52893..00000000000 --- a/FirebaseAppCheck/Sources/DebugProvider/API/FIRAppCheckDebugProviderAPIService.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -@class FBLPromise; -@class FIRAppCheckToken; -@protocol FIRAppCheckAPIServiceProtocol; - -NS_ASSUME_NONNULL_BEGIN - -@protocol FIRAppCheckDebugProviderAPIServiceProtocol - -- (FBLPromise *)appCheckTokenWithDebugToken:(NSString *)debugToken; - -@end - -@interface FIRAppCheckDebugProviderAPIService - : NSObject - -- (instancetype)initWithAPIService:(id)APIService - projectID:(NSString *)projectID - appID:(NSString *)appID; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/DebugProvider/API/FIRAppCheckDebugProviderAPIService.m b/FirebaseAppCheck/Sources/DebugProvider/API/FIRAppCheckDebugProviderAPIService.m deleted file mode 100644 index 0115032da3c..00000000000 --- a/FirebaseAppCheck/Sources/DebugProvider/API/FIRAppCheckDebugProviderAPIService.m +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/DebugProvider/API/FIRAppCheckDebugProviderAPIService.h" - -#if __has_include() -#import -#else -#import "FBLPromises.h" -#endif - -#import -#import - -#import "FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckAPIService.h" -#import "FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckToken+APIResponse.h" - -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" -#import "FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h" - -#import "FirebaseCore/Extension/FirebaseCoreInternal.h" - -NS_ASSUME_NONNULL_BEGIN - -static NSString *const kContentTypeKey = @"Content-Type"; -static NSString *const kJSONContentType = @"application/json"; -static NSString *const kDebugTokenField = @"debug_token"; - -@interface FIRAppCheckDebugProviderAPIService () - -@property(nonatomic, readonly) id APIService; - -@property(nonatomic, readonly) NSString *projectID; -@property(nonatomic, readonly) NSString *appID; - -@end - -@implementation FIRAppCheckDebugProviderAPIService - -- (instancetype)initWithAPIService:(id)APIService - projectID:(NSString *)projectID - appID:(NSString *)appID { - self = [super init]; - if (self) { - _APIService = APIService; - _projectID = projectID; - _appID = appID; - } - return self; -} - -#pragma mark - Public API - -- (FBLPromise *)appCheckTokenWithDebugToken:(NSString *)debugToken { - NSString *URLString = - [NSString stringWithFormat:@"%@/projects/%@/apps/%@:exchangeDebugToken", - self.APIService.baseURL, self.projectID, self.appID]; - NSURL *URL = [NSURL URLWithString:URLString]; - - return [self HTTPBodyWithDebugToken:debugToken] - .then(^FBLPromise *(NSData *HTTPBody) { - return [self.APIService sendRequestWithURL:URL - HTTPMethod:@"POST" - body:HTTPBody - additionalHeaders:@{kContentTypeKey : kJSONContentType}]; - }) - .then(^id _Nullable(GULURLSessionDataResponse *_Nullable response) { - return [self.APIService appCheckTokenWithAPIResponse:response]; - }); -} - -#pragma mark - Helpers - -- (FBLPromise *)HTTPBodyWithDebugToken:(NSString *)debugToken { - if (debugToken.length <= 0) { - FBLPromise *rejectedPromise = [FBLPromise pendingPromise]; - [rejectedPromise - reject:[FIRAppCheckErrorUtil errorWithFailureReason:@"Debug token must not be empty."]]; - return rejectedPromise; - } - - return [FBLPromise onQueue:[self backgroundQueue] - do:^id _Nullable { - NSError *encodingError; - NSData *payloadJSON = [NSJSONSerialization - dataWithJSONObject:@{kDebugTokenField : debugToken} - options:0 - error:&encodingError]; - - if (payloadJSON != nil) { - return payloadJSON; - } else { - return [FIRAppCheckErrorUtil JSONSerializationError:encodingError]; - } - }]; -} - -- (dispatch_queue_t)backgroundQueue { - return dispatch_get_global_queue(QOS_CLASS_UTILITY, 0); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/DebugProvider/FIRAppCheckDebugProvider.m b/FirebaseAppCheck/Sources/DebugProvider/FIRAppCheckDebugProvider.m index 9439d40381f..a2fb54bab7d 100644 --- a/FirebaseAppCheck/Sources/DebugProvider/FIRAppCheckDebugProvider.m +++ b/FirebaseAppCheck/Sources/DebugProvider/FIRAppCheckDebugProvider.m @@ -22,29 +22,30 @@ #import "FBLPromises.h" #endif -#import "FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckAPIService.h" +#import + +#import "FirebaseAppCheck/Sources/Core/FIRApp+AppCheck.h" #import "FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h" +#import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h" #import "FirebaseAppCheck/Sources/Core/FIRAppCheckValidator.h" -#import "FirebaseAppCheck/Sources/DebugProvider/API/FIRAppCheckDebugProviderAPIService.h" -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckToken.h" +#import "FirebaseAppCheck/Sources/Core/FIRHeartbeatLogger+AppCheck.h" #import "FirebaseCore/Extension/FirebaseCoreInternal.h" NS_ASSUME_NONNULL_BEGIN -static NSString *const kDebugTokenEnvKey = @"FIRAAppCheckDebugToken"; -static NSString *const kDebugTokenUserDefaultsKey = @"FIRAAppCheckDebugToken"; - @interface FIRAppCheckDebugProvider () -@property(nonatomic, readonly) id APIService; + +@property(nonatomic, readonly) GACAppCheckDebugProvider *debugProvider; + @end @implementation FIRAppCheckDebugProvider -- (instancetype)initWithAPIService:(id)APIService { +- (instancetype)initWithDebugProvider:(GACAppCheckDebugProvider *)debugProvider { self = [super init]; if (self) { - _APIService = APIService; + _debugProvider = debugProvider; } return self; } @@ -60,69 +61,50 @@ - (nullable instancetype)initWithApp:(FIRApp *)app { return nil; } - NSURLSession *URLSession = [NSURLSession - sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; + GACAppCheckDebugProvider *debugProvider = + [[GACAppCheckDebugProvider alloc] initWithServiceName:app.name + resourceName:app.resourceName + baseURL:nil + APIKey:app.options.APIKey + requestHooks:@[ [app.heartbeatLogger requestHook] ]]; - FIRAppCheckAPIService *APIService = - [[FIRAppCheckAPIService alloc] initWithURLSession:URLSession - APIKey:app.options.APIKey - appID:app.options.googleAppID - heartbeatLogger:app.heartbeatLogger]; - - FIRAppCheckDebugProviderAPIService *debugAPIService = - [[FIRAppCheckDebugProviderAPIService alloc] initWithAPIService:APIService - projectID:app.options.projectID - appID:app.options.googleAppID]; - - return [self initWithAPIService:debugAPIService]; + return [self initWithDebugProvider:debugProvider]; } - (NSString *)currentDebugToken { - NSString *envVariableValue = [[NSProcessInfo processInfo] environment][kDebugTokenEnvKey]; - if (envVariableValue.length > 0) { - return envVariableValue; - } else { - return [self localDebugToken]; - } + return [self.debugProvider currentDebugToken]; } - (NSString *)localDebugToken { - return [self storedDebugToken] ?: [self generateAndStoreDebugToken]; -} - -- (nullable NSString *)storedDebugToken { - return [[NSUserDefaults standardUserDefaults] stringForKey:kDebugTokenUserDefaultsKey]; -} - -- (void)storeDebugToken:(nullable NSString *)token { - [[NSUserDefaults standardUserDefaults] setObject:token forKey:kDebugTokenUserDefaultsKey]; -} - -- (NSString *)generateAndStoreDebugToken { - NSString *token = [NSUUID UUID].UUIDString; - [self storeDebugToken:token]; - return token; + return [self.debugProvider localDebugToken]; } #pragma mark - FIRAppCheckProvider - (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable token, NSError *_Nullable error))handler { - [FBLPromise do:^NSString * { - return [self currentDebugToken]; - }] - .then(^FBLPromise *(NSString *debugToken) { - return [self.APIService appCheckTokenWithDebugToken:debugToken]; - }) - .then(^id(FIRAppCheckToken *appCheckToken) { - handler(appCheckToken, nil); - return nil; - }) - .catch(^void(NSError *error) { - FIRAppCheckDebugLog(kFIRLoggerAppCheckMessageDebugProviderFailedExchange, - @"Failed to exchange debug token to app check token: %@", error); - handler(nil, error); - }); + [self.debugProvider getTokenWithCompletion:^(GACAppCheckToken *_Nullable internalToken, + NSError *_Nullable error) { + if (error) { + handler(nil, error); + return; + } + + handler([[FIRAppCheckToken alloc] initWithInternalToken:internalToken], nil); + }]; +} + +- (void)getLimitedUseTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable, + NSError *_Nullable))handler { + [self.debugProvider getLimitedUseTokenWithCompletion:^(GACAppCheckToken *_Nullable internalToken, + NSError *_Nullable error) { + if (error) { + handler(nil, error); + return; + } + + handler([[FIRAppCheckToken alloc] initWithInternalToken:internalToken], nil); + }]; } @end diff --git a/FirebaseAppCheck/Sources/DeviceCheckProvider/API/FIRDeviceCheckAPIService.h b/FirebaseAppCheck/Sources/DeviceCheckProvider/API/FIRDeviceCheckAPIService.h deleted file mode 100644 index 3972eb9a511..00000000000 --- a/FirebaseAppCheck/Sources/DeviceCheckProvider/API/FIRDeviceCheckAPIService.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -@class FBLPromise; -@class FIRAppCheckToken; -@protocol FIRAppCheckAPIServiceProtocol; - -NS_ASSUME_NONNULL_BEGIN - -@protocol FIRDeviceCheckAPIServiceProtocol - -- (FBLPromise *)appCheckTokenWithDeviceToken:(NSData *)deviceToken; - -@end - -@interface FIRDeviceCheckAPIService : NSObject - -- (instancetype)initWithAPIService:(id)APIService - projectID:(NSString *)projectID - appID:(NSString *)appID; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/DeviceCheckProvider/API/FIRDeviceCheckAPIService.m b/FirebaseAppCheck/Sources/DeviceCheckProvider/API/FIRDeviceCheckAPIService.m deleted file mode 100644 index 5e2c1fc1cf3..00000000000 --- a/FirebaseAppCheck/Sources/DeviceCheckProvider/API/FIRDeviceCheckAPIService.m +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/DeviceCheckProvider/API/FIRDeviceCheckAPIService.h" - -#if __has_include() -#import -#else -#import "FBLPromises.h" -#endif - -#import - -#import "FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckAPIService.h" -#import "FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckToken+APIResponse.h" - -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" -#import "FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h" - -#import "FirebaseCore/Extension/FirebaseCoreInternal.h" - -NS_ASSUME_NONNULL_BEGIN - -static NSString *const kContentTypeKey = @"Content-Type"; -static NSString *const kJSONContentType = @"application/json"; -static NSString *const kDeviceTokenField = @"device_token"; - -@interface FIRDeviceCheckAPIService () - -@property(nonatomic, readonly) id APIService; - -@property(nonatomic, readonly) NSString *projectID; -@property(nonatomic, readonly) NSString *appID; - -@end - -@implementation FIRDeviceCheckAPIService - -- (instancetype)initWithAPIService:(id)APIService - projectID:(NSString *)projectID - appID:(NSString *)appID { - self = [super init]; - if (self) { - _APIService = APIService; - _projectID = projectID; - _appID = appID; - } - return self; -} - -#pragma mark - Public API - -- (FBLPromise *)appCheckTokenWithDeviceToken:(NSData *)deviceToken { - NSString *URLString = - [NSString stringWithFormat:@"%@/projects/%@/apps/%@:exchangeDeviceCheckToken", - self.APIService.baseURL, self.projectID, self.appID]; - NSURL *URL = [NSURL URLWithString:URLString]; - - return [self HTTPBodyWithDeviceToken:deviceToken] - .then(^FBLPromise *(NSData *HTTPBody) { - return [self.APIService sendRequestWithURL:URL - HTTPMethod:@"POST" - body:HTTPBody - additionalHeaders:@{kContentTypeKey : kJSONContentType}]; - }) - .then(^id _Nullable(GULURLSessionDataResponse *_Nullable response) { - return [self.APIService appCheckTokenWithAPIResponse:response]; - }); -} - -- (FBLPromise *)HTTPBodyWithDeviceToken:(NSData *)deviceToken { - if (deviceToken.length <= 0) { - FBLPromise *rejectedPromise = [FBLPromise pendingPromise]; - [rejectedPromise reject:[FIRAppCheckErrorUtil - errorWithFailureReason:@"DeviceCheck token must not be empty."]]; - return rejectedPromise; - } - - return [FBLPromise onQueue:[self backgroundQueue] - do:^id _Nullable { - NSString *base64EncodedToken = - [deviceToken base64EncodedStringWithOptions:0]; - - NSError *encodingError; - NSData *payloadJSON = [NSJSONSerialization - dataWithJSONObject:@{kDeviceTokenField : base64EncodedToken} - options:0 - error:&encodingError]; - - if (payloadJSON != nil) { - return payloadJSON; - } else { - return [FIRAppCheckErrorUtil JSONSerializationError:encodingError]; - } - }]; -} - -- (dispatch_queue_t)backgroundQueue { - return dispatch_get_global_queue(QOS_CLASS_UTILITY, 0); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/DeviceCheckProvider/DCDevice+FIRDeviceCheckTokenGenerator.h b/FirebaseAppCheck/Sources/DeviceCheckProvider/DCDevice+FIRDeviceCheckTokenGenerator.h deleted file mode 100644 index 9b352dd617f..00000000000 --- a/FirebaseAppCheck/Sources/DeviceCheckProvider/DCDevice+FIRDeviceCheckTokenGenerator.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import "FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckTokenGenerator.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DCDevice (FIRDeviceCheckTokenGenerator) - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/DeviceCheckProvider/DCDevice+FIRDeviceCheckTokenGenerator.m b/FirebaseAppCheck/Sources/DeviceCheckProvider/DCDevice+FIRDeviceCheckTokenGenerator.m deleted file mode 100644 index c2c44312057..00000000000 --- a/FirebaseAppCheck/Sources/DeviceCheckProvider/DCDevice+FIRDeviceCheckTokenGenerator.m +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Sources/DeviceCheckProvider/DCDevice+FIRDeviceCheckTokenGenerator.h" - -@implementation DCDevice (FIRDeviceCheckTokenGenerator) - -@end diff --git a/FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckProvider.m b/FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckProvider.m index b3dbb70141e..e5dada66e37 100644 --- a/FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckProvider.m +++ b/FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckProvider.m @@ -14,62 +14,38 @@ * limitations under the License. */ -#import - -#if __has_include() -#import -#else -#import "FBLPromises.h" -#endif +#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckAvailability.h" #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRDeviceCheckProvider.h" -#import "FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckAPIService.h" -#import "FirebaseAppCheck/Sources/Core/Backoff/FIRAppCheckBackoffWrapper.h" -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" +#import + +#import "FirebaseAppCheck/Sources/Core/FIRApp+AppCheck.h" #import "FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h" +#import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h" #import "FirebaseAppCheck/Sources/Core/FIRAppCheckValidator.h" -#import "FirebaseAppCheck/Sources/DeviceCheckProvider/API/FIRDeviceCheckAPIService.h" -#import "FirebaseAppCheck/Sources/DeviceCheckProvider/DCDevice+FIRDeviceCheckTokenGenerator.h" -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckToken.h" +#import "FirebaseAppCheck/Sources/Core/FIRHeartbeatLogger+AppCheck.h" #import "FirebaseCore/Extension/FirebaseCoreInternal.h" NS_ASSUME_NONNULL_BEGIN @interface FIRDeviceCheckProvider () -@property(nonatomic, readonly) id APIService; -@property(nonatomic, readonly) id deviceTokenGenerator; -@property(nonatomic, readonly) id backoffWrapper; -- (instancetype)initWithAPIService:(id)APIService - deviceTokenGenerator:(id)deviceTokenGenerator - backoffWrapper:(id)backoffWrapper - NS_DESIGNATED_INITIALIZER; +@property(nonatomic, readonly) GACDeviceCheckProvider *deviceCheckProvider; @end @implementation FIRDeviceCheckProvider -- (instancetype)initWithAPIService:(id)APIService - deviceTokenGenerator:(id)deviceTokenGenerator - backoffWrapper:(id)backoffWrapper { +- (instancetype)initWithDeviceCheckProvider:(GACDeviceCheckProvider *)deviceCheckProvider { self = [super init]; if (self) { - _APIService = APIService; - _deviceTokenGenerator = deviceTokenGenerator; - _backoffWrapper = backoffWrapper; + _deviceCheckProvider = deviceCheckProvider; } return self; } -- (instancetype)initWithAPIService:(id)APIService { - FIRAppCheckBackoffWrapper *backoffWrapper = [[FIRAppCheckBackoffWrapper alloc] init]; - return [self initWithAPIService:APIService - deviceTokenGenerator:[DCDevice currentDevice] - backoffWrapper:backoffWrapper]; -} - - (nullable instancetype)initWithApp:(FIRApp *)app { NSArray *missingOptionsFields = [FIRAppCheckValidator tokenExchangeMissingFieldsInOptions:app.options]; @@ -82,74 +58,42 @@ - (nullable instancetype)initWithApp:(FIRApp *)app { return nil; } - NSURLSession *URLSession = [NSURLSession - sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; - - FIRAppCheckAPIService *APIService = - [[FIRAppCheckAPIService alloc] initWithURLSession:URLSession - APIKey:app.options.APIKey - appID:app.options.googleAppID - heartbeatLogger:app.heartbeatLogger]; - - FIRDeviceCheckAPIService *deviceCheckAPIService = - [[FIRDeviceCheckAPIService alloc] initWithAPIService:APIService - projectID:app.options.projectID - appID:app.options.googleAppID]; + GACDeviceCheckProvider *deviceCheckProvider = + [[GACDeviceCheckProvider alloc] initWithServiceName:app.name + resourceName:app.resourceName + APIKey:app.options.APIKey + requestHooks:@[ [app.heartbeatLogger requestHook] ]]; - return [self initWithAPIService:deviceCheckAPIService]; + return [self initWithDeviceCheckProvider:deviceCheckProvider]; } #pragma mark - FIRAppCheckProvider - (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable token, NSError *_Nullable error))handler { - [self.backoffWrapper - applyBackoffToOperation:^FBLPromise *_Nonnull { - return [self getTokenPromise]; - } - errorHandler:[self.backoffWrapper defaultAppCheckProviderErrorHandler]] - // Call the handler with either token or error. - .then(^id(FIRAppCheckToken *appCheckToken) { - handler(appCheckToken, nil); - return nil; - }) - .catch(^void(NSError *error) { - handler(nil, error); - }); + [self.deviceCheckProvider getTokenWithCompletion:^(GACAppCheckToken *_Nullable internalToken, + NSError *_Nullable error) { + if (error) { + handler(nil, error); + return; + } + + handler([[FIRAppCheckToken alloc] initWithInternalToken:internalToken], nil); + }]; } -- (FBLPromise *)getTokenPromise { - // Get DeviceCheck token - return [self deviceToken] - // Exchange DeviceCheck token for FAC token. - .then(^FBLPromise *(NSData *deviceToken) { - return [self.APIService appCheckTokenWithDeviceToken:deviceToken]; - }); -} - -#pragma mark - DeviceCheck - -- (FBLPromise *)deviceToken { - 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; - } +- (void)getLimitedUseTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable, + NSError *_Nullable))handler { + [self.deviceCheckProvider + getLimitedUseTokenWithCompletion:^(GACAppCheckToken *_Nullable internalToken, + NSError *_Nullable error) { + if (error) { + handler(nil, error); + return; + } + + handler([[FIRAppCheckToken alloc] initWithInternalToken:internalToken], nil); + }]; } @end diff --git a/FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckTokenGenerator.h b/FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckTokenGenerator.h deleted file mode 100644 index f1daebbd742..00000000000 --- a/FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckTokenGenerator.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -NS_ASSUME_NONNULL_BEGIN - -@protocol FIRDeviceCheckTokenGenerator - -@property(getter=isSupported, readonly) BOOL supported; - -- (void)generateTokenWithCompletionHandler:(void (^)(NSData* _Nullable token, - NSError* _Nullable error))completion; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckDebugProvider.h b/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckDebugProvider.h index f0efe2c89a0..78b4e0b74bd 100644 --- a/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckDebugProvider.h +++ b/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckDebugProvider.h @@ -19,7 +19,6 @@ #import "FIRAppCheckProvider.h" @class FIRApp; -@protocol FIRAppCheckDebugProviderAPIServiceProtocol; NS_ASSUME_NONNULL_BEGIN diff --git a/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckProvider.h b/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckProvider.h index f07d9302dad..10ccfa323db 100644 --- a/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckProvider.h +++ b/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckProvider.h @@ -32,6 +32,18 @@ NS_SWIFT_NAME(AppCheckProvider) (void (^)(FIRAppCheckToken *_Nullable token, NSError *_Nullable error))handler NS_SWIFT_NAME(getToken(completion:)); +@optional + +/// Returns a new Firebase App Check token. +/// When implementing this method for your custom provider, the token returned should be suitable +/// for consumption in a limited-use scenario. If you do not implement this method, the +/// getTokenWithCompletion will be invoked instead whenever a limited-use token is requested. +/// @param handler The completion handler. Make sure to call the handler with either a token +/// or an error. +- (void)getLimitedUseTokenWithCompletion: + (void (^)(FIRAppCheckToken *_Nullable token, NSError *_Nullable error))handler + NS_SWIFT_NAME(getLimitedUseToken(completion:)); + @end NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Tests/Integration/AppCheckE2ETests.swift b/FirebaseAppCheck/Tests/Integration/AppCheckE2ETests.swift index 18b9d3dddc4..fc8c025d6d0 100644 --- a/FirebaseAppCheck/Tests/Integration/AppCheckE2ETests.swift +++ b/FirebaseAppCheck/Tests/Integration/AppCheckE2ETests.swift @@ -101,10 +101,28 @@ final class AppCheckE2ETests: XCTestCase { wait(for: [expectation], timeout: 0.5) } #endif // !os(macOS) && !targetEnvironment(macCatalyst) + + func testGetLimitedUseToken() throws { + guard let appCheck = AppCheck.appCheck(app: app) else { + XCTFail("AppCheck instance is nil.") + return + } + + let expectation = XCTestExpectation() + appCheck.limitedUseToken { token, error in + XCTAssertNil(error) + XCTAssertNotNil(token) + XCTAssertEqual(token!.token, TestAppCheckProvider.limitedUseTokenValue) + expectation.fulfill() + } + + wait(for: [expectation], timeout: 0.5) + } } class TestAppCheckProvider: NSObject, AppCheckProvider { static let tokenValue = "TestToken" + static let limitedUseTokenValue = "TestLimitedUseToken" func getToken(completion handler: @escaping (AppCheckToken?, Error?) -> Void) { let token = AppCheckToken( @@ -113,6 +131,14 @@ class TestAppCheckProvider: NSObject, AppCheckProvider { ) handler(token, nil) } + + func getLimitedUseToken(completion handler: @escaping (AppCheckToken?, Error?) -> Void) { + let token = AppCheckToken( + token: TestAppCheckProvider.limitedUseTokenValue, + expirationDate: Date.distantFuture + ) + handler(token, nil) + } } class TestAppCheckProviderFactory: NSObject, AppCheckProviderFactory { diff --git a/FirebaseAppCheck/Tests/Unit/AppAttestProvider/FIRAppAttestAPIServiceTests.m b/FirebaseAppCheck/Tests/Unit/AppAttestProvider/FIRAppAttestAPIServiceTests.m deleted file mode 100644 index d496b537713..00000000000 --- a/FirebaseAppCheck/Tests/Unit/AppAttestProvider/FIRAppAttestAPIServiceTests.m +++ /dev/null @@ -1,584 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import -#import "FBLPromise+Testing.h" - -#import - -#import "FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAPIService.h" -#import "FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAttestationResponse.h" -#import "FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckAPIService.h" -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckHTTPError.h" -#import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h" -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckErrors.h" - -#import "FirebaseAppCheck/Tests/Unit/Utils/FIRFixtureLoader.h" -#import "SharedTestUtilities/Date/FIRDateTestUtils.h" -#import "SharedTestUtilities/URLSession/FIRURLSessionOCMockStub.h" - -@interface FIRAppAttestAPIServiceTests : XCTestCase - -@property(nonatomic) FIRAppAttestAPIService *appAttestAPIService; - -@property(nonatomic) id mockAPIService; - -@property(nonatomic) NSString *projectID; -@property(nonatomic) NSString *appID; - -@end - -@implementation FIRAppAttestAPIServiceTests - -- (void)setUp { - [super setUp]; - - self.projectID = @"project_id"; - self.appID = @"app_id"; - - self.mockAPIService = OCMProtocolMock(@protocol(FIRAppCheckAPIServiceProtocol)); - OCMStub([self.mockAPIService baseURL]).andReturn(@"https://test.appcheck.url.com/beta"); - - self.appAttestAPIService = [[FIRAppAttestAPIService alloc] initWithAPIService:self.mockAPIService - projectID:self.projectID - appID:self.appID]; -} - -- (void)tearDown { - [super tearDown]; - - self.appAttestAPIService = nil; - [self.mockAPIService stopMocking]; - self.mockAPIService = nil; -} - -#pragma mark - Random challenge request - -- (void)testGetRandomChallengeWhenAPIResponseValid { - // 1. Prepare API response. - NSData *responseBody = [FIRFixtureLoader loadFixtureNamed:@"AppAttestResponseSuccess.json"]; - GULURLSessionDataResponse *validAPIResponse = [self APIResponseWithCode:200 - responseBody:responseBody]; - // 2. Stub API Service Request to return prepared API response. - [self stubMockAPIServiceRequestForChallengeRequestWithResponse:validAPIResponse]; - - // 3. Request the random challenge and verify results. - __auto_type *promise = [self.appAttestAPIService getRandomChallenge]; - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - XCTAssert(promise.isFulfilled); - XCTAssertNotNil(promise.value); - XCTAssertNil(promise.error); - - NSString *challengeString = [[NSString alloc] initWithData:promise.value - encoding:NSUTF8StringEncoding]; - // The challenge stored in `AppAttestResponseSuccess.json` is a valid base64 encoding of - // the string "random_challenge". - XCTAssert([challengeString isEqualToString:@"random_challenge"]); - - OCMVerifyAll(self.mockAPIService); -} - -- (void)testGetRandomChallengeWhenAPIError { - // 1. Prepare API response. - NSString *responseBodyString = @"Generate challenge failed with invalid format."; - NSData *responseBody = [responseBodyString dataUsingEncoding:NSUTF8StringEncoding]; - GULURLSessionDataResponse *invalidAPIResponse = [self APIResponseWithCode:300 - responseBody:responseBody]; - FIRAppCheckHTTPError *APIError = - [FIRAppCheckErrorUtil APIErrorWithHTTPResponse:invalidAPIResponse.HTTPResponse - data:invalidAPIResponse.HTTPBody]; - // 2. Stub API Service Request to return prepared API response. - [self stubMockAPIServiceRequestForChallengeRequestWithResponse:APIError]; - - // 3. Request the random challenge and verify results. - __auto_type *promise = [self.appAttestAPIService getRandomChallenge]; - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - XCTAssert(promise.isRejected); - XCTAssertNotNil(promise.error); - XCTAssertNil(promise.value); - - // Assert error is as expected. - XCTAssertEqualObjects(promise.error.domain, FIRAppCheckErrorDomain); - XCTAssertEqual(promise.error.code, FIRAppCheckErrorCodeUnknown); - - // Expect response body and HTTP status code to be included in the error. - NSString *failureReason = promise.error.userInfo[NSLocalizedFailureReasonErrorKey]; - XCTAssertTrue([failureReason containsString:@"300"]); - XCTAssertTrue([failureReason containsString:responseBodyString]); - - OCMVerifyAll(self.mockAPIService); -} - -- (void)testGetRandomChallengeWhenAPIResponseEmpty { - // 1. Prepare API response. - NSData *responseBody = [NSData data]; - GULURLSessionDataResponse *emptyAPIResponse = [self APIResponseWithCode:200 - responseBody:responseBody]; - // 2. Stub API Service Request to return prepared API response. - [self stubMockAPIServiceRequestForChallengeRequestWithResponse:emptyAPIResponse]; - - // 3. Request the random challenge and verify results. - __auto_type *promise = [self.appAttestAPIService getRandomChallenge]; - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - XCTAssert(promise.isRejected); - XCTAssertNotNil(promise.error); - XCTAssertNil(promise.value); - - // Expect response body and HTTP status code to be included in the error. - NSString *failureReason = promise.error.userInfo[NSLocalizedFailureReasonErrorKey]; - XCTAssertEqualObjects(failureReason, @"Empty server response body."); - - OCMVerifyAll(self.mockAPIService); -} - -- (void)testGetRandomChallengeWhenAPIResponseInvalidFormat { - // 1. Prepare API response. - NSString *responseBodyString = @"Generate challenge failed with invalid format."; - NSData *responseBody = [responseBodyString dataUsingEncoding:NSUTF8StringEncoding]; - GULURLSessionDataResponse *validAPIResponse = [self APIResponseWithCode:200 - responseBody:responseBody]; - // 2. Stub API Service Request to return prepared API response. - [self stubMockAPIServiceRequestForChallengeRequestWithResponse:validAPIResponse]; - - // 3. Request the random challenge and verify results. - __auto_type *promise = [self.appAttestAPIService getRandomChallenge]; - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - XCTAssert(promise.isRejected); - XCTAssertNotNil(promise.error); - XCTAssertNil(promise.value); - - // Expect response body and HTTP status code to be included in the error. - NSString *failureReason = promise.error.userInfo[NSLocalizedFailureReasonErrorKey]; - XCTAssertEqualObjects(failureReason, @"JSON serialization error."); - - OCMVerifyAll(self.mockAPIService); -} - -- (void)testGetRandomChallengeWhenResponseMissingField { - [self assertMissingFieldErrorWithFixture:@"AppAttestResponseMissingChallenge.json" - missingField:@"challenge"]; -} - -- (void)assertMissingFieldErrorWithFixture:(NSString *)fixtureName - missingField:(NSString *)fieldName { - // 1. Prepare API response. - NSData *missingFieldBody = [FIRFixtureLoader loadFixtureNamed:fixtureName]; - GULURLSessionDataResponse *incompleteAPIResponse = [self APIResponseWithCode:200 - responseBody:missingFieldBody]; - // 2. Stub API Service Request to return prepared API response. - [self stubMockAPIServiceRequestForChallengeRequestWithResponse:incompleteAPIResponse]; - - // 3. Request the random challenge and verify results. - __auto_type *promise = [self.appAttestAPIService getRandomChallenge]; - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - XCTAssert(promise.isRejected); - XCTAssertNotNil(promise.error); - XCTAssertNil(promise.value); - - // Assert error is as expected. - XCTAssertEqualObjects(promise.error.domain, FIRAppCheckErrorDomain); - XCTAssertEqual(promise.error.code, FIRAppCheckErrorCodeUnknown); - - // Expect missing field name to be included in the error. - NSString *failureReason = promise.error.userInfo[NSLocalizedFailureReasonErrorKey]; - NSString *fieldNameString = [NSString stringWithFormat:@"`%@`", fieldName]; - XCTAssertTrue([failureReason containsString:fieldNameString], - @"Fixture `%@`: expected missing field %@ error not found", fixtureName, - fieldNameString); -} - -#pragma mark - Assertion request - -- (void)testGetAppCheckTokenSuccess { - NSData *artifact = [self generateRandomData]; - NSData *challenge = [self generateRandomData]; - NSData *assertion = [self generateRandomData]; - - // 1. Prepare response. - NSData *responseBody = - [FIRFixtureLoader loadFixtureNamed:@"FACTokenExchangeResponseSuccess.json"]; - GULURLSessionDataResponse *validAPIResponse = [self APIResponseWithCode:200 - responseBody:responseBody]; - - // 2. Stub API Service - // 2.1. Return prepared response. - [self expectTokenAPIRequestWithArtifact:artifact - challenge:challenge - assertion:assertion - response:validAPIResponse - error:nil]; - // 2.2. Return token from parsed response. - FIRAppCheckToken *expectedToken = [[FIRAppCheckToken alloc] initWithToken:@"app_check_token" - expirationDate:[NSDate date] - receivedAtDate:[NSDate date]]; - [self expectTokenWithAPIReponse:validAPIResponse toReturnToken:expectedToken]; - - // 3. Send request. - __auto_type promise = [self.appAttestAPIService getAppCheckTokenWithArtifact:artifact - challenge:challenge - assertion:assertion]; - // 4. Verify. - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - - XCTAssertTrue(promise.isFulfilled); - XCTAssertNil(promise.error); - - XCTAssertEqualObjects(promise.value, expectedToken); - XCTAssertEqualObjects(promise.value.token, expectedToken.token); - XCTAssertEqualObjects(promise.value.expirationDate, expectedToken.expirationDate); - XCTAssertEqualObjects(promise.value.receivedAtDate, expectedToken.receivedAtDate); - - OCMVerifyAll(self.mockAPIService); -} - -- (void)testGetAppCheckTokenNetworkError { - NSData *artifact = [self generateRandomData]; - NSData *challenge = [self generateRandomData]; - NSData *assertion = [self generateRandomData]; - - // 1. Prepare response. - NSData *responseBody = - [FIRFixtureLoader loadFixtureNamed:@"FACTokenExchangeResponseSuccess.json"]; - GULURLSessionDataResponse *validAPIResponse = [self APIResponseWithCode:200 - responseBody:responseBody]; - - // 2. Stub API Service - // 2.1. Return prepared response. - NSError *networkError = [NSError errorWithDomain:self.name code:0 userInfo:nil]; - [self expectTokenAPIRequestWithArtifact:artifact - challenge:challenge - assertion:assertion - response:validAPIResponse - error:networkError]; - - // 3. Send request. - __auto_type promise = [self.appAttestAPIService getAppCheckTokenWithArtifact:artifact - challenge:challenge - assertion:assertion]; - // 4. Verify. - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - - XCTAssertTrue(promise.isRejected); - XCTAssertNil(promise.value); - XCTAssertEqualObjects(promise.error, networkError); - - OCMVerifyAll(self.mockAPIService); -} - -- (void)testGetAppCheckTokenUnexpectedResponse { - NSData *artifact = [self generateRandomData]; - NSData *challenge = [self generateRandomData]; - NSData *assertion = [self generateRandomData]; - - // 1. Prepare response. - NSData *responseBody = - [FIRFixtureLoader loadFixtureNamed:@"DeviceCheckResponseMissingToken.json"]; - GULURLSessionDataResponse *validAPIResponse = [self APIResponseWithCode:200 - responseBody:responseBody]; - - // 2. Stub API Service - // 2.1. Return prepared response. - [self expectTokenAPIRequestWithArtifact:artifact - challenge:challenge - assertion:assertion - response:validAPIResponse - error:nil]; - // 2.2. Return token from parsed response. - [self expectTokenWithAPIReponse:validAPIResponse toReturnToken:nil]; - - // 3. Send request. - __auto_type promise = [self.appAttestAPIService getAppCheckTokenWithArtifact:artifact - challenge:challenge - assertion:assertion]; - // 4. Verify. - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - - XCTAssertTrue(promise.isRejected); - XCTAssertNil(promise.value); - XCTAssertNotNil(promise.error); - - OCMVerifyAll(self.mockAPIService); -} - -#pragma mark - Attestation request - -- (void)testAttestKeySuccess { - NSData *attestation = [self generateRandomData]; - NSData *challenge = [self generateRandomData]; - NSString *keyID = [NSUUID UUID].UUIDString; - - // 1. Prepare response. - NSData *responseBody = - [FIRFixtureLoader loadFixtureNamed:@"AppAttestAttestationResponseSuccess.json"]; - GULURLSessionDataResponse *validAPIResponse = [self APIResponseWithCode:200 - responseBody:responseBody]; - - // 2. Stub API Service - // 2.1. Return prepared response. - [self expectAttestAPIRequestWithAttestation:attestation - keyID:keyID - challenge:challenge - response:validAPIResponse - error:nil]; - - // 3. Send request. - __auto_type promise = [self.appAttestAPIService attestKeyWithAttestation:attestation - keyID:keyID - challenge:challenge]; - - // 4. Verify. - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - - XCTAssertTrue(promise.isFulfilled); - XCTAssertNil(promise.error); - - NSData *expectedArtifact = - [@"valid Firebase app attest artifact" dataUsingEncoding:NSUTF8StringEncoding]; - - XCTAssertEqualObjects(promise.value.artifact, expectedArtifact); - XCTAssertEqualObjects(promise.value.token.token, @"valid_app_check_token"); - XCTAssertTrue([FIRDateTestUtils isDate:promise.value.token.expirationDate - approximatelyEqualCurrentPlusTimeInterval:1800 - precision:10]); - - OCMVerifyAll(self.mockAPIService); -} - -- (void)testAttestKeyNetworkError { - NSData *attestation = [self generateRandomData]; - NSData *challenge = [self generateRandomData]; - NSString *keyID = [NSUUID UUID].UUIDString; - - // 1. Stub API Service - // 1.1. Return prepared response. - NSError *networkError = [NSError errorWithDomain:self.name code:0 userInfo:nil]; - [self expectAttestAPIRequestWithAttestation:attestation - keyID:keyID - challenge:challenge - response:nil - error:networkError]; - - // 2. Send request. - __auto_type promise = [self.appAttestAPIService attestKeyWithAttestation:attestation - keyID:keyID - challenge:challenge]; - - // 3. Verify. - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - - XCTAssertTrue(promise.isRejected); - XCTAssertNil(promise.value); - XCTAssertEqualObjects(promise.error, networkError); - - OCMVerifyAll(self.mockAPIService); -} - -- (void)testAttestKeyUnexpectedResponse { - NSData *attestation = [self generateRandomData]; - NSData *challenge = [self generateRandomData]; - NSString *keyID = [NSUUID UUID].UUIDString; - - // 1. Prepare unexpected response. - NSData *responseBody = - [FIRFixtureLoader loadFixtureNamed:@"FACTokenExchangeResponseSuccess.json"]; - GULURLSessionDataResponse *validAPIResponse = [self APIResponseWithCode:200 - responseBody:responseBody]; - - // 2. Stub API Service - // 2.1. Return prepared response. - [self expectAttestAPIRequestWithAttestation:attestation - keyID:keyID - challenge:challenge - response:validAPIResponse - error:nil]; - - // 3. Send request. - __auto_type promise = [self.appAttestAPIService attestKeyWithAttestation:attestation - keyID:keyID - challenge:challenge]; - - // 4. Verify. - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - - XCTAssertTrue(promise.isRejected); - XCTAssertNil(promise.value); - XCTAssertNotNil(promise.error); - - OCMVerifyAll(self.mockAPIService); -} - -#pragma mark - Helpers - -- (GULURLSessionDataResponse *)APIResponseWithCode:(NSInteger)code - responseBody:(NSData *)responseBody { - XCTAssertNotNil(responseBody); - NSHTTPURLResponse *HTTPResponse = [FIRURLSessionOCMockStub HTTPResponseWithCode:code]; - GULURLSessionDataResponse *APIResponse = - [[GULURLSessionDataResponse alloc] initWithResponse:HTTPResponse HTTPBody:responseBody]; - return APIResponse; -} - -- (void)stubMockAPIServiceRequestForChallengeRequestWithResponse:(id)response { - id URLValidationArg = [self URLValidationArgumentWithResource:@"generateAppAttestChallenge"]; - OCMStub([self.mockAPIService sendRequestWithURL:URLValidationArg - HTTPMethod:@"POST" - body:nil - additionalHeaders:nil]) - .andDo(^(NSInvocation *invocation) { - XCTAssertFalse([NSThread isMainThread]); - }) - .andReturn([FBLPromise resolvedWith:response]); -} - -- (id)URLValidationArgumentWithResource:(NSString *)resource { - NSString *expectedRequestURL = - [NSString stringWithFormat:@"%@/projects/%@/apps/%@:%@", [self.mockAPIService baseURL], - self.projectID, self.appID, resource]; - - id URLValidationArg = [OCMArg checkWithBlock:^BOOL(NSURL *URL) { - XCTAssertEqualObjects(URL.absoluteString, expectedRequestURL); - return YES; - }]; - return URLValidationArg; -} - -- (void)expectTokenAPIRequestWithArtifact:(NSData *)attestation - challenge:(NSData *)challenge - assertion:(NSData *)assertion - response:(nullable GULURLSessionDataResponse *)response - error:(nullable NSError *)error { - id URLValidationArg = [self URLValidationArgumentWithResource:@"exchangeAppAttestAssertion"]; - - id bodyValidationArg = [OCMArg checkWithBlock:^BOOL(NSData *requestBody) { - NSDictionary *decodedData = [NSJSONSerialization JSONObjectWithData:requestBody - options:0 - error:nil]; - - XCTAssert([decodedData isKindOfClass:[NSDictionary class]]); - - // Validate artifact field. - NSString *base64EncodedArtifact = decodedData[@"artifact"]; - XCTAssert([base64EncodedArtifact isKindOfClass:[NSString class]]); - - NSData *decodedAttestation = [[NSData alloc] initWithBase64EncodedString:base64EncodedArtifact - options:0]; - XCTAssertEqualObjects(decodedAttestation, attestation); - - // Validate challenge field. - NSString *base64EncodedChallenge = decodedData[@"challenge"]; - XCTAssert([base64EncodedChallenge isKindOfClass:[NSString class]]); - - NSData *decodedChallenge = [[NSData alloc] initWithBase64EncodedString:base64EncodedChallenge - options:0]; - XCTAssertEqualObjects(decodedChallenge, challenge); - - // Validate assertion field. - NSString *base64EncodedAssertion = decodedData[@"assertion"]; - XCTAssert([base64EncodedAssertion isKindOfClass:[NSString class]]); - - NSData *decodedAssertion = [[NSData alloc] initWithBase64EncodedString:base64EncodedAssertion - options:0]; - XCTAssertEqualObjects(decodedAssertion, assertion); - - return YES; - }]; - - FBLPromise *responsePromise = [FBLPromise pendingPromise]; - if (error) { - [responsePromise reject:error]; - } else { - [responsePromise fulfill:response]; - } - OCMExpect([self.mockAPIService sendRequestWithURL:URLValidationArg - HTTPMethod:@"POST" - body:bodyValidationArg - additionalHeaders:@{@"Content-Type" : @"application/json"}]) - .andReturn(responsePromise); -} - -- (void)expectTokenWithAPIReponse:(nonnull GULURLSessionDataResponse *)response - toReturnToken:(nullable FIRAppCheckToken *)token { - FBLPromise *tokenPromise = [FBLPromise pendingPromise]; - if (token) { - [tokenPromise fulfill:token]; - } else { - NSError *tokenError = [NSError errorWithDomain:self.name code:0 userInfo:nil]; - [tokenPromise reject:tokenError]; - } - OCMExpect([self.mockAPIService appCheckTokenWithAPIResponse:response]).andReturn(tokenPromise); -} - -- (void)expectAttestAPIRequestWithAttestation:(NSData *)attestation - keyID:(NSString *)keyID - challenge:(NSData *)challenge - response:(nullable GULURLSessionDataResponse *)response - error:(nullable NSError *)error { - id URLValidationArg = [self URLValidationArgumentWithResource:@"exchangeAppAttestAttestation"]; - - id bodyValidationArg = [OCMArg checkWithBlock:^BOOL(NSData *requestBody) { - NSDictionary *decodedData = [NSJSONSerialization JSONObjectWithData:requestBody - options:0 - error:nil]; - - XCTAssert([decodedData isKindOfClass:[NSDictionary class]]); - - // Validate attestation field. - NSString *base64EncodedAttestation = decodedData[@"attestation_statement"]; - XCTAssert([base64EncodedAttestation isKindOfClass:[NSString class]]); - - NSData *decodedAttestation = - [[NSData alloc] initWithBase64EncodedString:base64EncodedAttestation options:0]; - XCTAssertEqualObjects(decodedAttestation, attestation); - - // Validate challenge field. - NSString *base64EncodedChallenge = decodedData[@"challenge"]; - XCTAssert([base64EncodedAttestation isKindOfClass:[NSString class]]); - - NSData *decodedChallenge = [[NSData alloc] initWithBase64EncodedString:base64EncodedChallenge - options:0]; - XCTAssertEqualObjects(decodedChallenge, challenge); - - // Validate key ID field. - NSString *keyIDField = decodedData[@"key_id"]; - XCTAssert([base64EncodedAttestation isKindOfClass:[NSString class]]); - - XCTAssertEqualObjects(keyIDField, keyID); - - return YES; - }]; - - FBLPromise *resultPromise = [FBLPromise pendingPromise]; - if (error) { - [resultPromise reject:error]; - } else { - [resultPromise fulfill:response]; - } - - OCMExpect([self.mockAPIService sendRequestWithURL:URLValidationArg - HTTPMethod:@"POST" - body:bodyValidationArg - additionalHeaders:@{@"Content-Type" : @"application/json"}]) - .andReturn(resultPromise); -} - -- (NSData *)generateRandomData { - return [[NSUUID UUID].UUIDString dataUsingEncoding:NSUTF8StringEncoding]; -} - -@end diff --git a/FirebaseAppCheck/Tests/Unit/AppAttestProvider/FIRAppAttestProviderTests.m b/FirebaseAppCheck/Tests/Unit/AppAttestProvider/FIRAppAttestProviderTests.m index ac5d499d90b..3d8ef2f3e6c 100644 --- a/FirebaseAppCheck/Tests/Unit/AppAttestProvider/FIRAppAttestProviderTests.m +++ b/FirebaseAppCheck/Tests/Unit/AppAttestProvider/FIRAppAttestProviderTests.m @@ -16,52 +16,33 @@ #import +#import #import #import -#import "FBLPromise+Testing.h" +#import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h" #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppAttestProvider.h" -#import "FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAPIService.h" -#import "FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAttestationResponse.h" -#import "FirebaseAppCheck/Sources/AppAttestProvider/Errors/FIRAppAttestRejectionError.h" -#import "FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestService.h" -#import "FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestArtifactStorage.h" -#import "FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestKeyIDStorage.h" -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckHTTPError.h" -#import "FirebaseAppCheck/Sources/Core/Utils/FIRAppCheckCryptoUtils.h" -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckAvailability.h" -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckToken.h" - #import "FirebaseCore/Extension/FirebaseCoreInternal.h" -#import "SharedTestUtilities/AppCheckBackoffWrapperFake/FIRAppCheckBackoffWrapperFake.h" +static NSString *const kAppName = @"test_app_name"; +static NSString *const kAppID = @"test_app_id"; +static NSString *const kAPIKey = @"test_api_key"; +static NSString *const kProjectID = @"test_project_id"; +static NSString *const kProjectNumber = @"123456789"; FIR_APP_ATTEST_PROVIDER_AVAILABILITY @interface FIRAppAttestProvider (Tests) -- (instancetype)initWithAppAttestService:(id)appAttestService - APIService:(id)APIService - keyIDStorage:(id)keyIDStorage - artifactStorage:(id)artifactStorage - backoffWrapper:(id)backoffWrapper; +- (instancetype)initWithAppAttestProvider:(GACAppAttestProvider *)appAttestProvider; @end FIR_APP_ATTEST_PROVIDER_AVAILABILITY @interface FIRAppAttestProviderTests : XCTestCase +@property(nonatomic, copy) NSString *resourceName; +@property(nonatomic) id appAttestProviderMock; @property(nonatomic) FIRAppAttestProvider *provider; -@property(nonatomic) OCMockObject *mockAppAttestService; -@property(nonatomic) OCMockObject *mockAPIService; -@property(nonatomic) OCMockObject *mockStorage; -@property(nonatomic) OCMockObject *mockArtifactStorage; - -@property(nonatomic) NSData *randomChallenge; -@property(nonatomic) NSData *randomChallengeHash; - -@property(nonatomic) FIRAppCheckBackoffWrapperFake *fakeBackoffWrapper; - @end @implementation FIRAppAttestProviderTests @@ -69,1099 +50,68 @@ @implementation FIRAppAttestProviderTests - (void)setUp { [super setUp]; - self.mockAppAttestService = OCMProtocolMock(@protocol(FIRAppAttestService)); - self.mockAPIService = OCMProtocolMock(@protocol(FIRAppAttestAPIServiceProtocol)); - self.mockStorage = OCMProtocolMock(@protocol(FIRAppAttestKeyIDStorageProtocol)); - self.mockArtifactStorage = OCMProtocolMock(@protocol(FIRAppAttestArtifactStorageProtocol)); - - self.fakeBackoffWrapper = [[FIRAppCheckBackoffWrapperFake alloc] init]; - // Don't backoff by default. - self.fakeBackoffWrapper.isNextOperationAllowed = YES; - - self.provider = [[FIRAppAttestProvider alloc] initWithAppAttestService:self.mockAppAttestService - APIService:self.mockAPIService - keyIDStorage:self.mockStorage - artifactStorage:self.mockArtifactStorage - backoffWrapper:self.fakeBackoffWrapper]; - - self.randomChallenge = [@"random challenge" dataUsingEncoding:NSUTF8StringEncoding]; - self.randomChallengeHash = - [[NSData alloc] initWithBase64EncodedString:@"vEq8yE9g+WwfifNqC2wsXN9M3NIDeOKpDBVYLpGbUDY=" - options:0]; + self.resourceName = [NSString stringWithFormat:@"projects/%@/apps/%@", kProjectID, kAppID]; + self.appAttestProviderMock = OCMStrictClassMock([GACAppAttestProvider class]); + self.provider = + [[FIRAppAttestProvider alloc] initWithAppAttestProvider:self.appAttestProviderMock]; } - (void)tearDown { self.provider = nil; - self.mockArtifactStorage = nil; - self.mockStorage = nil; - self.mockAPIService = nil; - self.mockAppAttestService = nil; - self.fakeBackoffWrapper = nil; + [self.appAttestProviderMock stopMocking]; + self.appAttestProviderMock = nil; } #pragma mark - Init tests -#if !TARGET_OS_MACCATALYST -// Keychain dependent logic require additional configuration on Catalyst (enabling Keychain -// sharing). For now, keychain dependent tests are disabled for Catalyst. - (void)testInitWithValidApp { - FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"app_id" GCMSenderID:@"sender_id"]; - options.APIKey = @"api_key"; - options.projectID = @"project_id"; - FIRApp *app = [[FIRApp alloc] initInstanceWithName:@"testInitWithValidApp" options:options]; + FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:kAppID GCMSenderID:kProjectNumber]; + options.APIKey = kAPIKey; + options.projectID = kProjectID; + FIRApp *app = [[FIRApp alloc] initInstanceWithName:kAppName options:options]; + // The following disables automatic token refresh, which could interfere with tests. + app.dataCollectionDefaultEnabled = NO; XCTAssertNotNil([[FIRAppAttestProvider alloc] initWithApp:app]); } -#endif // !TARGET_OS_MACCATALYST - -#pragma mark - Initial handshake (attestation) - -- (void)testGetTokenWhenAppAttestIsNotSupported { - NSError *expectedError = - [FIRAppCheckErrorUtil unsupportedAttestationProvider:@"AppAttestProvider"]; - - // 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 FIRAppAttestService.isSupported. - [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(NO)]; - - // 2. Don't expect other operations. - OCMReject([self.mockStorage getAppAttestKeyID]); - OCMReject([self.mockAppAttestService generateKeyWithCompletionHandler:OCMOCK_ANY]); - OCMReject([self.mockArtifactStorage getArtifactForKey:OCMOCK_ANY]); - OCMReject([self.mockAPIService getRandomChallenge]); - OCMReject([self.mockStorage setAppAttestKeyID:OCMOCK_ANY]); - OCMReject([self.mockAppAttestService attestKey:OCMOCK_ANY - clientDataHash:OCMOCK_ANY - completionHandler:OCMOCK_ANY]); - OCMReject([self.mockAPIService attestKeyWithAttestation:OCMOCK_ANY - keyID:OCMOCK_ANY - challenge:OCMOCK_ANY]); - - // 3. Call get token. - 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 mocks. - [self verifyAllMocks]; -} - -- (void)testGetToken_WhenNoExistingKey_Success { - // 0. Expect backoff wrapper to be used. - self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"]; - - // 1. Expect FIRAppAttestService.isSupported. - [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)]; - - // 2. Expect storage getAppAttestKeyID. - FBLPromise *rejectedPromise = [FBLPromise pendingPromise]; - NSError *error = [NSError errorWithDomain:@"testGetToken_WhenNoExistingKey_Success" - code:NSNotFound - userInfo:nil]; - [rejectedPromise reject:error]; - OCMExpect([self.mockStorage getAppAttestKeyID]).andReturn(rejectedPromise); - - // 3. Expect App Attest key to be generated. - NSString *generatedKeyID = @"generatedKeyID"; - id completionArg = [OCMArg invokeBlockWithArgs:generatedKeyID, [NSNull null], nil]; - OCMExpect([self.mockAppAttestService generateKeyWithCompletionHandler:completionArg]); - - // 4. Expect the key ID to be stored. - OCMExpect([self.mockStorage setAppAttestKeyID:generatedKeyID]) - .andReturn([FBLPromise resolvedWith:generatedKeyID]); - - // 5. Expect random challenge to be requested. - OCMExpect([self.mockAPIService getRandomChallenge]) - .andReturn([FBLPromise resolvedWith:self.randomChallenge]); - - // 6. Expect the key to be attested with the challenge. - NSData *attestationData = [@"attestation data" dataUsingEncoding:NSUTF8StringEncoding]; - id attestCompletionArg = [OCMArg invokeBlockWithArgs:attestationData, [NSNull null], nil]; - OCMExpect([self.mockAppAttestService attestKey:generatedKeyID - clientDataHash:self.randomChallengeHash - completionHandler:attestCompletionArg]); - - // 7. Expect key attestation request to be sent. - FIRAppCheckToken *FACToken = [[FIRAppCheckToken alloc] initWithToken:@"FAC token" - expirationDate:[NSDate date]]; - NSData *artifactData = [@"attestation artifact" dataUsingEncoding:NSUTF8StringEncoding]; - __auto_type attestKeyResponse = - [[FIRAppAttestAttestationResponse alloc] initWithArtifact:artifactData token:FACToken]; - OCMExpect([self.mockAPIService attestKeyWithAttestation:attestationData - keyID:generatedKeyID - challenge:self.randomChallenge]) - .andReturn([FBLPromise resolvedWith:attestKeyResponse]); - - // 8. Expect the artifact received from Firebase backend to be saved. - OCMExpect([self.mockArtifactStorage setArtifact:artifactData forKey:generatedKeyID]) - .andReturn([FBLPromise resolvedWith:artifactData]); - - // 9. Call get token. - XCTestExpectation *completionExpectation = - [self expectationWithDescription:@"completionExpectation"]; - [self.provider - getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - [completionExpectation fulfill]; - - XCTAssertEqualObjects(token.token, FACToken.token); - XCTAssertEqualObjects(token.expirationDate, FACToken.expirationDate); - XCTAssertNil(error); - }]; - - [self waitForExpectations:@[ self.fakeBackoffWrapper.backoffExpectation, completionExpectation ] - timeout:0.5 - enforceOrder:YES]; - - // 10. Verify mocks. - [self verifyAllMocks]; - - // 11. Verify backoff result. - XCTAssertEqualObjects(((FIRAppCheckToken *)self.fakeBackoffWrapper.operationResult).token, - FACToken.token); -} - -- (void)testGetToken_WhenExistingUnregisteredKey_Success { - // 0. Expect backoff wrapper to be used. - self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"]; - - // 1. Expect FIRAppAttestService.isSupported. - [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)]; - - // 2. Expect storage getAppAttestKeyID. - NSString *existingKeyID = @"existingKeyID"; - OCMExpect([self.mockStorage getAppAttestKeyID]) - .andReturn([FBLPromise resolvedWith:existingKeyID]); - - // 3. Don't expect App Attest key to be generated. - OCMReject([self.mockAppAttestService generateKeyWithCompletionHandler:OCMOCK_ANY]); - - // 4. Don't expect the key ID to be stored. - OCMReject([self.mockStorage setAppAttestKeyID:OCMOCK_ANY]); - - // 5. Expect a stored artifact to be requested. - __auto_type rejectedPromise = [self rejectedPromiseWithError:[NSError errorWithDomain:self.name - code:NSNotFound - userInfo:nil]]; - OCMExpect([self.mockArtifactStorage getArtifactForKey:existingKeyID]).andReturn(rejectedPromise); - - // 6. Expect random challenge to be requested. - OCMExpect([self.mockAPIService getRandomChallenge]) - .andReturn([FBLPromise resolvedWith:self.randomChallenge]); - - // 7. Expect the key to be attested with the challenge. - NSData *attestationData = [@"attestation data" dataUsingEncoding:NSUTF8StringEncoding]; - id attestCompletionArg = [OCMArg invokeBlockWithArgs:attestationData, [NSNull null], nil]; - OCMExpect([self.mockAppAttestService attestKey:existingKeyID - clientDataHash:self.randomChallengeHash - completionHandler:attestCompletionArg]); - - // 8. Expect key attestation request to be sent. - FIRAppCheckToken *FACToken = [[FIRAppCheckToken alloc] initWithToken:@"FAC token" - expirationDate:[NSDate date]]; - NSData *artifactData = [@"attestation artifact" dataUsingEncoding:NSUTF8StringEncoding]; - __auto_type attestKeyResponse = - [[FIRAppAttestAttestationResponse alloc] initWithArtifact:artifactData token:FACToken]; - OCMExpect([self.mockAPIService attestKeyWithAttestation:attestationData - keyID:existingKeyID - challenge:self.randomChallenge]) - .andReturn([FBLPromise resolvedWith:attestKeyResponse]); - - // 9. Expect the artifact received from Firebase backend to be saved. - OCMExpect([self.mockArtifactStorage setArtifact:artifactData forKey:existingKeyID]) - .andReturn([FBLPromise resolvedWith:artifactData]); - - // 10. Call get token. - XCTestExpectation *completionExpectation = - [self expectationWithDescription:@"completionExpectation"]; - [self.provider - getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - [completionExpectation fulfill]; - - XCTAssertEqualObjects(token.token, FACToken.token); - XCTAssertEqualObjects(token.expirationDate, FACToken.expirationDate); - XCTAssertNil(error); - }]; - - [self waitForExpectations:@[ self.fakeBackoffWrapper.backoffExpectation, completionExpectation ] - timeout:0.5 - enforceOrder:YES]; - - // 11. Verify mocks. - [self verifyAllMocks]; - - // 12. Verify backoff result. - XCTAssertEqualObjects(((FIRAppCheckToken *)self.fakeBackoffWrapper.operationResult).token, - FACToken.token); -} - -- (void)testGetToken_WhenUnregisteredKeyAndRandomChallengeError { - // 0. Expect backoff wrapper to be used. - self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"]; - - // 1. Expect FIRAppAttestService.isSupported. - [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)]; - - // 2. Expect storage getAppAttestKeyID. - NSString *existingKeyID = @"existingKeyID"; - OCMExpect([self.mockStorage getAppAttestKeyID]) - .andReturn([FBLPromise resolvedWith:existingKeyID]); - - // 3. Expect a stored artifact to be requested. - __auto_type rejectedPromise = [self rejectedPromiseWithError:[NSError errorWithDomain:self.name - code:NSNotFound - userInfo:nil]]; - OCMExpect([self.mockArtifactStorage getArtifactForKey:existingKeyID]).andReturn(rejectedPromise); - - // 4. Expect random challenge to be requested. - NSError *challengeError = [self expectRandomChallengeRequestError]; - - // 5. Don't expect other steps. - OCMReject([self.mockStorage setAppAttestKeyID:OCMOCK_ANY]); - OCMReject([self.mockAppAttestService attestKey:OCMOCK_ANY - clientDataHash:OCMOCK_ANY - completionHandler:OCMOCK_ANY]); - OCMReject([self.mockAPIService attestKeyWithAttestation:OCMOCK_ANY - keyID:OCMOCK_ANY - challenge:OCMOCK_ANY]); - - // 6. Call get token. - XCTestExpectation *completionExpectation = - [self expectationWithDescription:@"completionExpectation"]; - [self.provider - getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - [completionExpectation fulfill]; - - XCTAssertNil(token); - XCTAssertEqualObjects(error, challengeError); - }]; - - [self waitForExpectations:@[ self.fakeBackoffWrapper.backoffExpectation, completionExpectation ] - timeout:0.5 - enforceOrder:YES]; - - // 7. Verify mocks. - [self verifyAllMocks]; - - // 8. Verify backoff error. - XCTAssertEqualObjects(self.fakeBackoffWrapper.operationError, challengeError); -} - -- (void)testGetToken_WhenUnregisteredKeyAndKeyAttestationError { - // 0. Expect backoff wrapper to be used. - self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"]; - - // 1. Expect FIRAppAttestService.isSupported. - [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)]; - - // 2. Expect storage getAppAttestKeyID. - NSString *existingKeyID = @"existingKeyID"; - OCMExpect([self.mockStorage getAppAttestKeyID]) - .andReturn([FBLPromise resolvedWith:existingKeyID]); - - // 3. Expect a stored artifact to be requested. - __auto_type rejectedPromise = [self rejectedPromiseWithError:[NSError errorWithDomain:self.name - code:NSNotFound - userInfo:nil]]; - OCMExpect([self.mockArtifactStorage getArtifactForKey:existingKeyID]).andReturn(rejectedPromise); - - // 4. Expect random challenge to be requested. - OCMExpect([self.mockAPIService getRandomChallenge]) - .andReturn([FBLPromise resolvedWith:self.randomChallenge]); - - // 5. Expect the key to be attested with the challenge. - NSError *attestationError = [NSError errorWithDomain:@"testGetTokenWhenKeyAttestationError" - code:0 - userInfo:nil]; - NSError *expectedError = - [FIRAppCheckErrorUtil appAttestAttestKeyFailedWithError:attestationError - keyId:existingKeyID - clientDataHash:self.randomChallengeHash]; - id attestCompletionArg = [OCMArg invokeBlockWithArgs:[NSNull null], attestationError, nil]; - OCMExpect([self.mockAppAttestService attestKey:existingKeyID - clientDataHash:self.randomChallengeHash - completionHandler:attestCompletionArg]); - - // 6. Don't exchange API request. - OCMReject([self.mockAPIService attestKeyWithAttestation:OCMOCK_ANY - keyID:OCMOCK_ANY - challenge:OCMOCK_ANY]); - - // 7. Call get token. - 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, completionExpectation ] - timeout:0.5 - enforceOrder:YES]; - - // 8. Verify mocks. - [self verifyAllMocks]; - - // 9. Verify backoff error. - XCTAssertEqualObjects(self.fakeBackoffWrapper.operationError, expectedError); -} - -- (void)testGetToken_WhenUnregisteredKeyAndKeyAttestationExchangeError { - // 0. Expect backoff wrapper to be used. - self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"]; - - // 1. Expect FIRAppAttestService.isSupported. - [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)]; - - // 2. Expect storage getAppAttestKeyID. - NSString *existingKeyID = @"existingKeyID"; - OCMExpect([self.mockStorage getAppAttestKeyID]) - .andReturn([FBLPromise resolvedWith:existingKeyID]); - - // 3. Expect a stored artifact to be requested. - __auto_type rejectedPromise = [self rejectedPromiseWithError:[NSError errorWithDomain:self.name - code:NSNotFound - userInfo:nil]]; - OCMExpect([self.mockArtifactStorage getArtifactForKey:existingKeyID]).andReturn(rejectedPromise); - - // 4. Expect random challenge to be requested. - OCMExpect([self.mockAPIService getRandomChallenge]) - .andReturn([FBLPromise resolvedWith:self.randomChallenge]); - - // 5. Expect the key to be attested with the challenge. - NSData *attestationData = [@"attestation data" dataUsingEncoding:NSUTF8StringEncoding]; - id attestCompletionArg = [OCMArg invokeBlockWithArgs:attestationData, [NSNull null], nil]; - OCMExpect([self.mockAppAttestService attestKey:existingKeyID - clientDataHash:self.randomChallengeHash - completionHandler:attestCompletionArg]); - - // 6. Expect exchange request to be sent. - NSError *exchangeError = [NSError errorWithDomain:@"testGetTokenWhenKeyAttestationExchangeError" - code:0 - userInfo:nil]; - OCMExpect([self.mockAPIService attestKeyWithAttestation:attestationData - keyID:existingKeyID - challenge:self.randomChallenge]) - .andReturn([self rejectedPromiseWithError:exchangeError]); - - // 7. Call get token. - XCTestExpectation *completionExpectation = - [self expectationWithDescription:@"completionExpectation"]; - [self.provider - getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - [completionExpectation fulfill]; - - XCTAssertNil(token); - XCTAssertEqualObjects(error, exchangeError); - }]; - - [self waitForExpectations:@[ self.fakeBackoffWrapper.backoffExpectation, completionExpectation ] - timeout:0.5 - enforceOrder:YES]; - - // 8. Verify mocks. - [self verifyAllMocks]; - - // 9. Verify backoff error. - XCTAssertEqualObjects(self.fakeBackoffWrapper.operationError, exchangeError); -} - -#pragma mark Rejected Attestation - -- (void)testGetToken_WhenAttestationIsRejected_ThenAttestationIsResetAndRetriedOnceSuccess { - // 1. Expect App Attest availability to be requested and stored key ID request to fail. - [self expectAppAttestAvailabilityToBeCheckedAndNotExistingStoredKeyRequested]; - - // 2. Expect the App Attest key pair to be generated and attested. - NSString *keyID1 = @"keyID1"; - NSData *attestationData1 = [[NSUUID UUID].UUIDString dataUsingEncoding:NSUTF8StringEncoding]; - [self expectAppAttestKeyGeneratedAndAttestedWithKeyID:keyID1 attestationData:attestationData1]; - - // 3. Expect exchange request to be sent. - FIRAppCheckHTTPError *APIError = [self attestationRejectionHTTPError]; - OCMExpect([self.mockAPIService attestKeyWithAttestation:attestationData1 - keyID:keyID1 - challenge:self.randomChallenge]) - .andReturn([self rejectedPromiseWithError:APIError]); - - // 4. Stored attestation to be reset. - [self expectAttestationReset]; - - // 5. Expect the App Attest key pair to be generated and attested. - NSString *keyID2 = @"keyID2"; - NSData *attestationData2 = [[NSUUID UUID].UUIDString dataUsingEncoding:NSUTF8StringEncoding]; - [self expectAppAttestKeyGeneratedAndAttestedWithKeyID:keyID2 attestationData:attestationData2]; - - // 6. Expect exchange request to be sent. - FIRAppCheckToken *FACToken = [[FIRAppCheckToken alloc] initWithToken:@"FAC token" - expirationDate:[NSDate date]]; - NSData *artifactData = [@"attestation artifact" dataUsingEncoding:NSUTF8StringEncoding]; - __auto_type attestKeyResponse = - [[FIRAppAttestAttestationResponse alloc] initWithArtifact:artifactData token:FACToken]; - OCMExpect([self.mockAPIService attestKeyWithAttestation:attestationData2 - keyID:keyID2 - challenge:self.randomChallenge]) - .andReturn([FBLPromise resolvedWith:attestKeyResponse]); - - // 7. Expect the artifact received from Firebase backend to be saved. - OCMExpect([self.mockArtifactStorage setArtifact:artifactData forKey:keyID2]) - .andReturn([FBLPromise resolvedWith:artifactData]); - - // 8. Call get token. - XCTestExpectation *completionExpectation = - [self expectationWithDescription:@"completionExpectation"]; - [self.provider - getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - [completionExpectation fulfill]; - - XCTAssertEqualObjects(token.token, FACToken.token); - XCTAssertEqualObjects(token.expirationDate, FACToken.expirationDate); - XCTAssertNil(error); - }]; - - [self waitForExpectations:@[ completionExpectation ] timeout:0.5 enforceOrder:YES]; - - // 8. Verify mocks. - [self verifyAllMocks]; -} - -- (void)testGetToken_WhenAttestationIsRejected_ThenAttestationIsResetAndRetriedOnceError { - // 1. Expect App Attest availability to be requested and stored key ID request to fail. - [self expectAppAttestAvailabilityToBeCheckedAndNotExistingStoredKeyRequested]; - - // 2. Expect the App Attest key pair to be generated and attested. - NSString *keyID1 = @"keyID1"; - NSData *attestationData1 = [[NSUUID UUID].UUIDString dataUsingEncoding:NSUTF8StringEncoding]; - [self expectAppAttestKeyGeneratedAndAttestedWithKeyID:keyID1 attestationData:attestationData1]; - - // 3. Expect exchange request to be sent. - FIRAppCheckHTTPError *APIError = [self attestationRejectionHTTPError]; - OCMExpect([self.mockAPIService attestKeyWithAttestation:attestationData1 - keyID:keyID1 - challenge:self.randomChallenge]) - .andReturn([self rejectedPromiseWithError:APIError]); - - // 4. Stored attestation to be reset. - [self expectAttestationReset]; - - // 5. Expect the App Attest key pair to be generated and attested. - NSString *keyID2 = @"keyID2"; - NSData *attestationData2 = [[NSUUID UUID].UUIDString dataUsingEncoding:NSUTF8StringEncoding]; - [self expectAppAttestKeyGeneratedAndAttestedWithKeyID:keyID2 attestationData:attestationData2]; - - // 6. Expect exchange request to be sent. - OCMExpect([self.mockAPIService attestKeyWithAttestation:attestationData2 - keyID:keyID2 - challenge:self.randomChallenge]) - .andReturn([self rejectedPromiseWithError:APIError]); - - // 7. Stored attestation to be reset. - [self expectAttestationReset]; - - // 8. Don't expect the artifact received from Firebase backend to be saved. - OCMReject([self.mockArtifactStorage setArtifact:OCMOCK_ANY forKey:OCMOCK_ANY]); - - // 9. Call get token. - XCTestExpectation *completionExpectation = - [self expectationWithDescription:@"completionExpectation"]; - [self.provider - getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - [completionExpectation fulfill]; - - XCTAssertNil(token); - FIRAppAttestRejectionError *expectedError = [[FIRAppAttestRejectionError alloc] init]; - XCTAssertEqualObjects(error, expectedError); - }]; - - [self waitForExpectations:@[ completionExpectation ] timeout:0.5 enforceOrder:YES]; - - // 9. Verify mocks. - [self verifyAllMocks]; -} - -- (void)testGetToken_WhenExistingKeyIsRejectedByApple_ThenAttestationIsResetAndRetriedOnce_Success { - // 1. Expect FIRAppAttestService.isSupported. - [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)]; - - // 2. Expect storage getAppAttestKeyID. - NSString *existingKeyID = @"existingKeyID"; - OCMExpect([self.mockStorage getAppAttestKeyID]) - .andReturn([FBLPromise resolvedWith:existingKeyID]); - // 3. Expect a stored artifact to be requested. - __auto_type rejectedPromise = [self rejectedPromiseWithError:[NSError errorWithDomain:self.name - code:NSNotFound - userInfo:nil]]; - OCMExpect([self.mockArtifactStorage getArtifactForKey:existingKeyID]).andReturn(rejectedPromise); +- (void)testGetTokenSuccess { + // 1. Stub internal debug provider. + GACAppCheckToken *validInternalToken = [[GACAppCheckToken alloc] initWithToken:@"valid_token" + expirationDate:[NSDate date] + receivedAtDate:[NSDate date]]; + OCMExpect([self.appAttestProviderMock + getTokenWithCompletion:([OCMArg + invokeBlockWithArgs:validInternalToken, [NSNull null], nil])]); - // 4. Expect random challenge to be requested. - OCMExpect([self.mockAPIService getRandomChallenge]) - .andReturn([FBLPromise resolvedWith:self.randomChallenge]); - - // 5. Expect the key to be attested with the challenge. - NSError *attestationError = [NSError errorWithDomain:DCErrorDomain - code:DCErrorInvalidKey - userInfo:nil]; - id attestCompletionArg = [OCMArg invokeBlockWithArgs:[NSNull null], attestationError, nil]; - OCMExpect([self.mockAppAttestService attestKey:existingKeyID - clientDataHash:self.randomChallengeHash - completionHandler:attestCompletionArg]); - - // 6. Stored attestation to be reset. - [self expectAttestationReset]; - - // 7. Expect the App Attest key pair to be generated and attested. - NSString *newKeyID = @"newKeyID"; - NSData *attestationData = [[NSUUID UUID].UUIDString dataUsingEncoding:NSUTF8StringEncoding]; - [self expectAppAttestKeyGeneratedAndAttestedWithKeyID:newKeyID attestationData:attestationData]; - - // 8. Expect exchange request to be sent. - FIRAppCheckToken *FACToken = [[FIRAppCheckToken alloc] initWithToken:@"FAC token" - expirationDate:[NSDate date]]; - NSData *artifactData = [@"attestation artifact" dataUsingEncoding:NSUTF8StringEncoding]; - __auto_type attestKeyResponse = - [[FIRAppAttestAttestationResponse alloc] initWithArtifact:artifactData token:FACToken]; - OCMExpect([self.mockAPIService attestKeyWithAttestation:attestationData - keyID:newKeyID - challenge:self.randomChallenge]) - .andReturn([FBLPromise resolvedWith:attestKeyResponse]); - - // 9. Expect the artifact received from Firebase backend to be saved. - OCMExpect([self.mockArtifactStorage setArtifact:artifactData forKey:newKeyID]) - .andReturn([FBLPromise resolvedWith:artifactData]); - - // 10. Call get token. - XCTestExpectation *completionExpectation = - [self expectationWithDescription:@"completionExpectation"]; + // 2. Validate get token. [self.provider getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - [completionExpectation fulfill]; - - XCTAssertEqualObjects(token.token, FACToken.token); - XCTAssertEqualObjects(token.expirationDate, FACToken.expirationDate); + XCTAssertEqualObjects(token.token, validInternalToken.token); + XCTAssertEqualObjects(token.expirationDate, validInternalToken.expirationDate); + XCTAssertEqualObjects(token.receivedAtDate, validInternalToken.receivedAtDate); XCTAssertNil(error); }]; - [self waitForExpectations:@[ completionExpectation ] timeout:0.5 enforceOrder:YES]; - - // 11. Verify mocks. - [self verifyAllMocks]; -} - -#pragma mark - FAC token refresh (assertion) - -- (void)testGetToken_WhenKeyRegistered_Success { - [self assertGetToken_WhenKeyRegistered_Success]; + // 3. Verify mock debug provider. + OCMVerifyAll(self.appAttestProviderMock); } -- (void)testGetToken_WhenKeyRegisteredAndChallengeRequestError { - // 1. Expect FIRAppAttestService.isSupported. - [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)]; - - // 2. Expect storage getAppAttestKeyID. - NSString *existingKeyID = @"existingKeyID"; - OCMExpect([self.mockStorage getAppAttestKeyID]) - .andReturn([FBLPromise resolvedWith:existingKeyID]); - - // 3. Expect a stored artifact to be requested. - NSData *storedArtifact = [@"storedArtifact" dataUsingEncoding:NSUTF8StringEncoding]; - OCMExpect([self.mockArtifactStorage getArtifactForKey:existingKeyID]) - .andReturn([FBLPromise resolvedWith:storedArtifact]); - - // 4. Expect random challenge to be requested. - NSError *challengeError = [self expectRandomChallengeRequestError]; +- (void)testGetTokenAPIError { + // 1. Stub internal debug provider. + NSError *expectedError = [NSError errorWithDomain:@"testGetTokenAPIError" code:-1 userInfo:nil]; + OCMExpect([self.appAttestProviderMock + getTokenWithCompletion:([OCMArg invokeBlockWithArgs:[NSNull null], expectedError, nil])]); - // 5. Don't expect assertion to be requested. - OCMReject([self.mockAppAttestService generateAssertion:OCMOCK_ANY - clientDataHash:OCMOCK_ANY - completionHandler:OCMOCK_ANY]); - - // 6. Don't expect assertion request to be sent. - OCMReject([self.mockAPIService getAppCheckTokenWithArtifact:OCMOCK_ANY - challenge:OCMOCK_ANY - assertion:OCMOCK_ANY]); - - // 7. Call get token. - XCTestExpectation *completionExpectation = - [self expectationWithDescription:@"completionExpectation"]; + // 2. Validate get token. [self.provider getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - [completionExpectation fulfill]; - - XCTAssertNil(token); - XCTAssertEqualObjects(error, challengeError); - }]; - - [self waitForExpectations:@[ completionExpectation ] timeout:0.5]; - - // 8. Verify mocks. - [self verifyAllMocks]; -} - -- (void)testGetToken_WhenKeyRegisteredAndGenerateAssertionError { - // 1. Expect FIRAppAttestService.isSupported. - [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)]; - - // 2. Expect storage getAppAttestKeyID. - NSString *existingKeyID = @"existingKeyID"; - OCMExpect([self.mockStorage getAppAttestKeyID]) - .andReturn([FBLPromise resolvedWith:existingKeyID]); - - // 3. Expect a stored artifact to be requested. - NSData *storedArtifact = [@"storedArtifact" dataUsingEncoding:NSUTF8StringEncoding]; - OCMExpect([self.mockArtifactStorage getArtifactForKey:existingKeyID]) - .andReturn([FBLPromise resolvedWith:storedArtifact]); - - // 4. Expect random challenge to be requested. - OCMExpect([self.mockAPIService getRandomChallenge]) - .andReturn([FBLPromise resolvedWith:self.randomChallenge]); - - // 5. Don't expect assertion to be requested. - NSError *generateAssertionError = - [NSError errorWithDomain:@"testGetToken_WhenKeyRegisteredAndGenerateAssertionError" - code:0 - userInfo:nil]; - NSError *expectedError = - [FIRAppCheckErrorUtil appAttestGenerateAssertionFailedWithError:generateAssertionError - keyId:existingKeyID - clientDataHash:self.randomChallengeHash]; - id completionBlockArg = [OCMArg invokeBlockWithArgs:[NSNull null], generateAssertionError, nil]; - OCMExpect([self.mockAppAttestService - generateAssertion:existingKeyID - clientDataHash:[self dataHashForAssertionWithArtifactData:storedArtifact] - completionHandler:completionBlockArg]); - - // 6. Don't expect assertion request to be sent. - OCMReject([self.mockAPIService getAppCheckTokenWithArtifact:OCMOCK_ANY - challenge:OCMOCK_ANY - assertion:OCMOCK_ANY]); - - // 7. Call get token. - XCTestExpectation *completionExpectation = - [self expectationWithDescription:@"completionExpectation"]; - [self.provider - getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - [completionExpectation fulfill]; - XCTAssertNil(token); XCTAssertEqualObjects(error, expectedError); }]; - [self waitForExpectations:@[ completionExpectation ] timeout:0.5]; - - // 8. Verify mocks. - [self verifyAllMocks]; -} - -- (void)testGetToken_WhenKeyRegisteredAndTokenExchangeRequestError { - // 1. Expect FIRAppAttestService.isSupported. - [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)]; - - // 2. Expect storage getAppAttestKeyID. - NSString *existingKeyID = @"existingKeyID"; - OCMExpect([self.mockStorage getAppAttestKeyID]) - .andReturn([FBLPromise resolvedWith:existingKeyID]); - - // 3. Expect a stored artifact to be requested. - NSData *storedArtifact = [@"storedArtifact" dataUsingEncoding:NSUTF8StringEncoding]; - OCMExpect([self.mockArtifactStorage getArtifactForKey:existingKeyID]) - .andReturn([FBLPromise resolvedWith:storedArtifact]); - - // 4. Expect random challenge to be requested. - OCMExpect([self.mockAPIService getRandomChallenge]) - .andReturn([FBLPromise resolvedWith:self.randomChallenge]); - - // 5. Don't expect assertion to be requested. - NSData *assertion = [@"generatedAssertion" dataUsingEncoding:NSUTF8StringEncoding]; - id completionBlockArg = [OCMArg invokeBlockWithArgs:assertion, [NSNull null], nil]; - OCMExpect([self.mockAppAttestService - generateAssertion:existingKeyID - clientDataHash:[self dataHashForAssertionWithArtifactData:storedArtifact] - completionHandler:completionBlockArg]); - - // 6. Expect assertion request to be sent. - NSError *tokenExchangeError = - [NSError errorWithDomain:@"testGetToken_WhenKeyRegisteredAndTokenExchangeRequestError" - code:0 - userInfo:nil]; - OCMExpect([self.mockAPIService getAppCheckTokenWithArtifact:storedArtifact - challenge:self.randomChallenge - assertion:assertion]) - .andReturn([self rejectedPromiseWithError:tokenExchangeError]); - - // 7. Call get token. - XCTestExpectation *completionExpectation = - [self expectationWithDescription:@"completionExpectation"]; - [self.provider - getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - [completionExpectation fulfill]; - - XCTAssertNil(token); - XCTAssertEqualObjects(error, tokenExchangeError); - }]; - - [self waitForExpectations:@[ completionExpectation ] timeout:0.5]; - - // 8. Verify mocks. - [self verifyAllMocks]; -} - -#pragma mark - Request merging - -- (void)testGetToken_WhenCalledSeveralTimesSuccess_ThenThereIsOnlyOneOngoingHandshake { - // 0. Expect backoff wrapper to be used only once. - self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"]; - - // 1. Expect FIRAppAttestService.isSupported. - [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)]; - - // 2. Expect storage getAppAttestKeyID. - NSString *existingKeyID = @"existingKeyID"; - OCMExpect([self.mockStorage getAppAttestKeyID]) - .andReturn([FBLPromise resolvedWith:existingKeyID]); - - // 3. Expect a stored artifact to be requested. - NSData *storedArtifact = [@"storedArtifact" dataUsingEncoding:NSUTF8StringEncoding]; - OCMExpect([self.mockArtifactStorage getArtifactForKey:existingKeyID]) - .andReturn([FBLPromise resolvedWith:storedArtifact]); - - // 4. Expect random challenge to be requested. - // 4.1. Create a pending promise to fulfill later. - FBLPromise *challengeRequestPromise = [FBLPromise pendingPromise]; - // 4.2. Stub getRandomChallenge method. - OCMExpect([self.mockAPIService getRandomChallenge]).andReturn(challengeRequestPromise); - - // 5. Expect assertion to be requested. - NSData *assertion = [@"generatedAssertion" dataUsingEncoding:NSUTF8StringEncoding]; - id completionBlockArg = [OCMArg invokeBlockWithArgs:assertion, [NSNull null], nil]; - OCMExpect([self.mockAppAttestService - generateAssertion:existingKeyID - clientDataHash:[self dataHashForAssertionWithArtifactData:storedArtifact] - completionHandler:completionBlockArg]); - - // 6. Expect assertion request to be sent. - FIRAppCheckToken *FACToken = [[FIRAppCheckToken alloc] initWithToken:@"FAC token" - expirationDate:[NSDate date]]; - OCMExpect([self.mockAPIService getAppCheckTokenWithArtifact:storedArtifact - challenge:self.randomChallenge - assertion:assertion]) - .andReturn([FBLPromise resolvedWith:FACToken]); - - // 7. Call get token several times. - NSInteger callsCount = 10; - NSMutableArray *completionExpectations = [NSMutableArray arrayWithCapacity:callsCount]; - - for (NSInteger i = 0; i < callsCount; i++) { - // 7.1 Expect the completion to be called for each get token method called. - XCTestExpectation *completionExpectation = [self - expectationWithDescription:[NSString stringWithFormat:@"completionExpectation%@", @(i)]]; - [completionExpectations addObject:completionExpectation]; - - // 7.2. Call get token. - [self.provider - getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - [completionExpectation fulfill]; - - XCTAssertEqualObjects(token.token, FACToken.token); - XCTAssertEqualObjects(token.expirationDate, FACToken.expirationDate); - XCTAssertNil(error); - }]; - } - - // 7.3. Resolve get challenge promise to finish the operation. - [challengeRequestPromise fulfill:self.randomChallenge]; - - // 7.4. Wait for all completions to be called. - NSArray *expectations = - [completionExpectations arrayByAddingObject:self.fakeBackoffWrapper.backoffExpectation]; - [self waitForExpectations:expectations timeout:1]; - - // 8. Verify mocks. - [self verifyAllMocks]; - - // 9. Check another get token call after. - [self assertGetToken_WhenKeyRegistered_Success]; -} - -- (void)testGetToken_WhenCalledSeveralTimesError_ThenThereIsOnlyOneOngoingHandshake { - // 0. Expect backoff wrapper to be used only once. - self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"]; - - // 1. Expect FIRAppAttestService.isSupported. - [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)]; - - // 2. Expect storage getAppAttestKeyID. - NSString *existingKeyID = @"existingKeyID"; - OCMExpect([self.mockStorage getAppAttestKeyID]) - .andReturn([FBLPromise resolvedWith:existingKeyID]); - - // 3. Expect a stored artifact to be requested. - NSData *storedArtifact = [@"storedArtifact" dataUsingEncoding:NSUTF8StringEncoding]; - OCMExpect([self.mockArtifactStorage getArtifactForKey:existingKeyID]) - .andReturn([FBLPromise resolvedWith:storedArtifact]); - - // 4. Expect random challenge to be requested. - OCMExpect([self.mockAPIService getRandomChallenge]) - .andReturn([FBLPromise resolvedWith:self.randomChallenge]); - - // 5. Don't expect assertion to be requested. - NSData *assertion = [@"generatedAssertion" dataUsingEncoding:NSUTF8StringEncoding]; - id completionBlockArg = [OCMArg invokeBlockWithArgs:assertion, [NSNull null], nil]; - OCMExpect([self.mockAppAttestService - generateAssertion:existingKeyID - clientDataHash:[self dataHashForAssertionWithArtifactData:storedArtifact] - completionHandler:completionBlockArg]); - - // 6. Expect assertion request to be sent. - // 6.1. Create a pending promise to reject later. - FBLPromise *assertionRequestPromise = [FBLPromise pendingPromise]; - // 6.2. Stub assertion request. - OCMExpect([self.mockAPIService getAppCheckTokenWithArtifact:storedArtifact - challenge:self.randomChallenge - assertion:assertion]) - .andReturn(assertionRequestPromise); - // 6.3. Create an expected error to be rejected with later. - NSError *assertionRequestError = [NSError errorWithDomain:self.name code:0 userInfo:nil]; - - // 7. Call get token several times. - NSInteger callsCount = 10; - NSMutableArray *completionExpectations = [NSMutableArray arrayWithCapacity:callsCount]; - - for (NSInteger i = 0; i < callsCount; i++) { - // 7.1 Expect the completion to be called for each get token method called. - XCTestExpectation *completionExpectation = [self - expectationWithDescription:[NSString stringWithFormat:@"completionExpectation%@", @(i)]]; - [completionExpectations addObject:completionExpectation]; - - // 7.2. Call get token. - [self.provider - getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - [completionExpectation fulfill]; - - XCTAssertEqualObjects(error, assertionRequestError); - XCTAssertNil(token); - }]; - } - - // 7.3. Reject get challenge promise to finish the operation. - [assertionRequestPromise reject:assertionRequestError]; - - // 7.4. Wait for all completions to be called. - NSArray *expectations = - [completionExpectations arrayByAddingObject:self.fakeBackoffWrapper.backoffExpectation]; - [self waitForExpectations:expectations timeout:1]; - - // 8. Verify mocks. - [self verifyAllMocks]; - - // 9. Check another get token call after. - [self assertGetToken_WhenKeyRegistered_Success]; -} - -#pragma mark - Backoff tests - -- (void)testGetTokenBackoff { - // 1. Configure backoff. - self.fakeBackoffWrapper.isNextOperationAllowed = NO; - self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"]; - - // 2. Don't expect any operations. - OCMReject([self.mockAppAttestService isSupported]); - OCMReject([self.mockStorage getAppAttestKeyID]); - OCMReject([self.mockAppAttestService generateKeyWithCompletionHandler:OCMOCK_ANY]); - OCMReject([self.mockArtifactStorage getArtifactForKey:OCMOCK_ANY]); - OCMReject([self.mockAPIService getRandomChallenge]); - OCMReject([self.mockStorage setAppAttestKeyID:OCMOCK_ANY]); - OCMReject([self.mockAppAttestService attestKey:OCMOCK_ANY - clientDataHash:OCMOCK_ANY - completionHandler:OCMOCK_ANY]); - OCMReject([self.mockAPIService attestKeyWithAttestation:OCMOCK_ANY - keyID:OCMOCK_ANY - challenge:OCMOCK_ANY]); - - // 3. Call get token. - XCTestExpectation *completionExpectation = - [self expectationWithDescription:@"completionExpectation"]; - [self.provider - getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - [completionExpectation fulfill]; - - XCTAssertNil(token); - XCTAssertEqualObjects(error, self.fakeBackoffWrapper.backoffError); - }]; - - [self waitForExpectations:@[ self.fakeBackoffWrapper.backoffExpectation, completionExpectation ] - timeout:0.5 - enforceOrder:YES]; - - // 4. Verify mocks. - [self verifyAllMocks]; -} - -#pragma mark - Helpers - -- (NSData *)dataHashForAssertionWithArtifactData:(NSData *)artifact { - NSMutableData *statement = [artifact mutableCopy]; - [statement appendData:self.randomChallenge]; - return [FIRAppCheckCryptoUtils sha256HashFromData:statement]; -} - -- (FBLPromise *)rejectedPromiseWithError:(NSError *)error { - FBLPromise *rejectedPromise = [FBLPromise pendingPromise]; - [rejectedPromise reject:error]; - return rejectedPromise; -} - -- (NSError *)expectRandomChallengeRequestError { - NSError *challengeError = [NSError errorWithDomain:@"testGetToken_WhenRandomChallengeError" - code:NSNotFound - userInfo:nil]; - OCMExpect([self.mockAPIService getRandomChallenge]) - .andReturn([self rejectedPromiseWithError:challengeError]); - return challengeError; -} - -- (void)verifyAllMocks { - OCMVerifyAll(self.mockAppAttestService); - OCMVerifyAll(self.mockAPIService); - OCMVerifyAll(self.mockStorage); - OCMVerifyAll(self.mockArtifactStorage); -} - -- (FIRAppCheckHTTPError *)attestationRejectionHTTPError { - NSHTTPURLResponse *response = - [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"http://localhost"] - statusCode:403 - HTTPVersion:@"HTTP/1.1" - headerFields:nil]; - NSData *responseBody = [@"Could not verify attestation" dataUsingEncoding:NSUTF8StringEncoding]; - return [[FIRAppCheckHTTPError alloc] initWithHTTPResponse:response data:responseBody]; -} - -- (void)assertGetToken_WhenKeyRegistered_Success { - // 0. Expect backoff wrapper to be used. - self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"]; - - // 1. Expect FIRAppAttestService.isSupported. - [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)]; - - // 2. Expect storage getAppAttestKeyID. - NSString *existingKeyID = [NSUUID UUID].UUIDString; - OCMExpect([self.mockStorage getAppAttestKeyID]) - .andReturn([FBLPromise resolvedWith:existingKeyID]); - - // 3. Expect a stored artifact to be requested. - NSData *storedArtifact = [[NSUUID UUID].UUIDString dataUsingEncoding:NSUTF8StringEncoding]; - OCMExpect([self.mockArtifactStorage getArtifactForKey:existingKeyID]) - .andReturn([FBLPromise resolvedWith:storedArtifact]); - - // 4. Expect random challenge to be requested. - OCMExpect([self.mockAPIService getRandomChallenge]) - .andReturn([FBLPromise resolvedWith:self.randomChallenge]); - - // 5. Expect assertion to be requested. - NSData *assertion = [[NSUUID UUID].UUIDString dataUsingEncoding:NSUTF8StringEncoding]; - id completionBlockArg = [OCMArg invokeBlockWithArgs:assertion, [NSNull null], nil]; - OCMExpect([self.mockAppAttestService - generateAssertion:existingKeyID - clientDataHash:[self dataHashForAssertionWithArtifactData:storedArtifact] - completionHandler:completionBlockArg]); - - // 6. Expect assertion request to be sent. - FIRAppCheckToken *FACToken = [[FIRAppCheckToken alloc] initWithToken:[NSUUID UUID].UUIDString - expirationDate:[NSDate date]]; - OCMExpect([self.mockAPIService getAppCheckTokenWithArtifact:storedArtifact - challenge:self.randomChallenge - assertion:assertion]) - .andReturn([FBLPromise resolvedWith:FACToken]); - - // 7. Call get token. - XCTestExpectation *completionExpectation = - [self expectationWithDescription:@"completionExpectation"]; - [self.provider - getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - [completionExpectation fulfill]; - - XCTAssertEqualObjects(token.token, FACToken.token); - XCTAssertEqualObjects(token.expirationDate, FACToken.expirationDate); - XCTAssertNil(error); - }]; - - [self waitForExpectations:@[ self.fakeBackoffWrapper.backoffExpectation, completionExpectation ] - timeout:0.5]; - - // 8. Verify mocks. - [self verifyAllMocks]; - - // 9. Verify backoff result. - XCTAssertEqualObjects(((FIRAppCheckToken *)self.fakeBackoffWrapper.operationResult).token, - FACToken.token); -} - -- (void)expectAppAttestAvailabilityToBeCheckedAndNotExistingStoredKeyRequested { - // 1. Expect FIRAppAttestService.isSupported. - [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)]; - - // 2. Expect storage getAppAttestKeyID. - FBLPromise *rejectedPromise = [FBLPromise pendingPromise]; - NSError *error = [NSError errorWithDomain:@"testGetToken_WhenNoExistingKey_Success" - code:NSNotFound - userInfo:nil]; - [rejectedPromise reject:error]; - OCMExpect([self.mockStorage getAppAttestKeyID]).andReturn(rejectedPromise); -} - -- (void)expectAppAttestKeyGeneratedAndAttestedWithKeyID:(NSString *)keyID - attestationData:(NSData *)attestationData { - // 1. Expect App Attest key to be generated. - id completionArg = [OCMArg invokeBlockWithArgs:keyID, [NSNull null], nil]; - OCMExpect([self.mockAppAttestService generateKeyWithCompletionHandler:completionArg]); - - // 2. Expect the key ID to be stored. - OCMExpect([self.mockStorage setAppAttestKeyID:keyID]).andReturn([FBLPromise resolvedWith:keyID]); - - // 3. Expect random challenge to be requested. - OCMExpect([self.mockAPIService getRandomChallenge]) - .andReturn([FBLPromise resolvedWith:self.randomChallenge]); - - // 4. Expect the key to be attested with the challenge. - id attestCompletionArg = [OCMArg invokeBlockWithArgs:attestationData, [NSNull null], nil]; - OCMExpect([self.mockAppAttestService attestKey:keyID - clientDataHash:self.randomChallengeHash - completionHandler:attestCompletionArg]); -} - -- (void)expectAttestationReset { - // 1. Expect stored key ID to be reset. - OCMExpect([self.mockStorage setAppAttestKeyID:nil]).andReturn([FBLPromise resolvedWith:nil]); - - // 2. Expect stored attestation artifact to be reset. - OCMExpect([self.mockArtifactStorage setArtifact:nil forKey:@""]) - .andReturn([FBLPromise resolvedWith:nil]); + // 3. Verify mock debug provider. + OCMVerifyAll(self.appAttestProviderMock); } @end diff --git a/FirebaseAppCheck/Tests/Unit/AppAttestProvider/Storage/FIRAppAttestArtifactStorageTests.m b/FirebaseAppCheck/Tests/Unit/AppAttestProvider/Storage/FIRAppAttestArtifactStorageTests.m deleted file mode 100644 index 6c4d6c32789..00000000000 --- a/FirebaseAppCheck/Tests/Unit/AppAttestProvider/Storage/FIRAppAttestArtifactStorageTests.m +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -// Tests that use the Keychain require a host app and Swift Package Manager -// does not support adding a host app to test targets. -#if !SWIFT_PACKAGE - -// Skip keychain tests on Catalyst and macOS. Tests are skipped because they -// involve interactions with the keychain that require a provisioning profile. -// See go/firebase-macos-keychain-popups for more details. -#if !TARGET_OS_MACCATALYST && !TARGET_OS_OSX - -#import - -#import - -#import - -#import "FBLPromise+Testing.h" - -#import "FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestArtifactStorage.h" -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" - -@interface FIRAppAttestArtifactStorageTests : XCTestCase - -@property(nonatomic) NSString *appName; -@property(nonatomic) NSString *appID; -@property(nonatomic) FIRAppAttestArtifactStorage *storage; - -@end - -@implementation FIRAppAttestArtifactStorageTests - -- (void)setUp { - [super setUp]; - - self.appName = @"FIRAppAttestArtifactStorageTests"; - self.appID = @"1:100000000000:ios:aaaaaaaaaaaaaaaaaaaaaaaa"; - - self.storage = [[FIRAppAttestArtifactStorage alloc] initWithAppName:self.appName - appID:self.appID - accessGroup:nil]; -} - -- (void)tearDown { - self.storage = nil; - [super tearDown]; -} - -- (void)testSetAndGetArtifact { - [self assertSetGetForStorage]; -} - -- (void)testRemoveArtifact { - NSString *keyID = [NSUUID UUID].UUIDString; - - // 1. Save an artifact to storage and check it is stored. - [self assertSetGetForStorage]; - - // 2. Remove artifact. - __auto_type setPromise = [self.storage setArtifact:nil forKey:keyID]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertNil(setPromise.value); - XCTAssertNil(setPromise.error); - - // 3. Check it has been removed. - __auto_type getPromise = [self.storage getArtifactForKey:keyID]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertNil(getPromise.value); - XCTAssertNil(getPromise.error); -} - -- (void)testSetAndGetPerApp { - // Assert storages for apps with the same name can independently set/get artifact. - [self assertIndependentSetGetForStoragesWithAppName1:self.appName - appID1:@"app_id" - appName2:self.appName - appID2:@"app_id_2"]; - // Assert storages for apps with the same app ID can independently set/get artifact. - [self assertIndependentSetGetForStoragesWithAppName1:@"app_1" - appID1:self.appID - appName2:@"app_2" - appID2:self.appID]; - // Assert storages for apps with different info can independently set/get artifact. - [self assertIndependentSetGetForStoragesWithAppName1:@"app_1" - appID1:@"app_id_1" - appName2:@"app_2" - appID2:@"app_id_2"]; -} - -- (void)testSetArtifactForOneKeyGetForAnotherKey { - // Set an artifact for a key. - [self assertSetGetForStorage]; - - // Try to get artifact for a different key. - NSString *keyID = [NSUUID UUID].UUIDString; - __auto_type getPromise = [self.storage getArtifactForKey:keyID]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertNil(getPromise.value); - XCTAssertNil(getPromise.error); -} - -- (void)testSetArtifactForNewKeyRemovesArtifactForOldKey { - // 1. Store an artifact. - NSString *oldKeyID = [self assertSetGetForStorage]; - - // 2. Replace the artifact. - NSString *newKeyID = [self assertSetGetForStorage]; - XCTAssertNotEqualObjects(oldKeyID, newKeyID); - - // 3. Check old artifact was removed. - __auto_type getPromise = [self.storage getArtifactForKey:oldKeyID]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertNil(getPromise.value); - XCTAssertNil(getPromise.error); -} - -- (void)testGetArtifact_KeychainError { - // 1. Set up storage mock. - id mockKeychainStorage = OCMClassMock([GULKeychainStorage class]); - FIRAppAttestArtifactStorage *artifactStorage = - [[FIRAppAttestArtifactStorage alloc] initWithAppName:self.appName - appID:self.appID - keychainStorage:mockKeychainStorage - accessGroup:nil]; - - // 2. Create and expect keychain error. - NSError *gulsKeychainError = [NSError errorWithDomain:@"com.guls.keychain" code:-1 userInfo:nil]; - OCMExpect([mockKeychainStorage getObjectForKey:[OCMArg any] - objectClass:[OCMArg any] - accessGroup:[OCMArg any]]) - .andReturn([FBLPromise resolvedWith:gulsKeychainError]); - - // 3. Get artifact and verify results. - __auto_type getPromise = [artifactStorage getArtifactForKey:@"key"]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertNotNil(getPromise.error); - XCTAssertEqualObjects(getPromise.error, - [FIRAppCheckErrorUtil keychainErrorWithError:gulsKeychainError]); - - // 4. Verify storage mock. - OCMVerifyAll(mockKeychainStorage); -} - -- (void)testSetArtifact_KeychainError { - // 1. Set up storage mock. - id mockKeychainStorage = OCMClassMock([GULKeychainStorage class]); - FIRAppAttestArtifactStorage *artifactStorage = - [[FIRAppAttestArtifactStorage alloc] initWithAppName:self.appName - appID:self.appID - keychainStorage:mockKeychainStorage - accessGroup:nil]; - // 2. Create and expect keychain error. - NSError *gulsKeychainError = [NSError errorWithDomain:@"com.guls.keychain" code:-1 userInfo:nil]; - OCMExpect([mockKeychainStorage setObject:[OCMArg any] - forKey:[OCMArg any] - accessGroup:[OCMArg any]]) - .andReturn([FBLPromise resolvedWith:gulsKeychainError]); - - // 3. Set artifact and verify results. - NSData *artifact = [@"artifact" dataUsingEncoding:NSUTF8StringEncoding]; - __auto_type setPromise = [artifactStorage setArtifact:artifact forKey:@"key"]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertNotNil(setPromise.error); - XCTAssertEqualObjects(setPromise.error, - [FIRAppCheckErrorUtil keychainErrorWithError:gulsKeychainError]); - - // 4. Verify storage mock. - OCMVerifyAll(mockKeychainStorage); -} - -- (void)testRemoveArtifact_KeychainError { - // 1. Set up storage mock. - id mockKeychainStorage = OCMClassMock([GULKeychainStorage class]); - FIRAppAttestArtifactStorage *artifactStorage = - [[FIRAppAttestArtifactStorage alloc] initWithAppName:self.appName - appID:self.appID - keychainStorage:mockKeychainStorage - accessGroup:nil]; - - // 2. Create and expect keychain error. - NSError *gulsKeychainError = [NSError errorWithDomain:@"com.guls.keychain" code:-1 userInfo:nil]; - OCMExpect([mockKeychainStorage removeObjectForKey:[OCMArg any] accessGroup:[OCMArg any]]) - .andReturn([FBLPromise resolvedWith:gulsKeychainError]); - - // 3. Remove artifact and verify results. - __auto_type setPromise = [artifactStorage setArtifact:nil forKey:@"key"]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertNotNil(setPromise.error); - XCTAssertEqualObjects(setPromise.error, - [FIRAppCheckErrorUtil keychainErrorWithError:gulsKeychainError]); - - // 4. Verify storage mock. - OCMVerifyAll(mockKeychainStorage); -} - -#pragma mark - Helpers - -/// Sets a random artifact for a random key and asserts it can be read. -/// @return The random key ID used to set and get the artifact. -- (NSString *)assertSetGetForStorage { - NSData *artifactToSet = [[NSUUID UUID].UUIDString dataUsingEncoding:NSUTF8StringEncoding]; - NSString *keyID = [NSUUID UUID].UUIDString; - - __auto_type setPromise = [self.storage setArtifact:artifactToSet forKey:keyID]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertEqualObjects(setPromise.value, artifactToSet); - XCTAssertNil(setPromise.error); - - __auto_type getPromise = [self.storage getArtifactForKey:keyID]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertEqualObjects(getPromise.value, artifactToSet); - XCTAssertNil(getPromise.error); - - __weak __auto_type weakSelf = self; - [self addTeardownBlock:^{ - // Cleanup storage. - [weakSelf.storage setArtifact:nil forKey:keyID]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - }]; - - return keyID; -} - -- (void)assertIndependentSetGetForStoragesWithAppName1:(NSString *)appName1 - appID1:(NSString *)appID1 - appName2:(NSString *)appName2 - appID2:(NSString *)appID2 { - NSString *keyID = [NSUUID UUID].UUIDString; - - // Create two storages. - FIRAppAttestArtifactStorage *storage1 = - [[FIRAppAttestArtifactStorage alloc] initWithAppName:appName1 appID:appID1 accessGroup:nil]; - FIRAppAttestArtifactStorage *storage2 = - [[FIRAppAttestArtifactStorage alloc] initWithAppName:appName2 appID:appID2 accessGroup:nil]; - // 1. Independently set artifacts for the two storages. - NSData *artifact1 = [@"app_attest_artifact1" dataUsingEncoding:NSUTF8StringEncoding]; - FBLPromise *setPromise1 = [storage1 setArtifact:artifact1 forKey:keyID]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertEqualObjects(setPromise1.value, artifact1); - XCTAssertNil(setPromise1.error); - - NSData *artifact2 = [@"app_attest_artifact2" dataUsingEncoding:NSUTF8StringEncoding]; - __auto_type setPromise2 = [storage2 setArtifact:artifact2 forKey:keyID]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertEqualObjects(setPromise2.value, artifact2); - XCTAssertNil(setPromise2.error); - - // 2. Get artifacts for the two storages. - __auto_type getPromise1 = [storage1 getArtifactForKey:keyID]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertEqualObjects(getPromise1.value, artifact1); - XCTAssertNil(getPromise1.error); - - __auto_type getPromise2 = [storage2 getArtifactForKey:keyID]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertEqualObjects(getPromise2.value, artifact2); - XCTAssertNil(getPromise2.error); - - // 3. Assert that artifacts were set and retrieved independently of one another. - XCTAssertNotEqualObjects(getPromise1.value, getPromise2.value); - - // Cleanup storages. - [storage1 setArtifact:nil forKey:keyID]; - [storage2 setArtifact:nil forKey:keyID]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); -} - -@end - -#endif // !TARGET_OS_MACCATALYST && !TARGET_OS_OSX - -#endif // !SWIFT_PACKAGE diff --git a/FirebaseAppCheck/Tests/Unit/AppAttestProvider/Storage/FIRAppAttestKeyIDStorageTests.m b/FirebaseAppCheck/Tests/Unit/AppAttestProvider/Storage/FIRAppAttestKeyIDStorageTests.m deleted file mode 100644 index f2db101a5e8..00000000000 --- a/FirebaseAppCheck/Tests/Unit/AppAttestProvider/Storage/FIRAppAttestKeyIDStorageTests.m +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import "FBLPromise+Testing.h" - -#import "FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestKeyIDStorage.h" - -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" - -@interface FIRAppAttestKeyIDStorageTests : XCTestCase -@property(nonatomic) NSString *appName; -@property(nonatomic) NSString *appID; -@property(nonatomic) FIRAppAttestKeyIDStorage *storage; -@end - -@implementation FIRAppAttestKeyIDStorageTests - -- (void)setUp { - [super setUp]; - - self.appName = @"FIRAppAttestKeyIDStorageTestsApp"; - self.appID = @"app_id"; - self.storage = [[FIRAppAttestKeyIDStorage alloc] initWithAppName:self.appName appID:self.appID]; -} - -- (void)tearDown { - // Remove the app attest key ID from storage. - [self.storage setAppAttestKeyID:nil]; - FBLWaitForPromisesWithTimeout(1.0); - self.storage = nil; - - [super tearDown]; -} - -- (void)testInitWithApp { - XCTAssertNotNil([[FIRAppAttestKeyIDStorage alloc] initWithAppName:self.appName appID:self.appID]); -} - -- (void)testSetAndGetAppAttestKeyID { - NSString *appAttestKeyID = @"app_attest_key_ID"; - - FBLPromise *setPromise = [self.storage setAppAttestKeyID:appAttestKeyID]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertEqualObjects(setPromise.value, appAttestKeyID); - XCTAssertNil(setPromise.error); - - __auto_type getPromise = [self.storage getAppAttestKeyID]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertEqualObjects(getPromise.value, appAttestKeyID); - XCTAssertNil(getPromise.error); -} - -- (void)testRemoveAppAttestKeyID { - FBLPromise *setPromise = [self.storage setAppAttestKeyID:nil]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertEqualObjects(setPromise.value, nil); - XCTAssertNil(setPromise.error); -} - -- (void)testGetAppAttestKeyID_WhenAppAttestKeyIDNotFoundError { - __auto_type getPromise = [self.storage getAppAttestKeyID]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertNotNil(getPromise.error); - XCTAssertEqualObjects(getPromise.error, [FIRAppCheckErrorUtil appAttestKeyIDNotFound]); -} - -- (void)testSetGetAppAttestKeyIDPerApp { - // Assert storages for apps with the same name can independently set/get app attest key ID. - [self assertIndependentSetGetForStoragesWithAppName1:self.appName - appID1:@"app_id" - appName2:self.appName - appID2:@"app_id_2"]; - // Assert storages for apps with the same app ID can independently set/get app attest key ID. - [self assertIndependentSetGetForStoragesWithAppName1:@"app_1" - appID1:self.appID - appName2:@"app_2" - appID2:self.appID]; - // Assert storages for apps with different info can independently set/get app attest key ID. - [self assertIndependentSetGetForStoragesWithAppName1:@"app_1" - appID1:@"app_id_1" - appName2:@"app_2" - appID2:@"app_id_2"]; -} - -#pragma mark - Helpers - -- (void)assertIndependentSetGetForStoragesWithAppName1:(NSString *)appName1 - appID1:(NSString *)appID1 - appName2:(NSString *)appName2 - appID2:(NSString *)appID2 { - // Create two storages. - FIRAppAttestKeyIDStorage *storage1 = [[FIRAppAttestKeyIDStorage alloc] initWithAppName:appName1 - appID:appID1]; - FIRAppAttestKeyIDStorage *storage2 = [[FIRAppAttestKeyIDStorage alloc] initWithAppName:appName2 - appID:appID2]; - // 1. Independently set app attest key IDs for the two storages. - NSString *appAttestKeyID1 = @"app_attest_key_ID1"; - FBLPromise *setPromise1 = [storage1 setAppAttestKeyID:appAttestKeyID1]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertEqualObjects(setPromise1.value, appAttestKeyID1); - XCTAssertNil(setPromise1.error); - - NSString *appAttestKeyID2 = @"app_attest_key_ID2"; - __auto_type setPromise2 = [storage2 setAppAttestKeyID:appAttestKeyID2]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertEqualObjects(setPromise2.value, appAttestKeyID2); - XCTAssertNil(setPromise2.error); - - // 2. Get app attest key IDs for the two storages. - __auto_type getPromise1 = [storage1 getAppAttestKeyID]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertEqualObjects(getPromise1.value, appAttestKeyID1); - XCTAssertNil(getPromise1.error); - - __auto_type getPromise2 = [storage2 getAppAttestKeyID]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertEqualObjects(getPromise2.value, appAttestKeyID2); - XCTAssertNil(getPromise2.error); - - // 3. Assert that the app attest key IDs were set and retrieved independently of one another. - XCTAssertNotEqualObjects(getPromise1.value, getPromise2.value); - - // Cleanup storages. - [storage1 setAppAttestKeyID:nil]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - [storage2 setAppAttestKeyID:nil]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); -} - -@end diff --git a/FirebaseAppCheck/Tests/Unit/Core/Errors/FIRAppCheckErrorUtilTests.m b/FirebaseAppCheck/Tests/Unit/Core/Errors/FIRAppCheckErrorUtilTests.m new file mode 100644 index 00000000000..5d5d0f3098c --- /dev/null +++ b/FirebaseAppCheck/Tests/Unit/Core/Errors/FIRAppCheckErrorUtilTests.m @@ -0,0 +1,169 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" + +#import + +#import + +static NSString *const kTestErrorDomain = @"com.google.test.error-domain"; +static NSInteger kTestErrorCode = 42; + +@interface FIRAppCheckErrorUtilTests : XCTestCase + +@property(nonatomic) NSError *underlyingError; +@property(nonatomic) NSDictionary *userInfo; + +@end + +@implementation FIRAppCheckErrorUtilTests + +- (void)setUp { + self.underlyingError = [NSError errorWithDomain:kTestErrorDomain + code:kTestErrorCode + userInfo:nil]; + self.userInfo = @{ + NSUnderlyingErrorKey : self.underlyingError, + NSLocalizedFailureReasonErrorKey : @"Sample Failure Reason" + }; +} + +- (void)testPublicDomainErrorForPublicError { + NSError *expectedError = [NSError errorWithDomain:FIRAppCheckErrorDomain + code:FIRAppCheckErrorCodeServerUnreachable + userInfo:self.userInfo]; + + NSError *publicError = [FIRAppCheckErrorUtil publicDomainErrorWithError:expectedError]; + + XCTAssertNotNil(publicError); + XCTAssertEqualObjects(expectedError, publicError); +} + +- (void)testPublicDomainErrorForGACErrorUnknown { + NSError *unknownError = [NSError errorWithDomain:GACAppCheckErrorDomain + code:GACAppCheckErrorCodeUnknown + userInfo:self.userInfo]; + + NSError *publicUnknownError = [FIRAppCheckErrorUtil publicDomainErrorWithError:unknownError]; + + XCTAssertNotNil(publicUnknownError); + XCTAssertEqualObjects(publicUnknownError.domain, FIRAppCheckErrorDomain); + XCTAssertEqual(publicUnknownError.code, FIRAppCheckErrorCodeUnknown); + XCTAssertEqualObjects(unknownError.userInfo, self.userInfo); + XCTAssertEqualObjects(publicUnknownError.userInfo, unknownError.userInfo); +} + +- (void)testPublicDomainErrorForGACErrorServerUnreachable { + NSError *serverError = [NSError errorWithDomain:GACAppCheckErrorDomain + code:GACAppCheckErrorCodeServerUnreachable + userInfo:self.userInfo]; + + NSError *publicServerError = [FIRAppCheckErrorUtil publicDomainErrorWithError:serverError]; + + XCTAssertNotNil(publicServerError); + XCTAssertEqualObjects(publicServerError.domain, FIRAppCheckErrorDomain); + XCTAssertEqual(publicServerError.code, FIRAppCheckErrorCodeServerUnreachable); + XCTAssertEqualObjects(serverError.userInfo, self.userInfo); + XCTAssertEqualObjects(publicServerError.userInfo, serverError.userInfo); +} + +- (void)testPublicDomainErrorForGACErrorInvalidConfiguration { + NSError *invalidConfigurationError = + [NSError errorWithDomain:GACAppCheckErrorDomain + code:GACAppCheckErrorCodeInvalidConfiguration + userInfo:self.userInfo]; + + NSError *publicInvalidConfigurationError = + [FIRAppCheckErrorUtil publicDomainErrorWithError:invalidConfigurationError]; + + XCTAssertNotNil(publicInvalidConfigurationError); + XCTAssertEqualObjects(publicInvalidConfigurationError.domain, FIRAppCheckErrorDomain); + XCTAssertEqual(publicInvalidConfigurationError.code, FIRAppCheckErrorCodeInvalidConfiguration); + XCTAssertEqualObjects(invalidConfigurationError.userInfo, self.userInfo); + XCTAssertEqualObjects(publicInvalidConfigurationError.userInfo, + invalidConfigurationError.userInfo); +} + +- (void)testPublicDomainErrorForGACErrorKeychain { + NSError *keychainError = [NSError errorWithDomain:GACAppCheckErrorDomain + code:GACAppCheckErrorCodeKeychain + userInfo:self.userInfo]; + + NSError *publicKeychainError = [FIRAppCheckErrorUtil publicDomainErrorWithError:keychainError]; + + XCTAssertNotNil(publicKeychainError); + XCTAssertEqualObjects(publicKeychainError.domain, FIRAppCheckErrorDomain); + XCTAssertEqual(publicKeychainError.code, FIRAppCheckErrorCodeKeychain); + XCTAssertEqualObjects(keychainError.userInfo, self.userInfo); + XCTAssertEqualObjects(publicKeychainError.userInfo, keychainError.userInfo); +} + +- (void)testPublicDomainErrorForGACErrorUnsupported { + NSError *unsupportedError = [NSError errorWithDomain:GACAppCheckErrorDomain + code:GACAppCheckErrorCodeUnsupported + userInfo:self.userInfo]; + + NSError *publicUnsupportedError = + [FIRAppCheckErrorUtil publicDomainErrorWithError:unsupportedError]; + + XCTAssertNotNil(publicUnsupportedError); + XCTAssertEqualObjects(publicUnsupportedError.domain, FIRAppCheckErrorDomain); + XCTAssertEqual(publicUnsupportedError.code, FIRAppCheckErrorCodeUnsupported); + XCTAssertEqualObjects(unsupportedError.userInfo, self.userInfo); + XCTAssertEqualObjects(publicUnsupportedError.userInfo, unsupportedError.userInfo); +} + +- (void)testPublicDomainErrorForGACErrorUnrecognizedCode { + NSInteger unrecognizedErrorCode = -1000; // Not part of the GACAppCheckErrorCode enum + NSError *unknownError = [NSError errorWithDomain:GACAppCheckErrorDomain + code:unrecognizedErrorCode + userInfo:self.userInfo]; + + NSError *publicUnknownError = [FIRAppCheckErrorUtil publicDomainErrorWithError:unknownError]; + + XCTAssertNotNil(publicUnknownError); + XCTAssertEqualObjects(publicUnknownError.domain, FIRAppCheckErrorDomain); + XCTAssertEqual(publicUnknownError.code, FIRAppCheckErrorCodeUnknown); + XCTAssertEqual(publicUnknownError.localizedFailureReason, unknownError.localizedFailureReason); + + // Verify that the unrecognized error is wrapped as an underlying error. + NSError *underlyingError = publicUnknownError.userInfo[NSUnderlyingErrorKey]; + XCTAssertNotNil(underlyingError); + XCTAssertEqualObjects(underlyingError, unknownError); +} + +- (void)testPublicDomainErrorForUnrecognizedDomainError { + // Error from an unrecognized domain (i.e., not FIRAppCheckErrorDomain or GACAppCheckErrorDomain). + NSError *unrecognizedError = [NSError errorWithDomain:kTestErrorDomain + code:kTestErrorCode + userInfo:self.userInfo]; + + NSError *publicUnknownError = [FIRAppCheckErrorUtil publicDomainErrorWithError:unrecognizedError]; + + XCTAssertNotNil(publicUnknownError); + XCTAssertEqualObjects(publicUnknownError.domain, FIRAppCheckErrorDomain); + XCTAssertEqual(publicUnknownError.code, FIRAppCheckErrorCodeUnknown); + XCTAssertEqual(publicUnknownError.localizedFailureReason, + unrecognizedError.localizedFailureReason); + + // Verify that the unrecognized error is wrapped as an underlying error. + NSError *underlyingError = publicUnknownError.userInfo[NSUnderlyingErrorKey]; + XCTAssertNotNil(underlyingError); + XCTAssertEqualObjects(underlyingError, unrecognizedError); +} + +@end diff --git a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckAPIServiceTests.m b/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckAPIServiceTests.m deleted file mode 100644 index 1806ceff31b..00000000000 --- a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckAPIServiceTests.m +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import -#import "FBLPromise+Testing.h" - -@import FirebaseCoreInternal; - -#import -#import - -#import "FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckAPIService.h" -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckErrors.h" -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckToken.h" - -#import "FirebaseAppCheck/Tests/Unit/Utils/FIRFixtureLoader.h" -#import "SharedTestUtilities/Date/FIRDateTestUtils.h" -#import "SharedTestUtilities/URLSession/FIRURLSessionOCMockStub.h" - -#import "FirebaseCore/Extension/FirebaseCoreInternal.h" - -#pragma mark - Fakes - -/// A fake heartbeat logger used for dependency injection during testing. -@interface FIRHeartbeatLoggerFake : NSObject -@property(nonatomic, copy, nullable) FIRHeartbeatsPayload * (^onFlushHeartbeatsIntoPayloadHandler) - (void); -@property(nonatomic, copy, nullable) FIRDailyHeartbeatCode (^onHeartbeatCodeForTodayHandler)(void); -@end - -@implementation FIRHeartbeatLoggerFake - -- (nonnull FIRHeartbeatsPayload *)flushHeartbeatsIntoPayload { - if (self.onFlushHeartbeatsIntoPayloadHandler) { - return self.onFlushHeartbeatsIntoPayloadHandler(); - } else { - return nil; - } -} - -- (FIRDailyHeartbeatCode)heartbeatCodeForToday { - // This API should not be used by the below tests because the AppCheck SDK - // uses only the V2 heartbeat API (`flushHeartbeatsIntoPayload`) for getting - // heartbeats. - [self doesNotRecognizeSelector:_cmd]; - return FIRDailyHeartbeatCodeNone; -} - -- (void)log { - // This API should not be used by the below tests because the AppCheck SDK - // does not log heartbeats in it's networking context. - [self doesNotRecognizeSelector:_cmd]; -} - -@end - -#pragma mark - FIRAppCheckAPIServiceTests - -@interface FIRAppCheckAPIServiceTests : XCTestCase - -@property(nonatomic) FIRAppCheckAPIService *APIService; - -@property(nonatomic) id mockURLSession; - -@property(nonatomic) NSString *APIKey; -@property(nonatomic) NSString *appID; - -@property(nonatomic) FIRHeartbeatLoggerFake *heartbeatLoggerFake; - -@end - -@implementation FIRAppCheckAPIServiceTests - -- (void)setUp { - [super setUp]; - - self.APIKey = @"api_key"; - self.appID = @"app_id"; - - self.mockURLSession = OCMStrictClassMock([NSURLSession class]); - - self.heartbeatLoggerFake = [[FIRHeartbeatLoggerFake alloc] init]; - self.APIService = [[FIRAppCheckAPIService alloc] initWithURLSession:self.mockURLSession - APIKey:self.APIKey - appID:self.appID - heartbeatLogger:self.heartbeatLoggerFake]; -} - -- (void)tearDown { - [super tearDown]; - - self.APIService = nil; - [self.mockURLSession stopMocking]; - self.mockURLSession = nil; -} - -- (void)testDataRequestSuccessWhenNoHeartbeatsNeedSending { - // Given - FIRHeartbeatsPayload *emptyHeartbeatsPayload = - [FIRHeartbeatLoggingTestUtils emptyHeartbeatsPayload]; - // When - self.heartbeatLoggerFake.onFlushHeartbeatsIntoPayloadHandler = ^FIRHeartbeatsPayload * { - return emptyHeartbeatsPayload; - }; - // Then - [self assertDataRequestSuccessWhenSendingHeartbeatsPayload:emptyHeartbeatsPayload]; -} - -- (void)testDataRequestSuccessWhenHeartbeatsNeedSending { - // Given - FIRHeartbeatsPayload *nonEmptyHeartbeatsPayload = - [FIRHeartbeatLoggingTestUtils nonEmptyHeartbeatsPayload]; - // When - self.heartbeatLoggerFake.onFlushHeartbeatsIntoPayloadHandler = ^FIRHeartbeatsPayload * { - return nonEmptyHeartbeatsPayload; - }; - // Then - [self assertDataRequestSuccessWhenSendingHeartbeatsPayload:nonEmptyHeartbeatsPayload]; -} - -- (void)testDataRequestNetworkError { - NSURL *URL = [NSURL URLWithString:@"https://some.url.com"]; - NSDictionary *additionalHeaders = @{@"header1" : @"value1"}; - NSData *requestBody = [@"Request body" dataUsingEncoding:NSUTF8StringEncoding]; - - // 1. Stub URL session. - NSError *networkError = [NSError errorWithDomain:self.name code:-1 userInfo:nil]; - - [self stubURLSessionDataTaskPromiseWithResponse:nil - body:nil - error:networkError - URLSessionMock:self.mockURLSession - requestValidationBlock:nil]; - - // 2. Send request. - __auto_type requestPromise = [self.APIService sendRequestWithURL:URL - HTTPMethod:@"POST" - body:requestBody - additionalHeaders:additionalHeaders]; - - // 3. Verify. - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - - XCTAssertTrue(requestPromise.isRejected); - XCTAssertNotNil(requestPromise.error); - XCTAssertEqualObjects(requestPromise.error.domain, FIRAppCheckErrorDomain); - XCTAssertEqual(requestPromise.error.code, FIRAppCheckErrorCodeServerUnreachable); - XCTAssertEqualObjects(requestPromise.error.userInfo[NSUnderlyingErrorKey], networkError); - - OCMVerifyAll(self.mockURLSession); -} - -- (void)testDataRequestNot2xxHTTPStatusCode { - NSURL *URL = [NSURL URLWithString:@"https://some.url.com"]; - NSData *requestBody = [@"Request body" dataUsingEncoding:NSUTF8StringEncoding]; - NSString *responseBodyString = @"Token verification failed."; - - NSData *HTTPResponseBody = [responseBodyString dataUsingEncoding:NSUTF8StringEncoding]; - NSHTTPURLResponse *HTTPResponse = [FIRURLSessionOCMockStub HTTPResponseWithCode:300]; - [self stubURLSessionDataTaskPromiseWithResponse:HTTPResponse - body:HTTPResponseBody - error:nil - URLSessionMock:self.mockURLSession - requestValidationBlock:nil]; - - // 2. Send request. - __auto_type requestPromise = [self.APIService sendRequestWithURL:URL - HTTPMethod:@"POST" - body:requestBody - additionalHeaders:nil]; - - // 3. Verify. - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - - XCTAssertTrue(requestPromise.isRejected); - XCTAssertNil(requestPromise.value); - - XCTAssertNotNil(requestPromise.error); - XCTAssertEqualObjects(requestPromise.error.domain, FIRAppCheckErrorDomain); - XCTAssertEqual(requestPromise.error.code, FIRAppCheckErrorCodeUnknown); - - // Expect response body and HTTP status code to be included in the error. - NSString *failureReason = requestPromise.error.userInfo[NSLocalizedFailureReasonErrorKey]; - XCTAssertNotNil(failureReason); - XCTAssertTrue([failureReason containsString:@"300"]); - XCTAssertTrue([failureReason containsString:responseBodyString]); - - OCMVerifyAll(self.mockURLSession); -} - -#pragma mark - Token Exchange API response - -- (void)testAppCheckTokenWithAPIResponseValidResponse { - // 1. Prepare input parameters. - NSData *responseBody = - [FIRFixtureLoader loadFixtureNamed:@"FACTokenExchangeResponseSuccess.json"]; - XCTAssertNotNil(responseBody); - NSHTTPURLResponse *HTTPResponse = [FIRURLSessionOCMockStub HTTPResponseWithCode:200]; - GULURLSessionDataResponse *APIResponse = - [[GULURLSessionDataResponse alloc] initWithResponse:HTTPResponse HTTPBody:responseBody]; - - // 2. Expected result. - NSString *expectedFACToken = @"valid_app_check_token"; - - // 3. Parse API response. - __auto_type tokenPromise = [self.APIService appCheckTokenWithAPIResponse:APIResponse]; - - // 4. Verify. - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - - XCTAssertTrue(tokenPromise.isFulfilled); - XCTAssertNil(tokenPromise.error); - - XCTAssertEqualObjects(tokenPromise.value.token, expectedFACToken); - XCTAssertTrue([FIRDateTestUtils isDate:tokenPromise.value.expirationDate - approximatelyEqualCurrentPlusTimeInterval:1800 - precision:10]); -} - -- (void)testAppCheckTokenWithAPIResponseInvalidFormat { - // 1. Prepare input parameters. - NSString *responseBodyString = @"Token verification failed."; - NSData *responseBody = [responseBodyString dataUsingEncoding:NSUTF8StringEncoding]; - NSHTTPURLResponse *HTTPResponse = [FIRURLSessionOCMockStub HTTPResponseWithCode:200]; - GULURLSessionDataResponse *APIResponse = - [[GULURLSessionDataResponse alloc] initWithResponse:HTTPResponse HTTPBody:responseBody]; - - // 2. Parse API response. - __auto_type tokenPromise = [self.APIService appCheckTokenWithAPIResponse:APIResponse]; - - // 3. Verify. - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - - XCTAssertTrue(tokenPromise.isRejected); - XCTAssertNil(tokenPromise.value); - - XCTAssertNotNil(tokenPromise.error); - XCTAssertEqualObjects(tokenPromise.error.domain, FIRAppCheckErrorDomain); - XCTAssertEqual(tokenPromise.error.code, FIRAppCheckErrorCodeUnknown); - - // Expect response body and HTTP status code to be included in the error. - NSString *failureReason = tokenPromise.error.userInfo[NSLocalizedFailureReasonErrorKey]; - XCTAssertEqualObjects(failureReason, @"JSON serialization error."); -} - -- (void)testAppCheckTokenResponseMissingFields { - [self assertMissingFieldErrorWithFixture:@"DeviceCheckResponseMissingToken.json" - missingField:@"token"]; - [self assertMissingFieldErrorWithFixture:@"DeviceCheckResponseMissingTimeToLive.json" - missingField:@"ttl"]; -} - -- (void)assertMissingFieldErrorWithFixture:(NSString *)fixtureName - missingField:(NSString *)fieldName { - // 1. Parse API response. - NSData *missingFiledBody = [FIRFixtureLoader loadFixtureNamed:fixtureName]; - XCTAssertNotNil(missingFiledBody); - - NSHTTPURLResponse *HTTPResponse = [FIRURLSessionOCMockStub HTTPResponseWithCode:200]; - GULURLSessionDataResponse *APIResponse = - [[GULURLSessionDataResponse alloc] initWithResponse:HTTPResponse HTTPBody:missingFiledBody]; - - // 2. Parse API response. - __auto_type tokenPromise = [self.APIService appCheckTokenWithAPIResponse:APIResponse]; - - // 3. Verify. - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - - XCTAssertTrue(tokenPromise.isRejected); - XCTAssertNil(tokenPromise.value); - - XCTAssertNotNil(tokenPromise.error); - XCTAssertEqualObjects(tokenPromise.error.domain, FIRAppCheckErrorDomain); - XCTAssertEqual(tokenPromise.error.code, FIRAppCheckErrorCodeUnknown); - - // Expect missing field name to be included in the error. - NSString *failureReason = tokenPromise.error.userInfo[NSLocalizedFailureReasonErrorKey]; - NSString *fieldNameString = [NSString stringWithFormat:@"`%@`", fieldName]; - XCTAssertTrue([failureReason containsString:fieldNameString], - @"Fixture `%@`: expected missing field %@ error not found", fixtureName, - fieldNameString); -} - -#pragma mark - Helpers - -- (void)assertDataRequestSuccessWhenSendingHeartbeatsPayload: - (nullable FIRHeartbeatsPayload *)heartbeatsPayload { - NSURL *URL = [NSURL URLWithString:@"https://some.url.com"]; - NSDictionary *additionalHeaders = @{@"header1" : @"value1"}; - NSData *requestBody = [@"Request body" dataUsingEncoding:NSUTF8StringEncoding]; - - // 1. Stub URL session. - FIRRequestValidationBlock requestValidation = ^BOOL(NSURLRequest *request) { - XCTAssertEqualObjects(request.URL, URL); - - NSMutableDictionary *expectedHTTPHeaderFields = @{ - @"X-Goog-Api-Key" : self.APIKey, - @"X-Ios-Bundle-Identifier" : [[NSBundle mainBundle] bundleIdentifier], - @"header1" : @"value1", - } - .mutableCopy; - - NSString *_Nullable heartbeatHeaderValue = - FIRHeaderValueFromHeartbeatsPayload(heartbeatsPayload); - if (heartbeatHeaderValue) { - expectedHTTPHeaderFields[@"X-firebase-client"] = heartbeatHeaderValue; - } - - XCTAssertEqualObjects(request.allHTTPHeaderFields, expectedHTTPHeaderFields); - - XCTAssertEqualObjects(request.HTTPMethod, @"POST"); - XCTAssertEqualObjects(request.HTTPBody, requestBody); - - return YES; - }; - - NSData *HTTPResponseBody = [@"A response" dataUsingEncoding:NSUTF8StringEncoding]; - NSHTTPURLResponse *HTTPResponse = [FIRURLSessionOCMockStub HTTPResponseWithCode:200]; - [self stubURLSessionDataTaskPromiseWithResponse:HTTPResponse - body:HTTPResponseBody - error:nil - URLSessionMock:self.mockURLSession - requestValidationBlock:requestValidation]; - - // 2. Send request. - __auto_type requestPromise = [self.APIService sendRequestWithURL:URL - HTTPMethod:@"POST" - body:requestBody - additionalHeaders:additionalHeaders]; - - // 3. Verify. - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - - XCTAssertTrue(requestPromise.isFulfilled); - XCTAssertNil(requestPromise.error); - - XCTAssertEqualObjects(requestPromise.value.HTTPResponse, HTTPResponse); - XCTAssertEqualObjects(requestPromise.value.HTTPBody, HTTPResponseBody); - - OCMVerifyAll(self.mockURLSession); -} - -- (void)stubURLSessionDataTaskPromiseWithResponse:(NSHTTPURLResponse *)HTTPResponse - body:(NSData *)body - error:(NSError *)error - URLSessionMock:(id)URLSessionMock - requestValidationBlock: - (FIRRequestValidationBlock)requestValidationBlock { - // Validate request content. - FIRRequestValidationBlock nonOptionalRequestValidationBlock = - requestValidationBlock ?: ^BOOL(id request) { - return YES; - }; - - id URLRequestValidationArg = [OCMArg checkWithBlock:nonOptionalRequestValidationBlock]; - - // Result promise. - FBLPromise *result = [FBLPromise pendingPromise]; - if (error == nil) { - GULURLSessionDataResponse *response = - [[GULURLSessionDataResponse alloc] initWithResponse:HTTPResponse HTTPBody:body]; - [result fulfill:response]; - } else { - [result reject:error]; - } - - // Stub the method. - OCMExpect([URLSessionMock gul_dataTaskPromiseWithRequest:URLRequestValidationArg]) - .andReturn(result); -} - -@end diff --git a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckBackoffWrapperTests.m b/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckBackoffWrapperTests.m deleted file mode 100644 index 63a8f78fbba..00000000000 --- a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckBackoffWrapperTests.m +++ /dev/null @@ -1,392 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import "FBLPromise+Testing.h" -#if __has_include() -#import -#else -#import "FBLPromises.h" -#endif - -#import "FirebaseAppCheck/Sources/Core/Backoff/FIRAppCheckBackoffWrapper.h" - -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckHTTPError.h" - -@interface FIRAppCheckBackoffWrapperTests : XCTestCase - -@property(nonatomic, nullable) FIRAppCheckBackoffWrapper *backoffWrapper; - -@property(nonatomic) NSDate *currentDate; - -/// `NSObject` subclass for resolve the `self.operation` with in the case of success or `NSError` -/// for a failure. -@property(nonatomic) id operationResult; -/// Operation to apply backoff to. It configure with the helper methods during tests. -@property(nonatomic) FIRAppCheckBackoffOperationProvider operationProvider; -/// Expectation to fulfill when operation is completed. It is configured with the `self.operation` -/// in setup helpers. -@property(nonatomic) XCTestExpectation *operationFinishExpectation; - -/// Test error handler that returns `self.errorHandlerResult` and fulfills -/// `self.errorHandlerExpectation`. -@property(nonatomic, copy) FIRAppCheckBackoffErrorHandler errorHandler; -/// Expectation to fulfill when error handlers is executed. -@property(nonatomic) XCTestExpectation *errorHandlerExpectation; - -@end - -@implementation FIRAppCheckBackoffWrapperTests - -- (void)setUp { - [super setUp]; - - __auto_type __weak weakSelf = self; - self.backoffWrapper = [[FIRAppCheckBackoffWrapper alloc] initWithDateProvider:^NSDate *_Nonnull { - return weakSelf.currentDate ?: [NSDate date]; - }]; -} - -- (void)tearDown { - self.backoffWrapper = nil; - self.operationProvider = nil; - - [super tearDown]; -} - -- (void)testBackoffFirstOperationAlwaysExecuted { - // 1. Set up operation success. - [self setUpOperationSuccess]; - [self setUpErrorHandlerWithBackoffType:FIRAppCheckBackoffTypeNone]; - self.errorHandlerExpectation.inverted = YES; - - // 2. Compose operation with backoff. - __auto_type operationWithBackoff = - [self.backoffWrapper applyBackoffToOperation:self.operationProvider - errorHandler:self.errorHandler]; - - // 3. Wait for operation to complete and check. - [self waitForExpectationsWithTimeout:0.5 handler:NULL]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - - XCTAssertEqualObjects(operationWithBackoff.value, self.operationResult); -} - -- (void)testBackoff1DayBackoffAfterFailure { - // 0. Set current date. - self.currentDate = [NSDate date]; - - // 1. Check initial failure. - // 1.1. Set up operation failure. - [self setUpOperationError]; - [self setUpErrorHandlerWithBackoffType:FIRAppCheckBackoffType1Day]; - - // 1.2. Compose operation with backoff. - __auto_type operationWithBackoff = - [self.backoffWrapper applyBackoffToOperation:self.operationProvider - errorHandler:self.errorHandler]; - - // 1.3. Wait for operation to complete. - [self waitForExpectationsWithTimeout:0.5 handler:NULL]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - - // 1.4. Expect the promise to be rejected with the operation error. - XCTAssertEqualObjects(operationWithBackoff.error, self.operationResult); - - // 2. Check backoff in 12 hours. - // 2.1. Set up another operation. - [self setUpOperationError]; - [self setUpErrorHandlerWithBackoffType:FIRAppCheckBackoffType1Day]; - - // Don't expect operation to be called. - self.operationFinishExpectation.inverted = YES; - // Don't expect error handler to be called. - self.errorHandlerExpectation.inverted = YES; - - // 2.2. Move current date. - self.currentDate = [self.currentDate dateByAddingTimeInterval:12 * 60 * 60]; - - // 2.3. Compose operation with backoff. - operationWithBackoff = [self.backoffWrapper applyBackoffToOperation:self.operationProvider - errorHandler:self.errorHandler]; - - // 2.4. Wait for operation to complete. - [self waitForExpectationsWithTimeout:0.5 handler:NULL]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - - // 2.5. Expect the promise to be rejected with a backoff error. - XCTAssertTrue(operationWithBackoff.isRejected); - XCTAssertTrue([self isBackoffError:operationWithBackoff.error]); - - // 3. Check backoff one minute before allowing retry. - // 3.1. Set up another operation. - [self setUpOperationError]; - [self setUpErrorHandlerWithBackoffType:FIRAppCheckBackoffType1Day]; - - // Don't expect operation to be called. - self.operationFinishExpectation.inverted = YES; - // Don't expect error handler to be called. - self.errorHandlerExpectation.inverted = YES; - - // 3.2. Move current date. - self.currentDate = [self.currentDate dateByAddingTimeInterval:11 * 60 * 60 + 59 * 60]; - - // 3.3. Compose operation with backoff. - operationWithBackoff = [self.backoffWrapper applyBackoffToOperation:self.operationProvider - errorHandler:self.errorHandler]; - - // 3.4. Wait for operation to complete. - [self waitForExpectationsWithTimeout:0.5 handler:NULL]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - - // 3.5. Expect the promise to be rejected with a backoff error. - XCTAssertTrue(operationWithBackoff.isRejected); - XCTAssertTrue([self isBackoffError:operationWithBackoff.error]); - - // 4. Check backoff one minute after allowing retry. - // 4.1. Set up another operation. - [self setUpOperationError]; - [self setUpErrorHandlerWithBackoffType:FIRAppCheckBackoffType1Day]; - - // 4.2. Move current date. - self.currentDate = [self.currentDate dateByAddingTimeInterval:12 * 60 * 60 + 1 * 60]; - - // 4.3. Compose operation with backoff. - operationWithBackoff = [self.backoffWrapper applyBackoffToOperation:self.operationProvider - errorHandler:self.errorHandler]; - - // 4.4. Wait for operation to complete and check failure. - [self waitForExpectationsWithTimeout:0.5 handler:NULL]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - - // 4.5. Expect the promise to be rejected with the operation error. - XCTAssertEqualObjects(operationWithBackoff.error, self.operationResult); -} - -#pragma mark - Exponential backoff - -- (void)testExponentialBackoff { - // 0. Set current date. - self.currentDate = [NSDate date]; - - // 1. Check initial failure. - // 1.1. Set up operation failure. - [self setUpOperationError]; - [self setUpErrorHandlerWithBackoffType:FIRAppCheckBackoffTypeExponential]; - - // 1.2. Compose operation with backoff. - __auto_type operationWithBackoff = - [self.backoffWrapper applyBackoffToOperation:self.operationProvider - errorHandler:self.errorHandler]; - - // 1.4. Wait for operation to complete. - [self waitForExpectationsWithTimeout:0.5 handler:NULL]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - - // 1.5. Expect the promise to be rejected with the operation error. - XCTAssertEqualObjects(operationWithBackoff.error, self.operationResult); - - // 2. Check exponential backoff. - NSUInteger numberOfAttempts = 20; - NSTimeInterval maximumBackoff = 4 * 60 * 60; // 4 hours. - // The maximum of original backoff interval that can be added. - double maxJitterPortion = 0.5; // Backoff is up to 50% longer. - - for (NSUInteger attempt = 0; attempt < numberOfAttempts; attempt++) { - NSTimeInterval expectedMinBackoff = MIN(pow(2, attempt), maximumBackoff); - NSTimeInterval expectedMaxBackoff = - MIN(expectedMinBackoff * (1 + maxJitterPortion), maximumBackoff); - - [self assertBackoffIntervalIsAtLeast:expectedMinBackoff andAtMost:expectedMaxBackoff]; - } - - // 3. Test recovery after success. - // 3.1. Set time after max backoff. - self.currentDate = [self.currentDate dateByAddingTimeInterval:maximumBackoff]; - - // 3.2. Set up operation success. - [self setUpOperationSuccess]; - [self setUpErrorHandlerWithBackoffType:FIRAppCheckBackoffTypeNone]; - self.errorHandlerExpectation.inverted = YES; - - // 3.3. Compose operation with backoff. - operationWithBackoff = [self.backoffWrapper applyBackoffToOperation:self.operationProvider - errorHandler:self.errorHandler]; - - // 3.4. Wait for operation to complete. - [self waitForExpectationsWithTimeout:0.5 handler:NULL]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - - // 3.5. Expect the promise to be rejected with the operation error. - XCTAssertEqualObjects(operationWithBackoff.value, self.operationResult); - - // 3.6. Set up operation failure. - // We expect an operation to be executed with no backoff after a success. - [self setUpOperationError]; - [self setUpErrorHandlerWithBackoffType:FIRAppCheckBackoffTypeExponential]; - - // 3.7. Compose operation with backoff. - operationWithBackoff = [self.backoffWrapper applyBackoffToOperation:self.operationProvider - errorHandler:self.errorHandler]; - - // 3.8. Wait for operation to complete. - [self waitForExpectationsWithTimeout:0.5 handler:NULL]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - - // 3.9. Expect the promise to be rejected with the operation error. - XCTAssertEqualObjects(operationWithBackoff.error, self.operationResult); -} - -#pragma mark - Error handling - -- (void)testDefaultAppCheckProviderErrorHandler { - __auto_type errorHandler = [self.backoffWrapper defaultAppCheckProviderErrorHandler]; - - NSError *nonHTTPError = [NSError errorWithDomain:self.name code:1 userInfo:nil]; - XCTAssertEqual(errorHandler(nonHTTPError), FIRAppCheckBackoffTypeNone); - - FIRAppCheckHTTPError *HTTP400Error = [self httpErrorWithStatusCode:400]; - XCTAssertEqual(errorHandler(HTTP400Error), FIRAppCheckBackoffType1Day); - - FIRAppCheckHTTPError *HTTP403Error = [self httpErrorWithStatusCode:403]; - XCTAssertEqual(errorHandler(HTTP403Error), FIRAppCheckBackoffTypeExponential); - - FIRAppCheckHTTPError *HTTP404Error = [self httpErrorWithStatusCode:404]; - XCTAssertEqual(errorHandler(HTTP404Error), FIRAppCheckBackoffType1Day); - - FIRAppCheckHTTPError *HTTP429Error = [self httpErrorWithStatusCode:429]; - XCTAssertEqual(errorHandler(HTTP429Error), FIRAppCheckBackoffTypeExponential); - - FIRAppCheckHTTPError *HTTP503Error = [self httpErrorWithStatusCode:503]; - XCTAssertEqual(errorHandler(HTTP503Error), FIRAppCheckBackoffTypeExponential); - - // Test all other codes from 400 to 599. - for (NSInteger statusCode = 400; statusCode < 600; statusCode++) { - if (statusCode == 400 || statusCode == 404) { - // Skip status codes with non-exponential backoff. - continue; - } - - FIRAppCheckHTTPError *HTTPError = [self httpErrorWithStatusCode:statusCode]; - XCTAssertEqual(errorHandler(HTTPError), FIRAppCheckBackoffTypeExponential); - } -} - -#pragma mark - Helpers - -- (void)setUpErrorHandlerWithBackoffType:(FIRAppCheckBackoffType)backoffType { - __auto_type __weak weakSelf = self; - self.errorHandlerExpectation = [self expectationWithDescription:@"Error handler"]; - self.errorHandler = ^FIRAppCheckBackoffType(NSError *_Nonnull error) { - [weakSelf.errorHandlerExpectation fulfill]; - return backoffType; - }; -} - -- (void)setUpOperationSuccess { - self.operationFinishExpectation = [self expectationWithDescription:@"Operation performed"]; - self.operationResult = [[NSObject alloc] init]; - __auto_type __weak weakSelf = self; - self.operationProvider = ^FBLPromise *() { - return [FBLPromise do:^id(void) { - [weakSelf.operationFinishExpectation fulfill]; - return weakSelf.operationResult; - }]; - }; -} - -- (void)setUpOperationError { - self.operationFinishExpectation = [self expectationWithDescription:@"Operation performed"]; - self.operationResult = [NSError errorWithDomain:self.name code:-1 userInfo:nil]; - __auto_type __weak weakSelf = self; - self.operationProvider = ^FBLPromise *() { - return [FBLPromise do:^id(void) { - [weakSelf.operationFinishExpectation fulfill]; - return weakSelf.operationResult; - }]; - }; -} - -- (BOOL)isBackoffError:(NSError *)error { - return [error.localizedDescription containsString:@"Too many attempts. Underlying error:"]; -} - -- (FIRAppCheckHTTPError *)httpErrorWithStatusCode:(NSInteger)statusCode { - NSHTTPURLResponse *httpResponse = - [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"https://localhost"] - statusCode:statusCode - HTTPVersion:nil - headerFields:nil]; - FIRAppCheckHTTPError *error = [[FIRAppCheckHTTPError alloc] initWithHTTPResponse:httpResponse - data:nil]; - return error; -} - -// Asserts that the backoff interval is within the provided range. -// Assumes that `self.currentDate` contains the last failure date. -// Sets `self.currentDate` to the date when the most recent retry happened. -- (void)assertBackoffIntervalIsAtLeast:(NSTimeInterval)minBackoff - andAtMost:(NSTimeInterval)maxBackoff { - NSDate *lastFailureDate = self.currentDate; - - // 1. Test backoff before min interval. - // 1.1 Move the date 0.5 sec before the minimum backoff date. - self.currentDate = [lastFailureDate dateByAddingTimeInterval:minBackoff - 0.5]; - - // 1.2 Set up operation failure. - [self setUpOperationError]; - [self setUpErrorHandlerWithBackoffType:FIRAppCheckBackoffTypeExponential]; - - // 1.3 Don't expect operation to be executed. - self.operationFinishExpectation.inverted = YES; - self.errorHandlerExpectation.inverted = YES; - - // 1.4 Compose operation with backoff. - __auto_type operationWithBackoff = - [self.backoffWrapper applyBackoffToOperation:self.operationProvider - errorHandler:self.errorHandler]; - - // 1.5 Wait for operation to complete. - [self waitForExpectationsWithTimeout:0.5 handler:NULL]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - - // 1.6 Expect the promise to be rejected with a backoff error. - XCTAssertTrue(operationWithBackoff.isRejected); - XCTAssertTrue([self isBackoffError:operationWithBackoff.error]); - - // 2. Test backoff after max interval. - // 2.1 Move the date 0.5 sec before the minimum backoff date. - self.currentDate = [lastFailureDate dateByAddingTimeInterval:maxBackoff + 0.5]; - - // 2.2. Set up operation failure and expect it to be completed. - [self setUpOperationError]; - [self setUpErrorHandlerWithBackoffType:FIRAppCheckBackoffTypeExponential]; - - // 2.3 Compose operation with backoff. - operationWithBackoff = [self.backoffWrapper applyBackoffToOperation:self.operationProvider - errorHandler:self.errorHandler]; - - // 2.4 Wait for operation to complete. - [self waitForExpectationsWithTimeout:0.5 handler:NULL]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - - // 2.5 Expect the promise to be rejected with a backoff error. - XCTAssertTrue(operationWithBackoff.isRejected); - XCTAssertFalse([self isBackoffError:operationWithBackoff.error]); -} - -@end diff --git a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckCryptoUtilsTests.m b/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckCryptoUtilsTests.m deleted file mode 100644 index 5460d715f16..00000000000 --- a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckCryptoUtilsTests.m +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import "FirebaseAppCheck/Sources/Core/Utils/FIRAppCheckCryptoUtils.h" - -@interface FIRAppCheckCryptoUtilsTests : XCTestCase - -@end - -@implementation FIRAppCheckCryptoUtilsTests - -- (void)testSHA256HashFromData { - NSData *dataToHash = [@"some data to hash" dataUsingEncoding:NSUTF8StringEncoding]; - - NSData *hashData = [FIRAppCheckCryptoUtils sha256HashFromData:dataToHash]; - - // Convert to a base64 encoded string to compare. - NSString *base64EncodedHashString = [hashData base64EncodedStringWithOptions:0]; - - // Base64 encoded hash of UTF8 encoded string "some data to hash". - NSString *expectedHashString = @"ai2iCUOTHpg0/BLP5btHu9muQ0iaMHJpYrV29OOZPlA="; - - XCTAssertEqualObjects(base64EncodedHashString, expectedHashString); -} - -@end diff --git a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckIntegrationTests.m b/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckIntegrationTests.m deleted file mode 100644 index 6cb3f4accc9..00000000000 --- a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckIntegrationTests.m +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import - -#import - -#import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefresher.h" -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheck.h" -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckAvailability.h" -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckProviderFactory.h" -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckToken.h" - -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRDeviceCheckProvider.h" -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRDeviceCheckProviderFactory.h" - -#import "FirebaseCore/Extension/FirebaseCoreInternal.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface DummyAppCheckProvider : NSObject -@end - -@implementation DummyAppCheckProvider - -- (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable, NSError *_Nullable))handler { - FIRAppCheckToken *token = [[FIRAppCheckToken alloc] initWithToken:@"Token" - expirationDate:[NSDate distantFuture]]; - handler(token, nil); -} - -@end - -@interface AppCheckProviderFactory : NSObject -@end - -@implementation AppCheckProviderFactory - -- (nullable id)createProviderWithApp:(nonnull FIRApp *)app { - return [[DummyAppCheckProvider alloc] init]; -} - -@end - -@interface FIRAppCheckIntegrationTests : XCTestCase - -@property(nonatomic, nullable) id mockProviderFactory; -@property(nonatomic, nullable) id mockAppCheckProvider; -@property(nonatomic, nullable) id mockTokenRefresher; - -- (void)testDefaultAppCheckProvider FIR_DEVICE_CHECK_PROVIDER_AVAILABILITY; - -@end - -@implementation FIRAppCheckIntegrationTests - -- (void)setUp { - [super setUp]; - - // Disable token refresher to avoid any unexpected async tasks being scheduled. - [self disableTokenRefresher]; - - self.mockAppCheckProvider = OCMProtocolMock(@protocol(FIRAppCheckProvider)); - self.mockProviderFactory = OCMProtocolMock(@protocol(FIRAppCheckProviderFactory)); -} - -- (void)tearDown { - [FIRApp resetApps]; - - if (@available(iOS 11.0, macOS 10.15, macCatalyst 13.0, tvOS 11.0, watchOS 9.0, *)) { - // Recover default provider factory. - [FIRAppCheck setAppCheckProviderFactory:[[FIRDeviceCheckProviderFactory alloc] init]]; - } - - [self.mockTokenRefresher stopMocking]; - self.mockTokenRefresher = nil; - [self.mockProviderFactory stopMocking]; - self.mockProviderFactory = nil; - [self.mockAppCheckProvider stopMocking]; - self.mockAppCheckProvider = nil; - - [super tearDown]; -} - -- (void)testDefaultAppCheckProvider { - NSString *appName = @"testDefaultAppCheckProvider"; - - // 1. Expect FIRDeviceCheckProvider to be instantiated. - - id deviceCheckProviderMock = OCMClassMock([FIRDeviceCheckProvider class]); - id appValidationArg = [OCMArg checkWithBlock:^BOOL(FIRApp *app) { - XCTAssertEqualObjects(app.name, appName); - return YES; - }]; - - OCMStub([deviceCheckProviderMock alloc]).andReturn(deviceCheckProviderMock); - OCMExpect([deviceCheckProviderMock initWithApp:appValidationArg]) - .andReturn(deviceCheckProviderMock); - - // 2. Configure Firebase - [self configureAppWithName:appName]; - - FIRApp *app = [FIRApp appNamed:appName]; - XCTAssertNotNil(FIR_COMPONENT(FIRAppCheckInterop, app.container)); - - // 3. Verify - OCMVerifyAll(deviceCheckProviderMock); - - // 4. Cleanup - // Recover default provider factory. - [FIRAppCheck setAppCheckProviderFactory:[[FIRDeviceCheckProviderFactory alloc] init]]; - [deviceCheckProviderMock stopMocking]; -} - -// Tests that use the Keychain require a host app and Swift Package Manager -// does not support adding a host app to test targets. -#if !SWIFT_PACKAGE - -// Skip keychain tests on Catalyst and macOS. Tests are skipped because they -// involve interactions with the keychain that require a provisioning profile. -// See go/firebase-macos-keychain-popups for more details. -#if !TARGET_OS_MACCATALYST && !TARGET_OS_OSX - -- (void)testSetAppCheckProviderFactoryWithDefaultApp { - NSString *appName = kFIRDefaultAppName; - - // 1. Set App Check Provider Factory. - [FIRAppCheck setAppCheckProviderFactory:self.mockProviderFactory]; - - // 2. Expect factory to be used on [FIRApp configure]. - id appValidationArg = [OCMArg checkWithBlock:^BOOL(FIRApp *app) { - XCTAssertEqual(app.name, appName); - return YES; - }]; - OCMExpect([self.mockProviderFactory createProviderWithApp:appValidationArg]) - .andReturn(self.mockAppCheckProvider); - - // 3. Configure FIRApp. - [self configureAppWithName:appName]; - - // 4. Expect App Check Provider to be called on getToken. - FIRAppCheckToken *fakeToken = [[FIRAppCheckToken alloc] initWithToken:@"token" - expirationDate:[NSDate distantFuture]]; - id completionBlockArg = [OCMArg invokeBlockWithArgs:fakeToken, [NSNull null], nil]; - OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionBlockArg]); - - // 5. Call getToken and check the result. - FIRApp *app = [FIRApp appNamed:appName]; - id appCheck = FIR_COMPONENT(FIRAppCheckInterop, app.container); - - XCTestExpectation *completionExpectation = - [self expectationWithDescription:@"completionExpectation"]; - [appCheck getTokenForcingRefresh:YES - completion:^(id tokenResult) { - [completionExpectation fulfill]; - XCTAssertNil(tokenResult.error); - XCTAssertNotNil(tokenResult); - XCTAssertEqualObjects(tokenResult.token, fakeToken.token); - XCTAssertNil(tokenResult.error); - }]; - [self waitForExpectations:@[ completionExpectation ] timeout:0.5]; - - // 6. Verify mocks - OCMVerifyAll(self.mockProviderFactory); - OCMVerifyAll(self.mockAppCheckProvider); -} - -#endif // !TARGET_OS_MACCATALYST && !TARGET_OS_OSX - -#endif // !SWIFT_PACKAGE - -#pragma mark - Helpers - -- (void)configureAppWithName:(NSString *)appName { - FIROptions *options = - [[FIROptions alloc] initWithGoogleAppID:@"1:100000000000:ios:aaaaaaaaaaaaaaaaaaaaaaaa" - GCMSenderID:@"sender_id"]; - [FIRApp configureWithName:appName options:options]; -} - -- (void)usageExample { - // Set a custom app check provider factory for the default FIRApp. - [FIRAppCheck setAppCheckProviderFactory:[[AppCheckProviderFactory alloc] init]]; - [FIRApp configure]; - - FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:@"path"]; - [FIRApp configureWithName:@"AppName" options:options]; - - FIRApp *defaultApp = [FIRApp defaultApp]; - - id defaultAppCheck = FIR_COMPONENT(FIRAppCheckInterop, defaultApp.container); - - [defaultAppCheck getTokenForcingRefresh:NO - completion:^(id tokenResult) { - NSLog(@"Token: %@", tokenResult.token); - if (tokenResult.error) { - NSLog(@"Error: %@", tokenResult.error); - } - }]; -} - -- (void)disableTokenRefresher { - self.mockTokenRefresher = OCMClassMock([FIRAppCheckTokenRefresher class]); - OCMStub([self.mockTokenRefresher alloc]).andReturn(self.mockTokenRefresher); - OCMStub([self.mockTokenRefresher initWithRefreshResult:[OCMArg any] settings:[OCMArg any]]) - .andReturn(self.mockTokenRefresher); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckLoggerTests.m b/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckLoggerTests.m new file mode 100644 index 00000000000..e1f72fc5c2b --- /dev/null +++ b/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckLoggerTests.m @@ -0,0 +1,68 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h" + +#import + +@interface FIRAppCheckLoggerTests : XCTestCase +@end + +@implementation FIRAppCheckLoggerTests + +- (void)testGetGACAppCheckLogLevel_Error { + FIRSetLoggerLevel(FIRLoggerLevelError); + + GACAppCheckLogLevel logLevel = FIRGetGACAppCheckLogLevel(); + + XCTAssertEqual(logLevel, GACAppCheckLogLevelError); +} + +- (void)testGetGACAppCheckLogLevel_Warning { + FIRSetLoggerLevel(FIRLoggerLevelWarning); + + GACAppCheckLogLevel logLevel = FIRGetGACAppCheckLogLevel(); + + XCTAssertEqual(logLevel, GACAppCheckLogLevelWarning); +} + +- (void)testGetGACAppCheckLogLevel_Notice { + FIRSetLoggerLevel(FIRLoggerLevelNotice); + + GACAppCheckLogLevel logLevel = FIRGetGACAppCheckLogLevel(); + + XCTAssertEqual(logLevel, GACAppCheckLogLevelWarning); +} + +- (void)testGetGACAppCheckLogLevel_Info { + FIRSetLoggerLevel(FIRLoggerLevelInfo); + + GACAppCheckLogLevel logLevel = FIRGetGACAppCheckLogLevel(); + + XCTAssertEqual(logLevel, GACAppCheckLogLevelInfo); +} + +- (void)testGetGACAppCheckLogLevel_Debug { + FIRSetLoggerLevel(FIRLoggerLevelDebug); + + GACAppCheckLogLevel logLevel = FIRGetGACAppCheckLogLevel(); + + XCTAssertEqual(logLevel, GACAppCheckLogLevelDebug); +} + +@end diff --git a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckSettingsTests.m b/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckSettingsTests.m index 1b7862f857c..6704fc62759 100644 --- a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckSettingsTests.m +++ b/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckSettingsTests.m @@ -104,7 +104,7 @@ - (void)testIsTokenAutoRefreshEnabledWhenSetThisAppRun { OCMReject([self.mockApp isDataCollectionDefaultEnabled]); // 1.4. Expect the new value to be saved to the user defaults. - OCMExpect([self.mockUserDefaults setObject:@(newFlagValue) forKey:self.userDefaultKey]); + OCMExpect([self.mockUserDefaults setBool:newFlagValue forKey:self.userDefaultKey]); // 2. Set flag value. self.settings.isTokenAutoRefreshEnabled = newFlagValue; @@ -206,7 +206,7 @@ - (void)testIsTokenAutoRefreshEnabledWhenAppDeallocated { - (void)testSetIsTokenAutoRefreshEnabled { // 1. Set first time. // 1.1. Expect the new value to be saved to the user defaults. - OCMExpect([self.mockUserDefaults setObject:@(YES) forKey:self.userDefaultKey]); + OCMExpect([self.mockUserDefaults setBool:YES forKey:self.userDefaultKey]); // 1.2 Set. self.settings.isTokenAutoRefreshEnabled = YES; @@ -217,7 +217,7 @@ - (void)testSetIsTokenAutoRefreshEnabled { // 2. Set second time. // 2.1. Expect the new value to be saved to the user defaults. - OCMExpect([self.mockUserDefaults setObject:@(NO) forKey:self.userDefaultKey]); + OCMExpect([self.mockUserDefaults setBool:NO forKey:self.userDefaultKey]); // 2.2 Set. self.settings.isTokenAutoRefreshEnabled = NO; diff --git a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckStorageTests.m b/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckStorageTests.m deleted file mode 100644 index 71f82396db7..00000000000 --- a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckStorageTests.m +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -// Tests that use the Keychain require a host app and Swift Package Manager -// does not support adding a host app to test targets. -#if !SWIFT_PACKAGE - -// Skip keychain tests on Catalyst and macOS. Tests are skipped because they -// involve interactions with the keychain that require a provisioning profile. -// See go/firebase-macos-keychain-popups for more details. -#if !TARGET_OS_MACCATALYST && !TARGET_OS_OSX - -#import - -#import - -#import - -#import "FBLPromise+Testing.h" - -#import "FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStorage.h" - -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" -#import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h" - -@interface FIRAppCheckStorageTests : XCTestCase -@property(nonatomic) NSString *appName; -@property(nonatomic) NSString *appID; -@property(nonatomic) FIRAppCheckStorage *storage; -@end - -@implementation FIRAppCheckStorageTests - -- (void)setUp { - [super setUp]; - - self.appName = @"FIRAppCheckStorageTestsApp"; - self.appID = @"1:100000000000:ios:aaaaaaaaaaaaaaaaaaaaaaaa"; - self.storage = [[FIRAppCheckStorage alloc] initWithAppName:self.appName - appID:self.appID - accessGroup:nil]; -} - -- (void)tearDown { - self.storage = nil; - [super tearDown]; -} - -- (void)testSetAndGetToken { - FIRAppCheckToken *tokenToStore = [[FIRAppCheckToken alloc] initWithToken:@"token" - expirationDate:[NSDate distantPast] - receivedAtDate:[NSDate date]]; - - FBLPromise *setPromise = [self.storage setToken:tokenToStore]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertEqualObjects(setPromise.value, tokenToStore); - XCTAssertNil(setPromise.error); - - __auto_type getPromise = [self.storage getToken]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertEqualObjects(getPromise.value.token, tokenToStore.token); - XCTAssertEqualObjects(getPromise.value.expirationDate, tokenToStore.expirationDate); - XCTAssertEqualObjects(getPromise.value.receivedAtDate, tokenToStore.receivedAtDate); - XCTAssertNil(getPromise.error); -} - -- (void)testRemoveToken { - FBLPromise *setPromise = [self.storage setToken:nil]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertEqualObjects(setPromise.value, nil); - XCTAssertNil(setPromise.error); - - __auto_type getPromise = [self.storage getToken]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertNil(getPromise.value); - XCTAssertNil(getPromise.error); -} - -- (void)testGetToken_KeychainError { - // 1. Set up storage mock. - id mockKeychainStorage = OCMClassMock([GULKeychainStorage class]); - FIRAppCheckStorage *storage = [[FIRAppCheckStorage alloc] initWithAppName:self.appName - appID:self.appID - keychainStorage:mockKeychainStorage - accessGroup:nil]; - // 2. Create and expect keychain error. - NSError *gulsKeychainError = [NSError errorWithDomain:@"com.guls.keychain" code:-1 userInfo:nil]; - OCMExpect([mockKeychainStorage getObjectForKey:[OCMArg any] - objectClass:[OCMArg any] - accessGroup:[OCMArg any]]) - .andReturn([FBLPromise resolvedWith:gulsKeychainError]); - - // 3. Get token and verify results. - __auto_type getPromise = [storage getToken]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertNotNil(getPromise.error); - XCTAssertEqualObjects(getPromise.error, - [FIRAppCheckErrorUtil keychainErrorWithError:gulsKeychainError]); - - // 4. Verify storage mock. - OCMVerifyAll(mockKeychainStorage); -} - -- (void)testSetToken_KeychainError { - // 1. Set up storage mock. - id mockKeychainStorage = OCMClassMock([GULKeychainStorage class]); - FIRAppCheckStorage *storage = [[FIRAppCheckStorage alloc] initWithAppName:self.appName - appID:self.appID - keychainStorage:mockKeychainStorage - accessGroup:nil]; - - // 2. Create and expect keychain error. - NSError *gulsKeychainError = [NSError errorWithDomain:@"com.guls.keychain" code:-1 userInfo:nil]; - OCMExpect([mockKeychainStorage setObject:[OCMArg any] - forKey:[OCMArg any] - accessGroup:[OCMArg any]]) - .andReturn([FBLPromise resolvedWith:gulsKeychainError]); - - // 3. Set token and verify results. - FIRAppCheckToken *tokenToStore = [[FIRAppCheckToken alloc] initWithToken:@"token" - expirationDate:[NSDate distantPast] - receivedAtDate:[NSDate date]]; - __auto_type getPromise = [storage setToken:tokenToStore]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertNotNil(getPromise.error); - XCTAssertEqualObjects(getPromise.error, - [FIRAppCheckErrorUtil keychainErrorWithError:gulsKeychainError]); - - // 4. Verify storage mock. - OCMVerifyAll(mockKeychainStorage); -} - -- (void)testRemoveToken_KeychainError { - // 1. Set up storage mock. - id mockKeychainStorage = OCMClassMock([GULKeychainStorage class]); - FIRAppCheckStorage *storage = [[FIRAppCheckStorage alloc] initWithAppName:self.appName - appID:self.appID - keychainStorage:mockKeychainStorage - accessGroup:nil]; - - // 2. Create and expect keychain error. - NSError *gulsKeychainError = [NSError errorWithDomain:@"com.guls.keychain" code:-1 userInfo:nil]; - OCMExpect([mockKeychainStorage removeObjectForKey:[OCMArg any] accessGroup:[OCMArg any]]) - .andReturn([FBLPromise resolvedWith:gulsKeychainError]); - - // 3. Remove token and verify results. - __auto_type getPromise = [storage setToken:nil]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertNotNil(getPromise.error); - XCTAssertEqualObjects(getPromise.error, - [FIRAppCheckErrorUtil keychainErrorWithError:gulsKeychainError]); - - // 4. Verify storage mock. - OCMVerifyAll(mockKeychainStorage); -} - -- (void)testSetTokenPerApp { - // 1. Set token with a storage. - FIRAppCheckToken *tokenToStore = [[FIRAppCheckToken alloc] initWithToken:@"token" - expirationDate:[NSDate distantPast] - receivedAtDate:[NSDate date]]; - - FBLPromise *setPromise = [self.storage setToken:tokenToStore]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertEqualObjects(setPromise.value, tokenToStore); - XCTAssertNil(setPromise.error); - - // 2. Try to read the token with another storage. - FIRAppCheckStorage *storage2 = - [[FIRAppCheckStorage alloc] initWithAppName:self.appName - appID:@"1:200000000000:ios:aaaaaaaaaaaaaaaaaaaaaaaa" - accessGroup:nil]; - __auto_type getPromise = [storage2 getToken]; - XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); - XCTAssertNil(getPromise.value); - XCTAssertNil(getPromise.error); -} - -@end - -#endif // !TARGET_OS_MACCATALYST && !TARGET_OS_OSX - -#endif // !SWIFT_PACKAGE diff --git a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckStoredTokenTests.m b/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckStoredTokenTests.m deleted file mode 100644 index 8281527904f..00000000000 --- a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckStoredTokenTests.m +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h" -#import "FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken+FIRAppCheckToken.h" -#import "FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken.h" - -#import - -@interface FIRAppCheckStoredTokenTests : XCTestCase - -@end - -@implementation FIRAppCheckStoredTokenTests - -- (void)testSecureCoding { - FIRAppCheckStoredToken *tokenToArchive = [[FIRAppCheckStoredToken alloc] init]; - tokenToArchive.token = @"some_token"; - tokenToArchive.expirationDate = [NSDate date]; - tokenToArchive.receivedAtDate = [tokenToArchive.expirationDate dateByAddingTimeInterval:-10]; - - NSError *error; - NSData *archivedToken = [GULSecureCoding archivedDataWithRootObject:tokenToArchive error:&error]; - XCTAssertNotNil(archivedToken); - XCTAssertNil(error); - - FIRAppCheckStoredToken *unarchivedToken = - [GULSecureCoding unarchivedObjectOfClass:[FIRAppCheckStoredToken class] - fromData:archivedToken - error:&error]; - XCTAssertNotNil(unarchivedToken); - XCTAssertNil(error); - XCTAssertEqualObjects(unarchivedToken.token, tokenToArchive.token); - XCTAssertEqualObjects(unarchivedToken.expirationDate, tokenToArchive.expirationDate); - XCTAssertEqualObjects(unarchivedToken.receivedAtDate, tokenToArchive.receivedAtDate); - XCTAssertEqual(unarchivedToken.storageVersion, tokenToArchive.storageVersion); -} - -- (void)testConvertingToAndFromFIRAppCheckToken { - FIRAppCheckToken *originalToken = [[FIRAppCheckToken alloc] initWithToken:@"___" - expirationDate:[NSDate date] - receivedAtDate:[NSDate date]]; - - FIRAppCheckStoredToken *storedToken = [[FIRAppCheckStoredToken alloc] init]; - [storedToken updateWithToken:originalToken]; - XCTAssertEqualObjects(originalToken.token, storedToken.token); - XCTAssertEqualObjects(originalToken.expirationDate, storedToken.expirationDate); - XCTAssertEqualObjects(originalToken.receivedAtDate, storedToken.receivedAtDate); - - FIRAppCheckToken *recoveredToken = [storedToken appCheckToken]; - XCTAssertEqualObjects(recoveredToken.token, storedToken.token); - XCTAssertEqualObjects(recoveredToken.expirationDate, storedToken.expirationDate); - XCTAssertEqualObjects(recoveredToken.receivedAtDate, storedToken.receivedAtDate); -} - -@end diff --git a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckTests.m b/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckTests.m index 8ad5428d5c8..3b80258f078 100644 --- a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckTests.m +++ b/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckTests.m @@ -31,38 +31,37 @@ #import "FirebaseAppCheck/Sources/Core/FIRAppCheckSettings.h" #import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h" #import "FirebaseAppCheck/Sources/Core/FIRAppCheckTokenResult.h" -#import "FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStorage.h" -#import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefreshResult.h" -#import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefresher.h" #import "FirebaseCore/Extension/FirebaseCoreInternal.h" // The FAC token value returned when an error occurs. static NSString *const kDummyToken = @"eyJlcnJvciI6IlVOS05PV05fRVJST1IifQ=="; -@interface FIRAppCheck (Tests) +extern void FIRResetLogger(void); + +@interface FIRAppCheck (Tests) - (instancetype)initWithAppName:(NSString *)appName + appCheckCore:(GACAppCheck *)appCheckCore appCheckProvider:(id)appCheckProvider - storage:(id)storage - tokenRefresher:(id)tokenRefresher notificationCenter:(NSNotificationCenter *)notificationCenter - settings:(id)settings; + settings:(FIRAppCheckSettings *)settings; - (nullable instancetype)initWithApp:(FIRApp *)app; + +- (void)tokenDidUpdate:(nonnull GACAppCheckToken *)token + serviceName:(nonnull NSString *)serviceName; + @end @interface FIRAppCheckTests : XCTestCase @property(nonatomic) NSString *appName; -@property(nonatomic) OCMockObject *mockStorage; @property(nonatomic) OCMockObject *mockAppCheckProvider; -@property(nonatomic) OCMockObject *mockTokenRefresher; -@property(nonatomic) OCMockObject *mockSettings; +@property(nonatomic) id mockSettings; @property(nonatomic) NSNotificationCenter *notificationCenter; +@property(nonatomic) id mockAppCheckCore; @property(nonatomic) FIRAppCheck *appCheck; -@property(nonatomic, copy, nullable) FIRAppCheckTokenRefreshBlock tokenRefreshHandler; - @end @implementation FIRAppCheckTests @@ -70,123 +69,66 @@ @implementation FIRAppCheckTests - (void)setUp { [super setUp]; + FIRResetLogger(); + self.appName = @"FIRAppCheckTests"; - self.mockStorage = OCMProtocolMock(@protocol(FIRAppCheckStorageProtocol)); - self.mockAppCheckProvider = OCMProtocolMock(@protocol(FIRAppCheckProvider)); - self.mockTokenRefresher = OCMProtocolMock(@protocol(FIRAppCheckTokenRefresherProtocol)); - self.mockSettings = OCMProtocolMock(@protocol(FIRAppCheckSettingsProtocol)); + self.mockAppCheckProvider = OCMStrictProtocolMock(@protocol(FIRAppCheckProvider)); + self.mockSettings = OCMStrictClassMock([FIRAppCheckSettings class]); self.notificationCenter = [[NSNotificationCenter alloc] init]; - [self stubSetTokenRefreshHandler]; + self.mockAppCheckCore = OCMStrictClassMock([GACAppCheck class]); self.appCheck = [[FIRAppCheck alloc] initWithAppName:self.appName + appCheckCore:self.mockAppCheckCore appCheckProvider:self.mockAppCheckProvider - storage:self.mockStorage - tokenRefresher:self.mockTokenRefresher notificationCenter:self.notificationCenter settings:self.mockSettings]; } - (void)tearDown { self.appCheck = nil; - [self.mockAppCheckProvider stopMocking]; + self.mockAppCheckCore = nil; self.mockAppCheckProvider = nil; - [self.mockStorage stopMocking]; - self.mockStorage = nil; - [self.mockTokenRefresher stopMocking]; - self.mockTokenRefresher = nil; [super tearDown]; } - (void)testInitWithApp { + NSString *projectID = @"testInitWithApp_projectID"; NSString *googleAppID = @"testInitWithApp_googleAppID"; NSString *appName = @"testInitWithApp_appName"; NSString *appGroupID = @"testInitWithApp_appGroupID"; // 1. Stub FIRApp and validate usage. - id mockApp = OCMStrictClassMock([FIRApp class]); - id mockAppOptions = OCMStrictClassMock([FIROptions class]); - OCMStub([mockApp name]).andReturn(appName); - OCMStub([(FIRApp *)mockApp options]).andReturn(mockAppOptions); - OCMExpect([mockAppOptions googleAppID]).andReturn(googleAppID); - OCMExpect([mockAppOptions appGroupID]).andReturn(appGroupID); - - // 2. Stub FIRAppCheckTokenRefresher and validate usage. - id mockTokenRefresher = OCMClassMock([FIRAppCheckTokenRefresher class]); - OCMExpect([mockTokenRefresher alloc]).andReturn(mockTokenRefresher); - - id refresherDateValidator = - [OCMArg checkWithBlock:^BOOL(FIRAppCheckTokenRefreshResult *refreshResult) { - XCTAssertEqual(refreshResult.status, FIRAppCheckTokenRefreshStatusNever); - XCTAssertEqual(refreshResult.tokenExpirationDate, nil); - XCTAssertEqual(refreshResult.tokenReceivedAtDate, nil); - return YES; - }]; - - id settingsValidator = [OCMArg checkWithBlock:^BOOL(id obj) { - XCTAssert([obj isKindOfClass:[FIRAppCheckSettings class]]); - return YES; - }]; - - OCMExpect([mockTokenRefresher initWithRefreshResult:refresherDateValidator - settings:settingsValidator]) - .andReturn(mockTokenRefresher); - OCMExpect([mockTokenRefresher setTokenRefreshHandler:[OCMArg any]]); - - // 3. Stub FIRAppCheckStorage and validate usage. - id mockStorage = OCMClassMock([FIRAppCheckStorage class]); - OCMExpect([mockStorage alloc]).andReturn(mockStorage); - OCMExpect([mockStorage initWithAppName:appName appID:googleAppID accessGroup:appGroupID]) - .andReturn(mockStorage); - - // 4. Stub attestation provider. + FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:googleAppID GCMSenderID:@""]; + options.projectID = projectID; + options.appGroupID = appGroupID; + FIRApp *app = [[FIRApp alloc] initInstanceWithName:appName options:options]; + // The following disables automatic token refresh, which could interfere with tests. + app.dataCollectionDefaultEnabled = NO; + + // 2. Stub attestation provider. OCMockObject *mockProviderFactory = - OCMProtocolMock(@protocol(FIRAppCheckProviderFactory)); - OCMockObject *mockProvider = OCMProtocolMock(@protocol(FIRAppCheckProvider)); - OCMExpect([mockProviderFactory createProviderWithApp:mockApp]).andReturn(mockProvider); + OCMStrictProtocolMock(@protocol(FIRAppCheckProviderFactory)); + OCMockObject *mockProvider = + OCMStrictProtocolMock(@protocol(FIRAppCheckProvider)); + OCMExpect([mockProviderFactory createProviderWithApp:app]).andReturn(mockProvider); [FIRAppCheck setAppCheckProviderFactory:mockProviderFactory]; - // 5. Call init. - FIRAppCheck *appCheck = [[FIRAppCheck alloc] initWithApp:mockApp]; + // 3. Set the Firebase logging level to Debug. + FIRSetLoggerLevel(FIRLoggerLevelDebug); + + // 4. Call init. + FIRAppCheck *appCheck = [[FIRAppCheck alloc] initWithApp:app]; XCTAssert([appCheck isKindOfClass:[FIRAppCheck class]]); - // 6. Verify mocks. - OCMVerifyAll(mockApp); - OCMVerifyAll(mockAppOptions); - OCMVerifyAll(mockTokenRefresher); - OCMVerifyAll(mockStorage); + // 5. Verify mocks. OCMVerifyAll(mockProviderFactory); OCMVerifyAll(mockProvider); - // 7. Stop mocking real class mocks. - [mockApp stopMocking]; - mockApp = nil; - [mockAppOptions stopMocking]; - mockAppOptions = nil; - [mockTokenRefresher stopMocking]; - mockTokenRefresher = nil; - [mockStorage stopMocking]; - mockStorage = nil; -} - -- (void)testAppCheckDefaultInstance { - // Should throw an exception when the default app is not configured. - XCTAssertThrows([FIRAppCheck appCheck]); - - // Configure default FIRApp. - FIROptions *options = - [[FIROptions alloc] initWithGoogleAppID:@"1:100000000000:ios:aaaaaaaaaaaaaaaaaaaaaaaa" - GCMSenderID:@"sender_id"]; - options.APIKey = @"api_key"; - options.projectID = @"project_id"; - [FIRApp configureWithOptions:options]; - - // Check. - XCTAssertNotNil([FIRAppCheck appCheck]); - - [FIRApp resetApps]; + // 6. Verify that the App Check Core logging level is also Debug. + XCTAssertEqual(GACAppCheckLogger.logLevel, GACAppCheckLogLevelDebug); } - (void)testAppCheckInstanceForApp { @@ -196,72 +138,25 @@ - (void)testAppCheckInstanceForApp { options.APIKey = @"api_key"; options.projectID = @"project_id"; - [FIRApp configureWithName:@"testAppCheckInstanceForApp" options:options]; - FIRApp *app = [FIRApp appNamed:@"testAppCheckInstanceForApp"]; + FIRApp *app = [[FIRApp alloc] initInstanceWithName:@"testAppCheckInstanceForApp" options:options]; + // The following disables automatic token refresh, which could interfere with tests. + app.dataCollectionDefaultEnabled = NO; XCTAssertNotNil(app); XCTAssertNotNil([FIRAppCheck appCheckWithApp:app]); - [FIRApp resetApps]; + // Verify that the App Check Core logging level is the default (Warning). + XCTAssertEqual(GACAppCheckLogger.logLevel, GACAppCheckLogLevelWarning); } #pragma mark - Public Get Token -- (void)testGetToken_WhenNoCache_Success { - // 1. Create expected token and configure expectations. - FIRAppCheckToken *expectedToken = [self validToken]; - - NSArray * /*[tokenNotification, getToken]*/ expectations = - [self configuredExpectations_GetTokenWhenNoCache_withExpectedToken:expectedToken]; - - // 2. Request token and verify result. - [self.appCheck - tokenForcingRefresh:NO - completion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - [expectations.lastObject fulfill]; - XCTAssertNotNil(token); - XCTAssertEqualObjects(token.token, expectedToken.token); - XCTAssertNil(error); - }]; - - // 3. Wait for expectations and validate mocks. - [self waitForExpectations:expectations timeout:0.5]; - [self verifyAllMocks]; -} - -- (void)testGetToken_WhenCachedTokenIsValid_Success { - [self assertGetToken_WhenCachedTokenIsValid_Success]; -} - -- (void)testGetTokenForcingRefresh_WhenCachedTokenIsValid_Success { +- (void)testGetToken_Success { // 1. Create expected token and configure expectations. FIRAppCheckToken *expectedToken = [self validToken]; NSArray * /*[tokenNotification, getToken]*/ expectations = - [self configuredExpectations_GetTokenForcingRefreshWhenCacheIsValid_withExpectedToken: - expectedToken]; - - // 2. Request token and verify result. - [self.appCheck - tokenForcingRefresh:YES - completion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - [expectations.lastObject fulfill]; - XCTAssertNotNil(token); - XCTAssertEqualObjects(token.token, expectedToken.token); - XCTAssertNil(error); - }]; - - // 3. Wait for expectations and validate mocks. - [self waitForExpectations:expectations timeout:0.5]; - [self verifyAllMocks]; -} - -- (void)testGetToken_WhenCachedTokenExpired_Success { - // 1. Create expected token and configure expectations. - FIRAppCheckToken *expectedToken = [self validToken]; - - NSArray * /*[tokenNotification, getToken]*/ expectations = - [self configuredExpectations_GetTokenWhenCachedTokenExpired_withExpectedToken:expectedToken]; + [self configuredExpectations_TokenForcingRefresh_withExpectedToken:expectedToken]; // 2. Request token and verify result. [self.appCheck @@ -305,7 +200,10 @@ - (void)testGetToken_AppCheckProviderError { - (void)testGetToken_ServerUnreachableError { // 1. Create expected error and configure expectations. - NSError *serverError = [FIRAppCheckErrorUtil APIErrorWithNetworkError:[self internalError]]; + NSError *serverError = [self appCheckCoreErrorWithCode:GACAppCheckErrorCodeServerUnreachable + failureReason:@"API request error." + underlyingError:[self internalError]]; + NSError *publicServerError = [FIRAppCheckErrorUtil publicDomainErrorWithError:serverError]; NSArray * /*[tokenNotification, getToken]*/ expectations = [self configuredExpectations_GetTokenWhenError_withError:serverError andToken:nil]; @@ -317,8 +215,10 @@ - (void)testGetToken_ServerUnreachableError { [expectations.lastObject fulfill]; XCTAssertNil(token); XCTAssertNotNil(error); - XCTAssertEqualObjects(error, serverError); + XCTAssertEqualObjects(error, publicServerError); XCTAssertEqualObjects(error.domain, FIRAppCheckErrorDomain); + XCTAssertEqual(error.code, FIRAppCheckErrorCodeServerUnreachable); + XCTAssertEqualObjects(error.userInfo, serverError.userInfo); }]; // 3. Wait for expectations and validate mocks. @@ -326,45 +226,12 @@ - (void)testGetToken_ServerUnreachableError { [self verifyAllMocks]; } -- (void)testGetToken_KeychainError { - // 1. Expect token to be requested from storage. - NSError *keychainError = [FIRAppCheckErrorUtil keychainErrorWithError:[self internalError]]; - OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:keychainError]); - - // 2. Expect token requested from app check provider. - FIRAppCheckToken *expectedToken = [self validToken]; - id completionArg = [OCMArg invokeBlockWithArgs:expectedToken, [NSNull null], nil]; - OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]); - - // 3. Expect new token to be stored. - OCMExpect([self.mockStorage setToken:expectedToken]) - .andReturn([FBLPromise resolvedWith:keychainError]); - - // 4. Don't expect token update notification to be sent. - XCTestExpectation *notificationExpectation = [self tokenUpdateNotificationWithExpectedToken:@"" - isInverted:YES]; - - // 5. Request token and verify result. - XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"]; - [self.appCheck - tokenForcingRefresh:NO - completion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - [getTokenExpectation fulfill]; - XCTAssertNil(token); - XCTAssertNotNil(error); - XCTAssertEqualObjects(error, keychainError); - XCTAssertEqualObjects(error.domain, FIRAppCheckErrorDomain); - }]; - - // 3. Wait for expectations and validate mocks. - [self waitForExpectations:@[ notificationExpectation, getTokenExpectation ] timeout:0.5]; - [self verifyAllMocks]; -} - - (void)testGetToken_UnsupportedError { // 1. Create expected error and configure expectations. - NSError *providerError = - [FIRAppCheckErrorUtil unsupportedAttestationProvider:@"AppAttestProvider"]; + NSError *providerError = [self appCheckCoreErrorWithCode:GACAppCheckErrorCodeUnsupported + failureReason:@"AppAttestProvider unsupported" + underlyingError:nil]; + NSError *publicProviderError = [FIRAppCheckErrorUtil publicDomainErrorWithError:providerError]; NSArray * /*[tokenNotification, getToken]*/ expectations = [self configuredExpectations_GetTokenWhenError_withError:providerError andToken:nil]; @@ -376,8 +243,10 @@ - (void)testGetToken_UnsupportedError { [expectations.lastObject fulfill]; XCTAssertNil(token); XCTAssertNotNil(error); - XCTAssertEqualObjects(error, providerError); + XCTAssertEqualObjects(error, publicProviderError); XCTAssertEqualObjects(error.domain, FIRAppCheckErrorDomain); + XCTAssertEqual(error.code, FIRAppCheckErrorCodeUnsupported); + XCTAssertEqualObjects(error.userInfo, providerError.userInfo); }]; // 3. Wait for expectations and validate mocks. @@ -387,58 +256,12 @@ - (void)testGetToken_UnsupportedError { #pragma mark - FIRAppCheckInterop Get Token -- (void)testInteropGetToken_WhenNoCache_Success { - // 1. Create expected token and configure expectations. - FIRAppCheckToken *expectedToken = [self validToken]; - - NSArray * /*[tokenNotification, getToken]*/ expectations = - [self configuredExpectations_GetTokenWhenNoCache_withExpectedToken:expectedToken]; - - // 2. Request token and verify result. - [self.appCheck getTokenForcingRefresh:NO - completion:^(id tokenResult) { - [expectations.lastObject fulfill]; - XCTAssertNotNil(tokenResult); - XCTAssertEqualObjects(tokenResult.token, expectedToken.token); - XCTAssertNil(tokenResult.error); - }]; - - // 3. Wait for expectations and validate mocks. - [self waitForExpectations:expectations timeout:0.5]; - [self verifyAllMocks]; -} - -- (void)testInteropGetToken_WhenCachedTokenIsValid_Success { - [self assertInteropGetToken_WhenCachedTokenIsValid_Success]; -} - -- (void)testInteropGetTokenForcingRefresh_WhenCachedTokenIsValid_Success { - // 1. Create expected token and configure expectations. - FIRAppCheckToken *expectedToken = [self validToken]; - NSArray * /*[tokenNotification, getToken]*/ expectations = - [self configuredExpectations_GetTokenForcingRefreshWhenCacheIsValid_withExpectedToken: - expectedToken]; - - // 2. Request token and verify result. - [self.appCheck getTokenForcingRefresh:YES - completion:^(id tokenResult) { - [expectations.lastObject fulfill]; - XCTAssertNotNil(tokenResult); - XCTAssertEqualObjects(tokenResult.token, expectedToken.token); - XCTAssertNil(tokenResult.error); - }]; - - // 3. Wait for expectations and validate mocks. - [self waitForExpectations:expectations timeout:0.5]; - [self verifyAllMocks]; -} - -- (void)testInteropGetToken_WhenCachedTokenExpired_Success { +- (void)testInteropGetTokenForcingRefresh_Success { // 1. Create expected token and configure expectations. FIRAppCheckToken *expectedToken = [self validToken]; NSArray * /*[tokenNotification, getToken]*/ expectations = - [self configuredExpectations_GetTokenWhenCachedTokenExpired_withExpectedToken:expectedToken]; + [self configuredExpectations_TokenForcingRefresh_withExpectedToken:expectedToken]; // 2. Request token and verify result. [self.appCheck getTokenForcingRefresh:NO @@ -454,7 +277,7 @@ - (void)testInteropGetToken_WhenCachedTokenExpired_Success { [self verifyAllMocks]; } -- (void)testInteropGetToken_AppCheckProviderError { +- (void)testInteropGetTokenForcingRefresh_AppCheckProviderError { // 1. Create expected tokens and errors and configure expectations. FIRAppCheckToken *cachedToken = [self soonExpiringToken]; NSError *providerError = [NSError errorWithDomain:@"FIRAppCheckTests" code:-1 userInfo:nil]; @@ -479,94 +302,18 @@ - (void)testInteropGetToken_AppCheckProviderError { [self verifyAllMocks]; } -#pragma mark - Token refresher - -- (void)testTokenRefreshTriggeredAndRefreshSuccess { - // 1. Expect token to be requested from storage. - OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:nil]); - - // 2. Expect token requested from app check provider. - NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:10000]; - FIRAppCheckToken *tokenToReturn = [[FIRAppCheckToken alloc] initWithToken:@"valid" - expirationDate:expirationDate]; - id completionArg = [OCMArg invokeBlockWithArgs:tokenToReturn, [NSNull null], nil]; - OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]); - - // 3. Expect new token to be stored. - OCMExpect([self.mockStorage setToken:tokenToReturn]) - .andReturn([FBLPromise resolvedWith:tokenToReturn]); - - // 4. Expect token update notification to be sent. - XCTestExpectation *notificationExpectation = - [self tokenUpdateNotificationWithExpectedToken:tokenToReturn.token]; - - // 5. Trigger refresh and expect the result. - if (self.tokenRefreshHandler == nil) { - XCTFail(@"`tokenRefreshHandler` must be not `nil`."); - return; - } - - XCTestExpectation *completionExpectation = [self expectationWithDescription:@"completion"]; - self.tokenRefreshHandler(^(FIRAppCheckTokenRefreshResult *refreshResult) { - [completionExpectation fulfill]; - XCTAssertEqualObjects(refreshResult.tokenExpirationDate, expirationDate); - XCTAssertEqual(refreshResult.status, FIRAppCheckTokenRefreshStatusSuccess); - }); - - [self waitForExpectations:@[ notificationExpectation, completionExpectation ] timeout:0.5]; - [self verifyAllMocks]; -} - -- (void)testTokenRefreshTriggeredAndRefreshError { - // 1. Expect token to be requested from storage. - OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:nil]); - - // 2. Expect token requested from app check provider. - NSError *providerError = [self internalError]; - id completionArg = [OCMArg invokeBlockWithArgs:[NSNull null], providerError, nil]; - OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]); - - // 3. Don't expect token requested from app check provider. - OCMReject([self.mockAppCheckProvider getTokenWithCompletion:[OCMArg any]]); - - // 4. Don't expect token update notification to be sent. - XCTestExpectation *notificationExpectation = [self tokenUpdateNotificationWithExpectedToken:@"" - isInverted:YES]; - - // 5. Trigger refresh and expect the result. - if (self.tokenRefreshHandler == nil) { - XCTFail(@"`tokenRefreshHandler` must be not `nil`."); - return; - } - - XCTestExpectation *completionExpectation = [self expectationWithDescription:@"completion"]; - self.tokenRefreshHandler(^(FIRAppCheckTokenRefreshResult *refreshResult) { - [completionExpectation fulfill]; - XCTAssertEqual(refreshResult.status, FIRAppCheckTokenRefreshStatusFailure); - XCTAssertNil(refreshResult.tokenExpirationDate); - XCTAssertNil(refreshResult.tokenReceivedAtDate); - }); - - [self waitForExpectations:@[ notificationExpectation, completionExpectation ] timeout:0.5]; - [self verifyAllMocks]; -} - - (void)testLimitedUseTokenWithSuccess { - // 1. Don't expect token to be requested from storage. - OCMReject([self.mockStorage getToken]); - - // 2. Expect token requested from app check provider. + // 1. Expect token requested from app check provider. FIRAppCheckToken *expectedToken = [self validToken]; - id completionArg = [OCMArg invokeBlockWithArgs:expectedToken, [NSNull null], nil]; - OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]); + GACAppCheckToken *expectedInternalToken = [expectedToken internalToken]; + GACAppCheckTokenResult *expectedTokenResult = + [[GACAppCheckTokenResult alloc] initWithToken:expectedInternalToken]; + id completionArg = [OCMArg invokeBlockWithArgs:expectedTokenResult, nil]; + OCMStub([self.mockAppCheckCore limitedUseTokenWithCompletion:completionArg]); - // 3. Don't expect token requested from storage. - OCMReject([self.mockStorage setToken:expectedToken]); - - // 4. Don't expect token update notification to be sent. - XCTestExpectation *notificationExpectation = [self tokenUpdateNotificationWithExpectedToken:@"" - isInverted:YES]; - // 5. Expect token request to be completed. + // 2. Don't expect token update notification to be sent. + XCTestExpectation *notificationExpectation = [self tokenUpdateNotificationNotPosted]; + // 3. Expect token request to be completed. XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"]; [self.appCheck @@ -581,21 +328,22 @@ - (void)testLimitedUseTokenWithSuccess { } - (void)testLimitedUseToken_WhenTokenGenerationErrors { - // 1. Don't expect token to be requested from storage. - OCMReject([self.mockStorage getToken]); + // 1. Expect error when requesting token from app check provider. + NSError *providerError = [self appCheckCoreErrorWithCode:GACAppCheckErrorCodeKeychain + failureReason:@"Keychain access error." + underlyingError:[self internalError]]; + NSError *publicProviderError = [FIRAppCheckErrorUtil publicDomainErrorWithError:providerError]; + GACAppCheckTokenResult *expectedTokenResult = + [[GACAppCheckTokenResult alloc] initWithError:providerError]; + id completionArg = [OCMArg invokeBlockWithArgs:expectedTokenResult, nil]; + OCMStub([self.mockAppCheckCore limitedUseTokenWithCompletion:completionArg]); - // 2. Expect error when requesting token from app check provider. - NSError *providerError = [FIRAppCheckErrorUtil keychainErrorWithError:[self internalError]]; - id completionArg = [OCMArg invokeBlockWithArgs:[NSNull null], providerError, nil]; - OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]); - - // 3. Don't expect token requested from app check provider. + // 2. Don't expect token requested from app check provider. OCMReject([self.mockAppCheckProvider getTokenWithCompletion:[OCMArg any]]); - // 4. Don't expect token update notification to be sent. - XCTestExpectation *notificationExpectation = [self tokenUpdateNotificationWithExpectedToken:@"" - isInverted:YES]; - // 5. Expect token request to be completed. + // 3. Don't expect token update notification to be sent. + XCTestExpectation *notificationExpectation = [self tokenUpdateNotificationNotPosted]; + // 4. Expect token request to be completed. XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"]; [self.appCheck @@ -603,8 +351,10 @@ - (void)testLimitedUseToken_WhenTokenGenerationErrors { [getTokenExpectation fulfill]; XCTAssertNotNil(error); XCTAssertNil(token.token); - XCTAssertEqualObjects(error, providerError); + XCTAssertEqualObjects(error, publicProviderError); XCTAssertEqualObjects(error.domain, FIRAppCheckErrorDomain); + XCTAssertEqual(error.code, FIRAppCheckErrorCodeKeychain); + XCTAssertEqualObjects(error.userInfo, providerError.userInfo); }]; [self waitForExpectations:@[ notificationExpectation, getTokenExpectation ] timeout:0.5]; @@ -644,204 +394,22 @@ - (void)testSetIsTokenAutoRefreshEnabled { OCMVerifyAll(self.mockSettings); } -#pragma mark - Merging multiple get token requests - -- (void)testGetToken_WhenCalledSeveralTimesSuccess_ThenThereIsOnlyOneOperation { - // 1. Expect a token to be requested and stored. - NSArray * /*[expectedToken, storeTokenPromise]*/ expectedTokenAndPromise = - [self expectTokenRequestFromAppCheckProvider]; - FIRAppCheckToken *expectedToken = expectedTokenAndPromise.firstObject; - FBLPromise *storeTokenPromise = expectedTokenAndPromise.lastObject; - - // 2. Expect token update notification to be sent. - XCTestExpectation *notificationExpectation = - [self tokenUpdateNotificationWithExpectedToken:expectedToken.token]; - - // 3. Request token several times. - NSInteger getTokenCallsCount = 10; - NSMutableArray *getTokenCompletionExpectations = - [NSMutableArray arrayWithCapacity:getTokenCallsCount]; - - for (NSInteger i = 0; i < getTokenCallsCount; i++) { - // 3.1. Expect a completion to be called for each method call. - XCTestExpectation *getTokenExpectation = - [self expectationWithDescription:[NSString stringWithFormat:@"getToken%@", @(i)]]; - [getTokenCompletionExpectations addObject:getTokenExpectation]; - - // 3.2. Request token and verify result. - [self.appCheck - tokenForcingRefresh:NO - completion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - [getTokenExpectation fulfill]; - XCTAssertNotNil(token); - XCTAssertEqualObjects(token.token, expectedToken.token); - XCTAssertNil(error); - }]; - } - - // 3.3. Fulfill the pending promise to finish the get token operation. - [storeTokenPromise fulfill:expectedToken]; - - // 4. Wait for expectations and validate mocks. - NSArray *expectations = - [getTokenCompletionExpectations arrayByAddingObject:notificationExpectation]; - [self waitForExpectations:expectations timeout:0.5]; - [self verifyAllMocks]; - - // 5. Check a get token call after. - [self assertGetToken_WhenCachedTokenIsValid_Success]; -} - -- (void)testGetToken_WhenCalledSeveralTimesError_ThenThereIsOnlyOneOperation { - // 1. Expect a token to be requested and stored. - NSArray * /*[expectedToken, storeTokenPromise]*/ expectedTokenAndPromise = - [self expectTokenRequestFromAppCheckProvider]; - FIRAppCheckToken *expectedToken = expectedTokenAndPromise.firstObject; - FBLPromise *storeTokenPromise = expectedTokenAndPromise.lastObject; - - // 1.1. Create an expected error to be rejected with later. - NSError *storageError = [NSError errorWithDomain:self.name code:0 userInfo:nil]; - - // 2. Don't expect token update notification to be sent. - XCTestExpectation *notificationExpectation = - [self tokenUpdateNotificationWithExpectedToken:expectedToken.token isInverted:YES]; - - // 3. Request token several times. - NSInteger getTokenCallsCount = 10; - NSMutableArray *getTokenCompletionExpectations = - [NSMutableArray arrayWithCapacity:getTokenCallsCount]; - - for (NSInteger i = 0; i < getTokenCallsCount; i++) { - // 3.1. Expect a completion to be called for each method call. - XCTestExpectation *getTokenExpectation = - [self expectationWithDescription:[NSString stringWithFormat:@"getToken%@", @(i)]]; - [getTokenCompletionExpectations addObject:getTokenExpectation]; - - // 3.2. Request token and verify result. - [self.appCheck - tokenForcingRefresh:NO - completion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - [getTokenExpectation fulfill]; - XCTAssertNil(token); - XCTAssertNotNil(error); - XCTAssertNotEqualObjects(error, storageError); - XCTAssertEqualObjects(error.domain, FIRAppCheckErrorDomain); - XCTAssertEqualObjects(error.userInfo[NSUnderlyingErrorKey], storageError); - }]; - } - - // 3.3. Reject the pending promise to finish the get token operation. - [storeTokenPromise reject:storageError]; - - // 4. Wait for expectations and validate mocks. - NSArray *expectations = - [getTokenCompletionExpectations arrayByAddingObject:notificationExpectation]; - [self waitForExpectations:expectations timeout:0.5]; - [self verifyAllMocks]; - - // 5. Check a get token call after. - [self assertGetToken_WhenCachedTokenIsValid_Success]; -} - -- (void)testInteropGetToken_WhenCalledSeveralTimesSuccess_ThenThereIsOnlyOneOperation { - // 1. Expect a token to be requested and stored. - NSArray * /*[expectedToken, storeTokenPromise]*/ expectedTokenAndPromise = - [self expectTokenRequestFromAppCheckProvider]; - FIRAppCheckToken *expectedToken = expectedTokenAndPromise.firstObject; - FBLPromise *storeTokenPromise = expectedTokenAndPromise.lastObject; - - // 2. Expect token update notification to be sent. - XCTestExpectation *notificationExpectation = - [self tokenUpdateNotificationWithExpectedToken:expectedToken.token]; - - // 3. Request token several times. - NSInteger getTokenCallsCount = 10; - NSMutableArray *getTokenCompletionExpectations = - [NSMutableArray arrayWithCapacity:getTokenCallsCount]; - - for (NSInteger i = 0; i < getTokenCallsCount; i++) { - // 3.1. Expect a completion to be called for each method call. - XCTestExpectation *getTokenExpectation = - [self expectationWithDescription:[NSString stringWithFormat:@"getToken%@", @(i)]]; - [getTokenCompletionExpectations addObject:getTokenExpectation]; - - // 3.2. Request token and verify result. - [self.appCheck getTokenForcingRefresh:NO - completion:^(id tokenResult) { - [getTokenExpectation fulfill]; - XCTAssertNotNil(tokenResult); - XCTAssertEqualObjects(tokenResult.token, expectedToken.token); - XCTAssertNil(tokenResult.error); - }]; - } - - // 3.3. Fulfill the pending promise to finish the get token operation. - [storeTokenPromise fulfill:expectedToken]; - - // 4. Wait for expectations and validate mocks. - NSArray *expectations = - [getTokenCompletionExpectations arrayByAddingObject:notificationExpectation]; - [self waitForExpectations:expectations timeout:0.5]; - [self verifyAllMocks]; - - // 5. Check a get token call after. - [self assertInteropGetToken_WhenCachedTokenIsValid_Success]; -} - -- (void)testInteropGetToken_WhenCalledSeveralTimesError_ThenThereIsOnlyOneOperation { - // 1. Expect a token to be requested and stored. - NSArray * /*[expectedToken, storeTokenPromise]*/ expectedTokenAndPromise = - [self expectTokenRequestFromAppCheckProvider]; - FIRAppCheckToken *expectedToken = expectedTokenAndPromise.firstObject; - FBLPromise *storeTokenPromise = expectedTokenAndPromise.lastObject; - - // 1.1. Create an expected error to be reject the store token promise with later. - NSError *storageError = [NSError errorWithDomain:self.name code:0 userInfo:nil]; - - // 2. Don't expect token update notification to be sent. - XCTestExpectation *notificationExpectation = - [self tokenUpdateNotificationWithExpectedToken:expectedToken.token isInverted:YES]; - - // 3. Request token several times. - NSInteger getTokenCallsCount = 10; - NSMutableArray *getTokenCompletionExpectations = - [NSMutableArray arrayWithCapacity:getTokenCallsCount]; - - for (NSInteger i = 0; i < getTokenCallsCount; i++) { - // 3.1. Expect a completion to be called for each method call. - XCTestExpectation *getTokenExpectation = - [self expectationWithDescription:[NSString stringWithFormat:@"getToken%@", @(i)]]; - [getTokenCompletionExpectations addObject:getTokenExpectation]; - - // 3.2. Request token and verify result. - [self.appCheck getTokenForcingRefresh:NO - completion:^(id tokenResult) { - [getTokenExpectation fulfill]; - XCTAssertNotNil(tokenResult); - XCTAssertEqualObjects(tokenResult.error, storageError); - XCTAssertEqualObjects(tokenResult.token, kDummyToken); - }]; - } - - // 3.3. Reject the pending promise to finish the get token operation. - [storeTokenPromise reject:storageError]; - - // 4. Wait for expectations and validate mocks. - NSArray *expectations = - [getTokenCompletionExpectations arrayByAddingObject:notificationExpectation]; - [self waitForExpectations:expectations timeout:0.5]; - [self verifyAllMocks]; - - // 5. Check a get token call after. - [self assertInteropGetToken_WhenCachedTokenIsValid_Success]; -} - #pragma mark - Helpers - (NSError *)internalError { return [NSError errorWithDomain:@"com.internal.error" code:-1 userInfo:nil]; } +- (NSError *)appCheckCoreErrorWithCode:(GACAppCheckErrorCode)code + failureReason:(nullable NSString *)failureReason + underlyingError:(nullable NSError *)underlyingError { + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + userInfo[NSUnderlyingErrorKey] = underlyingError; + userInfo[NSLocalizedFailureReasonErrorKey] = failureReason; + + return [NSError errorWithDomain:GACAppCheckErrorDomain code:code userInfo:userInfo]; +} + - (FIRAppCheckToken *)validToken { return [[FIRAppCheckToken alloc] initWithToken:[NSUUID UUID].UUIDString expirationDate:[NSDate distantFuture]]; @@ -852,20 +420,7 @@ - (FIRAppCheckToken *)soonExpiringToken { return [[FIRAppCheckToken alloc] initWithToken:@"valid" expirationDate:soonExpiringTokenDate]; } -- (void)stubSetTokenRefreshHandler { - id arg = [OCMArg checkWithBlock:^BOOL(id handler) { - self.tokenRefreshHandler = handler; - return YES; - }]; - OCMExpect([self.mockTokenRefresher setTokenRefreshHandler:arg]); -} - - (XCTestExpectation *)tokenUpdateNotificationWithExpectedToken:(NSString *)expectedToken { - return [self tokenUpdateNotificationWithExpectedToken:expectedToken isInverted:NO]; -} - -- (XCTestExpectation *)tokenUpdateNotificationWithExpectedToken:(NSString *)expectedToken - isInverted:(BOOL)isInverted { XCTestExpectation *expectation = [self expectationForNotification:[self.appCheck tokenDidChangeNotificationName] object:nil @@ -880,191 +435,67 @@ - (XCTestExpectation *)tokenUpdateNotificationWithExpectedToken:(NSString *)expe XCTAssertEqualObjects(notification.object, self.appCheck); return YES; }]; - expectation.inverted = isInverted; return expectation; } -- (void)assertGetToken_WhenCachedTokenIsValid_Success { - // 1. Create expected token and configure expectations. - FIRAppCheckToken *cachedToken = [self validToken]; - - NSArray * /*[tokenNotification, getToken]*/ expectations = - [self configuredExpectations_GetTokenWhenCacheTokenIsValid_withExpectedToken:cachedToken]; - - // 2. Request token and verify result. - [self.appCheck - tokenForcingRefresh:NO - completion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - [expectations.lastObject fulfill]; - XCTAssertNotNil(token); - XCTAssertEqualObjects(token.token, cachedToken.token); - XCTAssertNil(error); - }]; - - // 3. Wait for expectations and validate mocks. - [self waitForExpectations:expectations timeout:0.5]; - [self verifyAllMocks]; -} - -- (void)assertInteropGetToken_WhenCachedTokenIsValid_Success { - // 1. Create expected token and configure expectations. - FIRAppCheckToken *cachedToken = [self validToken]; - - NSArray * /*[tokenNotification, getToken]*/ expectations = - [self configuredExpectations_GetTokenWhenCacheTokenIsValid_withExpectedToken:cachedToken]; - - // 2. Request token and verify result. - [self.appCheck getTokenForcingRefresh:NO - completion:^(id tokenResult) { - [expectations.lastObject fulfill]; - XCTAssertNotNil(tokenResult); - XCTAssertEqualObjects(tokenResult.token, cachedToken.token); - XCTAssertNil(tokenResult.error); - }]; - - // 3. Wait for expectations and validate mocks. - [self waitForExpectations:expectations timeout:0.5]; - [self verifyAllMocks]; +- (XCTestExpectation *)tokenUpdateNotificationNotPosted { + XCTNSNotificationExpectation *expectation = [[XCTNSNotificationExpectation alloc] + initWithName:[self.appCheck tokenDidChangeNotificationName] + object:nil + notificationCenter:self.notificationCenter]; + expectation.inverted = YES; + return expectation; } -- (NSArray *)configuredExpectations_GetTokenWhenNoCache_withExpectedToken: +- (NSArray *)configuredExpectations_TokenForcingRefresh_withExpectedToken: (FIRAppCheckToken *)expectedToken { - // 1. Expect token to be requested from storage. - OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:nil]); - - // 2. Expect token requested from app check provider. - id completionArg = [OCMArg invokeBlockWithArgs:expectedToken, [NSNull null], nil]; - OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]); + // 1. Expect token requested from app check core. + GACAppCheckToken *expectedInternalToken = [expectedToken internalToken]; + GACAppCheckTokenResult *expectedTokenResult = + [[GACAppCheckTokenResult alloc] initWithToken:expectedInternalToken]; + id completionArg = [OCMArg invokeBlockWithArgs:expectedTokenResult, nil]; + OCMExpect([self.mockAppCheckCore tokenForcingRefresh:NO completion:completionArg]) + .andDo(^(NSInvocation *invocation) { + [self.appCheck tokenDidUpdate:expectedInternalToken serviceName:self.appName]; + }) + .ignoringNonObjectArgs(); - // 3. Expect new token to be stored. - OCMExpect([self.mockStorage setToken:expectedToken]) - .andReturn([FBLPromise resolvedWith:expectedToken]); - - // 4. Expect token update notification to be sent. + // 2. Expect token update notification to be sent. XCTestExpectation *tokenNotificationExpectation = [self tokenUpdateNotificationWithExpectedToken:expectedToken.token]; - // 5. Expect token request to be completed. + // 3. Expect token request to be completed. XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"]; return @[ tokenNotificationExpectation, getTokenExpectation ]; } -- (NSArray *) - configuredExpectations_GetTokenWhenCacheTokenIsValid_withExpectedToken: - (FIRAppCheckToken *)expectedToken { - // 1. Expect token to be requested from storage. - OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:expectedToken]); - - // 2. Don't expect token requested from app check provider. - OCMReject([self.mockAppCheckProvider getTokenWithCompletion:[OCMArg any]]); - - // 3. Don't expect token update notification to be sent. - XCTestExpectation *tokenNotificationExpectation = - [self tokenUpdateNotificationWithExpectedToken:@"" isInverted:YES]; - - // 4. Expect token request to be completed. - XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"]; - - return @[ tokenNotificationExpectation, getTokenExpectation ]; -} - -- (NSArray *) - configuredExpectations_GetTokenForcingRefreshWhenCacheIsValid_withExpectedToken: - (FIRAppCheckToken *)expectedToken { - // 1. Don't expect token to be requested from storage. - OCMReject([self.mockStorage getToken]); - - // 2. Expect token requested from app check provider. - id completionArg = [OCMArg invokeBlockWithArgs:expectedToken, [NSNull null], nil]; - OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]); - - // 3. Expect new token to be stored. - OCMExpect([self.mockStorage setToken:expectedToken]) - .andReturn([FBLPromise resolvedWith:expectedToken]); - - // 4. Expect token update notification to be sent. - XCTestExpectation *notificationExpectation = - [self tokenUpdateNotificationWithExpectedToken:expectedToken.token]; - - // 5. Expect token request to be completed. - XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"]; - - return @[ notificationExpectation, getTokenExpectation ]; -} - -- (NSArray *) - configuredExpectations_GetTokenWhenCachedTokenExpired_withExpectedToken: - (FIRAppCheckToken *)expectedToken { - // 1. Expect token to be requested from storage. - FIRAppCheckToken *cachedToken = [[FIRAppCheckToken alloc] initWithToken:@"expired" - expirationDate:[NSDate date]]; - OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:cachedToken]); - - // 2. Expect token requested from app check provider. - id completionArg = [OCMArg invokeBlockWithArgs:expectedToken, [NSNull null], nil]; - OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]); - - // 3. Expect new token to be stored. - OCMExpect([self.mockStorage setToken:expectedToken]) - .andReturn([FBLPromise resolvedWith:expectedToken]); - - // 4. Expect token update notification to be sent. - XCTestExpectation *notificationExpectation = - [self tokenUpdateNotificationWithExpectedToken:expectedToken.token]; - - // 5. Expect token request to be completed. - XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"]; - - return @[ notificationExpectation, getTokenExpectation ]; -} - - (NSArray *) configuredExpectations_GetTokenWhenError_withError:(NSError *_Nonnull)error andToken:(FIRAppCheckToken *_Nullable)token { - // 1. Expect token to be requested from storage. - OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:token]); - - // 2. Expect token requested from app check provider. - id completionArg = [OCMArg invokeBlockWithArgs:[NSNull null], error, nil]; - OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]); + // 1. Expect token requested from app check core. + GACAppCheckTokenResult *expectedTokenResult = + [[GACAppCheckTokenResult alloc] initWithError:error]; + id completionArg = [OCMArg invokeBlockWithArgs:expectedTokenResult, nil]; + OCMExpect([self.mockAppCheckCore tokenForcingRefresh:NO completion:completionArg]) + .ignoringNonObjectArgs(); - // 3. Don't expect token requested from app check provider. + // 2. Don't expect token requested from app check provider. OCMReject([self.mockAppCheckProvider getTokenWithCompletion:[OCMArg any]]); - // 4. Expect token update notification to be sent. - XCTestExpectation *notificationExpectation = [self tokenUpdateNotificationWithExpectedToken:@"" - isInverted:YES]; + // 3. Expect token update notification to be sent. + XCTestExpectation *notificationExpectation = [self tokenUpdateNotificationNotPosted]; - // 5. Expect token request to be completed. + // 4. Expect token request to be completed. XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"]; return @[ notificationExpectation, getTokenExpectation ]; } -- (NSArray *)expectTokenRequestFromAppCheckProvider { - // 1. Expect token to be requested from storage. - OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:nil]); - - // 2. Expect token requested from app check provider. - FIRAppCheckToken *expectedToken = [self validToken]; - id completionArg = [OCMArg invokeBlockWithArgs:expectedToken, [NSNull null], nil]; - OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]); - - // 3. Expect new token to be stored. - // 3.1. Create a pending promise to resolve later. - FBLPromise *storeTokenPromise = [FBLPromise pendingPromise]; - // 3.2. Stub storage set token method. - OCMExpect([self.mockStorage setToken:expectedToken]).andReturn(storeTokenPromise); - - return @[ expectedToken, storeTokenPromise ]; -} - - (void)verifyAllMocks { OCMVerifyAll(self.mockAppCheckProvider); - OCMVerifyAll(self.mockStorage); OCMVerifyAll(self.mockSettings); - OCMVerifyAll(self.mockTokenRefresher); + OCMVerifyAll(self.mockAppCheckCore); } @end diff --git a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckTimerTests.m b/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckTimerTests.m deleted file mode 100644 index 9ab60bc04e4..00000000000 --- a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckTimerTests.m +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTimer.h" - -@interface FIRAppCheckTimerTests : XCTestCase - -@end - -@implementation FIRAppCheckTimerTests - -- (void)testTimerProvider { - dispatch_queue_t queue = - dispatch_queue_create("FIRAppCheckTimerTests.testInit", DISPATCH_QUEUE_SERIAL); - NSTimeInterval fireTimerIn = 1; - NSDate *startTime = [NSDate date]; - NSDate *fireDate = [NSDate dateWithTimeIntervalSinceNow:fireTimerIn]; - - FIRTimerProvider timerProvider = [FIRAppCheckTimer timerProvider]; - - XCTestExpectation *timerExpectation = [self expectationWithDescription:@"timer"]; - FIRAppCheckTimer *timer = timerProvider(fireDate, queue, ^{ - NSTimeInterval actuallyFiredIn = [[NSDate date] timeIntervalSinceDate:startTime]; - // Check that fired at proper time (allowing some timer drift). - XCTAssertLessThan(ABS(actuallyFiredIn - fireTimerIn), 0.5); - - [timerExpectation fulfill]; - }); - - XCTAssertNotNil(timer); - - [self waitForExpectations:@[ timerExpectation ] timeout:fireTimerIn + 1]; -} - -- (void)testInit { - dispatch_queue_t queue = - dispatch_queue_create("FIRAppCheckTimerTests.testInit", DISPATCH_QUEUE_SERIAL); - NSTimeInterval fireTimerIn = 2; - NSDate *startTime = [NSDate date]; - NSDate *fireDate = [NSDate dateWithTimeIntervalSinceNow:fireTimerIn]; - - XCTestExpectation *timerExpectation = [self expectationWithDescription:@"timer"]; - FIRAppCheckTimer *timer = [[FIRAppCheckTimer alloc] - initWithFireDate:fireDate - dispatchQueue:queue - block:^{ - NSTimeInterval actuallyFiredIn = [[NSDate date] timeIntervalSinceDate:startTime]; - // Check that fired at proper time (allowing some timer drift). - XCTAssertLessThan(ABS(actuallyFiredIn - fireTimerIn), 0.5); - - [timerExpectation fulfill]; - }]; - - XCTAssertNotNil(timer); - - [self waitForExpectations:@[ timerExpectation ] timeout:fireTimerIn + 1]; -} - -@end diff --git a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckTokenRefresherTests.m b/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckTokenRefresherTests.m deleted file mode 100644 index 2ffa18441d3..00000000000 --- a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckTokenRefresherTests.m +++ /dev/null @@ -1,502 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import - -#import "FirebaseAppCheck/Sources/Core/FIRAppCheckSettings.h" -#import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefreshResult.h" -#import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefresher.h" -#import "FirebaseAppCheck/Tests/Unit/Utils/FIRFakeTimer.h" -#import "SharedTestUtilities/Date/FIRDateTestUtils.h" - -@interface FIRAppCheckTokenRefresherTests : XCTestCase - -@property(nonatomic) FIRFakeTimer *fakeTimer; - -@property(nonatomic) OCMockObject *mockSettings; - -@property(nonatomic) FIRAppCheckTokenRefreshResult *initialTokenRefreshResult; - -@end - -@implementation FIRAppCheckTokenRefresherTests - -- (void)setUp { - self.mockSettings = OCMProtocolMock(@protocol(FIRAppCheckSettingsProtocol)); - self.fakeTimer = [[FIRFakeTimer alloc] init]; - - NSDate *receivedAtDate = [NSDate date]; - self.initialTokenRefreshResult = [[FIRAppCheckTokenRefreshResult alloc] - initWithStatusSuccessAndExpirationDate:[receivedAtDate dateByAddingTimeInterval:1000] - receivedAtDate:receivedAtDate]; -} - -- (void)tearDown { - self.fakeTimer = nil; - [self.mockSettings stopMocking]; - self.mockSettings = nil; -} - -#pragma mark - Auto refresh is allowed - -- (void)testInitialRefreshWhenAutoRefreshAllowed { - __auto_type weakSelf = self; - - self.initialTokenRefreshResult = [[FIRAppCheckTokenRefreshResult alloc] initWithStatusNever]; - FIRAppCheckTokenRefresher *refresher = [self createRefresher]; - - // 1. Expect checking if auto-refresh allowed before scheduling the initial refresh. - [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled]; - - // 2. Don't expect the timer to be scheduled for the first refresh as the refresh should be - // triggered straight away. - XCTestExpectation *initialTimerCreatedExpectation = - [self expectationWithDescription:@"initial refresh timer created"]; - initialTimerCreatedExpectation.inverted = YES; - self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) { - weakSelf.fakeTimer.createHandler = nil; - [initialTimerCreatedExpectation fulfill]; - }; - - // 3. Expect checking if auto-refresh allowed before triggering the initial refresh. - [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled]; - - // 4. Expect initial refresh handler to be called. - __block FIRAppCheckTokenRefreshCompletion initialRefreshCompletion; - XCTestExpectation *initialRefreshExpectation = - [self expectationWithDescription:@"initial refresh"]; - refresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) { - // Save completion to be called later. - initialRefreshCompletion = completion; - - [initialRefreshExpectation fulfill]; - }; - - NSDate *initialTokenExpirationDate = [NSDate dateWithTimeIntervalSinceNow:60 * 60]; - NSDate *initialTokenReceivedDate = [NSDate date]; - __auto_type initialRefreshResult = [[FIRAppCheckTokenRefreshResult alloc] - initWithStatusSuccessAndExpirationDate:initialTokenExpirationDate - receivedAtDate:initialTokenReceivedDate]; - - [self waitForExpectations:@[ initialTimerCreatedExpectation, initialRefreshExpectation ] - timeout:1]; - - // 5. Expect checking if auto-refresh allowed before scheduling next refresh. - [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled]; - - // 6. Expect a next refresh timer to be scheduled on initial refresh completion. - NSDate *expectedRefreshDate = - [self expectedRefreshDateWithReceivedDate:initialTokenReceivedDate - expirationDate:initialTokenExpirationDate]; - XCTestExpectation *nextTimerCreateExpectation = - [self expectationWithDescription:@"next refresh create timer"]; - self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) { - weakSelf.fakeTimer.createHandler = nil; - XCTAssertEqualObjects(fireDate, expectedRefreshDate); - [nextTimerCreateExpectation fulfill]; - }; - - // 7. Call initial refresh completion and wait for next refresh timer to be scheduled. - initialRefreshCompletion(initialRefreshResult); - [self waitForExpectations:@[ nextTimerCreateExpectation ] timeout:0.5]; - - // 8. Expect checking if auto-refresh allowed before triggering the next refresh. - [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled]; - - // 9. Expect refresh handler to be called for the next refresh. - __auto_type nextRefreshResult = [[FIRAppCheckTokenRefreshResult alloc] - initWithStatusSuccessAndExpirationDate:[expectedRefreshDate dateByAddingTimeInterval:60 * 60] - receivedAtDate:expectedRefreshDate]; - XCTestExpectation *nextRefreshExpectation = [self expectationWithDescription:@"next refresh"]; - refresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) { - [nextRefreshExpectation fulfill]; - - // Call completion. - completion(nextRefreshResult); - }; - - // 10. Fire the timer. - [self fireTimer]; - - // 11. Wait for the next refresh handler to be called. - [self waitForExpectations:@[ nextRefreshExpectation ] timeout:1]; - - OCMVerifyAll(self.mockSettings); -} - -- (void)testNoTimeScheduledUntilHandlerSet { - // 1. Don't expect timer to be scheduled. - XCTestExpectation *timerCreateExpectation1 = [self expectationWithDescription:@"create timer 1"]; - timerCreateExpectation1.inverted = YES; - self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) { - [timerCreateExpectation1 fulfill]; - }; - - // 2. Create a publisher. - FIRAppCheckTokenRefresher *refresher = [self createRefresher]; - - XCTAssertNotNil(refresher); - - [self waitForExpectations:@[ timerCreateExpectation1 ] timeout:0.5]; - - // 3. Expect timer to be created after the handler has been set. - // 3.1. Expect checking if auto-refresh allowed one more time when timer fires. - [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled]; - - // 3.2. Expect timer to fire. - XCTestExpectation *timerCreateExpectation2 = [self expectationWithDescription:@"create timer 2"]; - self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) { - [timerCreateExpectation2 fulfill]; - }; - - // 3.3. Set handler. - refresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) { - }; - - [self waitForExpectations:@[ timerCreateExpectation2 ] timeout:0.5]; - - OCMVerifyAll(self.mockSettings); -} - -- (void)testNextRefreshOnRefreshSuccess { - FIRAppCheckTokenRefresher *refresher = [self createRefresher]; - - NSDate *refreshedTokenExpirationDate = - [self.initialTokenRefreshResult.tokenExpirationDate dateByAddingTimeInterval:60 * 60]; - __auto_type refreshResult = [[FIRAppCheckTokenRefreshResult alloc] - initWithStatusSuccessAndExpirationDate:refreshedTokenExpirationDate - receivedAtDate:self.initialTokenRefreshResult.tokenExpirationDate]; - - // 1. Expect checking if auto-refresh allowed before scheduling initial refresh. - [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled]; - - // 2. Expect checking if auto-refresh allowed before calling the refresh handler. - [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled]; - - // 3. Expect refresh handler. - XCTestExpectation *initialRefreshExpectation = - [self expectationWithDescription:@"initial refresh"]; - refresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) { - [initialRefreshExpectation fulfill]; - - // Call completion in a while. - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), - dispatch_get_main_queue(), ^{ - completion(refreshResult); - }); - }; - - // 4. Expect for new timer to be created. - NSDate *expectedFireDate = - [self expectedRefreshDateWithReceivedDate:refreshResult.tokenReceivedAtDate - expirationDate:refreshResult.tokenExpirationDate]; - XCTestExpectation *createTimerExpectation = [self expectationWithDescription:@"create timer"]; - self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) { - [createTimerExpectation fulfill]; - XCTAssertEqualObjects(fireDate, expectedFireDate); - }; - - // 5. Expect checking if auto-refresh allowed before refreshing. - [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled]; - - // 6. Fire initial timer and wait for expectations. - [self fireTimer]; - - [self waitForExpectations:@[ initialRefreshExpectation, createTimerExpectation ] - timeout:1 - enforceOrder:YES]; - - OCMVerifyAll(self.mockSettings); -} - -- (void)testBackoff { - FIRAppCheckTokenRefresher *refresher = [self createRefresher]; - - // Initial backoff interval. - NSTimeInterval expectedBackoffTime = 0; - NSTimeInterval maximumBackoffTime = 16 * 60; // 16 min. - - // 1. Expect checking if auto-refresh allowed before scheduling initial refresh. - [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled]; - - for (NSInteger i = 0; i < 10; i++) { - // 2. Expect checking if auto-refresh allowed before calling the refresh handler. - [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled]; - - // 3. Expect refresh handler. - XCTestExpectation *initialRefreshExpectation = - [self expectationWithDescription:@"initial refresh"]; - refresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) { - [initialRefreshExpectation fulfill]; - - // Call completion in a while. - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), - dispatch_get_main_queue(), ^{ - __auto_type refreshFailure = - [[FIRAppCheckTokenRefreshResult alloc] initWithStatusFailure]; - completion(refreshFailure); - }); - }; - - // 4. Expect for new timer to be created. - // No backoff initially, 1st backoff 30sec, double backoff on each next attempt until 16min. - expectedBackoffTime = expectedBackoffTime == 0 ? 30 : expectedBackoffTime * 2; - expectedBackoffTime = MIN(expectedBackoffTime, maximumBackoffTime); - NSDate *expectedFireDate = [[NSDate date] dateByAddingTimeInterval:expectedBackoffTime]; - - XCTestExpectation *createTimerExpectation = [self expectationWithDescription:@"create timer"]; - self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) { - [createTimerExpectation fulfill]; - - // Check expected and actual fire date are not too different (account for the random part - // and request attempt delay). - XCTAssertLessThan(ABS([expectedFireDate timeIntervalSinceDate:fireDate]), 2); - }; - - // 5. Expect checking if auto-refresh allowed before refreshing. - [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled]; - - // 6. Fire initial timer and wait for expectations. - [self fireTimer]; - - [self waitForExpectations:@[ initialRefreshExpectation, createTimerExpectation ] - timeout:1 - enforceOrder:YES]; - } - - OCMVerifyAll(self.mockSettings); -} - -#pragma mark - Auto refresh is not allowed - -- (void)testNoInitialRefreshWhenAutoRefreshIsNotAllowed { - FIRAppCheckTokenRefresher *refresher = [self createRefresher]; - - // 1. Expect checking if auto-refresh allowed before scheduling initial refresh. - [[[self.mockSettings expect] andReturnValue:@(NO)] isTokenAutoRefreshEnabled]; - - // 2. Don't expect timer to be scheduled. - XCTestExpectation *timerCreateExpectation = [self expectationWithDescription:@"create timer"]; - timerCreateExpectation.inverted = YES; - - __auto_type weakSelf = self; - self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) { - weakSelf.fakeTimer.createHandler = nil; - [timerCreateExpectation fulfill]; - }; - - // 3. Don't expect refresh handler to be called. - __auto_type refreshResult = [[FIRAppCheckTokenRefreshResult alloc] - initWithStatusSuccessAndExpirationDate:[NSDate dateWithTimeIntervalSinceNow:60 * 60] - receivedAtDate:[NSDate date]]; - XCTestExpectation *refreshExpectation = [self expectationWithDescription:@"refresh"]; - refreshExpectation.inverted = YES; - - refresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) { - [refreshExpectation fulfill]; - - // Call completion. - completion(refreshResult); - }; - - // 4. Check if the handler is not fired before the timer. - [self waitForExpectations:@[ timerCreateExpectation, refreshExpectation ] timeout:1]; - - OCMVerifyAll(self.mockSettings); -} - -- (void)testNoRefreshWhenAutoRefreshWasDisabledAfterInit { - FIRAppCheckTokenRefresher *refresher = [self createRefresher]; - - // 1. Expect checking if auto-refresh allowed before scheduling initial refresh. - [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled]; - - // 2. Expect timer to be scheduled. - NSDate *expectedTimerFireDate = - [self expectedRefreshDateWithReceivedDate:self.initialTokenRefreshResult.tokenReceivedAtDate - expirationDate:self.initialTokenRefreshResult.tokenExpirationDate]; - XCTestExpectation *timerCreateExpectation = [self expectationWithDescription:@"create timer"]; - - __auto_type weakSelf = self; - self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) { - weakSelf.fakeTimer.createHandler = nil; - XCTAssertEqualObjects(fireDate, expectedTimerFireDate); - [timerCreateExpectation fulfill]; - }; - - // 3. Expect refresh handler to be called. - __auto_type refreshResult = [[FIRAppCheckTokenRefreshResult alloc] - initWithStatusSuccessAndExpirationDate:[expectedTimerFireDate - dateByAddingTimeInterval:60 * 60] - receivedAtDate:expectedTimerFireDate]; - XCTestExpectation *noRefreshExpectation = [self expectationWithDescription:@"initial refresh"]; - noRefreshExpectation.inverted = YES; - refresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) { - [noRefreshExpectation fulfill]; - - // Call completion. - completion(refreshResult); - }; - - // 4. Check if the handler is not fired before the timer. - [self waitForExpectations:@[ timerCreateExpectation ] timeout:1]; - - // 5. Expect checking if auto-refresh allowed before refreshing. - [[[self.mockSettings expect] andReturnValue:@(NO)] isTokenAutoRefreshEnabled]; - - // 6. Fire the timer and wait for completion. - [self fireTimer]; - - [self waitForExpectations:@[ noRefreshExpectation ] timeout:1]; - - OCMVerifyAll(self.mockSettings); -} - -#pragma mark - Update token expiration - -- (void)testUpdateWithRefreshResultWhenAutoRefreshIsAllowed { - FIRAppCheckTokenRefresher *refresher = [self createRefresher]; - - NSDate *newExpirationDate = - [self.initialTokenRefreshResult.tokenExpirationDate dateByAddingTimeInterval:10 * 60]; - __auto_type newRefreshResult = [[FIRAppCheckTokenRefreshResult alloc] - initWithStatusSuccessAndExpirationDate:newExpirationDate - receivedAtDate:self.initialTokenRefreshResult.tokenExpirationDate]; - - // 1. Expect checking if auto-refresh allowed before scheduling refresh. - [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled]; - - // 2. Expect timer to be scheduled. - NSDate *expectedTimerFireDate = - [self expectedRefreshDateWithReceivedDate:newRefreshResult.tokenReceivedAtDate - expirationDate:newRefreshResult.tokenExpirationDate]; - XCTestExpectation *timerCreateExpectation = [self expectationWithDescription:@"create timer"]; - - __auto_type weakSelf = self; - self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) { - weakSelf.fakeTimer.createHandler = nil; - XCTAssertEqualObjects(fireDate, expectedTimerFireDate); - [timerCreateExpectation fulfill]; - }; - - // 3. Update token expiration date. - [refresher updateWithRefreshResult:newRefreshResult]; - - // 4. Wait for timer to be created. - [self waitForExpectations:@[ timerCreateExpectation ] timeout:1]; - - OCMVerifyAll(self.mockSettings); -} - -- (void)testUpdateWithRefreshResultWhenAutoRefreshIsNotAllowed { - FIRAppCheckTokenRefresher *refresher = [self createRefresher]; - - __auto_type newRefreshResult = [[FIRAppCheckTokenRefreshResult alloc] - initWithStatusSuccessAndExpirationDate:[NSDate dateWithTimeIntervalSinceNow:60 * 60] - receivedAtDate:self.initialTokenRefreshResult.tokenExpirationDate]; - - // 1. Expect checking if auto-refresh allowed before scheduling initial refresh. - [[[self.mockSettings expect] andReturnValue:@(NO)] isTokenAutoRefreshEnabled]; - - // 2. Don't expect timer to be scheduled. - XCTestExpectation *timerCreateExpectation = [self expectationWithDescription:@"create timer"]; - timerCreateExpectation.inverted = YES; - - __auto_type weakSelf = self; - self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) { - weakSelf.fakeTimer.createHandler = nil; - [timerCreateExpectation fulfill]; - }; - - // 3. Update token expiration date. - [refresher updateWithRefreshResult:newRefreshResult]; - - // 4. Wait for timer to be created. - [self waitForExpectations:@[ timerCreateExpectation ] timeout:1]; - - OCMVerifyAll(self.mockSettings); -} - -- (void)testUpdateWithRefreshResult_WhenTokenExpiresLessThanIn1Minute { - FIRAppCheckTokenRefresher *refresher = [self createRefresher]; - - NSDate *newExpirationDate = [NSDate dateWithTimeIntervalSinceNow:0.5 * 60]; - __auto_type newRefreshResult = [[FIRAppCheckTokenRefreshResult alloc] - initWithStatusSuccessAndExpirationDate:newExpirationDate - receivedAtDate:[NSDate date]]; - - // 1. Expect checking if auto-refresh allowed before scheduling refresh. - [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled]; - - // 2. Expect timer to be scheduled in at least 1 minute. - XCTestExpectation *timerCreateExpectation = [self expectationWithDescription:@"create timer"]; - - __auto_type weakSelf = self; - self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) { - weakSelf.fakeTimer.createHandler = nil; - - // 1 minute is the minimal interval between successful refreshes. - XCTAssert([FIRDateTestUtils isDate:fireDate - approximatelyEqualCurrentPlusTimeInterval:60 - precision:1]); - [timerCreateExpectation fulfill]; - }; - - // 3. Update token expiration date. - [refresher updateWithRefreshResult:newRefreshResult]; - - // 4. Wait for timer to be created. - [self waitForExpectations:@[ timerCreateExpectation ] timeout:1]; - - OCMVerifyAll(self.mockSettings); -} - -#pragma mark - Helpers - -- (void)fireTimer { - if (self.fakeTimer.handler) { - self.fakeTimer.handler(); - } else { - XCTFail(@"handler must not be nil!"); - } -} - -- (FIRAppCheckTokenRefresher *)createRefresher { - return [[FIRAppCheckTokenRefresher alloc] initWithRefreshResult:self.initialTokenRefreshResult - timerProvider:[self.fakeTimer fakeTimerProvider] - settings:self.mockSettings]; -} - -- (NSDate *)expectedRefreshDateWithReceivedDate:(NSDate *)receivedDate - expirationDate:(NSDate *)expirationDate { - NSTimeInterval timeToLive = [expirationDate timeIntervalSinceDate:receivedDate]; - XCTAssertGreaterThanOrEqual(timeToLive, 0); - - NSTimeInterval timeToRefresh = timeToLive / 2 + 5 * 60; // 50% of TTL + 5 min - - NSTimeInterval minimalAutoRefreshInterval = 60; // 1 min - timeToRefresh = MAX(timeToRefresh, minimalAutoRefreshInterval); - - NSDate *refreshDate = [receivedDate dateByAddingTimeInterval:timeToRefresh]; - - NSDate *now = [NSDate date]; - - return [refreshDate laterDate:now]; -} - -@end diff --git a/FirebaseAppCheck/Tests/Unit/DebugProvider/FIRAppCheckDebugProviderAPIServiceTests.m b/FirebaseAppCheck/Tests/Unit/DebugProvider/FIRAppCheckDebugProviderAPIServiceTests.m deleted file mode 100644 index 7b984e8c9ce..00000000000 --- a/FirebaseAppCheck/Tests/Unit/DebugProvider/FIRAppCheckDebugProviderAPIServiceTests.m +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import -#import "FBLPromise+Testing.h" - -#import -#import - -#import "FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckAPIService.h" -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" -#import "FirebaseAppCheck/Sources/DebugProvider/API/FIRAppCheckDebugProviderAPIService.h" - -#import "SharedTestUtilities/URLSession/FIRURLSessionOCMockStub.h" - -@interface FIRAppCheckDebugProviderAPIServiceTests : XCTestCase -@property(nonatomic) FIRAppCheckDebugProviderAPIService *debugAPIService; - -@property(nonatomic) id mockAPIService; - -@property(nonatomic) NSString *projectID; -@property(nonatomic) NSString *appID; -@end - -@implementation FIRAppCheckDebugProviderAPIServiceTests - -- (void)setUp { - [super setUp]; - - self.projectID = @"project_id"; - self.appID = @"app_id"; - - self.mockAPIService = OCMProtocolMock(@protocol(FIRAppCheckAPIServiceProtocol)); - OCMStub([self.mockAPIService baseURL]).andReturn(@"https://test.appcheck.url.com/alpha"); - - self.debugAPIService = - [[FIRAppCheckDebugProviderAPIService alloc] initWithAPIService:self.mockAPIService - projectID:self.projectID - appID:self.appID]; -} - -- (void)tearDown { - self.debugAPIService = nil; - [self.mockAPIService stopMocking]; - self.mockAPIService = nil; - [super tearDown]; -} - -- (void)testAppCheckTokenSuccess { - NSString *debugToken = [NSUUID UUID].UUIDString; - FIRAppCheckToken *expectedResult = [[FIRAppCheckToken alloc] initWithToken:@"app_check_token" - expirationDate:[NSDate date]]; - - // 1. Stub API service. - // 1.1. Stub API response. - NSString *expectedRequestURL = - [NSString stringWithFormat:@"%@%@", [self.mockAPIService baseURL], - @"/projects/project_id/apps/app_id:exchangeDebugToken"]; - id URLValidationArg = [OCMArg checkWithBlock:^BOOL(NSURL *URL) { - XCTAssertEqualObjects(URL.absoluteString, expectedRequestURL); - return YES; - }]; - - id HTTPBodyValidationArg = [self HTTPBodyValidationArgWithDebugToken:debugToken]; - NSData *fakeResponseData = [@"fake response" dataUsingEncoding:NSUTF8StringEncoding]; - NSHTTPURLResponse *HTTPResponse = [FIRURLSessionOCMockStub HTTPResponseWithCode:200]; - GULURLSessionDataResponse *APIResponse = - [[GULURLSessionDataResponse alloc] initWithResponse:HTTPResponse HTTPBody:fakeResponseData]; - - OCMExpect([self.mockAPIService sendRequestWithURL:URLValidationArg - HTTPMethod:@"POST" - body:HTTPBodyValidationArg - additionalHeaders:@{@"Content-Type" : @"application/json"}]) - .andReturn([FBLPromise resolvedWith:APIResponse]); - - // 1.2. Stub response parsing. - OCMExpect([self.mockAPIService appCheckTokenWithAPIResponse:APIResponse]) - .andReturn([FBLPromise resolvedWith:expectedResult]); - - // 2. Send request. - __auto_type tokenPromise = [self.debugAPIService appCheckTokenWithDebugToken:debugToken]; - - // 3. Verify. - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - - XCTAssertTrue(tokenPromise.isFulfilled); - XCTAssertNil(tokenPromise.error); - - XCTAssertEqualObjects(tokenPromise.value.token, expectedResult.token); - XCTAssertEqualObjects(tokenPromise.value.expirationDate, expectedResult.expirationDate); - - OCMVerifyAll(self.mockAPIService); -} - -- (void)testAppCheckTokenResponseParsingError { - NSString *debugToken = [NSUUID UUID].UUIDString; - NSError *parsingError = [NSError errorWithDomain:@"testAppCheckTokenResponseParsingError" - code:-1 - userInfo:nil]; - - // 1. Stub API service. - // 1.1. Stub API response. - NSString *expectedRequestURL = - [NSString stringWithFormat:@"%@%@", [self.mockAPIService baseURL], - @"/projects/project_id/apps/app_id:exchangeDebugToken"]; - id URLValidationArg = [OCMArg checkWithBlock:^BOOL(NSURL *URL) { - XCTAssertEqualObjects(URL.absoluteString, expectedRequestURL); - return YES; - }]; - - id HTTPBodyValidationArg = [self HTTPBodyValidationArgWithDebugToken:debugToken]; - NSData *fakeResponseData = [@"fake response" dataUsingEncoding:NSUTF8StringEncoding]; - NSHTTPURLResponse *HTTPResponse = [FIRURLSessionOCMockStub HTTPResponseWithCode:200]; - GULURLSessionDataResponse *APIResponse = - [[GULURLSessionDataResponse alloc] initWithResponse:HTTPResponse HTTPBody:fakeResponseData]; - - OCMExpect([self.mockAPIService sendRequestWithURL:URLValidationArg - HTTPMethod:@"POST" - body:HTTPBodyValidationArg - additionalHeaders:@{@"Content-Type" : @"application/json"}]) - .andReturn([FBLPromise resolvedWith:APIResponse]); - - // 1.2. Stub response parsing. - FBLPromise *rejectedPromise = [FBLPromise pendingPromise]; - [rejectedPromise reject:parsingError]; - OCMExpect([self.mockAPIService appCheckTokenWithAPIResponse:APIResponse]) - .andReturn(rejectedPromise); - - // 2. Send request. - __auto_type tokenPromise = [self.debugAPIService appCheckTokenWithDebugToken:debugToken]; - - // 3. Verify. - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - - XCTAssertTrue(tokenPromise.isRejected); - XCTAssertEqualObjects(tokenPromise.error, parsingError); - XCTAssertNil(tokenPromise.value); - - OCMVerifyAll(self.mockAPIService); -} - -- (void)testAppCheckTokenNetworkError { - NSString *debugToken = [NSUUID UUID].UUIDString; - NSError *APIError = [NSError errorWithDomain:@"testAppCheckTokenNetworkError" - code:-1 - userInfo:nil]; - - // 1. Stub API service. - FBLPromise *rejectedPromise = [FBLPromise pendingPromise]; - [rejectedPromise reject:APIError]; - - id HTTPBodyValidationArg = [self HTTPBodyValidationArgWithDebugToken:debugToken]; - OCMExpect([self.mockAPIService sendRequestWithURL:[OCMArg any] - HTTPMethod:@"POST" - body:HTTPBodyValidationArg - additionalHeaders:@{@"Content-Type" : @"application/json"}]) - .andReturn(rejectedPromise); - - // 2. Send request. - __auto_type tokenPromise = [self.debugAPIService appCheckTokenWithDebugToken:debugToken]; - - // 3. Verify. - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - - XCTAssertTrue(tokenPromise.isRejected); - XCTAssertNil(tokenPromise.value); - XCTAssertEqualObjects(tokenPromise.error, APIError); - - OCMVerifyAll(self.mockAPIService); -} - -#pragma mark - Helpores - -- (id)HTTPBodyValidationArgWithDebugToken:(NSString *)debugToken { - return [OCMArg checkWithBlock:^BOOL(NSData *body) { - NSDictionary *decodedData = [NSJSONSerialization JSONObjectWithData:body - options:0 - error:nil]; - XCTAssert([decodedData isKindOfClass:[NSDictionary class]]); - - NSString *decodeDebugToken = decodedData[@"debug_token"]; - XCTAssertNotNil(decodeDebugToken); - XCTAssertEqualObjects(decodeDebugToken, debugToken); - return YES; - }]; -} - -@end diff --git a/FirebaseAppCheck/Tests/Unit/DebugProvider/FIRAppCheckDebugProviderFactoryTests.m b/FirebaseAppCheck/Tests/Unit/DebugProvider/FIRAppCheckDebugProviderFactoryTests.m index 8101c635a00..9e524812b58 100644 --- a/FirebaseAppCheck/Tests/Unit/DebugProvider/FIRAppCheckDebugProviderFactoryTests.m +++ b/FirebaseAppCheck/Tests/Unit/DebugProvider/FIRAppCheckDebugProviderFactoryTests.m @@ -31,6 +31,8 @@ - (void)testCreateProviderWithApp { options.APIKey = @"api_key"; options.projectID = @"project_id"; FIRApp *app = [[FIRApp alloc] initInstanceWithName:@"testInitWithValidApp" options:options]; + // The following disables automatic token refresh, which could interfere with tests. + app.dataCollectionDefaultEnabled = NO; FIRAppCheckDebugProviderFactory *factory = [[FIRAppCheckDebugProviderFactory alloc] init]; diff --git a/FirebaseAppCheck/Tests/Unit/DebugProvider/FIRAppCheckDebugProviderTests.m b/FirebaseAppCheck/Tests/Unit/DebugProvider/FIRAppCheckDebugProviderTests.m index 821ab1193ca..09eef3edfe6 100644 --- a/FirebaseAppCheck/Tests/Unit/DebugProvider/FIRAppCheckDebugProviderTests.m +++ b/FirebaseAppCheck/Tests/Unit/DebugProvider/FIRAppCheckDebugProviderTests.m @@ -17,166 +17,128 @@ #import #import -#import "FBLPromise+Testing.h" +#import "FirebaseAppCheck/Sources/Core/FIRApp+AppCheck.h" #import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h" -#import "FirebaseAppCheck/Sources/DebugProvider/API/FIRAppCheckDebugProviderAPIService.h" +#import "FirebaseAppCheck/Sources/Core/FIRHeartbeatLogger+AppCheck.h" #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckDebugProvider.h" #import "FirebaseCore/Extension/FirebaseCoreInternal.h" -static NSString *const kDebugTokenEnvKey = @"FIRAAppCheckDebugToken"; -static NSString *const kDebugTokenUserDefaultsKey = @"FIRAAppCheckDebugToken"; +static NSString *const kAppName = @"test_app_name"; +static NSString *const kAppID = @"test_app_id"; +static NSString *const kAPIKey = @"test_api_key"; +static NSString *const kProjectID = @"test_project_id"; +static NSString *const kProjectNumber = @"123456789"; @interface FIRAppCheckDebugProvider (Tests) -- (instancetype)initWithAPIService:(id)APIService; +- (instancetype)initWithDebugProvider:(GACAppCheckDebugProvider *)debugProvider; @end @interface FIRAppCheckDebugProviderTests : XCTestCase +@property(nonatomic, copy) NSString *resourceName; +@property(nonatomic) id debugProviderMock; @property(nonatomic) FIRAppCheckDebugProvider *provider; -@property(nonatomic) id processInfoMock; -@property(nonatomic) id fakeAPIService; @end -typedef void (^FIRAppCheckTokenValidationBlock)(FIRAppCheckToken *_Nullable token, - NSError *_Nullable error); - @implementation FIRAppCheckDebugProviderTests - (void)setUp { - self.processInfoMock = OCMPartialMock([NSProcessInfo processInfo]); - - self.fakeAPIService = OCMProtocolMock(@protocol(FIRAppCheckDebugProviderAPIServiceProtocol)); - self.provider = [[FIRAppCheckDebugProvider alloc] initWithAPIService:self.fakeAPIService]; + self.resourceName = [NSString stringWithFormat:@"projects/%@/apps/%@", kProjectID, kAppID]; + self.debugProviderMock = OCMStrictClassMock([GACAppCheckDebugProvider class]); + self.provider = [[FIRAppCheckDebugProvider alloc] initWithDebugProvider:self.debugProviderMock]; } - (void)tearDown { self.provider = nil; - [self.processInfoMock stopMocking]; - self.processInfoMock = nil; - [[NSUserDefaults standardUserDefaults] removeObjectForKey:kDebugTokenUserDefaultsKey]; + [self.debugProviderMock stopMocking]; + self.debugProviderMock = nil; } #pragma mark - Initialization - (void)testInitWithValidApp { - FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"app_id" GCMSenderID:@"sender_id"]; - options.APIKey = @"api_key"; - options.projectID = @"project_id"; - FIRApp *app = [[FIRApp alloc] initInstanceWithName:@"testInitWithValidApp" options:options]; + FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:kAppID GCMSenderID:kProjectNumber]; + options.APIKey = kAPIKey; + options.projectID = kProjectID; + FIRApp *app = [[FIRApp alloc] initInstanceWithName:kAppName options:options]; + // The following disables automatic token refresh, which could interfere with tests. + app.dataCollectionDefaultEnabled = NO; XCTAssertNotNil([[FIRAppCheckDebugProvider alloc] initWithApp:app]); } - (void)testInitWithIncompleteApp { - FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"app_id" GCMSenderID:@"sender_id"]; + FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:kAppID GCMSenderID:kProjectNumber]; + options.projectID = kProjectID; + FIRApp *missingAPIKeyApp = [[FIRApp alloc] initInstanceWithName:kAppName options:options]; + // The following disables automatic token refresh, which could interfere with tests. + missingAPIKeyApp.dataCollectionDefaultEnabled = NO; - options.projectID = @"project_id"; - FIRApp *missingAPIKeyApp = [[FIRApp alloc] initInstanceWithName:@"testInitWithValidApp" - options:options]; XCTAssertNil([[FIRAppCheckDebugProvider alloc] initWithApp:missingAPIKeyApp]); options.projectID = nil; - options.APIKey = @"api_key"; - FIRApp *missingProjectIDApp = [[FIRApp alloc] initInstanceWithName:@"testInitWithValidApp" - options:options]; + options.APIKey = kAPIKey; + FIRApp *missingProjectIDApp = [[FIRApp alloc] initInstanceWithName:kAppName options:options]; + // The following disables automatic token refresh, which could interfere with tests. + missingProjectIDApp.dataCollectionDefaultEnabled = NO; XCTAssertNil([[FIRAppCheckDebugProvider alloc] initWithApp:missingProjectIDApp]); } -#pragma mark - Debug token generating/storing - -- (void)testCurrentTokenWhenEnvironmentVariableSetAndTokenStored { - [[NSUserDefaults standardUserDefaults] setObject:@"stored token" - forKey:kDebugTokenUserDefaultsKey]; - NSString *envToken = @"env token"; - OCMStub([self.processInfoMock processInfo]).andReturn(self.processInfoMock); - OCMExpect([self.processInfoMock environment]).andReturn(@{kDebugTokenEnvKey : envToken}); - - XCTAssertEqualObjects([self.provider currentDebugToken], envToken); -} - -- (void)testCurrentTokenWhenNoEnvironmentVariableAndTokenStored { - NSString *storedToken = @"stored token"; - [[NSUserDefaults standardUserDefaults] setObject:storedToken forKey:kDebugTokenUserDefaultsKey]; - - XCTAssertNil(NSProcessInfo.processInfo.environment[kDebugTokenEnvKey]); - - XCTAssertEqualObjects([self.provider currentDebugToken], storedToken); -} - -- (void)testCurrentTokenWhenNoEnvironmentVariableAndNoTokenStored { - XCTAssertNil(NSProcessInfo.processInfo.environment[kDebugTokenEnvKey]); - XCTAssertNil([[NSUserDefaults standardUserDefaults] stringForKey:kDebugTokenUserDefaultsKey]); +#pragma mark - Current Debug token - NSString *generatedToken = [self.provider currentDebugToken]; - XCTAssertNotNil(generatedToken); +- (void)testCurrentTokenShim { + NSString *currentToken = @"debug_token"; + OCMExpect([self.debugProviderMock currentDebugToken]).andReturn(currentToken); - // Check if the generated token is stored to the user defaults. - XCTAssertEqualObjects( - [[NSUserDefaults standardUserDefaults] stringForKey:kDebugTokenUserDefaultsKey], - generatedToken); + XCTAssertEqualObjects([self.provider currentDebugToken], currentToken); - // Check if the same token is used once generated. - XCTAssertEqualObjects([self.provider currentDebugToken], generatedToken); + OCMVerifyAll(self.debugProviderMock); } #pragma mark - Debug token to FAC token exchange - (void)testGetTokenSuccess { - // 1. Stub API service. - NSString *expectedDebugToken = [self.provider currentDebugToken]; - FIRAppCheckToken *validToken = [[FIRAppCheckToken alloc] initWithToken:@"valid_token" - expirationDate:[NSDate date] - receivedAtDate:[NSDate date]]; - OCMExpect([self.fakeAPIService appCheckTokenWithDebugToken:expectedDebugToken]) - .andReturn([FBLPromise resolvedWith:validToken]); + // 1. Stub internal debug provider. + GACAppCheckToken *validInternalToken = [[GACAppCheckToken alloc] initWithToken:@"valid_token" + expirationDate:[NSDate date] + receivedAtDate:[NSDate date]]; + OCMExpect([self.debugProviderMock + getTokenWithCompletion:([OCMArg + invokeBlockWithArgs:validInternalToken, [NSNull null], nil])]); // 2. Validate get token. - [self validateGetToken:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - XCTAssertNil(error); - XCTAssertEqualObjects(token.token, validToken.token); - XCTAssertEqualObjects(token.expirationDate, validToken.expirationDate); - XCTAssertEqualObjects(token.receivedAtDate, validToken.receivedAtDate); - }]; - - // 3. Verify fakes. - OCMVerifyAll(self.fakeAPIService); + [self.provider + getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { + XCTAssertEqualObjects(token.token, validInternalToken.token); + XCTAssertEqualObjects(token.expirationDate, validInternalToken.expirationDate); + XCTAssertEqualObjects(token.receivedAtDate, validInternalToken.receivedAtDate); + XCTAssertNil(error); + }]; + + // 3. Verify mock debug provider. + OCMVerifyAll(self.debugProviderMock); } - (void)testGetTokenAPIError { - // 1. Stub API service. - NSString *expectedDebugToken = [self.provider currentDebugToken]; - NSError *APIError = [NSError errorWithDomain:@"testGetTokenAPIError" code:-1 userInfo:nil]; - FBLPromise *rejectedPromise = [FBLPromise pendingPromise]; - [rejectedPromise reject:APIError]; - OCMExpect([self.fakeAPIService appCheckTokenWithDebugToken:expectedDebugToken]) - .andReturn(rejectedPromise); + // 1. Stub internal debug provider. + NSError *expectedError = [NSError errorWithDomain:@"testGetTokenAPIError" code:-1 userInfo:nil]; + OCMExpect([self.debugProviderMock + getTokenWithCompletion:([OCMArg invokeBlockWithArgs:[NSNull null], expectedError, nil])]); // 2. Validate get token. - [self validateGetToken:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - XCTAssertEqualObjects(error, APIError); - XCTAssertNil(token); - }]; - - // 3. Verify fakes. - OCMVerifyAll(self.fakeAPIService); -} - -#pragma mark - Helpers - -- (void)validateGetToken:(FIRAppCheckTokenValidationBlock)validationBlock { - XCTestExpectation *expectation = [self expectationWithDescription:@"getToken"]; [self.provider getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - validationBlock(token, error); - [expectation fulfill]; + XCTAssertNil(token); + XCTAssertEqualObjects(error, expectedError); }]; - [self waitForExpectations:@[ expectation ] timeout:0.5]; + // 3. Verify mock debug provider. + OCMVerifyAll(self.debugProviderMock); } @end diff --git a/FirebaseAppCheck/Tests/Unit/DeviceCheckProvider/FIRDeviceCheckAPIServiceTests.m b/FirebaseAppCheck/Tests/Unit/DeviceCheckProvider/FIRDeviceCheckAPIServiceTests.m deleted file mode 100644 index 640b17c8fdd..00000000000 --- a/FirebaseAppCheck/Tests/Unit/DeviceCheckProvider/FIRDeviceCheckAPIServiceTests.m +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import -#import "FBLPromise+Testing.h" - -#import -#import - -#import "FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckAPIService.h" -#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h" -#import "FirebaseAppCheck/Sources/DeviceCheckProvider/API/FIRDeviceCheckAPIService.h" -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckErrors.h" - -#import "FirebaseAppCheck/Tests/Unit/Utils/FIRFixtureLoader.h" -#import "SharedTestUtilities/URLSession/FIRURLSessionOCMockStub.h" - -#import "FirebaseCore/Extension/FirebaseCoreInternal.h" - -typedef BOOL (^FIRRequestValidationBlock)(NSURLRequest *request); - -@interface FIRDeviceCheckAPIServiceTests : XCTestCase -@property(nonatomic) FIRDeviceCheckAPIService *APIService; - -@property(nonatomic) id mockAPIService; - -@property(nonatomic) NSString *projectID; -@property(nonatomic) NSString *appID; - -@end - -@implementation FIRDeviceCheckAPIServiceTests - -- (void)setUp { - [super setUp]; - - self.projectID = @"project_id"; - self.appID = @"app_id"; - - self.mockAPIService = OCMProtocolMock(@protocol(FIRAppCheckAPIServiceProtocol)); - OCMStub([self.mockAPIService baseURL]).andReturn(@"https://test.appcheck.url.com/alpha"); - - self.APIService = [[FIRDeviceCheckAPIService alloc] initWithAPIService:self.mockAPIService - projectID:self.projectID - appID:self.appID]; -} - -- (void)tearDown { - self.APIService = nil; - [self.mockAPIService stopMocking]; - self.mockAPIService = nil; - - [super tearDown]; -} - -- (void)testAppCheckTokenSuccess { - NSData *deviceTokenData = [@"device_token" dataUsingEncoding:NSUTF8StringEncoding]; - FIRAppCheckToken *expectedResult = [[FIRAppCheckToken alloc] initWithToken:@"app_check_token" - expirationDate:[NSDate date]]; - - // 1. Stub API service. - // 1.1 Stub send request. - NSString *expectedRequestURL = - [NSString stringWithFormat:@"%@%@", [self.mockAPIService baseURL], - @"/projects/project_id/apps/app_id:exchangeDeviceCheckToken"]; - id URLValidationArg = [OCMArg checkWithBlock:^BOOL(NSURL *URL) { - XCTAssertEqualObjects(URL.absoluteString, expectedRequestURL); - return YES; - }]; - - id HTTPBodyValidationArg = [self HTTPBodyValidationArgWithDeviceToken:deviceTokenData]; - - NSData *responseBody = - [FIRFixtureLoader loadFixtureNamed:@"FACTokenExchangeResponseSuccess.json"]; - XCTAssertNotNil(responseBody); - - NSHTTPURLResponse *HTTPResponse = [FIRURLSessionOCMockStub HTTPResponseWithCode:200]; - GULURLSessionDataResponse *APIResponse = - [[GULURLSessionDataResponse alloc] initWithResponse:HTTPResponse HTTPBody:responseBody]; - - OCMExpect([self.mockAPIService sendRequestWithURL:URLValidationArg - HTTPMethod:@"POST" - body:HTTPBodyValidationArg - additionalHeaders:@{@"Content-Type" : @"application/json"}]) - .andReturn([FBLPromise resolvedWith:APIResponse]); - - // 1.2. Stub response parsing. - OCMExpect([self.mockAPIService appCheckTokenWithAPIResponse:APIResponse]) - .andReturn([FBLPromise resolvedWith:expectedResult]); - - // 2. Send request. - __auto_type tokenPromise = [self.APIService appCheckTokenWithDeviceToken:deviceTokenData]; - - // 3. Verify. - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - - XCTAssertTrue(tokenPromise.isFulfilled); - XCTAssertNil(tokenPromise.error); - - XCTAssertEqualObjects(tokenPromise.value.token, expectedResult.token); - XCTAssertEqualObjects(tokenPromise.value.expirationDate, expectedResult.expirationDate); - - OCMVerifyAll(self.mockAPIService); -} - -- (void)testAppCheckTokenResponseParsingError { - NSData *deviceTokenData = [@"device_token" dataUsingEncoding:NSUTF8StringEncoding]; - NSError *parsingError = [NSError errorWithDomain:@"testAppCheckTokenResponseParsingError" - code:-1 - userInfo:nil]; - - // 1. Stub API service. - // 1.1 Stub send request. - NSString *expectedRequestURL = - [NSString stringWithFormat:@"%@%@", [self.mockAPIService baseURL], - @"/projects/project_id/apps/app_id:exchangeDeviceCheckToken"]; - id URLValidationArg = [OCMArg checkWithBlock:^BOOL(NSURL *URL) { - XCTAssertEqualObjects(URL.absoluteString, expectedRequestURL); - return YES; - }]; - - id HTTPBodyValidationArg = [self HTTPBodyValidationArgWithDeviceToken:deviceTokenData]; - - NSData *responseBody = - [FIRFixtureLoader loadFixtureNamed:@"FACTokenExchangeResponseSuccess.json"]; - XCTAssertNotNil(responseBody); - - NSHTTPURLResponse *HTTPResponse = [FIRURLSessionOCMockStub HTTPResponseWithCode:200]; - GULURLSessionDataResponse *APIResponse = - [[GULURLSessionDataResponse alloc] initWithResponse:HTTPResponse HTTPBody:responseBody]; - - OCMExpect([self.mockAPIService sendRequestWithURL:URLValidationArg - HTTPMethod:@"POST" - body:HTTPBodyValidationArg - additionalHeaders:@{@"Content-Type" : @"application/json"}]) - .andReturn([FBLPromise resolvedWith:APIResponse]); - - // 1.2. Stub response parsing. - FBLPromise *rejectedPromise = [FBLPromise pendingPromise]; - [rejectedPromise reject:parsingError]; - OCMExpect([self.mockAPIService appCheckTokenWithAPIResponse:APIResponse]) - .andReturn(rejectedPromise); - - // 2. Send request. - __auto_type tokenPromise = [self.APIService appCheckTokenWithDeviceToken:deviceTokenData]; - - // 3. Verify. - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - - XCTAssertTrue(tokenPromise.isRejected); - XCTAssertEqualObjects(tokenPromise.error, parsingError); - XCTAssertNil(tokenPromise.value); - - OCMVerifyAll(self.mockAPIService); -} - -- (void)testAppCheckTokenNetworkError { - NSData *deviceTokenData = [@"device_token" dataUsingEncoding:NSUTF8StringEncoding]; - NSError *APIError = [NSError errorWithDomain:@"testAppCheckTokenNetworkError" - code:-1 - userInfo:nil]; - - // 1. Stub API service. - FBLPromise *rejectedPromise = [FBLPromise pendingPromise]; - [rejectedPromise reject:APIError]; - - id HTTPBodyValidationArg = [self HTTPBodyValidationArgWithDeviceToken:deviceTokenData]; - OCMExpect([self.mockAPIService sendRequestWithURL:[OCMArg any] - HTTPMethod:@"POST" - body:HTTPBodyValidationArg - additionalHeaders:@{@"Content-Type" : @"application/json"}]) - .andReturn(rejectedPromise); - - // 2. Send request. - __auto_type tokenPromise = [self.APIService appCheckTokenWithDeviceToken:deviceTokenData]; - - // 3. Verify. - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - - XCTAssertTrue(tokenPromise.isRejected); - XCTAssertNil(tokenPromise.value); - XCTAssertEqualObjects(tokenPromise.error, APIError); - - OCMVerifyAll(self.mockAPIService); -} - -- (void)testAppCheckTokenEmptyDeviceToken { - NSData *deviceTokenData = [NSData data]; - - // 1. Stub API service. - OCMReject([self.mockAPIService sendRequestWithURL:[OCMArg any] - HTTPMethod:[OCMArg any] - body:[OCMArg any] - additionalHeaders:[OCMArg any]]); - - // 2. Send request. - __auto_type tokenPromise = [self.APIService appCheckTokenWithDeviceToken:deviceTokenData]; - - // 3. Verify. - XCTAssert(FBLWaitForPromisesWithTimeout(1)); - - XCTAssertTrue(tokenPromise.isRejected); - XCTAssertNil(tokenPromise.value); - - XCTAssertNotNil(tokenPromise.error); - XCTAssertEqualObjects(tokenPromise.error.domain, FIRAppCheckErrorDomain); - XCTAssertEqual(tokenPromise.error.code, FIRAppCheckErrorCodeUnknown); - - // Expect response body and HTTP status code to be included in the error. - NSString *failureReason = tokenPromise.error.userInfo[NSLocalizedFailureReasonErrorKey]; - XCTAssertEqualObjects(failureReason, @"DeviceCheck token must not be empty."); - - OCMVerifyAll(self.mockAPIService); -} - -#pragma mark - Helpers - -- (id)HTTPBodyValidationArgWithDeviceToken:(NSData *)deviceToken { - return [OCMArg checkWithBlock:^BOOL(NSData *body) { - NSDictionary *decodedData = [NSJSONSerialization JSONObjectWithData:body - options:0 - error:nil]; - XCTAssert([decodedData isKindOfClass:[NSDictionary class]]); - - NSString *base64EncodedDeviceToken = decodedData[@"device_token"]; - XCTAssertNotNil(base64EncodedDeviceToken); - - NSData *decodedToken = [[NSData alloc] initWithBase64EncodedString:base64EncodedDeviceToken - options:0]; - XCTAssertEqualObjects(decodedToken, deviceToken); - return YES; - }]; -} - -@end diff --git a/FirebaseAppCheck/Tests/Unit/DeviceCheckProvider/FIRDeviceCheckProviderTests.m b/FirebaseAppCheck/Tests/Unit/DeviceCheckProvider/FIRDeviceCheckProviderTests.m index 976b631b5e2..add63a9961c 100644 --- a/FirebaseAppCheck/Tests/Unit/DeviceCheckProvider/FIRDeviceCheckProviderTests.m +++ b/FirebaseAppCheck/Tests/Unit/DeviceCheckProvider/FIRDeviceCheckProviderTests.m @@ -21,31 +21,29 @@ #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" -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckAvailability.h" #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRDeviceCheckProvider.h" #import "FirebaseCore/Extension/FirebaseCoreInternal.h" -#import "SharedTestUtilities/AppCheckBackoffWrapperFake/FIRAppCheckBackoffWrapperFake.h" +static NSString *const kAppName = @"test_app_name"; +static NSString *const kAppID = @"test_app_id"; +static NSString *const kAPIKey = @"test_api_key"; +static NSString *const kProjectID = @"test_project_id"; +static NSString *const kProjectNumber = @"123456789"; FIR_DEVICE_CHECK_PROVIDER_AVAILABILITY @interface FIRDeviceCheckProvider (Tests) -- (instancetype)initWithAPIService:(id)APIService - deviceTokenGenerator:(id)deviceTokenGenerator - backoffWrapper:(id)backoffWrapper; +- (instancetype)initWithDeviceCheckProvider:(GACDeviceCheckProvider *)deviceCheckProvider; @end FIR_DEVICE_CHECK_PROVIDER_AVAILABILITY @interface FIRDeviceCheckProviderTests : XCTestCase +@property(nonatomic, copy) NSString *resourceName; +@property(nonatomic) id deviceCheckProviderMock; @property(nonatomic) FIRDeviceCheckProvider *provider; -@property(nonatomic) id fakeAPIService; -@property(nonatomic) id fakeTokenGenerator; -@property(nonatomic) FIRAppCheckBackoffWrapperFake *fakeBackoffWrapper; @end @@ -54,274 +52,83 @@ @implementation FIRDeviceCheckProviderTests - (void)setUp { [super setUp]; - self.fakeAPIService = OCMProtocolMock(@protocol(FIRDeviceCheckAPIServiceProtocol)); - self.fakeTokenGenerator = OCMProtocolMock(@protocol(FIRDeviceCheckTokenGenerator)); - - self.fakeBackoffWrapper = [[FIRAppCheckBackoffWrapperFake alloc] init]; - // Don't backoff by default. - self.fakeBackoffWrapper.isNextOperationAllowed = YES; - - self.provider = [[FIRDeviceCheckProvider alloc] initWithAPIService:self.fakeAPIService - deviceTokenGenerator:self.fakeTokenGenerator - backoffWrapper:self.fakeBackoffWrapper]; + self.resourceName = [NSString stringWithFormat:@"projects/%@/apps/%@", kProjectID, kAppID]; + self.deviceCheckProviderMock = OCMStrictClassMock([GACDeviceCheckProvider class]); + self.provider = + [[FIRDeviceCheckProvider alloc] initWithDeviceCheckProvider:self.deviceCheckProviderMock]; } - (void)tearDown { self.provider = nil; - self.fakeAPIService = nil; - self.fakeTokenGenerator = nil; - self.fakeBackoffWrapper = nil; + [self.deviceCheckProviderMock stopMocking]; + self.deviceCheckProviderMock = nil; } - (void)testInitWithValidApp { - FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"app_id" GCMSenderID:@"sender_id"]; - options.APIKey = @"api_key"; - options.projectID = @"project_id"; - FIRApp *app = [[FIRApp alloc] initInstanceWithName:@"testInitWithValidApp" options:options]; + FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:kAppID GCMSenderID:kProjectNumber]; + options.APIKey = kAPIKey; + options.projectID = kProjectID; + FIRApp *app = [[FIRApp alloc] initInstanceWithName:kAppName options:options]; + // The following disables automatic token refresh, which could interfere with tests. + app.dataCollectionDefaultEnabled = NO; XCTAssertNotNil([[FIRDeviceCheckProvider alloc] initWithApp:app]); } - (void)testInitWithIncompleteApp { - FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"app_id" GCMSenderID:@"sender_id"]; + FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:kAppID GCMSenderID:kProjectNumber]; + options.projectID = kProjectID; + FIRApp *missingAPIKeyApp = [[FIRApp alloc] initInstanceWithName:kAppName options:options]; + // The following disables automatic token refresh, which could interfere with tests. + missingAPIKeyApp.dataCollectionDefaultEnabled = NO; - options.projectID = @"project_id"; - FIRApp *missingAPIKeyApp = [[FIRApp alloc] initInstanceWithName:@"testInitWithValidApp" - options:options]; XCTAssertNil([[FIRDeviceCheckProvider alloc] initWithApp:missingAPIKeyApp]); options.projectID = nil; - options.APIKey = @"api_key"; - FIRApp *missingProjectIDApp = [[FIRApp alloc] initInstanceWithName:@"testInitWithValidApp" - options:options]; + options.APIKey = kAPIKey; + FIRApp *missingProjectIDApp = [[FIRApp alloc] initInstanceWithName:kAppName options:options]; + // The following disables automatic token refresh, which could interfere with tests. + missingProjectIDApp.dataCollectionDefaultEnabled = NO; XCTAssertNil([[FIRDeviceCheckProvider alloc] initWithApp:missingProjectIDApp]); } - (void)testGetTokenSuccess { - // 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]); - - // 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]); - - // 4. Expect backoff wrapper to be used. - self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"]; - - // 5. Call getToken and validate the result. - XCTestExpectation *completionExpectation = - [self expectationWithDescription:@"completionExpectation"]; + // 1. Stub internal DeviceCheck provider. + GACAppCheckToken *validInternalToken = [[GACAppCheckToken alloc] initWithToken:@"valid_token" + expirationDate:[NSDate date] + receivedAtDate:[NSDate date]]; + OCMExpect([self.deviceCheckProviderMock + getTokenWithCompletion:([OCMArg + invokeBlockWithArgs:validInternalToken, [NSNull null], nil])]); + + // 2. Validate get token. [self.provider getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - [completionExpectation fulfill]; - XCTAssertEqualObjects(token.token, validToken.token); - XCTAssertEqualObjects(token.expirationDate, validToken.expirationDate); - XCTAssertEqualObjects(token.receivedAtDate, validToken.receivedAtDate); + XCTAssertEqualObjects(token.token, validInternalToken.token); + XCTAssertEqualObjects(token.expirationDate, validInternalToken.expirationDate); + XCTAssertEqualObjects(token.receivedAtDate, validInternalToken.receivedAtDate); XCTAssertNil(error); }]; - [self waitForExpectations:@[ self.fakeBackoffWrapper.backoffExpectation, completionExpectation ] - timeout:0.5 - enforceOrder:YES]; - - // 6. Verify. - XCTAssertNil(self.fakeBackoffWrapper.operationError); - FIRAppCheckToken *wrapperResult = - [self.fakeBackoffWrapper.operationResult isKindOfClass:[FIRAppCheckToken class]] - ? self.fakeBackoffWrapper.operationResult - : nil; - XCTAssertEqualObjects(wrapperResult.token, validToken.token); - - OCMVerifyAll(self.fakeAPIService); - OCMVerifyAll(self.fakeTokenGenerator); + // 3. Verify mock DeviceCheck provider. + OCMVerifyAll(self.deviceCheckProviderMock); } -- (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); +- (void)testGetTokenAPIError { + // 1. Stub internal DeviceCheck provider. + NSError *expectedError = [NSError errorWithDomain:@"testGetTokenAPIError" code:-1 userInfo:nil]; + OCMExpect([self.deviceCheckProviderMock + getTokenWithCompletion:([OCMArg invokeBlockWithArgs:[NSNull null], expectedError, nil])]); - // 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"]; + // 2. Validate get token. [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 - userInfo:nil]; - - // 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, deviceTokenError); - [errorHandlerExpectation fulfill]; - return FIRAppCheckBackoffType1Day; - }; - - // 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]); - - // 3. Don't expect FAA token to be requested. - OCMReject([self.fakeAPIService appCheckTokenWithDeviceToken:[OCMArg any]]); - - // 4. 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, deviceTokenError); - }]; - - [self waitForExpectations:@[ - self.fakeBackoffWrapper.backoffExpectation, errorHandlerExpectation, completionExpectation - ] - timeout:0.5 - enforceOrder:YES]; - - // 5. Verify. - OCMVerifyAll(self.fakeAPIService); - OCMVerifyAll(self.fakeTokenGenerator); - - XCTAssertEqualObjects(self.fakeBackoffWrapper.operationError, deviceTokenError); - XCTAssertNil(self.fakeBackoffWrapper.operationResult); -} - -- (void)testGetTokenWhenAPIServiceFails { - NSError *APIServiceError = [NSError errorWithDomain:@"FIRDeviceCheckProviderTests" - code:-1 - userInfo:nil]; - - // 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, APIServiceError); - [errorHandlerExpectation fulfill]; - return FIRAppCheckBackoffType1Day; - }; - - // 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]); - - // 3. Expect FAA token to be requested. - FBLPromise *rejectedPromise = [FBLPromise pendingPromise]; - [rejectedPromise reject:APIServiceError]; - OCMExpect([self.fakeAPIService appCheckTokenWithDeviceToken:deviceToken]) - .andReturn(rejectedPromise); - - // 4. 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, APIServiceError); - }]; - - [self waitForExpectations:@[ - self.fakeBackoffWrapper.backoffExpectation, errorHandlerExpectation, completionExpectation - ] - timeout:0.5 - enforceOrder:YES]; - - // 5. Verify. - OCMVerifyAll(self.fakeAPIService); - OCMVerifyAll(self.fakeTokenGenerator); - - XCTAssertEqualObjects(self.fakeBackoffWrapper.operationError, APIServiceError); - XCTAssertNil(self.fakeBackoffWrapper.operationResult); -} - -#pragma mark - Backoff tests - -- (void)testGetTokenBackoff { - // 1. Configure backoff. - self.fakeBackoffWrapper.isNextOperationAllowed = NO; - self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"]; - - // 2. Don't expect any operations. - OCMReject([self.fakeAPIService appCheckTokenWithDeviceToken:[OCMArg any]]); - OCMReject([self.fakeTokenGenerator generateTokenWithCompletionHandler: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, self.fakeBackoffWrapper.backoffError); - }]; - - [self waitForExpectations:@[ self.fakeBackoffWrapper.backoffExpectation, completionExpectation ] - timeout:0.5 - enforceOrder:YES]; - - // 4. Verify. - OCMVerifyAll(self.fakeAPIService); - OCMVerifyAll(self.fakeTokenGenerator); + // 3. Verify mock DeviceCheck provider. + OCMVerifyAll(self.deviceCheckProviderMock); } @end diff --git a/FirebaseAppCheck/Tests/Unit/Fixture/AppAttestAttestationResponseSuccess.json b/FirebaseAppCheck/Tests/Unit/Fixture/AppAttestAttestationResponseSuccess.json deleted file mode 100644 index 63b6d7e432e..00000000000 --- a/FirebaseAppCheck/Tests/Unit/Fixture/AppAttestAttestationResponseSuccess.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "artifact" : "dmFsaWQgRmlyZWJhc2UgYXBwIGF0dGVzdCBhcnRpZmFjdA==", - "appCheckToken" : { - "token": "valid_app_check_token", - "ttl": "1800s" - } -} diff --git a/FirebaseAppCheck/Tests/Unit/Fixture/AppAttestResponseMissingChallenge.json b/FirebaseAppCheck/Tests/Unit/Fixture/AppAttestResponseMissingChallenge.json deleted file mode 100644 index c75cd2bab33..00000000000 --- a/FirebaseAppCheck/Tests/Unit/Fixture/AppAttestResponseMissingChallenge.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "ttl": "300s" -} diff --git a/FirebaseAppCheck/Tests/Unit/Fixture/AppAttestResponseSuccess.json b/FirebaseAppCheck/Tests/Unit/Fixture/AppAttestResponseSuccess.json deleted file mode 100644 index fb834e9128b..00000000000 --- a/FirebaseAppCheck/Tests/Unit/Fixture/AppAttestResponseSuccess.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "challenge": "cmFuZG9tX2NoYWxsZW5nZQ==", - "ttl": "300s" -} diff --git a/FirebaseAppCheck/Tests/Unit/Fixture/DeviceCheckResponseMissingTimeToLive.json b/FirebaseAppCheck/Tests/Unit/Fixture/DeviceCheckResponseMissingTimeToLive.json deleted file mode 100644 index be5f8d61ca6..00000000000 --- a/FirebaseAppCheck/Tests/Unit/Fixture/DeviceCheckResponseMissingTimeToLive.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "token": "valid_app_check_token" -} diff --git a/FirebaseAppCheck/Tests/Unit/Fixture/DeviceCheckResponseMissingToken.json b/FirebaseAppCheck/Tests/Unit/Fixture/DeviceCheckResponseMissingToken.json deleted file mode 100644 index ae9ce15fbfa..00000000000 --- a/FirebaseAppCheck/Tests/Unit/Fixture/DeviceCheckResponseMissingToken.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "timeToLive": "3600s" -} diff --git a/FirebaseAppCheck/Tests/Unit/Fixture/FACTokenExchangeResponseSuccess.json b/FirebaseAppCheck/Tests/Unit/Fixture/FACTokenExchangeResponseSuccess.json deleted file mode 100644 index 125364fde26..00000000000 --- a/FirebaseAppCheck/Tests/Unit/Fixture/FACTokenExchangeResponseSuccess.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "token": "valid_app_check_token", - "ttl": "1800s" -} diff --git a/FirebaseAppCheck/Tests/Unit/Utils/FIRFakeTimer.h b/FirebaseAppCheck/Tests/Unit/Utils/FIRFakeTimer.h deleted file mode 100644 index b71340fbcd6..00000000000 --- a/FirebaseAppCheck/Tests/Unit/Utils/FIRFakeTimer.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTimer.h" - -NS_ASSUME_NONNULL_BEGIN - -typedef void (^FIRFakeTimerCreateHandler)(NSDate *fireDate); - -@interface FIRFakeTimer : NSObject - -- (FIRTimerProvider)fakeTimerProvider; - -/// `createHandler` is called each time the timer provider returned by `fakeTimerProvider` is asked -/// to create a timer. -@property(nonatomic, copy, nullable) FIRFakeTimerCreateHandler createHandler; - -@property(nonatomic, copy, nullable) dispatch_block_t invalidationHandler; - -/// The timer handler passed in the timer provider returned by `fakeTimerProvider` method. -@property(nonatomic, copy, nullable) dispatch_block_t handler; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Tests/Unit/Utils/FIRFakeTimer.m b/FirebaseAppCheck/Tests/Unit/Utils/FIRFakeTimer.m deleted file mode 100644 index 19b943e9de7..00000000000 --- a/FirebaseAppCheck/Tests/Unit/Utils/FIRFakeTimer.m +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FirebaseAppCheck/Tests/Unit/Utils/FIRFakeTimer.h" - -@implementation FIRFakeTimer - -- (FIRTimerProvider)fakeTimerProvider { - return ^id _Nullable(NSDate *fireDate, dispatch_queue_t queue, - dispatch_block_t handler) { - self.handler = handler; - if (self.createHandler) { - self.createHandler(fireDate); - } - - return self; - }; -} - -- (void)invalidate { - if (self.invalidationHandler) { - self.invalidationHandler(); - } -} - -@end diff --git a/Package.swift b/Package.swift index 7d742bbdfc5..a89de67c949 100644 --- a/Package.swift +++ b/Package.swift @@ -182,6 +182,7 @@ let package = Package( url: "https://github.com/google/interop-ios-for-google-sdks.git", "100.0.0" ..< "101.0.0" ), + .package(url: "https://github.com/google/app-check.git", "10.18.0" ..< "11.0.0"), ], targets: [ .target( @@ -1223,6 +1224,7 @@ let package = Package( dependencies: [ "FirebaseAppCheckInterop", "FirebaseCore", + .product(name: "AppCheckCore", package: "app-check"), .product(name: "FBLPromises", package: "Promises"), .product(name: "GULEnvironment", package: "GoogleUtilities"), ], @@ -1262,9 +1264,6 @@ let package = Package( // are not supported (as of Xcode 15.0). "Swift", ], - resources: [ - .process("Fixture"), - ], cSettings: [ .headerSearchPath("../../.."), ] @@ -1272,10 +1271,7 @@ let package = Package( .testTarget( name: "FirebaseAppCheckUnitSwift", dependencies: ["FirebaseAppCheck"], - path: "FirebaseAppCheck/Tests/Unit/Swift", - cSettings: [ - .headerSearchPath("../.."), - ] + path: "FirebaseAppCheck/Tests/Unit/Swift" ), // MARK: Testing support diff --git a/SharedTestUtilities/AppCheckBackoffWrapperFake/FIRAppCheckBackoffWrapperFake.h b/SharedTestUtilities/AppCheckBackoffWrapperFake/FIRAppCheckBackoffWrapperFake.h deleted file mode 100644 index 24f7aca3074..00000000000 --- a/SharedTestUtilities/AppCheckBackoffWrapperFake/FIRAppCheckBackoffWrapperFake.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import - -#import "FirebaseAppCheck/Sources/Core/Backoff/FIRAppCheckBackoffWrapper.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface FIRAppCheckBackoffWrapperFake : NSObject - -/// If `YES` then the next operation passed to `[backoff:errorHandler:]` method will be performed. -/// If `NO` then it will fail with a backoff error. -@property(nonatomic) BOOL isNextOperationAllowed; - -/// Result of the last performed operation if it succeeded. -@property(nonatomic, nullable, readonly) id operationResult; - -/// Error of the last performed operation if it failed. -@property(nonatomic, nullable, readonly) NSError *operationError; - -/// Default error handler. -@property(nonatomic, copy) FIRAppCheckBackoffErrorHandler defaultErrorHandler; - -/// Assign expectation to fulfill on `[backoff:errorHandler:]` method call to this property. -@property(nonatomic, nullable) XCTestExpectation *backoffExpectation; - -/// Error returned when retry is not allowed. -@property(nonatomic, readonly) NSError *backoffError; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SharedTestUtilities/AppCheckBackoffWrapperFake/FIRAppCheckBackoffWrapperFake.m b/SharedTestUtilities/AppCheckBackoffWrapperFake/FIRAppCheckBackoffWrapperFake.m deleted file mode 100644 index ae8bfbd6ae8..00000000000 --- a/SharedTestUtilities/AppCheckBackoffWrapperFake/FIRAppCheckBackoffWrapperFake.m +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "SharedTestUtilities/AppCheckBackoffWrapperFake/FIRAppCheckBackoffWrapperFake.h" - -#if __has_include() -#import -#else -#import "FBLPromises.h" -#endif - -NS_ASSUME_NONNULL_BEGIN - -@implementation FIRAppCheckBackoffWrapperFake - -- (FBLPromise *)applyBackoffToOperation:(FIRAppCheckBackoffOperationProvider)operationProvider - errorHandler:(FIRAppCheckBackoffErrorHandler)errorHandler { - [self.backoffExpectation fulfill]; - - if (self.isNextOperationAllowed) { - return operationProvider() - .then(^id(id value) { - self->_operationResult = value; - self->_operationError = nil; - return value; - }) - .recover(^id(NSError *error) { - self->_operationError = error; - self->_operationResult = nil; - - errorHandler(error); - - return error; - }); - } else { - FBLPromise *rejectedPromise = [FBLPromise pendingPromise]; - [rejectedPromise reject:self.backoffError]; - return rejectedPromise; - } -} - -- (FIRAppCheckBackoffErrorHandler)defaultAppCheckProviderErrorHandler { - if (_defaultErrorHandler) { - return _defaultErrorHandler; - } - - return ^FIRAppCheckBackoffType(NSError *error) { - return FIRAppCheckBackoffTypeNone; - }; -} - -- (NSError *)backoffError { - return [NSError errorWithDomain:@"FIRAppCheckBackoffWrapperFake.backoff" code:-1 userInfo:nil]; -} - -@end - -NS_ASSUME_NONNULL_END