blob: 288ec326040c5e5768bfe75bba3173c90ca83aca [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/macros.h"
#include "base/strings/stringprintf.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/sync/test/integration/bookmarks_helper.h"
#include "chrome/browser/sync/test/integration/profile_sync_service_harness.h"
#include "chrome/browser/sync/test/integration/single_client_status_change_checker.h"
#include "chrome/browser/sync/test/integration/sync_test.h"
#include "chrome/browser/sync/test/integration/updated_progress_marker_checker.h"
#include "chrome/common/pref_names.h"
#include "components/bookmarks/browser/bookmark_node.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/identity_test_utils.h"
#include "components/sync/driver/profile_sync_service.h"
#include "components/sync/driver/sync_driver_switches.h"
#include "components/sync/driver/sync_token_status.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "net/http/http_status_code.h"
#include "net/url_request/url_request_status.h"
namespace {
constexpr char kShortLivedOAuth2Token[] = R"(
{
"refresh_token": "short_lived_refresh_token",
"access_token": "short_lived_access_token",
"expires_in": 5, // 5 seconds.
"token_type": "Bearer"
})";
constexpr char kValidOAuth2Token[] = R"({
"refresh_token": "new_refresh_token",
"access_token": "new_access_token",
"expires_in": 3600, // 1 hour.
"token_type": "Bearer"
})";
constexpr char kInvalidGrantOAuth2Token[] = R"({
"error": "invalid_grant"
})";
constexpr char kInvalidClientOAuth2Token[] = R"({
"error": "invalid_client"
})";
constexpr char kEmptyOAuth2Token[] = "";
constexpr char kMalformedOAuth2Token[] = R"({ "foo": )";
bool HasUserPrefValue(const PrefService* pref_service,
const std::string& pref) {
return pref_service->GetUserPrefValue(pref) != nullptr;
}
// Waits until local changes are committed or an auth error is encountered.
class TestForAuthError : public UpdatedProgressMarkerChecker {
public:
explicit TestForAuthError(syncer::ProfileSyncService* service)
: UpdatedProgressMarkerChecker(service) {}
// StatusChangeChecker implementation.
bool IsExitConditionSatisfied() override {
// Note: This is quite fragile. It relies on Sync trying to fetch a new
// access token, even though it might already be in a persistent auth error
// state.
return (service()
->GetSyncTokenStatusForDebugging()
.last_get_token_error.state() !=
GoogleServiceAuthError::NONE) ||
UpdatedProgressMarkerChecker::IsExitConditionSatisfied();
}
std::string GetDebugMessage() const override {
return "Waiting for auth error";
}
};
class SyncTransportActiveChecker : public SingleClientStatusChangeChecker {
public:
explicit SyncTransportActiveChecker(syncer::ProfileSyncService* service)
: SingleClientStatusChangeChecker(service) {}
// StatusChangeChecker implementation.
bool IsExitConditionSatisfied() override {
return service()->GetTransportState() ==
syncer::SyncService::TransportState::ACTIVE;
}
std::string GetDebugMessage() const override { return "Sync Active"; }
};
class SyncAuthTest : public SyncTest {
public:
SyncAuthTest() : SyncTest(SINGLE_CLIENT), bookmark_index_(0) {}
~SyncAuthTest() override {}
// Helper function that adds a bookmark and waits for either an auth error, or
// for the bookmark to be committed. Returns true if it detects an auth
// error, false if the bookmark is committed successfully.
bool AttemptToTriggerAuthError() {
int bookmark_index = GetNextBookmarkIndex();
std::string title = base::StringPrintf("Bookmark %d", bookmark_index);
GURL url = GURL(base::StringPrintf("http://www.foo%d.com", bookmark_index));
EXPECT_NE(nullptr, bookmarks_helper::AddURL(0, title, url));
// Run until the bookmark is committed or an auth error is encountered.
TestForAuthError(GetSyncService(0)).Wait();
GoogleServiceAuthError oauth_error = GetSyncService(0)
->GetSyncTokenStatusForDebugging()
.last_get_token_error;
return oauth_error.state() != GoogleServiceAuthError::NONE;
}
void DisableTokenFetchRetries() {
// If ProfileSyncService observes a transient error like SERVICE_UNAVAILABLE
// or CONNECTION_FAILED, this means the access token fetcher has given
// up trying to reach Gaia. In practice, the access token fetching code
// retries a fixed number of times, but the count is transparent to PSS.
// Disable retries so that we instantly trigger the case where
// ProfileSyncService must pick up where the access token fetcher left off
// (in terms of retries).
signin::DisableAccessTokenFetchRetries(
IdentityManagerFactory::GetForProfile(GetProfile(0)));
}
private:
int GetNextBookmarkIndex() {
return bookmark_index_++;
}
int bookmark_index_;
DISALLOW_COPY_AND_ASSIGN(SyncAuthTest);
};
// Verify that sync works with a valid OAuth2 token.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, Sanity) {
ASSERT_TRUE(SetupSync());
GetFakeServer()->ClearHttpError();
DisableTokenFetchRetries();
SetOAuth2TokenResponse(kValidOAuth2Token,
net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
ASSERT_FALSE(AttemptToTriggerAuthError());
}
// Verify that ProfileSyncService continues trying to fetch access tokens
// when the access token fetcher has encountered more than a fixed number of
// HTTP_INTERNAL_SERVER_ERROR (500) errors.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnInternalServerError500) {
ASSERT_TRUE(SetupSync());
ASSERT_FALSE(AttemptToTriggerAuthError());
GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);
DisableTokenFetchRetries();
SetOAuth2TokenResponse(kValidOAuth2Token,
net::HTTP_INTERNAL_SERVER_ERROR,
net::URLRequestStatus::SUCCESS);
ASSERT_TRUE(AttemptToTriggerAuthError());
ASSERT_TRUE(GetSyncService(0)->IsRetryingAccessTokenFetchForTest());
}
// Verify that ProfileSyncService continues trying to fetch access tokens
// when the access token fetcher has encountered more than a fixed number of
// HTTP_FORBIDDEN (403) errors.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnHttpForbidden403) {
ASSERT_TRUE(SetupSync());
ASSERT_FALSE(AttemptToTriggerAuthError());
GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);
DisableTokenFetchRetries();
SetOAuth2TokenResponse(kEmptyOAuth2Token,
net::HTTP_FORBIDDEN,
net::URLRequestStatus::SUCCESS);
ASSERT_TRUE(AttemptToTriggerAuthError());
ASSERT_TRUE(GetSyncService(0)->IsRetryingAccessTokenFetchForTest());
}
// Verify that ProfileSyncService continues trying to fetch access tokens
// when the access token fetcher has encountered a URLRequestStatus of FAILED.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnRequestFailed) {
ASSERT_TRUE(SetupSync());
ASSERT_FALSE(AttemptToTriggerAuthError());
GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);
DisableTokenFetchRetries();
SetOAuth2TokenResponse(kEmptyOAuth2Token,
net::HTTP_INTERNAL_SERVER_ERROR,
net::URLRequestStatus::FAILED);
ASSERT_TRUE(AttemptToTriggerAuthError());
ASSERT_TRUE(GetSyncService(0)->IsRetryingAccessTokenFetchForTest());
}
// Verify that ProfileSyncService continues trying to fetch access tokens
// when the access token fetcher receives a malformed token.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnMalformedToken) {
ASSERT_TRUE(SetupSync());
ASSERT_FALSE(AttemptToTriggerAuthError());
GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);
DisableTokenFetchRetries();
SetOAuth2TokenResponse(kMalformedOAuth2Token,
net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
ASSERT_TRUE(AttemptToTriggerAuthError());
ASSERT_TRUE(GetSyncService(0)->IsRetryingAccessTokenFetchForTest());
}
// Verify that ProfileSyncService ends up with an INVALID_GAIA_CREDENTIALS auth
// error when an invalid_grant error is returned by the access token fetcher
// with an HTTP_BAD_REQUEST (400) response code.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, InvalidGrant) {
ASSERT_TRUE(SetupSync());
ASSERT_FALSE(AttemptToTriggerAuthError());
GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);
DisableTokenFetchRetries();
SetOAuth2TokenResponse(kInvalidGrantOAuth2Token,
net::HTTP_BAD_REQUEST,
net::URLRequestStatus::SUCCESS);
ASSERT_TRUE(AttemptToTriggerAuthError());
ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
GetSyncService(0)->GetAuthError().state());
}
// Verify that ProfileSyncService retries after SERVICE_ERROR auth error when
// an invalid_client error is returned by the access token fetcher with an
// HTTP_BAD_REQUEST (400) response code.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryInvalidClient) {
ASSERT_TRUE(SetupSync());
ASSERT_FALSE(AttemptToTriggerAuthError());
GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);
DisableTokenFetchRetries();
SetOAuth2TokenResponse(kInvalidClientOAuth2Token,
net::HTTP_BAD_REQUEST,
net::URLRequestStatus::SUCCESS);
ASSERT_TRUE(AttemptToTriggerAuthError());
ASSERT_TRUE(GetSyncService(0)->IsRetryingAccessTokenFetchForTest());
}
// Verify that ProfileSyncService retries after REQUEST_CANCELED auth error
// when the access token fetcher has encountered a URLRequestStatus of
// CANCELED.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryRequestCanceled) {
ASSERT_TRUE(SetupSync());
ASSERT_FALSE(AttemptToTriggerAuthError());
GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);
DisableTokenFetchRetries();
SetOAuth2TokenResponse(kEmptyOAuth2Token,
net::HTTP_INTERNAL_SERVER_ERROR,
net::URLRequestStatus::CANCELED);
ASSERT_TRUE(AttemptToTriggerAuthError());
ASSERT_TRUE(GetSyncService(0)->IsRetryingAccessTokenFetchForTest());
}
// Verify that ProfileSyncService fails initial sync setup during backend
// initialization and ends up with an INVALID_GAIA_CREDENTIALS auth error when
// an invalid_grant error is returned by the access token fetcher with an
// HTTP_BAD_REQUEST (400) response code.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, FailInitialSetupWithPersistentError) {
ASSERT_TRUE(SetupClients());
GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);
DisableTokenFetchRetries();
SetOAuth2TokenResponse(kInvalidGrantOAuth2Token,
net::HTTP_BAD_REQUEST,
net::URLRequestStatus::SUCCESS);
ASSERT_FALSE(GetClient(0)->SetupSync());
ASSERT_FALSE(GetSyncService(0)->IsSyncFeatureActive());
ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
GetSyncService(0)->GetAuthError().state());
}
// Verify that ProfileSyncService fails initial sync setup during backend
// initialization, but continues trying to fetch access tokens when
// the access token fetcher receives an HTTP_INTERNAL_SERVER_ERROR (500)
// response code.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryInitialSetupWithTransientError) {
ASSERT_TRUE(SetupClients());
GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);
DisableTokenFetchRetries();
SetOAuth2TokenResponse(kEmptyOAuth2Token,
net::HTTP_INTERNAL_SERVER_ERROR,
net::URLRequestStatus::SUCCESS);
ASSERT_FALSE(GetClient(0)->SetupSync());
ASSERT_FALSE(GetSyncService(0)->IsSyncFeatureActive());
ASSERT_TRUE(GetSyncService(0)->IsRetryingAccessTokenFetchForTest());
}
// Verify that ProfileSyncService fetches a new token when an old token expires.
// Disabled due to flakiness: https://crbug.com/860200
IN_PROC_BROWSER_TEST_F(SyncAuthTest, DISABLED_TokenExpiry) {
// Initial sync succeeds with a short lived OAuth2 Token.
ASSERT_TRUE(SetupClients());
GetFakeServer()->ClearHttpError();
DisableTokenFetchRetries();
SetOAuth2TokenResponse(kShortLivedOAuth2Token,
net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
ASSERT_TRUE(GetClient(0)->SetupSync());
std::string old_token = GetSyncService(0)->GetAccessTokenForTest();
// Wait until the token has expired.
base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(5));
// Trigger an auth error on the server so PSS requests OA2TS for a new token
// during the next sync cycle.
GetFakeServer()->SetHttpError(net::HTTP_UNAUTHORIZED);
SetOAuth2TokenResponse(kEmptyOAuth2Token,
net::HTTP_INTERNAL_SERVER_ERROR,
net::URLRequestStatus::SUCCESS);
ASSERT_TRUE(AttemptToTriggerAuthError());
ASSERT_TRUE(GetSyncService(0)->IsRetryingAccessTokenFetchForTest());
// Trigger an auth success state and set up a new valid OAuth2 token.
GetFakeServer()->ClearHttpError();
SetOAuth2TokenResponse(kValidOAuth2Token,
net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
// Verify that the next sync cycle is successful, and uses the new auth token.
ASSERT_TRUE(UpdatedProgressMarkerChecker(GetSyncService(0)).Wait());
std::string new_token = GetSyncService(0)->GetAccessTokenForTest();
ASSERT_NE(old_token, new_token);
}
class NoAuthErrorChecker : public SingleClientStatusChangeChecker {
public:
explicit NoAuthErrorChecker(syncer::ProfileSyncService* service)
: SingleClientStatusChangeChecker(service) {}
// StatusChangeChecker implementation.
bool IsExitConditionSatisfied() override {
return service()->GetAuthError().state() == GoogleServiceAuthError::NONE;
}
std::string GetDebugMessage() const override {
return "Waiting for auth error to be cleared";
}
};
IN_PROC_BROWSER_TEST_F(SyncAuthTest, SyncPausedState) {
ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
ASSERT_TRUE(GetSyncService(0)->IsSyncFeatureActive());
ASSERT_EQ(GetSyncService(0)->GetTransportState(),
syncer::SyncService::TransportState::ACTIVE);
const syncer::ModelTypeSet active_types =
GetSyncService(0)->GetActiveDataTypes();
ASSERT_FALSE(active_types.Empty());
// Enter the "Sync paused" state.
GetClient(0)->EnterSyncPausedStateForPrimaryAccount();
ASSERT_TRUE(GetSyncService(0)->GetAuthError().IsPersistentError());
if (base::FeatureList::IsEnabled(switches::kStopSyncInPausedState)) {
// Sync should have shut itself down.
EXPECT_EQ(GetSyncService(0)->GetTransportState(),
syncer::SyncService::TransportState::DISABLED);
EXPECT_TRUE(GetSyncService(0)->HasDisableReason(
syncer::SyncService::DISABLE_REASON_PAUSED));
} else {
ASSERT_TRUE(AttemptToTriggerAuthError());
// Pausing sync may issue a reconfiguration, so wait until it finishes.
SyncTransportActiveChecker(GetSyncService(0)).Wait();
}
// The active data types should now be empty.
EXPECT_TRUE(GetSyncService(0)->GetActiveDataTypes().Empty());
// Clear the "Sync paused" state again.
GetClient(0)->ExitSyncPausedStateForPrimaryAccount();
// SyncService will clear its auth error state only once it gets a valid
// access token again, so wait for that to happen.
NoAuthErrorChecker(GetSyncService(0)).Wait();
ASSERT_FALSE(GetSyncService(0)->GetAuthError().IsPersistentError());
if (base::FeatureList::IsEnabled(switches::kStopSyncInPausedState)) {
// Once the auth error is gone, wait for Sync to start up again.
GetClient(0)->AwaitSyncSetupCompletion();
} else {
// Resuming sync could issue a reconfiguration, so wait until it finishes.
SyncTransportActiveChecker(GetSyncService(0)).Wait();
}
// Now the active data types should be back.
EXPECT_TRUE(GetSyncService(0)->IsSyncFeatureActive());
EXPECT_EQ(GetSyncService(0)->GetActiveDataTypes(), active_types);
}
IN_PROC_BROWSER_TEST_F(SyncAuthTest, ShouldTrackDeletionsInSyncPausedState) {
ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
ASSERT_TRUE(GetSyncService(0)->IsSyncFeatureActive());
ASSERT_EQ(GetSyncService(0)->GetTransportState(),
syncer::SyncService::TransportState::ACTIVE);
// USS type.
ASSERT_TRUE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::BOOKMARKS));
// Pseudo-USS type.
ASSERT_TRUE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::PREFERENCES));
const GURL kTestURL("http://mail.google.com");
PrefService* pref_service = GetProfile(0)->GetPrefs();
// Create a bookmark...
const bookmarks::BookmarkNode* bar = bookmarks_helper::GetBookmarkBarNode(0);
ASSERT_FALSE(bookmarks_helper::HasNodeWithURL(0, kTestURL));
const bookmarks::BookmarkNode* bookmark = bookmarks_helper::AddURL(
0, bar, bar->children().size(), "Title", kTestURL);
// ...set a pref...
ASSERT_FALSE(HasUserPrefValue(pref_service, prefs::kHomePageIsNewTabPage));
pref_service->SetBoolean(prefs::kHomePageIsNewTabPage, true);
// ...and wait for both to be synced up.
UpdatedProgressMarkerChecker(GetSyncService(0)).Wait();
// Enter the "Sync paused" state.
GetClient(0)->EnterSyncPausedStateForPrimaryAccount();
ASSERT_TRUE(GetSyncService(0)->GetAuthError().IsPersistentError());
if (base::FeatureList::IsEnabled(switches::kStopSyncInPausedState)) {
// Sync should have shut itself down.
EXPECT_EQ(GetSyncService(0)->GetTransportState(),
syncer::SyncService::TransportState::DISABLED);
EXPECT_TRUE(GetSyncService(0)->HasDisableReason(
syncer::SyncService::DISABLE_REASON_PAUSED));
} else {
ASSERT_TRUE(AttemptToTriggerAuthError());
// Pausing sync may issue a reconfiguration, so wait until it finishes.
SyncTransportActiveChecker(GetSyncService(0)).Wait();
}
ASSERT_FALSE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::BOOKMARKS));
ASSERT_FALSE(
GetSyncService(0)->GetActiveDataTypes().Has(syncer::PREFERENCES));
// Delete the bookmark and the pref.
// Note that AttemptToTriggerAuthError() also creates bookmarks, so the index
// of our test bookmark might have changed.
ASSERT_EQ(bar->children().back().get(), bookmark);
bookmarks_helper::Remove(0, bar, bar->children().size() - 1);
ASSERT_FALSE(bookmarks_helper::HasNodeWithURL(0, kTestURL));
pref_service->ClearPref(prefs::kHomePageIsNewTabPage);
// Clear the "Sync paused" state again.
GetClient(0)->ExitSyncPausedStateForPrimaryAccount();
// SyncService will clear its auth error state only once it gets a valid
// access token again, so wait for that to happen.
NoAuthErrorChecker(GetSyncService(0)).Wait();
ASSERT_FALSE(GetSyncService(0)->GetAuthError().IsPersistentError());
if (base::FeatureList::IsEnabled(switches::kStopSyncInPausedState)) {
// Once the auth error is gone, wait for Sync to start up again.
GetClient(0)->AwaitSyncSetupCompletion();
} else {
// Resuming sync could issue a reconfiguration, so wait until it finishes.
SyncTransportActiveChecker(GetSyncService(0)).Wait();
}
// Resuming sync could issue a reconfiguration, so wait until it finishes.
SyncTransportActiveChecker(GetSyncService(0)).Wait();
ASSERT_TRUE(GetSyncService(0)->IsSyncFeatureActive());
// Resuming sync should *not* have re-created the deleted items.
EXPECT_FALSE(bookmarks_helper::HasNodeWithURL(0, kTestURL));
EXPECT_FALSE(HasUserPrefValue(pref_service, prefs::kHomePageIsNewTabPage));
}
} // namespace