diff options
-rw-r--r-- | README.md | 30 | ||||
-rw-r--r-- | aux/80-fprintd.rules | 29 | ||||
-rwxr-xr-x | fprintd_tk.py | 54 | ||||
-rw-r--r-- | fprintd_tk_lib.py | 66 |
4 files changed, 162 insertions, 17 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..e60cbfb --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# README for fprintd_tk +This project is a desktop application that makes it easy for a user to control fingerprint enrollments. + +## Upstream +The upstream project is hosted at <https://bgstack15.ddns.net/cgit/fprintd_tk> + +## Alternatives + +* Use `fprintd-enroll` on cli. +* Use whatever probably-GNOME software is demonstrated in <https://wiki.debian.org/SecurityManagement/fingerprint%20authentication> + +## Reason for existence +I couldn't find a window-manager friendly gui for managing fingerprints. + +## Using +Run the program. + +## Dependencies + +* [python-tkstackrpms](https://bgstack15.ddns.net/cgit/python3-tkstackrpms/) +* python3-tk +* python3-pil + +The application really needs the user to have password-less auth permission in dbus/polkit to the fprintd actions. You will want a file like [80-fprintd.rules](aux/80-fprintd.rules) + +## Building + +## References + +* Icons adapted from [Numix Circle icon theme](https://github.com/numixproject/numix-icon-theme-circle) under GPL 3.0 diff --git a/aux/80-fprintd.rules b/aux/80-fprintd.rules new file mode 100644 index 0000000..1da22cf --- /dev/null +++ b/aux/80-fprintd.rules @@ -0,0 +1,29 @@ +/* +.. File: 80-fprintd.rules +.. Startdate: 2023-01-12-5 15:59 +.. History: +.. 2024-09-24 updated for current correctness required +.. Purpose: replaced fprintd.pkla for bgconf 0.1.34 +.. Origin: placed by fingerprint-scanner.sh +*/ +polkit.addRule(function(action, subject) { + if ( + ( + action.id.match("net.reactivated.fprint.device.enroll") || + action.id.match("net.reactivated.fprint.device.verify") + ) && subject.active) { + polkit.log("action=" + action); + polkit.log("subject=" + subject); + return polkit.Result.YES; + } +}); +polkit.addRule(function(action, subject) { + if ( + ( + action.id.match("net.reactivated.fprint.device.setusername") + ) && subject.active && subject.isInGroup("admins")) { + polkit.log("action=" + action); + polkit.log("subject=" + subject); + return polkit.Result.YES; + } +}); diff --git a/fprintd_tk.py b/fprintd_tk.py index 492f2eb..f7442d8 100755 --- a/fprintd_tk.py +++ b/fprintd_tk.py @@ -1,11 +1,23 @@ #!/usr/bin/env python3 +# File: fprintd_tk.py +# Location: https://bgstack15.cgit/fprintd-tk +# Author: bgstack15 # Startdate: 2024-09-22-1 14:26 +# SPDX-License-Identifier: GPL-3.0-only +# Title: Gui for fprintd # Purpose: tkinter desktop gui app for management fingerprint enrollments -# Dependencies: -# python3-tkstackrpms, python3-tk, python3-pil -# References: +# Project: fprintd-tk +# History: +# Usage: +# Run from window manager application menu +# Reference: # stackrpms_tk.py # Improve: +# Dependencies: +# dep-devuan: python3-tkstackrpms, python3-tk, python3-pil +# fprintd_tk_lib.py +# Documentation: +# README.md import tkinter as tk, os, tkinter.simpledialog, sys, threading, time import tkstackrpms as stk @@ -19,7 +31,7 @@ SPDX-License-Identifier: GPL-3.0-only Icons adapted from Numix-Icon-Theme-Circle (GPL 3) """ -# configurable by admin +# configurable by admin or installation img_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),"images") # These will be used a lot in the program. @@ -39,13 +51,15 @@ class App(tk.Frame): self.advanced.trace_add("write",self.load_data_into_form) self.current_username = os.getenv("USER") self.username = tk.StringVar(value=self.current_username) + self.verbose = tk.BooleanVar(value=False) self.master.title("Gui for fprintd") imgicon = stk.get_scaled_icon("fingerprint-gui",24,"default", "","apps") self.master.tk.call("wm","iconphoto",self.master._w,imgicon) menu = tk.Menu(self.master) menu_file = tk.Menu(menu,tearoff=0) - menu_file.add_checkbutton(label="Advanced features", variable=self.advanced, underline=0) + menu_file.add_checkbutton(label="Advanced features", variable=self.advanced, underline=0, state="disabled") + menu_file.add_checkbutton(label="Verbose", variable=self.verbose, underline=0) menu_file.add_command(label="Delete...", command=self.func_delete, underline=0) menu_file.add_separator() menu_file.add_command(label="Exit", command=self.func_exit, underline=1) @@ -106,6 +120,15 @@ class App(tk.Frame): # status bar stk.StatusBar(self.master,var=self.statustext) + # check if user has setusername polkit-1 permission, which lets him control fingerprint enrollments for other users. + temp1 = lib.check_setusername_permission(self.func_update_status) + if self.verbose.get(): + print(f"DEBUG (init): has set_username: {temp1}") + if temp1: + #self.chk_advanced.configure(state="enabled") + #menu_file.child[0].child[0].configure(state="enabled") + menu_file.entryconfigure(0,state="normal") + #print(menu_file.children) # and now, load data into form for the first time self.load_data_into_form() @@ -128,10 +151,12 @@ class App(tk.Frame): # show advanced toolbar used_user = self.get_used_user() if advanced: - print(f"DEBUG: showing advanced toolbar") + if self.verbose.get(): + print(f"DEBUG: showing advanced toolbar") self.frm_advanced.grid(row=0,column=0,columnspan=100) else: - print(f"DEBUG: hiding advanced toolbar") + if self.verbose.get(): + print(f"DEBUG: hiding advanced toolbar") self.frm_advanced.grid_forget() # update enrolled fingers icons try: @@ -143,9 +168,11 @@ class App(tk.Frame): if temp1: self.enrolled_fingers = temp1 else: - print(f"DEBUG (load_data_into_form): having to skip empty response from get_enrolled_fingers") + if self.verbose.get(): + print(f"DEBUG (load_data_into_form): having to skip empty response from get_enrolled_fingers") self.func_update_status(f"Unable to read enrolled fingers for user {used_user}.", reload = False) - print(f"DEBUG (load_data_into_form): got user {used_user} enrolled fingers {self.enrolled_fingers}") + if self.verbose.get(): + print(f"DEBUG (load_data_into_form): got user {used_user} enrolled fingers {self.enrolled_fingers}") for i in self.fingers: if str(i.cget('text')) in self.enrolled_fingers: i.config(image=self.img_enrolled) @@ -163,7 +190,8 @@ class App(tk.Frame): self.master.quit() def func_finger_button(self, finger): - print(f"DEBUG: func_finger_button finger {finger}, action {self.action.get()}") + if self.verbose.get(): + print(f"DEBUG: func_finger_button finger {finger}, action {self.action.get()}") action = self.action.get() used_user = self.get_used_user() # position in array is same as the value coming from radio button for actions. @@ -188,13 +216,15 @@ class App(tk.Frame): def func_update_status(self, msg, reload = True): msg = msg.strip() - print(f"DEBUG (func_update_status): msg {msg}",file=sys.stderr) + if self.verbose.get(): + print(f"DEBUG (func_update_status): msg {msg}",file=sys.stderr) self.statustext.set(msg) try: this_finger = [i for i in self.fingers if i.cget("text") == self.last_fingerbutton_used.get()][0] except Exception as e: this_finger = None - print(f"DEBUG (func_update_status): while evaluating last-used finger, got {e}") + if self.verbose.get(): + print(f"DEBUG (func_update_status): while evaluating last-used finger, got {e}") # flash these red failure_messages = [ "enroll-duplicate", diff --git a/fprintd_tk_lib.py b/fprintd_tk_lib.py index bc97448..18470cb 100644 --- a/fprintd_tk_lib.py +++ b/fprintd_tk_lib.py @@ -1,9 +1,20 @@ #!/usr/bin/env python3 +# File: fprintd_tk_lib.py +# Location: https://bgstack15.cgit/fprintd-tk +# Author: bgstack15 # Startdate: 2024-09-22-1 15:33 -# Purpose: backend for fprintd_tk that uses fprintd-* binaries, in case I ever rewrite this to use dbus directly? +# SPDX-License-Identifier: GPL-3.0-only +# Title: Backend for fprintd_tk that uses fprintd binaries +# Purpose: In case this gets rewritten to use dbus directly or something +# Project: fprintd-tk +# History: +# Usage: +# Reference: +# Improve: # Dependencies: # /usr/bin/fprintd-* -# Improve: +# Documentation: +# README.md import os, subprocess, re @@ -17,11 +28,14 @@ prevent_success_messages = [ ] def get_enrolled_fingers(user = None, verbose = False): - # return list of full strings of fingers that are enrolled. + """ + Returns list of full strings of fingers that are enrolled for the listed user. + """ enrolled_fingers = [] if user is None: user = _user - print(f"DEBUG (get_enrolled_fingers): user {user}") + if verbose: + print(f"DEBUG (get_enrolled_fingers): user {user}") proc = subprocess.Popen( ["fprintd-list",user], stdout = subprocess.PIPE, @@ -29,12 +43,16 @@ def get_enrolled_fingers(user = None, verbose = False): ) while True: line = proc.stdout.readline() + if verbose: + print(f"DEBUG (get_enrolled_fingers): line {line}") if not line: break if fre.match(line): enrolled_fingers.append(fre.match(line).groups()[0].strip()) elif re.match("^.*No devices available.*", line): return [] + elif re.match("^.*has no fingers enrolled.*", line): + return ["none"] else: pass return enrolled_fingers @@ -52,7 +70,8 @@ def fprintd_action(action, finger, status_function = None, user = None, verbose if status_function: status_function(f"Invalid action {action}") return False - print(f"DEBUG (fprintd_action): command {command}") + if verbose: + print(f"DEBUG (fprintd_action): command {command}") proc = subprocess.Popen( command, stdout = subprocess.PIPE, @@ -88,3 +107,40 @@ def fprintd_action(action, finger, status_function = None, user = None, verbose break if display: status_function(f"Succeeded! {display_line}") + +def check_setusername_permission(status_function = None, verbose = False): + """ + This permission depends on a rule like this in /etc/polkit-1/rules.d/80-fprintd.rules: + + polkit.addRule(function(action, subject) { + if ( + ( + action.id.match("net.reactivated.fprint.device.setusername") + ) && subject.active && subject.isInGroup("admins")) { + polkit.log("action=" + action); + polkit.log("subject=" + subject); + return polkit.Result.YES; + } + }); + + We need to check for the ability to run `fprintd-list root` and if it does not print "not authorized", we can allow the advanced actions. + """ + proc = subprocess.Popen( + ["fprintd-list","root"], + stdout = subprocess.PIPE, + universal_newlines = True # or maybe text=True + ) + has_setusername = True + while True: + line = proc.stdout.readline() + if verbose: + print(f"DEBUG (check_setusername_permission): line {line}") + if not line: + break + if re.match(".*Not Authorized.*",line): + if verbose: + print(f"DEBUG (check_setusername_permission): setting has_setusername to false...") + has_setusername = False + if status_function: + status_function(f"Have advanced permissions: {has_setusername}") + return has_setusername |