Chromium Code Reviews| Index: chrome/browser/chromeos/settings/device_oauth2_token_service_unittest.cc |
| 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 6424ccbb6b88a905e52a55eeed9c7b3bdbcfc432..b066cebd20cf5c118329fcb30762d8b2075d4aad 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,16 @@ |
| #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 "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 +28,156 @@ using ::testing::StrictMock; |
| namespace chromeos { |
| +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; |
| + } |
|
Mattias Nissler (ping if slow)
2013/06/19 17:53:17
nit: newline
David Roche
2013/06/20 17:49:29
Done.
|
| + protected: |
| + // Skip calling into the policy subsystem and return our test value. |
| + virtual std::string GetRobotAccountId() OVERRIDE { |
| + return robot_account_id_; |
| + } |
|
Mattias Nissler (ping if slow)
2013/06/19 17:53:17
nit: newline
David Roche
2013/06/20 17:49:29
Done.
|
| + 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())), |
| + cryptohome_library_(chromeos::CryptohomeLibrary::GetTestImpl()) {} |
| virtual ~DeviceOAuth2TokenServiceTest() {} |
| virtual void SetUp() OVERRIDE { |
| + oauth2_service_ = new TestDeviceOAuth2TokenService( |
| + 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 void TearDown() OVERRIDE { |
| + delete oauth2_service_; |
|
Mattias Nissler (ping if slow)
2013/06/19 17:53:17
wrap in a scoped_ptr_, or make a plain class membe
David Roche
2013/06/20 17:49:29
Can't use scoped_ptr, since the constructor/destru
|
| + 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) { |
| + chromeos::CryptohomeLibrary::SetForTest(cryptohome_library_.get()); |
| + scoped_testing_local_state_.Get()->SetManagedPref( |
| + prefs::kDeviceRobotAnyApiRefreshToken, |
| + Value::CreateStringValue(refresh_token)); |
| + } |
| + |
| + std::string GetValidTokenInfoResponse(const std::string issued_to) { |
| + return "{ \"issued_to\": \"" + issued_to + "\"," |
| + " \"user_id\": \"1234567890\" }"; |
| + } |
| + |
| + // See implementation below for comments. |
| + void AssertRefreshTokenValidation( |
| + net::HttpStatusCode tokeninfo_access_token_fetch_response_code, |
| + const std::string& tokeninfo_access_token_fetch_response_string, |
| + int tokeninfo_access_token_fetch_successful_tokens, |
| + int tokeninfo_access_token_fetch_number_of_errors, |
| + net::HttpStatusCode tokeninfo_api_call_response_code, |
| + const std::string& tokeninfo_api_call_response_string, |
| + int tokeninfo_api_call_successful_tokens, |
| + int tokeninfo_api_call_number_of_errors, |
| + net::HttpStatusCode cloudprint_access_token_fetch_response_code, |
| + const std::string& cloudprint_access_token_fetch_response_string, |
| + int cloudprint_access_token_fetch_successful_tokens, |
| + int cloudprint_access_token_fetch_number_of_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::AssertRefreshTokenValidation( |
| + // Step 1: fetch the access token for the tokeninfo API. |
| + net::HttpStatusCode tokeninfo_access_token_fetch_response_code, |
| + const std::string& tokeninfo_access_token_fetch_response_string, |
| + int tokeninfo_access_token_fetch_successful_tokens, |
| + int tokeninfo_access_token_fetch_number_of_errors, |
| + |
| + // Step 2: call the tokeninfo API. |
| + net::HttpStatusCode tokeninfo_api_call_response_code, |
| + const std::string& tokeninfo_api_call_response_string, |
| + int tokeninfo_api_call_successful_tokens, |
| + int tokeninfo_api_call_number_of_errors, |
| + |
| + // Step 3: if steps 1 and 2 pass, fetch the access token for the requested |
| + // scope (in this case, cloudprint). |
| + net::HttpStatusCode cloudprint_access_token_fetch_response_code, |
| + const std::string& cloudprint_access_token_fetch_response_string, |
| + int cloudprint_access_token_fetch_successful_tokens, |
| + int cloudprint_access_token_fetch_number_of_errors) { |
| + |
| + SetDeviceRefreshTokenInLocalState("device_refresh_token_4_test"); |
| + |
| + scoped_ptr<OAuth2TokenService::Request> request( |
| + oauth2_service_->StartRequest(std::set<std::string>(), &consumer_)); |
| + message_loop_.RunUntilIdle(); |
| + |
| + EXPECT_EQ(0, consumer_.number_of_successful_tokens_); |
| + EXPECT_EQ(0, consumer_.number_of_errors_); |
| + |
| + // First the access token with tokeninfo scope is fetched. |
| + net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0); |
| + ASSERT_TRUE(fetcher); |
| + fetcher->set_response_code(tokeninfo_access_token_fetch_response_code); |
| + fetcher->SetResponseString(tokeninfo_access_token_fetch_response_string); |
| + fetcher->delegate()->OnURLFetchComplete(fetcher); |
| + EXPECT_EQ(tokeninfo_access_token_fetch_successful_tokens, |
| + consumer_.number_of_successful_tokens_); |
| + EXPECT_EQ(tokeninfo_access_token_fetch_number_of_errors, |
| + consumer_.number_of_errors_); |
| + |
| + // Then the actual tokeninfo call is made. |
| + if (consumer_.number_of_successful_tokens_ == 0 && |
| + consumer_.number_of_errors_ == 0) { |
| + fetcher = factory_.GetFetcherByID(0); |
| + fetcher->set_response_code(tokeninfo_api_call_response_code); |
| + fetcher->SetResponseString(tokeninfo_api_call_response_string); |
| + fetcher->delegate()->OnURLFetchComplete(fetcher); |
| + EXPECT_EQ(tokeninfo_api_call_successful_tokens, |
| + consumer_.number_of_successful_tokens_); |
| + EXPECT_EQ(tokeninfo_api_call_number_of_errors, |
| + consumer_.number_of_errors_); |
| + } |
| + |
| + // Finally, the token for the requested scope is requested. |
| + if (consumer_.number_of_successful_tokens_ == 0 && |
| + consumer_.number_of_errors_ == 0) { |
| + fetcher = factory_.GetFetcherByID(0); |
| + fetcher->set_response_code(cloudprint_access_token_fetch_response_code); |
| + fetcher->SetResponseString(cloudprint_access_token_fetch_response_string); |
| + fetcher->delegate()->OnURLFetchComplete(fetcher); |
| + EXPECT_EQ(cloudprint_access_token_fetch_successful_tokens, |
| + consumer_.number_of_successful_tokens_); |
| + EXPECT_EQ(cloudprint_access_token_fetch_number_of_errors, |
| + consumer_.number_of_errors_); |
| + } |
| +} |
| + |
| TEST_F(DeviceOAuth2TokenServiceTest, SaveEncryptedToken) { |
| StrictMock<MockCryptohomeLibrary> mock_cryptohome_library; |
| CryptohomeLibrary::SetForTest(&mock_cryptohome_library); |
| @@ -58,16 +194,192 @@ 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) { |
| + oauth2_service_->SetRobotAccountIdPolicyValue("service_acct@g.com"); |
| + |
| + AssertRefreshTokenValidation( |
| + |
| + // TokenInfo-scoped access token fetch. |
| + net::HTTP_OK, |
| + GetValidTokenResponse("tokeninfo_access_token", 3600), |
| + 0, // tokens |
| + 0, // errors |
| + |
| + // TokenInfo API call. |
| + net::HTTP_OK, |
| + GetValidTokenInfoResponse("service_acct@g.com"), |
| + 0, // tokens |
| + 0, // errors |
| + |
| + // CloudPrint access token fetch. |
| + net::HTTP_OK, |
| + GetValidTokenResponse("scoped_access_token", 3600), |
| + 1, // tokens |
| + 0); // errors |
| + |
| + EXPECT_EQ("scoped_access_token", consumer_.last_token_); |
| +} |
| + |
| +TEST_F(DeviceOAuth2TokenServiceTest, |
| + RefreshTokenValidation_Failure_TokenInfoAccessTokenHttpError) { |
| + oauth2_service_->SetRobotAccountIdPolicyValue("service_acct@g.com"); |
| + |
| + AssertRefreshTokenValidation( |
| + |
| + // TokenInfo-scoped access token fetch. |
| + net::HTTP_UNAUTHORIZED, |
| + "", |
| + 0, // tokens |
| + 1, // errors |
| + |
| + // TokenInfo API call skipped (error returned in previous step). |
| + net::HTTP_OK, "", 0, 0, |
| + |
| + // CloudPrint access token fetch skipped (error returned in previous step). |
| + net::HTTP_OK, "", 0, 0); |
| +} |
| + |
| +TEST_F(DeviceOAuth2TokenServiceTest, |
| + RefreshTokenValidation_Failure_TokenInfoAccessTokenInvalidResponse) { |
| + oauth2_service_->SetRobotAccountIdPolicyValue("service_acct@g.com"); |
| + |
| + AssertRefreshTokenValidation( |
| + |
| + // TokenInfo-scoped access token fetch. |
| + net::HTTP_OK, |
| + "invalid response", |
| + 0, // tokens |
| + 1, // errors |
| + |
| + // TokenInfo API call skipped (error returned in previous step). |
| + net::HTTP_OK, "", 0, 0, |
| + |
| + // CloudPrint access token fetch skipped (error returned in previous step). |
| + net::HTTP_OK, "", 0, 0); |
| +} |
| + |
| +TEST_F(DeviceOAuth2TokenServiceTest, |
| + RefreshTokenValidation_Failure_TokenInfoApiCallHttpError) { |
| + oauth2_service_->SetRobotAccountIdPolicyValue("service_acct@g.com"); |
| + |
| + AssertRefreshTokenValidation( |
| + |
| + // TokenInfo-scoped access token fetch. |
| + net::HTTP_OK, |
| + GetValidTokenResponse("tokeninfo_access_token", 3600), |
| + 0, // tokens |
| + 0, // errors |
| + |
| + // TokenInfo API call. |
| + net::HTTP_INTERNAL_SERVER_ERROR, |
| + "", |
| + 0, // tokens |
| + 1, // errors |
| + |
| + // CloudPrint access token fetch skipped (error returned in previous step). |
| + net::HTTP_OK, "", 0, 0); |
| +} |
| + |
| +TEST_F(DeviceOAuth2TokenServiceTest, |
| + RefreshTokenValidation_Failure_TokenInfoApiCallInvalidResponse) { |
| + oauth2_service_->SetRobotAccountIdPolicyValue("service_acct@g.com"); |
| + |
| + AssertRefreshTokenValidation( |
| + |
| + // TokenInfo-scoped access token fetch. |
| + net::HTTP_OK, |
| + GetValidTokenResponse("tokeninfo_access_token", 3600), |
| + 0, // tokens |
| + 0, // errors |
| + |
| + // TokenInfo API call. |
| + net::HTTP_OK, |
| + "invalid response", |
| + 0, // tokens |
| + 1, // errors |
| + |
| + // CloudPrint access token fetch skipped (error returned in previous step). |
| + net::HTTP_OK, "", 0, 0); |
| +} |
| + |
| +TEST_F(DeviceOAuth2TokenServiceTest, |
| + RefreshTokenValidation_Failure_CloudPrintAccessTokenHttpError) { |
| + oauth2_service_->SetRobotAccountIdPolicyValue("service_acct@g.com"); |
| + |
| + AssertRefreshTokenValidation( |
| + |
| + // TokenInfo-scoped access token fetch. |
| + net::HTTP_OK, |
| + GetValidTokenResponse("tokeninfo_access_token", 3600), |
| + 0, // tokens |
| + 0, // errors |
| + |
| + // TokenInfo API call. |
| + net::HTTP_OK, |
| + GetValidTokenInfoResponse("service_acct@g.com"), |
| + 0, // tokens |
| + 0, // errors |
| + |
| + // CloudPrint access token fetch. |
| + net::HTTP_BAD_REQUEST, |
| + "", |
| + 0, // tokens |
| + 1); // errors |
| +} |
| + |
| +TEST_F(DeviceOAuth2TokenServiceTest, |
| + RefreshTokenValidation_Failure_CloudPrintAccessTokenInvalidResponse) { |
| + oauth2_service_->SetRobotAccountIdPolicyValue("service_acct@g.com"); |
| + |
| + AssertRefreshTokenValidation( |
| + |
| + // TokenInfo-scoped access token fetch. |
| + net::HTTP_OK, |
| + GetValidTokenResponse("tokeninfo_access_token", 3600), |
| + 0, // tokens |
| + 0, // errors |
| + |
| + // TokenInfo API call. |
| + net::HTTP_OK, |
| + GetValidTokenInfoResponse("service_acct@g.com"), |
| + 0, // tokens |
| + 0, // errors |
| + |
| + // CloudPrint access token fetch. |
| + net::HTTP_OK, |
| + "invalid request", |
| + 0, // tokens |
| + 1); // errors |
| +} |
| + |
| +TEST_F(DeviceOAuth2TokenServiceTest, RefreshTokenValidation_Failure_BadOwner) { |
| + oauth2_service_->SetRobotAccountIdPolicyValue("WRONG_service_acct@g.com"); |
| + |
| + AssertRefreshTokenValidation( |
| + |
| + // TokenInfo-scoped access token fetch. |
| + net::HTTP_OK, |
| + GetValidTokenResponse("tokeninfo_access_token", 3600), |
| + 0, // tokens |
| + 0, // errors |
| + |
| + // TokenInfo API call. |
| + // The token owner doesn't match the policy value; an error is returned. |
| + net::HTTP_OK, |
| + GetValidTokenInfoResponse("service_acct@g.com"), |
| + 0, // tokens |
| + 1, // errors |
| + |
| + // CloudPrint access token fetch is skipped. |
| + net::HTTP_OK, "", 0, 0); |
| } |
| } // namespace chromeos |