GIDAuthStateMigration.m 9.9 KB

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