Identity Service: Have GetPrimaryAccountWhenAvailable() check refresh token
https://chromium-review.googlesource.com/539637 introduced the
Identity Service GetPrimaryAccountWhenAvailable() method and changed the
identity extension API implementation to use it. However, that CL
inadvertently introduced a semantic change in the identity extension API
implementation in the case where the user was signed in with their refresh
token in an error state. Previously, the implementation would start a
signin flow and wait for a notification that a refresh token was available.
Crucially, this notification would come in only when a *new* refresh token
was available. GetPrimaryAccountWhenAvailable(), by contrast, returned
immediately if the user had a refresh token available (even if that
refresh token was in an error state). Due to a separate bug in the
identity API extension implementation itself, the net effect of this
semantic change is that the identity extension API implementation would loop
forever trying new requests and then going through the signin flow again
when those requests inevitably failed (because they were all being made
with the same bad refresh token).
This CL changes the semantics of the Identity Services'
GetPrimaryAccountWhenAvailable() method: it returns only when the user
has a primary account and that primary account has a refresh token *in a
non-error state.* This change, which better reflects the spirit of the
method, also restores the previous behavior of the identity extension API
implementation. Note that as the identity extension API implementation is
currently the only caller of this method in the codebase, this change
will have no other effects.
To test this change, do the following:
- Install an extension with the identity API in its manifest.
- Sign in to Chrome.
- Go to accounts.google.com and revoke your refresh token for Chrome there.
- Ensure that your account in Chrome goes into an error state (verify
that a red exclamation point appears in the top right by your account name).
- Go to chrome://extensions, enable developer mode, and inspect the background
page of the above app. At the JS console that that brings up, execute:
chrome.identity.getAuthToken({interactive: true}, (token) => {console.log(token);} )
- Verify that you can close the signin tab that then opens and continue
using the browser as normal.
- Sign in to Chrome.
- Verify that at the JS console referenced above a token gets printed.
This CL also adds a unittest that fails without the production change. As a
followup, I am going to investigate adding browsertests of the Chrome Identity
extension API that also would have failed before this change.
Bug: 772122
Change-Id: I544406684de1945b51807acac303bb667363615a
Reviewed-on: https://chromium-review.googlesource.com/704697
Reviewed-by: Mike West <[email protected]>
Reviewed-by: Mihai Sardarescu <[email protected]>
Commit-Queue: Colin Blundell (at a convergence, slow until Oct 16) <[email protected]>
Cr-Commit-Position: refs/heads/master@{#507394}
diff --git a/services/identity/identity_manager_unittest.cc b/services/identity/identity_manager_unittest.cc
index 1cbc78f..c6f7b43e 100644
--- a/services/identity/identity_manager_unittest.cc
+++ b/services/identity/identity_manager_unittest.cc
@@ -10,6 +10,7 @@
#include "components/signin/core/browser/fake_signin_manager.h"
#include "components/signin/core/browser/test_signin_client.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "google_apis/gaia/fake_oauth2_token_service_delegate.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "services/identity/identity_service.h"
#include "services/identity/public/cpp/account_state.h"
@@ -539,6 +540,49 @@
EXPECT_TRUE(account_state2.is_primary_account);
}
+// Check that GetPrimaryAccountWhenAvailable() doesn't return the account as
+// available if the refresh token has an auth error.
+TEST_F(IdentityManagerTest,
+ GetPrimaryAccountWhenAvailableRefreshTokenHasAuthError) {
+ signin_manager()->SetAuthenticatedAccountInfo(kTestGaiaId, kTestEmail);
+ token_service()->UpdateCredentials(
+ signin_manager()->GetAuthenticatedAccountId(), kTestRefreshToken);
+ FakeOAuth2TokenServiceDelegate* delegate =
+ static_cast<FakeOAuth2TokenServiceDelegate*>(
+ token_service()->GetDelegate());
+ delegate->SetLastErrorForAccount(
+ signin_manager()->GetAuthenticatedAccountId(),
+ GoogleServiceAuthError(
+ GoogleServiceAuthError::State::INVALID_GAIA_CREDENTIALS));
+
+ AccountInfo account_info;
+ AccountState account_state;
+ base::RunLoop run_loop;
+ GetIdentityManager()->GetPrimaryAccountWhenAvailable(base::Bind(
+ &IdentityManagerTest::OnPrimaryAccountAvailable, base::Unretained(this),
+ run_loop.QuitClosure(), base::Unretained(&account_info),
+ base::Unretained(&account_state)));
+
+ // Flush the Identity Manager and check that the callback didn't fire.
+ FlushIdentityManagerForTesting();
+ EXPECT_TRUE(account_info.account_id.empty());
+
+ // Clear the auth error, update credentials, and check that the callback
+ // fires.
+ delegate->SetLastErrorForAccount(
+ signin_manager()->GetAuthenticatedAccountId(), GoogleServiceAuthError());
+ token_service()->UpdateCredentials(
+ signin_manager()->GetAuthenticatedAccountId(), kTestRefreshToken);
+ run_loop.Run();
+
+ EXPECT_EQ(signin_manager()->GetAuthenticatedAccountId(),
+ account_info.account_id);
+ EXPECT_EQ(kTestGaiaId, account_info.gaia);
+ EXPECT_EQ(kTestEmail, account_info.email);
+ EXPECT_TRUE(account_state.has_refresh_token);
+ EXPECT_TRUE(account_state.is_primary_account);
+}
+
// Check that the account info for a given GAIA ID is null if that GAIA ID is
// unknown.
TEST_F(IdentityManagerTest, GetAccountInfoForUnknownGaiaID) {