premier commit

This commit is contained in:
Vincent BENOIT committed 2022-10-04 15:02:50 +02:00
1 parent b525495612
commit 10de8153aa
13 files changed
+700

No files matched your search

+1
View File
@@ -0,0 +1 @@
1.0.0
View File
Whitespace-only changes.
+131
View File
@@ -0,0 +1,131 @@
# -*- encoding: utf-8 -*-
# @author : vincent.benoit@benserv.fr
# @brief : Configurateur Backend Flask API
#########################################################
# Importation de modules externes #
import sys, re, os
import logging as log
from logging.config import dictConfig
from flask import Flask
from flask.logging import default_handler
from flask_cors import CORS, cross_origin
from flask_jwt_extended import JWTManager
import jwt
from src.config import DefaultConfig
from src.auth import auth
from src.log import log as logs
#########################################################
# Corps principal du programme #
def create_app(config=None, app_name=None):
''' Create and configure the app
:param config:
configuration object
:param app_name:
application name
'''
if app_name is None:
app_name = DefaultConfig.PROJECT
# create the app
# tells the app that configuration files are relative to the instance folder.
app = Flask(app_name,
instance_path=os.path.join(os.getcwd(), app_name),
instance_relative_config=True)
if not configure_app(app, config):
return None
configure_log(app)
configure_blueprints(app)
return app
def configure_app(app=None, config=None):
''' configure the application with configuration file and/or object
:param app:
Application object
:param config:
Configuration object
:return bool:
True if OK, otherwise False
'''
# load default config
app.config.from_object(DefaultConfig)
try:
if config:
# load specific config
app.config.from_object(config)
except ModuleNotFoundError as e:
print("Module de configuration non trouvé: {}".format(e))
return False
# setup the Flask-JWT-Extended extension
jwt = JWTManager(app)
# setup the Flask-CORS extension for handling Cross Origin Resource Sharing
CORS(app, resources={r"/api/*": {
# "origins": ["http://localhost:4200","http://localhost:5000"],
"origins": "*",
"supports_credentials": True
}})
return True
def configure_log(app=None):
''' Configure log handler
:param app:
Application object
:return None:
'''
if not os.path.exists(app.config['LOG_FOLDER']):
try:
os.makedirs(app.config['LOG_FOLDER'])
except OSError:
pass
# On vire tous les handlers
for h in app.logger.handlers:
app.logger.removeHandler(h)
# Set info level on logger, which might be overwritten by handers.
# Suppress DEBUG messages.
app.logger.setLevel(log.DEBUG)
formatter = log.Formatter('%(asctime)s - %(name)s [%(module)s.%(funcName)s:%(lineno)d] - %(levelname)s - %(message)s')
info_log = os.path.join(app.config['LOG_FOLDER'], DefaultConfig.PROJECT + '.log')
info_file_handler = log.handlers.RotatingFileHandler(info_log, maxBytes=100000, backupCount=10)
info_file_handler.setLevel(log.DEBUG)
info_file_handler.setFormatter(formatter)
app.logger.addHandler(info_file_handler)
fl = log.StreamHandler()
fl.setLevel(log.DEBUG)
fl.setFormatter(formatter)
app.logger.addHandler(fl)
#logging.getLogger('apscheduler').setLevel(logging.DEBUG)
def configure_blueprints(app=None):
''' configure blueprints
:param app:
Application object
:return None:
'''
for bp in [auth, logs]:
app.register_blueprint(bp)
+6
View File
@@ -0,0 +1,6 @@
# -*- encoding: utf-8 -*-
# @author : vincent.benoit@benserv.fr
# @brief : auth views and models
from .views import auth
+148
View File
@@ -0,0 +1,148 @@
# -*- encoding: utf-8 -*-
# @author : vincent.benoit@benserv.fr
# @brief : Auth routes
#########################################################
# Importation de modules externes #
import sys, re, os
import logging as log
from datetime import datetime, timezone
from flask import Flask, Blueprint, request, abort, jsonify, current_app
from flask_api import status
from flask_jwt_extended import create_access_token
from flask_jwt_extended import get_jwt
from flask_jwt_extended import set_access_cookies
from flask_jwt_extended import unset_jwt_cookies
from flask_jwt_extended import get_jwt_identity
from flask_jwt_extended import jwt_required, decode_token
import json
import shutil
import hashlib
from werkzeug.exceptions import HTTPException
#########################################################
# Class et Methods #
auth = Blueprint('auth', __name__, url_prefix='/api/configurateur')
@auth.errorhandler(HTTPException)
def handle_exception(e):
''' return JSON instead of HTML for HTTP errors '''
response = e.get_response()
# replace the body with JSON
response.data = json.dumps({
'code': e.code,
'name': e.name,
'description': e.description,
})
response.content_type = "application/json"
return response
@auth.after_request
def refresh_expiring_tokens(response):
''' Using an 'after_request' callback, we refresh any token that is within
30 minutes of expiring.'''
try:
exp_timestamp = get_jwt()['exp']
now = datetime.now(timezone.utc)
target_timestamp = datetime.timestamp(now + current_app.config['DELTA'])
if target_timestamp > exp_timestamp:
current_app.logger.warning("On doit recréer un token JWT ....")
access_token = create_access_token(identity=get_jwt_identity())
# refresh token in storage place
if os.path.exists(os.path.join("/tmp", current_app.config['PROJECT'])):
with open(os.path.join("/tmp", current_app.config['PROJECT'], get_jwt_identity()['id']), 'w') as f:
f.write(access_token)
# Modifiy a Flask Response to set a cookie containing the access JWT.
set_access_cookies(response, access_token)
return response
except (RuntimeError, KeyError):
return response
@auth.route('/login', methods=['POST'])
def login():
''' Authenticate User '''
auth = request.authorization
current_app.logger.info("Connexion de l'utilisateur : {}".format(auth.username))
if not auth or not auth.username or not auth.password:
current_app.logger.error("Login and Password required !")
abort(401, description='Login and Password required')
try:
hashstr = hashlib.sha256(auth.password.encode('utf-8')).hexdigest()
with open(current_app.config['DB_PATH'], 'r') as f:
data = json.load(f)
# authenticate user with his username and password hash
if hashstr == data['utilisateur']['password']:
# create a new access token
current_app.logger.debug("create JWT Token with id: {}".format(auth.username))
token = create_access_token(identity={'id': auth.username})
if not os.path.exists(os.path.join("/tmp", current_app.config['PROJECT'])):
os.mkdir(os.path.join("/tmp", current_app.config['PROJECT']))
with open(os.path.join("/tmp", current_app.config['PROJECT'], auth.username), 'w') as f:
f.write(token)
content = jsonify({'message': 'login successful !'})
# set token as cookie
set_access_cookies(content, token)
return content, status.HTTP_200_OK
current_app.logger.error("Authorization failed !")
except Exception as e:
current_app.logger.error("Erreur : {}".format(e))
abort(500, description='Authentification impossible')
abort(401, description="Authorization failed !")
@auth.route('/logout', methods=['POST'])
@jwt_required()
def logout():
""" Handles HTTP requests to URL: /api/configurateur/logout """
current_app.logger.info("Deconnexion de l'utilisateur")
current_user = get_jwt_identity()
content = jsonify({'message': 'logout successful !'})
unset_jwt_cookies(content)
userpath = os.path.join("/tmp", current_app.config['PROJECT'])
if os.path.exists(userpath):
shutil.rmtree(userpath)
return content, status.HTTP_200_OK
@auth.route('/current', methods=['GET'])
@jwt_required()
def current_user():
''' retourne l'utilisateur courant connecté '''
# Access the identity of the current user with get_jwt_identity
current_user = get_jwt_identity()
return jsonify(current_user)
@auth.route('/userConnected', methods=['GET'])
def user_connected():
''' retourne "oui" si un utilisateur est déjà connecté sinon "non" '''
flag = False
resp = 'non'
uid = ''
path = os.path.join("/tmp", current_app.config['PROJECT'])
if os.path.exists(path):
# search unique file in path
for root, dirs, files in os.walk(path):
for f in files:
uid = str(f)
# retreive token in file
with open(os.path.join(path, f), 'r') as fp:
token = fp.read()
exp = decode_token(token, allow_expired = True)['exp']
now = datetime.now(timezone.utc)
target = datetime.timestamp(now)
if target > exp:
current_app.logger.warning("Le token de l'ancien utilisateur a expiré")
# remove tmp dir
shutil.rmtree(os.path.join("/tmp", current_app.config['PROJECT']))
flag = True
break
if not flag:
current_app.logger.warning("Un autre utilisateur est déjà connecté")
resp = 'oui'
content = ({'message':resp, 'uid':uid})
return jsonify(content), status.HTTP_200_OK
+60
View File
@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
#
# @author: vincent.benoit@benserv.fr
# @brief: Flask Config classes
import os
import datetime
class BaseConfig(object):
PROJECT = "configurateur"
# Get app root path, also can use flask.root_path.
PROJECT_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
DEBUG = False
TESTING = False
FLASK_ENV = 'production'
ADMINS = ['vincent.benoit@benserv.fr']
LOG_FOLDER = os.path.join(os.getcwd(), 'log')
SECRET_KEY = "d]omg;<*|uHfs}ogN=dk_$YW"
# Setup the Flask-JWT-Extended extension
JWT_SECRET_KEY = "xbNNvwHCY*cN,)Yq;A,e(]|P"
JWT_COOKIE_SECURE = False
JWT_TOKEN_LOCATION = ["cookies"]
JWT_ACCESS_TOKEN_EXPIRES = datetime.timedelta(minutes=30)
DELTA = datetime.timedelta(minutes=20) # in minutes
# Controls if Cross Site Request Forgery (CSRF) protection is enabled when using cookies
# This should always be True in production
JWT_COOKIE_CSRF_PROTECT = True
JWT_CSRF_IN_COOKIES = True
STATIC_FOLDER = os.path.join(os.getcwd(), 'static')
UPLOAD_FOLDER = STATIC_FOLDER + '/upload'
DOWNLOAD_FOLDER = STATIC_FOLDER + '/download'
ALLOWED_EXTENSIONS = {'json', 'tar', 'txt'}
MAX_CONTENT_LENGTH = 200 * 1024 * 1024 # 100 megabytes
DB_PATH = os.path.join(os.getcwd(), 'db.json')
class DefaultConfig(BaseConfig):
DEBUG = True
TESTING = True
FLASK_ENV = 'development'
SECRET_KEY = "secretkey1"
# Setup the Flask-JWT-Extended extension
JWT_SECRET_KEY = "secretkeyjwt1"
JWT_ACCESS_TOKEN_EXPIRES = datetime.timedelta(minutes=10)
DELTA = datetime.timedelta(minutes=5) # in minutes
class ProdConfig(BaseConfig):
ROOT_BASE_FOLDER = "/opt"
# Log Folder path
LOG_FOLDER = os.path.join("/var/log", BaseConfig.PROJECT)
+6
View File
@@ -0,0 +1,6 @@
# -*- encoding: utf-8 -*-
# @author : vincent.benoit@benserv.fr
# @brief : log views and models
from .views import log
+80
View File
@@ -0,0 +1,80 @@
# -*- encoding: utf-8 -*-
# @author : vincent.benoit@benserv.fr
# @brief : log routes
#########################################################
# Importation de modules externes #
import sys, re, os
import logging as log
from datetime import datetime, timezone
from flask import Flask, Blueprint, request, abort, jsonify, current_app
from flask_api import status
from flask_jwt_extended import create_access_token
from flask_jwt_extended import get_jwt
from flask_jwt_extended import set_access_cookies
from flask_jwt_extended import get_jwt_identity
from flask_jwt_extended import jwt_required
import json
from werkzeug.exceptions import HTTPException
#########################################################
# Class et Methods #
log = Blueprint('log', __name__, url_prefix='/api/configurateur')
@log.errorhandler(HTTPException)
def handle_exception(e):
''' return JSON instead of HTML for HTTP errors '''
response = e.get_response()
# replace the body with JSON
response.data = json.dumps({
'code': e.code,
'name': e.name,
'description': e.description,
})
response.content_type = "application/json"
return response
@log.after_request
def refresh_expiring_tokens(response):
''' Using an 'after_request' callback, we refresh any token that is within
30 minutes of expiring.'''
try:
exp_timestamp = get_jwt()['exp']
now = datetime.now(timezone.utc)
target_timestamp = datetime.timestamp(now + current_app.config['DELTA'])
if target_timestamp > exp_timestamp:
currenT_app.logger.warning("On doit recréer un token ....")
access_token = create_access_token(identity=get_jwt_identity())
# refresh token in storage place
if os.path.exists(os.path.join("/tmp", current_app.config['PROJECT'])):
with open(os.path.join("/tmp", current_app.config['PROJECT'], get_jwt_identity()['id']), 'w') as f:
f.write(access_token)
# Modifiy a Flask Response to set a cookie containing the access JWT.
set_access_cookies(response, access_token)
return response
except (RuntimeError, KeyError):
return response
@log.route('/logs', methods=['GET'])
@jwt_required()
def get_all_logs():
''' Retreive app logs '''
# Access the identity of the current user with get_jwt_identity
current_user = get_jwt_identity()
lines = []
with open(os.path.join(current_app.config['LOG_FOLDER'], current_app.config['PROJECT'] + '.log')) as f:
for idx, line in enumerate(f.readlines()):
try:
date_time, head, gravity, msg = line.split(' - ', 3)
content = {'datetime': date_time, 'header': head, 'gravity': gravity, 'msg':msg}
lines.append(content)
except ValueError as e:
lines[-1]['msg'] = lines[-1]['msg'] + line
return jsonify(lines), status.HTTP_200_OK