From 3f5687c618d04ca78d539844690618ca12ecf405 Mon Sep 17 00:00:00 2001 From: Ibrahim-Debug-1010 <201203058@nileuniversity.edu.ng> Date: Sat, 17 May 2025 22:39:49 +0100 Subject: [PATCH] Add files via upload --- .../Folder/Instructions | 1 + .../Folder/guest.py | 23 ++ .../Folder/hotel_management.py | 347 ++++++++++++++++++ .../Folder/reservation.py | 25 ++ .../Folder/room.py | 27 ++ .../PullRequestInstructions | 60 +++ .../README.md | 40 ++ OOP-Project/Readme.md | 63 ++++ .../encryption_util.cpython-311.pyc | Bin 0 -> 3212 bytes .../password_entry.cpython-311.pyc | Bin 0 -> 3674 bytes .../password_manager.cpython-311.pyc | Bin 0 -> 9461 bytes OOP-Project/encryption_util.py | 78 ++++ OOP-Project/main.py | 235 ++++++++++++ OOP-Project/password_entry.py | 85 +++++ OOP-Project/password_manager.py | 215 +++++++++++ 15 files changed, 1199 insertions(+) create mode 100644 OOP-Project/OOP-PythonAssignment-200L-NUN-2025/Folder/Instructions create mode 100644 OOP-Project/OOP-PythonAssignment-200L-NUN-2025/Folder/guest.py create mode 100644 OOP-Project/OOP-PythonAssignment-200L-NUN-2025/Folder/hotel_management.py create mode 100644 OOP-Project/OOP-PythonAssignment-200L-NUN-2025/Folder/reservation.py create mode 100644 OOP-Project/OOP-PythonAssignment-200L-NUN-2025/Folder/room.py create mode 100644 OOP-Project/OOP-PythonAssignment-200L-NUN-2025/PullRequestInstructions create mode 100644 OOP-Project/OOP-PythonAssignment-200L-NUN-2025/README.md create mode 100644 OOP-Project/Readme.md create mode 100644 OOP-Project/__pycache__/encryption_util.cpython-311.pyc create mode 100644 OOP-Project/__pycache__/password_entry.cpython-311.pyc create mode 100644 OOP-Project/__pycache__/password_manager.cpython-311.pyc create mode 100644 OOP-Project/encryption_util.py create mode 100644 OOP-Project/main.py create mode 100644 OOP-Project/password_entry.py create mode 100644 OOP-Project/password_manager.py 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 0000000000000000000000000000000000000000..f55bfb8fa11d6f979c7ea3b107ea78ac5f9f4d64 GIT binary patch literal 3212 zcmb_e&2JM|5P$3MI3I?D1SnvZR)Sc7L(-5+0V47Np`rxTl&Z2Ktv22l+hBX$w`-8C zMC1@vrbTL~QiVe+rIi{~rHB3*JtoK!)=HeJ9&$4cTsU>+?MJds+9I{j_Uycw_c0&8 znf24w)-Zt~{YKJ1gb4W^C-(4J%+_bXEE9z!h$1MStdJ1+)06PP)0_3?dK^g$5f0s1K{RtXvS1bqA zJUUjs92Q{=i$(5bNla$Jjeqe}(;4^=e&W`Nq!To-EC4@rWGgD56SKnn0UbDd+;Mot zvkLs0GoKT5zrBi(S)$`f)^MkW9hfC6cKYol{OJzF9MPM%)q3kTeg6nR_uCaf2*1=ZhLuoSP;#_dxB+XIG`qa&vx2A4cO-8yQ=j9X#w*w_%3C9a1 zqa|e_NrD^7N3OUEKE6|lxYoV`GEeGCVwRS zk^p&yq~d+uMk1Znwv#K`)AEnyUwbxAjZ{yKY_yG5+eRziTBLn(tWsJJ9ohnOh^wby zjCfD^4wz-Kv!6hp*wKCV=o34%c&<6zRS$i_=?Bnxgob+cK%JunyGCQ3Lq!YaO$U%x z*hNLwWw8jTN^xKpn0haxPzAyMR^`k0@2FWm*HybkIQJon;7qGztpeXh2kKmFoyndx z%In^264n`DGG6U$i}@MChqWW=K!Q0kg4Xhw*YM$b_6|yg84a~yB0GXhj{_-pSs{Y% zfv=*twX+9w+7OU=@)|(dwWxeQy3s6Fn?(fRiCV|OA6|cS zy&}{i`@SxHS$cBd;e$sH)cC&jR5%Y3*nPLUbe2U5#`ZdRVikXZ;_t<8 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..cf1e78c4d51319d8b8ba925c9ea9fc7052f2ecb0 GIT binary patch literal 3674 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 literal 0 HcmV?d00001 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