Initial commit
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
from core import PasswordHasher, IntegrityChecker, CryptoManager, AuditLogger, AccountManager
|
||||
from gui import MainWindow
|
||||
|
||||
|
||||
class App:
|
||||
"""
|
||||
Класс точки входа для запуска менеджера учётных записей.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.db_file = None
|
||||
self.hash_file = "data/accounts.db.hash"
|
||||
self.log_file = "data/audit.log"
|
||||
|
||||
self.password_hasher = PasswordHasher()
|
||||
self.integrity_checker = IntegrityChecker()
|
||||
self.crypto_manager = CryptoManager()
|
||||
self.audit_logger = AuditLogger(self.log_file)
|
||||
self.account_manager = AccountManager(self.db_file)
|
||||
|
||||
self.gui = MainWindow(
|
||||
account_manager=self.account_manager,
|
||||
password_hasher=self.password_hasher,
|
||||
audit_logger=self.audit_logger,
|
||||
crypto_manager=self.crypto_manager,
|
||||
integrity_checker=self.integrity_checker,
|
||||
)
|
||||
|
||||
def run(self):
|
||||
self.gui.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
App().run()
|
||||
@@ -0,0 +1,5 @@
|
||||
from .password_hasher import PasswordHasher
|
||||
from .integrity_checker import IntegrityChecker
|
||||
from .crypto_manager import CryptoManager
|
||||
from .audit_logger import AuditLogger
|
||||
from .account_manager import AccountManager
|
||||
@@ -0,0 +1,62 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class AccountManager:
|
||||
"""
|
||||
Класс для управления учётными записями пользователей.
|
||||
"""
|
||||
|
||||
def __init__(self, db_file: str | None):
|
||||
self.db_file = Path(db_file) if db_file else None
|
||||
if isinstance(self.db_file, Path) and not self.db_file.exists():
|
||||
self._save_db({})
|
||||
|
||||
def set_db_file(self, db_file: str | None) -> None:
|
||||
self.db_file = Path(db_file) if db_file else None
|
||||
if isinstance(self.db_file, Path) and not self.db_file.exists():
|
||||
self._save_db({})
|
||||
|
||||
def _load_db(self) -> dict:
|
||||
if not isinstance(self.db_file, Path):
|
||||
return {}
|
||||
if self.db_file.exists():
|
||||
return json.loads(self.db_file.read_text(encoding="utf-8"))
|
||||
return {}
|
||||
|
||||
def _save_db(self, data: dict) -> None:
|
||||
if not isinstance(self.db_file, Path):
|
||||
raise RuntimeError("База данных не открыта")
|
||||
self.db_file.write_text(json.dumps(
|
||||
data, indent=2, ensure_ascii=False), encoding="utf-8")
|
||||
|
||||
def add_user(self, username: str, password_hash: str, salt: str) -> None:
|
||||
data = self._load_db()
|
||||
data[username] = {"hash": password_hash, "salt": salt}
|
||||
self._save_db(data)
|
||||
|
||||
def update_user(self, username: str, password_hash: str, salt: str) -> bool:
|
||||
data = self._load_db()
|
||||
if username not in data:
|
||||
return False
|
||||
data[username] = {"hash": password_hash, "salt": salt}
|
||||
self._save_db(data)
|
||||
return True
|
||||
|
||||
def delete_user(self, username: str) -> bool:
|
||||
data = self._load_db()
|
||||
if username not in data:
|
||||
return False
|
||||
del data[username]
|
||||
self._save_db(data)
|
||||
return True
|
||||
|
||||
def get_user(self, username: str) -> dict | None:
|
||||
data = self._load_db()
|
||||
return data.get(username)
|
||||
|
||||
def list_users(self) -> list[str]:
|
||||
if not isinstance(self.db_file, Path):
|
||||
return []
|
||||
data = self._load_db()
|
||||
return list(data.keys())
|
||||
@@ -0,0 +1,17 @@
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class AuditLogger:
|
||||
"""
|
||||
Класс для ведения журнала событий.
|
||||
"""
|
||||
|
||||
def __init__(self, log_file: str):
|
||||
self.log_file = Path(log_file)
|
||||
|
||||
def log(self, event: str) -> None:
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
line = f"[{timestamp}] {event}\n"
|
||||
with open(self.log_file, "a", encoding="utf-8") as f:
|
||||
f.write(line)
|
||||
@@ -0,0 +1,23 @@
|
||||
from cryptography.fernet import Fernet
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class CryptoManager:
|
||||
"""
|
||||
Класс для симметричного шифрования и расшифрования файлов.
|
||||
"""
|
||||
|
||||
def generate_key(self) -> bytes:
|
||||
return Fernet.generate_key()
|
||||
|
||||
def encrypt_file(self, file_path: str, key: bytes, output_path: str) -> None:
|
||||
fernet = Fernet(key)
|
||||
data = Path(file_path).read_bytes()
|
||||
encrypted = fernet.encrypt(data)
|
||||
Path(output_path).write_bytes(encrypted)
|
||||
|
||||
def decrypt_file(self, file_path: str, key: bytes, output_path: str) -> None:
|
||||
fernet = Fernet(key)
|
||||
data = Path(file_path).read_bytes()
|
||||
decrypted = fernet.decrypt(data)
|
||||
Path(output_path).write_bytes(decrypted)
|
||||
@@ -0,0 +1,26 @@
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class IntegrityChecker:
|
||||
"""
|
||||
Класс для проверки целостности файлов по хэш-сумме.
|
||||
"""
|
||||
|
||||
def calculate_hash(self, file_path: str) -> str:
|
||||
hasher = hashlib.sha256()
|
||||
with open(file_path, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(4096), b""):
|
||||
hasher.update(chunk)
|
||||
return hasher.hexdigest()
|
||||
|
||||
def save_hash(self, file_path: str, hash_file: str) -> None:
|
||||
file_hash = self.calculate_hash(file_path)
|
||||
Path(hash_file).write_text(file_hash, encoding="utf-8")
|
||||
|
||||
def verify_hash(self, file_path: str, hash_file: str) -> bool:
|
||||
if not Path(hash_file).exists():
|
||||
return False
|
||||
expected = Path(hash_file).read_text(encoding="utf-8").strip()
|
||||
actual = self.calculate_hash(file_path)
|
||||
return expected == actual
|
||||
@@ -0,0 +1,19 @@
|
||||
import hashlib
|
||||
import secrets
|
||||
|
||||
|
||||
class PasswordHasher:
|
||||
"""
|
||||
Класс для безопасного хэширования паролей с использованием соли.
|
||||
"""
|
||||
|
||||
def generate_salt(self, length: int = 16) -> str:
|
||||
return secrets.token_hex(length)
|
||||
|
||||
def hash_password(self, password: str, salt: str) -> str:
|
||||
hasher = hashlib.sha256()
|
||||
hasher.update(salt.encode("utf-8") + password.encode("utf-8"))
|
||||
return hasher.hexdigest()
|
||||
|
||||
def verify_password(self, password: str, salt: str, expected_hash: str) -> bool:
|
||||
return self.hash_password(password, salt) == expected_hash
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"user2": {
|
||||
"hash": "4d3596717c7887c07c607485fa8c287547a5e65559b3269844261417b1e7a97b",
|
||||
"salt": "a1bb56a86e8d504634668696d9e65189"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
109fb5ce85641ad0ed60cf8ae800289517d33c2cb100f39e5c7c74cb88b9b722
|
||||
@@ -0,0 +1,3 @@
|
||||
[2025-10-01 21:51:42] Добавлен пользователь: user
|
||||
[2025-10-01 21:51:48] Добавлен пользователь: user2
|
||||
[2025-10-01 21:51:55] Добавлен пользователь: user3
|
||||
@@ -0,0 +1 @@
|
||||
gAAAAABo3Wp_Jc6ye1Yf6rYq3GQ64_S6XHAQjm7WSCvS6fK48kMk5NmP7zFyX5AGShawV7uvFEVvx2v5Efn_hFWdML4I_iAf1x6jZheZLJuWzMLtsm4x25wEAo7c8vokR8YV-MaiJWKw_c9kdAzPoPpkfuZSAuwSU6hD_oNHooG8SPCWVTbbvmqwnquMchLpXaK_gfyYOgye_ERB5s5GBxMICwM47W87tGoKnFL6jtyGk8iIowPYD0xlrAhUwvuTeDfxlIhyOZBc72R5iTmiSbOgxNu6DyJy9-49kdSau35LTVWRThg7MEWxK1xTC5P6stXkhovNsjujsdcSBk8Rx-AgKBmSRPGznkC7zYTNw6gQhYujsi_WtaQ=
|
||||
@@ -0,0 +1 @@
|
||||
ir7gZ6qT1gP-wAcNvKB3Ua_k7zJ52bH_GmgdSO9iryo=
|
||||
@@ -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
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Создание виртуального окружения в .venv (если ещё нет)
|
||||
if [ ! -d ".venv" ]; then
|
||||
python3 -m venv .venv
|
||||
fi
|
||||
|
||||
# Активация окружения
|
||||
. .venv/bin/activate
|
||||
|
||||
# Обновление pip и установка зависимостей
|
||||
pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
|
||||
echo "Виртуальное окружение создано и зависимости установлены."
|
||||
echo "Активируйте его командой: . .venv/bin/activate"
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['app.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name='lab5-manager',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=False,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
@@ -0,0 +1,2 @@
|
||||
cryptography>=42.0.0
|
||||
pytest>=7.0
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Проверка наличия окружения
|
||||
if [ ! -d ".venv" ]; then
|
||||
echo "Виртуальное окружение не найдено. Сначала выполните ./install.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Активация окружения
|
||||
. .venv/bin/activate
|
||||
|
||||
# Запуск приложения
|
||||
python app.py
|
||||
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Проверка наличия окружения
|
||||
if [ ! -d ".venv" ]; then
|
||||
echo "Виртуальное окружение не найдено. Сначала выполните ./install.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Активация окружения
|
||||
. .venv/bin/activate
|
||||
|
||||
# Запуск тестов
|
||||
pytest -v tests
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.insert(0, os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "..")))
|
||||
@@ -0,0 +1,23 @@
|
||||
from core.account_manager import AccountManager
|
||||
|
||||
|
||||
def test_crud_operations(tmp_path):
|
||||
db_file = tmp_path / "accounts.json"
|
||||
manager = AccountManager(db_file)
|
||||
|
||||
# Добавление
|
||||
manager.add_user("alice", "hash1", "salt1")
|
||||
assert "alice" in manager.list_users()
|
||||
|
||||
# Чтение
|
||||
user = manager.get_user("alice")
|
||||
assert user["hash"] == "hash1"
|
||||
|
||||
# Обновление
|
||||
assert manager.update_user("alice", "hash2", "salt2")
|
||||
user = manager.get_user("alice")
|
||||
assert user["hash"] == "hash2"
|
||||
|
||||
# Удаление
|
||||
assert manager.delete_user("alice")
|
||||
assert "alice" not in manager.list_users()
|
||||
@@ -0,0 +1,12 @@
|
||||
from core.audit_logger import AuditLogger
|
||||
|
||||
|
||||
def test_log_event(tmp_path):
|
||||
log_file = tmp_path / "audit.log"
|
||||
logger = AuditLogger(log_file)
|
||||
|
||||
logger.log("Test event")
|
||||
content = log_file.read_text(encoding="utf-8")
|
||||
|
||||
assert "Test event" in content
|
||||
assert "[" in content # должно быть время
|
||||
@@ -0,0 +1,17 @@
|
||||
from core.crypto_manager import CryptoManager
|
||||
|
||||
|
||||
def test_encrypt_decrypt_file(tmp_path):
|
||||
file = tmp_path / "plain.txt"
|
||||
file.write_text("super secret", encoding="utf-8")
|
||||
|
||||
enc_file = tmp_path / "plain.enc"
|
||||
dec_file = tmp_path / "plain.dec"
|
||||
|
||||
crypto = CryptoManager()
|
||||
key = crypto.generate_key()
|
||||
|
||||
crypto.encrypt_file(file, key, enc_file)
|
||||
crypto.decrypt_file(enc_file, key, dec_file)
|
||||
|
||||
assert dec_file.read_text(encoding="utf-8") == "super secret"
|
||||
@@ -0,0 +1,17 @@
|
||||
from pathlib import Path
|
||||
from core.integrity_checker import IntegrityChecker
|
||||
|
||||
|
||||
def test_calculate_and_verify_hash(tmp_path: Path):
|
||||
file = tmp_path / "data.txt"
|
||||
file.write_text("hello", encoding="utf-8")
|
||||
|
||||
hash_file = tmp_path / "data.hash"
|
||||
checker = IntegrityChecker()
|
||||
|
||||
checker.save_hash(file, hash_file)
|
||||
assert checker.verify_hash(file, hash_file)
|
||||
|
||||
# Нарушим целостность
|
||||
file.write_text("changed", encoding="utf-8")
|
||||
assert not checker.verify_hash(file, hash_file)
|
||||
@@ -0,0 +1,12 @@
|
||||
import pytest
|
||||
from core.password_hasher import PasswordHasher
|
||||
|
||||
|
||||
def test_hash_and_verify():
|
||||
hasher = PasswordHasher()
|
||||
salt = hasher.generate_salt()
|
||||
pwd = "secret123"
|
||||
hashed = hasher.hash_password(pwd, salt)
|
||||
|
||||
assert hasher.verify_password(pwd, salt, hashed)
|
||||
assert not hasher.verify_password("wrong", salt, hashed)
|
||||
Reference in New Issue
Block a user