sync_auth_test.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
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  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.
121IN_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.
134IN_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.
150IN_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.
165IN_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.
180IN_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.
196IN_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 ends up with an SERVICE_ERROR auth error when
210// an invalid_client error is returned by OAuth2TokenService with an
211// HTTP_BAD_REQUEST (400) response code.
212IN_PROC_BROWSER_TEST_F(SyncAuthTest, InvalidClient) {
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_EQ(GoogleServiceAuthError::SERVICE_ERROR,
222            GetSyncService((0))->GetAuthError().state());
223}
224
225// Verify that ProfileSyncService ends up with a REQUEST_CANCELED auth error
226// when when OAuth2TokenService has encountered a URLRequestStatus of CANCELED.
227IN_PROC_BROWSER_TEST_F(SyncAuthTest, RequestCanceled) {
228  ASSERT_TRUE(SetupSync());
229  ASSERT_FALSE(AttemptToTriggerAuthError());
230  GetFakeServer()->SetUnauthenticated();
231  DisableTokenFetchRetries();
232  SetOAuth2TokenResponse(kEmptyOAuth2Token,
233                         net::HTTP_INTERNAL_SERVER_ERROR,
234                         net::URLRequestStatus::CANCELED);
235  ASSERT_TRUE(AttemptToTriggerAuthError());
236  ASSERT_EQ(GoogleServiceAuthError::REQUEST_CANCELED,
237            GetSyncService((0))->GetAuthError().state());
238}
239
240// Verify that ProfileSyncService fails initial sync setup during backend
241// initialization and ends up with an INVALID_GAIA_CREDENTIALS auth error when
242// an invalid_grant error is returned by OAuth2TokenService with an
243// HTTP_BAD_REQUEST (400) response code.
244IN_PROC_BROWSER_TEST_F(SyncAuthTest, FailInitialSetupWithPersistentError) {
245  ASSERT_TRUE(SetupClients());
246  GetFakeServer()->SetUnauthenticated();
247  DisableTokenFetchRetries();
248  SetOAuth2TokenResponse(kInvalidGrantOAuth2Token,
249                         net::HTTP_BAD_REQUEST,
250                         net::URLRequestStatus::SUCCESS);
251  ASSERT_FALSE(GetClient(0)->SetupSync());
252  ASSERT_FALSE(GetSyncService((0))->sync_initialized());
253  ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
254            GetSyncService((0))->GetAuthError().state());
255}
256
257// Verify that ProfileSyncService fails initial sync setup during backend
258// initialization, but continues trying to fetch access tokens when
259// OAuth2TokenService receives an HTTP_INTERNAL_SERVER_ERROR (500) response
260// code.
261IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryInitialSetupWithTransientError) {
262  ASSERT_TRUE(SetupClients());
263  GetFakeServer()->SetUnauthenticated();
264  DisableTokenFetchRetries();
265  SetOAuth2TokenResponse(kEmptyOAuth2Token,
266                         net::HTTP_INTERNAL_SERVER_ERROR,
267                         net::URLRequestStatus::SUCCESS);
268  ASSERT_FALSE(GetClient(0)->SetupSync());
269  ASSERT_FALSE(GetSyncService((0))->sync_initialized());
270  ASSERT_TRUE(
271      GetSyncService((0))->IsRetryingAccessTokenFetchForTest());
272}
273
274// Verify that ProfileSyncService fetches a new token when an old token expires.
275IN_PROC_BROWSER_TEST_F(SyncAuthTest, TokenExpiry) {
276  // Initial sync succeeds with a short lived OAuth2 Token.
277  ASSERT_TRUE(SetupClients());
278  GetFakeServer()->SetAuthenticated();
279  DisableTokenFetchRetries();
280  SetOAuth2TokenResponse(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  GetFakeServer()->SetUnauthenticated();
292  SetOAuth2TokenResponse(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  GetFakeServer()->SetAuthenticated();
301  SetOAuth2TokenResponse(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