GIDAuthStateMigration.m 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  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 check has been performed.
  24. static NSString *const kMigrationCheckPerformedKey = @"GID_MigrationCheckPerformed";
  25. // Keychain account used to store additional state in SDKs previous to v5, including GPPSignIn.
  26. static NSString *const kOldKeychainAccount = @"GooglePlus";
  27. // The value used for the kSecAttrGeneric key by GTMAppAuth and GTMOAuth2.
  28. static NSString *const kGenericAttribute = @"OAuth";
  29. // Keychain service name used to store the last used fingerprint value.
  30. static NSString *const kFingerprintService = @"fingerprint";
  31. @interface GIDAuthStateMigration ()
  32. @property (nonatomic, strong) GTMKeychainStore *keychainStore;
  33. @end
  34. @implementation GIDAuthStateMigration
  35. - (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore {
  36. self = [super init];
  37. if (self) {
  38. _keychainStore = keychainStore;
  39. }
  40. return self;
  41. }
  42. - (instancetype)init {
  43. GTMKeychainStore *keychainStore = [[GTMKeychainStore alloc] initWithItemName:@"auth"];
  44. return [self initWithKeychainStore:keychainStore];
  45. }
  46. - (void)migrateIfNeededWithTokenURL:(NSURL *)tokenURL
  47. callbackPath:(NSString *)callbackPath
  48. keychainName:(NSString *)keychainName
  49. isFreshInstall:(BOOL)isFreshInstall {
  50. // See if we've performed the migration check previously.
  51. NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  52. if ([defaults boolForKey:kMigrationCheckPerformedKey]) {
  53. return;
  54. }
  55. // If this is not a fresh install, attempt to migrate state. If this is a fresh install, take no
  56. // action and go on to mark the migration check as having been performed.
  57. if (!isFreshInstall) {
  58. // Attempt migration
  59. GTMAuthSession *authSession =
  60. [self extractAuthSessionWithTokenURL:tokenURL callbackPath:callbackPath];
  61. // If migration was successful, save our migrated state to the keychain.
  62. if (authSession) {
  63. NSError *err;
  64. [self.keychainStore saveAuthSession:authSession error:&err];
  65. // If we're unable to save to the keychain, return without marking migration performed.
  66. if (err) {
  67. return;
  68. };
  69. }
  70. }
  71. // Mark the migration check as having been performed.
  72. [defaults setBool:YES forKey:kMigrationCheckPerformedKey];
  73. }
  74. // Returns a |GTMAuthSession| object containing any old auth state or |nil| if none
  75. // was found or the migration failed.
  76. - (nullable GTMAuthSession *)extractAuthSessionWithTokenURL:(NSURL *)tokenURL
  77. callbackPath:(NSString *)callbackPath {
  78. // Retrieve the last used fingerprint.
  79. NSString *fingerprint = [GIDAuthStateMigration passwordForService:kFingerprintService];
  80. if (!fingerprint) {
  81. return nil;
  82. }
  83. // Retrieve the GTMOAuth2 persistence string.
  84. NSError *passwordError;
  85. NSString *GTMOAuth2PersistenceString =
  86. [self.keychainStore.keychainHelper passwordForService:fingerprint error:&passwordError];
  87. if (passwordError) {
  88. return nil;
  89. }
  90. // Parse the fingerprint.
  91. NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier];
  92. NSString *pattern =
  93. [NSString stringWithFormat:@"^%@-(.+)-(?:email|profile|https:\\/\\/).*$", bundleID];
  94. NSError *error;
  95. NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern
  96. options:0
  97. error:&error];
  98. NSRange matchRange = NSMakeRange(0, fingerprint.length);
  99. NSArray<NSTextCheckingResult *> *matches = [regex matchesInString:fingerprint
  100. options:0
  101. range:matchRange];
  102. if ([matches count] != 1) {
  103. return nil;
  104. }
  105. // Extract the client ID from the fingerprint.
  106. NSString *clientID = [fingerprint substringWithRange:[matches[0] rangeAtIndex:1]];
  107. // Generate the redirect URI from the extracted client ID.
  108. NSString *scheme =
  109. [[[GIDSignInCallbackSchemes alloc] initWithClientIdentifier:clientID] clientIdentifierScheme];
  110. NSString *redirectURI = [NSString stringWithFormat:@"%@:%@", scheme, callbackPath];
  111. // Retrieve the additional token request parameters value.
  112. NSString *additionalTokenRequestParametersService =
  113. [NSString stringWithFormat:@"%@~~atrp", fingerprint];
  114. NSString *additionalTokenRequestParameters =
  115. [GIDAuthStateMigration passwordForService:additionalTokenRequestParametersService];
  116. // Generate a persistence string that includes additional token request parameters if present.
  117. NSString *persistenceString = GTMOAuth2PersistenceString;
  118. if (additionalTokenRequestParameters) {
  119. persistenceString = [NSString stringWithFormat:@"%@&%@",
  120. GTMOAuth2PersistenceString,
  121. additionalTokenRequestParameters];
  122. }
  123. // Use |GTMOAuth2Compatibility| to generate a |GTMAuthSession| from the
  124. // persistence string, redirect URI, client ID, and token endpoint URL.
  125. GTMAuthSession *authSession =
  126. [GTMOAuth2Compatibility authSessionForPersistenceString:persistenceString
  127. tokenURL:tokenURL
  128. redirectURI:redirectURI
  129. clientID:clientID
  130. clientSecret:nil
  131. error:nil];
  132. return authSession;
  133. }
  134. // Returns the password string for a given service string stored by an old version of the SDK or
  135. // |nil| if no matching keychain item was found.
  136. + (nullable NSString *)passwordForService:(NSString *)service {
  137. if (!service.length) {
  138. return nil;
  139. }
  140. CFDataRef result = NULL;
  141. NSDictionary<id, id> *query = @{
  142. (id)kSecClass : (id)kSecClassGenericPassword,
  143. (id)kSecAttrGeneric : kGenericAttribute,
  144. (id)kSecAttrAccount : kOldKeychainAccount,
  145. (id)kSecAttrService : service,
  146. (id)kSecReturnData : (id)kCFBooleanTrue,
  147. (id)kSecMatchLimit : (id)kSecMatchLimitOne,
  148. };
  149. OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef *)&result);
  150. NSData *passwordData;
  151. if (status == noErr && [(__bridge NSData *)result length] > 0) {
  152. passwordData = [(__bridge NSData *)result copy];
  153. }
  154. if (result != NULL) {
  155. CFRelease(result);
  156. }
  157. if (!passwordData) {
  158. return nil;
  159. }
  160. NSString *password = [[NSString alloc] initWithData:passwordData encoding:NSUTF8StringEncoding];
  161. return password;
  162. }
  163. @end
  164. NS_ASSUME_NONNULL_END