Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion GoogleSignIn/Sources/GIDGoogleUser.m
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,16 @@ - (void)refreshTokensIfNeededWithCompletion:(GIDGoogleUserCompletion)completion
});
return;
}
if (self.refreshToken.expirationDate && [self.refreshToken.expirationDate timeIntervalSinceNow] <= 0) {
NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain
code:kGIDSignInErrorCodeRefreshTokenExpired
userInfo:nil];
dispatch_async(dispatch_get_main_queue(), ^{
completion(nil, error);
});
return;
}

@synchronized (_tokenRefreshHandlerQueue) {
// Push the handler into the callback queue.
[_tokenRefreshHandlerQueue addObject:[completion copy]];
Expand Down Expand Up @@ -275,8 +285,17 @@ - (void)updateTokensWithAuthState:(OIDAuthState *)authState {
self.accessToken = accessToken;
}

NSDictionary *additionalParameters = authState.lastTokenResponse.additionalParameters;
NSNumber *refreshTokenExpiresIn = nil;
NSDate *refreshTokenExpirationDate = nil;
id expiresInValue = additionalParameters[@"refresh_token_expires_in"];
if ([expiresInValue isKindOfClass:[NSNumber class]]) {
refreshTokenExpiresIn = (NSNumber *)expiresInValue;
NSTimeInterval interval = [refreshTokenExpiresIn doubleValue];
refreshTokenExpirationDate = [NSDate dateWithTimeIntervalSinceNow:interval];
}
GIDToken *refreshToken = [[GIDToken alloc] initWithTokenString:authState.refreshToken
expirationDate:nil];
expirationDate:refreshTokenExpirationDate];
if (![self.refreshToken isEqualToToken:refreshToken]) {
self.refreshToken = refreshToken;
}
Expand Down
4 changes: 3 additions & 1 deletion GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ typedef NS_ERROR_ENUM(kGIDSignInErrorDomain, GIDSignInErrorCode) {
/// Indicates there is an operation on a previous user.
kGIDSignInErrorCodeMismatchWithCurrentUser = -9,
/// Indicates that an object could not be serialized into a `JSON` string.
kGIDSignInErrorCodeJSONSerializationFailure = -10
kGIDSignInErrorCodeJSONSerializationFailure = -10,
/// Indicates that the refresh token has expired and the user must be re-authorized.
kGIDSignInErrorCodeRefreshTokenExpired = -11,
};

/// This class is used to sign in users with their Google account and manage their session.
Expand Down
33 changes: 33 additions & 0 deletions GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,25 @@ - (void)testRefreshTokensIfNeededWithCompletion_handleConcurrentRefresh {
[self waitForExpectationsWithTimeout:1 handler:nil];
}

- (void)testRefreshTokensIfNeededWithCompletion_noRefresh_givenRefreshTokenExpired {
NSTimeInterval expiresIn = -10;
GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:expiresIn
idTokenExpiresIn:expiresIn
refreshTokenExpiresIn:expiresIn];

XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"];

[user refreshTokensIfNeededWithCompletion:^(GIDGoogleUser * _Nullable user,
NSError * _Nullable error) {
[expectation fulfill];
XCTAssertNil(user);
XCTAssertEqualObjects(error.domain, kGIDSignInErrorDomain);
XCTAssertEqual(error.code, kGIDSignInErrorCodeRefreshTokenExpired);
}];

[self waitForExpectationsWithTimeout:1 handler:nil];
}

# pragma mark - Test `addScopes:`

- (void)testAddScopes_success {
Expand Down Expand Up @@ -560,6 +579,20 @@ - (GIDGoogleUser *)googleUserWithAccessTokenExpiresIn:(NSTimeInterval)accessToke
return [[GIDGoogleUser alloc] initWithAuthState:authState profileData:nil];
}

- (GIDGoogleUser *)googleUserWithAccessTokenExpiresIn:(NSTimeInterval)accessTokenExpiresIn
idTokenExpiresIn:(NSTimeInterval)idTokenExpiresIn
refreshTokenExpiresIn:(NSTimeInterval)refreshTokenExpiresIn {
NSString *idToken = [self idTokenWithExpiresIn:idTokenExpiresIn];

OIDAuthState *authState = [OIDAuthState testInstanceWithIDToken:idToken
accessToken:kAccessToken
accessTokenExpiresIn:accessTokenExpiresIn
refreshToken:kRefreshToken
refreshTokenExpiresIn:refreshTokenExpiresIn];

return [[GIDGoogleUser alloc] initWithAuthState:authState profileData:nil];
}

- (NSString *)idTokenWithExpiresIn:(NSTimeInterval)expiresIn {
// The expireTime should be based on 1970.
NSTimeInterval expireTime = [[NSDate date] timeIntervalSince1970] + expiresIn;
Expand Down
1 change: 1 addition & 0 deletions GoogleSignIn/Tests/Unit/GIDSignInTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ - (void)setUp {
OCMStub([_authState alloc]).andReturn(_authState);
OCMStub([_authState initWithAuthorizationResponse:OCMOCK_ANY]).andReturn(_authState);
_tokenResponse = OCMStrictClassMock([OIDTokenResponse class]);
OCMStub([_tokenResponse additionalParameters]).andReturn(@{});
_tokenRequest = OCMStrictClassMock([OIDTokenRequest class]);
_authorization = OCMStrictClassMock([GTMAuthSession class]);
_keychainStore = OCMStrictClassMock([GTMKeychainStore class]);
Expand Down
15 changes: 14 additions & 1 deletion GoogleSignIn/Tests/Unit/OIDAuthState+Testing.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,25 @@
/**
* @idToken The ID token.
* @accessToken The access token string.
* @accessTokenExipresIn The life time of the access token starting from the moment when `OIDTokenResponse` is created.
* @accessTokenExpiresIn The life time of the access token starting from the moment when `OIDTokenResponse` is created.
* @refreshToken The refresh token string.
*/
+ (instancetype)testInstanceWithIDToken:(NSString *)idToken
accessToken:(NSString *)accessToken
accessTokenExpiresIn:(NSTimeInterval)accessTokenExpiresIn
refreshToken:(NSString *)refreshToken;

/**
* @idToken The ID token.
* @accessToken The access token string.
* @accessTokenExpiresIn The life time of the access token starting from the moment when `OIDTokenResponse` is created.
* @refreshToken The refresh token string.
* @refreshTokenExpiresIn The life time of the refresh token starting from the moment when `OIDTokenResponse` is created.
*/
+ (instancetype)testInstanceWithIDToken:(NSString *)idToken
accessToken:(NSString *)accessToken
accessTokenExpiresIn:(NSTimeInterval)accessTokenExpiresIn
refreshToken:(NSString *)refreshToken
refreshTokenExpiresIn:(NSTimeInterval)refreshTokenExpiresIn;

@end
16 changes: 16 additions & 0 deletions GoogleSignIn/Tests/Unit/OIDAuthState+Testing.m
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,21 @@ + (instancetype)testInstanceWithIDToken:(NSString *)idToken
return [self testInstanceWithTokenResponse:newResponse];
}

+ (instancetype)testInstanceWithIDToken:(NSString *)idToken
accessToken:(NSString *)accessToken
accessTokenExpiresIn:(NSTimeInterval)accessTokenExpiresIn
refreshToken:(NSString *)refreshToken
refreshTokenExpiresIn:(NSTimeInterval)refreshTokenExpiresIn {
OIDTokenResponse *newResponse =
[OIDTokenResponse testInstanceWithIDToken:idToken
accessToken:accessToken
expiresIn:@(accessTokenExpiresIn)
refreshToken:refreshToken
refreshExpiresIn:@(refreshTokenExpiresIn)
authTime:nil
tokenRequest:nil];
return [self testInstanceWithTokenResponse:newResponse];
}


@end
8 changes: 8 additions & 0 deletions GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ extern NSString * const kFatPictureURL;
authTime:(NSString *)authTime
tokenRequest:(OIDTokenRequest *)tokenRequest;

+ (instancetype)testInstanceWithIDToken:(NSString *)idToken
accessToken:(NSString *)accessToken
expiresIn:(NSNumber *)expiresIn
refreshToken:(NSString *)refreshToken
refreshExpiresIn:(NSNumber *)refreshExpiresIn
authTime:(NSString *)authTime
tokenRequest:(OIDTokenRequest *)tokenRequest;

+ (NSString *)idToken;

+ (NSString *)fatIDToken;
Expand Down
21 changes: 20 additions & 1 deletion GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.m
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,24 @@ + (instancetype)testInstanceWithIDToken:(NSString *)idToken
refreshToken:(NSString *)refreshToken
authTime:(NSString *)authTime
tokenRequest:(OIDTokenRequest *)tokenRequest {

return [OIDTokenResponse testInstanceWithIDToken:idToken
accessToken:accessToken
expiresIn:expiresIn
refreshToken:refreshToken
refreshExpiresIn:nil
authTime:authTime
tokenRequest:tokenRequest];
}

NSMutableDictionary<NSString *, NSString *> *parameters = [[NSMutableDictionary alloc] initWithDictionary:@{
+ (instancetype)testInstanceWithIDToken:(NSString *)idToken
accessToken:(NSString *)accessToken
expiresIn:(NSNumber *)expiresIn
refreshToken:(NSString *)refreshToken
refreshExpiresIn:(NSNumber *)refreshExpiresIn
authTime:(NSString *)authTime
tokenRequest:(OIDTokenRequest *)tokenRequest {
NSMutableDictionary<NSString *, NSObject<NSCopying> *> *parameters = [[NSMutableDictionary alloc] initWithDictionary:@{
@"access_token" : accessToken ?: kAccessToken,
@"expires_in" : expiresIn ?: @(kAccessTokenExpiresIn),
@"token_type" : @"example_token_type",
Expand All @@ -97,6 +113,9 @@ + (instancetype)testInstanceWithIDToken:(NSString *)idToken
if (idToken) {
parameters[@"id_token"] = idToken;
}
if (refreshExpiresIn) {
parameters[@"refresh_token_expires_in"] = refreshExpiresIn;
}
return [[OIDTokenResponse alloc] initWithRequest:tokenRequest ?: [OIDTokenRequest testInstance]
parameters:parameters];
}
Expand Down
Loading