Rebase opt-in fingerprint workaround onto KScreenLocker 6.7.5
This commit is contained in:
@@ -21,7 +21,7 @@ restores the current KScreenLocker package from Fedora's `fedora` or `updates`
|
|||||||
repository.
|
repository.
|
||||||
|
|
||||||
The provided build currently supports Fedora 44 on x86_64 and is based on
|
The provided build currently supports Fedora 44 on x86_64 and is based on
|
||||||
`kscreenlocker-6.7.4-1.fc44`. A different base version is rejected by default.
|
`kscreenlocker-6.7.5-1.fc44`. A different base version is rejected by default.
|
||||||
To intentionally install the older provided build after a KScreenLocker update:
|
To intentionally install the older provided build after a KScreenLocker update:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -41,7 +41,7 @@ Install the resulting package directly through the controller:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo plasma-fingerprint-workaround enable \
|
sudo plasma-fingerprint-workaround enable \
|
||||||
--rpm rpmbuild/RPMS/x86_64/kscreenlocker-6.7.4-1.fc44.ajp5.x86_64.rpm
|
--rpm rpmbuild/RPMS/x86_64/kscreenlocker-6.7.5-1.fc44.ajp6.x86_64.rpm
|
||||||
```
|
```
|
||||||
|
|
||||||
For another upstream version, supply its version, Fedora base release, and
|
For another upstream version, supply its version, Fedora base release, and
|
||||||
@@ -58,3 +58,21 @@ A locally built package based on a different installed version requires
|
|||||||
`--force`. This flag permits the version mismatch and allows DNF to downgrade;
|
`--force`. This flag permits the version mismatch and allows DNF to downgrade;
|
||||||
it does not bypass the package identity, architecture, integrity, or workaround
|
it does not bypass the package identity, architecture, integrity, or workaround
|
||||||
metadata checks.
|
metadata checks.
|
||||||
|
|
||||||
|
## Authentication regression tests
|
||||||
|
|
||||||
|
With an extracted source tree and the patches applied in numeric order:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake -S plasma-fingerprint-workaround/tests/authentication \
|
||||||
|
-B local/fingerprint-tests -DKSCREENLOCKER_SOURCE=/path/to/patched/kscreenlocker
|
||||||
|
cmake --build local/fingerprint-tests
|
||||||
|
ctest --test-dir local/fingerprint-tests --output-on-failure
|
||||||
|
```
|
||||||
|
|
||||||
|
These tests compile the upstream authentication classes against a simulated
|
||||||
|
PAM backend. They check retries after fingerprint timeout or temporary
|
||||||
|
unavailability, preservation of the password conversation, absent smartcards,
|
||||||
|
missing modules, the grace-period guard and incorrect passwords. They never
|
||||||
|
access the system's authentication configuration. Actual reader behaviour and
|
||||||
|
the lock-screen interface still require testing on the laptop.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
%{!?upstream_version:%global upstream_version 6.7.4}
|
%{!?upstream_version:%global upstream_version 6.7.5}
|
||||||
%{!?base_release:%global base_release 1}
|
%{!?base_release:%global base_release 1}
|
||||||
%{!?workaround_release:%global workaround_release 5}
|
%{!?workaround_release:%global workaround_release 6}
|
||||||
|
|
||||||
Name: kscreenlocker
|
Name: kscreenlocker
|
||||||
Version: %{upstream_version}
|
Version: %{upstream_version}
|
||||||
@@ -103,6 +103,9 @@ install -D -m 0755 %{SOURCE1} \
|
|||||||
%{_datadir}/dbus-1/interfaces/*.xml
|
%{_datadir}/dbus-1/interfaces/*.xml
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Sat Sep 12 2026 Anton - 6.7.5-1.fc44.ajp6
|
||||||
|
- Rebase the existing fingerprint workaround onto Fedora's Plasma 6.7.5
|
||||||
|
|
||||||
* Fri Sep 04 2026 Anton - 6.7.4-1.fc44.ajp5
|
* Fri Sep 04 2026 Anton - 6.7.4-1.fc44.ajp5
|
||||||
- Package the fprintd resume hook and compatibility metadata
|
- Package the fprintd resume hook and compatibility metadata
|
||||||
- Apply the tested fingerprint retry patch set
|
- Apply the tested fingerprint retry patch set
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
payload_url=https://git.ajpanton.se/api/packages/ajp_anton/generic/plasma-fingerprint-workaround/6.7.4-1.fc44.ajp5/kscreenlocker-6.7.4-1.fc44.ajp5.x86_64.rpm
|
payload_url=https://git.ajpanton.se/api/packages/ajp_anton/generic/plasma-fingerprint-workaround/6.7.5-1.fc44.ajp6/kscreenlocker-6.7.5-1.fc44.ajp6.x86_64.rpm
|
||||||
payload_sha256=d64515f3c7cb9c44b1c8fc194e90503be5944b1d24094aab2f68d989b3eca381
|
payload_sha256=ce454bfb8bcdb7c80e698fb0549f7765bc4570f5383ad7314eb2c292b7708532
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Name: plasma-fingerprint-workaround
|
Name: plasma-fingerprint-workaround
|
||||||
Version: 0.1.0
|
Version: 0.1.0
|
||||||
Release: 3%{?dist}
|
Release: 4%{?dist}
|
||||||
Summary: Opt-in patched KScreenLocker for fingerprint recovery after suspend
|
Summary: Opt-in patched KScreenLocker for fingerprint recovery after suspend
|
||||||
|
|
||||||
License: MIT
|
License: MIT
|
||||||
@@ -51,6 +51,9 @@ install -D -m 0644 %{SOURCE3} \
|
|||||||
%{_datadir}/plasma-fingerprint-workaround/payload.conf
|
%{_datadir}/plasma-fingerprint-workaround/payload.conf
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Sat Sep 12 2026 fedora-tools contributors - 0.1.0-4
|
||||||
|
- Update the opt-in payload to Fedora KScreenLocker 6.7.5
|
||||||
|
|
||||||
* Sat Sep 05 2026 fedora-tools contributors - 0.1.0-3
|
* Sat Sep 05 2026 fedora-tools contributors - 0.1.0-3
|
||||||
- Clarify what the workaround patches
|
- Clarify what the workaround patches
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.25)
|
||||||
|
project(fingerprintRetryTests LANGUAGES CXX)
|
||||||
|
|
||||||
|
# Compile the actual patched upstream classes, but never use the host's PAM
|
||||||
|
# configuration or credentials. The test executable supplies a fake PAM API.
|
||||||
|
set(KSCREENLOCKER_SOURCE "" CACHE PATH "Extracted, patched KScreenLocker source")
|
||||||
|
if(NOT EXISTS "${KSCREENLOCKER_SOURCE}/greeter/pamauthenticator.cpp")
|
||||||
|
message(FATAL_ERROR "Set KSCREENLOCKER_SOURCE to the patched source directory")
|
||||||
|
endif()
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
find_package(Qt6 REQUIRED COMPONENTS Core Test)
|
||||||
|
find_package(ECM REQUIRED NO_MODULE)
|
||||||
|
list(APPEND CMAKE_MODULE_PATH "${ECM_MODULE_PATH}")
|
||||||
|
include(ECMQtDeclareLoggingCategory)
|
||||||
|
|
||||||
|
set(sources retrytest.cpp
|
||||||
|
${KSCREENLOCKER_SOURCE}/greeter/pamauthenticator.cpp
|
||||||
|
${KSCREENLOCKER_SOURCE}/greeter/pamauthenticators.cpp)
|
||||||
|
ecm_qt_declare_logging_category(sources
|
||||||
|
HEADER kscreenlocker_greet_logging.h
|
||||||
|
IDENTIFIER KSCREENLOCKER_GREET
|
||||||
|
CATEGORY_NAME kscreenlocker_greet)
|
||||||
|
add_executable(retrytest ${sources})
|
||||||
|
target_include_directories(retrytest PRIVATE
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR} ${KSCREENLOCKER_SOURCE}/greeter)
|
||||||
|
target_compile_definitions(retrytest PRIVATE HAVE_PAM_FAIL_DELAY)
|
||||||
|
target_link_libraries(retrytest PRIVATE Qt6::Core Qt6::Test)
|
||||||
|
enable_testing()
|
||||||
|
add_test(NAME fingerprint-retry COMMAND retrytest)
|
||||||
|
set_tests_properties(fingerprint-retry PROPERTIES TIMEOUT 30)
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
#include "pamauthenticators.h"
|
||||||
|
|
||||||
|
#include <QSignalSpy>
|
||||||
|
#include <QTest>
|
||||||
|
#include <security/pam_appl.h>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
// Test-only PAM implementation. No real authentication or system files are used.
|
||||||
|
struct pam_handle {
|
||||||
|
QByteArray service;
|
||||||
|
pam_conv conversation;
|
||||||
|
int attempts = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
int pam_start(const char *service, const char *, const pam_conv *conversation, pam_handle_t **handle)
|
||||||
|
{
|
||||||
|
*handle = new pam_handle{service, *conversation};
|
||||||
|
return PAM_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pam_end(pam_handle_t *handle, int)
|
||||||
|
{
|
||||||
|
delete handle;
|
||||||
|
return PAM_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pam_set_item(pam_handle_t *, int, const void *) { return PAM_SUCCESS; }
|
||||||
|
int pam_setcred(pam_handle_t *, int) { return PAM_SUCCESS; }
|
||||||
|
const char *pam_strerror(pam_handle_t *, int) { return "simulated PAM result"; }
|
||||||
|
|
||||||
|
int pam_authenticate(pam_handle_t *handle, int)
|
||||||
|
{
|
||||||
|
++handle->attempts;
|
||||||
|
if (handle->service == "missing") {
|
||||||
|
return PAM_MODULE_UNKNOWN;
|
||||||
|
}
|
||||||
|
if (handle->service == "transient" && handle->attempts == 1) {
|
||||||
|
return PAM_AUTHINFO_UNAVAIL;
|
||||||
|
}
|
||||||
|
if (handle->service == "timeout" && handle->attempts == 1) {
|
||||||
|
return PAM_MAXTRIES;
|
||||||
|
}
|
||||||
|
pam_message message{PAM_PROMPT_ECHO_OFF, "Test credential:"};
|
||||||
|
const pam_message *messages = &message;
|
||||||
|
pam_response *response = nullptr;
|
||||||
|
const auto &conversation = handle->conversation;
|
||||||
|
const int result = conversation.conv(1, &messages, &response, conversation.appdata_ptr);
|
||||||
|
if (result != PAM_SUCCESS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const bool accepted = response && response->resp && std::strcmp(response->resp, "correct") == 0;
|
||||||
|
if (response) {
|
||||||
|
std::free(response->resp);
|
||||||
|
std::free(response);
|
||||||
|
}
|
||||||
|
return accepted ? PAM_SUCCESS : PAM_AUTH_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RetryTest : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
private Q_SLOTS:
|
||||||
|
void retryWithActivePassword_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<QString>("service");
|
||||||
|
QTest::newRow("temporary unavailability") << QStringLiteral("transient");
|
||||||
|
QTest::newRow("attempt timeout") << QStringLiteral("timeout");
|
||||||
|
}
|
||||||
|
|
||||||
|
void retryWithActivePassword()
|
||||||
|
{
|
||||||
|
QFETCH(QString, service);
|
||||||
|
auto password = std::make_unique<PamAuthenticator>(QStringLiteral("password"), QStringLiteral("test"));
|
||||||
|
auto fingerprint = std::make_unique<PamAuthenticator>(service, QStringLiteral("test"), PamAuthenticator::Fingerprint);
|
||||||
|
auto *fingerprintPtr = fingerprint.get();
|
||||||
|
QSignalSpy passwordPrompts(password.get(), &PamAuthenticator::promptForSecret);
|
||||||
|
QSignalSpy fingerprintPrompts(fingerprintPtr, &PamAuthenticator::promptForSecret);
|
||||||
|
QSignalSpy fingerprintFailures(fingerprintPtr, &PamAuthenticator::failed);
|
||||||
|
std::vector<std::unique_ptr<PamAuthenticator>> others;
|
||||||
|
others.push_back(std::move(fingerprint));
|
||||||
|
PamAuthenticators authenticators(std::move(password), std::move(others));
|
||||||
|
QSignalSpy successes(&authenticators, &PamAuthenticators::succeeded);
|
||||||
|
|
||||||
|
authenticators.startAuthenticating();
|
||||||
|
QTRY_COMPARE(passwordPrompts.count(), 1);
|
||||||
|
QTRY_COMPARE_WITH_TIMEOUT(fingerprintFailures.count(), 1, 2000);
|
||||||
|
QTRY_VERIFY(!fingerprintPtr->isAvailable());
|
||||||
|
QCOMPARE(authenticators.state(), PamAuthenticators::Authenticating);
|
||||||
|
QVERIFY(!authenticators.isUnlocked());
|
||||||
|
|
||||||
|
authenticators.startAuthenticating();
|
||||||
|
QTRY_COMPARE_WITH_TIMEOUT(fingerprintPrompts.count(), 1, 2000);
|
||||||
|
QTRY_VERIFY(fingerprintPtr->isAvailable());
|
||||||
|
// Repeated starts must not duplicate either active conversation.
|
||||||
|
authenticators.startAuthenticating();
|
||||||
|
QTest::qWait(100);
|
||||||
|
QCOMPARE(passwordPrompts.count(), 1);
|
||||||
|
QCOMPARE(fingerprintPrompts.count(), 1);
|
||||||
|
QCOMPARE(successes.count(), 0);
|
||||||
|
|
||||||
|
// The original password conversation still accepts a response.
|
||||||
|
authenticators.respond("correct");
|
||||||
|
QTRY_COMPARE(successes.count(), 1);
|
||||||
|
QVERIFY(authenticators.isUnlocked());
|
||||||
|
}
|
||||||
|
|
||||||
|
void permanentUnavailability_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<QString>("service");
|
||||||
|
QTest::addColumn<int>("type");
|
||||||
|
QTest::newRow("absent smartcard") << QStringLiteral("transient") << int(PamAuthenticator::Smartcard);
|
||||||
|
QTest::newRow("missing fingerprint module") << QStringLiteral("missing") << int(PamAuthenticator::Fingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
void permanentUnavailability()
|
||||||
|
{
|
||||||
|
QFETCH(QString, service);
|
||||||
|
QFETCH(int, type);
|
||||||
|
PamAuthenticator auth(service, QStringLiteral("test"), PamAuthenticator::NoninteractiveAuthenticatorType(type));
|
||||||
|
QSignalSpy availability(&auth, &PamAuthenticator::availableChanged);
|
||||||
|
QSignalSpy prompts(&auth, &PamAuthenticator::promptForSecret);
|
||||||
|
auth.tryUnlock();
|
||||||
|
QTRY_VERIFY(availability.count() >= 3);
|
||||||
|
QVERIFY(!auth.isAvailable());
|
||||||
|
availability.clear();
|
||||||
|
auth.tryUnlock();
|
||||||
|
QTest::qWait(100);
|
||||||
|
QCOMPARE(availability.count(), 0);
|
||||||
|
QCOMPARE(prompts.count(), 0);
|
||||||
|
QVERIFY(!auth.isUnlocked());
|
||||||
|
}
|
||||||
|
|
||||||
|
void graceAndIncorrectPassword()
|
||||||
|
{
|
||||||
|
auto password = std::make_unique<PamAuthenticator>(QStringLiteral("password"), QStringLiteral("test"));
|
||||||
|
PamAuthenticators authenticators(std::move(password), {});
|
||||||
|
QSignalSpy prompts(&authenticators, &PamAuthenticators::promptForSecretChanged);
|
||||||
|
QSignalSpy failures(&authenticators, &PamAuthenticators::failed);
|
||||||
|
QSignalSpy successes(&authenticators, &PamAuthenticators::succeeded);
|
||||||
|
authenticators.setGraceLocked(true);
|
||||||
|
authenticators.startAuthenticating();
|
||||||
|
QTest::qWait(100);
|
||||||
|
QCOMPARE(prompts.count(), 0);
|
||||||
|
authenticators.setGraceLocked(false);
|
||||||
|
authenticators.startAuthenticating();
|
||||||
|
QTRY_COMPARE(prompts.count(), 1);
|
||||||
|
authenticators.respond("incorrect");
|
||||||
|
QTRY_COMPARE(failures.count(), 1);
|
||||||
|
QCOMPARE(successes.count(), 0);
|
||||||
|
QVERIFY(!authenticators.isUnlocked());
|
||||||
|
authenticators.startAuthenticating();
|
||||||
|
QTRY_COMPARE(prompts.count(), 2);
|
||||||
|
authenticators.respond("correct");
|
||||||
|
QTRY_COMPARE(successes.count(), 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QTEST_GUILESS_MAIN(RetryTest)
|
||||||
|
#include "retrytest.moc"
|
||||||
@@ -5,13 +5,15 @@ set -euo pipefail
|
|||||||
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
source_dir="$repo_root/plasma-fingerprint-workaround/kscreenlocker"
|
source_dir="$repo_root/plasma-fingerprint-workaround/kscreenlocker"
|
||||||
topdir="$repo_root/rpmbuild"
|
topdir="$repo_root/rpmbuild"
|
||||||
version=${KSCREENLOCKER_VERSION:-6.7.4}
|
version=${KSCREENLOCKER_VERSION:-6.7.5}
|
||||||
base_release=${KSCREENLOCKER_BASE_RELEASE:-1}
|
base_release=${KSCREENLOCKER_BASE_RELEASE:-1}
|
||||||
workaround_release=${KSCREENLOCKER_WORKAROUND_RELEASE:-5}
|
workaround_release=${KSCREENLOCKER_WORKAROUND_RELEASE:-6}
|
||||||
archive="$topdir/SOURCES/kscreenlocker-$version.tar.xz"
|
archive="$topdir/SOURCES/kscreenlocker-$version.tar.xz"
|
||||||
archive_url="https://download.kde.org/stable/plasma/$version/kscreenlocker-$version.tar.xz"
|
archive_url="https://download.kde.org/stable/plasma/$version/kscreenlocker-$version.tar.xz"
|
||||||
|
|
||||||
if [[ $version == 6.7.4 ]]; then
|
if [[ $version == 6.7.5 ]]; then
|
||||||
|
default_archive_sha256=6c41ce2cb79d2b85d4fbe5a3a61c09e40c70e5a869ae6f0352c7e1cb3b44606b
|
||||||
|
elif [[ $version == 6.7.4 ]]; then
|
||||||
default_archive_sha256=f9cd525d501f08931f2c0b4d08f5384588967bc89fac62e39ed4ae2da2925a39
|
default_archive_sha256=f9cd525d501f08931f2c0b4d08f5384588967bc89fac62e39ed4ae2da2925a39
|
||||||
else
|
else
|
||||||
default_archive_sha256=
|
default_archive_sha256=
|
||||||
|
|||||||
Reference in New Issue
Block a user