From 03ca1c607f25e320dc44b188fe44399595b2513e Mon Sep 17 00:00:00 2001 From: Claude Project Manager Date: Sun, 16 Nov 2025 22:57:59 +0100 Subject: [PATCH] Kein PW bei Export --- .claude/settings.local.json | 31 +------ CLAUDE.md | 0 CLAUDE_PROJECT_README.md | 15 ++- controllers/profile_export_controller.py | 67 +++----------- utils/profile_export_service.py | 110 ++-------------------- views/dialogs/export_success_dialog.py | 111 ++--------------------- views/dialogs/profile_export_dialog.py | 39 +------- 7 files changed, 45 insertions(+), 328 deletions(-) create mode 100644 CLAUDE.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 6a24f8f..479f7ec 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,38 +1,13 @@ { "permissions": { "allow": [ - "Bash(curl:*)", - "Bash(nslookup:*)", - "WebFetch(domain:multilogin.com)", - "WebFetch(domain:dicloak.com)", - "WebFetch(domain:support.google.com)", - "Bash(python3 -m pip list:*)", - "Bash(python3:*)", - "Bash(grep:*)", - "Bash(cat:*)", - "Bash(claude config)", - "Bash(claude config list:*)", - "Bash(claude mcp)", - "Bash(claude mcp:*)", - "Bash(sqlite3:*)", - "Bash(python -m pip install:*)", - "Bash(python -m pytest teststest_generator_tab_factory.py -v)", - "Bash(python tests:*)", - "Bash(python tests/test_generator_tab_factory.py)", - "Bash(git push:*)", - "Bash(git remote set-url:*)", - "WebSearch", - "Bash(find:*)", - "Bash(mount:*)", - "Read(//mnt/a/**)", - "Read(//mnt/c/Users/Administrator/AppData/Local/Programs/Python/Python310/**)", - "Read(//mnt/c/Users/Administrator/**)", - "Bash(/mnt/c/Users/Administrator/AppData/Local/Programs/Python/Python310/python.exe -c \"\nimport sys\nsys.dont_write_bytecode = True # Verhindere .pyc Erstellung\nimport utils.email_handler as eh\nhandler = eh.EmailHandler()\npw = handler.config[''imap_pass'']\nprint(f''Windows Python lädt Passwort: {pw[:4]}...{pw[-4:]}'')\nif ''GZsg'' in pw:\n print(''✅ NEUES Passwort wird geladen!'')\nelse:\n print(''❌ ALTES Passwort wird noch geladen!'')\n\")" + "Bash(python3:*)" ], "deny": [], "defaultMode": "acceptEdits", "additionalDirectories": [ "/mnt/a/GiTea/Styleguide" - ] + ], + "ask": [] } } diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e69de29 diff --git a/CLAUDE_PROJECT_README.md b/CLAUDE_PROJECT_README.md index cd8aa9a..d665929 100644 --- a/CLAUDE_PROJECT_README.md +++ b/CLAUDE_PROJECT_README.md @@ -5,9 +5,9 @@ ## Project Overview - **Path**: `A:\GiTea\AccountForger` -- **Files**: 1317 files -- **Size**: 240.4 MB -- **Last Modified**: 2025-11-09 20:55 +- **Files**: 1393 files +- **Size**: 257.7 MB +- **Last Modified**: 2025-11-16 22:31 ## Technology Stack @@ -65,6 +65,7 @@ config/ controllers/ │ ├── account_controller.py │ ├── main_controller.py +│ ├── profile_export_controller.py │ ├── session_controller.py │ ├── settings_controller.py │ └── platform_controllers/ @@ -360,8 +361,8 @@ utils/ │ ├── modal_test.py │ ├── password_generator.py │ ├── performance_monitor.py -│ ├── proxy_rotator.py -│ └── result_decorators.py +│ ├── process_guard.py +│ └── profile_export_service.py views/ ├── about_dialog.py ├── main_window.py @@ -376,7 +377,9 @@ views/ │ └── __init__.py ├── dialogs/ │ ├── account_creation_result_dialog.py + │ ├── export_success_dialog.py │ ├── license_activation_dialog.py + │ ├── profile_export_dialog.py │ └── __init__.py ├── tabs/ │ ├── accounts_tab.py @@ -431,3 +434,5 @@ This project is managed with Claude Project Manager. To work with this project: - README updated on 2025-10-08 22:34:43 - README updated on 2025-10-18 22:23:22 - README updated on 2025-11-09 21:00:06 +- README updated on 2025-11-16 22:31:39 +- README updated on 2025-11-16 22:32:35 diff --git a/controllers/profile_export_controller.py b/controllers/profile_export_controller.py index e03d0b9..efbf675 100644 --- a/controllers/profile_export_controller.py +++ b/controllers/profile_export_controller.py @@ -71,7 +71,7 @@ class ProfileExportController: logger.info(f"Export-Optionen: Formate={formats}, Passwort={password_protect}") # 3. Speicherort wählen - save_directory = self._select_save_location(parent_widget, account_data, formats, password_protect) + save_directory = self._select_save_location(parent_widget, account_data, formats) if not save_directory: logger.info("Kein Speicherort ausgewählt - Export abgebrochen") @@ -79,11 +79,11 @@ class ProfileExportController: logger.info(f"Speicherort: {save_directory}") - # 4. Export durchführen - files_dict, password = self.export_service.export_account( + # 4. Export durchführen (ohne Passwortschutz) + files_dict = self.export_service.export_account( account_data, formats, - password_protect + password_protect=False ) if not files_dict: @@ -117,8 +117,7 @@ class ProfileExportController: show_export_success( parent_widget, saved_files, - save_directory, - password + save_directory ) logger.info(f"Export erfolgreich: {len(saved_files)} Datei(en) gespeichert") @@ -136,12 +135,6 @@ class ProfileExportController: "Bitte installieren Sie die erforderlichen Bibliotheken:\n" "pip install reportlab svglib" ) - elif "pyzipper" in error_message.lower(): - error_message = ( - "Passwortgeschützter Export ist nicht verfügbar.\n\n" - "Bitte installieren Sie die erforderliche Bibliothek:\n" - "pip install pyzipper" - ) show_error( parent_widget, @@ -154,62 +147,30 @@ class ProfileExportController: self, parent_widget, account_data: Dict[str, Any], - formats: list, - password_protect: bool + formats: list ) -> Optional[str]: """ - Öffnet einen Datei-Dialog zur Auswahl des Speicherorts. + Öffnet einen Verzeichnis-Dialog zur Auswahl des Speicherorts. Args: parent_widget: Parent-Widget account_data: Account-Daten formats: Liste der Export-Formate - password_protect: Ob Passwortschutz aktiviert ist Returns: Ausgewähltes Verzeichnis oder None bei Abbruch """ - # Standard-Dateiname generieren - if password_protect: - # Bei Passwortschutz wird eine ZIP erstellt - default_filename = self.export_service.generate_filename( - account_data, - "zip" - ) - else: - # Ohne Passwortschutz: Ersten Format als Beispiel nehmen - first_format = formats[0] if formats else "csv" - default_filename = self.export_service.generate_filename( - account_data, - first_format - ) - # Standard-Speicherort: Benutzer-Downloads-Ordner default_directory = str(Path.home() / "Downloads") - if password_protect: - # Für ZIP: File-Dialog - file_path, _ = QFileDialog.getSaveFileName( - parent_widget, - "Profil exportieren", - os.path.join(default_directory, default_filename), - "ZIP-Archiv (*.zip)" - ) + # Verzeichnis-Dialog für mehrere Dateien + directory = QFileDialog.getExistingDirectory( + parent_widget, + "Speicherort für Export auswählen", + default_directory + ) - if not file_path: - return None - - # Verzeichnis aus Dateipfad extrahieren - return os.path.dirname(file_path) - else: - # Für mehrere Dateien: Verzeichnis-Dialog - directory = QFileDialog.getExistingDirectory( - parent_widget, - "Speicherort für Export auswählen", - default_directory - ) - - return directory if directory else None + return directory if directory else None def export_multiple_accounts(self, parent_widget, account_ids: list) -> bool: """ diff --git a/utils/profile_export_service.py b/utils/profile_export_service.py index bfbbb38..7cba9cc 100644 --- a/utils/profile_export_service.py +++ b/utils/profile_export_service.py @@ -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 diff --git a/views/dialogs/export_success_dialog.py b/views/dialogs/export_success_dialog.py index 221f180..d41d990 100644 --- a/views/dialogs/export_success_dialog.py +++ b/views/dialogs/export_success_dialog.py @@ -1,7 +1,7 @@ """ Erfolgs-Dialog für Profil-Export -Zeigt exportierte Dateien und optional das generierte Passwort an. +Zeigt exportierte Dateien an. """ import os @@ -9,11 +9,11 @@ import logging import subprocess import platform from pathlib import Path -from typing import List, Optional +from typing import List from PyQt5.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, - QFrame, QGraphicsDropShadowEffect, QApplication, QScrollArea, QWidget + QFrame, QGraphicsDropShadowEffect, QScrollArea, QWidget ) from PyQt5.QtCore import Qt from PyQt5.QtGui import QFont, QColor @@ -28,8 +28,7 @@ class ExportSuccessDialog(QDialog): self, parent=None, exported_files: List[str] = None, - export_directory: str = "", - password: Optional[str] = None + export_directory: str = "" ): """ Initialisiert den Erfolgs-Dialog. @@ -38,12 +37,10 @@ class ExportSuccessDialog(QDialog): parent: Parent-Widget exported_files: Liste der exportierten Dateinamen export_directory: Verzeichnis wo Dateien gespeichert wurden - password: Optional generiertes Passwort (nur bei Passwortschutz) """ super().__init__(parent) self.exported_files = exported_files or [] self.export_directory = export_directory - self.password = password self.init_ui() def init_ui(self): @@ -55,10 +52,8 @@ class ExportSuccessDialog(QDialog): self.setAttribute(Qt.WA_TranslucentBackground, True) self.setModal(True) - # Dynamische Höhe basierend auf Inhalt + # Dynamische Höhe basierend auf Anzahl der Dateien base_height = 280 - if self.password: - base_height += 80 # Extra Platz für Passwort-Anzeige if len(self.exported_files) > 2: base_height += 20 * (len(self.exported_files) - 2) # Extra für mehr Dateien @@ -153,72 +148,6 @@ class ExportSuccessDialog(QDialog): container_layout.addWidget(scroll_area) - # Passwort-Anzeige (nur wenn Passwort vorhanden) - if self.password: - password_frame = QFrame() - password_frame.setStyleSheet(""" - QFrame { - background-color: #FEF3C7; - border: 1px solid #FCD34D; - border-radius: 6px; - padding: 12px; - } - """) - password_layout = QVBoxLayout(password_frame) - password_layout.setSpacing(8) - - # Warnung - warning_label = QLabel("⚠ Bitte Passwort speichern!") - warning_label.setStyleSheet(""" - color: #92400E; - font-size: 13px; - font-weight: 600; - """) - - # Passwort mit Copy-Button - password_row = QHBoxLayout() - password_row.setSpacing(10) - - password_label = QLabel(f"🔒 Passwort: {self.password}") - password_label.setStyleSheet(""" - color: #78350F; - font-size: 13px; - font-family: 'Courier New', monospace; - font-weight: 600; - """) - password_label.setTextInteractionFlags(Qt.TextSelectableByMouse) - - copy_button = QPushButton("Kopieren") - copy_button.setMaximumWidth(80) - copy_button.setMinimumHeight(28) - copy_button.setCursor(Qt.PointingHandCursor) - copy_button.setStyleSheet(""" - QPushButton { - background-color: #F59E0B; - color: white; - border: none; - border-radius: 4px; - font-size: 12px; - font-weight: 600; - padding: 4px 8px; - } - QPushButton:hover { - background-color: #D97706; - } - QPushButton:pressed { - background-color: #B45309; - } - """) - copy_button.clicked.connect(self.copy_password) - - password_row.addWidget(password_label, 1) - password_row.addWidget(copy_button) - - password_layout.addWidget(warning_label) - password_layout.addLayout(password_row) - - container_layout.addWidget(password_frame) - # Speicherort location_label = QLabel(f"Gespeichert in:") location_label.setStyleSheet("color: #6B7280; font-size: 12px;") @@ -299,30 +228,6 @@ class ExportSuccessDialog(QDialog): # Container zum Hauptlayout hinzufügen main_layout.addWidget(self.container) - def copy_password(self): - """Kopiert das Passwort in die Zwischenablage""" - if self.password: - clipboard = QApplication.clipboard() - clipboard.setText(self.password) - logger.info("Passwort in Zwischenablage kopiert") - - # Kurzes visuelles Feedback - sender = self.sender() - if sender: - original_text = sender.text() - sender.setText("✓ Kopiert!") - sender.setStyleSheet(sender.styleSheet().replace("#F59E0B", "#10B981")) - - # Nach 2 Sekunden zurücksetzen - from PyQt5.QtCore import QTimer - QTimer.singleShot(2000, lambda: self._reset_copy_button(sender, original_text)) - - def _reset_copy_button(self, button, original_text): - """Setzt den Copy-Button zurück""" - if button: - button.setText(original_text) - button.setStyleSheet(button.styleSheet().replace("#10B981", "#F59E0B")) - def open_folder(self): """Öffnet den Export-Ordner im Datei-Explorer""" if not self.export_directory or not os.path.exists(self.export_directory): @@ -373,8 +278,7 @@ class ExportSuccessDialog(QDialog): def show_export_success( parent, exported_files: List[str], - export_directory: str, - password: Optional[str] = None + export_directory: str ): """ Zeigt den Export-Erfolgs-Dialog modal an. @@ -383,7 +287,6 @@ def show_export_success( parent: Parent-Widget exported_files: Liste der exportierten Dateinamen export_directory: Verzeichnis wo Dateien gespeichert wurden - password: Optional generiertes Passwort """ - dialog = ExportSuccessDialog(parent, exported_files, export_directory, password) + dialog = ExportSuccessDialog(parent, exported_files, export_directory) dialog.exec_() diff --git a/views/dialogs/profile_export_dialog.py b/views/dialogs/profile_export_dialog.py index a85b8cf..f989cfe 100644 --- a/views/dialogs/profile_export_dialog.py +++ b/views/dialogs/profile_export_dialog.py @@ -1,7 +1,7 @@ """ Export-Dialog für Account-Profile -Ermöglicht Auswahl von Export-Formaten und Passwortschutz. +Ermöglicht Auswahl von Export-Formaten. """ import logging @@ -153,36 +153,6 @@ class ProfileExportDialog(QDialog): container_layout.addWidget(format_group) - # Passwortschutz-Checkbox - self.password_checkbox = QCheckBox("Mit Passwort schützen") - self.password_checkbox.setChecked(False) - self.password_checkbox.setStyleSheet(""" - QCheckBox { - font-size: 13px; - font-family: 'Poppins', sans-serif; - font-weight: 500; - color: #374151; - spacing: 8px; - } - QCheckBox::indicator { - width: 18px; - height: 18px; - border-radius: 4px; - border: 2px solid #D1D5DB; - } - QCheckBox::indicator:checked { - background-color: #10B981; - border-color: #10B981; - } - """) - - # Info-Text für Passwortschutz - password_info = QLabel("(Wird automatisch generiert)") - password_info.setStyleSheet("color: #9CA3AF; font-size: 11px; font-family: 'Poppins', sans-serif; margin-left: 26px;") - - container_layout.addWidget(self.password_checkbox) - container_layout.addWidget(password_info) - # Spacer container_layout.addStretch() @@ -271,8 +241,8 @@ class ProfileExportDialog(QDialog): ) return - # Passwortschutz-Option - password_protect = self.password_checkbox.isChecked() + # Passwortschutz ist entfernt - immer False + password_protect = False # Signal emittieren self.export_confirmed.emit(selected_formats, password_protect) @@ -295,7 +265,8 @@ class ProfileExportDialog(QDialog): if self.pdf_checkbox.isChecked(): formats.append("pdf") - password_protect = self.password_checkbox.isChecked() + # Passwortschutz ist entfernt - immer False + password_protect = False return formats, password_protect