blob: 637008d24493c16dd35040f57a2e66b494eff972 [file] [log] [blame]
// Copyright 2016 The Chromium OS 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 "biod/biometrics_daemon.h"
#include <algorithm>
#include <utility>
#include <base/bind.h>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <brillo/dbus/async_event_sequencer.h>
#include <chromeos/dbus/service_constants.h>
#include "biod/cros_fp_biometrics_manager.h"
#include "biod/power_button_filter.h"
#include "biod/proto_bindings/constants.pb.h"
#include "biod/proto_bindings/messages.pb.h"
namespace biod {
using brillo::dbus_utils::AsyncEventSequencer;
using brillo::dbus_utils::DBusInterface;
using brillo::dbus_utils::DBusObject;
using brillo::dbus_utils::ExportedObjectManager;
using brillo::dbus_utils::ExportedProperty;
using brillo::dbus_utils::ExportedPropertySet;
using dbus::ObjectPath;
namespace dbus_constants {
const int kDbusTimeoutMs = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT;
const char kSessionStateStarted[] = "started";
constexpr char kSessionStateStopped[] = "stopped";
} // namespace dbus_constants
namespace errors {
const char kDomain[] = "biod";
const char kInternalError[] = "internal_error";
const char kInvalidArguments[] = "invalid_arguments";
} // namespace errors
void LogOnSignalConnected(const std::string& interface_name,
const std::string& signal_name,
bool success) {
if (!success)
LOG(ERROR) << "Failed to connect to signal " << signal_name
<< " of interface " << interface_name;
}
BiometricsManagerWrapper::BiometricsManagerWrapper(
std::unique_ptr<BiometricsManager> biometrics_manager,
ExportedObjectManager* object_manager,
ObjectPath object_path,
const AsyncEventSequencer::CompletionAction& completion_callback)
: biometrics_manager_(std::move(biometrics_manager)),
dbus_object_(object_manager, object_manager->GetBus(), object_path),
object_path_(std::move(object_path)),
enroll_session_object_path_(object_path_.value() + "/EnrollSession"),
auth_session_object_path_(object_path_.value() + "/AuthSession") {
CHECK(biometrics_manager_);
biometrics_manager_->SetEnrollScanDoneHandler(base::Bind(
&BiometricsManagerWrapper::OnEnrollScanDone, base::Unretained(this)));
biometrics_manager_->SetAuthScanDoneHandler(base::Bind(
&BiometricsManagerWrapper::OnAuthScanDone, base::Unretained(this)));
biometrics_manager_->SetSessionFailedHandler(base::Bind(
&BiometricsManagerWrapper::OnSessionFailed, base::Unretained(this)));
dbus::ObjectProxy* bus_proxy = object_manager->GetBus()->GetObjectProxy(
dbus::kDBusServiceName, dbus::ObjectPath(dbus::kDBusServicePath));
bus_proxy->ConnectToSignal(
dbus::kDBusInterface, "NameOwnerChanged",
base::Bind(&BiometricsManagerWrapper::OnNameOwnerChanged,
base::Unretained(this)),
base::Bind(&LogOnSignalConnected));
DBusInterface* bio_interface =
dbus_object_.AddOrGetInterface(kBiometricsManagerInterface);
property_type_.SetValue(
static_cast<uint32_t>(biometrics_manager_->GetType()));
bio_interface->AddProperty(kBiometricsManagerBiometricTypeProperty,
&property_type_);
bio_interface->AddSimpleMethodHandlerWithErrorAndMessage(
kBiometricsManagerStartEnrollSessionMethod,
base::Bind(&BiometricsManagerWrapper::StartEnrollSession,
base::Unretained(this)));
bio_interface->AddSimpleMethodHandlerWithError(
kBiometricsManagerGetRecordsForUserMethod,
base::Bind(&BiometricsManagerWrapper::GetRecordsForUser,
base::Unretained(this)));
bio_interface->AddSimpleMethodHandlerWithError(
kBiometricsManagerDestroyAllRecordsMethod,
base::Bind(&BiometricsManagerWrapper::DestroyAllRecords,
base::Unretained(this)));
bio_interface->AddSimpleMethodHandlerWithErrorAndMessage(
kBiometricsManagerStartAuthSessionMethod,
base::Bind(&BiometricsManagerWrapper::StartAuthSession,
base::Unretained(this)));
dbus_object_.RegisterAsync(completion_callback);
RefreshRecordObjects();
}
BiometricsManagerWrapper::RecordWrapper::RecordWrapper(
BiometricsManagerWrapper* biometrics_manager,
std::unique_ptr<BiometricsManager::Record> record,
ExportedObjectManager* object_manager,
const ObjectPath& object_path)
: biometrics_manager_(biometrics_manager),
record_(std::move(record)),
dbus_object_(object_manager, object_manager->GetBus(), object_path),
object_path_(object_path) {
DBusInterface* record_interface =
dbus_object_.AddOrGetInterface(kRecordInterface);
property_label_.SetValue(record_->GetLabel());
record_interface->AddProperty(kRecordLabelProperty, &property_label_);
record_interface->AddSimpleMethodHandlerWithError(
kRecordSetLabelMethod,
base::Bind(&RecordWrapper::SetLabel, base::Unretained(this)));
record_interface->AddSimpleMethodHandlerWithError(
kRecordRemoveMethod,
base::Bind(&RecordWrapper::Remove, base::Unretained(this)));
dbus_object_.RegisterAndBlock();
}
BiometricsManagerWrapper::RecordWrapper::~RecordWrapper() {
dbus_object_.UnregisterAsync();
}
bool BiometricsManagerWrapper::RecordWrapper::SetLabel(
brillo::ErrorPtr* error, const std::string& new_label) {
if (!record_->SetLabel(new_label)) {
*error =
brillo::Error::Create(FROM_HERE, errors::kDomain,
errors::kInternalError, "Failed to set label");
return false;
}
property_label_.SetValue(new_label);
return true;
}
bool BiometricsManagerWrapper::RecordWrapper::Remove(brillo::ErrorPtr* error) {
if (!record_->Remove()) {
*error = brillo::Error::Create(FROM_HERE, errors::kDomain,
errors::kInternalError,
"Failed to remove record");
return false;
}
biometrics_manager_->RefreshRecordObjects();
return true;
}
void BiometricsManagerWrapper::FinalizeEnrollSessionObject() {
enroll_session_owner_.clear();
enroll_session_dbus_object_->UnregisterAsync();
enroll_session_dbus_object_.reset();
}
void BiometricsManagerWrapper::FinalizeAuthSessionObject() {
auth_session_owner_.clear();
auth_session_dbus_object_->UnregisterAsync();
auth_session_dbus_object_.reset();
}
void BiometricsManagerWrapper::OnNameOwnerChanged(dbus::Signal* sig) {
dbus::MessageReader reader(sig);
std::string name, old_owner, new_owner;
if (!reader.PopString(&name) || !reader.PopString(&old_owner) ||
!reader.PopString(&new_owner)) {
LOG(ERROR) << "Received invalid NameOwnerChanged signal";
return;
}
// We are only interested in cases where a name gets dropped from D-Bus.
if (name.empty() || !new_owner.empty())
return;
// If one of the session was owned by the dropped name, the session should
// also be dropped, as there is nobody left to end it explicitly.
if (name == enroll_session_owner_) {
LOG(INFO) << "EnrollSession object owner " << enroll_session_owner_
<< " has died. EnrollSession is canceled automatically.";
if (enroll_session_)
enroll_session_.End();
if (enroll_session_dbus_object_)
FinalizeEnrollSessionObject();
}
if (name == auth_session_owner_) {
LOG(INFO) << "AuthSession object owner " << auth_session_owner_
<< " has died. AuthSession is ended automatically.";
if (auth_session_)
auth_session_.End();
if (auth_session_dbus_object_)
FinalizeAuthSessionObject();
}
}
void BiometricsManagerWrapper::OnEnrollScanDone(
ScanResult scan_result,
const BiometricsManager::EnrollStatus& enroll_status) {
if (!enroll_session_dbus_object_)
return;
dbus::Signal enroll_scan_done_signal(kBiometricsManagerInterface,
kBiometricsManagerEnrollScanDoneSignal);
dbus::MessageWriter writer(&enroll_scan_done_signal);
EnrollScanDone proto;
proto.set_scan_result(scan_result);
proto.set_done(enroll_status.done);
if (enroll_status.percent_complete >= 0) {
proto.set_percent_complete(enroll_status.percent_complete);
}
writer.AppendProtoAsArrayOfBytes(proto);
dbus_object_.SendSignal(&enroll_scan_done_signal);
if (enroll_status.done) {
enroll_session_.End();
FinalizeEnrollSessionObject();
RefreshRecordObjects();
}
}
void BiometricsManagerWrapper::OnAuthScanDone(
ScanResult scan_result, BiometricsManager::AttemptMatches matches) {
if (!auth_session_dbus_object_)
return;
dbus::Signal auth_scan_done_signal(kBiometricsManagerInterface,
kBiometricsManagerAuthScanDoneSignal);
dbus::MessageWriter writer(&auth_scan_done_signal);
writer.AppendUint32(static_cast<uint32_t>(scan_result));
dbus::MessageWriter matches_writer(nullptr);
writer.OpenArray("{sao}", &matches_writer);
for (const auto& match : matches) {
dbus::MessageWriter entry_writer(nullptr);
matches_writer.OpenDictEntry(&entry_writer);
entry_writer.AppendString(match.first);
std::vector<ObjectPath> record_object_paths;
record_object_paths.resize(match.second.size());
std::transform(match.second.begin(), match.second.end(),
record_object_paths.begin(),
[this](const std::string& record_id) {
return ObjectPath(object_path_.value() +
std::string("/Record") + record_id);
});
entry_writer.AppendArrayOfObjectPaths(record_object_paths);
matches_writer.CloseContainer(&entry_writer);
}
writer.CloseContainer(&matches_writer);
dbus_object_.SendSignal(&auth_scan_done_signal);
}
void BiometricsManagerWrapper::OnSessionFailed() {
if (enroll_session_dbus_object_) {
dbus::Signal session_failed_signal(kBiometricsManagerInterface,
kBiometricsManagerSessionFailedSignal);
dbus_object_.SendSignal(&session_failed_signal);
FinalizeEnrollSessionObject();
}
if (enroll_session_)
enroll_session_.End();
if (auth_session_dbus_object_) {
dbus::Signal session_failed_signal(kBiometricsManagerInterface,
kBiometricsManagerSessionFailedSignal);
dbus_object_.SendSignal(&session_failed_signal);
FinalizeAuthSessionObject();
}
if (auth_session_)
auth_session_.End();
}
bool BiometricsManagerWrapper::StartEnrollSession(
brillo::ErrorPtr* error,
dbus::Message* message,
const std::string& user_id,
const std::string& label,
ObjectPath* enroll_session_path) {
BiometricsManager::EnrollSession enroll_session =
biometrics_manager_->StartEnrollSession(user_id, label);
if (!enroll_session) {
*error = brillo::Error::Create(FROM_HERE, errors::kDomain,
errors::kInternalError,
"Failed to start EnrollSession");
return false;
}
enroll_session_ = std::move(enroll_session);
enroll_session_dbus_object_ = std::make_unique<DBusObject>(
nullptr, dbus_object_.GetBus(), enroll_session_object_path_);
DBusInterface* enroll_session_interface =
enroll_session_dbus_object_->AddOrGetInterface(kEnrollSessionInterface);
enroll_session_interface->AddSimpleMethodHandlerWithError(
kEnrollSessionCancelMethod,
base::Bind(&BiometricsManagerWrapper::EnrollSessionCancel,
base::Unretained(this)));
enroll_session_dbus_object_->RegisterAndBlock();
*enroll_session_path = enroll_session_object_path_;
enroll_session_owner_ = message->GetSender();
return true;
}
bool BiometricsManagerWrapper::GetRecordsForUser(brillo::ErrorPtr* error,
const std::string& user_id,
std::vector<ObjectPath>* out) {
for (const auto& record : records_) {
if (record->GetUserId() == user_id)
out->emplace_back(record->path());
}
return true;
}
bool BiometricsManagerWrapper::DestroyAllRecords(brillo::ErrorPtr* error) {
if (!biometrics_manager_->DestroyAllRecords()) {
*error = brillo::Error::Create(FROM_HERE, errors::kDomain,
errors::kInternalError,
"Failed to destroy all records");
return false;
}
RefreshRecordObjects();
return true;
}
bool BiometricsManagerWrapper::StartAuthSession(brillo::ErrorPtr* error,
dbus::Message* message,
ObjectPath* auth_session_path) {
BiometricsManager::AuthSession auth_session =
biometrics_manager_->StartAuthSession();
if (!auth_session) {
*error = brillo::Error::Create(FROM_HERE, errors::kDomain,
errors::kInternalError,
"Failed to start AuthSession");
return false;
}
auth_session_ = std::move(auth_session);
auth_session_dbus_object_ = std::make_unique<DBusObject>(
nullptr, dbus_object_.GetBus(), auth_session_object_path_);
DBusInterface* auth_session_interface =
auth_session_dbus_object_->AddOrGetInterface(kAuthSessionInterface);
auth_session_interface->AddSimpleMethodHandlerWithError(
kAuthSessionEndMethod,
base::Bind(&BiometricsManagerWrapper::AuthSessionEnd,
base::Unretained(this)));
auth_session_dbus_object_->RegisterAndBlock();
*auth_session_path = auth_session_object_path_;
auth_session_owner_ = message->GetSender();
return true;
}
bool BiometricsManagerWrapper::EnrollSessionCancel(brillo::ErrorPtr* error) {
if (!enroll_session_) {
LOG(WARNING) << "DBus client attempted to cancel null EnrollSession";
*error = brillo::Error::Create(FROM_HERE, errors::kDomain,
errors::kInvalidArguments,
"EnrollSession object was null");
return false;
}
enroll_session_.End();
// TODO(crbug.com/715302): FpcBiometricsManager need to wait here for
// EnrollSession to end completely before any other session could start. Wait
// time is ~200 milliseconds.
if (enroll_session_dbus_object_) {
FinalizeEnrollSessionObject();
}
return true;
}
bool BiometricsManagerWrapper::AuthSessionEnd(brillo::ErrorPtr* error) {
if (!auth_session_) {
LOG(WARNING) << "DBus client attempted to cancel null AuthSession";
*error = brillo::Error::Create(FROM_HERE, errors::kDomain,
errors::kInvalidArguments,
"AuthSession object was null");
return false;
}
auth_session_.End();
// TODO(crbug.com/715302): FpcBiometricsManager need to wait here for
// AuthSession to end completely before any other session could start. Wait
// time is ~200 milliseconds.
if (auth_session_dbus_object_) {
FinalizeAuthSessionObject();
}
return true;
}
void BiometricsManagerWrapper::RefreshRecordObjects() {
records_.clear();
std::vector<std::unique_ptr<BiometricsManager::Record>> records =
biometrics_manager_->GetRecords();
ExportedObjectManager* object_manager = dbus_object_.GetObjectManager().get();
std::string records_root_path = object_path_.value() + std::string("/Record");
for (std::unique_ptr<BiometricsManager::Record>& record : records) {
ObjectPath record_path(records_root_path + record->GetId());
records_.emplace_back(std::make_unique<RecordWrapper>(
this, std::move(record), object_manager, record_path));
}
}
BiometricsDaemon::BiometricsDaemon() {
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SYSTEM;
bus_ = base::MakeRefCounted<dbus::Bus>(options);
CHECK(bus_->Connect()) << "Failed to connect to system D-Bus";
object_manager_ = std::make_unique<ExportedObjectManager>(
bus_, ObjectPath(kBiodServicePath));
auto sequencer = base::MakeRefCounted<AsyncEventSequencer>();
object_manager_->RegisterAsync(
sequencer->GetHandler("Manager.RegisterAsync() failed.", true));
ObjectPath cros_fp_bio_path = ObjectPath(base::StringPrintf(
"%s/%s", kBiodServicePath, kCrosFpBiometricsManagerName));
auto biod_metrics = std::make_unique<BiodMetrics>();
auto cros_fp_bio = std::make_unique<CrosFpBiometricsManager>(
PowerButtonFilter::Create(bus_),
CrosFpDevice::Create(biod_metrics.get(),
std::make_unique<EcCommandFactory>()),
std::move(biod_metrics));
if (cros_fp_bio) {
biometrics_managers_.emplace_back(
std::make_unique<BiometricsManagerWrapper>(
std::move(cros_fp_bio), object_manager_.get(), cros_fp_bio_path,
sequencer->GetHandler(
"Failed to register CrosFpBiometricsManager object", true)));
} else {
LOG(INFO) << "No CrosFpBiometricsManager detected.";
}
session_manager_proxy_ = bus_->GetObjectProxy(
login_manager::kSessionManagerServiceName,
dbus::ObjectPath(login_manager::kSessionManagerServicePath));
if (RetrievePrimarySession()) {
for (const auto& biometrics_manager_wrapper : biometrics_managers_) {
biometrics_manager_wrapper->get().SetDiskAccesses(true);
biometrics_manager_wrapper->get().ReadRecordsForSingleUser(primary_user_);
biometrics_manager_wrapper->RefreshRecordObjects();
}
}
session_manager_proxy_->ConnectToSignal(
login_manager::kSessionManagerInterface,
login_manager::kSessionStateChangedSignal,
base::Bind(&BiometricsDaemon::OnSessionStateChanged,
base::Unretained(this)),
base::Bind(&LogOnSignalConnected));
CHECK(bus_->RequestOwnershipAndBlock(kBiodServiceName,
dbus::Bus::REQUIRE_PRIMARY));
}
bool BiometricsDaemon::RetrievePrimarySession() {
primary_user_.clear();
dbus::MethodCall method_call(
login_manager::kSessionManagerInterface,
login_manager::kSessionManagerRetrievePrimarySession);
std::unique_ptr<dbus::Response> response =
session_manager_proxy_->CallMethodAndBlock(
&method_call, dbus_constants::kDbusTimeoutMs);
if (!response.get()) {
LOG(ERROR) << "Cannot retrieve username for primary session.";
return false;
}
dbus::MessageReader response_reader(response.get());
std::string username;
if (!response_reader.PopString(&username)) {
LOG(ERROR) << "Primary session username bad format.";
return false;
}
std::string sanitized_username;
if (!response_reader.PopString(&sanitized_username)) {
LOG(ERROR) << "Primary session sanitized username bad format.";
return false;
}
if (sanitized_username.empty()) {
LOG(INFO) << "Primary session does not exist.";
return false;
}
LOG(INFO) << "Primary user updated to " << sanitized_username << ".";
primary_user_.assign(sanitized_username);
return true;
}
void BiometricsDaemon::OnSessionStateChanged(dbus::Signal* signal) {
dbus::MessageReader signal_reader(signal);
std::string state;
CHECK(signal_reader.PopString(&state));
LOG(INFO) << "Session state changed to " << state << ".";
if (state == dbus_constants::kSessionStateStarted) {
// If a primary session doesn't exist, we can safely reset the sensors
// before loading in templates. But if one exists, we should leave the
// sensors as is.
if (!primary_user_.empty()) {
LOG(INFO) << "Primary user already exists. Not updating primary user.";
return;
}
for (const auto& biometrics_manager_wrapper : biometrics_managers_) {
if (!biometrics_manager_wrapper->get().ResetSensor()) {
LOG(ERROR) << "Failed to reset biometric sensor type: "
<< biometrics_manager_wrapper->get().GetType();
}
}
if (RetrievePrimarySession()) {
for (const auto& biometrics_manager_wrapper : biometrics_managers_) {
biometrics_manager_wrapper->get().SetDiskAccesses(true);
biometrics_manager_wrapper->get().ReadRecordsForSingleUser(
primary_user_);
biometrics_manager_wrapper->RefreshRecordObjects();
biometrics_manager_wrapper->get().SendStatsOnLogin();
}
}
} else if (state == dbus_constants::kSessionStateStopped) {
// Assuming that log out will always log out all users at the same time.
for (const auto& biometrics_manager_wrapper : biometrics_managers_) {
biometrics_manager_wrapper->get().SetDiskAccesses(false);
biometrics_manager_wrapper->get().RemoveRecordsFromMemory();
biometrics_manager_wrapper->RefreshRecordObjects();
}
primary_user_.clear();
}
}
} // namespace biod