Commit 598e71b5ea
Verified · cmc
Layout: unified · split
.github/workflows/pylint.yml added +23
| @@ -0,0 +1,23 @@ | |||
| 1 | name: Pylint | ||
| 2 | |||
| 3 | on: [push] | ||
| 4 | |||
| 5 | jobs: | ||
| 6 | build: | ||
| 7 | runs-on: ubuntu-latest | ||
| 8 | strategy: | ||
| 9 | matrix: | ||
| 10 | python-version: ["3.8", "3.9", "3.10"] | ||
| 11 | steps: | ||
| 12 | - uses: actions/checkout@v4 | ||
| 13 | - name: Set up Python ${{ matrix.python-version }} | ||
| 14 | uses: actions/setup-python@v4 | ||
| 15 | with: | ||
| 16 | python-version: ${{ matrix.python-version }} | ||
| 17 | - name: Install dependencies | ||
| 18 | run: | | ||
| 19 | python -m pip install --upgrade pip | ||
| 20 | pip install pylint pandas dash plotly.express | ||
| 21 | - name: Analysing the code with pylint | ||
| 22 | run: | | ||
| 23 | pylint -d R0801 $(git ls-files '*.py') | ||
Account.py +16
| @@ -1,7 +1,16 @@ | |||
| 1 | """ | ||
| 2 | This script imports necessary modules for database interactions. | ||
| 3 | |||
| 4 | Modules imported: | ||
| 5 | - database: A custom module providing database functionality. | ||
| 6 | """ | ||
| 7 | |||
| 1 | import database | 8 | import database |
| 2 | 9 | ||
| 3 | 10 | ||
| 4 | class Account: | 11 | class Account: |
| 12 | """Represents a login account.""" | ||
| 13 | |||
| 5 | def __init__(self, uuid: str, application: str, username: str, | 14 | def __init__(self, uuid: str, application: str, username: str, |
| 6 | password: str, url: str) -> None: | 15 | password: str, url: str) -> None: |
| 7 | self.uuid = uuid | 16 | self.uuid = uuid |
| @@ -11,6 +20,7 @@ class Account: | |||
| 11 | self.url = url | 20 | self.url = url |
| 12 | 21 | ||
| 13 | def display_account(self) -> None: | 22 | def display_account(self) -> None: |
| 23 | """Print the account details.""" | ||
| 14 | print('ID:', self.uuid) | 24 | print('ID:', self.uuid) |
| 15 | print('Application:', self.application) | 25 | print('Application:', self.application) |
| 16 | print('Username:', self.username) | 26 | print('Username:', self.username) |
| @@ -18,9 +28,15 @@ class Account: | |||
| 18 | print('URL:', self.url) | 28 | print('URL:', self.url) |
| 19 | 29 | ||
| 20 | def save_account(self) -> None: | 30 | def save_account(self) -> None: |
| 31 | """Save the account details to the database.""" | ||
| 21 | database.create_account( | 32 | database.create_account( |
| 22 | self.uuid, self.application, self.username, self.password, self.url) | 33 | self.uuid, self.application, self.username, self.password, self.url) |
| 23 | 34 | ||
| 24 | def delete_account(self) -> bool: | 35 | def delete_account(self) -> bool: |
| 36 | """Delete the account from the database. | ||
| 37 | |||
| 38 | Returns: | ||
| 39 | bool: True if the deletion was successful. | ||
| 40 | """ | ||
| 25 | database.delete_account(self.uuid) | 41 | database.delete_account(self.uuid) |
| 26 | return True | 42 | return True |
crypto.py +30 −15
| @@ -1,30 +1,45 @@ | |||
| 1 | """ | ||
| 2 | This module imports the Fernet symmetric encryption algorithm from the cryptography library. | ||
| 3 | |||
| 4 | It allows for secure encryption and decryption of data using a secret key. | ||
| 5 | """ | ||
| 6 | |||
| 1 | from cryptography.fernet import Fernet | 7 | from cryptography.fernet import Fernet |
| 2 | 8 | ||
| 3 | vault_file = 'vault.sqlite' | 9 | VAULT_FILE = 'vault.sqlite' |
| 4 | 10 | ||
| 5 | 11 | ||
| 6 | def generate_key() -> bytes: | 12 | def generate_key() -> bytes: |
| 7 | new_key = Fernet.generate_key() | 13 | """Generates a new encryption key.""" |
| 8 | return new_key | 14 | return Fernet.generate_key() |
| 9 | 15 | ||
| 10 | 16 | ||
| 11 | def load_key(key_file: str) -> bytes: | 17 | def load_key(key_file: str) -> bytes: |
| 12 | return open(key_file, 'rb').read() | 18 | """ |
| 19 | Loads an existing encryption key from the file. | ||
| 20 | |||
| 21 | Args: | ||
| 22 | key_file (str): Path to the key file. | ||
| 23 | """ | ||
| 24 | with open(key_file, 'rb') as key: | ||
| 25 | return key.read() | ||
| 13 | 26 | ||
| 14 | 27 | ||
| 15 | def encrypt(key, filename=vault_file) -> None: | 28 | def encrypt(key: bytes, filename: str = VAULT_FILE) -> None: |
| 29 | """Encrypts the data in the specified file using the provided key.""" | ||
| 16 | f = Fernet(key) | 30 | f = Fernet(key) |
| 17 | with open(filename, 'rb') as file: | 31 | with open(filename, 'rb') as vault: |
| 18 | file_data = file.read() | 32 | data = vault.read() |
| 19 | encrypted_data = f.encrypt(file_data) | 33 | encrypted_data = f.encrypt(data) |
| 20 | with open(filename, 'wb') as file: | 34 | with open(filename, 'wb') as vault: |
| 21 | file.write(encrypted_data) | 35 | vault.write(encrypted_data) |
| 22 | 36 | ||
| 23 | 37 | ||
| 24 | def decrypt(key, filename=vault_file) -> None: | 38 | def decrypt(key: bytes, filename: str = VAULT_FILE) -> None: |
| 39 | """Decrypts the data in the specified file using the provided key.""" | ||
| 25 | f = Fernet(key) | 40 | f = Fernet(key) |
| 26 | with open(filename, 'rb') as file: | 41 | with open(filename, 'rb') as vault: |
| 27 | encrypted_data = file.read() | 42 | encrypted_data = vault.read() |
| 28 | decrypted_data = f.decrypt(encrypted_data) | 43 | decrypted_data = f.decrypt(encrypted_data) |
| 29 | with open(filename, 'wb') as file: | 44 | with open(filename, 'wb') as vault: |
| 30 | file.write(decrypted_data) | 45 | vault.write(decrypted_data) |
database.py +45 −32
| @@ -1,34 +1,41 @@ | |||
| 1 | # Import Python modules | 1 | """ |
| 2 | This module provides a basic interface for connecting to and interacting with a SQLite database. | ||
| 3 | It includes functions for creating connections, executing queries, and retrieving results. | ||
| 4 | """ | ||
| 5 | |||
| 2 | import sqlite3 | 6 | import sqlite3 |
| 3 | import sys | 7 | import sys |
| 4 | import os | 8 | import os |
| 5 | 9 | ||
| 6 | # Specify the name of the vault database | 10 | VAULT_DECRYPTED = 'vault.sqlite' |
| 7 | vault_decrypted = 'vault.sqlite' | 11 | VAULT_ENCRYPTED = 'vault.sqlite.aes' |
| 8 | vault_encrypted = 'vault.sqlite.aes' | ||
| 9 | 12 | ||
| 10 | 13 | ||
| 11 | # Create the accounts table inside the vault database | ||
| 12 | def create_table() -> None: | 14 | def create_table() -> None: |
| 13 | db_connection = sqlite3.connect(vault_decrypted) | 15 | """Create the accounts table within the vault database.""" |
| 16 | db_connection = sqlite3.connect(VAULT_DECRYPTED) | ||
| 14 | cursor = db_connection.cursor() | 17 | cursor = db_connection.cursor() |
| 15 | cursor.execute( | 18 | cursor.execute( |
| 16 | ''' CREATE TABLE accounts (uuid text, application text, username text, password text, url text) ''') | 19 | ''' CREATE TABLE IF NOT EXISTS accounts (uuid text, application text, |
| 20 | username text, password text, url text) ''' | ||
| 21 | ) | ||
| 17 | db_connection.commit() | 22 | db_connection.commit() |
| 18 | db_connection.close() | 23 | db_connection.close() |
| 19 | 24 | ||
| 20 | 25 | ||
| 21 | # Check if the account table exists within the database | 26 | def check_table() -> bool: |
| 22 | def table_check() -> bool: | 27 | """Check if the 'accounts' table exists within the vault database.""" |
| 23 | check = False | 28 | check = False |
| 24 | db_connection = sqlite3.connect(vault_decrypted) | 29 | db_connection = sqlite3.connect(VAULT_DECRYPTED) |
| 25 | cursor = db_connection.cursor() | 30 | cursor = db_connection.cursor() |
| 26 | cursor.execute( | 31 | cursor.execute( |
| 27 | ''' SELECT count(name) FROM sqlite_master WHERE type='table' AND name='accounts' ''') | 32 | ''' SELECT count(name) FROM sqlite_master WHERE type='table' |
| 33 | AND name='accounts' ''' | ||
| 34 | ) | ||
| 28 | if cursor.fetchone()[0] != 1: | 35 | if cursor.fetchone()[0] != 1: |
| 29 | user_choice = input( | 36 | user_choice = input( |
| 30 | 'Password vault does not exist. Would you like to create it now? (y/n): ') | 37 | 'Password vault does not exist. Would you like to create it now? (y/n): ') |
| 31 | if user_choice == 'y' or user_choice == 'Y': | 38 | if user_choice.lower() == 'y': |
| 32 | create_table() | 39 | create_table() |
| 33 | check = True | 40 | check = True |
| 34 | else: | 41 | else: |
| @@ -40,44 +47,48 @@ def table_check() -> bool: | |||
| 40 | return check | 47 | return check |
| 41 | 48 | ||
| 42 | 49 | ||
| 43 | # Add a new account to the database | 50 | def add_account(uuid: str, application: str, username: str, password: str, |
| 44 | def create_account(uuid: str, application: str, username: str, password: str, | ||
| 45 | url: str) -> None: | 51 | url: str) -> None: |
| 46 | db_connection = sqlite3.connect(vault_decrypted) | 52 | """Add a new account within the vault database.""" |
| 53 | db_connection = sqlite3.connect(VAULT_DECRYPTED) | ||
| 47 | cursor = db_connection.cursor() | 54 | cursor = db_connection.cursor() |
| 48 | cursor.execute( | 55 | cursor.execute( |
| 49 | ''' INSERT INTO accounts VALUES (:uuid,:application,:username,:password,:url) ''', | 56 | ''' INSERT INTO accounts VALUES (:uuid,:application,:username, |
| 50 | {'uuid': uuid, 'application': application, 'username': username, | 57 | :password,:url) ''', { |
| 51 | 'password': password, 'url': url}) | 58 | 'uuid': uuid, 'application': application, 'username': username, |
| 59 | 'password': password, 'url': url | ||
| 60 | } | ||
| 61 | ) | ||
| 52 | db_connection.commit() | 62 | db_connection.commit() |
| 53 | db_connection.close() | 63 | db_connection.close() |
| 54 | 64 | ||
| 55 | 65 | ||
| 56 | # Delete an account with a specified UUID | ||
| 57 | def delete_account(uuid: str) -> None: | 66 | def delete_account(uuid: str) -> None: |
| 58 | db_connection = sqlite3.connect(vault_decrypted) | 67 | """Delete an account within the vault database by its unique ID.""" |
| 68 | db_connection = sqlite3.connect(VAULT_DECRYPTED) | ||
| 59 | cursor = db_connection.cursor() | 69 | cursor = db_connection.cursor() |
| 60 | cursor.execute( | 70 | cursor.execute( |
| 61 | ''' DELETE FROM accounts WHERE uuid = :uuid ''', {'uuid': uuid}) | 71 | ''' DELETE FROM accounts WHERE uuid = :uuid ''', {'uuid': uuid} |
| 72 | ) | ||
| 62 | db_connection.commit() | 73 | db_connection.commit() |
| 63 | db_connection.close() | 74 | db_connection.close() |
| 64 | 75 | ||
| 65 | 76 | ||
| 66 | # Find a specific account by UUID | ||
| 67 | def find_account(uuid: str) -> list: | 77 | def find_account(uuid: str) -> list: |
| 68 | db_connection = sqlite3.connect(vault_decrypted) | 78 | """Find an account within the vault database by its unique ID.""" |
| 79 | db_connection = sqlite3.connect(VAULT_DECRYPTED) | ||
| 69 | cursor = db_connection.cursor() | 80 | cursor = db_connection.cursor() |
| 70 | cursor.execute( | 81 | cursor.execute( |
| 71 | ''' SELECT * FROM accounts WHERE uuid = :uuid ''', {'uuid': uuid}) | 82 | ''' SELECT * FROM accounts WHERE uuid = :uuid ''', {'uuid': uuid} |
| 83 | ) | ||
| 72 | account = cursor.fetchall() | 84 | account = cursor.fetchall() |
| 73 | db_connection.close() | 85 | db_connection.close() |
| 74 | return account | 86 | return account |
| 75 | 87 | ||
| 76 | 88 | ||
| 77 | # Return all accounts found within the database table | ||
| 78 | # The `accounts` variable is a list of tuples | ||
| 79 | def find_accounts() -> list: | 89 | def find_accounts() -> list: |
| 80 | db_connection = sqlite3.connect(vault_decrypted) | 90 | """Return all accounts stored within the vault database.""" |
| 91 | db_connection = sqlite3.connect(VAULT_DECRYPTED) | ||
| 81 | cursor = db_connection.cursor() | 92 | cursor = db_connection.cursor() |
| 82 | cursor.execute(''' SELECT * FROM accounts ''') | 93 | cursor.execute(''' SELECT * FROM accounts ''') |
| 83 | accounts = cursor.fetchall() | 94 | accounts = cursor.fetchall() |
| @@ -86,22 +97,23 @@ def find_accounts() -> list: | |||
| 86 | 97 | ||
| 87 | 98 | ||
| 88 | def update_account(field_name: str, new_value: str, uuid: str) -> None: | 99 | def update_account(field_name: str, new_value: str, uuid: str) -> None: |
| 100 | """Update an account within the vault database by its unique ID.""" | ||
| 89 | queries = { | 101 | queries = { |
| 90 | 'application': 'UPDATE accounts SET application = :new_value WHERE uuid = :uuid', | 102 | 'application': 'UPDATE accounts SET application = :new_value WHERE uuid = :uuid', |
| 91 | 'username': 'UPDATE accounts SET username = :new_value WHERE uuid = :uuid', | 103 | 'username': 'UPDATE accounts SET username = :new_value WHERE uuid = :uuid', |
| 92 | 'password': 'UPDATE accounts SET password = :new_value WHERE uuid = :uuid', | 104 | 'password': 'UPDATE accounts SET password = :new_value WHERE uuid = :uuid', |
| 93 | 'url': 'UPDATE accounts SET url = :new_value WHERE uuid = :uuid' | 105 | 'url': 'UPDATE accounts SET url = :new_value WHERE uuid = :uuid' |
| 94 | } | 106 | } |
| 95 | db_connection = sqlite3.connect(vault_decrypted) | 107 | db_connection = sqlite3.connect(VAULT_DECRYPTED) |
| 96 | cursor = db_connection.cursor() | 108 | cursor = db_connection.cursor() |
| 97 | cursor.execute(queries[field_name], | 109 | cursor.execute(queries[field_name], {'new_value': new_value, 'uuid': uuid}) |
| 98 | {'new_value': new_value, 'uuid': uuid}) | ||
| 99 | db_connection.commit() | 110 | db_connection.commit() |
| 100 | db_connection.close() | 111 | db_connection.close() |
| 101 | 112 | ||
| 102 | 113 | ||
| 103 | def purge_table() -> None: | 114 | def purge_table() -> None: |
| 104 | db_connection = sqlite3.connect(vault_decrypted) | 115 | """Purge the 'accounts' table within the vault database.""" |
| 116 | db_connection = sqlite3.connect(VAULT_DECRYPTED) | ||
| 105 | cursor = db_connection.cursor() | 117 | cursor = db_connection.cursor() |
| 106 | cursor.execute(''' DROP TABLE accounts ''') | 118 | cursor.execute(''' DROP TABLE accounts ''') |
| 107 | db_connection.commit() | 119 | db_connection.commit() |
| @@ -109,4 +121,5 @@ def purge_table() -> None: | |||
| 109 | 121 | ||
| 110 | 122 | ||
| 111 | def purge_database() -> None: | 123 | def purge_database() -> None: |
| 112 | os.remove(vault_decrypted) | 124 | """Purge the entire vault database.""" |
| 125 | os.remove(VAULT_DECRYPTED) | ||
main.py +68 −19
| @@ -1,3 +1,9 @@ | |||
| 1 | """ | ||
| 2 | This script uses argparse to parse command line arguments. | ||
| 3 | |||
| 4 | It imports the required modules and sets up a parser with basic options for demonstration purposes. | ||
| 5 | """ | ||
| 6 | |||
| 1 | import argparse | 7 | import argparse |
| 2 | import crypto | 8 | import crypto |
| 3 | import database | 9 | import database |
| @@ -5,47 +11,87 @@ import process | |||
| 5 | 11 | ||
| 6 | if __name__ == '__main__': | 12 | if __name__ == '__main__': |
| 7 | parser = argparse.ArgumentParser( | 13 | parser = argparse.ArgumentParser( |
| 8 | description='Manage your username and passwords via a convenient CLI vault.') | 14 | description='Manage your username and passwords via a convenient CLI vault.' |
| 15 | ) | ||
| 9 | 16 | ||
| 10 | # Top-level arguments | 17 | # Top-level arguments |
| 11 | group_one = parser.add_mutually_exclusive_group() | 18 | group_one = parser.add_mutually_exclusive_group() |
| 12 | group_one.add_argument( | 19 | group_one.add_argument( |
| 13 | '-n', '--new', help='Create a new account', action='store_true') | 20 | '-n', '--new', |
| 21 | help='Create a new account.', | ||
| 22 | action='store_true' | ||
| 23 | ) | ||
| 14 | group_one.add_argument( | 24 | group_one.add_argument( |
| 15 | '-l', '--list', help='List all saved accounts', action='store_true') | 25 | '-l', '--list', |
| 26 | help='List all saved accounts.', | ||
| 27 | action='store_true' | ||
| 28 | ) | ||
| 16 | group_one.add_argument( | 29 | group_one.add_argument( |
| 17 | '-e', '--edit', help='Edit a saved account', action='store_true') | 30 | '-e', '--edit', |
| 31 | help='Edit a saved account.', | ||
| 32 | action='store_true' | ||
| 33 | ) | ||
| 18 | group_one.add_argument( | 34 | group_one.add_argument( |
| 19 | '-d', '--delete', help='Delete a saved account', action='store_true') | 35 | '-d', '--delete', |
| 36 | help='Delete a saved account.', | ||
| 37 | action='store_true' | ||
| 38 | ) | ||
| 20 | group_one.add_argument( | 39 | group_one.add_argument( |
| 21 | '--purge', help='Purge all accounts and delete the vault', | 40 | '--purge', |
| 22 | action='store_true') | 41 | help=( |
| 42 | 'Purge all accounts and delete the vault. ' | ||
| 43 | '(Caution: this will irreversibly destroy your data.)' | ||
| 44 | ), | ||
| 45 | action='store_true' | ||
| 46 | ) | ||
| 23 | group_one.add_argument( | 47 | group_one.add_argument( |
| 24 | '--encrypt', help='Encrypt the vault', action='store_true') | 48 | '--encrypt', |
| 49 | help='Encrypt the vault.', | ||
| 50 | action='store_true' | ||
| 51 | ) | ||
| 25 | group_one.add_argument( | 52 | group_one.add_argument( |
| 26 | '--decrypt', help='Decrypt the vault', action='store_true') | 53 | '--decrypt', |
| 54 | help='Decrypt the vault.', | ||
| 55 | action='store_true' | ||
| 56 | ) | ||
| 27 | 57 | ||
| 28 | # Encryption flags | 58 | # Encryption flags |
| 29 | group_two = parser.add_mutually_exclusive_group() | 59 | group_two = parser.add_mutually_exclusive_group() |
| 30 | group_two.add_argument( | 60 | group_two.add_argument( |
| 31 | '-g', '--generate', | 61 | '-g', '--generate', |
| 32 | help='When using the --encrypt option, generate a new encryption key', | 62 | help=( |
| 33 | action='store_true') | 63 | 'When using the --encrypt option, generate a new encryption key.' |
| 64 | ), | ||
| 65 | action='store_true' | ||
| 66 | ) | ||
| 34 | group_two.add_argument( | 67 | group_two.add_argument( |
| 35 | '-k', '--keyfile', | 68 | '-k', '--keyfile', |
| 36 | help='When using the --encrypt or --decrypt options, specify an existing key file path', | 69 | help='Path to existing key file.', |
| 37 | action='store', nargs=1, type=str) | 70 | action='store', |
| 71 | nargs=1, | ||
| 72 | type=str | ||
| 73 | ) | ||
| 38 | 74 | ||
| 39 | # Edit flags | 75 | # Edit flags |
| 40 | group_three = parser.add_argument_group() | 76 | group_three = parser.add_argument_group() |
| 41 | group_three.add_argument( | 77 | group_three.add_argument( |
| 42 | '-u', '--uuid', | 78 | '-u', '--uuid', |
| 43 | help='When using the --edit or --delete options, provide the account UUID', | 79 | help=( |
| 44 | action='store', nargs=1, type=str) | 80 | 'When using the --edit or --delete options, provide the account UUID.' |
| 81 | ), | ||
| 82 | action='store', | ||
| 83 | nargs=1, | ||
| 84 | type=str | ||
| 85 | ) | ||
| 45 | group_three.add_argument( | 86 | group_three.add_argument( |
| 46 | '-f', '--field', | 87 | '-f', '--field', |
| 47 | help='When using the --edit option, provide the field to edit', | 88 | help=( |
| 48 | action='store', nargs=1, type=int) | 89 | 'When using the --edit option, specify the field to edit (integer index).' |
| 90 | ), | ||
| 91 | action='store', | ||
| 92 | nargs=1, | ||
| 93 | type=int | ||
| 94 | ) | ||
| 49 | 95 | ||
| 50 | args = parser.parse_args() | 96 | args = parser.parse_args() |
| 51 | 97 | ||
| @@ -59,7 +105,9 @@ if __name__ == '__main__': | |||
| 59 | if args.generate: | 105 | if args.generate: |
| 60 | key = crypto.generate_key() | 106 | key = crypto.generate_key() |
| 61 | print( | 107 | print( |
| 62 | 'WRITE THIS KEY DOWN SOMEWHERE SAFE. YOU WILL NOT BE ABLE TO DECRYPT YOUR DATA WITHOUT IT!') | 108 | 'WRITE THIS KEY DOWN SOMEWHERE SAFE. YOU WILL NOT BE ABLE TO DECRYPT ' |
| 109 | 'YOUR DATA WITHOUT IT!' | ||
| 110 | ) | ||
| 63 | print(key.decode()) | 111 | print(key.decode()) |
| 64 | print('\n') | 112 | print('\n') |
| 65 | else: | 113 | else: |
| @@ -81,4 +129,5 @@ if __name__ == '__main__': | |||
| 81 | process.purge_accounts() | 129 | process.purge_accounts() |
| 82 | else: | 130 | else: |
| 83 | raise TypeError( | 131 | raise TypeError( |
| 84 | 'Please specify a command or use the --help flag for more information.') | 132 | 'Please specify a command or use the --help flag for more information.' |
| 133 | ) | ||
process.py +124 −33
| @@ -1,4 +1,29 @@ | |||
| 1 | from Account import Account | 1 | """ |
| 2 | Password Vault Manager | ||
| 3 | |||
| 4 | This script provides various functions for managing password vaults. | ||
| 5 | It allows users to create, list, edit and delete accounts. | ||
| 6 | |||
| 7 | The `Account` class represents an individual account, with attributes for | ||
| 8 | the application name, username, password, and URL. The database module is used | ||
| 9 | to interact with the SQLite database file (`vault.sqlite`) that stores the | ||
| 10 | accounts data. | ||
| 11 | |||
| 12 | Functions: | ||
| 13 | generate_characters(n): generates a list of random characters | ||
| 14 | shuffle_characters(characters): shuffles the characters to create a password | ||
| 15 | generate_passphrase(n, sep): generates an XKCD-style passphrase with n words and separator | ||
| 16 | list_accounts(): lists all saved accounts in the database | ||
| 17 | delete_account(uuid): deletes an account by its UUID | ||
| 18 | purge_accounts(): purges the entire database (irreversible) | ||
| 19 | create_account(): creates a new account by prompting user for details | ||
| 20 | edit_account(uuid, edit_parameter): edits an existing account's details | ||
| 21 | |||
| 22 | Usage: | ||
| 23 | Run this script in your terminal to access these functions. | ||
| 24 | """ | ||
| 25 | |||
| 26 | from account import Account | ||
| 2 | import database | 27 | import database |
| 3 | from string import ascii_letters, punctuation, digits | 28 | from string import ascii_letters, punctuation, digits |
| 4 | import random | 29 | import random |
| @@ -6,39 +31,70 @@ import uuid | |||
| 6 | from prettytable import PrettyTable | 31 | from prettytable import PrettyTable |
| 7 | 32 | ||
| 8 | 33 | ||
| 9 | # Generate a list of characters | ||
| 10 | def generate_characters(n: int) -> list: | 34 | def generate_characters(n: int) -> list: |
| 11 | characters = list() | 35 | """ |
| 36 | Generates a list of n random characters from the set of ASCII letters, | ||
| 37 | punctuation and digits. | ||
| 38 | |||
| 39 | Args: | ||
| 40 | n (int): The number of characters to generate | ||
| 41 | |||
| 42 | Returns: | ||
| 43 | list: A list of n random characters | ||
| 44 | """ | ||
| 45 | characters = [] | ||
| 12 | password_format = ascii_letters + punctuation + digits | 46 | password_format = ascii_letters + punctuation + digits |
| 13 | for x in range(n): | 47 | for _ in range(n): |
| 14 | characters.append(random.choice(password_format)) | 48 | characters.append(random.choice(password_format)) |
| 15 | return characters | 49 | return characters |
| 16 | 50 | ||
| 17 | 51 | ||
| 18 | # Randomly shuffle the characters | ||
| 19 | def shuffle_characters(characters: list) -> str: | 52 | def shuffle_characters(characters: list) -> str: |
| 53 | """ | ||
| 54 | Shuffles the characters to create a password. | ||
| 55 | |||
| 56 | Args: | ||
| 57 | characters (list): The list of characters | ||
| 58 | |||
| 59 | Returns: | ||
| 60 | str: A string representation of the shuffled characters | ||
| 61 | """ | ||
| 20 | random.shuffle(characters) | 62 | random.shuffle(characters) |
| 21 | character_string = ''.join(characters) | 63 | character_string = ''.join(characters) |
| 22 | return character_string | 64 | return character_string |
| 23 | 65 | ||
| 24 | 66 | ||
| 25 | # Generate a combination of passphrases | ||
| 26 | def generate_passphrase(n: int, sep: str) -> str: | 67 | def generate_passphrase(n: int, sep: str) -> str: |
| 68 | """ | ||
| 69 | Generates an XKCD-style passphrase with n words and separator. | ||
| 70 | |||
| 71 | Args: | ||
| 72 | n (int): The number of words to include | ||
| 73 | sep (str): The separator symbol | ||
| 74 | |||
| 75 | Returns: | ||
| 76 | str: A string representation of the passphrase | ||
| 77 | """ | ||
| 27 | phrases = [] | 78 | phrases = [] |
| 28 | lucky_number = random.choice(range(0, n)) | 79 | lucky_number = random.choice(range(0, n)) |
| 29 | for x in range(0, n): | 80 | for _ in range(n): |
| 30 | line = random.choice(open('wordlist.txt').readlines()) | 81 | line = random.choice(open('wordlist.txt').readlines()) |
| 31 | line = line.replace('\n', '') | 82 | line = line.replace('\n', '') |
| 32 | if x == lucky_number: | 83 | if _ == lucky_number: |
| 33 | phrases.append(line.strip().capitalize() + str(x)) | 84 | phrases.append(line.strip().capitalize() + str(_)) |
| 34 | else: | 85 | else: |
| 35 | phrases.append(line.strip().capitalize()) | 86 | phrases.append(line.strip().capitalize()) |
| 36 | passphrase = sep.join(phrases) | 87 | passphrase = sep.join(phrases) |
| 37 | return passphrase | 88 | return passphrase |
| 38 | 89 | ||
| 39 | 90 | ||
| 40 | # List all saved accounts | ||
| 41 | def list_accounts() -> None: | 91 | def list_accounts() -> None: |
| 92 | """ | ||
| 93 | Lists all saved accounts in the database. | ||
| 94 | |||
| 95 | Returns: | ||
| 96 | None | ||
| 97 | """ | ||
| 42 | accounts = database.find_accounts() | 98 | accounts = database.find_accounts() |
| 43 | t = PrettyTable(['UUID', 'Application', 'Username', 'Password', 'URL']) | 99 | t = PrettyTable(['UUID', 'Application', 'Username', 'Password', 'URL']) |
| 44 | for account in accounts: | 100 | for account in accounts: |
| @@ -46,10 +102,18 @@ def list_accounts() -> None: | |||
| 46 | print(t) | 102 | print(t) |
| 47 | 103 | ||
| 48 | 104 | ||
| 49 | # Delete a single account by UUID | ||
| 50 | def delete_account(uuid: str) -> None: | 105 | def delete_account(uuid: str) -> None: |
| 106 | """ | ||
| 107 | Deletes an account by its UUID. | ||
| 108 | |||
| 109 | Args: | ||
| 110 | uuid (str): The UUID of the account to delete | ||
| 111 | |||
| 112 | Returns: | ||
| 113 | None | ||
| 114 | """ | ||
| 51 | account_record = database.find_account(uuid) | 115 | account_record = database.find_account(uuid) |
| 52 | account: Account = Account(account_record[0][0], | 116 | account = Account(account_record[0][0], |
| 53 | account_record[0][1], | 117 | account_record[0][1], |
| 54 | account_record[0][2], | 118 | account_record[0][2], |
| 55 | account_record[0][3], | 119 | account_record[0][3], |
| @@ -58,52 +122,79 @@ def delete_account(uuid: str) -> None: | |||
| 58 | print('Account successfully deleted.') | 122 | print('Account successfully deleted.') |
| 59 | 123 | ||
| 60 | 124 | ||
| 61 | # Purge the `accounts` table and `vault.sqlite` file | ||
| 62 | def purge_accounts() -> None: | 125 | def purge_accounts() -> None: |
| 126 | """ | ||
| 127 | Purges the entire database (irreversible). | ||
| 128 | |||
| 129 | Returns: | ||
| 130 | None | ||
| 131 | """ | ||
| 63 | check = input( | 132 | check = input( |
| 64 | 'Are you absolutely sure you want to delete your password vault? This action is irreversible. (y/n): ') | 133 | 'Are you absolutely sure you want to delete your password vault? This action is irreversible. (y/n): ') |
| 65 | if check: | 134 | if check.lower() == 'y': |
| 66 | database.purge_table() | 135 | database.purge_table() |
| 67 | database.purge_database() | 136 | database.purge_database() |
| 68 | print('The password vault has been purged. You may now exit or create a new one.') | 137 | print('The password vault has been purged. You may now exit or create a new one.') |
| 69 | 138 | ||
| 70 | 139 | ||
| 71 | # Request user input and create an account | ||
| 72 | def create_account() -> None: | 140 | def create_account() -> None: |
| 141 | """ | ||
| 142 | Creates a new account by prompting user for details. | ||
| 143 | |||
| 144 | Returns: | ||
| 145 | None | ||
| 146 | """ | ||
| 73 | application_string = input('Please enter a name for this account: ') | 147 | application_string = input('Please enter a name for this account: ') |
| 74 | username_string = input('Please enter your username for this account: ') | 148 | username_string = input('Please enter your username for this account: ') |
| 75 | url_string = input('(Optional) Please enter a URL for this account: ') | 149 | url_string = input('(Optional) Please enter a URL for this account: ') |
| 150 | |||
| 76 | password_type = input( | 151 | password_type = input( |
| 77 | '''Do you want a random character password (p), an XKCD-style passphrase | 152 | '''Do you want a random character password (p), an XKCD-style passphrase |
| 78 | (x), or a custom password (c)? (p|x|c): ''') | 153 | (x), or a custom password (c)? (p|x|c): ''' |
| 79 | if password_type == "x" or password_type == "xkcd": | 154 | ) |
| 155 | if password_type not in ['p', 'x', 'c']: | ||
| 156 | print('Error: Invalid choice. Please choose p, x, or c.') | ||
| 157 | return | ||
| 158 | |||
| 159 | if password_type == 'x': | ||
| 80 | password_length = int( | 160 | password_length = int( |
| 81 | input('Please enter number of words to include (min. 2): ')) | 161 | input('Please enter number of words to include (min. 2): ') |
| 82 | password_separator = input( | 162 | ) |
| 83 | 'Please enter your desired separator symbol (_, -, ~, etc.: ') | ||
| 84 | if password_length < 3: | 163 | if password_length < 3: |
| 85 | print('Error: Your passphrase length must be at least 3 words.') | 164 | print('Error: Your passphrase length must be at least 3 words.') |
| 86 | else: | 165 | return |
| 87 | password_string = generate_passphrase( | 166 | password_separator = input( |
| 88 | password_length, password_separator) | 167 | 'Please enter your desired separator symbol (_,-, ~, etc.): ' |
| 89 | elif password_type == "p": | 168 | ) |
| 169 | password_string = generate_passphrase(password_length, password_separator) | ||
| 170 | elif password_type == 'p': | ||
| 90 | password_length = int( | 171 | password_length = int( |
| 91 | input('Please enter your desired password length (min. 8): ')) | 172 | input('Please enter your desired password length (min. 8): ') |
| 173 | ) | ||
| 92 | if password_length < 8: | 174 | if password_length < 8: |
| 93 | print('Error: Your password length must be at least 8 characters.') | 175 | print('Error: Your password length must be at least 8 characters.') |
| 94 | else: | 176 | return |
| 95 | password_characters = generate_characters(password_length) | 177 | password_string = generate_password(password_length) |
| 96 | password_string = shuffle_characters(password_characters) | ||
| 97 | else: | 178 | else: |
| 98 | password_string = input('Please enter your desired password: ') | 179 | password_string = input('Please enter your desired password: ') |
| 180 | |||
| 99 | account = Account(str(uuid.uuid4()), application_string, | 181 | account = Account(str(uuid.uuid4()), application_string, |
| 100 | username_string, password_string, url_string) | 182 | username_string, password_string, url_string) |
| 101 | account.save_account() | 183 | account.save_account() |
| 102 | print('Account saved to the vault. Use `--list` to see all saved accounts.') | 184 | print('Account saved to the vault. Use `--list` to see all saved accounts.') |
| 103 | 185 | ||
| 104 | 186 | ||
| 105 | # Allow users to edit any account info except the UUID | 187 | |
| 106 | def edit_account(uuid: str, edit_parameter: int) -> None: | 188 | def edit_account(uuid: str, edit_parameter: int) -> None: |
| 189 | """ | ||
| 190 | Allow users to edit any account information except the UUID. | ||
| 191 | |||
| 192 | Args: | ||
| 193 | uuid (str): Unique identifier of the account. | ||
| 194 | edit_parameter (int): Parameter indicating which field to edit. | ||
| 195 | Valid values are 1 for application name, 2 for username, | ||
| 196 | 3 for password, and 4 for URL. | ||
| 197 | """ | ||
| 107 | if edit_parameter == 1: | 198 | if edit_parameter == 1: |
| 108 | field_name = 'application' | 199 | field_name = 'application' |
| 109 | new_value = input('Please enter your desired Application name: ') | 200 | new_value = input('Please enter your desired Application name: ') |
| @@ -113,10 +204,10 @@ def edit_account(uuid: str, edit_parameter: int) -> None: | |||
| 113 | elif edit_parameter == 3: | 204 | elif edit_parameter == 3: |
| 114 | field_name = 'password' | 205 | field_name = 'password' |
| 115 | type_check = input( | 206 | type_check = input( |
| 116 | 'Do you want a new random password or to enter a custom password? (random/custom): ') | 207 | 'Do you want a new random password or to enter a custom password? ' |
| 208 | '(random/custom): ').lower() | ||
| 117 | if type_check == 'random': | 209 | if type_check == 'random': |
| 118 | password_length = int( | 210 | password_length = int(input('Please enter your desired password length: ')) |
| 119 | input('Please enter your desired password length: ')) | ||
| 120 | if password_length < 8: | 211 | if password_length < 8: |
| 121 | print('Error: Your password length must be at least 8 characters.') | 212 | print('Error: Your password length must be at least 8 characters.') |
| 122 | else: | 213 | else: |