diff --git a/GoogleSignIn/Sources/GIDVerifyAccountDetail/Fake/GIDVerifiedAccountDetailHandlingFake.h b/GoogleSignIn/Sources/GIDVerifyAccountDetail/Fake/GIDVerifiedAccountDetailHandlingFake.h new file mode 100644 index 00000000..99b0db46 --- /dev/null +++ b/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 + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +#import + +#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 + +/// 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 + *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 diff --git a/GoogleSignIn/Sources/GIDVerifyAccountDetail/Fake/GIDVerifiedAccountDetailHandlingFake.m b/GoogleSignIn/Sources/GIDVerifyAccountDetail/Fake/GIDVerifiedAccountDetailHandlingFake.m new file mode 100644 index 00000000..34b69c03 --- /dev/null +++ b/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 +#import +#import +#endif + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +@implementation GIDVerifiedAccountDetailHandlingFake + +- (instancetype)initWithLastTokenResponse:(OIDTokenResponse *)tokenResponse + accountDetails:(NSArray *)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 *accountDetailsString = + [OIDScopeUtilities scopesArrayWithString:response.scope]; + + NSMutableArray *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 diff --git a/GoogleSignIn/Sources/GIDVerifyAccountDetail/Implementations/GIDVerifiableAccountDetail.m b/GoogleSignIn/Sources/GIDVerifyAccountDetail/Implementations/GIDVerifiableAccountDetail.m index 2d0bef77..71e48da2 100644 --- a/GoogleSignIn/Sources/GIDVerifyAccountDetail/Implementations/GIDVerifiableAccountDetail.m +++ b/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 @@ - (nullable NSString *)scope { } } +- (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 diff --git a/GoogleSignIn/Sources/GIDVerifyAccountDetail/Implementations/GIDVerifiedAccountDetailResult.m b/GoogleSignIn/Sources/GIDVerifyAccountDetail/Implementations/GIDVerifiedAccountDetailResult.m index 1d1b7928..3dc467bd 100644 --- a/GoogleSignIn/Sources/GIDVerifyAccountDetail/Implementations/GIDVerifiedAccountDetailResult.m +++ b/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 +#import +#import +#import +#import +#import +#import +#endif + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +NS_ASSUME_NONNULL_BEGIN + @implementation GIDVerifiedAccountDetailResult + +- (instancetype)initWithLastTokenResponse:(OIDTokenResponse *)tokenResponse + accountDetails:(NSArray *)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 *accountDetailsString = + [OIDScopeUtilities scopesArrayWithString:tokenResponse.scope]; + NSMutableArray *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 diff --git a/GoogleSignIn/Sources/GIDVerifyAccountDetail/Implementations/GIDVerifyAccountDetail.m b/GoogleSignIn/Sources/GIDVerifyAccountDetail/Implementations/GIDVerifyAccountDetail.m index 3dfb27e7..163b6add 100644 --- a/GoogleSignIn/Sources/GIDVerifyAccountDetail/Implementations/GIDVerifyAccountDetail.m +++ b/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 #import #import #import #import +#import +#import +#import #import #import +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST +#import +#endif + + #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST #import #endif @@ -51,6 +64,10 @@ NSErrorDomain const kGIDVerifyErrorDomain = @"com.google.GIDVerifyAccountDetail"; @implementation GIDVerifyAccountDetail { + /// Represents the list of account details to verify. + NSArray *_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 @@ - (void)verifyAccountDetails:(NSArray *)accountDet 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 @@ - (void)processAuthorizationResponse:(OIDAuthorizationResponse *)authorizationRe 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 @@ - (void)assertValidPresentingViewController:(GIDSignInInternalOptions *)options } } +- (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 diff --git a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiableAccountDetail.h b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiableAccountDetail.h index 89adae7d..81b055ce 100644 --- a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiableAccountDetail.h +++ b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiableAccountDetail.h @@ -14,12 +14,18 @@ * limitations under the License. */ +#import + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + #import 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 diff --git a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiedAccountDetailHandling.h b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiedAccountDetailHandling.h new file mode 100644 index 00000000..75e720b3 --- /dev/null +++ b/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 + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +#import + +@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 *)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 diff --git a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiedAccountDetailResult.h b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiedAccountDetailResult.h index c9762920..ec1bd70f 100644 --- a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiedAccountDetailResult.h +++ b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiedAccountDetailResult.h @@ -14,9 +14,38 @@ * limitations under the License. */ +#import + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + #import +#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 + +/// 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 + *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 diff --git a/GoogleSignIn/Sources/Public/GoogleSignIn/GoogleSignIn.h b/GoogleSignIn/Sources/Public/GoogleSignIn/GoogleSignIn.h index b1b4d240..df92aea1 100644 --- a/GoogleSignIn/Sources/Public/GoogleSignIn/GoogleSignIn.h +++ b/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 diff --git a/GoogleSignIn/Tests/Unit/GIDSignInTest.m b/GoogleSignIn/Tests/Unit/GIDSignInTest.m index 537375d0..b60959b8 100644 --- a/GoogleSignIn/Tests/Unit/GIDSignInTest.m +++ b/GoogleSignIn/Tests/Unit/GIDSignInTest.m @@ -1226,6 +1226,9 @@ - (void)testTokenEndpointEMMError { 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 diff --git a/GoogleSignIn/Tests/Unit/GIDVerifiableAccountDetailTest.m b/GoogleSignIn/Tests/Unit/GIDVerifiableAccountDetailTest.m index dcbdef56..88c050ee 100644 --- a/GoogleSignIn/Tests/Unit/GIDVerifiableAccountDetailTest.m +++ b/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 +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDVerifiableAccountDetail.h" @interface GIDVerifiableAccountDetailTests : XCTestCase @@ -27,3 +42,5 @@ - (void)testScopeRetrieval_MissingScope { } @end + +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST diff --git a/GoogleSignIn/Tests/Unit/GIDVerifiedAccountDetailResultTest.m b/GoogleSignIn/Tests/Unit/GIDVerifiedAccountDetailResultTest.m new file mode 100644 index 00000000..7c2ed544 --- /dev/null +++ b/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 + +#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 *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 *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 diff --git a/GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h b/GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h index 2bff75ef..5dacb0ba 100644 --- a/GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h +++ b/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; diff --git a/GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.m b/GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.m index 45b0e77e..5b14f135 100644 --- a/GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.m +++ b/GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.m @@ -57,6 +57,22 @@ + (instancetype)testInstance { return [self testInstanceWithIDToken:[self idToken]]; } ++ (instancetype)testInstanceWithScope:(NSString *)scope { + NSMutableDictionary *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