diff --git a/OOP-Project/OOP-PythonAssignment-200L-NUN-2025/Folder/Instructions b/OOP-Project/OOP-PythonAssignment-200L-NUN-2025/Folder/Instructions new file mode 100644 index 0000000..bd437b4 --- /dev/null +++ b/OOP-Project/OOP-PythonAssignment-200L-NUN-2025/Folder/Instructions @@ -0,0 +1 @@ +Submit your code here diff --git a/OOP-Project/OOP-PythonAssignment-200L-NUN-2025/Folder/guest.py b/OOP-Project/OOP-PythonAssignment-200L-NUN-2025/Folder/guest.py new file mode 100644 index 0000000..d1e3bae --- /dev/null +++ b/OOP-Project/OOP-PythonAssignment-200L-NUN-2025/Folder/guest.py @@ -0,0 +1,23 @@ +class Guest: + def __init__(self, name, contact_info): + self.name = name + self.contact_info = contact_info + self.checked_in = False + + def checkIn(self): + """Checks a guest into the hotel""" + self.checked_in = True + return f"{self.name} has checked in" + + def checkOut(self): + """Checks a guest out of the hotel""" + if self.checked_in: + self.checked_in = False + return f"{self.name} has checked out." + return f"{self.name} is not checked in." + + def __str__(self): + """"Returns a clean/sanitized string represantation of a guest""" + status = "Checked in" if self.checked_in else "Not checked in" + return f"Guest: {self.name}, Contact: {self.contact_info}, Status: {status}" + \ No newline at end of file diff --git a/OOP-Project/OOP-PythonAssignment-200L-NUN-2025/Folder/hotel_management.py b/OOP-Project/OOP-PythonAssignment-200L-NUN-2025/Folder/hotel_management.py new file mode 100644 index 0000000..5ab14e9 --- /dev/null +++ b/OOP-Project/OOP-PythonAssignment-200L-NUN-2025/Folder/hotel_management.py @@ -0,0 +1,347 @@ +from pydoc import text +from sqlite3 import Date +from tkinter import messagebox +from turtle import width +from guest import Guest +from reservation import Reservation +from room import HotelRoom +import json +import tkinter as tk +from tkinter import * +from tkinter import simpledialog +from tkinter import Tk +from tkinter import ttk +import datetime + +import room + +### Important notes +# Docstrings give relevant information on functions so when you hover over them with your mouse you can see the information given +# they are defined with triple quotes """Information here""" + +class HotelManagement: + def __init__(self, root): + self.root = root + self.root.title("Hotel Management System") + self.root.geometry("600x600") + self.create_widgets() + self.database = self.load_from_file() + self.rooms = self.database.get('rooms', []) + self.reservations = self.database.get('reservations', {}) + self.configure_tree_view() + self.room_tree_view.bind("<>", self.on_tree_select) # Binds the on_tree_select function to the main tree view + self.selected_room_number = None + + def create_widgets(self): + """Handles the initial creation of all the gui elements.""" + self.create_room_button = ttk.Button(self.root, text="Add Room", width=60, command=self.request_room_info) + self.book_reservation_button = ttk.Button(self.root, text="Book Reservation", width=60, command=self.request_reservation_info) + self.check_in_guest_button = ttk.Button(self.root, text="Check In", width=60, command=self.request_check_in_info) + self.check_out_guest_button = ttk.Button(self.root, text="Check Out", width=60, command=self.request_check_out_info) + self.cancel_reservation_button = ttk.Button(self.root, text="Cancel Reservation", width=60, command=self.request_cancel_info) + self.room_tree_view = ttk.Treeview(self.root, selectmode='browse') + + self.create_room_button.grid(row=1, column=0,padx=5, pady=5) + self.book_reservation_button.grid(row=2, column=0, padx=5, pady=5) + self.cancel_reservation_button.grid(row=3, column=0, padx=5, pady=5) + self.check_in_guest_button.grid(row=4, column=0, padx=5, pady=5) + self.check_out_guest_button.grid(row=5, column=0, padx=5, pady=5) + + + self.room_tree_view.grid(row=0, column=0, padx=5, pady=5) + + self.room_tree_view["columns"] = ("1", "2", "3", "4") + self.room_tree_view['show'] = 'headings' + + self.room_tree_view.column("1", width=90, anchor='c') + self.room_tree_view.column("2", width=90, anchor='se') + self.room_tree_view.column("3", width=90, anchor='se') + self.room_tree_view.column("4", width=90, anchor='se') + + self.room_tree_view.heading("1", text="Number") + self.room_tree_view.heading("2", text="Type") + self.room_tree_view.heading("3", text="Price") + self.room_tree_view.heading("4", text="Available") + + def configure_tree_view(self): + for room in self.rooms: + self.room_tree_view.insert("", 0, text="L10", values=(room['room_number'], room['room_type'], room['price'], room['availibility'],)) + + def on_tree_select(self, event): + selected_items = self.room_tree_view.selection() + if selected_items: + selected_item = selected_items[0] + room_values = self.room_tree_view.item(selected_item, 'values') + if room_values: + self.selected_room_number = int(room_values[0]) + room = self.get_room(self.selected_room_number) + if room: + pass + else: + self.selected_room_number = None + else: + self.selected_room_number = None + + + def load_from_file(self): + try: + with open("database.json", "r") as file: + return json.load(file) + except FileNotFoundError: + return {'rooms': [], 'reservations': {}} # Return an empty list if the file doesn't exist yet to prevent a crash. + except json.JSONDecodeError: + return {'rooms': [], 'reservations': {}} # Return an empty list if the file is corrupted to prevent a crash. + + def save_to_file(self): + self.database['rooms'] = self.rooms + self.database['reservations'] = self.reservations + try: + with open("database.json", "w") as file: + json.dump(self.database, file) + except IOError as e: + messagebox.showerror("File Error", f"Could not save data: {e}") # Log the error + pass + + def parse_date(self, date_str): + """Parses a string in DD/MM/YYYY formate to a datetime object to avoid issues""" + try: + day, month, year = map(int, date_str.split('/')) + return datetime.date(year, month, day) + except ValueError: + return None + + def date_to_str(self, date_obj): + """Converts a date object to a string""" + if isinstance(date_obj, datetime.date): + return date_obj.strftime("%d/%m/%Y") + return str(date_obj) + + def request_reservation_info(self): + if self.selected_room_number is None: + messagebox.showerror("Error", "Please select a room first.") + return + else: + check_in_date = Date.today() + guest_name = simpledialog.askstring("Guest Info", "Enter the guests name.") + if guest_name is None: + messagebox.showerror("Invalid Input", "Guest name can only be a string.") + return + + guest_contact_info = simpledialog.askstring("Guest Info", "Enter the guests contact info.") + if guest_contact_info is None: + messagebox.showerror("Invalid Input", "Contact info can only be a int.") + return + + check_out_date = simpledialog.askstring("Guest Info", "When will the guest be checking out" + "Please input a valid date in the format DD/MM/YYYY. ") + if check_out_date is None: + messagebox.showerror("Invalid Input", "Please input a valid date in the format DD/MM/YYYY.") + return + + current_guest = { + "name": guest_name, + "contact_info": guest_contact_info, + "checked_in": False + } + self.book_room(self.selected_room_number, current_guest, check_in_date, check_out_date) + + def request_check_in_info(self): + """Request information needed to check a guest in.""" + if self.selected_room_number is None: + messagebox.showerror("Error", "Please select a room first.") + return + + # Check if the reservation exists + reservation_id = None + for res_id, res in self.reservations.items(): + if res["room_number"] == self.selected_room_number: + reservation_id = res_id + break + + if not reservation_id: + messagebox.showerror("Error", f"No reservation found for room{self.selected_room_number}") + return + + if self.reservations[reservation_id]["checked_in"]: + messagebox.showerror("Error", f"Guest already checked in to room {self.selected_room_number}.") + return + + # Get guest information + guest_name = simpledialog.askstring("Guest Info", "Enter the guest's name") + if guest_name is None: + messagebox.showerror("Error", "Please input a guest name.") + return + + guest_contact_info = simpledialog.askstring("Guest Info", "Enter the guest's contact info") + if guest_contact_info is None: + messagebox.showerror("Error", "Please input guest contact information.") + return + + guest = { + "name": guest_name, + "contact_info": guest_contact_info + } + + self.check_in_guest(self.selected_room_number, guest) + + def request_check_out_info(self): + """Requests information needed to check out a guest.""" + if self.selected_room_number is None: + messagebox.showerror("Error", "Please select a room first.") + return + + confirm = messagebox.askyesno("Confirm Check-out", f"Are your sure you want to check out the guest form room {self.selected_room_number}?") + + if confirm: + self.check_out_guest(self.selected_room_number) + + def request_cancel_info(self): + """Requests information needed to cancel a reservation.""" + if self.selected_room_number is None: + messagebox.showerror("Error", "Please select a room first.") + return + + confirm = messagebox.askyesno("Confirm Cancellation", f"Are you sure you want to cancel the reservation for room {self.selected_room_number}?") + + if confirm: + self.cancel_reservation(self.selected_room_number) + + def request_room_info(self): + """Opens dialogs to get room details from the user using tkinter gui and adds the room to the json database.""" + + room_number = simpledialog.askinteger("Add Room", "Enter room number") + if room_number is None: + return + + room_type = simpledialog.askstring("Add Room", "Enter room type (e.g., Single, Double, Suite):") + if room_type is None: #Throws an error if one of the valid room types is not provided. + return + + price = simpledialog.askfloat("Add Room", "Enter price per night: ") + if price is None: + return + + self.add_room(room_number, room_type, price) + self.room_tree_view.delete(*self.room_tree_view.get_children()) + self.configure_tree_view() + + def add_room(self, room_number, room_type, price): + """Adds a new room to the hotel database.""" + room_exists = any(room['room_number'] == room_number for room in self.rooms) + + if room_exists: + messagebox.showerror("Error", f"Room {room_number} already exists.") + else: + new_room = { + "room_number": room_number, + "room_type": room_type, + "price": price, + "availibility": True + } + self.rooms.append(new_room) + self.save_to_file() + messagebox.showinfo("Success", f"Room {room_number} added successfully!") + + def get_room(self, room_number): + """Retrieves a room by its number""" + for room in self.rooms: + if room["room_number"] == room_number: + return room + return None + + def book_room(self, room_number: int, guest, check_in_date, check_out_date): + room = self.get_room(room_number) + if not room: + messagebox.showerror("Error", f"Room {room_number} does not exist.") + return + + if not room["availibility"]: + messagebox.showerror("Error", f"Room {room_number} has already been booked.") + return + + room["availibility"] = False + guest_dict = guest.__dict__ if hasattr(guest, "__dict__") else guest + + reservation_id = f"{room_number}_{self.date_to_str(check_in_date)}" + + self.reservations[reservation_id] = { + "room_number": room_number, + "guest": guest_dict, + "check_in_date": self.date_to_str(check_in_date), + "check_out_date": self.date_to_str(check_out_date), + "checked_in": False + } + + self.save_to_file() + messagebox.showinfo("Success", f"Room {room_number} has been booked successfully!") + + self.room_tree_view.delete(*self.room_tree_view.get_children()) + self.configure_tree_view() + + def cancel_reservation(self, room_number: int): + reservation_id = None + for res_id, res in self.reservations.items(): + if res["room_number"] == room_number: + reservation_id = res_id + break + + if not reservation_id: + messagebox.showerror("Error", f"No reservation found for room {room_number}.") + return + + room = self.get_room(room_number) + if room: + room["availibility"] = True + + del self.reservations[reservation_id] # Delete the reservation found using the reservation id + self.save_to_file() + messagebox.showinfo("Success", f"Reservation for room {room_number} cancelled!") + + self.room_tree_view.delete(*self.room_tree_view.get_children()) + self.configure_tree_view() + + def check_in_guest(self, room_number, guest): + reservation_id = None + for res_id, res in self.reservations.items(): + if res["room_number"] == room_number: + reservation_id = res_id + break + + if reservation_id is None: + messagebox.showerror("Error", f"No reservation found for room {room_number}.") + return + + self.reservations[reservation_id]["checked_in"] = True + + if guest: + guest_dict = guest.__dict__ if hasattr(guest, "__dict__") else guest + self.reservations[reservation_id]["guest"] = guest_dict + + self.save_to_file() + messagebox.showinfo("Success", f"Guest checked into room {room_number}!") + + def check_out_guest(self, room_number): + reservation_id = None + for res_id, res in self.reservations.items(): + if res["room_number"] == room_number: + reservation_id = res_id + break + + if not reservation_id or not self.reservations[reservation_id]["checked_in"]: + messagebox.showerror("Error", f"No checked-in guest found for room {room_number}.") + return + + room = self.get_room(room_number) + if room: + room["availibility"] = True + + del self.reservations[reservation_id] + self.save_to_file() + messagebox.showinfo("Success", f"Guest checked out from room {room_number}") + + self.room_tree_view.delete(*self.room_tree_view.get_children()) + self.configure_tree_view() + +root = Tk() +app = HotelManagement(root) +root.mainloop() \ No newline at end of file diff --git a/OOP-Project/OOP-PythonAssignment-200L-NUN-2025/Folder/reservation.py b/OOP-Project/OOP-PythonAssignment-200L-NUN-2025/Folder/reservation.py new file mode 100644 index 0000000..bd1a355 --- /dev/null +++ b/OOP-Project/OOP-PythonAssignment-200L-NUN-2025/Folder/reservation.py @@ -0,0 +1,25 @@ +from room import HotelRoom +from guest import Guest + +class Reservation(object): + def __init__(self, room: HotelRoom, guest: Guest,check_in_date, check_out_date): + self.room = room + self.guest = guest + self.check_in_date = check_in_date + self.check_out_date = check_out_date + self.active = True + + def __str__(self): + return f"Reservation for {self.guest.name} in room {self.room.room_number} from {self.check_in_date} to {self.check_out_date}" + + def book_reservation(self): + self.room.availibility = True + pass + + + def cancel_reservation(self): + if self.active: + self.active = False + self.room.availibility = True + return f"The reservation for {self.guest.name} in {self.room.room_number} at {self.check_in_date} has been canceled" + return f"This reservation has already been canceled." diff --git a/OOP-Project/OOP-PythonAssignment-200L-NUN-2025/Folder/room.py b/OOP-Project/OOP-PythonAssignment-200L-NUN-2025/Folder/room.py new file mode 100644 index 0000000..0759546 --- /dev/null +++ b/OOP-Project/OOP-PythonAssignment-200L-NUN-2025/Folder/room.py @@ -0,0 +1,27 @@ +class HotelRoom(object): + room_number = 0 + room_type = "" + price = 0 + availibility = True + + def __init__(self, room_number: int, room_type: str, price: int, availibility: bool): + self.room_number = room_number + self.room_type = room_type + self.price = price + self.availibility = availibility + + def __repr__(self): + return f"Room(room_number='{self.room_number}', room_type={self.room_number}, price={self.price})" + + def getRoomNumber(self): + return self.room_number + + def getRoomPrice(self): + return self.price + + def getRoomType(self): + return self.room_type + + def getRoomAvailibility(self): + return self.availibility + diff --git a/OOP-Project/OOP-PythonAssignment-200L-NUN-2025/PullRequestInstructions b/OOP-Project/OOP-PythonAssignment-200L-NUN-2025/PullRequestInstructions new file mode 100644 index 0000000..db2e05c --- /dev/null +++ b/OOP-Project/OOP-PythonAssignment-200L-NUN-2025/PullRequestInstructions @@ -0,0 +1,60 @@ +Creating a pull request is a common workflow in collaborative software development, particularly when using version control systems like Git. The original repository is https://github.com/eyahi/OOP-PythonAssignment-200L-NUN-2025.git and you do not have write access to it, you will need to make a pull request for me to see your work. The steps to do pull request are given below + +1. Fork the Repository: + +If you don't have write access to the original repository, you need to fork it. This creates a copy of the repository under your GitHub account. + +2. Clone the Repository: + +Clone your forked repository to your local machine using the `git clone` command. Replace `` with the URL of your forked repository. + +git clone https://github.com/your-username/your-forked-repository.git +cd your-forked-repository + + +3. Create a Branch: + +Create a new branch for your changes. It's a good practice to create a branch for each feature or bug fix. + +git checkout -b branch-name + +4. Make Changes: + +Make the necessary changes to the codebase on your local machine. In the changes add your code to the right assignment, like add to Assignment 1 folder + +5. Commit Changes: + +Once you've made your changes, commit them to your local branch. + +git add . +git commit -m "Your name’s assignment" + +6. Push Changes: + +Push your changes to your forked repository on GitHub. + +git push origin branch-name + +Although, I am fine with using the main branch + + 7. Create a Pull Request: + +Go to your forked repository on GitHub. You should see a notification prompting you to create a pull request. If not, navigate to the "Pull Requests" tab and click on "New Pull Request." + +Choose the base repository (the original repository) and the branch you want to merge into. Also, choose your forked repository and the branch with your changes. + +8. Provide Details: + +Write a clear and concise title and description for your pull request. Explain the purpose of your changes. + +9. Submit Pull Request: + +Click the "Create Pull Request" button to submit your pull request. This will notify the maintainers of the original repository that you have changes you'd like them to review. + +10. Discussion and Changes: + +Be prepared for feedback and discussion. Make the necessary changes locally, commit them, and push them to your branch. + +Repeat this process until your changes are approved and merged into the original repository. + + diff --git a/OOP-Project/OOP-PythonAssignment-200L-NUN-2025/README.md b/OOP-Project/OOP-PythonAssignment-200L-NUN-2025/README.md new file mode 100644 index 0000000..6b7b351 --- /dev/null +++ b/OOP-Project/OOP-PythonAssignment-200L-NUN-2025/README.md @@ -0,0 +1,40 @@ +# Python Hotel Management System + +A Hotel Management System Created in python using tkinter [Python TKinter]([https://www.pygame.org/docs/](https://docs.python.org/3/library/tk.html)). + +## Basic Instructions + +Launch the gui and add a room to the database using the add room button +1. Create a room using the add room button. +2. Select a room and book a reservation. + +## Download and Run + +1. Make sure you have Python installed. Install Python at [python.org](https://www.python.org/downloads/). +2. Download the folder from github +3. Create a virtual environment. + +``` +python -m venv environment +``` + +4. Activate the virtual environment + +- For windows + +``` +.\path\to\venv\Scripts\Activate +``` + +- For macOS/Linux + +``` +source path/to/venv/bin/activate +``` + +5. Run the hotel_management.py + +``` +python hotel_management.py +``` + diff --git a/OOP-Project/Readme.md b/OOP-Project/Readme.md new file mode 100644 index 0000000..a467f91 --- /dev/null +++ b/OOP-Project/Readme.md @@ -0,0 +1,63 @@ +# Secure Password Manager + +Provided By : Ibrahim DIkko (201203058) & Almustapha Ahmed (201203033) + +A simple command-line password manager built with Python that allows you to securely store, retrieve, and manage your passwords. + +## Features + +- **Secure Storage**: All passwords are encrypted before storage +- **Master Password Protection**: Access to stored passwords requires authentication +- **Easy Management**: Simple interface to add, retrieve, update, and delete passwords +- **Data Persistence**: Password data is saved to a file for persistence between sessions + +## Project Structure + +``` +password-manager/ +│ +├── main.py # Main application entry point +├── password_manager.py # Password Manager class +├── password_entry.py # Password Entry class +├── encryption_util.py # Encryption utilities +└── passwords.json # Encrypted password data (created on first run) +``` + +## Requirements + +- Python 3.11 +- cryptography package + +1. Install the required dependencies: +``` +pip install cryptography +``` + +## Usage + +1. Run the application: +``` +python main.py +``` + +2. First-time setup: + - Create a master password when prompted + - Remember this password as it cannot be recovered + +3. Managing Passwords: + - Use the menu options to add, retrieve, update, or delete password entries + - All data is automatically saved when you exit using the "Save and exit" option + +## Security Notes + +- **Master Password**: Choose a strong master password that you can remember +- **Data File**: The `passwords.json` file contains your encrypted passwords. Keep it safe. +- **No Recovery**: If you forget your master password, there is no way to recover your stored passwords + +## Future Enhancements + +- Password strength checker +- Auto-generated secure passwords +- Clipboard integration +- Backup and restore functionality +- GUI interface diff --git a/OOP-Project/__pycache__/encryption_util.cpython-311.pyc b/OOP-Project/__pycache__/encryption_util.cpython-311.pyc new file mode 100644 index 0000000..f55bfb8 Binary files /dev/null and b/OOP-Project/__pycache__/encryption_util.cpython-311.pyc differ diff --git a/OOP-Project/__pycache__/password_entry.cpython-311.pyc b/OOP-Project/__pycache__/password_entry.cpython-311.pyc new file mode 100644 index 0000000..cf1e78c Binary files /dev/null and b/OOP-Project/__pycache__/password_entry.cpython-311.pyc differ diff --git a/OOP-Project/__pycache__/password_manager.cpython-311.pyc b/OOP-Project/__pycache__/password_manager.cpython-311.pyc new file mode 100644 index 0000000..7b8c4bd Binary files /dev/null and b/OOP-Project/__pycache__/password_manager.cpython-311.pyc differ diff --git a/OOP-Project/encryption_util.py b/OOP-Project/encryption_util.py new file mode 100644 index 0000000..79b0da0 --- /dev/null +++ b/OOP-Project/encryption_util.py @@ -0,0 +1,78 @@ +# encryption_util.py +""" +Utilities for encrypting and decrypting passwords. +""" + +import base64 +import os +from cryptography.fernet import Fernet +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + +class EncryptionUtil: + """ + A utility class for encrypting and decrypting strings. + """ + + @staticmethod + def generate_key(master_password, salt=None): + """ + Generate an encryption key from the master password. + + Args: + master_password (str): The master password + salt (bytes, optional): Salt for key derivation + + Returns: + tuple: (key, salt) where key is the encryption key and salt is the salt used + """ + if salt is None: + salt = os.urandom(16) + + # Convert password to bytes if it's a string + if isinstance(master_password, str): + master_password = master_password.encode() + + # Use PBKDF2 to derive a key from the password + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + length=32, + salt=salt, + iterations=100000, + ) + + key = base64.urlsafe_b64encode(kdf.derive(master_password)) + return key, salt + + @staticmethod + def encrypt(data, key): + """ + Encrypt data using the provided key. + + Args: + data (str): The data to encrypt + key (bytes): The encryption key + + Returns: + bytes: The encrypted data + """ + if isinstance(data, str): + data = data.encode() + + f = Fernet(key) + return f.encrypt(data) + + @staticmethod + def decrypt(encrypted_data, key): + """ + Decrypt data using the provided key. + + Args: + encrypted_data (bytes): The encrypted data + key (bytes): The encryption key + + Returns: + str: The decrypted data + """ + f = Fernet(key) + return f.decrypt(encrypted_data).decode() \ No newline at end of file diff --git a/OOP-Project/main.py b/OOP-Project/main.py new file mode 100644 index 0000000..04d68f9 --- /dev/null +++ b/OOP-Project/main.py @@ -0,0 +1,235 @@ +# main.py +""" +Main entry point for the Password Manager application. +""" + +import getpass +import os +import sys +import time +from password_manager import PasswordManager + +def clear_screen(): + """Clear the terminal screen.""" + os.system('cls' if os.name == 'nt' else 'clear') + +def print_header(): + """Print the application header.""" + clear_screen() + print("=" * 50) + print(" SECURE PASSWORD MANAGER ") + print("=" * 50) + print() + +def get_master_password(for_verification=False): + """ + Prompt the user for the master password. + + Args: + for_verification (bool): Whether to prompt for verification + + Returns: + str: The entered master password + """ + if for_verification: + return getpass.getpass("Enter your master password: ") + + while True: + password = getpass.getpass("Create a master password: ") + confirm = getpass.getpass("Confirm master password: ") + + if password == confirm: + return password + + print("Passwords do not match. Please try again.") + +def initialize_password_manager(): + """ + Initialize or load the password manager. + + Returns: + PasswordManager: The initialized password manager + """ + data_file = "passwords.json" + + if os.path.exists(data_file): + # File exists, load existing data + while True: + master_password = get_master_password(for_verification=True) + password_manager = PasswordManager(master_password, data_file) + + if password_manager.verify_master_password(master_password): + print("Password manager unlocked successfully!") + time.sleep(1) + return password_manager + else: + print("Incorrect master password. Please try again.") + time.sleep(1) + else: + # Create new password manager + print("No existing password file found. Creating a new one.") + master_password = get_master_password() + password_manager = PasswordManager(master_password, data_file) + print("Password manager created successfully!") + time.sleep(1) + return password_manager + +def display_menu(): + """Display the main menu.""" + print("\nMenu:") + print("1. Add a new password") + print("2. Retrieve a password") + print("3. Update a password") + print("4. Delete a password") + print("5. List all websites/services") + print("6. Save and exit") + print("7. Exit without saving") + + choice = input("\nEnter your choice (1-7): ") + return choice + +def add_password(password_manager): + """Add a new password entry.""" + print_header() + print("ADD NEW PASSWORD\n") + + website = input("Enter website/service name: ") + username = input("Enter username: ") + password = getpass.getpass("Enter password: ") + + if password_manager.add_password(website, username, password): + print(f"\nPassword for {website} added successfully!") + else: + print(f"\nEntry for {website} already exists. Use the update option.") + + input("\nPress Enter to continue...") + +def retrieve_password(password_manager): + """Retrieve a password entry.""" + print_header() + print("RETRIEVE PASSWORD\n") + + website = input("Enter website/service name: ") + username, password = password_manager.get_password(website) + + if username and password: + print(f"\nWebsite: {website}") + print(f"Username: {username}") + print(f"Password: {password}") + else: + print(f"\nNo entry found for {website}.") + + input("\nPress Enter to continue...") + +def update_password(password_manager): + """Update an existing password entry.""" + print_header() + print("UPDATE PASSWORD\n") + + website = input("Enter website/service name: ") + + # Check if the entry exists + username, _ = password_manager.get_password(website) + if not username: + print(f"\nNo entry found for {website}.") + input("\nPress Enter to continue...") + return + + print(f"\nCurrent username for {website}: {username}") + + new_username = input("Enter new username (leave blank to keep current): ") + if not new_username: + new_username = username + + new_password = getpass.getpass("Enter new password (leave blank to keep current): ") + if not new_password: + _, new_password = password_manager.get_password(website) + + if password_manager.update_password(website, new_username, new_password): + print(f"\nPassword for {website} updated successfully!") + else: + print(f"\nFailed to update password for {website}.") + + input("\nPress Enter to continue...") + +def delete_password(password_manager): + """Delete a password entry.""" + print_header() + print("DELETE PASSWORD\n") + + website = input("Enter website/service name: ") + + # Check if the entry exists + username, _ = password_manager.get_password(website) + if not username: + print(f"\nNo entry found for {website}.") + input("\nPress Enter to continue...") + return + + confirm = input(f"Are you sure you want to delete the entry for {website}? (y/n): ") + if confirm.lower() == 'y': + if password_manager.delete_password(website): + print(f"\nEntry for {website} deleted successfully!") + else: + print(f"\nFailed to delete entry for {website}.") + else: + print("\nDeletion cancelled.") + + input("\nPress Enter to continue...") + +def list_websites(password_manager): + """List all stored websites/services.""" + print_header() + print("STORED WEBSITES/SERVICES\n") + + websites = password_manager.list_websites() + + if websites: + for i, website in enumerate(sorted(websites), 1): + print(f"{i}. {website}") + else: + print("No passwords stored yet.") + + input("\nPress Enter to continue...") + +def main(): + """Main function of the password manager application.""" + try: + password_manager = initialize_password_manager() + + while True: + print_header() + choice = display_menu() + + if choice == '1': + add_password(password_manager) + elif choice == '2': + retrieve_password(password_manager) + elif choice == '3': + update_password(password_manager) + elif choice == '4': + delete_password(password_manager) + elif choice == '5': + list_websites(password_manager) + elif choice == '6': + password_manager.save_to_file() + print("\nChanges saved successfully!") + print("Exiting Password Manager...") + time.sleep(1) + break + elif choice == '7': + confirm = input("\nExit without saving changes? (y/n): ") + if confirm.lower() == 'y': + print("Exiting Password Manager without saving...") + time.sleep(1) + break + else: + print("\nInvalid choice. Please try again.") + time.sleep(1) + + except KeyboardInterrupt: + print("\n\nProgram interrupted. Exiting...") + sys.exit(0) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/OOP-Project/password_entry.py b/OOP-Project/password_entry.py new file mode 100644 index 0000000..1aea518 --- /dev/null +++ b/OOP-Project/password_entry.py @@ -0,0 +1,85 @@ +# password_entry.py +""" +Class to represent a single password entry in the password manager. +""" + +class PasswordEntry: + """ + A class to represent a single password entry. + + Attributes: + website (str): The website or service name + username (str): The username for the service + password (str): The encrypted password for the service + """ + + def __init__(self, website, username, password): + """ + Initialize a new PasswordEntry object. + + Args: + website (str): The website or service name + username (str): The username for the service + password (str): The password for the service + """ + self.website = website + self.username = username + self.password = password + + def get_website(self): + """Return the website/service name.""" + return self.website + + def get_username(self): + """Return the username.""" + return self.username + + def get_password(self): + """Return the password.""" + return self.password + + def set_website(self, website): + """Set the website/service name.""" + self.website = website + + def set_username(self, username): + """Set the username.""" + self.username = username + + def set_password(self, password): + """Set the password.""" + self.password = password + + def to_dict(self): + """ + Convert the entry to a dictionary format for serialization. + + Returns: + dict: The entry as a dictionary + """ + return { + 'website': self.website, + 'username': self.username, + 'password': self.password + } + + @classmethod + def from_dict(cls, data): + """ + Create a PasswordEntry object from a dictionary. + + Args: + data (dict): Dictionary containing entry data + + Returns: + PasswordEntry: A new PasswordEntry object + """ + return cls( + website=data.get('website'), + username=data.get('username'), + password=data.get('password') + ) + + def __str__(self): + """Return a string representation of the entry.""" + return f"Website: {self.website}, Username: {self.username}" \ No newline at end of file diff --git a/OOP-Project/password_manager.py b/OOP-Project/password_manager.py new file mode 100644 index 0000000..b62c817 --- /dev/null +++ b/OOP-Project/password_manager.py @@ -0,0 +1,215 @@ +# password_manager.py +""" +Main application file for the Password Manager. +""" + +import json +import os +import getpass +import base64 +from encryption_util import EncryptionUtil +from password_entry import PasswordEntry + +class PasswordManager: + """ + A class to manage password entries. + + Attributes: + entries (dict): Dictionary of password entries with website as the key + master_password (str): The master password for the password manager + key (bytes): The encryption key derived from the master password + salt (bytes): The salt used for key derivation + data_file (str): Path to the data file + """ + + def __init__(self, master_password, data_file="passwords.json"): + """ + Initialize a new PasswordManager object. + + Args: + master_password (str): The master password + data_file (str): Path to the data file + """ + self.entries = {} + self.master_password = master_password + self.data_file = data_file + self.salt = None + + # If data file exists, load from it + if os.path.exists(data_file): + self.load_from_file() + else: + # Generate a new key and salt + self.key, self.salt = EncryptionUtil.generate_key(master_password) + + def add_password(self, website, username, password): + """ + Add a new password entry. + + Args: + website (str): The website or service name + username (str): The username for the service + password (str): The password for the service + + Returns: + bool: True if successful, False if entry already exists + """ + if website in self.entries: + return False + + # Encrypt the password + encrypted_password = EncryptionUtil.encrypt(password, self.key) + # Store the encrypted password as a base64 string for JSON serialization + encrypted_password_str = base64.b64encode(encrypted_password).decode('utf-8') + + entry = PasswordEntry(website, username, encrypted_password_str) + self.entries[website] = entry + return True + + def get_password(self, website): + """ + Retrieve a password entry by website. + + Args: + website (str): The website or service name + + Returns: + tuple: (username, decrypted_password) or (None, None) if not found + """ + entry = self.entries.get(website) + if entry is None: + return None, None + + # Decrypt the password + encrypted_password_bytes = base64.b64decode(entry.get_password()) + decrypted_password = EncryptionUtil.decrypt(encrypted_password_bytes, self.key) + + return entry.get_username(), decrypted_password + + def update_password(self, website, username, password): + """ + Update an existing password entry. + + Args: + website (str): The website or service name + username (str): The username for the service + password (str): The password for the service + + Returns: + bool: True if successful, False if entry does not exist + """ + if website not in self.entries: + return False + + # Encrypt the password + encrypted_password = EncryptionUtil.encrypt(password, self.key) + # Store the encrypted password as a base64 string for JSON serialization + encrypted_password_str = base64.b64encode(encrypted_password).decode('utf-8') + + entry = self.entries[website] + entry.set_username(username) + entry.set_password(encrypted_password_str) + return True + + def delete_password(self, website): + """ + Delete a password entry. + + Args: + website (str): The website or service name + + Returns: + bool: True if successful, False if entry does not exist + """ + if website not in self.entries: + return False + + del self.entries[website] + return True + + def list_websites(self): + """ + List all stored websites/services. + + Returns: + list: List of website names + """ + return list(self.entries.keys()) + + def save_to_file(self): + """ + Save password entries to a file. + + Returns: + bool: True if successful, False otherwise + """ + try: + data = { + "salt": base64.b64encode(self.salt).decode('utf-8'), + "entries": { + website: entry.to_dict() + for website, entry in self.entries.items() + } + } + + with open(self.data_file, 'w') as f: + json.dump(data, f) + + return True + except Exception as e: + print(f"Error saving to file: {e}") + return False + + def load_from_file(self): + """ + Load password entries from a file. + + Returns: + bool: True if successful, False otherwise + """ + try: + with open(self.data_file, 'r') as f: + data = json.load(f) + + # Get the salt and regenerate the key + self.salt = base64.b64decode(data.get("salt")) + self.key, _ = EncryptionUtil.generate_key(self.master_password, self.salt) + + # Load entries + entries_data = data.get("entries", {}) + for website, entry_data in entries_data.items(): + entry = PasswordEntry.from_dict(entry_data) + self.entries[website] = entry + + return True + except Exception as e: + print(f"Error loading from file: {e}") + return False + + def verify_master_password(self, password): + """ + Verify if the given password matches the master password. + + This is done by trying to decrypt a test entry. + + Args: + password (str): The password to verify + + Returns: + bool: True if verification succeeds, False otherwise + """ + try: + test_key, _ = EncryptionUtil.generate_key(password, self.salt) + + # Try to decrypt one entry to verify the password + if self.entries: + website = next(iter(self.entries)) + entry = self.entries[website] + encrypted_password_bytes = base64.b64decode(entry.get_password()) + EncryptionUtil.decrypt(encrypted_password_bytes, test_key) + return True + + # If there are no entries, just compare the keys + return test_key == self.key + except Exception: + return False \ No newline at end of file