diff --git a/.gitignore b/.gitignore index e2ef110..71c2247 100644 --- a/.gitignore +++ b/.gitignore @@ -160,5 +160,5 @@ cython_debug/ #.idea/ # User Files -*.json -*.txt +audit_log.txt +config.py \ No newline at end of file diff --git a/FurEver_Friends.py b/FurEver_Friends.py index 36437ce..765ea36 100644 --- a/FurEver_Friends.py +++ b/FurEver_Friends.py @@ -1,40 +1,79 @@ import os -import json import time from colorama import Fore, Style from view_animals import view_animals from add_animal import add_animal from change_adopted_status import change_adopted_status -from common_functions import clear_screen, log_action +from common_functions import clear_screen, log_action, generate_salt, hash_password, load_animal_data from login import login +from edit_animal_entries import modify_animal +from pymongo import MongoClient +from config import mongodb_uri -# File paths for user and animal data -USER_DATA_FILE = "users.json" -ANIMAL_DATA_FILE = "animals.json" -# Default user data if files do not exist + +# Check if config.py exists, if not, prompt the user to enter MongoDB URI and create it +if os.path.isfile('config.py'): + # Read the contents of the config file: + with open('config.py', 'r') as f: + config_content = f.read() + # Check f the URI has been inputted in the file + if 'URI Inputted' in config_content: + uri_inputted = True + else: + uri_inputted = False +else: + uri_inputted = False + +if not uri_inputted: + mongodb_uri = input("Please enter your MongoDB connection URI: ") + # Update config file to indicate that URI has been inputted + with open('config.py', 'w') as f: + f.write(f"mongodb_uri = '{mongodb_uri}'\n") + f.write("# URI Inputted\n") + + uri = mongodb_uri + client = MongoClient(uri) + db = client['animal_rescue'] + users_collection = db['users'] + + # Check if client is connected + if client is not None: + print("Connected to MongoDB successfully.") + time.sleep(2) + else: + print("Failed to connect to MongoDB.") + time.sleep(2) +else: + pass + +# Connect to MongoDB +uri = mongodb_uri +client = MongoClient(uri) +db = client['animal_rescue'] +users_collection = db['users'] + +# Default password +default_password = "ADMIN" + +# Generate salt and hash password +salt = generate_salt() +hashed_password = hash_password(default_password, salt) + +salt_hex = salt.hex() + +# Default user data if collection do not exist DEFAULT_USER_DATA = { - "ADMIN": { - "password": "ADMIN", - "level": 3 - } + "username": "ADMIN", + "hashed_password": hashed_password, + "salt": salt_hex, + "level": 3 } -DEFAULT_ANIMAL_DATA = {} def main(): clear_screen() - # Check if user.json exists, if not, create it with default data - if not os.path.exists(USER_DATA_FILE): - with open(USER_DATA_FILE, 'w') as user_file: - json.dump(DEFAULT_USER_DATA, user_file, indent=4) - - # Check if animals.json exists, if not, create it with default data - if not os.path.exists(ANIMAL_DATA_FILE): - with open(ANIMAL_DATA_FILE, 'w') as animal_file: - json.dump(DEFAULT_ANIMAL_DATA, animal_file, indent=4) - try: while True: # Display main menu options @@ -46,8 +85,8 @@ def main(): if choice == '1': clear_screen() # Pull the username and user level from the login function - username, user_level = login() - if username is not None: + current_user, user_level = login() + if current_user is not None: while True: clear_screen() # Display main menu after successful login @@ -73,17 +112,23 @@ def main(): option = input("\nPlease select an option: ") if option == '1': + time.sleep(1) + log_action(current_user, "Entered, 'Animal Database" ) view_animals() elif option == '2' and user_level >= 2: + time.sleep(1) + log_action(current_user, "Entered 'Add an animal'") add_animal() elif option == '3' and user_level >= 3: + time.sleep(1) change_adopted_status() elif option == '4' and user_level >= 3: - print("\nFeature coming soon") - time.sleep(2) + time.sleep(1) + modify_animal() elif option == str(option_counter) and user_level >= 1: print("\nLogging out...") time.sleep(2) + log_action(current_user, f"Logged Out") clear_screen() break else: @@ -104,4 +149,8 @@ def main(): time.sleep(2) if __name__ == "__main__": + # Check if the users collection exists, if not, create it with default values + collection_names = db.list_collection_names() + if 'users' not in collection_names: + users_collection.insert_one(DEFAULT_USER_DATA) main() \ No newline at end of file diff --git a/add_animal.py b/add_animal.py index 7e984e0..d4025ac 100644 --- a/add_animal.py +++ b/add_animal.py @@ -1,14 +1,18 @@ import time from colorama import Fore, Style -from common_functions import clear_screen, load_data, save_data, log_action +from common_functions import clear_screen, log_action, hash_animal_data, generate_salt from sudo_user import sudo_user +from pymongo import MongoClient +from config import mongodb_uri -ANIMAL_DATA_FILE = "animals.json" +# Connect to MongoDB +uri = mongodb_uri +client = MongoClient(uri) -def add_animal(): - # Load animal data from file - animals = load_data(ANIMAL_DATA_FILE) +db = client['animal_rescue'] +animals_collection = db['animals'] +def add_animal(): # Continuous loop for adding animals while True: clear_screen() @@ -18,7 +22,7 @@ def add_animal(): print("Enter animal details or type 'exit' to cancel:") - #Input fields for animal data + # Input fields for animal data name = input(Fore.CYAN + "Name: " + Style.RESET_ALL).strip() # Check if user wants to exit @@ -46,27 +50,44 @@ def add_animal(): age = int(age) - # Add animals to the data dictionary - animals[name] = { + # Make the user verify their identity + current_user = sudo_user() + + # Generate salt + salt = generate_salt() + + # Hash the new animal data with the salt + hashed_animal_data = hash_animal_data({ 'name': name, 'species': species, 'breed': breed, 'gender': gender, 'age': age, 'adopted': False - } + }, salt) - # Make the user verify their identity - current_user = sudo_user() + # Store the salt in hexadecimal format + salt_hex = salt.hex() - save_data(animals, ANIMAL_DATA_FILE) + # Add hashed animal data to the animals dictionary + animals_collection.insert_one ({ + 'name': name, + 'species': species, + 'breed': breed, + 'gender': gender, + 'age': age, + 'adopted': False, + 'salt': salt_hex, + 'hashed_animal_data': hashed_animal_data, + }) - # Log the action of adding animal into the audit file - log_action(current_user, f"Added animal: {name}") + # Log the action of adding the animal into the audit file + log_action(current_user, f"Added animal: {name}, {species}, {breed}") # Confirm successful addition of the animal print(Fore.GREEN + "\n✨ Animal added successfully! ✨" + Style.RESET_ALL) + log_action(current_user, f"Exited 'Add an animal'") time.sleep(2) # Exit the loop after successful addition - break + break \ No newline at end of file diff --git a/admin_dashboard.py b/admin_dashboard.py index 94d1f00..23a7369 100644 --- a/admin_dashboard.py +++ b/admin_dashboard.py @@ -1,9 +1,10 @@ import time from colorama import Fore, Style -from common_functions import clear_screen, load_data, save_data +from common_functions import clear_screen, log_action from register import register from user_management import user_management + USER_DATA_FILE = "users.json" def admin_dashboard(): @@ -28,6 +29,8 @@ def admin_dashboard(): time.sleep(2) elif option == '4': print("\nLogging out...") + log_action("ADMIN", "Logged Out") + time.sleep(2) exit() else: print(Fore.RED + "\nInvalid option. Please try again." + Style.RESET_ALL) diff --git a/change_adopted_status.py b/change_adopted_status.py index 668d0ea..5c09d6e 100644 --- a/change_adopted_status.py +++ b/change_adopted_status.py @@ -1,34 +1,49 @@ -import json -import os import time from colorama import Fore, Style -from common_functions import clear_screen, load_data, save_data +from sudo_user import sudo_user +from common_functions import clear_screen, load_animal_data, save_data, log_action +from pymongo import MongoClient +from config import mongodb_uri -ANIMAL_DATA_FILE = "animals.json" +# Connect to MongoDB +uri = mongodb_uri +client = MongoClient(uri) + +db = client['animal_rescue'] +animals_collection = db['animals'] def change_adopted_status(): clear_screen() # Load animal data from file - animals = load_data(ANIMAL_DATA_FILE) + animals = load_animal_data(animals_collection) + current_user = sudo_user() - name = input("\nEnter the name of the animal to toggle adoption status: ") + name = input("\nEnter the name of the animal to toggle adoption status (type 'exit' to leave): ") - # Check if animal exists in the data - if name in animals: - - # Toggle the adopted status - animals[name]['adopted'] = not animals[name].get('adopted', False) - save_data(animals, ANIMAL_DATA_FILE) - - # Notify the user about the updated adoptino status - if animals[name]['adopted']: - print(Fore.GREEN + f"\n{name} has been marked as " + Fore.CYAN + "adopted!" + Style.RESET_ALL) - else: - print(Fore.GREEN + f"\n{name} has been marked as " + Fore.RED + "not adopted!" + Style.RESET_ALL) + if name == "exit": + log_action(current_user, "Exited 'Change Adoption Status'") + print("\nExiting...") + time.sleep(2) + return - # Notify the user if the animal is not found else: - print(Fore.RED + f"\nNo animal found with the name {name}" + Style.RESET_ALL) + # Check if animal exists in the data + if name in animals: + + # Toggle the adopted status + animals[name]['adopted'] = not animals[name].get('adopted', False) + save_data(animals) + + # Notify the user about the updated adoptino status + if animals[name]['adopted']: + print(Fore.GREEN + f"\n{name} has been marked as " + Fore.CYAN + "adopted!" + Style.RESET_ALL) + log_action(current_user, f"{name} marked as adopted") + else: + print(Fore.GREEN + f"\n{name} has been marked as " + Fore.RED + "not adopted!" + Style.RESET_ALL) + log_action(current_user, f"{name} marked as not adopted") + # Notify the user if the animal is not found + else: + print(Fore.RED + f"\nNo animal found with the name {name}" + Style.RESET_ALL) - time.sleep(2) \ No newline at end of file + time.sleep(2) \ No newline at end of file diff --git a/common_functions.py b/common_functions.py index 9984044..a714adf 100644 --- a/common_functions.py +++ b/common_functions.py @@ -1,5 +1,6 @@ import json import os +import hashlib import datetime def clear_screen(): @@ -11,6 +12,19 @@ def load_data(file_name): with open(file_name, 'r') as f: return json.load(f) +def load_animal_data(collection): + """ + Load animal data from the MongoDB collection + Args: + collection (str): Name of the collection to load data from. + Returns: + dict: Animal data loaded from the collection + """ + animals = {} + for animal in collection.find(): + animals[animal['name']] = animal + return animals + def save_data(data, file_name): # Save data to the JSON file with indentation for readability with open(file_name, 'w') as f: @@ -18,7 +32,7 @@ def save_data(data, file_name): def log_action(username, action_description): # Get the current timestamp - current_time = datetime.datetime.now() + current_time = datetime.datetime.now().replace(microsecond=0) # Format the log entry log_entry = f"[{current_time}] - {username}: {action_description}" @@ -26,12 +40,32 @@ def log_action(username, action_description): # Define the filepath for the audit log file log_file_path = "audit_log.txt" + # Add separator between log entries + separator = '-' * 50 + '\n' # Check if the file exists, if not, create it if not os.path.exists(log_file_path): - with open(log_file_path, "w"): - pass - - # Append the log entry to the file - with open(log_file_path, "a") as log_file: - log_file.write(log_entry + "\n") + with open(log_file_path, "w") as log_file: + log_file.write("=== Audit Log ===\n\n") + + try: + # Append the log entry to the file + with open(log_file_path, "a") as log_file: + log_file.write(log_entry + "\n") + log_file.write(separator) # Add separator after each entry + except Exception as e: + print(f"Error occurred while writing to log file: {e}") + +def generate_salt(): + return os.urandom(16) # Generate a 16-byte (128-bit) random salt + +def hash_password(password, salt): + combined_value = password.encode('utf-8') + salt + hashed_password = hashlib.sha256(combined_value).hexdigest() + return hashed_password + +def hash_animal_data(animals, salt): + animal_data_string = json.dumps(animals, sort_keys=True) + combined_value = animal_data_string.encode('utf-8') + salt + hashed_animal_data = hashlib.sha256(combined_value).hexdigest() + return hashed_animal_data \ No newline at end of file diff --git a/edit_animal_entries.py b/edit_animal_entries.py new file mode 100644 index 0000000..5d89b99 --- /dev/null +++ b/edit_animal_entries.py @@ -0,0 +1,66 @@ +import time +from common_functions import clear_screen +from colorama import Fore, Style +from pymongo import MongoClient +from config import mongodb_uri + +# Connect to MongoDB +uri = mongodb_uri +client = MongoClient(uri) + +db = client['animal_rescue'] +animals_collection = db['animals'] + +def modify_animal(): + clear_screen() + print(Fore.CYAN + "\n🐾 Modify Animal 🐾\n" + Style.RESET_ALL) + + # Input the name of the animal to modify + animal_name = input(Fore.CYAN + "Enter the name of the animal to modify: " + Style.RESET_ALL).strip() + + # Search for the animal in the database + animal = animals_collection.find_one({'name': animal_name}) + + if animal: + print(Fore.GREEN + "\nAnimal found. You can modify the following fields:") + print("1. Name") + print("2. Species") + print("3. Breed") + print("4. Gender") + print("5. Age") + print(Style.RESET_ALL) + + field_choice = input("Enter the number of the field to modify or 'exit' to cancel: ") + + if field_choice.lower() == 'exit': + print(Fore.YELLOW + "\nExiting..." + Style.RESET_ALL) + time.sleep(2) + return + + if field_choice.isdigit(): + field_choice = int(field_choice) + if field_choice == 1: + new_value = input("Enter new name: ").strip() + animals_collection.update_one({'name': animal_name}, {'$set': {'name': new_value}}) + elif field_choice == 2: + new_value = input("Enter new species: ").strip() + animals_collection.update_one({'name': animal_name}, {'$set': {'species': new_value}}) + elif field_choice == 3: + new_value = input("Enter new breed: ").strip() + animals_collection.update_one({'name': animal_name}, {'$set': {'breed': new_value}}) + elif field_choice == 4: + new_value = input("Enter new gender: ").strip() + animals_collection.update_one({'name': animal_name}, {'$set': {'gender': new_value}}) + elif field_choice == 5: + new_value = input("Enter new age: ").strip() + if new_value.isdigit() and int(new_value) > 0: + animals_collection.update_one({'name': animal_name}, {'$set': {'age': int(new_value)}}) + else: + print(Fore.RED + "Invalid age. Please enter a positive integer.") + else: + print(Fore.RED + "Invalid choice.") + + else: + print(Fore.RED + "Animal not found.") + + input(Fore.GREEN + "Press Enter to continue..." + Style.RESET_ALL) \ No newline at end of file diff --git a/login.py b/login.py index 37138f5..67cc7fa 100644 --- a/login.py +++ b/login.py @@ -1,13 +1,20 @@ -import json import getpass import time from colorama import Fore, Style -from common_functions import clear_screen +from common_functions import clear_screen, log_action, hash_password, generate_salt from admin_dashboard import admin_dashboard +from pymongo import MongoClient +from config import mongodb_uri -USER_DATA_FILE = "users.json" +MAX_ATTEMPTS = 3 -def change_admin_password(): +# Connect to MongoDB +uri = mongodb_uri +client = MongoClient(uri) +db = client['animal_rescue'] +users_collection = db['users'] + +def change_admin_password(username): clear_screen() # Notify the user about changing the ADMIN password @@ -17,68 +24,132 @@ def change_admin_password(): new_password = getpass.getpass("\nEnter a new password for ADMIN: ") confirm_password = getpass.getpass("Confirm the new password: ") - # Check if passowrds match + # Check if passwords match if new_password == confirm_password: - #Update the password in the user data file - with open(USER_DATA_FILE, 'r+') as user_file: - data = json.load(user_file) - data["ADMIN"] = { - "password": new_password, - "level": 5 - } - user_file.seek(0) - json.dump(data, user_file, indent=4) - user_file.truncate() - print(Fore.GREEN + "\nPassword changed successfully!" + Style.RESET_ALL) - time.sleep(2) - clear_screen() - # Notify the user about mismatching passwords and prompt again + # Generate salt and hash password + salt = generate_salt() + hashed_password = hash_password(new_password, salt) + + # Convert salt to hexadecimal string for serialization + salt_hex = salt.hex() + + # Update the password in the MongoDB collection for ADMIN + users_collection.update_one( + {'username': 'ADMIN'}, + {'$set': {'hashed_password': hashed_password, 'salt': salt_hex}} + ) + + log_action(username, f"Admin Password has been changed") + print(Fore.GREEN + "\nPassword changed successfully!" + Style.RESET_ALL) + time.sleep(2) + clear_screen() + else: + # Notify the user about mismatching passwords and prompt again + print(Fore.RED + "\nPasswords do not match. Please try again." + Style.RESET_ALL) + log_action("Failed attempt to access ADMIN") + time.sleep(2) + clear_screen() + change_admin_password(username) + +def reset_password(username): + clear_screen() + + # Notify the user about changing the password + print(Fore.YELLOW + f"\nYour password must be changed for security reasons." + Style.RESET_ALL) + + # Prompt user for a new password and confirmation + new_password = getpass.getpass("\nEnter a new password: ") + confirm_password = getpass.getpass("Confirm the new password: ") + + # Check if passwords match + if new_password == confirm_password: + # Generate salt and hash password + salt = generate_salt() + hashed_password = hash_password(new_password, salt) + + # Convert salt to hexadecimal string for serialization + salt_hex = salt.hex() + + # Update the password in the MongoDB collection + users_collection.update_one( + {'username': username}, + {'$set': {'hashed_password': hashed_password, 'salt': salt_hex}} + ) + + log_action(username, f"Password has been reset for user {username}") + print(Fore.GREEN + "\nPassword changed successfully!" + Style.RESET_ALL) + time.sleep(2) + clear_screen() else: + # Notify the user about mismatching passwords and prompt again print(Fore.RED + "\nPasswords do not match. Please try again." + Style.RESET_ALL) time.sleep(2) clear_screen() - change_admin_password() + reset_password(username) def login(): + attempts = 0 # Continuous loop for login - while True: + while attempts < MAX_ATTEMPTS: # Display login prompt print("\n👤 User Login 👤") username = input("\nEnter your username: ") - password = getpass.getpass("Enter your password: ") - - # Check user credentials from the user data file - with open(USER_DATA_FILE, 'r') as user_file: - users = json.load(user_file) - - if username in users: - if users[username]['password'] == password: - user_level = users[username]['level'] - # Change admin password if not already done so - if username == "ADMIN" and password == "ADMIN": - change_admin_password() - admin_dashboard() - return username, user_level - - # Go straight to admin dashboard - elif username == "ADMIN": - admin_dashboard() - return username, user_level - - # Log in as regular user - else: - print("\nLogging in...") - time.sleep(2) - return username, user_level - - # Notify about incorrect password - else: - print(Fore.RED + "\nIncorrect password. Please try again." + Style.RESET_ALL) + password = getpass.getpass("Enter your password: ") # No need for getpass as input is hidden in most consoles + + # Query user credentials from MongoDB + user = users_collection.find_one({'username': username}) + + if user: + stored_password = user['hashed_password'] + salt = bytes.fromhex(user['salt']) + + # Hash the entered password with the stored salt + entered_password_hash = hash_password(password, salt) + + if stored_password == entered_password_hash: + user_level = user['level'] + + if username == "ADMIN" and password == "ADMIN": + print("\nLogging In...") + time.sleep(2) + change_admin_password(username) + admin_dashboard() + return username, user_level + + elif username == "ADMIN": + log_action(username, f"ADMIN Logged In") + print("\nLogging In...") time.sleep(2) - clear_screen() + admin_dashboard() + return username, user_level + + elif password == "password": + print("\nLogging In...") + # Pass username to the reset_password function + reset_password(username) - # Notify about non-existing username + else: + print("\nLogging in...") + time.sleep(2) + log_action(username, f"Logged In") + return username, user_level + else: - print(Fore.RED + "\nUsername not found. Please try again." + Style.RESET_ALL) + print(Fore.RED + "\nIncorrect password." + Style.RESET_ALL) + attempts += 1 + time.sleep(2) + print(Fore.RED + f"\nRemaining attempts: {MAX_ATTEMPTS - attempts}" + Style.RESET_ALL) + log_action(username, "Failed login attempt") time.sleep(2) clear_screen() + + else: + print(Fore.RED + "\nUsername not found. Please try again." + Style.RESET_ALL) + time.sleep(2) + clear_screen() + + print(Fore.RED + "\nMaximum login attempts reached" + Style.RESET_ALL) + time.sleep(1) + print("\nExiting...") + time.sleep(2) + exit() \ No newline at end of file diff --git a/register.py b/register.py index 38c22be..0c0b12a 100644 --- a/register.py +++ b/register.py @@ -1,52 +1,66 @@ import getpass import time from colorama import Fore, Style -from common_functions import clear_screen, load_data, save_data +from common_functions import clear_screen, generate_salt, hash_password +from pymongo import MongoClient +from config import mongodb_uri -USER_DATA_FILE = "users.json" +# Connect to MongoDB +uri = mongodb_uri +client = MongoClient(uri) +db = client['animal_rescue'] +users_collection = db['users'] def register(): clear_screen() - # Load user data from file - users = load_data(USER_DATA_FILE) + while True: + # Prompt user to enter a username + username = input("\nEnter a username: ").strip() + + # Check if username already exists in the database + if users_collection.find_one({'username': username}): + print(Fore.RED + "\nUsername already exists. Please choose another one." + Style.RESET_ALL) + time.sleep(2) + clear_screen() + continue - # Prompt user to enter a username - username = input("\nEnter a username: ") - - # Check if usernaem already exists - if username in users: - print(Fore.RED + "\nUsername already exists. Please choose another one." + Style.RESET_ALL) - time.sleep(2) - return + # Continuous loop for user registration + while True: + # Prompt user to enter and confirm a password + password = getpass.getpass("Enter a password: ") + confirm_password = getpass.getpass("Confirm your password: ") - # Continuous loop for user registration - while True: - # Prompt user to enter and confirm a password - password = getpass.getpass("Enter a password: ") - confirm_password = getpass.getpass("Confirm your password: ") - - # Check if passwords match - if password == confirm_password: - # Prompt user to enter their user level - user_level = input("Enter your user level (1-3): ") - - # Validate user level input - if user_level.isdigit() and 1 <= int(user_level) <= 3: - # Update user data with new registration information - users[username] = {'password': password, 'level': int(user_level)} - save_data(users, USER_DATA_FILE) - print(Fore.GREEN + "\nRegistration successful!" + Style.RESET_ALL) - time.sleep(2) - clear_screen() - # Exit the loop when registration is successful - break - # Notify about invalid user level + # Check if passwords match + if password == confirm_password: + # Prompt user to enter their user level + user_level = input("Enter your user level (1-3): ") + + # Validate user level input + if user_level.isdigit() and 1 <= int(user_level) <= 3: + # Generate salt and hash password + salt = generate_salt() + hashed_password = hash_password(password, salt) + + # Convert salt to hexadecimal string for serialization + salt_hex = salt.hex() + + # Insert user data into the MongoDB collection + users_collection.insert_one({ + 'username': username, + 'hashed_password': hashed_password, + 'salt': salt_hex, + 'level': int(user_level) + }) + + print(Fore.GREEN + "\nRegistration successful!" + Style.RESET_ALL) + time.sleep(2) + clear_screen() + return + else: + print(Fore.RED + "\nInvalid user level. Please enter a number between 1 and 3." + Style.RESET_ALL) else: - print(Fore.RED + "\nInvalid user level. Please enter a number between 1 and 3." + Style.RESET_ALL) - # Notify about mismatching passwords - else: - print(Fore.RED + "\nPasswords do not match. Please try again." + Style.RESET_ALL) + print(Fore.RED + "\nPasswords do not match. Please try again." + Style.RESET_ALL) if __name__ == "__main__": register() diff --git a/sudo_user.py b/sudo_user.py index 4c32c04..e515782 100644 --- a/sudo_user.py +++ b/sudo_user.py @@ -1,25 +1,40 @@ import getpass import json import time +import hashlib from colorama import Fore, Style from common_functions import clear_screen, log_action +from pymongo import MongoClient +from config import mongodb_uri -USER_DATA_FILE = "users.json" + +MAX_ATTEMPTS = 2 + +# Connect to MongoDB +uri = mongodb_uri +client = MongoClient(uri) +db = client['animal_rescue'] +users_collection = db['users'] def sudo_user(): - # Continuous loop for sudo user authentication - while True: + # Continuous loop for sudo user authentication + attempts = 0 + while attempts < MAX_ATTEMPTS: print("\nPlease enter your credentials") username = input("\nEnter your username: ") password = getpass.getpass("Enter your password: ") - # Check user credentials from the user data file - with open(USER_DATA_FILE, 'r') as user_file: - users = json.load(user_file) + user = users_collection.find_one({'username': username}) + + if user: + stored_password = user['hashed_password'] + salt = bytes.fromhex(user['salt']) - if username in users: - if users[username]['password'] == password: - user_level = users[username]['level'] + # Hash the entered password with the stored salt + entered_password_hash = hashlib.sha256(password.encode('utf-8') + salt).hexdigest() + + if stored_password == entered_password_hash: + user_level = user['level'] # Throw error if user is logging in with ADMIN account if username == "ADMIN": print("\nNot a valid username") @@ -31,9 +46,10 @@ def sudo_user(): elif user_level >= 2: print(Fore.GREEN +"\nUser Verified..." + Style.RESET_ALL) time.sleep(2) - return username,user_level + clear_screen() + return username - # Notify insufficent clearance + # Notify insufficient clearance else: print(Fore.RED + "\nYou do not have clearance to do this." + Style.RESET_ALL) time.sleep(1) @@ -43,18 +59,30 @@ def sudo_user(): time.sleep(1) # Log user's username and add it to audit log - current_user = username - log_action(current_user, f"Tried to access without clearance") + log_action(username, f"Tried to access without clearance") exit() # Notify about incorrect password else: - print(Fore.RED + "\nIncorrect password. Please try again." + Style.RESET_ALL) + print(Fore.RED + "\nIncorrect password." + Style.RESET_ALL) + attempts += 1 + time.sleep(2) + print(Fore.RED + f"\nRemaining attempts: {MAX_ATTEMPTS - attempts}" + Style.RESET_ALL) + log_action(username, "Failed attempted access via sudo") time.sleep(2) clear_screen() + - # Notify about non-existing username - else: - print(Fore.RED + "\nUsername not found. Please try again." + Style.RESET_ALL) - time.sleep(2) - clear_screen() + # Notify about non-existing username + else: + print(Fore.RED + "\nUsername not found. Please try again." + Style.RESET_ALL) + time.sleep(2) + clear_screen() + + + print(Fore.RED + "\nMaximum login attempts reached" + Style.RESET_ALL) + time.sleep(1) + print("\nExiting...") + time.sleep(2) + exit() + diff --git a/view_animal_profile.py b/view_animal_profile.py index 00ae1c5..1e713c7 100644 --- a/view_animal_profile.py +++ b/view_animal_profile.py @@ -1,12 +1,19 @@ import tkinter as tk +import time from PIL import Image, ImageTk from tkinter import filedialog from colorama import Fore, Style -from tkinter import messagebox -from common_functions import clear_screen, load_data, save_data +from common_functions import clear_screen, load_animal_data, save_data, log_action +from sudo_user import sudo_user +from pymongo import MongoClient +from config import mongodb_uri +# Connect to MongoDB +uri = mongodb_uri +client = MongoClient(uri) -ANIMAL_DATA_FILE = "animals.json" +db = client['animal_rescue'] +animals_collection = db['animals'] def print_animal_table_with_index(animals): # Displays the table of animals with index numbers @@ -30,7 +37,7 @@ def print_animal_table_with_index(animals): def select_animal_to_view(animals): clear_screen() print_animal_table_with_index(animals) - + # Allows the user to select an animal from the table to view its profile selected_index = input("\nEnter the index of the animal to view its profile: ") @@ -46,12 +53,17 @@ def select_animal_to_view(animals): def view_animal_profile(): clear_screen() - animals = load_data(ANIMAL_DATA_FILE) + animals = load_animal_data(animals_collection) + + # Checks if the user has permisson to view the animal's profile + current_user = sudo_user() # Displays the profile of the selected animal in a Tkinter window print_animal_table_with_index(animals) selected_index = input("\nEnter the index of the animal to view its profile: ") + log_action(current_user, f"Viewed index: {selected_index} animal profile") + try: selected_index = int(selected_index) if 1 <= selected_index <= len(animals): @@ -104,7 +116,7 @@ def upload_image(): file_path = filedialog.askopenfilename() if file_path: animal['image'] = file_path - save_data(animals, ANIMAL_DATA_FILE) + save_data(animals) upload_button = tk.Button(root, text="Upload Image", command=upload_image, width=15, height=2) upload_button.pack(pady=10) @@ -121,7 +133,7 @@ def upload_image(): def view_animals(): clear_screen() - + current_user = sudo_user() while True: # Continuous loop for viewing the animal options print(Fore.CYAN + "\n⚙️ Options ⚙️" + Style.RESET_ALL) @@ -132,8 +144,13 @@ def view_animals(): if user_input == '1': view_animal_profile() + elif user_input == '2': + print("\nExiting...") + log_action(current_user, "Exited 'View Profile'") + time.sleep(2) clear_screen() return + else: print("\nInvalid input. Please choose one of the options.") \ No newline at end of file diff --git a/view_animals.py b/view_animals.py index 00cc767..88aa572 100644 --- a/view_animals.py +++ b/view_animals.py @@ -1,11 +1,18 @@ # Import necessary libraries and modules import time from colorama import Fore, Style -from common_functions import clear_screen, load_data +from common_functions import clear_screen, load_animal_data, log_action from view_animal_profile import view_animal_profile +from sudo_user import sudo_user +from pymongo import MongoClient +from config import mongodb_uri -# Define constant for animal data file -ANIMAL_DATA_FILE = "animals.json" +# Connect to MongoDB +uri = mongodb_uri +client = MongoClient(uri) + +db = client['animal_rescue'] +animals_collection = db['animals'] # Function to print the table of animals def print_animal_table(animals): @@ -41,8 +48,9 @@ def filter_animals(animals): animals (dict): Dictionary containing animal data. """ species_query = input(Fore.GREEN + "\nEnter species" + Style.RESET_ALL + " (leave blank to skip): ").lower() - breed_query = input(Fore.GREEN + "Enter breed " + Style.RESET_ALL + "(leave blank to skip): ").lower() - adopted_query = input(Fore.GREEN + "Enter adoption status " + Style.RESET_ALL + "(True/False, leave blank to skip): ").lower() + breed_query = input(Fore.GREEN + "\nEnter breed " + Style.RESET_ALL + "(leave blank to skip): ").lower() + gender_query = input(Fore.GREEN + "\nEnter gender "+ Style.RESET_ALL + "(leave blank to skip): ") + adopted_query = input(Fore.GREEN + "\nEnter adoption status " + Style.RESET_ALL + "(True/False, leave blank to skip): ").lower() clear_screen() filtered_animals = {} @@ -50,6 +58,7 @@ def filter_animals(animals): for name, data in animals.items(): if (not species_query or species_query == data['species'].lower()) and \ (not breed_query or breed_query == data['breed'].lower()) and \ + (not gender_query or gender_query == data['gender'].lower()) and \ (not adopted_query or adopted_query == str(data['adopted']).lower()): filtered_animals[name] = data @@ -63,42 +72,50 @@ def filter_animals(animals): print_animal_table(animals) # Function to search animals based on user input -def search_animals(animals): +def search_animals(animals, current_user): """ Search animals based on user input criteria. Args: animals (dict): Dictionary containing animal data. + current_user (str): Username of the current user. """ while True: - search_query = input("\nEnter (name/species/breed/age): ").lower() - found_results = False + clear_screen() + search_query = input("\nEnter (name/species/breed/gender/age): ").lower() + + log_action(current_user, f"Searched for {search_query}") - if search_query.strip(): + found_results = {} + + if search_query.strip(): for name, data in animals.items(): if search_query in name.lower() or \ - search_query in data['species'].lower() or \ - search_query in data['breed'].lower() or \ - search_query == str(data['age']): - found_results = True + search_query in data['species'].lower() or \ + search_query in data['breed'].lower() or \ + (search_query == 'male' and data['gender'].lower() == 'male') or \ + (search_query == 'female' and data['gender'].lower() == 'female') or \ + search_query == str(data['age']): + found_results[name] = data + + if found_results: + clear_screen() + print_animal_table(found_results) + print("\n1. " + Fore.GREEN + "Search for another animal" + Style.RESET_ALL) + print("2. " + Fore.YELLOW + "Exit" + Style.RESET_ALL) + exit_input = input("\nPlease select an option: ") + + if exit_input == '1': + continue + elif exit_input == '2': + clear_screen() + print_animal_table(animals) + return + else: + print(Fore.RED + "Invalid input. Please choose one of the options." + Style.RESET_ALL) + time.sleep(2) clear_screen() - print_animal_table({name: data}) - print("\n1. " + Fore.GREEN + "Search for animal" + Style.RESET_ALL) - print("2. " + Fore.YELLOW + "Exit" + Style.RESET_ALL) - exit_input = input("\nPlease select an option: ") - - if exit_input == '1': - clear_screen() - continue # Go back to the search menu - elif exit_input == '2': - clear_screen() - print_animal_table(animals) - return - else: - print(Fore.RED + "Invalid input. Please choose one of the options." + Style.RESET_ALL) - time.sleep(2) - clear_screen() - print_animal_table(animals) - if not found_results: + print_animal_table(animals) + else: print(Fore.RED + "No animals found matching the search criteria" + Style.RESET_ALL) time.sleep(2) clear_screen() @@ -135,7 +152,10 @@ def view_animals(): View animals and interact with different options. """ clear_screen() - animals = load_data(ANIMAL_DATA_FILE) + current_user = sudo_user() + + clear_screen() + animals = load_animal_data(animals_collection) print_animal_table(animals) while True: @@ -149,8 +169,9 @@ def view_animals(): user_input = input("\nPlease select an option: ") if user_input == '1': + time.sleep(1) clear_screen() - search_animals(animals) + search_animals(animals, current_user) elif user_input == '2': clear_screen() print_animal_table(animals) @@ -174,6 +195,9 @@ def view_animals(): elif user_input == '4': view_animal_profile() elif user_input == '5': + log_action(current_user, "Exited 'View Animal Database'") + print("\nExiting...") + time.sleep(2) clear_screen() return else: