| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- /*
- * 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 "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheck.h"
- #if __has_include(<FBLPromises/FBLPromises.h>)
- #import <FBLPromises/FBLPromises.h>
- #else
- #import "FBLPromises.h"
- #endif
- #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckErrors.h"
- #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckProvider.h"
- #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckSettings.h"
- #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckToken.h"
- #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckTokenDelegate.h"
- #import "AppCheckCore/Sources/Core/Errors/GACAppCheckErrorUtil.h"
- #import "AppCheckCore/Sources/Core/GACAppCheckLogger+Internal.h"
- #import "AppCheckCore/Sources/Core/Storage/GACAppCheckStorage.h"
- #import "AppCheckCore/Sources/Core/TokenRefresh/GACAppCheckTokenRefreshResult.h"
- #import "AppCheckCore/Sources/Core/TokenRefresh/GACAppCheckTokenRefresher.h"
- NS_ASSUME_NONNULL_BEGIN
- static const NSTimeInterval kTokenExpirationThreshold = 5 * 60; // 5 min.
- static NSString *const kDummyFACTokenValue = @"eyJlcnJvciI6IlVOS05PV05fRVJST1IifQ==";
- typedef void (^GACAppCheckTokenHandler)(id<GACAppCheckTokenProtocol> _Nullable token,
- NSError *_Nullable error);
- @interface GACAppCheck ()
- @property(nonatomic, readonly) NSString *serviceName;
- @property(nonatomic, readonly) id<GACAppCheckProvider> appCheckProvider;
- @property(nonatomic, readonly) id<GACAppCheckStorageProtocol> storage;
- @property(nonatomic, readonly) id<GACAppCheckSettingsProtocol> settings;
- @property(nonatomic, readonly, nullable, weak) id<GACAppCheckTokenDelegate> tokenDelegate;
- @property(nonatomic, readonly, nullable) id<GACAppCheckTokenRefresherProtocol> tokenRefresher;
- @property(nonatomic, nullable) FBLPromise<GACAppCheckToken *> *ongoingRetrieveOrRefreshTokenPromise;
- @end
- @implementation GACAppCheck
- #pragma mark - Internal
- - (instancetype)initWithServiceName:(NSString *)serviceName
- appCheckProvider:(id<GACAppCheckProvider>)appCheckProvider
- storage:(id<GACAppCheckStorageProtocol>)storage
- tokenRefresher:(id<GACAppCheckTokenRefresherProtocol>)tokenRefresher
- settings:(id<GACAppCheckSettingsProtocol>)settings
- tokenDelegate:(nullable id<GACAppCheckTokenDelegate>)tokenDelegate {
- self = [super init];
- if (self) {
- _serviceName = serviceName;
- _appCheckProvider = appCheckProvider;
- _storage = storage;
- _tokenRefresher = tokenRefresher;
- _settings = settings;
- _tokenDelegate = tokenDelegate;
- __auto_type __weak weakSelf = self;
- tokenRefresher.tokenRefreshHandler = ^(GACAppCheckTokenRefreshCompletion _Nonnull completion) {
- __auto_type strongSelf = weakSelf;
- [strongSelf periodicTokenRefreshWithCompletion:completion];
- };
- }
- return self;
- }
- #pragma mark - Public
- - (instancetype)initWithServiceName:(NSString *)serviceName
- resourceName:(NSString *)resourceName
- appCheckProvider:(id<GACAppCheckProvider>)appCheckProvider
- settings:(id<GACAppCheckSettingsProtocol>)settings
- tokenDelegate:(nullable id<GACAppCheckTokenDelegate>)tokenDelegate
- keychainAccessGroup:(nullable NSString *)accessGroup {
- GACAppCheckTokenRefreshResult *refreshResult =
- [[GACAppCheckTokenRefreshResult alloc] initWithStatusNever];
- GACAppCheckTokenRefresher *tokenRefresher =
- [[GACAppCheckTokenRefresher alloc] initWithRefreshResult:refreshResult settings:settings];
- NSString *tokenKey =
- [NSString stringWithFormat:@"app_check_token.%@.%@", serviceName, resourceName];
- GACAppCheckStorage *storage = [[GACAppCheckStorage alloc] initWithTokenKey:tokenKey
- accessGroup:accessGroup];
- return [self initWithServiceName:serviceName
- appCheckProvider:appCheckProvider
- storage:storage
- tokenRefresher:tokenRefresher
- settings:settings
- tokenDelegate:tokenDelegate];
- }
- #pragma mark - GACAppCheckInterop
- - (void)getTokenForcingRefresh:(BOOL)forcingRefresh completion:(GACAppCheckTokenHandler)handler {
- [self retrieveOrRefreshTokenForcingRefresh:forcingRefresh]
- .then(^id _Nullable(id<GACAppCheckTokenProtocol> token) {
- handler(token, nil);
- return token;
- })
- .catch(^(NSError *_Nonnull error) {
- handler(nil, error);
- });
- }
- - (void)getLimitedUseTokenWithCompletion:(GACAppCheckTokenHandler)handler {
- [self limitedUseToken]
- .then(^id _Nullable(id<GACAppCheckTokenProtocol> token) {
- handler(token, nil);
- return token;
- })
- .catch(^(NSError *_Nonnull error) {
- handler(nil, error);
- });
- }
- #pragma mark - FAA token cache
- - (FBLPromise<GACAppCheckToken *> *)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(^GACAppCheckToken *(GACAppCheckToken *token) {
- self.ongoingRetrieveOrRefreshTokenPromise = nil;
- return token;
- })
- .recover(^NSError *(NSError *error) {
- self.ongoingRetrieveOrRefreshTokenPromise = nil;
- return error;
- });
- }
- return self.ongoingRetrieveOrRefreshTokenPromise;
- }];
- }
- - (FBLPromise<GACAppCheckToken *> *)createRetrieveOrRefreshTokenPromiseForcingRefresh:
- (BOOL)forcingRefresh {
- return [self getCachedValidTokenForcingRefresh:forcingRefresh].recover(
- ^id _Nullable(NSError *_Nonnull error) {
- return [self refreshToken];
- });
- }
- - (FBLPromise<GACAppCheckToken *> *)getCachedValidTokenForcingRefresh:(BOOL)forcingRefresh {
- if (forcingRefresh) {
- FBLPromise *rejectedPromise = [FBLPromise pendingPromise];
- [rejectedPromise reject:[GACAppCheckErrorUtil cachedTokenNotFound]];
- return rejectedPromise;
- }
- return [self.storage getToken].then(^id(GACAppCheckToken *_Nullable token) {
- if (token == nil) {
- return [GACAppCheckErrorUtil cachedTokenNotFound];
- }
- BOOL isTokenExpiredOrExpiresSoon =
- [token.expirationDate timeIntervalSinceNow] < kTokenExpirationThreshold;
- if (isTokenExpiredOrExpiresSoon) {
- return [GACAppCheckErrorUtil cachedTokenExpired];
- }
- return token;
- });
- }
- - (FBLPromise<GACAppCheckToken *> *)refreshToken {
- return [FBLPromise
- wrapObjectOrErrorCompletion:^(FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
- [self.appCheckProvider getTokenWithCompletion:handler];
- }]
- .then(^id _Nullable(GACAppCheckToken *_Nullable token) {
- return [self.storage setToken:token];
- })
- .then(^id _Nullable(GACAppCheckToken *_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 = [[GACAppCheckTokenRefreshResult alloc]
- initWithStatusSuccessAndExpirationDate:token.expirationDate
- receivedAtDate:token.receivedAtDate];
- [self.tokenRefresher updateWithRefreshResult:refreshResult];
- if (self.tokenDelegate) {
- [self.tokenDelegate tokenDidUpdate:token serviceName:self.serviceName];
- }
- return token;
- });
- }
- - (FBLPromise<GACAppCheckToken *> *)limitedUseToken {
- return
- [FBLPromise wrapObjectOrErrorCompletion:^(
- FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
- [self.appCheckProvider getTokenWithCompletion:handler];
- }].then(^id _Nullable(GACAppCheckToken *_Nullable token) {
- return token;
- });
- }
- #pragma mark - Token auto refresh
- - (void)periodicTokenRefreshWithCompletion:(GACAppCheckTokenRefreshCompletion)completion {
- [self retrieveOrRefreshTokenForcingRefresh:NO]
- .then(^id _Nullable(GACAppCheckToken *_Nullable token) {
- __auto_type refreshResult = [[GACAppCheckTokenRefreshResult alloc]
- initWithStatusSuccessAndExpirationDate:token.expirationDate
- receivedAtDate:token.receivedAtDate];
- completion(refreshResult);
- return nil;
- })
- .catch(^(NSError *error) {
- __auto_type refreshResult = [[GACAppCheckTokenRefreshResult alloc] initWithStatusFailure];
- completion(refreshResult);
- });
- }
- @end
- NS_ASSUME_NONNULL_END
|