diff options
15 files changed, 908 insertions, 38 deletions
diff --git a/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos_unittest.cc b/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos_unittest.cc index dadb7a9..6600274 100644 --- a/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos_unittest.cc +++ b/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos_unittest.cc @@ -324,10 +324,7 @@ class DeviceCloudPolicyManagerChromeOSEnrollmentTest if (robot_auth_fetch_status_ == DM_STATUS_SUCCESS) { net::TestURLFetcher* url_fetcher = url_fetcher_factory_.GetFetcherByID(0); ASSERT_TRUE(url_fetcher); - // The logic in GaiaOAuthClient seems broken, it always retries 1x on - // non-200 response codes, even if the retries are set to 0. Seems like - // its num_retries_ > source->GetMaxRetriesOn5xx() should have a >=? - url_fetcher->SetMaxRetriesOn5xx(-2); + url_fetcher->SetMaxRetriesOn5xx(0); url_fetcher->set_status(net::URLRequestStatus()); url_fetcher->set_response_code(url_fetcher_response_code_); url_fetcher->SetResponseString(url_fetcher_response_string_); diff --git a/chrome/browser/managed_mode/managed_user_refresh_token_fetcher.cc b/chrome/browser/managed_mode/managed_user_refresh_token_fetcher.cc new file mode 100644 index 0000000..b79faf2 --- /dev/null +++ b/chrome/browser/managed_mode/managed_user_refresh_token_fetcher.cc @@ -0,0 +1,285 @@ +// 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 "chrome/browser/managed_mode/managed_user_refresh_token_fetcher.h" + +#include "base/callback.h" +#include "base/json/json_reader.h" +#include "base/logging.h" +#include "base/string16.h" +#include "base/stringprintf.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/signin/oauth2_token_service.h" +#include "google_apis/gaia/gaia_oauth_client.h" +#include "google_apis/gaia/gaia_urls.h" +#include "google_apis/gaia/google_service_auth_error.h" +#include "google_apis/gaia/oauth2_api_call_flow.h" +#include "net/base/escape.h" +#include "net/base/load_flags.h" +#include "net/base/net_errors.h" +#include "net/http/http_status_code.h" +#include "net/url_request/url_fetcher.h" +#include "net/url_request/url_request_status.h" + +using base::Time; +using gaia::GaiaOAuthClient; +using net::URLFetcher; +using net::URLFetcherDelegate; +using net::URLRequestContextGetter; + +namespace { + +const int kNumRetries = 1; + +static const char kChromeSyncManagedScope[] = + "https://www.googleapis.com/auth/chromesync_playpen"; + +static const char kIssueTokenBodyFormat[] = + "client_id=%s" + "&scope=&%s" + "&response_type=code" + "&profile_id=%s" + "&profile_name=%s" + "&device_name=%s"; + +static const char kAuthorizationHeaderFormat[] = + "Authorization: Bearer %s"; + +static const char kCodeKey[] = "code"; + +class ManagedUserRefreshTokenFetcherImpl + : public ManagedUserRefreshTokenFetcher, + public OAuth2TokenService::Consumer, + public URLFetcherDelegate, + public GaiaOAuthClient::Delegate { + public: + ManagedUserRefreshTokenFetcherImpl(OAuth2TokenService* oauth2_token_service, + URLRequestContextGetter* context); + virtual ~ManagedUserRefreshTokenFetcherImpl(); + + // ManagedUserRefreshTokenFetcher implementation: + virtual void Start(const std::string& managed_user_id, + const string16& name, + const std::string& device_name, + const TokenCallback& callback) OVERRIDE; + + protected: + // OAuth2TokenService::Consumer implementation: + virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request, + const std::string& access_token, + const Time& expiration_time) OVERRIDE; + virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request, + const GoogleServiceAuthError& error) OVERRIDE; + + // net::URLFetcherDelegate implementation. + virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE; + + // GaiaOAuthClient::Delegate implementation: + virtual void OnGetTokensResponse(const std::string& refresh_token, + const std::string& access_token, + int expires_in_seconds) OVERRIDE; + virtual void OnRefreshTokenResponse(const std::string& access_token, + int expires_in_seconds) OVERRIDE; + virtual void OnOAuthError() OVERRIDE; + virtual void OnNetworkError(int response_code) OVERRIDE; + + private: + // Requests an access token, which is the first thing we need. This is where + // we restart when the returned access token has expired. + void StartFetching(); + + void DispatchNetworkError(int error_code); + void DispatchGoogleServiceAuthError(const GoogleServiceAuthError& error, + const std::string& token); + OAuth2TokenService* oauth2_token_service_; + URLRequestContextGetter* context_; + + std::string device_name_; + std::string managed_user_id_; + string16 name_; + TokenCallback callback_; + + scoped_ptr<OAuth2TokenService::Request> access_token_request_; + std::string access_token_; + bool access_token_expired_; + scoped_ptr<URLFetcher> url_fetcher_; + scoped_ptr<GaiaOAuthClient> gaia_oauth_client_; +}; + +ManagedUserRefreshTokenFetcherImpl::ManagedUserRefreshTokenFetcherImpl( + OAuth2TokenService* oauth2_token_service, + URLRequestContextGetter* context) + : oauth2_token_service_(oauth2_token_service), + context_(context), + access_token_expired_(false) {} + +ManagedUserRefreshTokenFetcherImpl::~ManagedUserRefreshTokenFetcherImpl() {} + +void ManagedUserRefreshTokenFetcherImpl::Start( + const std::string& managed_user_id, + const string16& name, + const std::string& device_name, + const TokenCallback& callback) { + DCHECK(callback_.is_null()); + managed_user_id_ = managed_user_id; + name_ = name; + device_name_ = device_name; + callback_ = callback; + StartFetching(); +} + +void ManagedUserRefreshTokenFetcherImpl::StartFetching() { + OAuth2TokenService::ScopeSet scopes; + scopes.insert(GaiaUrls::GetInstance()->oauth1_login_scope()); + access_token_request_ = oauth2_token_service_->StartRequest(scopes, this); +} + +void ManagedUserRefreshTokenFetcherImpl::OnGetTokenSuccess( + const OAuth2TokenService::Request* request, + const std::string& access_token, + const Time& expiration_time) { + DCHECK_EQ(access_token_request_.get(), request); + access_token_ = access_token; + + GURL url(GaiaUrls::GetInstance()->oauth2_issue_token_url()); + // GaiaOAuthClient uses id 0, so we use 1 to distinguish the requests in + // unit tests. + const int id = 1; + + url_fetcher_.reset(URLFetcher::Create(id, url, URLFetcher::POST, this)); + + url_fetcher_->SetRequestContext(context_); + url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | + net::LOAD_DO_NOT_SAVE_COOKIES); + url_fetcher_->SetAutomaticallyRetryOnNetworkChanges(kNumRetries); + url_fetcher_->AddExtraRequestHeader( + base::StringPrintf(kAuthorizationHeaderFormat, access_token.c_str())); + + std::string body = base::StringPrintf( + kIssueTokenBodyFormat, + net::EscapeUrlEncodedData( + GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true).c_str(), + net::EscapeUrlEncodedData(kChromeSyncManagedScope, true).c_str(), + net::EscapeUrlEncodedData(managed_user_id_, true).c_str(), + net::EscapeUrlEncodedData(UTF16ToUTF8(name_), true).c_str(), + net::EscapeUrlEncodedData(device_name_, true).c_str()); + url_fetcher_->SetUploadData("application/x-www-form-urlencoded", body); + + url_fetcher_->Start(); +} + +void ManagedUserRefreshTokenFetcherImpl::OnGetTokenFailure( + const OAuth2TokenService::Request* request, + const GoogleServiceAuthError& error) { + DCHECK_EQ(access_token_request_.get(), request); + callback_.Run(error, std::string()); + callback_.Reset(); +} + +void ManagedUserRefreshTokenFetcherImpl::OnURLFetchComplete( + const URLFetcher* source) { + const net::URLRequestStatus& status = source->GetStatus(); + if (!status.is_success()) { + DispatchNetworkError(status.error()); + return; + } + + int response_code = source->GetResponseCode(); + if (response_code == net::HTTP_UNAUTHORIZED && !access_token_expired_) { + access_token_expired_ = true; + oauth2_token_service_->InvalidateToken(OAuth2TokenService::ScopeSet(), + access_token_); + StartFetching(); + return; + } + + if (response_code != net::HTTP_OK) { + // TODO(bauerb): We should return the HTTP response code somehow. + DLOG(WARNING) << "HTTP error " << response_code; + DispatchGoogleServiceAuthError( + GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED), + std::string()); + return; + } + + std::string response_body; + source->GetResponseAsString(&response_body); + scoped_ptr<base::Value> value(base::JSONReader::Read(response_body)); + DictionaryValue* dict = NULL; + if (!value.get() || !value->GetAsDictionary(&dict)) { + DispatchNetworkError(net::ERR_INVALID_RESPONSE); + return; + } + std::string auth_code; + if (!dict->GetString(kCodeKey, &auth_code)) { + DispatchNetworkError(net::ERR_INVALID_RESPONSE); + return; + } + + gaia::OAuthClientInfo client_info; + GaiaUrls* urls = GaiaUrls::GetInstance(); + client_info.client_id = urls->oauth2_chrome_client_id(); + client_info.client_secret = urls->oauth2_chrome_client_secret(); + gaia_oauth_client_.reset( + new gaia::GaiaOAuthClient(GaiaUrls::GetInstance()->oauth2_token_url(), + context_)); + gaia_oauth_client_->GetTokensFromAuthCode(client_info, auth_code, kNumRetries, + this); +} + +void ManagedUserRefreshTokenFetcherImpl::OnGetTokensResponse( + const std::string& refresh_token, + const std::string& access_token, + int expires_in_seconds) { + // TODO(bauerb): It would be nice if we could pass the access token as well, + // so we don't need to fetch another one immediately. + DispatchGoogleServiceAuthError(GoogleServiceAuthError::AuthErrorNone(), + refresh_token); +} + +void ManagedUserRefreshTokenFetcherImpl::OnRefreshTokenResponse( + const std::string& access_token, + int expires_in_seconds) { + NOTREACHED(); +} + +void ManagedUserRefreshTokenFetcherImpl::OnOAuthError() { + DispatchGoogleServiceAuthError( + GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED), + std::string()); +} + +void ManagedUserRefreshTokenFetcherImpl::OnNetworkError(int response_code) { + // TODO(bauerb): We should return the HTTP response code somehow. + DLOG(WARNING) << "HTTP error " << response_code; + DispatchGoogleServiceAuthError( + GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED), + std::string()); +} + +void ManagedUserRefreshTokenFetcherImpl::DispatchNetworkError(int error_code) { + DispatchGoogleServiceAuthError( + GoogleServiceAuthError::FromConnectionError(error_code), std::string()); +} + +void ManagedUserRefreshTokenFetcherImpl::DispatchGoogleServiceAuthError( + const GoogleServiceAuthError& error, + const std::string& token) { + callback_.Run(error, token); + callback_.Reset(); +} + +} // namespace + +// static +scoped_ptr<ManagedUserRefreshTokenFetcher> +ManagedUserRefreshTokenFetcher::Create(OAuth2TokenService* oauth2_token_service, + URLRequestContextGetter* context) { + scoped_ptr<ManagedUserRefreshTokenFetcher> fetcher( + new ManagedUserRefreshTokenFetcherImpl(oauth2_token_service, context)); + return fetcher.Pass(); +} + +ManagedUserRefreshTokenFetcher::~ManagedUserRefreshTokenFetcher() {} diff --git a/chrome/browser/managed_mode/managed_user_refresh_token_fetcher.h b/chrome/browser/managed_mode/managed_user_refresh_token_fetcher.h new file mode 100644 index 0000000..392f383 --- /dev/null +++ b/chrome/browser/managed_mode/managed_user_refresh_token_fetcher.h @@ -0,0 +1,50 @@ +// 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. + +#ifndef CHROME_BROWSER_MANAGED_MODE_MANAGED_USER_REFRESH_TOKEN_FETCHER_H_ +#define CHROME_BROWSER_MANAGED_MODE_MANAGED_USER_REFRESH_TOKEN_FETCHER_H_ + +#include <string> + +#include "base/callback_forward.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" + +class GoogleServiceAuthError; +class OAuth2TokenService; + +namespace net { +class URLRequestContextGetter; +} + +// This class fetches an OAuth2 refresh token that is tied to a managed user ID +// and downscoped to a special scope for Chrome Sync for managed users. +// Fetching the token consists of the following steps: +// 1. Get an access token for the custodian from OAuth2TokenService +// (either cached or fetched). +// 2. Call the IssueToken API to mint a scoped authorization code for a +// refresh token for the managed user from the custodian's access token. +// 3. Exchange the authorization code for a refresh token for the managed +// user and return it to the caller. The refresh token can only be used to +// mint tokens with the special managed user Sync scope. +class ManagedUserRefreshTokenFetcher { + public: + typedef base::Callback<void(const GoogleServiceAuthError& /* error */, + const std::string& /* refresh_token */)> + TokenCallback; + + static scoped_ptr<ManagedUserRefreshTokenFetcher> Create( + OAuth2TokenService* oauth2_token_service, + net::URLRequestContextGetter* context); + + virtual ~ManagedUserRefreshTokenFetcher(); + + virtual void Start(const std::string& managed_user_id, + const string16& name, + const std::string& device_name, + const TokenCallback& callback) = 0; +}; + +#endif // CHROME_BROWSER_MANAGED_MODE_MANAGED_USER_REFRESH_TOKEN_FETCHER_H_ diff --git a/chrome/browser/managed_mode/managed_user_refresh_token_fetcher_unittest.cc b/chrome/browser/managed_mode/managed_user_refresh_token_fetcher_unittest.cc new file mode 100644 index 0000000..83c8285 --- /dev/null +++ b/chrome/browser/managed_mode/managed_user_refresh_token_fetcher_unittest.cc @@ -0,0 +1,426 @@ +// 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/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/managed_mode/managed_user_refresh_token_fetcher.h" +#include "chrome/browser/signin/oauth2_token_service.h" +#include "chrome/test/base/testing_profile.h" +#include "content/public/test/test_browser_thread.h" +#include "google_apis/gaia/gaia_urls.h" +#include "google_apis/gaia/google_service_auth_error.h" +#include "net/base/net_errors.h" +#include "net/base/url_util.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_status_code.h" +#include "net/url_request/test_url_fetcher_factory.h" +#include "net/url_request/url_fetcher_delegate.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kManagedUserId[] = "abcdef"; +const char kName[] = "Homestar"; +const char kDeviceName[] = "Compy"; + +const char kAccessToken[] = "accesstoken"; +const char kAuthorizationCode[] = "authorizationcode"; +const char kManagedUserToken[] = "managedusertoken"; + +const char kIssueTokenResponseFormat[] = + "{" + " \"code\": \"%s\"" + "}"; + +const char kGetRefreshTokenResponseFormat[] = + "{" + " \"access_token\": \"<ignored>\"," + " \"expires_in\": 12345," + " \"refresh_token\": \"%s\"" + "}"; + +// MockOAuth2TokenService --------------------------------------------- + +class MockOAuth2TokenService : public OAuth2TokenService { + public: + class Request : public OAuth2TokenService::Request { + public: + Request(const OAuth2TokenService::ScopeSet& scopes, + OAuth2TokenService::Consumer* consumer, + MockOAuth2TokenService* owner); + virtual ~Request(); + + void Succeed(); + void Fail(GoogleServiceAuthError::State error); + + const OAuth2TokenService::ScopeSet& scopes() const { return scopes_; } + + private: + OAuth2TokenService::ScopeSet scopes_; + + OAuth2TokenService::Consumer* consumer_; + + MockOAuth2TokenService* owner_; + }; + + MockOAuth2TokenService(); + virtual ~MockOAuth2TokenService(); + + Request* request() const { return request_; } + + void ClearRequest(Request* request); + + private: + // OAuth2TokenService overrides: + virtual scoped_ptr<OAuth2TokenService::Request> StartRequest( + const OAuth2TokenService::ScopeSet& scopes, + OAuth2TokenService::Consumer* consumer) OVERRIDE; + virtual std::string GetRefreshToken() OVERRIDE; + + Request* request_; + + DISALLOW_COPY_AND_ASSIGN(MockOAuth2TokenService); +}; + +MockOAuth2TokenService::Request::Request( + const OAuth2TokenService::ScopeSet& scopes, + OAuth2TokenService::Consumer* consumer, + MockOAuth2TokenService* owner) + : scopes_(scopes), + consumer_(consumer), + owner_(owner) {} + +MockOAuth2TokenService::Request::~Request() { + owner_->ClearRequest(this); +} + +void MockOAuth2TokenService::Request::Succeed() { + base::Time expiration_date = base::Time::Now() + + base::TimeDelta::FromHours(1); + consumer_->OnGetTokenSuccess(this, kAccessToken, expiration_date); +} + +void MockOAuth2TokenService::Request::Fail( + GoogleServiceAuthError::State error) { + consumer_->OnGetTokenFailure(this, GoogleServiceAuthError(error)); +} + +MockOAuth2TokenService::MockOAuth2TokenService() + : OAuth2TokenService(NULL), + request_(NULL) {} + +MockOAuth2TokenService::~MockOAuth2TokenService() { + EXPECT_FALSE(request_); +} + +void MockOAuth2TokenService::ClearRequest( + MockOAuth2TokenService::Request* request) { + if (request_ == request) + request_ = NULL; +} + +scoped_ptr<OAuth2TokenService::Request> MockOAuth2TokenService::StartRequest( + const OAuth2TokenService::ScopeSet& scopes, + OAuth2TokenService::Consumer* consumer) { + scoped_ptr<Request> request(new Request(scopes, consumer, this)); + request_ = request.get(); + return request.PassAs<OAuth2TokenService::Request>(); +} + +std::string MockOAuth2TokenService::GetRefreshToken() { + NOTREACHED(); + return std::string(); +} + +// Utility methods -------------------------------------------------- + +// Slightly hacky way to extract a value from a URL-encoded POST request body. +bool GetValueForKey(const std::string& encoded_string, + const std::string& key, + std::string* value) { + GURL url("http://example.com/?" + encoded_string); + return net::GetValueForKeyInQuery(url, key, value); +} + +void SendResponse(net::TestURLFetcher* url_fetcher, + const std::string& response) { + url_fetcher->set_status( + net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0)); + url_fetcher->set_response_code(net::HTTP_OK); + url_fetcher->SetResponseString(response); + url_fetcher->delegate()->OnURLFetchComplete(url_fetcher); +} + +void SetNetworkError(net::TestURLFetcher* url_fetcher, int error) { + url_fetcher->set_status( + net::URLRequestStatus(net::URLRequestStatus::FAILED, error)); + url_fetcher->delegate()->OnURLFetchComplete(url_fetcher); +} + +void SetHttpError(net::TestURLFetcher* url_fetcher, int error) { + url_fetcher->set_status(net::URLRequestStatus()); + url_fetcher->set_response_code(error); + url_fetcher->delegate()->OnURLFetchComplete(url_fetcher); +} + +} // namespace + +class ManagedUserRefreshTokenFetcherTest : public testing::Test { + public: + ManagedUserRefreshTokenFetcherTest(); + virtual ~ManagedUserRefreshTokenFetcherTest() {} + + protected: + void StartFetching(); + + MockOAuth2TokenService::Request* GetOAuth2TokenServiceRequest(); + net::TestURLFetcher* GetIssueTokenRequest(); + net::TestURLFetcher* GetRefreshTokenRequest(); + + void MakeIssueTokenRequestSucceed(); + void MakeRefreshTokenFetchSucceed(); + + void Reset(); + + const GoogleServiceAuthError& error() const { return error_; } + const std::string& token() const { return token_; } + + private: + void OnTokenFetched(const GoogleServiceAuthError& error, + const std::string& token); + + base::WeakPtrFactory<ManagedUserRefreshTokenFetcherTest> weak_ptr_factory_; + base::MessageLoop message_loop_; + content::TestBrowserThread ui_thread_; + TestingProfile profile_; + MockOAuth2TokenService oauth2_token_service_; + net::TestURLFetcherFactory url_fetcher_factory_; + scoped_ptr<ManagedUserRefreshTokenFetcher> token_fetcher_; + + GoogleServiceAuthError error_; + std::string token_; +}; + +ManagedUserRefreshTokenFetcherTest::ManagedUserRefreshTokenFetcherTest() + : weak_ptr_factory_(this), + ui_thread_(content::BrowserThread::UI, &message_loop_), + token_fetcher_( + ManagedUserRefreshTokenFetcher::Create(&oauth2_token_service_, + profile_.GetRequestContext())), + error_(GoogleServiceAuthError::NONE) {} + +void ManagedUserRefreshTokenFetcherTest::StartFetching() { + token_fetcher_->Start(kManagedUserId, UTF8ToUTF16(kName), kDeviceName, + base::Bind( + &ManagedUserRefreshTokenFetcherTest::OnTokenFetched, + weak_ptr_factory_.GetWeakPtr())); +} + +MockOAuth2TokenService::Request* +ManagedUserRefreshTokenFetcherTest::GetOAuth2TokenServiceRequest() { + MockOAuth2TokenService::Request* request = oauth2_token_service_.request(); + + OAuth2TokenService::ScopeSet scopes = request->scopes(); + EXPECT_EQ(1u, scopes.size()); + EXPECT_EQ(1u, scopes.count(GaiaUrls::GetInstance()->oauth1_login_scope())); + return request; +} + +net::TestURLFetcher* +ManagedUserRefreshTokenFetcherTest::GetIssueTokenRequest() { + net::TestURLFetcher* url_fetcher = url_fetcher_factory_.GetFetcherByID(1); + if (!url_fetcher) + return NULL; + + EXPECT_EQ(GaiaUrls::GetInstance()->oauth2_issue_token_url(), + url_fetcher->GetOriginalURL().spec()); + std::string access_token; + net::HttpRequestHeaders headers; + url_fetcher->GetExtraRequestHeaders(&headers); + EXPECT_TRUE(headers.GetHeader("Authorization", &access_token)); + EXPECT_EQ(std::string("Bearer ") + kAccessToken, access_token); + const std::string upload_data = url_fetcher->upload_data(); + std::string managed_user_id; + EXPECT_TRUE(GetValueForKey(upload_data, "profile_id", &managed_user_id)); + EXPECT_EQ(kManagedUserId, managed_user_id); + std::string name; + EXPECT_TRUE(GetValueForKey(upload_data, "profile_name", &name)); + EXPECT_EQ(kName, name); + std::string device_name; + EXPECT_TRUE(GetValueForKey(upload_data, "device_name", &device_name)); + EXPECT_EQ(kDeviceName, device_name); + return url_fetcher; +} + +net::TestURLFetcher* +ManagedUserRefreshTokenFetcherTest::GetRefreshTokenRequest() { + net::TestURLFetcher* url_fetcher = url_fetcher_factory_.GetFetcherByID(0); + if (!url_fetcher) + return NULL; + + EXPECT_EQ(GaiaUrls::GetInstance()->oauth2_token_url(), + url_fetcher->GetOriginalURL().spec()); + std::string auth_code; + EXPECT_TRUE(GetValueForKey(url_fetcher->upload_data(), "code", &auth_code)); + EXPECT_EQ(kAuthorizationCode, auth_code); + return url_fetcher; +} + +void ManagedUserRefreshTokenFetcherTest::MakeIssueTokenRequestSucceed() { + SendResponse(GetIssueTokenRequest(), + base::StringPrintf(kIssueTokenResponseFormat, + kAuthorizationCode)); +} + +void ManagedUserRefreshTokenFetcherTest::MakeRefreshTokenFetchSucceed() { + SendResponse(GetRefreshTokenRequest(), + base::StringPrintf(kGetRefreshTokenResponseFormat, + kManagedUserToken)); +} + +void ManagedUserRefreshTokenFetcherTest::Reset() { + token_fetcher_.reset(); +} + +void ManagedUserRefreshTokenFetcherTest::OnTokenFetched( + const GoogleServiceAuthError& error, + const std::string& token) { + error_ = error; + token_ = token; +} + +// Tests -------------------------------------------------------- + +TEST_F(ManagedUserRefreshTokenFetcherTest, Success) { + StartFetching(); + GetOAuth2TokenServiceRequest()->Succeed(); + MakeIssueTokenRequestSucceed(); + MakeRefreshTokenFetchSucceed(); + + EXPECT_EQ(GoogleServiceAuthError::NONE, error().state()); + EXPECT_EQ(kManagedUserToken, token()); +} + +TEST_F(ManagedUserRefreshTokenFetcherTest, ExpiredAccessToken) { + StartFetching(); + GetOAuth2TokenServiceRequest()->Succeed(); + SetHttpError(GetIssueTokenRequest(), net::HTTP_UNAUTHORIZED); + GetOAuth2TokenServiceRequest()->Succeed(); + MakeIssueTokenRequestSucceed(); + MakeRefreshTokenFetchSucceed(); + + EXPECT_EQ(GoogleServiceAuthError::NONE, error().state()); + EXPECT_EQ(kManagedUserToken, token()); +} + +TEST_F(ManagedUserRefreshTokenFetcherTest, ExpiredAccessTokenRetry) { + // If we get a 401 error for the second time, we should give up instead of + // retrying again. + StartFetching(); + GetOAuth2TokenServiceRequest()->Succeed(); + SetHttpError(GetIssueTokenRequest(), net::HTTP_UNAUTHORIZED); + GetOAuth2TokenServiceRequest()->Succeed(); + SetHttpError(GetIssueTokenRequest(), net::HTTP_UNAUTHORIZED); + + EXPECT_EQ(GoogleServiceAuthError::CONNECTION_FAILED, error().state()); + EXPECT_EQ(net::ERR_FAILED, error().network_error()); + EXPECT_EQ(std::string(), token()); +} + +TEST_F(ManagedUserRefreshTokenFetcherTest, MalformedIssueTokenResponse) { + StartFetching(); + GetOAuth2TokenServiceRequest()->Succeed(); + SendResponse(GetIssueTokenRequest(), "choke"); + + EXPECT_EQ(GoogleServiceAuthError::CONNECTION_FAILED, error().state()); + EXPECT_EQ(net::ERR_INVALID_RESPONSE, error().network_error()); + EXPECT_EQ(std::string(), token()); +} + +TEST_F(ManagedUserRefreshTokenFetcherTest, FetchAccessTokenFailure) { + StartFetching(); + GetOAuth2TokenServiceRequest()->Fail( + GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); + + EXPECT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS, error().state()); + EXPECT_EQ(std::string(), token()); +} + +TEST_F(ManagedUserRefreshTokenFetcherTest, IssueTokenNetworkError) { + StartFetching(); + GetOAuth2TokenServiceRequest()->Succeed(); + SetNetworkError(GetIssueTokenRequest(), net::ERR_SSL_PROTOCOL_ERROR); + + EXPECT_EQ(GoogleServiceAuthError::CONNECTION_FAILED, error().state()); + EXPECT_EQ(net::ERR_SSL_PROTOCOL_ERROR, error().network_error()); + EXPECT_EQ(std::string(), token()); +} + +TEST_F(ManagedUserRefreshTokenFetcherTest, FetchRefreshTokenNetworkError) { + StartFetching(); + GetOAuth2TokenServiceRequest()->Succeed(); + MakeIssueTokenRequestSucceed(); + SetNetworkError(GetRefreshTokenRequest(), net::ERR_CONNECTION_REFUSED); + EXPECT_EQ(GoogleServiceAuthError::NONE, error().state()); + SetNetworkError(GetRefreshTokenRequest(), net::ERR_CONNECTION_REFUSED); + + EXPECT_EQ(GoogleServiceAuthError::CONNECTION_FAILED, error().state()); + EXPECT_EQ(net::ERR_FAILED, error().network_error()); + EXPECT_EQ(std::string(), token()); +} + +TEST_F(ManagedUserRefreshTokenFetcherTest, + FetchRefreshTokenTransientNetworkError) { + StartFetching(); + GetOAuth2TokenServiceRequest()->Succeed(); + MakeIssueTokenRequestSucceed(); + SetNetworkError(GetRefreshTokenRequest(), net::ERR_CONNECTION_REFUSED); + + EXPECT_EQ(GoogleServiceAuthError::NONE, error().state()); + MakeRefreshTokenFetchSucceed(); + + EXPECT_EQ(GoogleServiceAuthError::NONE, error().state()); + EXPECT_EQ(kManagedUserToken, token()); +} + +TEST_F(ManagedUserRefreshTokenFetcherTest, FetchRefreshTokenBadRequest) { + StartFetching(); + GetOAuth2TokenServiceRequest()->Succeed(); + MakeIssueTokenRequestSucceed(); + SetHttpError(GetRefreshTokenRequest(), net::HTTP_BAD_REQUEST); + + EXPECT_EQ(GoogleServiceAuthError::CONNECTION_FAILED, error().state()); + EXPECT_EQ(net::ERR_FAILED, error().network_error()); + EXPECT_EQ(std::string(), token()); +} + +TEST_F(ManagedUserRefreshTokenFetcherTest, CancelWhileFetchingAccessToken) { + StartFetching(); + Reset(); + + EXPECT_EQ(GoogleServiceAuthError::NONE, error().state()); + EXPECT_EQ(std::string(), token()); +} + +TEST_F(ManagedUserRefreshTokenFetcherTest, CancelWhileCallingIssueToken) { + StartFetching(); + GetOAuth2TokenServiceRequest()->Succeed(); + Reset(); + + EXPECT_EQ(GoogleServiceAuthError::NONE, error().state()); + EXPECT_EQ(std::string(), token()); +} + +TEST_F(ManagedUserRefreshTokenFetcherTest, CancelWhileFetchingRefreshToken) { + StartFetching(); + GetOAuth2TokenServiceRequest()->Succeed(); + MakeIssueTokenRequestSucceed(); + Reset(); + + EXPECT_EQ(GoogleServiceAuthError::NONE, error().state()); + EXPECT_EQ(std::string(), token()); +} diff --git a/chrome/browser/managed_mode/managed_user_registration_service.cc b/chrome/browser/managed_mode/managed_user_registration_service.cc index a4c9eaf..093d2d1f 100644 --- a/chrome/browser/managed_mode/managed_user_registration_service.cc +++ b/chrome/browser/managed_mode/managed_user_registration_service.cc @@ -9,11 +9,14 @@ #include "base/prefs/pref_service.h" #include "base/rand_util.h" #include "base/strings/utf_string_conversions.h" +#include "chrome/browser/managed_mode/managed_user_refresh_token_fetcher.h" #include "chrome/browser/managed_mode/managed_user_service.h" #include "chrome/browser/managed_mode/managed_user_service_factory.h" #include "chrome/browser/prefs/scoped_user_pref_update.h" +#include "chrome/browser/sync/glue/device_info.h" #include "chrome/common/pref_names.h" #include "components/user_prefs/pref_registry_syncable.h" +#include "google_apis/gaia/gaia_urls.h" #include "google_apis/gaia/google_service_auth_error.h" #include "sync/api/sync_change.h" #include "sync/api/sync_error_factory.h" @@ -52,9 +55,11 @@ SyncData CreateLocalSyncData(const std::string& id, } // namespace ManagedUserRegistrationService::ManagedUserRegistrationService( - PrefService* prefs) + PrefService* prefs, + scoped_ptr<ManagedUserRefreshTokenFetcher> token_fetcher) : weak_ptr_factory_(this), prefs_(prefs), + token_fetcher_(token_fetcher.Pass()), pending_managed_user_acknowledged_(false) { pref_change_registrar_.Init(prefs); pref_change_registrar_.Add( @@ -103,7 +108,9 @@ void ManagedUserRegistrationService::Register( } callback_ = callback; - OnReceivedToken("abcdef"); // TODO(bauerb): This is a stub implementation. + browser_sync::DeviceInfo::CreateLocalDeviceInfo( + base::Bind(&ManagedUserRegistrationService::FetchToken, + weak_ptr_factory_.GetWeakPtr(), name)); } ProfileManager::CreateCallback @@ -268,8 +275,24 @@ void ManagedUserRegistrationService::OnManagedUserAcknowledged( DispatchCallbackIfReady(); } -void ManagedUserRegistrationService::OnReceivedToken(const std::string& token) { - DCHECK(pending_managed_user_token_.empty()); +void ManagedUserRegistrationService::FetchToken( + const string16& name, + const browser_sync::DeviceInfo& device_info) { + token_fetcher_->Start( + pending_managed_user_id_, name, device_info.client_name(), + base::Bind(&ManagedUserRegistrationService::OnReceivedToken, + weak_ptr_factory_.GetWeakPtr())); +} + +void ManagedUserRegistrationService::OnReceivedToken( + const GoogleServiceAuthError& error, + const std::string& token) { + if (error.state() != GoogleServiceAuthError::NONE) { + DispatchCallback(error); + return; + } + + DCHECK(!token.empty()); pending_managed_user_token_ = token; DispatchCallbackIfReady(); } diff --git a/chrome/browser/managed_mode/managed_user_registration_service.h b/chrome/browser/managed_mode/managed_user_registration_service.h index fcdefb0..d6f9221 100644 --- a/chrome/browser/managed_mode/managed_user_registration_service.h +++ b/chrome/browser/managed_mode/managed_user_registration_service.h @@ -17,8 +17,13 @@ #include "sync/api/syncable_service.h" class GoogleServiceAuthError; +class ManagedUserRefreshTokenFetcher; class PrefService; +namespace browser_sync { +class DeviceInfo; +} + namespace user_prefs { class PrefRegistrySyncable; } @@ -37,13 +42,17 @@ class ManagedUserRegistrationService : public BrowserContextKeyedService, const std::string& /* token */)> RegistrationCallback; - explicit ManagedUserRegistrationService(PrefService* prefs); + ManagedUserRegistrationService( + PrefService* prefs, + scoped_ptr<ManagedUserRefreshTokenFetcher> token_fetcher); virtual ~ManagedUserRegistrationService(); static void RegisterUserPrefs(user_prefs::PrefRegistrySyncable* registry); // Registers a new managed user with the server. |name| is the display name of // the user. |callback| is called with the result of the registration. + // TODO(bauerb): There should be a way to cancel a pending managed user + // registration. void Register(const string16& name, const RegistrationCallback& callback); // Convenience method that registers a new managed user with the server and @@ -77,8 +86,13 @@ class ManagedUserRegistrationService : public BrowserContextKeyedService, // Called when the Sync server has acknowledged a newly created managed user. void OnManagedUserAcknowledged(const std::string& managed_user_id); + // Fetches the managed user token when we have the device info. + void FetchToken(const string16& name, + const browser_sync::DeviceInfo& device_info); + // Called when we have received a token for the managed user. - void OnReceivedToken(const std::string& token); + void OnReceivedToken(const GoogleServiceAuthError& error, + const std::string& token); // Dispatches the callback if all the conditions have been met. void DispatchCallbackIfReady(); @@ -94,6 +108,7 @@ class ManagedUserRegistrationService : public BrowserContextKeyedService, base::WeakPtrFactory<ManagedUserRegistrationService> weak_ptr_factory_; PrefService* prefs_; PrefChangeRegistrar pref_change_registrar_; + scoped_ptr<ManagedUserRefreshTokenFetcher> token_fetcher_; scoped_ptr<syncer::SyncChangeProcessor> sync_processor_; scoped_ptr<syncer::SyncErrorFactory> error_handler_; diff --git a/chrome/browser/managed_mode/managed_user_registration_service_factory.cc b/chrome/browser/managed_mode/managed_user_registration_service_factory.cc index 2ed577a..9e4cb41 100644 --- a/chrome/browser/managed_mode/managed_user_registration_service_factory.cc +++ b/chrome/browser/managed_mode/managed_user_registration_service_factory.cc @@ -4,7 +4,11 @@ #include "chrome/browser/managed_mode/managed_user_registration_service_factory.h" +#include "chrome/browser/managed_mode/managed_user_refresh_token_fetcher.h" #include "chrome/browser/managed_mode/managed_user_registration_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/signin/profile_oauth2_token_service.h" +#include "chrome/browser/signin/profile_oauth2_token_service_factory.h" #include "components/browser_context_keyed_service/browser_context_dependency_manager.h" // static @@ -23,13 +27,20 @@ ManagedUserRegistrationServiceFactory::GetInstance() { // static BrowserContextKeyedService* ManagedUserRegistrationServiceFactory::BuildInstanceFor(Profile* profile) { - return new ManagedUserRegistrationService(profile->GetPrefs()); + OAuth2TokenService* oauth2_token_service = + ProfileOAuth2TokenServiceFactory::GetForProfile(profile); + return new ManagedUserRegistrationService( + profile->GetPrefs(), + ManagedUserRefreshTokenFetcher::Create(oauth2_token_service, + profile->GetRequestContext())); } ManagedUserRegistrationServiceFactory::ManagedUserRegistrationServiceFactory() : BrowserContextKeyedServiceFactory( "ManagedUserRegistrationService", - BrowserContextDependencyManager::GetInstance()) {} + BrowserContextDependencyManager::GetInstance()) { + DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance()); +} ManagedUserRegistrationServiceFactory:: ~ManagedUserRegistrationServiceFactory() {} diff --git a/chrome/browser/managed_mode/managed_user_registration_service_unittest.cc b/chrome/browser/managed_mode/managed_user_registration_service_unittest.cc index f3b3f3c..3947786 100644 --- a/chrome/browser/managed_mode/managed_user_registration_service_unittest.cc +++ b/chrome/browser/managed_mode/managed_user_registration_service_unittest.cc @@ -5,7 +5,10 @@ #include <string> #include "base/bind.h" +#include "base/message_loop.h" +#include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" +#include "chrome/browser/managed_mode/managed_user_refresh_token_fetcher.h" #include "chrome/browser/managed_mode/managed_user_registration_service.h" #include "chrome/browser/prefs/scoped_user_pref_update.h" #include "chrome/common/pref_names.h" @@ -29,6 +32,8 @@ using syncer::SyncMergeResult; namespace { +const char kManagedUserToken[] = "managedusertoken"; + class MockChangeProcessor : public SyncChangeProcessor { public: MockChangeProcessor() {} @@ -62,6 +67,22 @@ SyncChange MockChangeProcessor::GetChange(const std::string& id) const { return SyncChange(); } +class MockManagedUserRefreshTokenFetcher + : public ManagedUserRefreshTokenFetcher { + public: + MockManagedUserRefreshTokenFetcher() {} + virtual ~MockManagedUserRefreshTokenFetcher() {} + + // ManagedUserRefreshTokenFetcher implementation: + virtual void Start(const std::string& managed_user_id, + const string16& name, + const std::string& device_name, + const TokenCallback& callback) OVERRIDE { + GoogleServiceAuthError error(GoogleServiceAuthError::NONE); + callback.Run(error, kManagedUserToken); + } +}; + } // namespace class ManagedUserRegistrationServiceTest : public ::testing::Test { @@ -94,6 +115,8 @@ class ManagedUserRegistrationServiceTest : public ::testing::Test { void OnManagedUserRegistered(const GoogleServiceAuthError& error, const std::string& token); + base::MessageLoop message_loop_; + base::RunLoop run_loop_; base::WeakPtrFactory<ManagedUserRegistrationServiceTest> weak_ptr_factory_; TestingPrefServiceSyncable prefs_; scoped_ptr<ManagedUserRegistrationService> service_; @@ -114,12 +137,15 @@ class ManagedUserRegistrationServiceTest : public ::testing::Test { ManagedUserRegistrationServiceTest::ManagedUserRegistrationServiceTest() : weak_ptr_factory_(this), - service_(new ManagedUserRegistrationService(&prefs_)), change_processor_(NULL), sync_data_id_(0), received_callback_(false), error_(GoogleServiceAuthError::NUM_STATES) { ManagedUserRegistrationService::RegisterUserPrefs(prefs_.registry()); + scoped_ptr<ManagedUserRefreshTokenFetcher> token_fetcher( + new MockManagedUserRefreshTokenFetcher); + service_.reset( + new ManagedUserRegistrationService(&prefs_, token_fetcher.Pass())); } ManagedUserRegistrationServiceTest::~ManagedUserRegistrationServiceTest() { @@ -180,6 +206,8 @@ void ManagedUserRegistrationServiceTest::Acknowledge() { SyncData::CreateRemoteData(++sync_data_id_, specifics))); } service()->ProcessSyncChanges(FROM_HERE, new_changes); + + run_loop_.Run(); } void ManagedUserRegistrationServiceTest::ResetService() { @@ -194,6 +222,7 @@ void ManagedUserRegistrationServiceTest::OnManagedUserRegistered( received_callback_ = true; error_ = error; token_ = token; + run_loop_.Quit(); } TEST_F(ManagedUserRegistrationServiceTest, MergeEmpty) { diff --git a/chrome/browser/signin/android_profile_oauth2_token_service.cc b/chrome/browser/signin/android_profile_oauth2_token_service.cc index 698ee44..c013185 100644 --- a/chrome/browser/signin/android_profile_oauth2_token_service.cc +++ b/chrome/browser/signin/android_profile_oauth2_token_service.cc @@ -33,7 +33,7 @@ scoped_ptr<OAuth2TokenService::Request> ProfileSyncServiceAndroid::GetProfileSyncServiceAndroid(); sync_service->FetchOAuth2Token( scope_list.front(), - base::Bind(&OAuth2TokenService::InformConsumer, + base::Bind(&RequestImpl::InformConsumer, request->AsWeakPtr())); return request.PassAs<Request>(); } diff --git a/chrome/browser/signin/oauth2_token_service.cc b/chrome/browser/signin/oauth2_token_service.cc index aeacd41..141c813 100644 --- a/chrome/browser/signin/oauth2_token_service.cc +++ b/chrome/browser/signin/oauth2_token_service.cc @@ -303,18 +303,6 @@ bool OAuth2TokenService::RefreshTokenIsAvailable() { return !GetRefreshToken().empty(); } -// static -void OAuth2TokenService::InformConsumer( - base::WeakPtr<OAuth2TokenService::RequestImpl> request, - const GoogleServiceAuthError& error, - const std::string& access_token, - const base::Time& expiration_date) { - DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); - - if (request) - request->InformConsumer(error, access_token, expiration_date); -} - scoped_ptr<OAuth2TokenService::Request> OAuth2TokenService::StartRequest( const OAuth2TokenService::ScopeSet& scopes, OAuth2TokenService::Consumer* consumer) { @@ -325,7 +313,7 @@ scoped_ptr<OAuth2TokenService::Request> OAuth2TokenService::StartRequest( std::string refresh_token = GetRefreshToken(); if (refresh_token.empty()) { base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind( - &OAuth2TokenService::InformConsumer, + &RequestImpl::InformConsumer, request->AsWeakPtr(), GoogleServiceAuthError( GoogleServiceAuthError::USER_NOT_SIGNED_UP), @@ -361,7 +349,7 @@ scoped_ptr<OAuth2TokenService::Request> const CacheEntry* cache_entry = GetCacheEntry(scopes); scoped_ptr<RequestImpl> request(new RequestImpl(consumer)); base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind( - &OAuth2TokenService::InformConsumer, + &RequestImpl::InformConsumer, request->AsWeakPtr(), GoogleServiceAuthError(GoogleServiceAuthError::NONE), cache_entry->access_token, @@ -440,7 +428,7 @@ bool OAuth2TokenService::RemoveCacheEntry( const std::string& token_to_remove) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); TokenCache::iterator token_iterator = token_cache_.find(scopes); - if (token_iterator == token_cache_.end() && + if (token_iterator != token_cache_.end() && token_iterator->second.access_token == token_to_remove) { token_cache_.erase(token_iterator); return true; diff --git a/chrome/browser/signin/oauth2_token_service.h b/chrome/browser/signin/oauth2_token_service.h index 9260f41..e96b9c1 100644 --- a/chrome/browser/signin/oauth2_token_service.h +++ b/chrome/browser/signin/oauth2_token_service.h @@ -148,12 +148,6 @@ class OAuth2TokenService { Consumer* const consumer_; }; - // Informs the consumer of |request| fetch results. - static void InformConsumer(base::WeakPtr<RequestImpl> request, - const GoogleServiceAuthError& error, - const std::string& access_token, - const base::Time& expiration_date); - private: // Class that fetches an OAuth2 access token for a given set of scopes and // OAuth2 refresh token. diff --git a/chrome/browser/signin/oauth2_token_service_unittest.cc b/chrome/browser/signin/oauth2_token_service_unittest.cc index 4acffb9..5808b9c 100644 --- a/chrome/browser/signin/oauth2_token_service_unittest.cc +++ b/chrome/browser/signin/oauth2_token_service_unittest.cc @@ -404,7 +404,7 @@ TEST_F(OAuth2TokenServiceTest, RetryingConsumer) { EXPECT_EQ(0, consumer.number_of_errors_); net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0); - EXPECT_TRUE(fetcher); + ASSERT_TRUE(fetcher); fetcher->set_response_code(net::HTTP_UNAUTHORIZED); fetcher->SetResponseString(std::string()); fetcher->delegate()->OnURLFetchComplete(fetcher); @@ -412,10 +412,59 @@ TEST_F(OAuth2TokenServiceTest, RetryingConsumer) { EXPECT_EQ(1, consumer.number_of_errors_); fetcher = factory_.GetFetcherByID(0); - EXPECT_TRUE(fetcher); + ASSERT_TRUE(fetcher); fetcher->set_response_code(net::HTTP_UNAUTHORIZED); fetcher->SetResponseString(std::string()); fetcher->delegate()->OnURLFetchComplete(fetcher); EXPECT_EQ(0, consumer.number_of_successful_tokens_); EXPECT_EQ(2, consumer.number_of_errors_); } + +TEST_F(OAuth2TokenServiceTest, InvalidateToken) { + std::set<std::string> scopes; + oauth2_service_->set_refresh_token("refreshToken"); + + // First request. + scoped_ptr<OAuth2TokenService::Request> request( + oauth2_service_->StartRequest(scopes, &consumer_)); + message_loop_.RunUntilIdle(); + + EXPECT_EQ(0, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0); + EXPECT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_OK); + fetcher->SetResponseString(GetValidTokenResponse("token", 3600)); + fetcher->delegate()->OnURLFetchComplete(fetcher); + EXPECT_EQ(1, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + EXPECT_EQ("token", consumer_.last_token_); + + // Second request, should return the same token without needing a network + // request. + scoped_ptr<OAuth2TokenService::Request> request2( + oauth2_service_->StartRequest(scopes, &consumer_)); + message_loop_.RunUntilIdle(); + + // No new network fetcher. + EXPECT_EQ(fetcher, factory_.GetFetcherByID(0)); + EXPECT_EQ(2, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + EXPECT_EQ("token", consumer_.last_token_); + + // Invalidating the token should return a new token on the next request. + oauth2_service_->InvalidateToken(scopes, consumer_.last_token_); + scoped_ptr<OAuth2TokenService::Request> request3( + oauth2_service_->StartRequest(scopes, &consumer_)); + message_loop_.RunUntilIdle(); + EXPECT_EQ(2, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + fetcher = factory_.GetFetcherByID(0); + EXPECT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_OK); + fetcher->SetResponseString(GetValidTokenResponse("token2", 3600)); + fetcher->delegate()->OnURLFetchComplete(fetcher); + EXPECT_EQ(3, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + EXPECT_EQ("token2", consumer_.last_token_); +} diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 979337d..b321f35 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -965,6 +965,8 @@ 'browser/managed_mode/managed_user_service.h', 'browser/managed_mode/managed_user_service_factory.cc', 'browser/managed_mode/managed_user_service_factory.h', + 'browser/managed_mode/managed_user_refresh_token_fetcher.cc', + 'browser/managed_mode/managed_user_refresh_token_fetcher.h', 'browser/media/audio_stream_indicator.cc', 'browser/media/audio_stream_indicator.h', 'browser/media/media_capture_devices_dispatcher.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 22860fd..4695541 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -915,6 +915,7 @@ 'browser/managed_mode/managed_user_passphrase_unittest.cc', 'browser/managed_mode/managed_user_registration_service_unittest.cc', 'browser/managed_mode/managed_user_service_unittest.cc', + 'browser/managed_mode/managed_user_refresh_token_fetcher_unittest.cc', 'browser/media_galleries/fileapi/itunes_finder_win_unittest.cc', 'browser/media_galleries/fileapi/native_media_file_util_unittest.cc', 'browser/media_galleries/fileapi/picasa/picasa_album_table_reader_unittest.cc', diff --git a/google_apis/gaia/gaia_oauth_client.cc b/google_apis/gaia/gaia_oauth_client.cc index a9bae4f..7e15c9d 100644 --- a/google_apis/gaia/gaia_oauth_client.cc +++ b/google_apis/gaia/gaia_oauth_client.cc @@ -203,7 +203,7 @@ void GaiaOAuthClient::Core::HandleResponse( // If we don't have an access token yet and the the error was not // RC_BAD_REQUEST, we may need to retry. if ((source->GetMaxRetriesOn5xx() != -1) && - (num_retries_ > source->GetMaxRetriesOn5xx())) { + (num_retries_ >= source->GetMaxRetriesOn5xx())) { // Retry limit reached. Give up. delegate_->OnNetworkError(source->GetResponseCode()); } else { |