GIDAuthStateMigration.m 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. // Copyright 2021 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 "GoogleSignIn/Sources/GIDAuthStateMigration/GIDAuthStateMigration.h"
  15. #import "GoogleSignIn/Sources/GIDSignInCallbackSchemes.h"
  16. @import GTMAppAuth;
  17. #ifdef SWIFT_PACKAGE
  18. @import AppAuth;
  19. #else
  20. #import <AppAuth/AppAuth.h>
  21. #endif
  22. NS_ASSUME_NONNULL_BEGIN
  23. // User preference key to detect whether or not the migration to GTMAppAuth has been performed.
  24. static NSString *const kGTMAppAuthMigrationCheckPerformedKey = @"GID_MigrationCheckPerformed";
  25. // User preference key to detect whether or not the data protected migration has been performed.
  26. static NSString *const kDataProtectedMigrationCheckPerformedKey =
  27. @"GID_DataProtectedMigrationCheckPerformed";
  28. // Keychain account used to store additional state in SDKs previous to v5, including GPPSignIn.
  29. static NSString *const kOldKeychainAccount = @"GooglePlus";
  30. // The value used for the kSecAttrGeneric key by GTMAppAuth and GTMOAuth2.
  31. static NSString *const kGenericAttribute = @"OAuth";
  32. // Keychain service name used to store the last used fingerprint value.
  33. static NSString *const kFingerprintService = @"fingerprint";
  34. @interface GIDAuthStateMigration ()
  35. @property (nonatomic, strong) GTMKeychainStore *keychainStore;
  36. @end
  37. @implementation GIDAuthStateMigration
  38. - (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore {
  39. self = [super init];
  40. if (self) {
  41. _keychainStore = keychainStore;
  42. }
  43. return self;
  44. }
  45. - (instancetype)init {
  46. GTMKeychainStore *keychainStore = [[GTMKeychainStore alloc] initWithItemName:@"auth"];
  47. return [self initWithKeychainStore:keychainStore];
  48. }
  49. - (void)migrateIfNeededWithTokenURL:(NSURL *)tokenURL
  50. callbackPath:(NSString *)callbackPath
  51. isFreshInstall:(BOOL)isFreshInstall {
  52. // If this is a fresh install, take no action and mark the migration checks as having been
  53. // performed.
  54. if (isFreshInstall) {
  55. NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  56. #if TARGET_OS_OSX
  57. [defaults setBool:YES forKey:kDataProtectedMigrationCheckPerformedKey];
  58. #elif TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  59. [defaults setBool:YES forKey:kGTMAppAuthMigrationCheckPerformedKey];
  60. #endif // TARGET_OS_OSX
  61. return;
  62. }
  63. #if TARGET_OS_OSX
  64. [self performDataProtectedMigrationIfNeeded];
  65. #elif TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  66. [self performGIDMigrationIfNeededWithTokenURL:tokenURL
  67. callbackPath:callbackPath];
  68. #endif // TARGET_OS_OSX
  69. }
  70. #if TARGET_OS_OSX
  71. // Migrate from the fileBasedKeychain to dataProtectedKeychain with GTMAppAuth 5.0.
  72. - (void)performDataProtectedMigrationIfNeeded {
  73. // See if we've performed the migration check previously.
  74. NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  75. if ([defaults boolForKey:kDataProtectedMigrationCheckPerformedKey]) {
  76. return;
  77. }
  78. GTMKeychainAttribute *fileBasedKeychain = [GTMKeychainAttribute useFileBasedKeychain];
  79. NSSet *attributes = [NSSet setWithArray:@[fileBasedKeychain]];
  80. GTMKeychainStore *keychainStoreLegacy =
  81. [[GTMKeychainStore alloc] initWithItemName:self.keychainStore.itemName
  82. keychainAttributes:attributes];
  83. GTMAuthSession *authSession = [keychainStoreLegacy retrieveAuthSessionWithError:nil];
  84. // If migration was successful, save our migrated state to the keychain.
  85. if (authSession) {
  86. NSError *err;
  87. [self.keychainStore saveAuthSession:authSession error:&err];
  88. [keychainStoreLegacy removeAuthSessionWithError:nil];
  89. }
  90. // Mark the migration check as having been performed.
  91. [defaults setBool:YES forKey:kDataProtectedMigrationCheckPerformedKey];
  92. }
  93. #elif TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  94. // Migrate from GPPSignIn 1.x or GIDSignIn 1.0 - 4.x to the GTMAppAuth storage introduced in
  95. // GIDSignIn 5.0.
  96. - (void)performGIDMigrationIfNeededWithTokenURL:(NSURL *)tokenURL
  97. callbackPath:(NSString *)callbackPath {
  98. // See if we've performed the migration check previously.
  99. NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  100. if ([defaults boolForKey:kGTMAppAuthMigrationCheckPerformedKey]) {
  101. return;
  102. }
  103. // Attempt migration
  104. GTMAuthSession *authSession =
  105. [self extractAuthSessionWithTokenURL:tokenURL callbackPath:callbackPath];
  106. // If migration was successful, save our migrated state to the keychain.
  107. if (authSession) {
  108. NSError *err;
  109. [self.keychainStore saveAuthSession:authSession error:&err];
  110. }
  111. // Mark the migration check as having been performed.
  112. [defaults setBool:YES forKey:kGTMAppAuthMigrationCheckPerformedKey];
  113. }
  114. // Returns a |GTMAuthSession| object containing any old auth state or |nil| if none
  115. // was found or the migration failed.
  116. - (nullable GTMAuthSession *)extractAuthSessionWithTokenURL:(NSURL *)tokenURL
  117. callbackPath:(NSString *)callbackPath {
  118. // Retrieve the last used fingerprint.
  119. NSString *fingerprint = [GIDAuthStateMigration passwordForService:kFingerprintService];
  120. if (!fingerprint) {
  121. return nil;
  122. }
  123. // Retrieve the GTMOAuth2 persistence string.
  124. NSError *passwordError;
  125. NSString *GTMOAuth2PersistenceString =
  126. [self.keychainStore.keychainHelper passwordForService:fingerprint error:&passwordError];
  127. if (passwordError) {
  128. return nil;
  129. }
  130. // Parse the fingerprint.
  131. NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier];
  132. NSString *pattern =
  133. [NSString stringWithFormat:@"^%@-(.+)-(?:email|profile|https:\\/\\/).*$", bundleID];
  134. NSError *error;
  135. NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern
  136. options:0
  137. error:&error];
  138. NSRange matchRange = NSMakeRange(0, fingerprint.length);
  139. NSArray<NSTextCheckingResult *> *matches = [regex matchesInString:fingerprint
  140. options:0
  141. range:matchRange];
  142. if ([matches count] != 1) {
  143. return nil;
  144. }
  145. // Extract the client ID from the fingerprint.
  146. NSString *clientID = [fingerprint substringWithRange:[matches[0] rangeAtIndex:1]];
  147. // Generate the redirect URI from the extracted client ID.
  148. NSString *scheme =
  149. [[[GIDSignInCallbackSchemes alloc] initWithClientIdentifier:clientID] clientIdentifierScheme];
  150. NSString *redirectURI = [NSString stringWithFormat:@"%@:%@", scheme, callbackPath];
  151. // Retrieve the additional token request parameters value.
  152. NSString *additionalTokenRequestParametersService =
  153. [NSString stringWithFormat:@"%@~~atrp", fingerprint];
  154. NSString *additionalTokenRequestParameters =
  155. [GIDAuthStateMigration passwordForService:additionalTokenRequestParametersService];
  156. // Generate a persistence string that includes additional token request parameters if present.
  157. NSString *persistenceString = GTMOAuth2PersistenceString;
  158. if (additionalTokenRequestParameters) {
  159. persistenceString = [NSString stringWithFormat:@"%@&%@",
  160. GTMOAuth2PersistenceString,
  161. additionalTokenRequestParameters];
  162. }
  163. // Use |GTMOAuth2Compatibility| to generate a |GTMAuthSession| from the
  164. // persistence string, redirect URI, client ID, and token endpoint URL.
  165. GTMAuthSession *authSession =
  166. [GTMOAuth2Compatibility authSessionForPersistenceString:persistenceString
  167. tokenURL:tokenURL
  168. redirectURI:redirectURI
  169. clientID:clientID
  170. clientSecret:nil
  171. error:nil];
  172. return authSession;
  173. }
  174. // Returns the password string for a given service string stored by an old version of the SDK or
  175. // |nil| if no matching keychain item was found.
  176. + (nullable NSString *)passwordForService:(NSString *)service {
  177. if (!service.length) {
  178. return nil;
  179. }
  180. CFDataRef result = NULL;
  181. NSDictionary<id, id> *query = @{
  182. (id)kSecClass : (id)kSecClassGenericPassword,
  183. (id)kSecAttrGeneric : kGenericAttribute,
  184. (id)kSecAttrAccount : kOldKeychainAccount,
  185. (id)kSecAttrService : service,
  186. (id)kSecReturnData : (id)kCFBooleanTrue,
  187. (id)kSecMatchLimit : (id)kSecMatchLimitOne,
  188. };
  189. OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef *)&result);
  190. NSData *passwordData;
  191. if (status == noErr && [(__bridge NSData *)result length] > 0) {
  192. passwordData = [(__bridge NSData *)result copy];
  193. }
  194. if (result != NULL) {
  195. CFRelease(result);
  196. }
  197. if (!passwordData) {
  198. return nil;
  199. }
  200. NSString *password = [[NSString alloc] initWithData:passwordData encoding:NSUTF8StringEncoding];
  201. return password;
  202. }
  203. #endif // TARGET_OS_OSX
  204. @end
  205. NS_ASSUME_NONNULL_END