FIRAppDistributionAuthPersistence.m 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. // Copyright 2020 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import <GoogleUtilities/GULKeychainUtils.h>
  15. #import "FIRAppDistributionAuthPersistence+Private.h"
  16. NS_ASSUME_NONNULL_BEGIN
  17. NSString *const kFIRAppDistributionAuthPersistenceErrorDomain =
  18. @"com.firebase.app_distribution.auth_persistence";
  19. static NSString *const kFIRAppDistributionAuthPersistenceErrorKeychainId =
  20. @"com.firebase.app_distribution.keychain_id";
  21. @interface FIRAppDistributionAuthPersistence ()
  22. @property(nonatomic, readonly) NSString *appID;
  23. @end
  24. @implementation FIRAppDistributionAuthPersistence
  25. - (instancetype)initWithAppId:(NSString *)appID {
  26. self = [super init];
  27. if (self) {
  28. _appID = appID;
  29. }
  30. return self;
  31. }
  32. - (void)handleAuthStateError:(NSError **_Nullable)error
  33. description:(NSString *)description
  34. code:(FIRAppDistributionKeychainError)code
  35. underlyingError:(NSError *_Nullable)underlyingError {
  36. if (error) {
  37. NSDictionary *userInfo =
  38. underlyingError
  39. ? @{NSLocalizedDescriptionKey : description, NSUnderlyingErrorKey : underlyingError}
  40. : @{NSLocalizedDescriptionKey : description};
  41. *error = [NSError errorWithDomain:kFIRAppDistributionAuthPersistenceErrorDomain
  42. code:code
  43. userInfo:userInfo];
  44. }
  45. }
  46. - (BOOL)clearAuthState:(NSError **_Nullable)error {
  47. NSMutableDictionary *keychainQuery = [self getKeyChainQuery];
  48. NSError *keychainError;
  49. BOOL success = [GULKeychainUtils removeItemWithQuery:keychainQuery error:&keychainError];
  50. if (!success) {
  51. NSString *description = NSLocalizedString(
  52. @"Failed to clear auth state from keychain. Tester will overwrite data on sign in.",
  53. @"Error message for failure to retrieve auth state from keychain");
  54. [self handleAuthStateError:error
  55. description:description
  56. code:FIRAppDistributionErrorTokenDeletionFailure
  57. underlyingError:keychainError];
  58. return NO;
  59. }
  60. return YES;
  61. }
  62. - (OIDAuthState *)retrieveAuthState:(NSError **_Nullable)error {
  63. NSMutableDictionary *keychainQuery = [self getKeyChainQuery];
  64. [keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
  65. [keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
  66. NSError *keychainError;
  67. NSData *passwordData = [GULKeychainUtils getItemWithQuery:keychainQuery error:&keychainError];
  68. NSData *result = nil;
  69. if (!passwordData) {
  70. if (keychainError) {
  71. NSString *description = NSLocalizedString(
  72. @"Failed to retrieve auth state from keychain. Tester will have to sign in again.",
  73. @"Error message for failure to retrieve auth state from keychain. ");
  74. [self handleAuthStateError:error
  75. description:description
  76. code:FIRAppDistributionErrorTokenRetrievalFailure
  77. underlyingError:keychainError];
  78. }
  79. return nil;
  80. }
  81. result = [passwordData copy];
  82. if (!result) {
  83. NSString *description =
  84. NSLocalizedString(@"Failed to unarchive auth state. Tester will have to sign in again.",
  85. @"Error message for failure to retrieve auth state from keychain");
  86. [self handleAuthStateError:error
  87. description:description
  88. code:FIRAppDistributionErrorTokenRetrievalFailure
  89. underlyingError:nil];
  90. return nil;
  91. }
  92. OIDAuthState *authState = [FIRAppDistributionAuthPersistence unarchiveKeychainResult:result];
  93. return authState;
  94. }
  95. - (BOOL)persistAuthState:(OIDAuthState *)authState error:(NSError **_Nullable)error {
  96. NSData *authorizationData = [FIRAppDistributionAuthPersistence archiveDataForKeychain:authState];
  97. NSMutableDictionary *keychainQuery = [self getKeyChainQuery];
  98. NSError *keychainError;
  99. // setItem performs an up-sert. Will automatically update the keychain entry if it already
  100. // exists.
  101. BOOL success = [GULKeychainUtils setItem:authorizationData
  102. withQuery:keychainQuery
  103. error:&keychainError];
  104. if (!success) {
  105. NSString *description = NSLocalizedString(
  106. @"Failed to persist auth state. Tester will have to sign in again after app close.",
  107. @"Error message for failure to persist auth state to keychain");
  108. [self handleAuthStateError:error
  109. description:description
  110. code:FIRAppDistributionErrorTokenPersistenceFailure
  111. underlyingError:keychainError];
  112. return NO;
  113. }
  114. return YES;
  115. }
  116. - (NSMutableDictionary *)getKeyChainQuery {
  117. NSMutableDictionary *keychainQuery = [@{
  118. (id)kSecClass : (id)kSecClassGenericPassword,
  119. (id)kSecAttrGeneric : kFIRAppDistributionAuthPersistenceErrorKeychainId,
  120. (id)kSecAttrAccount : [self keychainID],
  121. (id)kSecAttrService : [self keychainID]
  122. } mutableCopy];
  123. return keychainQuery;
  124. }
  125. - (NSString *)keychainID {
  126. return [NSString
  127. stringWithFormat:@"fad-auth-%@-%@", [[NSBundle mainBundle] bundleIdentifier], self.appID];
  128. }
  129. + (OIDAuthState *)unarchiveKeychainResult:(NSData *)result {
  130. return (OIDAuthState *)[NSKeyedUnarchiver unarchiveObjectWithData:result];
  131. }
  132. + (NSData *)archiveDataForKeychain:(OIDAuthState *)data {
  133. return [NSKeyedArchiver archivedDataWithRootObject:data];
  134. }
  135. @end
  136. NS_ASSUME_NONNULL_END