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..1ba7004a6339c911dfc5521cbc63acef9c922917 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 |