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 20 using bookmarks_helper::AddURL; 21 using sync_integration_test_util::AwaitCommitActivityCompletion; 22 23 const 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 31 const 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 38 const char kInvalidGrantOAuth2Token[] = "{" 39 " \"error\": \"invalid_grant\"" 40 "}"; 41 42 const char kInvalidClientOAuth2Token[] = "{" 43 " \"error\": \"invalid_client\"" 44 "}"; 45 46 const char kEmptyOAuth2Token[] = ""; 47 48 const char kMalformedOAuth2Token[] = "{ \"foo\": "; 49 50 class 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 58 TestForAuthError::TestForAuthError(ProfileSyncService* service) 59 : SingleClientStatusChangeChecker(service) {} 60 61 TestForAuthError::~TestForAuthError() {} 62 63 bool TestForAuthError::IsExitConditionSatisfied() { 64 return !service()->HasUnsyncedItems() || 65 (service()->GetSyncTokenStatus().last_get_token_error.state() != 66 GoogleServiceAuthError::NONE); 67 } 68 69 std::string TestForAuthError::GetDebugMessage() const { 70 return "Waiting for auth error"; 71 } 72 73 class SyncAuthTest : public SyncTest { 74 public: 75 SyncAuthTest() : SyncTest(SINGLE_CLIENT), bookmark_index_(0) {} 76 virtual ~SyncAuthTest() {} 77 78 // Helper function that adds a bookmark and waits for either an auth error, or 79 // for the bookmark to be committed. Returns true if it detects an auth 80 // error, false if the bookmark is committed successfully. 81 bool AttemptToTriggerAuthError() { 82 int bookmark_index = GetNextBookmarkIndex(); 83 std::string title = base::StringPrintf("Bookmark %d", bookmark_index); 84 GURL url = GURL(base::StringPrintf("http://www.foo%d.com", bookmark_index)); 85 EXPECT_TRUE(AddURL(0, title, url) != NULL); 86 87 // Run until the bookmark is committed or an auth error is encountered. 88 TestForAuthError checker_(GetSyncService((0))); 89 checker_.Wait(); 90 91 GoogleServiceAuthError oauth_error = 92 GetSyncService((0))->GetSyncTokenStatus().last_get_token_error; 93 94 return oauth_error.state() != GoogleServiceAuthError::NONE; 95 } 96 97 void DisableTokenFetchRetries() { 98 // If ProfileSyncService observes a transient error like SERVICE_UNAVAILABLE 99 // or CONNECTION_FAILED, this means the OAuth2TokenService has given up 100 // trying to reach Gaia. In practice, OA2TS retries a fixed number of times, 101 // but the count is transparent to PSS. 102 // Override the max retry count in TokenService so that we instantly trigger 103 // the case where ProfileSyncService must pick up where OAuth2TokenService 104 // left off (in terms of retries). 105 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile(0))-> 106 set_max_authorization_token_fetch_retries_for_testing(0); 107 } 108 109 110 private: 111 int GetNextBookmarkIndex() { 112 return bookmark_index_++; 113 } 114 115 int bookmark_index_; 116 117 DISALLOW_COPY_AND_ASSIGN(SyncAuthTest); 118 }; 119 120 // Verify that sync works with a valid OAuth2 token. 121 IN_PROC_BROWSER_TEST_F(SyncAuthTest, Sanity) { 122 ASSERT_TRUE(SetupSync()); 123 GetFakeServer()->SetAuthenticated(); 124 DisableTokenFetchRetries(); 125 SetOAuth2TokenResponse(kValidOAuth2Token, 126 net::HTTP_OK, 127 net::URLRequestStatus::SUCCESS); 128 ASSERT_FALSE(AttemptToTriggerAuthError()); 129 } 130 131 // Verify that ProfileSyncService continues trying to fetch access tokens 132 // when OAuth2TokenService has encountered more than a fixed number of 133 // HTTP_INTERNAL_SERVER_ERROR (500) errors. 134 IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnInternalServerError500) { 135 ASSERT_TRUE(SetupSync()); 136 ASSERT_FALSE(AttemptToTriggerAuthError()); 137 GetFakeServer()->SetUnauthenticated(); 138 DisableTokenFetchRetries(); 139 SetOAuth2TokenResponse(kValidOAuth2Token, 140 net::HTTP_INTERNAL_SERVER_ERROR, 141 net::URLRequestStatus::SUCCESS); 142 ASSERT_TRUE(AttemptToTriggerAuthError()); 143 ASSERT_TRUE( 144 GetSyncService((0))->IsRetryingAccessTokenFetchForTest()); 145 } 146 147 // Verify that ProfileSyncService continues trying to fetch access tokens 148 // when OAuth2TokenService has encountered more than a fixed number of 149 // HTTP_FORBIDDEN (403) errors. 150 IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnHttpForbidden403) { 151 ASSERT_TRUE(SetupSync()); 152 ASSERT_FALSE(AttemptToTriggerAuthError()); 153 GetFakeServer()->SetUnauthenticated(); 154 DisableTokenFetchRetries(); 155 SetOAuth2TokenResponse(kEmptyOAuth2Token, 156 net::HTTP_FORBIDDEN, 157 net::URLRequestStatus::SUCCESS); 158 ASSERT_TRUE(AttemptToTriggerAuthError()); 159 ASSERT_TRUE( 160 GetSyncService((0))->IsRetryingAccessTokenFetchForTest()); 161 } 162 163 // Verify that ProfileSyncService continues trying to fetch access tokens 164 // when OAuth2TokenService has encountered a URLRequestStatus of FAILED. 165 IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnRequestFailed) { 166 ASSERT_TRUE(SetupSync()); 167 ASSERT_FALSE(AttemptToTriggerAuthError()); 168 GetFakeServer()->SetUnauthenticated(); 169 DisableTokenFetchRetries(); 170 SetOAuth2TokenResponse(kEmptyOAuth2Token, 171 net::HTTP_INTERNAL_SERVER_ERROR, 172 net::URLRequestStatus::FAILED); 173 ASSERT_TRUE(AttemptToTriggerAuthError()); 174 ASSERT_TRUE( 175 GetSyncService((0))->IsRetryingAccessTokenFetchForTest()); 176 } 177 178 // Verify that ProfileSyncService continues trying to fetch access tokens 179 // when OAuth2TokenService receives a malformed token. 180 IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnMalformedToken) { 181 ASSERT_TRUE(SetupSync()); 182 ASSERT_FALSE(AttemptToTriggerAuthError()); 183 GetFakeServer()->SetUnauthenticated(); 184 DisableTokenFetchRetries(); 185 SetOAuth2TokenResponse(kMalformedOAuth2Token, 186 net::HTTP_OK, 187 net::URLRequestStatus::SUCCESS); 188 ASSERT_TRUE(AttemptToTriggerAuthError()); 189 ASSERT_TRUE( 190 GetSyncService((0))->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. 196 IN_PROC_BROWSER_TEST_F(SyncAuthTest, InvalidGrant) { 197 ASSERT_TRUE(SetupSync()); 198 ASSERT_FALSE(AttemptToTriggerAuthError()); 199 GetFakeServer()->SetUnauthenticated(); 200 DisableTokenFetchRetries(); 201 SetOAuth2TokenResponse(kInvalidGrantOAuth2Token, 202 net::HTTP_BAD_REQUEST, 203 net::URLRequestStatus::SUCCESS); 204 ASSERT_TRUE(AttemptToTriggerAuthError()); 205 ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS, 206 GetSyncService((0))->GetAuthError().state()); 207 } 208 209 // Verify that ProfileSyncService retries after SERVICE_ERROR auth error when 210 // an invalid_client error is returned by OAuth2TokenService with an 211 // HTTP_BAD_REQUEST (400) response code. 212 IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryInvalidClient) { 213 ASSERT_TRUE(SetupSync()); 214 ASSERT_FALSE(AttemptToTriggerAuthError()); 215 GetFakeServer()->SetUnauthenticated(); 216 DisableTokenFetchRetries(); 217 SetOAuth2TokenResponse(kInvalidClientOAuth2Token, 218 net::HTTP_BAD_REQUEST, 219 net::URLRequestStatus::SUCCESS); 220 ASSERT_TRUE(AttemptToTriggerAuthError()); 221 ASSERT_TRUE(GetSyncService((0))->IsRetryingAccessTokenFetchForTest()); 222 } 223 224 // Verify that ProfileSyncService retries after REQUEST_CANCELED auth error 225 // when OAuth2TokenService has encountered a URLRequestStatus of CANCELED. 226 IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryRequestCanceled) { 227 ASSERT_TRUE(SetupSync()); 228 ASSERT_FALSE(AttemptToTriggerAuthError()); 229 GetFakeServer()->SetUnauthenticated(); 230 DisableTokenFetchRetries(); 231 SetOAuth2TokenResponse(kEmptyOAuth2Token, 232 net::HTTP_INTERNAL_SERVER_ERROR, 233 net::URLRequestStatus::CANCELED); 234 ASSERT_TRUE(AttemptToTriggerAuthError()); 235 ASSERT_TRUE(GetSyncService((0))->IsRetryingAccessTokenFetchForTest()); 236 } 237 238 // Verify that ProfileSyncService fails initial sync setup during backend 239 // initialization and ends up with an INVALID_GAIA_CREDENTIALS auth error when 240 // an invalid_grant error is returned by OAuth2TokenService with an 241 // HTTP_BAD_REQUEST (400) response code. 242 IN_PROC_BROWSER_TEST_F(SyncAuthTest, FailInitialSetupWithPersistentError) { 243 ASSERT_TRUE(SetupClients()); 244 GetFakeServer()->SetUnauthenticated(); 245 DisableTokenFetchRetries(); 246 SetOAuth2TokenResponse(kInvalidGrantOAuth2Token, 247 net::HTTP_BAD_REQUEST, 248 net::URLRequestStatus::SUCCESS); 249 ASSERT_FALSE(GetClient(0)->SetupSync()); 250 ASSERT_FALSE(GetSyncService((0))->sync_initialized()); 251 ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS, 252 GetSyncService((0))->GetAuthError().state()); 253 } 254 255 // Verify that ProfileSyncService fails initial sync setup during backend 256 // initialization, but continues trying to fetch access tokens when 257 // OAuth2TokenService receives an HTTP_INTERNAL_SERVER_ERROR (500) response 258 // code. 259 IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryInitialSetupWithTransientError) { 260 ASSERT_TRUE(SetupClients()); 261 GetFakeServer()->SetUnauthenticated(); 262 DisableTokenFetchRetries(); 263 SetOAuth2TokenResponse(kEmptyOAuth2Token, 264 net::HTTP_INTERNAL_SERVER_ERROR, 265 net::URLRequestStatus::SUCCESS); 266 ASSERT_FALSE(GetClient(0)->SetupSync()); 267 ASSERT_FALSE(GetSyncService((0))->sync_initialized()); 268 ASSERT_TRUE( 269 GetSyncService((0))->IsRetryingAccessTokenFetchForTest()); 270 } 271 272 // Verify that ProfileSyncService fetches a new token when an old token expires. 273 IN_PROC_BROWSER_TEST_F(SyncAuthTest, TokenExpiry) { 274 // Initial sync succeeds with a short lived OAuth2 Token. 275 ASSERT_TRUE(SetupClients()); 276 GetFakeServer()->SetAuthenticated(); 277 DisableTokenFetchRetries(); 278 SetOAuth2TokenResponse(kShortLivedOAuth2Token, 279 net::HTTP_OK, 280 net::URLRequestStatus::SUCCESS); 281 ASSERT_TRUE(GetClient(0)->SetupSync()); 282 std::string old_token = GetSyncService((0))->GetAccessTokenForTest(); 283 284 // Wait until the token has expired. 285 base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(5)); 286 287 // Trigger an auth error on the server so PSS requests OA2TS for a new token 288 // during the next sync cycle. 289 GetFakeServer()->SetUnauthenticated(); 290 SetOAuth2TokenResponse(kEmptyOAuth2Token, 291 net::HTTP_INTERNAL_SERVER_ERROR, 292 net::URLRequestStatus::SUCCESS); 293 ASSERT_TRUE(AttemptToTriggerAuthError()); 294 ASSERT_TRUE( 295 GetSyncService((0))->IsRetryingAccessTokenFetchForTest()); 296 297 // Trigger an auth success state and set up a new valid OAuth2 token. 298 GetFakeServer()->SetAuthenticated(); 299 SetOAuth2TokenResponse(kValidOAuth2Token, 300 net::HTTP_OK, 301 net::URLRequestStatus::SUCCESS); 302 303 // Verify that the next sync cycle is successful, and uses the new auth token. 304 ASSERT_TRUE(AwaitCommitActivityCompletion(GetSyncService((0)))); 305 std::string new_token = GetSyncService((0))->GetAccessTokenForTest(); 306 ASSERT_NE(old_token, new_token); 307 } 308