edit : 기본 rest서버구조 추가

This commit is contained in:
2025-01-14 17:26:27 +09:00
parent e53282d7e7
commit bd28fcdca4
37 changed files with 4194 additions and 1 deletions

93
rest/app/routes/auth.py Normal file
View File

@@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
"""
@File: auth.py
@Date: 2020-09-14
@author: A2TEC
@section MODIFYINFO 수정정보
- 수정자/수정일 : 수정내역
- 2022-01-14/hsj100@a2tec.co.kr : refactoring
@brief: authentication api
"""
from itertools import groupby
from operator import attrgetter
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
import bcrypt
import jwt
from datetime import datetime, timedelta
from rest.app.common import consts
from rest.app import models as M
from rest.app.database.conn import db
from rest.app.common.config import conf
from rest.app.database.schema import Users, UserLog
from rest.app.utils.extra import query_to_groupby, AESCryptoCBC
from rest.app.utils.date_utils import D
router = APIRouter(prefix='/auth')
@router.get('/find-account/{account}', response_model=M.ResponseBase, summary='계정유무 검사')
async def find_account(account: str):
"""
## 계정유무 검사
주어진 계정이 존재하면 true, 없으면 false 처리
**결과**
- ResponseBase
"""
try:
search_info = Users.get(account=account)
if not search_info:
raise Exception(f'not found data: {account}')
return M.ResponseBase()
except Exception as e:
return M.ResponseBase.set_error(str(e))
@router.post('/logout/{account}', status_code=200, response_model=M.TokenRes, summary='사용자 접속종료')
async def logout(account: str):
"""
## 사용자 접속종료
현재 버전에서는 로그인/로그아웃의 상태를 유지하지 않고 상태값만을 서버에서 사용하기 때문에,\n
***로그상태는 실제상황과 다를 수 있다.***
정상처리시 Authorization(null) 반환
**결과**
- TokenRes
"""
user_info = None
try:
# TODO(hsj100): LOGIN_STATUS
user_info = Users.filter(account=account)
if not user_info:
raise Exception('not found user')
user_info.update(auto_commit=True, login='logout')
return M.TokenRes()
except Exception as e:
if user_info:
user_info.close()
return M.ResponseBase.set_error(e)
async def is_account_exist(account: str):
get_account = Users.get(account=account)
return True if get_account else False
def create_access_token(*, data: dict = None, expires_delta: int = None):
if conf().GLOBAL_TOKEN:
return conf().GLOBAL_TOKEN
to_encode = data.copy()
if expires_delta:
to_encode.update({'exp': datetime.utcnow() + timedelta(hours=expires_delta)})
encoded_jwt = jwt.encode(to_encode, consts.JWT_SECRET, algorithm=consts.JWT_ALGORITHM)
return encoded_jwt

137
rest/app/routes/dev.py Normal file
View File

@@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
"""
@File: dev.py
@Date: 2020-09-14
@author: A2TEC
@section MODIFYINFO 수정정보
- 수정자/수정일 : 수정내역
- 2022-01-14/hsj100@a2tec.co.kr : refactoring
@brief: Developments Test
"""
import struct
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
import bcrypt
from starlette.requests import Request
from rest.app.common import consts
from rest.app import models as M
from rest.app.database.conn import db, Base
from rest.app.database.schema import Users, UserLog
from rest.app.utils.extra import FernetCrypto, AESCryptoCBC, AESCipher
from custom_logger.custom_log import custom_logger as LOG
# mail test
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
def send_mail():
"""
구글 계정사용시 : 보안 수준이 낮은 앱에서의 접근 활성화
:return:
"""
sender = 'jolimola@gmail.com'
sender_pw = '!ghkdtmdwns1'
# recipient = 'hsj100@a2tec.co.kr'
recipient = 'jwkim@daooldns.co.kr'
list_cc = ['cc1@gmail.com', 'cc2@naver.com']
str_cc = ','.join(list_cc)
title = 'Test mail'
contents = '''
This is test mail
using smtplib.
'''
smtp_server = smtplib.SMTP( # 1
host='smtp.gmail.com',
port=587
)
smtp_server.ehlo() # 2
smtp_server.starttls() # 2
smtp_server.ehlo() # 2
smtp_server.login(sender, sender_pw) # 3
msg = MIMEMultipart() # 4
msg['From'] = sender # 5
msg['To'] = recipient # 5
# msg['Cc'] = str_cc # 5
msg['Subject'] = contents # 5
msg.attach(MIMEText(contents, 'plain')) # 6
smtp_server.send_message(msg) # 7
smtp_server.quit() # 8
router = APIRouter(prefix='/dev')
@router.get('/test', summary='테스트', response_model=M.SWInfo)
async def test(request: Request):
"""
## ELB 상태 체크용 API
**결과**
- SWInfo
"""
a = M.SWInfo()
a.name = '!ekdnfeldpsdptm1'
# a.name = 'testtesttest123'
simpleEnDecrypt = FernetCrypto()
a.data1 = simpleEnDecrypt.encrypt(a.name)
a.data2 = bcrypt.hashpw(a.name.encode('utf-8'), bcrypt.gensalt())
t = bytes(a.name.encode('utf-8'))
enc = AESCryptoCBC().encrypt(t)
dec = AESCryptoCBC().decrypt(enc)
t = enc.decode('utf-8')
# enc = AESCipher('daooldns12345678').encrypt(a.name).decode('utf-8')
# enc = 'E563ZFt+yJL8YY5yYlYyk602MSscPP2SCCD8UtXXpMI='
# dec = AESCipher('daooldns12345678').decrypt(enc).decode('utf-8')
a.data3 = f'enc: {enc}, {t}'
a.data4 = f'dec: {dec}'
a.name = '!ekdnfeldpsdptm1'
# simpleEnDecrypt = SimpleEnDecrypt()
a.data5 = simpleEnDecrypt.encrypt(a.name)
a.data6 = bcrypt.hashpw(a.name.encode('utf-8'), bcrypt.gensalt())
key = consts.ADMIN_INIT_ACCOUNT_INFO['aes_cbc_key']
t = bytes(a.name.encode('utf-8'))
enc = AESCryptoCBC(key).encrypt(t)
dec = AESCryptoCBC(key).decrypt(enc)
t = enc.decode('utf-8')
# enc = AESCipher('daooldns12345678').encrypt(a.name).decode('utf-8')
# enc = 'E563ZFt+yJL8YY5yYlYyk602MSscPP2SCCD8UtXXpMI='
# dec = AESCipher('daooldns12345678').decrypt(enc).decode('utf-8')
print(f'key: {key}')
a.data7 = f'enc: {enc}, {t}'
a.data8 = f'dec: {dec}'
eee = "gAAAAABioV5NucuS9nQugZJnz-KjVG_FGnaowB9KAfhOoWjjiQ4jGLuYJh4Qe94mT_lCm6m3HhuOJqUeOgjppwREDpIQYzrUXA=="
a.data8 = simpleEnDecrypt.decrypt(eee)
return a

32
rest/app/routes/index.py Normal file
View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
"""
@File: index.py
@Date: 2020-09-14
@author: A2TEC
@section MODIFYINFO 수정정보
- 수정자/수정일 : 수정내역
- 2022-01-14/hsj100@a2tec.co.kr : refactoring
@brief: basic & test api
"""
from fastapi import APIRouter
from rest.app.utils.date_utils import D
from rest.app.models import SWInfo
router = APIRouter()
@router.get('/', summary='서비스 정보', response_model=SWInfo)
async def index():
"""
## 서비스 정보
소프트웨어 이름, 버전정보, 현재시간
**결과**
- SWInfo
"""
sw_info = SWInfo()
sw_info.date = D.date_str()
return sw_info

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
@File: services.py
@Date: 2020-09-14
@author: A2TEC
@section MODIFYINFO 수정정보
- 수정자/수정일 : 수정내역
- 2022-01-14/hsj100@a2tec.co.kr : refactoring
@brief: services api
"""
import requests, json, traceback
from fastapi import APIRouter, Depends, Body
from starlette.requests import Request
from typing import Annotated, List
from rest.app.common import consts
from rest.app import models as M
from rest.app.utils.date_utils import D
from custom_logger.custom_log import custom_logger as LOG
router = APIRouter(prefix="/services")

298
rest/app/routes/users.py Normal file
View File

@@ -0,0 +1,298 @@
# -*- coding: utf-8 -*-
"""
@File: users.py
@Date: 2020-09-14
@author: A2TEC
@section MODIFYINFO 수정정보
- 수정자/수정일 : 수정내역
- 2022-01-14/hsj100@a2tec.co.kr : refactoring
@brief: users api
"""
from fastapi import APIRouter
from starlette.requests import Request
import bcrypt
from rest.app.common import consts
from rest.app import models as M
from rest.app.common.config import conf
from rest.app.database.schema import Users
from rest.app.database.crud import table_select, table_update, table_delete
from rest.app.utils.extra import AESCryptoCBC
router = APIRouter(prefix='/user')
@router.get('/me', response_model=M.UserSearchRes, summary='접속자 정보')
async def get_me(request: Request):
"""
## 현재 접속된 자신정보 확인
***현 버전 미지원(추후 상세 처리)***
**결과**
- UserSearchRes
"""
target_table = Users
search_info = None
try:
# request
if conf().GLOBAL_TOKEN:
raise Exception('not supported: use search api!')
accessor_info = request.state.user
if not accessor_info:
raise Exception('invalid accessor')
# search
search_info = target_table.get(account=accessor_info.account)
if not search_info:
raise Exception('not found data')
# result
result_info = list()
result_info.append(M.UserInfo.from_orm(search_info))
return M.UserSearchRes(data=result_info)
except Exception as e:
if search_info:
search_info.close()
return M.ResponseBase.set_error(str(e))
@router.post('/search', response_model=M.UserSearchPagingRes, summary='유저정보 검색')
async def user_search(request: Request, request_body_info: M.UserSearchPagingReq):
"""
## 유저정보 검색 (기본)
검색 조건은 유저 테이블 항목만 가능 (Request body: Schema 참조)\n
관련 정보 연동 검색은 별도 API 사용
**세부항목**
- **paging**\n
항목 미사용시에는 페이징기능 없이 검색조건(search) 결과 모두 반환
- **search**\n
검색에 필요한 항목들을 search 에 포함시킨다.\n
검색에 사용된 각 항목들은 AND 조건으로 처리된다.
- **전체검색**\n
empty object 사용 ( {} )\n
예) "search": {}
- **검색항목**\n
- 부분검색 항목\n
SQL 문법( %, _ )을 사용한다.\n
__like: 시작포함(X%), 중간포함(%X%), 끝포함(%X)
- 구간검색 항목
* __lt: 주어진 값보다 작은값
* __lte: 주어진 값보다 같거나 작은 값
* __gt: 주어진 값보다 큰값
* __gte: 주어진 값보다 같거나 큰값
**결과**
- UserSearchPagingRes
"""
return await table_select(request.state.user, Users, request_body_info, M.UserSearchPagingRes, M.UserInfo)
@router.put('/update', response_model=M.ResponseBase, summary='유저정보 변경')
async def user_update(request: Request, request_body_info: M.UserUpdateMultiReq):
"""
## 유저정보 변경
**search_info**: 변경대상\n
**update_info**: 변경내용\n
- **비밀번호** 제외
**결과**
- ResponseBase
"""
return await table_update(request.state.user, Users, request_body_info, M.ResponseBase)
@router.put('/update_pw', response_model=M.ResponseBase, summary='유저 비밀번호 변경')
async def user_update_pw(request: Request, request_body_info: M.UserUpdatePWReq):
"""
## 유저정보 비밀번호 변경
**account**의 **비밀번호**를 변경한다.
**결과**
- ResponseBase
"""
target_table = Users
search_info = None
try:
# request
accessor_info = request.state.user
if not accessor_info:
raise Exception('invalid accessor')
if not request_body_info.account:
raise Exception('invalid account')
# decrypt pw
try:
decode_cur_pw = request_body_info.current_pw.encode('utf-8')
desc_cur_pw = AESCryptoCBC().decrypt(decode_cur_pw)
except Exception as e:
raise Exception(f'failed decryption [current_pw]: {e}')
try:
decode_new_pw = request_body_info.new_pw.encode('utf-8')
desc_new_pw = AESCryptoCBC().decrypt(decode_new_pw)
except Exception as e:
raise Exception(f'failed decryption [new_pw]: {e}')
# search
target_user = target_table.get(account=request_body_info.account)
is_verified = bcrypt.checkpw(desc_cur_pw, target_user.pw.encode('utf-8'))
if not is_verified:
raise Exception('invalid password')
search_info = target_table.filter(id=target_user.id)
if not search_info.first():
raise Exception('not found data')
# process
hash_pw = bcrypt.hashpw(desc_new_pw, bcrypt.gensalt())
result_info = search_info.update(auto_commit=True, pw=hash_pw)
if not result_info or not result_info.id:
raise Exception('failed update')
# result
return M.ResponseBase()
except Exception as e:
if search_info:
search_info.close()
return M.ResponseBase.set_error(str(e))
@router.delete('/delete', response_model=M.ResponseBase, summary='유저정보 삭제')
async def user_delete(request: Request, request_body_info: M.UserSearchReq):
"""
## 유저정보 삭제
조건에 해당하는 정보를 모두 삭제한다.\n
- **본 API는 DB에서 완적삭제를 하는 함수이며, 서버관리자가 사용하는 것을 권장한다.**
- **update API를 사용하여 상태 항목을 변경해서 사용하는 것을 권장.**
`유저삭제시 관계 테이블의 정보도 같이 삭제된다.`
**결과**
- ResponseBase
"""
return await table_delete(request.state.user, Users, request_body_info, M.ResponseBase)
# NOTE(hsj100): apikey
"""
"""
# @router.get('/apikeys', response_model=List[M.GetApiKeyList])
# async def get_api_keys(request: Request):
# """
# API KEY 조회
# :param request:
# :return:
# """
# user = request.state.user
# api_keys = ApiKeys.filter(user_id=user.id).all()
# return api_keys
#
#
# @router.post('/apikeys', response_model=M.GetApiKeys)
# async def create_api_keys(request: Request, key_info: M.AddApiKey, session: Session = Depends(db.session)):
# """
# API KEY 생성
# :param request:
# :param key_info:
# :param session:
# :return:
# """
# user = request.state.user
#
# api_keys = ApiKeys.filter(session, user_id=user.id, status='active').count()
# if api_keys == MAX_API_KEY:
# raise ex.MaxKeyCountEx()
#
# alphabet = string.ascii_letters + string.digits
# s_key = ''.join(secrets.choice(alphabet) for _ in range(40))
# uid = None
# while not uid:
# uid_candidate = f'{str(uuid4())[:-12]}{str(uuid4())}'
# uid_check = ApiKeys.get(access_key=uid_candidate)
# if not uid_check:
# uid = uid_candidate
#
# key_info = key_info.dict()
# new_key = ApiKeys.create(session, auto_commit=True, secret_key=s_key, user_id=user.id, access_key=uid, **key_info)
# return new_key
#
#
# @router.put('/apikeys/{key_id}', response_model=M.GetApiKeyList)
# async def update_api_keys(request: Request, key_id: int, key_info: M.AddApiKey):
# """
# API KEY User Memo Update
# :param request:
# :param key_id:
# :param key_info:
# :return:
# """
# user = request.state.user
# key_data = ApiKeys.filter(id=key_id)
# if key_data and key_data.first().user_id == user.id:
# return key_data.update(auto_commit=True, **key_info.dict())
# raise ex.NoKeyMatchEx()
#
#
# @router.delete('/apikeys/{key_id}')
# async def delete_api_keys(request: Request, key_id: int, access_key: str):
# user = request.state.user
# await check_api_owner(user.id, key_id)
# search_by_key = ApiKeys.filter(access_key=access_key)
# if not search_by_key.first():
# raise ex.NoKeyMatchEx()
# search_by_key.delete(auto_commit=True)
# return MessageOk()
#
#
# @router.get('/apikeys/{key_id}/whitelists', response_model=List[M.GetAPIWhiteLists])
# async def get_api_keys(request: Request, key_id: int):
# user = request.state.user
# await check_api_owner(user.id, key_id)
# whitelists = ApiWhiteLists.filter(api_key_id=key_id).all()
# return whitelists
#
#
# @router.post('/apikeys/{key_id}/whitelists', response_model=M.GetAPIWhiteLists)
# async def create_api_keys(request: Request, key_id: int, ip: M.CreateAPIWhiteLists, session: Session = Depends(db.session)):
# user = request.state.user
# await check_api_owner(user.id, key_id)
# import ipaddress
# try:
# _ip = ipaddress.ip_address(ip.ip_addr)
# except Exception as e:
# raise ex.InvalidIpEx(ip.ip_addr, e)
# if ApiWhiteLists.filter(api_key_id=key_id).count() == MAX_API_WHITELIST:
# raise ex.MaxWLCountEx()
# ip_dup = ApiWhiteLists.get(api_key_id=key_id, ip_addr=ip.ip_addr)
# if ip_dup:
# return ip_dup
# ip_reg = ApiWhiteLists.create(session=session, auto_commit=True, api_key_id=key_id, ip_addr=ip.ip_addr)
# return ip_reg
#
#
# @router.delete('/apikeys/{key_id}/whitelists/{list_id}')
# async def delete_api_keys(request: Request, key_id: int, list_id: int):
# user = request.state.user
# await check_api_owner(user.id, key_id)
# ApiWhiteLists.filter(id=list_id, api_key_id=key_id).delete()
#
# return MessageOk()
#
#
# async def check_api_owner(user_id, key_id):
# api_keys = ApiKeys.get(id=key_id, user_id=user_id)
# if not api_keys:
# raise ex.NoKeyMatchEx()