diff options
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 |