GACAppCheck.m 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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 "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckErrors.h"
  23. #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckProvider.h"
  24. #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckSettings.h"
  25. #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckToken.h"
  26. #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckTokenDelegate.h"
  27. #import "AppCheckCore/Sources/Core/Errors/GACAppCheckErrorUtil.h"
  28. #import "AppCheckCore/Sources/Core/GACAppCheckLogger+Internal.h"
  29. #import "AppCheckCore/Sources/Core/Storage/GACAppCheckStorage.h"
  30. #import "AppCheckCore/Sources/Core/TokenRefresh/GACAppCheckTokenRefreshResult.h"
  31. #import "AppCheckCore/Sources/Core/TokenRefresh/GACAppCheckTokenRefresher.h"
  32. NS_ASSUME_NONNULL_BEGIN
  33. static const NSTimeInterval kTokenExpirationThreshold = 5 * 60; // 5 min.
  34. static NSString *const kDummyFACTokenValue = @"eyJlcnJvciI6IlVOS05PV05fRVJST1IifQ==";
  35. typedef void (^GACAppCheckTokenHandler)(id<GACAppCheckTokenProtocol> _Nullable token,
  36. NSError *_Nullable error);
  37. @interface GACAppCheck ()
  38. @property(nonatomic, readonly) NSString *serviceName;
  39. @property(nonatomic, readonly) id<GACAppCheckProvider> appCheckProvider;
  40. @property(nonatomic, readonly) id<GACAppCheckStorageProtocol> storage;
  41. @property(nonatomic, readonly) id<GACAppCheckSettingsProtocol> settings;
  42. @property(nonatomic, readonly, nullable, weak) id<GACAppCheckTokenDelegate> tokenDelegate;
  43. @property(nonatomic, readonly, nullable) id<GACAppCheckTokenRefresherProtocol> tokenRefresher;
  44. @property(nonatomic, nullable) FBLPromise<GACAppCheckToken *> *ongoingRetrieveOrRefreshTokenPromise;
  45. @end
  46. @implementation GACAppCheck
  47. #pragma mark - Internal
  48. - (instancetype)initWithServiceName:(NSString *)serviceName
  49. appCheckProvider:(id<GACAppCheckProvider>)appCheckProvider
  50. storage:(id<GACAppCheckStorageProtocol>)storage
  51. tokenRefresher:(id<GACAppCheckTokenRefresherProtocol>)tokenRefresher
  52. settings:(id<GACAppCheckSettingsProtocol>)settings
  53. tokenDelegate:(nullable id<GACAppCheckTokenDelegate>)tokenDelegate {
  54. self = [super init];
  55. if (self) {
  56. _serviceName = serviceName;
  57. _appCheckProvider = appCheckProvider;
  58. _storage = storage;
  59. _tokenRefresher = tokenRefresher;
  60. _settings = settings;
  61. _tokenDelegate = tokenDelegate;
  62. __auto_type __weak weakSelf = self;
  63. tokenRefresher.tokenRefreshHandler = ^(GACAppCheckTokenRefreshCompletion _Nonnull completion) {
  64. __auto_type strongSelf = weakSelf;
  65. [strongSelf periodicTokenRefreshWithCompletion:completion];
  66. };
  67. }
  68. return self;
  69. }
  70. #pragma mark - Public
  71. - (instancetype)initWithServiceName:(NSString *)serviceName
  72. resourceName:(NSString *)resourceName
  73. appCheckProvider:(id<GACAppCheckProvider>)appCheckProvider
  74. settings:(id<GACAppCheckSettingsProtocol>)settings
  75. tokenDelegate:(nullable id<GACAppCheckTokenDelegate>)tokenDelegate
  76. keychainAccessGroup:(nullable NSString *)accessGroup {
  77. GACAppCheckTokenRefreshResult *refreshResult =
  78. [[GACAppCheckTokenRefreshResult alloc] initWithStatusNever];
  79. GACAppCheckTokenRefresher *tokenRefresher =
  80. [[GACAppCheckTokenRefresher alloc] initWithRefreshResult:refreshResult settings:settings];
  81. NSString *tokenKey =
  82. [NSString stringWithFormat:@"app_check_token.%@.%@", serviceName, resourceName];
  83. GACAppCheckStorage *storage = [[GACAppCheckStorage alloc] initWithTokenKey:tokenKey
  84. accessGroup:accessGroup];
  85. return [self initWithServiceName:serviceName
  86. appCheckProvider:appCheckProvider
  87. storage:storage
  88. tokenRefresher:tokenRefresher
  89. settings:settings
  90. tokenDelegate:tokenDelegate];
  91. }
  92. #pragma mark - GACAppCheckInterop
  93. - (void)getTokenForcingRefresh:(BOOL)forcingRefresh completion:(GACAppCheckTokenHandler)handler {
  94. [self retrieveOrRefreshTokenForcingRefresh:forcingRefresh]
  95. .then(^id _Nullable(id<GACAppCheckTokenProtocol> token) {
  96. handler(token, nil);
  97. return token;
  98. })
  99. .catch(^(NSError *_Nonnull error) {
  100. handler(nil, error);
  101. });
  102. }
  103. - (void)getLimitedUseTokenWithCompletion:(GACAppCheckTokenHandler)handler {
  104. [self limitedUseToken]
  105. .then(^id _Nullable(id<GACAppCheckTokenProtocol> 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. if (self.tokenDelegate) {
  175. [self.tokenDelegate tokenDidUpdate:token serviceName:self.serviceName];
  176. }
  177. return token;
  178. });
  179. }
  180. - (FBLPromise<GACAppCheckToken *> *)limitedUseToken {
  181. return
  182. [FBLPromise wrapObjectOrErrorCompletion:^(
  183. FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
  184. [self.appCheckProvider getTokenWithCompletion:handler];
  185. }].then(^id _Nullable(GACAppCheckToken *_Nullable token) {
  186. return token;
  187. });
  188. }
  189. #pragma mark - Token auto refresh
  190. - (void)periodicTokenRefreshWithCompletion:(GACAppCheckTokenRefreshCompletion)completion {
  191. [self retrieveOrRefreshTokenForcingRefresh:NO]
  192. .then(^id _Nullable(GACAppCheckToken *_Nullable token) {
  193. __auto_type refreshResult = [[GACAppCheckTokenRefreshResult alloc]
  194. initWithStatusSuccessAndExpirationDate:token.expirationDate
  195. receivedAtDate:token.receivedAtDate];
  196. completion(refreshResult);
  197. return nil;
  198. })
  199. .catch(^(NSError *error) {
  200. __auto_type refreshResult = [[GACAppCheckTokenRefreshResult alloc] initWithStatusFailure];
  201. completion(refreshResult);
  202. });
  203. }
  204. @end
  205. NS_ASSUME_NONNULL_END