You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

164 lines
5.4 KiB

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