Initial commit

This commit is contained in:
user
2026-07-12 14:06:45 +04:00
commit a75028d68f
148 changed files with 3275 additions and 0 deletions
@@ -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