Commit d5cd51e4 authored by Benjamin REED's avatar Benjamin REED

working build, basically feature complete, could use some bug testing

parent 792c364a
Pipeline #3049 canceled with stages
import sys
from typing import Optional
import secrets
import os
import random
......@@ -156,22 +157,200 @@ class State:
file.close()
def caesar_cipher(inp, offset):
pass
def generate_password(length, charset):
s = []
for i in range(0, length):
s.append(random.choice(list(charset)))
return ''.join(s)
def aes_256(inp):
pass
def handle_args(db: str, password: str):
state = State(db, password)
return state
def handle_command(state: State, args: [str], cfg) -> Optional[bool]:
command = args[0]
if command == "q" or command == "quit" or command == "exit":
if len(args) == 2:
if args[1] == "f" or args[1] == "force" or args[1] == "nosave":
if len(args) > 2 and args[2] == "y":
return True
elif confirm("Are you sure you want to exit without saving?", "y", "n"):
return True
else:
return False
if command == "cfg" or command == "config" or command == "settings":
if len(args) < 2:
print(f"Settings: hints: {cfg['hints']}, ...")
return None
s = args[1].split("=")
if len(s) < 2:
print(f"Incorrect format for modifying program parameters. Usage: {USAGES['cfg']}")
return None
if s[0] not in cfg:
print(f"Unknown cfg parameter '{s[0]}'. Type 'cfg' for a list of config parameters.")
return None
try:
cfg[s[0]] = s[1]
except:
print(f"Incorrect format for cfg parameter {s[0]}, expected type {type(cfg[s[0]])}")
elif command == "help" or command == "h" or command == "?":
if len(args) == 1:
print("Available commands: ")
print("\t" + ", ".join(USAGES.keys()))
if cfg['hints'] == True:
print("(Hint: type 'help <command name>' for more detailed info about that command)")
else:
cmd = args[1]
if not cmd in USAGES:
print("Unknown command, you may have mistyped it.")
return None
print(USAGES[cmd])
elif command == "echo":
if len(args) < 2:
print(f"Too few arguments for {command}, usage: {USAGES['echo']}")
return None
print(" ".join(args[1:]))
elif command == "save" or command == "s":
try:
state.save()
print(f"State saved to {filepath}!")
except:
print("There was an error saving, maybe back up your stuff now. Sorry")
elif command == "drop" or command == "delete" or command == "del" or command == "rm":
if len(args) < 2:
print(f"Too few arguments for command {command}, usage: {USAGES['drop']}")
return None
label = args[1].strip().lower()
if label in state.to_remove:
if confirm(f"Are you sure you want to PERMANANTLY DELETE {label}? FOREVER????? (Note: this CANNOT be undone)", "y", "n"):
del state.passwords[label]
del state.to_remove[state.to_remove.index(label)]
print(f"Password '{label}' was permanently removed from database. This cannot be undone.")
elif label in state.passwords:
if confirm(f"Are you sure you want to delete {label}?", "y", "n"):
state.to_remove.append(label)
print(f"Password '{label}' removed from database.")
if cfg['hints'] == True:
print("(Hint: use 'undo' to undo this operation, and 'history' to see previous deleted passwords and restore them)")
else:
print(f"Password '{label}' was not found, have you spelt it correctly?")
elif command == "undo" or command == "un":
if len(state.to_remove) == 0:
print("Already on latest change")
return None
ltst = state.to_remove.pop()
print(f"Undone latest deletion: {ltst}")
elif command == "restore" or command == "res" or command == "r":
if len(args) < 2:
print(f"Too few arguments for command {command}. Usage: {USAGES['restore']}")
return None
args[1].strip()
og_label = args[1].lower()
label = og_label
if og_label in state.passwords and (og_label in state.overwritten or og_label in state.to_remove):
print("You will have to rename this password to restore it.")
label_in = input("New label (If left empty, a number will be used): ")
if label_in == "":
label = str(len(state.passwords) + 1) # oh so hacky, i hate python and i want my damn state to be operated on by its own damn functions. I hate this.
else:
label_in.strip()
label = label_in.lower()
if og_label in state.to_remove:
del state.to_remove[state.to_remove.index(og_label)]
print(f"Restored {og_label}")
elif og_label in state.overwritten:
del state.overwritten[state.overwritten.index(og_label)]
state.passwords[label] = state.passwords[f"old({og_label})"]
del state.passwords[f"old({og_label})"]
print(f"Restored {og_label}")
else:
print(f"Label '{og_label}' could not be found in deleted or overwritten passwords")
elif command == "rename" or command == "rn":
if len(args) < 3:
print(f"Too few args for {command}. Usage: {USAGES['rename']}.")
return None
if args[1] in state.passwords:
state.passwords[args[2]] = state.passwords[args[1]]
del state.passwords[args[1]]
print(f"{args[1]} -> {args[2]}")
else:
print(f"{args[1]} is not a known password label")
elif command == "history" or command == "his" or command == "undolist":
print("Deleted:")
for remd in state.to_remove:
print(f"{remd}: [...]", end=' ')
print("\nOverwritten:")
for over in state.overwritten:
print(f"{over}: [...]", end=' ')
print("")
elif command == "new": # Form: new <length> [ <label> ]
if len(args) < 2:
print(f"Too few arguments for {command}, usage: {USAGES['new']}")
return None
try:
length = int(args[1])
except:
print(f"Error parsing length as number, usage: {USAGES['new']}")
return None
if len(args) >= 3:
label = args[2]
else:
label = ""
if label in state.passwords:
print(f"Error generating new password, label '{label}' is already in use.")
if label in state.overwritten:
if confim("Do you want to PERMANENTLY OVERWRITE {label}? (THIS CANNOT BE UNDONE)", "y", "n"):
todo
if confirm("Would you like to replace it?", "y", "n"):
state.overwritten.append(label)
state.passwords[f"old({label})"] = state.passwords[label]
print(f"Overwriting '{label}' with new password...")
if cfg['hints'] == True:
print("(Hint: use the 'restore' command to undo this action)")
else:
return None
charset = "all"
chars = set()
if len(args) == 4:
charset = args[3]
if charset == "all":
chars = LOWERCASE_ALPHABET | UPPERCASE_ALPHABET | NUMBERS | SPECIAL
else:
s = charset.split("-")
for c in list(s[0]):
chars |= CHARSET_MAP[c]
if len(s) > 1:
for c in s[1]: # list of chars to exclude
chars ^= set(c)
password = generate_password(length, chars)
print(str(password))
state.add_password(label, password)
elif command == "list" or command == "ls":
if len(args) == 1:
# if state.passwords.len() >= 10:
# TODO: make it possible to scroll thru a list of passwords
for l in state.passwords:
if l not in state.to_remove and "old" not in l:
print(f"{l}: [...]")
if cfg['hints'] == True:
print("(Hint: type 'list <key>' to decrypt your chosen password")
elif len(args) == 2:
print(state.get_password(args[1]).decode('utf-8'))
return None
def main():
filepath = ""
if len(sys.argv) < 2:
......@@ -221,190 +400,16 @@ def main():
if len(args) == 0:
continue
command = args[0]
if command == "q" or command == "quit" or command == "exit":
if len(args) == 2:
if args[1] == "f" or args[1] == "force" or args[1] == "nosave":
if confirm("Are you sure you want to exit without saving?", "y", "n"):
return
should_immediately_exit = None
try:
should_immediately_exit = handle_command(state, args, cfg)
except e:
print(f"ERROR: handling the inputted command: {e}")
if should_immediately_exit is not None:
if should_immediately_exit:
return
else:
break
if command == "cfg" or command == "config" or command == "settings":
if len(args) < 2:
print(f"Settings: hints: {cfg['hints']}, ...")
continue
s = args[1].split("=")
if len(s) < 2:
print(f"Incorrect format for modifying program parameters. Usage: {USAGES['cfg']}")
continue
if s[0] not in cfg:
print(f"Unknown cfg parameter '{s[0]}'. Type 'cfg' for a list of config parameters.")
continue
try:
cfg[s[0]] = s[1]
except:
print(f"Incorrect format for cfg parameter {s[0]}, expected type {type(cfg[s[0]])}")
elif command == "help" or command == "h" or command == "?":
if len(args) == 1:
print("Available commands: ")
print("\t" + ", ".join(USAGES.keys()))
if cfg['hints'] == True:
print("(Hint: type 'help <command name>' for more detailed info about that command)")
else:
cmd = args[1]
if not cmd in USAGES:
print("Unknown command, you may have mistyped it.")
continue
print(USAGES[cmd])
elif command == "echo":
if len(args) < 2:
print(f"Too few arguments for {command}, usage: {USAGES['echo']}")
continue
print(" ".join(args[1:]))
elif command == "dbg":
print(vars(state))
elif command == "save" or command == "s":
try:
state.save()
print(f"State saved to {filepath}!")
except:
print("There was an error saving, maybe back up your stuff now. Sorry")
elif command == "drop" or command == "delete" or command == "del" or command == "rm":
if len(args) < 2:
print(f"Too few arguments for command {command}, usage: {USAGES['drop']}")
continue
label = args[1].strip().lower()
if label in state.to_remove:
if confirm(f"Are you sure you want to PERMANANTLY DELETE {label}? FOREVER????? (Note: this CANNOT be undone)", "y", "n"):
del state.passwords[label]
del state.to_remove[state.to_remove.index(label)]
print(f"Password '{label}' was permanently removed from database. This cannot be undone.")
elif label in state.passwords:
if confirm(f"Are you sure you want to delete {label}?", "y", "n"):
state.to_remove.append(label)
print(f"Password '{label}' removed from database.")
if cfg['hints'] == True:
print("(Hint: use 'undo' to undo this operation, and 'history' to see previous deleted passwords and restore them)")
else:
print(f"Password '{label}' was not found, have you spelt it correctly?")
elif command == "undo" or command == "un":
if len(state.to_remove) == 0:
print("Already on latest change")
continue
ltst = state.to_remove.pop()
print(f"Undone latest deletion: {ltst}")
elif command == "restore" or command == "res" or command == "r":
if len(args) < 2:
print(f"Too few arguments for command {command}. Usage: {USAGES['restore']}")
continue
args[1].strip()
og_label = args[1].lower()
label = og_label
if og_label in state.passwords and (og_label in state.overwritten or og_label in state.to_remove):
print("You will have to rename this password to restore it.")
label_in = input("New label (If left empty, a number will be used): ")
if label_in == "":
label = str(len(state.passwords) + 1) # oh so hacky, i hate python and i want my damn state to be operated on by its own damn functions. I hate this.
else:
label_in.strip()
label = label_in.lower()
if og_label in state.to_remove:
del state.to_remove[state.to_remove.index(og_label)]
print(f"Restored {og_label}")
elif og_label in state.overwritten:
del state.overwritten[state.overwritten.index(og_label)]
state.passwords[label] = state.passwords[f"old({og_label})"]
del state.passwords[f"old({og_label})"]
print(f"Restored {og_label}")
else:
print(f"Label '{og_label}' could not be found in deleted or overwritten passwords")
elif command == "rename" or command == "rn":
if len(args) < 3:
print(f"Too few args for {command}. Usage: {USAGES['rename']}.")
continue
if args[1] in state.passwords:
state.passwords[args[2]] = state.passwords[args[1]]
del state.passwords[args[1]]
print(f"{args[1]} -> {args[2]}")
else:
print(f"{args[1]} is not a known password label")
elif command == "history" or command == "his" or command == "undolist":
print("Deleted:")
for remd in state.to_remove:
print(f"{remd}: [...]", end=' ')
print("\nOverwritten:")
for over in state.overwritten:
print(f"{over}: [...]", end=' ')
print("")
elif command == "new": # Form: new <length> [ <label> ]
if len(args) < 2:
print(f"Too few arguments for {command}, usage: {USAGES['new']}")
continue
try:
length = int(args[1])
except:
print(f"Error parsing length as number, usage: {USAGES['new']}")
continue
if len(args) >= 3:
label = args[2]
else:
label = ""
if label in state.passwords:
print(f"Error generating new password, label '{label}' is already in use.")
if label in state.overwritten:
if confim("Do you want to PERMANENTLY OVERWRITE {label}? (THIS CANNOT BE UNDONE)", "y", "n"):
todo
if confirm("Would you like to replace it?", "y", "n"):
state.overwritten.append(label)
state.passwords[f"old({label})"] = state.passwords[label]
print(f"Overwriting '{label}' with new password...")
if cfg['hints'] == True:
print("(Hint: use the 'restore' command to undo this action)")
else:
continue
charset = "all"
chars = set()
if len(args) == 4:
charset = args[3]
if charset == "all":
chars = LOWERCASE_ALPHABET | UPPERCASE_ALPHABET | NUMBERS | SPECIAL
else:
s = charset.split("-")
for c in list(s[0]):
chars |= CHARSET_MAP[c]
if len(s) > 1:
for c in s[1]: # list of chars to exclude
chars ^= set(c)
password = generate_password(length, chars)
print(str(password))
state.add_password(label, password)
elif command == "list" or command == "ls":
if len(args) == 1:
# if state.passwords.len() >= 10:
# TODO: make it possible to scroll thru a list of passwords
for l in state.passwords:
if l not in state.to_remove and "old" not in l:
print(f"{l}: [...]")
if cfg['hints'] == True:
print("(Hint: type 'list <key>' to decrypt your chosen password")
elif len(args) == 2:
print(state.get_password(args[1]).decode('utf-8'))
state.save()
print("Thanks for using!")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment