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