Skip to content

Commit 1d65c61

Browse files
authored
Expose refresh token expiration date. (#577)
1 parent 913b400 commit 1d65c61

File tree

8 files changed

+115
-4
lines changed

8 files changed

+115
-4
lines changed

GoogleSignIn/Sources/GIDGoogleUser.m

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,16 @@ - (void)refreshTokensIfNeededWithCompletion:(GIDGoogleUserCompletion)completion
125125
});
126126
return;
127127
}
128+
if (self.refreshToken.expirationDate && [self.refreshToken.expirationDate timeIntervalSinceNow] <= 0) {
129+
NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain
130+
code:kGIDSignInErrorCodeRefreshTokenExpired
131+
userInfo:nil];
132+
dispatch_async(dispatch_get_main_queue(), ^{
133+
completion(nil, error);
134+
});
135+
return;
136+
}
137+
128138
@synchronized (_tokenRefreshHandlerQueue) {
129139
// Push the handler into the callback queue.
130140
[_tokenRefreshHandlerQueue addObject:[completion copy]];
@@ -275,8 +285,17 @@ - (void)updateTokensWithAuthState:(OIDAuthState *)authState {
275285
self.accessToken = accessToken;
276286
}
277287

288+
NSDictionary *additionalParameters = authState.lastTokenResponse.additionalParameters;
289+
NSNumber *refreshTokenExpiresIn = nil;
290+
NSDate *refreshTokenExpirationDate = nil;
291+
id expiresInValue = additionalParameters[@"refresh_token_expires_in"];
292+
if ([expiresInValue isKindOfClass:[NSNumber class]]) {
293+
refreshTokenExpiresIn = (NSNumber *)expiresInValue;
294+
NSTimeInterval interval = [refreshTokenExpiresIn doubleValue];
295+
refreshTokenExpirationDate = [NSDate dateWithTimeIntervalSinceNow:interval];
296+
}
278297
GIDToken *refreshToken = [[GIDToken alloc] initWithTokenString:authState.refreshToken
279-
expirationDate:nil];
298+
expirationDate:refreshTokenExpirationDate];
280299
if (![self.refreshToken isEqualToToken:refreshToken]) {
281300
self.refreshToken = refreshToken;
282301
}

GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ typedef NS_ERROR_ENUM(kGIDSignInErrorDomain, GIDSignInErrorCode) {
5353
/// Indicates there is an operation on a previous user.
5454
kGIDSignInErrorCodeMismatchWithCurrentUser = -9,
5555
/// Indicates that an object could not be serialized into a `JSON` string.
56-
kGIDSignInErrorCodeJSONSerializationFailure = -10
56+
kGIDSignInErrorCodeJSONSerializationFailure = -10,
57+
/// Indicates that the refresh token has expired and the user must be re-authorized.
58+
kGIDSignInErrorCodeRefreshTokenExpired = -11,
5759
};
5860

5961
/// This class is used to sign in users with their Google account and manage their session.

GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,25 @@ - (void)testRefreshTokensIfNeededWithCompletion_handleConcurrentRefresh {
459459
[self waitForExpectationsWithTimeout:1 handler:nil];
460460
}
461461

462+
- (void)testRefreshTokensIfNeededWithCompletion_noRefresh_givenRefreshTokenExpired {
463+
NSTimeInterval expiresIn = -10;
464+
GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:expiresIn
465+
idTokenExpiresIn:expiresIn
466+
refreshTokenExpiresIn:expiresIn];
467+
468+
XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"];
469+
470+
[user refreshTokensIfNeededWithCompletion:^(GIDGoogleUser * _Nullable user,
471+
NSError * _Nullable error) {
472+
[expectation fulfill];
473+
XCTAssertNil(user);
474+
XCTAssertEqualObjects(error.domain, kGIDSignInErrorDomain);
475+
XCTAssertEqual(error.code, kGIDSignInErrorCodeRefreshTokenExpired);
476+
}];
477+
478+
[self waitForExpectationsWithTimeout:1 handler:nil];
479+
}
480+
462481
# pragma mark - Test `addScopes:`
463482

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

582+
- (GIDGoogleUser *)googleUserWithAccessTokenExpiresIn:(NSTimeInterval)accessTokenExpiresIn
583+
idTokenExpiresIn:(NSTimeInterval)idTokenExpiresIn
584+
refreshTokenExpiresIn:(NSTimeInterval)refreshTokenExpiresIn {
585+
NSString *idToken = [self idTokenWithExpiresIn:idTokenExpiresIn];
586+
587+
OIDAuthState *authState = [OIDAuthState testInstanceWithIDToken:idToken
588+
accessToken:kAccessToken
589+
accessTokenExpiresIn:accessTokenExpiresIn
590+
refreshToken:kRefreshToken
591+
refreshTokenExpiresIn:refreshTokenExpiresIn];
592+
593+
return [[GIDGoogleUser alloc] initWithAuthState:authState profileData:nil];
594+
}
595+
563596
- (NSString *)idTokenWithExpiresIn:(NSTimeInterval)expiresIn {
564597
// The expireTime should be based on 1970.
565598
NSTimeInterval expireTime = [[NSDate date] timeIntervalSince1970] + expiresIn;

GoogleSignIn/Tests/Unit/GIDSignInTest.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ - (void)setUp {
310310
OCMStub([_authState alloc]).andReturn(_authState);
311311
OCMStub([_authState initWithAuthorizationResponse:OCMOCK_ANY]).andReturn(_authState);
312312
_tokenResponse = OCMStrictClassMock([OIDTokenResponse class]);
313+
OCMStub([_tokenResponse additionalParameters]).andReturn(@{});
313314
_tokenRequest = OCMStrictClassMock([OIDTokenRequest class]);
314315
_authorization = OCMStrictClassMock([GTMAuthSession class]);
315316
_keychainStore = OCMStrictClassMock([GTMKeychainStore class]);

GoogleSignIn/Tests/Unit/OIDAuthState+Testing.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,25 @@
3131
/**
3232
* @idToken The ID token.
3333
* @accessToken The access token string.
34-
* @accessTokenExipresIn The life time of the access token starting from the moment when `OIDTokenResponse` is created.
34+
* @accessTokenExpiresIn The life time of the access token starting from the moment when `OIDTokenResponse` is created.
3535
* @refreshToken The refresh token string.
3636
*/
3737
+ (instancetype)testInstanceWithIDToken:(NSString *)idToken
3838
accessToken:(NSString *)accessToken
3939
accessTokenExpiresIn:(NSTimeInterval)accessTokenExpiresIn
4040
refreshToken:(NSString *)refreshToken;
4141

42+
/**
43+
* @idToken The ID token.
44+
* @accessToken The access token string.
45+
* @accessTokenExpiresIn The life time of the access token starting from the moment when `OIDTokenResponse` is created.
46+
* @refreshToken The refresh token string.
47+
* @refreshTokenExpiresIn The life time of the refresh token starting from the moment when `OIDTokenResponse` is created.
48+
*/
49+
+ (instancetype)testInstanceWithIDToken:(NSString *)idToken
50+
accessToken:(NSString *)accessToken
51+
accessTokenExpiresIn:(NSTimeInterval)accessTokenExpiresIn
52+
refreshToken:(NSString *)refreshToken
53+
refreshTokenExpiresIn:(NSTimeInterval)refreshTokenExpiresIn;
54+
4255
@end

GoogleSignIn/Tests/Unit/OIDAuthState+Testing.m

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,21 @@ + (instancetype)testInstanceWithIDToken:(NSString *)idToken
4646
return [self testInstanceWithTokenResponse:newResponse];
4747
}
4848

49+
+ (instancetype)testInstanceWithIDToken:(NSString *)idToken
50+
accessToken:(NSString *)accessToken
51+
accessTokenExpiresIn:(NSTimeInterval)accessTokenExpiresIn
52+
refreshToken:(NSString *)refreshToken
53+
refreshTokenExpiresIn:(NSTimeInterval)refreshTokenExpiresIn {
54+
OIDTokenResponse *newResponse =
55+
[OIDTokenResponse testInstanceWithIDToken:idToken
56+
accessToken:accessToken
57+
expiresIn:@(accessTokenExpiresIn)
58+
refreshToken:refreshToken
59+
refreshExpiresIn:@(refreshTokenExpiresIn)
60+
authTime:nil
61+
tokenRequest:nil];
62+
return [self testInstanceWithTokenResponse:newResponse];
63+
}
64+
4965

5066
@end

GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ extern NSString * const kFatPictureURL;
6767
authTime:(NSString *)authTime
6868
tokenRequest:(OIDTokenRequest *)tokenRequest;
6969

70+
+ (instancetype)testInstanceWithIDToken:(NSString *)idToken
71+
accessToken:(NSString *)accessToken
72+
expiresIn:(NSNumber *)expiresIn
73+
refreshToken:(NSString *)refreshToken
74+
refreshExpiresIn:(NSNumber *)refreshExpiresIn
75+
authTime:(NSString *)authTime
76+
tokenRequest:(OIDTokenRequest *)tokenRequest;
77+
7078
+ (NSString *)idToken;
7179

7280
+ (NSString *)fatIDToken;

GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.m

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,24 @@ + (instancetype)testInstanceWithIDToken:(NSString *)idToken
8585
refreshToken:(NSString *)refreshToken
8686
authTime:(NSString *)authTime
8787
tokenRequest:(OIDTokenRequest *)tokenRequest {
88+
89+
return [OIDTokenResponse testInstanceWithIDToken:idToken
90+
accessToken:accessToken
91+
expiresIn:expiresIn
92+
refreshToken:refreshToken
93+
refreshExpiresIn:nil
94+
authTime:authTime
95+
tokenRequest:tokenRequest];
96+
}
8897

89-
NSMutableDictionary<NSString *, NSString *> *parameters = [[NSMutableDictionary alloc] initWithDictionary:@{
98+
+ (instancetype)testInstanceWithIDToken:(NSString *)idToken
99+
accessToken:(NSString *)accessToken
100+
expiresIn:(NSNumber *)expiresIn
101+
refreshToken:(NSString *)refreshToken
102+
refreshExpiresIn:(NSNumber *)refreshExpiresIn
103+
authTime:(NSString *)authTime
104+
tokenRequest:(OIDTokenRequest *)tokenRequest {
105+
NSMutableDictionary<NSString *, NSObject<NSCopying> *> *parameters = [[NSMutableDictionary alloc] initWithDictionary:@{
90106
@"access_token" : accessToken ?: kAccessToken,
91107
@"expires_in" : expiresIn ?: @(kAccessTokenExpiresIn),
92108
@"token_type" : @"example_token_type",
@@ -97,6 +113,9 @@ + (instancetype)testInstanceWithIDToken:(NSString *)idToken
97113
if (idToken) {
98114
parameters[@"id_token"] = idToken;
99115
}
116+
if (refreshExpiresIn) {
117+
parameters[@"refresh_token_expires_in"] = refreshExpiresIn;
118+
}
100119
return [[OIDTokenResponse alloc] initWithRequest:tokenRequest ?: [OIDTokenRequest testInstance]
101120
parameters:parameters];
102121
}

0 commit comments

Comments
 (0)