From ba80c680256bc01a3e2b1c330b8caff95b5bc5c4 Mon Sep 17 00:00:00 2001 From: Worthing ~ <115107835+w-goog@users.noreply.github.com> Date: Thu, 15 Oct 2026 13:55:18 -0700 Subject: [PATCH 1/4] g-orchestrated: add the GIDDevice and GIDLocalAuthenticationContext seams with fakes --- .../Sources/GIDDevice/API/GIDDevice.h | 39 ++++++++++++++ .../Sources/GIDDevice/Fake/GIDFakeDevice.h | 46 ++++++++++++++++ .../Sources/GIDDevice/Fake/GIDFakeDevice.m | 44 +++++++++++++++ .../Implementation/UIDevice+GIDDevice.h | 34 ++++++++++++ .../Implementation/UIDevice+GIDDevice.m | 30 +++++++++++ .../API/GIDLocalAuthenticationContext.h | 37 +++++++++++++ .../Fake/GIDFakeLocalAuthenticationContext.h | 43 +++++++++++++++ .../Fake/GIDFakeLocalAuthenticationContext.m | 54 +++++++++++++++++++ .../LAContext+GIDLocalAuthenticationContext.h | 34 ++++++++++++ .../LAContext+GIDLocalAuthenticationContext.m | 30 +++++++++++ 10 files changed, 391 insertions(+) create mode 100644 GoogleSignIn/Sources/GIDDevice/API/GIDDevice.h create mode 100644 GoogleSignIn/Sources/GIDDevice/Fake/GIDFakeDevice.h create mode 100644 GoogleSignIn/Sources/GIDDevice/Fake/GIDFakeDevice.m create mode 100644 GoogleSignIn/Sources/GIDDevice/Implementation/UIDevice+GIDDevice.h create mode 100644 GoogleSignIn/Sources/GIDDevice/Implementation/UIDevice+GIDDevice.m create mode 100644 GoogleSignIn/Sources/GIDLocalAuthentication/API/GIDLocalAuthenticationContext.h create mode 100644 GoogleSignIn/Sources/GIDLocalAuthentication/Fake/GIDFakeLocalAuthenticationContext.h create mode 100644 GoogleSignIn/Sources/GIDLocalAuthentication/Fake/GIDFakeLocalAuthenticationContext.m create mode 100644 GoogleSignIn/Sources/GIDLocalAuthentication/Implementation/LAContext+GIDLocalAuthenticationContext.h create mode 100644 GoogleSignIn/Sources/GIDLocalAuthentication/Implementation/LAContext+GIDLocalAuthenticationContext.m diff --git a/GoogleSignIn/Sources/GIDDevice/API/GIDDevice.h b/GoogleSignIn/Sources/GIDDevice/API/GIDDevice.h new file mode 100644 index 00000000..c91ac861 --- /dev/null +++ b/GoogleSignIn/Sources/GIDDevice/API/GIDDevice.h @@ -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 + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +#import + +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 + +/// 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 diff --git a/GoogleSignIn/Sources/GIDDevice/Fake/GIDFakeDevice.h b/GoogleSignIn/Sources/GIDDevice/Fake/GIDFakeDevice.h new file mode 100644 index 00000000..f1752239 --- /dev/null +++ b/GoogleSignIn/Sources/GIDDevice/Fake/GIDFakeDevice.h @@ -0,0 +1,46 @@ +/* + * 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 + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +#import + +#import "GoogleSignIn/Sources/GIDDevice/API/GIDDevice.h" + +NS_ASSUME_NONNULL_BEGIN + +/// An in-memory `GIDDevice` for tests. +@interface GIDFakeDevice : NSObject + +/// The operating system name to report. +@property(nonatomic, copy) NSString *systemName; + +/// The operating system version to report. +@property(nonatomic, copy) NSString *systemVersion; + +/// Initializes a fake device reporting `systemName` and `systemVersion`. +- (instancetype)initWithSystemName:(NSString *)systemName + systemVersion:(NSString *)systemVersion NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST diff --git a/GoogleSignIn/Sources/GIDDevice/Fake/GIDFakeDevice.m b/GoogleSignIn/Sources/GIDDevice/Fake/GIDFakeDevice.m new file mode 100644 index 00000000..71fcf5d9 --- /dev/null +++ b/GoogleSignIn/Sources/GIDDevice/Fake/GIDFakeDevice.m @@ -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 + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +#import "GoogleSignIn/Sources/GIDDevice/Fake/GIDFakeDevice.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation GIDFakeDevice + +@synthesize systemName = _systemName; +@synthesize systemVersion = _systemVersion; + +- (instancetype)initWithSystemName:(NSString *)systemName + systemVersion:(NSString *)systemVersion { + self = [super init]; + if (self) { + _systemName = [systemName copy]; + _systemVersion = [systemVersion copy]; + } + return self; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST diff --git a/GoogleSignIn/Sources/GIDDevice/Implementation/UIDevice+GIDDevice.h b/GoogleSignIn/Sources/GIDDevice/Implementation/UIDevice+GIDDevice.h new file mode 100644 index 00000000..b7d98b56 --- /dev/null +++ b/GoogleSignIn/Sources/GIDDevice/Implementation/UIDevice+GIDDevice.h @@ -0,0 +1,34 @@ +/* + * 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 + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +#import + +#import "GoogleSignIn/Sources/GIDDevice/API/GIDDevice.h" + +NS_ASSUME_NONNULL_BEGIN + +/// `UIDevice` already implements every `GIDDevice` requirement; this category only declares the +/// conformance. +@interface UIDevice (GIDDevice) +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST diff --git a/GoogleSignIn/Sources/GIDDevice/Implementation/UIDevice+GIDDevice.m b/GoogleSignIn/Sources/GIDDevice/Implementation/UIDevice+GIDDevice.m new file mode 100644 index 00000000..51237e61 --- /dev/null +++ b/GoogleSignIn/Sources/GIDDevice/Implementation/UIDevice+GIDDevice.m @@ -0,0 +1,30 @@ +/* + * 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 + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +#import "GoogleSignIn/Sources/GIDDevice/Implementation/UIDevice+GIDDevice.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation UIDevice (GIDDevice) +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST diff --git a/GoogleSignIn/Sources/GIDLocalAuthentication/API/GIDLocalAuthenticationContext.h b/GoogleSignIn/Sources/GIDLocalAuthentication/API/GIDLocalAuthenticationContext.h new file mode 100644 index 00000000..d8322361 --- /dev/null +++ b/GoogleSignIn/Sources/GIDLocalAuthentication/API/GIDLocalAuthenticationContext.h @@ -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 + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/// The subset of `LAContext` that Google Sign-In uses to probe whether a device passcode is set. +/// `LAContext` conforms; see `LAContext+GIDLocalAuthenticationContext.h`. +@protocol GIDLocalAuthenticationContext + +/// Returns whether `policy` can be evaluated, populating `error` when it cannot. +- (BOOL)canEvaluatePolicy:(LAPolicy)policy error:(NSError * _Nullable * _Nullable)error; + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST diff --git a/GoogleSignIn/Sources/GIDLocalAuthentication/Fake/GIDFakeLocalAuthenticationContext.h b/GoogleSignIn/Sources/GIDLocalAuthentication/Fake/GIDFakeLocalAuthenticationContext.h new file mode 100644 index 00000000..20f64269 --- /dev/null +++ b/GoogleSignIn/Sources/GIDLocalAuthentication/Fake/GIDFakeLocalAuthenticationContext.h @@ -0,0 +1,43 @@ +/* + * 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 + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +#import + +#import "GoogleSignIn/Sources/GIDLocalAuthentication/API/GIDLocalAuthenticationContext.h" + +NS_ASSUME_NONNULL_BEGIN + +/// An in-memory `GIDLocalAuthenticationContext` for tests. +@interface GIDFakeLocalAuthenticationContext : NSObject + +/// The value `-canEvaluatePolicy:error:` returns. Defaults to YES. +@property(nonatomic) BOOL canEvaluatePolicyResult; + +/// The error `-canEvaluatePolicy:error:` reports, if any. Defaults to nil. +@property(nonatomic, nullable) NSError *canEvaluatePolicyError; + +/// Every policy passed to `-canEvaluatePolicy:error:`, in call order, boxed as `NSNumber`. +@property(nonatomic, readonly) NSArray *evaluatedPolicies; + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST diff --git a/GoogleSignIn/Sources/GIDLocalAuthentication/Fake/GIDFakeLocalAuthenticationContext.m b/GoogleSignIn/Sources/GIDLocalAuthentication/Fake/GIDFakeLocalAuthenticationContext.m new file mode 100644 index 00000000..7c0d334b --- /dev/null +++ b/GoogleSignIn/Sources/GIDLocalAuthentication/Fake/GIDFakeLocalAuthenticationContext.m @@ -0,0 +1,54 @@ +/* + * 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 + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +#import "GoogleSignIn/Sources/GIDLocalAuthentication/Fake/GIDFakeLocalAuthenticationContext.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation GIDFakeLocalAuthenticationContext { + NSMutableArray *_evaluatedPolicies; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _canEvaluatePolicyResult = YES; + _evaluatedPolicies = [NSMutableArray array]; + } + return self; +} + +- (NSArray *)evaluatedPolicies { + return [_evaluatedPolicies copy]; +} + +- (BOOL)canEvaluatePolicy:(LAPolicy)policy error:(NSError * _Nullable * _Nullable)error { + [_evaluatedPolicies addObject:@(policy)]; + if (error) { + *error = self.canEvaluatePolicyError; + } + return self.canEvaluatePolicyResult; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST diff --git a/GoogleSignIn/Sources/GIDLocalAuthentication/Implementation/LAContext+GIDLocalAuthenticationContext.h b/GoogleSignIn/Sources/GIDLocalAuthentication/Implementation/LAContext+GIDLocalAuthenticationContext.h new file mode 100644 index 00000000..da49c142 --- /dev/null +++ b/GoogleSignIn/Sources/GIDLocalAuthentication/Implementation/LAContext+GIDLocalAuthenticationContext.h @@ -0,0 +1,34 @@ +/* + * 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 + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +#import + +#import "GoogleSignIn/Sources/GIDLocalAuthentication/API/GIDLocalAuthenticationContext.h" + +NS_ASSUME_NONNULL_BEGIN + +/// `LAContext` already implements every `GIDLocalAuthenticationContext` requirement; this category +/// only declares the conformance. +@interface LAContext (GIDLocalAuthenticationContext) +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST diff --git a/GoogleSignIn/Sources/GIDLocalAuthentication/Implementation/LAContext+GIDLocalAuthenticationContext.m b/GoogleSignIn/Sources/GIDLocalAuthentication/Implementation/LAContext+GIDLocalAuthenticationContext.m new file mode 100644 index 00000000..5032809e --- /dev/null +++ b/GoogleSignIn/Sources/GIDLocalAuthentication/Implementation/LAContext+GIDLocalAuthenticationContext.m @@ -0,0 +1,30 @@ +/* + * 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 + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +#import "GoogleSignIn/Sources/GIDLocalAuthentication/Implementation/LAContext+GIDLocalAuthenticationContext.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation LAContext (GIDLocalAuthenticationContext) +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST From 69b7c111e6b183845e8767a891ff78acb4d23f4e Mon Sep 17 00:00:00 2001 From: Worthing ~ <115107835+w-goog@users.noreply.github.com> Date: Thu, 15 Oct 2026 13:56:41 -0700 Subject: [PATCH 2/4] g-orchestrated: inject device and LAContext into GIDEMMSupport and GIDMDMPasscodeCache --- GoogleSignIn/Sources/GIDEMMSupport.h | 29 +++++++++++- GoogleSignIn/Sources/GIDEMMSupport.m | 53 ++++++++++++++++++---- GoogleSignIn/Sources/GIDMDMPasscodeCache.h | 12 +++++ GoogleSignIn/Sources/GIDMDMPasscodeCache.m | 31 +++++++------ GoogleSignIn/Tests/Unit/GIDSignInTest.m | 4 +- 5 files changed, 103 insertions(+), 26 deletions(-) diff --git a/GoogleSignIn/Sources/GIDEMMSupport.h b/GoogleSignIn/Sources/GIDEMMSupport.h index 7a5de88f..f9813ee0 100644 --- a/GoogleSignIn/Sources/GIDEMMSupport.h +++ b/GoogleSignIn/Sources/GIDEMMSupport.h @@ -22,22 +22,49 @@ @import GTMAppAuth; +#import "GoogleSignIn/Sources/GIDDevice/API/GIDDevice.h" +#import "GoogleSignIn/Sources/GIDEMMErrorHandler.h" + NS_ASSUME_NONNULL_BEGIN /// A class to support EMM (Enterprise Mobility Management). @interface GIDEMMSupport : NSObject -- (instancetype)init NS_DESIGNATED_INITIALIZER; +/// Creates an instance that reports EMM errors through `errorHandler` and describes the device +/// to the EMM backend using `device`. +- (instancetype)initWithErrorHandler:(GIDEMMErrorHandler *)errorHandler + device:(id)device NS_DESIGNATED_INITIALIZER; + +/// Creates an instance using `+[GIDEMMErrorHandler sharedInstance]` and `[UIDevice currentDevice]`. +- (instancetype)init; + +/// Handles potential EMM error from token fetch response. +- (void)handleTokenFetchEMMError:(nullable NSError *)error + completion:(void (^)(NSError *_Nullable))completion; + +/// Gets a new set of URL parameters that contains updated EMM-related URL parameters if needed. +- (NSDictionary *)updatedEMMParametersWithParameters: + (NSDictionary *)parameters; + +/// Gets a new set of URL parameters that also contains EMM-related URL parameters if needed. +/// +/// @note This function only returns parameter keys in the `NSDictionary` for string/number/bools. +- (NSDictionary *)parametersWithParameters:(NSDictionary *)parameters + emmSupport:(nullable NSString *)emmSupport + isPasscodeInfoRequired:(BOOL)isPasscodeInfoRequired; /// Handles potential EMM error from token fetch response. +/// Equivalent to calling the instance method on `[[GIDEMMSupport alloc] init]`. + (void)handleTokenFetchEMMError:(nullable NSError *)error completion:(void (^)(NSError *_Nullable))completion; /// Gets a new set of URL parameters that contains updated EMM-related URL parameters if needed. +/// Equivalent to calling the instance method on `[[GIDEMMSupport alloc] init]`. + (NSDictionary *)updatedEMMParametersWithParameters: (NSDictionary *)parameters; /// Gets a new set of URL parameters that also contains EMM-related URL parameters if needed. +/// Equivalent to calling the instance method on `[[GIDEMMSupport alloc] init]`. /// /// @note This function only returns parameter keys in the `NSDictionary` for string/number/bools. + (NSDictionary *)parametersWithParameters:(NSDictionary *)parameters diff --git a/GoogleSignIn/Sources/GIDEMMSupport.m b/GoogleSignIn/Sources/GIDEMMSupport.m index 1940df4f..e03990fa 100644 --- a/GoogleSignIn/Sources/GIDEMMSupport.m +++ b/GoogleSignIn/Sources/GIDEMMSupport.m @@ -22,6 +22,7 @@ #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h" +#import "GoogleSignIn/Sources/GIDDevice/Implementation/UIDevice+GIDDevice.h" #import "GoogleSignIn/Sources/GIDEMMErrorHandler.h" #import "GoogleSignIn/Sources/GIDMDMPasscodeState.h" @@ -58,18 +59,32 @@ typedef NS_ENUM(NSInteger, ErrorCode) { ErrorCodeAppVerificationRequired, }; -@implementation GIDEMMSupport +@implementation GIDEMMSupport { + GIDEMMErrorHandler *_errorHandler; + id _device; +} + +- (instancetype)initWithErrorHandler:(GIDEMMErrorHandler *)errorHandler + device:(id)device { + self = [super init]; + if (self) { + _errorHandler = errorHandler; + _device = device; + } + return self; +} - (instancetype)init { - return [super init]; + return [self initWithErrorHandler:[GIDEMMErrorHandler sharedInstance] + device:[UIDevice currentDevice]]; } -+ (void)handleTokenFetchEMMError:(nullable NSError *)error +- (void)handleTokenFetchEMMError:(nullable NSError *)error completion:(void (^)(NSError *_Nullable))completion { NSDictionary *errorJSON = error.userInfo[OIDOAuthErrorResponseErrorKey]; if (errorJSON) { - [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:errorJSON - completion:^(BOOL handled) { + [_errorHandler handleErrorFromResponse:errorJSON + completion:^(BOOL handled) { if (handled) { completion([NSError errorWithDomain:kGIDSignInErrorDomain code:kGIDSignInErrorCodeEMM @@ -83,14 +98,14 @@ + (void)handleTokenFetchEMMError:(nullable NSError *)error } } -+ (NSDictionary *)updatedEMMParametersWithParameters: +- (NSDictionary *)updatedEMMParametersWithParameters: (NSDictionary *)parameters { return [self parametersWithParameters:parameters emmSupport:parameters[kEMMSupportParameterName] isPasscodeInfoRequired:parameters[kEMMPasscodeInfoParameterName] != nil]; } -+ (NSDictionary *)parametersWithParameters:(NSDictionary *)parameters +- (NSDictionary *)parametersWithParameters:(NSDictionary *)parameters emmSupport:(nullable NSString *)emmSupport isPasscodeInfoRequired:(BOOL)isPasscodeInfoRequired { if (!emmSupport) { @@ -98,7 +113,7 @@ + (void)handleTokenFetchEMMError:(nullable NSError *)error } NSMutableDictionary *allParameters = [(parameters ?: @{}) mutableCopy]; allParameters[kEMMSupportParameterName] = emmSupport; - UIDevice *device = [UIDevice currentDevice]; + id device = _device; NSString *systemName = device.systemName; if ([systemName isEqualToString:kOldIOSSystemName]) { systemName = kNewIOSSystemName; @@ -111,18 +126,36 @@ + (void)handleTokenFetchEMMError:(nullable NSError *)error return [GIDEMMSupport dictionaryWithStringValuesFromDictionary:allParameters]; } ++ (void)handleTokenFetchEMMError:(nullable NSError *)error + completion:(void (^)(NSError *_Nullable))completion { + [[[self alloc] init] handleTokenFetchEMMError:error completion:completion]; +} + ++ (NSDictionary *)updatedEMMParametersWithParameters: + (NSDictionary *)parameters { + return [[[self alloc] init] updatedEMMParametersWithParameters:parameters]; +} + ++ (NSDictionary *)parametersWithParameters:(NSDictionary *)parameters + emmSupport:(nullable NSString *)emmSupport + isPasscodeInfoRequired:(BOOL)isPasscodeInfoRequired { + return [[[self alloc] init] parametersWithParameters:parameters + emmSupport:emmSupport + isPasscodeInfoRequired:isPasscodeInfoRequired]; +} + #pragma mark - GTMAuthSessionDelegate - (nullable NSDictionary *) additionalTokenRefreshParametersForAuthSession:(GTMAuthSession *)authSession { - return [GIDEMMSupport updatedEMMParametersWithParameters: + return [self updatedEMMParametersWithParameters: authSession.authState.lastTokenResponse.additionalParameters]; } - (void)updateErrorForAuthSession:(GTMAuthSession *)authSession originalError:(NSError *)originalError completion:(void (^)(NSError * _Nullable))completion { - [GIDEMMSupport handleTokenFetchEMMError:originalError completion:^(NSError *_Nullable error) { + [self handleTokenFetchEMMError:originalError completion:^(NSError *_Nullable error) { completion(error); }]; } diff --git a/GoogleSignIn/Sources/GIDMDMPasscodeCache.h b/GoogleSignIn/Sources/GIDMDMPasscodeCache.h index 569fc22a..bc762ca1 100644 --- a/GoogleSignIn/Sources/GIDMDMPasscodeCache.h +++ b/GoogleSignIn/Sources/GIDMDMPasscodeCache.h @@ -19,6 +19,8 @@ #import +#import "GoogleSignIn/Sources/GIDLocalAuthentication/API/GIDLocalAuthenticationContext.h" + @class GIDMDMPasscodeState; NS_ASSUME_NONNULL_BEGIN @@ -34,6 +36,16 @@ NS_ASSUME_NONNULL_BEGIN */ + (instancetype)sharedInstance; +/** + * Creates a cache that probes the passcode state through `localAuthenticationContext`. Pass nil + * when LocalAuthentication is unavailable; the cache then reports only the Keychain probe. + */ +- (instancetype)initWithLocalAuthenticationContext: + (nullable id)localAuthenticationContext NS_DESIGNATED_INITIALIZER; + +/** Unavailable; use `+sharedInstance` or the designated initializer. */ +- (instancetype)init NS_UNAVAILABLE; + /** * Retrieves the current passcode state. */ diff --git a/GoogleSignIn/Sources/GIDMDMPasscodeCache.m b/GoogleSignIn/Sources/GIDMDMPasscodeCache.m index bedb3a31..bcb44d07 100644 --- a/GoogleSignIn/Sources/GIDMDMPasscodeCache.m +++ b/GoogleSignIn/Sources/GIDMDMPasscodeCache.m @@ -22,6 +22,7 @@ #import #import +#import "GoogleSignIn/Sources/GIDLocalAuthentication/Implementation/LAContext+GIDLocalAuthenticationContext.h" #import "GoogleSignIn/Sources/GIDMDMPasscodeState.h" #import "GoogleSignIn/Sources/GIDMDMPasscodeState_Private.h" @@ -55,6 +56,8 @@ static const int64_t kObtainKeychainInfoWaitTime = 3 * NSEC_PER_SEC; @implementation GIDMDMPasscodeCache { + id _localAuthenticationContext; + /** Whether or not LocalAuthentication API is available. */ BOOL _hasLocalAuthentication; @@ -77,10 +80,12 @@ @implementation GIDMDMPasscodeCache { GIDMDMPasscodeState *_cachedState; } -- (instancetype)init { +- (instancetype)initWithLocalAuthenticationContext: + (nullable id)localAuthenticationContext { self = [super init]; if (self) { - _hasLocalAuthentication = [self hasLocalAuthentication]; + _localAuthenticationContext = localAuthenticationContext; + _hasLocalAuthentication = (localAuthenticationContext != nil) && [self hasLocalAuthentication]; _hasKeychain = [self hasKeychain]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground:) @@ -98,7 +103,14 @@ + (instancetype)sharedInstance { static GIDMDMPasscodeCache *sharedInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - sharedInstance = [[GIDMDMPasscodeCache alloc] init]; + LAContext *context; + @try { + context = [[LAContext alloc] init]; + } @catch (NSException *) { + // In theory there should be no exceptions but in practice there may be: b/23200390, b/23218643. + context = nil; + } + sharedInstance = [[GIDMDMPasscodeCache alloc] initWithLocalAuthenticationContext:context]; }); return sharedInstance; } @@ -185,18 +197,11 @@ - (void)obtainLocalAuthenticationInfo { NSLog(@"Calling LocalAuthentication API for device passcode state..."); #endif _hasEnteredBackground = NO; - static LAContext *context; - @try { - if (!context) { - context = [[LAContext alloc] init]; - } - } @catch (NSException *) { - // In theory there should be no exceptions but in practice there may be: b/23200390, b/23218643. - return; - } int result; NSError *error; - result = [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&error] ? 1 : 0; + result = + [_localAuthenticationContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication + error:&error] ? 1 : 0; if (error) { _localAuthenticationInfo = @{ kResultKey : @(result), diff --git a/GoogleSignIn/Tests/Unit/GIDSignInTest.m b/GoogleSignIn/Tests/Unit/GIDSignInTest.m index 1cd8b695..acc147e1 100644 --- a/GoogleSignIn/Tests/Unit/GIDSignInTest.m +++ b/GoogleSignIn/Tests/Unit/GIDSignInTest.m @@ -1936,8 +1936,8 @@ - (void)testTokenEndpointEMMError { code:12345 userInfo:@{ OIDOAuthErrorFieldError : errorJSON }]; id emmSupport = OCMStrictClassMock([GIDEMMSupport class]); - [[emmSupport expect] handleTokenFetchEMMError:emmError - completion:SAVE_TO_ARG_BLOCK(completion)]; + [[[emmSupport expect] classMethod] handleTokenFetchEMMError:emmError + completion:SAVE_TO_ARG_BLOCK(completion)]; [self OAuthLoginWithAddScopesFlow:NO authError:nil From 6647d4bbc617369a93406ae7a9e31784831affed Mon Sep 17 00:00:00 2001 From: Worthing ~ <115107835+w-goog@users.noreply.github.com> Date: Thu, 15 Oct 2026 12:05:23 -0700 Subject: [PATCH 3/4] g-orchestrated: test GIDMDMPasscodeCache through a fake LAContext instead of swizzling --- .../Tests/Unit/GIDMDMPasscodeStateTests.m | 101 ++++++------------ 1 file changed, 32 insertions(+), 69 deletions(-) diff --git a/GoogleSignIn/Tests/Unit/GIDMDMPasscodeStateTests.m b/GoogleSignIn/Tests/Unit/GIDMDMPasscodeStateTests.m index 785d841e..6c14dcd0 100644 --- a/GoogleSignIn/Tests/Unit/GIDMDMPasscodeStateTests.m +++ b/GoogleSignIn/Tests/Unit/GIDMDMPasscodeStateTests.m @@ -21,71 +21,34 @@ #import #import +#import "GoogleSignIn/Sources/GIDLocalAuthentication/Fake/GIDFakeLocalAuthenticationContext.h" +#import "GoogleSignIn/Sources/GIDMDMPasscodeCache.h" #import "GoogleSignIn/Sources/GIDMDMPasscodeState.h" -#import -#import - @interface GIDMDMPasscodeStateTests : XCTestCase @end @implementation GIDMDMPasscodeStateTests { - /** Whether or not the iOS version is equal or greater than 9.0. */ - BOOL _isIOS9orAbove; - - /** Whether or not `canEvaluatePolicy:error:` method has been called. */ - BOOL _canEvaluatePolicyCalled; - - /** The next result to be returned from the `canEvaluatePolicy:error:` method. */ - BOOL _nextCanEvaluatePolicyResult; - - /** The next error to be returned from the `canEvaluatePolicy:error:` method. */ - NSError *_nextCanEvaluatePolicyError; + /** Stands in for the `LAContext` the cache probes. */ + GIDFakeLocalAuthenticationContext *_context; + /** The cache under test. */ + GIDMDMPasscodeCache *_cache; } - (void)setUp { [super setUp]; - _isIOS9orAbove = [[NSProcessInfo processInfo] - isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){.majorVersion = 9}]; - if (!_isIOS9orAbove) { - return; - } - _canEvaluatePolicyCalled = NO; - id canEvaluatePolicyError = ^BOOL(id context, LAPolicy policy, NSError * _Nullable *error) { - self->_canEvaluatePolicyCalled = YES; - XCTAssertEqual(policy, LAPolicyDeviceOwnerAuthentication); - if (error) { - *error = self->_nextCanEvaluatePolicyError; - } - return self->_nextCanEvaluatePolicyResult; - }; - [GULSwizzler swizzleClass:[LAContext class] - selector:@selector(canEvaluatePolicy:error:) - isClassSelector:NO - withBlock:canEvaluatePolicyError]; - [self postApplicationDidEnterBackgroundNotification]; -} - -- (void)tearDown { - if (!_isIOS9orAbove) { - return; - } - [GULSwizzler unswizzleClass:[LAContext class] - selector:@selector(canEvaluatePolicy:error:) - isClassSelector:NO]; + _context = [[GIDFakeLocalAuthenticationContext alloc] init]; + _cache = [[GIDMDMPasscodeCache alloc] initWithLocalAuthenticationContext:_context]; } /** * Verifies the correct response when LocalAuthentication API returns without an error. */ - (void)testLocalAuthenticationNoError { - if (!_isIOS9orAbove) { - return; - } - _nextCanEvaluatePolicyResult = YES; - _nextCanEvaluatePolicyError = nil; - GIDMDMPasscodeState *passcodeState = [GIDMDMPasscodeState passcodeState]; - XCTAssertTrue(_canEvaluatePolicyCalled); + _context.canEvaluatePolicyResult = YES; + _context.canEvaluatePolicyError = nil; + GIDMDMPasscodeState *passcodeState = [_cache passcodeState]; + XCTAssertEqualObjects(_context.evaluatedPolicies, @[ @(LAPolicyDeviceOwnerAuthentication) ]); XCTAssertEqualObjects(passcodeState.status, @"YES"); NSDictionary *dict = [self dictWithEncodedString:passcodeState.info]; [self assertJSONNumber:dict[@"LocalAuthentication"][@"result"] isInteger:1]; @@ -97,17 +60,14 @@ - (void)testLocalAuthenticationNoError { * Verifies the correct response when LocalAuthentication API returns with an error. */ - (void)testLocalAuthenticationHasError { - if (!_isIOS9orAbove) { - return; - } NSString *fakeErrorDomain = @"asdf.hjkl"; NSInteger fakeErrorCode = -12345; - _nextCanEvaluatePolicyResult = NO; - _nextCanEvaluatePolicyError = [NSError errorWithDomain:fakeErrorDomain - code:fakeErrorCode - userInfo:nil]; - GIDMDMPasscodeState *passcodeState = [GIDMDMPasscodeState passcodeState]; - XCTAssertTrue(_canEvaluatePolicyCalled); + _context.canEvaluatePolicyResult = NO; + _context.canEvaluatePolicyError = [NSError errorWithDomain:fakeErrorDomain + code:fakeErrorCode + userInfo:nil]; + GIDMDMPasscodeState *passcodeState = [_cache passcodeState]; + XCTAssertEqualObjects(_context.evaluatedPolicies, @[ @(LAPolicyDeviceOwnerAuthentication) ]); XCTAssertEqualObjects(passcodeState.status, @"NO"); NSDictionary *dict = [self dictWithEncodedString:passcodeState.info]; [self assertJSONNumber:dict[@"LocalAuthentication"][@"result"] isInteger:0]; @@ -119,20 +79,16 @@ - (void)testLocalAuthenticationHasError { * Verifies caching behavior regarding to calling LocalAuthentication API. */ - (void)testLocalAuthenticationCache { - if (!_isIOS9orAbove) { - return; - } - GIDMDMPasscodeState *oldPasscodeState = [GIDMDMPasscodeState passcodeState]; - _canEvaluatePolicyCalled = false; - GIDMDMPasscodeState *newPasscodeState = [GIDMDMPasscodeState passcodeState]; - XCTAssertFalse(_canEvaluatePolicyCalled); + GIDMDMPasscodeState *oldPasscodeState = [_cache passcodeState]; + GIDMDMPasscodeState *newPasscodeState = [_cache passcodeState]; + XCTAssertEqual(_context.evaluatedPolicies.count, 1u); XCTAssertEqualObjects(oldPasscodeState.status, newPasscodeState.status); XCTAssertEqualObjects(oldPasscodeState.info, newPasscodeState.info); - // Verify that the cache is cleared after background notification. + // Verify that the cache re-probes LocalAuthentication after the app enters the background. [self postApplicationDidEnterBackgroundNotification]; - [GIDMDMPasscodeState passcodeState]; - XCTAssertTrue(_canEvaluatePolicyCalled); + [_cache passcodeState]; + XCTAssertEqual(_context.evaluatedPolicies.count, 2u); } /** @@ -140,11 +96,18 @@ - (void)testLocalAuthenticationCache { * Keychain API is in C thus there is no easy way to swizzler them. */ - (void)testKeychain { - GIDMDMPasscodeState *passcodeState = [GIDMDMPasscodeState passcodeState]; + GIDMDMPasscodeState *passcodeState = [_cache passcodeState]; NSDictionary *dict = [self dictWithEncodedString:passcodeState.info]; XCTAssertTrue([dict[@"Keychain"][@"result"] isKindOfClass:[NSNumber class]]); } +/** Verifies `+[GIDMDMPasscodeState passcodeState]` returns a state with a status and info. */ +- (void)testPasscodeStateClassMethod { + GIDMDMPasscodeState *passcodeState = [GIDMDMPasscodeState passcodeState]; + XCTAssertNotNil(passcodeState.status); + XCTAssertNotNil([self dictWithEncodedString:passcodeState.info]); +} + #pragma mark - Helpers /** From c7207b1c57706117b46b4cfa0f38faf53b44d24f Mon Sep 17 00:00:00 2001 From: Worthing ~ <115107835+w-goog@users.noreply.github.com> Date: Tue, 20 Oct 2026 13:44:20 -0700 Subject: [PATCH 4/4] g-orchestrated: Follow the Objective-C style guide in the device and LAContext seams --- .../Sources/GIDDevice/Fake/GIDFakeDevice.h | 1 + GoogleSignIn/Sources/GIDEMMSupport.h | 18 ++++++++++-------- GoogleSignIn/Sources/GIDEMMSupport.m | 17 ++++++++++------- .../Fake/GIDFakeLocalAuthenticationContext.m | 1 + .../LAContext+GIDLocalAuthenticationContext.m | 2 +- GoogleSignIn/Sources/GIDMDMPasscodeCache.h | 3 ++- GoogleSignIn/Sources/GIDMDMPasscodeCache.m | 6 ++++-- 7 files changed, 29 insertions(+), 19 deletions(-) diff --git a/GoogleSignIn/Sources/GIDDevice/Fake/GIDFakeDevice.h b/GoogleSignIn/Sources/GIDDevice/Fake/GIDFakeDevice.h index f1752239..38ef5b72 100644 --- a/GoogleSignIn/Sources/GIDDevice/Fake/GIDFakeDevice.h +++ b/GoogleSignIn/Sources/GIDDevice/Fake/GIDFakeDevice.h @@ -37,6 +37,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithSystemName:(NSString *)systemName systemVersion:(NSString *)systemVersion NS_DESIGNATED_INITIALIZER; +/// Unavailable. Use `-initWithSystemName:systemVersion:`. - (instancetype)init NS_UNAVAILABLE; @end diff --git a/GoogleSignIn/Sources/GIDEMMSupport.h b/GoogleSignIn/Sources/GIDEMMSupport.h index f9813ee0..e9c4f602 100644 --- a/GoogleSignIn/Sources/GIDEMMSupport.h +++ b/GoogleSignIn/Sources/GIDEMMSupport.h @@ -42,14 +42,15 @@ NS_ASSUME_NONNULL_BEGIN - (void)handleTokenFetchEMMError:(nullable NSError *)error completion:(void (^)(NSError *_Nullable))completion; -/// Gets a new set of URL parameters that contains updated EMM-related URL parameters if needed. +/// Returns a new set of URL parameters that contains updated EMM-related URL parameters if needed. - (NSDictionary *)updatedEMMParametersWithParameters: - (NSDictionary *)parameters; + (NSDictionary *)parameters; -/// Gets a new set of URL parameters that also contains EMM-related URL parameters if needed. +/// Returns a new set of URL parameters that also contains EMM-related URL parameters if needed. /// /// @note This function only returns parameter keys in the `NSDictionary` for string/number/bools. -- (NSDictionary *)parametersWithParameters:(NSDictionary *)parameters +- (NSDictionary *)parametersWithParameters: + (NSDictionary *)parameters emmSupport:(nullable NSString *)emmSupport isPasscodeInfoRequired:(BOOL)isPasscodeInfoRequired; @@ -58,16 +59,17 @@ NS_ASSUME_NONNULL_BEGIN + (void)handleTokenFetchEMMError:(nullable NSError *)error completion:(void (^)(NSError *_Nullable))completion; -/// Gets a new set of URL parameters that contains updated EMM-related URL parameters if needed. +/// Returns a new set of URL parameters that contains updated EMM-related URL parameters if needed. /// Equivalent to calling the instance method on `[[GIDEMMSupport alloc] init]`. + (NSDictionary *)updatedEMMParametersWithParameters: - (NSDictionary *)parameters; + (NSDictionary *)parameters; -/// Gets a new set of URL parameters that also contains EMM-related URL parameters if needed. +/// Returns a new set of URL parameters that also contains EMM-related URL parameters if needed. /// Equivalent to calling the instance method on `[[GIDEMMSupport alloc] init]`. /// /// @note This function only returns parameter keys in the `NSDictionary` for string/number/bools. -+ (NSDictionary *)parametersWithParameters:(NSDictionary *)parameters ++ (NSDictionary *)parametersWithParameters: + (NSDictionary *)parameters emmSupport:(nullable NSString *)emmSupport isPasscodeInfoRequired:(BOOL)isPasscodeInfoRequired; diff --git a/GoogleSignIn/Sources/GIDEMMSupport.m b/GoogleSignIn/Sources/GIDEMMSupport.m index e03990fa..2f8f3ef3 100644 --- a/GoogleSignIn/Sources/GIDEMMSupport.m +++ b/GoogleSignIn/Sources/GIDEMMSupport.m @@ -60,7 +60,9 @@ typedef NS_ENUM(NSInteger, ErrorCode) { }; @implementation GIDEMMSupport { + // Reports EMM errors from token fetch responses. GIDEMMErrorHandler *_errorHandler; + // Describes the device to the EMM backend. id _device; } @@ -99,13 +101,14 @@ - (void)handleTokenFetchEMMError:(nullable NSError *)error } - (NSDictionary *)updatedEMMParametersWithParameters: - (NSDictionary *)parameters { + (NSDictionary *)parameters { return [self parametersWithParameters:parameters emmSupport:parameters[kEMMSupportParameterName] isPasscodeInfoRequired:parameters[kEMMPasscodeInfoParameterName] != nil]; } -- (NSDictionary *)parametersWithParameters:(NSDictionary *)parameters +- (NSDictionary *)parametersWithParameters: + (NSDictionary *)parameters emmSupport:(nullable NSString *)emmSupport isPasscodeInfoRequired:(BOOL)isPasscodeInfoRequired { if (!emmSupport) { @@ -113,13 +116,12 @@ - (void)handleTokenFetchEMMError:(nullable NSError *)error } NSMutableDictionary *allParameters = [(parameters ?: @{}) mutableCopy]; allParameters[kEMMSupportParameterName] = emmSupport; - id device = _device; - NSString *systemName = device.systemName; + NSString *systemName = _device.systemName; if ([systemName isEqualToString:kOldIOSSystemName]) { systemName = kNewIOSSystemName; } allParameters[kEMMOSVersionParameterName] = - [NSString stringWithFormat:@"%@ %@", systemName, device.systemVersion]; + [NSString stringWithFormat:@"%@ %@", systemName, _device.systemVersion]; if (isPasscodeInfoRequired) { allParameters[kEMMPasscodeInfoParameterName] = [GIDMDMPasscodeState passcodeState].info; } @@ -132,11 +134,12 @@ + (void)handleTokenFetchEMMError:(nullable NSError *)error } + (NSDictionary *)updatedEMMParametersWithParameters: - (NSDictionary *)parameters { + (NSDictionary *)parameters { return [[[self alloc] init] updatedEMMParametersWithParameters:parameters]; } -+ (NSDictionary *)parametersWithParameters:(NSDictionary *)parameters ++ (NSDictionary *)parametersWithParameters: + (NSDictionary *)parameters emmSupport:(nullable NSString *)emmSupport isPasscodeInfoRequired:(BOOL)isPasscodeInfoRequired { return [[[self alloc] init] parametersWithParameters:parameters diff --git a/GoogleSignIn/Sources/GIDLocalAuthentication/Fake/GIDFakeLocalAuthenticationContext.m b/GoogleSignIn/Sources/GIDLocalAuthentication/Fake/GIDFakeLocalAuthenticationContext.m index 7c0d334b..e3aa0f5e 100644 --- a/GoogleSignIn/Sources/GIDLocalAuthentication/Fake/GIDFakeLocalAuthenticationContext.m +++ b/GoogleSignIn/Sources/GIDLocalAuthentication/Fake/GIDFakeLocalAuthenticationContext.m @@ -23,6 +23,7 @@ NS_ASSUME_NONNULL_BEGIN @implementation GIDFakeLocalAuthenticationContext { + // Every policy passed to `-canEvaluatePolicy:error:`, in call order. NSMutableArray *_evaluatedPolicies; } diff --git a/GoogleSignIn/Sources/GIDLocalAuthentication/Implementation/LAContext+GIDLocalAuthenticationContext.m b/GoogleSignIn/Sources/GIDLocalAuthentication/Implementation/LAContext+GIDLocalAuthenticationContext.m index 5032809e..de2ccd75 100644 --- a/GoogleSignIn/Sources/GIDLocalAuthentication/Implementation/LAContext+GIDLocalAuthenticationContext.m +++ b/GoogleSignIn/Sources/GIDLocalAuthentication/Implementation/LAContext+GIDLocalAuthenticationContext.m @@ -18,7 +18,7 @@ #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST -#import "GoogleSignIn/Sources/GIDLocalAuthentication/Implementation/LAContext+GIDLocalAuthenticationContext.h" +#import "GoogleSignIn/Sources/GIDLocalAuthentication/Implementation/LAContext+GIDLocalAuthenticationContext.h" // NOLINT NS_ASSUME_NONNULL_BEGIN diff --git a/GoogleSignIn/Sources/GIDMDMPasscodeCache.h b/GoogleSignIn/Sources/GIDMDMPasscodeCache.h index bc762ca1..4dcb1054 100644 --- a/GoogleSignIn/Sources/GIDMDMPasscodeCache.h +++ b/GoogleSignIn/Sources/GIDMDMPasscodeCache.h @@ -41,7 +41,8 @@ NS_ASSUME_NONNULL_BEGIN * when LocalAuthentication is unavailable; the cache then reports only the Keychain probe. */ - (instancetype)initWithLocalAuthenticationContext: - (nullable id)localAuthenticationContext NS_DESIGNATED_INITIALIZER; + (nullable id)localAuthenticationContext + NS_DESIGNATED_INITIALIZER; /** Unavailable; use `+sharedInstance` or the designated initializer. */ - (instancetype)init NS_UNAVAILABLE; diff --git a/GoogleSignIn/Sources/GIDMDMPasscodeCache.m b/GoogleSignIn/Sources/GIDMDMPasscodeCache.m index bcb44d07..97f083d5 100644 --- a/GoogleSignIn/Sources/GIDMDMPasscodeCache.m +++ b/GoogleSignIn/Sources/GIDMDMPasscodeCache.m @@ -22,7 +22,7 @@ #import #import -#import "GoogleSignIn/Sources/GIDLocalAuthentication/Implementation/LAContext+GIDLocalAuthenticationContext.h" +#import "GoogleSignIn/Sources/GIDLocalAuthentication/Implementation/LAContext+GIDLocalAuthenticationContext.h" // NOLINT #import "GoogleSignIn/Sources/GIDMDMPasscodeState.h" #import "GoogleSignIn/Sources/GIDMDMPasscodeState_Private.h" @@ -56,6 +56,7 @@ static const int64_t kObtainKeychainInfoWaitTime = 3 * NSEC_PER_SEC; @implementation GIDMDMPasscodeCache { + /** Probes the passcode state; nil when LocalAuthentication is unavailable. */ id _localAuthenticationContext; /** Whether or not LocalAuthentication API is available. */ @@ -107,7 +108,8 @@ + (instancetype)sharedInstance { @try { context = [[LAContext alloc] init]; } @catch (NSException *) { - // In theory there should be no exceptions but in practice there may be: b/23200390, b/23218643. + // In theory there should be no exceptions but in practice there may be: + // b/23200390, b/23218643. context = nil; } sharedInstance = [[GIDMDMPasscodeCache alloc] initWithLocalAuthenticationContext:context];