blob: 24ded1354d92aa982a58e1b1df7182ee0f6c5178 [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "biod/biometrics_manager_wrapper.h"
#include <algorithm>
#include <utility>
#include <base/check.h>
#include <base/functional/bind.h>
#include <base/logging.h>
#include <brillo/dbus/async_event_sequencer.h>
#include <dbus/object_proxy.h>
#include "biod/utils.h"
namespace biod {
using brillo::dbus_utils::AsyncEventSequencer;
using brillo::dbus_utils::DBusInterface;
using brillo::dbus_utils::DBusObject;
using brillo::dbus_utils::ExportedObjectManager;
using dbus::ObjectPath;
BiometricsManagerWrapper::BiometricsManagerWrapper(
std::unique_ptr<BiometricsManager> biometrics_manager,
ExportedObjectManager* object_manager,
SessionStateManagerInterface* session_state_manager,
ObjectPath object_path,
AsyncEventSequencer::CompletionAction completion_callback)
: biometrics_manager_(std::move(biometrics_manager)),
session_state_manager_(session_state_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::BindRepeating(
&BiometricsManagerWrapper::OnEnrollScanDone, base::Unretained(this)));
biometrics_manager_->SetAuthScanDoneHandler(base::BindRepeating(
&BiometricsManagerWrapper::OnAuthScanDone, base::Unretained(this)));
biometrics_manager_->SetSessionFailedHandler(base::BindRepeating(
&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::BindRepeating(&BiometricsManagerWrapper::OnNameOwnerChanged,
base::Unretained(this)),
base::BindOnce(&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::BindRepeating(&BiometricsManagerWrapper::StartEnrollSession,
base::Unretained(this)));
bio_interface->AddSimpleMethodHandlerWithError(
kBiometricsManagerGetRecordsForUserMethod,
base::BindRepeating(&BiometricsManagerWrapper::GetRecordsForUser,
base::Unretained(this)));
bio_interface->AddSimpleMethodHandlerWithError(
kBiometricsManagerDestroyAllRecordsMethod,
base::BindRepeating(&BiometricsManagerWrapper::DestroyAllRecords,
base::Unretained(this)));
bio_interface->AddSimpleMethodHandlerWithErrorAndMessage(
kBiometricsManagerStartAuthSessionMethod,
base::BindRepeating(&BiometricsManagerWrapper::StartAuthSession,
base::Unretained(this)));
dbus_object_.RegisterAsync(std::move(completion_callback));
// Add this BiometricsManagerWrapper instance to observe session state
// changes.
session_state_manager_->AddObserver(this);
}
BiometricsManagerWrapper::~BiometricsManagerWrapper() {
session_state_manager_->RemoveObserver(this);
}
void BiometricsManagerWrapper::FinalizeEnrollSessionObject() {
enroll_session_owner_.clear();
enroll_session_dbus_object_->UnregisterAndBlock();
enroll_session_dbus_object_.reset();
}
void BiometricsManagerWrapper::FinalizeAuthSessionObject() {
auth_session_owner_.clear();
auth_session_dbus_object_->UnregisterAndBlock();
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(
FingerprintMessage 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.AppendProtoAsArrayOfBytes(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();
}
void BiometricsManagerWrapper::EmitStatusChanged(
BiometricsManagerStatus status) {
dbus::Signal status_changed_signal(kBiometricsManagerInterface,
kBiometricsManagerStatusChangedSignal);
BiometricsManagerStatusChanged proto;
proto.set_status(status);
dbus::MessageWriter writer(&status_changed_signal);
writer.AppendProtoAsArrayOfBytes(proto);
dbus_object_.SendSignal(&status_changed_signal);
}
bool BiometricsManagerWrapper::StartEnrollSession(
brillo::ErrorPtr* error,
dbus::Message* message,
const std::string& user_id,
const std::string& label,
ObjectPath* enroll_session_path) {
if (session_state_manager_->GetPrimaryUser().empty()) {
LOG(WARNING) << message->GetSender() << " tried to start EnrollSession "
<< "when primary user is not set";
*error = brillo::Error::Create(FROM_HERE, kDomain, kInternalError,
"Primary user is not set");
return false;
}
BiometricsManager::EnrollSession enroll_session =
biometrics_manager_->StartEnrollSession(user_id, label);
if (!enroll_session) {
*error = brillo::Error::Create(FROM_HERE, kDomain, kInternalError,
enroll_session.error());
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::BindRepeating(&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) {
// Technically, it's fine to call GetRecordsForUser when primary user is not
// available - we will just return an empty vector. This situation can occur
// when Chrome asks for the list of records before session_manager sends
// information that user logged in. In that case, print warning and return
// error so we can determine if that was the reason why unlock using
// fingerprint is not available despite the fact that records were loaded
// correctly.
if (session_state_manager_->GetPrimaryUser().empty()) {
LOG(WARNING) << "GetRecordsForUser called when primary user is not "
<< "available.";
*error = brillo::Error::Create(FROM_HERE, kDomain, kInternalError,
"Primary user is not set");
return false;
}
for (const auto& record : records_) {
if (record->GetUserId() == user_id)
out->emplace_back(record->path());
}
return true;
}
bool BiometricsManagerWrapper::DestroyAllRecords(brillo::ErrorPtr* error) {
if (session_state_manager_->GetPrimaryUser().empty()) {
LOG(WARNING) << "DestroyAllRecords called when primary user is not set";
*error = brillo::Error::Create(FROM_HERE, kDomain, kInternalError,
"Primary user is not set");
return false;
}
if (!biometrics_manager_->DestroyAllRecords()) {
*error = brillo::Error::Create(FROM_HERE, kDomain, kInternalError,
"Failed to destroy all records");
return false;
}
RefreshRecordObjects();
return true;
}
bool BiometricsManagerWrapper::StartAuthSession(brillo::ErrorPtr* error,
dbus::Message* message,
ObjectPath* auth_session_path) {
if (session_state_manager_->GetPrimaryUser().empty()) {
LOG(WARNING) << message->GetSender() << " tried to start AuthSession when "
<< "primary user is not set";
*error = brillo::Error::Create(FROM_HERE, kDomain, kInternalError,
"Primary user is not set");
return false;
}
BiometricsManager::AuthSession auth_session =
biometrics_manager_->StartAuthSession();
if (!auth_session) {
*error = brillo::Error::Create(FROM_HERE, kDomain, kInternalError,
auth_session.error());
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::BindRepeating(&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, kDomain, 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, kDomain, 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();
// There is nothing to do when user is not logged in.
if (session_state_manager_->GetPrimaryUser().empty()) {
return;
}
std::vector<std::unique_ptr<BiometricsManagerRecordInterface>> records =
biometrics_manager_->GetLoadedRecords();
ExportedObjectManager* object_manager = dbus_object_.GetObjectManager().get();
std::string records_root_path = object_path_.value() + std::string("/Record");
for (std::unique_ptr<BiometricsManagerRecordInterface>& record : records) {
ObjectPath record_path(records_root_path + record->GetId());
records_.emplace_back(std::make_unique<BiometricsManagerRecordWrapper>(
this, std::move(record), object_manager, record_path));
}
}
void BiometricsManagerWrapper::OnUserLoggedIn(
const std::string& sanitized_username, bool is_new_login) {
if (!biometrics_manager_->ResetSensor()) {
LOG(ERROR) << "Failed to reset biometric sensor type: "
<< biometrics_manager_->GetType();
}
biometrics_manager_->SetDiskAccesses(true);
biometrics_manager_->ReadRecordsForSingleUser(sanitized_username);
RefreshRecordObjects();
if (is_new_login) {
biometrics_manager_->SendStatsOnLogin();
}
// Inform clients that biometrics manager is ready for requests.
EmitStatusChanged(BiometricsManagerStatus::INITIALIZED);
}
void BiometricsManagerWrapper::OnUserLoggedOut() {
// Assuming that log out will always log out all users at the same time.
biometrics_manager_->SetDiskAccesses(false);
biometrics_manager_->RemoveRecordsFromMemory();
RefreshRecordObjects();
}
} // namespace biod