Explorar el Código

Move the fetcher authorizer into GIDGoogleUser API (#230)

pinlu hace 3 años
padre
commit
64b35f8bad

+ 36 - 0
GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h

@@ -0,0 +1,36 @@
+/*
+ * Copyright 2022 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
+
+#ifdef SWIFT_PACKAGE
+@import GTMAppAuth;
+#else
+#import <GTMAppAuth/GTMAppAuth.h>
+#endif
+
+NS_ASSUME_NONNULL_BEGIN
+
+// A specialized GTMAppAuthFetcherAuthorization subclass with EMM support.
+@interface GIDAppAuthFetcherAuthorizationWithEMMSupport : GTMAppAuthFetcherAuthorization
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST

+ 129 - 0
GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m

@@ -0,0 +1,129 @@
+/*
+ * Copyright 2022 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/GIDAppAuthFetcherAuthorizationWithEMMSupport.h"
+
+#import "GoogleSignIn/Sources/GIDEMMSupport.h"
+
+#ifdef SWIFT_PACKAGE
+@import AppAuth;
+@import GTMAppAuth;
+#else
+#import <AppAuth/AppAuth.h>
+#import <GTMAppAuth/GTMAppAuth.h>
+#endif
+
+NS_ASSUME_NONNULL_BEGIN
+
+// The specialized GTMAppAuthFetcherAuthorization delegate that handles potential EMM error
+// responses.
+@interface GIDAppAuthFetcherAuthorizationEMMChainedDelegate : NSObject
+
+// Initializes with chained delegate and selector.
+- (instancetype)initWithDelegate:(id)delegate selector:(SEL)selector;
+
+// The callback method for GTMAppAuthFetcherAuthorization to invoke.
+- (void)authentication:(GTMAppAuthFetcherAuthorization *)auth
+               request:(NSMutableURLRequest *)request
+     finishedWithError:(nullable NSError *)error;
+
+@end
+
+@implementation GIDAppAuthFetcherAuthorizationEMMChainedDelegate {
+  // We use a weak reference here to match GTMAppAuthFetcherAuthorization.
+  __weak id _delegate;
+  SEL _selector;
+  // We need to maintain a reference to the chained delegate because GTMAppAuthFetcherAuthorization
+  // only keeps a weak reference.
+  GIDAppAuthFetcherAuthorizationEMMChainedDelegate *_retained_self;
+}
+
+- (instancetype)initWithDelegate:(id)delegate selector:(SEL)selector {
+  self = [super init];
+  if (self) {
+    _delegate = delegate;
+    _selector = selector;
+    _retained_self = self;
+  }
+  return self;
+}
+
+- (void)authentication:(GTMAppAuthFetcherAuthorization *)auth
+               request:(NSMutableURLRequest *)request
+     finishedWithError:(nullable NSError *)error {
+  [GIDEMMSupport handleTokenFetchEMMError:error completion:^(NSError *_Nullable error) {
+    if (!self->_delegate || !self->_selector) {
+      return;
+    }
+    NSMethodSignature *signature = [self->_delegate methodSignatureForSelector:self->_selector];
+    if (!signature) {
+      return;
+    }
+    id argument1 = auth;
+    id argument2 = request;
+    id argument3 = error;
+    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
+    [invocation setTarget:self->_delegate];  // index 0
+    [invocation setSelector:self->_selector];  // index 1
+    [invocation setArgument:&argument1 atIndex:2];
+    [invocation setArgument:&argument2 atIndex:3];
+    [invocation setArgument:&argument3 atIndex:4];
+    [invocation invoke];
+  }];
+  // Prepare to deallocate the chained delegate instance because the above block will retain the
+  // iVar references it uses.
+  _retained_self = nil;
+}
+
+@end
+
+@implementation GIDAppAuthFetcherAuthorizationWithEMMSupport
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-implementations"
+- (void)authorizeRequest:(nullable NSMutableURLRequest *)request
+                delegate:(id)delegate
+       didFinishSelector:(SEL)sel {
+#pragma clang diagnostic pop
+  GIDAppAuthFetcherAuthorizationEMMChainedDelegate *chainedDelegate =
+      [[GIDAppAuthFetcherAuthorizationEMMChainedDelegate alloc] initWithDelegate:delegate
+                                                                        selector:sel];
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+  [super authorizeRequest:request
+                 delegate:chainedDelegate
+        didFinishSelector:@selector(authentication:request:finishedWithError:)];
+#pragma clang diagnostic pop
+}
+
+- (void)authorizeRequest:(nullable NSMutableURLRequest *)request
+       completionHandler:(GTMAppAuthFetcherAuthorizationCompletion)handler {
+  [super authorizeRequest:request completionHandler:^(NSError *_Nullable error) {
+    [GIDEMMSupport handleTokenFetchEMMError:error completion:^(NSError *_Nullable error) {
+      handler(error);
+    }];
+  }];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST

+ 0 - 137
GoogleSignIn/Sources/GIDAuthentication.m

@@ -57,106 +57,6 @@ static NSString *const kOldIOSSystemName = @"iPhone OS";
 // New UIDevice system name for iOS.
 static NSString *const kNewIOSSystemName = @"iOS";
 
-#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
-
-// The specialized GTMAppAuthFetcherAuthorization delegate that handles potential EMM error
-// responses.
-@interface GTMAppAuthFetcherAuthorizationEMMChainedDelegate : NSObject
-
-// Initializes with chained delegate and selector.
-- (instancetype)initWithDelegate:(id)delegate selector:(SEL)selector;
-
-// The callback method for GTMAppAuthFetcherAuthorization to invoke.
-- (void)authentication:(GTMAppAuthFetcherAuthorization *)auth
-               request:(NSMutableURLRequest *)request
-     finishedWithError:(nullable NSError *)error;
-
-@end
-
-@implementation GTMAppAuthFetcherAuthorizationEMMChainedDelegate {
-  // We use a weak reference here to match GTMAppAuthFetcherAuthorization.
-  __weak id _delegate;
-  SEL _selector;
-  // We need to maintain a reference to the chained delegate because GTMAppAuthFetcherAuthorization
-  // only keeps a weak reference.
-  GTMAppAuthFetcherAuthorizationEMMChainedDelegate *_retained_self;
-}
-
-- (instancetype)initWithDelegate:(id)delegate selector:(SEL)selector {
-  self = [super init];
-  if (self) {
-    _delegate = delegate;
-    _selector = selector;
-    _retained_self = self;
-  }
-  return self;
-}
-
-- (void)authentication:(GTMAppAuthFetcherAuthorization *)auth
-               request:(NSMutableURLRequest *)request
-     finishedWithError:(nullable NSError *)error {
-  [GIDAuthentication handleTokenFetchEMMError:error completion:^(NSError *_Nullable error) {
-    if (!self->_delegate || !self->_selector) {
-      return;
-    }
-    NSMethodSignature *signature = [self->_delegate methodSignatureForSelector:self->_selector];
-    if (!signature) {
-      return;
-    }
-    id argument1 = auth;
-    id argument2 = request;
-    id argument3 = error;
-    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
-    [invocation setTarget:self->_delegate];  // index 0
-    [invocation setSelector:self->_selector];  // index 1
-    [invocation setArgument:&argument1 atIndex:2];
-    [invocation setArgument:&argument2 atIndex:3];
-    [invocation setArgument:&argument3 atIndex:4];
-    [invocation invoke];
-  }];
-  // Prepare to deallocate the chained delegate instance because the above block will retain the
-  // iVar references it uses.
-  _retained_self = nil;
-}
-
-@end
-
-// A specialized GTMAppAuthFetcherAuthorization subclass with EMM support.
-@interface GTMAppAuthFetcherAuthorizationWithEMMSupport : GTMAppAuthFetcherAuthorization
-@end
-
-@implementation GTMAppAuthFetcherAuthorizationWithEMMSupport
-
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-implementations"
-- (void)authorizeRequest:(nullable NSMutableURLRequest *)request
-                delegate:(id)delegate
-       didFinishSelector:(SEL)sel {
-#pragma clang diagnostic pop
-  GTMAppAuthFetcherAuthorizationEMMChainedDelegate *chainedDelegate =
-      [[GTMAppAuthFetcherAuthorizationEMMChainedDelegate alloc] initWithDelegate:delegate
-                                                                        selector:sel];
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-  [super authorizeRequest:request
-                 delegate:chainedDelegate
-        didFinishSelector:@selector(authentication:request:finishedWithError:)];
-#pragma clang diagnostic pop
-}
-
-- (void)authorizeRequest:(nullable NSMutableURLRequest *)request
-       completionHandler:(GTMAppAuthFetcherAuthorizationCompletion)handler {
-  [super authorizeRequest:request completionHandler:^(NSError *_Nullable error) {
-    [GIDAuthentication handleTokenFetchEMMError:error completion:^(NSError *_Nullable error) {
-      handler(error);
-    }];
-  }];
-}
-
-@end
-
-#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
-
 @implementation GIDAuthentication {
   // A queue for pending authentication handlers so we don't fire multiple requests in parallel.
   // Access to this ivar should be synchronized.
@@ -201,33 +101,8 @@ static NSString *const kNewIOSSystemName = @"iOS";
   return [[[OIDIDToken alloc] initWithIDTokenString:self.idToken] expiresAt];
 }
 
-#pragma mark - Private property accessors
-
-#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
-- (NSString *)emmSupport {
-  return
-      _authState.lastAuthorizationResponse.request.additionalParameters[kEMMSupportParameterName];
-}
-#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
-
 #pragma mark - Public methods
 
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-- (id<GTMFetcherAuthorizationProtocol>)fetcherAuthorizer {
-#pragma clang diagnostic pop
-#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
-  GTMAppAuthFetcherAuthorization *authorization = self.emmSupport ?
-      [[GTMAppAuthFetcherAuthorizationWithEMMSupport alloc] initWithAuthState:_authState] :
-      [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:_authState];
-#elif TARGET_OS_OSX || TARGET_OS_MACCATALYST
-  GTMAppAuthFetcherAuthorization *authorization =
-      [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:_authState];
-#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
-  authorization.tokenRefreshDelegate = self;
-  return authorization;
-}
-
 - (void)doWithFreshTokens:(GIDAuthenticationCompletion)completion {
   if (!([self.accessTokenExpirationDate timeIntervalSinceNow] < kMinimalTimeToExpire ||
       (self.idToken && [self.idTokenExpirationDate timeIntervalSinceNow] < kMinimalTimeToExpire))) {
@@ -360,18 +235,6 @@ static NSString *const kNewIOSSystemName = @"iOS";
 
 #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
 
-#pragma mark - GTMAppAuthFetcherAuthorizationTokenRefreshDelegate
-
-- (nullable NSDictionary *)additionalRefreshParameters:
-    (GTMAppAuthFetcherAuthorization *)authorization {
-#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
-  return [GIDAuthentication updatedEMMParametersWithParameters:
-      authorization.authState.lastTokenResponse.request.additionalParameters];
-#elif TARGET_OS_OSX || TARGET_OS_MACCATALYST
-  return authorization.authState.lastTokenResponse.request.additionalParameters;
-#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
-}
-
 #pragma mark - NSSecureCoding
 
 + (BOOL)supportsSecureCoding {

+ 1 - 28
GoogleSignIn/Sources/GIDAuthentication_Private.h

@@ -16,43 +16,16 @@
 
 #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDAuthentication.h"
 
-#ifdef SWIFT_PACKAGE
-@import AppAuth;
-@import GTMAppAuth;
-#else
-#import <AppAuth/AppAuth.h>
-#import <GTMAppAuth/GTMAppAuth.h>
-#endif
-
 NS_ASSUME_NONNULL_BEGIN
 
 // Internal methods for the class that are not part of the public API.
-@interface GIDAuthentication () <GTMAppAuthFetcherAuthorizationTokenRefreshDelegate>
+@interface GIDAuthentication ()
 
 // A representation of the state of the OAuth session for this instance.
 @property(nonatomic, readonly) OIDAuthState *authState;
 
-#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
-// A string indicating support for Enterprise Mobility Management.
-@property(nonatomic, readonly) NSString *emmSupport;
-#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
-
 - (instancetype)initWithAuthState:(OIDAuthState *)authState;
 
-#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
-// Gets a new set of URL parameters that also contains EMM-related URL parameters if needed.
-+ (NSDictionary *)parametersWithParameters:(NSDictionary *)parameters
-                                emmSupport:(nullable NSString *)emmSupport
-                    isPasscodeInfoRequired:(BOOL)isPasscodeInfoRequired;
-
-// Gets a new set of URL parameters that contains updated EMM-related URL parameters if needed.
-+ (NSDictionary *)updatedEMMParametersWithParameters:(NSDictionary *)parameters;
-
-// Handles potential EMM error from token fetch response.
-+ (void)handleTokenFetchEMMError:(nullable NSError *)error
-                      completion:(void (^)(NSError *_Nullable))completion;
-#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
-
 @end
 
 NS_ASSUME_NONNULL_END

+ 44 - 0
GoogleSignIn/Sources/GIDEMMSupport.h

@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 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
+
+// A class to support EMM (Enterprise Mobility Management).
+@interface GIDEMMSupport : NSObject
+
+// 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.
++ (NSDictionary *)parametersWithParameters:(NSDictionary *)parameters
+                                emmSupport:(nullable NSString *)emmSupport
+                    isPasscodeInfoRequired:(BOOL)isPasscodeInfoRequired;
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST

+ 101 - 0
GoogleSignIn/Sources/GIDEMMSupport.m

@@ -0,0 +1,101 @@
+/*
+ * Copyright 2022 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/GIDEMMSupport.h"
+
+#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h"
+
+#import "GoogleSignIn/Sources/GIDEMMErrorHandler.h"
+#import "GoogleSignIn/Sources/GIDMDMPasscodeState.h"
+
+#ifdef SWIFT_PACKAGE
+@import AppAuth;
+#else
+#import <AppAuth/AppAuth.h>
+#endif
+
+NS_ASSUME_NONNULL_BEGIN
+
+// Additional parameter names for EMM.
+static NSString *const kEMMSupportParameterName = @"emm_support";
+static NSString *const kEMMOSVersionParameterName = @"device_os";
+static NSString *const kEMMPasscodeInfoParameterName = @"emm_passcode_info";
+
+// Old UIDevice system name for iOS.
+static NSString *const kOldIOSSystemName = @"iPhone OS";
+
+// New UIDevice system name for iOS.
+static NSString *const kNewIOSSystemName = @"iOS";
+
+@implementation GIDEMMSupport
+
++ (void)handleTokenFetchEMMError:(nullable NSError *)error
+                      completion:(void (^)(NSError *_Nullable))completion {
+  NSDictionary *errorJSON = error.userInfo[OIDOAuthErrorResponseErrorKey];
+  if (errorJSON) {
+    __block BOOL handled = NO;
+    handled = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:errorJSON
+                                                                completion:^() {
+      if (handled) {
+        completion([NSError errorWithDomain:kGIDSignInErrorDomain
+                                       code:kGIDSignInErrorCodeEMM
+                                   userInfo:error.userInfo]);
+      } else {
+        completion(error);
+      }
+    }];
+  } else {
+    completion(error);
+  }
+}
+
++ (NSDictionary *)updatedEMMParametersWithParameters:(NSDictionary *)parameters {
+  return [self parametersWithParameters:parameters
+                             emmSupport:parameters[kEMMSupportParameterName]
+                 isPasscodeInfoRequired:parameters[kEMMPasscodeInfoParameterName] != nil];
+}
+
+
++ (NSDictionary *)parametersWithParameters:(NSDictionary *)parameters
+                                emmSupport:(nullable NSString *)emmSupport
+                    isPasscodeInfoRequired:(BOOL)isPasscodeInfoRequired {
+  if (!emmSupport) {
+    return parameters;
+  }
+  NSMutableDictionary *allParameters = [(parameters ?: @{}) mutableCopy];
+  allParameters[kEMMSupportParameterName] = emmSupport;
+  UIDevice *device = [UIDevice currentDevice];
+  NSString *systemName = device.systemName;
+  if ([systemName isEqualToString:kOldIOSSystemName]) {
+    systemName = kNewIOSSystemName;
+  }
+  allParameters[kEMMOSVersionParameterName] =
+      [NSString stringWithFormat:@"%@ %@", systemName, device.systemVersion];
+  if (isPasscodeInfoRequired) {
+    allParameters[kEMMPasscodeInfoParameterName] = [GIDMDMPasscodeState passcodeState].info;
+  }
+  return allParameters;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST

+ 36 - 11
GoogleSignIn/Sources/GIDGoogleUser.m

@@ -18,7 +18,9 @@
 
 #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDConfiguration.h"
 
+#import "GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h"
 #import "GoogleSignIn/Sources/GIDAuthentication_Private.h"
+#import "GoogleSignIn/Sources/GIDEMMSupport.h"
 #import "GoogleSignIn/Sources/GIDProfileData_Private.h"
 #import "GoogleSignIn/Sources/GIDToken_Private.h"
 
@@ -28,6 +30,8 @@
 #import <AppAuth/AppAuth.h>
 #endif
 
+NS_ASSUME_NONNULL_BEGIN
+
 // The ID Token claim key for the hosted domain value.
 static NSString *const kHostedDomainIDTokenClaimKey = @"hd";
 
@@ -40,17 +44,8 @@ static NSString *const kAuthState = @"authState";
 static NSString *const kAudienceParameter = @"audience";
 static NSString *const kOpenIDRealmParameter = @"openid.realm";
 
-NS_ASSUME_NONNULL_BEGIN
-
-@interface GIDGoogleUser ()
-
-@property(nonatomic, readwrite) GIDToken *accessToken;
-
-@property(nonatomic, readwrite) GIDToken *refreshToken;
-
-@property(nonatomic, readwrite, nullable) GIDToken *idToken;
-
-@end
+// Additional parameter names for EMM.
+static NSString *const kEMMSupportParameterName = @"emm_support";
 
 @implementation GIDGoogleUser {
   OIDAuthState *_authState;
@@ -108,6 +103,13 @@ NS_ASSUME_NONNULL_BEGIN
 
 #pragma mark - Private Methods
 
+#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
+- (nullable NSString *)emmSupport {
+  return
+      _authState.lastAuthorizationResponse.request.additionalParameters[kEMMSupportParameterName];
+}
+#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
+
 - (instancetype)initWithAuthState:(OIDAuthState *)authState
                       profileData:(nullable GIDProfileData *)profileData {
   self = [super init];
@@ -124,6 +126,17 @@ NS_ASSUME_NONNULL_BEGIN
     _authentication = [[GIDAuthentication alloc] initWithAuthState:authState];
     _profile = profileData;
     
+#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
+    GTMAppAuthFetcherAuthorization *authorization = self.emmSupport ?
+        [[GIDAppAuthFetcherAuthorizationWithEMMSupport alloc] initWithAuthState:_authState] :
+        [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:_authState];
+#elif TARGET_OS_OSX || TARGET_OS_MACCATALYST
+    GTMAppAuthFetcherAuthorization *authorization =
+        [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:_authState];
+#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
+    authorization.tokenRefreshDelegate = self;
+    self.fetcherAuthorizer = authorization;
+    
     [self updateTokensWithAuthState:authState];
   }
 }
@@ -170,6 +183,18 @@ NS_ASSUME_NONNULL_BEGIN
   return nil;
 }
 
+#pragma mark - GTMAppAuthFetcherAuthorizationTokenRefreshDelegate
+
+- (nullable NSDictionary *)additionalRefreshParameters:
+    (GTMAppAuthFetcherAuthorization *)authorization {
+#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
+  return [GIDEMMSupport updatedEMMParametersWithParameters:
+      authorization.authState.lastTokenResponse.request.additionalParameters];
+#elif TARGET_OS_OSX || TARGET_OS_MACCATALYST
+  return authorization.authState.lastTokenResponse.request.additionalParameters;
+#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
+}
+
 #pragma mark - NSSecureCoding
 
 + (BOOL)supportsSecureCoding {

+ 25 - 1
GoogleSignIn/Sources/GIDGoogleUser_Private.h

@@ -16,12 +16,36 @@
 
 #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h"
 
+#ifdef SWIFT_PACKAGE
+@import AppAuth;
+@import GTMAppAuth;
+#else
+#import <AppAuth/AppAuth.h>
+#import <GTMAppAuth/GTMAppAuth.h>
+#endif
+
 NS_ASSUME_NONNULL_BEGIN
 
 @class OIDAuthState;
 
 // Internal methods for the class that are not part of the public API.
-@interface GIDGoogleUser ()
+@interface GIDGoogleUser () <GTMAppAuthFetcherAuthorizationTokenRefreshDelegate>
+
+@property(nonatomic, readwrite) GIDToken *accessToken;
+
+@property(nonatomic, readwrite) GIDToken *refreshToken;
+
+@property(nonatomic, readwrite, nullable) GIDToken *idToken;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+@property(nonatomic, readwrite) id<GTMFetcherAuthorizationProtocol> fetcherAuthorizer;
+#pragma clang diagnostic pop
+
+#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
+// A string indicating support for Enterprise Mobility Management.
+@property(nonatomic, readonly, nullable) NSString *emmSupport;
+#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
 
 // Create a object with an auth state, scopes, and profile data.
 - (instancetype)initWithAuthState:(OIDAuthState *)authState

+ 8 - 7
GoogleSignIn/Sources/GIDSignIn.m

@@ -22,6 +22,7 @@
 #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDProfileData.h"
 #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDUserAuth.h"
 
+#import "GoogleSignIn/Sources/GIDEMMSupport.h"
 #import "GoogleSignIn/Sources/GIDSignInInternalOptions.h"
 #import "GoogleSignIn/Sources/GIDSignInPreferences.h"
 #import "GoogleSignIn/Sources/GIDCallbackQueue.h"
@@ -583,9 +584,9 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
 
 #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
   [additionalParameters addEntriesFromDictionary:
-      [GIDAuthentication parametersWithParameters:options.extraParams
-                                       emmSupport:emmSupport
-                           isPasscodeInfoRequired:NO]];
+      [GIDEMMSupport parametersWithParameters:options.extraParams
+                                   emmSupport:emmSupport
+                       isPasscodeInfoRequired:NO]];
 #elif TARGET_OS_OSX || TARGET_OS_MACCATALYST
   [additionalParameters addEntriesFromDictionary:options.extraParams];
 #endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
@@ -732,9 +733,9 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
       authState.lastAuthorizationResponse.additionalParameters;
   NSString *passcodeInfoRequired = (NSString *)params[kEMMPasscodeInfoRequiredKeyName];
   [additionalParameters addEntriesFromDictionary:
-      [GIDAuthentication parametersWithParameters:@{}
-                                       emmSupport:authFlow.emmSupport
-                           isPasscodeInfoRequired:passcodeInfoRequired.length > 0]];
+      [GIDEMMSupport parametersWithParameters:@{}
+                                   emmSupport:authFlow.emmSupport
+                       isPasscodeInfoRequired:passcodeInfoRequired.length > 0]];
 #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
   additionalParameters[kSDKVersionLoggingParameter] = GIDVersion();
   additionalParameters[kEnvironmentLoggingParameter] = GIDEnvironment();
@@ -760,7 +761,7 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
 
 #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
     if (authFlow.emmSupport) {
-      [GIDAuthentication handleTokenFetchEMMError:error completion:^(NSError *error) {
+      [GIDEMMSupport handleTokenFetchEMMError:error completion:^(NSError *error) {
         authFlow.error = error;
         [authFlow next];
       }];

+ 0 - 8
GoogleSignIn/Sources/Public/GoogleSignIn/GIDAuthentication.h

@@ -56,14 +56,6 @@ typedef void (^GIDAuthenticationCompletion)(GIDAuthentication *_Nullable authent
 /// The estimated expiration date of the ID token.
 @property(nonatomic, readonly, nullable) NSDate *idTokenExpirationDate;
 
-/// Gets a new authorizer for `GTLService`, `GTMSessionFetcher`, or `GTMHTTPFetcher`.
-///
-/// @return A new authorizer
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-- (id<GTMFetcherAuthorizationProtocol>)fetcherAuthorizer;
-#pragma clang diagnostic pop
-
 /// Get a valid access token and a valid ID token, refreshing them first if they have expired or are
 /// about to expire.
 ///

+ 15 - 1
GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h

@@ -16,13 +16,21 @@
 
 #import <Foundation/Foundation.h>
 
-NS_ASSUME_NONNULL_BEGIN
+// We have to import GTMAppAuth because forward declaring the protocol does
+// not generate the `fetcherAuthorizer` property below for Swift.
+#ifdef SWIFT_PACKAGE
+@import GTMAppAuth;
+#else
+#import <GTMAppAuth/GTMAppAuthFetcherAuthorization.h>
+#endif
 
 @class GIDAuthentication;
 @class GIDConfiguration;
 @class GIDToken;
 @class GIDProfileData;
 
+NS_ASSUME_NONNULL_BEGIN
+
 /// This class represents a user account.
 @interface GIDGoogleUser : NSObject <NSSecureCoding>
 
@@ -53,6 +61,12 @@ NS_ASSUME_NONNULL_BEGIN
 /// see https://developers.google.com/identity/sign-in/ios/backend-auth.
 @property(nonatomic, readonly, nullable) GIDToken *idToken;
 
+/// The authorizer for `GTLService`, `GTMSessionFetcher`, or `GTMHTTPFetcher`.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+@property(nonatomic, readonly) id<GTMFetcherAuthorizationProtocol> fetcherAuthorizer;
+#pragma clang diagnostic pop
+
 @end
 
 NS_ASSUME_NONNULL_END

+ 0 - 13
GoogleSignIn/Tests/Unit/GIDAuthenticationTest.m

@@ -240,19 +240,6 @@ _Static_assert(kChangeTypeEnd == (sizeof(kObservedProperties) / sizeof(*kObserve
 }
 #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
 
-- (void)testFetcherAuthorizer {
-  // This is really hard to test without assuming how GTMAppAuthFetcherAuthorization works
-  // internally, so let's just take the shortcut here by asserting we get a
-  // GTMAppAuthFetcherAuthorization object.
-  GIDAuthentication *auth = [self auth];
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-  id<GTMFetcherAuthorizationProtocol> fetcherAuthroizer = auth.fetcherAuthorizer;
-#pragma clang diagnostic pop
-  XCTAssertTrue([fetcherAuthroizer isKindOfClass:[GTMAppAuthFetcherAuthorization class]]);
-  XCTAssertTrue([fetcherAuthroizer canAuthorize]);
-}
-
 - (void)testDoWithFreshTokensWithBothExpired {
   // Both tokens expired 10 seconds ago.
   [self setExpireTimeForAccessToken:-10 IDToken:-10];

+ 27 - 0
GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m

@@ -149,6 +149,33 @@ static NSTimeInterval const kNewIDTokenExpiresIn = 200;
   XCTAssertIdentical(user.refreshToken, refreshTokenBeforeUpdate);
 }
 
+- (void)testFetcherAuthorizer {
+  // This is really hard to test without assuming how GTMAppAuthFetcherAuthorization works
+  // internally, so let's just take the shortcut here by asserting we get a
+  // GTMAppAuthFetcherAuthorization object.
+  GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:kAccessTokenExpiresIn
+                                                idTokenExpiresIn:kIDTokenExpiresIn];
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+  id<GTMFetcherAuthorizationProtocol> fetcherAuthorizer = user.fetcherAuthorizer;
+#pragma clang diagnostic pop
+  XCTAssertTrue([fetcherAuthorizer isKindOfClass:[GTMAppAuthFetcherAuthorization class]]);
+  XCTAssertTrue([fetcherAuthorizer canAuthorize]);
+}
+
+- (void)testFetcherAuthorizer_returnTheSameInstance {
+  GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:kAccessTokenExpiresIn
+                                                idTokenExpiresIn:kIDTokenExpiresIn];
+  
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+  id<GTMFetcherAuthorizationProtocol> fetcherAuthorizer = user.fetcherAuthorizer;
+  id<GTMFetcherAuthorizationProtocol> fetcherAuthorizer2 = user.fetcherAuthorizer;
+#pragma clang diagnostic pop
+
+  XCTAssertIdentical(fetcherAuthorizer, fetcherAuthorizer2);
+}
+
 #pragma mark - Helpers
 
 - (GIDGoogleUser *)googleUserWithAccessTokenExpiresIn:(NSTimeInterval)accessTokenExpiresIn

+ 6 - 4
GoogleSignIn/Tests/Unit/GIDSignInTest.m

@@ -26,6 +26,7 @@
 // Test module imports
 @import GoogleSignIn;
 
+#import "GoogleSignIn/Sources/GIDEMMSupport.h"
 #import "GoogleSignIn/Sources/GIDGoogleUser_Private.h"
 #import "GoogleSignIn/Sources/GIDSignIn_Private.h"
 #import "GoogleSignIn/Sources/GIDSignInPreferences.h"
@@ -396,6 +397,7 @@ static void *kTestObserverContext = &kTestObserverContext;
 
 - (void)testRestorePreviousSignInNoRefresh_hasPreviousUser {
   [[[_authorization expect] andReturn:_authState] authState];
+  [[_authorization expect] setTokenRefreshDelegate:OCMOCK_ANY];
   OCMStub([_authState lastTokenResponse]).andReturn(_tokenResponse);
   OCMStub([_authState refreshToken]).andReturn(kRefreshToken);
 
@@ -1068,9 +1070,9 @@ static void *kTestObserverContext = &kTestObserverContext;
   NSError *emmError = [NSError errorWithDomain:@"anydomain"
                                           code:12345
                                       userInfo:@{ OIDOAuthErrorFieldError : errorJSON }];
-  [[_authentication expect] handleTokenFetchEMMError:emmError
-                                          completion:SAVE_TO_ARG_BLOCK(completion)];
-
+  id emmSupport = OCMStrictClassMock([GIDEMMSupport class]);
+  [[emmSupport expect] handleTokenFetchEMMError:emmError
+                                     completion:SAVE_TO_ARG_BLOCK(completion)];
 
   [self OAuthLoginWithAddScopesFlow:NO
                           authError:nil
@@ -1089,7 +1091,7 @@ static void *kTestObserverContext = &kTestObserverContext;
 
   [self waitForExpectationsWithTimeout:1 handler:nil];
 
-  [_authentication verify];
+  [emmSupport verify];
   XCTAssertFalse(_keychainSaved, @"should not save to keychain");
   XCTAssertTrue(_completionCalled, @"should call delegate");
   XCTAssertNotNil(_authError, @"should have error");