device_oauth2_token_service_unittest.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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 "base/threading/sequenced_worker_pool.h"
11#include "chrome/browser/chromeos/policy/device_policy_builder.h"
12#include "chrome/browser/chromeos/settings/cros_settings.h"
13#include "chrome/browser/chromeos/settings/device_settings_service.h"
14#include "chrome/browser/chromeos/settings/device_settings_test_helper.h"
15#include "chrome/browser/chromeos/settings/token_encryptor.h"
16#include "chrome/common/pref_names.h"
17#include "chrome/test/base/scoped_testing_local_state.h"
18#include "chrome/test/base/testing_browser_process.h"
19#include "chromeos/cryptohome/system_salt_getter.h"
20#include "chromeos/dbus/dbus_thread_manager.h"
21#include "chromeos/dbus/fake_cryptohome_client.h"
22#include "components/ownership/mock_owner_key_util.h"
23#include "content/public/browser/browser_thread.h"
24#include "content/public/test/test_browser_thread.h"
25#include "google_apis/gaia/gaia_oauth_client.h"
26#include "google_apis/gaia/oauth2_token_service_test_util.h"
27#include "net/http/http_status_code.h"
28#include "net/url_request/test_url_fetcher_factory.h"
29#include "net/url_request/url_fetcher_delegate.h"
30#include "net/url_request/url_request_test_util.h"
31#include "testing/gtest/include/gtest/gtest.h"
32
33namespace chromeos {
34
35static const int kOAuthTokenServiceUrlFetcherId = 0;
36static const int kValidatorUrlFetcherId = gaia::GaiaOAuthClient::kUrlFetcherId;
37
38class DeviceOAuth2TokenServiceTest : public testing::Test {
39 public:
40  DeviceOAuth2TokenServiceTest()
41      : scoped_testing_local_state_(TestingBrowserProcess::GetGlobal()),
42        request_context_getter_(new net::TestURLRequestContextGetter(
43            message_loop_.message_loop_proxy())) {}
44  virtual ~DeviceOAuth2TokenServiceTest() {}
45
46  // Most tests just want a noop crypto impl with a dummy refresh token value in
47  // Local State (if the value is an empty string, it will be ignored).
48  void SetUpDefaultValues() {
49    SetDeviceRefreshTokenInLocalState("device_refresh_token_4_test");
50    SetRobotAccountId("service_acct@g.com");
51    CreateService();
52    AssertConsumerTokensAndErrors(0, 0);
53
54    base::RunLoop().RunUntilIdle();
55  }
56
57  void SetUpWithPendingSalt() {
58    fake_cryptohome_client_->set_system_salt(std::vector<uint8>());
59    fake_cryptohome_client_->SetServiceIsAvailable(false);
60    SetUpDefaultValues();
61  }
62
63  void SetRobotAccountId(const std::string& account_id) {
64    device_policy_.policy_data().set_service_account_identity(account_id);
65    device_policy_.Build();
66    device_settings_test_helper_.set_policy_blob(device_policy_.GetBlob());
67    DeviceSettingsService::Get()->Load();
68    device_settings_test_helper_.Flush();
69  }
70
71  scoped_ptr<OAuth2TokenService::Request> StartTokenRequest() {
72    return oauth2_service_->StartRequest(oauth2_service_->GetRobotAccountId(),
73                                         std::set<std::string>(),
74                                         &consumer_);
75  }
76
77  virtual void SetUp() OVERRIDE {
78    fake_cryptohome_client_ = new FakeCryptohomeClient;
79    fake_cryptohome_client_->SetServiceIsAvailable(true);
80    fake_cryptohome_client_->set_system_salt(
81        FakeCryptohomeClient::GetStubSystemSalt());
82    chromeos::DBusThreadManager::GetSetterForTesting()->SetCryptohomeClient(
83        scoped_ptr<CryptohomeClient>(fake_cryptohome_client_));
84
85    SystemSaltGetter::Initialize();
86
87    DeviceSettingsService::Initialize();
88    scoped_refptr<ownership::MockOwnerKeyUtil> owner_key_util_(
89        new ownership::MockOwnerKeyUtil());
90    owner_key_util_->SetPublicKeyFromPrivateKey(
91        *device_policy_.GetSigningKey());
92    DeviceSettingsService::Get()->SetSessionManager(
93        &device_settings_test_helper_, owner_key_util_);
94
95    CrosSettings::Initialize();
96  }
97
98  virtual void TearDown() OVERRIDE {
99    CrosSettings::Shutdown();
100    TestingBrowserProcess::GetGlobal()->SetBrowserPolicyConnector(NULL);
101    content::BrowserThread::GetBlockingPool()->FlushForTesting();
102    DeviceSettingsService::Get()->UnsetSessionManager();
103    DeviceSettingsService::Shutdown();
104    SystemSaltGetter::Shutdown();
105    DBusThreadManager::Shutdown();
106    base::RunLoop().RunUntilIdle();
107  }
108
109  void CreateService() {
110    oauth2_service_.reset(new DeviceOAuth2TokenService(
111        request_context_getter_.get(), scoped_testing_local_state_.Get()));
112    oauth2_service_->max_refresh_token_validation_retries_ = 0;
113    oauth2_service_->set_max_authorization_token_fetch_retries_for_testing(0);
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()->SetUserPref(
120        prefs::kDeviceRobotAnyApiRefreshToken,
121        new base::StringValue(refresh_token));
122  }
123
124  std::string GetValidTokenInfoResponse(const std::string email) {
125    return "{ \"email\": \"" + email + "\","
126           "  \"user_id\": \"1234567890\" }";
127  }
128
129  bool RefreshTokenIsAvailable() {
130    return oauth2_service_->RefreshTokenIsAvailable(
131        oauth2_service_->GetRobotAccountId());
132  }
133
134  std::string GetRefreshToken() {
135    if (!RefreshTokenIsAvailable())
136      return std::string();
137
138    return oauth2_service_->GetRefreshToken(
139        oauth2_service_->GetRobotAccountId());
140  }
141
142  // A utility method to return fake URL results, for testing the refresh token
143  // validation logic.  For a successful validation attempt, this method will be
144  // called three times for the steps listed below (steps 1 and 2 happen in
145  // parallel).
146  //
147  // Step 1a: fetch the access token for the tokeninfo API.
148  // Step 1b: call the tokeninfo API.
149  // Step 2:  Fetch the access token for the requested scope
150  //          (in this case, cloudprint).
151  void ReturnOAuthUrlFetchResults(int fetcher_id,
152                                  net::HttpStatusCode response_code,
153                                  const std::string&  response_string);
154
155  // Generates URL fetch replies with the specified results for requests
156  // generated by the token service.
157  void PerformURLFetchesWithResults(
158      net::HttpStatusCode tokeninfo_access_token_status,
159      const std::string& tokeninfo_access_token_response,
160      net::HttpStatusCode tokeninfo_fetch_status,
161      const std::string& tokeninfo_fetch_response,
162      net::HttpStatusCode service_access_token_status,
163      const std::string& service_access_token_response);
164
165  // Generates URL fetch replies for the success path.
166  void PerformURLFetches();
167
168  void AssertConsumerTokensAndErrors(int num_tokens, int num_errors);
169
170 protected:
171  // This is here because DeviceOAuth2TokenService's destructor is private;
172  // base::DefaultDeleter therefore doesn't work. However, the test class is
173  // declared friend in DeviceOAuth2TokenService, so this deleter works.
174  struct TokenServiceDeleter {
175    inline void operator()(DeviceOAuth2TokenService* ptr) const {
176      delete ptr;
177    }
178  };
179
180  base::MessageLoop message_loop_;
181  ScopedTestingLocalState scoped_testing_local_state_;
182  scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_;
183  net::TestURLFetcherFactory factory_;
184  FakeCryptohomeClient* fake_cryptohome_client_;
185  DeviceSettingsTestHelper device_settings_test_helper_;
186  policy::DevicePolicyBuilder device_policy_;
187  scoped_ptr<DeviceOAuth2TokenService, TokenServiceDeleter> oauth2_service_;
188  TestingOAuth2TokenServiceConsumer consumer_;
189};
190
191void DeviceOAuth2TokenServiceTest::ReturnOAuthUrlFetchResults(
192    int fetcher_id,
193    net::HttpStatusCode response_code,
194    const std::string& response_string) {
195  net::TestURLFetcher* fetcher = factory_.GetFetcherByID(fetcher_id);
196  if (fetcher) {
197    factory_.RemoveFetcherFromMap(fetcher_id);
198    fetcher->set_response_code(response_code);
199    fetcher->SetResponseString(response_string);
200    fetcher->delegate()->OnURLFetchComplete(fetcher);
201    base::RunLoop().RunUntilIdle();
202  }
203}
204
205void DeviceOAuth2TokenServiceTest::PerformURLFetchesWithResults(
206    net::HttpStatusCode tokeninfo_access_token_status,
207    const std::string& tokeninfo_access_token_response,
208    net::HttpStatusCode tokeninfo_fetch_status,
209    const std::string& tokeninfo_fetch_response,
210    net::HttpStatusCode service_access_token_status,
211    const std::string& service_access_token_response) {
212  ReturnOAuthUrlFetchResults(
213      kValidatorUrlFetcherId,
214      tokeninfo_access_token_status,
215      tokeninfo_access_token_response);
216
217  ReturnOAuthUrlFetchResults(
218      kValidatorUrlFetcherId,
219      tokeninfo_fetch_status,
220      tokeninfo_fetch_response);
221
222  ReturnOAuthUrlFetchResults(
223      kOAuthTokenServiceUrlFetcherId,
224      service_access_token_status,
225      service_access_token_response);
226}
227
228void DeviceOAuth2TokenServiceTest::PerformURLFetches() {
229  PerformURLFetchesWithResults(
230      net::HTTP_OK, GetValidTokenResponse("tokeninfo_access_token", 3600),
231      net::HTTP_OK, GetValidTokenInfoResponse("service_acct@g.com"),
232      net::HTTP_OK, GetValidTokenResponse("scoped_access_token", 3600));
233}
234
235void DeviceOAuth2TokenServiceTest::AssertConsumerTokensAndErrors(
236    int num_tokens,
237    int num_errors) {
238  EXPECT_EQ(num_tokens, consumer_.number_of_successful_tokens_);
239  EXPECT_EQ(num_errors, consumer_.number_of_errors_);
240}
241
242TEST_F(DeviceOAuth2TokenServiceTest, SaveEncryptedToken) {
243  CreateService();
244
245  oauth2_service_->SetAndSaveRefreshToken(
246      "test-token", DeviceOAuth2TokenService::StatusCallback());
247  EXPECT_EQ("test-token", GetRefreshToken());
248}
249
250TEST_F(DeviceOAuth2TokenServiceTest, SaveEncryptedTokenEarly) {
251  // Set a new refresh token without the system salt available.
252  SetUpWithPendingSalt();
253
254  oauth2_service_->SetAndSaveRefreshToken(
255      "test-token", DeviceOAuth2TokenService::StatusCallback());
256  EXPECT_EQ("test-token", GetRefreshToken());
257
258  // Make the system salt available.
259  fake_cryptohome_client_->set_system_salt(
260      FakeCryptohomeClient::GetStubSystemSalt());
261  fake_cryptohome_client_->SetServiceIsAvailable(true);
262  base::RunLoop().RunUntilIdle();
263
264  // The original token should still be present.
265  EXPECT_EQ("test-token", GetRefreshToken());
266
267  // Reloading shouldn't change the token either.
268  CreateService();
269  base::RunLoop().RunUntilIdle();
270  EXPECT_EQ("test-token", GetRefreshToken());
271}
272
273TEST_F(DeviceOAuth2TokenServiceTest, RefreshTokenValidation_Success) {
274  SetUpDefaultValues();
275  scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
276
277  PerformURLFetches();
278  AssertConsumerTokensAndErrors(1, 0);
279
280  EXPECT_EQ("scoped_access_token", consumer_.last_token_);
281}
282
283TEST_F(DeviceOAuth2TokenServiceTest, RefreshTokenValidation_SuccessAsyncLoad) {
284  SetUpWithPendingSalt();
285
286  scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
287  PerformURLFetches();
288  AssertConsumerTokensAndErrors(0, 0);
289
290  fake_cryptohome_client_->set_system_salt(
291      FakeCryptohomeClient::GetStubSystemSalt());
292  fake_cryptohome_client_->SetServiceIsAvailable(true);
293  base::RunLoop().RunUntilIdle();
294
295  PerformURLFetches();
296  AssertConsumerTokensAndErrors(1, 0);
297
298  EXPECT_EQ("scoped_access_token", consumer_.last_token_);
299}
300
301TEST_F(DeviceOAuth2TokenServiceTest, RefreshTokenValidation_Cancel) {
302  SetUpDefaultValues();
303  scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
304  request.reset();
305
306  PerformURLFetches();
307
308  // Test succeeds if this line is reached without a crash.
309}
310
311TEST_F(DeviceOAuth2TokenServiceTest, RefreshTokenValidation_NoSalt) {
312  fake_cryptohome_client_->set_system_salt(std::vector<uint8>());
313  fake_cryptohome_client_->SetServiceIsAvailable(true);
314  SetUpDefaultValues();
315
316  EXPECT_FALSE(RefreshTokenIsAvailable());
317
318  scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
319  base::RunLoop().RunUntilIdle();
320
321  AssertConsumerTokensAndErrors(0, 1);
322}
323
324TEST_F(DeviceOAuth2TokenServiceTest,
325       RefreshTokenValidation_Failure_TokenInfoAccessTokenHttpError) {
326  SetUpDefaultValues();
327  scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
328
329  PerformURLFetchesWithResults(
330      net::HTTP_UNAUTHORIZED, "",
331      net::HTTP_OK, GetValidTokenInfoResponse("service_acct@g.com"),
332      net::HTTP_OK, GetValidTokenResponse("ignored", 3600));
333
334  AssertConsumerTokensAndErrors(0, 1);
335}
336
337TEST_F(DeviceOAuth2TokenServiceTest,
338       RefreshTokenValidation_Failure_TokenInfoAccessTokenInvalidResponse) {
339  SetUpDefaultValues();
340  scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
341
342  PerformURLFetchesWithResults(
343      net::HTTP_OK, "invalid response",
344      net::HTTP_OK, GetValidTokenInfoResponse("service_acct@g.com"),
345      net::HTTP_OK, GetValidTokenResponse("ignored", 3600));
346
347  AssertConsumerTokensAndErrors(0, 1);
348}
349
350TEST_F(DeviceOAuth2TokenServiceTest,
351       RefreshTokenValidation_Failure_TokenInfoApiCallHttpError) {
352  SetUpDefaultValues();
353  scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
354
355  PerformURLFetchesWithResults(
356      net::HTTP_OK, GetValidTokenResponse("tokeninfo_access_token", 3600),
357      net::HTTP_INTERNAL_SERVER_ERROR, "",
358      net::HTTP_OK, GetValidTokenResponse("ignored", 3600));
359
360  AssertConsumerTokensAndErrors(0, 1);
361}
362
363TEST_F(DeviceOAuth2TokenServiceTest,
364       RefreshTokenValidation_Failure_TokenInfoApiCallInvalidResponse) {
365  SetUpDefaultValues();
366  scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
367
368  PerformURLFetchesWithResults(
369      net::HTTP_OK, GetValidTokenResponse("tokeninfo_access_token", 3600),
370      net::HTTP_OK, "invalid response",
371      net::HTTP_OK, GetValidTokenResponse("ignored", 3600));
372
373  AssertConsumerTokensAndErrors(0, 1);
374}
375
376TEST_F(DeviceOAuth2TokenServiceTest,
377       RefreshTokenValidation_Failure_CloudPrintAccessTokenHttpError) {
378  SetUpDefaultValues();
379  scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
380
381  PerformURLFetchesWithResults(
382      net::HTTP_OK, GetValidTokenResponse("tokeninfo_access_token", 3600),
383      net::HTTP_OK, GetValidTokenInfoResponse("service_acct@g.com"),
384      net::HTTP_BAD_REQUEST, "");
385
386  AssertConsumerTokensAndErrors(0, 1);
387}
388
389TEST_F(DeviceOAuth2TokenServiceTest,
390       RefreshTokenValidation_Failure_CloudPrintAccessTokenInvalidResponse) {
391  SetUpDefaultValues();
392  scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
393
394  PerformURLFetchesWithResults(
395      net::HTTP_OK, GetValidTokenResponse("tokeninfo_access_token", 3600),
396      net::HTTP_OK, GetValidTokenInfoResponse("service_acct@g.com"),
397      net::HTTP_OK, "invalid request");
398
399  AssertConsumerTokensAndErrors(0, 1);
400}
401
402TEST_F(DeviceOAuth2TokenServiceTest, RefreshTokenValidation_Failure_BadOwner) {
403  SetUpDefaultValues();
404  scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
405
406  SetRobotAccountId("WRONG_service_acct@g.com");
407
408  PerformURLFetchesWithResults(
409      net::HTTP_OK, GetValidTokenResponse("tokeninfo_access_token", 3600),
410      net::HTTP_OK, GetValidTokenInfoResponse("service_acct@g.com"),
411      net::HTTP_OK, GetValidTokenResponse("ignored", 3600));
412
413  AssertConsumerTokensAndErrors(0, 1);
414}
415
416TEST_F(DeviceOAuth2TokenServiceTest, RefreshTokenValidation_Retry) {
417  SetUpDefaultValues();
418  scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
419
420  PerformURLFetchesWithResults(
421      net::HTTP_INTERNAL_SERVER_ERROR, "",
422      net::HTTP_OK, GetValidTokenInfoResponse("service_acct@g.com"),
423      net::HTTP_OK, GetValidTokenResponse("ignored", 3600));
424
425  AssertConsumerTokensAndErrors(0, 1);
426
427  // Retry should succeed.
428  request = StartTokenRequest();
429  PerformURLFetches();
430  AssertConsumerTokensAndErrors(1, 1);
431}
432
433}  // namespace chromeos
434