sync_auth_test.cc revision e5d81f57cb97b3b6b7fccc9c5610d21eb81db09d
1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "base/strings/stringprintf.h" 6#include "base/threading/platform_thread.h" 7#include "base/time/time.h" 8#include "chrome/browser/signin/profile_oauth2_token_service_factory.h" 9#include "chrome/browser/sync/profile_sync_service.h" 10#include "chrome/browser/sync/test/integration/bookmarks_helper.h" 11#include "chrome/browser/sync/test/integration/profile_sync_service_harness.h" 12#include "chrome/browser/sync/test/integration/single_client_status_change_checker.h" 13#include "chrome/browser/sync/test/integration/sync_integration_test_util.h" 14#include "chrome/browser/sync/test/integration/sync_test.h" 15#include "components/signin/core/browser/profile_oauth2_token_service.h" 16#include "google_apis/gaia/google_service_auth_error.h" 17#include "net/http/http_status_code.h" 18#include "net/url_request/url_request_status.h" 19 20using bookmarks_helper::AddURL; 21using sync_integration_test_util::AwaitCommitActivityCompletion; 22 23const char kShortLivedOAuth2Token[] = 24 "{" 25 " \"refresh_token\": \"short_lived_refresh_token\"," 26 " \"access_token\": \"short_lived_access_token\"," 27 " \"expires_in\": 5," // 5 seconds. 28 " \"token_type\": \"Bearer\"" 29 "}"; 30 31const char kValidOAuth2Token[] = "{" 32 " \"refresh_token\": \"new_refresh_token\"," 33 " \"access_token\": \"new_access_token\"," 34 " \"expires_in\": 3600," // 1 hour. 35 " \"token_type\": \"Bearer\"" 36 "}"; 37 38const char kInvalidGrantOAuth2Token[] = "{" 39 " \"error\": \"invalid_grant\"" 40 "}"; 41 42const char kInvalidClientOAuth2Token[] = "{" 43 " \"error\": \"invalid_client\"" 44 "}"; 45 46const char kEmptyOAuth2Token[] = ""; 47 48const char kMalformedOAuth2Token[] = "{ \"foo\": "; 49 50class TestForAuthError : public SingleClientStatusChangeChecker { 51 public: 52 explicit TestForAuthError(ProfileSyncService* service); 53 virtual ~TestForAuthError(); 54 virtual bool IsExitConditionSatisfied() OVERRIDE; 55 virtual std::string GetDebugMessage() const OVERRIDE; 56}; 57 58TestForAuthError::TestForAuthError(ProfileSyncService* service) 59 : SingleClientStatusChangeChecker(service) {} 60 61TestForAuthError::~TestForAuthError() {} 62 63bool TestForAuthError::IsExitConditionSatisfied() { 64 return !service()->HasUnsyncedItems() || 65 (service()->GetSyncTokenStatus().last_get_token_error.state() != 66 GoogleServiceAuthError::NONE); 67} 68 69std::string TestForAuthError::GetDebugMessage() const { 70 return "Waiting for auth error"; 71} 72 73class SyncAuthTest : public SyncTest { 74 public: 75 // TODO(pvalenzuela): Switch to SINGLE_CLIENT once FakeServer 76 // supports this scenario. 77 SyncAuthTest() : SyncTest(SINGLE_CLIENT_LEGACY), bookmark_index_(0) {} 78 virtual ~SyncAuthTest() {} 79 80 // Helper function that adds a bookmark and waits for either an auth error, or 81 // for the bookmark to be committed. Returns true if it detects an auth 82 // error, false if the bookmark is committed successfully. 83 bool AttemptToTriggerAuthError() { 84 int bookmark_index = GetNextBookmarkIndex(); 85 std::wstring title = base::StringPrintf(L"Bookmark %d", bookmark_index); 86 GURL url = GURL(base::StringPrintf("http://www.foo%d.com", bookmark_index)); 87 EXPECT_TRUE(AddURL(0, title, url) != NULL); 88 89 // Run until the bookmark is committed or an auth error is encountered. 90 TestForAuthError checker_(GetSyncService((0))); 91 checker_.Wait(); 92 93 GoogleServiceAuthError oauth_error = 94 GetSyncService((0))->GetSyncTokenStatus().last_get_token_error; 95 96 return oauth_error.state() != GoogleServiceAuthError::NONE; 97 } 98 99 // Sets the authenticated state of the python sync server to |auth_state| and 100 // sets the canned response that will be returned to the OAuth2TokenService 101 // when it tries to fetch an access token. 102 void SetAuthStateAndTokenResponse(PythonServerAuthState auth_state, 103 const std::string& response_data, 104 net::HttpStatusCode response_code, 105 net::URLRequestStatus::Status status) { 106 TriggerAuthState(auth_state); 107 108 // If ProfileSyncService observes a transient error like SERVICE_UNAVAILABLE 109 // or CONNECTION_FAILED, this means the OAuth2TokenService has given up 110 // trying to reach Gaia. In practice, OA2TS retries a fixed number of times, 111 // but the count is transparent to PSS. 112 // Override the max retry count in TokenService so that we instantly trigger 113 // the case where ProfileSyncService must pick up where OAuth2TokenService 114 // left off (in terms of retries). 115 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile(0))-> 116 set_max_authorization_token_fetch_retries_for_testing(0); 117 118 SetOAuth2TokenResponse(response_data, response_code, status); 119 } 120 121 private: 122 int GetNextBookmarkIndex() { 123 return bookmark_index_++; 124 } 125 126 int bookmark_index_; 127 128 DISALLOW_COPY_AND_ASSIGN(SyncAuthTest); 129}; 130 131// Verify that sync works with a valid OAuth2 token. 132IN_PROC_BROWSER_TEST_F(SyncAuthTest, Sanity) { 133 ASSERT_TRUE(SetupSync()); 134 SetAuthStateAndTokenResponse(AUTHENTICATED_TRUE, 135 kValidOAuth2Token, 136 net::HTTP_OK, 137 net::URLRequestStatus::SUCCESS); 138 ASSERT_FALSE(AttemptToTriggerAuthError()); 139} 140 141// Verify that ProfileSyncService continues trying to fetch access tokens 142// when OAuth2TokenService has encountered more than a fixed number of 143// HTTP_INTERNAL_SERVER_ERROR (500) errors. 144IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnInternalServerError500) { 145 ASSERT_TRUE(SetupSync()); 146 ASSERT_FALSE(AttemptToTriggerAuthError()); 147 SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE, 148 kValidOAuth2Token, 149 net::HTTP_INTERNAL_SERVER_ERROR, 150 net::URLRequestStatus::SUCCESS); 151 ASSERT_TRUE(AttemptToTriggerAuthError()); 152 ASSERT_TRUE( 153 GetSyncService((0))->IsRetryingAccessTokenFetchForTest()); 154} 155 156// Verify that ProfileSyncService continues trying to fetch access tokens 157// when OAuth2TokenService has encountered more than a fixed number of 158// HTTP_FORBIDDEN (403) errors. 159IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnHttpForbidden403) { 160 ASSERT_TRUE(SetupSync()); 161 ASSERT_FALSE(AttemptToTriggerAuthError()); 162 SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE, 163 kEmptyOAuth2Token, 164 net::HTTP_FORBIDDEN, 165 net::URLRequestStatus::SUCCESS); 166 ASSERT_TRUE(AttemptToTriggerAuthError()); 167 ASSERT_TRUE( 168 GetSyncService((0))->IsRetryingAccessTokenFetchForTest()); 169} 170 171// Verify that ProfileSyncService continues trying to fetch access tokens 172// when OAuth2TokenService has encountered a URLRequestStatus of FAILED. 173IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnRequestFailed) { 174 ASSERT_TRUE(SetupSync()); 175 ASSERT_FALSE(AttemptToTriggerAuthError()); 176 SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE, 177 kEmptyOAuth2Token, 178 net::HTTP_INTERNAL_SERVER_ERROR, 179 net::URLRequestStatus::FAILED); 180 ASSERT_TRUE(AttemptToTriggerAuthError()); 181 ASSERT_TRUE( 182 GetSyncService((0))->IsRetryingAccessTokenFetchForTest()); 183} 184 185// Verify that ProfileSyncService continues trying to fetch access tokens 186// when OAuth2TokenService receives a malformed token. 187IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnMalformedToken) { 188 ASSERT_TRUE(SetupSync()); 189 ASSERT_FALSE(AttemptToTriggerAuthError()); 190 SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE, 191 kMalformedOAuth2Token, 192 net::HTTP_OK, 193 net::URLRequestStatus::SUCCESS); 194 ASSERT_TRUE(AttemptToTriggerAuthError()); 195 ASSERT_TRUE( 196 GetSyncService((0))->IsRetryingAccessTokenFetchForTest()); 197} 198 199// Verify that ProfileSyncService ends up with an INVALID_GAIA_CREDENTIALS auth 200// error when an invalid_grant error is returned by OAuth2TokenService with an 201// HTTP_BAD_REQUEST (400) response code. 202IN_PROC_BROWSER_TEST_F(SyncAuthTest, InvalidGrant) { 203 ASSERT_TRUE(SetupSync()); 204 ASSERT_FALSE(AttemptToTriggerAuthError()); 205 SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE, 206 kInvalidGrantOAuth2Token, 207 net::HTTP_BAD_REQUEST, 208 net::URLRequestStatus::SUCCESS); 209 ASSERT_TRUE(AttemptToTriggerAuthError()); 210 ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS, 211 GetSyncService((0))->GetAuthError().state()); 212} 213 214// Verify that ProfileSyncService ends up with an SERVICE_ERROR auth error when 215// an invalid_client error is returned by OAuth2TokenService with an 216// HTTP_BAD_REQUEST (400) response code. 217IN_PROC_BROWSER_TEST_F(SyncAuthTest, InvalidClient) { 218 ASSERT_TRUE(SetupSync()); 219 ASSERT_FALSE(AttemptToTriggerAuthError()); 220 SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE, 221 kInvalidClientOAuth2Token, 222 net::HTTP_BAD_REQUEST, 223 net::URLRequestStatus::SUCCESS); 224 ASSERT_TRUE(AttemptToTriggerAuthError()); 225 ASSERT_EQ(GoogleServiceAuthError::SERVICE_ERROR, 226 GetSyncService((0))->GetAuthError().state()); 227} 228 229// Verify that ProfileSyncService ends up with a REQUEST_CANCELED auth error 230// when when OAuth2TokenService has encountered a URLRequestStatus of CANCELED. 231IN_PROC_BROWSER_TEST_F(SyncAuthTest, RequestCanceled) { 232 ASSERT_TRUE(SetupSync()); 233 ASSERT_FALSE(AttemptToTriggerAuthError()); 234 SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE, 235 kEmptyOAuth2Token, 236 net::HTTP_INTERNAL_SERVER_ERROR, 237 net::URLRequestStatus::CANCELED); 238 ASSERT_TRUE(AttemptToTriggerAuthError()); 239 ASSERT_EQ(GoogleServiceAuthError::REQUEST_CANCELED, 240 GetSyncService((0))->GetAuthError().state()); 241} 242 243// Verify that ProfileSyncService fails initial sync setup during backend 244// initialization and ends up with an INVALID_GAIA_CREDENTIALS auth error when 245// an invalid_grant error is returned by OAuth2TokenService with an 246// HTTP_BAD_REQUEST (400) response code. 247IN_PROC_BROWSER_TEST_F(SyncAuthTest, FailInitialSetupWithPersistentError) { 248 ASSERT_TRUE(SetupClients()); 249 SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE, 250 kInvalidGrantOAuth2Token, 251 net::HTTP_BAD_REQUEST, 252 net::URLRequestStatus::SUCCESS); 253 ASSERT_FALSE(GetClient(0)->SetupSync()); 254 ASSERT_FALSE(GetSyncService((0))->sync_initialized()); 255 ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS, 256 GetSyncService((0))->GetAuthError().state()); 257} 258 259// Verify that ProfileSyncService fails initial sync setup during backend 260// initialization, but continues trying to fetch access tokens when 261// OAuth2TokenService receives an HTTP_INTERNAL_SERVER_ERROR (500) response 262// code. 263IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryInitialSetupWithTransientError) { 264 ASSERT_TRUE(SetupClients()); 265 SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE, 266 kEmptyOAuth2Token, 267 net::HTTP_INTERNAL_SERVER_ERROR, 268 net::URLRequestStatus::SUCCESS); 269 ASSERT_FALSE(GetClient(0)->SetupSync()); 270 ASSERT_FALSE(GetSyncService((0))->sync_initialized()); 271 ASSERT_TRUE( 272 GetSyncService((0))->IsRetryingAccessTokenFetchForTest()); 273} 274 275// Verify that ProfileSyncService fetches a new token when an old token expires. 276IN_PROC_BROWSER_TEST_F(SyncAuthTest, TokenExpiry) { 277 // Initial sync succeeds with a short lived OAuth2 Token. 278 ASSERT_TRUE(SetupClients()); 279 SetAuthStateAndTokenResponse(AUTHENTICATED_TRUE, 280 kShortLivedOAuth2Token, 281 net::HTTP_OK, 282 net::URLRequestStatus::SUCCESS); 283 ASSERT_TRUE(GetClient(0)->SetupSync()); 284 std::string old_token = GetSyncService((0))->GetAccessTokenForTest(); 285 286 // Wait until the token has expired. 287 base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(5)); 288 289 // Trigger an auth error on the server so PSS requests OA2TS for a new token 290 // during the next sync cycle. 291 SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE, 292 kEmptyOAuth2Token, 293 net::HTTP_INTERNAL_SERVER_ERROR, 294 net::URLRequestStatus::SUCCESS); 295 ASSERT_TRUE(AttemptToTriggerAuthError()); 296 ASSERT_TRUE( 297 GetSyncService((0))->IsRetryingAccessTokenFetchForTest()); 298 299 // Trigger an auth success state and set up a new valid OAuth2 token. 300 SetAuthStateAndTokenResponse(AUTHENTICATED_TRUE, 301 kValidOAuth2Token, 302 net::HTTP_OK, 303 net::URLRequestStatus::SUCCESS); 304 305 // Verify that the next sync cycle is successful, and uses the new auth token. 306 ASSERT_TRUE(AwaitCommitActivityCompletion(GetSyncService((0)))); 307 std::string new_token = GetSyncService((0))->GetAccessTokenForTest(); 308 ASSERT_NE(old_token, new_token); 309} 310