Completed CLI rework

This commit is contained in:
boltgolt 2018-04-13 21:19:28 +02:00
parent 0cdb4b78bd
commit 4f9e240869
11 changed files with 189 additions and 137 deletions

View file

@ -14,7 +14,7 @@ sudo apt update
sudo apt install howdy sudo apt install howdy
``` ```
This will guide you through the installation. When that's done run `sudo howdy add $USER` and replace `$USER` with your username to add a face model. This will guide you through the installation. When that's done run `sudo howdy add` to add a face model.
If nothing went wrong we should be able to run sudo by just showing your face. Open a new terminal and run `sudo -i` to see it in action. If nothing went wrong we should be able to run sudo by just showing your face. Open a new terminal and run `sudo -i` to see it in action.
@ -22,22 +22,22 @@ If nothing went wrong we should be able to run sudo by just showing your face. O
### Command line ### Command line
The installer adds a `howdy` command to manage face models for the current user. Use `howdy help` to list the available options. The installer adds a `howdy` command to manage face models for the current user. Use `howdy --help` to list the available options.
Usage: Usage:
``` ```
howdy <command> [user] [argument] howdy [-U user] [-y] command [argument]
``` ```
| Command | Description | User required | | Command | Description |
|-----------|-----------------------------------------------|---------------| |-----------|-----------------------------------------------|
| `add` | Add a new face model for the given user | Yes | | `add` | Add a new face model for the given user |
| `clear` | Remove all face models for the given user | Yes | | `clear` | Remove all face models for the given user |
| `config` | Open the config file in nano | No | | `config` | Open the config file in nano |
| `disable` | Disable or enable howdy | No | | `disable` | Disable or enable howdy |
| `list` | List all saved face models for the given user | Yes | | `list` | List all saved face models for the given user |
| `remove` | Remove a specific model for the given user | Yes | | `remove` | Remove a specific model for the given user |
| `test` | Test the camera and recognition methods | No | | `test` | Test the camera and recognition methods |
### Troubleshooting ### Troubleshooting

View file

@ -7,102 +7,91 @@ import os
import subprocess import subprocess
import getpass import getpass
import argparse import argparse
import builtins
# Try to get the original username (not "root") from shell
user = subprocess.check_output("echo $(logname 2>/dev/null || echo $SUDO_USER)", shell=True).decode("ascii").strip() user = subprocess.check_output("echo $(logname 2>/dev/null || echo $SUDO_USER)", shell=True).decode("ascii").strip()
# If that fails, try to get the direct user
if user == "root" or user == "": if user == "root" or user == "":
env_user = getpass.getuser().strip() env_user = getpass.getuser().strip()
if env_user == "root" or env_user == "": # If even that fails, error out
if env_user == "":
print("Could not determine user, please use the --user flag") print("Could not determine user, please use the --user flag")
sys.exit(1) sys.exit(1)
else: else:
user = env_user user = env_user
# Check if we have rootish rights
if os.getenv("SUDO_USER") is None:
print("Please run this command with sudo")
sys.exit(1)
# Basic command setup
parser = argparse.ArgumentParser(description="Command line interface for Howdy face authentication.", parser = argparse.ArgumentParser(description="Command line interface for Howdy face authentication.",
formatter_class=argparse.RawDescriptionHelpFormatter, formatter_class=argparse.RawDescriptionHelpFormatter,
add_help=False, add_help=False,
prog="howdy", prog="howdy",
epilog="For support please visit\nhttps://github.com/Boltgolt/howdy") epilog="For support please visit\nhttps://github.com/Boltgolt/howdy")
# Add an argument for the command
parser.add_argument("command", parser.add_argument("command",
help="The command option to execute, can be one of the following: add, clear, config, disable, list, remove or test.", help="The command option to execute, can be one of the following: add, clear, config, disable, list, remove or test.",
metavar="command", metavar="command",
choices=["add", "clear", "config", "disable", "list", "remove", "test"]) choices=["add", "clear", "config", "disable", "list", "remove", "test"])
# Add an argument for the extra arguments of diable and remove
parser.add_argument("argument", parser.add_argument("argument",
help="Either 0 or 1 for the disable command, or the model ID for the remove command.", help="Either 0 (enable) or 1 (disable) for the disable command, or the model ID for the remove command.",
nargs="?") nargs="?")
# Add the user flag
parser.add_argument("-U", "--user", parser.add_argument("-U", "--user",
default=user, default=user,
help="Set the user account to use.") help="Set the user account to use.")
# Add the -y flag
parser.add_argument("-y", parser.add_argument("-y",
help="Skip all questions.", help="Skip all questions.",
action="store_true") action="store_true")
# Overwrite the default help message so we can use a uppercase S
parser.add_argument("-h", "--help", parser.add_argument("-h", "--help",
action="help", action="help",
default=argparse.SUPPRESS, default=argparse.SUPPRESS,
help="Show this help message and exit.") help="Show this help message and exit.")
# If we only have 1 argument we print the help text
if len(sys.argv) < 2: if len(sys.argv) < 2:
print("current active user: " + user + "\n") print("current active user: " + user + "\n")
parser.print_help() parser.print_help()
sys.exit(0) sys.exit(0)
# Parse all arguments above
args = parser.parse_args() args = parser.parse_args()
print(args) # Save the args and user as builtins which can be accessed by the imports
sys.exit(0) builtins.howdy_args = args
builtins.howdy_user = args.user
# Check if if a command has been given and print help otherwise # Beond this point the user can't change anymore, if we still have root as user we need to abort
if (len(sys.argv) < 2): if args.user == "root":
print("Howdy IR face recognition help") print("Can't run howdy commands as root, please run this command with the --user flag")
import cli.help sys.exit(1)
sys.exit()
# The command given # Execute the right command
cmd = sys.argv[1] if args.command == "add":
import cli.add
# Call the right files for commands that don't need root elif args.command == "clear":
if cmd == "help": import cli.clear
print("Howdy IR face recognition") elif args.command == "config":
import cli.help import cli.config
elif cmd == "test": elif args.command == "disable":
import cli.disable
elif args.command == "list":
import cli.list
elif args.command == "remove":
import cli.remove
elif args.command == "test":
import cli.test import cli.test
else:
# Check if the minimum of 3 arugemnts has been met and print help otherwise
if (len(sys.argv) < 3):
print("Howdy IR face recognition help")
import cli.help
sys.exit()
# Requre sudo for comamnds that need root rights to read the model files
if os.getenv("SUDO_USER") is None:
print("Please run this command with sudo")
sys.exit(1)
# Frome here on we require the second argument to be the username,
# switching the command to the 3rd
cmd = sys.argv[2]
if cmd == "list":
import cli.list
elif cmd == "add":
import cli.add
elif cmd == "remove":
import cli.remove
elif cmd == "clear":
import cli.clear
else:
# If the comand is invalid, check if the user hasn't swapped the username and command
if sys.argv[1] in ["list", "add", "remove", "clear"]:
print("Usage: howdy <user> <command>")
sys.exit(1)
else:
print('Unknown command "' + cmd + '"')
import cli.help
sys.exit(1)

View file

@ -8,6 +8,7 @@ import sys
import json import json
import cv2 import cv2
import configparser import configparser
import builtins
# Try to import face_recognition and give a nice error if we can't # Try to import face_recognition and give a nice error if we can't
# Add should be the first point where import issues show up # Add should be the first point where import issues show up
@ -18,7 +19,7 @@ except ImportError as err:
print("\nCan't import the face_recognition module, check the output of") print("\nCan't import the face_recognition module, check the output of")
print("pip3 show face_recognition") print("pip3 show face_recognition")
sys.exit() sys.exit(1)
# Get the absolute path to the current file # Get the absolute path to the current file
path = os.path.dirname(os.path.abspath(__file__)) path = os.path.dirname(os.path.abspath(__file__))
@ -27,8 +28,7 @@ path = os.path.dirname(os.path.abspath(__file__))
config = configparser.ConfigParser() config = configparser.ConfigParser()
config.read(path + "/../config.ini") config.read(path + "/../config.ini")
# The current user user = builtins.howdy_user
user = sys.argv[1]
# The permanent file to store the encoded model in # The permanent file to store the encoded model in
enc_file = path + "/../models/" + user + ".dat" enc_file = path + "/../models/" + user + ".dat"
# Known encodings # Known encodings
@ -46,11 +46,11 @@ except FileNotFoundError:
encodings = [] encodings = []
# Print a warning if too many encodings are being added # Print a warning if too many encodings are being added
if len(encodings) > 2: if len(encodings) > 3:
print("WARNING: Every additional model slows down the face recognition engine") print("WARNING: Every additional model slows down the face recognition engine")
print("Press ctrl+C to cancel") print("Press ctrl+C to cancel\n")
print("Adding face model for the user account " + user) print("Adding face model for the user " + user)
# Set the default label # Set the default label
label = "Initial model" label = "Initial model"
@ -59,12 +59,16 @@ label = "Initial model"
if len(encodings) > 0: if len(encodings) > 0:
label = "Model #" + str(len(encodings) + 1) label = "Model #" + str(len(encodings) + 1)
# Ask the user for a custom label # Keep de default name if we can't ask questions
label_in = input("Enter a label for this new model [" + label + "]: ") if builtins.howdy_args.y:
print("Using default label \"" + label + "\" because of -y flag")
else:
# Ask the user for a custom label
label_in = input("Enter a label for this new model [" + label + "]: ")
# Set the custom label (if any) and limit it to 24 characters # Set the custom label (if any) and limit it to 24 characters
if label_in != "": if label_in != "":
label = label_in[:24] label = label_in[:24]
# Prepare the metadata for insertion # Prepare the metadata for insertion
insert_model = { insert_model = {
@ -106,12 +110,12 @@ while frames < 60:
# If 0 faces are detected we can't continue # If 0 faces are detected we can't continue
if len(enc) == 0: if len(enc) == 0:
print("No face detected, aborting") print("No face detected, aborting")
sys.exit() sys.exit(1)
# If more than 1 faces are detected we can't know wich one belongs to the user # If more than 1 faces are detected we can't know wich one belongs to the user
if len(enc) > 1: if len(enc) > 1:
print("Multiple faces detected, aborting") print("Multiple faces detected, aborting")
sys.exit() sys.exit(1)
# Totally clean array that can be exported as JSON # Totally clean array that can be exported as JSON
clean_enc = [] clean_enc = []
@ -130,5 +134,6 @@ with open(enc_file, "w") as datafile:
json.dump(encodings, datafile) json.dump(encodings, datafile)
# Give let the user know how it went # Give let the user know how it went
print("Scan complete") print("""Scan complete
print("\nAdded a new model to " + user)
Added a new model to """ + user)

View file

@ -3,30 +3,33 @@
# Import required modules # Import required modules
import os import os
import sys import sys
import builtins
# Get the full path to this file # Get the full path to this file
path = os.path.dirname(os.path.abspath(__file__)) path = os.path.dirname(os.path.abspath(__file__))
# Get the passed user # Get the passed user
user = sys.argv[1] user = builtins.howdy_user
# Check if the models folder is there # Check if the models folder is there
if not os.path.exists(path + "/../models"): if not os.path.exists(path + "/../models"):
print("No models created yet, can't clear them if they don't exist") print("No models created yet, can't clear them if they don't exist")
sys.exit() sys.exit(1)
# Check if the user has a models file to delete # Check if the user has a models file to delete
if not os.path.isfile(path + "/../models/" + user + ".dat"): if not os.path.isfile(path + "/../models/" + user + ".dat"):
print(user + " has no models or they have been cleared already") print(user + " has no models or they have been cleared already")
sys.exit() sys.exit(1)
# Double check with the user # Only ask the user if there's no -y flag
print("This will clear all models for " + user) if not builtins.howdy_args.y:
ans = input("Do you want to continue [y/N]: ") # Double check with the user
print("This will clear all models for " + user)
ans = input("Do you want to continue [y/N]: ")
# Abort if they don't answer y or Y # Abort if they don't answer y or Y
if (ans.lower() != "y"): if (ans.lower() != "y"):
print('\nInerpeting as a "NO"') print('\nInerpeting as a "NO"')
sys.exit() sys.exit(1)
# Delete otherwise # Delete otherwise
os.remove(path + "/../models/" + user + ".dat") os.remove(path + "/../models/" + user + ".dat")

15
src/cli/config.py Normal file
View file

@ -0,0 +1,15 @@
# Open the config file in gedit
# Import required modules
import os
import time
import subprocess
# Let the user know what we're doing
print("Opening condig.ini in gedit")
# Open gedit as a subprocess and fork it
subprocess.Popen(["gedit", os.path.dirname(os.path.realpath(__file__)) + "/../config.ini"],
cwd="/",
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)

41
src/cli/disable.py Normal file
View file

@ -0,0 +1,41 @@
# Set the disable flag
# Import required modules
import sys
import os
import json
import builtins
import fileinput
import configparser
# Get the absolute filepath
config_path = os.path.dirname(os.path.abspath(__file__)) + "/../config.ini"
# Read config from disk
config = configparser.ConfigParser()
config.read(config_path)
# Check if enough arguments have been passed
if builtins.howdy_args.argument == None:
print("Please add a 0 (enable) or a 1 (disable) as an argument")
sys.exit(1)
# Translate the argument to the right string
if builtins.howdy_args.argument == "1":
out_value = "true"
elif builtins.howdy_args.argument == "0":
out_value = "false"
else:
# Of it's not a 0 or a 1, it's invalid
print("Please only use a 0 (enable) or a 1 (disable) as an argument")
sys.exit(1)
# Loop though the config file and only replace the line containing the disable config
for line in fileinput.input([config_path], inplace=1):
print(line.replace("disabled = " + config.get("core", "disabled"), "disabled = " + out_value), end="")
# Print what we just did
if builtins.howdy_args.argument == "1":
print("Howdy has been disabled")
else:
print("Howdy has been enabled")

View file

@ -1,17 +0,0 @@
# Prints a simple help page for the CLI
print("""
Usage:
howdy <user> <command> [argument]
Commands:
help Show this help page
list List all saved face models for the current user
add Add a new face model for the current user
remove [id] Remove a specific model
clear Remove all face models for the current user
test Test the camera and recognition methods
For support please visit
https://github.com/Boltgolt/howdy\
""")

View file

@ -5,10 +5,11 @@ import sys
import os import os
import json import json
import time import time
import builtins
# Get the absolute path and the username # Get the absolute path and the username
path = os.path.dirname(os.path.realpath(__file__)) + "/.." path = os.path.dirname(os.path.realpath(__file__)) + "/.."
user = sys.argv[1] user = builtins.howdy_user
# Check if the models file has been created yet # Check if the models file has been created yet
if not os.path.exists(path + "/models"): if not os.path.exists(path + "/models"):

View file

@ -4,22 +4,23 @@
import sys import sys
import os import os
import json import json
import builtins
# Get the absolute path and the username # Get the absolute path and the username
path = os.path.dirname(os.path.realpath(__file__)) + "/.." path = os.path.dirname(os.path.realpath(__file__)) + "/.."
user = sys.argv[1] user = builtins.howdy_user
# Check if enough arguments have been passed # Check if enough arguments have been passed
if len(sys.argv) == 3: if builtins.howdy_args.argument == None:
print("Please add the ID of the model to remove as an argument") print("Please add the ID of the model you want to remove as an argument")
print("You can find the IDs by running:") print("You can find the IDs by running:")
print("\n\thowdy " + user + " list\n") print("\n\thowdy list\n")
sys.exit(1) sys.exit(1)
# Check if the models file has been created yet # Check if the models file has been created yet
if not os.path.exists(path + "/models"): if not os.path.exists(path + "/models"):
print("Face models have not been initialized yet, please run:") print("Face models have not been initialized yet, please run:")
print("\n\thowdy " + user + " add\n") print("\n\thowdy add\n")
sys.exit(1) sys.exit(1)
# Path to the models file # Path to the models file
@ -30,7 +31,7 @@ try:
encodings = json.load(open(enc_file)) encodings = json.load(open(enc_file))
except FileNotFoundError: except FileNotFoundError:
print("No face model known for the user " + user + ", please run:") print("No face model known for the user " + user + ", please run:")
print("\n\thowdy " + user + " add\n") print("\n\thowdy add\n")
sys.exit(1) sys.exit(1)
# Tracks if a encoding with that id has been found # Tracks if a encoding with that id has been found
@ -38,24 +39,28 @@ found = False
# Loop though all encodings and check if they match the argument # Loop though all encodings and check if they match the argument
for enc in encodings: for enc in encodings:
if str(enc["id"]) == sys.argv[3]: if str(enc["id"]) == builtins.howdy_args.argument:
# Double check with the user # Only ask the user if there's no -y flag
print('This will remove the model called "' + enc["label"] + '" for ' + user) if not builtins.howdy_args.y:
ans = input("Do you want to continue [y/N]: ") # Double check with the user
print('This will remove the model called "' + enc["label"] + '" for ' + user)
ans = input("Do you want to continue [y/N]: ")
# Abort if the answer isn't yes # Abort if the answer isn't yes
if (ans.lower() != "y"): if (ans.lower() != "y"):
print('\nInerpeting as a "NO"') print('\nInerpeting as a "NO"')
sys.exit() sys.exit()
# Add a padding empty line
print()
# Mark as found and print an enter # Mark as found and print an enter
found = True found = True
print()
break break
# Abort if no matching id was found # Abort if no matching id was found
if not found: if not found:
print("No model with ID " + sys.argv[3] + " exists for " + user) print("No model with ID " + builtins.howdy_args.argument + " exists for " + user)
sys.exit() sys.exit()
# Remove the entire file if this encoding is the only one # Remove the entire file if this encoding is the only one
@ -68,11 +73,11 @@ else:
# Loop though all encodin and only add thos that don't need to be removed # Loop though all encodin and only add thos that don't need to be removed
for enc in encodings: for enc in encodings:
if str(enc["id"]) != sys.argv[3]: if str(enc["id"]) != builtins.howdy_args.argument:
new_encodings.append(enc) new_encodings.append(enc)
# Save this new set to disk # Save this new set to disk
with open(enc_file, "w") as datafile: with open(enc_file, "w") as datafile:
json.dump(new_encodings, datafile) json.dump(new_encodings, datafile)
print("Removed model " + sys.argv[3]) print("Removed model " + builtins.howdy_args.argument)

View file

@ -1,3 +1,5 @@
# Howdy config file
[core] [core]
# Do not print anything when a face verification succeeds # Do not print anything when a face verification succeeds
no_confirmation = false no_confirmation = false
@ -11,6 +13,10 @@ suppress_unknown = false
# Expirimental, can behave incorrectly on some systems # Expirimental, can behave incorrectly on some systems
dismiss_lockscreen = false dismiss_lockscreen = false
# Disable howdy in the PAM
# The howdy command will still function
disabled = false
[video] [video]
# The certainty of the detected face belonging to the user of the account # The certainty of the detected face belonging to the user of the account
# On a scale from 1 to 10, values above 5 are not recommended # On a scale from 1 to 10, values above 5 are not recommended

View file

@ -15,6 +15,10 @@ config.read(os.path.dirname(os.path.abspath(__file__)) + "/config.ini")
def doAuth(pamh): def doAuth(pamh):
"""Start authentication in a seperate process""" """Start authentication in a seperate process"""
# Abort is Howdy is disabled
if config.get("core", "disabled") == "true":
sys.exit(0)
# Run compare as python3 subprocess to circumvent python version and import issues # Run compare as python3 subprocess to circumvent python version and import issues
status = subprocess.call(["python3", os.path.dirname(os.path.abspath(__file__)) + "/compare.py", pamh.get_user()]) status = subprocess.call(["python3", os.path.dirname(os.path.abspath(__file__)) + "/compare.py", pamh.get_user()])