blob: 83d1f91b739eb405ce381412531cfc6e1a2f5d44 [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/strings/stringprintf.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "chrome/browser/signin/profile_oauth2_token_service.h"
#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
#include "chrome/browser/sync/profile_sync_service.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/sync_test.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"
using bookmarks_helper::AddURL;
const char kShortLivedOAuth2Token[] =
"{"
" \"refresh_token\": \"short_lived_refresh_token\","
" \"access_token\": \"short_lived_access_token\","
" \"expires_in\": 5," // 5 seconds.
" \"token_type\": \"Bearer\""
"}";
const char kValidOAuth2Token[] = "{"
" \"refresh_token\": \"new_refresh_token\","
" \"access_token\": \"new_access_token\","
" \"expires_in\": 3600," // 1 hour.
" \"token_type\": \"Bearer\""
"}";
const char kInvalidGrantOAuth2Token[] = "{"
" \"error\": \"invalid_grant\""
"}";
const char kInvalidClientOAuth2Token[] = "{"
" \"error\": \"invalid_client\""
"}";
const char kEmptyOAuth2Token[] = "";
const char kMalformedOAuth2Token[] = "{ \"foo\": ";
class SyncAuthTest : public SyncTest {
public:
SyncAuthTest() : SyncTest(SINGLE_CLIENT) {}
virtual ~SyncAuthTest() {}
// Helper function that adds a bookmark with index |bookmark_index| and waits
// for sync to complete. The return value indicates whether the sync cycle
// completed successfully (true) or encountered an auth error (false).
bool AddBookmarkAndWaitForSync(int bookmark_index) {
std::wstring title = base::StringPrintf(L"Bookmark %d", bookmark_index);
GURL url = GURL(base::StringPrintf("http://www.foo%d.com", bookmark_index));
EXPECT_TRUE(AddURL(0, title, url) != NULL);
return GetClient(0)->AwaitFullSyncCompletion();
}
// Sets the authenticated state of the python sync server to |auth_state| and
// sets the canned response that will be returned to the OAuth2TokenService
// when it tries to fetch an access token.
void SetAuthStateAndTokenResponse(PythonServerAuthState auth_state,
const std::string& response_data,
net::HttpStatusCode response_code,
net::URLRequestStatus::Status status) {
TriggerAuthState(auth_state);
// If ProfileSyncService observes a transient error like SERVICE_UNAVAILABLE
// or CONNECTION_FAILED, this means the OAuth2TokenService has given up
// trying to reach Gaia. In practice, OA2TS retries a fixed number of times,
// but the count is transparent to PSS.
// Override the max retry count in TokenService so that we instantly trigger
// the case where ProfileSyncService must pick up where OAuth2TokenService
// left off (in terms of retries).
ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile(0))->
set_max_authorization_token_fetch_retries_for_testing(0);
SetOAuth2TokenResponse(response_data, response_code, status);
}
private:
DISALLOW_COPY_AND_ASSIGN(SyncAuthTest);
};
// Verify that sync works with a valid OAuth2 token.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, Sanity) {
ASSERT_TRUE(SetupSync());
SetAuthStateAndTokenResponse(AUTHENTICATED_TRUE,
kValidOAuth2Token,
net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
ASSERT_TRUE(AddBookmarkAndWaitForSync(1));
}
// Verify that ProfileSyncService continues trying to fetch access tokens
// when OAuth2TokenService 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_TRUE(AddBookmarkAndWaitForSync(1));
SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
kValidOAuth2Token,
net::HTTP_INTERNAL_SERVER_ERROR,
net::URLRequestStatus::SUCCESS);
ASSERT_FALSE(AddBookmarkAndWaitForSync(2));
ASSERT_TRUE(
GetClient(0)->service()->IsRetryingAccessTokenFetchForTest());
}
// Verify that ProfileSyncService continues trying to fetch access tokens
// when OAuth2TokenService has encountered more than a fixed number of
// HTTP_FORBIDDEN (403) errors.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnHttpForbidden403) {
ASSERT_TRUE(SetupSync());
ASSERT_TRUE(AddBookmarkAndWaitForSync(1));
SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
kEmptyOAuth2Token,
net::HTTP_FORBIDDEN,
net::URLRequestStatus::SUCCESS);
ASSERT_FALSE(AddBookmarkAndWaitForSync(2));
ASSERT_TRUE(
GetClient(0)->service()->IsRetryingAccessTokenFetchForTest());
}
// Verify that ProfileSyncService continues trying to fetch access tokens
// when OAuth2TokenService has encountered a URLRequestStatus of FAILED.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnRequestFailed) {
ASSERT_TRUE(SetupSync());
ASSERT_TRUE(AddBookmarkAndWaitForSync(1));
SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
kEmptyOAuth2Token,
net::HTTP_INTERNAL_SERVER_ERROR,
net::URLRequestStatus::FAILED);
ASSERT_FALSE(AddBookmarkAndWaitForSync(2));
ASSERT_TRUE(
GetClient(0)->service()->IsRetryingAccessTokenFetchForTest());
}
// Verify that ProfileSyncService continues trying to fetch access tokens
// when OAuth2TokenService receives a malformed token.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnMalformedToken) {
ASSERT_TRUE(SetupSync());
ASSERT_TRUE(AddBookmarkAndWaitForSync(1));
SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
kMalformedOAuth2Token,
net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
ASSERT_FALSE(AddBookmarkAndWaitForSync(2));
ASSERT_TRUE(
GetClient(0)->service()->IsRetryingAccessTokenFetchForTest());
}
// Verify that ProfileSyncService ends up with an INVALID_GAIA_CREDENTIALS auth
// error when an invalid_grant error is returned by OAuth2TokenService with an
// HTTP_BAD_REQUEST (400) response code.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, InvalidGrant) {
ASSERT_TRUE(SetupSync());
ASSERT_TRUE(AddBookmarkAndWaitForSync(1));
SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
kInvalidGrantOAuth2Token,
net::HTTP_BAD_REQUEST,
net::URLRequestStatus::SUCCESS);
ASSERT_FALSE(AddBookmarkAndWaitForSync(2));
ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
GetClient(0)->service()->GetAuthError().state());
}
// Verify that ProfileSyncService ends up with an SERVICE_ERROR auth error when
// an invalid_client error is returned by OAuth2TokenService with an
// HTTP_BAD_REQUEST (400) response code.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, InvalidClient) {
ASSERT_TRUE(SetupSync());
ASSERT_TRUE(AddBookmarkAndWaitForSync(1));
SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
kInvalidClientOAuth2Token,
net::HTTP_BAD_REQUEST,
net::URLRequestStatus::SUCCESS);
ASSERT_FALSE(AddBookmarkAndWaitForSync(2));
ASSERT_EQ(GoogleServiceAuthError::SERVICE_ERROR,
GetClient(0)->service()->GetAuthError().state());
}
// Verify that ProfileSyncService ends up with a REQUEST_CANCELED auth error
// when when OAuth2TokenService has encountered a URLRequestStatus of CANCELED.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, RequestCanceled) {
ASSERT_TRUE(SetupSync());
ASSERT_TRUE(AddBookmarkAndWaitForSync(1));
SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
kEmptyOAuth2Token,
net::HTTP_INTERNAL_SERVER_ERROR,
net::URLRequestStatus::CANCELED);
ASSERT_FALSE(AddBookmarkAndWaitForSync(2));
ASSERT_EQ(GoogleServiceAuthError::REQUEST_CANCELED,
GetClient(0)->service()->GetAuthError().state());
}
// 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 OAuth2TokenService with an
// HTTP_BAD_REQUEST (400) response code.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, FailInitialSetupWithPersistentError) {
ASSERT_TRUE(SetupClients());
SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
kInvalidGrantOAuth2Token,
net::HTTP_BAD_REQUEST,
net::URLRequestStatus::SUCCESS);
ASSERT_FALSE(GetClient(0)->SetupSync());
ASSERT_FALSE(GetClient(0)->service()->sync_initialized());
ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
GetClient(0)->service()->GetAuthError().state());
}
// Verify that ProfileSyncService fails initial sync setup during backend
// initialization, but continues trying to fetch access tokens when
// OAuth2TokenService receives an HTTP_INTERNAL_SERVER_ERROR (500) response
// code.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryInitialSetupWithTransientError) {
ASSERT_TRUE(SetupClients());
SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
kEmptyOAuth2Token,
net::HTTP_INTERNAL_SERVER_ERROR,
net::URLRequestStatus::SUCCESS);
ASSERT_FALSE(GetClient(0)->SetupSync());
ASSERT_FALSE(GetClient(0)->service()->sync_initialized());
ASSERT_TRUE(
GetClient(0)->service()->IsRetryingAccessTokenFetchForTest());
}
// Verify that ProfileSyncService fetches a new token when an old token expires.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, TokenExpiry) {
// Initial sync succeeds with a short lived OAuth2 Token.
ASSERT_TRUE(SetupClients());
SetAuthStateAndTokenResponse(AUTHENTICATED_TRUE,
kShortLivedOAuth2Token,
net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
ASSERT_TRUE(GetClient(0)->SetupSync());
std::string old_token = GetClient(0)->service()->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.
SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
kEmptyOAuth2Token,
net::HTTP_INTERNAL_SERVER_ERROR,
net::URLRequestStatus::SUCCESS);
ASSERT_FALSE(AddBookmarkAndWaitForSync(1));
ASSERT_TRUE(
GetClient(0)->service()->IsRetryingAccessTokenFetchForTest());
// Trigger an auth success state and set up a new valid OAuth2 token.
SetAuthStateAndTokenResponse(AUTHENTICATED_TRUE,
kValidOAuth2Token,
net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
// Verify that the next sync cycle is successful, and uses the new auth token.
ASSERT_TRUE(GetClient(0)->AwaitFullSyncCompletion());
std::string new_token = GetClient(0)->service()->GetAccessTokenForTest();
ASSERT_NE(old_token, new_token);
}