device_oauth2_token_service_unittest.cc revision 9ab5563a3196760eb381d102cbb2bc0f7abc6a50
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
25using ::testing::_;
26using ::testing::AnyNumber;
27using ::testing::Return;
28using ::testing::StrEq;
29using ::testing::StrictMock;
30
31namespace chromeos {
32
33static const int kOAuthTokenServiceUrlFetcherId = 0;
34static const int kValidatorUrlFetcherId = gaia::GaiaOAuthClient::kUrlFetcherId;
35
36class 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
57class 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@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
130void 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
142void 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
149TEST_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
173TEST_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@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
197TEST_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
219TEST_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
241TEST_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
266TEST_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
291TEST_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@g.com"));
305
306  ReturnOAuthUrlFetchResults(
307      kOAuthTokenServiceUrlFetcherId,
308      net::HTTP_BAD_REQUEST,
309      "");
310
311  AssertConsumerTokensAndErrors(0, 1);
312}
313
314TEST_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@g.com"));
328
329  ReturnOAuthUrlFetchResults(
330      kOAuthTokenServiceUrlFetcherId,
331      net::HTTP_OK,
332      "invalid request");
333
334  AssertConsumerTokensAndErrors(0, 1);
335}
336
337TEST_F(DeviceOAuth2TokenServiceTest, RefreshTokenValidation_Failure_BadOwner) {
338  SetUpDefaultValues();
339  scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
340
341  oauth2_service_.SetRobotAccountIdPolicyValue("WRONG_service_acct@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@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