diff --git a/FirebaseAppCheck.podspec b/FirebaseAppCheck.podspec index 25517557006..c696cb6f18a 100644 --- a/FirebaseAppCheck.podspec +++ b/FirebaseAppCheck.podspec @@ -45,7 +45,7 @@ Pod::Spec.new do |s| s.tvos.weak_framework = 'DeviceCheck' # TODO(andrewheard): Update version number before merging into `master`. - s.dependency 'AppCheckCore', '~> 0.1.0-alpha' + s.dependency 'AppCheckCore', '0.1.0-alpha.9' s.dependency 'FirebaseAppCheckInterop', '~> 10.16' s.dependency 'FirebaseCore', '~> 10.0' diff --git a/FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestProvider.m b/FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestProvider.m index 9cbbaacbe42..f589ac8a5fe 100644 --- a/FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestProvider.m +++ b/FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestProvider.m @@ -73,6 +73,19 @@ - (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable, NSError *_ }]; } +- (void)getLimitedUseTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable, + NSError *_Nullable))handler { + [self.appAttestProvider getLimitedUseTokenWithCompletion:^( + GACAppCheckToken *_Nullable internalToken, NSError *_Nullable error) { + if (error) { + handler(nil, error); + return; + } + + handler([[FIRAppCheckToken alloc] initWithInternalToken:internalToken], nil); + }]; +} + @end NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Core/FIRAppCheck.m b/FirebaseAppCheck/Sources/Core/FIRAppCheck.m index 8608a9fd642..0d2431e2468 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 AppCheckCore; @import FirebaseAppCheckInterop; -#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) FIRAppCheckSettings *settings; +@property(nonatomic, readonly) GACAppCheck *appCheckCore; -@property(nonatomic, readonly, nullable) id tokenRefresher; - -@property(nonatomic, nullable) FBLPromise *ongoingRetrieveOrRefreshTokenPromise; @end @implementation FIRAppCheck @@ -98,47 +85,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:(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 +146,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 +200,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 +235,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; - }]; -} +#pragma mark - GACAppCheckTokenDelegate -- (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 - 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 +254,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/FIRAppCheckToken+Internal.h b/FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h index ecfb435fa7d..a98dc2dad09 100644 --- a/FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h +++ b/FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h @@ -40,6 +40,8 @@ NS_ASSUME_NONNULL_BEGIN /// @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 f937090fa28..1067a2dcc16 100644 --- a/FirebaseAppCheck/Sources/Core/FIRAppCheckToken.m +++ b/FirebaseAppCheck/Sources/Core/FIRAppCheckToken.m @@ -42,6 +42,12 @@ - (instancetype)initWithInternalToken:(GACAppCheckToken *)token { 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/FIRInternalAppCheckProvider.h similarity index 69% rename from FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken+FIRAppCheckToken.h rename to FirebaseAppCheck/Sources/Core/FIRInternalAppCheckProvider.h index 4ba3862755d..31b95708f0e 100644 --- a/FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken+FIRAppCheckToken.h +++ b/FirebaseAppCheck/Sources/Core/FIRInternalAppCheckProvider.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,17 @@ * limitations under the License. */ -#import "FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken.h" +#import -@class FIRAppCheckToken; +@import AppCheckCore; NS_ASSUME_NONNULL_BEGIN -@interface FIRAppCheckStoredToken (FIRAppCheckToken) +@protocol FIRAppCheckProvider; -- (void)updateWithToken:(FIRAppCheckToken *)token; +@interface FIRInternalAppCheckProvider : NSObject -- (FIRAppCheckToken *)appCheckToken; +- (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 6c249e74885..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 GACAppCheckSettingsProtocol; -@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 ab3d83444ce..00000000000 --- a/FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefresher.m +++ /dev/null @@ -1,216 +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 AppCheckCore; - -#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/DebugProvider/FIRAppCheckDebugProvider.m b/FirebaseAppCheck/Sources/DebugProvider/FIRAppCheckDebugProvider.m index 5305d9b0780..f7941bd032d 100644 --- a/FirebaseAppCheck/Sources/DebugProvider/FIRAppCheckDebugProvider.m +++ b/FirebaseAppCheck/Sources/DebugProvider/FIRAppCheckDebugProvider.m @@ -94,6 +94,19 @@ - (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable token, }]; } +- (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 NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckProvider.m b/FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckProvider.m index ae6f6c0ebe5..a34fbed389b 100644 --- a/FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckProvider.m +++ b/FirebaseAppCheck/Sources/DeviceCheckProvider/FIRDeviceCheckProvider.m @@ -84,6 +84,20 @@ - (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable token, }]; } +- (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 NS_ASSUME_NONNULL_END 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 b2166f49edf..3c5a42a1f60 100644 --- a/FirebaseAppCheck/Tests/Integration/AppCheckE2ETests.swift +++ b/FirebaseAppCheck/Tests/Integration/AppCheckE2ETests.swift @@ -18,12 +18,11 @@ import FirebaseAppCheck import FirebaseCore final class AppCheckE2ETests: XCTestCase { - // TODO(andrewheard): Add integration tests that exercise the public API. - let appName = "test_app_name" var app: FirebaseApp! override func setUp() { + AppCheck.setAppCheckProviderFactory(TestAppCheckProviderFactory()) let options = FirebaseOptions(googleAppID: "1:123456789:ios:abc123", gcmSenderID: "123456789") options.projectID = "test_project_id" options.apiKey = "test_api_key" @@ -41,7 +40,6 @@ final class AppCheckE2ETests: XCTestCase { } func testInitAppCheck() throws { - AppCheck.setAppCheckProviderFactory(AppCheckDebugProviderFactory()) let appCheck = AppCheck.appCheck(app: app) XCTAssertNotNil(appCheck) @@ -52,4 +50,96 @@ final class AppCheckE2ETests: XCTestCase { XCTAssertNotNil(debugProvider) } + + func testInitAppCheckDebugProviderFactory() throws { + let debugProvider = AppCheckDebugProviderFactory().createProvider(with: app) + + XCTAssertNotNil(debugProvider) + } + + @available(iOS 11.0, macOS 10.15, macCatalyst 13.0, tvOS 11.0, watchOS 9.0, *) + func testInitDeviceCheckProvider() throws { + let deviceCheckProvider = DeviceCheckProvider(app: app) + + XCTAssertNotNil(deviceCheckProvider) + } + + @available(iOS 11.0, macOS 10.15, macCatalyst 13.0, tvOS 11.0, watchOS 9.0, *) + func testDeviceCheckProviderFactoryCreate() throws { + let deviceCheckProvider = DeviceCheckProviderFactory().createProvider(with: app) + + XCTAssertNotNil(deviceCheckProvider) + } + + @available(iOS 14.0, macOS 11.3, macCatalyst 14.5, tvOS 15.0, watchOS 9.0, *) + func testInitAppAttestProvider() throws { + let appAttestProvider = AppAttestProvider(app: app) + + XCTAssertNotNil(appAttestProvider) + } + + // The following test is disabled on macOS since `token(forcingRefresh:handler:)` requires a + // provisioning profile to access the keychain to cache tokens. + #if !os(macOS) + func testGetToken() throws { + guard let appCheck = AppCheck.appCheck(app: app) else { + XCTFail("AppCheck instance is nil.") + return + } + + let expectation = XCTestExpectation() + appCheck.token(forcingRefresh: true) { token, error in + XCTAssertNil(error) + XCTAssertNotNil(token) + XCTAssertEqual(token?.token, TestAppCheckProvider.tokenValue) + expectation.fulfill() + } + + wait(for: [expectation], timeout: 0.5) + } + #endif + + 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( + token: TestAppCheckProvider.tokenValue, + expirationDate: Date.distantFuture + ) + 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 { + func createProvider(with app: FirebaseApp) -> AppCheckProvider? { + return TestAppCheckProvider() + } } diff --git a/FirebaseAppCheck/Tests/Unit/AppAttestProvider/FIRAppAttestProviderTests.m b/FirebaseAppCheck/Tests/Unit/AppAttestProvider/FIRAppAttestProviderTests.m index f0544f9c202..040195078d8 100644 --- a/FirebaseAppCheck/Tests/Unit/AppAttestProvider/FIRAppAttestProviderTests.m +++ b/FirebaseAppCheck/Tests/Unit/AppAttestProvider/FIRAppAttestProviderTests.m @@ -71,19 +71,10 @@ - (void)testInitWithValidApp { options.APIKey = kAPIKey; options.projectID = kProjectID; FIRApp *app = [[FIRApp alloc] initInstanceWithName:kAppName options:options]; - - OCMExpect([self.appAttestProviderMock alloc]).andReturn(self.appAttestProviderMock); - OCMExpect([self.appAttestProviderMock initWithServiceName:kAppName - resourceName:self.resourceName - baseURL:nil - APIKey:kAPIKey - keychainAccessGroup:OCMOCK_ANY - requestHooks:OCMOCK_ANY]) - .andReturn(self.appAttestProviderMock); + // The following disables automatic token refresh, which could interfere with tests. + app.dataCollectionDefaultEnabled = NO; XCTAssertNotNil([[FIRAppAttestProvider alloc] initWithApp:app]); - - OCMVerifyAll(self.appAttestProviderMock); } - (void)testGetTokenSuccess { diff --git a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckIntegrationTests.m b/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckIntegrationTests.m deleted file mode 100644 index e739c614b93..00000000000 --- a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckIntegrationTests.m +++ /dev/null @@ -1,231 +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 FirebaseAppCheckInterop; - -#import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefresher.h" -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheck.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 - -#if FIR_DEVICE_CHECK_SUPPORTED_TARGETS - -@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 - -#endif // FIR_DEVICE_CHECK_SUPPORTED_TARGETS - -NS_ASSUME_NONNULL_END 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 168ab7c513d..bac97065d6f 100644 --- a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckTests.m +++ b/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckTests.m @@ -18,8 +18,6 @@ #import -#import "FBLPromise+Testing.h" - @import FirebaseAppCheckInterop; #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheck.h" @@ -31,38 +29,35 @@ #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) +@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) id mockSettings; @property(nonatomic) NSNotificationCenter *notificationCenter; +@property(nonatomic) id mockAppCheckCore; @property(nonatomic) FIRAppCheck *appCheck; -@property(nonatomic, copy, nullable) FIRAppCheckTokenRefreshBlock tokenRefreshHandler; - @end @implementation FIRAppCheckTests @@ -71,122 +66,57 @@ - (void)setUp { [super setUp]; self.appName = @"FIRAppCheckTests"; - self.mockStorage = OCMProtocolMock(@protocol(FIRAppCheckStorageProtocol)); - self.mockAppCheckProvider = OCMProtocolMock(@protocol(FIRAppCheckProvider)); - self.mockTokenRefresher = OCMProtocolMock(@protocol(FIRAppCheckTokenRefresherProtocol)); - self.mockSettings = OCMClassMock([FIRAppCheckSettings class]); + 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. Call init. + FIRAppCheck *appCheck = [[FIRAppCheck alloc] initWithApp:app]; XCTAssert([appCheck isKindOfClass:[FIRAppCheck class]]); - // 6. Verify mocks. - OCMVerifyAll(mockApp); - OCMVerifyAll(mockAppOptions); - OCMVerifyAll(mockTokenRefresher); - OCMVerifyAll(mockStorage); + // 4. 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]; } - (void)testAppCheckInstanceForApp { @@ -196,72 +126,22 @@ - (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]; } #pragma mark - Public Get Token -- (void)testGetToken_WhenNoCache_Success { +- (void)testGetToken_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 { - // 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 @@ -326,41 +206,6 @@ - (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 = @@ -387,12 +232,12 @@ - (void)testGetToken_UnsupportedError { #pragma mark - FIRAppCheckInterop Get Token -- (void)testInteropGetToken_WhenNoCache_Success { +- (void)testInteropGetTokenForcingRefresh_Success { // 1. Create expected token and configure expectations. FIRAppCheckToken *expectedToken = [self validToken]; NSArray * /*[tokenNotification, getToken]*/ expectations = - [self configuredExpectations_GetTokenWhenNoCache_withExpectedToken:expectedToken]; + [self configuredExpectations_TokenForcingRefresh_withExpectedToken:expectedToken]; // 2. Request token and verify result. [self.appCheck getTokenForcingRefresh:NO @@ -408,53 +253,7 @@ - (void)testInteropGetToken_WhenNoCache_Success { [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 { - // 1. Create expected token and configure expectations. - FIRAppCheckToken *expectedToken = [self validToken]; - - NSArray * /*[tokenNotification, getToken]*/ expectations = - [self configuredExpectations_GetTokenWhenCachedTokenExpired_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_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 +278,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 +304,19 @@ - (void)testLimitedUseTokenWithSuccess { } - (void)testLimitedUseToken_WhenTokenGenerationErrors { - // 1. Don't expect token to be requested from storage. - OCMReject([self.mockStorage getToken]); - - // 2. Expect error when requesting token from app check provider. + // 1. 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]); + GACAppCheckTokenResult *expectedTokenResult = + [[GACAppCheckTokenResult alloc] initWithError:providerError]; + id completionArg = [OCMArg invokeBlockWithArgs:expectedTokenResult, nil]; + OCMStub([self.mockAppCheckCore limitedUseTokenWithCompletion: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 @@ -644,198 +365,6 @@ - (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 { @@ -852,20 +381,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 +396,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 ca78b1fd68a..00000000000 --- a/FirebaseAppCheck/Tests/Unit/Core/FIRAppCheckTokenRefresherTests.m +++ /dev/null @@ -1,503 +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 AppCheckCore; - -#import - -#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(GACAppCheckSettingsProtocol)); - 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/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 bc08c69f7b9..09eef3edfe6 100644 --- a/FirebaseAppCheck/Tests/Unit/DebugProvider/FIRAppCheckDebugProviderTests.m +++ b/FirebaseAppCheck/Tests/Unit/DebugProvider/FIRAppCheckDebugProviderTests.m @@ -66,33 +66,27 @@ - (void)testInitWithValidApp { options.APIKey = kAPIKey; options.projectID = kProjectID; FIRApp *app = [[FIRApp alloc] initInstanceWithName:kAppName options:options]; - - OCMExpect([self.debugProviderMock alloc]).andReturn(self.debugProviderMock); - OCMExpect([self.debugProviderMock initWithServiceName:kAppName - resourceName:self.resourceName - baseURL:nil - APIKey:kAPIKey - requestHooks:OCMOCK_ANY]) - .andReturn(self.debugProviderMock); + // The following disables automatic token refresh, which could interfere with tests. + app.dataCollectionDefaultEnabled = NO; XCTAssertNotNil([[FIRAppCheckDebugProvider alloc] initWithApp:app]); - - OCMVerifyAll(self.debugProviderMock); } - (void)testInitWithIncompleteApp { 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; XCTAssertNil([[FIRAppCheckDebugProvider alloc] initWithApp:missingAPIKeyApp]); options.projectID = nil; 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]); - - OCMVerifyAll(self.debugProviderMock); } #pragma mark - Current Debug token diff --git a/FirebaseAppCheck/Tests/Unit/DeviceCheckProvider/FIRDeviceCheckProviderTests.m b/FirebaseAppCheck/Tests/Unit/DeviceCheckProvider/FIRDeviceCheckProviderTests.m index b053e5cc9b0..d6a8bbc36ad 100644 --- a/FirebaseAppCheck/Tests/Unit/DeviceCheckProvider/FIRDeviceCheckProviderTests.m +++ b/FirebaseAppCheck/Tests/Unit/DeviceCheckProvider/FIRDeviceCheckProviderTests.m @@ -71,32 +71,27 @@ - (void)testInitWithValidApp { options.APIKey = kAPIKey; options.projectID = kProjectID; FIRApp *app = [[FIRApp alloc] initInstanceWithName:kAppName options:options]; - - OCMExpect([self.deviceCheckProviderMock alloc]).andReturn(self.deviceCheckProviderMock); - OCMExpect([self.deviceCheckProviderMock initWithServiceName:kAppName - resourceName:self.resourceName - APIKey:kAPIKey - requestHooks:OCMOCK_ANY]) - .andReturn(self.deviceCheckProviderMock); + // The following disables automatic token refresh, which could interfere with tests. + app.dataCollectionDefaultEnabled = NO; XCTAssertNotNil([[FIRDeviceCheckProvider alloc] initWithApp:app]); - - OCMVerifyAll(self.deviceCheckProviderMock); } - (void)testInitWithIncompleteApp { 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; XCTAssertNil([[FIRDeviceCheckProvider alloc] initWithApp:missingAPIKeyApp]); options.projectID = nil; 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]); - - OCMVerifyAll(self.deviceCheckProviderMock); } - (void)testGetTokenSuccess { 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