device_oauth2_token_service_unittest.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/chromeos/settings/device_oauth2_token_service.h" 6 7#include "base/message_loop/message_loop.h" 8#include "base/prefs/testing_pref_service.h" 9#include "base/run_loop.h" 10#include "chrome/browser/chromeos/settings/token_encryptor.h" 11#include "chrome/common/pref_names.h" 12#include "chrome/test/base/scoped_testing_local_state.h" 13#include "chrome/test/base/testing_browser_process.h" 14#include "chromeos/cryptohome/system_salt_getter.h" 15#include "chromeos/dbus/fake_dbus_thread_manager.h" 16#include "content/public/browser/browser_thread.h" 17#include "content/public/test/test_browser_thread.h" 18#include "google_apis/gaia/gaia_oauth_client.h" 19#include "google_apis/gaia/oauth2_token_service_test_util.h" 20#include "net/http/http_status_code.h" 21#include "net/url_request/test_url_fetcher_factory.h" 22#include "net/url_request/url_fetcher_delegate.h" 23#include "net/url_request/url_request_test_util.h" 24#include "testing/gmock/include/gmock/gmock.h" 25#include "testing/gtest/include/gtest/gtest.h" 26 27using ::testing::_; 28using ::testing::AnyNumber; 29using ::testing::Return; 30using ::testing::ReturnArg; 31using ::testing::StrEq; 32using ::testing::StrictMock; 33 34namespace chromeos { 35 36static const int kOAuthTokenServiceUrlFetcherId = 0; 37static const int kValidatorUrlFetcherId = gaia::GaiaOAuthClient::kUrlFetcherId; 38 39class TestDeviceOAuth2TokenService : public DeviceOAuth2TokenService { 40 public: 41 explicit TestDeviceOAuth2TokenService(net::URLRequestContextGetter* getter, 42 PrefService* local_state, 43 TokenEncryptor* token_encryptor) 44 : DeviceOAuth2TokenService(getter, 45 local_state, 46 token_encryptor) { 47 } 48 void SetRobotAccountIdPolicyValue(const std::string& id) { 49 robot_account_id_ = id; 50 } 51 52 // Skip calling into the policy subsystem and return our test value. 53 virtual std::string GetRobotAccountId() OVERRIDE { 54 return robot_account_id_; 55 } 56 57 private: 58 std::string robot_account_id_; 59 DISALLOW_COPY_AND_ASSIGN(TestDeviceOAuth2TokenService); 60}; 61 62class MockTokenEncryptor : public TokenEncryptor { 63 public: 64 MOCK_METHOD1(EncryptWithSystemSalt, std::string(const std::string&)); 65 MOCK_METHOD1(DecryptWithSystemSalt, std::string(const std::string&)); 66}; 67 68class DeviceOAuth2TokenServiceTest : public testing::Test { 69 public: 70 DeviceOAuth2TokenServiceTest() 71 : ui_thread_(content::BrowserThread::UI, &message_loop_), 72 scoped_testing_local_state_(TestingBrowserProcess::GetGlobal()), 73 request_context_getter_(new net::TestURLRequestContextGetter( 74 message_loop_.message_loop_proxy())), 75 mock_token_encryptor_(new StrictMock<MockTokenEncryptor>), 76 oauth2_service_(request_context_getter_.get(), 77 scoped_testing_local_state_.Get(), 78 mock_token_encryptor_) { 79 oauth2_service_.max_refresh_token_validation_retries_ = 0; 80 oauth2_service_.set_max_authorization_token_fetch_retries_for_testing(0); 81 } 82 virtual ~DeviceOAuth2TokenServiceTest() {} 83 84 // Most tests just want a noop crypto impl with a dummy refresh token value in 85 // Local State (if the value is an empty string, it will be ignored). 86 void SetUpDefaultValues() { 87 SetDeviceRefreshTokenInLocalState("device_refresh_token_4_test"); 88 oauth2_service_.SetRobotAccountIdPolicyValue("service_acct@g.com"); 89 AssertConsumerTokensAndErrors(0, 0); 90 91 // Returns the input token as-is. 92 EXPECT_CALL(*mock_token_encryptor_, DecryptWithSystemSalt(_)) 93 .WillRepeatedly(ReturnArg<0>()); 94 } 95 96 scoped_ptr<OAuth2TokenService::Request> StartTokenRequest() { 97 return oauth2_service_.StartRequest(oauth2_service_.GetRobotAccountId(), 98 std::set<std::string>(), 99 &consumer_); 100 } 101 102 virtual void SetUp() OVERRIDE { 103 // TODO(xiyuan): Remove this when cleaning up the system salt load temp fix. 104 scoped_ptr<FakeDBusThreadManager> fake_dbus_thread_manager( 105 new FakeDBusThreadManager); 106 DBusThreadManager::InitializeForTesting(fake_dbus_thread_manager.release()); 107 SystemSaltGetter::Initialize(); 108 } 109 110 virtual void TearDown() OVERRIDE { 111 SystemSaltGetter::Shutdown(); 112 DBusThreadManager::Shutdown(); 113 base::RunLoop().RunUntilIdle(); 114 } 115 116 // Utility method to set a value in Local State for the device refresh token 117 // (it must have a non-empty value or it won't be used). 118 void SetDeviceRefreshTokenInLocalState(const std::string& refresh_token) { 119 scoped_testing_local_state_.Get()->SetManagedPref( 120 prefs::kDeviceRobotAnyApiRefreshToken, 121 base::Value::CreateStringValue(refresh_token)); 122 } 123 124 std::string GetValidTokenInfoResponse(const std::string email) { 125 return "{ \"email\": \"" + email + "\"," 126 " \"user_id\": \"1234567890\" }"; 127 } 128 129 // A utility method to return fake URL results, for testing the refresh token 130 // validation logic. For a successful validation attempt, this method will be 131 // called three times for the steps listed below (steps 1 and 2 happen in 132 // parallel). 133 // 134 // Step 1a: fetch the access token for the tokeninfo API. 135 // Step 1b: call the tokeninfo API. 136 // Step 2: Fetch the access token for the requested scope 137 // (in this case, cloudprint). 138 void ReturnOAuthUrlFetchResults(int fetcher_id, 139 net::HttpStatusCode response_code, 140 const std::string& response_string); 141 142 void AssertConsumerTokensAndErrors(int num_tokens, int num_errors); 143 144 protected: 145 base::MessageLoop message_loop_; 146 content::TestBrowserThread ui_thread_; 147 ScopedTestingLocalState scoped_testing_local_state_; 148 scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_; 149 net::TestURLFetcherFactory factory_; 150 // Owned by oauth2_service_. 151 StrictMock<MockTokenEncryptor>* mock_token_encryptor_; 152 TestDeviceOAuth2TokenService oauth2_service_; 153 TestingOAuth2TokenServiceConsumer consumer_; 154}; 155 156void DeviceOAuth2TokenServiceTest::ReturnOAuthUrlFetchResults( 157 int fetcher_id, 158 net::HttpStatusCode response_code, 159 const std::string& response_string) { 160 161 net::TestURLFetcher* fetcher = factory_.GetFetcherByID(fetcher_id); 162 ASSERT_TRUE(fetcher); 163 fetcher->set_response_code(response_code); 164 fetcher->SetResponseString(response_string); 165 fetcher->delegate()->OnURLFetchComplete(fetcher); 166} 167 168void DeviceOAuth2TokenServiceTest::AssertConsumerTokensAndErrors( 169 int num_tokens, 170 int num_errors) { 171 ASSERT_EQ(num_tokens, consumer_.number_of_successful_tokens_); 172 ASSERT_EQ(num_errors, consumer_.number_of_errors_); 173} 174 175TEST_F(DeviceOAuth2TokenServiceTest, SaveEncryptedToken) { 176 EXPECT_CALL(*mock_token_encryptor_, DecryptWithSystemSalt(StrEq(""))) 177 .Times(1) 178 .WillOnce(Return("")); 179 EXPECT_CALL(*mock_token_encryptor_, 180 EncryptWithSystemSalt(StrEq("test-token"))) 181 .Times(1) 182 .WillOnce(Return("encrypted")); 183 EXPECT_CALL(*mock_token_encryptor_, 184 DecryptWithSystemSalt(StrEq("encrypted"))) 185 .Times(1) 186 .WillOnce(Return("test-token")); 187 188 ASSERT_EQ("", oauth2_service_.GetRefreshToken( 189 oauth2_service_.GetRobotAccountId())); 190 oauth2_service_.SetAndSaveRefreshToken("test-token"); 191 ASSERT_EQ("test-token", oauth2_service_.GetRefreshToken( 192 oauth2_service_.GetRobotAccountId())); 193 194 // This call won't invoke decrypt again, since the value is cached. 195 ASSERT_EQ("test-token", oauth2_service_.GetRefreshToken( 196 oauth2_service_.GetRobotAccountId())); 197} 198 199TEST_F(DeviceOAuth2TokenServiceTest, RefreshTokenValidation_Success) { 200 SetUpDefaultValues(); 201 scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest(); 202 203 ReturnOAuthUrlFetchResults( 204 kValidatorUrlFetcherId, 205 net::HTTP_OK, 206 GetValidTokenResponse("tokeninfo_access_token", 3600)); 207 208 ReturnOAuthUrlFetchResults( 209 kValidatorUrlFetcherId, 210 net::HTTP_OK, 211 GetValidTokenInfoResponse("service_acct@g.com")); 212 213 ReturnOAuthUrlFetchResults( 214 kOAuthTokenServiceUrlFetcherId, 215 net::HTTP_OK, 216 GetValidTokenResponse("scoped_access_token", 3600)); 217 218 AssertConsumerTokensAndErrors(1, 0); 219 220 EXPECT_EQ("scoped_access_token", consumer_.last_token_); 221} 222 223TEST_F(DeviceOAuth2TokenServiceTest, 224 RefreshTokenValidation_Failure_TokenInfoAccessTokenHttpError) { 225 SetUpDefaultValues(); 226 scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest(); 227 228 ReturnOAuthUrlFetchResults( 229 kValidatorUrlFetcherId, 230 net::HTTP_UNAUTHORIZED, 231 ""); 232 233 // TokenInfo API call skipped (error returned in previous step). 234 235 // CloudPrint access token fetch is successful, but consumer still given error 236 // due to bad refresh token. 237 ReturnOAuthUrlFetchResults( 238 kOAuthTokenServiceUrlFetcherId, 239 net::HTTP_OK, 240 GetValidTokenResponse("ignored_scoped_access_token", 3600)); 241 242 AssertConsumerTokensAndErrors(0, 1); 243} 244 245TEST_F(DeviceOAuth2TokenServiceTest, 246 RefreshTokenValidation_Failure_TokenInfoAccessTokenInvalidResponse) { 247 SetUpDefaultValues(); 248 scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest(); 249 250 ReturnOAuthUrlFetchResults( 251 kValidatorUrlFetcherId, 252 net::HTTP_OK, 253 "invalid response"); 254 255 // TokenInfo API call skipped (error returned in previous step). 256 257 ReturnOAuthUrlFetchResults( 258 kOAuthTokenServiceUrlFetcherId, 259 net::HTTP_OK, 260 GetValidTokenResponse("ignored_scoped_access_token", 3600)); 261 262 // CloudPrint access token fetch is successful, but consumer still given error 263 // due to bad refresh token. 264 AssertConsumerTokensAndErrors(0, 1); 265} 266 267TEST_F(DeviceOAuth2TokenServiceTest, 268 RefreshTokenValidation_Failure_TokenInfoApiCallHttpError) { 269 SetUpDefaultValues(); 270 scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest(); 271 272 ReturnOAuthUrlFetchResults( 273 kValidatorUrlFetcherId, 274 net::HTTP_OK, 275 GetValidTokenResponse("tokeninfo_access_token", 3600)); 276 277 ReturnOAuthUrlFetchResults( 278 kValidatorUrlFetcherId, 279 net::HTTP_INTERNAL_SERVER_ERROR, 280 ""); 281 282 ReturnOAuthUrlFetchResults( 283 kOAuthTokenServiceUrlFetcherId, 284 net::HTTP_OK, 285 GetValidTokenResponse("ignored_scoped_access_token", 3600)); 286 287 // CloudPrint access token fetch is successful, but consumer still given error 288 // due to bad refresh token. 289 AssertConsumerTokensAndErrors(0, 1); 290} 291 292TEST_F(DeviceOAuth2TokenServiceTest, 293 RefreshTokenValidation_Failure_TokenInfoApiCallInvalidResponse) { 294 SetUpDefaultValues(); 295 scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest(); 296 297 ReturnOAuthUrlFetchResults( 298 kValidatorUrlFetcherId, 299 net::HTTP_OK, 300 GetValidTokenResponse("tokeninfo_access_token", 3600)); 301 302 ReturnOAuthUrlFetchResults( 303 kValidatorUrlFetcherId, 304 net::HTTP_OK, 305 "invalid response"); 306 307 ReturnOAuthUrlFetchResults( 308 kOAuthTokenServiceUrlFetcherId, 309 net::HTTP_OK, 310 GetValidTokenResponse("ignored_scoped_access_token", 3600)); 311 312 // CloudPrint access token fetch is successful, but consumer still given error 313 // due to bad refresh token. 314 AssertConsumerTokensAndErrors(0, 1); 315} 316 317TEST_F(DeviceOAuth2TokenServiceTest, 318 RefreshTokenValidation_Failure_CloudPrintAccessTokenHttpError) { 319 SetUpDefaultValues(); 320 scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest(); 321 322 ReturnOAuthUrlFetchResults( 323 kValidatorUrlFetcherId, 324 net::HTTP_OK, 325 GetValidTokenResponse("tokeninfo_access_token", 3600)); 326 327 ReturnOAuthUrlFetchResults( 328 kValidatorUrlFetcherId, 329 net::HTTP_OK, 330 GetValidTokenInfoResponse("service_acct@g.com")); 331 332 ReturnOAuthUrlFetchResults( 333 kOAuthTokenServiceUrlFetcherId, 334 net::HTTP_BAD_REQUEST, 335 ""); 336 337 AssertConsumerTokensAndErrors(0, 1); 338} 339 340TEST_F(DeviceOAuth2TokenServiceTest, 341 RefreshTokenValidation_Failure_CloudPrintAccessTokenInvalidResponse) { 342 SetUpDefaultValues(); 343 scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest(); 344 345 ReturnOAuthUrlFetchResults( 346 kValidatorUrlFetcherId, 347 net::HTTP_OK, 348 GetValidTokenResponse("tokeninfo_access_token", 3600)); 349 350 ReturnOAuthUrlFetchResults( 351 kValidatorUrlFetcherId, 352 net::HTTP_OK, 353 GetValidTokenInfoResponse("service_acct@g.com")); 354 355 ReturnOAuthUrlFetchResults( 356 kOAuthTokenServiceUrlFetcherId, 357 net::HTTP_OK, 358 "invalid request"); 359 360 AssertConsumerTokensAndErrors(0, 1); 361} 362 363TEST_F(DeviceOAuth2TokenServiceTest, RefreshTokenValidation_Failure_BadOwner) { 364 SetUpDefaultValues(); 365 scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest(); 366 367 oauth2_service_.SetRobotAccountIdPolicyValue("WRONG_service_acct@g.com"); 368 369 // The requested token comes in before any of the validation calls complete, 370 // but the consumer still gets an error, since the results don't get returned 371 // until validation is over. 372 ReturnOAuthUrlFetchResults( 373 kOAuthTokenServiceUrlFetcherId, 374 net::HTTP_OK, 375 GetValidTokenResponse("ignored_scoped_access_token", 3600)); 376 AssertConsumerTokensAndErrors(0, 0); 377 378 ReturnOAuthUrlFetchResults( 379 kValidatorUrlFetcherId, 380 net::HTTP_OK, 381 GetValidTokenResponse("tokeninfo_access_token", 3600)); 382 AssertConsumerTokensAndErrors(0, 0); 383 384 ReturnOAuthUrlFetchResults( 385 kValidatorUrlFetcherId, 386 net::HTTP_OK, 387 GetValidTokenInfoResponse("service_acct@g.com")); 388 389 // All fetches were successful, but consumer still given error since 390 // the token owner doesn't match the policy value. 391 AssertConsumerTokensAndErrors(0, 1); 392} 393 394} // namespace chromeos 395