diff --git a/core/Constants.py b/core/Constants.py index 9ba2eb1..105d539 100644 --- a/core/Constants.py +++ b/core/Constants.py @@ -18,11 +18,14 @@ class Constants: # ticketing group: TICKET_API_BASE_URL: Final[str] = os.environ.get( - "TICKET_API_BASE_URL", "https://api.hydraveil.net" + # "TICKET_API_BASE_URL", "https://api.hydraveil.net" + "TICKET_API_BASE_URL", "http://localhost:8000" ) - SP_API_BASE_URL: Final[str] = os.environ.get('SP_API_BASE_URL', 'https://api.hydraveil.net/api/v1') - SP_BASE_URL: Final[str] = os.environ.get('SP_BASE_URL', 'https://api.hydraveil.net/api') + SP_API_BASE_URL: Final[str] = os.environ.get('SP_API_BASE_URL', 'http://localhost:8000/api/v1') + SP_BASE_URL: Final[str] = os.environ.get('SP_BASE_URL', 'http://localhost:8000/api') + # SP_API_BASE_URL: Final[str] = os.environ.get('SP_API_BASE_URL', 'https://api.hydraveil.net/api/v1') + # SP_BASE_URL: Final[str] = os.environ.get('SP_BASE_URL', 'https://api.hydraveil.net/api') PING_URL: Final[str] = os.environ.get('PING_URL', 'https://api.hydraveil.net/api/v1/health') diff --git a/core/controllers/tickets/FailedVerificationController.py b/core/controllers/tickets/FailedVerificationController.py index 4bca873..cedaf01 100644 --- a/core/controllers/tickets/FailedVerificationController.py +++ b/core/controllers/tickets/FailedVerificationController.py @@ -4,9 +4,9 @@ if TYPE_CHECKING: from core.essentials.observers.ConnectionObserver import ConnectionObserver from core.observers.TicketObserver import TicketObserver # -from core.services.prepare_tickets.get_public_key_by_config import ( - get_public_key_from_LOCAL_files_only, -) +# from core.services.prepare_tickets.get_public_key_by_config import ( +# get_public_key_from_LOCAL_files_only, +# ) from core.services.failed_verification.is_the_key_to_blame import is_the_key_to_blame from core.services.failed_verification.prep_with_previously_saved_blind_sigs import ( prep_with_previously_saved_blind_sigs, diff --git a/core/controllers/tickets/TicketPrepController.py b/core/controllers/tickets/TicketPrepController.py index e2a82e1..209c851 100644 --- a/core/controllers/tickets/TicketPrepController.py +++ b/core/controllers/tickets/TicketPrepController.py @@ -1,12 +1,8 @@ -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.observers.ConnectionObserver import ConnectionObserver +from core.observers.TicketObserver import TicketObserver from core.services.payment_phase.payment_state import TicketPaymentData -# from core.services.prepare_tickets.get_pub_key import get_and_save_pub_key +from core.services.prepare_tickets.get_pub_key import get_and_save_pub_key from core.utils.type_checker import type_checker 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 @@ -14,7 +10,6 @@ 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 @@ -82,11 +77,12 @@ def prepare_tickets( logger.error(error_msg) return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg) - # got_key = get_and_save_pub_key(which_key) - # if not got_key.valid: - # logger.error(got_key.message) - # return got_key - # logger.info("Got public key..") + got_key = get_and_save_pub_key(which_key, connection_observer) + if not got_key.valid: + logger.error(got_key.message) + return got_key + public_key = got_key.data + logger.info("Got public key..") notification = "Preparing Cryptography Locally" if ticket_observer is not None: @@ -95,6 +91,7 @@ def prepare_tickets( # 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, + public_key=public_key, ticket_observer=ticket_observer, connection_observer=connection_observer, which_ticket=which_ticket, # only relevant for single ticket prep diff --git a/core/services/failed_verification/is_the_key_to_blame.py b/core/services/failed_verification/is_the_key_to_blame.py index 068e067..ea106a8 100644 --- a/core/services/failed_verification/is_the_key_to_blame.py +++ b/core/services/failed_verification/is_the_key_to_blame.py @@ -7,7 +7,7 @@ if TYPE_CHECKING: # services from core.services.crypto.TicketCustomer import TicketCustomer from core.services.helpers.validate_number_format import number_of_valid_length -from core.services.prepare_tickets.get_public_key_by_config import get_public_key_by_config +# from core.services.prepare_tickets.get_public_key_by_config import get_public_key_by_config from core.services.prepare_tickets.get_pub_key import key_is_in_valid_format, get_pub_key from core.services.failed_verification.test_if_new_key_works import test_if_new_key_works from core.services.networking.make_url import make_url diff --git a/core/services/payment_phase/old_get_pub_key.py b/core/services/payment_phase/old_get_pub_key.py new file mode 100644 index 0000000..82b14bb --- /dev/null +++ b/core/services/payment_phase/old_get_pub_key.py @@ -0,0 +1,252 @@ +# services +from core.services.networking.make_url import make_url +from core.services.networking.httpx import connect +from core.services.helpers.get_plan_data import get_plan_data +from core.services.payment_phase.payment_state import TicketPaymentData + +# utils +from core.utils.get_data import read_and_convert_with_eval +from core.models.Result import Result, ResultError +from core.utils.basic_operations.write_or_read_from_json import get_value_from_json_file +from core.utils.basic_operations.does_file_exist import does_file_exist +from core.utils.basic_operations.write_string_to_text_file import write_string_to_text_file +from core.errors.logger import logger +from core.Constants import Constants + +# generic +from typing import Any, Optional + + +def key_is_in_valid_format(public_key: Any) -> bool: + """ + Purpose: + Confirms a Public Key's Format with X and Y in a dictionary. + Called by: + get_pub_key (this module) + validate_blind_signatures' make_sure_we_have_inputs (external modules) + """ + return set(public_key.keys()) == {"x", "y"} + + +def fetch_key_from_server(which_key: str) -> dict|bool: + """ + Purpose: + Gets the public key from the API. + Called by: + get_and_save_pub_key (this module) + get_public_key_used (this module) + """ + url = make_url(which_key) + + public_key_results = connect.single_endpoint( + method="get", + url=url, + payload=None + ) + + if not public_key_results.valid: + error_msg = f"Error with API getting the Public Key: {public_key_results.error_type} and {public_key_results.message}" + logger.error(error_msg) + return False + + # extract: + public_key = public_key_results.data + + return public_key + + +def get_and_save_pub_key(which_key: str) -> Result: + """ + Purpose: + Fetches a public key and saves it. But you have to know which key you want. + Called by: + TicketPayController's initiate_payment + """ + from core.Constants import Constants + data_folder = Constants.HV_TICKETING_DATA_HOME + file_path = f"{data_folder}/{str(which_key)}.json" + + if not which_key: + error_msg = "You have to know which key you want to use this function." + return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg) + + public_key = fetch_key_from_server(which_key) + + if not public_key: + error_msg = "Failed to connect to the API Server to get the public key." + logger.error(error_msg) + return Result(valid=False, error_type=ResultError.CONNECTION, message=error_msg) + + if not key_is_in_valid_format(public_key): + error_msg = f"The server gave the {which_key} public key in an Invalid Format." + logger.error(error_msg) + return Result(valid=False, error_type=ResultError.INVALID_API_REPLY, message=error_msg) + + # save it: + if not write_string_to_text_file(public_key, file_path): + error_msg = f"Error with saving the public key {which_key}. Please check your file system permissions. It tried to use this path: {file_path}" + logger.error(error_msg) + return Result(valid=False, error_type=ResultError.FILE_SYSTEM, message=error_msg) + + # finally: + return Result(valid=True, data=public_key) + + +def get_from_config(get_what: str) -> str|bool: + """ + Purpose: + Wrapper with config file structure for external loaders. + Called by: + actions.begin_ticket_prep + """ + from core.Constants import Constants + filepath = Constants.TICKET_BILLING + return _get_from_config(filepath, get_what) + + +def _get_from_config(filepath: str, what_want: str) -> str|bool: + """ + Get some data from a dict-based file, and handle errors. + It assumes you have loaded the path on your own. + """ + try: + return get_value_from_json_file(filepath, what_want) + except (ValueError, KeyError) as e: + logger.error(f"We could not get {what_want} from the config at {filepath}, because {str(e)}") + return False + + +def find_out_which_key_we_used() -> Result: + """Returns which key plan was used based on the local config.""" + + billing_data = TicketPaymentData() + which_key = billing_data.which_key + + if which_key: + return Result(valid=True, data=which_key) + + + # OLD DEAD CODE: + + billing_folder = Constants.HV_TICKETING_CONFIG_HOME + filepath = f"{billing_folder}/billing_choices.json" + + if not does_file_exist(filepath): + return Result(valid=False, error_type=ResultError.MISSING_DATA, message="The Billing Choices file is missing, we can't figure out what key or plan was used.") + + which_key = _get_from_config(filepath, "which_key") + if which_key: + return Result(valid=True, data=which_key) + + # can't find plan? try to get temp billing id. + temp_billing_code = _get_from_config(filepath, "billing_code") + + if not temp_billing_code: + return Result(valid=False, error_type=ResultError.MISSING_DATA, message="The Billing Choices file does not have what key or plan was used.") + + # fetch from server: + fetching_result = get_plan_data(temp_billing_code) + + if not fetching_result.valid: + return fetching_result # this is already Result format with the error. + + plan_data = fetching_result.data + which_key = plan_data.get("which_key", False) + + if which_key: + return Result(valid=True, data=which_key) + else: + return Result(valid=False, error_type=ResultError.INVALID_API_REPLY, message="The API gave us a reply, but it lacked the critical key data we needed.") + + +def get_local_copy_of_public_key(which_key: str) -> Result: + """Once we know which key we're looking for, get and convert it from local files.""" + + from core.Constants import Constants + filepath = f"{Constants.HV_TICKETING_DATA_HOME}/{which_key}.json" + + if not does_file_exist(filepath): + return Result(valid=False, error_type=ResultError.MISSING_DATA, message=f"The file doesn't exist, or you lack the permissions to open it, at {filepath}") + + # Convert it to the format we need: + public_key = read_and_convert_with_eval(filepath) + + if not public_key: + error_msg = f"The local key of {which_key} is in the wrong format, even though the file exists." + logger.error(error_msg) + return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg) + + # this just checks if it has an X and Y in a dictionary: + if not key_is_in_valid_format(public_key) or not isinstance(public_key, dict): + error_msg = f"The local key of {which_key} is in the wrong format, even though the file exists." + logger.error(error_msg) + return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg) + + return Result(valid=True, data=public_key) + + +def get_public_key_used(local_only: Optional[bool] = False, save_it: Optional[bool] = False) -> Result: + """ + 1. Use the local config to figure out which key/plan + 2. See if there's a local copy first, convert it. + 3. If not fetch that from the API (unless local_only flag is on.) + + Called by: + TicketPrepController's prepare_tickets + FailedVerificationController's evaluate_if_its_the_key + """ + # ------------------------- + # Step 1. Which key was used? + # ------------------------- + key_results = find_out_which_key_we_used() + + if not key_results.valid: + return key_results + + which_key = key_results.data + + # ------------------------- + # Step 2. Do we have it locally? + # ------------------------- + local_copy_result = get_local_copy_of_public_key(which_key) + + if local_only: + return local_copy_result + + if local_copy_result.valid: + return local_copy_result + + # ------------------------- + # Step 3. Fetch it from the API + # ------------------------- + public_key = fetch_key_from_server(which_key) + + if not public_key: + error_msg = f"Failed to connect to the API Server to get the public key {which_key}." + logger.error(error_msg) + return Result(valid=False, error_type=ResultError.CONNECTION, message=error_msg) + + if save_it: + return save_key(public_key, which_key) + + # if they didn't flag on save_it, then we're done: + return Result(valid=True, data=public_key) + + +def save_key(public_key: dict, which_key: str) -> Result: + """ + Purpose: + Saves a public key locally, with a file path based on it's name. + Called by: + get_public_key_used (this module) + """ + from core.Constants import Constants + filepath = f"{Constants.HV_TICKETING_DATA_HOME}/{which_key}.json" + + if write_string_to_text_file(public_key, filepath): + return Result(valid=True, data=public_key) + else: + error_msg = f"Error with saving the public key {which_key}. Please check your file system permissions. It tried to use this path: {filepath}" + logger.error(error_msg) + return Result(valid=False, error_type=ResultError.FILE_SYSTEM, message=error_msg) + diff --git a/core/services/prepare_tickets/get_pub_key.py b/core/services/prepare_tickets/get_pub_key.py index e3f8263..a0f402a 100644 --- a/core/services/prepare_tickets/get_pub_key.py +++ b/core/services/prepare_tickets/get_pub_key.py @@ -1,120 +1,244 @@ -from __future__ import annotations -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from essentials.observers.ConnectionObserver import ConnectionObserver -from typing import Any # services from core.services.networking.make_url import make_url from core.services.networking.httpx import connect -from core.services.networking.api_requests.ApiResponseModel import ApiResponse, ErrorType +from core.services.helpers.get_plan_data import get_plan_data # utils +from core.utils.get_data import read_and_convert_with_eval +from core.models.Result import Result, ResultError +from core.utils.basic_operations.write_or_read_from_json import get_value_from_json_file from core.utils.basic_operations.does_file_exist import does_file_exist from core.utils.basic_operations.write_string_to_text_file import write_string_to_text_file -# errors & constants -from core.Constants import Constants from core.errors.logger import logger +from core.Constants import Constants +from core.observers.ConnectionObserver import ConnectionObserver + # generic -import ast +from typing import Any, Optional def key_is_in_valid_format(public_key: Any) -> bool: + """ + Purpose: + Confirms a Public Key's Format with X and Y in a dictionary. + Called by: + get_pub_key (this module) + validate_blind_signatures' make_sure_we_have_inputs (external modules) + """ return set(public_key.keys()) == {"x", "y"} -def get_from_server_and_save( - string_form_of_key_name: str, - file_path: str, - connection_observer: ConnectionObserver, -) -> dict: - - url = make_url(string_form_of_key_name) +def fetch_key_from_server(which_key: str, connection_observer: ConnectionObserver) -> dict|bool: + """ + Purpose: + Gets the public key from the API. + Called by: + get_and_save_pub_key (this module) + get_public_key_used (this module) + """ + url = make_url(which_key) public_key_results = connect.single_endpoint( method="get", url=url, - observer=connection_observer, - payload=None + payload=None, + observer=connection_observer ) if not public_key_results.valid: error_msg = f"Error with API getting the Public Key: {public_key_results.error_type} and {public_key_results.message}" logger.error(error_msg) - return {"valid": False, "message": error_msg} + return False # extract: public_key = public_key_results.data - print(f"public_key is {public_key}") + return public_key + + +def get_and_save_pub_key(which_key: str, connection_observer: ConnectionObserver) -> Result: + """ + Purpose: + Fetches a public key and saves it. But you have to know which key you want. + Called by: + TicketPayController's initiate_payment + """ + data_folder = Constants.HV_TICKETING_DATA_HOME + file_path = f"{data_folder}/{str(which_key)}.json" + + if not which_key: + error_msg = "You have to know which key you want to use this function." + return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg) + + public_key = fetch_key_from_server(which_key, connection_observer) + + if not public_key: + error_msg = "Failed to connect to the API Server to get the public key." + logger.error(error_msg) + return Result(valid=False, error_type=ResultError.CONNECTION, message=error_msg) + + if not key_is_in_valid_format(public_key): + error_msg = f"The server gave the {which_key} public key in an Invalid Format." + logger.error(error_msg) + return Result(valid=False, error_type=ResultError.INVALID_API_REPLY, message=error_msg) # save it: - did_it_save = write_string_to_text_file(public_key, file_path) + if not write_string_to_text_file(public_key, file_path): + error_msg = f"Error with saving the public key {which_key}. Please check your file system permissions. It tried to use this path: {file_path}" + log_and_notify(error_msg) + return Result(valid=False, error_type=ResultError.FILE_SYSTEM, message=error_msg) - if not did_it_save: - logger.error(f"Error with saving the public key {public_key} with write_string_to_text_file, but still returning data..") - - # regardless of saving: - return {"valid": True, "data": public_key} + # finally: + return Result(valid=True, data=public_key) -def get_pub_key( - which_key: str, - connection_observer: ConnectionObserver, - check_locally: str, -) -> dict: +def get_from_config(get_what: str) -> str|bool: + """ + Purpose: + Wrapper with config file structure for external loaders. + Called by: + actions.begin_ticket_prep + """ + from core.Constants import Constants + filepath = Constants.TICKET_BILLING + return _get_from_config(filepath, get_what) - # prep file path for either saving or using the existing one: - data_folder = Constants.HV_TICKETING_DATA_HOME - string_form_of_key_name = str(which_key) - file_path = f"{data_folder}/{string_form_of_key_name}.json" - # do we want to use the key if it's already stored locally? - if check_locally == "local": - # does the public key already exist locally? - exists_already = does_file_exist(file_path) +def _get_from_config(filepath: str, what_want: str) -> str|bool: + """ + Get some data from a dict-based file, and handle errors. + It assumes you have loaded the path on your own. + """ + try: + return get_value_from_json_file(filepath, what_want) + except (ValueError, KeyError) as e: + logger.error(f"We could not get {what_want} from the config at {filepath}, because {str(e)}") + return False + + +def find_out_which_key_we_used() -> Result: + """Returns which key plan was used based on the local config.""" + + from core.Constants import Constants + billing_folder = Constants.HV_TICKETING_CONFIG_HOME + filepath = f"{billing_folder}/billing_choices.json" + + if not does_file_exist(filepath): + return Result(valid=False, error_type=ResultError.MISSING_DATA, message="The Billing Choices file is missing, we can't figure out what key or plan was used.") + + which_key = _get_from_config(filepath, "which_key") + if which_key: + return Result(valid=True, data=which_key) + + # can't find plan? try to get temp billing id. + temp_billing_code = _get_from_config(filepath, "temp_billing_code") + + if not temp_billing_code: + return Result(valid=False, error_type=ResultError.MISSING_DATA, message="The Billing Choices file does not have what key or plan was used.") + + # fetch from server: + fetching_result = get_plan_data(temp_billing_code) + + if not fetching_result.valid: + return fetching_result # this is already Result format with the error. + + plan_data = fetching_result.data + which_key = plan_data.get("which_key", False) + + if which_key: + return Result(valid=True, data=which_key) else: - logger.debug("Getting Public Key from API..", exc_info=True) - # setting this to false bypasses it being local for the next section, and skips to the end - exists_already = False + return Result(valid=False, error_type=ResultError.INVALID_API_REPLY, message="The API gave us a reply, but it lacked the critical key data we needed.") - # Next Section - if exists_already == True: - # we're going to try to get it from the file locally: - try: - with open(file_path, "r") as file: - public_key_as_string = file.read() - # this gets in pyth dict with a single ', which is how the crypto functions need it. - public_key = ast.literal_eval(public_key_as_string) +def get_local_copy_of_public_key(which_key: str) -> Result: + """Once we know which key we're looking for, get and convert it from local files.""" - if key_is_in_valid_format(public_key): - return {"valid": True, "data": public_key} + from core.Constants import Constants + filepath = f"{Constants.HV_TICKETING_DATA_HOME}/{which_key}.json" - else: - print( - "Key format is Invalid! Let's replace it by getting a new one from the server," - ) - public_key_results = get_from_server_and_save( - string_form_of_key_name, file_path, connection_observer - ) - return public_key_results + if not does_file_exist(filepath): + return Result(valid=False, error_type=ResultError.MISSING_DATA, message=f"The file doesn't exist, or you lack the permissions to open it, at {filepath}") - # but if the local key doesn't format properly, then get it from the server, - except: - public_key_results = get_from_server_and_save( - string_form_of_key_name, file_path, connection_observer - ) + # Convert it to the format we need: + public_key = read_and_convert_with_eval(filepath) - return public_key_results + if not public_key: + error_msg = f"The local key of {which_key} is in the wrong format, even though the file exists." + logger.error(error_msg) + return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg) - # no local key? get from server, + # this just checks if it has an X and Y in a dictionary: + if not key_is_in_valid_format(public_key) or not isinstance(public_key, dict): + error_msg = f"The local key of {which_key} is in the wrong format, even though the file exists." + logger.error(error_msg) + return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg) + + return Result(valid=True, data=public_key) + + +def get_public_key_used(local_only: Optional[bool] = False, save_it: Optional[bool] = False) -> Result: + """ + 1. Use the local config to figure out which key/plan + 2. See if there's a local copy first, convert it. + 3. If not fetch that from the API (unless local_only flag is on.) + + Called by: + TicketPrepController's prepare_tickets + FailedVerificationController's evaluate_if_its_the_key + """ + # ------------------------- + # Step 1. Which key was used? + # ------------------------- + key_results = find_out_which_key_we_used() + + if not key_results.valid: + return key_results + + which_key = key_results.data + + # ------------------------- + # Step 2. Do we have it locally? + # ------------------------- + local_copy_result = get_local_copy_of_public_key(which_key) + + if local_only: + return local_copy_result + + if local_copy_result.valid: + return local_copy_result + + # ------------------------- + # Step 3. Fetch it from the API + # ------------------------- + public_key = fetch_key_from_server(which_key) + + if not public_key: + error_msg = f"Failed to connect to the API Server to get the public key {which_key}." + logger.error(error_msg) + return Result(valid=False, error_type=ResultError.CONNECTION, message=error_msg) + + if save_it: + return save_key(public_key, which_key) + + # if they didn't flag on save_it, then we're done: + return Result(valid=True, data=public_key) + + +def save_key(public_key: dict, which_key: str) -> Result: + """ + Purpose: + Saves a public key locally, with a file path based on it's name. + Called by: + get_public_key_used (this module) + """ + from core.Constants import Constants + filepath = f"{Constants.HV_TICKETING_DATA_HOME}/{which_key}.json" + + if write_string_to_text_file(public_key, filepath): + return Result(valid=True, data=public_key) else: - logger.debug( - "You do not have the public key locally. So we are getting it from the server.." - ) - public_key_results = get_from_server_and_save( - string_form_of_key_name, file_path, connection_observer - ) + error_msg = f"Error with saving the public key {which_key}. Please check your file system permissions. It tried to use this path: {filepath}" + log_and_notify(error_msg) + return Result(valid=False, error_type=ResultError.FILE_SYSTEM, message=error_msg) - return public_key_results diff --git a/core/services/prepare_tickets/get_public_key_by_config.py b/core/services/prepare_tickets/get_public_key_by_config.py deleted file mode 100644 index 46eea03..0000000 --- a/core/services/prepare_tickets/get_public_key_by_config.py +++ /dev/null @@ -1,186 +0,0 @@ -from __future__ import annotations -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from essentials.observers.ConnectionObserver import ConnectionObserver -# services & helpers -from core.services.networking.api_requests.ApiResponseModel import ApiResponse, ErrorType -from core.services.prepare_tickets.get_pub_key import get_pub_key -from core.services.prepare_tickets.get_pub_key import key_is_in_valid_format -from core.services.helpers.get_plan_data import get_plan_data -# utils -from core.utils.basic_operations.filter_data import filter_data -from core.utils.basic_operations.write_or_read_from_json import get_value_from_json_file -from core.utils.basic_operations.does_file_exist import does_file_exist -# constants -from core.Constants import Constants -# generic -import ast - -""" -This service tries to get a public key, based on the config file. -It first checks if the config file has which key it wants. -Then it checks for a temp billing code, and if there is one, asks the server what the key is. -""" - - -# Get which key plan they have. -def get_public_key_FROM_config(filepath) -> str | None: - try: - which_key = get_value_from_json_file(filepath, "which_key") - return which_key - - except: - return None - - -def get_public_key_by_config(connection_observer: ConnectionObserver) -> dict: - list_of_failures = [None, False, ""] - billing_folder = Constants.HV_TICKETING_CONFIG_HOME - filepath = f"{billing_folder}/billing_choices.json" - - which_key = get_public_key_FROM_config(filepath) - - # if there is no key in the config? - if which_key is None: - - # then try to get the temp billing id. - temp_billing_code = get_value_from_json_file(filepath, "temp_billing_code") - - # but what if there is not even a temp billing ID? - if temp_billing_code in list_of_failures: - return { - "status": False, - "message": f"Issue with finding key plan or billing code. Please check {filepath}", - } - - # Now we're assuming we have a temp billing code, - # So we can use that to get the key from the server, - api_reply_object = get_plan_data(temp_billing_code, connection_observer) - - complete_failure_msg = { - "status": False, - "message": f"Issues with both finding your local key plan and even connecting to the server for it. Please check {filepath}", - } - - if not api_reply_object.valid: - return complete_failure_msg - - reply = api_reply_object.data - - if reply in list_of_failures: - return complete_failure_msg - - if not isinstance(reply, dict): - return { - "status": False, - "message": f"Server returned an invalid format, and even accessing via local files. Please check {filepath}", - } - - # from the server's reply, get the key - which_key = filter_data(reply, "which_key") - if which_key in list_of_failures: - return { - "status": False, - "message": f"Issues with both finding your local key plan and getting it from the server. Please check {filepath}", - } - # The reason we convert it to integer, only to convert it back to a string in the next functions, - # is because those functions can be accessed directly in other ways that would have the key as an integer. - # which_key = int(which_key) - - # send to service: - public_key_results = get_pub_key(which_key, connection_observer, "local") - - # if public_key_results is None: - # return {'status': False, 'message': "no_public_key"} - - # assert public_key_results is not None - return public_key_results - - -""" -This seems like a duplicate function, but it's used in situations where there was a failed validation. -The other functions get the key from local files OR the api. And they save it, which would replace the key we want to study. -Instead, this function gets it only from local files. -""" - - -def get_public_key_from_LOCAL_files_only( - connection_observer: ConnectionObserver, -) -> dict: - billing_folder = Constants.HV_TICKETING_CONFIG_HOME - filepath = f"{billing_folder}/billing_choices.json" - try: - which_key = get_value_from_json_file(filepath, "which_key") - except: - which_key = None - finally: - list_of_failures = [None, False, ""] - if which_key in list_of_failures: - # can't find plan? try to get temp billing id. - temp_billing_code = get_value_from_json_file(filepath, "temp_billing_code") - - if temp_billing_code in list_of_failures: - return { - "status": False, - "message": f"Issue with finding key plan or billing code. Please check {filepath}", - } - - api_reply_object = get_plan_data(temp_billing_code, connection_observer) - - complete_failure_msg = { - "status": False, - "message": f"Issues with both finding your local key plan and even connecting to the server for it. Please check {filepath}", - } - - if not api_reply_object.valid: - return complete_failure_msg - - reply = api_reply_object.data - - if reply in list_of_failures: - return complete_failure_msg - - if not isinstance(reply, dict): - return { - "status": False, - "message": f"Server returned an invalid format, and even accessing via local files. Please check {filepath}", - } - - which_key = filter_data(reply, "which_key") - if which_key in list_of_failures: - return { - "status": False, - "message": f"Issues with both finding your local key plan and getting it from the server. Please check {filepath}", - } - - # now that we know the key plan, move on to getting the data: - data_folder = Constants.HV_TICKETING_DATA_HOME - string_form_of_key_name = str(which_key) - file_path = f"{data_folder}/{string_form_of_key_name}.json" - - # does it exist? - exists_already = does_file_exist(file_path) - if not exists_already: - return {"valid": False, "message": "Local key file doesn't exist"} - - try: - with open(file_path, "r") as file: - public_key_as_string = file.read() - - # this gets in pyth dict with a single ', which is how the crypto functions need it. - public_key = ast.literal_eval(public_key_as_string) - - # this only checks if the key has 'x' and 'y' keys: - if key_is_in_valid_format(public_key): - return {"valid": True, "public_key": public_key} - else: - return { - "valid": False, - "message": "Key file exists but is in an invalid format.", - } - except: - return { - "valid": False, - "message": "Key file exists but is in an invalid format or unreadable.", - } diff --git a/core/services/prepare_tickets/make_sure_pub_key_exists.py b/core/services/prepare_tickets/make_sure_pub_key_exists.py index 16e0187..6676526 100644 --- a/core/services/prepare_tickets/make_sure_pub_key_exists.py +++ b/core/services/prepare_tickets/make_sure_pub_key_exists.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: from essentials.observers.ConnectionObserver import ConnectionObserver # -from core.services.prepare_tickets.get_public_key_by_config import get_public_key_by_config +# from core.services.prepare_tickets.get_public_key_by_config import get_public_key_by_config from core.utils.basic_operations.does_file_exist import does_file_exist from core.utils.basic_operations.write_or_read_from_json import get_value_from_json_file from core.Constants import Constants diff --git a/core/services/prepare_tickets/old_validate_blind_signatures.py b/core/services/prepare_tickets/old_validate_blind_signatures.py new file mode 100644 index 0000000..2fa00e7 --- /dev/null +++ b/core/services/prepare_tickets/old_validate_blind_signatures.py @@ -0,0 +1,181 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from core.observers.TicketObserver import TicketObserver + from essentials.observers.ConnectionObserver import ConnectionObserver +# services +from core.services.crypto.TicketCustomer import TicketCustomer +from core.services.prepare_tickets.get_public_key_by_config import get_public_key_by_config +from core.services.prepare_tickets.get_pub_key import key_is_in_valid_format, get_pub_key +# utils +from core.utils.save_data import save_data +from core.utils.basic_operations.write_or_read_from_json import get_value_from_json_file +# observers, constants, errors +from core.observers.BaseObserver import BaseObserver +from core.models.Event import Event +from core.Constants import Constants +from core.errors.exceptions import * +from core.errors.logger import logger +# generic +import traceback +import json +from typing import Optional + +def make_sure_we_have_inputs( + ALL_signed_blind_signatures: list, + profile_object: TicketCustomer, + ticket_observer: TicketObserver, + connection_observer: ConnectionObserver, +) -> dict: + + try: + if ALL_signed_blind_signatures == False or ALL_signed_blind_signatures == None: + error_msg = "The Server gave back no data or it's missing for ALL_signed_blind_signatures in the unblind_and_prove_ALL_profiles function . This can't unblind blank data." + logger.error(error_msg, exc_info=True) + logger.debug(error_msg) + return {"valid": False, "error_msg": error_msg} + + # get the public key for entire loop: + public_key_results = get_public_key_by_config(connection_observer) + + # did we actually get the public key? + if "data" not in public_key_results: + error_msg = ( + "The validation function can't even get the public key to do it's loop" + ) + logger.error(error_msg, exc_info=True) + logger.debug(error_msg) + return {"valid": False, "error_msg": error_msg} + + public_key = public_key_results.get("data") + + rejected_reasons = [None, False, ""] + if public_key in rejected_reasons: + error_msg = "The validation function can't get the public key" + logger.debug(error_msg) + return {"valid": False, "error_msg": error_msg} + + # this just checks if it has an X and Y in a dictionary: + if not key_is_in_valid_format(public_key) or not isinstance(public_key, dict): + return {"valid": False, "error_msg": "invalid_key_format"} + + logger.debug("Checking if the public key is even able to be loaded...") + valid_key = profile_object.load_key(public_key) + + if valid_key: + logger.debug("Public Key can be loaded") + return {"valid": True, "public_key": public_key} + + else: + error_msg = "The validation function is working with an invalid public key" + logger.debug(error_msg) + return {"valid": False, "error_msg": error_msg} + + except: + error_msg = ( + "We lack the ability to get all the inputs for the verification loop" + ) + logger.error(error_msg, exc_info=True) + logger.debug(error_msg) + return {"valid": False, "error_msg": error_msg} + + +""" +Technically this entire module is optional, as it isn't used for the actual ticket preparation. + +This module exists so the customer can be sure that the server did not maliciously sign with a fake new key, +to try to secretly identify users (by using different keys for each one). +""" + + +def validate_blind_signatures( + ALL_signed_blind_signatures: list, + ticket_observer: TicketObserver, + connection_observer: ConnectionObserver, + which_ticket: Optional[int] = None, +): + try: + # Setup the entire class object of "profile_object" for using all these other functions, + profile_object = TicketCustomer() + + # start: confirm inputs + inputs = make_sure_we_have_inputs( + ALL_signed_blind_signatures, + profile_object, + ticket_observer, + connection_observer, + ) + if not inputs["valid"]: + logger.debug("ending the validation process early.") + return inputs + public_key = inputs["public_key"] + # end: confirm inputs + + # setup basic counters, flags, and lists for the loop below: + + if which_ticket is None: + which_ticket = 1 + + did_we_answer_the_key_question_yet = "no" + list_of_failed_verifications = [] + + """ + LOOP STARTS: + + Looping through each signature, verifying it against the single public key. + """ + for each_blind_signature in ALL_signed_blind_signatures: + # prep from server's format: + blind_signature = json.loads(each_blind_signature) + + #### Check blind signature validity for THAT signature: + validity_data = profile_object.test_blind_signature_validity( + which_ticket, blind_signature, public_key + ) + + if validity_data["valid"] == True: + # verification went fine: + notification = f"Verified {which_ticket}'s blind signature" + ticket_observer.notify("preparing", subject=notification) + else: + # is it an invalid pub key? + if validity_data["message"] == "invalid_key": + notification = f"Verification of all blind signatures is halted, because you have an invalid public key!" + ticket_observer.notify("preparing", subject=notification) + return [which_ticket] + + # otherwise: + list_of_failed_verifications.append(which_ticket) + notification = f"Invalid {which_ticket} blind signature" + ticket_observer.notify("preparing", subject=notification) + + # bump loop for next round: + which_ticket = which_ticket + 1 + + ### LOOP DONE + logger.debug( + f"Exiting the validation loop with the list_of_failed_verifications: {list_of_failed_verifications}" + ) + return list_of_failed_verifications + + except FailedToSave as e: + error_msg = f"FailedToSave: {e}" + logger.error(error_msg, exc_info=True) + return {"valid": False, "message": "error", "error": error_msg} + + except InvalidData as e: + error_msg = f"InvalidData error: {e}" + logger.error(error_msg, exc_info=True) + return {"valid": False, "message": "error", "error": error_msg} + + except MissingData as e: + error_msg = f"MissingData: {e}" + logger.error(error_msg, exc_info=True) + return {"valid": False, "message": "error", "error": error_msg} + + except Exception as e: + tb = traceback.extract_tb(e.__traceback__)[-1] + error_msg = f"error type {type(e).__name__} happened on line {tb.lineno}, in function {tb.name}, giving error: {e}. The actual code in question is: {tb.line}. in this file: {tb.filename}" + logger.error(error_msg, exc_info=True) + return {"valid": False, "message": "error", "error": error_msg} diff --git a/core/services/prepare_tickets/send_blind_commitments.py b/core/services/prepare_tickets/send_blind_commitments.py index 1eab670..ccdd72f 100644 --- a/core/services/prepare_tickets/send_blind_commitments.py +++ b/core/services/prepare_tickets/send_blind_commitments.py @@ -6,22 +6,18 @@ if TYPE_CHECKING: from core.observers.TicketObserver import TicketObserver # services -# from core.services.networking.httpx import connect -# from core.services.networking.api_requests.ApiResponseModel import ApiResponse, ErrorType from core.services.networking.make_url import make_url -# from core.services.payment_phase.ticket_config_tools import get_which_billing_key # models & their controllers from core.models.manage.server_reply_tools import api_call_and_extraction, ApiCall, translate_server_failure from core.models.pydantic_models.ServerReply import ServerReply, ServerError -# from core.models.session.SessionProfile import SessionProfile -# from core.models.system.SystemProfile import SystemProfile +from core.services.payment_phase.payment_state import TicketPaymentData # utils from core.utils.basic_operations.write_or_read_from_json import get_value_from_json_file + # constants & errors from core.Constants import Constants -# from core.errors.exceptions import InvalidData, ServerSideError from core.models.Result import Result, ResultError from core.errors.logger import logger @@ -40,12 +36,15 @@ def prep_payload_for_bulk(list_of_all_blinded_data: list) -> dict: Rank: Mini-Helper """ - # get temp billing id from local storage: - temp_billing_code = get_value_from_json_file(billing_path, "temp_billing_code") + # billing code + billing_data = TicketPaymentData() + billing_code = billing_data.billing_code + + # temp_billing_code = get_value_from_json_file(billing_path, "temp_billing_code") # prep the JSON payload, it doesn't need the public key, since the server can look that up, payload = { - "temp_billing_code": temp_billing_code, + "billing_code": billing_code, "blinded_data": list_of_all_blinded_data, } diff --git a/core/services/prepare_tickets/ticket_prep_orchestrator.py b/core/services/prepare_tickets/ticket_prep_orchestrator.py index c8bdc8a..4fd6ef8 100644 --- a/core/services/prepare_tickets/ticket_prep_orchestrator.py +++ b/core/services/prepare_tickets/ticket_prep_orchestrator.py @@ -2,9 +2,9 @@ from __future__ import annotations from typing import TYPE_CHECKING if TYPE_CHECKING: - from core.observers.TicketObserver import TicketObserver - from essentials.observers.ConnectionObserver import ConnectionObserver + from core.observers.ConnectionObserver import ConnectionObserver +from core.observers.TicketObserver import TicketObserver from core.services.crypto.make_commitments import make_ALL_commitments from core.services.prepare_tickets.unblind_all_tickets import unblind_ALL_tickets @@ -46,6 +46,7 @@ Called by: @type_checker def ticket_prep_orchestrator( how_many_profiles: int, + public_key: dict, ticket_observer: TicketObserver, connection_observer: ConnectionObserver, which_ticket: Optional[int] = None, @@ -106,11 +107,14 @@ def ticket_prep_orchestrator( # verify the server's blind signatures against the public key, notification = "Evaluating the Server's Blind Replies" ticket_observer.notify("preparing", subject=notification) - failed_validations = validate_blind_signatures( - blind_signatures, ticket_observer, connection_observer, which_ticket + validations = validate_blind_signatures( + ALL_signed_blind_signatures=blind_signatures, + public_key=public_key, + ticket_observer=ticket_observer, + which_ticket=which_ticket ) - logger.debug(f"failed_validations is {failed_validations}") + failed_validations = validations.data ##################################################### # FAILED BLIND SIG VERIFICATION @@ -129,14 +133,6 @@ def ticket_prep_orchestrator( message=notification ) - ##################################################### - # PAID - ##################################################### - # saving on config that it's paid: - config_marked = ConfigurationController.mark_as_paid(True) - if not config_marked: - return Result(valid=False, error_type=ResultError.FILE_SYSTEM, message="Failed to mark as paid in the config. You still have the tickets locally, just can't use them till this file system error is resolved.") - ##################################################### # UNBLIND ##################################################### @@ -149,6 +145,7 @@ def ticket_prep_orchestrator( connection_observer=connection_observer, which_ticket=which_ticket ) + logger.info(f"Unblind completed. Was it a success? {did_prep_work}") ##################################################### # TICKET TRACKER & APP-WIDE CONFIG @@ -157,6 +154,7 @@ def ticket_prep_orchestrator( setup_tracker = setup_ticket_tracker(how_many_profiles) if not setup_tracker: return Result(valid=False, error_type=ResultError.FILE_SYSTEM, message="Failed to setup the ticket tracker. You still have the tickets locally, just can't use them till this file system error is resolved.") + logger.info("Ticket tracker setup completed.") ##################################################### # FINAL EVALUATION @@ -165,6 +163,7 @@ def ticket_prep_orchestrator( # this means it unblinded, & setup the tracker without erroring out, # but it does NOT mean that verification of the blind sigs worked. ConfigurationController.tickets_setup(True) + logger.info("Setup Complete.") return Result(valid=True, message="Setup worked.") diff --git a/core/services/prepare_tickets/validate_blind_signatures.py b/core/services/prepare_tickets/validate_blind_signatures.py index 2fa00e7..a522b77 100644 --- a/core/services/prepare_tickets/validate_blind_signatures.py +++ b/core/services/prepare_tickets/validate_blind_signatures.py @@ -1,85 +1,17 @@ -from __future__ import annotations -from typing import TYPE_CHECKING +from core.observers.TicketObserver import TicketObserver -if TYPE_CHECKING: - from core.observers.TicketObserver import TicketObserver - from essentials.observers.ConnectionObserver import ConnectionObserver # services from core.services.crypto.TicketCustomer import TicketCustomer -from core.services.prepare_tickets.get_public_key_by_config import get_public_key_by_config -from core.services.prepare_tickets.get_pub_key import key_is_in_valid_format, get_pub_key + # utils -from core.utils.save_data import save_data -from core.utils.basic_operations.write_or_read_from_json import get_value_from_json_file -# observers, constants, errors -from core.observers.BaseObserver import BaseObserver -from core.models.Event import Event -from core.Constants import Constants -from core.errors.exceptions import * +from core.models.Result import Result #, ResultError from core.errors.logger import logger + # generic -import traceback import json from typing import Optional -def make_sure_we_have_inputs( - ALL_signed_blind_signatures: list, - profile_object: TicketCustomer, - ticket_observer: TicketObserver, - connection_observer: ConnectionObserver, -) -> dict: - - try: - if ALL_signed_blind_signatures == False or ALL_signed_blind_signatures == None: - error_msg = "The Server gave back no data or it's missing for ALL_signed_blind_signatures in the unblind_and_prove_ALL_profiles function . This can't unblind blank data." - logger.error(error_msg, exc_info=True) - logger.debug(error_msg) - return {"valid": False, "error_msg": error_msg} - - # get the public key for entire loop: - public_key_results = get_public_key_by_config(connection_observer) - - # did we actually get the public key? - if "data" not in public_key_results: - error_msg = ( - "The validation function can't even get the public key to do it's loop" - ) - logger.error(error_msg, exc_info=True) - logger.debug(error_msg) - return {"valid": False, "error_msg": error_msg} - - public_key = public_key_results.get("data") - - rejected_reasons = [None, False, ""] - if public_key in rejected_reasons: - error_msg = "The validation function can't get the public key" - logger.debug(error_msg) - return {"valid": False, "error_msg": error_msg} - - # this just checks if it has an X and Y in a dictionary: - if not key_is_in_valid_format(public_key) or not isinstance(public_key, dict): - return {"valid": False, "error_msg": "invalid_key_format"} - - logger.debug("Checking if the public key is even able to be loaded...") - valid_key = profile_object.load_key(public_key) - - if valid_key: - logger.debug("Public Key can be loaded") - return {"valid": True, "public_key": public_key} - - else: - error_msg = "The validation function is working with an invalid public key" - logger.debug(error_msg) - return {"valid": False, "error_msg": error_msg} - - except: - error_msg = ( - "We lack the ability to get all the inputs for the verification loop" - ) - logger.error(error_msg, exc_info=True) - logger.debug(error_msg) - return {"valid": False, "error_msg": error_msg} - +REJECTED_INPUTS = [False, None, "error"] """ Technically this entire module is optional, as it isn't used for the actual ticket preparation. @@ -91,91 +23,56 @@ to try to secretly identify users (by using different keys for each one). def validate_blind_signatures( ALL_signed_blind_signatures: list, + public_key: dict, ticket_observer: TicketObserver, - connection_observer: ConnectionObserver, - which_ticket: Optional[int] = None, -): - try: - # Setup the entire class object of "profile_object" for using all these other functions, - profile_object = TicketCustomer() + which_ticket: Optional[int] = None, # Single or Multiple Ticket Prep. If there's a value here, it's a single ticket. +) -> Result: - # start: confirm inputs - inputs = make_sure_we_have_inputs( - ALL_signed_blind_signatures, - profile_object, - ticket_observer, - connection_observer, + # Setup the entire class object of "profile_object" for using all these other functions, + profile_object = TicketCustomer() + + #----------------------------------- + # PREPARE FOR UPCOMING LOOP + #----------------------------------- + # setup basic counters, flags, and lists for the loop below: + + if which_ticket is None: # this is if it's a single or multiple ticket prep. And not just basic filtering + which_ticket = 1 + + list_of_failed_verifications = [] + + #----------------------------------- + # LOOP: + #----------------------------------- + # Looping through each signature, verifying it against the single public key. + for each_blind_signature in ALL_signed_blind_signatures: + # prep from server's format: + blind_signature = json.loads(each_blind_signature) + + #### Check blind signature validity for THAT signature: + validity_data = profile_object.test_blind_signature_validity( + which_ticket, blind_signature, public_key ) - if not inputs["valid"]: - logger.debug("ending the validation process early.") - return inputs - public_key = inputs["public_key"] - # end: confirm inputs - # setup basic counters, flags, and lists for the loop below: + if validity_data["valid"] == True: + # verification went fine: + logger.info(f"Verified {which_ticket}'s blind signature") + else: + # is it an invalid pub key? + if validity_data["message"] == "invalid_key": + error_msg = "Verification of all blind signatures is halted, because you have an invalid public key!" + return Result(valid=True, data=[which_ticket], message=error_msg) - if which_ticket is None: - which_ticket = 1 + # otherwise: + list_of_failed_verifications.append(which_ticket) + logger.error(f"Invalid {which_ticket} blind signature") - did_we_answer_the_key_question_yet = "no" - list_of_failed_verifications = [] + # bump loop for next round: + which_ticket = which_ticket + 1 - """ - LOOP STARTS: + ### LOOP DONE - Looping through each signature, verifying it against the single public key. - """ - for each_blind_signature in ALL_signed_blind_signatures: - # prep from server's format: - blind_signature = json.loads(each_blind_signature) - - #### Check blind signature validity for THAT signature: - validity_data = profile_object.test_blind_signature_validity( - which_ticket, blind_signature, public_key - ) - - if validity_data["valid"] == True: - # verification went fine: - notification = f"Verified {which_ticket}'s blind signature" - ticket_observer.notify("preparing", subject=notification) - else: - # is it an invalid pub key? - if validity_data["message"] == "invalid_key": - notification = f"Verification of all blind signatures is halted, because you have an invalid public key!" - ticket_observer.notify("preparing", subject=notification) - return [which_ticket] - - # otherwise: - list_of_failed_verifications.append(which_ticket) - notification = f"Invalid {which_ticket} blind signature" - ticket_observer.notify("preparing", subject=notification) - - # bump loop for next round: - which_ticket = which_ticket + 1 - - ### LOOP DONE - logger.debug( - f"Exiting the validation loop with the list_of_failed_verifications: {list_of_failed_verifications}" - ) - return list_of_failed_verifications - - except FailedToSave as e: - error_msg = f"FailedToSave: {e}" - logger.error(error_msg, exc_info=True) - return {"valid": False, "message": "error", "error": error_msg} - - except InvalidData as e: - error_msg = f"InvalidData error: {e}" - logger.error(error_msg, exc_info=True) - return {"valid": False, "message": "error", "error": error_msg} - - except MissingData as e: - error_msg = f"MissingData: {e}" - logger.error(error_msg, exc_info=True) - return {"valid": False, "message": "error", "error": error_msg} - - except Exception as e: - tb = traceback.extract_tb(e.__traceback__)[-1] - error_msg = f"error type {type(e).__name__} happened on line {tb.lineno}, in function {tb.name}, giving error: {e}. The actual code in question is: {tb.line}. in this file: {tb.filename}" - logger.error(error_msg, exc_info=True) - return {"valid": False, "message": "error", "error": error_msg} + logger.debug( + f"Exiting the validation loop with the list_of_failed_verifications: {list_of_failed_verifications}" + ) + return Result(valid=True, data=list_of_failed_verifications)