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