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