update: cli_failed_verification flow
This commit is contained in:
parent
cf817a62d0
commit
2e735c7303
2 changed files with 292 additions and 9 deletions
299
gui/__main__.py
299
gui/__main__.py
|
|
@ -38,6 +38,10 @@ from core.observers.TicketObserver import TicketObserver
|
|||
from core.controllers.tickets.TicketSyncController import sync_ticket_prices
|
||||
from core.controllers.tickets.TicketPayController import initiate_payment, check_if_paid
|
||||
from core.controllers.tickets.TicketPrepController import prepare_tickets
|
||||
from core.controllers.tickets.FailedVerificationController import (
|
||||
evaluate_if_its_the_key,
|
||||
prepare_tickets_with_saved_blind_sigs,
|
||||
)
|
||||
from core.controllers.tickets.UseTicketController import (
|
||||
use_ticket,
|
||||
modify_random_tickets_setting,
|
||||
|
|
@ -733,6 +737,59 @@ class CustomWindow(QMainWindow):
|
|||
with open(self.gui_config_file, 'w') as f:
|
||||
json.dump(config, f, indent=4)
|
||||
|
||||
def _default_gui_config(self):
|
||||
return {"logging": {"gui_logging_enabled": False, "log_level": "INFO"}}
|
||||
|
||||
def save_ticket_verification_failure(self, result):
|
||||
try:
|
||||
failed_validations = result.get('failed_validations', []) if isinstance(result, dict) else []
|
||||
if not failed_validations:
|
||||
return False
|
||||
|
||||
config = self._load_gui_config()
|
||||
if config is None:
|
||||
config = self._default_gui_config()
|
||||
if "tickets" not in config:
|
||||
config["tickets"] = {}
|
||||
|
||||
config["tickets"]["failed_verification"] = {
|
||||
"message": result.get('message', 'verification_failed'),
|
||||
"how_many_failed": result.get('how_many_failed', len(failed_validations)),
|
||||
"failed_validations": list(failed_validations),
|
||||
"updated_at": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
self._save_gui_config(config)
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(f"Error saving ticket verification failure: {e}")
|
||||
return False
|
||||
|
||||
def get_ticket_verification_failure(self):
|
||||
try:
|
||||
config = self._load_gui_config()
|
||||
if not config:
|
||||
return None
|
||||
failure = config.get("tickets", {}).get("failed_verification")
|
||||
if not isinstance(failure, dict):
|
||||
return None
|
||||
failed_validations = failure.get("failed_validations")
|
||||
if not failed_validations:
|
||||
return None
|
||||
return failure
|
||||
except Exception as e:
|
||||
logging.error(f"Error loading ticket verification failure: {e}")
|
||||
return None
|
||||
|
||||
def clear_ticket_verification_failure(self):
|
||||
try:
|
||||
config = self._load_gui_config()
|
||||
if not config or "tickets" not in config:
|
||||
return
|
||||
config["tickets"].pop("failed_verification", None)
|
||||
self._save_gui_config(config)
|
||||
except Exception as e:
|
||||
logging.error(f"Error clearing ticket verification failure: {e}")
|
||||
|
||||
def check_logging(self):
|
||||
config = self._load_gui_config()
|
||||
if config is None:
|
||||
|
|
@ -1084,18 +1141,25 @@ class CustomWindow(QMainWindow):
|
|||
def clear_data(self):
|
||||
self._data = {"Profile_1": {}}
|
||||
|
||||
def _set_status_font_size(self, font_size):
|
||||
self.status_label.setStyleSheet(
|
||||
f"color: rgb(0, 255, 255); font-size: {font_size}px;")
|
||||
|
||||
|
||||
|
||||
def update_status(self, text, clear=False):
|
||||
if text is None:
|
||||
self._set_status_font_size(16)
|
||||
self.status_label.setText('Status:')
|
||||
self.disable_marquee()
|
||||
return
|
||||
|
||||
if clear:
|
||||
self._set_status_font_size(16)
|
||||
self.status_label.setText('')
|
||||
return
|
||||
|
||||
full_text = f'Status: {text}'
|
||||
self.status_label.setText(full_text)
|
||||
self.status_label.setText('Status: ' + text)
|
||||
self.disable_marquee()
|
||||
|
||||
def check_first_launch(self):
|
||||
|
|
@ -7890,6 +7954,7 @@ class Settings(Page):
|
|||
self.content_layout.setCurrentWidget(self.tickets_page)
|
||||
self._select_menu_button("Tickets")
|
||||
self._refresh_tickets_inventory()
|
||||
self._refresh_ticket_recovery_controls()
|
||||
|
||||
def show_registrations_page(self):
|
||||
self.content_layout.setCurrentWidget(self.registrations_page)
|
||||
|
|
@ -7928,14 +7993,33 @@ class Settings(Page):
|
|||
|
||||
def create_tickets_page(self):
|
||||
page = QWidget()
|
||||
layout = QVBoxLayout(page)
|
||||
layout.setSpacing(15)
|
||||
layout.setContentsMargins(20, 20, 20, 20)
|
||||
page_layout = QVBoxLayout(page)
|
||||
page_layout.setSpacing(15)
|
||||
page_layout.setContentsMargins(20, 20, 20, 20)
|
||||
|
||||
title = QLabel("TICKETS")
|
||||
title.setStyleSheet(
|
||||
f"color: #808080; font-size: 12px; font-weight: bold; {self.font_style}")
|
||||
layout.addWidget(title)
|
||||
page_layout.addWidget(title)
|
||||
|
||||
scroll_area = QScrollArea()
|
||||
scroll_area.setWidgetResizable(True)
|
||||
scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||
scroll_area.setStyleSheet("""
|
||||
QScrollArea {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
QScrollArea > QWidget > QWidget {
|
||||
background-color: transparent;
|
||||
}
|
||||
""")
|
||||
|
||||
scroll_content = QWidget()
|
||||
layout = QVBoxLayout(scroll_content)
|
||||
layout.setSpacing(15)
|
||||
layout.setContentsMargins(0, 0, 8, 0)
|
||||
|
||||
random_group = QGroupBox("Random Ticket Use")
|
||||
random_group.setStyleSheet(
|
||||
|
|
@ -7978,9 +8062,181 @@ class Settings(Page):
|
|||
inventory_layout.addWidget(self.refresh_tickets_button)
|
||||
layout.addWidget(inventory_group)
|
||||
|
||||
saved_failure = self.update_status.get_ticket_verification_failure()
|
||||
has_failure = saved_failure is not None
|
||||
|
||||
recovery_group = QGroupBox("Verification Failure Recovery")
|
||||
recovery_group.setStyleSheet(
|
||||
f"QGroupBox {{ color: white; padding: 15px; {self.font_style} }}")
|
||||
recovery_layout = QVBoxLayout(recovery_group)
|
||||
|
||||
self.ticket_recovery_status_label = QLabel(self._format_ticket_failure_status(saved_failure))
|
||||
self.ticket_recovery_status_label.setStyleSheet(
|
||||
f"color: white; font-size: 12px; {self.font_style}")
|
||||
self.ticket_recovery_status_label.setWordWrap(True)
|
||||
recovery_layout.addWidget(self.ticket_recovery_status_label)
|
||||
|
||||
self.evaluate_public_key_button = QPushButton("Evaluate Public Key")
|
||||
self.evaluate_public_key_button.setFixedSize(180, 36)
|
||||
self.evaluate_public_key_button.setEnabled(has_failure)
|
||||
self.evaluate_public_key_button.setStyleSheet(f"""
|
||||
QPushButton {{
|
||||
background: #00aaff; color: white; border: none;
|
||||
border-radius: 5px; font-weight: bold; {self.font_style}
|
||||
}}
|
||||
QPushButton:hover:!disabled {{ background: #0088cc; }}
|
||||
QPushButton:disabled {{ background: #666666; color: #bbbbbb; }}
|
||||
""")
|
||||
self.evaluate_public_key_button.clicked.connect(self.evaluate_ticket_public_key)
|
||||
recovery_layout.addWidget(self.evaluate_public_key_button)
|
||||
|
||||
self.ticket_recovery_output = TerminalWidget()
|
||||
self.ticket_recovery_output.setFixedHeight(130)
|
||||
if not has_failure:
|
||||
self.ticket_recovery_output.setPlainText("No saved verification failure.")
|
||||
recovery_layout.addWidget(self.ticket_recovery_output)
|
||||
|
||||
layout.addWidget(recovery_group)
|
||||
|
||||
debug_group = QGroupBox("Debug Recovery")
|
||||
debug_group.setStyleSheet(
|
||||
f"QGroupBox {{ color: white; padding: 15px; {self.font_style} }}")
|
||||
debug_layout = QVBoxLayout(debug_group)
|
||||
|
||||
self.prepare_saved_blind_sigs_button = QPushButton(
|
||||
"Prepare Tickets even if validation of the server's signature failed")
|
||||
self.prepare_saved_blind_sigs_button.setFixedSize(500, 40)
|
||||
self.prepare_saved_blind_sigs_button.setEnabled(has_failure)
|
||||
self.prepare_saved_blind_sigs_button.setStyleSheet(f"""
|
||||
QPushButton {{
|
||||
background: #c0392b; color: white; border: none;
|
||||
border-radius: 5px; font-size: 10px; font-weight: bold; {self.font_style}
|
||||
}}
|
||||
QPushButton:hover:!disabled {{ background: #a93226; }}
|
||||
QPushButton:disabled {{ background: #666666; color: #bbbbbb; }}
|
||||
""")
|
||||
self.prepare_saved_blind_sigs_button.clicked.connect(self.prepare_tickets_with_saved_blind_signatures)
|
||||
debug_layout.addWidget(self.prepare_saved_blind_sigs_button)
|
||||
|
||||
layout.addWidget(debug_group)
|
||||
layout.addStretch()
|
||||
scroll_area.setWidget(scroll_content)
|
||||
page_layout.addWidget(scroll_area)
|
||||
return page
|
||||
|
||||
def _format_ticket_failure_status(self, failure):
|
||||
if not failure:
|
||||
return "No saved verification failure. If ticket preparation fails validation, recovery data will appear here."
|
||||
failed_validations = failure.get("failed_validations", [])
|
||||
how_many_failed = failure.get("how_many_failed", len(failed_validations))
|
||||
updated_at = failure.get("updated_at", "unknown time")
|
||||
failed_text = ", ".join(str(item) for item in failed_validations)
|
||||
return (
|
||||
f"Saved verification failure: {how_many_failed} failed. "
|
||||
f"Failed validation indices: {failed_text}. Saved: {updated_at}"
|
||||
)
|
||||
|
||||
def _refresh_ticket_recovery_controls(self):
|
||||
failure = self.update_status.get_ticket_verification_failure()
|
||||
has_failure = failure is not None
|
||||
if hasattr(self, 'ticket_recovery_status_label'):
|
||||
self.ticket_recovery_status_label.setText(self._format_ticket_failure_status(failure))
|
||||
if hasattr(self, 'evaluate_public_key_button'):
|
||||
self.evaluate_public_key_button.setEnabled(has_failure)
|
||||
if hasattr(self, 'prepare_saved_blind_sigs_button'):
|
||||
self.prepare_saved_blind_sigs_button.setEnabled(has_failure)
|
||||
|
||||
def _set_ticket_recovery_busy(self, busy):
|
||||
if hasattr(self, 'evaluate_public_key_button'):
|
||||
self.evaluate_public_key_button.setEnabled(not busy and self.update_status.get_ticket_verification_failure() is not None)
|
||||
if hasattr(self, 'prepare_saved_blind_sigs_button'):
|
||||
self.prepare_saved_blind_sigs_button.setEnabled(not busy and self.update_status.get_ticket_verification_failure() is not None)
|
||||
|
||||
def _write_ticket_recovery_output(self, text):
|
||||
if hasattr(self, 'ticket_recovery_output'):
|
||||
self.ticket_recovery_output.setPlainText(text)
|
||||
|
||||
def _format_ticket_recovery_result(self, label, result):
|
||||
try:
|
||||
payload = json.dumps(result, indent=2, default=str)
|
||||
except TypeError:
|
||||
payload = str(result)
|
||||
return f"{label}:\n{payload}"
|
||||
|
||||
def _get_ticket_failure_for_action(self):
|
||||
failure = self.update_status.get_ticket_verification_failure()
|
||||
if failure is None:
|
||||
self._write_ticket_recovery_output("No saved verification failure.")
|
||||
self.update_status.update_status("No ticket verification failure is saved.")
|
||||
self._refresh_ticket_recovery_controls()
|
||||
return None
|
||||
return failure
|
||||
|
||||
def evaluate_ticket_public_key(self):
|
||||
failure = self._get_ticket_failure_for_action()
|
||||
if failure is None:
|
||||
return
|
||||
failed_validations = list(failure.get("failed_validations", []))
|
||||
self._write_ticket_recovery_output("Evaluating public key...")
|
||||
self.update_status.update_status("Evaluating ticket public key...")
|
||||
self._set_ticket_recovery_busy(True)
|
||||
|
||||
self.ticket_recovery_worker = TicketingWorkerThread(
|
||||
'EVALUATE_FAILED_VERIFICATION',
|
||||
params={'failed_validations': failed_validations},
|
||||
)
|
||||
self.ticket_recovery_worker.failed_verification_evaluated.connect(self.on_failed_verification_evaluated)
|
||||
self.ticket_recovery_worker.error.connect(self.on_ticket_recovery_error)
|
||||
self.ticket_recovery_worker.start()
|
||||
|
||||
def on_failed_verification_evaluated(self, result):
|
||||
self._write_ticket_recovery_output(self._format_ticket_recovery_result("evaluation_results", result))
|
||||
self.update_status.update_status("Ticket public key evaluation complete.")
|
||||
self._set_ticket_recovery_busy(False)
|
||||
self._refresh_ticket_recovery_controls()
|
||||
|
||||
def prepare_tickets_with_saved_blind_signatures(self):
|
||||
failure = self._get_ticket_failure_for_action()
|
||||
if failure is None:
|
||||
return
|
||||
|
||||
reply = QMessageBox.warning(
|
||||
self,
|
||||
"Prepare Tickets Anyway",
|
||||
"This will prepare tickets even though server signature validation failed. Continue only if you have evaluated the situation.",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
QMessageBox.StandardButton.No,
|
||||
)
|
||||
if reply != QMessageBox.StandardButton.Yes:
|
||||
return
|
||||
|
||||
self._write_ticket_recovery_output("Preparing tickets with saved blind signatures...")
|
||||
self.update_status.update_status("Preparing tickets with saved blind signatures...")
|
||||
self._set_ticket_recovery_busy(True)
|
||||
|
||||
self.ticket_recovery_worker = TicketingWorkerThread('PREPARE_SAVED_BLIND_SIGS')
|
||||
self.ticket_recovery_worker.saved_blind_prep_done.connect(self.on_saved_blind_prep_done)
|
||||
self.ticket_recovery_worker.error.connect(self.on_ticket_recovery_error)
|
||||
self.ticket_recovery_worker.start()
|
||||
|
||||
def on_saved_blind_prep_done(self, result):
|
||||
self._write_ticket_recovery_output(self._format_ticket_recovery_result("Results of preparation", result))
|
||||
if isinstance(result, dict) and result.get('valid') is True:
|
||||
self.update_status.clear_ticket_verification_failure()
|
||||
self.update_status.update_status("Tickets prepared from saved blind signatures.")
|
||||
self._refresh_tickets_inventory()
|
||||
else:
|
||||
msg = result.get('message', 'failed') if isinstance(result, dict) else 'failed'
|
||||
self.update_status.update_status(f"Saved blind signature prep failed: {msg}")
|
||||
self._set_ticket_recovery_busy(False)
|
||||
self._refresh_ticket_recovery_controls()
|
||||
|
||||
def on_ticket_recovery_error(self, msg):
|
||||
self._write_ticket_recovery_output(f"EXCEPTION: {msg}")
|
||||
self.update_status.update_status(f"Ticket recovery error: {msg}")
|
||||
self._set_ticket_recovery_busy(False)
|
||||
self._refresh_ticket_recovery_controls()
|
||||
|
||||
def _on_random_toggle(self, checked):
|
||||
try:
|
||||
result = modify_random_tickets_setting('on' if checked else 'off', ticket_observer)
|
||||
|
|
@ -10488,6 +10744,8 @@ class TicketingWorkerThread(QThread):
|
|||
paid_check_failed = pyqtSignal(str)
|
||||
prep_done = pyqtSignal(object)
|
||||
use_done = pyqtSignal(object)
|
||||
failed_verification_evaluated = pyqtSignal(object)
|
||||
saved_blind_prep_done = pyqtSignal(object)
|
||||
error = pyqtSignal(str)
|
||||
|
||||
def __init__(self, action, params=None):
|
||||
|
|
@ -10524,6 +10782,16 @@ class TicketingWorkerThread(QThread):
|
|||
elif self.action == 'PREPARE_TICKETS':
|
||||
result = prepare_tickets(self.params['how_many_profiles'], ticket_observer, connection_observer)
|
||||
self.prep_done.emit(result)
|
||||
elif self.action == 'EVALUATE_FAILED_VERIFICATION':
|
||||
result = evaluate_if_its_the_key(
|
||||
self.params['failed_validations'],
|
||||
ticket_observer,
|
||||
connection_observer,
|
||||
)
|
||||
self.failed_verification_evaluated.emit(result)
|
||||
elif self.action == 'PREPARE_SAVED_BLIND_SIGS':
|
||||
result = prepare_tickets_with_saved_blind_sigs(ticket_observer, connection_observer)
|
||||
self.saved_blind_prep_done.emit(result)
|
||||
elif self.action == 'USE_TICKET':
|
||||
result = use_ticket(
|
||||
self.params['which_ticket'],
|
||||
|
|
@ -10758,6 +11026,7 @@ class TicketPrepPage(Page):
|
|||
self.update_status = main_window
|
||||
self.worker = None
|
||||
self._terminal_bound = False
|
||||
self._tickets_ready = False
|
||||
|
||||
self.title.setText("Preparing Tickets")
|
||||
self.title.setGeometry(QtCore.QRect(280, 20, 240, 40))
|
||||
|
|
@ -10780,7 +11049,7 @@ class TicketPrepPage(Page):
|
|||
}
|
||||
QPushButton:disabled { background-color: #555555; }
|
||||
""")
|
||||
self.continue_button.setEnabled(False)
|
||||
self.continue_button.setEnabled(True)
|
||||
self.continue_button.clicked.connect(self.on_continue)
|
||||
|
||||
def _bind_terminal_once(self):
|
||||
|
|
@ -10796,7 +11065,8 @@ class TicketPrepPage(Page):
|
|||
def start_prep(self):
|
||||
self._bind_terminal_once()
|
||||
self.terminal.append("=== Starting ticket preparation ===")
|
||||
self.continue_button.setEnabled(False)
|
||||
self._tickets_ready = False
|
||||
self.continue_button.setEnabled(True)
|
||||
self.status_label.setText("Preparing tickets...")
|
||||
self.update_status.update_status("Preparing tickets...")
|
||||
|
||||
|
|
@ -10813,6 +11083,8 @@ class TicketPrepPage(Page):
|
|||
self.status_label.setText("Preparation failed.")
|
||||
return
|
||||
if result.get('valid') is True:
|
||||
self._tickets_ready = True
|
||||
self.update_status.clear_ticket_verification_failure()
|
||||
self.terminal.append("=== Ticket preparation complete ===")
|
||||
self.status_label.setText("Tickets ready. Click Continue to apply one to your profile.")
|
||||
self.update_status.update_status("Tickets ready.")
|
||||
|
|
@ -10825,18 +11097,29 @@ class TicketPrepPage(Page):
|
|||
failed_validations = result.get('failed_validations', [])
|
||||
self.terminal.append(f" failed count: {how_many_failed}")
|
||||
self.terminal.append(f" failed indices: {failed_validations}")
|
||||
if self.update_status.save_ticket_verification_failure(result):
|
||||
self.terminal.append("Recovery data saved. Open Settings > Tickets to evaluate or resume.")
|
||||
self.status_label.setText("Verification failed. Open Settings > Tickets for recovery.")
|
||||
self.update_status.update_status("Ticket verification failed")
|
||||
self.continue_button.setEnabled(True)
|
||||
return
|
||||
self.status_label.setText(f"Preparation failed: {msg}")
|
||||
self.update_status.update_status(f"An error occurred")
|
||||
self.continue_button.setEnabled(True)
|
||||
|
||||
def on_error(self, msg):
|
||||
self.terminal.append(f"EXCEPTION: {msg}")
|
||||
self.status_label.setText(f"An unkown error occured")
|
||||
self.continue_button.setEnabled(True)
|
||||
|
||||
def on_continue(self):
|
||||
menu_page = self.page_stack.findChild(MenuPage)
|
||||
profile_id = getattr(self.update_status, 'current_profile_id', None)
|
||||
if menu_page:
|
||||
self.page_stack.setCurrentWidget(menu_page)
|
||||
if not self._tickets_ready:
|
||||
self.update_status.update_status("Ticket preparation was not completed.")
|
||||
return
|
||||
if profile_id is None or menu_page is None:
|
||||
self.update_status.update_status("Tickets ready. Random ticket use is ON.")
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "sp-hydra-veil-gui"
|
||||
version = "2.2.7"
|
||||
version = "2.2.8"
|
||||
authors = [
|
||||
{ name = "Simplified Privacy" },
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in a new issue