Working on user module

This commit is contained in:
Scott Idem
2021-03-19 16:34:38 +00:00
parent c620ce5f18
commit affec1bf37
2 changed files with 152 additions and 72 deletions

View File

@@ -108,7 +108,7 @@ def sql_insert(sql:str=None, data:dict=None, table_name:str=None, id_random_leng
# ### BEGIN ### Core Help CRUD ### sql_update() ###
def sql_update(sql:str=None, data:dict=None, table_name:str=None, record_id:int=None, record_id_random:str=None, rm_id_random=None, id_random_length:int=8):
def sql_update(sql:str=None, data:dict=None, table_name:str=None, record_id:int=None, record_id_random:str=None, rm_id_random=None, id_random_length:None|int=8):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())

View File

@@ -11,6 +11,7 @@ from app.db_sql import *
from .api_crud import delete_obj_template, get_obj_template, get_obj_li_template, patch_obj_template, post_obj_template
from ..models.common_field_schema import default_num_bytes
from ..models.user_model import User_Base, User_New_Base, User_Out_Base
from ..models.user_methods import load_user_obj
from ..models.response_model import *
@@ -77,6 +78,51 @@ async def post_user_new_obj(
return mk_resp(data=False, status_message='The user account was not created. Something seems to have gone wrong on insert.')
@router.patch('/change_password/{user_id}', response_model=Resp_Body_Base)
async def change_user_obj_password(
user_id: Union[int,str],
password: Optional[str] = Query(None, min_length=6, max_length=50),
x_account_id: Optional[str] = Header(..., ),
return_obj: bool = False,
inc_contact: bool = False,
inc_organization: bool = False,
inc_person: bool = False,
by_alias: bool = True,
exclude_unset: bool = True,
):
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if password and len(password) >= 10: pass
else:
log.warning('The password given must be at least 10 characters. Generating a new random password.')
password = secrets.token_urlsafe(default_num_bytes)
if user_id := redis_lookup_id_random(record_id_random=user_id, table_name='user'): pass
else: return mk_resp(data=False, status_code=404) # Not Found
user_data = {}
#user_data['user_id'] = user_id
#user_data['username'] = username #????
user_data['password'] = secure_hash_string(string=password)
table_name = 'user'
user_rec_update_result = sql_update(data=user_data, table_name=table_name, record_id=user_id, id_random_length=None)
if return_obj:
user_obj = load_user_obj(
user_id=user_id,
inc_contact=inc_contact,
inc_organization=inc_organization,
inc_person=inc_person
).dict(by_alias=by_alias, exclude_unset=exclude_unset)
data = user_obj
else:
data = True
return mk_resp(data=data)
#return mk_resp(data=None, status_code=501) # Not Implemented
@router.patch('/{obj_id}', response_model=Resp_Body_Base)
async def patch_user_obj(
obj_id: str = Query(..., min_length=1, max_length=22),
@@ -104,72 +150,114 @@ async def patch_user_obj(
return result
# This will look up a user based on the auth key given
# This can only be done once per key. It will be deleted if found
# A new one will need to be requested for a particular user each time
@router.get('/authenticate/auth_key/{auth_key}', response_model=Resp_Body_Base)
async def auth_key_get_user_obj(
auth_key: str = Query(..., min_length=11, max_length=22),
# ### BEGIN ### API User Routers ### user_authenticate() ###
# Authenticate a username and password OR by authorization key
# An authorization key can only be done once. It will be deleted if found.
# A new key will need to be requested for a particular user each time.
@router.get('/authenticate', response_model=Resp_Body_Base)
async def user_authenticate(
account_id: Optional[Union[int,str]] = None,
username: Optional[str] = Query(None, min_length=2, max_length=50),
password: Optional[str] = Query(None, min_length=6, max_length=50),
auth_key: Optional[str] = Query(None, min_length=11, max_length=22),
x_account_id: str = Header(...),
by_alias: Optional[bool] = True,
exclude_unset: Optional[bool] = True,
exclude_none: Optional[bool] = True,
inc_contact: bool = False,
inc_organization: bool = False,
inc_person: bool = False,
by_alias: bool = True,
exclude_unset: bool = True,
exclude_none: bool = True,
):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if sql_select_result := sql_select(table_name='user', field_name='auth_key', field_value=auth_key):
log.debug(sql_select_result)
if account_id and username and password:
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return mk_resp(data=False, status_code=404) # Not Found
resp_data = {}
resp_data['user_id_random'] = sql_select_result.get('id_random')
resp_data['username'] = sql_select_result.get('username')
resp_data['enable'] = sql_select_result.get('enable')
resp_data['enable_from'] = sql_select_result.get('enable_from')
resp_data['enable_to'] = sql_select_result.get('enable_to')
try:
user_obj = User_Base(**sql_select_result).dict(by_alias=by_alias, exclude_unset=exclude_unset, exclude_none=exclude_none)
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(user_obj)
log.debug(user_obj.get('enable_from', None))
except ValidationError as e:
log.error(e.json())
user_data = {}
user_data['account_id'] = account_id
user_data['username'] = username
sql_select(table_name='user', data=user_data)
sql = f"""
SELECT `user`.id AS 'user_id', `user`.id_random AS 'user_id_random', `user`.password, `user`.enable, `user`.enable_from, `user`.enable_to
FROM `user` AS `user`
WHERE `user`.account_id = :account_id AND `user`.username = :username
LIMIT 1
"""
# This will return a list if selecting by account ID
if user_rec_result := sql_select(data=user_data, sql=sql):
user_id = user_rec_result.get('user_id', None)
if password_hash := user_rec_result.get('password', None):
if verify_secure_hash_string(string=password, string_hash=password_hash):
log.info('The username was found, and the password matched.')
#return mk_resp(data=False, status_message='The username was found, and the password matched.')
else:
log.info('The username was found, but the password did not match.')
return mk_resp(data=False, status_message='The username was found, but the password did not match.')
else:
log.error('The password has was not found. This should not happen.')
return mk_resp(data=False, status_message='The password has was not found. This should not happen.')
else: return mk_resp(data=None, status_code=404, status_message='The user account was not found')
elif auth_key:
if user_rec_result := sql_select(table_name='user', field_name='auth_key', field_value=auth_key):
update_user_data = {}
update_user_data['id'] = user_rec_result.get('id', None)
update_user_data['auth_key'] = None
if user_rec_update_result := sql_update(table_name='user', data=update_user_data):
log.info('The user record was updated with a NULL auth_key')
else:
log.info('The user record was not updated with a NULL auth_key')
log.debug(user_rec_update_result)
user_id = user_rec_result.get('id', None) # NOTE: This us looking for "id", not "user_id"
else: return mk_resp(data=None, status_code=404, status_message='A user account with that auth key was not found')
else:
return mk_resp(data=None, status_code=400, status_message='One more user account fields was missing or unexpected.') # Bad Request
log.debug(user_rec_result)
if isinstance(user_rec_result, dict):
current_utc_datetime = datetime.datetime.now(datetime.timezone.utc)
log.debug(user_obj.get('enable_from', None).astimezone(pytz.UTC))
user_enable_from = user_obj.get('enable_from', None).astimezone(pytz.UTC)
log.debug(user_enable_from)
log.debug(user_obj.get('enable_to', None))
user_enable_to = user_obj.get('enable_to', None).astimezone(pytz.UTC)
log.debug(user_enable_to)
if resp_data['enable']: pass
if user_rec_result.get('enable', None):
log.info('The user account is enabled')
else:
log.info('The user account has been disabled')
if user_enable_from <= current_utc_datetime:
log.info('Enable from datetime is valid')
else:
log.info('Enable from datetime is in the future. Please wait.')
if user_enable_to >= current_utc_datetime:
log.info('Enable to datetime is valid')
else:
log.info('Enable to datetime is in the past. Your user account has been disabled.')
log.info('The user account is not enabled')
return mk_resp(data=False, status_message='This user account is not enabled')
update_data = {}
update_data['id'] = sql_select_result.get('id')
update_data['auth_key'] = None
if user_enable_from := user_rec_result.get('enable_from', None).astimezone(pytz.UTC):
if user_enable_from <= current_utc_datetime:
log.info('Enable from datetime is valid')
else:
log.info('Enable from datetime is in the future. Please wait.')
return mk_resp(data=False, status_message='This account is not yet enabled')
if sql_update_resp := sql_update(table_name='user', data=update_data):
log.info('The user record was updated with a NULL auth_key')
else:
log.info('The user record was not updated with a NULL auth_key')
log.debug(sql_update_resp)
if user_enable_to := user_rec_result.get('enable_to', None).astimezone(pytz.UTC):
if user_enable_to >= current_utc_datetime:
log.info('Enable to datetime is valid')
else:
log.info('Enable to datetime is in the past. Your user account has been disabled.')
return mk_resp(data=False, status_message='This account is not enabled because the expiratation date has passed')
user_obj = load_user_obj(
user_id=user_id,
inc_contact=inc_contact,
inc_organization=inc_organization,
inc_person=inc_person
).dict(by_alias=by_alias, exclude_unset=exclude_unset)
data = user_obj
return mk_resp(data=user_obj)
else:
return mk_resp(data=None, status_code=404)
log.error('SQL result was unexpected. A dict result type was expected. This should not happen.')
return mk_resp(data=False, status_code=500)
# ### END ### API User Routers ### user_authenticate() ###
@router.get('/list', response_model=Resp_Body_Base)
@@ -239,9 +327,9 @@ async def lookup_user_obj(
"""
# This will return a list if selecting by account ID
user_obj_result = sql_select(data=data, sql=sql, as_list=as_list)
if isinstance(user_obj_result, dict):
user_id = user_obj_result.get('user_id', None)
user_rec_result = sql_select(data=data, sql=sql, as_list=as_list)
if isinstance(user_rec_result, dict):
user_id = user_rec_result.get('user_id', None)
user_obj = load_user_obj(
user_id=user_id,
inc_contact=inc_contact,
@@ -249,9 +337,9 @@ async def lookup_user_obj(
inc_person=inc_person
).dict(by_alias=by_alias, exclude_unset=exclude_unset)
data = user_obj
elif isinstance(user_obj_result, list):
elif isinstance(user_rec_result, list):
user_obj_li = []
for user_obj in user_obj_result:
for user_obj in user_rec_result:
user_id = user_obj.get('user_id', None)
user_obj_li.append(
load_user_obj(
@@ -263,7 +351,7 @@ async def lookup_user_obj(
)
data = user_obj_li
else:
log.debug(user_obj_result)
log.debug(user_rec_result)
return mk_resp(data=None, status_code=404) # Not Found
return mk_resp(data=data)
@@ -327,23 +415,15 @@ async def lookup_username_obj(
return mk_resp(data=data)
# Authenticate a username and password
@router.get('/authenticate', response_model=Resp_Body_Base)
async def user_authenticate(
username: str = Query(..., min_length=2, max_length=50),
password: str = Query(..., min_length=6, max_length=50),
x_account_id: str = Header(...),
):
return mk_resp(data=None, status_code=501) # Not Implemented
@router.get('/{obj_id}', response_model=Resp_Body_Base)
async def get_user_obj(
obj_id: str = Query(..., min_length=1, max_length=22),
x_account_id: str = Header(...),
by_alias: Optional[bool] = True,
exclude_unset: Optional[bool] = True,
inc_contact: bool = False,
inc_organization: bool = False,
inc_person: bool = False,
by_alias: bool = True,
exclude_unset: bool = True,
):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())