1 // Copyright 2014 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.
5 #include "components/proximity_auth/cryptauth/cryptauth_client.h"
7 #include "base/command_line.h"
8 #include "base/test/null_task_runner.h"
9 #include "components/proximity_auth/cryptauth/cryptauth_access_token_fetcher.h"
10 #include "components/proximity_auth/cryptauth/cryptauth_api_call_flow.h"
11 #include "components/proximity_auth/cryptauth/proto/cryptauth_api.pb.h"
12 #include "components/proximity_auth/switches.h"
13 #include "net/url_request/test_url_fetcher_factory.h"
14 #include "net/url_request/url_request_test_util.h"
15 #include "testing/gmock/include/gmock/gmock.h"
16 #include "testing/gtest/include/gtest/gtest.h"
20 using testing::Return
;
21 using testing::SaveArg
;
22 using testing::StrictMock
;
24 namespace proximity_auth
{
28 const char kTestGoogleApisUrl
[] = "https://www.testgoogleapis.com";
29 const char kAccessToken
[] = "access_token";
30 const char kPublicKey1
[] = "public_key1";
31 const char kPublicKey2
[] = "public_key2";
32 const char kBluetoothAddress1
[] = "AA:AA:AA:AA:AA:AA";
33 const char kBluetoothAddress2
[] = "BB:BB:BB:BB:BB:BB";
35 // CryptAuthAccessTokenFetcher implementation simply returning a predetermined
37 class FakeCryptAuthAccessTokenFetcher
: public CryptAuthAccessTokenFetcher
{
39 FakeCryptAuthAccessTokenFetcher() : access_token_(kAccessToken
) {}
41 void FetchAccessToken(const AccessTokenCallback
& callback
) override
{
42 callback
.Run(access_token_
);
45 void set_access_token(const std::string
& access_token
) {
46 access_token_
= access_token
;
50 std::string access_token_
;
53 // Mock CryptAuthApiCallFlow, which handles the HTTP requests to CryptAuth.
54 class MockCryptAuthApiCallFlow
: public CryptAuthApiCallFlow
{
56 MockCryptAuthApiCallFlow() : CryptAuthApiCallFlow(GURL(std::string())) {}
57 virtual ~MockCryptAuthApiCallFlow() {}
60 void(net::URLRequestContextGetter
* context
,
61 const std::string
& access_token
,
62 const std::string
& serialized_request
,
63 const ResultCallback
& result_callback
,
64 const ErrorCallback
& error_callback
));
67 DISALLOW_COPY_AND_ASSIGN(MockCryptAuthApiCallFlow
);
70 // Subclass of CryptAuthClient to use as test harness.
71 class MockCryptAuthClient
: public CryptAuthClient
{
73 // Ownership of |access_token_fetcher| is passed to the superclass. Due to the
74 // limitations of gmock, we need to use a raw pointer argument rather than a
77 CryptAuthAccessTokenFetcher
* access_token_fetcher
,
78 scoped_refptr
<net::URLRequestContextGetter
> url_request_context
)
79 : CryptAuthClient(make_scoped_ptr(access_token_fetcher
),
80 url_request_context
) {}
81 virtual ~MockCryptAuthClient() {}
83 MOCK_METHOD1(CreateFlowProxy
, CryptAuthApiCallFlow
*(const GURL
& request_url
));
85 scoped_ptr
<CryptAuthApiCallFlow
> CreateFlow(
86 const GURL
& request_url
) override
{
87 return make_scoped_ptr(CreateFlowProxy(request_url
));
91 DISALLOW_COPY_AND_ASSIGN(MockCryptAuthClient
);
94 // Callback that should never be invoked.
96 void NotCalled(const T
& type
) {
100 // Callback that saves the result returned by CryptAuthClient.
102 void SaveResult(T
* out
, const T
& result
) {
108 class ProximityAuthCryptAuthClientTest
: public testing::Test
{
110 ProximityAuthCryptAuthClientTest()
111 : access_token_fetcher_(new FakeCryptAuthAccessTokenFetcher()),
112 url_request_context_(
113 new net::TestURLRequestContextGetter(new base::NullTaskRunner())),
114 serialized_request_(std::string()) {}
116 void SetUp() override
{
117 base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
118 switches::kCryptAuthHTTPHost
, kTestGoogleApisUrl
);
120 client_
.reset(new StrictMock
<MockCryptAuthClient
>(access_token_fetcher_
,
121 url_request_context_
));
124 // Sets up an expectation and captures a CryptAuth API request to
126 void ExpectRequest(const std::string
& request_url
) {
127 StrictMock
<MockCryptAuthApiCallFlow
>* api_call_flow
=
128 new StrictMock
<MockCryptAuthApiCallFlow
>();
130 EXPECT_CALL(*client_
, CreateFlowProxy(GURL(request_url
)))
131 .WillOnce(Return(api_call_flow
));
133 EXPECT_CALL(*api_call_flow
,
134 Start(url_request_context_
.get(), kAccessToken
, _
, _
, _
))
135 .WillOnce(DoAll(SaveArg
<2>(&serialized_request_
),
136 SaveArg
<3>(&flow_result_callback_
),
137 SaveArg
<4>(&flow_error_callback_
)));
140 // Returns |response_proto| as the result to the current API request.
141 // ExpectResult() must have been called first.
142 void FinishApiCallFlow(const google::protobuf::MessageLite
* response_proto
) {
143 flow_result_callback_
.Run(response_proto
->SerializeAsString());
146 // Ends the current API request with |error_message|. ExpectResult() must have
147 // been called first.
148 void FailApiCallFlow(const std::string
& error_message
) {
149 flow_error_callback_
.Run(error_message
);
153 // Owned by |client_|.
154 FakeCryptAuthAccessTokenFetcher
* access_token_fetcher_
;
156 scoped_refptr
<net::URLRequestContextGetter
> url_request_context_
;
157 scoped_ptr
<StrictMock
<MockCryptAuthClient
>> client_
;
159 std::string serialized_request_
;
160 CryptAuthApiCallFlow::ResultCallback flow_result_callback_
;
161 CryptAuthApiCallFlow::ErrorCallback flow_error_callback_
;
164 TEST_F(ProximityAuthCryptAuthClientTest
, GetMyDevicesSuccess
) {
166 "https://www.testgoogleapis.com/cryptauth/v1/deviceSync/"
167 "getmydevices?alt=proto");
169 cryptauth::GetMyDevicesResponse result_proto
;
170 cryptauth::GetMyDevicesRequest request_proto
;
171 request_proto
.set_allow_stale_read(true);
172 client_
->GetMyDevices(
174 base::Bind(&SaveResult
<cryptauth::GetMyDevicesResponse
>, &result_proto
),
175 base::Bind(&NotCalled
<std::string
>));
177 cryptauth::GetMyDevicesRequest expected_request
;
178 EXPECT_TRUE(expected_request
.ParseFromString(serialized_request_
));
179 EXPECT_TRUE(expected_request
.allow_stale_read());
181 // Return two devices, one unlock key and one unlockable device.
183 cryptauth::GetMyDevicesResponse response_proto
;
184 response_proto
.add_devices();
185 response_proto
.mutable_devices(0)->set_public_key(kPublicKey1
);
186 response_proto
.mutable_devices(0)->set_unlock_key(true);
187 response_proto
.mutable_devices(0)
188 ->set_bluetooth_address(kBluetoothAddress1
);
189 response_proto
.add_devices();
190 response_proto
.mutable_devices(1)->set_public_key(kPublicKey2
);
191 response_proto
.mutable_devices(1)->set_unlockable(true);
192 FinishApiCallFlow(&response_proto
);
195 // Check that the result received in callback is the same as the response.
196 ASSERT_EQ(2, result_proto
.devices_size());
197 EXPECT_EQ(kPublicKey1
, result_proto
.devices(0).public_key());
198 EXPECT_TRUE(result_proto
.devices(0).unlock_key());
199 EXPECT_EQ(kBluetoothAddress1
, result_proto
.devices(0).bluetooth_address());
200 EXPECT_EQ(kPublicKey2
, result_proto
.devices(1).public_key());
201 EXPECT_TRUE(result_proto
.devices(1).unlockable());
204 TEST_F(ProximityAuthCryptAuthClientTest
, GetMyDevicesFailure
) {
206 "https://www.testgoogleapis.com/cryptauth/v1/deviceSync/"
207 "getmydevices?alt=proto");
209 std::string error_message
;
210 client_
->GetMyDevices(cryptauth::GetMyDevicesRequest(),
211 base::Bind(&NotCalled
<cryptauth::GetMyDevicesResponse
>),
212 base::Bind(&SaveResult
<std::string
>, &error_message
));
214 std::string
kStatus500Error("HTTP status: 500");
215 FailApiCallFlow(kStatus500Error
);
216 EXPECT_EQ(kStatus500Error
, error_message
);
219 TEST_F(ProximityAuthCryptAuthClientTest
, FindEligibleUnlockDevicesSuccess
) {
221 "https://www.testgoogleapis.com/cryptauth/v1/deviceSync/"
222 "findeligibleunlockdevices?alt=proto");
224 cryptauth::FindEligibleUnlockDevicesResponse result_proto
;
225 cryptauth::FindEligibleUnlockDevicesRequest request_proto
;
226 request_proto
.set_callback_bluetooth_address(kBluetoothAddress2
);
227 client_
->FindEligibleUnlockDevices(
229 base::Bind(&SaveResult
<cryptauth::FindEligibleUnlockDevicesResponse
>,
231 base::Bind(&NotCalled
<std::string
>));
233 cryptauth::FindEligibleUnlockDevicesRequest expected_request
;
234 EXPECT_TRUE(expected_request
.ParseFromString(serialized_request_
));
235 EXPECT_EQ(kBluetoothAddress2
, expected_request
.callback_bluetooth_address());
237 // Return a response proto with one eligible and one ineligible device.
238 cryptauth::FindEligibleUnlockDevicesResponse response_proto
;
239 response_proto
.add_eligible_devices();
240 response_proto
.mutable_eligible_devices(0)->set_public_key(kPublicKey1
);
242 const std::string kIneligibilityReason
= "You require more vespine gas.";
243 response_proto
.add_ineligible_devices();
244 response_proto
.mutable_ineligible_devices(0)
246 ->set_public_key(kPublicKey2
);
247 response_proto
.mutable_ineligible_devices(0)
248 ->add_reasons(kIneligibilityReason
);
249 FinishApiCallFlow(&response_proto
);
251 // Check that the result received in callback is the same as the response.
252 ASSERT_EQ(1, result_proto
.eligible_devices_size());
253 EXPECT_EQ(kPublicKey1
, result_proto
.eligible_devices(0).public_key());
254 ASSERT_EQ(1, result_proto
.ineligible_devices_size());
255 EXPECT_EQ(kPublicKey2
,
256 result_proto
.ineligible_devices(0).device().public_key());
257 ASSERT_EQ(1, result_proto
.ineligible_devices(0).reasons_size());
258 EXPECT_EQ(kIneligibilityReason
,
259 result_proto
.ineligible_devices(0).reasons(0));
262 TEST_F(ProximityAuthCryptAuthClientTest
, FindEligibleUnlockDevicesFailure
) {
264 "https://www.testgoogleapis.com/cryptauth/v1/deviceSync/"
265 "findeligibleunlockdevices?alt=proto");
267 std::string error_message
;
268 cryptauth::FindEligibleUnlockDevicesRequest request_proto
;
269 request_proto
.set_callback_bluetooth_address(kBluetoothAddress1
);
270 client_
->FindEligibleUnlockDevices(
272 base::Bind(&NotCalled
<cryptauth::FindEligibleUnlockDevicesResponse
>),
273 base::Bind(&SaveResult
<std::string
>, &error_message
));
275 std::string
kStatus403Error("HTTP status: 403");
276 FailApiCallFlow(kStatus403Error
);
277 EXPECT_EQ(kStatus403Error
, error_message
);
280 TEST_F(ProximityAuthCryptAuthClientTest
, SendDeviceSyncTickleSuccess
) {
282 "https://www.testgoogleapis.com/cryptauth/v1/deviceSync/"
283 "senddevicesynctickle?alt=proto");
285 cryptauth::SendDeviceSyncTickleResponse result_proto
;
286 client_
->SendDeviceSyncTickle(
287 cryptauth::SendDeviceSyncTickleRequest(),
288 base::Bind(&SaveResult
<cryptauth::SendDeviceSyncTickleResponse
>,
290 base::Bind(&NotCalled
<std::string
>));
292 cryptauth::SendDeviceSyncTickleRequest expected_request
;
293 EXPECT_TRUE(expected_request
.ParseFromString(serialized_request_
));
295 cryptauth::SendDeviceSyncTickleResponse response_proto
;
296 FinishApiCallFlow(&response_proto
);
299 TEST_F(ProximityAuthCryptAuthClientTest
, ToggleEasyUnlockSuccess
) {
301 "https://www.testgoogleapis.com/cryptauth/v1/deviceSync/"
302 "toggleeasyunlock?alt=proto");
304 cryptauth::ToggleEasyUnlockResponse result_proto
;
305 cryptauth::ToggleEasyUnlockRequest request_proto
;
306 request_proto
.set_enable(true);
307 request_proto
.set_apply_to_all(false);
308 request_proto
.set_public_key(kPublicKey1
);
309 client_
->ToggleEasyUnlock(
311 base::Bind(&SaveResult
<cryptauth::ToggleEasyUnlockResponse
>,
313 base::Bind(&NotCalled
<std::string
>));
315 cryptauth::ToggleEasyUnlockRequest expected_request
;
316 EXPECT_TRUE(expected_request
.ParseFromString(serialized_request_
));
317 EXPECT_TRUE(expected_request
.enable());
318 EXPECT_EQ(kPublicKey1
, expected_request
.public_key());
319 EXPECT_FALSE(expected_request
.apply_to_all());
321 cryptauth::ToggleEasyUnlockResponse response_proto
;
322 FinishApiCallFlow(&response_proto
);
325 TEST_F(ProximityAuthCryptAuthClientTest
, SetupEnrollmentSuccess
) {
327 "https://www.testgoogleapis.com/cryptauth/v1/enrollment/"
328 "setupenrollment?alt=proto");
330 std::string kApplicationId
= "mkaes";
331 std::vector
<std::string
> supported_protocols
;
332 supported_protocols
.push_back("gcmV1");
333 supported_protocols
.push_back("testProtocol");
335 cryptauth::SetupEnrollmentResponse result_proto
;
336 cryptauth::SetupEnrollmentRequest request_proto
;
337 request_proto
.set_application_id(kApplicationId
);
338 request_proto
.add_types("gcmV1");
339 request_proto
.add_types("testProtocol");
340 client_
->SetupEnrollment(
341 request_proto
, base::Bind(&SaveResult
<cryptauth::SetupEnrollmentResponse
>,
343 base::Bind(&NotCalled
<std::string
>));
345 cryptauth::SetupEnrollmentRequest expected_request
;
346 EXPECT_TRUE(expected_request
.ParseFromString(serialized_request_
));
347 EXPECT_EQ(kApplicationId
, expected_request
.application_id());
348 ASSERT_EQ(2, expected_request
.types_size());
349 EXPECT_EQ("gcmV1", expected_request
.types(0));
350 EXPECT_EQ("testProtocol", expected_request
.types(1));
352 // Return a fake enrollment session.
354 cryptauth::SetupEnrollmentResponse response_proto
;
355 response_proto
.set_status("OK");
356 response_proto
.add_infos();
357 response_proto
.mutable_infos(0)->set_type("gcmV1");
358 response_proto
.mutable_infos(0)->set_enrollment_session_id("session_id");
359 response_proto
.mutable_infos(0)->set_server_ephemeral_key("ephemeral_key");
360 FinishApiCallFlow(&response_proto
);
363 // Check that the returned proto is the same as the one just created.
364 EXPECT_EQ("OK", result_proto
.status());
365 ASSERT_EQ(1, result_proto
.infos_size());
366 EXPECT_EQ("gcmV1", result_proto
.infos(0).type());
367 EXPECT_EQ("session_id", result_proto
.infos(0).enrollment_session_id());
368 EXPECT_EQ("ephemeral_key", result_proto
.infos(0).server_ephemeral_key());
371 TEST_F(ProximityAuthCryptAuthClientTest
, FinishEnrollmentSuccess
) {
373 "https://www.testgoogleapis.com/cryptauth/v1/enrollment/"
374 "finishenrollment?alt=proto");
376 const char kEnrollmentSessionId
[] = "enrollment_session_id";
377 const char kEnrollmentMessage
[] = "enrollment_message";
378 const char kDeviceEphemeralKey
[] = "device_ephermal_key";
379 cryptauth::FinishEnrollmentResponse result_proto
;
380 cryptauth::FinishEnrollmentRequest request_proto
;
381 request_proto
.set_enrollment_session_id(kEnrollmentSessionId
);
382 request_proto
.set_enrollment_message(kEnrollmentMessage
);
383 request_proto
.set_device_ephemeral_key(kDeviceEphemeralKey
);
384 client_
->FinishEnrollment(
386 base::Bind(&SaveResult
<cryptauth::FinishEnrollmentResponse
>,
388 base::Bind(&NotCalled
<const std::string
&>));
390 cryptauth::FinishEnrollmentRequest expected_request
;
391 EXPECT_TRUE(expected_request
.ParseFromString(serialized_request_
));
392 EXPECT_EQ(kEnrollmentSessionId
, expected_request
.enrollment_session_id());
393 EXPECT_EQ(kEnrollmentMessage
, expected_request
.enrollment_message());
394 EXPECT_EQ(kDeviceEphemeralKey
, expected_request
.device_ephemeral_key());
397 cryptauth::FinishEnrollmentResponse response_proto
;
398 response_proto
.set_status("OK");
399 FinishApiCallFlow(&response_proto
);
401 EXPECT_EQ("OK", result_proto
.status());
404 TEST_F(ProximityAuthCryptAuthClientTest
, FetchAccessTokenFailure
) {
405 access_token_fetcher_
->set_access_token("");
407 std::string error_message
;
408 client_
->GetMyDevices(cryptauth::GetMyDevicesRequest(),
409 base::Bind(&NotCalled
<cryptauth::GetMyDevicesResponse
>),
410 base::Bind(&SaveResult
<std::string
>, &error_message
));
412 EXPECT_EQ("Failed to get a valid access token.", error_message
);
415 TEST_F(ProximityAuthCryptAuthClientTest
, ParseResponseProtoFailure
) {
417 "https://www.testgoogleapis.com/cryptauth/v1/deviceSync/"
418 "getmydevices?alt=proto");
420 std::string error_message
;
421 client_
->GetMyDevices(cryptauth::GetMyDevicesRequest(),
422 base::Bind(&NotCalled
<cryptauth::GetMyDevicesResponse
>),
423 base::Bind(&SaveResult
<std::string
>, &error_message
));
425 flow_result_callback_
.Run("Not a valid serialized response message.");
426 EXPECT_EQ("Failed to parse response proto.", error_message
);
429 TEST_F(ProximityAuthCryptAuthClientTest
,
430 MakeSecondRequestBeforeFirstRequestSucceeds
) {
432 "https://www.testgoogleapis.com/cryptauth/v1/deviceSync/"
433 "getmydevices?alt=proto");
435 // Make first request.
436 cryptauth::GetMyDevicesResponse result_proto
;
437 client_
->GetMyDevices(
438 cryptauth::GetMyDevicesRequest(),
439 base::Bind(&SaveResult
<cryptauth::GetMyDevicesResponse
>, &result_proto
),
440 base::Bind(&NotCalled
<std::string
>));
442 // With request pending, make second request.
444 std::string error_message
;
445 client_
->FindEligibleUnlockDevices(
446 cryptauth::FindEligibleUnlockDevicesRequest(),
447 base::Bind(&NotCalled
<cryptauth::FindEligibleUnlockDevicesResponse
>),
448 base::Bind(&SaveResult
<std::string
>, &error_message
));
449 EXPECT_EQ("Client has been used for another request. Do not reuse.",
453 // Complete first request.
455 cryptauth::GetMyDevicesResponse response_proto
;
456 response_proto
.add_devices();
457 response_proto
.mutable_devices(0)->set_public_key(kPublicKey1
);
458 FinishApiCallFlow(&response_proto
);
461 ASSERT_EQ(1, result_proto
.devices_size());
462 EXPECT_EQ(kPublicKey1
, result_proto
.devices(0).public_key());
465 TEST_F(ProximityAuthCryptAuthClientTest
,
466 MakeSecondRequestBeforeFirstRequestFails
) {
468 "https://www.testgoogleapis.com/cryptauth/v1/deviceSync/"
469 "getmydevices?alt=proto");
471 // Make first request.
472 std::string error_message
;
473 client_
->GetMyDevices(cryptauth::GetMyDevicesRequest(),
474 base::Bind(&NotCalled
<cryptauth::GetMyDevicesResponse
>),
475 base::Bind(&SaveResult
<std::string
>, &error_message
));
477 // With request pending, make second request.
479 std::string error_message
;
480 client_
->FindEligibleUnlockDevices(
481 cryptauth::FindEligibleUnlockDevicesRequest(),
482 base::Bind(&NotCalled
<cryptauth::FindEligibleUnlockDevicesResponse
>),
483 base::Bind(&SaveResult
<std::string
>, &error_message
));
484 EXPECT_EQ("Client has been used for another request. Do not reuse.",
488 // Fail first request.
489 std::string kStatus429Error
= "HTTP status: 429";
490 FailApiCallFlow(kStatus429Error
);
491 EXPECT_EQ(kStatus429Error
, error_message
);
494 TEST_F(ProximityAuthCryptAuthClientTest
,
495 MakeSecondRequestAfterFirstRequestSucceeds
) {
496 // Make first request successfully.
499 "https://www.testgoogleapis.com/cryptauth/v1/deviceSync/"
500 "getmydevices?alt=proto");
501 cryptauth::GetMyDevicesResponse result_proto
;
502 client_
->GetMyDevices(
503 cryptauth::GetMyDevicesRequest(),
504 base::Bind(&SaveResult
<cryptauth::GetMyDevicesResponse
>, &result_proto
),
505 base::Bind(&NotCalled
<std::string
>));
507 cryptauth::GetMyDevicesResponse response_proto
;
508 response_proto
.add_devices();
509 response_proto
.mutable_devices(0)->set_public_key(kPublicKey1
);
510 FinishApiCallFlow(&response_proto
);
511 ASSERT_EQ(1, result_proto
.devices_size());
512 EXPECT_EQ(kPublicKey1
, result_proto
.devices(0).public_key());
515 // Second request fails.
517 std::string error_message
;
518 client_
->FindEligibleUnlockDevices(
519 cryptauth::FindEligibleUnlockDevicesRequest(),
520 base::Bind(&NotCalled
<cryptauth::FindEligibleUnlockDevicesResponse
>),
521 base::Bind(&SaveResult
<std::string
>, &error_message
));
522 EXPECT_EQ("Client has been used for another request. Do not reuse.",
527 } // namespace proximity_auth