GACAppCheck.m 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. /*
  2. * Copyright 2020 Google LLC
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheck.h"
  17. #if __has_include(<FBLPromises/FBLPromises.h>)
  18. #import <FBLPromises/FBLPromises.h>
  19. #else
  20. #import "FBLPromises.h"
  21. #endif
  22. @import AppCheckCoreInterop;
  23. #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckErrors.h"
  24. #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckProvider.h"
  25. #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckSettings.h"
  26. #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckToken.h"
  27. #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckTokenDelegate.h"
  28. #import "AppCheckCore/Sources/Core/Errors/GACAppCheckErrorUtil.h"
  29. #import "AppCheckCore/Sources/Core/GACAppCheckLogger+Internal.h"
  30. #import "AppCheckCore/Sources/Core/Storage/GACAppCheckStorage.h"
  31. #import "AppCheckCore/Sources/Core/TokenRefresh/GACAppCheckTokenRefreshResult.h"
  32. #import "AppCheckCore/Sources/Core/TokenRefresh/GACAppCheckTokenRefresher.h"
  33. NS_ASSUME_NONNULL_BEGIN
  34. static const NSTimeInterval kTokenExpirationThreshold = 5 * 60; // 5 min.
  35. static NSString *const kDummyFACTokenValue = @"eyJlcnJvciI6IlVOS05PV05fRVJST1IifQ==";
  36. @interface GACAppCheck ()
  37. @property(nonatomic, readonly) NSString *instanceName;
  38. @property(nonatomic, readonly) id<GACAppCheckProvider> appCheckProvider;
  39. @property(nonatomic, readonly) id<GACAppCheckStorageProtocol> storage;
  40. @property(nonatomic, readonly) id<GACAppCheckSettingsProtocol> settings;
  41. @property(nonatomic, readonly, weak) id<GACAppCheckTokenDelegate> tokenDelegate;
  42. @property(nonatomic, readonly, nullable) id<GACAppCheckTokenRefresherProtocol> tokenRefresher;
  43. @property(nonatomic, nullable) FBLPromise<GACAppCheckToken *> *ongoingRetrieveOrRefreshTokenPromise;
  44. @end
  45. @implementation GACAppCheck
  46. #pragma mark - Internal
  47. - (instancetype)initWithInstanceName:(NSString *)instanceName
  48. appCheckProvider:(id<GACAppCheckProvider>)appCheckProvider
  49. storage:(id<GACAppCheckStorageProtocol>)storage
  50. tokenRefresher:(id<GACAppCheckTokenRefresherProtocol>)tokenRefresher
  51. settings:(id<GACAppCheckSettingsProtocol>)settings
  52. tokenDelegate:(id<GACAppCheckTokenDelegate>)tokenDelegate {
  53. self = [super init];
  54. if (self) {
  55. _instanceName = instanceName;
  56. _appCheckProvider = appCheckProvider;
  57. _storage = storage;
  58. _tokenRefresher = tokenRefresher;
  59. _settings = settings;
  60. _tokenDelegate = tokenDelegate;
  61. __auto_type __weak weakSelf = self;
  62. tokenRefresher.tokenRefreshHandler = ^(GACAppCheckTokenRefreshCompletion _Nonnull completion) {
  63. __auto_type strongSelf = weakSelf;
  64. [strongSelf periodicTokenRefreshWithCompletion:completion];
  65. };
  66. }
  67. return self;
  68. }
  69. #pragma mark - Public
  70. - (instancetype)initWithInstanceName:(NSString *)instanceName
  71. appCheckProvider:(id<GACAppCheckProvider>)appCheckProvider
  72. settings:(id<GACAppCheckSettingsProtocol>)settings
  73. tokenDelegate:(id<GACAppCheckTokenDelegate>)tokenDelegate
  74. resourceName:(NSString *)resourceName
  75. keychainAccessGroup:(nullable NSString *)accessGroup {
  76. GACAppCheckTokenRefreshResult *refreshResult =
  77. [[GACAppCheckTokenRefreshResult alloc] initWithStatusNever];
  78. GACAppCheckTokenRefresher *tokenRefresher =
  79. [[GACAppCheckTokenRefresher alloc] initWithRefreshResult:refreshResult settings:settings];
  80. NSString *tokenKey =
  81. [NSString stringWithFormat:@"app_check_token.%@.%@", instanceName, resourceName];
  82. GACAppCheckStorage *storage = [[GACAppCheckStorage alloc] initWithTokenKey:tokenKey
  83. accessGroup:accessGroup];
  84. return [self initWithInstanceName:instanceName
  85. appCheckProvider:appCheckProvider
  86. storage:storage
  87. tokenRefresher:tokenRefresher
  88. settings:settings
  89. tokenDelegate:tokenDelegate];
  90. }
  91. #pragma mark - GACAppCheckInterop
  92. - (void)getTokenForcingRefresh:(BOOL)forcingRefresh
  93. completion:(GACAppCheckTokenHandlerInterop)handler {
  94. [self retrieveOrRefreshTokenForcingRefresh:forcingRefresh]
  95. .then(^id _Nullable(GACAppCheckToken *token) {
  96. handler(token, nil);
  97. return token;
  98. })
  99. .catch(^(NSError *_Nonnull error) {
  100. handler(nil, error);
  101. });
  102. }
  103. - (void)getLimitedUseTokenWithCompletion:(GACAppCheckTokenHandlerInterop)handler {
  104. [self limitedUseToken]
  105. .then(^id _Nullable(GACAppCheckToken *token) {
  106. handler(token, nil);
  107. return token;
  108. })
  109. .catch(^(NSError *_Nonnull error) {
  110. handler(nil, error);
  111. });
  112. }
  113. #pragma mark - FAA token cache
  114. - (FBLPromise<GACAppCheckToken *> *)retrieveOrRefreshTokenForcingRefresh:(BOOL)forcingRefresh {
  115. return [FBLPromise do:^id _Nullable {
  116. if (self.ongoingRetrieveOrRefreshTokenPromise == nil) {
  117. // Kick off a new operation only when there is not an ongoing one.
  118. self.ongoingRetrieveOrRefreshTokenPromise =
  119. [self createRetrieveOrRefreshTokenPromiseForcingRefresh:forcingRefresh]
  120. // Release the ongoing operation promise on completion.
  121. .then(^GACAppCheckToken *(GACAppCheckToken *token) {
  122. self.ongoingRetrieveOrRefreshTokenPromise = nil;
  123. return token;
  124. })
  125. .recover(^NSError *(NSError *error) {
  126. self.ongoingRetrieveOrRefreshTokenPromise = nil;
  127. return error;
  128. });
  129. }
  130. return self.ongoingRetrieveOrRefreshTokenPromise;
  131. }];
  132. }
  133. - (FBLPromise<GACAppCheckToken *> *)createRetrieveOrRefreshTokenPromiseForcingRefresh:
  134. (BOOL)forcingRefresh {
  135. return [self getCachedValidTokenForcingRefresh:forcingRefresh].recover(
  136. ^id _Nullable(NSError *_Nonnull error) {
  137. return [self refreshToken];
  138. });
  139. }
  140. - (FBLPromise<GACAppCheckToken *> *)getCachedValidTokenForcingRefresh:(BOOL)forcingRefresh {
  141. if (forcingRefresh) {
  142. FBLPromise *rejectedPromise = [FBLPromise pendingPromise];
  143. [rejectedPromise reject:[GACAppCheckErrorUtil cachedTokenNotFound]];
  144. return rejectedPromise;
  145. }
  146. return [self.storage getToken].then(^id(GACAppCheckToken *_Nullable token) {
  147. if (token == nil) {
  148. return [GACAppCheckErrorUtil cachedTokenNotFound];
  149. }
  150. BOOL isTokenExpiredOrExpiresSoon =
  151. [token.expirationDate timeIntervalSinceNow] < kTokenExpirationThreshold;
  152. if (isTokenExpiredOrExpiresSoon) {
  153. return [GACAppCheckErrorUtil cachedTokenExpired];
  154. }
  155. return token;
  156. });
  157. }
  158. - (FBLPromise<GACAppCheckToken *> *)refreshToken {
  159. return [FBLPromise
  160. wrapObjectOrErrorCompletion:^(FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
  161. [self.appCheckProvider getTokenWithCompletion:handler];
  162. }]
  163. .then(^id _Nullable(GACAppCheckToken *_Nullable token) {
  164. return [self.storage setToken:token];
  165. })
  166. .then(^id _Nullable(GACAppCheckToken *_Nullable token) {
  167. // TODO: Make sure the self.tokenRefresher is updated only once. Currently the timer will be
  168. // updated twice in the case when the refresh triggered by self.tokenRefresher, but it
  169. // should be fine for now as it is a relatively cheap operation.
  170. __auto_type refreshResult = [[GACAppCheckTokenRefreshResult alloc]
  171. initWithStatusSuccessAndExpirationDate:token.expirationDate
  172. receivedAtDate:token.receivedAtDate];
  173. [self.tokenRefresher updateWithRefreshResult:refreshResult];
  174. [self.tokenDelegate tokenDidUpdate:token instanceName:self.instanceName];
  175. return token;
  176. });
  177. }
  178. - (FBLPromise<GACAppCheckToken *> *)limitedUseToken {
  179. return
  180. [FBLPromise wrapObjectOrErrorCompletion:^(
  181. FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
  182. [self.appCheckProvider getTokenWithCompletion:handler];
  183. }].then(^id _Nullable(GACAppCheckToken *_Nullable token) {
  184. return token;
  185. });
  186. }
  187. #pragma mark - Token auto refresh
  188. - (void)periodicTokenRefreshWithCompletion:(GACAppCheckTokenRefreshCompletion)completion {
  189. [self retrieveOrRefreshTokenForcingRefresh:NO]
  190. .then(^id _Nullable(GACAppCheckToken *_Nullable token) {
  191. __auto_type refreshResult = [[GACAppCheckTokenRefreshResult alloc]
  192. initWithStatusSuccessAndExpirationDate:token.expirationDate
  193. receivedAtDate:token.receivedAtDate];
  194. completion(refreshResult);
  195. return nil;
  196. })
  197. .catch(^(NSError *error) {
  198. __auto_type refreshResult = [[GACAppCheckTokenRefreshResult alloc] initWithStatusFailure];
  199. completion(refreshResult);
  200. });
  201. }
  202. @end
  203. NS_ASSUME_NONNULL_END