Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/chromeos/settings/device_oauth2_token_service.h" | 5 #include "chrome/browser/chromeos/settings/device_oauth2_token_service.h" |
| 6 | 6 |
| 7 #include "base/message_loop.h" | 7 #include "base/message_loop.h" |
| 8 #include "base/prefs/testing_pref_service.h" | 8 #include "base/prefs/testing_pref_service.h" |
| 9 #include "chrome/browser/signin/oauth2_token_service_test_util.h" | |
| 10 #include "chrome/common/pref_names.h" | |
| 9 #include "chrome/test/base/scoped_testing_local_state.h" | 11 #include "chrome/test/base/scoped_testing_local_state.h" |
| 10 #include "chrome/test/base/testing_browser_process.h" | 12 #include "chrome/test/base/testing_browser_process.h" |
| 11 #include "chromeos/cryptohome/mock_cryptohome_library.h" | 13 #include "chromeos/cryptohome/mock_cryptohome_library.h" |
| 12 #include "content/public/browser/browser_thread.h" | 14 #include "content/public/browser/browser_thread.h" |
| 13 #include "content/public/test/test_browser_thread.h" | 15 #include "content/public/test/test_browser_thread.h" |
| 16 #include "net/http/http_status_code.h" | |
| 17 #include "net/url_request/test_url_fetcher_factory.h" | |
| 18 #include "net/url_request/url_fetcher_delegate.h" | |
| 14 #include "net/url_request/url_request_test_util.h" | 19 #include "net/url_request/url_request_test_util.h" |
| 15 #include "testing/gmock/include/gmock/gmock.h" | 20 #include "testing/gmock/include/gmock/gmock.h" |
| 16 #include "testing/gtest/include/gtest/gtest.h" | 21 #include "testing/gtest/include/gtest/gtest.h" |
| 17 | 22 |
| 18 using ::testing::_; | 23 using ::testing::_; |
| 19 using ::testing::AnyNumber; | 24 using ::testing::AnyNumber; |
| 20 using ::testing::Return; | 25 using ::testing::Return; |
| 21 using ::testing::StrEq; | 26 using ::testing::StrEq; |
| 22 using ::testing::StrictMock; | 27 using ::testing::StrictMock; |
| 23 | 28 |
| 24 namespace chromeos { | 29 namespace chromeos { |
| 25 | 30 |
| 31 class TestDeviceOAuth2TokenService : public DeviceOAuth2TokenService { | |
| 32 public: | |
| 33 explicit TestDeviceOAuth2TokenService(net::URLRequestContextGetter* getter, | |
| 34 PrefService* local_state) | |
| 35 : DeviceOAuth2TokenService(getter, local_state) { | |
| 36 } | |
| 37 void SetRobotAccountIdPolicyValue(const std::string& id) { | |
| 38 robot_account_id_ = id; | |
| 39 } | |
|
Mattias Nissler (ping if slow)
2013/06/19 17:53:17
nit: newline
David Roche
2013/06/20 17:49:29
Done.
| |
| 40 protected: | |
| 41 // Skip calling into the policy subsystem and return our test value. | |
| 42 virtual std::string GetRobotAccountId() OVERRIDE { | |
| 43 return robot_account_id_; | |
| 44 } | |
|
Mattias Nissler (ping if slow)
2013/06/19 17:53:17
nit: newline
David Roche
2013/06/20 17:49:29
Done.
| |
| 45 private: | |
| 46 std::string robot_account_id_; | |
| 47 DISALLOW_COPY_AND_ASSIGN(TestDeviceOAuth2TokenService); | |
| 48 }; | |
| 49 | |
| 26 class DeviceOAuth2TokenServiceTest : public testing::Test { | 50 class DeviceOAuth2TokenServiceTest : public testing::Test { |
| 27 public: | 51 public: |
| 28 DeviceOAuth2TokenServiceTest() | 52 DeviceOAuth2TokenServiceTest() |
| 29 : ui_thread_(content::BrowserThread::UI, &message_loop_), | 53 : ui_thread_(content::BrowserThread::UI, &message_loop_), |
| 30 scoped_testing_local_state_(TestingBrowserProcess::GetGlobal()) {} | 54 scoped_testing_local_state_(TestingBrowserProcess::GetGlobal()), |
| 55 request_context_getter_(new net::TestURLRequestContextGetter( | |
| 56 message_loop_.message_loop_proxy())), | |
| 57 cryptohome_library_(chromeos::CryptohomeLibrary::GetTestImpl()) {} | |
| 31 virtual ~DeviceOAuth2TokenServiceTest() {} | 58 virtual ~DeviceOAuth2TokenServiceTest() {} |
| 32 | 59 |
| 33 virtual void SetUp() OVERRIDE { | 60 virtual void SetUp() OVERRIDE { |
| 61 oauth2_service_ = new TestDeviceOAuth2TokenService( | |
| 62 request_context_getter_, | |
| 63 scoped_testing_local_state_.Get()); | |
| 64 oauth2_service_->max_refresh_token_validation_retries_ = 0; | |
| 65 oauth2_service_->set_max_authorization_token_fetch_retries_for_testing(0); | |
| 34 } | 66 } |
| 35 | 67 |
| 36 virtual void TearDown() OVERRIDE { | 68 virtual void TearDown() OVERRIDE { |
| 69 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
| |
| 70 CryptohomeLibrary::SetForTest(NULL); | |
| 37 } | 71 } |
| 38 | 72 |
| 73 // Utility method to set a value in Local State for the device refresh token | |
| 74 // (it must have a non-empty value or it won't be used). | |
| 75 void SetDeviceRefreshTokenInLocalState(const std::string& refresh_token) { | |
| 76 chromeos::CryptohomeLibrary::SetForTest(cryptohome_library_.get()); | |
| 77 scoped_testing_local_state_.Get()->SetManagedPref( | |
| 78 prefs::kDeviceRobotAnyApiRefreshToken, | |
| 79 Value::CreateStringValue(refresh_token)); | |
| 80 } | |
| 81 | |
| 82 std::string GetValidTokenInfoResponse(const std::string issued_to) { | |
| 83 return "{ \"issued_to\": \"" + issued_to + "\"," | |
| 84 " \"user_id\": \"1234567890\" }"; | |
| 85 } | |
| 86 | |
| 87 // See implementation below for comments. | |
| 88 void AssertRefreshTokenValidation( | |
| 89 net::HttpStatusCode tokeninfo_access_token_fetch_response_code, | |
| 90 const std::string& tokeninfo_access_token_fetch_response_string, | |
| 91 int tokeninfo_access_token_fetch_successful_tokens, | |
| 92 int tokeninfo_access_token_fetch_number_of_errors, | |
| 93 net::HttpStatusCode tokeninfo_api_call_response_code, | |
| 94 const std::string& tokeninfo_api_call_response_string, | |
| 95 int tokeninfo_api_call_successful_tokens, | |
| 96 int tokeninfo_api_call_number_of_errors, | |
| 97 net::HttpStatusCode cloudprint_access_token_fetch_response_code, | |
| 98 const std::string& cloudprint_access_token_fetch_response_string, | |
| 99 int cloudprint_access_token_fetch_successful_tokens, | |
| 100 int cloudprint_access_token_fetch_number_of_errors); | |
| 101 | |
| 39 protected: | 102 protected: |
| 40 base::MessageLoop message_loop_; | 103 base::MessageLoop message_loop_; |
| 41 content::TestBrowserThread ui_thread_; | 104 content::TestBrowserThread ui_thread_; |
| 42 ScopedTestingLocalState scoped_testing_local_state_; | 105 ScopedTestingLocalState scoped_testing_local_state_; |
| 106 scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_; | |
| 107 net::TestURLFetcherFactory factory_; | |
| 108 TestDeviceOAuth2TokenService* oauth2_service_; | |
| 109 TestingOAuth2TokenServiceConsumer consumer_; | |
| 110 scoped_ptr<chromeos::CryptohomeLibrary> cryptohome_library_; | |
| 111 | |
| 43 }; | 112 }; |
| 44 | 113 |
| 114 void DeviceOAuth2TokenServiceTest::AssertRefreshTokenValidation( | |
| 115 // Step 1: fetch the access token for the tokeninfo API. | |
| 116 net::HttpStatusCode tokeninfo_access_token_fetch_response_code, | |
| 117 const std::string& tokeninfo_access_token_fetch_response_string, | |
| 118 int tokeninfo_access_token_fetch_successful_tokens, | |
| 119 int tokeninfo_access_token_fetch_number_of_errors, | |
| 120 | |
| 121 // Step 2: call the tokeninfo API. | |
| 122 net::HttpStatusCode tokeninfo_api_call_response_code, | |
| 123 const std::string& tokeninfo_api_call_response_string, | |
| 124 int tokeninfo_api_call_successful_tokens, | |
| 125 int tokeninfo_api_call_number_of_errors, | |
| 126 | |
| 127 // Step 3: if steps 1 and 2 pass, fetch the access token for the requested | |
| 128 // scope (in this case, cloudprint). | |
| 129 net::HttpStatusCode cloudprint_access_token_fetch_response_code, | |
| 130 const std::string& cloudprint_access_token_fetch_response_string, | |
| 131 int cloudprint_access_token_fetch_successful_tokens, | |
| 132 int cloudprint_access_token_fetch_number_of_errors) { | |
| 133 | |
| 134 SetDeviceRefreshTokenInLocalState("device_refresh_token_4_test"); | |
| 135 | |
| 136 scoped_ptr<OAuth2TokenService::Request> request( | |
| 137 oauth2_service_->StartRequest(std::set<std::string>(), &consumer_)); | |
| 138 message_loop_.RunUntilIdle(); | |
| 139 | |
| 140 EXPECT_EQ(0, consumer_.number_of_successful_tokens_); | |
| 141 EXPECT_EQ(0, consumer_.number_of_errors_); | |
| 142 | |
| 143 // First the access token with tokeninfo scope is fetched. | |
| 144 net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0); | |
| 145 ASSERT_TRUE(fetcher); | |
| 146 fetcher->set_response_code(tokeninfo_access_token_fetch_response_code); | |
| 147 fetcher->SetResponseString(tokeninfo_access_token_fetch_response_string); | |
| 148 fetcher->delegate()->OnURLFetchComplete(fetcher); | |
| 149 EXPECT_EQ(tokeninfo_access_token_fetch_successful_tokens, | |
| 150 consumer_.number_of_successful_tokens_); | |
| 151 EXPECT_EQ(tokeninfo_access_token_fetch_number_of_errors, | |
| 152 consumer_.number_of_errors_); | |
| 153 | |
| 154 // Then the actual tokeninfo call is made. | |
| 155 if (consumer_.number_of_successful_tokens_ == 0 && | |
| 156 consumer_.number_of_errors_ == 0) { | |
| 157 fetcher = factory_.GetFetcherByID(0); | |
| 158 fetcher->set_response_code(tokeninfo_api_call_response_code); | |
| 159 fetcher->SetResponseString(tokeninfo_api_call_response_string); | |
| 160 fetcher->delegate()->OnURLFetchComplete(fetcher); | |
| 161 EXPECT_EQ(tokeninfo_api_call_successful_tokens, | |
| 162 consumer_.number_of_successful_tokens_); | |
| 163 EXPECT_EQ(tokeninfo_api_call_number_of_errors, | |
| 164 consumer_.number_of_errors_); | |
| 165 } | |
| 166 | |
| 167 // Finally, the token for the requested scope is requested. | |
| 168 if (consumer_.number_of_successful_tokens_ == 0 && | |
| 169 consumer_.number_of_errors_ == 0) { | |
| 170 fetcher = factory_.GetFetcherByID(0); | |
| 171 fetcher->set_response_code(cloudprint_access_token_fetch_response_code); | |
| 172 fetcher->SetResponseString(cloudprint_access_token_fetch_response_string); | |
| 173 fetcher->delegate()->OnURLFetchComplete(fetcher); | |
| 174 EXPECT_EQ(cloudprint_access_token_fetch_successful_tokens, | |
| 175 consumer_.number_of_successful_tokens_); | |
| 176 EXPECT_EQ(cloudprint_access_token_fetch_number_of_errors, | |
| 177 consumer_.number_of_errors_); | |
| 178 } | |
| 179 } | |
| 180 | |
| 45 TEST_F(DeviceOAuth2TokenServiceTest, SaveEncryptedToken) { | 181 TEST_F(DeviceOAuth2TokenServiceTest, SaveEncryptedToken) { |
| 46 StrictMock<MockCryptohomeLibrary> mock_cryptohome_library; | 182 StrictMock<MockCryptohomeLibrary> mock_cryptohome_library; |
| 47 CryptohomeLibrary::SetForTest(&mock_cryptohome_library); | 183 CryptohomeLibrary::SetForTest(&mock_cryptohome_library); |
| 48 | 184 |
| 49 EXPECT_CALL(mock_cryptohome_library, DecryptWithSystemSalt(StrEq(""))) | 185 EXPECT_CALL(mock_cryptohome_library, DecryptWithSystemSalt(StrEq(""))) |
| 50 .Times(1) | 186 .Times(1) |
| 51 .WillOnce(Return("")); | 187 .WillOnce(Return("")); |
| 52 EXPECT_CALL(mock_cryptohome_library, | 188 EXPECT_CALL(mock_cryptohome_library, |
| 53 EncryptWithSystemSalt(StrEq("test-token"))) | 189 EncryptWithSystemSalt(StrEq("test-token"))) |
| 54 .Times(1) | 190 .Times(1) |
| 55 .WillOnce(Return("encrypted")); | 191 .WillOnce(Return("encrypted")); |
| 56 EXPECT_CALL(mock_cryptohome_library, | 192 EXPECT_CALL(mock_cryptohome_library, |
| 57 DecryptWithSystemSalt(StrEq("encrypted"))) | 193 DecryptWithSystemSalt(StrEq("encrypted"))) |
| 58 .Times(1) | 194 .Times(1) |
| 59 .WillOnce(Return("test-token")); | 195 .WillOnce(Return("test-token")); |
| 60 | 196 |
| 61 DeviceOAuth2TokenService oauth2_service( | 197 ASSERT_EQ("", oauth2_service_->GetRefreshToken()); |
| 62 new net::TestURLRequestContextGetter(message_loop_.message_loop_proxy()), | 198 oauth2_service_->SetAndSaveRefreshToken("test-token"); |
| 63 scoped_testing_local_state_.Get()); | 199 ASSERT_EQ("test-token", oauth2_service_->GetRefreshToken()); |
| 64 | |
| 65 ASSERT_EQ("", oauth2_service.GetRefreshToken()); | |
| 66 oauth2_service.SetAndSaveRefreshToken("test-token"); | |
| 67 ASSERT_EQ("test-token", oauth2_service.GetRefreshToken()); | |
| 68 | 200 |
| 69 // This call won't invoke decrypt again, since the value is cached. | 201 // This call won't invoke decrypt again, since the value is cached. |
| 70 ASSERT_EQ("test-token", oauth2_service.GetRefreshToken()); | 202 ASSERT_EQ("test-token", oauth2_service_->GetRefreshToken()); |
| 203 } | |
| 204 | |
| 205 TEST_F(DeviceOAuth2TokenServiceTest, RefreshTokenValidation_Success) { | |
| 206 oauth2_service_->SetRobotAccountIdPolicyValue("service_acct@g.com"); | |
| 207 | |
| 208 AssertRefreshTokenValidation( | |
| 209 | |
| 210 // TokenInfo-scoped access token fetch. | |
| 211 net::HTTP_OK, | |
| 212 GetValidTokenResponse("tokeninfo_access_token", 3600), | |
| 213 0, // tokens | |
| 214 0, // errors | |
| 215 | |
| 216 // TokenInfo API call. | |
| 217 net::HTTP_OK, | |
| 218 GetValidTokenInfoResponse("service_acct@g.com"), | |
| 219 0, // tokens | |
| 220 0, // errors | |
| 221 | |
| 222 // CloudPrint access token fetch. | |
| 223 net::HTTP_OK, | |
| 224 GetValidTokenResponse("scoped_access_token", 3600), | |
| 225 1, // tokens | |
| 226 0); // errors | |
| 227 | |
| 228 EXPECT_EQ("scoped_access_token", consumer_.last_token_); | |
| 229 } | |
| 230 | |
| 231 TEST_F(DeviceOAuth2TokenServiceTest, | |
| 232 RefreshTokenValidation_Failure_TokenInfoAccessTokenHttpError) { | |
| 233 oauth2_service_->SetRobotAccountIdPolicyValue("service_acct@g.com"); | |
| 234 | |
| 235 AssertRefreshTokenValidation( | |
| 236 | |
| 237 // TokenInfo-scoped access token fetch. | |
| 238 net::HTTP_UNAUTHORIZED, | |
| 239 "", | |
| 240 0, // tokens | |
| 241 1, // errors | |
| 242 | |
| 243 // TokenInfo API call skipped (error returned in previous step). | |
| 244 net::HTTP_OK, "", 0, 0, | |
| 245 | |
| 246 // CloudPrint access token fetch skipped (error returned in previous step). | |
| 247 net::HTTP_OK, "", 0, 0); | |
| 248 } | |
| 249 | |
| 250 TEST_F(DeviceOAuth2TokenServiceTest, | |
| 251 RefreshTokenValidation_Failure_TokenInfoAccessTokenInvalidResponse) { | |
| 252 oauth2_service_->SetRobotAccountIdPolicyValue("service_acct@g.com"); | |
| 253 | |
| 254 AssertRefreshTokenValidation( | |
| 255 | |
| 256 // TokenInfo-scoped access token fetch. | |
| 257 net::HTTP_OK, | |
| 258 "invalid response", | |
| 259 0, // tokens | |
| 260 1, // errors | |
| 261 | |
| 262 // TokenInfo API call skipped (error returned in previous step). | |
| 263 net::HTTP_OK, "", 0, 0, | |
| 264 | |
| 265 // CloudPrint access token fetch skipped (error returned in previous step). | |
| 266 net::HTTP_OK, "", 0, 0); | |
| 267 } | |
| 268 | |
| 269 TEST_F(DeviceOAuth2TokenServiceTest, | |
| 270 RefreshTokenValidation_Failure_TokenInfoApiCallHttpError) { | |
| 271 oauth2_service_->SetRobotAccountIdPolicyValue("service_acct@g.com"); | |
| 272 | |
| 273 AssertRefreshTokenValidation( | |
| 274 | |
| 275 // TokenInfo-scoped access token fetch. | |
| 276 net::HTTP_OK, | |
| 277 GetValidTokenResponse("tokeninfo_access_token", 3600), | |
| 278 0, // tokens | |
| 279 0, // errors | |
| 280 | |
| 281 // TokenInfo API call. | |
| 282 net::HTTP_INTERNAL_SERVER_ERROR, | |
| 283 "", | |
| 284 0, // tokens | |
| 285 1, // errors | |
| 286 | |
| 287 // CloudPrint access token fetch skipped (error returned in previous step). | |
| 288 net::HTTP_OK, "", 0, 0); | |
| 289 } | |
| 290 | |
| 291 TEST_F(DeviceOAuth2TokenServiceTest, | |
| 292 RefreshTokenValidation_Failure_TokenInfoApiCallInvalidResponse) { | |
| 293 oauth2_service_->SetRobotAccountIdPolicyValue("service_acct@g.com"); | |
| 294 | |
| 295 AssertRefreshTokenValidation( | |
| 296 | |
| 297 // TokenInfo-scoped access token fetch. | |
| 298 net::HTTP_OK, | |
| 299 GetValidTokenResponse("tokeninfo_access_token", 3600), | |
| 300 0, // tokens | |
| 301 0, // errors | |
| 302 | |
| 303 // TokenInfo API call. | |
| 304 net::HTTP_OK, | |
| 305 "invalid response", | |
| 306 0, // tokens | |
| 307 1, // errors | |
| 308 | |
| 309 // CloudPrint access token fetch skipped (error returned in previous step). | |
| 310 net::HTTP_OK, "", 0, 0); | |
| 311 } | |
| 312 | |
| 313 TEST_F(DeviceOAuth2TokenServiceTest, | |
| 314 RefreshTokenValidation_Failure_CloudPrintAccessTokenHttpError) { | |
| 315 oauth2_service_->SetRobotAccountIdPolicyValue("service_acct@g.com"); | |
| 316 | |
| 317 AssertRefreshTokenValidation( | |
| 318 | |
| 319 // TokenInfo-scoped access token fetch. | |
| 320 net::HTTP_OK, | |
| 321 GetValidTokenResponse("tokeninfo_access_token", 3600), | |
| 322 0, // tokens | |
| 323 0, // errors | |
| 324 | |
| 325 // TokenInfo API call. | |
| 326 net::HTTP_OK, | |
| 327 GetValidTokenInfoResponse("service_acct@g.com"), | |
| 328 0, // tokens | |
| 329 0, // errors | |
| 330 | |
| 331 // CloudPrint access token fetch. | |
| 332 net::HTTP_BAD_REQUEST, | |
| 333 "", | |
| 334 0, // tokens | |
| 335 1); // errors | |
| 336 } | |
| 337 | |
| 338 TEST_F(DeviceOAuth2TokenServiceTest, | |
| 339 RefreshTokenValidation_Failure_CloudPrintAccessTokenInvalidResponse) { | |
| 340 oauth2_service_->SetRobotAccountIdPolicyValue("service_acct@g.com"); | |
| 341 | |
| 342 AssertRefreshTokenValidation( | |
| 343 | |
| 344 // TokenInfo-scoped access token fetch. | |
| 345 net::HTTP_OK, | |
| 346 GetValidTokenResponse("tokeninfo_access_token", 3600), | |
| 347 0, // tokens | |
| 348 0, // errors | |
| 349 | |
| 350 // TokenInfo API call. | |
| 351 net::HTTP_OK, | |
| 352 GetValidTokenInfoResponse("service_acct@g.com"), | |
| 353 0, // tokens | |
| 354 0, // errors | |
| 355 | |
| 356 // CloudPrint access token fetch. | |
| 357 net::HTTP_OK, | |
| 358 "invalid request", | |
| 359 0, // tokens | |
| 360 1); // errors | |
| 361 } | |
| 362 | |
| 363 TEST_F(DeviceOAuth2TokenServiceTest, RefreshTokenValidation_Failure_BadOwner) { | |
| 364 oauth2_service_->SetRobotAccountIdPolicyValue("WRONG_service_acct@g.com"); | |
| 365 | |
| 366 AssertRefreshTokenValidation( | |
| 367 | |
| 368 // TokenInfo-scoped access token fetch. | |
| 369 net::HTTP_OK, | |
| 370 GetValidTokenResponse("tokeninfo_access_token", 3600), | |
| 371 0, // tokens | |
| 372 0, // errors | |
| 373 | |
| 374 // TokenInfo API call. | |
| 375 // The token owner doesn't match the policy value; an error is returned. | |
| 376 net::HTTP_OK, | |
| 377 GetValidTokenInfoResponse("service_acct@g.com"), | |
| 378 0, // tokens | |
| 379 1, // errors | |
| 380 | |
| 381 // CloudPrint access token fetch is skipped. | |
| 382 net::HTTP_OK, "", 0, 0); | |
| 71 } | 383 } |
| 72 | 384 |
| 73 } // namespace chromeos | 385 } // namespace chromeos |
| OLD | NEW |