|
17 | 17 | #import "SignInViewController.h" |
18 | 18 |
|
19 | 19 | @import GoogleSignIn; |
| 20 | +@import AppAuth; |
| 21 | +@import GTMAppAuth; |
20 | 22 |
|
21 | 23 | #import "AuthInspectorViewController.h" |
22 | 24 | #import "DataPickerState.h" |
23 | 25 | #import "DataPickerViewController.h" |
24 | 26 | #import <AppAuth/OIDTokenUtilities.h> |
| 27 | +#import <CommonCrypto/CommonDigest.h> |
25 | 28 |
|
26 | 29 | static NSString *const kSignInViewTitle = @"Sign-In Sample"; |
27 | 30 | static NSString *const kPlaceholderUserName = @"<Name>"; |
|
38 | 41 | // Accessibility Identifiers. |
39 | 42 | static NSString *const kCredentialsButtonAccessibilityIdentifier = @"Credentials"; |
40 | 43 |
|
| 44 | +@interface SignInViewController () |
| 45 | + |
| 46 | +- (void)logTestStateForEvent:(NSString *)event; |
| 47 | +- (nullable NSString *)fingerprintForToken:(nullable NSString *)token; |
| 48 | +- (void)forceTokenRefresh; |
| 49 | + |
| 50 | +@end |
| 51 | + |
41 | 52 | @implementation SignInViewController { |
42 | 53 | // This is an array of arrays, each one corresponding to the cell |
43 | 54 | // labels for its respective section. |
@@ -114,7 +125,23 @@ - (id)initWithCoder:(NSCoder *)aDecoder { |
114 | 125 | - (void)viewDidLoad { |
115 | 126 | [super viewDidLoad]; |
116 | 127 |
|
| 128 | + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; |
| 129 | + if ([defaults boolForKey:@"GSITestSignOutOnLaunch"]) { |
| 130 | + [GIDSignIn.sharedInstance signOut]; |
| 131 | + NSLog(@"SignInSample test hook: signed out on launch"); |
| 132 | + } |
| 133 | + |
| 134 | + self.signInButton.accessibilityIdentifier = @"sign_in_button"; |
| 135 | + self.signOutButton.accessibilityIdentifier = @"sign_out_button"; |
| 136 | + self.disconnectButton.accessibilityIdentifier = @"disconnect_button"; |
| 137 | + self.addScopesButton.accessibilityIdentifier = @"add_scopes_button"; |
117 | 138 | self.credentialsButton.accessibilityIdentifier = kCredentialsButtonAccessibilityIdentifier; |
| 139 | + |
| 140 | + [self logTestStateForEvent:@"launch"]; |
| 141 | + |
| 142 | + if ([defaults boolForKey:@"GSITestForceRefreshOnLaunch"]) { |
| 143 | + [self forceTokenRefresh]; |
| 144 | + } |
118 | 145 | } |
119 | 146 |
|
120 | 147 | - (void)viewWillAppear:(BOOL)animated { |
@@ -184,6 +211,7 @@ - (void)reportAuthStatus { |
184 | 211 | } |
185 | 212 |
|
186 | 213 | [self refreshUserInfo]; |
| 214 | + [self logTestStateForEvent:@"state_changed"]; |
187 | 215 | } |
188 | 216 |
|
189 | 217 | // Update the interface elements containing user data to reflect the |
@@ -432,4 +460,90 @@ - (void)tableView:(UITableView *)tableView |
432 | 460 | [self.navigationController pushViewController:dataPicker animated:YES]; |
433 | 461 | } |
434 | 462 |
|
| 463 | +#pragma mark - Test hooks |
| 464 | + |
| 465 | +- (void)logTestStateForEvent:(NSString *)event { |
| 466 | + GIDGoogleUser *currentUser = GIDSignIn.sharedInstance.currentUser; |
| 467 | + BOOL signedIn = (currentUser != nil); |
| 468 | + BOOL hasRefreshToken = (currentUser.refreshToken.tokenString.length > 0); |
| 469 | + NSString *accessTokenFingerprint = [self fingerprintForToken:currentUser.accessToken.tokenString]; |
| 470 | + |
| 471 | + NSString *accessTokenExpiration = nil; |
| 472 | + if (currentUser.accessToken.expirationDate) { |
| 473 | + NSISO8601DateFormatter *formatter = [[NSISO8601DateFormatter alloc] init]; |
| 474 | + accessTokenExpiration = [formatter stringFromDate:currentUser.accessToken.expirationDate]; |
| 475 | + } |
| 476 | + |
| 477 | + NSInteger grantedScopeCount = currentUser.grantedScopes.count; |
| 478 | + |
| 479 | + NSDictionary *state = @{ |
| 480 | + @"event": event, |
| 481 | + @"signedIn": @(signedIn), |
| 482 | + @"hasRefreshToken": @(hasRefreshToken), |
| 483 | + @"accessTokenFingerprint": accessTokenFingerprint ?: [NSNull null], |
| 484 | + @"accessTokenExpiration": accessTokenExpiration ?: [NSNull null], |
| 485 | + @"grantedScopeCount": @(grantedScopeCount) |
| 486 | + }; |
| 487 | + |
| 488 | + NSError *error; |
| 489 | + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:state options:0 error:&error]; |
| 490 | + if (jsonData) { |
| 491 | + NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; |
| 492 | + NSLog(@"GSI_TEST_STATE %@", jsonString); |
| 493 | + } |
| 494 | +} |
| 495 | + |
| 496 | +- (nullable NSString *)fingerprintForToken:(nullable NSString *)token { |
| 497 | + if (token.length == 0) { |
| 498 | + return nil; |
| 499 | + } |
| 500 | + NSData *data = [token dataUsingEncoding:NSUTF8StringEncoding]; |
| 501 | + uint8_t digest[CC_SHA256_DIGEST_LENGTH]; |
| 502 | + CC_SHA256(data.bytes, (CC_LONG)data.length, digest); |
| 503 | + NSMutableString *output = [NSMutableString stringWithCapacity:8]; |
| 504 | + for (int i = 0; i < 4; i++) { |
| 505 | + [output appendFormat:@"%02x", digest[i]]; |
| 506 | + } |
| 507 | + return output; |
| 508 | +} |
| 509 | + |
| 510 | +- (void)forceTokenRefresh { |
| 511 | + GIDGoogleUser *currentUser = GIDSignIn.sharedInstance.currentUser; |
| 512 | + if (!currentUser) { |
| 513 | + NSLog(@"SignInSample test hook: force refresh skipped, not signed in"); |
| 514 | + return; |
| 515 | + } |
| 516 | + |
| 517 | + id<GTMFetcherAuthorizationProtocol> authorizer = currentUser.fetcherAuthorizer; |
| 518 | + if (![authorizer isKindOfClass:[GTMAuthSession class]]) { |
| 519 | + NSLog(@"SignInSample test hook: fetcherAuthorizer is not a GTMAuthSession"); |
| 520 | + return; |
| 521 | + } |
| 522 | + |
| 523 | + GTMAuthSession *authSession = (GTMAuthSession *)authorizer; |
| 524 | + NSString *oldFingerprint = [self fingerprintForToken:currentUser.accessToken.tokenString]; |
| 525 | + |
| 526 | + [authSession.authState setNeedsTokenRefresh]; |
| 527 | + __weak SignInViewController *weakSelf = self; |
| 528 | + [authSession.authState performActionWithFreshTokens:^(NSString *_Nullable accessToken, |
| 529 | + NSString *_Nullable idToken, |
| 530 | + NSError *_Nullable error) { |
| 531 | + dispatch_async(dispatch_get_main_queue(), ^{ |
| 532 | + SignInViewController *strongSelf = weakSelf; |
| 533 | + if (!strongSelf) return; |
| 534 | + |
| 535 | + if (error) { |
| 536 | + NSLog(@"SignInSample test hook: force refresh failed: %@", error.localizedDescription); |
| 537 | + } else { |
| 538 | + NSString *newFingerprint = [strongSelf fingerprintForToken: |
| 539 | + GIDSignIn.sharedInstance.currentUser.accessToken.tokenString]; |
| 540 | + NSLog(@"SignInSample test hook: force refresh succeeded; " |
| 541 | + "access token fingerprint %@ -> %@", |
| 542 | + oldFingerprint, newFingerprint); |
| 543 | + } |
| 544 | + [strongSelf logTestStateForEvent:@"force_refresh"]; |
| 545 | + }); |
| 546 | + }]; |
| 547 | +} |
| 548 | + |
435 | 549 | @end |
0 commit comments