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