-
-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
auth: Support parallel fingerprint auth
I chose to use Fprint's dbus interface directly rather than going through pam (which uses Fprint's dbus interface) due to poor handling of system sleep somewhere between fprintd and pam. When preparing for sleep, fprintd puts the device to sleep, which causes VerifyStatus to emit with verify-unknown-error, which normally should be responded to by calling both Device.StopVerify and Device.Release (and this is what pam does). Unfortunately, if you try to release the device when the system is preparing for sleep, you'll get an error that the device is busy and then you can't can't claim or release the device for 30 seconds. pam also has a max timeout for pam_fprintd.so of 99 seconds, and so if we used pam, we'd have to deal with the timeouts and keep restarting the auth conversation. gdm/gnome-session lock seems to get around these issues by having a shutter on top of the lock screen that you have to interact with first that gives gnome-session a trigger to start fingerprint auth.
- Loading branch information
Showing
6 changed files
with
323 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
#include "Fingerprint.hpp" | ||
#include "../helpers/Log.hpp" | ||
#include "src/config/ConfigManager.hpp" | ||
|
||
#include <filesystem> | ||
#include <unistd.h> | ||
#include <pwd.h> | ||
|
||
#include <cstring> | ||
#include <thread> | ||
|
||
static const auto FPRINT = "net.reactivated.Fprint"; | ||
static const auto DEVICE = "net.reactivated.Fprint.Device"; | ||
static const auto MANAGER = "net.reactivated.Fprint.Manager"; | ||
static const auto LOGIN_MANAGER = "org.freedesktop.login1.Manager"; | ||
static const auto RETRY_MESSAGE = "Could not match fingerprint. Try again."; | ||
|
||
CFingerprint::CFingerprint() { | ||
static auto* const PFINGERPRINTREADY = (Hyprlang::STRING*)(g_pConfigManager->getValuePtr("general:fingerprint_ready_message")); | ||
m_sFingerprintReady = *PFINGERPRINTREADY; | ||
} | ||
|
||
static void fingerprintUnlockCallback(std::shared_ptr<CTimer> self, void* data) { | ||
g_pHyprlock->unlock(); | ||
} | ||
|
||
void CFingerprint::start() { | ||
static auto* const PENABLEFINGERPRINT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:enable_fingerprint"); | ||
const bool ENABLEFINGERPRINT = **PENABLEFINGERPRINT; | ||
if (!ENABLEFINGERPRINT) { | ||
return; | ||
} | ||
std::thread([this]() { | ||
// For grace or SIGUSR1 unlocks | ||
if (g_pHyprlock->isUnlocked()) | ||
return; | ||
|
||
setupDBUS(); | ||
|
||
if (m_bAuthenticated) | ||
g_pHyprlock->addTimer(std::chrono::milliseconds(1), fingerprintUnlockCallback, nullptr); | ||
}).detach(); | ||
} | ||
|
||
std::optional<std::string> CFingerprint::getLastMessage() { | ||
return m_sDBUSState.message.empty() ? std::nullopt : std::optional(m_sDBUSState.message); | ||
} | ||
|
||
void CFingerprint::terminate() { | ||
if (m_sDBUSState.connection) | ||
m_sDBUSState.connection->leaveEventLoop(); | ||
} | ||
|
||
bool CFingerprint::setupDBUS() { | ||
m_sDBUSState.connection = sdbus::createSystemBusConnection(); | ||
|
||
registerSleepHandler(); | ||
|
||
if (m_sDBUSState.sleeping) { | ||
m_sDBUSState.connection->enterEventLoop(); | ||
} else if (startVerify()) { | ||
m_sDBUSState.message = m_sFingerprintReady; | ||
g_pHyprlock->enqueueForceUpdateTimers(); | ||
m_sDBUSState.connection->enterEventLoop(); | ||
} | ||
|
||
if (m_sDBUSState.abort) | ||
return true; | ||
|
||
releaseDevice(); | ||
return true; | ||
} | ||
|
||
void CFingerprint::registerSleepHandler() { | ||
m_sDBUSState.login = sdbus::createProxy(*m_sDBUSState.connection.get(), "org.freedesktop.login1", "/org/freedesktop/login1"); | ||
|
||
m_sDBUSState.sleeping = m_sDBUSState.login->getProperty("PreparingForSleep").onInterface(LOGIN_MANAGER); | ||
m_sDBUSState.login->uponSignal("PrepareForSleep").onInterface(LOGIN_MANAGER).call([this](bool start) { | ||
Debug::log(LOG, "prepare for sleep"); | ||
if (start) { | ||
m_sDBUSState.sleeping = true; | ||
stopVerify(); | ||
m_sDBUSState.inhibitLock.reset(); | ||
} else { | ||
m_sDBUSState.sleeping = false; | ||
inhibitSleep(); | ||
std::this_thread::sleep_for(std::chrono::seconds(1)); | ||
if (startVerify()) { | ||
m_sDBUSState.message = m_sFingerprintReady; | ||
g_pHyprlock->enqueueForceUpdateTimers(); | ||
} | ||
} | ||
}); | ||
m_sDBUSState.login->finishRegistration(); | ||
if (!m_sDBUSState.sleeping) | ||
inhibitSleep(); | ||
} | ||
|
||
void CFingerprint::inhibitSleep() { | ||
m_sDBUSState.login->callMethod("Inhibit") | ||
.onInterface(LOGIN_MANAGER) | ||
.withArguments("sleep", "hyprlock", "Release fingerprint before sleep", "delay") | ||
.storeResultsTo(m_sDBUSState.inhibitLock); | ||
} | ||
|
||
bool CFingerprint::createDeviceProxy() { | ||
auto proxy = sdbus::createProxy(*m_sDBUSState.connection.get(), FPRINT, "/net/reactivated/Fprint/Manager"); | ||
|
||
sdbus::ObjectPath path; | ||
try { | ||
proxy->callMethod("GetDefaultDevice").onInterface(MANAGER).storeResultsTo(path); | ||
} catch (sdbus::Error& e) { | ||
Debug::log(WARN, "Couldn't connect to Fprint service ({})", e.what()); | ||
return false; | ||
} | ||
Debug::log(LOG, "Using dbus path {}", path.c_str()); | ||
m_sDBUSState.device = sdbus::createProxy(*m_sDBUSState.connection.get(), FPRINT, path); | ||
|
||
m_sDBUSState.device->uponSignal("VerifyFingerSelected").onInterface(DEVICE).call([](const std::string& finger) { Debug::log(LOG, "Finger: {}", finger); }); | ||
m_sDBUSState.device->uponSignal("VerifyStatus").onInterface(DEVICE).call([this](const std::string& result, const bool done) { handleVerifyStatus(result, done); }); | ||
|
||
m_sDBUSState.device->finishRegistration(); | ||
return true; | ||
} | ||
|
||
void CFingerprint::handleVerifyStatus(const std::string& result, bool done) { | ||
Debug::log(LOG, "Status: {} {}", done, result); | ||
auto matchResult = s_mapStringToTestType[result]; | ||
if (m_sDBUSState.sleeping && matchResult != MatchResult::Disconnected) { | ||
return; | ||
} | ||
std::unique_lock<std::mutex> lk(m_sDBUSState.messageMutex); | ||
switch (matchResult) { | ||
case MatchResult::NoMatch: //The verification did not match, Device.VerifyStop should now be called. | ||
stopVerify(); | ||
if (m_sDBUSState.retries < 2) { | ||
startVerify(); | ||
done = false; | ||
m_sDBUSState.retries++; | ||
m_sDBUSState.message = RETRY_MESSAGE; | ||
if (clearRetryTextTimer) | ||
clearRetryTextTimer->cancel(); | ||
clearRetryTextTimer = g_pHyprlock->addTimer( | ||
std::chrono::seconds(2), | ||
[this](std::shared_ptr<CTimer>, void* data) { | ||
std::unique_lock<std::mutex> lk(m_sDBUSState.messageMutex); | ||
if (m_sDBUSState.message == RETRY_MESSAGE) { | ||
m_sDBUSState.message = m_sFingerprintReady; | ||
g_pHyprlock->enqueueForceUpdateTimers(); | ||
} | ||
}, | ||
nullptr); | ||
} else { | ||
m_sDBUSState.message = "Fingerprint auth disabled: too many failed attempts."; | ||
} | ||
break; | ||
case MatchResult::UnknownError: | ||
stopVerify(); | ||
m_sDBUSState.message = "Unknown fingerprint error."; | ||
break; | ||
case MatchResult::Match: | ||
stopVerify(); | ||
m_bAuthenticated = true; | ||
m_sDBUSState.message = ""; | ||
Debug::log(LOG, "matched"); | ||
break; | ||
case MatchResult::Retry: m_sDBUSState.message = "Please retry fingerprint scan."; break; | ||
case MatchResult::SwipeTooShort: m_sDBUSState.message = "Swipe too short - try again."; break; | ||
case MatchResult::FingerNotCentered: m_sDBUSState.message = "Finger not centered - try again."; break; | ||
case MatchResult::RemoveAndRetry: m_sDBUSState.message = "Remove your finger and try again."; break; | ||
case MatchResult::Disconnected: m_sDBUSState.abort = true; break; | ||
} | ||
g_pHyprlock->enqueueForceUpdateTimers(); | ||
if (done || m_sDBUSState.abort) { | ||
Debug::log(LOG, "leaving dbus loop"); | ||
m_sDBUSState.connection->leaveEventLoop(); | ||
} | ||
} | ||
|
||
bool CFingerprint::claimDevice() { | ||
try { | ||
const auto currentUser = ""; // Empty string means use the caller's id. | ||
m_sDBUSState.device->callMethod("Claim").onInterface(DEVICE).withArguments(currentUser); | ||
} catch (sdbus::Error& e) { | ||
Debug::log(WARN, "could not claim device, {}", e.what()); | ||
return false; | ||
} | ||
Debug::log(LOG, "Claimed device"); | ||
return true; | ||
} | ||
|
||
bool CFingerprint::startVerify() { | ||
if (!m_sDBUSState.device) { | ||
if (!createDeviceProxy()) | ||
return false; | ||
|
||
claimDevice(); | ||
} | ||
try { | ||
auto finger = "any"; // Any finger. | ||
m_sDBUSState.device->callMethod("VerifyStart").onInterface(DEVICE).withArguments(finger); | ||
} catch (sdbus::Error& e) { | ||
Debug::log(WARN, "Could not start verifying, {}", e.what()); | ||
return false; | ||
} | ||
Debug::log(LOG, "started verifying"); | ||
return true; | ||
} | ||
|
||
bool CFingerprint::stopVerify() { | ||
if (!m_sDBUSState.device) | ||
return false; | ||
try { | ||
m_sDBUSState.device->callMethod("VerifyStop").onInterface(DEVICE); | ||
} catch (sdbus::Error& e) { | ||
Debug::log(WARN, "could not stop verifying, {}", e.what()); | ||
return false; | ||
} | ||
Debug::log(LOG, "Stopped verification"); | ||
return true; | ||
} | ||
|
||
bool CFingerprint::releaseDevice() { | ||
if (!m_sDBUSState.device) | ||
return false; | ||
try { | ||
m_sDBUSState.device->callMethod("Release").onInterface(DEVICE); | ||
} catch (sdbus::Error& e) { | ||
Debug::log(WARN, "could not release device, {}", e.what()); | ||
return false; | ||
} | ||
Debug::log(LOG, "Released device"); | ||
return true; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
#pragma once | ||
|
||
#include "hyprlock.hpp" | ||
|
||
#include <memory> | ||
#include <optional> | ||
#include <string> | ||
#include <mutex> | ||
#include <condition_variable> | ||
#include <sdbus-c++/sdbus-c++.h> | ||
|
||
class CFingerprint { | ||
public: | ||
CFingerprint(); | ||
|
||
void start(); | ||
|
||
std::optional<std::string> getLastMessage(); | ||
|
||
void terminate(); | ||
bool setupDBUS(); | ||
|
||
private: | ||
enum class MatchResult { | ||
NoMatch, | ||
Match, | ||
Retry, | ||
SwipeTooShort, | ||
FingerNotCentered, | ||
RemoveAndRetry, | ||
Disconnected, | ||
UnknownError, | ||
}; | ||
struct SDBUSState { | ||
std::string message = ""; | ||
|
||
std::mutex messageMutex; | ||
|
||
std::unique_ptr<sdbus::IConnection> connection; | ||
std::unique_ptr<sdbus::IProxy> login; | ||
std::unique_ptr<sdbus::IProxy> device; | ||
sdbus::UnixFd inhibitLock; | ||
|
||
bool abort = false; | ||
int retries = 0; | ||
bool sleeping = false; | ||
} m_sDBUSState; | ||
|
||
std::string m_sFingerprintReady; | ||
bool m_bAuthenticated = false; | ||
std::shared_ptr<CTimer> clearRetryTextTimer; | ||
|
||
std::map<std::string, MatchResult> s_mapStringToTestType = {{"verify-no-match", MatchResult::NoMatch}, | ||
{"verify-match", MatchResult::Match}, | ||
{"verify-retry-scan", MatchResult::Retry}, | ||
{"verify-swipe-too-short", MatchResult::SwipeTooShort}, | ||
{"verify-finger-not-centered", MatchResult::FingerNotCentered}, | ||
{"verify-remove-and-retry", MatchResult::RemoveAndRetry}, | ||
{"verify-disconnected", MatchResult::Disconnected}, | ||
{"verify-unknown-error", MatchResult::UnknownError}}; | ||
void handleVerifyStatus(const std::string& result, const bool done); | ||
|
||
void registerSleepHandler(); | ||
void inhibitSleep(); | ||
|
||
bool createDeviceProxy(); | ||
bool claimDevice(); | ||
bool startVerify(); | ||
bool stopVerify(); | ||
bool releaseDevice(); | ||
}; | ||
|
||
inline std::unique_ptr<CFingerprint> g_pFingerprint; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters