diff --git a/analysis/app_ui.py b/analysis/app_ui.py index 4990758c5728096576d324875767ccd50251a089..6b68267fd607f325721e501519ece456e6fa1ef0 100644 --- a/analysis/app_ui.py +++ b/analysis/app_ui.py @@ -24,6 +24,7 @@ from analysis.ui.config import UiConfig from analysis.ui.database import ui_tuning, ui_analysis, ui_user, ui_command from analysis.ui import offline from analysis.ui import echo +from analysis.ui.cache import LocalCache class AppUI(App): """app ui""" @@ -37,12 +38,17 @@ class AppUI(App): self.api.add_resource(offline.OfflineTunning, '/v2/UI/offline/') self.api.add_resource(echo.EchoTunning, '/v2/UI/echo') + def add_cache(self): + """flask app ui add local cache""" + LocalCache.cache.init_app(self.app) + def main(filename): """app main function""" if not UiConfig.initial_params(filename): return app_ui = AppUI() + app_ui.add_cache() app_ui.startup_app(UiConfig.ui_host, UiConfig.ui_port, UiConfig.ui_tls, UiConfig.ui_server_cert, UiConfig.ui_server_key, diff --git a/analysis/ui/cache.py b/analysis/ui/cache.py new file mode 100644 index 0000000000000000000000000000000000000000..2b5689bd26710e7ba76e6c03451ec3789742fce6 --- /dev/null +++ b/analysis/ui/cache.py @@ -0,0 +1,40 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Copyright (c) 2020 Huawei Technologies Co., Ltd. +# A-Tune is licensed under the Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# PURPOSE. +# See the Mulan PSL v2 for more details. +# Create: 2024-3-23 +from flask_caching import Cache + + +class LocalCache: + cache = Cache(config={ + "CACHE_TYPE": "simple" + }) + + @staticmethod + def put(key, value): + """cache put method""" + return LocalCache.cache.set(key, value) + + @staticmethod + def put_with_time(key, value, timeout): + """cache put method with timeout""" + return LocalCache.cache.set(key, value, timeout) + + @staticmethod + def pop(key): + """cache pop method""" + return LocalCache.cache.delete(key) + + @staticmethod + def get(key): + """cache get method""" + res = LocalCache.cache.get(key) + return res diff --git a/analysis/ui/database/ui_analysis.py b/analysis/ui/database/ui_analysis.py index bdd87eaa93a543e07fd4a86e02f4982f929819a0..7d366ab5e365d27bb3d40ca35dfa251ecec222c5 100644 --- a/analysis/ui/database/ui_analysis.py +++ b/analysis/ui/database/ui_analysis.py @@ -23,6 +23,7 @@ from flask_restful import Resource from analysis.ui.parser import UI_ANALYSIS_GET_PARSER from analysis.ui.config import UiConfig from analysis.engine import transfer_web +from analysis.ui.util import authenticate LOGGER = logging.getLogger(__name__) CORS = [('Access-Control-Allow-Origin', '*')] @@ -30,6 +31,7 @@ CORS = [('Access-Control-Allow-Origin', '*')] class UiAnalysis(Resource): """restful api for web ui analysis page""" + method_decorators = [authenticate] def get(self, cmd): """restful api get""" diff --git a/analysis/ui/database/ui_command.py b/analysis/ui/database/ui_command.py index 7d5eae7dc9fa7c6f6b2d94f556cb5334c62c5ae0..72a803d167bcb37dcc3773b27ed499b911ca4b8e 100644 --- a/analysis/ui/database/ui_command.py +++ b/analysis/ui/database/ui_command.py @@ -23,12 +23,15 @@ from flask_restful import Resource from analysis.ui.parser import UI_COMMAND_GET_PARSER from analysis.ui.config import UiConfig from analysis.engine import transfer_web +from analysis.ui.util import authenticate LOGGER = logging.getLogger(__name__) CORS = [('Access-Control-Allow-Origin', '*')] class UiCommand(Resource): """restful api for web ui command page""" + method_decorators = [authenticate] + def get(self, cmd): """restful api get""" if not cmd: diff --git a/analysis/ui/database/ui_role.py b/analysis/ui/database/ui_role.py index 4494efe0826d2ac3ddbb5ad69606c84accb8e38d..8893ba35fd50b7e80e2ad89ea7afa0d754707a54 100644 --- a/analysis/ui/database/ui_role.py +++ b/analysis/ui/database/ui_role.py @@ -23,6 +23,7 @@ from flask_restful import Resource from analysis.ui.parser import UI_ROLE_GET_PARSER from analysis.ui.config import UiConfig from analysis.ui.database import trigger_user +from analysis.ui.util import authenticate LOGGER = logging.getLogger(__name__) CORS = [('Access-Control-Allow-Origin', 'http://localhost:3000')] @@ -30,6 +31,7 @@ CORS = [('Access-Control-Allow-Origin', 'http://localhost:3000')] class UiRole(Resource): """Role restful API used for role management""" + method_decorators = [authenticate] def get(self, cmd): """restful api get""" diff --git a/analysis/ui/database/ui_tuning.py b/analysis/ui/database/ui_tuning.py index 7e9289ae4170eeaa8bdfd2586ed909ce89c1497e..6258689d49254aa12334a9bba0889b48dbe6afab 100644 --- a/analysis/ui/database/ui_tuning.py +++ b/analysis/ui/database/ui_tuning.py @@ -23,6 +23,7 @@ from flask_restful import Resource from analysis.ui.parser import UI_TUNING_GET_PARSER from analysis.ui.config import UiConfig from analysis.engine import transfer_web +from analysis.ui.util import authenticate LOGGER = logging.getLogger(__name__) CORS = [('Access-Control-Allow-Origin', '*')] @@ -30,6 +31,7 @@ CORS = [('Access-Control-Allow-Origin', '*')] class UiTuning(Resource): """restful api for web ui tuning page""" + method_decorators = [authenticate] def get(self, cmd): """restful api get""" diff --git a/analysis/ui/database/ui_user.py b/analysis/ui/database/ui_user.py index 1a0b7e69208480127f4a333af80835dfd769fd07..a372a1676f4b5b34b7963ca5762c097efed59d5a 100644 --- a/analysis/ui/database/ui_user.py +++ b/analysis/ui/database/ui_user.py @@ -20,9 +20,10 @@ import json from flask import abort, request from flask_restful import Resource +from analysis.ui.cache import LocalCache from analysis.ui.parser import UI_USER_GET_PARSER, UI_USER_POST_PARSER from analysis.ui.config import UiConfig -from analysis.ui.util import JwtUtil, verify_server_connectivity, decode_server_password +from analysis.ui.util import JwtUtil, verify_server_connectivity, decode_server_password, authenticate LOGGER = logging.getLogger(__name__) CORS = [('Access-Control-Allow-Origin', '*')] @@ -30,9 +31,11 @@ CORS = [('Access-Control-Allow-Origin', '*')] class UiUser(Resource): """restful api for web ui user login/profile page""" + method_decorators = [authenticate] def get(self, cmd): """restful api get""" + if not cmd: abort(404, 'does not get command') @@ -52,6 +55,11 @@ class UiUser(Resource): token_vaild, msg = jwt.is_token_vaild(token, uid) return json.dumps({'vaild': token_vaild, 'msg': msg}), 200, CORS + if cmd == 'signOut': + uid = args.get('userId') + res = LocalCache.pop(uid) + return json.dumps({'signOut': res}), 200, CORS + if cmd == 'ipList': uid = args.get('userId') return json.dumps({'ipList': trigger_user.user_ip_list(uid)}), 200, CORS @@ -80,8 +88,16 @@ class UiUser(Resource): pwd = args.get('password') res, name = trigger_user.user_exist(email, pwd) if res == -1: - return json.dumps({'login': False}), 200, CORS + return json.dumps({'login': False, 'msg': '账号密码不正确'}), 200, CORS + # check if token is existed and valid + val = LocalCache.get(key=res) + if val is not None: + token_valid, msg = jwt.is_token_vaild(val, res) + if token_valid: + return json.dumps({'login': False, 'msg': '您的账号已于其他平台上登录'}), 200, CORS token = jwt.encode({'user_id': res}) + # put token into cache + LocalCache.put(res, token) return json.dumps({'login': True, 'user_id': res, 'user_name': name, 'token': token}), 200, CORS diff --git a/analysis/ui/util.py b/analysis/ui/util.py index ac67dc9c847d67f020bc9d4f868e52cc9e5e68e9..a1b39b7a4af3aa1827327a9c1c4fbf2347b9c436 100644 --- a/analysis/ui/util.py +++ b/analysis/ui/util.py @@ -15,15 +15,24 @@ """ Provide utility functions for ui """ +import json import socket import base64 from datetime import datetime, timedelta +from functools import wraps import jwt import paramiko +from flask import request from paramiko import ssh_exception from paramiko.ssh_exception import AuthenticationException +from analysis.ui.cache import LocalCache +from analysis.ui.config import UiConfig + +NON_AUTHENTICATED_URL = ['/v1/UI/user/login', '/v1/UI/user/signUp', '/v1/UI/user/initialPage'] + +CORS = [('Access-Control-Allow-Origin', '*')] class JwtUtil: """jwt util class""" @@ -41,6 +50,8 @@ class JwtUtil: def is_token_vaild(self, token, uid): """Judge whether the token is valid""" try: + if LocalCache.get(uid) != token: + return False, "Token removed" payload = jwt.decode(jwt=token, key=self.secret, algorithms=['HS256']) if payload['user_id'] != uid: return False, "Invalid user" @@ -51,7 +62,22 @@ class JwtUtil: return False, "Token expired" except Exception: return False, "Verification failed" - + + def is_token_legal(self, token): + """Judge whether the token is legal""" + try: + payload = jwt.decode(jwt=token, key=self.secret, algorithms=['HS256']) + uid = payload['user_id'] + if LocalCache.get(uid) != token: + return False, "Illegal token" + return True, "" + except jwt.exceptions.InvalidSignatureError: + return False, "Illegal token" + except jwt.exceptions.ExpiredSignatureError: + return False, "Token expired" + except Exception: + return False, "Verification failed" + def verify_server_connectivity(ip_addrs, ip_port, server_user, server_password): """Verify server connectivity""" @@ -71,4 +97,21 @@ def verify_server_connectivity(ip_addrs, ip_port, server_user, server_password): def decode_server_password(pwd): - return base64.b64decode(pwd) \ No newline at end of file + return base64.b64decode(pwd) + + +def authenticate(func): + """authentication decorator""" + @wraps(func) + def wrapper(*args, **kwargs): + path = request.path + if path in NON_AUTHENTICATED_URL: + return func(*args, **kwargs) + token = request.headers.get('Authorization') + jwt = JwtUtil(UiConfig.jwt_secret) + token_valid, msg = jwt.is_token_legal(token) + if not token_valid: + return json.dumps({'valid': token_valid, 'msg': msg}), 200, CORS + else: + return func(*args, **kwargs) + return wrapper \ No newline at end of file