Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
47 changes: 47 additions & 0 deletions GoogleSignIn/Sources/GIDDevice/Fake/GIDFakeDevice.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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>

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

NS_ASSUME_NONNULL_BEGIN

/// An in-memory `GIDDevice` for tests.
@interface GIDFakeDevice : NSObject <GIDDevice>

/// 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;

/// Unavailable. Use `-initWithSystemName:systemVersion:`.
- (instancetype)init NS_UNAVAILABLE;

@end

NS_ASSUME_NONNULL_END

#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
44 changes: 44 additions & 0 deletions GoogleSignIn/Sources/GIDDevice/Fake/GIDFakeDevice.m
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 <TargetConditionals.h>

#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
34 changes: 34 additions & 0 deletions GoogleSignIn/Sources/GIDDevice/Implementation/UIDevice+GIDDevice.h
Original file line number Diff line number Diff line change
@@ -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 <TargetConditionals.h>

#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST

#import <UIKit/UIKit.h>

#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) <GIDDevice>
@end

NS_ASSUME_NONNULL_END

#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
30 changes: 30 additions & 0 deletions GoogleSignIn/Sources/GIDDevice/Implementation/UIDevice+GIDDevice.m
Original file line number Diff line number Diff line change
@@ -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 <TargetConditionals.h>

#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
39 changes: 34 additions & 5 deletions GoogleSignIn/Sources/GIDEMMSupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,54 @@

@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<GTMAuthSessionDelegate>

- (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<GIDDevice>)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;

/// Returns a new set of URL parameters that contains updated EMM-related URL parameters if needed.
- (NSDictionary<NSString *,NSString *> *)updatedEMMParametersWithParameters:
(NSDictionary<NSString *, id> *)parameters;

/// 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<NSString *,NSString *> *)parametersWithParameters:
(NSDictionary<NSString *, id> *)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.
/// 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<NSString *,NSString *> *)updatedEMMParametersWithParameters:
(NSDictionary *)parameters;
(NSDictionary<NSString *, id> *)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<NSString *,NSString *> *)parametersWithParameters:(NSDictionary *)parameters
+ (NSDictionary<NSString *,NSString *> *)parametersWithParameters:
(NSDictionary<NSString *, id> *)parameters
emmSupport:(nullable NSString *)emmSupport
isPasscodeInfoRequired:(BOOL)isPasscodeInfoRequired;

Expand Down
62 changes: 49 additions & 13 deletions GoogleSignIn/Sources/GIDEMMSupport.m
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -58,18 +59,34 @@ typedef NS_ENUM(NSInteger, ErrorCode) {
ErrorCodeAppVerificationRequired,
};

@implementation GIDEMMSupport
@implementation GIDEMMSupport {
// Reports EMM errors from token fetch responses.
GIDEMMErrorHandler *_errorHandler;
// Describes the device to the EMM backend.
id<GIDDevice> _device;
}

- (instancetype)initWithErrorHandler:(GIDEMMErrorHandler *)errorHandler
device:(id<GIDDevice>)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
Expand All @@ -83,46 +100,65 @@ + (void)handleTokenFetchEMMError:(nullable NSError *)error
}
}

+ (NSDictionary<NSString *,NSString *> *)updatedEMMParametersWithParameters:
(NSDictionary *)parameters {
- (NSDictionary<NSString *,NSString *> *)updatedEMMParametersWithParameters:
(NSDictionary<NSString *, id> *)parameters {
return [self parametersWithParameters:parameters
emmSupport:parameters[kEMMSupportParameterName]
isPasscodeInfoRequired:parameters[kEMMPasscodeInfoParameterName] != nil];
}

+ (NSDictionary<NSString *,NSString *> *)parametersWithParameters:(NSDictionary *)parameters
- (NSDictionary<NSString *,NSString *> *)parametersWithParameters:
(NSDictionary<NSString *, id> *)parameters
emmSupport:(nullable NSString *)emmSupport
isPasscodeInfoRequired:(BOOL)isPasscodeInfoRequired {
if (!emmSupport) {
return [GIDEMMSupport dictionaryWithStringValuesFromDictionary:parameters];
}
NSMutableDictionary *allParameters = [(parameters ?: @{}) mutableCopy];
allParameters[kEMMSupportParameterName] = emmSupport;
UIDevice *device = [UIDevice currentDevice];
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;
}
return [GIDEMMSupport dictionaryWithStringValuesFromDictionary:allParameters];
}

+ (void)handleTokenFetchEMMError:(nullable NSError *)error
completion:(void (^)(NSError *_Nullable))completion {
[[[self alloc] init] handleTokenFetchEMMError:error completion:completion];
}

+ (NSDictionary<NSString *,NSString *> *)updatedEMMParametersWithParameters:
(NSDictionary<NSString *, id> *)parameters {
return [[[self alloc] init] updatedEMMParametersWithParameters:parameters];
}

+ (NSDictionary<NSString *,NSString *> *)parametersWithParameters:
(NSDictionary<NSString *, id> *)parameters
emmSupport:(nullable NSString *)emmSupport
isPasscodeInfoRequired:(BOOL)isPasscodeInfoRequired {
return [[[self alloc] init] parametersWithParameters:parameters
emmSupport:emmSupport
isPasscodeInfoRequired:isPasscodeInfoRequired];
}

#pragma mark - GTMAuthSessionDelegate

- (nullable NSDictionary<NSString *,NSString *> *)
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);
}];
}
Expand Down
Loading
Loading