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/client_impl.h"
7 #include "base/callback.h"
8 #include "base/macros.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "components/proximity_auth/client_observer.h"
11 #include "components/proximity_auth/connection.h"
12 #include "components/proximity_auth/remote_device.h"
13 #include "components/proximity_auth/remote_status_update.h"
14 #include "components/proximity_auth/secure_context.h"
15 #include "components/proximity_auth/wire_message.h"
16 #include "testing/gmock/include/gmock/gmock.h"
17 #include "testing/gtest/include/gtest/gtest.h"
21 using testing::EndsWith
;
24 using testing::NiceMock
;
25 using testing::Pointee
;
26 using testing::Return
;
27 using testing::StrictMock
;
29 namespace proximity_auth
{
32 const char kChallenge
[] = "a most difficult challenge";
33 const char kFakeEncodingSuffix
[] = ", but encoded";
35 class MockSecureContext
: public SecureContext
{
38 // By default, mock a secure context that uses the 3.1 protocol. Individual
39 // tests override this as needed.
40 ON_CALL(*this, GetProtocolVersion())
41 .WillByDefault(Return(SecureContext::PROTOCOL_VERSION_THREE_ONE
));
43 ~MockSecureContext() override
{}
45 MOCK_CONST_METHOD0(GetReceivedAuthMessage
, std::string());
46 MOCK_CONST_METHOD0(GetProtocolVersion
, ProtocolVersion());
48 void Encode(const std::string
& message
,
49 const MessageCallback
& callback
) override
{
50 callback
.Run(message
+ kFakeEncodingSuffix
);
53 void Decode(const std::string
& encoded_message
,
54 const MessageCallback
& callback
) override
{
55 EXPECT_THAT(encoded_message
, EndsWith(kFakeEncodingSuffix
));
56 std::string decoded_message
= encoded_message
;
57 decoded_message
.erase(decoded_message
.rfind(kFakeEncodingSuffix
));
58 callback
.Run(decoded_message
);
62 DISALLOW_COPY_AND_ASSIGN(MockSecureContext
);
65 class FakeConnection
: public Connection
{
67 FakeConnection() : Connection(RemoteDevice()) { Connect(); }
68 ~FakeConnection() override
{ Disconnect(); }
70 void Connect() override
{ SetStatus(CONNECTED
); }
72 void Disconnect() override
{ SetStatus(DISCONNECTED
); }
74 void SendMessageImpl(scoped_ptr
<WireMessage
> message
) override
{
75 ASSERT_FALSE(current_message_
);
76 current_message_
= message
.Pass();
79 // Completes the current send operation with success |success|.
80 void FinishSendingMessageWithSuccess(bool success
) {
81 ASSERT_TRUE(current_message_
);
82 // Capture a copy of the message, as OnDidSendMessage() might reentrantly
83 // call SendMessage().
84 scoped_ptr
<WireMessage
> sent_message
= current_message_
.Pass();
85 OnDidSendMessage(*sent_message
, success
);
88 // Simulates receiving a wire message with the given |payload|.
89 void ReceiveMessageWithPayload(const std::string
& payload
) {
90 pending_payload_
= payload
;
91 OnBytesReceived(std::string());
92 pending_payload_
.clear();
95 // Returns a message containing the payload set via
96 // ReceiveMessageWithPayload().
97 scoped_ptr
<WireMessage
> DeserializeWireMessage(
98 bool* is_incomplete_message
) override
{
99 *is_incomplete_message
= false;
100 return make_scoped_ptr(new WireMessage(pending_payload_
));
103 WireMessage
* current_message() { return current_message_
.get(); }
106 // The message currently being sent. Only set between a call to
107 // SendMessageImpl() and FinishSendingMessageWithSuccess().
108 scoped_ptr
<WireMessage
> current_message_
;
110 // The payload that should be returned when DeserializeWireMessage() is
112 std::string pending_payload_
;
114 DISALLOW_COPY_AND_ASSIGN(FakeConnection
);
117 class MockClientObserver
: public ClientObserver
{
119 explicit MockClientObserver(Client
* client
) : client_(client
) {
120 client_
->AddObserver(this);
122 virtual ~MockClientObserver() { client_
->RemoveObserver(this); }
124 MOCK_METHOD1(OnUnlockEventSent
, void(bool success
));
125 MOCK_METHOD1(OnRemoteStatusUpdate
,
126 void(const RemoteStatusUpdate
& status_update
));
127 MOCK_METHOD1(OnDecryptResponseProxy
,
128 void(const std::string
* decrypted_bytes
));
129 MOCK_METHOD1(OnUnlockResponse
, void(bool success
));
130 MOCK_METHOD0(OnDisconnected
, void());
132 virtual void OnDecryptResponse(scoped_ptr
<std::string
> decrypted_bytes
) {
133 OnDecryptResponseProxy(decrypted_bytes
.get());
137 // The client that |this| instance observes.
138 Client
* const client_
;
140 DISALLOW_COPY_AND_ASSIGN(MockClientObserver
);
143 class TestClient
: public ClientImpl
{
146 : ClientImpl(make_scoped_ptr(new NiceMock
<FakeConnection
>()),
147 make_scoped_ptr(new NiceMock
<MockSecureContext
>())) {}
148 ~TestClient() override
{}
150 // Simple getters for the mock objects owned by |this| client.
151 FakeConnection
* GetFakeConnection() {
152 return static_cast<FakeConnection
*>(connection());
154 MockSecureContext
* GetMockSecureContext() {
155 return static_cast<MockSecureContext
*>(secure_context());
159 DISALLOW_COPY_AND_ASSIGN(TestClient
);
164 TEST(ProximityAuthClientImplTest
, SupportsSignIn_ProtocolVersionThreeZero
) {
166 ON_CALL(*client
.GetMockSecureContext(), GetProtocolVersion())
167 .WillByDefault(Return(SecureContext::PROTOCOL_VERSION_THREE_ZERO
));
168 EXPECT_FALSE(client
.SupportsSignIn());
171 TEST(ProximityAuthClientImplTest
, SupportsSignIn_ProtocolVersionThreeOne
) {
173 ON_CALL(*client
.GetMockSecureContext(), GetProtocolVersion())
174 .WillByDefault(Return(SecureContext::PROTOCOL_VERSION_THREE_ONE
));
175 EXPECT_TRUE(client
.SupportsSignIn());
178 TEST(ProximityAuthClientImplTest
,
179 OnConnectionStatusChanged_ConnectionDisconnects
) {
181 MockClientObserver
observer(&client
);
183 EXPECT_CALL(observer
, OnDisconnected());
184 client
.GetFakeConnection()->Disconnect();
187 TEST(ProximityAuthClientImplTest
, DispatchUnlockEvent_SendsExpectedMessage
) {
189 client
.DispatchUnlockEvent();
191 WireMessage
* message
= client
.GetFakeConnection()->current_message();
192 ASSERT_TRUE(message
);
193 EXPECT_EQ(std::string(), message
->permit_id());
196 "\"name\":\"easy_unlock\","
202 TEST(ProximityAuthClientImplTest
, DispatchUnlockEvent_SendMessageFails
) {
204 MockClientObserver
observer(&client
);
205 client
.DispatchUnlockEvent();
207 EXPECT_CALL(observer
, OnUnlockEventSent(false));
208 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(false);
211 TEST(ProximityAuthClientImplTest
, DispatchUnlockEvent_SendMessageSucceeds
) {
213 MockClientObserver
observer(&client
);
214 client
.DispatchUnlockEvent();
216 EXPECT_CALL(observer
, OnUnlockEventSent(true));
217 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
220 TEST(ProximityAuthClientImplTest
,
221 RequestDecryption_SignInUnsupported_DoesntSendMessage
) {
223 ON_CALL(*client
.GetMockSecureContext(), GetProtocolVersion())
224 .WillByDefault(Return(SecureContext::PROTOCOL_VERSION_THREE_ZERO
));
225 client
.RequestDecryption(kChallenge
);
226 EXPECT_FALSE(client
.GetFakeConnection()->current_message());
229 TEST(ProximityAuthClientImplTest
, RequestDecryption_SendsExpectedMessage
) {
231 client
.RequestDecryption(kChallenge
);
233 WireMessage
* message
= client
.GetFakeConnection()->current_message();
234 ASSERT_TRUE(message
);
235 EXPECT_EQ(std::string(), message
->permit_id());
238 "\"encrypted_data\":\"YSBtb3N0IGRpZmZpY3VsdCBjaGFsbGVuZ2U=\","
239 "\"type\":\"decrypt_request\""
244 TEST(ProximityAuthClientImplTest
,
245 RequestDecryption_SendsExpectedMessage_UsingBase64UrlEncoding
) {
247 client
.RequestDecryption("\xFF\xE6");
249 WireMessage
* message
= client
.GetFakeConnection()->current_message();
250 ASSERT_TRUE(message
);
251 EXPECT_EQ(std::string(), message
->permit_id());
254 "\"encrypted_data\":\"_-Y=\","
255 "\"type\":\"decrypt_request\""
260 TEST(ProximityAuthClientImplTest
, RequestDecryption_SendMessageFails
) {
262 MockClientObserver
observer(&client
);
263 client
.RequestDecryption(kChallenge
);
265 EXPECT_CALL(observer
, OnDecryptResponseProxy(nullptr));
266 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(false);
269 TEST(ProximityAuthClientImplTest
,
270 RequestDecryption_SendSucceeds_WaitsForReply
) {
272 MockClientObserver
observer(&client
);
273 client
.RequestDecryption(kChallenge
);
275 EXPECT_CALL(observer
, OnDecryptResponseProxy(_
)).Times(0);
276 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
279 TEST(ProximityAuthClientImplTest
,
280 RequestDecryption_SendSucceeds_NotifiesObserversOnReply_NoData
) {
282 MockClientObserver
observer(&client
);
283 client
.RequestDecryption(kChallenge
);
284 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
286 EXPECT_CALL(observer
, OnDecryptResponseProxy(nullptr));
287 client
.GetFakeConnection()->ReceiveMessageWithPayload(
288 "{\"type\":\"decrypt_response\"}, but encoded");
291 TEST(ProximityAuthClientImplTest
,
292 RequestDecryption_SendSucceeds_NotifiesObserversOnReply_InvalidData
) {
294 MockClientObserver
observer(&client
);
295 client
.RequestDecryption(kChallenge
);
296 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
298 EXPECT_CALL(observer
, OnDecryptResponseProxy(nullptr));
299 client
.GetFakeConnection()->ReceiveMessageWithPayload(
301 "\"type\":\"decrypt_response\","
302 "\"data\":\"not a base64-encoded string\""
306 TEST(ProximityAuthClientImplTest
,
307 RequestDecryption_SendSucceeds_NotifiesObserversOnReply_ValidData
) {
309 MockClientObserver
observer(&client
);
310 client
.RequestDecryption(kChallenge
);
311 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
313 EXPECT_CALL(observer
, OnDecryptResponseProxy(Pointee(Eq("a winner is you"))));
314 client
.GetFakeConnection()->ReceiveMessageWithPayload(
316 "\"type\":\"decrypt_response\","
317 "\"data\":\"YSB3aW5uZXIgaXMgeW91\"" // "a winner is you", base64-encoded
321 // Verify that the client correctly parses base64url encoded data.
322 TEST(ProximityAuthClientImplTest
,
323 RequestDecryption_SendSucceeds_ParsesBase64UrlEncodingInReply
) {
325 MockClientObserver
observer(&client
);
326 client
.RequestDecryption(kChallenge
);
327 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
329 EXPECT_CALL(observer
, OnDecryptResponseProxy(Pointee(Eq("\xFF\xE6"))));
330 client
.GetFakeConnection()->ReceiveMessageWithPayload(
332 "\"type\":\"decrypt_response\","
333 "\"data\":\"_-Y=\"" // "\0xFF\0xE6", base64url-encoded.
337 TEST(ProximityAuthClientImplTest
,
338 RequestUnlock_SignInUnsupported_DoesntSendMessage
) {
340 ON_CALL(*client
.GetMockSecureContext(), GetProtocolVersion())
341 .WillByDefault(Return(SecureContext::PROTOCOL_VERSION_THREE_ZERO
));
342 client
.RequestUnlock();
343 EXPECT_FALSE(client
.GetFakeConnection()->current_message());
346 TEST(ProximityAuthClientImplTest
, RequestUnlock_SendsExpectedMessage
) {
348 client
.RequestUnlock();
350 WireMessage
* message
= client
.GetFakeConnection()->current_message();
351 ASSERT_TRUE(message
);
352 EXPECT_EQ(std::string(), message
->permit_id());
353 EXPECT_EQ("{\"type\":\"unlock_request\"}, but encoded", message
->payload());
356 TEST(ProximityAuthClientImplTest
, RequestUnlock_SendMessageFails
) {
358 MockClientObserver
observer(&client
);
359 client
.RequestUnlock();
361 EXPECT_CALL(observer
, OnUnlockResponse(false));
362 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(false);
365 TEST(ProximityAuthClientImplTest
, RequestUnlock_SendSucceeds_WaitsForReply
) {
367 MockClientObserver
observer(&client
);
368 client
.RequestUnlock();
370 EXPECT_CALL(observer
, OnUnlockResponse(_
)).Times(0);
371 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
374 TEST(ProximityAuthClientImplTest
,
375 RequestUnlock_SendSucceeds_NotifiesObserversOnReply
) {
377 MockClientObserver
observer(&client
);
378 client
.RequestUnlock();
379 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
381 EXPECT_CALL(observer
, OnUnlockResponse(true));
382 client
.GetFakeConnection()->ReceiveMessageWithPayload(
383 "{\"type\":\"unlock_response\"}, but encoded");
386 TEST(ProximityAuthClientImplTest
,
387 OnMessageReceived_RemoteStatusUpdate_Invalid
) {
389 MockClientObserver
observer(&client
);
391 // Receive a status update message that's missing all the data.
392 EXPECT_CALL(observer
, OnRemoteStatusUpdate(_
)).Times(0);
393 client
.GetFakeConnection()->ReceiveMessageWithPayload(
394 "{\"type\":\"status_update\"}, but encoded");
397 TEST(ProximityAuthClientImplTest
, OnMessageReceived_RemoteStatusUpdate_Valid
) {
399 MockClientObserver
observer(&client
);
401 EXPECT_CALL(observer
,
402 OnRemoteStatusUpdate(
403 AllOf(Field(&RemoteStatusUpdate::user_presence
, USER_PRESENT
),
404 Field(&RemoteStatusUpdate::secure_screen_lock_state
,
405 SECURE_SCREEN_LOCK_ENABLED
),
406 Field(&RemoteStatusUpdate::trust_agent_state
,
407 TRUST_AGENT_UNSUPPORTED
))));
408 client
.GetFakeConnection()->ReceiveMessageWithPayload(
410 "\"type\":\"status_update\","
411 "\"user_presence\":\"present\","
412 "\"secure_screen_lock\":\"enabled\","
413 "\"trust_agent\":\"unsupported\""
417 TEST(ProximityAuthClientImplTest
, OnMessageReceived_InvalidJSON
) {
419 StrictMock
<MockClientObserver
> observer(&client
);
420 client
.RequestUnlock();
421 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
423 // The StrictMock will verify that no observer methods are called.
424 client
.GetFakeConnection()->ReceiveMessageWithPayload(
425 "Not JSON, but encoded");
428 TEST(ProximityAuthClientImplTest
, OnMessageReceived_MissingTypeField
) {
430 StrictMock
<MockClientObserver
> observer(&client
);
431 client
.RequestUnlock();
432 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
434 // The StrictMock will verify that no observer methods are called.
435 client
.GetFakeConnection()->ReceiveMessageWithPayload(
436 "{\"some key that's not 'type'\":\"some value\"}, but encoded");
439 TEST(ProximityAuthClientImplTest
, OnMessageReceived_UnexpectedReply
) {
441 StrictMock
<MockClientObserver
> observer(&client
);
443 // The StrictMock will verify that no observer methods are called.
444 client
.GetFakeConnection()->ReceiveMessageWithPayload(
445 "{\"type\":\"unlock_response\"}, but encoded");
448 TEST(ProximityAuthClientImplTest
,
449 OnMessageReceived_MismatchedReply_UnlockInReplyToDecrypt
) {
451 StrictMock
<MockClientObserver
> observer(&client
);
453 client
.RequestDecryption(kChallenge
);
454 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
456 // The StrictMock will verify that no observer methods are called.
457 client
.GetFakeConnection()->ReceiveMessageWithPayload(
458 "{\"type\":\"unlock_response\"}, but encoded");
461 TEST(ProximityAuthClientImplTest
,
462 OnMessageReceived_MismatchedReply_DecryptInReplyToUnlock
) {
464 StrictMock
<MockClientObserver
> observer(&client
);
466 client
.RequestUnlock();
467 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
469 // The StrictMock will verify that no observer methods are called.
470 client
.GetFakeConnection()->ReceiveMessageWithPayload(
472 "\"type\":\"decrypt_response\","
473 "\"data\":\"YSB3aW5uZXIgaXMgeW91\""
477 TEST(ProximityAuthClientImplTest
, BuffersMessages_WhileSending
) {
479 MockClientObserver
observer(&client
);
481 // Initiate a decryption request, and then initiate an unlock request before
482 // the decryption request is even finished sending.
483 client
.RequestDecryption(kChallenge
);
484 client
.RequestUnlock();
486 EXPECT_CALL(observer
, OnDecryptResponseProxy(nullptr));
487 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(false);
489 EXPECT_CALL(observer
, OnUnlockResponse(false));
490 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(false);
493 TEST(ProximityAuthClientImplTest
, BuffersMessages_WhileAwaitingReply
) {
495 MockClientObserver
observer(&client
);
497 // Initiate a decryption request, and allow the message to be sent.
498 client
.RequestDecryption(kChallenge
);
499 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
501 // At this point, the client is awaiting a reply to the decryption message.
502 // While it's waiting, initiate an unlock request.
503 client
.RequestUnlock();
505 // Now simulate a response arriving for the original decryption request.
506 EXPECT_CALL(observer
, OnDecryptResponseProxy(Pointee(Eq("a winner is you"))));
507 client
.GetFakeConnection()->ReceiveMessageWithPayload(
509 "\"type\":\"decrypt_response\","
510 "\"data\":\"YSB3aW5uZXIgaXMgeW91\""
513 // The unlock request should have remained buffered, and should only now be
515 EXPECT_CALL(observer
, OnUnlockResponse(false));
516 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(false);
519 } // namespace proximity_auth