Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@ @implementation GIDFakeAuthStateMigration

@synthesize migrationInvokedCallback = _migrationInvokedCallback;

- (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore {
self = [super initWithKeychainStore:keychainStore];
return self;
}

- (void)migrateIfNeededWithTokenURL:(NSURL *)tokenURL
callbackPath:(NSString *)callbackPath
isFreshInstall:(BOOL)isFreshInstall {
Expand Down
17 changes: 15 additions & 2 deletions GoogleSignIn/Sources/GIDAuthStateMigration/GIDAuthStateMigration.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

#import <Foundation/Foundation.h>

#import "GoogleSignIn/Sources/GIDBundle/API/GIDBundle.h"

@class GTMKeychainStore;
@class GTMAuthSession;

Expand All @@ -24,8 +26,19 @@ NS_ASSUME_NONNULL_BEGIN
/// A class providing migration support for auth state saved by older versions of the SDK.
@interface GIDAuthStateMigration : NSObject

/// Creates an instance of this migration type with the keychain storage wrapper it will use.
- (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore NS_DESIGNATED_INITIALIZER;
/// Creates an instance of this migration type with the keychain storage wrapper it will use, the
/// user defaults in which it records that a migration check has run, and the bundle whose
/// identifier was used to fingerprint legacy auth state.
///
/// `bundle` must report the identifier of the app that wrote the legacy keychain entries, which in
/// production is `NSBundle.mainBundle`. With any other identifier the fingerprint never matches and
/// the migration silently finds nothing.
- (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore
userDefaults:(NSUserDefaults *)userDefaults
bundle:(id<GIDBundle>)bundle NS_DESIGNATED_INITIALIZER;

/// Same as above, using `[NSUserDefaults standardUserDefaults]` and `[NSBundle mainBundle]`.
- (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore;

/// Perform necessary migrations from legacy auth state storage to most recent GTMAppAuth version.
- (void)migrateIfNeededWithTokenURL:(NSURL *)tokenURL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
// limitations under the License.

#import "GoogleSignIn/Sources/GIDAuthStateMigration/GIDAuthStateMigration.h"

#import "GoogleSignIn/Sources/GIDBundle/Implementation/NSBundle+GIDBundle.h"
#import "GoogleSignIn/Sources/GIDSignInCallbackSchemes.h"

@import GTMAppAuth;
Expand Down Expand Up @@ -47,16 +49,31 @@ @interface GIDAuthStateMigration ()

@end

@implementation GIDAuthStateMigration
@implementation GIDAuthStateMigration {
// The user defaults in which the migration-check flags are recorded.
NSUserDefaults *_userDefaults;
// The bundle whose identifier fingerprints legacy auth state.
id<GIDBundle> _bundle;
}

- (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore {
- (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore
userDefaults:(NSUserDefaults *)userDefaults
bundle:(id<GIDBundle>)bundle {
self = [super init];
if (self) {
_keychainStore = keychainStore;
_userDefaults = userDefaults;
_bundle = bundle;
}
return self;
}

- (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore {
return [self initWithKeychainStore:keychainStore
userDefaults:[NSUserDefaults standardUserDefaults]
bundle:[NSBundle mainBundle]];
}

- (instancetype)init {
GTMKeychainStore *keychainStore = [[GTMKeychainStore alloc] initWithItemName:@"auth"];
return [self initWithKeychainStore:keychainStore];
Expand All @@ -68,11 +85,10 @@ - (void)migrateIfNeededWithTokenURL:(NSURL *)tokenURL
// If this is a fresh install, take no action and mark the migration checks as having been
// performed.
if (isFreshInstall) {
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
#if TARGET_OS_OSX
[defaults setBool:YES forKey:kDataProtectedMigrationCheckPerformedKey];
[_userDefaults setBool:YES forKey:kDataProtectedMigrationCheckPerformedKey];
#elif TARGET_OS_IOS && !TARGET_OS_MACCATALYST
[defaults setBool:YES forKey:kGTMAppAuthMigrationCheckPerformedKey];
[_userDefaults setBool:YES forKey:kGTMAppAuthMigrationCheckPerformedKey];
#endif // TARGET_OS_OSX
return;
}
Expand All @@ -89,8 +105,7 @@ - (void)migrateIfNeededWithTokenURL:(NSURL *)tokenURL
// Migrate from the fileBasedKeychain to dataProtectedKeychain with GTMAppAuth 5.0.
- (void)performDataProtectedMigrationIfNeeded {
// See if we've performed the migration check previously.
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
if ([defaults boolForKey:kDataProtectedMigrationCheckPerformedKey]) {
if ([_userDefaults boolForKey:kDataProtectedMigrationCheckPerformedKey]) {
return;
}

Expand All @@ -108,7 +123,7 @@ - (void)performDataProtectedMigrationIfNeeded {
}

// Mark the migration check as having been performed.
[defaults setBool:YES forKey:kDataProtectedMigrationCheckPerformedKey];
[_userDefaults setBool:YES forKey:kDataProtectedMigrationCheckPerformedKey];
}

#elif TARGET_OS_IOS && !TARGET_OS_MACCATALYST
Expand All @@ -117,8 +132,7 @@ - (void)performDataProtectedMigrationIfNeeded {
- (void)performGIDMigrationIfNeededWithTokenURL:(NSURL *)tokenURL
callbackPath:(NSString *)callbackPath {
// See if we've performed the migration check previously.
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
if ([defaults boolForKey:kGTMAppAuthMigrationCheckPerformedKey]) {
if ([_userDefaults boolForKey:kGTMAppAuthMigrationCheckPerformedKey]) {
return;
}

Expand All @@ -133,7 +147,7 @@ - (void)performGIDMigrationIfNeededWithTokenURL:(NSURL *)tokenURL
}

// Mark the migration check as having been performed.
[defaults setBool:YES forKey:kGTMAppAuthMigrationCheckPerformedKey];
[_userDefaults setBool:YES forKey:kGTMAppAuthMigrationCheckPerformedKey];
}

// Returns a |GTMAuthSession| object containing any old auth state or |nil| if none
Expand All @@ -155,7 +169,7 @@ - (nullable GTMAuthSession *)extractAuthSessionWithTokenURL:(NSURL *)tokenURL
}

// Parse the fingerprint.
NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier];
NSString *bundleID = _bundle.bundleIdentifier;
NSString *pattern =
[NSString stringWithFormat:@"^%@-(.+)-(?:email|profile|https:\\/\\/).*$", bundleID];
NSError *error;
Expand All @@ -174,8 +188,9 @@ - (nullable GTMAuthSession *)extractAuthSessionWithTokenURL:(NSURL *)tokenURL
NSString *clientID = [fingerprint substringWithRange:[matches[0] rangeAtIndex:1]];

// Generate the redirect URI from the extracted client ID.
NSString *scheme =
[[[GIDSignInCallbackSchemes alloc] initWithClientIdentifier:clientID] clientIdentifierScheme];
GIDSignInCallbackSchemes *schemes =
[[GIDSignInCallbackSchemes alloc] initWithClientIdentifier:clientID bundle:_bundle];
NSString *scheme = [schemes clientIdentifierScheme];
NSString *redirectURI = [NSString stringWithFormat:@"%@:%@", scheme, callbackPath];

// Retrieve the additional token request parameters value.
Expand Down
37 changes: 37 additions & 0 deletions GoogleSignIn/Sources/GIDBundle/API/GIDBundle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

/// The subset of `NSBundle` that Google Sign-In reads at runtime.
///
/// The SDK depends on this protocol rather than on `NSBundle` directly so that the host app's
/// `Info.plist` can be substituted in tests. `NSBundle` conforms; see `NSBundle+GIDBundle.h`.
/// Production code always passes `NSBundle.mainBundle`: the URL schemes and bundle identifier the
/// SDK reads here must match the app the OS is actually running.
@protocol GIDBundle <NSObject>

/// The receiver's bundle identifier, if any.
@property(nonatomic, readonly, copy, nullable) NSString *bundleIdentifier;

/// Returns the value associated with `key` in the receiver's information property list.
- (nullable id)objectForInfoDictionaryKey:(NSString *)key;

@end

NS_ASSUME_NONNULL_END
44 changes: 44 additions & 0 deletions GoogleSignIn/Sources/GIDBundle/Fake/GIDFakeBundle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <Foundation/Foundation.h>

#import "GoogleSignIn/Sources/GIDBundle/API/GIDBundle.h"

NS_ASSUME_NONNULL_BEGIN

/// An in-memory `GIDBundle` for tests. Tests populate it with the Info.plist values and bundle
/// identifier the code under test reads.
@interface GIDFakeBundle : NSObject <GIDBundle>

/// The bundle identifier to report. Defaults to nil.
@property(nonatomic, copy, nullable) NSString *bundleIdentifier;

/// Creates a bundle that reports `bundleIdentifier` and whose Info.plist holds the entries of
/// `infoDictionary`.
- (instancetype)initWithBundleIdentifier:(nullable NSString *)bundleIdentifier
infoDictionary:(NSDictionary<NSString *, id> *)infoDictionary
NS_DESIGNATED_INITIALIZER;

/// Creates a bundle with no identifier and an empty Info.plist.
- (instancetype)init;

/// Sets (or, with nil, removes) the value returned by `-objectForInfoDictionaryKey:` for `key`.
- (void)setObject:(nullable id)object forInfoDictionaryKey:(NSString *)key;

@end

NS_ASSUME_NONNULL_END
56 changes: 56 additions & 0 deletions GoogleSignIn/Sources/GIDBundle/Fake/GIDFakeBundle.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import "GoogleSignIn/Sources/GIDBundle/Fake/GIDFakeBundle.h"

NS_ASSUME_NONNULL_BEGIN

@implementation GIDFakeBundle {
// The entries `-objectForInfoDictionaryKey:` reads.
NSMutableDictionary<NSString *, id> *_infoDictionary;
}

@synthesize bundleIdentifier = _bundleIdentifier;

- (instancetype)initWithBundleIdentifier:(nullable NSString *)bundleIdentifier
infoDictionary:(NSDictionary<NSString *, id> *)infoDictionary {
self = [super init];
if (self) {
_bundleIdentifier = [bundleIdentifier copy];
_infoDictionary = [infoDictionary mutableCopy];
}
return self;
}

- (instancetype)init {
return [self initWithBundleIdentifier:nil infoDictionary:@{}];
}

- (nullable id)objectForInfoDictionaryKey:(NSString *)key {
return _infoDictionary[key];
}

- (void)setObject:(nullable id)object forInfoDictionaryKey:(NSString *)key {
if (object) {
[_infoDictionary setObject:object forKey:key];
} else {
[_infoDictionary removeObjectForKey:key];
}
}

@end

NS_ASSUME_NONNULL_END
28 changes: 28 additions & 0 deletions GoogleSignIn/Sources/GIDBundle/Implementation/NSBundle+GIDBundle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <Foundation/Foundation.h>

#import "GoogleSignIn/Sources/GIDBundle/API/GIDBundle.h"

NS_ASSUME_NONNULL_BEGIN

/// `NSBundle` already implements every `GIDBundle` requirement; this category only declares the
/// conformance.
@interface NSBundle (GIDBundle) <GIDBundle>
@end

NS_ASSUME_NONNULL_END
24 changes: 24 additions & 0 deletions GoogleSignIn/Sources/GIDBundle/Implementation/NSBundle+GIDBundle.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import "GoogleSignIn/Sources/GIDBundle/Implementation/NSBundle+GIDBundle.h"

NS_ASSUME_NONNULL_BEGIN

@implementation NSBundle (GIDBundle)
@end

NS_ASSUME_NONNULL_END
39 changes: 39 additions & 0 deletions GoogleSignIn/Sources/GIDDevice/API/GIDDevice.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <TargetConditionals.h>

#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

/// The subset of `UIDevice` that Google Sign-In reads when describing the device to Google's EMM
/// (Enterprise Mobility Management) backend. `UIDevice` conforms; see `UIDevice+GIDDevice.h`.
@protocol GIDDevice <NSObject>

/// The name of the operating system, e.g. "iOS".
@property(nonatomic, readonly, copy) NSString *systemName;

/// The version of the operating system, e.g. "17.0.1".
@property(nonatomic, readonly, copy) NSString *systemVersion;

@end

NS_ASSUME_NONNULL_END

#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
Loading
Loading