GIDAuthStateMigration.m 6.8 KB

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