hyprlock/src/auth/Fingerprint.cpp
Maximilian Seidler 4f964371cc
auth: fixup prompt and fail substitution (#641)
BREAKING:
- Removed $PROMPT variable. Either use $PAMPROMPT or $FPRINTPROMPT.
- Removed $FPRINTMESSAGE. Use $FPRINTPROMPT instead. There is also
  $FPRINTFAIL.
2025-01-12 17:18:18 +00:00

275 lines
10 KiB
C++

#include "Fingerprint.hpp"
#include "../core/hyprlock.hpp"
#include "../helpers/Log.hpp"
#include "../config/ConfigManager.hpp"
#include <memory>
#include <unistd.h>
#include <pwd.h>
#include <cstring>
static const auto FPRINT = sdbus::ServiceName{"net.reactivated.Fprint"};
static const auto DEVICE = sdbus::ServiceName{"net.reactivated.Fprint.Device"};
static const auto MANAGER = sdbus::ServiceName{"net.reactivated.Fprint.Manager"};
static const auto LOGIN_MANAGER = sdbus::ServiceName{"org.freedesktop.login1.Manager"};
enum MatchResult {
MATCH_INVALID = 0,
MATCH_NO_MATCH,
MATCH_MATCHED,
MATCH_RETRY,
MATCH_SWIPE_TOO_SHORT,
MATCH_FINGER_NOT_CENTERED,
MATCH_REMOVE_AND_RETRY,
MATCH_DISCONNECTED,
MATCH_UNKNOWN_ERROR,
};
static std::map<std::string, MatchResult> s_mapStringToTestType = {{"verify-no-match", MATCH_NO_MATCH},
{"verify-match", MATCH_MATCHED},
{"verify-retry-scan", MATCH_RETRY},
{"verify-swipe-too-short", MATCH_SWIPE_TOO_SHORT},
{"verify-finger-not-centered", MATCH_FINGER_NOT_CENTERED},
{"verify-remove-and-retry", MATCH_REMOVE_AND_RETRY},
{"verify-disconnected", MATCH_DISCONNECTED},
{"verify-unknown-error", MATCH_UNKNOWN_ERROR}};
CFingerprint::CFingerprint() {
static auto* const PFINGERPRINTREADY = (Hyprlang::STRING*)(g_pConfigManager->getValuePtr("auth:fingerprint:ready_message"));
m_sFingerprintReady = *PFINGERPRINTREADY;
static auto* const PFINGERPRINTPRESENT = (Hyprlang::STRING*)(g_pConfigManager->getValuePtr("auth:fingerprint:present_message"));
m_sFingerprintPresent = *PFINGERPRINTPRESENT;
}
CFingerprint::~CFingerprint() {
;
}
void CFingerprint::init() {
m_sDBUSState.connection = sdbus::createSystemBusConnection();
m_sDBUSState.login = sdbus::createProxy(*m_sDBUSState.connection, sdbus::ServiceName{"org.freedesktop.login1"}, sdbus::ObjectPath{"/org/freedesktop/login1"});
m_sDBUSState.login->getPropertyAsync("PreparingForSleep").onInterface(LOGIN_MANAGER).uponReplyInvoke([this](std::optional<sdbus::Error> e, sdbus::Variant preparingForSleep) {
if (e) {
Debug::log(WARN, "fprint: Failed getting value for PreparingForSleep: {}", e->what());
return;
}
m_sDBUSState.sleeping = preparingForSleep.get<bool>();
// When entering sleep, the wake signal will trigger startVerify().
if (m_sDBUSState.sleeping)
return;
inhibitSleep();
startVerify();
});
m_sDBUSState.login->uponSignal("PrepareForSleep").onInterface(LOGIN_MANAGER).call([this](bool start) {
Debug::log(LOG, "fprint: PrepareForSleep (start: {})", start);
if (start) {
m_sDBUSState.sleeping = true;
stopVerify();
m_sDBUSState.inhibitLock.reset();
} else {
m_sDBUSState.sleeping = false;
inhibitSleep();
startVerify();
}
});
}
void CFingerprint::handleInput(const std::string& input) {
;
}
std::optional<std::string> CFingerprint::getLastFailText() {
if (!m_sFailureReason.empty())
return std::optional(m_sFailureReason);
return std::nullopt;
}
std::optional<std::string> CFingerprint::getLastPrompt() {
if (!m_sPrompt.empty())
return std::optional(m_sPrompt);
return std::nullopt;
}
bool CFingerprint::checkWaiting() {
return false;
}
void CFingerprint::terminate() {
if (!m_sDBUSState.abort)
releaseDevice();
}
std::shared_ptr<sdbus::IConnection> CFingerprint::getConnection() {
return m_sDBUSState.connection;
}
void CFingerprint::inhibitSleep() {
m_sDBUSState.login->callMethodAsync("Inhibit")
.onInterface(LOGIN_MANAGER)
.withArguments("sleep", "hyprlock", "Fingerprint verifcation must be stopped before sleep", "delay")
.uponReplyInvoke([this](std::optional<sdbus::Error> e, sdbus::UnixFd fd) {
if (e)
Debug::log(WARN, "fprint: could not inhibit sleep: {}", e->what());
else
m_sDBUSState.inhibitLock = fd;
});
}
bool CFingerprint::createDeviceProxy() {
auto proxy = sdbus::createProxy(*m_sDBUSState.connection, FPRINT, sdbus::ObjectPath{"/net/reactivated/Fprint/Manager"});
sdbus::ObjectPath path;
try {
proxy->callMethod("GetDefaultDevice").onInterface(MANAGER).storeResultsTo(path);
} catch (sdbus::Error& e) {
Debug::log(WARN, "fprint: couldn't connect to Fprint service ({})", e.what());
return false;
}
Debug::log(LOG, "fprint: using device path {}", path.c_str());
m_sDBUSState.device = sdbus::createProxy(*m_sDBUSState.connection, FPRINT, path);
m_sDBUSState.device->uponSignal("VerifyFingerSelected").onInterface(DEVICE).call([](const std::string& finger) { Debug::log(LOG, "fprint: finger selected: {}", finger); });
m_sDBUSState.device->uponSignal("VerifyStatus").onInterface(DEVICE).call([this](const std::string& result, const bool done) { handleVerifyStatus(result, done); });
m_sDBUSState.device->uponSignal("PropertiesChanged")
.onInterface("org.freedesktop.DBus.Properties")
.call([this](const std::string& interface, const std::map<std::string, sdbus::Variant>& properties) {
if (interface != DEVICE || m_sDBUSState.done)
return;
try {
const auto presentVariant = properties.at("finger-present");
bool isPresent = presentVariant.get<bool>();
if (!isPresent)
return;
m_sPrompt = m_sFingerprintPresent;
g_pHyprlock->enqueueForceUpdateTimers();
} catch (std::out_of_range& e) {}
});
return true;
}
void CFingerprint::handleVerifyStatus(const std::string& result, bool done) {
Debug::log(LOG, "fprint: handling status {}", result);
auto matchResult = s_mapStringToTestType[result];
bool authenticated = false;
bool retry = false;
if (m_sDBUSState.sleeping && matchResult != MATCH_DISCONNECTED)
return;
switch (matchResult) {
case MATCH_INVALID: Debug::log(WARN, "fprint: unknown status: {}", result); break;
case MATCH_NO_MATCH:
stopVerify();
if (m_sDBUSState.retries >= 3) {
m_sFailureReason = "Fingerprint auth disabled (too many failed attempts)";
} else {
done = false;
static const auto RETRYDELAY = **(Hyprlang::INT* const*)(g_pConfigManager->getValuePtr("auth:fingerprint:retry_delay"));
g_pHyprlock->addTimer(std::chrono::milliseconds(RETRYDELAY), [](std::shared_ptr<CTimer> self, void* data) { ((CFingerprint*)data)->startVerify(true); }, this);
m_sFailureReason = "Fingerprint did not match";
}
break;
case MATCH_UNKNOWN_ERROR:
stopVerify();
m_sFailureReason = "Fingerprint auth disabled (unknown error)";
break;
case MATCH_MATCHED:
stopVerify();
authenticated = true;
g_pAuth->enqueueUnlock();
break;
case MATCH_RETRY:
retry = true;
m_sPrompt = "Please retry fingerprint scan";
break;
case MATCH_SWIPE_TOO_SHORT:
retry = true;
m_sPrompt = "Swipe too short - try again";
break;
case MATCH_FINGER_NOT_CENTERED:
retry = true;
m_sPrompt = "Finger not centered - try again";
break;
case MATCH_REMOVE_AND_RETRY:
retry = true;
m_sPrompt = "Remove your finger and try again";
break;
case MATCH_DISCONNECTED:
m_sFailureReason = "Fingerprint device disconnected";
m_sDBUSState.abort = true;
break;
}
if (!authenticated && !retry)
g_pAuth->enqueueFail(m_sFailureReason, AUTH_IMPL_FINGERPRINT);
if (done || m_sDBUSState.abort)
m_sDBUSState.done = true;
}
void CFingerprint::claimDevice() {
const auto currentUser = ""; // Empty string means use the caller's id.
m_sDBUSState.device->callMethodAsync("Claim").onInterface(DEVICE).withArguments(currentUser).uponReplyInvoke([this](std::optional<sdbus::Error> e) {
if (e)
Debug::log(WARN, "fprint: could not claim device, {}", e->what());
else {
Debug::log(LOG, "fprint: claimed device");
startVerify();
}
});
}
void CFingerprint::startVerify(bool isRetry) {
if (!m_sDBUSState.device) {
if (!createDeviceProxy())
return;
claimDevice();
return;
}
auto finger = "any"; // Any finger.
m_sDBUSState.device->callMethodAsync("VerifyStart").onInterface(DEVICE).withArguments(finger).uponReplyInvoke([this, isRetry](std::optional<sdbus::Error> e) {
if (e) {
Debug::log(WARN, "fprint: could not start verifying, {}", e->what());
if (isRetry)
m_sFailureReason = "Fingerprint auth disabled (failed to restart)";
} else {
Debug::log(LOG, "fprint: started verifying");
if (isRetry) {
m_sDBUSState.retries++;
m_sPrompt = "Could not match fingerprint. Try again.";
} else
m_sPrompt = m_sFingerprintReady;
}
g_pHyprlock->enqueueForceUpdateTimers();
});
}
bool CFingerprint::stopVerify() {
if (!m_sDBUSState.device)
return false;
try {
m_sDBUSState.device->callMethod("VerifyStop").onInterface(DEVICE);
} catch (sdbus::Error& e) {
Debug::log(WARN, "fprint: could not stop verifying, {}", e.what());
return false;
}
Debug::log(LOG, "fprint: 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, "fprint: could not release device, {}", e.what());
return false;
}
Debug::log(LOG, "fprint: released device");
return true;
}