Kein PW bei Export
Dieser Commit ist enthalten in:
@ -1,18 +1,16 @@
|
||||
"""
|
||||
Profil-Export-Service für Account-Daten
|
||||
|
||||
Exportiert Account-Profile in verschiedene Formate (CSV, TXT, PDF)
|
||||
mit optionalem Passwortschutz.
|
||||
Exportiert Account-Profile in verschiedene Formate (CSV, TXT, PDF).
|
||||
"""
|
||||
|
||||
import os
|
||||
import csv
|
||||
import secrets
|
||||
import string
|
||||
import logging
|
||||
from io import BytesIO, StringIO
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, List, Optional, Tuple
|
||||
from typing import Dict, Any, List
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger("profile_export_service")
|
||||
@ -287,88 +285,6 @@ class ProfileExportService:
|
||||
logger.error(f"Fehler beim PDF-Export: {e}")
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def generate_password(length: int = 10) -> str:
|
||||
"""
|
||||
Generiert ein sicheres zufälliges Passwort.
|
||||
|
||||
Args:
|
||||
length: Länge des Passworts (Standard: 10)
|
||||
|
||||
Returns:
|
||||
Generiertes Passwort
|
||||
"""
|
||||
# Zeichensatz: Groß- und Kleinbuchstaben, Zahlen, Sonderzeichen
|
||||
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
|
||||
|
||||
# Sicherstellen dass mindestens ein Zeichen von jedem Typ vorhanden ist
|
||||
password_chars = [
|
||||
secrets.choice(string.ascii_uppercase), # Mindestens ein Großbuchstabe
|
||||
secrets.choice(string.ascii_lowercase), # Mindestens ein Kleinbuchstabe
|
||||
secrets.choice(string.digits), # Mindestens eine Zahl
|
||||
secrets.choice("!@#$%^&*") # Mindestens ein Sonderzeichen
|
||||
]
|
||||
|
||||
# Rest auffüllen
|
||||
for _ in range(length - 4):
|
||||
password_chars.append(secrets.choice(alphabet))
|
||||
|
||||
# Mischen für Zufälligkeit
|
||||
secrets.SystemRandom().shuffle(password_chars)
|
||||
|
||||
password = ''.join(password_chars)
|
||||
logger.info("Passwort generiert")
|
||||
return password
|
||||
|
||||
@staticmethod
|
||||
def create_protected_zip(files_dict: Dict[str, bytes], password: str) -> bytes:
|
||||
"""
|
||||
Erstellt eine passwortgeschützte ZIP-Datei.
|
||||
|
||||
Args:
|
||||
files_dict: Dictionary mit {filename: content} Paaren
|
||||
password: Passwort für die ZIP-Datei
|
||||
|
||||
Returns:
|
||||
ZIP-Daten als bytes
|
||||
"""
|
||||
try:
|
||||
import pyzipper
|
||||
|
||||
# ZIP-Buffer erstellen
|
||||
buffer = BytesIO()
|
||||
|
||||
# ZIP mit AES-Verschlüsselung erstellen
|
||||
with pyzipper.AESZipFile(
|
||||
buffer,
|
||||
'w',
|
||||
compression=pyzipper.ZIP_DEFLATED,
|
||||
encryption=pyzipper.WZ_AES
|
||||
) as zf:
|
||||
# Passwort setzen
|
||||
zf.setpassword(password.encode('utf-8'))
|
||||
|
||||
# Dateien hinzufügen
|
||||
for filename, content in files_dict.items():
|
||||
zf.writestr(filename, content)
|
||||
|
||||
# Buffer-Wert holen
|
||||
zip_content = buffer.getvalue()
|
||||
buffer.close()
|
||||
|
||||
logger.info(f"Passwortgeschützte ZIP erstellt mit {len(files_dict)} Dateien")
|
||||
return zip_content
|
||||
|
||||
except ImportError as e:
|
||||
logger.error(f"pyzipper nicht installiert: {e}")
|
||||
raise Exception(
|
||||
"ZIP mit Passwortschutz erfordert 'pyzipper' Library. "
|
||||
"Bitte installieren Sie: pip install pyzipper"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Erstellen der geschützten ZIP: {e}")
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def generate_filename(
|
||||
account_data: Dict[str, Any],
|
||||
@ -427,19 +343,17 @@ class ProfileExportService:
|
||||
account_data: Dict[str, Any],
|
||||
formats: List[str],
|
||||
password_protect: bool = False
|
||||
) -> Tuple[Dict[str, bytes], Optional[str]]:
|
||||
) -> Dict[str, bytes]:
|
||||
"""
|
||||
Exportiert Account-Daten in angegebene Formate.
|
||||
|
||||
Args:
|
||||
account_data: Account-Daten zum Exportieren
|
||||
formats: Liste von Formaten ["csv", "txt", "pdf"]
|
||||
password_protect: Ob Dateien passwortgeschützt werden sollen
|
||||
password_protect: Wird ignoriert (für Rückwärtskompatibilität)
|
||||
|
||||
Returns:
|
||||
Tuple von (files_dict, password)
|
||||
- files_dict: {filename: content} für alle Formate
|
||||
- password: Generiertes Passwort (None wenn nicht geschützt)
|
||||
Dict mit {filename: content} für alle Formate
|
||||
"""
|
||||
files_dict = {}
|
||||
|
||||
@ -464,16 +378,4 @@ class ProfileExportService:
|
||||
else:
|
||||
logger.warning(f"Unbekanntes Format ignoriert: {fmt}")
|
||||
|
||||
# Passwortschutz wenn gewünscht
|
||||
password = None
|
||||
if password_protect and files_dict:
|
||||
password = ProfileExportService.generate_password()
|
||||
|
||||
# Alle Dateien in ZIP packen
|
||||
zip_filename = ProfileExportService.generate_filename(account_data, "zip")
|
||||
zip_content = ProfileExportService.create_protected_zip(files_dict, password)
|
||||
|
||||
# Nur ZIP zurückgeben
|
||||
files_dict = {zip_filename: zip_content}
|
||||
|
||||
return files_dict, password
|
||||
return files_dict
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren