187 lines
7.7 KiB
Python
187 lines
7.7 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
|
|
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)
|