FIRAppDistributionAuthPersistence.m 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  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 <AppAuth/AppAuth.h>
  15. #import "FIRAppDistributionAuthPersistence+Private.h"
  16. NS_ASSUME_NONNULL_BEGIN
  17. NSString *const kFIRAppDistributionKeychainErrorDomain = @"com.firebase.app_distribution.internal";
  18. @implementation FIRAppDistributionAuthPersistence
  19. + (void)handleAuthStateError:(NSError **_Nullable)error
  20. description:(NSString *)description
  21. code:(FIRAppDistributionKeychainError)code {
  22. if (error) {
  23. NSDictionary *userInfo = @{NSLocalizedDescriptionKey : description};
  24. *error = [NSError errorWithDomain:kFIRAppDistributionKeychainErrorDomain
  25. code:code
  26. userInfo:userInfo];
  27. }
  28. }
  29. + (BOOL)clearAuthState:(NSError **_Nullable)error {
  30. NSMutableDictionary *keychainQuery = [self getKeyChainQuery];
  31. OSStatus status = SecItemDelete((CFDictionaryRef)keychainQuery);
  32. if (status != errSecSuccess && status != errSecItemNotFound) {
  33. NSString *description = NSLocalizedString(
  34. @"Failed to clear auth state from keychain. Tester will overwrite data on sign in.",
  35. @"Error message for failure to retrieve auth state from keychain");
  36. [self handleAuthStateError:error
  37. description:description
  38. code:FIRAppDistributionErrorTokenDeletionFailure];
  39. return NO;
  40. }
  41. return YES;
  42. }
  43. + (OIDAuthState *)retrieveAuthState:(NSError **_Nullable)error {
  44. NSMutableDictionary *keychainQuery = [self getKeyChainQuery];
  45. [keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
  46. [keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
  47. NSData *passwordData = NULL;
  48. NSData *result = nil;
  49. OSStatus status = SecItemCopyMatching((CFDictionaryRef)keychainQuery, (void *)&passwordData);
  50. if (status != noErr || 0 == [passwordData length]) {
  51. NSString *description = NSLocalizedString(
  52. @"Failed to retrieve auth state from keychain. Tester will have to sign in again.",
  53. @"Error message for failure to retrieve auth state from keychain");
  54. [self handleAuthStateError:error
  55. description:description
  56. code:FIRAppDistributionErrorTokenRetrievalFailure];
  57. return nil;
  58. }
  59. result = [passwordData copy];
  60. if (!result) {
  61. NSString *description =
  62. NSLocalizedString(@"Failed to unarchive auth state. Tester will have to sign in again.",
  63. @"Error message for failure to retrieve auth state from keychain");
  64. [self handleAuthStateError:error
  65. description:description
  66. code:FIRAppDistributionErrorTokenRetrievalFailure];
  67. return nil;
  68. }
  69. OIDAuthState *authState = (OIDAuthState *)[NSKeyedUnarchiver unarchiveObjectWithData:result];
  70. return authState;
  71. }
  72. + (BOOL)persistAuthState:(OIDAuthState *)authState error:(NSError **_Nullable)error {
  73. NSData *authorizationData = [NSKeyedArchiver archivedDataWithRootObject:authState];
  74. NSMutableDictionary *keychainQuery = [self getKeyChainQuery];
  75. OSStatus status = noErr;
  76. if ([self retrieveAuthState:NULL]) {
  77. status = SecItemUpdate((CFDictionaryRef)keychainQuery,
  78. (CFDictionaryRef) @{(id)kSecValueData : authorizationData});
  79. } else {
  80. [keychainQuery setObject:authorizationData forKey:(id)kSecValueData];
  81. status = SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
  82. }
  83. if (status != noErr) {
  84. NSString *description = NSLocalizedString(
  85. @"Failed to persist auth state. Tester will have to sign in again after app close.",
  86. @"Error message for failure to persist auth state to keychain");
  87. [self handleAuthStateError:error
  88. description:description
  89. code:FIRAppDistributionErrorTokenPersistenceFailure];
  90. return NO;
  91. }
  92. return YES;
  93. }
  94. + (NSMutableDictionary *)getKeyChainQuery {
  95. NSMutableDictionary *keychainQuery = [NSMutableDictionary
  96. dictionaryWithObjectsAndKeys:(id)kSecClassGenericPassword, (id)kSecClass, @"OAuth",
  97. (id)kSecAttrGeneric, @"OAuth", (id)kSecAttrAccount,
  98. @"fire-fad-auth", (id)kSecAttrService, nil];
  99. return keychainQuery;
  100. }
  101. @end
  102. NS_ASSUME_NONNULL_END