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.h"
7 #include "base/macros.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "components/proximity_auth/client_observer.h"
10 #include "components/proximity_auth/connection.h"
11 #include "components/proximity_auth/remote_device.h"
12 #include "components/proximity_auth/remote_status_update.h"
13 #include "components/proximity_auth/secure_context.h"
14 #include "components/proximity_auth/wire_message.h"
15 #include "testing/gmock/include/gmock/gmock.h"
16 #include "testing/gtest/include/gtest/gtest.h"
20 using testing::EndsWith
;
23 using testing::NiceMock
;
24 using testing::Pointee
;
25 using testing::Return
;
26 using testing::StrictMock
;
28 namespace proximity_auth
{
31 const char kChallenge
[] = "a most difficult challenge";
32 const char kFakeEncodingSuffix
[] = ", but encoded";
34 class MockSecureContext
: public SecureContext
{
37 // By default, mock a secure context that uses the 3.1 protocol. Individual
38 // tests override this as needed.
39 ON_CALL(*this, GetProtocolVersion())
40 .WillByDefault(Return(SecureContext::PROTOCOL_VERSION_THREE_ONE
));
42 ~MockSecureContext() override
{}
44 MOCK_CONST_METHOD0(GetReceivedAuthMessage
, std::string());
45 MOCK_CONST_METHOD0(GetProtocolVersion
, ProtocolVersion());
47 std::string
Encode(const std::string
& message
) override
{
48 return message
+ kFakeEncodingSuffix
;
51 std::string
Decode(const std::string
& encoded_message
) override
{
52 EXPECT_THAT(encoded_message
, EndsWith(kFakeEncodingSuffix
));
53 std::string decoded_message
= encoded_message
;
54 decoded_message
.erase(decoded_message
.rfind(kFakeEncodingSuffix
));
55 return decoded_message
;
59 DISALLOW_COPY_AND_ASSIGN(MockSecureContext
);
62 class FakeConnection
: public Connection
{
64 FakeConnection() : Connection(RemoteDevice()) { Connect(); }
65 ~FakeConnection() override
{ Disconnect(); }
67 void Connect() override
{ SetStatus(CONNECTED
); }
69 void Disconnect() override
{ SetStatus(DISCONNECTED
); }
71 void SendMessageImpl(scoped_ptr
<WireMessage
> message
) override
{
72 ASSERT_FALSE(current_message_
);
73 current_message_
= message
.Pass();
76 // Completes the current send operation with success |success|.
77 void FinishSendingMessageWithSuccess(bool success
) {
78 ASSERT_TRUE(current_message_
);
79 // Capture a copy of the message, as OnDidSendMessage() might reentrantly
80 // call SendMessage().
81 scoped_ptr
<WireMessage
> sent_message
= current_message_
.Pass();
82 OnDidSendMessage(*sent_message
, success
);
85 // Simulates receiving a wire message with the given |payload|.
86 void ReceiveMessageWithPayload(const std::string
& payload
) {
87 pending_payload_
= payload
;
88 OnBytesReceived(std::string());
89 pending_payload_
.clear();
92 // Returns a message containing the payload set via
93 // ReceiveMessageWithPayload().
94 scoped_ptr
<WireMessage
> DeserializeWireMessage(
95 bool* is_incomplete_message
) override
{
96 *is_incomplete_message
= false;
97 return make_scoped_ptr(new WireMessage(std::string(), pending_payload_
));
100 WireMessage
* current_message() { return current_message_
.get(); }
103 // The message currently being sent. Only set between a call to
104 // SendMessageImpl() and FinishSendingMessageWithSuccess().
105 scoped_ptr
<WireMessage
> current_message_
;
107 // The payload that should be returned when DeserializeWireMessage() is
109 std::string pending_payload_
;
111 DISALLOW_COPY_AND_ASSIGN(FakeConnection
);
114 class MockClientObserver
: public ClientObserver
{
116 explicit MockClientObserver(Client
* client
) : client_(client
) {
117 client_
->AddObserver(this);
119 virtual ~MockClientObserver() { client_
->RemoveObserver(this); }
121 MOCK_METHOD1(OnUnlockEventSent
, void(bool success
));
122 MOCK_METHOD1(OnRemoteStatusUpdate
,
123 void(const RemoteStatusUpdate
& status_update
));
124 MOCK_METHOD1(OnDecryptResponseProxy
,
125 void(const std::string
* decrypted_bytes
));
126 MOCK_METHOD1(OnUnlockResponse
, void(bool success
));
127 MOCK_METHOD0(OnDisconnected
, void());
129 virtual void OnDecryptResponse(scoped_ptr
<std::string
> decrypted_bytes
) {
130 OnDecryptResponseProxy(decrypted_bytes
.get());
134 // The client that |this| instance observes.
135 Client
* const client_
;
137 DISALLOW_COPY_AND_ASSIGN(MockClientObserver
);
140 class TestClient
: public Client
{
143 : Client(make_scoped_ptr(new NiceMock
<FakeConnection
>()),
144 make_scoped_ptr(new NiceMock
<MockSecureContext
>())) {}
145 ~TestClient() override
{}
147 // Simple getters for the mock objects owned by |this| client.
148 FakeConnection
* GetFakeConnection() {
149 return static_cast<FakeConnection
*>(connection());
151 MockSecureContext
* GetMockSecureContext() {
152 return static_cast<MockSecureContext
*>(secure_context());
156 DISALLOW_COPY_AND_ASSIGN(TestClient
);
161 TEST(ProximityAuthClientTest
, SupportsSignIn_ProtocolVersionThreeZero
) {
163 ON_CALL(*client
.GetMockSecureContext(), GetProtocolVersion())
164 .WillByDefault(Return(SecureContext::PROTOCOL_VERSION_THREE_ZERO
));
165 EXPECT_FALSE(client
.SupportsSignIn());
168 TEST(ProximityAuthClientTest
, SupportsSignIn_ProtocolVersionThreeOne
) {
170 ON_CALL(*client
.GetMockSecureContext(), GetProtocolVersion())
171 .WillByDefault(Return(SecureContext::PROTOCOL_VERSION_THREE_ONE
));
172 EXPECT_TRUE(client
.SupportsSignIn());
175 TEST(ProximityAuthClientTest
, OnConnectionStatusChanged_ConnectionDisconnects
) {
177 MockClientObserver
observer(&client
);
179 EXPECT_CALL(observer
, OnDisconnected());
180 client
.GetFakeConnection()->Disconnect();
183 TEST(ProximityAuthClientTest
, DispatchUnlockEvent_SendsExpectedMessage
) {
185 client
.DispatchUnlockEvent();
187 WireMessage
* message
= client
.GetFakeConnection()->current_message();
188 ASSERT_TRUE(message
);
189 EXPECT_EQ(std::string(), message
->permit_id());
192 "\"name\":\"easy_unlock\","
198 TEST(ProximityAuthClientTest
, DispatchUnlockEvent_SendMessageFails
) {
200 MockClientObserver
observer(&client
);
201 client
.DispatchUnlockEvent();
203 EXPECT_CALL(observer
, OnUnlockEventSent(false));
204 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(false);
207 TEST(ProximityAuthClientTest
, DispatchUnlockEvent_SendMessageSucceeds
) {
209 MockClientObserver
observer(&client
);
210 client
.DispatchUnlockEvent();
212 EXPECT_CALL(observer
, OnUnlockEventSent(true));
213 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
216 TEST(ProximityAuthClientTest
,
217 RequestDecryption_SignInUnsupported_DoesntSendMessage
) {
219 ON_CALL(*client
.GetMockSecureContext(), GetProtocolVersion())
220 .WillByDefault(Return(SecureContext::PROTOCOL_VERSION_THREE_ZERO
));
221 client
.RequestDecryption(kChallenge
);
222 EXPECT_FALSE(client
.GetFakeConnection()->current_message());
225 TEST(ProximityAuthClientTest
, RequestDecryption_SendsExpectedMessage
) {
227 client
.RequestDecryption(kChallenge
);
229 WireMessage
* message
= client
.GetFakeConnection()->current_message();
230 ASSERT_TRUE(message
);
231 EXPECT_EQ(std::string(), message
->permit_id());
234 "\"encrypted_data\":\"YSBtb3N0IGRpZmZpY3VsdCBjaGFsbGVuZ2U=\","
235 "\"type\":\"decrypt_request\""
240 TEST(ProximityAuthClientTest
,
241 RequestDecryption_SendsExpectedMessage_UsingBase64UrlEncoding
) {
243 client
.RequestDecryption("\xFF\xE6");
245 WireMessage
* message
= client
.GetFakeConnection()->current_message();
246 ASSERT_TRUE(message
);
247 EXPECT_EQ(std::string(), message
->permit_id());
250 "\"encrypted_data\":\"_-Y=\","
251 "\"type\":\"decrypt_request\""
256 TEST(ProximityAuthClientTest
, RequestDecryption_SendMessageFails
) {
258 MockClientObserver
observer(&client
);
259 client
.RequestDecryption(kChallenge
);
261 EXPECT_CALL(observer
, OnDecryptResponseProxy(nullptr));
262 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(false);
265 TEST(ProximityAuthClientTest
, RequestDecryption_SendSucceeds_WaitsForReply
) {
267 MockClientObserver
observer(&client
);
268 client
.RequestDecryption(kChallenge
);
270 EXPECT_CALL(observer
, OnDecryptResponseProxy(_
)).Times(0);
271 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
274 TEST(ProximityAuthClientTest
,
275 RequestDecryption_SendSucceeds_NotifiesObserversOnReply_NoData
) {
277 MockClientObserver
observer(&client
);
278 client
.RequestDecryption(kChallenge
);
279 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
281 EXPECT_CALL(observer
, OnDecryptResponseProxy(nullptr));
282 client
.GetFakeConnection()->ReceiveMessageWithPayload(
283 "{\"type\":\"decrypt_response\"}, but encoded");
286 TEST(ProximityAuthClientTest
,
287 RequestDecryption_SendSucceeds_NotifiesObserversOnReply_InvalidData
) {
289 MockClientObserver
observer(&client
);
290 client
.RequestDecryption(kChallenge
);
291 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
293 EXPECT_CALL(observer
, OnDecryptResponseProxy(nullptr));
294 client
.GetFakeConnection()->ReceiveMessageWithPayload(
296 "\"type\":\"decrypt_response\","
297 "\"data\":\"not a base64-encoded string\""
301 TEST(ProximityAuthClientTest
,
302 RequestDecryption_SendSucceeds_NotifiesObserversOnReply_ValidData
) {
304 MockClientObserver
observer(&client
);
305 client
.RequestDecryption(kChallenge
);
306 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
308 EXPECT_CALL(observer
, OnDecryptResponseProxy(Pointee(Eq("a winner is you"))));
309 client
.GetFakeConnection()->ReceiveMessageWithPayload(
311 "\"type\":\"decrypt_response\","
312 "\"data\":\"YSB3aW5uZXIgaXMgeW91\"" // "a winner is you", base64-encoded
316 // Verify that the client correctly parses base64url encoded data.
317 TEST(ProximityAuthClientTest
,
318 RequestDecryption_SendSucceeds_ParsesBase64UrlEncodingInReply
) {
320 MockClientObserver
observer(&client
);
321 client
.RequestDecryption(kChallenge
);
322 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
324 EXPECT_CALL(observer
, OnDecryptResponseProxy(Pointee(Eq("\xFF\xE6"))));
325 client
.GetFakeConnection()->ReceiveMessageWithPayload(
327 "\"type\":\"decrypt_response\","
328 "\"data\":\"_-Y=\"" // "\0xFF\0xE6", base64url-encoded.
332 TEST(ProximityAuthClientTest
,
333 RequestUnlock_SignInUnsupported_DoesntSendMessage
) {
335 ON_CALL(*client
.GetMockSecureContext(), GetProtocolVersion())
336 .WillByDefault(Return(SecureContext::PROTOCOL_VERSION_THREE_ZERO
));
337 client
.RequestUnlock();
338 EXPECT_FALSE(client
.GetFakeConnection()->current_message());
341 TEST(ProximityAuthClientTest
, RequestUnlock_SendsExpectedMessage
) {
343 client
.RequestUnlock();
345 WireMessage
* message
= client
.GetFakeConnection()->current_message();
346 ASSERT_TRUE(message
);
347 EXPECT_EQ(std::string(), message
->permit_id());
348 EXPECT_EQ("{\"type\":\"unlock_request\"}, but encoded", message
->payload());
351 TEST(ProximityAuthClientTest
, RequestUnlock_SendMessageFails
) {
353 MockClientObserver
observer(&client
);
354 client
.RequestUnlock();
356 EXPECT_CALL(observer
, OnUnlockResponse(false));
357 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(false);
360 TEST(ProximityAuthClientTest
, RequestUnlock_SendSucceeds_WaitsForReply
) {
362 MockClientObserver
observer(&client
);
363 client
.RequestUnlock();
365 EXPECT_CALL(observer
, OnUnlockResponse(_
)).Times(0);
366 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
369 TEST(ProximityAuthClientTest
,
370 RequestUnlock_SendSucceeds_NotifiesObserversOnReply
) {
372 MockClientObserver
observer(&client
);
373 client
.RequestUnlock();
374 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
376 EXPECT_CALL(observer
, OnUnlockResponse(true));
377 client
.GetFakeConnection()->ReceiveMessageWithPayload(
378 "{\"type\":\"unlock_response\"}, but encoded");
381 TEST(ProximityAuthClientTest
, OnMessageReceived_RemoteStatusUpdate_Invalid
) {
383 MockClientObserver
observer(&client
);
385 // Receive a status update message that's missing all the data.
386 EXPECT_CALL(observer
, OnRemoteStatusUpdate(_
)).Times(0);
387 client
.GetFakeConnection()->ReceiveMessageWithPayload(
388 "{\"type\":\"status_update\"}, but encoded");
391 TEST(ProximityAuthClientTest
, OnMessageReceived_RemoteStatusUpdate_Valid
) {
393 MockClientObserver
observer(&client
);
395 EXPECT_CALL(observer
,
396 OnRemoteStatusUpdate(
397 AllOf(Field(&RemoteStatusUpdate::user_presence
, USER_PRESENT
),
398 Field(&RemoteStatusUpdate::secure_screen_lock_state
,
399 SECURE_SCREEN_LOCK_ENABLED
),
400 Field(&RemoteStatusUpdate::trust_agent_state
,
401 TRUST_AGENT_UNSUPPORTED
))));
402 client
.GetFakeConnection()->ReceiveMessageWithPayload(
404 "\"type\":\"status_update\","
405 "\"user_presence\":\"present\","
406 "\"secure_screen_lock\":\"enabled\","
407 "\"trust_agent\":\"unsupported\""
411 TEST(ProximityAuthClientTest
, OnMessageReceived_InvalidJSON
) {
413 StrictMock
<MockClientObserver
> observer(&client
);
414 client
.RequestUnlock();
415 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
417 // The StrictMock will verify that no observer methods are called.
418 client
.GetFakeConnection()->ReceiveMessageWithPayload(
419 "Not JSON, but encoded");
422 TEST(ProximityAuthClientTest
, OnMessageReceived_MissingTypeField
) {
424 StrictMock
<MockClientObserver
> observer(&client
);
425 client
.RequestUnlock();
426 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
428 // The StrictMock will verify that no observer methods are called.
429 client
.GetFakeConnection()->ReceiveMessageWithPayload(
430 "{\"some key that's not 'type'\":\"some value\"}, but encoded");
433 TEST(ProximityAuthClientTest
, OnMessageReceived_UnexpectedReply
) {
435 StrictMock
<MockClientObserver
> observer(&client
);
437 // The StrictMock will verify that no observer methods are called.
438 client
.GetFakeConnection()->ReceiveMessageWithPayload(
439 "{\"type\":\"unlock_response\"}, but encoded");
442 TEST(ProximityAuthClientTest
,
443 OnMessageReceived_MismatchedReply_UnlockInReplyToDecrypt
) {
445 StrictMock
<MockClientObserver
> observer(&client
);
447 client
.RequestDecryption(kChallenge
);
448 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
450 // The StrictMock will verify that no observer methods are called.
451 client
.GetFakeConnection()->ReceiveMessageWithPayload(
452 "{\"type\":\"unlock_response\"}, but encoded");
455 TEST(ProximityAuthClientTest
,
456 OnMessageReceived_MismatchedReply_DecryptInReplyToUnlock
) {
458 StrictMock
<MockClientObserver
> observer(&client
);
460 client
.RequestUnlock();
461 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
463 // The StrictMock will verify that no observer methods are called.
464 client
.GetFakeConnection()->ReceiveMessageWithPayload(
466 "\"type\":\"decrypt_response\","
467 "\"data\":\"YSB3aW5uZXIgaXMgeW91\""
471 TEST(ProximityAuthClientTest
, BuffersMessages_WhileSending
) {
473 MockClientObserver
observer(&client
);
475 // Initiate a decryption request, and then initiate an unlock request before
476 // the decryption request is even finished sending.
477 client
.RequestDecryption(kChallenge
);
478 client
.RequestUnlock();
480 EXPECT_CALL(observer
, OnDecryptResponseProxy(nullptr));
481 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(false);
483 EXPECT_CALL(observer
, OnUnlockResponse(false));
484 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(false);
487 TEST(ProximityAuthClientTest
, BuffersMessages_WhileAwaitingReply
) {
489 MockClientObserver
observer(&client
);
491 // Initiate a decryption request, and allow the message to be sent.
492 client
.RequestDecryption(kChallenge
);
493 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
495 // At this point, the client is awaiting a reply to the decryption message.
496 // While it's waiting, initiate an unlock request.
497 client
.RequestUnlock();
499 // Now simulate a response arriving for the original decryption request.
500 EXPECT_CALL(observer
, OnDecryptResponseProxy(Pointee(Eq("a winner is you"))));
501 client
.GetFakeConnection()->ReceiveMessageWithPayload(
503 "\"type\":\"decrypt_response\","
504 "\"data\":\"YSB3aW5uZXIgaXMgeW91\""
507 // The unlock request should have remained buffered, and should only now be
509 EXPECT_CALL(observer
, OnUnlockResponse(false));
510 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(false);
513 } // namespace proximity_auth