Initial commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
from .main_window import MainWindow
|
||||
|
||||
__all__ = ["MainWindow"]
|
||||
@@ -0,0 +1,16 @@
|
||||
import tkinter as tk
|
||||
|
||||
|
||||
class ControlsPanel:
|
||||
"""
|
||||
Панель управления действиями над пользователями.
|
||||
"""
|
||||
|
||||
def __init__(self, master, on_add, on_update, on_delete):
|
||||
self.frame = tk.Frame(master)
|
||||
tk.Button(self.frame, text="Добавить",
|
||||
command=on_add).pack(side=tk.LEFT, padx=5)
|
||||
tk.Button(self.frame, text="Изменить",
|
||||
command=on_update).pack(side=tk.LEFT, padx=5)
|
||||
tk.Button(self.frame, text="Удалить", command=on_delete).pack(
|
||||
side=tk.LEFT, padx=5)
|
||||
@@ -0,0 +1,92 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from tkinter import messagebox, filedialog as fd
|
||||
|
||||
|
||||
class CryptoTools:
|
||||
"""Операции шифрования и расшифрования файлов."""
|
||||
|
||||
def __init__(self, crypto_manager, audit_logger):
|
||||
self.crypto_manager = crypto_manager
|
||||
self.audit_logger = audit_logger
|
||||
|
||||
def _generate_and_save_key(self) -> bytes | None:
|
||||
key = self.crypto_manager.generate_key()
|
||||
path = fd.asksaveasfilename(
|
||||
title="Сохранить ключ шифрования",
|
||||
defaultextension=".key",
|
||||
filetypes=[("Key file", "*.key"), ("All files", "*.*")],
|
||||
initialfile="encryption.key",
|
||||
)
|
||||
if not path:
|
||||
return None
|
||||
try:
|
||||
Path(path).write_bytes(key)
|
||||
except Exception as e:
|
||||
messagebox.showerror("Сохранение ключа",
|
||||
f"Не удалось сохранить ключ:\n{e}")
|
||||
return None
|
||||
messagebox.showinfo("Ключ сохранён", f"Ключ сохранён в:\n{path}")
|
||||
return key
|
||||
|
||||
def _load_key_from_file(self) -> bytes | None:
|
||||
path = fd.askopenfilename(
|
||||
title="Выберите файл ключа",
|
||||
filetypes=[("Key file", "*.key"), ("All files", "*.*")],
|
||||
)
|
||||
if not path:
|
||||
return None
|
||||
try:
|
||||
return Path(path).read_bytes()
|
||||
except Exception as e:
|
||||
messagebox.showerror(
|
||||
"Чтение ключа", f"Не удалось прочитать ключ:\n{e}")
|
||||
return None
|
||||
|
||||
def _select_output_path(self, suggested: Path, title: str, defext: str, pattern: str) -> Path | None:
|
||||
path = fd.asksaveasfilename(
|
||||
title=title,
|
||||
defaultextension=defext,
|
||||
filetypes=[(f"*{defext}", pattern), ("All files", "*.*")],
|
||||
initialdir=str(suggested.parent),
|
||||
initialfile=suggested.name,
|
||||
)
|
||||
return Path(path) if path else None
|
||||
|
||||
def encrypt_file(self):
|
||||
src = fd.askopenfilename(title="Выберите файл для шифрования")
|
||||
if not src:
|
||||
return
|
||||
key = self._generate_and_save_key()
|
||||
if key is None:
|
||||
return
|
||||
suggested = Path(src).with_suffix(Path(src).suffix + ".enc")
|
||||
out = self._select_output_path(
|
||||
suggested, "Сохранить зашифрованный файл", ".enc", "*.enc")
|
||||
if out is None:
|
||||
return
|
||||
self.crypto_manager.encrypt_file(src, key, str(out))
|
||||
self.audit_logger.log(f"Зашифрован файл → {out.name}")
|
||||
messagebox.showinfo("Шифрование файла", f"Создан файл: {out}")
|
||||
|
||||
def decrypt_file(self):
|
||||
enc = fd.askopenfilename(title="Выберите зашифрованный файл", filetypes=[
|
||||
("Encrypted", "*.enc"), ("All files", "*.*")])
|
||||
if not enc:
|
||||
return
|
||||
key = self._load_key_from_file()
|
||||
if key is None:
|
||||
return
|
||||
suggested = Path(enc).with_suffix(".dec")
|
||||
out = self._select_output_path(
|
||||
suggested, "Сохранить расшифрованный файл", ".dec", "*.dec")
|
||||
if out is None:
|
||||
return
|
||||
try:
|
||||
self.crypto_manager.decrypt_file(enc, key, str(out))
|
||||
except Exception as e:
|
||||
messagebox.showerror("Расшифрование файла", f"Ошибка:\n{e}")
|
||||
return
|
||||
self.audit_logger.log(f"Расшифрован файл → {out.name}")
|
||||
messagebox.showinfo("Расшифрование файла", f"Создан файл: {out}")
|
||||
@@ -0,0 +1,73 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from tkinter import messagebox, filedialog as fd
|
||||
|
||||
|
||||
class IntegrityTools:
|
||||
"""Операции сохранения и проверки хэша БД."""
|
||||
|
||||
def __init__(self, integrity_checker, audit_logger, db_path: str | None):
|
||||
self.integrity_checker = integrity_checker
|
||||
self.audit_logger = audit_logger
|
||||
self.db_path = Path(db_path) if db_path else None
|
||||
|
||||
def set_db_path(self, db_path: str | None) -> None:
|
||||
self.db_path = Path(db_path) if db_path else None
|
||||
|
||||
def _hash_path(self) -> Path:
|
||||
return self.db_path.with_suffix(self.db_path.suffix + ".hash")
|
||||
|
||||
def save_db_hash(self):
|
||||
if not self.db_path:
|
||||
messagebox.showwarning("Сохранить хэш", "База данных не выбрана.")
|
||||
return
|
||||
if not self.db_path.exists():
|
||||
messagebox.showwarning(
|
||||
"Сохранить хэш", f"Файл БД не найден:\n{self.db_path}")
|
||||
return
|
||||
suggested = self.db_path.with_suffix(self.db_path.suffix + ".hash")
|
||||
path = fd.asksaveasfilename(
|
||||
title="Сохранить контрольную сумму БД",
|
||||
defaultextension=".hash",
|
||||
filetypes=[("Hash file", "*.hash"), ("All files", "*.*")],
|
||||
initialdir=str(suggested.parent),
|
||||
initialfile=suggested.name,
|
||||
)
|
||||
if not path:
|
||||
return
|
||||
try:
|
||||
self.integrity_checker.save_hash(str(self.db_path), str(path))
|
||||
except Exception as e:
|
||||
messagebox.showerror("Сохранить хэш", f"Ошибка сохранения:\n{e}")
|
||||
return
|
||||
self.audit_logger.log(
|
||||
f"Сохранена контрольная сумма БД → {Path(path).name}")
|
||||
messagebox.showinfo("Сохранить хэш", f"Хэш сохранён в {path}")
|
||||
|
||||
def verify_db_hash(self):
|
||||
if not self.db_path:
|
||||
messagebox.showwarning(
|
||||
"Проверка целостности", "База данных не выбрана.")
|
||||
return
|
||||
if not self.db_path.exists():
|
||||
messagebox.showwarning(
|
||||
"Проверка целостности", f"Файл БД не найден:\n{self.db_path}")
|
||||
return
|
||||
suggested = self.db_path.with_suffix(self.db_path.suffix + ".hash")
|
||||
path = fd.askopenfilename(
|
||||
title="Выбрать файл контрольной суммы БД",
|
||||
filetypes=[("Hash file", "*.hash"), ("All files", "*.*")],
|
||||
initialdir=str(suggested.parent),
|
||||
initialfile=suggested.name,
|
||||
)
|
||||
if not path:
|
||||
return
|
||||
ok = self.integrity_checker.verify_hash(str(self.db_path), str(path))
|
||||
self.audit_logger.log("Проверка целостности БД: " +
|
||||
("OK" if ok else "НЕСОВПАДЕНИЕ"))
|
||||
if ok:
|
||||
messagebox.showinfo("Проверка целостности", "БД в целостности.")
|
||||
else:
|
||||
messagebox.showerror("Проверка целостности",
|
||||
"БД была изменена извне.")
|
||||
@@ -0,0 +1,135 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import messagebox, simpledialog, filedialog as fd
|
||||
from pathlib import Path
|
||||
|
||||
from .user_list_view import UserListView
|
||||
from .controls_panel import ControlsPanel
|
||||
from .integrity_tools import IntegrityTools
|
||||
from .crypto_tools import CryptoTools
|
||||
|
||||
|
||||
class MainWindow:
|
||||
"""
|
||||
Главное окно менеджера учётных записей.
|
||||
"""
|
||||
|
||||
def __init__(self, account_manager, password_hasher, audit_logger, crypto_manager, integrity_checker):
|
||||
self.account_manager = account_manager
|
||||
self.password_hasher = password_hasher
|
||||
self.audit_logger = audit_logger
|
||||
|
||||
self.integrity_tools = IntegrityTools(
|
||||
integrity_checker, audit_logger, account_manager.db_file)
|
||||
self.crypto_tools = CryptoTools(crypto_manager, audit_logger)
|
||||
|
||||
self.root = tk.Tk()
|
||||
self.root.title("Менеджер учётных записей")
|
||||
|
||||
self._build_menu()
|
||||
|
||||
self.users_view = UserListView(self.root)
|
||||
self.users_view.frame.pack()
|
||||
|
||||
self.controls = ControlsPanel(
|
||||
self.root,
|
||||
on_add=self.add_user,
|
||||
on_update=self.update_user,
|
||||
on_delete=self.delete_user
|
||||
)
|
||||
self.controls.frame.pack(pady=5)
|
||||
|
||||
self.refresh_users()
|
||||
|
||||
def _build_menu(self):
|
||||
menubar = tk.Menu(self.root)
|
||||
|
||||
file_menu = tk.Menu(menubar, tearoff=0)
|
||||
file_menu.add_command(
|
||||
label="Открыть базу данных...", command=self.open_db)
|
||||
file_menu.add_command(label="Закрыть базу данных",
|
||||
command=self.close_db)
|
||||
menubar.add_cascade(label="Файл", menu=file_menu)
|
||||
|
||||
tools = tk.Menu(menubar, tearoff=0)
|
||||
tools.add_command(label="Сохранить хэш БД",
|
||||
command=self.integrity_tools.save_db_hash)
|
||||
tools.add_command(label="Проверить целостность БД",
|
||||
command=self.integrity_tools.verify_db_hash)
|
||||
tools.add_separator()
|
||||
tools.add_command(label="Зашифровать файл...",
|
||||
command=self.crypto_tools.encrypt_file)
|
||||
tools.add_command(label="Расшифровать файл...",
|
||||
command=self.crypto_tools.decrypt_file)
|
||||
|
||||
menubar.add_cascade(label="Инструменты", menu=tools)
|
||||
self.root.config(menu=menubar)
|
||||
|
||||
# --- File ops ---
|
||||
|
||||
def open_db(self):
|
||||
path = fd.askopenfilename(
|
||||
title="Открыть базу данных",
|
||||
filetypes=[("JSON", "*.json"), ("DB/TXT", "*.db *.txt"),
|
||||
("Все файлы", "*.*")],
|
||||
initialdir="data"
|
||||
)
|
||||
if not path:
|
||||
return
|
||||
self.account_manager.set_db_file(path)
|
||||
self.integrity_tools.set_db_path(path)
|
||||
self.audit_logger.log(f"Открыта база данных: {Path(path).name}")
|
||||
self.refresh_users()
|
||||
|
||||
# --- Account ops ---
|
||||
def refresh_users(self):
|
||||
self.users_view.set_items(self.account_manager.list_users())
|
||||
|
||||
def add_user(self):
|
||||
username = simpledialog.askstring(
|
||||
"Добавить пользователя", "Введите имя пользователя:")
|
||||
if not username:
|
||||
return
|
||||
password = simpledialog.askstring(
|
||||
"Пароль", "Введите пароль:", show="*")
|
||||
if not password:
|
||||
return
|
||||
salt = self.password_hasher.generate_salt()
|
||||
password_hash = self.password_hasher.hash_password(password, salt)
|
||||
self.account_manager.add_user(username, password_hash, salt)
|
||||
self.audit_logger.log(f"Добавлен пользователь: {username}")
|
||||
self.refresh_users()
|
||||
|
||||
def update_user(self):
|
||||
username = self.users_view.get_selected()
|
||||
if not username:
|
||||
messagebox.showwarning("Изменить", "Выберите пользователя")
|
||||
return
|
||||
password = simpledialog.askstring(
|
||||
"Новый пароль", f"Введите новый пароль для {username}:", show="*")
|
||||
if not password:
|
||||
return
|
||||
salt = self.password_hasher.generate_salt()
|
||||
password_hash = self.password_hasher.hash_password(password, salt)
|
||||
if self.account_manager.update_user(username, password_hash, salt):
|
||||
self.audit_logger.log(f"Обновлён пароль пользователя: {username}")
|
||||
self.refresh_users()
|
||||
|
||||
def delete_user(self):
|
||||
username = self.users_view.get_selected()
|
||||
if not username:
|
||||
messagebox.showwarning("Удалить", "Выберите пользователя")
|
||||
return
|
||||
if self.account_manager.delete_user(username):
|
||||
self.audit_logger.log(f"Удалён пользователь: {username}")
|
||||
self.refresh_users()
|
||||
|
||||
def run(self):
|
||||
self.root.mainloop()
|
||||
|
||||
def close_db(self):
|
||||
self.account_manager.set_db_file(None)
|
||||
self.integrity_tools.set_db_path(None)
|
||||
self.audit_logger.log("Закрыта база данных")
|
||||
self.users_view.set_items([])
|
||||
@@ -0,0 +1,21 @@
|
||||
import tkinter as tk
|
||||
|
||||
|
||||
class UserListView:
|
||||
"""
|
||||
Виджет списка пользователей.
|
||||
"""
|
||||
|
||||
def __init__(self, master):
|
||||
self.frame = tk.Frame(master)
|
||||
self._listbox = tk.Listbox(self.frame, width=40)
|
||||
self._listbox.pack(padx=10, pady=10)
|
||||
|
||||
def set_items(self, items: list[str]) -> None:
|
||||
self._listbox.delete(0, tk.END)
|
||||
for it in items:
|
||||
self._listbox.insert(tk.END, it)
|
||||
|
||||
def get_selected(self) -> str | None:
|
||||
sel = self._listbox.curselection()
|
||||
return self._listbox.get(sel[0]) if sel else None
|
||||
Reference in New Issue
Block a user