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