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/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 ClientImpl
{
143 : ClientImpl(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(ProximityAuthClientImplTest
, SupportsSignIn_ProtocolVersionThreeZero
) {
163 ON_CALL(*client
.GetMockSecureContext(), GetProtocolVersion())
164 .WillByDefault(Return(SecureContext::PROTOCOL_VERSION_THREE_ZERO
));
165 EXPECT_FALSE(client
.SupportsSignIn());
168 TEST(ProximityAuthClientImplTest
, SupportsSignIn_ProtocolVersionThreeOne
) {
170 ON_CALL(*client
.GetMockSecureContext(), GetProtocolVersion())
171 .WillByDefault(Return(SecureContext::PROTOCOL_VERSION_THREE_ONE
));
172 EXPECT_TRUE(client
.SupportsSignIn());
175 TEST(ProximityAuthClientImplTest
,
176 OnConnectionStatusChanged_ConnectionDisconnects
) {
178 MockClientObserver
observer(&client
);
180 EXPECT_CALL(observer
, OnDisconnected());
181 client
.GetFakeConnection()->Disconnect();
184 TEST(ProximityAuthClientImplTest
, DispatchUnlockEvent_SendsExpectedMessage
) {
186 client
.DispatchUnlockEvent();
188 WireMessage
* message
= client
.GetFakeConnection()->current_message();
189 ASSERT_TRUE(message
);
190 EXPECT_EQ(std::string(), message
->permit_id());
193 "\"name\":\"easy_unlock\","
199 TEST(ProximityAuthClientImplTest
, DispatchUnlockEvent_SendMessageFails
) {
201 MockClientObserver
observer(&client
);
202 client
.DispatchUnlockEvent();
204 EXPECT_CALL(observer
, OnUnlockEventSent(false));
205 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(false);
208 TEST(ProximityAuthClientImplTest
, DispatchUnlockEvent_SendMessageSucceeds
) {
210 MockClientObserver
observer(&client
);
211 client
.DispatchUnlockEvent();
213 EXPECT_CALL(observer
, OnUnlockEventSent(true));
214 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
217 TEST(ProximityAuthClientImplTest
,
218 RequestDecryption_SignInUnsupported_DoesntSendMessage
) {
220 ON_CALL(*client
.GetMockSecureContext(), GetProtocolVersion())
221 .WillByDefault(Return(SecureContext::PROTOCOL_VERSION_THREE_ZERO
));
222 client
.RequestDecryption(kChallenge
);
223 EXPECT_FALSE(client
.GetFakeConnection()->current_message());
226 TEST(ProximityAuthClientImplTest
, RequestDecryption_SendsExpectedMessage
) {
228 client
.RequestDecryption(kChallenge
);
230 WireMessage
* message
= client
.GetFakeConnection()->current_message();
231 ASSERT_TRUE(message
);
232 EXPECT_EQ(std::string(), message
->permit_id());
235 "\"encrypted_data\":\"YSBtb3N0IGRpZmZpY3VsdCBjaGFsbGVuZ2U=\","
236 "\"type\":\"decrypt_request\""
241 TEST(ProximityAuthClientImplTest
,
242 RequestDecryption_SendsExpectedMessage_UsingBase64UrlEncoding
) {
244 client
.RequestDecryption("\xFF\xE6");
246 WireMessage
* message
= client
.GetFakeConnection()->current_message();
247 ASSERT_TRUE(message
);
248 EXPECT_EQ(std::string(), message
->permit_id());
251 "\"encrypted_data\":\"_-Y=\","
252 "\"type\":\"decrypt_request\""
257 TEST(ProximityAuthClientImplTest
, RequestDecryption_SendMessageFails
) {
259 MockClientObserver
observer(&client
);
260 client
.RequestDecryption(kChallenge
);
262 EXPECT_CALL(observer
, OnDecryptResponseProxy(nullptr));
263 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(false);
266 TEST(ProximityAuthClientImplTest
,
267 RequestDecryption_SendSucceeds_WaitsForReply
) {
269 MockClientObserver
observer(&client
);
270 client
.RequestDecryption(kChallenge
);
272 EXPECT_CALL(observer
, OnDecryptResponseProxy(_
)).Times(0);
273 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
276 TEST(ProximityAuthClientImplTest
,
277 RequestDecryption_SendSucceeds_NotifiesObserversOnReply_NoData
) {
279 MockClientObserver
observer(&client
);
280 client
.RequestDecryption(kChallenge
);
281 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
283 EXPECT_CALL(observer
, OnDecryptResponseProxy(nullptr));
284 client
.GetFakeConnection()->ReceiveMessageWithPayload(
285 "{\"type\":\"decrypt_response\"}, but encoded");
288 TEST(ProximityAuthClientImplTest
,
289 RequestDecryption_SendSucceeds_NotifiesObserversOnReply_InvalidData
) {
291 MockClientObserver
observer(&client
);
292 client
.RequestDecryption(kChallenge
);
293 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
295 EXPECT_CALL(observer
, OnDecryptResponseProxy(nullptr));
296 client
.GetFakeConnection()->ReceiveMessageWithPayload(
298 "\"type\":\"decrypt_response\","
299 "\"data\":\"not a base64-encoded string\""
303 TEST(ProximityAuthClientImplTest
,
304 RequestDecryption_SendSucceeds_NotifiesObserversOnReply_ValidData
) {
306 MockClientObserver
observer(&client
);
307 client
.RequestDecryption(kChallenge
);
308 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
310 EXPECT_CALL(observer
, OnDecryptResponseProxy(Pointee(Eq("a winner is you"))));
311 client
.GetFakeConnection()->ReceiveMessageWithPayload(
313 "\"type\":\"decrypt_response\","
314 "\"data\":\"YSB3aW5uZXIgaXMgeW91\"" // "a winner is you", base64-encoded
318 // Verify that the client correctly parses base64url encoded data.
319 TEST(ProximityAuthClientImplTest
,
320 RequestDecryption_SendSucceeds_ParsesBase64UrlEncodingInReply
) {
322 MockClientObserver
observer(&client
);
323 client
.RequestDecryption(kChallenge
);
324 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
326 EXPECT_CALL(observer
, OnDecryptResponseProxy(Pointee(Eq("\xFF\xE6"))));
327 client
.GetFakeConnection()->ReceiveMessageWithPayload(
329 "\"type\":\"decrypt_response\","
330 "\"data\":\"_-Y=\"" // "\0xFF\0xE6", base64url-encoded.
334 TEST(ProximityAuthClientImplTest
,
335 RequestUnlock_SignInUnsupported_DoesntSendMessage
) {
337 ON_CALL(*client
.GetMockSecureContext(), GetProtocolVersion())
338 .WillByDefault(Return(SecureContext::PROTOCOL_VERSION_THREE_ZERO
));
339 client
.RequestUnlock();
340 EXPECT_FALSE(client
.GetFakeConnection()->current_message());
343 TEST(ProximityAuthClientImplTest
, RequestUnlock_SendsExpectedMessage
) {
345 client
.RequestUnlock();
347 WireMessage
* message
= client
.GetFakeConnection()->current_message();
348 ASSERT_TRUE(message
);
349 EXPECT_EQ(std::string(), message
->permit_id());
350 EXPECT_EQ("{\"type\":\"unlock_request\"}, but encoded", message
->payload());
353 TEST(ProximityAuthClientImplTest
, RequestUnlock_SendMessageFails
) {
355 MockClientObserver
observer(&client
);
356 client
.RequestUnlock();
358 EXPECT_CALL(observer
, OnUnlockResponse(false));
359 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(false);
362 TEST(ProximityAuthClientImplTest
, RequestUnlock_SendSucceeds_WaitsForReply
) {
364 MockClientObserver
observer(&client
);
365 client
.RequestUnlock();
367 EXPECT_CALL(observer
, OnUnlockResponse(_
)).Times(0);
368 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
371 TEST(ProximityAuthClientImplTest
,
372 RequestUnlock_SendSucceeds_NotifiesObserversOnReply
) {
374 MockClientObserver
observer(&client
);
375 client
.RequestUnlock();
376 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
378 EXPECT_CALL(observer
, OnUnlockResponse(true));
379 client
.GetFakeConnection()->ReceiveMessageWithPayload(
380 "{\"type\":\"unlock_response\"}, but encoded");
383 TEST(ProximityAuthClientImplTest
,
384 OnMessageReceived_RemoteStatusUpdate_Invalid
) {
386 MockClientObserver
observer(&client
);
388 // Receive a status update message that's missing all the data.
389 EXPECT_CALL(observer
, OnRemoteStatusUpdate(_
)).Times(0);
390 client
.GetFakeConnection()->ReceiveMessageWithPayload(
391 "{\"type\":\"status_update\"}, but encoded");
394 TEST(ProximityAuthClientImplTest
, OnMessageReceived_RemoteStatusUpdate_Valid
) {
396 MockClientObserver
observer(&client
);
398 EXPECT_CALL(observer
,
399 OnRemoteStatusUpdate(
400 AllOf(Field(&RemoteStatusUpdate::user_presence
, USER_PRESENT
),
401 Field(&RemoteStatusUpdate::secure_screen_lock_state
,
402 SECURE_SCREEN_LOCK_ENABLED
),
403 Field(&RemoteStatusUpdate::trust_agent_state
,
404 TRUST_AGENT_UNSUPPORTED
))));
405 client
.GetFakeConnection()->ReceiveMessageWithPayload(
407 "\"type\":\"status_update\","
408 "\"user_presence\":\"present\","
409 "\"secure_screen_lock\":\"enabled\","
410 "\"trust_agent\":\"unsupported\""
414 TEST(ProximityAuthClientImplTest
, OnMessageReceived_InvalidJSON
) {
416 StrictMock
<MockClientObserver
> observer(&client
);
417 client
.RequestUnlock();
418 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
420 // The StrictMock will verify that no observer methods are called.
421 client
.GetFakeConnection()->ReceiveMessageWithPayload(
422 "Not JSON, but encoded");
425 TEST(ProximityAuthClientImplTest
, OnMessageReceived_MissingTypeField
) {
427 StrictMock
<MockClientObserver
> observer(&client
);
428 client
.RequestUnlock();
429 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
431 // The StrictMock will verify that no observer methods are called.
432 client
.GetFakeConnection()->ReceiveMessageWithPayload(
433 "{\"some key that's not 'type'\":\"some value\"}, but encoded");
436 TEST(ProximityAuthClientImplTest
, OnMessageReceived_UnexpectedReply
) {
438 StrictMock
<MockClientObserver
> observer(&client
);
440 // The StrictMock will verify that no observer methods are called.
441 client
.GetFakeConnection()->ReceiveMessageWithPayload(
442 "{\"type\":\"unlock_response\"}, but encoded");
445 TEST(ProximityAuthClientImplTest
,
446 OnMessageReceived_MismatchedReply_UnlockInReplyToDecrypt
) {
448 StrictMock
<MockClientObserver
> observer(&client
);
450 client
.RequestDecryption(kChallenge
);
451 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
453 // The StrictMock will verify that no observer methods are called.
454 client
.GetFakeConnection()->ReceiveMessageWithPayload(
455 "{\"type\":\"unlock_response\"}, but encoded");
458 TEST(ProximityAuthClientImplTest
,
459 OnMessageReceived_MismatchedReply_DecryptInReplyToUnlock
) {
461 StrictMock
<MockClientObserver
> observer(&client
);
463 client
.RequestUnlock();
464 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
466 // The StrictMock will verify that no observer methods are called.
467 client
.GetFakeConnection()->ReceiveMessageWithPayload(
469 "\"type\":\"decrypt_response\","
470 "\"data\":\"YSB3aW5uZXIgaXMgeW91\""
474 TEST(ProximityAuthClientImplTest
, BuffersMessages_WhileSending
) {
476 MockClientObserver
observer(&client
);
478 // Initiate a decryption request, and then initiate an unlock request before
479 // the decryption request is even finished sending.
480 client
.RequestDecryption(kChallenge
);
481 client
.RequestUnlock();
483 EXPECT_CALL(observer
, OnDecryptResponseProxy(nullptr));
484 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(false);
486 EXPECT_CALL(observer
, OnUnlockResponse(false));
487 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(false);
490 TEST(ProximityAuthClientImplTest
, BuffersMessages_WhileAwaitingReply
) {
492 MockClientObserver
observer(&client
);
494 // Initiate a decryption request, and allow the message to be sent.
495 client
.RequestDecryption(kChallenge
);
496 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(true);
498 // At this point, the client is awaiting a reply to the decryption message.
499 // While it's waiting, initiate an unlock request.
500 client
.RequestUnlock();
502 // Now simulate a response arriving for the original decryption request.
503 EXPECT_CALL(observer
, OnDecryptResponseProxy(Pointee(Eq("a winner is you"))));
504 client
.GetFakeConnection()->ReceiveMessageWithPayload(
506 "\"type\":\"decrypt_response\","
507 "\"data\":\"YSB3aW5uZXIgaXMgeW91\""
510 // The unlock request should have remained buffered, and should only now be
512 EXPECT_CALL(observer
, OnUnlockResponse(false));
513 client
.GetFakeConnection()->FinishSendingMessageWithSuccess(false);
516 } // namespace proximity_auth