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/sync_test.h"
14#include "google_apis/gaia/google_service_auth_error.h"
15#include "net/http/http_status_code.h"
16#include "net/url_request/url_request_status.h"
17
18using bookmarks_helper::AddURL;
19
20const char kShortLivedOAuth2Token[] =
21    "{"
22    "  \"refresh_token\": \"short_lived_refresh_token\","
23    "  \"access_token\": \"short_lived_access_token\","
24    "  \"expires_in\": 5,"  // 5 seconds.
25    "  \"token_type\": \"Bearer\""
26    "}";
27
28const char kValidOAuth2Token[] = "{"
29                                 "  \"refresh_token\": \"new_refresh_token\","
30                                 "  \"access_token\": \"new_access_token\","
31                                 "  \"expires_in\": 3600,"  // 1 hour.
32                                 "  \"token_type\": \"Bearer\""
33                                 "}";
34
35const char kInvalidGrantOAuth2Token[] = "{"
36                                        "  \"error\": \"invalid_grant\""
37                                        "}";
38
39const char kInvalidClientOAuth2Token[] = "{"
40                                         "  \"error\": \"invalid_client\""
41                                         "}";
42
43const char kEmptyOAuth2Token[] = "";
44
45const char kMalformedOAuth2Token[] = "{ \"foo\": ";
46
47class SyncAuthTest : public SyncTest {
48 public:
49  SyncAuthTest() : SyncTest(SINGLE_CLIENT) {}
50  virtual ~SyncAuthTest() {}
51
52  // Helper function that adds a bookmark with index |bookmark_index| and waits
53  // for sync to complete. The return value indicates whether the sync cycle
54  // completed successfully (true) or encountered an auth error (false).
55  bool AddBookmarkAndWaitForSync(int bookmark_index) {
56    std::wstring title = base::StringPrintf(L"Bookmark %d", bookmark_index);
57    GURL url = GURL(base::StringPrintf("http://www.foo%d.com", bookmark_index));
58    EXPECT_TRUE(AddURL(0, title, url) != NULL);
59    return GetClient(0)->AwaitFullSyncCompletion();
60  }
61
62  // Sets the authenticated state of the python sync server to |auth_state| and
63  // sets the canned response that will be returned to the OAuth2TokenService
64  // when it tries to fetch an access token.
65  void SetAuthStateAndTokenResponse(PythonServerAuthState auth_state,
66                                    const std::string& response_data,
67                                    net::HttpStatusCode response_code,
68                                    net::URLRequestStatus::Status status) {
69    TriggerAuthState(auth_state);
70
71    // If ProfileSyncService observes a transient error like SERVICE_UNAVAILABLE
72    // or CONNECTION_FAILED, this means the OAuth2TokenService has given up
73    // trying to reach Gaia. In practice, OA2TS retries a fixed number of times,
74    // but the count is transparent to PSS.
75    // Override the max retry count in TokenService so that we instantly trigger
76    // the case where ProfileSyncService must pick up where OAuth2TokenService
77    // left off (in terms of retries).
78    ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile(0))->
79        set_max_authorization_token_fetch_retries_for_testing(0);
80
81    SetOAuth2TokenResponse(response_data, response_code, status);
82  }
83
84 private:
85  DISALLOW_COPY_AND_ASSIGN(SyncAuthTest);
86};
87
88// Verify that sync works with a valid OAuth2 token.
89IN_PROC_BROWSER_TEST_F(SyncAuthTest, Sanity) {
90  ASSERT_TRUE(SetupSync());
91  SetAuthStateAndTokenResponse(AUTHENTICATED_TRUE,
92                               kValidOAuth2Token,
93                               net::HTTP_OK,
94                               net::URLRequestStatus::SUCCESS);
95  ASSERT_TRUE(AddBookmarkAndWaitForSync(1));
96}
97
98// Verify that ProfileSyncService continues trying to fetch access tokens
99// when OAuth2TokenService has encountered more than a fixed number of
100// HTTP_INTERNAL_SERVER_ERROR (500) errors.
101IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnInternalServerError500) {
102  ASSERT_TRUE(SetupSync());
103  ASSERT_TRUE(AddBookmarkAndWaitForSync(1));
104  SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
105                               kValidOAuth2Token,
106                               net::HTTP_INTERNAL_SERVER_ERROR,
107                               net::URLRequestStatus::SUCCESS);
108  ASSERT_FALSE(AddBookmarkAndWaitForSync(2));
109  ASSERT_TRUE(
110      GetClient(0)->service()->IsRetryingAccessTokenFetchForTest());
111}
112
113// Verify that ProfileSyncService continues trying to fetch access tokens
114// when OAuth2TokenService has encountered more than a fixed number of
115// HTTP_FORBIDDEN (403) errors.
116IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnHttpForbidden403) {
117  ASSERT_TRUE(SetupSync());
118  ASSERT_TRUE(AddBookmarkAndWaitForSync(1));
119  SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
120                               kEmptyOAuth2Token,
121                               net::HTTP_FORBIDDEN,
122                               net::URLRequestStatus::SUCCESS);
123  ASSERT_FALSE(AddBookmarkAndWaitForSync(2));
124  ASSERT_TRUE(
125      GetClient(0)->service()->IsRetryingAccessTokenFetchForTest());
126}
127
128// Verify that ProfileSyncService continues trying to fetch access tokens
129// when OAuth2TokenService has encountered a URLRequestStatus of FAILED.
130IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnRequestFailed) {
131  ASSERT_TRUE(SetupSync());
132  ASSERT_TRUE(AddBookmarkAndWaitForSync(1));
133  SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
134                               kEmptyOAuth2Token,
135                               net::HTTP_INTERNAL_SERVER_ERROR,
136                               net::URLRequestStatus::FAILED);
137  ASSERT_FALSE(AddBookmarkAndWaitForSync(2));
138  ASSERT_TRUE(
139      GetClient(0)->service()->IsRetryingAccessTokenFetchForTest());
140}
141
142// Verify that ProfileSyncService continues trying to fetch access tokens
143// when OAuth2TokenService receives a malformed token.
144IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnMalformedToken) {
145  ASSERT_TRUE(SetupSync());
146  ASSERT_TRUE(AddBookmarkAndWaitForSync(1));
147  SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
148                               kMalformedOAuth2Token,
149                               net::HTTP_OK,
150                               net::URLRequestStatus::SUCCESS);
151  ASSERT_FALSE(AddBookmarkAndWaitForSync(2));
152  ASSERT_TRUE(
153      GetClient(0)->service()->IsRetryingAccessTokenFetchForTest());
154}
155
156// Verify that ProfileSyncService ends up with an INVALID_GAIA_CREDENTIALS auth
157// error when an invalid_grant error is returned by OAuth2TokenService with an
158// HTTP_BAD_REQUEST (400) response code.
159IN_PROC_BROWSER_TEST_F(SyncAuthTest, InvalidGrant) {
160  ASSERT_TRUE(SetupSync());
161  ASSERT_TRUE(AddBookmarkAndWaitForSync(1));
162  SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
163                               kInvalidGrantOAuth2Token,
164                               net::HTTP_BAD_REQUEST,
165                               net::URLRequestStatus::SUCCESS);
166  ASSERT_FALSE(AddBookmarkAndWaitForSync(2));
167  ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
168            GetClient(0)->service()->GetAuthError().state());
169}
170
171// Verify that ProfileSyncService ends up with an SERVICE_ERROR auth error when
172// an invalid_client error is returned by OAuth2TokenService with an
173// HTTP_BAD_REQUEST (400) response code.
174IN_PROC_BROWSER_TEST_F(SyncAuthTest, InvalidClient) {
175  ASSERT_TRUE(SetupSync());
176  ASSERT_TRUE(AddBookmarkAndWaitForSync(1));
177  SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
178                               kInvalidClientOAuth2Token,
179                               net::HTTP_BAD_REQUEST,
180                               net::URLRequestStatus::SUCCESS);
181  ASSERT_FALSE(AddBookmarkAndWaitForSync(2));
182  ASSERT_EQ(GoogleServiceAuthError::SERVICE_ERROR,
183            GetClient(0)->service()->GetAuthError().state());
184}
185
186// Verify that ProfileSyncService ends up with a REQUEST_CANCELED auth error
187// when when OAuth2TokenService has encountered a URLRequestStatus of CANCELED.
188IN_PROC_BROWSER_TEST_F(SyncAuthTest, RequestCanceled) {
189  ASSERT_TRUE(SetupSync());
190  ASSERT_TRUE(AddBookmarkAndWaitForSync(1));
191  SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
192                               kEmptyOAuth2Token,
193                               net::HTTP_INTERNAL_SERVER_ERROR,
194                               net::URLRequestStatus::CANCELED);
195  ASSERT_FALSE(AddBookmarkAndWaitForSync(2));
196  ASSERT_EQ(GoogleServiceAuthError::REQUEST_CANCELED,
197            GetClient(0)->service()->GetAuthError().state());
198}
199
200// Verify that ProfileSyncService fails initial sync setup during backend
201// initialization and ends up with an INVALID_GAIA_CREDENTIALS auth error when
202// an invalid_grant error is returned by OAuth2TokenService with an
203// HTTP_BAD_REQUEST (400) response code.
204IN_PROC_BROWSER_TEST_F(SyncAuthTest, FailInitialSetupWithPersistentError) {
205  ASSERT_TRUE(SetupClients());
206  SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
207                               kInvalidGrantOAuth2Token,
208                               net::HTTP_BAD_REQUEST,
209                               net::URLRequestStatus::SUCCESS);
210  ASSERT_FALSE(GetClient(0)->SetupSync());
211  ASSERT_FALSE(GetClient(0)->service()->sync_initialized());
212  ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
213            GetClient(0)->service()->GetAuthError().state());
214}
215
216// Verify that ProfileSyncService fails initial sync setup during backend
217// initialization, but continues trying to fetch access tokens when
218// OAuth2TokenService receives an HTTP_INTERNAL_SERVER_ERROR (500) response
219// code.
220IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryInitialSetupWithTransientError) {
221  ASSERT_TRUE(SetupClients());
222  SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
223                               kEmptyOAuth2Token,
224                               net::HTTP_INTERNAL_SERVER_ERROR,
225                               net::URLRequestStatus::SUCCESS);
226  ASSERT_FALSE(GetClient(0)->SetupSync());
227  ASSERT_FALSE(GetClient(0)->service()->sync_initialized());
228  ASSERT_TRUE(
229      GetClient(0)->service()->IsRetryingAccessTokenFetchForTest());
230}
231
232// Verify that ProfileSyncService fetches a new token when an old token expires.
233IN_PROC_BROWSER_TEST_F(SyncAuthTest, TokenExpiry) {
234  // Initial sync succeeds with a short lived OAuth2 Token.
235  ASSERT_TRUE(SetupClients());
236  SetAuthStateAndTokenResponse(AUTHENTICATED_TRUE,
237                               kShortLivedOAuth2Token,
238                               net::HTTP_OK,
239                               net::URLRequestStatus::SUCCESS);
240  ASSERT_TRUE(GetClient(0)->SetupSync());
241  std::string old_token = GetClient(0)->service()->GetAccessTokenForTest();
242
243  // Wait until the token has expired.
244  base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(5));
245
246  // Trigger an auth error on the server so PSS requests OA2TS for a new token
247  // during the next sync cycle.
248  SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
249                               kEmptyOAuth2Token,
250                               net::HTTP_INTERNAL_SERVER_ERROR,
251                               net::URLRequestStatus::SUCCESS);
252  ASSERT_FALSE(AddBookmarkAndWaitForSync(1));
253  ASSERT_TRUE(
254      GetClient(0)->service()->IsRetryingAccessTokenFetchForTest());
255
256  // Trigger an auth success state and set up a new valid OAuth2 token.
257  SetAuthStateAndTokenResponse(AUTHENTICATED_TRUE,
258                               kValidOAuth2Token,
259                               net::HTTP_OK,
260                               net::URLRequestStatus::SUCCESS);
261
262  // Verify that the next sync cycle is successful, and uses the new auth token.
263  ASSERT_TRUE(GetClient(0)->AwaitFullSyncCompletion());
264  std::string new_token = GetClient(0)->service()->GetAccessTokenForTest();
265  ASSERT_NE(old_token, new_token);
266}
267