root/chrome/browser/sync/test/integration/sync_auth_test.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. IsExitConditionSatisfied
  2. GetDebugMessage
  3. bookmark_index_
  4. AttemptToTriggerAuthError
  5. SetAuthStateAndTokenResponse
  6. GetNextBookmarkIndex
  7. IN_PROC_BROWSER_TEST_F
  8. IN_PROC_BROWSER_TEST_F
  9. IN_PROC_BROWSER_TEST_F
  10. IN_PROC_BROWSER_TEST_F
  11. IN_PROC_BROWSER_TEST_F
  12. IN_PROC_BROWSER_TEST_F
  13. IN_PROC_BROWSER_TEST_F
  14. IN_PROC_BROWSER_TEST_F
  15. IN_PROC_BROWSER_TEST_F
  16. IN_PROC_BROWSER_TEST_F
  17. IN_PROC_BROWSER_TEST_F

// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/strings/stringprintf.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/sync/test/integration/bookmarks_helper.h"
#include "chrome/browser/sync/test/integration/profile_sync_service_harness.h"
#include "chrome/browser/sync/test/integration/single_client_status_change_checker.h"
#include "chrome/browser/sync/test/integration/sync_integration_test_util.h"
#include "chrome/browser/sync/test/integration/sync_test.h"
#include "components/signin/core/browser/profile_oauth2_token_service.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "net/http/http_status_code.h"
#include "net/url_request/url_request_status.h"

using bookmarks_helper::AddURL;
using sync_integration_test_util::AwaitCommitActivityCompletion;

const char kShortLivedOAuth2Token[] =
    "{"
    "  \"refresh_token\": \"short_lived_refresh_token\","
    "  \"access_token\": \"short_lived_access_token\","
    "  \"expires_in\": 5,"  // 5 seconds.
    "  \"token_type\": \"Bearer\""
    "}";

const char kValidOAuth2Token[] = "{"
                                 "  \"refresh_token\": \"new_refresh_token\","
                                 "  \"access_token\": \"new_access_token\","
                                 "  \"expires_in\": 3600,"  // 1 hour.
                                 "  \"token_type\": \"Bearer\""
                                 "}";

const char kInvalidGrantOAuth2Token[] = "{"
                                        "  \"error\": \"invalid_grant\""
                                        "}";

const char kInvalidClientOAuth2Token[] = "{"
                                         "  \"error\": \"invalid_client\""
                                         "}";

const char kEmptyOAuth2Token[] = "";

const char kMalformedOAuth2Token[] = "{ \"foo\": ";

class TestForAuthError : public SingleClientStatusChangeChecker {
 public:
  explicit TestForAuthError(ProfileSyncService* service);
  virtual ~TestForAuthError();
  virtual bool IsExitConditionSatisfied() OVERRIDE;
  virtual std::string GetDebugMessage() const OVERRIDE;
};

TestForAuthError::TestForAuthError(ProfileSyncService* service)
  : SingleClientStatusChangeChecker(service) {}

TestForAuthError::~TestForAuthError() {}

bool TestForAuthError::IsExitConditionSatisfied() {
  return !service()->HasUnsyncedItems() ||
      (service()->GetSyncTokenStatus().last_get_token_error.state() !=
       GoogleServiceAuthError::NONE);
}

std::string TestForAuthError::GetDebugMessage() const {
  return "Waiting for auth error";
}

class SyncAuthTest : public SyncTest {
 public:
  // TODO(pvalenzuela): Switch to SINGLE_CLIENT once FakeServer
  // supports this scenario.
  SyncAuthTest() : SyncTest(SINGLE_CLIENT_LEGACY), bookmark_index_(0) {}
  virtual ~SyncAuthTest() {}

  // Helper function that adds a bookmark and waits for either an auth error, or
  // for the bookmark to be committed.  Returns true if it detects an auth
  // error, false if the bookmark is committed successfully.
  bool AttemptToTriggerAuthError() {
    int bookmark_index = GetNextBookmarkIndex();
    std::wstring title = base::StringPrintf(L"Bookmark %d", bookmark_index);
    GURL url = GURL(base::StringPrintf("http://www.foo%d.com", bookmark_index));
    EXPECT_TRUE(AddURL(0, title, url) != NULL);

    // Run until the bookmark is committed or an auth error is encountered.
    TestForAuthError checker_(GetSyncService((0)));
    checker_.Wait();

    GoogleServiceAuthError oauth_error =
        GetSyncService((0))->GetSyncTokenStatus().last_get_token_error;

    return oauth_error.state() != GoogleServiceAuthError::NONE;
  }

  // Sets the authenticated state of the python sync server to |auth_state| and
  // sets the canned response that will be returned to the OAuth2TokenService
  // when it tries to fetch an access token.
  void SetAuthStateAndTokenResponse(PythonServerAuthState auth_state,
                                    const std::string& response_data,
                                    net::HttpStatusCode response_code,
                                    net::URLRequestStatus::Status status) {
    TriggerAuthState(auth_state);

    // If ProfileSyncService observes a transient error like SERVICE_UNAVAILABLE
    // or CONNECTION_FAILED, this means the OAuth2TokenService has given up
    // trying to reach Gaia. In practice, OA2TS retries a fixed number of times,
    // but the count is transparent to PSS.
    // Override the max retry count in TokenService so that we instantly trigger
    // the case where ProfileSyncService must pick up where OAuth2TokenService
    // left off (in terms of retries).
    ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile(0))->
        set_max_authorization_token_fetch_retries_for_testing(0);

    SetOAuth2TokenResponse(response_data, response_code, status);
  }

 private:
  int GetNextBookmarkIndex() {
    return bookmark_index_++;
  }

  int bookmark_index_;

  DISALLOW_COPY_AND_ASSIGN(SyncAuthTest);
};

// Verify that sync works with a valid OAuth2 token.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, Sanity) {
  ASSERT_TRUE(SetupSync());
  SetAuthStateAndTokenResponse(AUTHENTICATED_TRUE,
                               kValidOAuth2Token,
                               net::HTTP_OK,
                               net::URLRequestStatus::SUCCESS);
  ASSERT_FALSE(AttemptToTriggerAuthError());
}

// Verify that ProfileSyncService continues trying to fetch access tokens
// when OAuth2TokenService has encountered more than a fixed number of
// HTTP_INTERNAL_SERVER_ERROR (500) errors.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnInternalServerError500) {
  ASSERT_TRUE(SetupSync());
  ASSERT_FALSE(AttemptToTriggerAuthError());
  SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
                               kValidOAuth2Token,
                               net::HTTP_INTERNAL_SERVER_ERROR,
                               net::URLRequestStatus::SUCCESS);
  ASSERT_TRUE(AttemptToTriggerAuthError());
  ASSERT_TRUE(
      GetSyncService((0))->IsRetryingAccessTokenFetchForTest());
}

// Verify that ProfileSyncService continues trying to fetch access tokens
// when OAuth2TokenService has encountered more than a fixed number of
// HTTP_FORBIDDEN (403) errors.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnHttpForbidden403) {
  ASSERT_TRUE(SetupSync());
  ASSERT_FALSE(AttemptToTriggerAuthError());
  SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
                               kEmptyOAuth2Token,
                               net::HTTP_FORBIDDEN,
                               net::URLRequestStatus::SUCCESS);
  ASSERT_TRUE(AttemptToTriggerAuthError());
  ASSERT_TRUE(
      GetSyncService((0))->IsRetryingAccessTokenFetchForTest());
}

// Verify that ProfileSyncService continues trying to fetch access tokens
// when OAuth2TokenService has encountered a URLRequestStatus of FAILED.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnRequestFailed) {
  ASSERT_TRUE(SetupSync());
  ASSERT_FALSE(AttemptToTriggerAuthError());
  SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
                               kEmptyOAuth2Token,
                               net::HTTP_INTERNAL_SERVER_ERROR,
                               net::URLRequestStatus::FAILED);
  ASSERT_TRUE(AttemptToTriggerAuthError());
  ASSERT_TRUE(
      GetSyncService((0))->IsRetryingAccessTokenFetchForTest());
}

// Verify that ProfileSyncService continues trying to fetch access tokens
// when OAuth2TokenService receives a malformed token.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryOnMalformedToken) {
  ASSERT_TRUE(SetupSync());
  ASSERT_FALSE(AttemptToTriggerAuthError());
  SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
                               kMalformedOAuth2Token,
                               net::HTTP_OK,
                               net::URLRequestStatus::SUCCESS);
  ASSERT_TRUE(AttemptToTriggerAuthError());
  ASSERT_TRUE(
      GetSyncService((0))->IsRetryingAccessTokenFetchForTest());
}

// Verify that ProfileSyncService ends up with an INVALID_GAIA_CREDENTIALS auth
// error when an invalid_grant error is returned by OAuth2TokenService with an
// HTTP_BAD_REQUEST (400) response code.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, InvalidGrant) {
  ASSERT_TRUE(SetupSync());
  ASSERT_FALSE(AttemptToTriggerAuthError());
  SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
                               kInvalidGrantOAuth2Token,
                               net::HTTP_BAD_REQUEST,
                               net::URLRequestStatus::SUCCESS);
  ASSERT_TRUE(AttemptToTriggerAuthError());
  ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
            GetSyncService((0))->GetAuthError().state());
}

// Verify that ProfileSyncService ends up with an SERVICE_ERROR auth error when
// an invalid_client error is returned by OAuth2TokenService with an
// HTTP_BAD_REQUEST (400) response code.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, InvalidClient) {
  ASSERT_TRUE(SetupSync());
  ASSERT_FALSE(AttemptToTriggerAuthError());
  SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
                               kInvalidClientOAuth2Token,
                               net::HTTP_BAD_REQUEST,
                               net::URLRequestStatus::SUCCESS);
  ASSERT_TRUE(AttemptToTriggerAuthError());
  ASSERT_EQ(GoogleServiceAuthError::SERVICE_ERROR,
            GetSyncService((0))->GetAuthError().state());
}

// Verify that ProfileSyncService ends up with a REQUEST_CANCELED auth error
// when when OAuth2TokenService has encountered a URLRequestStatus of CANCELED.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, RequestCanceled) {
  ASSERT_TRUE(SetupSync());
  ASSERT_FALSE(AttemptToTriggerAuthError());
  SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
                               kEmptyOAuth2Token,
                               net::HTTP_INTERNAL_SERVER_ERROR,
                               net::URLRequestStatus::CANCELED);
  ASSERT_TRUE(AttemptToTriggerAuthError());
  ASSERT_EQ(GoogleServiceAuthError::REQUEST_CANCELED,
            GetSyncService((0))->GetAuthError().state());
}

// Verify that ProfileSyncService fails initial sync setup during backend
// initialization and ends up with an INVALID_GAIA_CREDENTIALS auth error when
// an invalid_grant error is returned by OAuth2TokenService with an
// HTTP_BAD_REQUEST (400) response code.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, FailInitialSetupWithPersistentError) {
  ASSERT_TRUE(SetupClients());
  SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
                               kInvalidGrantOAuth2Token,
                               net::HTTP_BAD_REQUEST,
                               net::URLRequestStatus::SUCCESS);
  ASSERT_FALSE(GetClient(0)->SetupSync());
  ASSERT_FALSE(GetSyncService((0))->sync_initialized());
  ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS,
            GetSyncService((0))->GetAuthError().state());
}

// Verify that ProfileSyncService fails initial sync setup during backend
// initialization, but continues trying to fetch access tokens when
// OAuth2TokenService receives an HTTP_INTERNAL_SERVER_ERROR (500) response
// code.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, RetryInitialSetupWithTransientError) {
  ASSERT_TRUE(SetupClients());
  SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
                               kEmptyOAuth2Token,
                               net::HTTP_INTERNAL_SERVER_ERROR,
                               net::URLRequestStatus::SUCCESS);
  ASSERT_FALSE(GetClient(0)->SetupSync());
  ASSERT_FALSE(GetSyncService((0))->sync_initialized());
  ASSERT_TRUE(
      GetSyncService((0))->IsRetryingAccessTokenFetchForTest());
}

// Verify that ProfileSyncService fetches a new token when an old token expires.
IN_PROC_BROWSER_TEST_F(SyncAuthTest, TokenExpiry) {
  // Initial sync succeeds with a short lived OAuth2 Token.
  ASSERT_TRUE(SetupClients());
  SetAuthStateAndTokenResponse(AUTHENTICATED_TRUE,
                               kShortLivedOAuth2Token,
                               net::HTTP_OK,
                               net::URLRequestStatus::SUCCESS);
  ASSERT_TRUE(GetClient(0)->SetupSync());
  std::string old_token = GetSyncService((0))->GetAccessTokenForTest();

  // Wait until the token has expired.
  base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(5));

  // Trigger an auth error on the server so PSS requests OA2TS for a new token
  // during the next sync cycle.
  SetAuthStateAndTokenResponse(AUTHENTICATED_FALSE,
                               kEmptyOAuth2Token,
                               net::HTTP_INTERNAL_SERVER_ERROR,
                               net::URLRequestStatus::SUCCESS);
  ASSERT_TRUE(AttemptToTriggerAuthError());
  ASSERT_TRUE(
      GetSyncService((0))->IsRetryingAccessTokenFetchForTest());

  // Trigger an auth success state and set up a new valid OAuth2 token.
  SetAuthStateAndTokenResponse(AUTHENTICATED_TRUE,
                               kValidOAuth2Token,
                               net::HTTP_OK,
                               net::URLRequestStatus::SUCCESS);

  // Verify that the next sync cycle is successful, and uses the new auth token.
  ASSERT_TRUE(AwaitCommitActivityCompletion(GetSyncService((0))));
  std::string new_token = GetSyncService((0))->GetAccessTokenForTest();
  ASSERT_NE(old_token, new_token);
}

/* [<][>][^][v][top][bottom][index][help] */