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 |