From e35155a4ddd59bc52a657ed0c365753abb7416de Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Tue, 26 Nov 2024 08:27:19 -0500 Subject: [PATCH] adding user add/update/verify functionality --- src/common/pg_impl.py | 77 ++++++++++++++++ src/common/pg_utils_multi.py | 4 +- src/server.py | 166 +++++++++++++++++++++++++++++++++-- 3 files changed, 239 insertions(+), 8 deletions(-) diff --git a/src/common/pg_impl.py b/src/common/pg_impl.py index 31e2792..6d336f0 100644 --- a/src/common/pg_impl.py +++ b/src/common/pg_impl.py @@ -601,3 +601,80 @@ def get_instance_names(self, name: str, project_code: str = None) -> EnumType: # Return Pandas dataframe return ret_val + + def verify_user(self, email: str, password_hash: str) -> dict: + """ + verifies the user has an account and the password is correct. + + if the verification is successful, return a JSON object with pass/fail and user account data + + :param email: + :param password_hash: + :return: + """ + # init the return value: + ret_val = None + + # prep the email param for the SP + if email is None: + email = 'null' + else: + email = f"'{email}'" + + # prep the password param for the SP + if password_hash is None: + password_hash = 'null' + else: + password_hash = f"'{password_hash}'" + + # build the query. this will also return the users profile + sql = f"SELECT verify_user(_email := {email}, _password_hash := {password_hash});" + + # get the info + ret_val = self.exec_sql('apsviz', sql) + + # return the result of the inquiry + return ret_val + + def add_user(self, **kwargs) -> dict: + """ + Adds the user and profile and returns a pass/fail dict + + if the call is successful, returns a pass/fail dict + + :param: + :return: + """ + # init the return value: + ret_val = None + + # build the query + sql = f"SELECT public.add_user(_email:={kwargs['email']}, _password_hash:={kwargs['password_hash']}, _role_id:={kwargs['role_id']}, _details:={kwargs['details']});" + + # get the info + ret_val = self.exec_sql('apsviz', sql) + + # Return Pandas dataframe + return ret_val + + def update_user(self, **kwargs) -> dict: + """ + Updates the user profile and returns a pass/fail dict + + if the call is successful, returns a pass/fail dict + + :param : + + :return: + """ + # init the return value: + ret_val = None + + # create the SQL query + sql = f"SELECT public.update_user({kwargs['email']}, _password_hash:={kwargs['password_hash']}, _role_id:={kwargs['role_id']}, _details:={kwargs['details']});" + + # get the info + ret_val = self.exec_sql('apsviz', sql) + + # Return Pandas dataframe + return ret_val diff --git a/src/common/pg_utils_multi.py b/src/common/pg_utils_multi.py index 5f5bd5a..efc0092 100644 --- a/src/common/pg_utils_multi.py +++ b/src/common/pg_utils_multi.py @@ -251,7 +251,7 @@ def exec_sql(self, db_name: str, sql_stmt: str): # insure we have a valid DB connection success = self.get_db_connection(db_info) - # did we get a connection + # if we get a connection if success: # init the cursor cursor = None @@ -274,7 +274,7 @@ def exec_sql(self, db_name: str, sql_stmt: str): # specify a return code on an empty result ret_val = -1 else: - # get the one and only record of json + # get the result payload ret_val = ret_val[0] except Exception: diff --git a/src/server.py b/src/server.py index 3d250de..20abdf7 100644 --- a/src/server.py +++ b/src/server.py @@ -69,7 +69,7 @@ async def get_ui_data(grid_type: Union[str, None] = Query(default=None), event_t limit: Union[int, None] = Query(default=7), use_new_wb: Union[bool, None] = Query(default=False), use_v3_sp: Union[bool, None] = Query(default=False)) -> json: """ - Gets the json formatted map UI catalog data. + Gets the JSON formatted map UI catalog data.
Note: Leave filtering params empty if not desired.
   grid_type: Filter by the name of the ECFLOW grid
   event_type: Filter by the event type @@ -94,7 +94,8 @@ async def get_ui_data(grid_type: Union[str, None] = Query(default=None), event_t try: logger.debug('Params - grid_type: %s, event_type: %s, instance_name: %s, met_class: %s, storm_name: %s, cycle: %s, advisory_number: %s, ' 'run_date: %s, end_date: %s, project_code %s, product_type: %s, limit: %s, ensemble_name: %s', grid_type, event_type, - instance_name, met_class, storm_name, cycle, advisory_number, run_date, end_date, project_code, product_type, limit, ensemble_name) + instance_name, met_class, storm_name, cycle, advisory_number, run_date, end_date, project_code, product_type, limit, + ensemble_name) # init the kwargs variable kwargs: dict = {} @@ -264,7 +265,7 @@ async def get_ui_data_secure(run_id: Union[str, None] = Query(default=None), gri product_type: Union[str, None] = Query(default=None), limit: Union[int, None] = Query(default=7), use_new_wb: Union[bool, None] = Query(default=False), use_v3_sp: Union[bool, None] = Query(default=False), ) -> json: """ - Gets the json formatted map UI catalog data. + Gets the JSON formatted map UI catalog data. 4460-2024020500-gfsforecast
Note: Leave filtering params empty if not desired.
   run_id: Filter by the run ID @@ -353,7 +354,7 @@ async def get_ui_data_file(file_name: Union[str, None] = Query(default='apsviz.j product_type: Union[str, None] = Query(default=None), limit: Union[int, None] = Query(default=7), use_new_wb: Union[bool, None] = Query(default=False), use_v3_sp: Union[bool, None] = Query(default=False), ) -> json: """ - Returns the json formatted map UI catalog data in a file specified. + Returns the JSON formatted map UI catalog data in a file specified.
Note: Leave filtering params empty if not desired.
   file_name: The name of the output file (default is apsviz.json)
   grid_type: Filter by the name of the grid @@ -644,7 +645,7 @@ async def get_station_data_file(file_name: Union[str, None] = Query(default='sta async def get_catalog_member_records(run_id: Union[str, None] = Query(default=None), project_code: Union[str, None] = Query(default=None), filter_event_type: Union[str, None] = Query(default=None), limit: Union[int, None] = Query(default=4)) -> json: """ - Gets the json formatted catalog member data. + Gets the JSON formatted catalog member data.
Note: Leave filtering params empty if not desired.
   run_id: Filter by the name of the ECFLOW grid. Leaving this empty will result in getting the latest records.
   project_code: Filter by the project code. @@ -713,7 +714,7 @@ async def get_pulldown_data(grid_type: Union[str, None] = Query(default=None), e end_date: Union[str, None] = Query(default=None), project_code: Union[str, None] = Query(default=None), product_type: Union[str, None] = Query(default=None), psc_output: bool = False) -> json: """ - Gets the json formatted UI pulldown data. + Gets the JSON formatted UI pulldown data.
Note: Leave filtering params empty if not desired.
   grid_type: Filter by the name of the ECFLOW grid
   event_type: Filter by the event type @@ -778,3 +779,156 @@ async def get_pulldown_data(grid_type: Union[str, None] = Query(default=None), e # return to the caller return JSONResponse(content=ret_val, status_code=status_code, media_type="application/json") + + +@APP.get('/verify_user', status_code=200, response_model=None) +async def verify_user(email: Union[str, None] = Query(default=None), password_hash: Union[str, None] = Query(default=None)): + """ + Verifies that the user exists and returns their profile if they do. + +
   The user's email address +
   The user's password (hashed) + """ + # pylint: disable=locally-disabled, unused-argument + + # init the returned data and HTML status code + ret_val: dict = {} + status_code: int = 200 + + try: + # try to make the call for records + ret_val: dict = db_info.verify_user(email, password_hash) + + # check the return + if ret_val['success']: + # create the JWT payload + payload = {'bearer_name': os.environ.get("BEARER_NAME"), 'bearer_secret': os.environ.get("BEARER_SECRET")} + + # create an access token + token = security.sign_jwt(payload) + + # create a new dict element with the JWT token + ret_val['token'] = token['access_token'] + # the verification was not successful + else: + ret_val = {'Error': "Could not verify the user's credentials."} + + # set the status to a server error + status_code = 404 + + except Exception: + # return a failure message + ret_val = {'Error': 'Exception detected trying to verify the user.'} + + # log the exception + logger.exception(ret_val) + + # set the status to a server error + status_code = 500 + + # return to the caller + return JSONResponse(content=ret_val, status_code=status_code, media_type="application/json") + + +@APP.get('/update_user', status_code=200, response_model=None) +async def update_user(email: Union[str, None] = Query(default=None), password_hash: Union[str, None] = Query(default=None), + role_id: Union[str, None] = Query(default=None), details: Union[str, None] = Query(default=None)): + """ + update_user the user profile. +
   The user's email address +
   The user's password (hashed) +
   The user's role +
   The user's details + """ + # pylint: disable=locally-disabled, unused-argument + + # init the returned data and HTML status code + ret_val: dict = {} + status_code: int = 200 + + try: + # init the kwargs variable + kwargs: dict = {} + + # create the param list + params: list = ['email', 'password_hash', 'role_id', 'details'] + + # loop through the SP params passed in + for param in params: + # add this parm to the list + kwargs.update({param: 'null' if not locals()[param] else f"'{locals()[param]}'"}) + + # try to make the call for records + ret_val: dict = db_info.update_user(**kwargs) + + # check the return + if not ret_val['success']: + ret_val = {'Error': 'Database error updating the users information.'} + + # set the status to a server error + status_code = 500 + + except Exception: + # return a failure message + ret_val = {'Error': 'Exception detected trying to update the user profile.'} + + # log the exception + logger.exception(ret_val) + + # set the status to a server error + status_code = 500 + + # return to the caller + return JSONResponse(content=ret_val, status_code=status_code, media_type="application/json") + + +@APP.get('/add_user', status_code=200, response_model=None) +async def add_user(email: Union[str, None] = Query(default=None), password_hash: Union[str, None] = Query(default=None), + role_id: Union[str, None] = Query(default=None), details: Union[str, None] = Query(default=None)): + """ + Adds the user and their profile. +
   The user's email address +
   The user's password (hashed) +
   The user's role +
   The user's details + + """ + # pylint: disable=locally-disabled, unused-argument + + # init the returned data and HTML status code + ret_val: dict = {} + status_code: int = 200 + + try: # init the kwargs variable + kwargs: dict = {} + + # create the param list + params: list = ['email', 'password_hash', 'role_id', 'details'] + + # loop through the SP params passed in + for param in params: + # add this parm to the list + kwargs.update({param: 'null' if not locals()[param] else f"'{locals()[param]}'"}) + + # try to make the call for records + ret_val: dict = db_info.add_user(**kwargs) + + # check the return + if not ret_val['success']: + ret_val = {'Error': 'Database error adding the user.'} + + # set the status to a server error + status_code = 500 + + except Exception: + # return a failure message + ret_val = {'Error': 'Exception detected trying to add the user.'} + + # log the exception + logger.exception(ret_val) + + # set the status to a server error + status_code = 500 + + # return to the caller + return JSONResponse(content=ret_val, status_code=status_code, media_type="application/json")