blob: 31cd5f69070e46b5c1587b58c28575df303eeb0c [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/components/proximity_auth/webui/proximity_auth_webui_handler.h"
#include <algorithm>
#include <memory>
#include <sstream>
#include <utility>
#include "base/base64url.h"
#include "base/bind.h"
#include "base/i18n/time_formatting.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/default_clock.h"
#include "base/time/default_tick_clock.h"
#include "base/values.h"
#include "chromeos/chromeos_features.h"
#include "chromeos/components/proximity_auth/logging/logging.h"
#include "chromeos/components/proximity_auth/messenger.h"
#include "chromeos/components/proximity_auth/remote_device_life_cycle_impl.h"
#include "chromeos/components/proximity_auth/remote_status_update.h"
#include "components/cryptauth/software_feature_state.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_ui.h"
#include "device/bluetooth/bluetooth_uuid.h"
namespace proximity_auth {
namespace {
constexpr const cryptauth::SoftwareFeature kAllSoftareFeatures[] = {
cryptauth::SoftwareFeature::BETTER_TOGETHER_HOST,
cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
cryptauth::SoftwareFeature::EASY_UNLOCK_HOST,
cryptauth::SoftwareFeature::EASY_UNLOCK_CLIENT,
cryptauth::SoftwareFeature::MAGIC_TETHER_HOST,
cryptauth::SoftwareFeature::MAGIC_TETHER_CLIENT,
cryptauth::SoftwareFeature::SMS_CONNECT_HOST,
cryptauth::SoftwareFeature::SMS_CONNECT_CLIENT};
// Keys in the JSON representation of a log message.
const char kLogMessageTextKey[] = "text";
const char kLogMessageTimeKey[] = "time";
const char kLogMessageFileKey[] = "file";
const char kLogMessageLineKey[] = "line";
const char kLogMessageSeverityKey[] = "severity";
// Keys in the JSON representation of a SyncState object for enrollment or
// device sync.
const char kSyncStateLastSuccessTime[] = "lastSuccessTime";
const char kSyncStateNextRefreshTime[] = "nextRefreshTime";
const char kSyncStateRecoveringFromFailure[] = "recoveringFromFailure";
const char kSyncStateOperationInProgress[] = "operationInProgress";
// Converts |log_message| to a raw dictionary value used as a JSON argument to
// JavaScript functions.
std::unique_ptr<base::DictionaryValue> LogMessageToDictionary(
const LogBuffer::LogMessage& log_message) {
std::unique_ptr<base::DictionaryValue> dictionary(
new base::DictionaryValue());
dictionary->SetString(kLogMessageTextKey, log_message.text);
dictionary->SetString(
kLogMessageTimeKey,
base::TimeFormatTimeOfDayWithMilliseconds(log_message.time));
dictionary->SetString(kLogMessageFileKey, log_message.file);
dictionary->SetInteger(kLogMessageLineKey, log_message.line);
dictionary->SetInteger(kLogMessageSeverityKey,
static_cast<int>(log_message.severity));
return dictionary;
}
// Keys in the JSON representation of an ExternalDeviceInfo proto.
const char kExternalDevicePublicKey[] = "publicKey";
const char kExternalDevicePublicKeyTruncated[] = "publicKeyTruncated";
const char kExternalDeviceFriendlyName[] = "friendlyDeviceName";
const char kExternalDeviceUnlockKey[] = "unlockKey";
const char kExternalDeviceMobileHotspot[] = "hasMobileHotspot";
const char kExternalDeviceConnectionStatus[] = "connectionStatus";
const char kExternalDeviceFeatureStates[] = "featureStates";
const char kExternalDeviceRemoteState[] = "remoteState";
// The possible values of the |kExternalDeviceConnectionStatus| field.
const char kExternalDeviceConnected[] = "connected";
const char kExternalDeviceDisconnected[] = "disconnected";
const char kExternalDeviceConnecting[] = "connecting";
// Creates a SyncState JSON object that can be passed to the WebUI.
std::unique_ptr<base::DictionaryValue> CreateSyncStateDictionary(
double last_success_time,
double next_refresh_time,
bool is_recovering_from_failure,
bool is_enrollment_in_progress) {
std::unique_ptr<base::DictionaryValue> sync_state(
new base::DictionaryValue());
sync_state->SetDouble(kSyncStateLastSuccessTime, last_success_time);
sync_state->SetDouble(kSyncStateNextRefreshTime, next_refresh_time);
sync_state->SetBoolean(kSyncStateRecoveringFromFailure,
is_recovering_from_failure);
sync_state->SetBoolean(kSyncStateOperationInProgress,
is_enrollment_in_progress);
return sync_state;
}
std::string GenerateFeaturesString(const cryptauth::RemoteDeviceRef& device) {
std::stringstream ss;
ss << "{";
bool logged_feature = false;
for (const auto& software_feature : kAllSoftareFeatures) {
cryptauth::SoftwareFeatureState state =
device.GetSoftwareFeatureState(software_feature);
// Only log features with values.
if (state == cryptauth::SoftwareFeatureState::kNotSupported)
continue;
logged_feature = true;
ss << software_feature << ": " << state << ", ";
}
if (logged_feature)
ss.seekp(-2, ss.cur); // Remove last ", " from the stream.
ss << "}";
return ss.str();
}
} // namespace
ProximityAuthWebUIHandler::ProximityAuthWebUIHandler(
chromeos::device_sync::DeviceSyncClient* device_sync_client,
chromeos::secure_channel::SecureChannelClient* secure_channel_client)
: device_sync_client_(device_sync_client),
secure_channel_client_(secure_channel_client),
web_contents_initialized_(false),
weak_ptr_factory_(this) {}
ProximityAuthWebUIHandler::~ProximityAuthWebUIHandler() {
LogBuffer::GetInstance()->RemoveObserver(this);
device_sync_client_->RemoveObserver(this);
}
void ProximityAuthWebUIHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"onWebContentsInitialized",
base::BindRepeating(&ProximityAuthWebUIHandler::OnWebContentsInitialized,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"clearLogBuffer",
base::BindRepeating(&ProximityAuthWebUIHandler::ClearLogBuffer,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getLogMessages",
base::BindRepeating(&ProximityAuthWebUIHandler::GetLogMessages,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"toggleUnlockKey",
base::BindRepeating(&ProximityAuthWebUIHandler::ToggleUnlockKey,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"findEligibleUnlockDevices",
base::BindRepeating(&ProximityAuthWebUIHandler::FindEligibleUnlockDevices,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getLocalState",
base::BindRepeating(&ProximityAuthWebUIHandler::GetLocalState,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"forceEnrollment",
base::BindRepeating(&ProximityAuthWebUIHandler::ForceEnrollment,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"forceDeviceSync",
base::BindRepeating(&ProximityAuthWebUIHandler::ForceDeviceSync,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"toggleConnection",
base::BindRepeating(&ProximityAuthWebUIHandler::ToggleConnection,
base::Unretained(this)));
}
void ProximityAuthWebUIHandler::OnLogMessageAdded(
const LogBuffer::LogMessage& log_message) {
std::unique_ptr<base::DictionaryValue> dictionary =
LogMessageToDictionary(log_message);
web_ui()->CallJavascriptFunctionUnsafe("LogBufferInterface.onLogMessageAdded",
*dictionary);
}
void ProximityAuthWebUIHandler::OnLogBufferCleared() {
web_ui()->CallJavascriptFunctionUnsafe(
"LogBufferInterface.onLogBufferCleared");
}
void ProximityAuthWebUIHandler::OnEnrollmentFinished() {
// OnGetDebugInfo() will call NotifyOnEnrollmentFinished() with the enrollment
// state info.
enrollment_update_waiting_for_debug_info_ = true;
device_sync_client_->GetDebugInfo(
base::BindOnce(&ProximityAuthWebUIHandler::OnGetDebugInfo,
weak_ptr_factory_.GetWeakPtr()));
}
void ProximityAuthWebUIHandler::OnNewDevicesSynced() {
// OnGetDebugInfo() will call NotifyOnSyncFinished() with the device sync
// state info.
sync_update_waiting_for_debug_info_ = true;
device_sync_client_->GetDebugInfo(
base::BindOnce(&ProximityAuthWebUIHandler::OnGetDebugInfo,
weak_ptr_factory_.GetWeakPtr()));
}
void ProximityAuthWebUIHandler::OnWebContentsInitialized(
const base::ListValue* args) {
if (!web_contents_initialized_) {
device_sync_client_->AddObserver(this);
LogBuffer::GetInstance()->AddObserver(this);
web_contents_initialized_ = true;
}
}
void ProximityAuthWebUIHandler::GetLogMessages(const base::ListValue* args) {
base::ListValue json_logs;
for (const auto& log : *LogBuffer::GetInstance()->logs()) {
json_logs.Append(LogMessageToDictionary(log));
}
web_ui()->CallJavascriptFunctionUnsafe("LogBufferInterface.onGotLogMessages",
json_logs);
}
void ProximityAuthWebUIHandler::ClearLogBuffer(const base::ListValue* args) {
// The OnLogBufferCleared() observer function will be called after the buffer
// is cleared.
LogBuffer::GetInstance()->Clear();
}
void ProximityAuthWebUIHandler::ToggleUnlockKey(const base::ListValue* args) {
std::string public_key_b64, public_key;
bool make_unlock_key;
if (args->GetSize() != 2 || !args->GetString(0, &public_key_b64) ||
!args->GetBoolean(1, &make_unlock_key) ||
!base::Base64UrlDecode(public_key_b64,
base::Base64UrlDecodePolicy::REQUIRE_PADDING,
&public_key)) {
PA_LOG(ERROR) << "Invalid arguments to toggleUnlockKey";
return;
}
device_sync_client_->SetSoftwareFeatureState(
public_key, cryptauth::SoftwareFeature::EASY_UNLOCK_HOST,
true /* enabled */, true /* is_exclusive */,
base::BindOnce(&ProximityAuthWebUIHandler::OnSetSoftwareFeatureState,
weak_ptr_factory_.GetWeakPtr(), public_key));
}
void ProximityAuthWebUIHandler::FindEligibleUnlockDevices(
const base::ListValue* args) {
device_sync_client_->FindEligibleDevices(
cryptauth::SoftwareFeature::EASY_UNLOCK_HOST,
base::BindOnce(&ProximityAuthWebUIHandler::OnFindEligibleDevices,
weak_ptr_factory_.GetWeakPtr()));
}
void ProximityAuthWebUIHandler::ForceEnrollment(const base::ListValue* args) {
device_sync_client_->ForceEnrollmentNow(
base::BindOnce(&ProximityAuthWebUIHandler::OnForceEnrollmentNow,
weak_ptr_factory_.GetWeakPtr()));
}
void ProximityAuthWebUIHandler::ForceDeviceSync(const base::ListValue* args) {
device_sync_client_->ForceSyncNow(
base::BindOnce(&ProximityAuthWebUIHandler::OnForceSyncNow,
weak_ptr_factory_.GetWeakPtr()));
}
void ProximityAuthWebUIHandler::ToggleConnection(const base::ListValue* args) {
std::string b64_public_key;
std::string public_key;
if (!args->GetSize() || !args->GetString(0, &b64_public_key) ||
!base::Base64UrlDecode(b64_public_key,
base::Base64UrlDecodePolicy::REQUIRE_PADDING,
&public_key)) {
PA_LOG(ERROR) << "Unlock key (" << b64_public_key << ") not found";
return;
}
std::string selected_device_public_key;
if (selected_remote_device_)
selected_device_public_key = selected_remote_device_->public_key();
for (const auto& remote_device : device_sync_client_->GetSyncedDevices()) {
if (remote_device.public_key() != public_key)
continue;
if (life_cycle_ && selected_device_public_key == public_key) {
CleanUpRemoteDeviceLifeCycle();
return;
}
StartRemoteDeviceLifeCycle(remote_device);
}
}
void ProximityAuthWebUIHandler::GetLocalState(const base::ListValue* args) {
// OnGetDebugInfo() will call NotifyGotLocalState() with the enrollment and
// device sync state info.
get_local_state_update_waiting_for_debug_info_ = true;
device_sync_client_->GetDebugInfo(
base::BindOnce(&ProximityAuthWebUIHandler::OnGetDebugInfo,
weak_ptr_factory_.GetWeakPtr()));
}
std::unique_ptr<base::Value>
ProximityAuthWebUIHandler::GetTruncatedLocalDeviceId() {
return std::make_unique<base::Value>(
device_sync_client_->GetLocalDeviceMetadata()
->GetTruncatedDeviceIdForLogs());
}
std::unique_ptr<base::ListValue>
ProximityAuthWebUIHandler::GetRemoteDevicesList() {
std::unique_ptr<base::ListValue> devices_list_value(new base::ListValue());
for (const auto& remote_device : device_sync_client_->GetSyncedDevices())
devices_list_value->Append(RemoteDeviceToDictionary(remote_device));
return devices_list_value;
}
void ProximityAuthWebUIHandler::StartRemoteDeviceLifeCycle(
cryptauth::RemoteDeviceRef remote_device) {
base::Optional<cryptauth::RemoteDeviceRef> local_device;
local_device = device_sync_client_->GetLocalDeviceMetadata();
selected_remote_device_ = remote_device;
life_cycle_.reset(new RemoteDeviceLifeCycleImpl(
*selected_remote_device_, local_device, secure_channel_client_));
life_cycle_->AddObserver(this);
life_cycle_->Start();
}
void ProximityAuthWebUIHandler::CleanUpRemoteDeviceLifeCycle() {
if (selected_remote_device_) {
PA_LOG(VERBOSE) << "Cleaning up connection to "
<< selected_remote_device_->name();
}
life_cycle_.reset();
selected_remote_device_ = base::nullopt;
last_remote_status_update_.reset();
web_ui()->CallJavascriptFunctionUnsafe(
"LocalStateInterface.onRemoteDevicesChanged", *GetRemoteDevicesList());
}
std::unique_ptr<base::DictionaryValue>
ProximityAuthWebUIHandler::RemoteDeviceToDictionary(
const cryptauth::RemoteDeviceRef& remote_device) {
// Set the fields in the ExternalDeviceInfo proto.
std::unique_ptr<base::DictionaryValue> dictionary(
new base::DictionaryValue());
dictionary->SetString(kExternalDevicePublicKey, remote_device.GetDeviceId());
dictionary->SetString(kExternalDevicePublicKeyTruncated,
remote_device.GetTruncatedDeviceIdForLogs());
dictionary->SetString(kExternalDeviceFriendlyName, remote_device.name());
dictionary->SetBoolean(kExternalDeviceUnlockKey,
remote_device.GetSoftwareFeatureState(
cryptauth::SoftwareFeature::EASY_UNLOCK_HOST) ==
cryptauth::SoftwareFeatureState::kEnabled);
dictionary->SetBoolean(kExternalDeviceMobileHotspot,
remote_device.GetSoftwareFeatureState(
cryptauth::SoftwareFeature::MAGIC_TETHER_HOST) ==
cryptauth::SoftwareFeatureState::kSupported);
dictionary->SetString(kExternalDeviceConnectionStatus,
kExternalDeviceDisconnected);
dictionary->SetString(kExternalDeviceFeatureStates,
GenerateFeaturesString(remote_device));
// TODO(crbug.com/852836): Add kExternalDeviceIsArcPlusPlusEnrollment and
// kExternalDeviceIsPixelPhone values to the dictionary once RemoteDevice
// carries those relevant fields.
std::string selected_device_public_key;
if (selected_remote_device_)
selected_device_public_key = selected_remote_device_->public_key();
// If it's the selected remote device, combine the already-populated
// dictionary with corresponding local device data (e.g. connection status and
// remote status updates).
if (selected_device_public_key != remote_device.public_key())
return dictionary;
// Fill in the current Bluetooth connection status.
std::string connection_status = kExternalDeviceDisconnected;
if (life_cycle_ &&
life_cycle_->GetState() ==
RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED) {
connection_status = kExternalDeviceConnected;
} else if (life_cycle_) {
connection_status = kExternalDeviceConnecting;
}
dictionary->SetString(kExternalDeviceConnectionStatus, connection_status);
// Fill the remote status dictionary.
if (last_remote_status_update_) {
std::unique_ptr<base::DictionaryValue> status_dictionary(
new base::DictionaryValue());
status_dictionary->SetInteger("userPresent",
last_remote_status_update_->user_presence);
status_dictionary->SetInteger(
"secureScreenLock",
last_remote_status_update_->secure_screen_lock_state);
status_dictionary->SetInteger(
"trustAgent", last_remote_status_update_->trust_agent_state);
dictionary->Set(kExternalDeviceRemoteState, std::move(status_dictionary));
}
return dictionary;
}
void ProximityAuthWebUIHandler::OnLifeCycleStateChanged(
RemoteDeviceLifeCycle::State old_state,
RemoteDeviceLifeCycle::State new_state) {
// Do not re-attempt to find a connection after the first one fails--just
// abort.
if ((old_state != RemoteDeviceLifeCycle::State::STOPPED &&
new_state == RemoteDeviceLifeCycle::State::FINDING_CONNECTION) ||
new_state == RemoteDeviceLifeCycle::State::AUTHENTICATION_FAILED) {
// Clean up the life cycle asynchronously, because we are currently in the
// call stack of |life_cycle_|.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&ProximityAuthWebUIHandler::CleanUpRemoteDeviceLifeCycle,
weak_ptr_factory_.GetWeakPtr()));
} else if (new_state ==
RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED) {
life_cycle_->GetMessenger()->AddObserver(this);
}
web_ui()->CallJavascriptFunctionUnsafe(
"LocalStateInterface.onRemoteDevicesChanged", *GetRemoteDevicesList());
}
void ProximityAuthWebUIHandler::OnRemoteStatusUpdate(
const RemoteStatusUpdate& status_update) {
PA_LOG(VERBOSE) << "Remote status update:"
<< "\n user_presence: "
<< static_cast<int>(status_update.user_presence)
<< "\n secure_screen_lock_state: "
<< static_cast<int>(status_update.secure_screen_lock_state)
<< "\n trust_agent_state: "
<< static_cast<int>(status_update.trust_agent_state);
last_remote_status_update_.reset(new RemoteStatusUpdate(status_update));
std::unique_ptr<base::ListValue> synced_devices = GetRemoteDevicesList();
web_ui()->CallJavascriptFunctionUnsafe(
"LocalStateInterface.onRemoteDevicesChanged", *synced_devices);
}
void ProximityAuthWebUIHandler::OnForceEnrollmentNow(bool success) {
PA_LOG(VERBOSE) << "Force enrollment result: " << success;
}
void ProximityAuthWebUIHandler::OnForceSyncNow(bool success) {
PA_LOG(VERBOSE) << "Force sync result: " << success;
}
void ProximityAuthWebUIHandler::OnSetSoftwareFeatureState(
const std::string public_key,
chromeos::device_sync::mojom::NetworkRequestResult result_code) {
std::string device_id =
cryptauth::RemoteDeviceRef::GenerateDeviceId(public_key);
if (result_code ==
chromeos::device_sync::mojom::NetworkRequestResult::kSuccess) {
PA_LOG(VERBOSE) << "Successfully set SoftwareFeature state for device: "
<< device_id;
} else {
PA_LOG(ERROR) << "Failed to set SoftwareFeature state for device: "
<< device_id << ", error code: " << result_code;
}
}
void ProximityAuthWebUIHandler::OnFindEligibleDevices(
chromeos::device_sync::mojom::NetworkRequestResult result_code,
cryptauth::RemoteDeviceRefList eligible_devices,
cryptauth::RemoteDeviceRefList ineligible_devices) {
if (result_code !=
chromeos::device_sync::mojom::NetworkRequestResult::kSuccess) {
PA_LOG(ERROR) << "Failed to find eligible devices: " << result_code;
return;
}
base::ListValue eligible_devices_list_value;
for (const auto& device : eligible_devices) {
eligible_devices_list_value.Append(RemoteDeviceToDictionary(device));
}
base::ListValue ineligible_devices_list_value;
for (const auto& device : ineligible_devices) {
ineligible_devices_list_value.Append(RemoteDeviceToDictionary(device));
}
PA_LOG(VERBOSE) << "Found " << eligible_devices_list_value.GetSize()
<< " eligible devices and "
<< ineligible_devices_list_value.GetSize()
<< " ineligible devices.";
web_ui()->CallJavascriptFunctionUnsafe(
"CryptAuthInterface.onGotEligibleDevices", eligible_devices_list_value,
ineligible_devices_list_value);
}
void ProximityAuthWebUIHandler::OnGetDebugInfo(
chromeos::device_sync::mojom::DebugInfoPtr debug_info_ptr) {
if (enrollment_update_waiting_for_debug_info_) {
enrollment_update_waiting_for_debug_info_ = false;
NotifyOnEnrollmentFinished(
true /* success */,
CreateSyncStateDictionary(
debug_info_ptr->last_enrollment_time.ToJsTime(),
debug_info_ptr->time_to_next_enrollment_attempt.InMillisecondsF(),
debug_info_ptr->is_recovering_from_enrollment_failure,
debug_info_ptr->is_enrollment_in_progress));
}
if (sync_update_waiting_for_debug_info_) {
sync_update_waiting_for_debug_info_ = false;
NotifyOnSyncFinished(
true /* was_sync_successful */, true /* changed */,
CreateSyncStateDictionary(
debug_info_ptr->last_sync_time.ToJsTime(),
debug_info_ptr->time_to_next_sync_attempt.InMillisecondsF(),
debug_info_ptr->is_recovering_from_sync_failure,
debug_info_ptr->is_sync_in_progress));
}
if (get_local_state_update_waiting_for_debug_info_) {
get_local_state_update_waiting_for_debug_info_ = false;
NotifyGotLocalState(
GetTruncatedLocalDeviceId(),
CreateSyncStateDictionary(
debug_info_ptr->last_enrollment_time.ToJsTime(),
debug_info_ptr->time_to_next_enrollment_attempt.InMillisecondsF(),
debug_info_ptr->is_recovering_from_enrollment_failure,
debug_info_ptr->is_enrollment_in_progress),
CreateSyncStateDictionary(
debug_info_ptr->last_sync_time.ToJsTime(),
debug_info_ptr->time_to_next_sync_attempt.InMillisecondsF(),
debug_info_ptr->is_recovering_from_sync_failure,
debug_info_ptr->is_sync_in_progress),
GetRemoteDevicesList());
}
}
void ProximityAuthWebUIHandler::NotifyOnEnrollmentFinished(
bool success,
std::unique_ptr<base::DictionaryValue> enrollment_state) {
PA_LOG(VERBOSE) << "Enrollment attempt completed with success=" << success
<< ":\n"
<< *enrollment_state;
web_ui()->CallJavascriptFunctionUnsafe(
"LocalStateInterface.onEnrollmentStateChanged", *enrollment_state);
}
void ProximityAuthWebUIHandler::NotifyOnSyncFinished(
bool was_sync_successful,
bool changed,
std::unique_ptr<base::DictionaryValue> device_sync_state) {
PA_LOG(VERBOSE) << "Device sync completed with result=" << was_sync_successful
<< ":\n"
<< *device_sync_state;
web_ui()->CallJavascriptFunctionUnsafe(
"LocalStateInterface.onDeviceSyncStateChanged", *device_sync_state);
if (changed) {
std::unique_ptr<base::ListValue> synced_devices = GetRemoteDevicesList();
PA_LOG(VERBOSE) << "New unlock keys obtained after device sync:\n"
<< *synced_devices;
web_ui()->CallJavascriptFunctionUnsafe(
"LocalStateInterface.onRemoteDevicesChanged", *synced_devices);
}
}
void ProximityAuthWebUIHandler::NotifyGotLocalState(
std::unique_ptr<base::Value> truncated_local_device_id,
std::unique_ptr<base::DictionaryValue> enrollment_state,
std::unique_ptr<base::DictionaryValue> device_sync_state,
std::unique_ptr<base::ListValue> synced_devices) {
PA_LOG(VERBOSE) << "==== Got Local State ====\n"
<< "Device ID (truncated): " << *truncated_local_device_id
<< "\nEnrollment State: \n"
<< *enrollment_state << "Device Sync State: \n"
<< *device_sync_state << "Synced devices: \n"
<< *synced_devices;
web_ui()->CallJavascriptFunctionUnsafe(
"LocalStateInterface.onGotLocalState", *truncated_local_device_id,
*enrollment_state, *device_sync_state, *synced_devices);
}
} // namespace proximity_auth