| // 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 |