summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordavidroche@chromium.org <davidroche@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-06-22 13:38:22 +0000
committerdavidroche@chromium.org <davidroche@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-06-22 13:38:22 +0000
commitf29f42b92d789486366cdcd0e26ff9fefb9f3ce6 (patch)
tree89f81fc034f7cd6cd016d913c1ce0b70365ec663
parentd95cbbf84a22d12d599cf3573cc13141748fcc26 (diff)
downloadchromium_src-f29f42b92d789486366cdcd0e26ff9fefb9f3ce6.zip
chromium_src-f29f42b92d789486366cdcd0e26ff9fefb9f3ce6.tar.gz
chromium_src-f29f42b92d789486366cdcd0e26ff9fefb9f3ce6.tar.bz2
Device robot refresh token integrity validation.
Before using the robot account refresh token stored in an enterprise device's Local State, verify that the token is owned by the service account id found in the device policy. BUG=245121 Review URL: https://chromiumcodereview.appspot.com/17109006 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@208019 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc5
-rw-r--r--chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h5
-rw-r--r--chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos_unittest.cc9
-rw-r--r--chrome/browser/chromeos/policy/enrollment_handler_chromeos.cc1
-rw-r--r--chrome/browser/chromeos/settings/device_oauth2_token_service.cc228
-rw-r--r--chrome/browser/chromeos/settings/device_oauth2_token_service.h25
-rw-r--r--chrome/browser/chromeos/settings/device_oauth2_token_service_unittest.cc313
-rw-r--r--chrome/browser/managed_mode/managed_user_refresh_token_fetcher.cc4
-rw-r--r--chrome/browser/managed_mode/managed_user_refresh_token_fetcher_unittest.cc4
-rw-r--r--chrome/browser/policy/cloud/policy_builder.cc2
-rw-r--r--chrome/browser/policy/cloud/policy_builder.h1
-rw-r--r--chrome/browser/policy/proto/cloud/device_management_backend.proto4
-rw-r--r--chrome/browser/signin/oauth2_token_service.cc63
-rw-r--r--chrome/browser/signin/oauth2_token_service.h44
-rw-r--r--chrome/browser/signin/ubertoken_fetcher.cc11
-rw-r--r--chrome/service/cloud_print/cloud_print_auth.cc5
-rw-r--r--google_apis/gaia/gaia_oauth_client.cc89
-rw-r--r--google_apis/gaia/gaia_oauth_client.h25
-rw-r--r--google_apis/gaia/gaia_oauth_client_unittest.cc168
-rw-r--r--google_apis/gaia/gaia_urls.cc6
-rw-r--r--google_apis/gaia/gaia_urls.h2
-rw-r--r--remoting/host/setup/host_starter.cc4
-rw-r--r--remoting/host/setup/host_starter.h1
-rw-r--r--remoting/host/setup/start_host.cc2
-rw-r--r--remoting/host/setup/win/start_host_window.cc1
-rw-r--r--remoting/host/signaling_connector.cc8
26 files changed, 860 insertions, 170 deletions
diff --git a/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc b/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc
index ade9de8..e12200f 100644
--- a/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc
+++ b/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc
@@ -223,6 +223,11 @@ std::string DeviceCloudPolicyManagerChromeOS::GetMachineModel() {
return GetMachineStatistic(kMachineInfoSystemHwqual);
}
+std::string DeviceCloudPolicyManagerChromeOS::GetRobotAccountId() {
+ const enterprise_management::PolicyData* policy = device_store_->policy();
+ return policy ? policy->service_account_identity() : std::string();
+}
+
scoped_ptr<CloudPolicyClient> DeviceCloudPolicyManagerChromeOS::CreateClient() {
return make_scoped_ptr(
new CloudPolicyClient(GetMachineID(), GetMachineModel(),
diff --git a/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h b/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h
index 005028d..e10a54a 100644
--- a/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h
+++ b/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h
@@ -88,6 +88,11 @@ class DeviceCloudPolicyManagerChromeOS : public CloudPolicyManager {
// Returns the machine model, or an empty string if not available.
static std::string GetMachineModel();
+ // Returns the robot 'email address' associated with the device robot
+ // account (sometimes called a service account) associated with this device
+ // during enterprise enrollment.
+ std::string GetRobotAccountId();
+
private:
// Creates a new CloudPolicyClient.
scoped_ptr<CloudPolicyClient> CreateClient();
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 22b47d6..2c7c0be 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
@@ -28,6 +28,7 @@
#include "chromeos/cryptohome/mock_cryptohome_library.h"
#include "chromeos/dbus/cryptohome_client.h"
#include "chromeos/dbus/dbus_client_implementation_type.h"
+#include "google_apis/gaia/gaia_oauth_client.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "net/url_request/url_request_test_util.h"
#include "policy/policy_constants.h"
@@ -158,6 +159,9 @@ TEST_F(DeviceCloudPolicyManagerChromeOSTest, EnrolledDevice) {
manager_.Shutdown();
EXPECT_TRUE(manager_.policies().Equals(bundle));
+
+ EXPECT_EQ(manager_.GetRobotAccountId(),
+ PolicyBuilder::kFakeServiceAccountIdentity);
}
TEST_F(DeviceCloudPolicyManagerChromeOSTest, ConsumerDevice) {
@@ -326,7 +330,8 @@ class DeviceCloudPolicyManagerChromeOSEnrollmentTest
// We return a successful OAuth response via a TestURLFetcher to trigger the
// happy path for these classes so that enrollment can continue.
if (robot_auth_fetch_status_ == DM_STATUS_SUCCESS) {
- net::TestURLFetcher* url_fetcher = url_fetcher_factory_.GetFetcherByID(0);
+ net::TestURLFetcher* url_fetcher = url_fetcher_factory_.GetFetcherByID(
+ gaia::GaiaOAuthClient::kUrlFetcherId);
ASSERT_TRUE(url_fetcher);
url_fetcher->SetMaxRetriesOn5xx(0);
url_fetcher->set_status(net::URLRequestStatus());
@@ -484,5 +489,5 @@ TEST_F(DeviceCloudPolicyManagerChromeOSEnrollmentTest, LoadError) {
status_.store_status());
}
-} // namespace test
+} // namespace
} // namespace policy
diff --git a/chrome/browser/chromeos/policy/enrollment_handler_chromeos.cc b/chrome/browser/chromeos/policy/enrollment_handler_chromeos.cc
index 9f74d3f..e23ce1e 100644
--- a/chrome/browser/chromeos/policy/enrollment_handler_chromeos.cc
+++ b/chrome/browser/chromeos/policy/enrollment_handler_chromeos.cc
@@ -216,7 +216,6 @@ void EnrollmentHandlerChromeOS::OnRobotAuthCodesFetched(
// Use the system request context to avoid sending user cookies.
gaia_oauth_client_.reset(new gaia::GaiaOAuthClient(
- GaiaUrls::GetInstance()->oauth2_token_url(),
g_browser_process->system_request_context()));
gaia_oauth_client_->GetTokensFromAuthCode(client_info,
client->robot_api_auth_code(),
diff --git a/chrome/browser/chromeos/settings/device_oauth2_token_service.cc b/chrome/browser/chromeos/settings/device_oauth2_token_service.cc
index 1e277a5..d00264e 100644
--- a/chrome/browser/chromeos/settings/device_oauth2_token_service.cc
+++ b/chrome/browser/chromeos/settings/device_oauth2_token_service.cc
@@ -4,22 +4,242 @@
#include "chrome/browser/chromeos/settings/device_oauth2_token_service.h"
+#include <string>
+#include <vector>
+
#include "base/prefs/pref_registry_simple.h"
#include "base/prefs/pref_service.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h"
+#include "chrome/browser/policy/browser_policy_connector.h"
+#include "chrome/browser/policy/proto/cloud/device_management_backend.pb.h"
#include "chrome/common/pref_names.h"
#include "chromeos/cryptohome/cryptohome_library.h"
#include "content/public/browser/browser_thread.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+
+namespace {
+const char kServiceScopeGetUserInfo[] =
+ "https://www.googleapis.com/auth/userinfo.email";
+} // namespace
namespace chromeos {
+// A wrapper for the consumer passed to StartRequest, which doesn't call
+// through to the target Consumer unless the refresh token validation is
+// complete.
+class DeviceOAuth2TokenService::ValidatingConsumer
+ : public OAuth2TokenService::Consumer,
+ public gaia::GaiaOAuthClient::Delegate {
+ public:
+ explicit ValidatingConsumer(DeviceOAuth2TokenService* token_service,
+ Consumer* consumer);
+ virtual ~ValidatingConsumer();
+
+ void StartValidation();
+
+ // OAuth2TokenService::Consumer
+ virtual void OnGetTokenSuccess(
+ const Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) OVERRIDE;
+ virtual void OnGetTokenFailure(
+ const Request* request,
+ const GoogleServiceAuthError& error) OVERRIDE;
+
+ // gaia::GaiaOAuthClient::Delegate implementation.
+ virtual void OnRefreshTokenResponse(const std::string& access_token,
+ int expires_in_seconds) OVERRIDE;
+ virtual void OnGetTokenInfoResponse(scoped_ptr<DictionaryValue> token_info)
+ OVERRIDE;
+ virtual void OnOAuthError() OVERRIDE;
+ virtual void OnNetworkError(int response_code) OVERRIDE;
+
+ private:
+ void RefreshTokenIsValid(bool is_valid);
+ void InformConsumer();
+
+ DeviceOAuth2TokenService* token_service_;
+ Consumer* consumer_;
+ scoped_ptr<gaia::GaiaOAuthClient> gaia_oauth_client_;
+
+ // We don't know which will complete first: the validation or the token
+ // minting. So, we need to cache the results so the final callback can
+ // take action.
+
+ // RefreshTokenValidationConsumer results
+ bool token_validation_done_;
+ bool token_is_valid_;
+
+ // OAuth2TokenService::Consumer results
+ const Request* request_;
+ std::string access_token_;
+ base::Time expiration_time_;
+ scoped_ptr<GoogleServiceAuthError> error_;
+};
+
+DeviceOAuth2TokenService::ValidatingConsumer::ValidatingConsumer(
+ DeviceOAuth2TokenService* token_service,
+ Consumer* consumer)
+ : token_service_(token_service),
+ consumer_(consumer),
+ token_validation_done_(false),
+ token_is_valid_(false),
+ request_(NULL) {
+}
+
+DeviceOAuth2TokenService::ValidatingConsumer::~ValidatingConsumer() {
+}
+
+void DeviceOAuth2TokenService::ValidatingConsumer::StartValidation() {
+ DCHECK(!gaia_oauth_client_);
+ gaia_oauth_client_.reset(new gaia::GaiaOAuthClient(
+ g_browser_process->system_request_context()));
+
+ GaiaUrls* gaia_urls = GaiaUrls::GetInstance();
+ gaia::OAuthClientInfo client_info;
+ client_info.client_id = gaia_urls->oauth2_chrome_client_id();
+ client_info.client_secret = gaia_urls->oauth2_chrome_client_secret();
+
+ gaia_oauth_client_->RefreshToken(
+ client_info,
+ token_service_->GetRefreshToken(),
+ std::vector<std::string>(1, kServiceScopeGetUserInfo),
+ token_service_->max_refresh_token_validation_retries_,
+ this);
+}
+
+void DeviceOAuth2TokenService::ValidatingConsumer::OnRefreshTokenResponse(
+ const std::string& access_token,
+ int expires_in_seconds) {
+ gaia_oauth_client_->GetTokenInfo(
+ access_token,
+ token_service_->max_refresh_token_validation_retries_,
+ this);
+}
+
+void DeviceOAuth2TokenService::ValidatingConsumer::OnGetTokenInfoResponse(
+ scoped_ptr<DictionaryValue> token_info) {
+ std::string gaia_robot_id;
+ token_info->GetString("email", &gaia_robot_id);
+
+ std::string policy_robot_id = token_service_->GetRobotAccountId();
+
+ if (policy_robot_id == gaia_robot_id) {
+ RefreshTokenIsValid(true);
+ } else {
+ if (gaia_robot_id.empty()) {
+ LOG(WARNING) << "Device service account owner in policy is empty.";
+ } else {
+ LOG(INFO) << "Device service account owner in policy does not match "
+ << "refresh token owner \"" << gaia_robot_id << "\".";
+ }
+ RefreshTokenIsValid(false);
+ }
+}
+
+void DeviceOAuth2TokenService::ValidatingConsumer::OnOAuthError() {
+ RefreshTokenIsValid(false);
+}
+
+void DeviceOAuth2TokenService::ValidatingConsumer::OnNetworkError(
+ int response_code) {
+ RefreshTokenIsValid(false);
+}
+
+void DeviceOAuth2TokenService::ValidatingConsumer::OnGetTokenSuccess(
+ const Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) {
+ request_ = request;
+ access_token_ = access_token;
+ expiration_time_ = expiration_time;
+ if (token_validation_done_)
+ InformConsumer();
+}
+
+void DeviceOAuth2TokenService::ValidatingConsumer::OnGetTokenFailure(
+ const Request* request,
+ const GoogleServiceAuthError& error) {
+ request_ = request;
+ error_.reset(new GoogleServiceAuthError(error.state()));
+ if (token_validation_done_)
+ InformConsumer();
+}
+
+void DeviceOAuth2TokenService::ValidatingConsumer::RefreshTokenIsValid(
+ bool is_valid) {
+ token_validation_done_ = true;
+ token_is_valid_ = is_valid;
+ // If we have a request pointer, then the minting is complete.
+ if (request_)
+ InformConsumer();
+}
+
+void DeviceOAuth2TokenService::ValidatingConsumer::InformConsumer() {
+ DCHECK(request_);
+ DCHECK(token_validation_done_);
+ if (!token_is_valid_) {
+ consumer_->OnGetTokenFailure(request_, GoogleServiceAuthError(
+ GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
+ } else if (error_) {
+ consumer_->OnGetTokenFailure(request_, *error_.get());
+ } else {
+ consumer_->OnGetTokenSuccess(request_, access_token_, expiration_time_);
+ }
+ token_service_->OnValidationComplete(this, token_is_valid_);
+}
+
DeviceOAuth2TokenService::DeviceOAuth2TokenService(
net::URLRequestContextGetter* getter,
PrefService* local_state)
: OAuth2TokenService(getter),
+ refresh_token_is_valid_(false),
+ max_refresh_token_validation_retries_(3),
+ pending_validators_(new std::set<ValidatingConsumer*>()),
local_state_(local_state) {
}
DeviceOAuth2TokenService::~DeviceOAuth2TokenService() {
+ STLDeleteElements(pending_validators_.get());
+}
+
+// TODO(davidroche): if the caller deletes the returned Request while
+// the fetches are in-flight, the OAuth2TokenService class won't call
+// back into the ValidatingConsumer and we'll end up with stale values
+// in pending_validators_ until this object is deleted. Probably not a
+// big deal, but it should be resolved by returning a Request that this
+// object owns.
+scoped_ptr<OAuth2TokenService::Request> DeviceOAuth2TokenService::StartRequest(
+ const OAuth2TokenService::ScopeSet& scopes,
+ OAuth2TokenService::Consumer* consumer) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+
+ if (refresh_token_is_valid_) {
+ return OAuth2TokenService::StartRequest(scopes, consumer).Pass();
+ } else {
+ ValidatingConsumer* validating_consumer = new ValidatingConsumer(this,
+ consumer);
+ pending_validators_->insert(validating_consumer);
+
+ validating_consumer->StartValidation();
+ return OAuth2TokenService::StartRequest(scopes, validating_consumer).Pass();
+ }
+}
+
+void DeviceOAuth2TokenService::OnValidationComplete(
+ ValidatingConsumer* validator,
+ bool refresh_token_is_valid) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ refresh_token_is_valid_ = refresh_token_is_valid;
+ std::set<ValidatingConsumer*>::iterator iter = pending_validators_->find(
+ validator);
+ if (iter != pending_validators_->end())
+ pending_validators_->erase(iter);
+ else
+ LOG(ERROR) << "OnValidationComplete called for unknown validator";
}
// static
@@ -49,4 +269,12 @@ std::string DeviceOAuth2TokenService::GetRefreshToken() {
return refresh_token_;
}
+std::string DeviceOAuth2TokenService::GetRobotAccountId() {
+ policy::BrowserPolicyConnector* connector =
+ g_browser_process->browser_policy_connector();
+ if (connector)
+ return connector->GetDeviceCloudPolicyManager()->GetRobotAccountId();
+ return std::string();
+}
+
} // namespace chromeos
diff --git a/chrome/browser/chromeos/settings/device_oauth2_token_service.h b/chrome/browser/chromeos/settings/device_oauth2_token_service.h
index 9d4bc6a..5b5017f 100644
--- a/chrome/browser/chromeos/settings/device_oauth2_token_service.h
+++ b/chrome/browser/chromeos/settings/device_oauth2_token_service.h
@@ -5,12 +5,16 @@
#ifndef CHROME_BROWSER_CHROMEOS_SETTINGS_DEVICE_OAUTH2_TOKEN_SERVICE_H_
#define CHROME_BROWSER_CHROMEOS_SETTINGS_DEVICE_OAUTH2_TOKEN_SERVICE_H_
+#include <set>
#include <string>
#include "base/basictypes.h"
#include "base/gtest_prod_util.h"
#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/time.h"
#include "chrome/browser/signin/oauth2_token_service.h"
+#include "google_apis/gaia/gaia_oauth_client.h"
#include "net/url_request/url_request_context_getter.h"
namespace net {
@@ -33,6 +37,11 @@ namespace chromeos {
// Note that requests must be made from the UI thread.
class DeviceOAuth2TokenService : public OAuth2TokenService {
public:
+ // Specialization of StartRequest that in parallel validates that the refresh
+ // token stored on the device is owned by the device service account.
+ virtual scoped_ptr<Request> StartRequest(const ScopeSet& scopes,
+ Consumer* consumer) OVERRIDE;
+
// Persist the given refresh token on the device. Overwrites any previous
// value. Should only be called during initial device setup.
void SetAndSaveRefreshToken(const std::string& refresh_token);
@@ -41,15 +50,29 @@ class DeviceOAuth2TokenService : public OAuth2TokenService {
virtual std::string GetRefreshToken() OVERRIDE;
+ protected:
+ // Pull the robot account ID from device policy.
+ virtual std::string GetRobotAccountId();
+
private:
+ class ValidatingConsumer;
+ friend class ValidatingConsumer;
friend class DeviceOAuth2TokenServiceFactory;
- FRIEND_TEST_ALL_PREFIXES(DeviceOAuth2TokenServiceTest, SaveEncryptedToken);
+ friend class DeviceOAuth2TokenServiceTest;
+ friend class TestDeviceOAuth2TokenService;
// Use DeviceOAuth2TokenServiceFactory to get an instance of this class.
explicit DeviceOAuth2TokenService(net::URLRequestContextGetter* getter,
PrefService* local_state);
virtual ~DeviceOAuth2TokenService();
+ void OnValidationComplete(ValidatingConsumer* validator, bool token_is_valid);
+
+ bool refresh_token_is_valid_;
+ int max_refresh_token_validation_retries_;
+
+ scoped_ptr<std::set<ValidatingConsumer*> > pending_validators_;
+
// Cache the decrypted refresh token, so we only decrypt once.
std::string refresh_token_;
PrefService* local_state_;
diff --git a/chrome/browser/chromeos/settings/device_oauth2_token_service_unittest.cc b/chrome/browser/chromeos/settings/device_oauth2_token_service_unittest.cc
index 6424ccb..1ba7004 100644
--- a/chrome/browser/chromeos/settings/device_oauth2_token_service_unittest.cc
+++ b/chrome/browser/chromeos/settings/device_oauth2_token_service_unittest.cc
@@ -6,11 +6,17 @@
#include "base/message_loop.h"
#include "base/prefs/testing_pref_service.h"
+#include "chrome/browser/signin/oauth2_token_service_test_util.h"
+#include "chrome/common/pref_names.h"
#include "chrome/test/base/scoped_testing_local_state.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chromeos/cryptohome/mock_cryptohome_library.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/test_browser_thread.h"
+#include "google_apis/gaia/gaia_oauth_client.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 "net/url_request/url_request_test_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -23,25 +29,121 @@ using ::testing::StrictMock;
namespace chromeos {
+static const int kOAuthTokenServiceUrlFetcherId = 0;
+static const int kValidatorUrlFetcherId = gaia::GaiaOAuthClient::kUrlFetcherId;
+
+class TestDeviceOAuth2TokenService : public DeviceOAuth2TokenService {
+ public:
+ explicit TestDeviceOAuth2TokenService(net::URLRequestContextGetter* getter,
+ PrefService* local_state)
+ : DeviceOAuth2TokenService(getter, local_state) {
+ }
+ void SetRobotAccountIdPolicyValue(const std::string& id) {
+ robot_account_id_ = id;
+ }
+
+ protected:
+ // Skip calling into the policy subsystem and return our test value.
+ virtual std::string GetRobotAccountId() OVERRIDE {
+ return robot_account_id_;
+ }
+
+ private:
+ std::string robot_account_id_;
+ DISALLOW_COPY_AND_ASSIGN(TestDeviceOAuth2TokenService);
+};
+
class DeviceOAuth2TokenServiceTest : public testing::Test {
public:
DeviceOAuth2TokenServiceTest()
: ui_thread_(content::BrowserThread::UI, &message_loop_),
- scoped_testing_local_state_(TestingBrowserProcess::GetGlobal()) {}
+ scoped_testing_local_state_(TestingBrowserProcess::GetGlobal()),
+ request_context_getter_(new net::TestURLRequestContextGetter(
+ message_loop_.message_loop_proxy())),
+ oauth2_service_(request_context_getter_,
+ scoped_testing_local_state_.Get()) {
+ oauth2_service_.max_refresh_token_validation_retries_ = 0;
+ oauth2_service_.set_max_authorization_token_fetch_retries_for_testing(0);
+ }
virtual ~DeviceOAuth2TokenServiceTest() {}
- virtual void SetUp() OVERRIDE {
+ // Most tests just want a noop crypto impl with a dummy refresh token value in
+ // Local State (if the value is an empty string, it will be ignored).
+ void SetUpDefaultValues() {
+ cryptohome_library_.reset(chromeos::CryptohomeLibrary::GetTestImpl());
+ chromeos::CryptohomeLibrary::SetForTest(cryptohome_library_.get());
+ SetDeviceRefreshTokenInLocalState("device_refresh_token_4_test");
+ oauth2_service_.SetRobotAccountIdPolicyValue("service_acct@g.com");
+ AssertConsumerTokensAndErrors(0, 0);
+ }
+
+ scoped_ptr<OAuth2TokenService::Request> StartTokenRequest() {
+ return oauth2_service_.StartRequest(std::set<std::string>(), &consumer_);
}
virtual void TearDown() OVERRIDE {
+ CryptohomeLibrary::SetForTest(NULL);
+ }
+
+ // Utility method to set a value in Local State for the device refresh token
+ // (it must have a non-empty value or it won't be used).
+ void SetDeviceRefreshTokenInLocalState(const std::string& refresh_token) {
+ scoped_testing_local_state_.Get()->SetManagedPref(
+ prefs::kDeviceRobotAnyApiRefreshToken,
+ Value::CreateStringValue(refresh_token));
}
+ std::string GetValidTokenInfoResponse(const std::string email) {
+ return "{ \"email\": \"" + email + "\","
+ " \"user_id\": \"1234567890\" }";
+ }
+
+ // A utility method to return fake URL results, for testing the refresh token
+ // validation logic. For a successful validation attempt, this method will be
+ // called three times for the steps listed below (steps 1 and 2 happen in
+ // parallel).
+ //
+ // Step 1a: fetch the access token for the tokeninfo API.
+ // Step 1b: call the tokeninfo API.
+ // Step 2: Fetch the access token for the requested scope
+ // (in this case, cloudprint).
+ void ReturnOAuthUrlFetchResults(int fetcher_id,
+ net::HttpStatusCode response_code,
+ const std::string& response_string);
+
+ void AssertConsumerTokensAndErrors(int num_tokens, int num_errors);
+
protected:
base::MessageLoop message_loop_;
content::TestBrowserThread ui_thread_;
ScopedTestingLocalState scoped_testing_local_state_;
+ scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_;
+ net::TestURLFetcherFactory factory_;
+ TestDeviceOAuth2TokenService oauth2_service_;
+ TestingOAuth2TokenServiceConsumer consumer_;
+ scoped_ptr<chromeos::CryptohomeLibrary> cryptohome_library_;
+
};
+void DeviceOAuth2TokenServiceTest::ReturnOAuthUrlFetchResults(
+ int fetcher_id,
+ net::HttpStatusCode response_code,
+ const std::string& response_string) {
+
+ net::TestURLFetcher* fetcher = factory_.GetFetcherByID(fetcher_id);
+ ASSERT_TRUE(fetcher);
+ fetcher->set_response_code(response_code);
+ fetcher->SetResponseString(response_string);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+}
+
+void DeviceOAuth2TokenServiceTest::AssertConsumerTokensAndErrors(
+ int num_tokens,
+ int num_errors) {
+ ASSERT_EQ(num_tokens, consumer_.number_of_successful_tokens_);
+ ASSERT_EQ(num_errors, consumer_.number_of_errors_);
+}
+
TEST_F(DeviceOAuth2TokenServiceTest, SaveEncryptedToken) {
StrictMock<MockCryptohomeLibrary> mock_cryptohome_library;
CryptohomeLibrary::SetForTest(&mock_cryptohome_library);
@@ -58,16 +160,207 @@ TEST_F(DeviceOAuth2TokenServiceTest, SaveEncryptedToken) {
.Times(1)
.WillOnce(Return("test-token"));
- DeviceOAuth2TokenService oauth2_service(
- new net::TestURLRequestContextGetter(message_loop_.message_loop_proxy()),
- scoped_testing_local_state_.Get());
-
- ASSERT_EQ("", oauth2_service.GetRefreshToken());
- oauth2_service.SetAndSaveRefreshToken("test-token");
- ASSERT_EQ("test-token", oauth2_service.GetRefreshToken());
+ ASSERT_EQ("", oauth2_service_.GetRefreshToken());
+ oauth2_service_.SetAndSaveRefreshToken("test-token");
+ ASSERT_EQ("test-token", oauth2_service_.GetRefreshToken());
// This call won't invoke decrypt again, since the value is cached.
- ASSERT_EQ("test-token", oauth2_service.GetRefreshToken());
+ ASSERT_EQ("test-token", oauth2_service_.GetRefreshToken());
+}
+
+TEST_F(DeviceOAuth2TokenServiceTest, RefreshTokenValidation_Success) {
+ SetUpDefaultValues();
+ scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
+
+ ReturnOAuthUrlFetchResults(
+ kValidatorUrlFetcherId,
+ net::HTTP_OK,
+ GetValidTokenResponse("tokeninfo_access_token", 3600));
+
+ ReturnOAuthUrlFetchResults(
+ kValidatorUrlFetcherId,
+ net::HTTP_OK,
+ GetValidTokenInfoResponse("service_acct@g.com"));
+
+ ReturnOAuthUrlFetchResults(
+ kOAuthTokenServiceUrlFetcherId,
+ net::HTTP_OK,
+ GetValidTokenResponse("scoped_access_token", 3600));
+
+ AssertConsumerTokensAndErrors(1, 0);
+
+ EXPECT_EQ("scoped_access_token", consumer_.last_token_);
+}
+
+TEST_F(DeviceOAuth2TokenServiceTest,
+ RefreshTokenValidation_Failure_TokenInfoAccessTokenHttpError) {
+ SetUpDefaultValues();
+ scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
+
+ ReturnOAuthUrlFetchResults(
+ kValidatorUrlFetcherId,
+ net::HTTP_UNAUTHORIZED,
+ "");
+
+ // TokenInfo API call skipped (error returned in previous step).
+
+ // CloudPrint access token fetch is successful, but consumer still given error
+ // due to bad refresh token.
+ ReturnOAuthUrlFetchResults(
+ kOAuthTokenServiceUrlFetcherId,
+ net::HTTP_OK,
+ GetValidTokenResponse("ignored_scoped_access_token", 3600));
+
+ AssertConsumerTokensAndErrors(0, 1);
+}
+
+TEST_F(DeviceOAuth2TokenServiceTest,
+ RefreshTokenValidation_Failure_TokenInfoAccessTokenInvalidResponse) {
+ SetUpDefaultValues();
+ scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
+
+ ReturnOAuthUrlFetchResults(
+ kValidatorUrlFetcherId,
+ net::HTTP_OK,
+ "invalid response");
+
+ // TokenInfo API call skipped (error returned in previous step).
+
+ ReturnOAuthUrlFetchResults(
+ kOAuthTokenServiceUrlFetcherId,
+ net::HTTP_OK,
+ GetValidTokenResponse("ignored_scoped_access_token", 3600));
+
+ // CloudPrint access token fetch is successful, but consumer still given error
+ // due to bad refresh token.
+ AssertConsumerTokensAndErrors(0, 1);
+}
+
+TEST_F(DeviceOAuth2TokenServiceTest,
+ RefreshTokenValidation_Failure_TokenInfoApiCallHttpError) {
+ SetUpDefaultValues();
+ scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
+
+ ReturnOAuthUrlFetchResults(
+ kValidatorUrlFetcherId,
+ net::HTTP_OK,
+ GetValidTokenResponse("tokeninfo_access_token", 3600));
+
+ ReturnOAuthUrlFetchResults(
+ kValidatorUrlFetcherId,
+ net::HTTP_INTERNAL_SERVER_ERROR,
+ "");
+
+ ReturnOAuthUrlFetchResults(
+ kOAuthTokenServiceUrlFetcherId,
+ net::HTTP_OK,
+ GetValidTokenResponse("ignored_scoped_access_token", 3600));
+
+ // CloudPrint access token fetch is successful, but consumer still given error
+ // due to bad refresh token.
+ AssertConsumerTokensAndErrors(0, 1);
+}
+
+TEST_F(DeviceOAuth2TokenServiceTest,
+ RefreshTokenValidation_Failure_TokenInfoApiCallInvalidResponse) {
+ SetUpDefaultValues();
+ scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
+
+ ReturnOAuthUrlFetchResults(
+ kValidatorUrlFetcherId,
+ net::HTTP_OK,
+ GetValidTokenResponse("tokeninfo_access_token", 3600));
+
+ ReturnOAuthUrlFetchResults(
+ kValidatorUrlFetcherId,
+ net::HTTP_OK,
+ "invalid response");
+
+ ReturnOAuthUrlFetchResults(
+ kOAuthTokenServiceUrlFetcherId,
+ net::HTTP_OK,
+ GetValidTokenResponse("ignored_scoped_access_token", 3600));
+
+ // CloudPrint access token fetch is successful, but consumer still given error
+ // due to bad refresh token.
+ AssertConsumerTokensAndErrors(0, 1);
+}
+
+TEST_F(DeviceOAuth2TokenServiceTest,
+ RefreshTokenValidation_Failure_CloudPrintAccessTokenHttpError) {
+ SetUpDefaultValues();
+ scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
+
+ ReturnOAuthUrlFetchResults(
+ kValidatorUrlFetcherId,
+ net::HTTP_OK,
+ GetValidTokenResponse("tokeninfo_access_token", 3600));
+
+ ReturnOAuthUrlFetchResults(
+ kValidatorUrlFetcherId,
+ net::HTTP_OK,
+ GetValidTokenInfoResponse("service_acct@g.com"));
+
+ ReturnOAuthUrlFetchResults(
+ kOAuthTokenServiceUrlFetcherId,
+ net::HTTP_BAD_REQUEST,
+ "");
+
+ AssertConsumerTokensAndErrors(0, 1);
+}
+
+TEST_F(DeviceOAuth2TokenServiceTest,
+ RefreshTokenValidation_Failure_CloudPrintAccessTokenInvalidResponse) {
+ SetUpDefaultValues();
+ scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
+
+ ReturnOAuthUrlFetchResults(
+ kValidatorUrlFetcherId,
+ net::HTTP_OK,
+ GetValidTokenResponse("tokeninfo_access_token", 3600));
+
+ ReturnOAuthUrlFetchResults(
+ kValidatorUrlFetcherId,
+ net::HTTP_OK,
+ GetValidTokenInfoResponse("service_acct@g.com"));
+
+ ReturnOAuthUrlFetchResults(
+ kOAuthTokenServiceUrlFetcherId,
+ net::HTTP_OK,
+ "invalid request");
+
+ AssertConsumerTokensAndErrors(0, 1);
+}
+
+TEST_F(DeviceOAuth2TokenServiceTest, RefreshTokenValidation_Failure_BadOwner) {
+ SetUpDefaultValues();
+ scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
+
+ oauth2_service_.SetRobotAccountIdPolicyValue("WRONG_service_acct@g.com");
+
+ // The requested token comes in before any of the validation calls complete,
+ // but the consumer still gets an error, since the results don't get returned
+ // until validation is over.
+ ReturnOAuthUrlFetchResults(
+ kOAuthTokenServiceUrlFetcherId,
+ net::HTTP_OK,
+ GetValidTokenResponse("ignored_scoped_access_token", 3600));
+ AssertConsumerTokensAndErrors(0, 0);
+
+ ReturnOAuthUrlFetchResults(
+ kValidatorUrlFetcherId,
+ net::HTTP_OK,
+ GetValidTokenResponse("tokeninfo_access_token", 3600));
+ AssertConsumerTokensAndErrors(0, 0);
+
+ ReturnOAuthUrlFetchResults(
+ kValidatorUrlFetcherId,
+ net::HTTP_OK,
+ GetValidTokenInfoResponse("service_acct@g.com"));
+
+ // All fetches were successful, but consumer still given error since
+ // the token owner doesn't match the policy value.
+ AssertConsumerTokensAndErrors(0, 1);
}
} // namespace chromeos
diff --git a/chrome/browser/managed_mode/managed_user_refresh_token_fetcher.cc b/chrome/browser/managed_mode/managed_user_refresh_token_fetcher.cc
index b15f051..5c5a1a6 100644
--- a/chrome/browser/managed_mode/managed_user_refresh_token_fetcher.cc
+++ b/chrome/browser/managed_mode/managed_user_refresh_token_fetcher.cc
@@ -221,9 +221,7 @@ void ManagedUserRefreshTokenFetcherImpl::OnURLFetchComplete(
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_.reset(new gaia::GaiaOAuthClient(context_));
gaia_oauth_client_->GetTokensFromAuthCode(client_info, auth_code, kNumRetries,
this);
}
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
index d488c11..424ed86 100644
--- a/chrome/browser/managed_mode/managed_user_refresh_token_fetcher_unittest.cc
+++ b/chrome/browser/managed_mode/managed_user_refresh_token_fetcher_unittest.cc
@@ -11,6 +11,7 @@
#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_oauth_client.h"
#include "google_apis/gaia/gaia_urls.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "net/base/net_errors.h"
@@ -258,7 +259,8 @@ ManagedUserRefreshTokenFetcherTest::GetIssueTokenRequest() {
net::TestURLFetcher*
ManagedUserRefreshTokenFetcherTest::GetRefreshTokenRequest() {
- net::TestURLFetcher* url_fetcher = url_fetcher_factory_.GetFetcherByID(0);
+ net::TestURLFetcher* url_fetcher = url_fetcher_factory_.GetFetcherByID(
+ gaia::GaiaOAuthClient::kUrlFetcherId);
if (!url_fetcher)
return NULL;
diff --git a/chrome/browser/policy/cloud/policy_builder.cc b/chrome/browser/policy/cloud/policy_builder.cc
index 67d94d1..94ff451 100644
--- a/chrome/browser/policy/cloud/policy_builder.cc
+++ b/chrome/browser/policy/cloud/policy_builder.cc
@@ -95,6 +95,7 @@ const int PolicyBuilder::kFakePublicKeyVersion = 17;
const int64 PolicyBuilder::kFakeTimestamp = 365LL * 24 * 60 * 60 * 1000;
const char PolicyBuilder::kFakeToken[] = "token";
const char PolicyBuilder::kFakeUsername[] = "username@example.com";
+const char PolicyBuilder::kFakeServiceAccountIdentity[] = "robot4test@g.com";
PolicyBuilder::PolicyBuilder()
: policy_data_(new em::PolicyData()),
@@ -107,6 +108,7 @@ PolicyBuilder::PolicyBuilder()
policy_data_->set_username(kFakeUsername);
policy_data_->set_device_id(kFakeDeviceId);
policy_data_->set_state(em::PolicyData::ACTIVE);
+ policy_data_->set_service_account_identity(kFakeServiceAccountIdentity);
}
PolicyBuilder::~PolicyBuilder() {}
diff --git a/chrome/browser/policy/cloud/policy_builder.h b/chrome/browser/policy/cloud/policy_builder.h
index 628483a..2daac43 100644
--- a/chrome/browser/policy/cloud/policy_builder.h
+++ b/chrome/browser/policy/cloud/policy_builder.h
@@ -35,6 +35,7 @@ class PolicyBuilder {
static const int64 kFakeTimestamp;
static const char kFakeToken[];
static const char kFakeUsername[];
+ static const char kFakeServiceAccountIdentity[];
// Creates a policy builder. The builder will have all PolicyData fields
// initialized to dummy values and use the test signing keys.
diff --git a/chrome/browser/policy/proto/cloud/device_management_backend.proto b/chrome/browser/policy/proto/cloud/device_management_backend.proto
index ab31bfe..2d1e856 100644
--- a/chrome/browser/policy/proto/cloud/device_management_backend.proto
+++ b/chrome/browser/policy/proto/cloud/device_management_backend.proto
@@ -238,6 +238,10 @@ message PolicyData {
// Indicates which public account or extension/plug-in this policy data is
// for. See PolicyFetchRequest.settings_entity_id for more details.
optional string settings_entity_id = 11;
+
+ // Indicates the identity the device service account is associated with.
+ // This is only sent as part of device policy fetch.
+ optional string service_account_identity = 12;
}
message PolicyFetchResponse {
diff --git a/chrome/browser/signin/oauth2_token_service.cc b/chrome/browser/signin/oauth2_token_service.cc
index e21911d..7dfcc38 100644
--- a/chrome/browser/signin/oauth2_token_service.cc
+++ b/chrome/browser/signin/oauth2_token_service.cc
@@ -20,21 +20,7 @@
#include "google_apis/gaia/oauth2_access_token_fetcher.h"
#include "net/url_request/url_request_context_getter.h"
-namespace {
-
-// Maximum number of retries in fetching an OAuth2 access token.
-const int kMaxFetchRetryNum = 5;
-
-// Returns an exponential backoff in milliseconds including randomness less than
-// 1000 ms when retrying fetching an OAuth2 access token.
-int64 ComputeExponentialBackOffMilliseconds(int retry_num) {
- DCHECK(retry_num < kMaxFetchRetryNum);
- int64 exponential_backoff_in_seconds = 1 << retry_num;
- // Returns a backoff with randomness < 1000ms
- return (exponential_backoff_in_seconds + base::RandDouble()) * 1000;
-}
-
-} // namespace
+int OAuth2TokenService::max_fetch_retry_num_ = 5;
OAuth2TokenService::RequestImpl::RequestImpl(
OAuth2TokenService::Consumer* consumer)
@@ -119,7 +105,9 @@ class OAuth2TokenService::Fetcher : public OAuth2AccessTokenConsumer {
base::WeakPtr<RequestImpl> waiting_request);
void Start();
void InformWaitingRequests();
+ void InformWaitingRequestsAndDelete();
static bool ShouldRetry(const GoogleServiceAuthError& error);
+ int64 ComputeExponentialBackOffMilliseconds(int retry_num);
// |oauth2_token_service_| remains valid for the life of this Fetcher, since
// this Fetcher is destructed in the dtor of the OAuth2TokenService or is
@@ -209,18 +197,14 @@ void OAuth2TokenService::Fetcher::OnGetTokenSuccess(
scopes_,
access_token_,
expiration_date_);
- // Deregisters itself from the service to prevent more waiting requests to
- // be added when it calls back the waiting requests.
- oauth2_token_service_->OnFetchComplete(this);
- InformWaitingRequests();
- base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
+ InformWaitingRequestsAndDelete();
}
void OAuth2TokenService::Fetcher::OnGetTokenFailure(
const GoogleServiceAuthError& error) {
fetcher_.reset();
- if (ShouldRetry(error) && retry_number_ < kMaxFetchRetryNum) {
+ if (ShouldRetry(error) && retry_number_ < max_fetch_retry_num_) {
int64 backoff = ComputeExponentialBackOffMilliseconds(retry_number_);
++retry_number_;
retry_timer_.Stop();
@@ -231,14 +215,18 @@ void OAuth2TokenService::Fetcher::OnGetTokenFailure(
return;
}
- // Fetch completes.
error_ = error;
+ InformWaitingRequestsAndDelete();
+}
- // Deregisters itself from the service to prevent more waiting requests to be
- // added when it calls back the waiting requests.
- oauth2_token_service_->OnFetchComplete(this);
- InformWaitingRequests();
- base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
+// Returns an exponential backoff in milliseconds including randomness less than
+// 1000 ms when retrying fetching an OAuth2 access token.
+int64 OAuth2TokenService::Fetcher::ComputeExponentialBackOffMilliseconds(
+ int retry_num) {
+ DCHECK(retry_num < max_fetch_retry_num_);
+ int64 exponential_backoff_in_seconds = 1 << retry_num;
+ // Returns a backoff with randomness < 1000ms
+ return (exponential_backoff_in_seconds + base::RandDouble()) * 1000;
}
// static
@@ -261,6 +249,14 @@ void OAuth2TokenService::Fetcher::InformWaitingRequests() {
waiting_requests_.clear();
}
+void OAuth2TokenService::Fetcher::InformWaitingRequestsAndDelete() {
+ // Deregisters itself from the service to prevent more waiting requests to
+ // be added when it calls back the waiting requests.
+ oauth2_token_service_->OnFetchComplete(this);
+ InformWaitingRequests();
+ base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
+}
+
void OAuth2TokenService::Fetcher::AddWaitingRequest(
base::WeakPtr<OAuth2TokenService::RequestImpl> waiting_request) {
waiting_requests_.push_back(waiting_request);
@@ -325,9 +321,9 @@ scoped_ptr<OAuth2TokenService::Request> OAuth2TokenService::StartRequest(
if (HasCacheEntry(scopes))
return StartCacheLookupRequest(scopes, consumer);
- // Makes sure there is a pending fetcher for |scopes| and |refresh_token|.
- // Adds |request| to the waiting request list of this fetcher so |request|
- // will be called back when this fetcher finishes fetching.
+ // If there is already a pending fetcher for |scopes| and |refresh_token|,
+ // simply register this |request| for those results rather than starting
+ // a new fetcher.
FetchParameters fetch_parameters = std::make_pair(refresh_token, scopes);
std::map<FetchParameters, Fetcher*>::iterator iter =
pending_fetchers_.find(fetch_parameters);
@@ -335,6 +331,7 @@ scoped_ptr<OAuth2TokenService::Request> OAuth2TokenService::StartRequest(
iter->second->AddWaitingRequest(request->AsWeakPtr());
return request.PassAs<Request>();
}
+
pending_fetchers_[fetch_parameters] =
Fetcher::CreateAndStart(this,
request_context_getter_.get(),
@@ -463,3 +460,9 @@ void OAuth2TokenService::ClearCache() {
int OAuth2TokenService::cache_size_for_testing() const {
return token_cache_.size();
}
+
+void OAuth2TokenService::set_max_authorization_token_fetch_retries_for_testing(
+ int max_retries) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ max_fetch_retry_num_ = max_retries;
+}
diff --git a/chrome/browser/signin/oauth2_token_service.h b/chrome/browser/signin/oauth2_token_service.h
index e96b9c1..104015b 100644
--- a/chrome/browser/signin/oauth2_token_service.h
+++ b/chrome/browser/signin/oauth2_token_service.h
@@ -100,8 +100,29 @@ class OAuth2TokenService {
// Return the current number of entries in the cache.
int cache_size_for_testing() const;
+ void set_max_authorization_token_fetch_retries_for_testing(int max_retries);
protected:
+ // Implements a cancelable |OAuth2TokenService::Request|, which should be
+ // operated on the UI thread.
+ // TODO(davidroche): move this out of header file.
+ class RequestImpl : public base::SupportsWeakPtr<RequestImpl>,
+ public Request {
+ public:
+ // |consumer| is required to outlive this.
+ explicit RequestImpl(Consumer* consumer);
+ virtual ~RequestImpl();
+
+ // Informs |consumer_| that this request is completed.
+ void InformConsumer(const GoogleServiceAuthError& error,
+ const std::string& access_token,
+ const base::Time& expiration_date);
+
+ private:
+ // |consumer_| to call back when this request completes.
+ Consumer* const consumer_;
+ };
+
// Subclasses should return the refresh token maintained.
// If no token is available, return an empty string.
virtual std::string GetRefreshToken() = 0;
@@ -122,32 +143,13 @@ class OAuth2TokenService {
bool HasCacheEntry(const ScopeSet& scopes);
// Posts a task to fire the Consumer callback with the cached token. Must
- // only be called if HasCacheEntry() returns true.
+ // Must only be called if HasCacheEntry() returns true.
scoped_ptr<Request> StartCacheLookupRequest(const ScopeSet& scopes,
Consumer* consumer);
// Clears the internal token cache.
void ClearCache();
- // Implements a cancelable |OAuth2TokenService::Request|, which should be
- // operated on the UI thread.
- class RequestImpl : public base::SupportsWeakPtr<RequestImpl>,
- public Request {
- public:
- // |consumer| is required to outlive this.
- explicit RequestImpl(Consumer* consumer);
- virtual ~RequestImpl();
-
- // Informs |consumer_| that this request is completed.
- void InformConsumer(const GoogleServiceAuthError& error,
- const std::string& access_token,
- const base::Time& expiration_date);
-
- private:
- // |consumer_| to call back when this request completes.
- Consumer* const consumer_;
- };
-
private:
// Class that fetches an OAuth2 access token for a given set of scopes and
// OAuth2 refresh token.
@@ -189,6 +191,8 @@ class OAuth2TokenService {
// A map from fetch parameters to a fetcher that is fetching an OAuth2 access
// token using these parameters.
std::map<FetchParameters, Fetcher*> pending_fetchers_;
+ // Maximum number of retries in fetching an OAuth2 access token.
+ static int max_fetch_retry_num_;
DISALLOW_COPY_AND_ASSIGN(OAuth2TokenService);
};
diff --git a/chrome/browser/signin/ubertoken_fetcher.cc b/chrome/browser/signin/ubertoken_fetcher.cc
index b722f12..4cf5d39 100644
--- a/chrome/browser/signin/ubertoken_fetcher.cc
+++ b/chrome/browser/signin/ubertoken_fetcher.cc
@@ -2,12 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "chrome/browser/signin/ubertoken_fetcher.h"
+
+#include <vector>
+
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/token_service.h"
#include "chrome/browser/signin/token_service_factory.h"
-#include "chrome/browser/signin/ubertoken_fetcher.h"
#include "chrome/common/chrome_notification_types.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/gaia/gaia_urls.h"
@@ -47,9 +50,11 @@ void UbertokenFetcher::StartFetchingUbertoken() {
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(
- urls->oauth2_token_url(), profile_->GetRequestContext()));
+ profile_->GetRequestContext()));
+ std::vector<std::string> empty_scope_list; // (Use scope from refresh token.)
gaia_oauth_client_->RefreshToken(
- client_info, token_service->GetOAuth2LoginRefreshToken(), 1, this);
+ client_info, token_service->GetOAuth2LoginRefreshToken(),
+ empty_scope_list, 1, this);
}
void UbertokenFetcher::Observe(int type,
diff --git a/chrome/service/cloud_print/cloud_print_auth.cc b/chrome/service/cloud_print/cloud_print_auth.cc
index 9bc820c..5abfb99 100644
--- a/chrome/service/cloud_print/cloud_print_auth.cc
+++ b/chrome/service/cloud_print/cloud_print_auth.cc
@@ -62,7 +62,6 @@ void CloudPrintAuth::AuthenticateWithRobotAuthCode(
robot_email_ = robot_email;
// Now that we have an auth code we need to get the refresh and access tokens.
oauth_client_.reset(new gaia::GaiaOAuthClient(
- GaiaUrls::GetInstance()->oauth2_token_url(),
g_service_process->GetServiceURLRequestContextGetter()));
oauth_client_->GetTokensFromAuthCode(oauth_client_info_,
robot_oauth_auth_code,
@@ -72,10 +71,11 @@ void CloudPrintAuth::AuthenticateWithRobotAuthCode(
void CloudPrintAuth::RefreshAccessToken() {
oauth_client_.reset(new gaia::GaiaOAuthClient(
- GaiaUrls::GetInstance()->oauth2_token_url(),
g_service_process->GetServiceURLRequestContextGetter()));
+ std::vector<std::string> empty_scope_list; // (Use scope from refresh token.)
oauth_client_->RefreshToken(oauth_client_info_,
refresh_token_,
+ empty_scope_list,
kCloudPrintAuthMaxRetryCount,
this);
}
@@ -139,7 +139,6 @@ CloudPrintURLFetcher::ResponseAction CloudPrintAuth::HandleJSONData(
json_data->GetString(kXMPPJidValue, &robot_email_);
// Now that we have an auth code we need to get the refresh and access tokens.
oauth_client_.reset(new gaia::GaiaOAuthClient(
- GaiaUrls::GetInstance()->oauth2_token_url(),
g_service_process->GetServiceURLRequestContextGetter()));
oauth_client_->GetTokensFromAuthCode(oauth_client_info_,
auth_code,
diff --git a/google_apis/gaia/gaia_oauth_client.cc b/google_apis/gaia/gaia_oauth_client.cc
index f748fdb..d00b7d24e 100644
--- a/google_apis/gaia/gaia_oauth_client.cc
+++ b/google_apis/gaia/gaia_oauth_client.cc
@@ -7,6 +7,7 @@
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
+#include "base/string_util.h"
#include "base/values.h"
#include "google_apis/gaia/gaia_urls.h"
#include "googleurl/src/gurl.h"
@@ -24,17 +25,19 @@ const char kExpiresInValue[] = "expires_in";
namespace gaia {
+// Use a non-zero number, so unit tests can differentiate the URLFetcher used by
+// this class from other fetchers (most other code just hardcodes the ID to 0).
+const int GaiaOAuthClient::kUrlFetcherId = 17109006;
+
class GaiaOAuthClient::Core
: public base::RefCountedThreadSafe<GaiaOAuthClient::Core>,
public net::URLFetcherDelegate {
public:
- Core(const std::string& gaia_url,
- net::URLRequestContextGetter* request_context_getter)
- : gaia_url_(gaia_url),
- num_retries_(0),
- request_context_getter_(request_context_getter),
- delegate_(NULL),
- request_type_(NO_PENDING_REQUEST) {
+ Core(net::URLRequestContextGetter* request_context_getter)
+ : num_retries_(0),
+ request_context_getter_(request_context_getter),
+ delegate_(NULL),
+ request_type_(NO_PENDING_REQUEST) {
}
void GetTokensFromAuthCode(const OAuthClientInfo& oauth_client_info,
@@ -43,9 +46,13 @@ class GaiaOAuthClient::Core
GaiaOAuthClient::Delegate* delegate);
void RefreshToken(const OAuthClientInfo& oauth_client_info,
const std::string& refresh_token,
+ const std::vector<std::string>& scopes,
int max_retries,
GaiaOAuthClient::Delegate* delegate);
void GetUserInfo(const std::string& oauth_access_token,
+ int max_retries,
+ Delegate* delegate);
+ void GetTokenInfo(const std::string& oauth_access_token,
int max_retries,
Delegate* delegate);
@@ -59,18 +66,19 @@ class GaiaOAuthClient::Core
NO_PENDING_REQUEST,
TOKENS_FROM_AUTH_CODE,
REFRESH_TOKEN,
+ TOKEN_INFO,
USER_INFO,
};
virtual ~Core() {}
- void MakeGaiaRequest(const std::string& post_body,
+ void MakeGaiaRequest(const GURL& url,
+ const std::string& post_body,
int max_retries,
GaiaOAuthClient::Delegate* delegate);
void HandleResponse(const net::URLFetcher* source,
bool* should_retry_request);
- GURL gaia_url_;
int num_retries_;
scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
GaiaOAuthClient::Delegate* delegate_;
@@ -94,12 +102,14 @@ void GaiaOAuthClient::Core::GetTokensFromAuthCode(
"&redirect_uri=" +
net::EscapeUrlEncodedData(oauth_client_info.redirect_uri, true) +
"&grant_type=authorization_code";
- MakeGaiaRequest(post_body, max_retries, delegate);
+ MakeGaiaRequest(GURL(GaiaUrls::GetInstance()->oauth2_token_url()),
+ post_body, max_retries, delegate);
}
void GaiaOAuthClient::Core::RefreshToken(
const OAuthClientInfo& oauth_client_info,
const std::string& refresh_token,
+ const std::vector<std::string>& scopes,
int max_retries,
GaiaOAuthClient::Delegate* delegate) {
DCHECK_EQ(request_type_, NO_PENDING_REQUEST);
@@ -111,7 +121,14 @@ void GaiaOAuthClient::Core::RefreshToken(
"&client_secret=" +
net::EscapeUrlEncodedData(oauth_client_info.client_secret, true) +
"&grant_type=refresh_token";
- MakeGaiaRequest(post_body, max_retries, delegate);
+
+ if (!scopes.empty()) {
+ std::string scopes_string = JoinString(scopes, ' ');
+ post_body += "&scope=" + net::EscapeUrlEncodedData(scopes_string, true);
+ }
+
+ MakeGaiaRequest(GURL(GaiaUrls::GetInstance()->oauth2_token_url()),
+ post_body, max_retries, delegate);
}
void GaiaOAuthClient::Core::GetUserInfo(const std::string& oauth_access_token,
@@ -123,7 +140,7 @@ void GaiaOAuthClient::Core::GetUserInfo(const std::string& oauth_access_token,
delegate_ = delegate;
num_retries_ = 0;
request_.reset(net::URLFetcher::Create(
- 0, GURL(GaiaUrls::GetInstance()->oauth_user_info_url()),
+ kUrlFetcherId, GURL(GaiaUrls::GetInstance()->oauth_user_info_url()),
net::URLFetcher::GET, this));
request_->SetRequestContext(request_context_getter_.get());
request_->AddExtraRequestHeader("Authorization: OAuth " + oauth_access_token);
@@ -136,7 +153,22 @@ void GaiaOAuthClient::Core::GetUserInfo(const std::string& oauth_access_token,
request_->Start();
}
+void GaiaOAuthClient::Core::GetTokenInfo(const std::string& oauth_access_token,
+ int max_retries,
+ Delegate* delegate) {
+ DCHECK_EQ(request_type_, NO_PENDING_REQUEST);
+ DCHECK(!request_.get());
+ request_type_ = TOKEN_INFO;
+ std::string post_body =
+ "access_token=" + net::EscapeUrlEncodedData(oauth_access_token, true);
+ MakeGaiaRequest(GURL(GaiaUrls::GetInstance()->oauth2_token_info_url()),
+ post_body,
+ max_retries,
+ delegate);
+}
+
void GaiaOAuthClient::Core::MakeGaiaRequest(
+ const GURL& url,
const std::string& post_body,
int max_retries,
GaiaOAuthClient::Delegate* delegate) {
@@ -144,7 +176,7 @@ void GaiaOAuthClient::Core::MakeGaiaRequest(
delegate_ = delegate;
num_retries_ = 0;
request_.reset(net::URLFetcher::Create(
- 0, gaia_url_, net::URLFetcher::POST, this));
+ kUrlFetcherId, url, net::URLFetcher::POST, this));
request_->SetRequestContext(request_context_getter_.get());
request_->SetUploadData("application/x-www-form-urlencoded", post_body);
request_->SetMaxRetriesOn5xx(max_retries);
@@ -175,7 +207,9 @@ void GaiaOAuthClient::Core::OnURLFetchComplete(
void GaiaOAuthClient::Core::HandleResponse(
const net::URLFetcher* source,
bool* should_retry_request) {
- // Keep the URLFetcher object in case we need to reuse it.
+ // Move ownership of the request fetcher into a local scoped_ptr which
+ // will be nuked when we're done handling the request, unless we need
+ // to retry, in which case ownership will be returned to request_.
scoped_ptr<net::URLFetcher> old_request = request_.Pass();
DCHECK_EQ(source, old_request.get());
@@ -223,6 +257,11 @@ void GaiaOAuthClient::Core::HandleResponse(
break;
}
+ case TOKEN_INFO: {
+ delegate_->OnGetTokenInfoResponse(response_dict.Pass());
+ break;
+ }
+
case TOKENS_FROM_AUTH_CODE:
case REFRESH_TOKEN: {
std::string access_token;
@@ -252,9 +291,8 @@ void GaiaOAuthClient::Core::HandleResponse(
}
}
-GaiaOAuthClient::GaiaOAuthClient(const std::string& gaia_url,
- net::URLRequestContextGetter* context_getter) {
- core_ = new Core(gaia_url, context_getter);
+GaiaOAuthClient::GaiaOAuthClient(net::URLRequestContextGetter* context_getter) {
+ core_ = new Core(context_getter);
}
GaiaOAuthClient::~GaiaOAuthClient() {
@@ -271,12 +309,15 @@ void GaiaOAuthClient::GetTokensFromAuthCode(
delegate);
}
-void GaiaOAuthClient::RefreshToken(const OAuthClientInfo& oauth_client_info,
- const std::string& refresh_token,
- int max_retries,
- Delegate* delegate) {
+void GaiaOAuthClient::RefreshToken(
+ const OAuthClientInfo& oauth_client_info,
+ const std::string& refresh_token,
+ const std::vector<std::string>& scopes,
+ int max_retries,
+ Delegate* delegate) {
return core_->RefreshToken(oauth_client_info,
refresh_token,
+ scopes,
max_retries,
delegate);
}
@@ -287,4 +328,10 @@ void GaiaOAuthClient::GetUserInfo(const std::string& access_token,
return core_->GetUserInfo(access_token, max_retries, delegate);
}
+void GaiaOAuthClient::GetTokenInfo(const std::string& access_token,
+ int max_retries,
+ Delegate* delegate) {
+ return core_->GetTokenInfo(access_token, max_retries, delegate);
+}
+
} // namespace gaia
diff --git a/google_apis/gaia/gaia_oauth_client.h b/google_apis/gaia/gaia_oauth_client.h
index 17d6dbd..a8af658 100644
--- a/google_apis/gaia/gaia_oauth_client.h
+++ b/google_apis/gaia/gaia_oauth_client.h
@@ -6,15 +6,21 @@
#define GOOGLE_APIS_GAIA_GAIA_OAUTH_CLIENT_H_
#include <string>
+#include <vector>
#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop_proxy.h"
+#include "base/values.h"
namespace net {
class URLRequestContextGetter;
}
// A helper class to get and refresh OAuth tokens given an authorization code.
+// Also exposes utility methods for fetching user email and token owner.
+// Supports one request at a time; for parallel requests, create multiple
+// instances.
namespace gaia {
struct OAuthClientInfo {
@@ -25,17 +31,22 @@ struct OAuthClientInfo {
class GaiaOAuthClient {
public:
+ const static int kUrlFetcherId;
+
class Delegate {
public:
// Invoked on a successful response to the GetTokensFromAuthCode request.
virtual void OnGetTokensResponse(const std::string& refresh_token,
const std::string& access_token,
- int expires_in_seconds) = 0;
+ int expires_in_seconds) {}
// Invoked on a successful response to the RefreshToken request.
virtual void OnRefreshTokenResponse(const std::string& access_token,
- int expires_in_seconds) = 0;
+ int expires_in_seconds) {}
// Invoked on a successful response to the GetUserInfo request.
- virtual void OnGetUserInfoResponse(const std::string& user_email) {};
+ virtual void OnGetUserInfoResponse(const std::string& user_email) {}
+ // Invoked on a successful response to the GetTokenInfo request.
+ virtual void OnGetTokenInfoResponse(
+ scoped_ptr<DictionaryValue> token_info) {}
// Invoked when there is an OAuth error with one of the requests.
virtual void OnOAuthError() = 0;
// Invoked when there is a network error or upon receiving an invalid
@@ -46,8 +57,8 @@ class GaiaOAuthClient {
protected:
virtual ~Delegate() {}
};
- GaiaOAuthClient(const std::string& gaia_url,
- net::URLRequestContextGetter* context_getter);
+
+ GaiaOAuthClient(net::URLRequestContextGetter* context_getter);
~GaiaOAuthClient();
// In the below methods, |max_retries| specifies the maximum number of times
@@ -60,11 +71,15 @@ class GaiaOAuthClient {
Delegate* delegate);
void RefreshToken(const OAuthClientInfo& oauth_client_info,
const std::string& refresh_token,
+ const std::vector<std::string>& scopes,
int max_retries,
Delegate* delegate);
void GetUserInfo(const std::string& oauth_access_token,
int max_retries,
Delegate* delegate);
+ void GetTokenInfo(const std::string& oauth_access_token,
+ int max_retries,
+ Delegate* delegate);
private:
// The guts of the implementation live in this class.
diff --git a/google_apis/gaia/gaia_oauth_client_unittest.cc b/google_apis/gaia/gaia_oauth_client_unittest.cc
index ba0c410..e91e4f6 100644
--- a/google_apis/gaia/gaia_oauth_client_unittest.cc
+++ b/google_apis/gaia/gaia_oauth_client_unittest.cc
@@ -5,10 +5,11 @@
// A complete set of unit tests for GaiaOAuthClient.
#include <string>
+#include <vector>
#include "base/message_loop.h"
#include "base/strings/string_number_conversions.h"
-#include "base/strings/string_util.h"
+#include "base/values.h"
#include "chrome/test/base/testing_profile.h"
#include "google_apis/gaia/gaia_oauth_client.h"
#include "googleurl/src/gurl.h"
@@ -21,23 +22,27 @@
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::Pointee;
+using ::testing::SaveArg;
namespace {
-const char kGaiaOAuth2Url[] = "https://accounts.google.com/o/oauth2/token";
-
// Responds as though OAuth returned from the server.
class MockOAuthFetcher : public net::TestURLFetcher {
public:
MockOAuthFetcher(int response_code,
int max_failure_count,
+ bool complete_immediately,
const GURL& url,
const std::string& results,
net::URLFetcher::RequestType request_type,
net::URLFetcherDelegate* d)
: net::TestURLFetcher(0, url, d),
max_failure_count_(max_failure_count),
- current_failure_count_(0) {
+ current_failure_count_(0),
+ complete_immediately_(complete_immediately) {
set_url(url);
set_response_code(response_code);
SetResponseString(results);
@@ -58,12 +63,19 @@ class MockOAuthFetcher : public net::TestURLFetcher {
}
set_status(net::URLRequestStatus(code, 0));
+ if (complete_immediately_)
+ delegate()->OnURLFetchComplete(this);
+ }
+
+ void Finish() {
+ ASSERT_FALSE(complete_immediately_);
delegate()->OnURLFetchComplete(this);
}
private:
int max_failure_count_;
int current_failure_count_;
+ bool complete_immediately_;
DISALLOW_COPY_AND_ASSIGN(MockOAuthFetcher);
};
@@ -72,7 +84,8 @@ class MockOAuthFetcherFactory : public net::URLFetcherFactory,
public:
MockOAuthFetcherFactory()
: net::ScopedURLFetcherFactory(this),
- response_code_(net::HTTP_OK) {
+ response_code_(net::HTTP_OK),
+ complete_immediately_(true) {
}
virtual ~MockOAuthFetcherFactory() {}
virtual net::URLFetcher* CreateURLFetcher(
@@ -80,13 +93,15 @@ class MockOAuthFetcherFactory : public net::URLFetcherFactory,
const GURL& url,
net::URLFetcher::RequestType request_type,
net::URLFetcherDelegate* d) OVERRIDE {
- return new MockOAuthFetcher(
+ url_fetcher_ = new MockOAuthFetcher(
response_code_,
max_failure_count_,
+ complete_immediately_,
url,
results_,
request_type,
d);
+ return url_fetcher_;
}
void set_response_code(int response_code) {
response_code_ = response_code;
@@ -97,8 +112,16 @@ class MockOAuthFetcherFactory : public net::URLFetcherFactory,
void set_results(const std::string& results) {
results_ = results;
}
+ MockOAuthFetcher* get_url_fetcher() {
+ return url_fetcher_;
+ }
+ void set_complete_immediately(bool complete_immediately) {
+ complete_immediately_ = complete_immediately;
+ }
private:
+ MockOAuthFetcher* url_fetcher_;
int response_code_;
+ bool complete_immediately_;
int max_failure_count_;
std::string results_;
DISALLOW_COPY_AND_ASSIGN(MockOAuthFetcherFactory);
@@ -121,6 +144,12 @@ const std::string kDummyRefreshTokenResult =
const std::string kDummyUserInfoResult =
"{\"email\":\"" + kTestUserEmail + "\"}";
+
+const std::string kDummyTokenInfoResult =
+ "{\"issued_to\": \"1234567890.apps.googleusercontent.com\","
+ "\"audience\": \"1234567890.apps.googleusercontent.com\","
+ "\"scope\": \"https://googleapis.com/oauth2/v2/tokeninfo\","
+ "\"expires_in\":" + base::IntToString(kTestExpiresIn) + "}";
}
namespace gaia {
@@ -128,10 +157,16 @@ namespace gaia {
class GaiaOAuthClientTest : public testing::Test {
public:
GaiaOAuthClientTest() {}
+ virtual void SetUp() OVERRIDE {
+ client_info_.client_id = "test_client_id";
+ client_info_.client_secret = "test_client_secret";
+ client_info_.redirect_uri = "test_redirect_uri";
+ };
TestingProfile profile_;
protected:
base::MessageLoop message_loop_;
+ OAuthClientInfo client_info_;
};
class MockGaiaOAuthClientDelegate : public gaia::GaiaOAuthClient::Delegate {
@@ -147,6 +182,22 @@ class MockGaiaOAuthClientDelegate : public gaia::GaiaOAuthClient::Delegate {
MOCK_METHOD1(OnGetUserInfoResponse, void(const std::string& user_email));
MOCK_METHOD0(OnOAuthError, void());
MOCK_METHOD1(OnNetworkError, void(int response_code));
+
+ // gMock doesn't like methods that take or return scoped_ptr. A
+ // work-around is to create a mock method that takes a raw ptr, and
+ // override the problematic method to call through to it.
+ // https://groups.google.com/a/chromium.org/d/msg/chromium-dev/01sDxsJ1OYw/I_S0xCBRF2oJ
+ MOCK_METHOD1(OnGetTokenInfoResponsePtr,
+ void(const DictionaryValue* token_info));
+ virtual void OnGetTokenInfoResponse(scoped_ptr<DictionaryValue> token_info)
+ OVERRIDE {
+ token_info_.reset(token_info.release());
+ OnGetTokenInfoResponsePtr(token_info_.get());
+ }
+
+ private:
+ scoped_ptr<DictionaryValue> token_info_;
+ DISALLOW_COPY_AND_ASSIGN(MockGaiaOAuthClientDelegate);
};
TEST_F(GaiaOAuthClientTest, NetworkFailure) {
@@ -156,19 +207,12 @@ TEST_F(GaiaOAuthClientTest, NetworkFailure) {
EXPECT_CALL(delegate, OnNetworkError(response_code))
.Times(1);
- TestingProfile profile;
-
MockOAuthFetcherFactory factory;
factory.set_response_code(response_code);
factory.set_max_failure_count(4);
- OAuthClientInfo client_info;
- client_info.client_id = "test_client_id";
- client_info.client_secret = "test_client_secret";
- client_info.redirect_uri = "test_redirect_uri";
- GaiaOAuthClient auth(kGaiaOAuth2Url,
- profile_.GetRequestContext());
- auth.GetTokensFromAuthCode(client_info, "auth_code", 2, &delegate);
+ GaiaOAuthClient auth(profile_.GetRequestContext());
+ auth.GetTokensFromAuthCode(client_info_, "auth_code", 2, &delegate);
}
TEST_F(GaiaOAuthClientTest, NetworkFailureRecover) {
@@ -178,20 +222,13 @@ TEST_F(GaiaOAuthClientTest, NetworkFailureRecover) {
EXPECT_CALL(delegate, OnGetTokensResponse(kTestRefreshToken, kTestAccessToken,
kTestExpiresIn)).Times(1);
- TestingProfile profile;
-
MockOAuthFetcherFactory factory;
factory.set_response_code(response_code);
factory.set_max_failure_count(4);
factory.set_results(kDummyGetTokensResult);
- OAuthClientInfo client_info;
- client_info.client_id = "test_client_id";
- client_info.client_secret = "test_client_secret";
- client_info.redirect_uri = "test_redirect_uri";
- GaiaOAuthClient auth(kGaiaOAuth2Url,
- profile_.GetRequestContext());
- auth.GetTokensFromAuthCode(client_info, "auth_code", -1, &delegate);
+ GaiaOAuthClient auth(profile_.GetRequestContext());
+ auth.GetTokensFromAuthCode(client_info_, "auth_code", -1, &delegate);
}
TEST_F(GaiaOAuthClientTest, OAuthFailure) {
@@ -200,20 +237,13 @@ TEST_F(GaiaOAuthClientTest, OAuthFailure) {
MockGaiaOAuthClientDelegate delegate;
EXPECT_CALL(delegate, OnOAuthError()).Times(1);
- TestingProfile profile;
-
MockOAuthFetcherFactory factory;
factory.set_response_code(response_code);
factory.set_max_failure_count(-1);
factory.set_results(kDummyGetTokensResult);
- OAuthClientInfo client_info;
- client_info.client_id = "test_client_id";
- client_info.client_secret = "test_client_secret";
- client_info.redirect_uri = "test_redirect_uri";
- GaiaOAuthClient auth(kGaiaOAuth2Url,
- profile_.GetRequestContext());
- auth.GetTokensFromAuthCode(client_info, "auth_code", -1, &delegate);
+ GaiaOAuthClient auth(profile_.GetRequestContext());
+ auth.GetTokensFromAuthCode(client_info_, "auth_code", -1, &delegate);
}
@@ -222,18 +252,11 @@ TEST_F(GaiaOAuthClientTest, GetTokensSuccess) {
EXPECT_CALL(delegate, OnGetTokensResponse(kTestRefreshToken, kTestAccessToken,
kTestExpiresIn)).Times(1);
- TestingProfile profile;
-
MockOAuthFetcherFactory factory;
factory.set_results(kDummyGetTokensResult);
- OAuthClientInfo client_info;
- client_info.client_id = "test_client_id";
- client_info.client_secret = "test_client_secret";
- client_info.redirect_uri = "test_redirect_uri";
- GaiaOAuthClient auth(kGaiaOAuth2Url,
- profile_.GetRequestContext());
- auth.GetTokensFromAuthCode(client_info, "auth_code", -1, &delegate);
+ GaiaOAuthClient auth(profile_.GetRequestContext());
+ auth.GetTokensFromAuthCode(client_info_, "auth_code", -1, &delegate);
}
TEST_F(GaiaOAuthClientTest, RefreshTokenSuccess) {
@@ -241,36 +264,63 @@ TEST_F(GaiaOAuthClientTest, RefreshTokenSuccess) {
EXPECT_CALL(delegate, OnRefreshTokenResponse(kTestAccessToken,
kTestExpiresIn)).Times(1);
- TestingProfile profile;
-
MockOAuthFetcherFactory factory;
factory.set_results(kDummyRefreshTokenResult);
+ factory.set_complete_immediately(false);
+
+ GaiaOAuthClient auth(profile_.GetRequestContext());
+ auth.RefreshToken(client_info_, "refresh_token", std::vector<std::string>(),
+ -1, &delegate);
+ EXPECT_THAT(factory.get_url_fetcher()->upload_data(),
+ Not(HasSubstr("scope")));
+ factory.get_url_fetcher()->Finish();
+}
- OAuthClientInfo client_info;
- client_info.client_id = "test_client_id";
- client_info.client_secret = "test_client_secret";
- client_info.redirect_uri = "test_redirect_uri";
- GaiaOAuthClient auth(kGaiaOAuth2Url,
- profile_.GetRequestContext());
- auth.RefreshToken(client_info, "refresh_token", -1, &delegate);
+TEST_F(GaiaOAuthClientTest, RefreshTokenDownscopingSuccess) {
+ MockGaiaOAuthClientDelegate delegate;
+ EXPECT_CALL(delegate, OnRefreshTokenResponse(kTestAccessToken,
+ kTestExpiresIn)).Times(1);
+
+ MockOAuthFetcherFactory factory;
+ factory.set_results(kDummyRefreshTokenResult);
+ factory.set_complete_immediately(false);
+
+ GaiaOAuthClient auth(profile_.GetRequestContext());
+ auth.RefreshToken(client_info_, "refresh_token",
+ std::vector<std::string>(1, "scope4test"), -1, &delegate);
+ EXPECT_THAT(factory.get_url_fetcher()->upload_data(),
+ HasSubstr("&scope=scope4test"));
+ factory.get_url_fetcher()->Finish();
}
+
TEST_F(GaiaOAuthClientTest, GetUserInfo) {
MockGaiaOAuthClientDelegate delegate;
EXPECT_CALL(delegate, OnGetUserInfoResponse(kTestUserEmail)).Times(1);
- TestingProfile profile;
-
MockOAuthFetcherFactory factory;
factory.set_results(kDummyUserInfoResult);
- OAuthClientInfo client_info;
- client_info.client_id = "test_client_id";
- client_info.client_secret = "test_client_secret";
- client_info.redirect_uri = "test_redirect_uri";
- GaiaOAuthClient auth(kGaiaOAuth2Url,
- profile_.GetRequestContext());
+ GaiaOAuthClient auth(profile_.GetRequestContext());
auth.GetUserInfo("access_token", 1, &delegate);
}
+TEST_F(GaiaOAuthClientTest, GetTokenInfo) {
+ const DictionaryValue* captured_result;
+
+ MockGaiaOAuthClientDelegate delegate;
+ EXPECT_CALL(delegate, OnGetTokenInfoResponsePtr(_))
+ .WillOnce(SaveArg<0>(&captured_result));
+
+ MockOAuthFetcherFactory factory;
+ factory.set_results(kDummyTokenInfoResult);
+
+ GaiaOAuthClient auth(profile_.GetRequestContext());
+ auth.GetTokenInfo("access_token", 1, &delegate);
+
+ std::string issued_to;
+ ASSERT_TRUE(captured_result->GetString("issued_to", &issued_to));
+ ASSERT_EQ("1234567890.apps.googleusercontent.com", issued_to);
+}
+
} // namespace gaia
diff --git a/google_apis/gaia/gaia_urls.cc b/google_apis/gaia/gaia_urls.cc
index 4cd0be8a..578205c 100644
--- a/google_apis/gaia/gaia_urls.cc
+++ b/google_apis/gaia/gaia_urls.cc
@@ -39,6 +39,7 @@ const char kClientOAuthUrlSuffix[] = "/ClientOAuth";
// API calls from www.googleapis.com
const char kOAuth2IssueTokenUrlSuffix[] = "/oauth2/v2/IssueToken";
+const char kOAuth2TokenInfoUrlSuffix[] = "/oauth2/v2/tokeninfo";
const char kOAuthUserInfoUrlSuffix[] = "/oauth2/v1/userinfo";
const char kOAuthWrapBridgeUserInfoScopeUrlSuffix[] = "/auth/userinfo.email";
@@ -117,6 +118,7 @@ GaiaUrls::GaiaUrls() {
kOAuthWrapBridgeUserInfoScopeUrlSuffix;
std::string oauth2_issue_token_url = google_apis_origin_url_ +
kOAuth2IssueTokenUrlSuffix;
+ oauth2_token_info_url_ = google_apis_origin_url_ + kOAuth2TokenInfoUrlSuffix;
std::string oauth_user_info_url = google_apis_origin_url_ +
kOAuthUserInfoUrlSuffix;
@@ -234,6 +236,10 @@ const std::string& GaiaUrls::oauth2_issue_token_url() const {
return oauth2_issue_token_url_;
}
+const std::string& GaiaUrls::oauth2_token_info_url() const {
+ return oauth2_token_info_url_;
+}
+
const std::string& GaiaUrls::oauth2_revoke_url() const {
return oauth2_revoke_url_;
}
diff --git a/google_apis/gaia/gaia_urls.h b/google_apis/gaia/gaia_urls.h
index 605aa64..34f8d86 100644
--- a/google_apis/gaia/gaia_urls.h
+++ b/google_apis/gaia/gaia_urls.h
@@ -42,6 +42,7 @@ class GaiaUrls {
const std::string& oauth2_auth_url() const;
const std::string& oauth2_token_url() const;
const std::string& oauth2_issue_token_url() const;
+ const std::string& oauth2_token_info_url() const;
const std::string& oauth2_revoke_url() const;
const std::string& gaia_login_form_realm() const;
@@ -80,6 +81,7 @@ class GaiaUrls {
std::string oauth2_auth_url_;
std::string oauth2_token_url_;
std::string oauth2_issue_token_url_;
+ std::string oauth2_token_info_url_;
std::string oauth2_revoke_url_;
std::string gaia_login_form_realm_;
diff --git a/remoting/host/setup/host_starter.cc b/remoting/host/setup/host_starter.cc
index d44059d..1d69ef0 100644
--- a/remoting/host/setup/host_starter.cc
+++ b/remoting/host/setup/host_starter.cc
@@ -34,12 +34,10 @@ HostStarter::~HostStarter() {
}
scoped_ptr<HostStarter> HostStarter::Create(
- const std::string& oauth2_token_url,
const std::string& chromoting_hosts_url,
net::URLRequestContextGetter* url_request_context_getter) {
scoped_ptr<gaia::GaiaOAuthClient> oauth_client(
- new gaia::GaiaOAuthClient(
- oauth2_token_url, url_request_context_getter));
+ new gaia::GaiaOAuthClient(url_request_context_getter));
scoped_ptr<remoting::ServiceClient> service_client(
new remoting::ServiceClient(
chromoting_hosts_url, url_request_context_getter));
diff --git a/remoting/host/setup/host_starter.h b/remoting/host/setup/host_starter.h
index e743a75..a8b90f2 100644
--- a/remoting/host/setup/host_starter.h
+++ b/remoting/host/setup/host_starter.h
@@ -33,7 +33,6 @@ class HostStarter : public gaia::GaiaOAuthClient::Delegate,
// Creates a HostStarter.
static scoped_ptr<HostStarter> Create(
- const std::string& oauth2_token_url,
const std::string& chromoting_hosts_url,
net::URLRequestContextGetter* url_request_context_getter);
diff --git a/remoting/host/setup/start_host.cc b/remoting/host/setup/start_host.cc
index 1338156..fc2390d 100644
--- a/remoting/host/setup/start_host.cc
+++ b/remoting/host/setup/start_host.cc
@@ -10,7 +10,6 @@
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread.h"
-#include "google_apis/gaia/gaia_urls.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_request_context_getter.h"
#include "remoting/host/service_urls.h"
@@ -160,7 +159,6 @@ int main(int argc, char** argv) {
// Start the host.
scoped_ptr<HostStarter> host_starter(HostStarter::Create(
- GaiaUrls::GetInstance()->oauth2_token_url(),
remoting::ServiceUrls::GetInstance()->directory_hosts_url(),
url_request_context_getter.get()));
if (redirect_url.empty()) {
diff --git a/remoting/host/setup/win/start_host_window.cc b/remoting/host/setup/win/start_host_window.cc
index 87bd9eb..3712dae 100644
--- a/remoting/host/setup/win/start_host_window.cc
+++ b/remoting/host/setup/win/start_host_window.cc
@@ -22,7 +22,6 @@ namespace remoting {
StartHostWindow::StartHostWindow(
scoped_refptr<net::URLRequestContextGetter> url_request_context_getter)
: host_starter_(remoting::HostStarter::Create(
- GaiaUrls::GetInstance()->oauth2_token_url(),
remoting::ServiceUrls::GetInstance()->directory_hosts_url(),
url_request_context_getter)),
consent_to_collect_data_(true),
diff --git a/remoting/host/signaling_connector.cc b/remoting/host/signaling_connector.cc
index 29298f7..36af665 100644
--- a/remoting/host/signaling_connector.cc
+++ b/remoting/host/signaling_connector.cc
@@ -6,7 +6,6 @@
#include "base/bind.h"
#include "base/callback.h"
-#include "google_apis/gaia/gaia_urls.h"
#include "google_apis/google_api_keys.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_request_context_getter.h"
@@ -61,8 +60,7 @@ void SignalingConnector::EnableOAuth(
scoped_ptr<OAuthCredentials> oauth_credentials) {
oauth_credentials_ = oauth_credentials.Pass();
gaia_oauth_client_.reset(
- new gaia::GaiaOAuthClient(GaiaUrls::GetInstance()->oauth2_token_url(),
- url_request_context_getter_.get()));
+ new gaia::GaiaOAuthClient(url_request_context_getter_.get()));
}
void SignalingConnector::OnSignalStrategyStateChange(
@@ -239,8 +237,10 @@ void SignalingConnector::RefreshOAuthToken() {
};
refreshing_oauth_token_ = true;
+ std::vector<std::string> empty_scope_list; // (Use scope from refresh token.)
gaia_oauth_client_->RefreshToken(
- client_info, oauth_credentials_->refresh_token, 1, this);
+ client_info, oauth_credentials_->refresh_token, empty_scope_list,
+ 1, this);
}
} // namespace remoting