Files
OSIT-AE-API-FastAPI/app/routers/api.py
2021-08-10 19:06:40 -04:00

307 lines
12 KiB
Python

import datetime
#from datetime import datetime, time, timedelta
from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query, Response, status
from pydantic import BaseModel, EmailStr, Field
from typing import Dict, List, Optional, Set, Union
from app.lib_general import log, logging, sign_jwt, decode_jwt
from app.config import settings
from app.db_sql import sql_insert, sql_update, sql_insert_or_update, sql_select, sql_delete, redis_lookup_id_random
from app.routers.api_crud import delete_obj_template, get_obj_template, get_obj_li_template, patch_obj_template, post_obj_template
from app.models.api_models import Api_Base
from app.models.response_models import Resp_Body_Base, mk_resp
router = APIRouter()
# ### BEGIN ### API API ### request_jwt() ###
# Generate JWT using associated API private key
# Verify JWT using the API public key's associated API private key
# API server or trusted app can generate JWTs
# JWT contains:
# * client_token (to request a new short term client token)
# * iat
# * eat
# * account_id
# * client_id
# * order_cart_id
# * person_id
# * user_id
# API server verifies JWTs
# Updated 2021-07-14
@router.get('/request_jwt', response_model=Resp_Body_Base)
async def request_jwt(
x_aether_api_secret_key: Optional[str] = Header(None, min_length=22, max_length=22), # If passed then can also set TTL
x_aether_api_public_key: Optional[str] = Header(None, min_length=22, max_length=22), # Used to look up the API secret if not given
x_aether_api_token: Optional[str] = Header(None), # Token given to client by an API key holder (short max TTL)
account_id: str = None,
session_id: str = None, # End client (web browser)
client_id: str = None, # End client (web browser)
person_id: str = None,
user_id: str = None,
max_ttl: int = 300, # Number of seconds to live. Only use if given the API secret key.
# Seconds: 3600 = 1 hr; 300 = 5 min
max_renew: int = 5, # Decrease count by 1 until 0 if only sent a current API token.
response: Response = Response,
):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if x_aether_api_secret_key or x_aether_api_token: pass
else: return mk_resp(data=False, status_code=400, response=response) # Bad Request
if not x_aether_api_secret_key: max_ttl = 300 # Override any max_ttl if no API secret
if not x_aether_api_secret_key: max_renew = 5 # Override any max_rewnew if no API secret
api_secret_key = x_aether_api_secret_key
if x_aether_api_secret_key:
log.debug(f'Contains a value in x_aether_api_secret_key: {x_aether_api_secret_key}')
table_name_select = 'api_key'
field_name = 'secret_key'
field_value = api_secret_key
if api_key_rec_select_result := sql_select(table_name=table_name_select, field_name=field_name, field_value=field_value): pass
else:
log.warning('No results when looking up the API secret key')
return mk_resp(data=False, status_code=401, response=response) # Unauthorized
elif x_aether_api_public_key and x_aether_api_token:
table_name_select = 'api_key'
field_name = 'public_key'
field_value = x_aether_api_public_key
if api_key_rec_select_result := sql_select(table_name=table_name_select, field_name=field_name, field_value=field_value): pass
else:
log.warning('No results when looking up the API public key')
return mk_resp(data=False, status_code=401, response=response) # Unauthorized
# Check if the API keys are valid
if api_key_rec_select_result.get('enable', None):
api_key_rec = api_key_rec_select_result
else:
log.warning('API secret key not enabled')
return mk_resp(data=False, status_code=401, response=response) # Unauthorized
current_datetime = datetime.datetime.utcnow() # datetime.datetime.now() Gets server local datetime
if api_key_rec.get('enable_from', None) <= current_datetime and api_key_rec.get('enable_to', None) >= current_datetime:
pass
else:
log.warning('API secret key expired')
return mk_resp(data=False, status_code=401, response=response) # Unauthorized
if api_secret_key := api_key_rec.get('secret_key', None): pass
else:
log.warning('Secret key was not found')
return mk_resp(data=False, status_code=400, response=response) # Bad Request
if api_public_key := api_key_rec.get('public_key', None): pass
else:
log.warning('Public key was not found')
return mk_resp(data=False, status_code=400, response=response) # Bad Request
# Decode the JWT if an API token was sent and the API secret key was sent/found.
if x_aether_api_token and api_public_key and api_secret_key:
if current_token := decode_jwt(secret_key=api_secret_key, token=x_aether_api_token):
if current_token.get('max_renew', 0) > 0: pass
else:
message = 'The JWT sent is out of allowed renewals. Try again with a current JWT or just the API secret key.'
log.warning(message)
return mk_resp(data=False, status_code=401, status_message=message) # Unauthorized
max_renew = current_token.get('max_renew', 0) - 1
if not account_id: account_id = current_token.get('account_id', None)
if not person_id: person_id = current_token.get('person_id', None)
if not user_id: user_id = current_token.get('user_id', None)
else:
message = 'The JWT sent is either expired or otherwise invalid. Try again with a current JWT or just the API secret key.'
log.warning(message)
return mk_resp(data=False, status_code=401, status_message=message) # Unauthorized
payload = {}
payload['account_id'] = account_id
payload['person_id'] = person_id
payload['user_id'] = user_id
token = sign_jwt(secret_key=api_secret_key, public_key=api_public_key, ttl=max_ttl, max_renew=max_renew, **payload)
response_data = { 'api_access_jwt': token }
return mk_resp(data=response_data)
# ### END ### API API ### request_jwt() ###
@router.get('/temp_token', response_model=Resp_Body_Base)
async def get_api_temp_token(
x_aether_api_key: Optional[str] = Header(None),
x_aether_api_token: Optional[str] = Header(None),
x_aether_api_token_expire_on: Optional[str] = Header(None),
response: Response = Response,
):
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
table_name_select = 'api_key'
field_name = 'secret_key'
field_value = x_aether_api_key
if x_aether_api_key:
log.debug(f'Contains a value in x_aether_api_key: {x_aether_api_key}')
sql_result = sql_select(table_name=table_name_select, field_name=field_name, field_value=field_value)
else:
return mk_resp(data=False, status_code=400, response=response) # Bad Request
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
if sql_result:
log.debug(type(sql_result))
log.debug(sql_result)
base_name = Api_Base
resp_data = base_name(**sql_result).dict(by_alias=True, exclude_unset=False)
log.debug(resp_data)
return mk_resp(data=resp_data)
else:
log.debug(sql_result)
return mk_resp(data=False, status_code=404, response=response)
#obj_type = 'api'
#result = get_obj_template(
#obj_type=obj_type,
#obj_id=obj_id,
#by_alias=True,
#exclude_unset=True,
#)
#return result
@router.post('', response_model=Resp_Body_Base)
async def post_api_obj(
obj: Api_Base,
x_account_id: str = Header(...),
return_obj: Optional[bool] = True,
by_alias: Optional[bool] = True,
exclude_unset: Optional[bool] = True,
response: Response = Response,
):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
obj_type = 'api'
obj_data_dict = obj.dict(by_alias=False, exclude_unset=True)
result = post_obj_template(
obj_type=obj_type,
data=obj_data_dict,
return_obj=True,
by_alias=True,
exclude_unset=True,
)
return result
@router.patch('/{obj_id}', response_model=Resp_Body_Base)
async def patch_api_obj(
obj_id: str = Query(..., min_length=1, max_length=22),
obj: Api_Base = None,
x_account_id: Optional[str] = Header(..., ),
return_obj: Optional[bool] = True,
by_alias: Optional[bool] = True,
exclude_unset: Optional[bool] = True,
response: Response = Response,
):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
obj_type = 'api'
obj_data_dict = obj.dict(by_alias=False, exclude_unset=True)
obj_data_dict['id'] = redis_lookup_id_random(record_id_random=obj_id, table_name=obj_type)
obj_data_dict['id_random'] = obj_id
result = patch_obj_template(
obj_type=obj_type,
data=obj_data_dict,
obj_id=obj_id,
return_obj=True,
by_alias=True,
exclude_unset=True,
)
return result
@router.get('/list', response_model=Resp_Body_Base)
async def get_api_obj_li(
for_obj_type: Optional[str] = Query(None, min_length=2, max_length=50),
for_obj_id: Optional[str] = Query(None, min_length=1, max_length=22),
x_account_id: str = Header(...),
by_alias: Optional[bool] = True,
exclude_unset: Optional[bool] = True,
response: Response = Response,
):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
obj_type = 'api'
result = get_obj_li_template(
obj_type=obj_type,
for_obj_type=for_obj_type,
for_obj_id=for_obj_id,
by_alias=True,
exclude_unset=True,
)
return result
@router.get('/{obj_id}', response_model=Resp_Body_Base)
async def get_api_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,
response: Response = Response,
):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
obj_type = 'api'
result = get_obj_template(
obj_type=obj_type,
obj_id=obj_id,
by_alias=True,
exclude_unset=True,
)
return result
@router.delete('/{obj_id}', response_model=Resp_Body_Base)
async def delete_api_obj(
obj_id: str = Query(..., min_length=1, max_length=22),
x_account_id: str = Header(...),
response: Response = Response,
):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
obj_type = 'api'
result = delete_obj_template(
obj_type=obj_type,
obj_id=obj_id,
)
return result
@router.get('/get_id/{object_type}/{object_id_random}', response_model=Resp_Body_Base)
async def get_api_object_id(
object_type: str = Query(..., min_length=1, max_length=50),
object_id_random: str = Query(..., min_length=1, max_length=22),
x_account_id: str = Header(...),
by_alias: Optional[bool] = True,
exclude_unset: Optional[bool] = True,
response: Response = Response,
):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if object_id := redis_lookup_id_random(record_id_random=object_id_random, table_name=object_type):
return mk_resp(data={ 'object_id': object_id}, status_code=400)
else: return mk_resp(data=None, status_code=400)