// 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); }