from __future__ import annotations from typing import TYPE_CHECKING if TYPE_CHECKING: from core.essentials.observers.ConnectionObserver import ConnectionObserver from core.observers.TicketObserver import TicketObserver from core.services.prepare_tickets.ticket_prep_orchestrator import ticket_prep_orchestrator from core.services.prepare_tickets.make_sure_pub_key_exists import make_sure_pub_key_exists from core.services.prepare_tickets import ticket_tracker from core.services.helpers.get_how_many_profiles_were_ordered import ( get_how_many_profiles_were_ordered, ) from core.observers.BaseObserver import BaseObserver from core.models.Result import Result, ResultError from core.errors.logger import logger # generic from typing import Optional """ Goal: this is the controller for the view to speak with the high level "ticket_prep_orchestrator" service function, which makes commitments, sends to the server, and then unblinds them, and preps the ticket JSONs. Requires: a temp billing ID having paid already Doesn't Require: If it doesn't have the public key, then it checks the config file. If it doesn't have a config file, then it uses the temp billing id to get the public key from the server. """ # Bulk Prep: def prepare_tickets( how_many_profiles: int, ticket_observer: TicketObserver, connection_observer: ConnectionObserver, which_ticket: Optional[int] = None, billing_code: Optional[int] = None ) -> dict: # these two are for preparation of a single ticket to a profile. # can't have one blank without the other. (profile or ticket) if bool(which_ticket) != bool(billing_code): return {"valid": False, "error_code": "failed_input"} # make sure it's a number: if not isinstance(how_many_profiles, int): if ticket_observer is not None: ticket_observer.notify("failed_input", None) return {"valid": False, "error_code": "failed_input"} # allow single prep: if how_many_profiles != 1: # but if it's not a single ticket, then make sure that "how_many_profiles" is actually the number of profiles ordered # (which is based on locally saved data from the previous step): how_many_ordered = get_how_many_profiles_were_ordered() if how_many_profiles != how_many_ordered: if ticket_observer is not None: ticket_observer.notify("failed_input", None) return {"valid": False, "error_code": "failed_input"} # make sure this guy has a public key to verify against: does_he_have_public_key = make_sure_pub_key_exists(connection_observer) if does_he_have_public_key == False: if ticket_observer is not None: ticket_observer.notify("failed_input", None) return {"valid": False, "error_code": "failed_input"} notification = "Preparing Cryptography Locally" if ticket_observer is not None: ticket_observer.notify("preparing", subject=notification) # ok now we have the pre-reqs, let's use this high level orchestrator, prep_results = ticket_prep_orchestrator( how_many_profiles=how_many_profiles, ticket_observer=ticket_observer, connection_observer=connection_observer, which_ticket=which_ticket, # only relevant for single ticket prep billing_code=billing_code # only relevant for single ticket prep ) # rest of this function is evaluating the results: if prep_results == False or prep_results == None: return {"valid": False, "message": "error"} if "valid" not in prep_results: return {"valid": False, "message": "error"} if prep_results["valid"] == True: notification = f"Done! All Tickets Ready!" if ticket_observer is not None: ticket_observer.notify("preparing", subject=notification) return prep_results if "how_many_failed" in prep_results: how_many_failed = prep_results.get("how_many_failed", 0) failed_validations = prep_results.get("failed_validations", None) if failed_validations: notification = f"Error with Ticket Preparation or Verification!" if ticket_observer is not None: ticket_observer.notify("preparing", subject=notification) return prep_results notification = f"Error with Ticket Preparation or Verification!" if ticket_observer is not None: ticket_observer.notify("preparing", subject=notification) return prep_results # No profile required def respawn_billing_code_into_ticket( billing_code: str, which_ticket: int, ticket_observer: TicketObserver, connection_observer: ConnectionObserver ) -> Result: """ Purpose: Invalidates a billing code, & produces a valid unused ticket Requires: Valid Billing Code. Any Ticket slot folder, (but not an actual ticket there). No profile required. Method: Converts a valid VPN billing code, into a valid unused ticket. At the cost of the server expiring the billing code immediately. And the ticket slot is used (wipes existing used ticket) This has the effect of "wiping" the billing code, and starting over. The new ticket can be applied to any other profile now. Called by: ticket_respawn's respawn_profile """ ################################################# # PREP PRE-REQS ################################################# if not which_ticket: return Result(valid=False, error_type=ResultError.MISSING_DATA, message="Missing which ticket slot is being respawned") if not isinstance(which_ticket, int): try: which_ticket = int(which_ticket) except: return Result(valid=False, error_type=ResultError.INVALID_INPUT, message="Ticket slot must be a number.") ################################################# # SEND TO SERVER ################################################# ticket_result = prepare_tickets( how_many_profiles=1, ticket_observer=ticket_observer, connection_observer=connection_observer, which_ticket=which_ticket, billing_code=billing_code ) ################################################# # EVALUATE RESULTS ################################################# if not isinstance(ticket_result, dict): return Result(valid=False, error_type=ResultError.UNKNOWN, message=f"Error! The prepare_tickets function returned an invalid format of {type(ticket_result)} when it should be a dict. This is being recieved in the respawn_ticket function.") valid = ticket_result.get('valid', False) if not valid: notification = f"Error in Respawn Prep!" if ticket_observer is not None: ticket_observer.notify("preparing", subject=notification) return prep_error_result(ticket_result) ################################################# # WIPE LOCAL TICKET DATA ################################################# wiped_sub = ticket_tracker.wipe_one_ticket_sub(which_ticket) logger.info(f"Did local ticket tracker wipe? {wiped_sub}") ################################################# # NOTIFY & RETURN ################################################# notification = f"Respawn Done for Ticket Slot {which_ticket}!" if ticket_observer is not None: ticket_observer.notify("preparing", subject=notification) update_msg = f"It worked. Respawn Done! The billing code is no longer valid and you now have a valid ticket at slot {which_ticket}" logger.info(update_msg) return Result(valid=True, message=update_msg) def prep_error_result(ticket_result: dict): message = ticket_result.get('message', False) error_code = ticket_result.get('error_code', False) if message == "missing_data": return Result(valid=False, error_type=ResultError.MISSING_DATA, message="Missing Input Data. This is an error with the introduction functions.") elif message == "cant_save": return Result(valid=False, error_type=ResultError.FILE_SYSTEM, message="Can't save the data. Please check your file permissions and free space.") elif message == "verification_failed": how_many_failed = ticket_result.get('how_many_failed', False) return Result(valid=False, error_type=ResultError.ENCRYPTION, message=f"Verification of the encryption failed for {how_many_failed} profiles.") elif error_code == "failed": return Result(valid=False, error_type=ResultError.FILE_SYSTEM, message="There was an error with setting up ticket tracker or some local filesystem problem.") else: if message: error_msg = message elif error_code: error_msg = f"An error occured: {error_code}." else: error_msg = "An unknown error occured." return Result(valid=False, error_type=ResultError.UNKNOWN, message=error_msg)