sp-hydra-veil-core/core/controllers/tickets/TicketPrepController.py

190 lines
7.8 KiB
Python

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.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.models.session.SessionProfile import SessionProfile
from core.models.system.SystemProfile import SystemProfile
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):
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:
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:
ticket_observer.notify("failed_input", None)
return {"valid": False, "error_code": "failed_input"}
notification = "Preparing Cryptography Locally"
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!"
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!"
ticket_observer.notify("preparing", subject=notification)
return prep_results
notification = f"Error with Ticket Preparation or Verification!"
ticket_observer.notify("preparing", subject=notification)
return prep_results
def respawn_billing_code_into_ticket(
profile: Union[SessionProfile, SystemProfile],
which_ticket: int,
ticket_observer: TicketObserver,
connection_observer: ConnectionObserver
) -> bool:
"""
Converts a valid VPN billing code, into a valid unused ticket.
At the cost of the server expiring the billing code immediately.
This has the effect of "wiping" the billing code, and starting over.
The new ticket can be applied to any other profile now.
"""
# extract billing code:
billing_code = profile.subscription.billing_code
if not billing_code:
return Result(valid=False, error_type=ResultError.MISSING_DATA, message="You lack a valid subscription on the profile slot is being respawned")
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):
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message="Ticket slot must be a number.")
ticket_result = prepare_tickets(
how_many_profiles=1,
ticket_observer=ticket_observer,
connection_observer=connection_observer,
which_ticket=which_ticket,
billing_code=billing_code
)
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!"
ticket_observer.notify("preparing", subject=notification)
return prep_error_result(ticket_result)
logger.info("Got a valid result from the respawn's prepare_tickets. Now wiping the profile's subscription.")
# Now that the billing code has been wiped by the server, wipe it locally:
profile.subscription = None
# save the ticket,
profile.ticket = which_ticket
profile.save()
notification = f"Respawn Done for Profile {profile.id}!"
ticket_observer.notify("preparing", subject=notification)
return Result(valid=True, message=f"It worked. Respawn Done for Profile {profile.id}! It's completely wiped and you now have a valid ticket at slot {which_ticket}")
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)