From 1500c10d85db77bb850df63c73d3792a3eb038df Mon Sep 17 00:00:00 2001 From: josakko Date: Mon, 8 Jul 2024 00:53:08 +0200 Subject: [PATCH] re organized and re wrote big part of the app, database is still left --- .gitignore | 2 + src/app_database.py | 87 +----- src/dashboard.py | 498 +++++++++++++++------------------ src/database.py | 123 ++++++++ src/dialogs/about.py | 43 +++ src/{ => dialogs}/generator.py | 0 src/dialogs/register.py | 84 ++++++ src/gui/bottom_bar.py | 24 ++ src/login.py | 208 +++----------- src/main.py | 79 ++---- src/utils.py | 106 +++++++ 11 files changed, 691 insertions(+), 563 deletions(-) create mode 100644 src/database.py create mode 100644 src/dialogs/about.py rename src/{ => dialogs}/generator.py (100%) create mode 100644 src/dialogs/register.py create mode 100644 src/gui/bottom_bar.py create mode 100644 src/utils.py diff --git a/.gitignore b/.gitignore index 344ce0b..39ea79a 100644 --- a/.gitignore +++ b/.gitignore @@ -134,6 +134,8 @@ src/backups test.py *.db +test + # FILES ./packaging/linux/JK_PasswordManager/usr/share/JK_PasswordManager/JK_PasswordManager ./packaging/linux/JK_Passwordmanager.AppDir/usr/bin/JK_PasswordManager diff --git a/src/app_database.py b/src/app_database.py index 32d5337..b818f2e 100644 --- a/src/app_database.py +++ b/src/app_database.py @@ -1,79 +1,20 @@ import sqlite3 from cryptography.fernet import Fernet -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC -import base64 -import hashlib - - - -def hash(string: str, salt: str) -> str: - data = string.encode() + salt.encode() - - algorithm = hashlib.sha512() - algorithm.update(data) - return algorithm.hexdigest() - - -def generate_key(password: str, username: str) -> str: - salt = base64.urlsafe_b64encode(f"{password}{username}".encode()) - - kdf = PBKDF2HMAC( - algorithm=hashes.SHA256(), - iterations=100000, - salt=salt, - length=32 - ) - - return base64.urlsafe_b64encode(kdf.derive(password.encode())) - - -def encrypt(string: str, f: object=None, key: str=None): - if f: - return f.encrypt(string.encode()).decode() - elif key: - return Fernet(key).encrypt(string.encode()).decode() - else: - raise Exception("Key or Fernet instance is required") - - -def decrypt(string: str, f: object=None, key: str=None): - if f: - return f.decrypt(string.encode()).decode() - elif key: - return Fernet(key).decrypt(string.encode()).decode() - else: - raise Exception("Key or Fernet instance is required") - +from utils import * def database(): - conn = sqlite3.connect("password_vault.db") + conn = sqlite3.connect(DB_FILE) cursor = conn.cursor() - cursor.execute("""CREATE TABLE IF NOT EXISTS users( - user_id varchar PRIMARY KEY, - user_name text, - password text - )""") - - - cursor.execute("""CREATE TABLE IF NOT EXISTS users_data( - id INTEGER PRIMARY KEY, - platform text, - user_name text, - password text, - time text, - user_id varchar, - FOREIGN KEY (user_id) REFERENCES supplier_groups (user_id) - )""") + cursor.executescript(SCHEMA) conn.commit() conn.close() def sign_up(new_username, new_password): - conn = sqlite3.connect("password_vault.db") + conn = sqlite3.connect(DB_FILE) username = hash(new_username, new_username) @@ -101,7 +42,7 @@ def sign_up(new_username, new_password): char = 65 new_user_id = chr(char) + str(number) - key = encrypt(Fernet.generate_key().decode(), f=Fernet(generate_key(new_password, username))) + key = encrypt_key(Fernet.generate_key().decode(), generate_key_with_password(new_password, username)) data = [new_user_id, username, key] sql = """INSERT INTO users(user_id, user_name, password) @@ -118,10 +59,10 @@ def sign_up(new_username, new_password): def login(username, password): username_hash = hash(username, username) - password_key = generate_key(password, username_hash) + password_key = generate_key_with_password(password, username_hash) password_f = Fernet(password_key) - conn = sqlite3.connect("password_vault.db") + conn = sqlite3.connect(DB_FILE) cursor = conn.cursor() cursor.execute("SELECT * FROM users") @@ -131,7 +72,7 @@ def login(username, password): return 0 for row in rows: - try: key = decrypt(row[2], f=password_f) + try: key = decrypt(row[2], password_f) except: continue user_id = row[0] @@ -157,7 +98,7 @@ def insert(data, f): data.pop(2) data.insert(2, password) - conn = sqlite3.connect("password_vault.db") + conn = sqlite3.connect(DB_FILE) cursor = conn.cursor() sql = """INSERT INTO users_data(platform,user_name,password,time,user_id) @@ -182,7 +123,7 @@ def update(data, f): data.pop(2) data.insert(2, password) - conn = sqlite3.connect("password_vault.db") + conn = sqlite3.connect(DB_FILE) cursor = conn.cursor() sql = "UPDATE users_data SET platform=?,user_name=?,password=?,time=?,user_id=? WHERE id=?" @@ -194,7 +135,7 @@ def update(data, f): def delete(row_id): - conn = sqlite3.connect("password_vault.db") + conn = sqlite3.connect(DB_FILE) cursor = conn.cursor() sql = "DELETE FROM users_data WHERE id IN ({})".format(", ".join("?" * len(row_id))) @@ -206,7 +147,7 @@ def delete(row_id): def delete_user_data(user_id): - conn = sqlite3.connect("password_vault.db") + conn = sqlite3.connect(DB_FILE) cursor = conn.cursor() sql = "DELETE FROM users_data WHERE user_id=?" @@ -218,7 +159,7 @@ def delete_user_data(user_id): def get_password(row_id, user_id, f): - conn = sqlite3.connect("password_vault.db") + conn = sqlite3.connect(DB_FILE) cursor = conn.cursor() sql = "SELECT password FROM users_data WHERE id = ? AND user_id = ?" @@ -228,4 +169,4 @@ def get_password(row_id, user_id, f): conn.close() - return decrypt(requested_password, f=f) + return decrypt(requested_password, f) diff --git a/src/dashboard.py b/src/dashboard.py index ad032f2..c173941 100644 --- a/src/dashboard.py +++ b/src/dashboard.py @@ -1,25 +1,24 @@ import tkinter as tk import time -from tkinter import ttk, font, filedialog +from tkinter import ttk, filedialog import pyperclip from app_database import insert from app_database import update from app_database import delete from app_database import delete_user_data from app_database import get_password -from generator import PasswordGenerator +from dialogs.generator import PasswordGenerator import tkinter.messagebox as msg from handlers import login_handler -import webbrowser import csv import os - class Dashboard(tk.Frame): def __init__(self, parent, root, user_id, user_name, data, f): tk.Frame.__init__(self, parent, bg="#3d3d5c") self.root = root + self.parent = parent self.user_id = user_id self.user_name = user_name self.data = data @@ -30,77 +29,45 @@ def __init__(self, parent, root, user_id, user_name, data, f): tk.Label(heading_frame, text=self.user_name, font=("arial", 13), fg="white", bg="#33334d").pack(side="left") tk.Label(heading_frame, text=" " * 20, bg="#33334d").pack(padx=10, side="left") tk.Label(heading_frame, text="Total: ", font=("arial", 13), fg="white", bg="#33334d", ).pack(side="left") - total_entries = tk.Label(heading_frame, text=len(self.data), font=("arial", 13), fg="white", bg="#33334d", ) - total_entries.pack(side="left") + self.total_entries = tk.Label(heading_frame, text=len(self.data), font=("arial", 13), fg="white", bg="#33334d", ) + self.total_entries.pack(side="left") - def logout(): - login_handler.LoginHandler(parent, self.root) - - logout_button = tk.Button(heading_frame, text="LOGOUT", command=logout, width=15, relief="raised") + logout_button = tk.Button(heading_frame, text="LOGOUT", command=self.logout, width=15, relief="raised") logout_button.pack(padx=10, side="right") heading_frame.pack(fill="x", pady=10) - def copy_menu(event): - item = data_tree.identify("item", event.x, event.y) - column = data_tree.identify("column", event.x, event.y) - - if item and column: - def copy(): - if column == "#0": - text = data_tree.item(item, "text") - name = "Platform" - elif column == "#4": - msg.showerror("Error", 'Please use "Copy Password" button in order to copy password!'); return - else: - text = data_tree.set(item, column) - name = data_tree.heading(column)["text"] - - pyperclip.copy(text) - msg.showinfo("Info", f"{name} copied to clipboard!") - - - menu = tk.Menu(table_frame, tearoff=0) - menu.add_command(label="Copy", command=copy) - menu.post(event.x_root, event.y_root) - - - def deselect(event): - data_tree.selection_remove(data_tree.focus()) - - - table_frame = tk.Frame(self) - tree_scroll = tk.Scrollbar(table_frame) + self.table_frame = tk.Frame(self) + tree_scroll = tk.Scrollbar(self.table_frame) tree_scroll.pack(side="right", fill="y") - data_tree = ttk.Treeview(table_frame, yscrollcommand=tree_scroll.set, selectmode="extended") - data_tree.bind("", deselect) - data_tree.bind("", copy_menu) - tree_scroll.config(command=data_tree.yview) - - data_tree["columns"] = ("S.No", "Platform", "Username", "Password", "Time") - data_tree.column("#0", width=0, stretch="no") - data_tree.column("S.No", anchor="w", width=0) - data_tree.column("Platform", anchor="center", width=30) - data_tree.column("Username", anchor="center", width=30) - data_tree.column("Password", anchor="center", width=30) - data_tree.column("Time", anchor="center", width=30) - - data_tree.heading("#0", text="Label", anchor="w") - data_tree.heading("S.No", text="S.No", anchor="w") - data_tree.heading("Platform", text="Platform", anchor="center") - data_tree.heading("Username", text="Username", anchor="center") - data_tree.heading("Password", text="Password", anchor="center") - data_tree.heading("Time", text="Time", anchor="center") - - global count - count = 0 + self.data_tree = ttk.Treeview(self.table_frame, yscrollcommand=tree_scroll.set, selectmode="extended") + self.data_tree.bind("", self.deselect) + self.data_tree.bind("", self.copy_menu) + tree_scroll.config(command=self.data_tree.yview) + + self.data_tree["columns"] = ("S.No", "Platform", "Username", "Password", "Time") + self.data_tree.column("#0", width=0, stretch="no") + self.data_tree.column("S.No", anchor="w", width=0) + self.data_tree.column("Platform", anchor="center", width=30) + self.data_tree.column("Username", anchor="center", width=30) + self.data_tree.column("Password", anchor="center", width=30) + self.data_tree.column("Time", anchor="center", width=30) + + self.data_tree.heading("#0", text="Label", anchor="w") + self.data_tree.heading("S.No", text="S.No", anchor="w") + self.data_tree.heading("Platform", text="Platform", anchor="center") + self.data_tree.heading("Username", text="Username", anchor="center") + self.data_tree.heading("Password", text="Password", anchor="center") + self.data_tree.heading("Time", text="Time", anchor="center") + + self.count = 0 for record in self.data: - display_password = "*"*len(record[3]) - count += 1 - data_tree.insert(parent="", index="end", iid=record[0], text="", values=(count, record[1], record[2], display_password, record[4])) + display_password = "*" * len(record[3]) + self.count += 1 + self.data_tree.insert(parent="", index="end", iid=record[0], text="", values=(self.count, record[1], record[2], display_password, record[4])) - data_tree.pack(fill="both", expand="True") - table_frame.pack(fill="both", expand="True") + self.data_tree.pack(fill="both", expand="True") + self.table_frame.pack(fill="both", expand="True") button_frame1 = tk.Frame(self, relief="raised", bg="#3d3d5c") button_frame1.pack(pady=10) @@ -108,249 +75,246 @@ def deselect(event): tk.Label(button_frame1, text="Platform", fg="white", bg="#3d3d5c").grid(row=0, column=0) tk.Label(button_frame1, text="Username", fg="white", bg="#3d3d5c").grid(row=0, column=1) tk.Label(button_frame1, text="Password", fg="white", bg="#3d3d5c").grid(row=0, column=2) - add_update_platform = tk.Entry(button_frame1, textvariable="add_update_platform", font=13) - add_update_platform.grid(row=1, column=0) - add_update_username = tk.Entry(button_frame1, textvariable="add_update_username", font=13) - add_update_username.grid(row=1, column=1) - add_update_password = tk.Entry(button_frame1, textvariable="add_update_password", font=13) - add_update_password.grid(row=1, column=2) - - - def update_row(row_id, platform, username, password, time_stamp=time.strftime("%I:%M %p %d-%m-%Y")): - #time_stamp = time.strftime("%I:%M %p %d-%m-%Y") - - row = [platform, username, password, time_stamp, self.user_id, row_id] - update(row, self.f) - serial_number = data_tree.item(row_id, "values")[0] - display_password = "*" * len(password) - data_tree.item(row_id, text="", values=(serial_number, platform, username, display_password, time_stamp)) - - def insert_row(platform, username, password, time_stamp=time.strftime("%I:%M %p %d-%m-%Y")): - #time_stamp = time.strftime("%I:%M %p %d-%m-%Y") - global count - - row = [platform, username, password, time_stamp, self.user_id] - new_id = insert(row, self.f) - display_password = "*"*len(password) - count += 1 - data_tree.insert(parent="", index="end", iid=new_id, text="", values=(count, platform, username, display_password, time_stamp)) - total_entries["text"] = count - - def add_update_row(): - if not add_update_platform.get() and not add_update_username.get() and not add_update_password.get(): - msg.showerror("Error", "Fill out at least one field!") - return - - selected = data_tree.focus() - if data_tree.selection(): - decision = msg.askokcancel("Warning", "Are you sure you want to update selected row?") - if decision: - update_row(selected, add_update_platform.get(), add_update_username.get(), add_update_password.get()) - else: - return - else: - insert_row(add_update_platform.get(), add_update_username.get(), add_update_password.get()) - - add_update_platform.delete(0, "end") - add_update_username.delete(0, "end") - add_update_password.delete(0, "end") - - add_button = tk.Button(button_frame1, command=add_update_row, text="Add / Update", width=20, relief="raised") - add_button.grid(row=1, column=3, padx=20) + self.add_update_platform = tk.Entry(button_frame1, textvariable="add_update_platform", font=13) + self.add_update_platform.grid(row=1, column=0) + self.add_update_username = tk.Entry(button_frame1, textvariable="add_update_username", font=13) + self.add_update_username.grid(row=1, column=1) + self.add_update_password = tk.Entry(button_frame1, textvariable="add_update_password", font=13) + self.add_update_password.grid(row=1, column=2) + add_button = tk.Button(button_frame1, command=self.add_update_row, text="Add / Update", width=20, relief="raised") + add_button.grid(row=1, column=3, padx=20) + button_frame = tk.Frame(self, relief="raised", bg="#33334d") button_frame.pack(fill="x", pady=30) - - - def delete_row(): - if data_tree.selection(): - decision = msg.askokcancel("Warning", "Are you sure you want to delete selected password ?") - if decision: - x = data_tree.selection() - else: - return - delete(x) - global count - if len(x) == 1: - data_tree.delete(x) - count -= 1 - else: - for i in x: - data_tree.delete(i) - count -= 1 - total_entries["text"] = count - else: - msg.showerror("ERROR", "Please select one above!") - delete_button = tk.Button(button_frame, text="Delete",bg="red", command=delete_row, relief="raised", width=10) + delete_button = tk.Button(button_frame, text="Delete",bg="red", command=self.delete_row, relief="raised", width=10) delete_button.pack(pady=10, padx=10, side="left") - - def copy_password(): - if data_tree.selection(): - selected_password = get_password(data_tree.focus(), self.user_id, self.f) - pyperclip.copy(selected_password) - msg.showinfo("Info", "Password copied.") - else: - msg.showerror("ERROR", "Please select one above!") - - copy_button = tk.Button(button_frame, text="Copy Password", command=copy_password, relief="raised", width=15) + copy_button = tk.Button(button_frame, text="Copy Password", command=self.copy_password, relief="raised", width=15) copy_button.pack(pady=10, padx=15, side="left") - - def show_password(): - if data_tree.selection(): - selected_password = get_password(data_tree.focus(), self.user_id, self.f) - selected_row_data = data_tree.item(data_tree.focus(), "values") - msg.showinfo("Login Credentials", f'Your password for "{selected_row_data[1]}" is "{selected_password}" and username is "{selected_row_data[2]}".') - else: - msg.showerror("ERROR", "Please select one above!") - - show_button = tk.Button(button_frame, text="Show Password", command=show_password, relief="raised", width=15) + show_button = tk.Button(button_frame, text="Show Password", command=self.show_password, relief="raised", width=15) show_button.pack(pady=10, padx=15, side="left") + - - def run_password_generator(): - PasswordGenerator(self) - - password_generator_btn = tk.Button(button_frame, text="Password Generator", width=15, relief="raised", command=run_password_generator) + password_generator_btn = tk.Button( + button_frame, + text="Password Generator", + width=15, relief="raised", + command=lambda: PasswordGenerator(self) ) password_generator_btn.pack(pady=10, padx=15, side="left") - - def delete_all_row(): - decision = msg.askokcancel("Warning", "Are you sure to delete all ?") - if decision: - delete_user_data(self.user_id) - for x in data_tree.get_children(): - data_tree.delete(x) - global count - count = 0 - total_entries["text"] = 0 - - delete_all_button = tk.Button(button_frame, text="Delete All Passwords", command=delete_all_row, relief="raised", bg="red", width=20) + delete_all_button = tk.Button(button_frame, text="Delete All Passwords", command=self.delete_all_rows, relief="raised", bg="red", width=20) delete_all_button.pack(pady=10, padx=50, side="right") - def get_data(): - data = [] + export_button = tk.Button(button_frame, text="Export to CSV", command=self.export, relief="raised", width=15) + export_button.pack(pady=10, padx=15, side="right") + + import_button = tk.Button(button_frame, text="Import from CSV", command=self.import_, relief="raised", width=15) + import_button.pack(pady=10, padx=15, side="right") + + self.cp_menu = tk.Menu(self.table_frame, tearoff=0) + + + def logout(self): + login_handler.LoginHandler(self.parent, self.root) + self.destroy() - for id in data_tree.get_children(): - values = data_tree.item(id, "values") - password = get_password(id, self.user_id, self.f) - row = (values[1], values[2], password, values[4]) - data.append(row) + def copy_menu(self, event: tk.Event): + item = self.data_tree.identify("item", event.x, event.y) + column = self.data_tree.identify("column", event.x, event.y) - return data + if not item or not column: + return - def write(file): - try: - with open(file, "w", newline="", encoding="utf-8") as export_f: - f = csv.writer(export_f) - f.writerows(get_data()) - - msg.showinfo("Export", f"Successfully exported passwords to {file}!") - except: - msg.showerror("Error", "Invalid path!") + def copy(): + if column == "#0": + text = self.data_tree.item(item, "text") + name = "Platform" + elif column == "#4": + msg.showerror("Error", 'Please use "Copy Password" button in order to copy password!'); return + else: + text = self.data_tree.set(item, column) + name = self.data_tree.heading(column)["text"] + + pyperclip.copy(text) + msg.showinfo("Info", f"{name} copied to clipboard!") + + self.cp_menu.unpost() + self.cp_menu.delete(0, tk.END) + self.cp_menu.add_command(label="Copy", command=copy) + self.cp_menu.post(event.x_root, event.y_root) - def export(): - msg.showwarning("Warning", "If you continue all stored credentials will be exported in unprotected plain text and will be exposed to danger of being stolen!") - - file = filedialog.asksaveasfilename(title="Export", initialfile="export.csv", defaultextension=".csv", filetypes=[("CSV Files", "*.csv")]) - - if not file: - return - write(file) + def copy(self, column, item): + if column == "#0": + text = self.data_tree.item(item, "text") + name = "Platform" + elif column == "#4": + msg.showerror("Error", 'Please use "Copy Password" button in order to copy password!'); return + else: + text = self.data_tree.set(item, column) + name = self.data_tree.heading(column)["text"] + + pyperclip.copy(text) + msg.showinfo("Info", f"{name} copied to clipboard!") + + + def deselect(self, event: tk.Event): + self.data_tree.selection_remove(self.data_tree.focus()) + self.cp_menu.unpost() + + + def update_row(self, row_id, platform, username, password, time_stamp=time.strftime("%I:%M %p %d-%m-%Y")): + row = [platform, username, password, time_stamp, self.user_id, row_id] + update(row, self.f) + serial_number = self.data_tree.item(row_id, "values")[0] + display_password = "*" * len(password) + self.data_tree.item(row_id, text="", values=(serial_number, platform, username, display_password, time_stamp)) + + + def insert_row(self, platform, username, password, time_stamp=time.strftime("%I:%M %p %d-%m-%Y")): + row = [platform, username, password, time_stamp, self.user_id] + new_id = insert(row, self.f) + display_password = "*" * len(password) + self.count += 1 + self.data_tree.insert(parent="", index="end", iid=new_id, text="", values=(self.count, platform, username, display_password, time_stamp)) + self.total_entries["text"] = self.count - export_button = tk.Button(button_frame, text="Export to CSV", command=export, relief="raised", width=15) - export_button.pack(pady=10, padx=15, side="right") + + def add_update_row(self): + if not self.add_update_platform.get() and not self.add_update_username.get() and not self.add_update_password.get(): + msg.showerror("Error", "Fill out at least one field!") + return + + selected = self.data_tree.focus() + if not self.data_tree.selection(): + self.insert_row(self.add_update_platform.get(), self.add_update_username.get(), self.add_update_password.get()) + elif msg.askokcancel("Warning", "Are you sure you want to update selected row?"): + self.update_row(selected, self.add_update_platform.get(), self.add_update_username.get(), self.add_update_password.get()) - def read(file): - try: - with open(file, "r", encoding="utf-8") as import_f: - data = csv.reader(import_f) - - for row in data: - insert_row(row[0], row[1], row[2], time.strftime("%I:%M %p %d-%m-%Y") if not row[3] else row[3]) - - msg.showinfo("Import", f"Successfully imported passwords from {file}!") - except: - msg.showerror("Error", "Invalid file format or csv format!") - return - - def import_(): - file = filedialog.askopenfilename(title="Import", defaultextension=".csv", filetypes=[("CSV Files", "*.csv")]) - if not file: return - - if os.path.isfile(file): - read(file) - - else: - msg.showerror("Error", "Invalid file path!") - return - - import_button = tk.Button(button_frame, text="Import from CSV", command=import_, relief="raised", width=15) - import_button.pack(pady=10, padx=15, side="right") + else: return + self.add_update_platform.delete(0, "end") + self.add_update_username.delete(0, "end") + self.add_update_password.delete(0, "end") - def about(): - about = tk.Toplevel(self) - about.title("About - JK PasswordManager") - window_width = 600 - window_hight = 200 + def delete_row(self): + if not self.data_tree.selection(): + msg.showerror("ERROR", "Please select one above!") + return + + decision = msg.askokcancel("Warning", "Are you sure you want to delete selected password ?") + if not decision: + return + + rows = self.data_tree.selection() + delete(rows) - monitor_width = about.winfo_screenwidth() - monitor_hight = about.winfo_screenheight() + if len(rows) == 1: + self.data_tree.delete(rows) + self.count -= 1 + return - x = (monitor_width / 2) - (window_width / 2) - y = (monitor_hight / 2) - (window_hight / 2) + for row in rows: + self.data_tree.delete(row) + self.count -= 1 + self.total_entries["text"] = self.count - about.geometry(f"{window_width}x{window_hight}+{int(x)}+{int(y)}") - icon_image = tk.PhotoImage(file=os.path.join("assets", "icon.png")) - about.iconphoto(True, icon_image) - about.configure(bg="#f5f5f5") - about.resizable(False, False) - about.focus_force() - # about.grab_set() - custom_font = font.Font(family="Helvetica", size=12, weight="bold") + def copy_password(self): + if not self.data_tree.selection(): + msg.showerror("ERROR", "Please select one above!") + return - frame = tk.Frame(about, bg="#f5f5f5") - frame.pack(pady=50) + selected_password = get_password(self.data_tree.focus(), self.user_id, self.f) + pyperclip.copy(selected_password) + msg.showinfo("Info", "Password copied.") - text = "JK PasswordManager is an open source app created by Josakko, \nall documentation and instructions can be found on" - label_text = tk.Label(frame, text=text, font=custom_font, bg="#f5f5f5", fg="#333333") - label_text.pack(side=tk.LEFT) - link_text = tk.Label(frame, text="GitHub", font=custom_font, bg="#f5f5f5", fg="#007bff", cursor="hand2") - link_text.pack(side=tk.LEFT, anchor="sw") #side=tk.BOTTOM + def show_password(self): + if not self.data_tree.selection(): + msg.showerror("ERROR", "Please select one above!") + return - def open_link(event): - webbrowser.open_new("https://github.com/Josakko/JK_PasswordManager") + selected_password = get_password(self.data_tree.focus(), self.user_id, self.f) + selected_row_data = self.data_tree.item(self.data_tree.focus(), "values") + msg.showinfo("Login Credentials", f'Your password for "{selected_row_data[1]}" is "{selected_password}" and username is "{selected_row_data[2]}".') - link_text.bind("", open_link) + def delete_all_rows(self): + decision = msg.askokcancel("Warning", "Are you sure to delete all ?") + if not decision: + return + + delete_user_data(self.user_id) + for x in self.data_tree.get_children(): + self.data_tree.delete(x) + + self.count = 0 + self.total_entries["text"] = 0 + + + def get_data(self): + data = [] + + for id in self.data_tree.get_children(): + values = self.data_tree.item(id, "values") + password = get_password(id, self.user_id, self.f) + row = (values[1], values[2], password, values[4]) + data.append(row) + + return data + + + def write(self, file): + try: + with open(file, "w", newline="", encoding="utf-8") as export_f: + f = csv.writer(export_f) + f.writerows(self.get_data()) - bottom_frame = tk.Frame(self, relief="raised", borderwidth=3) - bottom_frame.pack(fill="x", side="bottom") + msg.showinfo("Export", f"Successfully exported passwords to {file}!") + except: + msg.showerror("Error", "Invalid path!") + + + def export(self): + msg.showwarning("Warning", "If you continue all stored credentials will be exported in unprotected plain text and will be exposed to danger of being stolen!") + file = filedialog.asksaveasfilename(title="Export", initialfile="export.csv", defaultextension=".csv", filetypes=[("CSV Files", "*.csv")]) - about_label = tk.Label(bottom_frame, text="About", fg="#007bff", font=("arial", 12, "bold"), cursor="hand2") - about_label.pack(side="left", padx=15) + if not file: + return - about_label.bind("