Skip to content

Commit 43b6160

Browse files
committed
update fake and create test
1 parent 023f733 commit 43b6160

File tree

2 files changed

+283
-0
lines changed

2 files changed

+283
-0
lines changed
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
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+
15+
#import "GoogleSignIn/Sources/GIDAuthStateMigration/GIDAuthStateMigration.h"
16+
17+
#import "GoogleSignIn/Sources/GIDSignInCallbackSchemes.h"
18+
19+
@import GTMAppAuth;
20+
21+
#ifdef SWIFT_PACKAGE
22+
@import AppAuth;
23+
#else
24+
#import <AppAuth/AppAuth.h>
25+
#endif
26+
27+
NS_ASSUME_NONNULL_BEGIN
28+
29+
// User preference key to detect whether or not the migration to GTMAppAuth has been performed.
30+
static NSString *const kGTMAppAuthMigrationCheckPerformedKey = @"GID_MigrationCheckPerformed";
31+
32+
// User preference key to detect whether or not the data protected migration has been performed.
33+
static NSString *const kDataProtectedMigrationCheckPerformedKey =
34+
@"GID_DataProtectedMigrationCheckPerformed";
35+
36+
// Keychain account used to store additional state in SDKs previous to v5, including GPPSignIn.
37+
static NSString *const kOldKeychainAccount = @"GooglePlus";
38+
39+
// The value used for the kSecAttrGeneric key by GTMAppAuth and GTMOAuth2.
40+
static NSString *const kGenericAttribute = @"OAuth";
41+
42+
// Keychain service name used to store the last used fingerprint value.
43+
static NSString *const kFingerprintService = @"fingerprint";
44+
45+
@interface GIDAuthStateMigration ()
46+
47+
@property (nonatomic, strong) GTMKeychainStore *keychainStore;
48+
49+
@end
50+
51+
@implementation GIDAuthStateMigration
52+
53+
- (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore {
54+
self = [super init];
55+
if (self) {
56+
_keychainStore = keychainStore;
57+
}
58+
return self;
59+
}
60+
61+
- (instancetype)init {
62+
GTMKeychainStore *keychainStore = [[GTMKeychainStore alloc] initWithItemName:@"auth"];
63+
return [self initWithKeychainStore:keychainStore];
64+
}
65+
66+
- (void)migrateIfNeededWithTokenURL:(NSURL *)tokenURL
67+
callbackPath:(NSString *)callbackPath
68+
keychainName:(NSString *)keychainName
69+
isFreshInstall:(BOOL)isFreshInstall {
70+
// If this is a fresh install, take no action and mark the migration checks as having been
71+
// performed.
72+
if (isFreshInstall) {
73+
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
74+
#if TARGET_OS_OSX
75+
[defaults setBool:YES forKey:kDataProtectedMigrationCheckPerformedKey];
76+
#elif TARGET_OS_IOS && !TARGET_OS_MACCATALYST
77+
[defaults setBool:YES forKey:kGTMAppAuthMigrationCheckPerformedKey];
78+
#endif // TARGET_OS_OSX
79+
return;
80+
}
81+
82+
#if TARGET_OS_OSX
83+
[self performDataProtectedMigrationIfNeeded];
84+
#elif TARGET_OS_IOS && !TARGET_OS_MACCATALYST
85+
[self performGIDMigrationIfNeededWithTokenURL:tokenURL
86+
callbackPath:callbackPath
87+
keychainName:keychainName];
88+
#endif // TARGET_OS_OSX
89+
}
90+
91+
#if TARGET_OS_OSX
92+
// Migrate from the fileBasedKeychain to dataProtectedKeychain with GTMAppAuth 5.0.
93+
- (void)performDataProtectedMigrationIfNeeded {
94+
// See if we've performed the migration check previously.
95+
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
96+
if ([defaults boolForKey:kDataProtectedMigrationCheckPerformedKey]) {
97+
return;
98+
}
99+
100+
GTMKeychainAttribute *fileBasedKeychain = [GTMKeychainAttribute useFileBasedKeychain];
101+
NSSet *attributes = [NSSet setWithArray:@[fileBasedKeychain]];
102+
GTMKeychainStore *keychainStoreLegacy =
103+
[[GTMKeychainStore alloc] initWithItemName:self.keychainStore.itemName
104+
keychainAttributes:attributes];
105+
GTMAuthSession *authSession = [keychainStoreLegacy retrieveAuthSessionWithError:nil];
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+
[keychainStoreLegacy removeAuthSessionWithError:nil];
111+
}
112+
113+
// Mark the migration check as having been performed.
114+
[defaults setBool:YES forKey:kDataProtectedMigrationCheckPerformedKey];
115+
}
116+
117+
#elif TARGET_OS_IOS && !TARGET_OS_MACCATALYST
118+
// Migrate from GPPSignIn 1.x or GIDSignIn 1.0 - 4.x to the GTMAppAuth storage introduced in
119+
// GIDSignIn 5.0.
120+
- (void)performGIDMigrationIfNeededWithTokenURL:(NSURL *)tokenURL
121+
callbackPath:(NSString *)callbackPath
122+
keychainName:(NSString *)keychainName {
123+
// See if we've performed the migration check previously.
124+
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
125+
if ([defaults boolForKey:kGTMAppAuthMigrationCheckPerformedKey]) {
126+
return;
127+
}
128+
129+
// Attempt migration
130+
GTMAuthSession *authSession =
131+
[self extractAuthSessionWithTokenURL:tokenURL callbackPath:callbackPath];
132+
133+
// If migration was successful, save our migrated state to the keychain.
134+
if (authSession) {
135+
NSError *err;
136+
[self.keychainStore saveAuthSession:authSession error:&err];
137+
}
138+
139+
// Mark the migration check as having been performed.
140+
[defaults setBool:YES forKey:kGTMAppAuthMigrationCheckPerformedKey];
141+
}
142+
143+
// Returns a |GTMAuthSession| object containing any old auth state or |nil| if none
144+
// was found or the migration failed.
145+
- (nullable GTMAuthSession *)extractAuthSessionWithTokenURL:(NSURL *)tokenURL
146+
callbackPath:(NSString *)callbackPath {
147+
// Retrieve the last used fingerprint.
148+
NSString *fingerprint = [GIDAuthStateMigration passwordForService:kFingerprintService];
149+
if (!fingerprint) {
150+
return nil;
151+
}
152+
153+
// Retrieve the GTMOAuth2 persistence string.
154+
NSError *passwordError;
155+
NSString *GTMOAuth2PersistenceString =
156+
[self.keychainStore.keychainHelper passwordForService:fingerprint error:&passwordError];
157+
if (passwordError) {
158+
return nil;
159+
}
160+
161+
// Parse the fingerprint.
162+
NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier];
163+
NSString *pattern =
164+
[NSString stringWithFormat:@"^%@-(.+)-(?:email|profile|https:\\/\\/).*$", bundleID];
165+
NSError *error;
166+
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern
167+
options:0
168+
error:&error];
169+
NSRange matchRange = NSMakeRange(0, fingerprint.length);
170+
NSArray<NSTextCheckingResult *> *matches = [regex matchesInString:fingerprint
171+
options:0
172+
range:matchRange];
173+
if ([matches count] != 1) {
174+
return nil;
175+
}
176+
177+
// Extract the client ID from the fingerprint.
178+
NSString *clientID = [fingerprint substringWithRange:[matches[0] rangeAtIndex:1]];
179+
180+
// Generate the redirect URI from the extracted client ID.
181+
NSString *scheme =
182+
[[[GIDSignInCallbackSchemes alloc] initWithClientIdentifier:clientID] clientIdentifierScheme];
183+
NSString *redirectURI = [NSString stringWithFormat:@"%@:%@", scheme, callbackPath];
184+
185+
// Retrieve the additional token request parameters value.
186+
NSString *additionalTokenRequestParametersService =
187+
[NSString stringWithFormat:@"%@~~atrp", fingerprint];
188+
NSString *additionalTokenRequestParameters =
189+
[GIDAuthStateMigration passwordForService:additionalTokenRequestParametersService];
190+
191+
// Generate a persistence string that includes additional token request parameters if present.
192+
NSString *persistenceString = GTMOAuth2PersistenceString;
193+
if (additionalTokenRequestParameters) {
194+
persistenceString = [NSString stringWithFormat:@"%@&%@",
195+
GTMOAuth2PersistenceString,
196+
additionalTokenRequestParameters];
197+
}
198+
199+
// Use |GTMOAuth2Compatibility| to generate a |GTMAuthSession| from the
200+
// persistence string, redirect URI, client ID, and token endpoint URL.
201+
GTMAuthSession *authSession =
202+
[GTMOAuth2Compatibility authSessionForPersistenceString:persistenceString
203+
tokenURL:tokenURL
204+
redirectURI:redirectURI
205+
clientID:clientID
206+
clientSecret:nil
207+
error:nil];
208+
209+
return authSession;
210+
}
211+
212+
// Returns the password string for a given service string stored by an old version of the SDK or
213+
// |nil| if no matching keychain item was found.
214+
+ (nullable NSString *)passwordForService:(NSString *)service {
215+
if (!service.length) {
216+
return nil;
217+
}
218+
CFDataRef result = NULL;
219+
NSDictionary<id, id> *query = @{
220+
(id)kSecClass : (id)kSecClassGenericPassword,
221+
(id)kSecAttrGeneric : kGenericAttribute,
222+
(id)kSecAttrAccount : kOldKeychainAccount,
223+
(id)kSecAttrService : service,
224+
(id)kSecReturnData : (id)kCFBooleanTrue,
225+
(id)kSecMatchLimit : (id)kSecMatchLimitOne,
226+
};
227+
OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef *)&result);
228+
NSData *passwordData;
229+
if (status == noErr && [(__bridge NSData *)result length] > 0) {
230+
passwordData = [(__bridge NSData *)result copy];
231+
}
232+
if (result != NULL) {
233+
CFRelease(result);
234+
}
235+
if (!passwordData) {
236+
return nil;
237+
}
238+
NSString *password = [[NSString alloc] initWithData:passwordData encoding:NSUTF8StringEncoding];
239+
return password;
240+
}
241+
#endif // TARGET_OS_OSX
242+
243+
@end
244+
245+
NS_ASSUME_NONNULL_END
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2021 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import <Foundation/Foundation.h>
18+
19+
@class GTMKeychainStore;
20+
@class GTMAuthSession;
21+
22+
NS_ASSUME_NONNULL_BEGIN
23+
24+
/// A class providing migration support for auth state saved by older versions of the SDK.
25+
@interface GIDAuthStateMigration : NSObject
26+
27+
/// Creates an instance of this migration type with the keychain storage wrapper it will use.
28+
- (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore NS_DESIGNATED_INITIALIZER;
29+
30+
/// Perform necessary migrations from legacy auth state storage to most recent GTMAppAuth version.
31+
- (void)migrateIfNeededWithTokenURL:(NSURL *)tokenURL
32+
callbackPath:(NSString *)callbackPath
33+
keychainName:(NSString *)keychainName
34+
isFreshInstall:(BOOL)isFreshInstall;
35+
36+
@end
37+
38+
NS_ASSUME_NONNULL_END

0 commit comments

Comments
 (0)