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