Roll src/third_party/WebKit d9c6159:8139f33 (svn 201974:201975)
[chromium-blink-merge.git] / components / proximity_auth / webui / proximity_auth_webui_handler.cc
blob905840fc9030a0b8f0e3ec373194ef8870a4adfa
1 // Copyright 2015 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/webui/proximity_auth_webui_handler.h"
7 #include <algorithm>
9 #include "base/bind.h"
10 #include "base/i18n/time_formatting.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/thread_task_runner_handle.h"
13 #include "base/time/default_clock.h"
14 #include "base/time/default_tick_clock.h"
15 #include "base/values.h"
16 #include "components/proximity_auth/ble/bluetooth_low_energy_connection_finder.h"
17 #include "components/proximity_auth/ble/bluetooth_low_energy_device_whitelist.h"
18 #include "components/proximity_auth/ble/pref_names.h"
19 #include "components/proximity_auth/bluetooth_connection_finder.h"
20 #include "components/proximity_auth/bluetooth_throttler_impl.h"
21 #include "components/proximity_auth/bluetooth_util.h"
22 #include "components/proximity_auth/client_impl.h"
23 #include "components/proximity_auth/cryptauth/base64url.h"
24 #include "components/proximity_auth/cryptauth/cryptauth_enrollment_manager.h"
25 #include "components/proximity_auth/cryptauth/proto/cryptauth_api.pb.h"
26 #include "components/proximity_auth/cryptauth/secure_message_delegate.h"
27 #include "components/proximity_auth/device_to_device_authenticator.h"
28 #include "components/proximity_auth/logging/logging.h"
29 #include "components/proximity_auth/remote_status_update.h"
30 #include "components/proximity_auth/secure_context.h"
31 #include "components/proximity_auth/webui/reachable_phone_flow.h"
32 #include "content/public/browser/browser_thread.h"
33 #include "content/public/browser/web_ui.h"
34 #include "device/bluetooth/bluetooth_uuid.h"
36 namespace proximity_auth {
38 namespace {
40 // The UUID of the Smart Lock classic Bluetooth service.
41 const char kClassicBluetoothServiceUUID[] =
42 "704EE561-3782-405A-A14B-2D47A2DDCDDF";
44 // The UUID of the Bluetooth Low Energy service.
45 const char kBLESmartLockServiceUUID[] = "b3b7e28e-a000-3e17-bd86-6e97b9e28c11";
47 // The UUID of the characteristic used to send data to the peripheral.
48 const char kBLEToPeripheralCharUUID[] = "977c6674-1239-4e72-993b-502369b8bb5a";
50 // The UUID of the characteristic used to receive data from the peripheral.
51 const char kBLEFromPeripheralCharUUID[] =
52 "f4b904a2-a030-43b3-98a8-221c536c03cb";
54 // Keys in the JSON representation of a log message.
55 const char kLogMessageTextKey[] = "text";
56 const char kLogMessageTimeKey[] = "time";
57 const char kLogMessageFileKey[] = "file";
58 const char kLogMessageLineKey[] = "line";
59 const char kLogMessageSeverityKey[] = "severity";
61 // Keys in the JSON representation of a SyncState object for enrollment or
62 // device sync.
63 const char kSyncStateLastSuccessTime[] = "lastSuccessTime";
64 const char kSyncStateNextRefreshTime[] = "nextRefreshTime";
65 const char kSyncStateRecoveringFromFailure[] = "recoveringFromFailure";
66 const char kSyncStateOperationInProgress[] = "operationInProgress";
68 // Converts |log_message| to a raw dictionary value used as a JSON argument to
69 // JavaScript functions.
70 scoped_ptr<base::DictionaryValue> LogMessageToDictionary(
71 const LogBuffer::LogMessage& log_message) {
72 scoped_ptr<base::DictionaryValue> dictionary(new base::DictionaryValue());
73 dictionary->SetString(kLogMessageTextKey, log_message.text);
74 dictionary->SetString(
75 kLogMessageTimeKey,
76 base::TimeFormatTimeOfDayWithMilliseconds(log_message.time));
77 dictionary->SetString(kLogMessageFileKey, log_message.file);
78 dictionary->SetInteger(kLogMessageLineKey, log_message.line);
79 dictionary->SetInteger(kLogMessageSeverityKey,
80 static_cast<int>(log_message.severity));
81 return dictionary.Pass();
84 // Keys in the JSON representation of an ExternalDeviceInfo proto.
85 const char kExternalDevicePublicKey[] = "publicKey";
86 const char kExternalDeviceFriendlyName[] = "friendlyDeviceName";
87 const char kExternalDeviceBluetoothAddress[] = "bluetoothAddress";
88 const char kExternalDeviceUnlockKey[] = "unlockKey";
89 const char kExternalDeviceConnectionStatus[] = "connectionStatus";
90 const char kExternalDeviceRemoteState[] = "remoteState";
92 // The possible values of the |kExternalDeviceConnectionStatus| field.
93 const char kExternalDeviceConnected[] = "connected";
94 const char kExternalDeviceDisconnected[] = "disconnected";
95 const char kExternalDeviceConnecting[] = "connecting";
97 // Keys in the JSON representation of an IneligibleDevice proto.
98 const char kIneligibleDeviceReasons[] = "ineligibilityReasons";
100 // Creates a SyncState JSON object that can be passed to the WebUI.
101 scoped_ptr<base::DictionaryValue> CreateSyncStateDictionary(
102 double last_success_time,
103 double next_refresh_time,
104 bool is_recovering_from_failure,
105 bool is_enrollment_in_progress) {
106 scoped_ptr<base::DictionaryValue> sync_state(new base::DictionaryValue());
107 sync_state->SetDouble(kSyncStateLastSuccessTime, last_success_time);
108 sync_state->SetDouble(kSyncStateNextRefreshTime, next_refresh_time);
109 sync_state->SetBoolean(kSyncStateRecoveringFromFailure,
110 is_recovering_from_failure);
111 sync_state->SetBoolean(kSyncStateOperationInProgress,
112 is_enrollment_in_progress);
113 return sync_state;
116 } // namespace
118 ProximityAuthWebUIHandler::ProximityAuthWebUIHandler(
119 ProximityAuthClient* proximity_auth_client)
120 : proximity_auth_client_(proximity_auth_client),
121 web_contents_initialized_(false),
122 weak_ptr_factory_(this) {
123 cryptauth_client_factory_ =
124 proximity_auth_client_->CreateCryptAuthClientFactory();
127 ProximityAuthWebUIHandler::~ProximityAuthWebUIHandler() {
128 LogBuffer::GetInstance()->RemoveObserver(this);
131 void ProximityAuthWebUIHandler::RegisterMessages() {
132 web_ui()->RegisterMessageCallback(
133 "onWebContentsInitialized",
134 base::Bind(&ProximityAuthWebUIHandler::OnWebContentsInitialized,
135 base::Unretained(this)));
137 web_ui()->RegisterMessageCallback(
138 "clearLogBuffer", base::Bind(&ProximityAuthWebUIHandler::ClearLogBuffer,
139 base::Unretained(this)));
141 web_ui()->RegisterMessageCallback(
142 "getLogMessages", base::Bind(&ProximityAuthWebUIHandler::GetLogMessages,
143 base::Unretained(this)));
145 web_ui()->RegisterMessageCallback(
146 "toggleUnlockKey", base::Bind(&ProximityAuthWebUIHandler::ToggleUnlockKey,
147 base::Unretained(this)));
149 web_ui()->RegisterMessageCallback(
150 "findEligibleUnlockDevices",
151 base::Bind(&ProximityAuthWebUIHandler::FindEligibleUnlockDevices,
152 base::Unretained(this)));
154 web_ui()->RegisterMessageCallback(
155 "findReachableDevices",
156 base::Bind(&ProximityAuthWebUIHandler::FindReachableDevices,
157 base::Unretained(this)));
159 web_ui()->RegisterMessageCallback(
160 "getLocalState", base::Bind(&ProximityAuthWebUIHandler::GetLocalState,
161 base::Unretained(this)));
163 web_ui()->RegisterMessageCallback(
164 "forceEnrollment", base::Bind(&ProximityAuthWebUIHandler::ForceEnrollment,
165 base::Unretained(this)));
167 web_ui()->RegisterMessageCallback(
168 "forceDeviceSync", base::Bind(&ProximityAuthWebUIHandler::ForceDeviceSync,
169 base::Unretained(this)));
171 web_ui()->RegisterMessageCallback(
172 "toggleConnection",
173 base::Bind(&ProximityAuthWebUIHandler::ToggleConnection,
174 base::Unretained(this)));
177 void ProximityAuthWebUIHandler::OnLogMessageAdded(
178 const LogBuffer::LogMessage& log_message) {
179 scoped_ptr<base::DictionaryValue> dictionary =
180 LogMessageToDictionary(log_message);
181 web_ui()->CallJavascriptFunction("LogBufferInterface.onLogMessageAdded",
182 *dictionary);
185 void ProximityAuthWebUIHandler::OnLogBufferCleared() {
186 web_ui()->CallJavascriptFunction("LogBufferInterface.onLogBufferCleared");
189 void ProximityAuthWebUIHandler::OnEnrollmentStarted() {
190 web_ui()->CallJavascriptFunction(
191 "LocalStateInterface.onEnrollmentStateChanged",
192 *GetEnrollmentStateDictionary());
195 void ProximityAuthWebUIHandler::OnEnrollmentFinished(bool success) {
196 scoped_ptr<base::DictionaryValue> enrollment_state =
197 GetEnrollmentStateDictionary();
198 PA_LOG(INFO) << "Enrollment attempt completed with success=" << success
199 << ":\n" << *enrollment_state;
200 web_ui()->CallJavascriptFunction(
201 "LocalStateInterface.onEnrollmentStateChanged", *enrollment_state);
204 void ProximityAuthWebUIHandler::OnSyncStarted() {
205 web_ui()->CallJavascriptFunction(
206 "LocalStateInterface.onDeviceSyncStateChanged",
207 *GetDeviceSyncStateDictionary());
210 void ProximityAuthWebUIHandler::OnSyncFinished(
211 CryptAuthDeviceManager::SyncResult sync_result,
212 CryptAuthDeviceManager::DeviceChangeResult device_change_result) {
213 scoped_ptr<base::DictionaryValue> device_sync_state =
214 GetDeviceSyncStateDictionary();
215 PA_LOG(INFO) << "Device sync completed with result="
216 << static_cast<int>(sync_result) << ":\n" << *device_sync_state;
217 web_ui()->CallJavascriptFunction(
218 "LocalStateInterface.onDeviceSyncStateChanged", *device_sync_state);
220 if (device_change_result ==
221 CryptAuthDeviceManager::DeviceChangeResult::CHANGED) {
222 scoped_ptr<base::ListValue> unlock_keys = GetUnlockKeysList();
223 PA_LOG(INFO) << "New unlock keys obtained after device sync:\n"
224 << *unlock_keys;
225 web_ui()->CallJavascriptFunction("LocalStateInterface.onUnlockKeysChanged",
226 *unlock_keys);
230 void ProximityAuthWebUIHandler::OnWebContentsInitialized(
231 const base::ListValue* args) {
232 if (!web_contents_initialized_) {
233 CryptAuthEnrollmentManager* enrollment_manager =
234 proximity_auth_client_->GetCryptAuthEnrollmentManager();
235 if (enrollment_manager)
236 enrollment_manager->AddObserver(this);
238 CryptAuthDeviceManager* device_manager =
239 proximity_auth_client_->GetCryptAuthDeviceManager();
240 if (device_manager)
241 device_manager->AddObserver(this);
243 LogBuffer::GetInstance()->AddObserver(this);
245 web_contents_initialized_ = true;
249 void ProximityAuthWebUIHandler::GetLogMessages(const base::ListValue* args) {
250 base::ListValue json_logs;
251 for (const auto& log : *LogBuffer::GetInstance()->logs()) {
252 json_logs.Append(LogMessageToDictionary(log).release());
254 web_ui()->CallJavascriptFunction("LogBufferInterface.onGotLogMessages",
255 json_logs);
258 void ProximityAuthWebUIHandler::ClearLogBuffer(const base::ListValue* args) {
259 // The OnLogBufferCleared() observer function will be called after the buffer
260 // is cleared.
261 LogBuffer::GetInstance()->Clear();
264 void ProximityAuthWebUIHandler::ToggleUnlockKey(const base::ListValue* args) {
265 std::string public_key_b64, public_key;
266 bool make_unlock_key;
267 if (args->GetSize() != 2 || !args->GetString(0, &public_key_b64) ||
268 !args->GetBoolean(1, &make_unlock_key) ||
269 !Base64UrlDecode(public_key_b64, &public_key)) {
270 PA_LOG(ERROR) << "Invalid arguments to toggleUnlockKey";
271 return;
274 cryptauth::ToggleEasyUnlockRequest request;
275 request.set_enable(make_unlock_key);
276 request.set_public_key(public_key);
277 *(request.mutable_device_classifier()) =
278 proximity_auth_client_->GetDeviceClassifier();
280 PA_LOG(INFO) << "Toggling unlock key:\n"
281 << " public_key: " << public_key_b64 << "\n"
282 << " make_unlock_key: " << make_unlock_key;
283 cryptauth_client_ = cryptauth_client_factory_->CreateInstance();
284 cryptauth_client_->ToggleEasyUnlock(
285 request, base::Bind(&ProximityAuthWebUIHandler::OnEasyUnlockToggled,
286 weak_ptr_factory_.GetWeakPtr()),
287 base::Bind(&ProximityAuthWebUIHandler::OnCryptAuthClientError,
288 weak_ptr_factory_.GetWeakPtr()));
291 void ProximityAuthWebUIHandler::FindEligibleUnlockDevices(
292 const base::ListValue* args) {
293 cryptauth_client_ = cryptauth_client_factory_->CreateInstance();
295 cryptauth::FindEligibleUnlockDevicesRequest request;
296 *(request.mutable_device_classifier()) =
297 proximity_auth_client_->GetDeviceClassifier();
298 cryptauth_client_->FindEligibleUnlockDevices(
299 request,
300 base::Bind(&ProximityAuthWebUIHandler::OnFoundEligibleUnlockDevices,
301 weak_ptr_factory_.GetWeakPtr()),
302 base::Bind(&ProximityAuthWebUIHandler::OnCryptAuthClientError,
303 weak_ptr_factory_.GetWeakPtr()));
306 void ProximityAuthWebUIHandler::FindReachableDevices(
307 const base::ListValue* args) {
308 if (reachable_phone_flow_) {
309 PA_LOG(INFO) << "Waiting for existing ReachablePhoneFlow to finish.";
310 return;
313 reachable_phone_flow_.reset(
314 new ReachablePhoneFlow(cryptauth_client_factory_.get()));
315 reachable_phone_flow_->Run(
316 base::Bind(&ProximityAuthWebUIHandler::OnReachablePhonesFound,
317 weak_ptr_factory_.GetWeakPtr()));
320 void ProximityAuthWebUIHandler::ForceEnrollment(const base::ListValue* args) {
321 CryptAuthEnrollmentManager* enrollment_manager =
322 proximity_auth_client_->GetCryptAuthEnrollmentManager();
323 if (enrollment_manager) {
324 enrollment_manager->ForceEnrollmentNow(cryptauth::INVOCATION_REASON_MANUAL);
328 void ProximityAuthWebUIHandler::ForceDeviceSync(const base::ListValue* args) {
329 CryptAuthDeviceManager* device_manager =
330 proximity_auth_client_->GetCryptAuthDeviceManager();
331 if (device_manager)
332 device_manager->ForceSyncNow(cryptauth::INVOCATION_REASON_MANUAL);
335 void ProximityAuthWebUIHandler::ToggleConnection(const base::ListValue* args) {
336 CryptAuthEnrollmentManager* enrollment_manager =
337 proximity_auth_client_->GetCryptAuthEnrollmentManager();
338 CryptAuthDeviceManager* device_manager =
339 proximity_auth_client_->GetCryptAuthDeviceManager();
340 if (!enrollment_manager || !device_manager)
341 return;
343 std::string b64_public_key;
344 std::string public_key;
345 if (!enrollment_manager || !device_manager || !args->GetSize() ||
346 !args->GetString(0, &b64_public_key) ||
347 !Base64UrlDecode(b64_public_key, &public_key)) {
348 return;
351 Connection* connection = GetConnection();
352 for (const auto& unlock_key : device_manager->unlock_keys()) {
353 if (unlock_key.public_key() == public_key) {
354 // Check if there is an existing connection to disconnect from first.
355 if (connection && connection->IsConnected() &&
356 selected_remote_device_.public_key == public_key) {
357 PA_LOG(INFO) << "Disconnecting from "
358 << unlock_key.friendly_device_name() << "["
359 << unlock_key.bluetooth_address() << "]";
360 connection->Disconnect();
361 return;
364 // Derive the PSK before connecting to the device.
365 PA_LOG(INFO) << "Deriving PSK before connecting to "
366 << unlock_key.friendly_device_name();
367 secure_message_delegate_ =
368 proximity_auth_client_->CreateSecureMessageDelegate();
369 secure_message_delegate_->DeriveKey(
370 enrollment_manager->GetUserPrivateKey(), unlock_key.public_key(),
371 base::Bind(&ProximityAuthWebUIHandler::OnPSKDerived,
372 weak_ptr_factory_.GetWeakPtr(), unlock_key));
374 return;
378 PA_LOG(ERROR) << "Unlock key (" << b64_public_key << ") not found";
381 void ProximityAuthWebUIHandler::OnCryptAuthClientError(
382 const std::string& error_message) {
383 PA_LOG(WARNING) << "CryptAuth request failed: " << error_message;
384 base::StringValue error_string(error_message);
385 web_ui()->CallJavascriptFunction("CryptAuthInterface.onError", error_string);
388 void ProximityAuthWebUIHandler::OnEasyUnlockToggled(
389 const cryptauth::ToggleEasyUnlockResponse& response) {
390 web_ui()->CallJavascriptFunction("CryptAuthInterface.onUnlockKeyToggled");
391 // TODO(tengs): Update the local state to reflect the toggle.
394 void ProximityAuthWebUIHandler::OnFoundEligibleUnlockDevices(
395 const cryptauth::FindEligibleUnlockDevicesResponse& response) {
396 base::ListValue eligible_devices;
397 for (const auto& external_device : response.eligible_devices()) {
398 eligible_devices.Append(ExternalDeviceInfoToDictionary(external_device));
401 base::ListValue ineligible_devices;
402 for (const auto& ineligible_device : response.ineligible_devices()) {
403 ineligible_devices.Append(IneligibleDeviceToDictionary(ineligible_device));
406 PA_LOG(INFO) << "Found " << eligible_devices.GetSize()
407 << " eligible devices and " << ineligible_devices.GetSize()
408 << " ineligible devices.";
409 web_ui()->CallJavascriptFunction("CryptAuthInterface.onGotEligibleDevices",
410 eligible_devices, ineligible_devices);
413 void ProximityAuthWebUIHandler::OnReachablePhonesFound(
414 const std::vector<cryptauth::ExternalDeviceInfo>& reachable_phones) {
415 reachable_phone_flow_.reset();
416 base::ListValue device_list;
417 for (const auto& external_device : reachable_phones) {
418 device_list.Append(ExternalDeviceInfoToDictionary(external_device));
420 web_ui()->CallJavascriptFunction("CryptAuthInterface.onGotReachableDevices",
421 device_list);
424 void ProximityAuthWebUIHandler::GetLocalState(const base::ListValue* args) {
425 scoped_ptr<base::DictionaryValue> enrollment_state =
426 GetEnrollmentStateDictionary();
427 scoped_ptr<base::DictionaryValue> device_sync_state =
428 GetDeviceSyncStateDictionary();
429 scoped_ptr<base::ListValue> unlock_keys = GetUnlockKeysList();
431 PA_LOG(INFO) << "==== Got Local State ====\n"
432 << "Enrollment State: \n" << *enrollment_state
433 << "Device Sync State: \n" << *device_sync_state
434 << "Unlock Keys: \n" << *unlock_keys;
435 web_ui()->CallJavascriptFunction("LocalStateInterface.onGotLocalState",
436 *enrollment_state, *device_sync_state,
437 *unlock_keys);
440 scoped_ptr<base::DictionaryValue>
441 ProximityAuthWebUIHandler::GetEnrollmentStateDictionary() {
442 CryptAuthEnrollmentManager* enrollment_manager =
443 proximity_auth_client_->GetCryptAuthEnrollmentManager();
444 if (!enrollment_manager)
445 return make_scoped_ptr(new base::DictionaryValue());
447 return CreateSyncStateDictionary(
448 enrollment_manager->GetLastEnrollmentTime().ToJsTime(),
449 enrollment_manager->GetTimeToNextAttempt().InMillisecondsF(),
450 enrollment_manager->IsRecoveringFromFailure(),
451 enrollment_manager->IsEnrollmentInProgress());
454 scoped_ptr<base::DictionaryValue>
455 ProximityAuthWebUIHandler::GetDeviceSyncStateDictionary() {
456 CryptAuthDeviceManager* device_manager =
457 proximity_auth_client_->GetCryptAuthDeviceManager();
458 if (!device_manager)
459 return make_scoped_ptr(new base::DictionaryValue());
461 return CreateSyncStateDictionary(
462 device_manager->GetLastSyncTime().ToJsTime(),
463 device_manager->GetTimeToNextAttempt().InMillisecondsF(),
464 device_manager->IsRecoveringFromFailure(),
465 device_manager->IsSyncInProgress());
468 scoped_ptr<base::ListValue> ProximityAuthWebUIHandler::GetUnlockKeysList() {
469 scoped_ptr<base::ListValue> unlock_keys(new base::ListValue());
470 CryptAuthDeviceManager* device_manager =
471 proximity_auth_client_->GetCryptAuthDeviceManager();
472 if (!device_manager)
473 return unlock_keys;
475 for (const auto& unlock_key : device_manager->unlock_keys()) {
476 unlock_keys->Append(ExternalDeviceInfoToDictionary(unlock_key));
479 return unlock_keys;
482 Connection* ProximityAuthWebUIHandler::GetConnection() {
483 Connection* connection = connection_.get();
484 if (client_) {
485 DCHECK(!connection);
486 connection = client_->connection();
488 return connection;
491 void ProximityAuthWebUIHandler::OnPSKDerived(
492 const cryptauth::ExternalDeviceInfo& unlock_key,
493 const std::string& persistent_symmetric_key) {
494 if (persistent_symmetric_key.empty()) {
495 PA_LOG(ERROR) << "Failed to derive PSK.";
496 return;
499 selected_remote_device_ =
500 RemoteDevice(unlock_key.friendly_device_name(), unlock_key.public_key(),
501 unlock_key.bluetooth_address(), persistent_symmetric_key);
503 // TODO(tengs): We distinguish whether the unlock key uses classic Bluetooth
504 // or BLE based on the presence of the |bluetooth_address| field. However, we
505 // should ideally have a separate field specifying the protocol.
506 if (selected_remote_device_.bluetooth_address.empty())
507 FindBluetoothLowEnergyConnection(selected_remote_device_);
508 else
509 FindBluetoothClassicConnection(selected_remote_device_);
512 void ProximityAuthWebUIHandler::FindBluetoothClassicConnection(
513 const RemoteDevice& remote_device) {
514 PA_LOG(INFO) << "Finding classic Bluetooth device " << remote_device.name
515 << " [" << remote_device.bluetooth_address << "].";
517 // TODO(tengs): Set a timeout to stop the connection finder eventually.
518 connection_finder_.reset(new BluetoothConnectionFinder(
519 remote_device, device::BluetoothUUID(kClassicBluetoothServiceUUID),
520 base::TimeDelta::FromSeconds(3)));
521 connection_finder_->Find(
522 base::Bind(&ProximityAuthWebUIHandler::OnConnectionFound,
523 weak_ptr_factory_.GetWeakPtr()));
525 web_ui()->CallJavascriptFunction("LocalStateInterface.onUnlockKeysChanged",
526 *GetUnlockKeysList());
529 void ProximityAuthWebUIHandler::FindBluetoothLowEnergyConnection(
530 const RemoteDevice& remote_device) {
531 PrefService* pref_service = proximity_auth_client_->GetPrefService();
532 if (!pref_service->FindPreference(
533 prefs::kBluetoothLowEnergyDeviceWhitelist)) {
534 PA_LOG(ERROR) << "Please enable the BLE experiment in chrome://flags.";
535 return;
538 PA_LOG(INFO) << "Finding Bluetooth Low Energy device " << remote_device.name;
539 if (!bluetooth_throttler_) {
540 bluetooth_throttler_.reset(new BluetoothThrottlerImpl(
541 make_scoped_ptr(new base::DefaultTickClock())));
544 ble_device_whitelist_.reset(new BluetoothLowEnergyDeviceWhitelist(
545 proximity_auth_client_->GetPrefService()));
547 // TODO(tengs): Set a timeout to stop the connection finder eventually.
548 connection_finder_.reset(new BluetoothLowEnergyConnectionFinder(
549 kBLESmartLockServiceUUID, kBLEToPeripheralCharUUID,
550 kBLEFromPeripheralCharUUID, ble_device_whitelist_.get(),
551 bluetooth_throttler_.get(), 3));
552 connection_finder_->Find(
553 base::Bind(&ProximityAuthWebUIHandler::OnConnectionFound,
554 weak_ptr_factory_.GetWeakPtr()));
556 web_ui()->CallJavascriptFunction("LocalStateInterface.onUnlockKeysChanged",
557 *GetUnlockKeysList());
560 void ProximityAuthWebUIHandler::OnAuthenticationResult(
561 Authenticator::Result result,
562 scoped_ptr<SecureContext> secure_context) {
563 secure_context_ = secure_context.Pass();
565 // Create the ClientImpl asynchronously. |client_| registers itself as an
566 // observer of |connection_|, so creating it synchronously would
567 // trigger |OnSendComplete()| as an observer call for |client_|.
568 base::ThreadTaskRunnerHandle::Get()->PostTask(
569 FROM_HERE,
570 base::Bind(&ProximityAuthWebUIHandler::CreateStatusUpdateClient,
571 weak_ptr_factory_.GetWeakPtr()));
574 void ProximityAuthWebUIHandler::OnConnectionFound(
575 scoped_ptr<Connection> connection) {
576 DCHECK(connection->IsConnected());
577 connection_ = connection.Pass();
578 connection_->AddObserver(this);
580 web_ui()->CallJavascriptFunction("LocalStateInterface.onUnlockKeysChanged",
581 *GetUnlockKeysList());
583 // TODO(tengs): Create an authenticator for BLE connections.
584 if (selected_remote_device_.bluetooth_address.empty())
585 return;
587 authenticator_.reset(new DeviceToDeviceAuthenticator(
588 connection_.get(), proximity_auth_client_->GetAccountId(),
589 proximity_auth_client_->CreateSecureMessageDelegate()));
590 authenticator_->Authenticate(
591 base::Bind(&ProximityAuthWebUIHandler::OnAuthenticationResult,
592 weak_ptr_factory_.GetWeakPtr()));
595 void ProximityAuthWebUIHandler::CreateStatusUpdateClient() {
596 client_.reset(new ClientImpl(connection_.Pass(), secure_context_.Pass()));
597 client_->AddObserver(this);
600 scoped_ptr<base::DictionaryValue>
601 ProximityAuthWebUIHandler::ExternalDeviceInfoToDictionary(
602 const cryptauth::ExternalDeviceInfo& device_info) {
603 std::string base64_public_key;
604 Base64UrlEncode(device_info.public_key(), &base64_public_key);
606 // Set the fields in the ExternalDeviceInfo proto.
607 scoped_ptr<base::DictionaryValue> dictionary(new base::DictionaryValue());
608 dictionary->SetString(kExternalDevicePublicKey, base64_public_key);
609 dictionary->SetString(kExternalDeviceFriendlyName,
610 device_info.friendly_device_name());
611 dictionary->SetString(kExternalDeviceBluetoothAddress,
612 device_info.bluetooth_address());
613 dictionary->SetBoolean(kExternalDeviceUnlockKey, device_info.unlock_key());
614 dictionary->SetString(kExternalDeviceConnectionStatus,
615 kExternalDeviceDisconnected);
617 CryptAuthDeviceManager* device_manager =
618 proximity_auth_client_->GetCryptAuthDeviceManager();
619 if (!device_manager)
620 return dictionary;
622 // If |device_info| is a known unlock key, then combine the proto data with
623 // the corresponding local device data (e.g. connection status and remote
624 // status updates).
625 std::string public_key = device_info.public_key();
626 auto iterator = std::find_if(
627 device_manager->unlock_keys().begin(),
628 device_manager->unlock_keys().end(),
629 [&public_key](const cryptauth::ExternalDeviceInfo& unlock_key) {
630 return unlock_key.public_key() == public_key;
633 if (iterator == device_manager->unlock_keys().end() ||
634 selected_remote_device_.public_key != device_info.public_key())
635 return dictionary;
637 // Fill in the current Bluetooth connection status.
638 std::string connection_status = kExternalDeviceDisconnected;
639 Connection* connection = GetConnection();
640 if (connection && connection->IsConnected()) {
641 connection_status = kExternalDeviceConnected;
642 } else if (connection_finder_) {
643 connection_status = kExternalDeviceConnecting;
645 dictionary->SetString(kExternalDeviceConnectionStatus, connection_status);
647 // Fill the remote status dictionary.
648 if (last_remote_status_update_) {
649 scoped_ptr<base::DictionaryValue> status_dictionary(
650 new base::DictionaryValue());
651 status_dictionary->SetInteger("userPresent",
652 last_remote_status_update_->user_presence);
653 status_dictionary->SetInteger(
654 "secureScreenLock",
655 last_remote_status_update_->secure_screen_lock_state);
656 status_dictionary->SetInteger(
657 "trustAgent", last_remote_status_update_->trust_agent_state);
658 dictionary->Set(kExternalDeviceRemoteState, status_dictionary.Pass());
661 return dictionary;
664 scoped_ptr<base::DictionaryValue>
665 ProximityAuthWebUIHandler::IneligibleDeviceToDictionary(
666 const cryptauth::IneligibleDevice& ineligible_device) {
667 scoped_ptr<base::ListValue> ineligibility_reasons(new base::ListValue());
668 for (const std::string& reason : ineligible_device.reasons()) {
669 ineligibility_reasons->AppendString(reason);
672 scoped_ptr<base::DictionaryValue> device_dictionary =
673 ExternalDeviceInfoToDictionary(ineligible_device.device());
674 device_dictionary->Set(kIneligibleDeviceReasons,
675 ineligibility_reasons.Pass());
676 return device_dictionary;
679 void ProximityAuthWebUIHandler::OnConnectionStatusChanged(
680 Connection* connection,
681 Connection::Status old_status,
682 Connection::Status new_status) {
683 PA_LOG(INFO) << "Connection status changed from " << old_status << " to "
684 << new_status;
686 if (new_status == Connection::DISCONNECTED) {
687 last_remote_status_update_.reset();
688 selected_remote_device_ = RemoteDevice();
689 connection_finder_.reset();
692 scoped_ptr<base::ListValue> unlock_keys = GetUnlockKeysList();
693 web_ui()->CallJavascriptFunction("LocalStateInterface.onUnlockKeysChanged",
694 *unlock_keys);
697 void ProximityAuthWebUIHandler::OnMessageReceived(const Connection& connection,
698 const WireMessage& message) {
699 std::string address = connection.remote_device().bluetooth_address;
700 PA_LOG(INFO) << "Message received from " << address;
703 void ProximityAuthWebUIHandler::OnRemoteStatusUpdate(
704 const RemoteStatusUpdate& status_update) {
705 PA_LOG(INFO) << "Remote status update:"
706 << "\n user_presence: "
707 << static_cast<int>(status_update.user_presence)
708 << "\n secure_screen_lock_state: "
709 << static_cast<int>(status_update.secure_screen_lock_state)
710 << "\n trust_agent_state: "
711 << static_cast<int>(status_update.trust_agent_state);
713 last_remote_status_update_.reset(new RemoteStatusUpdate(status_update));
714 scoped_ptr<base::ListValue> unlock_keys = GetUnlockKeysList();
715 web_ui()->CallJavascriptFunction("LocalStateInterface.onUnlockKeysChanged",
716 *unlock_keys);
719 } // namespace proximity_auth