FIRAuthAppCredentialManager.m 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. /*
  2. * Copyright 2017 Google
  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 <TargetConditionals.h>
  17. #if !TARGET_OS_OSX
  18. #import "FirebaseAuth/Sources/Auth/FIRAuthGlobalWorkQueue.h"
  19. #import "FirebaseAuth/Sources/Storage/FIRAuthKeychainServices.h"
  20. #import "FirebaseAuth/Sources/SystemService/FIRAuthAppCredential.h"
  21. #import "FirebaseAuth/Sources/SystemService/FIRAuthAppCredentialManager.h"
  22. NS_ASSUME_NONNULL_BEGIN
  23. /** @var kKeychainDataKey
  24. @brief The keychain key for the data.
  25. */
  26. static NSString *const kKeychainDataKey = @"app_credentials";
  27. /** @var kFullCredentialKey
  28. @brief The data key for the full app credential.
  29. */
  30. static NSString *const kFullCredentialKey = @"full_credential";
  31. /** @var kPendingReceiptsKey
  32. @brief The data key for the array of pending receipts.
  33. */
  34. static NSString *const kPendingReceiptsKey = @"pending_receipts";
  35. /** @var kMaximumNumberOfPendingReceipts
  36. @brief The maximum number of partial credentials kept by this class.
  37. */
  38. static const NSUInteger kMaximumNumberOfPendingReceipts = 32;
  39. @implementation FIRAuthAppCredentialManager {
  40. /** @var _keychainServices
  41. @brief The keychain for app credentials to load from and to save to.
  42. */
  43. FIRAuthKeychainServices *_keychainServices;
  44. /** @var _pendingReceipts
  45. @brief A list of pending receipts sorted in the order they were recorded.
  46. */
  47. NSMutableArray<NSString *> *_pendingReceipts;
  48. /** @var _callbacksByReceipt
  49. @brief A map from pending receipts to callbacks.
  50. */
  51. NSMutableDictionary<NSString *, FIRAuthAppCredentialCallback> *_callbacksByReceipt;
  52. }
  53. - (instancetype)initWithKeychain:(FIRAuthKeychainServices *)keychain {
  54. self = [super init];
  55. if (self) {
  56. _keychainServices = keychain;
  57. // Load the credentials from keychain if possible.
  58. NSError *error;
  59. NSData *encodedData = [_keychainServices dataForKey:kKeychainDataKey error:&error];
  60. if (!error && encodedData) {
  61. #if TARGET_OS_WATCH
  62. NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:encodedData
  63. error:&error];
  64. #else
  65. // iOS 12 deprecation
  66. #pragma clang diagnostic push
  67. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  68. NSKeyedUnarchiver *unarchiver =
  69. [[NSKeyedUnarchiver alloc] initForReadingWithData:encodedData];
  70. #pragma clang diagnostic pop
  71. #endif // TARGET_OS_WATCH
  72. FIRAuthAppCredential *credential =
  73. [unarchiver decodeObjectOfClass:[FIRAuthAppCredential class] forKey:kFullCredentialKey];
  74. if ([credential isKindOfClass:[FIRAuthAppCredential class]] && !error) {
  75. _credential = credential;
  76. }
  77. NSSet<Class> *allowedClasses =
  78. [NSSet<Class> setWithObjects:[NSArray class], [NSString class], nil];
  79. NSArray<NSString *> *pendingReceipts = [unarchiver decodeObjectOfClasses:allowedClasses
  80. forKey:kPendingReceiptsKey];
  81. if ([pendingReceipts isKindOfClass:[NSArray class]]) {
  82. _pendingReceipts = [pendingReceipts mutableCopy];
  83. }
  84. }
  85. if (!_pendingReceipts) {
  86. _pendingReceipts = [[NSMutableArray<NSString *> alloc] init];
  87. }
  88. _callbacksByReceipt =
  89. [[NSMutableDictionary<NSString *, FIRAuthAppCredentialCallback> alloc] init];
  90. }
  91. return self;
  92. }
  93. - (NSUInteger)maximumNumberOfPendingReceipts {
  94. return kMaximumNumberOfPendingReceipts;
  95. }
  96. - (void)didStartVerificationWithReceipt:(NSString *)receipt
  97. timeout:(NSTimeInterval)timeout
  98. callback:(FIRAuthAppCredentialCallback)callback {
  99. [_pendingReceipts removeObject:receipt];
  100. if (_pendingReceipts.count >= kMaximumNumberOfPendingReceipts) {
  101. [_pendingReceipts removeObjectAtIndex:0];
  102. }
  103. [_pendingReceipts addObject:receipt];
  104. _callbacksByReceipt[receipt] = callback;
  105. [self saveData];
  106. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)),
  107. FIRAuthGlobalWorkQueue(), ^{
  108. [self callBackWithReceipt:receipt];
  109. });
  110. }
  111. - (BOOL)canFinishVerificationWithReceipt:(NSString *)receipt secret:(NSString *)secret {
  112. if (![_pendingReceipts containsObject:receipt]) {
  113. return NO;
  114. }
  115. [_pendingReceipts removeObject:receipt];
  116. _credential = [[FIRAuthAppCredential alloc] initWithReceipt:receipt secret:secret];
  117. [self saveData];
  118. [self callBackWithReceipt:receipt];
  119. return YES;
  120. }
  121. - (void)clearCredential {
  122. _credential = nil;
  123. [self saveData];
  124. }
  125. #pragma mark - Internal methods
  126. /** @fn saveData
  127. @brief Save the data in memory to the keychain ignoring any errors.
  128. */
  129. - (void)saveData {
  130. NSMutableData *archiveData = [NSMutableData data];
  131. // iOS 12 deprecation
  132. #pragma clang diagnostic push
  133. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  134. NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:archiveData];
  135. #pragma clang diagnostic pop
  136. [archiver encodeObject:_credential forKey:kFullCredentialKey];
  137. [archiver encodeObject:_pendingReceipts forKey:kPendingReceiptsKey];
  138. [archiver finishEncoding];
  139. [_keychainServices setData:archiveData forKey:kKeychainDataKey error:NULL];
  140. }
  141. /** @fn callBackWithReceipt:
  142. @brief Calls the saved callback for the specifc receipt.
  143. @param receipt The receipt associated with the callback.
  144. */
  145. - (void)callBackWithReceipt:(NSString *)receipt {
  146. FIRAuthAppCredentialCallback callback = _callbacksByReceipt[receipt];
  147. if (!callback) {
  148. return;
  149. }
  150. [_callbacksByReceipt removeObjectForKey:receipt];
  151. if (_credential) {
  152. callback(_credential);
  153. } else {
  154. callback([[FIRAuthAppCredential alloc] initWithReceipt:receipt secret:nil]);
  155. }
  156. }
  157. @end
  158. NS_ASSUME_NONNULL_END
  159. #endif