32 Commits

Author SHA1 Message Date
da60bddb3e Release 1.90.9 2021-01-13 13:23:24 +01:00
506d99e90c tests: Check that two consecutive runs work 2021-01-11 12:59:06 +00:00
e7f47e28d7 tests: Add finger and connection sharing for virtual image
This allows testing some more conditions (e.g. forcing VerifyStop to run
into a cancellation).
2021-01-11 12:59:06 +00:00
938c1aac5a device: Add common stoppable_action_completed function
The stoppable actions (Verify/Enroll) have the same logic during
completion. Create a common function to share this logic instead of
copying it in each of the handlers.

Fixes: #97
2021-01-11 12:59:06 +00:00
fd02922608 pam: Pick the device with more enrolled finger prints
When multiple devices are available PAM module will just pick the first
one, even if it has not enrolled fingers.

Since this can't be user configured (yet) we can be a bit smarter and
select the device that has more fingerprints configured for the user.
2021-01-05 12:16:09 +00:00
195f7eaf5f tests/fprintd: Check that fingers deletion will remove user print
But will keep state dir where it is
2021-01-05 13:07:19 +01:00
48ea3b89c9 file_storage: Cleanup the user storage path when removing prints
Try to remove user and devices directories if they are empty.
2021-01-05 13:07:19 +01:00
4cfa6b5b37 file_storage: Remove debug leftovers and add actual debug statements 2021-01-05 13:07:19 +01:00
c685f0d34c file_storage: Don't return an error if the print doesn't exist
We may just try to remove something isn't there so it's not an actual
error from our POV.
2021-01-05 13:07:19 +01:00
eece834231 file_storage: Do not remove the finger path two times
Return the actual operation error instead
2021-01-05 13:07:19 +01:00
3faaa81257 file_storage: Do not ignore STATE_DIRECTORY if it's set to an actual path 2021-01-05 13:07:19 +01:00
b9cdb58a1a device: Load the current finger print and not always the first print
In the garbage collection code we always ended up to load the first
enrolled print, and this may lead to removing from device storage prints
that are actually in use.
2021-01-05 12:57:55 +01:00
ab8dcfaa61 treewide: fix typos 2021-01-04 11:04:13 +01:00
25a97c8276 tests: Add disconnect tests for enroll/verify/identify
We test both the scenario where VerifyStop/EnrollStop is not called
while the operation is still ongoing or when the operation is already
finished.
2021-01-04 11:00:18 +01:00
8057e49d31 tests: Allow claiming in secondary bus helper
This saves an extra step when creating tests that disconnect.
2021-01-04 11:00:18 +01:00
f75e800d5c tests: Allow enrolling from different device
This simplfies disconnect tests as we can do the enroll from the client
that will disconnect later on.
2021-01-04 11:00:18 +01:00
6ae4f5e939 tests: Add get_secondary_bus_and_device helper
It returns a new bus object and the device on that bus. This allows
testing what happens if a client disconnects from the bus.
2021-01-04 11:00:18 +01:00
7c9a04c2ae device: Fix race when client vanishes from bus
We have a condition where a client vanishing instead of cleaning up the
operation using VerifyStop would cause fprintd to hang. This only
happens if the underlying enroll/verify/identify operation has already
finished when the client vanishes.

Fix this by correctly interpreting current_cancellable as a flag for
these operations.

Fixes: #97
2021-01-04 11:00:18 +01:00
7b7aa6e99d device: Fix typos 2021-01-04 09:49:24 +08:00
b624f8c8c7 manager: Do not use unnecessary volatile qualifier on GQuark
As per new GLib in CI image fprintd doesn't build anymore, since
g_once_init_enter now warns about using a volatile value, as this has
never been supposed to be the case, despite its signature [1].

Related to: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1719

[1] https://gitlab.gnome.org/GNOME/glib/-/issues/600
2020-12-19 22:34:12 +01:00
3e81179eca device: Add auto-cleanup function to unset the current action
This is useful in the functions where we have to unset the device's
current action but we may use early-return to handle multiple conditions
such as in open, close and delete functions.
The latest also currently is a bit buggy as it won't reset the state on
some failures.
2020-12-17 16:33:58 +01:00
c6647ba875 tests: Add test that enforces a verify/identify operation restart
In the usual test we cancel the operation immediately by calling
VerifyStop. This (often) tests the case where we don't end up restarting
the Verify operation internally.

We can easily force fprintd to have restarted already internally, so add
a test that does so by sleeping a bit. This should give us a slightly
higher branch coverage in the verify_cb/identify_cb tests.
2020-12-16 14:44:23 +01:00
988ee01f66 tests: Add retry test for PAM 2020-12-16 14:12:37 +01:00
32ee94c8a0 Add compatibility defines to allow compiling with older glib
We need at least the GFlagsClass autoptr, but just pull in most of the
definitions from libfprint.
2020-12-14 11:30:45 +01:00
7d22a2b5b9 Release 1.90.8 2020-12-11 16:00:28 +01:00
de725a91e4 verify: Print message about verification start from callback
It seems that GLib may process multiple DBus signals in one mainloop
iteration. This could cause messages to be re-ordered, which in turn
caused a race condition in the CI that could trigger random failures.
2020-12-11 16:00:28 +01:00
18392cba54 manager: Export the object manager in /net/reactivated/Fprint
Given we're going to use an object manager it can just stay at the root
of the project, while it will be just used to manage the devices
2020-12-11 15:30:26 +01:00
783d82f359 device: Expose method name when logging authorization steps 2020-12-11 14:03:37 +00:00
c00a3375d1 device: Use standard names for local errors and remove unused one 2020-12-11 14:03:37 +00:00
5aa61adabc build: make systemd dependency optional
The systemd dependency is only used to install some systemd service
files. This can easily be made optional.
2020-12-11 15:01:24 +01:00
1fc10f15ee pam: Stop authorization if we couldn't parse signals
This really should never ever happen. If it does, don't continue but
stop instead.
2020-12-11 10:34:51 +01:00
c24badfd68 pam: Move NameOwnerChanged registration after initialization
We must ignore NameOwnerChanged that happen due to automatic startup.
The easy way to do so is to just register it only when we get to the
point that a name owner change has security implications.

While add it, change it to always log at a warning level.

Fixes: #94
2020-12-11 10:34:51 +01:00
12 changed files with 462 additions and 153 deletions

20
NEWS
View File

@ -1,6 +1,26 @@
This file lists notable changes in each release. For the full history of all
changes, see ChangeLog.
Version 1.90.9:
Highlights:
- Fix multiple daemon lockup issues (#97)
- Fix print garbage collection to not delete used prints
- pam: Use the device with the most prints
Version 1.90.8:
It seems that we are finally reaching the end of the tunnel with regard
to regressions. One more issue that cropped up was that a pam_fprintd fix
to avoid a possible authentication bypass caused issues when fprintd was
just started on demand.
Highlights:
- pam: Only listen to NameOwnerChanged after fprintd is known to run (#94)
- Place new ObjectManager DBus API at /net/reactivated/Fprint
Version 1.90.7:
While 1.90.6 fixed a number of issues, we did have a bad regression due

View File

@ -11,7 +11,8 @@ configure_file(
install_dir: dbus_service_dir,
)
configure_file(
if get_option('systemd')
configure_file(
configuration: configuration_data({
'libexecdir': fprintd_installdir,
}),
@ -19,7 +20,8 @@ configure_file(
output: 'fprintd.service',
install: true,
install_dir: systemd_unit_dir,
)
)
endif
polkit_policy = 'net.reactivated.fprint.device.policy'
polkit_policy_target = i18n.merge_file(polkit_policy,

View File

@ -1,5 +1,5 @@
project('fprintd', 'c',
version: '1.90.7',
version: '1.90.9',
license: 'GPLv2+',
default_options: [
'buildtype=debugoptimized',
@ -94,13 +94,17 @@ pod2man = find_program('pod2man', required: get_option('man'))
xsltproc = find_program('xsltproc', required: get_option('gtk_doc'))
# StateDirectory was introduced in systemd 235
systemd_dep = dependency('systemd', version: '>= 235')
systemd_dep = dependency('systemd', version: '>= 235', required: false)
systemd_unit_dir = get_option('systemd_system_unit_dir')
if systemd_unit_dir == ''
if systemd_unit_dir == '' and systemd_dep.found()
systemd_unit_dir = systemd_dep.get_pkgconfig_variable('systemdsystemunitdir')
endif
if get_option('systemd') and systemd_unit_dir == ''
error('systemd development files or systemd_system_unit_dir is needed for systemd support.')
endif
dbus_service_dir = get_option('dbus_service_dir')
dbus_data_dir = datadir
dbus_interfaces_dir = ''

View File

@ -6,6 +6,10 @@ option('man',
description: 'Generate the man files',
type: 'boolean',
value: true)
option('systemd',
description: 'Install system service files',
type: 'boolean',
value: true)
option('systemd_system_unit_dir',
description: 'Directory for systemd service files',
type: 'string')

View File

@ -60,6 +60,11 @@ static unsigned timeout = DEFAULT_TIMEOUT;
#define USEC_PER_SEC ((uint64_t) 1000000ULL)
#define NSEC_PER_USEC ((uint64_t) 1000ULL)
static size_t user_enrolled_prints_num (pam_handle_t *pamh,
sd_bus *bus,
const char *dev,
const char *username);
static uint64_t
now (void)
{
@ -112,11 +117,13 @@ send_err_msg (pam_handle_t *pamh, const char *msg)
static char *
open_device (pam_handle_t *pamh,
sd_bus *bus,
const char *username,
bool *has_multiple_devices)
{
pf_auto (sd_bus_error) error = SD_BUS_ERROR_NULL;
pf_autoptr (sd_bus_message) m = NULL;
size_t num_devices;
size_t max_prints;
const char *path = NULL;
const char *s;
int r;
@ -143,12 +150,23 @@ open_device (pam_handle_t *pamh,
return NULL;
}
if (sd_bus_message_read_basic (m, 'o', &path) < 0)
return NULL;
num_devices = 1;
num_devices = 0;
max_prints = 0;
while ((r = sd_bus_message_read_basic (m, 'o', &s)) > 0)
{
size_t enrolled_prints = user_enrolled_prints_num (pamh, bus, s, username);
if (debug)
pam_syslog (pamh, LOG_DEBUG, "%s prints registered: %" PRIu64, s, enrolled_prints);
if (enrolled_prints > max_prints)
{
max_prints = enrolled_prints;
path = s;
}
num_devices++;
}
*has_multiple_devices = (num_devices > 1);
if (debug)
pam_syslog (pamh, LOG_DEBUG, "Using device %s (out of %ld devices)", path, num_devices);
@ -208,6 +226,7 @@ verify_result (sd_bus_message *m,
if ((r = sd_bus_message_read (m, "sb", &result, &done)) < 0)
{
pam_syslog (data->pamh, LOG_ERR, "Failed to parse VerifyResult signal: %d", r);
data->verify_ret = PAM_AUTHINFO_UNAVAIL;
return 0;
}
@ -255,6 +274,7 @@ verify_finger_selected (sd_bus_message *m,
if (sd_bus_message_read_basic (m, 's', &finger_name) < 0)
{
pam_syslog (data->pamh, LOG_ERR, "Failed to parse VerifyFingerSelected signal: %d", errno);
data->verify_ret = PAM_AUTHINFO_UNAVAIL;
return 0;
}
@ -526,8 +546,8 @@ do_verify (sd_bus *bus,
return PAM_AUTH_ERR;
}
static bool
user_has_prints (pam_handle_t *pamh,
static size_t
user_enrolled_prints_num (pam_handle_t *pamh,
sd_bus *bus,
const char *dev,
const char *username)
@ -555,21 +575,21 @@ user_has_prints (pam_handle_t *pamh,
if (debug)
pam_syslog (pamh, LOG_DEBUG, "ListEnrolledFingers failed for %s: %s",
username, error.message);
return false;
return 0;
}
r = sd_bus_message_enter_container (m, 'a', "s");
if (r < 0)
{
pam_syslog (pamh, LOG_ERR, "Failed to parse answer from ListEnrolledFingers(): %d", r);
return false;
return 0;
}
while ((r = sd_bus_message_read_basic (m, 's', &s)) > 0)
num_fingers++;
sd_bus_message_exit_container (m);
return num_fingers > 0;
return num_fingers;
}
static void
@ -630,6 +650,7 @@ name_owner_changed (sd_bus_message *m,
if (sd_bus_message_read (m, "sss", &name, &old_owner, &new_owner) < 0)
{
pam_syslog (data->pamh, LOG_ERR, "Failed to parse NameOwnerChanged signal: %d", errno);
data->verify_ret = PAM_AUTHINFO_UNAVAIL;
return 0;
}
@ -640,8 +661,7 @@ name_owner_changed (sd_bus_message *m,
* to events from a new name owner otherwise. */
data->verify_ret = PAM_AUTHINFO_UNAVAIL;
if (debug)
pam_syslog (data->pamh, LOG_ERR, "fprintd name owner changed during operation!\n");
pam_syslog (data->pamh, LOG_WARNING, "fprintd name owner changed during operation!");
return 0;
}
@ -649,8 +669,6 @@ name_owner_changed (sd_bus_message *m,
static int
do_auth (pam_handle_t *pamh, const char *username)
{
bool have_prints;
pf_autoptr (verify_data) data = NULL;
pf_autoptr (sd_bus) bus = NULL;
pf_autoptr (sd_bus_slot) name_owner_changed_slot = NULL;
@ -665,6 +683,13 @@ do_auth (pam_handle_t *pamh, const char *username)
return PAM_AUTHINFO_UNAVAIL;
}
data->dev = open_device (pamh, bus, username, &data->has_multiple_devices);
if (data->dev == NULL)
return PAM_AUTHINFO_UNAVAIL;
/* Only connect to NameOwnerChanged when needed. In case of automatic startup
* we rely on the fact that we never see those signals.
*/
name_owner_changed_slot = NULL;
sd_bus_match_signal (bus,
&name_owner_changed_slot,
@ -675,17 +700,6 @@ do_auth (pam_handle_t *pamh, const char *username)
name_owner_changed,
data);
data->dev = open_device (pamh, bus, &data->has_multiple_devices);
if (data->dev == NULL)
return PAM_AUTHINFO_UNAVAIL;
have_prints = user_has_prints (pamh, bus, data->dev, username);
if (debug)
pam_syslog (pamh, LOG_DEBUG, "prints registered: %s\n", have_prints ? "yes" : "no");
if (!have_prints)
return PAM_AUTHINFO_UNAVAIL;
if (claim_device (pamh, bus, data->dev, username))
{
int ret = do_verify (bus, data);

View File

@ -210,6 +210,16 @@ session_data_set_new (FprintDevicePrivate *priv, gchar *sender, gchar *username)
return new;
}
typedef FprintDevice FprintDeviceActionUnset;
static void
auto_device_action_unset (FprintDeviceActionUnset *self)
{
FprintDevicePrivate *priv = fprint_device_get_instance_private (self);
priv->current_action = ACTION_NONE;
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (FprintDeviceActionUnset, auto_device_action_unset);
static void
fprint_device_dispose (GObject *object)
{
@ -717,7 +727,7 @@ _fprint_device_check_for_username (FprintDevice *rdev,
GError **error)
{
g_autoptr(GVariant) ret = NULL;
g_autoptr(GError) err = NULL;
g_autoptr(GError) local_error = NULL;
GDBusConnection *connection;
const char *sender;
struct passwd *user;
@ -734,15 +744,13 @@ _fprint_device_check_for_username (FprintDevice *rdev,
"GetConnectionUnixUser",
g_variant_new ("(s)", sender),
NULL, G_DBUS_CALL_FLAGS_NONE, -1,
NULL, &err);
NULL, &local_error);
if (!ret)
{
g_autoptr(GError) e = NULL;
g_set_error (error, FPRINT_ERROR, FPRINT_ERROR_INTERNAL,
"Could not get conection unix user ID: %s",
err->message);
"Could not get connection unix user ID: %s",
local_error->message);
return NULL;
}
@ -786,14 +794,29 @@ _fprint_device_client_vanished (GDBusConnection *connection,
if (session != NULL &&
g_strcmp0 (session->sender, name) == 0)
{
while (priv->current_action != ACTION_NONE)
{
/* OPEN/CLOSE are not cancellable, we just need to wait */
if (priv->current_cancellable)
g_cancellable_cancel (priv->current_cancellable);
g_main_context_iteration (NULL, TRUE);
if (!priv->current_cancellable)
{
/* This isn't optimal, but for verify/identify/enroll we expect the stop
* command. And we use current_cancellable as a flag to know that the
* underlying operation has finished already.
* If it has finished, unset the current_action. */
switch (priv->current_action)
{
case ACTION_VERIFY:
case ACTION_IDENTIFY:
case ACTION_ENROLL:
priv->current_action = ACTION_NONE;
break;
default:
break;
}
}
while (priv->current_action != ACTION_NONE)
g_main_context_iteration (NULL, TRUE);
/* The session may have disappeared at this point if the device
* was already closing. */
@ -840,11 +863,12 @@ dev_open_cb (FpDevice *dev, GAsyncResult *res, void *user_data)
g_autoptr(SessionData) session = NULL;
g_autoptr(GDBusMethodInvocation) invocation = NULL;
g_autoptr(FprintDeviceActionUnset) action_unset = NULL;
action_unset = rdev;
session = session_data_get (priv);
invocation = g_steal_pointer (&session->invocation);
priv->current_action = ACTION_NONE;
if (!fp_device_open_finish (dev, res, &error))
{
g_autoptr(GError) dbus_error = NULL;
@ -941,12 +965,13 @@ dev_close_cb (FpDevice *dev, GAsyncResult *res, void *user_data)
g_autoptr(SessionData) session = NULL;
g_autoptr(GDBusMethodInvocation) invocation = NULL;
g_autoptr(FprintDeviceActionUnset) action_unset = NULL;
session = session_data_get (priv);
session_data_set_new (priv, NULL, NULL);
invocation = g_steal_pointer (&session->invocation);
action_unset = rdev;
priv->current_action = ACTION_NONE;
if (!fp_device_close_finish (dev, res, &error))
{
g_autoptr(GError) dbus_error = NULL;
@ -1054,7 +1079,7 @@ can_start_action (FprintDevice *rdev, GError **error)
case ACTION_VERIFY:
g_set_error (error,
FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE,
"Enrollment already in progress");
"Verification already in progress");
break;
case ACTION_OPEN:
@ -1085,6 +1110,47 @@ can_start_action (FprintDevice *rdev, GError **error)
return FALSE;
}
static void
stoppable_action_completed (FprintDevice *rdev)
{
g_autoptr(SessionData) session = NULL;
FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev);
FprintDBusDevice *dbus_dev = FPRINT_DBUS_DEVICE (rdev);
session = session_data_get (priv);
/* Return the cancellation or reset action right away if vanished. */
if (priv->current_cancel_invocation)
{
switch (priv->current_action)
{
case ACTION_VERIFY:
case ACTION_IDENTIFY:
fprint_dbus_device_complete_verify_stop (dbus_dev,
g_steal_pointer (&priv->current_cancel_invocation));
break;
case ACTION_ENROLL:
fprint_dbus_device_complete_enroll_stop (dbus_dev,
g_steal_pointer (&priv->current_cancel_invocation));
break;
default:
g_assert_not_reached ();
}
priv->current_action = ACTION_NONE;
session->verify_status_reported = FALSE;
}
else if (g_cancellable_is_cancelled (priv->current_cancellable))
{
priv->current_action = ACTION_NONE;
session->verify_status_reported = FALSE;
}
g_clear_object (&priv->current_cancellable);
}
static void
match_cb (FpDevice *device,
FpPrint *match,
@ -1111,10 +1177,8 @@ static void
verify_cb (FpDevice *dev, GAsyncResult *res, void *user_data)
{
g_autoptr(GError) error = NULL;
g_autoptr(SessionData) session = NULL;
FprintDevice *rdev = user_data;
FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev);
FprintDBusDevice *dbus_dev = FPRINT_DBUS_DEVICE (rdev);
gboolean success;
const char *name;
gboolean match;
@ -1123,8 +1187,6 @@ verify_cb (FpDevice *dev, GAsyncResult *res, void *user_data)
g_assert (!!success == !error);
name = verify_result_to_name (match, error);
session = session_data_get (priv);
g_debug ("verify_cb: result %s", name);
/* Automatically restart the operation for retry failures */
@ -1150,21 +1212,7 @@ verify_cb (FpDevice *dev, GAsyncResult *res, void *user_data)
error->message);
}
/* Return the cancellation or reset action right away if vanished. */
if (priv->current_cancel_invocation)
{
fprint_dbus_device_complete_verify_stop (dbus_dev,
g_steal_pointer (&priv->current_cancel_invocation));
priv->current_action = ACTION_NONE;
session->verify_status_reported = FALSE;
}
else if (g_cancellable_is_cancelled (priv->current_cancellable))
{
priv->current_action = ACTION_NONE;
session->verify_status_reported = FALSE;
}
g_clear_object (&priv->current_cancellable);
stoppable_action_completed (rdev);
}
}
@ -1175,7 +1223,6 @@ identify_cb (FpDevice *dev, GAsyncResult *res, void *user_data)
g_autoptr(FpPrint) match = NULL;
FprintDevice *rdev = user_data;
FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev);
FprintDBusDevice *dbus_dev = FPRINT_DBUS_DEVICE (rdev);
const char *name;
gboolean success;
@ -1208,22 +1255,7 @@ identify_cb (FpDevice *dev, GAsyncResult *res, void *user_data)
error->message);
}
/* Return the cancellation or reset action right away if vanished. */
if (priv->current_cancel_invocation)
{
fprint_dbus_device_complete_verify_stop (dbus_dev,
g_steal_pointer (&priv->current_cancel_invocation));
priv->current_action = ACTION_NONE;
}
else if (g_cancellable_is_cancelled (priv->current_cancellable))
{
g_autoptr(SessionData) session = NULL;
session = session_data_get (priv);
priv->current_action = ACTION_NONE;
session->verify_status_reported = FALSE;
}
g_clear_object (&priv->current_cancellable);
stoppable_action_completed (rdev);
}
}
@ -1446,7 +1478,7 @@ try_delete_print (FprintDevice *rdev)
guint index;
store.print_data_load (priv->dev,
GPOINTER_TO_UINT (fingers->data),
GPOINTER_TO_UINT (finger->data),
username,
&print);
@ -1520,7 +1552,6 @@ enroll_cb (FpDevice *dev, GAsyncResult *res, void *user_data)
g_autoptr(GError) error = NULL;
FprintDevice *rdev = user_data;
FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev);
FprintDBusDevice *dbus_dev = FPRINT_DBUS_DEVICE (rdev);
g_autoptr(FpPrint) print = NULL;
const char *name;
@ -1568,18 +1599,7 @@ enroll_cb (FpDevice *dev, GAsyncResult *res, void *user_data)
if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Device reported an error during enroll: %s", error->message);
/* Return the cancellation or reset action right away if vanished. */
if (priv->current_cancel_invocation)
{
fprint_dbus_device_complete_enroll_stop (dbus_dev,
g_steal_pointer (&priv->current_cancel_invocation));
priv->current_action = ACTION_NONE;
}
else if (g_cancellable_is_cancelled (priv->current_cancellable))
{
priv->current_action = ACTION_NONE;
}
g_clear_object (&priv->current_cancellable);
stoppable_action_completed (rdev);
}
@ -1824,6 +1844,7 @@ fprint_device_delete_enrolled_fingers (FprintDBusDevice *dbus_dev,
FprintDevice *rdev = FPRINT_DEVICE (dbus_dev);
FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev);
g_autoptr(FprintDeviceActionUnset) action_unset = NULL;
g_autoptr(GError) error = NULL;
g_autofree char *user = NULL;
const char *sender;
@ -1841,6 +1862,7 @@ fprint_device_delete_enrolled_fingers (FprintDBusDevice *dbus_dev,
}
priv->current_action = ACTION_DELETE;
action_unset = rdev;
if (!_fprint_device_check_claimed (rdev, invocation, &error))
{
@ -1874,8 +1896,6 @@ fprint_device_delete_enrolled_fingers (FprintDBusDevice *dbus_dev,
if (!opened && fp_device_has_storage (priv->dev))
fp_device_close_sync (priv->dev, NULL, NULL);
priv->current_action = ACTION_NONE;
fprint_dbus_device_complete_delete_enrolled_fingers (dbus_dev,
invocation);
return TRUE;
@ -1890,6 +1910,7 @@ fprint_device_delete_enrolled_fingers2 (FprintDBusDevice *dbus_dev,
g_autoptr(SessionData) session = NULL;
g_autoptr(GError) error = NULL;
g_autoptr(FprintDeviceActionUnset) action_unset = NULL;
if (!_fprint_device_check_claimed (rdev, invocation, &error))
{
@ -1904,13 +1925,12 @@ fprint_device_delete_enrolled_fingers2 (FprintDBusDevice *dbus_dev,
}
priv->current_action = ACTION_DELETE;
action_unset = rdev;
session = session_data_get (priv);
delete_enrolled_fingers (rdev, session->username);
priv->current_action = ACTION_NONE;
fprint_dbus_device_complete_delete_enrolled_fingers2 (dbus_dev,
invocation);
return TRUE;
@ -1925,8 +1945,9 @@ handle_unauthorized_access (FprintDevice *rdev,
g_assert (error);
g_warning ("Client %s not authorized for device %s: %s",
g_warning ("Client %s not authorized to call method '%s' for device %s: %s",
g_dbus_method_invocation_get_sender (invocation),
g_dbus_method_invocation_get_method_name (invocation),
fp_device_get_name (priv->dev),
error->message);
g_dbus_method_invocation_return_gerror (invocation, error);
@ -1980,8 +2001,9 @@ action_authorization_handler (GDBusInterfaceSkeleton *interface,
&error))
return handle_unauthorized_access (rdev, invocation, error);
g_debug ("Authorization granted to %s for device %s!",
g_debug ("Authorization granted to %s to call method '%s' for device %s!",
fp_device_get_name (priv->dev),
g_dbus_method_invocation_get_method_name (invocation),
g_dbus_method_invocation_get_sender (invocation));
return TRUE;

View File

@ -66,6 +66,10 @@ get_storage_path (void)
elems = g_strsplit (path, ":", -1);
storage_path = g_strdup (elems[0]);
}
else if (*path)
{
storage_path = g_strdup (path);
}
}
if (storage_path == NULL)
@ -151,7 +155,6 @@ file_storage_print_data_save (FpPrint *print)
return r;
}
//fp_dbg("saving to %s", path);
g_file_set_contents (path, buf, len, &err);
if (err)
{
@ -161,6 +164,8 @@ file_storage_print_data_save (FpPrint *print)
return err->code;
}
g_debug ("file_storage_print_data_save(): print saved to %s", path);
return 0;
}
@ -172,7 +177,6 @@ load_from_file (char *path, FpPrint **print)
g_autofree char *contents = NULL;
FpPrint *new;
//fp_dbg("from %s", path);
g_file_get_contents (path, &contents, &length, &err);
if (err)
{
@ -226,6 +230,7 @@ file_storage_print_data_load (FpDevice *dev,
int
file_storage_print_data_delete (FpDevice *dev, FpFinger finger, const char *username)
{
g_autoptr(GSList) prints = NULL;
g_autofree gchar *base_store = NULL;
g_autofree gchar *path = NULL;
int r;
@ -234,12 +239,27 @@ file_storage_print_data_delete (FpDevice *dev, FpFinger finger, const char *user
path = get_path_to_print_dscv (dev, finger, base_store);
if (!g_file_test (path, G_FILE_TEST_EXISTS))
return 0;
r = g_unlink (path);
g_debug ("file_storage_print_data_delete(): unlink(\"%s\") %s",
path, g_strerror (r));
/* FIXME: cleanup empty directory */
return g_unlink (path);
prints = file_storage_discover_prints (dev, username);
if (!prints)
{
g_autofree char *dir = g_steal_pointer (&path);
do
{
g_autofree char *tmp = g_steal_pointer (&dir);
dir = g_path_get_dirname (tmp);
}
while (g_str_has_prefix (dir, base_store) && g_rmdir (dir) == 0);
}
return r;
}
static GSList *

View File

@ -55,7 +55,7 @@ typedef enum {
/* Enum of possible permissions, orders and nick matter here:
- The order controls the priority of a required permission when various are
accepted: the lowest the value, the more priorty it has.
accepted: the lowest the value, the more priority it has.
- Nick must match the relative polkit rule.
*/
typedef enum {
@ -91,3 +91,18 @@ FprintDevice *fprint_device_new (FpDevice *dev);
guint32 _fprint_device_get_id (FprintDevice *rdev);
/* Print */
/* TODO */
/* Some compatibility definitions for older GLib. Copied from from libfprint. */
#if !GLIB_CHECK_VERSION (2, 57, 0)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GTypeClass, g_type_class_unref);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GEnumClass, g_type_class_unref);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GFlagsClass, g_type_class_unref);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GParamSpec, g_param_spec_unref);
#else
/* Re-define G_SOURCE_FUNC as we are technically not allowed to use it with
* the version we depend on currently. */
#undef G_SOURCE_FUNC
#endif
#define G_SOURCE_FUNC(f) ((GSourceFunc) (void (*)(void))(f))

View File

@ -295,7 +295,7 @@ fprint_manager_constructed (GObject *object)
GDBusObjectManagerServer *object_manager_server;
object_manager_server =
g_dbus_object_manager_server_new (FPRINT_SERVICE_PATH "/Device");
g_dbus_object_manager_server_new (FPRINT_SERVICE_PATH);
priv->object_manager = G_DBUS_OBJECT_MANAGER (object_manager_server);
priv->dbus_manager = fprint_dbus_manager_skeleton_new ();
@ -432,7 +432,7 @@ fprint_manager_get_default_device (FprintManager *manager,
GQuark
fprint_error_quark (void)
{
static volatile gsize quark = 0;
static gsize quark = 0;
if (g_once_init_enter (&quark))
{

View File

@ -296,24 +296,36 @@ class FPrintdTest(dbusmock.DBusTestCase):
'.*net\.reactivated\.Fprint\.Error\.{}.*'.format(fprint_error))
# From libfprint tests
def send_retry(self, retry_error=FPrint.DeviceRetry.TOO_SHORT):
with Connection(self.sockaddr) as con:
def send_retry(self, retry_error=FPrint.DeviceRetry.TOO_SHORT, con=None):
if con:
con.sendall(struct.pack('ii', -1, retry_error))
return
with Connection(self.sockaddr) as con:
self.send_retry(retry_error, con)
# From libfprint tests
def send_error(self, error=FPrint.DeviceError.GENERAL):
with Connection(self.sockaddr) as con:
def send_error(self, error=FPrint.DeviceError.GENERAL, con=None):
if con:
con.sendall(struct.pack('ii', -2, error))
return
with Connection(self.sockaddr) as con:
self.send_error(error, con)
# From libfprint tests
def send_remove(self):
with Connection(self.sockaddr) as con:
def send_remove(self, con=None):
if con:
con.sendall(struct.pack('ii', -5, 0))
return
with Connection(self.sockaddr) as con:
self.send_remove(con=con)
# From libfprint tests
def send_image(self, image):
def send_image(self, image, con=None):
if con:
img = self.prints[image]
with Connection(self.sockaddr) as con:
mem = img.get_data()
mem = mem.tobytes()
self.assertEqual(len(mem), img.get_width() * img.get_height())
@ -322,6 +334,28 @@ class FPrintdTest(dbusmock.DBusTestCase):
encoded_img += mem
con.sendall(encoded_img)
return
with Connection(self.sockaddr) as con:
self.send_image(image, con)
def send_finger_automatic(self, automatic, con=None):
# Set whether finger on/off is reported around images
if con:
con.sendall(struct.pack('ii', -3, 1 if automatic else 0))
return
with Connection(self.sockaddr) as con:
self.send_finger_automatic(automatic, con=con)
def send_finger_report(self, has_finger, con=None):
# Send finger on/off
if con:
con.sendall(struct.pack('ii', -4, 1 if has_finger else 0))
return
with Connection(self.sockaddr) as con:
self.send_finger_report(has_finger, con=con)
def call_device_method_async(self, method, *args):
""" add cancellable... """
@ -429,10 +463,12 @@ class FPrintdVirtualDeviceBaseTest(FPrintdTest):
if expected is not None:
self.assertEqual(self._last_result, expected)
def enroll_image(self, img, finger='right-index-finger', expected_result='enroll-completed'):
self.device.EnrollStart('(s)', finger)
def enroll_image(self, img, device=None, finger='right-index-finger', expected_result='enroll-completed'):
if device is None:
device = self.device
device.EnrollStart('(s)', finger)
stages = self.device.get_cached_property('num-enroll-stages').unpack()
stages = device.get_cached_property('num-enroll-stages').unpack()
for stage in range(stages):
self.send_image(img)
if stage < stages - 1:
@ -440,7 +476,7 @@ class FPrintdVirtualDeviceBaseTest(FPrintdTest):
else:
self.wait_for_result(expected_result)
self.device.EnrollStop()
device.EnrollStop()
self.assertEqual(self._last_result, expected_result)
def enroll_multiple_images(self, images_override={}, return_index=-1):
@ -462,6 +498,29 @@ class FPrintdVirtualDeviceBaseTest(FPrintdTest):
return (enrolled, enroll_map)
def get_secondary_bus_and_device(self, claim=None):
addr = os.environ['DBUS_SYSTEM_BUS_ADDRESS']
# Get a separate bus connection
bus = Gio.DBusConnection.new_for_address_sync(addr,
Gio.DBusConnectionFlags.MESSAGE_BUS_CONNECTION |
Gio.DBusConnectionFlags.AUTHENTICATION_CLIENT, None, None)
assert bus.is_closed() == False
dev_path = self.device.get_object_path()
dev = Gio.DBusProxy.new_sync(bus,
Gio.DBusProxyFlags.DO_NOT_AUTO_START,
None,
'net.reactivated.Fprint',
dev_path,
'net.reactivated.Fprint.Device',
None)
if claim is not None:
dev.Claim('(s)', claim)
return bus, dev
class FPrintdManagerTests(FPrintdVirtualDeviceBaseTest):
@ -700,23 +759,7 @@ class FPrintdVirtualDeviceTest(FPrintdVirtualDeviceBaseTest):
self.device.Release()
def test_claim_disconnect(self):
addr = os.environ['DBUS_SYSTEM_BUS_ADDRESS']
# Get a separat bus connection
dbus = Gio.DBusConnection.new_for_address_sync(addr,
Gio.DBusConnectionFlags.MESSAGE_BUS_CONNECTION |
Gio.DBusConnectionFlags.AUTHENTICATION_CLIENT, None, None)
assert dbus.is_closed() == False
dev_path = self.device.get_object_path()
dev = Gio.DBusProxy.new_sync(dbus,
Gio.DBusProxyFlags.DO_NOT_AUTO_START,
None,
'net.reactivated.Fprint',
dev_path,
'net.reactivated.Fprint.Device',
None)
bus, dev = self.get_secondary_bus_and_device()
def call_done(obj, result, user_data):
# Ignore the callback (should be an error)
@ -726,8 +769,91 @@ class FPrintdVirtualDeviceTest(FPrintdVirtualDeviceBaseTest):
dev.Claim('(s)', 'testuser', result_handler=call_done)
# Ensure the call is on the wire, then close immediately
dbus.flush_sync()
dbus.close_sync()
bus.flush_sync()
bus.close_sync()
time.sleep(1)
def test_enroll_running_disconnect(self):
bus, dev = self.get_secondary_bus_and_device(claim='testuser')
# Start an enroll and disconnect, without finishing/cancelling
dev.EnrollStart('(s)', 'left-index-finger')
# Ensure the call is on the wire, then close immediately
bus.flush_sync()
bus.close_sync()
time.sleep(1)
def test_enroll_done_disconnect(self):
bus, dev = self.get_secondary_bus_and_device(claim='testuser')
# Start an enroll and disconnect, without finishing/cancelling
dev.EnrollStart('(s)', 'left-index-finger')
# This works because we also receive the signals on the main connection
stages = dev.get_cached_property('num-enroll-stages').unpack()
for stage in range(stages):
self.send_image('whorl')
if stage < stages - 1:
self.wait_for_result('enroll-stage-passed')
else:
self.wait_for_result('enroll-completed')
bus.close_sync()
time.sleep(1)
def test_verify_running_disconnect(self):
bus, dev = self.get_secondary_bus_and_device(claim='testuser')
self.enroll_image('whorl', device=dev)
# Start an enroll and disconnect, without finishing/cancelling
dev.VerifyStart('(s)', 'right-index-finger')
bus.close_sync()
time.sleep(1)
def test_verify_done_disconnect(self):
bus, dev = self.get_secondary_bus_and_device(claim='testuser')
self.enroll_image('whorl', device=dev)
# Start an enroll and disconnect, without finishing/cancelling
dev.VerifyStart('(s)', 'right-index-finger')
self.send_image('whorl')
# Wait for match and sleep a bit to give fprintd time to wrap up
self.wait_for_result('verify-match')
time.sleep(1)
bus.close_sync()
time.sleep(1)
def test_identify_running_disconnect(self):
bus, dev = self.get_secondary_bus_and_device(claim='testuser')
self.enroll_image('whorl', device=dev)
# Start an enroll and disconnect, without finishing/cancelling
dev.VerifyStart('(s)', 'any')
bus.close_sync()
time.sleep(1)
def test_identify_done_disconnect(self):
bus, dev = self.get_secondary_bus_and_device(claim='testuser')
self.enroll_image('whorl', device=dev)
# Start an enroll and disconnect, without finishing/cancelling
dev.VerifyStart('(s)', 'any')
self.send_image('whorl')
# Wait for match and sleep a bit to give fprintd time to wrap up
self.wait_for_result('verify-match')
time.sleep(1)
bus.close_sync()
time.sleep(1)
@ -843,9 +969,11 @@ class FPrintdVirtualDeviceClaimedTest(FPrintdVirtualDeviceBaseTest):
self.device.DeleteEnrolledFingers2()
self.assertFalse(os.path.exists(os.path.join(self.state_dir, 'testuser/virtual_image/0/7')))
self.assertFalse(os.path.exists(os.path.join(self.state_dir, 'testuser')))
self.assertTrue(os.path.exists(self.state_dir))
def test_enroll_invalid_storage_dir(self):
# Directory wil not exist yet
# Directory will not exist yet
os.makedirs(self.state_dir, mode=0o500)
self.addCleanup(os.chmod, self.state_dir, mode=0o700)
@ -1214,6 +1342,12 @@ class FPrintdVirtualDeviceVerificationTests(FPrintdVirtualDeviceBaseTest):
def test_verify_retry_general(self):
self.assertVerifyRetry(FPrint.DeviceRetry.GENERAL, 'verify-retry-scan')
def test_verify_retry_general_restarted(self):
self.assertVerifyRetry(FPrint.DeviceRetry.GENERAL, 'verify-retry-scan')
# Give fprintd time to re-start the request. We can't force the other
# case (cancellation before restart happened), but we can force this one.
time.sleep(1)
def test_verify_retry_too_short(self):
self.assertVerifyRetry(FPrint.DeviceRetry.TOO_SHORT, 'verify-swipe-too-short')
@ -1250,6 +1384,38 @@ class FPrintdVirtualDeviceVerificationTests(FPrintdVirtualDeviceBaseTest):
def test_verify_error_data_full(self):
self.assertVerifyError(FPrint.DeviceError.DATA_FULL, 'verify-unknown-error')
def test_multiple_verify(self):
self.send_image('tented_arch')
self.wait_for_result()
self.assertTrue(self._verify_stopped)
self.assertEqual(self._last_result, 'verify-no-match')
self.device.VerifyStop()
self.device.VerifyStart('(s)', self.verify_finger)
self.send_image('whorl')
self.wait_for_result()
self.assertTrue(self._verify_stopped)
self.assertEqual(self._last_result, 'verify-match')
def test_multiple_verify_cancelled(self):
with Connection(self.sockaddr) as con:
self.send_finger_automatic(False, con=con)
self.send_finger_report(True, con=con)
self.send_image('tented_arch', con=con)
self.wait_for_result()
self.assertTrue(self._verify_stopped)
self.assertEqual(self._last_result, 'verify-no-match')
self.device.VerifyStop()
# We'll be cancelled at this point, so con is invalid
self.device.VerifyStart('(s)', self.verify_finger)
self.send_finger_report(False)
self.send_image('whorl')
self.wait_for_result()
self.assertTrue(self._verify_stopped)
self.assertEqual(self._last_result, 'verify-match')
def test_verify_start_during_verify(self):
with self.assertFprintError('AlreadyInUse'):
self.device.VerifyStart('(s)', self.verify_finger)

40
tests/pam/test_pam_fprintd.py Executable file → Normal file
View File

@ -154,6 +154,21 @@ class TestPamFprintd(dbusmock.DBusTestCase):
tc = pypamtest.TestCase(pypamtest.PAMTEST_AUTHENTICATE, expected_rv=PAM_AUTHINFO_UNAVAIL)
res = pypamtest.run_pamtest("toto", "fprintd-pam-test", [tc], [ 'unused' ])
def test_pam_fprintd_retry(self):
self.setup_device()
script = [
( 'verify-swipe-too-short', False, 1 ),
( 'verify-finger-not-centered', False, 1 ),
( 'verify-match', True, 1 )
]
self.device_mock.SetVerifyScript(script)
tc = pypamtest.TestCase(pypamtest.PAMTEST_AUTHENTICATE, expected_rv=PAM_SUCCESS)
res = pypamtest.run_pamtest("toto", "fprintd-pam-test", [tc], [ 'unused' ])
self.assertRegex(res.info[0], r'Swipe your left little finger across the fingerprint reader')
self.assertRegex(res.errors[0], r'Swipe was too short, try again')
self.assertRegex(res.errors[1], r'Your finger was not centered, try swiping your finger again')
def test_pam_fprintd_no_fingers_while_verifying(self):
self.setup_device()
script = [
@ -221,6 +236,31 @@ class TestPamFprintd(dbusmock.DBusTestCase):
self.assertRegex(res.info[0], r'Place your left middle finger on FDO Sandpaper Reader')
self.assertEqual(len(res.errors), 0)
def test_pam_fprintd_multi_reader_not_all_enrolled(self):
# Add a 1st device with actual enrolled prints
device_path = self.obj_fprintd_mock.AddDevice('FDO Empty reader', 3, 'press')
empty_reader = self.dbus_con.get_object('net.reactivated.Fprint', device_path)
empty_reader.SetEnrolledFingers('toto', dbus.Array(set([]), signature='s'))
# Add a 2nd device with actual enrolled prints
device_path = self.obj_fprintd_mock.AddDevice('FDO Most Used Reader', 3, 'press')
sandpaper_device_mock = self.dbus_con.get_object('net.reactivated.Fprint', device_path)
sandpaper_device_mock.SetEnrolledFingers('toto', ['left-middle-finger', 'right-middle-finger'])
script = [
( 'verify-match', True, 2 )
]
sandpaper_device_mock.SetVerifyScript(script)
# Add a 3rd device, with only one enrolled finger
self.setup_device()
self.device_mock.SetEnrolledFingers('toto', ['left-middle-finger'])
tc = pypamtest.TestCase(pypamtest.PAMTEST_AUTHENTICATE, expected_rv=PAM_SUCCESS)
res = pypamtest.run_pamtest("toto", "fprintd-pam-test", [tc], [ 'unused' ])
self.assertRegex(res.info[0], r'Place your left middle finger on FDO Most Used Reader')
self.assertEqual(len(res.errors), 0)
def test_pam_fprintd_last_try_auth(self):
self.setup_device()
script = [

View File

@ -160,7 +160,10 @@ verify_started_cb (GObject *obj,
struct VerifyState *verify_state = user_data;
if (fprint_dbus_device_call_verify_start_finish (FPRINT_DBUS_DEVICE (obj), res, &verify_state->error))
{
g_print ("Verify started!\n");
verify_state->started = TRUE;
}
}
static void
@ -226,7 +229,6 @@ do_verify (FprintDBusDevice *dev)
g_clear_error (&verify_state.error);
exit (1);
}
g_print ("Verify started!\n");
/* VerifyStatus signals are processing, wait for completion. */
while (!verify_state.completed)