Explorar el Código

Handle authorization response and return result object. (#426)

Brianna Morales hace 1 año
padre
commit
7e02188f0d

+ 62 - 0
GoogleSignIn/Sources/GIDVerifyAccountDetail/Fake/GIDVerifiedAccountDetailHandlingFake.h

@@ -0,0 +1,62 @@
+/*
+ * Copyright 2024 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/Public/GoogleSignIn/GIDVerifiedAccountDetailHandling.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class GIDVerifiedAccountDetailResult;
+@class GIDVerifiableAccountDetail;
+@class OIDAuthState;
+@class OIDTokenResponse;
+
+/// A fake implementation of `GIDVerifiedAccountDetailHandling` for testing purposes.
+@interface GIDVerifiedAccountDetailHandlingFake : NSObject <GIDVerifiedAccountDetailHandling>
+
+/// The token response to be updated in the auth state.
+@property (nonatomic, nullable) OIDTokenResponse *tokenResponse;
+
+/// The auth state to be used to refresh tokens.
+@property (nonatomic, nullable) OIDAuthState *verifiedAuthState;
+
+/// The error to be updated in the auth state.
+@property (nonatomic, nullable) NSError *error;
+
+/// A list of verified account details.
+@property(nonatomic, copy, readonly) NSArray<GIDVerifiableAccountDetail *>
+    *verifiedAccountDetails;
+
+/// Creates an instance conforming to `GIDVerifiedAccountDetailHandling` with the provided
+/// token response, auth state, and error.
+///
+/// @param tokenResponse The `OIDTokenResponse` instance to update the auth state.
+/// @param verifiedAuthState The `OIDAuthState` instance to refresh tokens.
+/// @param error Error to indicate failure getting the token response.
+- (instancetype)initWithTokenResponse:(nullable OIDTokenResponse *)tokenResponse
+                    verifiedAuthState:(nullable OIDAuthState *)verifiedAuthState
+                                error:(nullable NSError *)error;
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST

+ 94 - 0
GoogleSignIn/Sources/GIDVerifyAccountDetail/Fake/GIDVerifiedAccountDetailHandlingFake.m

@@ -0,0 +1,94 @@
+/*
+ * Copyright 2024 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/GIDVerifyAccountDetail/Fake/GIDVerifiedAccountDetailHandlingFake.h"
+
+#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiableAccountDetail.h"
+#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiedAccountDetailResult.h"
+
+#ifdef SWIFT_PACKAGE
+@import AppAuth;
+#else
+#import <AppAuth/OIDAuthState.h>
+#import <AppAuth/OIDTokenResponse.h>
+#import <AppAuth/OIDScopeUtilities.h>
+#endif
+
+#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
+
+@implementation GIDVerifiedAccountDetailHandlingFake
+
+- (instancetype)initWithLastTokenResponse:(OIDTokenResponse *)tokenResponse
+                           accountDetails:(NSArray<GIDVerifiableAccountDetail *> *)accountDetails
+                                authState:(OIDAuthState *)authState {
+  self = [super init];
+  if (self) {
+    NSAssert(false, @"This class is only to be used in testing. Do not use.");
+  }
+  return self;
+}
+
+- (instancetype)initWithTokenResponse:(nullable OIDTokenResponse *)tokenResponse
+                    verifiedAuthState:(nullable OIDAuthState *)verifiedAuthState
+                                error:(nullable NSError *)error {
+  self = [super init];
+  if (self) {
+    _tokenResponse = tokenResponse;
+    _verifiedAuthState = verifiedAuthState;
+    _error = error;
+  }
+  return self;
+}
+
+- (void)refreshTokensWithCompletion:(nullable void (^)(GIDVerifiedAccountDetailResult *,
+                                                      NSError *))completion {
+  if (_tokenResponse) {
+    [self.verifiedAuthState updateWithTokenResponse:_tokenResponse error:nil];
+  } else {
+    [self.verifiedAuthState updateWithAuthorizationError:_error];
+  }
+
+  [self updateVerifiedDetailsWithTokenResponse:_tokenResponse];
+
+  GIDVerifiedAccountDetailResult *result =
+      [[GIDVerifiedAccountDetailResult alloc] initWithLastTokenResponse:_tokenResponse
+                                                         accountDetails:_verifiedAccountDetails
+                                                              authState:_verifiedAuthState];
+  completion(result, _error);
+}
+
+- (void)updateVerifiedDetailsWithTokenResponse:(nullable OIDTokenResponse *)response {
+  if (response) {
+    NSArray<NSString *> *accountDetailsString =
+        [OIDScopeUtilities scopesArrayWithString:response.scope];
+
+    NSMutableArray<GIDVerifiableAccountDetail *> *verifiedAccountDetails = [NSMutableArray array];
+    for (NSString *type in accountDetailsString) {
+      GIDAccountDetailType detailType = [GIDVerifiableAccountDetail detailTypeWithString:type];
+      if (detailType != GIDAccountDetailTypeUnknown) {
+        [verifiedAccountDetails addObject:
+         [[GIDVerifiableAccountDetail alloc] initWithAccountDetailType:detailType]];
+      }
+    }
+    _verifiedAccountDetails = [verifiedAccountDetails copy];
+  } else {
+    _verifiedAccountDetails = @[];
+  }
+}
+
+@end
+
+#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST

+ 22 - 0
GoogleSignIn/Sources/GIDVerifyAccountDetail/Implementations/GIDVerifiableAccountDetail.m

@@ -16,6 +16,8 @@
 
 #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiableAccountDetail.h"
 
+#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
+
 NSString *const kAccountDetailTypeAgeOver18Scope = @"https://www.googleapis.com/auth/verified.age.over18.standard";
 
 @implementation GIDVerifiableAccountDetail
@@ -37,4 +39,24 @@ NSString *const kAccountDetailTypeAgeOver18Scope = @"https://www.googleapis.com/
   }
 }
 
+- (BOOL)isEqual:(id)object {
+  if (![object isKindOfClass:[GIDVerifiableAccountDetail class]]) {
+    return NO;
+  }
+  return self.accountDetailType == ((GIDVerifiableAccountDetail *)object).accountDetailType;
+}
+
+- (NSUInteger)hash {
+  return self.accountDetailType;
+}
+
++ (GIDAccountDetailType)detailTypeWithString:(NSString *)detailTypeString {
+  if ([detailTypeString isEqualToString:kAccountDetailTypeAgeOver18Scope]) {
+    return GIDAccountDetailTypeAgeOver18;
+  }
+  return GIDAccountDetailTypeUnknown;
+}
+
 @end
+
+#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST

+ 110 - 0
GoogleSignIn/Sources/GIDVerifyAccountDetail/Implementations/GIDVerifiedAccountDetailResult.m

@@ -16,5 +16,115 @@
 
 #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiedAccountDetailResult.h"
 
+#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiableAccountDetail.h"
+
+#ifdef SWIFT_PACKAGE
+@import AppAuth;
+#else
+#import <AppAuth/OIDAuthState.h>
+#import <AppAuth/OIDAuthorizationRequest.h>
+#import <AppAuth/OIDAuthorizationResponse.h>
+#import <AppAuth/OIDAuthorizationService.h>
+#import <AppAuth/OIDScopeUtilities.h>
+#import <AppAuth/OIDTokenRequest.h>
+#import <AppAuth/OIDTokenResponse.h>
+#endif
+
+#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
+
+NS_ASSUME_NONNULL_BEGIN
+
 @implementation GIDVerifiedAccountDetailResult
+
+- (instancetype)initWithLastTokenResponse:(OIDTokenResponse *)tokenResponse
+                           accountDetails:(NSArray<GIDVerifiableAccountDetail *> *)accountDetails
+                                authState:(OIDAuthState *)authState {
+  self = [super init];
+  if (self) {
+    _expirationDate = tokenResponse.accessTokenExpirationDate;
+    _accessTokenString = tokenResponse.accessToken;
+    _refreshTokenString = tokenResponse.refreshToken;
+    _verifiedAccountDetails = accountDetails;
+    _verifiedAuthState = authState;
+  }
+  return self;
+}
+
+// TODO: Migrate refresh logic to `GIDGoogleuser` (#441).
+- (void)refreshTokensWithCompletion:(nullable void (^)(GIDVerifiedAccountDetailResult *,
+                                                      NSError *))completion {
+  OIDAuthorizationResponse *authResponse = self.verifiedAuthState.lastAuthorizationResponse;
+  OIDAuthorizationRequest *request = authResponse.request;
+
+  OIDTokenRequest *refreshRequest = 
+      [[OIDTokenRequest alloc] initWithConfiguration:request.configuration
+                                           grantType:OIDGrantTypeAuthorizationCode
+                                   authorizationCode:authResponse.authorizationCode
+                                         redirectURL:request.redirectURL
+                                            clientID:request.clientID
+                                        clientSecret:request.clientSecret
+                                               scope:request.scope                     
+                                        refreshToken:self.refreshTokenString
+                                        codeVerifier:request.codeVerifier
+                                additionalParameters:request.additionalParameters];
+
+  [OIDAuthorizationService performTokenRequest:refreshRequest
+                 originalAuthorizationResponse:authResponse
+                                      callback:^(OIDTokenResponse * _Nullable tokenResponse, 
+                                                 NSError * _Nullable error) {
+    if (tokenResponse) {
+      [self.verifiedAuthState updateWithTokenResponse:tokenResponse error:nil];
+    } else {
+      [self.verifiedAuthState updateWithAuthorizationError:error];
+    }
+    [self updateVerifiedDetailsWithTokenResponse:tokenResponse];
+    completion(self, error);
+  }];
+}
+
+- (void)updateVerifiedDetailsWithTokenResponse:(nullable OIDTokenResponse *)tokenResponse {
+  if (tokenResponse) {
+    _expirationDate = tokenResponse.accessTokenExpirationDate;
+    _accessTokenString = tokenResponse.accessToken;
+    _refreshTokenString = tokenResponse.refreshToken;
+
+    NSArray<NSString *> *accountDetailsString =
+        [OIDScopeUtilities scopesArrayWithString:tokenResponse.scope];
+    NSMutableArray<GIDVerifiableAccountDetail *> *verifiedAccountDetails = [NSMutableArray array];
+    for (NSString *type in accountDetailsString) {
+      GIDAccountDetailType detailType = [GIDVerifiableAccountDetail detailTypeWithString:type];
+      if (detailType != GIDAccountDetailTypeUnknown) {
+        [verifiedAccountDetails addObject:
+         [[GIDVerifiableAccountDetail alloc] initWithAccountDetailType:detailType]];
+      }
+    }
+    _verifiedAccountDetails = [verifiedAccountDetails copy];
+  } else {
+    _verifiedAccountDetails = @[];
+  }
+}
+
+- (BOOL)isEqual:(id)object {
+  if (![object isKindOfClass:[GIDVerifiedAccountDetailResult class]]) {
+    return NO;
+  }
+
+  GIDVerifiedAccountDetailResult *other = (GIDVerifiedAccountDetailResult *)object;
+  return [self.expirationDate isEqual:other.expirationDate] &&
+         [self.accessTokenString isEqualToString:other.accessTokenString] &&
+         [self.refreshTokenString isEqualToString:other.refreshTokenString] &&
+         [self.verifiedAccountDetails isEqual:other.verifiedAccountDetails] &&
+         [self.verifiedAuthState isEqual:other.verifiedAuthState];
+}
+
+- (NSUInteger)hash {
+  return [self.expirationDate hash] ^ [self.accessTokenString hash] ^ 
+         [self.refreshTokenString hash] ^ [self.verifiedAccountDetails hash] ^
+         [self.verifiedAuthState hash];
+}
+
 @end
+
+NS_ASSUME_NONNULL_END
+
+#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST

+ 53 - 9
GoogleSignIn/Sources/GIDVerifyAccountDetail/Implementations/GIDVerifyAccountDetail.m

@@ -23,6 +23,10 @@
 #import "GoogleSignIn/Sources/GIDAuthorizationResponse/GIDAuthorizationResponseHelper.h"
 #import "GoogleSignIn/Sources/GIDAuthorizationResponse/Implementations/GIDAuthorizationResponseHandler.h"
 
+#import "GoogleSignIn/Sources/GIDAuthFlow.h"
+#import "GoogleSignIn/Sources/GIDAuthorizationResponse/GIDAuthorizationResponseHelper.h"
+#import "GoogleSignIn/Sources/GIDAuthorizationResponse/Implementations/GIDAuthorizationResponseHandler.h"
+
 #import "GoogleSignIn/Sources/GIDAuthFlow.h"
 #import "GoogleSignIn/Sources/GIDSignInInternalOptions.h"
 #import "GoogleSignIn/Sources/GIDSignInCallbackSchemes.h"
@@ -32,13 +36,22 @@
 #ifdef SWIFT_PACKAGE
 @import AppAuth;
 #else
+#import <AppAuth/OIDAuthState.h>
 #import <AppAuth/OIDAuthorizationRequest.h>
 #import <AppAuth/OIDAuthorizationResponse.h>
 #import <AppAuth/OIDAuthorizationService.h>
 #import <AppAuth/OIDExternalUserAgentSession.h>
+#import <AppAuth/OIDAuthorizationResponse.h>
+#import <AppAuth/OIDAuthorizationService.h>
+#import <AppAuth/OIDExternalUserAgentSession.h>
 #import <AppAuth/OIDResponseTypes.h>
 #import <AppAuth/OIDServiceConfiguration.h>
 
+#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
+#import <AppAuth/OIDAuthorizationService+IOS.h>
+#endif
+
+
 #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
 #import <AppAuth/OIDAuthorizationService+IOS.h>
 #endif
@@ -51,6 +64,10 @@
 NSErrorDomain const kGIDVerifyErrorDomain = @"com.google.GIDVerifyAccountDetail";
 
 @implementation GIDVerifyAccountDetail {
+  /// Represents the list of account details to verify.
+  NSArray<GIDVerifiableAccountDetail *> *_accountDetails;
+  /// Represents internal options for the verification flow.
+  GIDSignInInternalOptions *_options;
   /// AppAuth configuration object.
   OIDServiceConfiguration *_appAuthConfiguration;
   /// AppAuth external user-agent session state.
@@ -106,13 +123,14 @@ NSErrorDomain const kGIDVerifyErrorDomain = @"com.google.GIDVerifyAccountDetail"
                   completion:(nullable void (^)(GIDVerifiedAccountDetailResult *_Nullable verifyResult,
                                                 NSError *_Nullable error))completion {
   GIDSignInInternalOptions *options =
-  [GIDSignInInternalOptions defaultOptionsWithConfiguration:_configuration
-                                   presentingViewController:presentingViewController
-                                                  loginHint:hint
-                                              addScopesFlow:YES
-                                     accountDetailsToVerify:accountDetails
-                                           verifyCompletion:completion];
-
+      [GIDSignInInternalOptions defaultOptionsWithConfiguration:_configuration
+                                       presentingViewController:presentingViewController
+                                                      loginHint:hint
+                                                  addScopesFlow:YES
+                                         accountDetailsToVerify:accountDetails
+                                               verifyCompletion:completion];
+  self->_options = options;
+  self->_accountDetails = accountDetails;
   [self verifyAccountDetailsInteractivelyWithOptions:options];
 }
 
@@ -204,8 +222,11 @@ NSErrorDomain const kGIDVerifyErrorDomain = @"com.google.GIDVerifyAccountDetail"
   GIDAuthorizationResponseHelper *responseHelper =
       [[GIDAuthorizationResponseHelper alloc] initWithAuthorizationResponseHandler:responseHandler];
 
-  // TODO: Add completion callback method (#413).
-  __unused GIDAuthFlow *authFlow = [responseHelper fetchAuthFlowFromProcessedResponse];
+  GIDAuthFlow *authFlow = [responseHelper fetchAuthFlowFromProcessedResponse];
+
+  if (authFlow) {
+    [self addCompletionCallback:authFlow];
+  }
 }
 
 #pragma mark - Helpers
@@ -237,6 +258,29 @@ NSErrorDomain const kGIDVerifyErrorDomain = @"com.google.GIDVerifyAccountDetail"
   }
 }
 
+- (void)addCompletionCallback:(GIDAuthFlow *)authFlow {
+  __weak GIDAuthFlow *weakAuthFlow = authFlow;
+  [authFlow addCallback:^() {
+    GIDAuthFlow *handlerAuthFlow = weakAuthFlow;
+    if (self->_options.completion) {
+      GIDVerifyCompletion completion = self->_options.verifyCompletion;
+      self->_options = nil;
+      dispatch_async(dispatch_get_main_queue(), ^{
+        if (handlerAuthFlow.error) {
+          completion(nil, handlerAuthFlow.error);
+        } else {
+          OIDAuthState *authState = handlerAuthFlow.authState;
+          GIDVerifiedAccountDetailResult *verifiedResult = [[GIDVerifiedAccountDetailResult alloc]
+              initWithLastTokenResponse:authState.lastTokenResponse
+                         accountDetails:self->_accountDetails
+                              authState:authState];
+          completion(verifiedResult, nil);
+        }
+      });
+    }
+  }];
+}
+
 @end
 
 #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST

+ 13 - 0
GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiableAccountDetail.h

@@ -14,12 +14,18 @@
  * limitations under the License.
  */
 
+#import <TargetConditionals.h>
+
+#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
+
 #import <Foundation/Foundation.h>
 
 NS_ASSUME_NONNULL_BEGIN
 
 /// An enumeration defining the types of account details Google can verify.
 typedef NS_ENUM(NSInteger, GIDAccountDetailType) {
+  /// Used when the account detail type is unspecified or cannot be matched.
+  GIDAccountDetailTypeUnknown,
   /// User account detail for age over 18.
   GIDAccountDetailTypeAgeOver18,
 };
@@ -45,6 +51,13 @@ extern NSString *const kAccountDetailTypeAgeOver18Scope;
 /// @return A string representing the scope required to verify the account detail.
 - (nullable NSString *)scope;
 
+/// Retrieves the verified account detail type from the given scope.
+///
+/// @return The verified account detail the passed in scope maps to.
++ (GIDAccountDetailType)detailTypeWithString:(NSString *)detailTypeString;
+
 @end
 
 NS_ASSUME_NONNULL_END
+
+#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST

+ 53 - 0
GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiedAccountDetailHandling.h

@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 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>
+
+@class GIDVerifiableAccountDetail;
+@class GIDVerifiedAccountDetailResult;
+@class OIDAuthState;
+@class OIDTokenResponse;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol GIDVerifiedAccountDetailHandling
+
+/// Initialize a `GIDVerifiedAccountDetailHandling` object by specifying all available properties.
+///
+/// @param tokenResponse The last token response with expiration date, access token, and refresh token.
+/// @param accountDetails A list of verified account details.
+/// @param authState An updated to update the token response or authorization error.
+///
+/// @return An initialized `GIDVerifiedAccountDetailHandling` instance with expiration date, access token, and refresh token.
+- (instancetype)initWithLastTokenResponse:(OIDTokenResponse *)tokenResponse
+                           accountDetails:(NSArray<GIDVerifiableAccountDetail *> *)accountDetails
+                                authState:(OIDAuthState *)authState;
+
+/// Refresh the access token and refresh token with the current authorization state.
+///
+/// @param completion A completion block called when the refresh operation completes with the new result or error.
+- (void)refreshTokensWithCompletion:(nullable void (^)(GIDVerifiedAccountDetailResult *,
+                                                      NSError *))completion;
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST

+ 30 - 1
GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiedAccountDetailResult.h

@@ -14,9 +14,38 @@
  * limitations under the License.
  */
 
+#import <TargetConditionals.h>
+
+#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
+
 #import <Foundation/Foundation.h>
 
+#import "GIDVerifiedAccountDetailHandling.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class GIDVerifiableAccountDetail;
+@class OIDAuthState;
+@class OIDTokenResponse;
+
 /// A helper object that contains the result of a verification flow.
 /// This will pass back the necessary tokens to the requesting party.
-@interface GIDVerifiedAccountDetailResult : NSObject
+@interface GIDVerifiedAccountDetailResult : NSObject <GIDVerifiedAccountDetailHandling>
+
+/// The date when the access token expires.
+@property(nonatomic, readonly, nullable) NSDate *expirationDate;
+/// The access token string.
+@property(nonatomic, copy, readonly, nullable) NSString *accessTokenString;
+/// The refresh token string.
+@property(nonatomic, copy, readonly, nullable) NSString *refreshTokenString;
+/// A list of verified account details.
+@property(nonatomic, copy, readonly) NSArray<GIDVerifiableAccountDetail *>
+    *verifiedAccountDetails;
+/// The auth state to use to refresh tokens.
+@property(nonatomic, readonly) OIDAuthState *verifiedAuthState;
+
 @end
+
+NS_ASSUME_NONNULL_END
+
+#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST

+ 4 - 1
GoogleSignIn/Sources/Public/GoogleSignIn/GoogleSignIn.h

@@ -21,9 +21,12 @@
 #import "GIDSignIn.h"
 #import "GIDToken.h"
 #import "GIDSignInResult.h"
+#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
 #import "GIDVerifyAccountDetail.h"
 #import "GIDVerifiableAccountDetail.h"
 #import "GIDVerifiedAccountDetailResult.h"
+#import "GIDVerifiedAccountDetailHandling.h"
+#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
 #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
 #import "GIDSignInButton.h"
-#endif
+#endif //TARGET_OS_IOS || TARGET_OS_MACCATALYST

+ 3 - 0
GoogleSignIn/Tests/Unit/GIDSignInTest.m

@@ -1226,6 +1226,9 @@ static NSString *const kNewScope = @"newScope";
   XCTAssertEqualObjects(_authError.domain, kGIDSignInErrorDomain);
   XCTAssertEqual(_authError.code, kGIDSignInErrorCodeEMM);
   XCTAssertNil(_signIn.currentUser, @"should not have current user");
+
+  // TODO: Keep mocks from carrying forward to subsequent tests. (#410)
+  [_authState stopMocking];
 }
 
 #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST

+ 17 - 0
GoogleSignIn/Tests/Unit/GIDVerifiableAccountDetailTest.m

@@ -1,5 +1,20 @@
+// Copyright 2024 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 <XCTest/XCTest.h>
 
+#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
 #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiableAccountDetail.h"
 
 @interface GIDVerifiableAccountDetailTests : XCTestCase
@@ -27,3 +42,5 @@
 }
 
 @end
+
+#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST

+ 117 - 0
GoogleSignIn/Tests/Unit/GIDVerifiedAccountDetailResultTest.m

@@ -0,0 +1,117 @@
+// Copyright 2024 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 <XCTest/XCTest.h>
+
+#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
+#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiedAccountDetailResult.h"
+#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiableAccountDetail.h"
+
+#import "GoogleSignIn/Sources/GIDVerifyAccountDetail/Fake/GIDVerifiedAccountDetailHandlingFake.h"
+
+#import "GoogleSignIn/Sources/GIDSignIn_Private.h"
+
+#import "GoogleSignIn/Tests/Unit/OIDAuthState+Testing.h"
+#import "GoogleSignIn/Tests/Unit/OIDAuthorizationRequest+Testing.h"
+#import "GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h"
+
+@interface GIDVerifiedAccountDetailResultTest : XCTestCase
+@end
+
+@implementation GIDVerifiedAccountDetailResultTest : XCTestCase
+
+- (void)testInit {
+  OIDAuthState *authState = [OIDAuthState testInstance];
+
+  GIDVerifiableAccountDetail *verifiedAccountDetail =
+      [[GIDVerifiableAccountDetail alloc] initWithAccountDetailType:GIDAccountDetailTypeAgeOver18];
+
+  NSArray<GIDVerifiableAccountDetail *> *verifiedList =
+      @[verifiedAccountDetail, verifiedAccountDetail];
+
+  GIDVerifiedAccountDetailResult *result = 
+      [[GIDVerifiedAccountDetailResult alloc] initWithLastTokenResponse:authState.lastTokenResponse
+                                                         accountDetails:verifiedList
+                                                              authState:authState];
+
+  XCTAssertEqual(result.verifiedAuthState, authState);
+  XCTAssertEqual(result.verifiedAccountDetails, verifiedList);
+  XCTAssertEqual(result.expirationDate, authState.lastTokenResponse.accessTokenExpirationDate);
+  XCTAssertEqual(result.accessTokenString, authState.lastTokenResponse.accessToken);
+  XCTAssertEqual(result.refreshTokenString, authState.lastTokenResponse.refreshToken);
+}
+
+- (void)testRefreshTokensWithCompletion_success {
+  GIDVerifiableAccountDetail *verifiedAccountDetail = 
+      [[GIDVerifiableAccountDetail alloc] initWithAccountDetailType:GIDAccountDetailTypeAgeOver18];
+
+  NSString *kAccountDetailList = [NSString stringWithFormat:@"%@",
+                                  kAccountDetailTypeAgeOver18Scope];
+  OIDTokenResponse *tokenResponse = [OIDTokenResponse testInstanceWithScope:kAccountDetailList];
+  OIDAuthState *authState = [OIDAuthState testInstanceWithTokenResponse:tokenResponse];
+  GIDVerifiedAccountDetailHandlingFake *result =
+      [[GIDVerifiedAccountDetailHandlingFake alloc] initWithTokenResponse:authState.lastTokenResponse
+                                                        verifiedAuthState:authState
+                                                                    error:nil];
+
+  NSArray<GIDVerifiableAccountDetail *> *expectedVerifiedList =
+      @[verifiedAccountDetail];
+  GIDVerifiedAccountDetailResult *expectedResult =
+      [[GIDVerifiedAccountDetailResult alloc] initWithLastTokenResponse:authState.lastTokenResponse
+                                                         accountDetails:expectedVerifiedList
+                                                              authState:authState];
+
+  XCTestExpectation *expectation =
+      [self expectationWithDescription:@"Refreshed verified account details completion called"];
+  [result refreshTokensWithCompletion:^(GIDVerifiedAccountDetailResult * _Nullable refreshedResult,
+                                      NSError * _Nullable error) {
+    XCTAssertNil(error);
+    XCTAssertNotNil(refreshedResult);
+    XCTAssertTrue([refreshedResult isEqual:expectedResult]);
+    [expectation fulfill];
+  }];
+
+  [self waitForExpectationsWithTimeout:1 handler:nil];
+}
+
+- (void)testRefreshTokensWithCompletion_noTokenResponse {
+  OIDAuthState *authState = [OIDAuthState testInstanceWithTokenResponse:nil];
+  NSError *expectedError = [NSError errorWithDomain:kGIDSignInErrorDomain
+                                               code:kGIDSignInErrorCodeUnknown
+                                           userInfo:nil];
+
+  GIDVerifiedAccountDetailHandlingFake *result =
+      [[GIDVerifiedAccountDetailHandlingFake alloc] initWithTokenResponse:authState.lastTokenResponse
+                                                        verifiedAuthState:authState
+                                                                    error:expectedError];
+
+  XCTestExpectation *expectation = 
+    [self expectationWithDescription:@"Refreshed verified account details completion called"];
+  [result refreshTokensWithCompletion:^(GIDVerifiedAccountDetailResult * _Nullable refreshedResult,
+                                      NSError * _Nullable error) {
+    XCTAssertNotNil(error);
+    XCTAssertEqual(error, expectedError);
+    XCTAssertEqual(error.code, kGIDSignInErrorCodeUnknown);
+    XCTAssertNotNil(refreshedResult);
+    XCTAssertTrue([refreshedResult.verifiedAccountDetails count] == 0,
+                  @"verifiedAccountDetails should have a count of 0");
+    [expectation fulfill];
+  }];
+
+  [self waitForExpectationsWithTimeout:1 handler:nil];
+}
+
+@end
+
+#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST

+ 2 - 0
GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h

@@ -51,6 +51,8 @@ extern NSString * const kFatPictureURL;
 
 + (instancetype)testInstance;
 
++ (instancetype)testInstanceWithScope:(NSString *)scope;
+
 + (instancetype)testInstanceWithIDToken:(NSString *)idToken;
 
 + (instancetype)testInstanceWithAccessTokenExpiration:(NSNumber *)expiration;

+ 16 - 0
GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.m

@@ -57,6 +57,22 @@ NSString * const kFatPictureURL = @"fake_user_picture_url";
   return [self testInstanceWithIDToken:[self idToken]];
 }
 
++ (instancetype)testInstanceWithScope:(NSString *)scope {
+  NSMutableDictionary<NSString *, NSString *> *parameters;
+  parameters = [[NSMutableDictionary alloc] initWithDictionary:@{
+    @"access_token" : kAccessToken,
+    @"expires_in" : @(kAccessTokenExpiresIn),
+    @"token_type" : @"example_token_type",
+    @"refresh_token" : kRefreshToken,
+    @"scope" : scope,
+    @"server_code" : kServerAuthCode,
+  }];
+  parameters[@"id_token"] = [self idToken];
+
+  return [[OIDTokenResponse alloc] initWithRequest:[OIDTokenRequest testInstance]
+                                        parameters:parameters];
+}
+
 + (instancetype)testInstanceWithIDToken:(NSString *)idToken {
   return [OIDTokenResponse testInstanceWithIDToken:idToken
                                        accessToken:nil