FIRDatabaseConnectionContextProvider.m 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. /*
  2. * Copyright 2021 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 "FirebaseDatabase/Sources/Login/FIRDatabaseConnectionContextProvider.h"
  17. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  18. #import "FirebaseAppCheck/Interop/FIRAppCheckInterop.h"
  19. #import "FirebaseAppCheck/Interop/FIRAppCheckTokenResultInterop.h"
  20. #import "FirebaseAuth/Interop/FIRAuthInterop.h"
  21. #import "FirebaseDatabase/Sources/Api/Private/FIRDatabaseQuery_Private.h"
  22. #import "FirebaseDatabase/Sources/Utilities/FUtilities.h"
  23. NS_ASSUME_NONNULL_BEGIN
  24. @interface FAuthStateListenerWrapper : NSObject
  25. @property(nonatomic, copy) fbt_void_nsstring listener;
  26. @property(nonatomic, weak) id<FIRAuthInterop> auth;
  27. @end
  28. @implementation FAuthStateListenerWrapper
  29. - (instancetype)initWithListener:(fbt_void_nsstring)listener
  30. auth:(id<FIRAuthInterop>)auth {
  31. self = [super init];
  32. if (self != nil) {
  33. self->_listener = listener;
  34. self->_auth = auth;
  35. [[NSNotificationCenter defaultCenter]
  36. addObserver:self
  37. selector:@selector(authStateDidChangeNotification:)
  38. name:FIRAuthStateDidChangeInternalNotification
  39. object:nil];
  40. }
  41. return self;
  42. }
  43. - (void)authStateDidChangeNotification:(NSNotification *)notification {
  44. NSDictionary *userInfo = notification.userInfo;
  45. if (notification.object == self.auth) {
  46. NSString *token =
  47. userInfo[FIRAuthStateDidChangeInternalNotificationTokenKey];
  48. dispatch_async([FIRDatabaseQuery sharedQueue], ^{
  49. self.listener(token);
  50. });
  51. }
  52. }
  53. - (void)dealloc {
  54. [[NSNotificationCenter defaultCenter] removeObserver:self];
  55. }
  56. @end
  57. @implementation FIRDatabaseConnectionContext
  58. - (instancetype)initWithAuthToken:(nullable NSString *)authToken
  59. appCheckToken:(nullable NSString *)appCheckToken {
  60. self = [super init];
  61. if (self) {
  62. _authToken = [authToken copy];
  63. _appCheckToken = [appCheckToken copy];
  64. }
  65. return self;
  66. }
  67. @end
  68. @interface FIRDatabaseConnectionContextProvider ()
  69. @property(nonatomic, strong) id<FIRAppCheckInterop> appCheck;
  70. @property(nonatomic, strong) id<FIRAuthInterop> auth;
  71. /// Strong references to the auth listeners as they are only weak in
  72. /// FIRFirebaseApp.
  73. @property(nonatomic, readonly) NSMutableArray *authListeners;
  74. /// Observer objects returned by
  75. /// `-[NSNotificationCenter addObserverForName:object:queue:usingBlock:]`
  76. /// method. Required to cleanup the observers on dealloc.
  77. @property(nonatomic, readonly) NSMutableArray *appCheckNotificationObservers;
  78. /// An NSOperationQueue to call listeners on.
  79. @property(nonatomic, readonly) NSOperationQueue *listenerQueue;
  80. @end
  81. @implementation FIRDatabaseConnectionContextProvider
  82. - (instancetype)initWithAuth:(nullable id<FIRAuthInterop>)auth
  83. appCheck:(nullable id<FIRAppCheckInterop>)appCheck {
  84. self = [super init];
  85. if (self != nil) {
  86. self->_appCheck = appCheck;
  87. self->_auth = auth;
  88. self->_authListeners = [NSMutableArray array];
  89. self->_appCheckNotificationObservers = [NSMutableArray array];
  90. self->_listenerQueue = [[NSOperationQueue alloc] init];
  91. self->_listenerQueue.underlyingQueue = [FIRDatabaseQuery sharedQueue];
  92. }
  93. return self;
  94. }
  95. - (void)dealloc {
  96. @synchronized(self) {
  97. // Make sure notification observers are removed from
  98. // NSNotificationCenter.
  99. for (id notificationObserver in self.appCheckNotificationObservers) {
  100. [NSNotificationCenter.defaultCenter
  101. removeObserver:notificationObserver];
  102. }
  103. }
  104. }
  105. - (void)
  106. fetchContextForcingRefresh:(BOOL)forceRefresh
  107. withCallback:
  108. (void (^)(FIRDatabaseConnectionContext *_Nullable context,
  109. NSError *_Nullable error))callback {
  110. if (self.auth == nil && self.appCheck == nil) {
  111. // Nothing to fetch. Finish straight away.
  112. callback(nil, nil);
  113. return;
  114. }
  115. // Use dispatch group to call the callback when both Auth and FAC operations
  116. // finished.
  117. dispatch_group_t dispatchGroup = dispatch_group_create();
  118. __block NSString *authToken;
  119. __block NSString *appCheckToken;
  120. __block NSError *authError;
  121. if (self.auth) {
  122. dispatch_group_enter(dispatchGroup);
  123. [self.auth getTokenForcingRefresh:forceRefresh
  124. withCallback:^(NSString *_Nullable token,
  125. NSError *_Nullable error) {
  126. authToken = token;
  127. authError = error;
  128. dispatch_group_leave(dispatchGroup);
  129. }];
  130. }
  131. if (self.appCheck) {
  132. dispatch_group_enter(dispatchGroup);
  133. [self.appCheck
  134. getTokenForcingRefresh:forceRefresh
  135. completion:^(
  136. id<FIRAppCheckTokenResultInterop> _Nonnull tokenResult) {
  137. appCheckToken = tokenResult.token;
  138. if (tokenResult.error) {
  139. FFLog(@"I-RDB096001",
  140. @"Failed to fetch App Check token: %@",
  141. tokenResult.error);
  142. }
  143. dispatch_group_leave(dispatchGroup);
  144. }];
  145. }
  146. dispatch_group_notify(dispatchGroup, [FIRDatabaseQuery sharedQueue], ^{
  147. __auto_type context = [[FIRDatabaseConnectionContext alloc]
  148. initWithAuthToken:authToken
  149. appCheckToken:appCheckToken];
  150. // Pass only a possible Auth error. App Check errors should not change the
  151. // database SDK behaviour at this point as the App Check enforcement is
  152. // controlled on the backend.
  153. callback(context, authError);
  154. });
  155. }
  156. - (void)listenForAuthTokenChanges:(_Nonnull fbt_void_nsstring)listener {
  157. FAuthStateListenerWrapper *wrapper =
  158. [[FAuthStateListenerWrapper alloc] initWithListener:listener
  159. auth:self.auth];
  160. [self.authListeners addObject:wrapper];
  161. }
  162. - (void)listenForAppCheckTokenChanges:(fbt_void_nsstring)listener {
  163. if (self.appCheck == nil) {
  164. return;
  165. }
  166. NSString *appCheckTokenKey = [self.appCheck notificationTokenKey];
  167. __auto_type notificationObserver = [NSNotificationCenter.defaultCenter
  168. addObserverForName:[self.appCheck tokenDidChangeNotificationName]
  169. object:self.appCheck
  170. queue:self.listenerQueue
  171. usingBlock:^(NSNotification *_Nonnull notification) {
  172. NSString *appCheckToken =
  173. notification.userInfo[appCheckTokenKey];
  174. listener(appCheckToken);
  175. }];
  176. @synchronized(self) {
  177. [self.appCheckNotificationObservers addObject:notificationObserver];
  178. }
  179. }
  180. + (id<FIRDatabaseConnectionContextProvider>)
  181. contextProviderWithAuth:(nullable id<FIRAuthInterop>)auth
  182. appCheck:(nullable id<FIRAppCheckInterop>)appCheck {
  183. return [[self alloc] initWithAuth:auth appCheck:appCheck];
  184. }
  185. @end
  186. NS_ASSUME_NONNULL_END