Skip to content

Commit b84d505

Browse files
author
miguel
committed
Better checking of arguments
1 parent ba9b099 commit b84d505

File tree

2 files changed

+105
-33
lines changed

2 files changed

+105
-33
lines changed

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
# Password Manager
2-
Password manager for the terminal using gnugpg.
3-
42
Python 2 is required.
53

64

password_manager.py

Lines changed: 105 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import hashlib
1414
import json
1515
import random
16+
import inspect
17+
import datetime
1618

1719
import pyaes
1820
import pyperclip
@@ -21,29 +23,33 @@
2123
# TODO
2224
# - Python 3 support
2325
# - Possibility to protect keys of accounts with a second password
26+
# - Command to show data of only specific account and key
2427
# If a key is protected, a second password is required to print value or copy value to clipboard.
28+
# A key is protected if it contains the attribute 'protected' or 'encrypted' to True
2529
# - Different cryptographic methods
30+
# - If defined the environment variable EDITOR_FOR_PASSWORD_MANAGER, then all editions will be done using your that editor and saving intermediate results in temporal documents.
2631
# - More personalization for the generation of random passwords
2732
# - Create key with random password
2833
# - Edit key with random password
34+
# - Add creation and modification timestamps for accounts and keys of accounts
2935
# - Consider these libraries:
3036
# https://github.com/italorossi/ishell
3137
# https://github.com/jonathanslenders/python-prompt-toolkit
3238
# Print and ask copy to clipboard
33-
# To use module in pure python for symmetric encription instead of gnugpg
3439
# Mirar esto:
3540
# https://stackoverflow.com/questions/42568262/how-to-encrypt-text-with-a-value-in-python/44212550
3641
# hashlib.sha256("Nobody inspects the spammish repetition").hexdigest()
3742

3843

3944
PASSWORD_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
4045

41-
WHICH_CMD = 'which'
4246
CLIPBOARD_ENCODING = 'utf-8'
4347

4448
LIST_OF_TRUE_VALUES = ["y", "yes", "1", "on"]
4549
LIST_OF_FALSE_VALUES = ["n", "no", "0", "off"]
4650

51+
SIGNATURE = 'Ps54kNc8ZnUqWgxZ'
52+
4753

4854
def get_boolean(c):
4955
if isinstance(c, bool):
@@ -56,9 +62,37 @@ def get_boolean(c):
5662
return False
5763

5864

65+
def create_timestamp():
66+
return datetime.datetime.utcnow().strftime("%a %b %d %H:%M:%S %Z %Y")
67+
68+
69+
def generate_password(password_length):
70+
letters = string.ascii_lowercase
71+
password = ''.join(random.choice(letters) for i in range(password_length))
72+
73+
return password
74+
75+
76+
def which(program):
77+
import os
78+
def is_exe(fpath):
79+
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
80+
81+
fpath, fname = os.path.split(program)
82+
if fpath:
83+
if is_exe(program):
84+
return program
85+
else:
86+
for path in os.environ["PATH"].split(os.pathsep):
87+
exe_file = os.path.join(path, program)
88+
if is_exe(exe_file):
89+
return exe_file
90+
91+
return None
92+
93+
5994
def executable_exists(name):
60-
return subprocess.call([WHICH_CMD, name],
61-
stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0
95+
return which(name) is not None
6296

6397

6498
class PasswordManagerException(Exception):
@@ -72,6 +106,9 @@ def __str__(self):
72106
return self.__doc__
73107

74108

109+
class BadArguments(PasswordManagerException):
110+
"""Bad arguments passed to command"""
111+
75112
class AccountNotFoundException(PasswordManagerException):
76113
"""Account not found"""
77114

@@ -110,6 +147,7 @@ def info_print(s):
110147
def echo(text, indent=0, indent_char=" ", prefix=""):
111148
print("\n".join(prefix + indent_char*indent + l for l in text.splitlines()))
112149

150+
113151
def decrypt(enc_data, password):
114152
key_32 = hashlib.sha256(password).digest()
115153

@@ -120,7 +158,6 @@ def decrypt(enc_data, password):
120158
return dec_data
121159

122160

123-
124161
def encrypt(dec_data, password):
125162
key_32 = hashlib.sha256(password).digest()
126163

@@ -352,6 +389,8 @@ def create_help(command_methods):
352389

353390
class Password_Manager(object):
354391
__metaclass__ = Password_Manager_Meta
392+
#PROMPT = "Command:"
393+
PROMPT = ">"
355394

356395
def __init__(self, filename, master_password=None, title="Password Manager"):
357396
self._filename = filename
@@ -371,10 +410,26 @@ def all_accounts(self):
371410
return list_of_accounts
372411

373412
def save_accounts(self):
413+
modification_date = create_timestamp()
414+
self._root["last_modification"] = modification_date
415+
self._root["password_manager"] = SIGNATURE
416+
374417
encrypted_data = encrypt(json.dumps(self._root), self._master_password)
375418
with open(self._filename, "wb") as f:
376419
f.write(encrypted_data)
377420

421+
422+
def _print_all_accounts(self):
423+
list_of_accounts = self.all_accounts()
424+
425+
if list_of_accounts:
426+
echo("List of accounts:")
427+
for account_index, account in enumerate(list_of_accounts):
428+
echo("%d. "%account_index + account.name)
429+
430+
else:
431+
warn_print("No account")
432+
378433
def _ask_hidden_value(self, key_name, old_value=None):
379434
while True:
380435
if old_value is None or old_value == "":
@@ -525,28 +580,19 @@ def _command_create(self, account_name=None):
525580

526581
info_print("Account added!")
527582

528-
529583
@cmd("list", "l")
530584
def _command_list(self):
531585
"""List all available account by name"""
586+
self._print_all_acounts()
532587

533-
list_of_accounts = self.all_accounts()
534-
535-
if list_of_accounts:
536-
echo("List of accounts:")
537-
for account_index, account in enumerate(list_of_accounts):
538-
echo("%d. "%account_index + account.name)
539-
540-
else:
541-
warn_print("No account")
542588

543589
@cmd("delete", "d")
544590
def _command_delete(self, index=None):
545591
"""Delete account"""
546592

547593
account = self._ask_account_index(index)
548594

549-
if self._ask_confirmation("are you sure that you want to delete this account?"):
595+
if self._ask_confirmation("are you sure that you want to delete this account '%s'?"%account.name):
550596
account.dump(show_values=self._show_values)
551597

552598
account.remove()
@@ -725,11 +771,10 @@ def _command_clear(self):
725771
clear_screen()
726772
print(self.HELP_MESSAGE)
727773

728-
@cmd("exit")
774+
@cmd("exit", "q")
729775
def _command_exit(self):
730776
"""Exit"""
731777

732-
print "hola"
733778
clear_screen()
734779
exit()
735780

@@ -739,8 +784,8 @@ def _command_generate_password(self, password_length=10):
739784

740785
password_length = int(password_length)
741786

742-
letters = string.ascii_lowercase
743-
print ''.join(random.choice(letters) for i in range(password_length))
787+
password = generate_password(password_length)
788+
print(password)
744789

745790
def run(self):
746791
if os.path.isfile(self._filename):
@@ -771,7 +816,15 @@ def run(self):
771816
return
772817

773818
self._master_password = master_password
774-
self._root = {"accounts": []}
819+
820+
821+
creation_date = create_timestamp()
822+
self._root = {
823+
"accounts": [],
824+
"password_manager": SIGNATURE,
825+
"creation_date": creation_date,
826+
"last_modification": creation_date
827+
}
775828

776829
self.save_accounts()
777830
else:
@@ -782,27 +835,32 @@ def run(self):
782835

783836
try:
784837
root = decrypt(enc_data, self._master_password)
785-
except DecryptionException as e:
786-
error_print("Error doing decryption:\n%s"%str(e))
838+
except ValueError:
839+
error_print("Wrong Password")
787840
return
788841

789-
self._root = json.loads(root)
842+
try:
843+
self._root = json.loads(root)
844+
except ValueError:
845+
error_print("Wrong Password")
846+
return
847+
848+
if "password_manager" not in self._root or self._root["password_manager"] != SIGNATURE:
849+
error_print("Wrong Password")
850+
return
790851

791852
clear_screen()
792853

793854
header = "-"*len(self._title) + "\n" + self._title + "\n" + "-"*len(self._title) + "\n\n" + self.HELP_MESSAGE
794855
print(header)
795856

796-
list_of_accounts = self.all_accounts()
857+
self._print_all_accounts()
858+
print("\nPlease enter a command.")
797859

798-
if list_of_accounts:
799-
print("List of accounts:")
800-
for account_index, account in enumerate(list_of_accounts):
801-
print("%d. "%account_index + account.name)
802860

803861
while True:
804862
try:
805-
user_input = raw_input("\n\nCommand: ")
863+
user_input = raw_input("\n%s "%self.PROMPT)
806864
except KeyboardInterrupt:
807865
return
808866

@@ -823,6 +881,22 @@ def run(self):
823881
else:
824882
args.append(t)
825883

884+
func_args = inspect.getargspec(command_function).args[1:]
885+
886+
try:
887+
if len(args) > len(func_args):
888+
raise BadArguments
889+
890+
func_args = func_args[len(args):]
891+
892+
for kw in kwargs:
893+
if kw not in func_args:
894+
raise BadArguments
895+
896+
except BadArguments:
897+
error_print("Bad arguments")
898+
continue
899+
826900
try:
827901
command_function(*args, **kwargs)
828902
except PasswordManagerException as e:
@@ -832,6 +906,7 @@ def run(self):
832906
else:
833907
error_print("Command not found!")
834908

909+
print("\n")
835910

836911
if __name__ == "__main__":
837912
import argparse
@@ -841,5 +916,4 @@ def run(self):
841916
args = parser.parse_args()
842917
filename = args.filename
843918

844-
845919
Password_Manager(filename).run()

0 commit comments

Comments
 (0)