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