Browse Source

Implement -disconnectWithCallback:

Peter Andrews 4 years ago
parent
commit
503100cc79

+ 41 - 44
GoogleSignIn/Sources/GIDSignIn.m

@@ -211,8 +211,47 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
   [self signOutWithUser:_currentUser];
 }
 
-- (void)disconnect {
-  [self disconnectWithUser:_currentUser];
+- (void)disconnectWithCallback:(GIDSignInCallback)callback {
+  GIDGoogleUser *user = _currentUser;
+  OIDAuthState *authState = user.authentication.authState;
+  if (!authState) {
+    // Even the user is not signed in right now, we still need to remove any token saved in the
+    // keychain.
+    authState = [self loadAuthState];
+  }
+  // Either access or refresh token would work, but we won't have access token if the auth is
+  // retrieved from keychain.
+  NSString *token = authState.lastTokenResponse.accessToken;
+  if (!token) {
+    token = authState.lastTokenResponse.refreshToken;
+  }
+  if (!token) {
+    [self signOut];
+    // Nothing to do here, consider the operation successful.
+    callback(user, nil);
+    return;
+  }
+  NSString *revokeURLString = [NSString stringWithFormat:kRevokeTokenURLTemplate,
+      [GIDSignInPreferences googleAuthorizationServer], token];
+  // Append logging parameter
+  revokeURLString = [NSString stringWithFormat:@"%@&%@=%@",
+                     revokeURLString,
+                     kSDKVersionLoggingParameter,
+                     GIDVersion()];
+  NSURL *revokeURL = [NSURL URLWithString:revokeURLString];
+  [self startFetchURL:revokeURL
+              fromAuthState:authState
+                withComment:@"GIDSignIn: revoke tokens"
+      withCompletionHandler:^(NSData *data, NSError *error) {
+    // Revoking an already revoked token seems always successful, which saves the trouble here for
+    // us.
+    if (error) {
+      callback(nil, error);
+    } else {
+      [self signOut];
+      callback(user, nil);
+    }
+  }];
 }
 
 #pragma mark - Custom getters and setters
@@ -740,48 +779,6 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
   [self removeAllKeychainEntries];
 }
 
-- (void)disconnectWithUser:(GIDGoogleUser *)user {
-  OIDAuthState *authState = user.authentication.authState;
-  if (!authState) {
-    // Even the user is not signed in right now, we still need to remove any token saved in the
-    // keychain.
-    authState = [self loadAuthState];
-  }
-  // Either access or refresh token would work, but we won't have access token if the auth is
-  // retrieved from keychain.
-  NSString *token = authState.lastTokenResponse.accessToken;
-  if (!token) {
-    token = authState.lastTokenResponse.refreshToken;
-  }
-  if (!token) {
-    [self removeAllKeychainEntries];
-    // Nothing to do here, consider the operation successful.
-    [self didDisconnectWithUser:user error:nil];
-    return;
-  }
-  NSString *revokeURLString = [NSString stringWithFormat:kRevokeTokenURLTemplate,
-      [GIDSignInPreferences googleAuthorizationServer], token];
-  // Append logging parameter
-  revokeURLString = [NSString stringWithFormat:@"%@&%@=%@",
-                     revokeURLString,
-                     kSDKVersionLoggingParameter,
-                     GIDVersion()];
-  NSURL *revokeURL = [NSURL URLWithString:revokeURLString];
-  [self startFetchURL:revokeURL
-              fromAuthState:authState
-                withComment:@"GIDSignIn: revoke tokens"
-      withCompletionHandler:^(NSData *data, NSError *error) {
-    // Revoking an already revoked token seems always successful, which saves the trouble here for
-    // us.
-    if (error) {
-      [self didDisconnectWithUser:user error:error];
-    } else {
-      [self signOutWithUser:user];
-      [self didDisconnectWithUser:user error:nil];
-    }
-  }];
-}
-
 - (BOOL)saveAuthState:(OIDAuthState *)authState {
   GTMAppAuthFetcherAuthorization *authorization =
       [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState];

+ 7 - 1
GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h

@@ -40,6 +40,10 @@ typedef NS_ENUM(NSInteger, GIDSignInErrorCode) {
   kGIDSignInErrorCodeEMM = -6,
 };
 
+/// Represents a callback block that takes a `GIDGoogleUser` or an error if the operation was
+/// unsuccessful.
+typedef void (^GIDSignInCallback)(GIDGoogleUser *_Nullable user, NSError *_Nullable error);
+
 /// A protocol implemented by the delegate of `GIDSignIn` to receive a refresh token or an error.
 @protocol GIDSignInDelegate <NSObject>
 
@@ -164,7 +168,9 @@ typedef NS_ENUM(NSInteger, GIDSignInErrorCode) {
 
 /// Disconnects the current user from the app and revokes previous authentication. If the operation
 /// succeeds, the OAuth 2.0 token is also removed from keychain.
-- (void)disconnect;
+/// 
+/// @param callback The `GIDSignInCallback` block that is called on completion.
+- (void)disconnectWithCallback:(GIDSignInCallback)callback;
 
 @end
 

+ 36 - 68
GoogleSignIn/Tests/Unit/GIDSignInTest.m

@@ -593,17 +593,22 @@ static void *kTestObserverContext = &kTestObserverContext;
   XCTAssertFalse(_delegateCalled, @"should not call delegate");
 }
 
+#pragma mark - Tests - disconnectWithCallback:
+
 // Verifies disconnect calls delegate disconnect method with no errors if access token is present.
 - (void)testDisconnect_accessToken {
   [[[_authorization expect] andReturn:_authState] authState];
   [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
   [[[_tokenResponse expect] andReturn:kAccessToken] accessToken];
   [[[_authorization expect] andReturn:_fetcherService] fetcherService];
-  OCMockObject *mockDelegate = OCMStrictProtocolMock(@protocol(GIDSignInDelegate));
-  _signIn.delegate = (id <GIDSignInDelegate>)mockDelegate;
-  [_signIn disconnect];
-  [mockDelegate verify];
-  [self verifyAndRevokeToken:kAccessToken delegate:mockDelegate];
+  XCTestExpectation *expectation =
+      [self expectationWithDescription:@"Callback called with nil user and nil error"];
+  [_signIn disconnectWithCallback:^(GIDGoogleUser * _Nullable user, NSError * _Nullable error) {
+    if (user == nil && error == nil) {
+      [expectation fulfill];
+    }
+  }];
+  [self verifyAndRevokeToken:kAccessToken];
   [_authorization verify];
   [_authState verify];
   [_tokenResponse verify];
@@ -617,11 +622,14 @@ static void *kTestObserverContext = &kTestObserverContext;
   [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
   [[[_tokenResponse expect] andReturn:kRefreshToken] refreshToken];
   [[[_authorization expect] andReturn:_fetcherService] fetcherService];
-  OCMockObject *mockDelegate = OCMStrictProtocolMock(@protocol(GIDSignInDelegate));
-  _signIn.delegate = (id <GIDSignInDelegate>)mockDelegate;
-  [_signIn disconnect];
-  [mockDelegate verify];
-  [self verifyAndRevokeToken:kRefreshToken delegate:mockDelegate];
+  XCTestExpectation *expectation =
+      [self expectationWithDescription:@"Callback called with nil user and nil error"];
+  [_signIn disconnectWithCallback:^(GIDGoogleUser * _Nullable user, NSError * _Nullable error) {
+    if (user == nil && error == nil) {
+      [expectation fulfill];
+    }
+  }];
+  [self verifyAndRevokeToken:kRefreshToken];
   [_authorization verify];
   [_authState verify];
   [_tokenResponse verify];
@@ -633,16 +641,18 @@ static void *kTestObserverContext = &kTestObserverContext;
   [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
   [[[_tokenResponse expect] andReturn:kAccessToken] accessToken];
   [[[_authorization expect] andReturn:_fetcherService] fetcherService];
-  OCMockObject *mockDelegate = OCMStrictProtocolMock(@protocol(GIDSignInDelegate));
-  _signIn.delegate = (id <GIDSignInDelegate>)mockDelegate;
-  [_signIn disconnect];
-  [mockDelegate verify];
+  XCTestExpectation *expectation =
+      [self expectationWithDescription:@"Callback called with nil user and an error"];
+  [_signIn disconnectWithCallback:^(GIDGoogleUser * _Nullable user, NSError * _Nullable error) {
+    if (user == nil && error != nil) {
+      [expectation fulfill];
+    }
+  }];
   XCTAssertTrue([self isFetcherStarted], @"should start fetching");
   // Emulate result back from server.
   NSError *error = [self error];
-  [[mockDelegate expect] signIn:_signIn didDisconnectWithUser:nil withError:error];
   [self didFetch:nil error:error];
-  [mockDelegate verify];
+  [self waitForExpectationsWithTimeout:1 handler:nil];
   [_authorization verify];
   [_authState verify];
   [_tokenResponse verify];
@@ -656,13 +666,14 @@ static void *kTestObserverContext = &kTestObserverContext;
   [[[_tokenResponse expect] andReturn:nil] accessToken];
   [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
   [[[_tokenResponse expect] andReturn:nil] refreshToken];
-  OCMockObject *mockDelegate = OCMStrictProtocolMock(@protocol(GIDSignInDelegate));
-  [[mockDelegate expect] signIn:_signIn
-          didDisconnectWithUser:nil
-                      withError:nil];
-  _signIn.delegate = (id <GIDSignInDelegate>)mockDelegate;
-  [_signIn disconnect];
-  [mockDelegate verify];
+  XCTestExpectation *expectation =
+      [self expectationWithDescription:@"Callback called with nil user and nil error"];
+  [_signIn disconnectWithCallback:^(GIDGoogleUser * _Nullable user, NSError * _Nullable error) {
+    if (user == nil && error == nil) {
+      [expectation fulfill];
+    }
+  }];
+  [self waitForExpectationsWithTimeout:1 handler:nil];
   XCTAssertFalse([self isFetcherStarted], @"should not fetch");
   XCTAssertTrue(_keychainRemoved, @"keychain should be removed");
   [_authorization verify];
@@ -722,48 +733,6 @@ static void *kTestObserverContext = &kTestObserverContext;
   XCTAssertThrows([_signIn assertValidPresentingViewContoller]);
 }
 
-#pragma mark - Tests - GIDSignInDelegate
-
-- (void)testDisconnectWithUserWithoutAuth {
-  [[[_authorization expect] andReturn:_authState] authState];
-  [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
-  [[[_tokenResponse expect] andReturn:nil] accessToken];
-  [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
-  [[[_tokenResponse expect] andReturn:nil] refreshToken];
-  id signInDelegateMock = OCMStrictProtocolMock(@protocol(GIDSignInDelegate));
-  [[signInDelegateMock expect] signIn:[OCMArg any]
-                didDisconnectWithUser:[OCMArg any]
-                            withError:[OCMArg isNil]];
-
-  _signIn.delegate = signInDelegateMock;
-  [_signIn disconnect];
-
-  [_authorization verify];
-  [_authState verify];
-  [_tokenResponse verify];
-  [signInDelegateMock verify];
-}
-
-- (void)testDisconnectWithUserWithAccessToken {
-  [[[_authorization expect] andReturn:_authState] authState];
-  [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
-  [[[_tokenResponse expect] andReturn:kAccessToken] accessToken];
-  [[[_authorization expect] andReturn:_fetcherService] fetcherService];
-  id signInDelegateMock = OCMStrictProtocolMock(@protocol(GIDSignInDelegate));
-  [[signInDelegateMock expect] signIn:[OCMArg any]
-                didDisconnectWithUser:[OCMArg any]
-                            withError:[OCMArg isNil]];
-
-  _signIn.delegate = signInDelegateMock;
-  [_signIn disconnect];
-  [_fetcherService.fetchers[0] didFinishWithData:nil error:nil];
-
-  [_authorization verify];
-  [_authState verify];
-  [_tokenResponse verify];
-  [signInDelegateMock verify];
-}
-
 #pragma mark - Restarting Authentication Tests
 
 // Verifies that URL is not handled if there is no pending sign-in
@@ -941,7 +910,7 @@ static void *kTestObserverContext = &kTestObserverContext;
 }
 
 // Verifies a fetcher has started for revoking token and emulates a server response.
-- (void)verifyAndRevokeToken:(NSString *)token delegate:(OCMockObject *)mockDelegate {
+- (void)verifyAndRevokeToken:(NSString *)token {
   XCTAssertTrue([self isFetcherStarted], @"should start fetching");
   NSURL *url = [self fetchedURL];
   XCTAssertEqualObjects([url scheme], @"https", @"scheme must match");
@@ -952,9 +921,8 @@ static void *kTestObserverContext = &kTestObserverContext;
   XCTAssertEqualObjects([params valueForKey:@"token"], token,
                         @"token parameter should match");
   // Emulate result back from server.
-  [[mockDelegate expect] signIn:_signIn didDisconnectWithUser:nil withError:nil];
   [self didFetch:nil error:nil];
-  [mockDelegate verify];
+  [self waitForExpectationsWithTimeout:1 handler:nil];
   XCTAssertTrue(_keychainRemoved, @"should clear saved keychain name");
 }
 

+ 11 - 13
Sample/Source/SignInViewController.m

@@ -151,18 +151,6 @@ static NSString *const kCredentialsButtonAccessibilityIdentifier = @"Credentials
   [self updateButtons];
 }
 
-- (void)signIn:(GIDSignIn *)signIn
-    didDisconnectWithUser:(GIDGoogleUser *)user
-                withError:(NSError *)error {
-  if (error) {
-    _signInAuthStatus.text = [NSString stringWithFormat:@"Status: Failed to disconnect: %@", error];
-  } else {
-    _signInAuthStatus.text = [NSString stringWithFormat:@"Status: Disconnected"];
-  }
-  [self reportAuthStatus];
-  [self updateButtons];
-}
-
 - (void)presentSignInViewController:(UIViewController *)viewController {
   [[self navigationController] pushViewController:viewController animated:YES];
 }
@@ -295,7 +283,17 @@ static NSString *const kCredentialsButtonAccessibilityIdentifier = @"Credentials
 }
 
 - (IBAction)disconnect:(id)sender {
-  [[GIDSignIn sharedInstance] disconnect];
+  [[GIDSignIn sharedInstance] disconnectWithCallback:^(GIDGoogleUser * _Nullable user,
+                                                       NSError * _Nullable error) {
+    if (error) {
+      self->_signInAuthStatus.text = [NSString stringWithFormat:@"Status: Failed to disconnect: %@",
+                                      error];
+    } else {
+      self->_signInAuthStatus.text = [NSString stringWithFormat:@"Status: Disconnected"];
+    }
+    [self reportAuthStatus];
+    [self updateButtons];
+  }];
 }
 
 - (IBAction)showAuthInspector:(id)sender {