summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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