From d722390f892491a07fdc3a8c9082eb964d144a87 Mon Sep 17 00:00:00 2001 From: Terrtia Date: Wed, 14 Aug 2019 13:58:58 +0200 Subject: [PATCH] chg: [Flask server] add restAPI blueprint --- server/web/Flask_server.py | 10 +- server/web/blueprints/restApi.py | 151 ++++++++++++++++++++++++++++++ server/web/create_default_user.py | 2 +- 3 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 server/web/blueprints/restApi.py diff --git a/server/web/Flask_server.py b/server/web/Flask_server.py index 4eed83f..4f24881 100755 --- a/server/web/Flask_server.py +++ b/server/web/Flask_server.py @@ -17,7 +17,7 @@ import configparser import subprocess -from flask import Flask, render_template, jsonify, request, Blueprint, redirect, url_for +from flask import Flask, render_template, jsonify, request, Blueprint, redirect, url_for, Response from flask_login import LoginManager, current_user, login_user, logout_user, login_required import bcrypt @@ -27,9 +27,11 @@ from Role_Manager import create_user_db, check_password_strength, check_user_rol from Role_Manager import login_admin, login_analyst sys.path.append(os.path.join(os.environ['D4_HOME'], 'lib')) - from User import User +# Import Blueprint +from blueprints.restApi import restApi + baseUrl = '' if baseUrl != '': baseUrl = '/'+baseUrl @@ -108,6 +110,10 @@ login_manager.login_view = 'login' login_manager.init_app(app) # ========= =========# +# ========= BLUEPRINT =========# +app.register_blueprint(restApi) +# ========= =========# + # ========= LOGIN MANAGER ======== @login_manager.user_loader diff --git a/server/web/blueprints/restApi.py b/server/web/blueprints/restApi.py new file mode 100644 index 0000000..4bb82c2 --- /dev/null +++ b/server/web/blueprints/restApi.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +# -*-coding:UTF-8 -* + +''' + Flask functions and routes for the rest api +''' + +import os +import re +import sys +import uuid +import json +import redis +import datetime + +from flask import Flask, render_template, jsonify, request, Blueprint, redirect, url_for, Response +from flask_login import login_required + +from functools import wraps + +# ============ BLUEPRINT ============ + +restApi = Blueprint('restApi', __name__, template_folder='templates') + +# ============ VARIABLES ============ + +host_redis_metadata = os.getenv('D4_REDIS_METADATA_HOST', "localhost") +port_redis_metadata = int(os.getenv('D4_REDIS_METADATA_PORT', 6380)) + +r_serv_db = redis.StrictRedis( + host=host_redis_metadata, + port=port_redis_metadata, + db=1, + decode_responses=True) + +# ============ AUTH FUNCTIONS ============ + +def check_token_format(strg, search=re.compile(r'[^a-zA-Z0-9_-]').search): + return not bool(search(strg)) + +def verify_token(token): + if len(token) != 41: + return False + + if not check_token_format(token): + return False + + if r_serv_db.hexists('user:tokens', token): + return True + else: + return False + +def get_user_from_token(token): + return r_serv_db.hget('user:tokens', token) + +def verify_user_role(role, token): + user_id = get_user_from_token(token) + if user_id: + if is_in_role(user_id, role): + return True + else: + return False + else: + return False + +def is_in_role(user_id, role): + if r_serv_db.sismember('user_role:{}'.format(role), user_id): + return True + else: + return False + +# ============ DECORATOR ============ + +def token_required(user_role): + def actual_decorator(funct): + @wraps(funct) + def api_token(*args, **kwargs): + data = authErrors(user_role) + if data: + return Response(json.dumps(data[0], indent=2, sort_keys=True), mimetype='application/json'), data[1] + else: + return funct(*args, **kwargs) + return api_token + return actual_decorator + +def get_auth_from_header(): + token = request.headers.get('Authorization').replace(' ', '') # remove space + return token + +def authErrors(user_role): + # Check auth + if not request.headers.get('Authorization'): + return ({'status': 'error', 'reason': 'Authentication needed'}, 401) + token = get_auth_from_header() + data = None + # verify token format + + ''' + # brute force protection + current_ip = request.remote_addr + login_failed_ip = r_cache.get('failed_login_ip_api:{}'.format(current_ip)) + # brute force by ip + if login_failed_ip: + login_failed_ip = int(login_failed_ip) + if login_failed_ip >= 5: + return ({'status': 'error', 'reason': 'Max Connection Attempts reached, Please wait {}s'.format(r_cache.ttl('failed_login_ip_api:{}'.format(current_ip)))}, 401) + ''' + + try: + authenticated = False + if verify_token(token): + authenticated = True + + # check user role + if not verify_user_role(user_role, token): + data = ({'status': 'error', 'reason': 'Access Forbidden'}, 403) + + if not authenticated: + #r_cache.incr('failed_login_ip_api:{}'.format(current_ip)) + #r_cache.expire('failed_login_ip_api:{}'.format(current_ip), 300) + data = ({'status': 'error', 'reason': 'Authentication failed'}, 401) + except Exception as e: + print(e) + data = ({'status': 'error', 'reason': 'Malformed Authentication String'}, 400) + if data: + return data + else: + return None + +# ============ FUNCTIONS ============ + +def is_valid_uuid_v4(header_uuid): + try: + header_uuid=header_uuid.replace('-', '') + uuid_test = uuid.UUID(hex=header_uuid, version=4) + return uuid_test.hex == header_uuid + except: + return False + +def one(): + return 1 + +# ============= ROUTES ============== + + +@restApi.route("/api/v1/get/item", methods=['GET']) +@token_required('user') +def get_item_id(): + data = request.get_json() + res = ({'test': 2}, 200) + return Response(json.dumps(res[0], indent=2, sort_keys=True), mimetype='application/json'), res[1] diff --git a/server/web/create_default_user.py b/server/web/create_default_user.py index fdb5dbf..944f8b9 100755 --- a/server/web/create_default_user.py +++ b/server/web/create_default_user.py @@ -24,7 +24,7 @@ if __name__ == "__main__": # create role_list if not r_serv.exists('d4:all_role'): - role_dict = {'admin': 1, 'user': 1, 'sensor_register': 20} + role_dict = {'admin': 1, 'user': 2, 'sensor_register': 20} r_serv.zadd('d4:all_role', role_dict) username = 'admin@admin.test'