Skip to content

Commit

Permalink
Handle authorization response and return result object. (#426)
Browse files Browse the repository at this point in the history
  • Loading branch information
brnnmrls committed Jun 17, 2024
1 parent 95e7561 commit 7e02188
Show file tree
Hide file tree
Showing 14 changed files with 596 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading

0 comments on commit 7e02188

Please sign in to comment.