From 6846a4c98a793e74ae17b47f04a0ff8b210aeb24 Mon Sep 17 00:00:00 2001 From: zhaoqingang <zhaoqg0118@163.com> Date: 星期二, 01 四月 2025 16:52:51 +0800 Subject: [PATCH] 授权license --- app/service/pom/public_key.pem | 9 + app/api/auth.py | 8 + app/init_config/init_run_data.py | 3 /dev/null | 45 --------- app/api/system.py | 15 ++ app/service/system.py | 51 +++++++++- app/config/const.py | 3 app/api/v2/public_api.py | 2 app/models/system.py | 14 ++ app/utils/common.py | 25 +++++ main.py | 2 app/task/sync_system.py | 68 +++++++++++++ app/api/__init__.py | 8 + app/service/v2/initialize_data.py | 24 ++++ app/config/env_conf/system.yaml | 1 15 files changed, 218 insertions(+), 60 deletions(-) diff --git a/app/api/__init__.py b/app/api/__init__.py index 6cb4b05..de2282f 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -68,6 +68,14 @@ def get_current_user(token: str = Depends(oauth2_scheme)): try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + expired_time = payload.get("lex") + if not expired_time: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="浠ょ墝鏃犳晥鎴栧凡杩囨湡", + headers={"WWW-Authenticate": "Bearer"}) + if datetime.strptime(expired_time, "%Y-%m-%d %H:%M:%S") < datetime.now(): + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="绯荤粺鎺堟潈宸茶繃鏈燂紒", + headers={"WWW-Authenticate": "Bearer"}) + username: str = payload.get("sub") if username is None: raise HTTPException( diff --git a/app/api/auth.py b/app/api/auth.py index a8d381d..7cbfdcf 100644 --- a/app/api/auth.py +++ b/app/api/auth.py @@ -6,7 +6,8 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.api import Response, pwd_context, get_current_user from app.config.config import settings -from app.config.const import chat_server, RAGFLOW, workflow_server, DIFY, TMP_DICT +from app.config.const import chat_server, RAGFLOW, workflow_server, DIFY, TMP_DICT, SYSTEM_ID, SYSTEM_STATUS_ON +from app.models import SystemDataModel from app.models.app_token_model import AppToken from app.models.base_model import get_db from app.models.postgresql_base_model import get_pdb @@ -130,8 +131,11 @@ except Exception as e: return Response(code=500, msg=f"Failed to login with {app['id']}: {str(e)}") """ + system = db.query(SystemDataModel).filter_by(id=SYSTEM_ID).first() + if not system or system.status != SYSTEM_STATUS_ON: + return Response(code=400, msg="绯荤粺鐘舵�佸紓甯革紝璇锋巿鏉冩縺娲诲悗鎿嶄綔锛�") # 鍒涘缓鏈湴token - access_token = create_access_token(data={"sub": user.username, "user_id": user.id}) + access_token = create_access_token(data={"sub": user.username, "user_id": user.id, "lex": system.expired_at.strftime('%Y-%m-%d %H:%M:%S')}) # await update_token(db, user.id, access_token, token_dict) # await update_user_token(db, user.id, token_dict) diff --git a/app/api/system.py b/app/api/system.py index 72c9cec..b1d40f8 100644 --- a/app/api/system.py +++ b/app/api/system.py @@ -4,9 +4,10 @@ from app.api import Response, get_current_user from app.models.base_model import get_db from app.models.role_model import RoleData, RoleModel -from app.models.system import SystemData +from app.models.system import SystemData, SystemLicense from app.models.user_model import UserModel -from app.service.system import services_get_system_data, services_update_system_data, service_upload_logo_image +from app.service.system import services_get_system_data, services_update_system_data, service_upload_logo_image, \ + services_update_system_license system_router = APIRouter() @@ -33,3 +34,13 @@ if not file_name: return Response(code=500, msg="failed", data={"logo": ""}) return Response(code=200, msg="successfully", data={"logo": file_name}) + + +@system_router.put("/license", response_model=Response) +async def api_update_system_license(system: SystemLicense, db=Depends(get_db)): + + msg = await services_update_system_license(db, system.licenseCode) + if msg: + return Response(code=400, msg=msg, data={}) + return Response(code=200, msg="successfully", data={}) + diff --git a/app/api/v2/public_api.py b/app/api/v2/public_api.py index c86b787..2f49e37 100644 --- a/app/api/v2/public_api.py +++ b/app/api/v2/public_api.py @@ -13,7 +13,7 @@ from app.models.public_api_model import DfToken from app.service.v2.api_token import DfTokenDao from app.service.v2.initialize_data import dialog_menu_sync, create_menu_sync, user_update_app -from app.task.sync_resources import sync_knowledge, sync_dialog, sync_agent, sync_llm, sync_resource +# from app.task.sync_resources import sync_knowledge, sync_dialog, sync_agent, sync_llm, sync_resource from fastapi import Depends, APIRouter, File, UploadFile from sqlalchemy.orm import Session from app.config.const import smart_message_error, http_400, http_500, http_200, complex_dialog_chat diff --git a/app/config/const.py b/app/config/const.py index 30ee790..b0d8a39 100644 --- a/app/config/const.py +++ b/app/config/const.py @@ -114,6 +114,9 @@ ###-------------------------------system------------------------------------------------- SYSTEM_ID = 1 +SYSTEM_STATUS_EXPIRED = 2 +SYSTEM_STATUS_ON = 1 +SYSTEM_STATUS_OFF = 0 ### --------------------------------complex mode---------------------------------------------- complex_dialog_chat = 1 # 鏂囨。鍜屽熀纭�瀵硅瘽 diff --git a/app/config/env_conf/system.yaml b/app/config/env_conf/system.yaml index 4b2d6da..9cfb9f1 100644 --- a/app/config/env_conf/system.yaml +++ b/app/config/env_conf/system.yaml @@ -1,3 +1,4 @@ smart_system: title: SmartAI澶фā鍨嬪钩鍙� desc: SmartAI澶фā鍨嬪钩鍙� + version: 1.0.3 diff --git a/app/init_config/init_run_data.py b/app/init_config/init_run_data.py index c0ff67e..e735889 100644 --- a/app/init_config/init_run_data.py +++ b/app/init_config/init_run_data.py @@ -1,6 +1,6 @@ from app.models.base_model import SessionLocal from app.service.v2.initialize_data import dialog_menu_sync, default_group_sync, default_role_sync, \ - basic_agent_sync, admin_account_sync, sync_rg_api_token, sync_complex_api_token + basic_agent_sync, admin_account_sync, sync_rg_api_token, sync_complex_api_token, system_license_sync from app.task.sync_account_token import sync_token @@ -15,6 +15,7 @@ await sync_rg_api_token(db) # rg token await sync_token() # 璐﹀彿token鐧诲綍 await sync_complex_api_token(db) # 璐﹀彿token鐧诲綍 + await system_license_sync(db) # 鍚屾绯荤粺license except Exception as e: print(e) diff --git a/app/models/system.py b/app/models/system.py index 28dca04..2846bf9 100644 --- a/app/models/system.py +++ b/app/models/system.py @@ -12,6 +12,11 @@ id = Column(Integer, primary_key=True, index=True) title = Column(String(255)) desc = Column(String(1000)) + version = Column(String(32)) + machine_id = Column(String(255)) + license_code = Column(String(1000)) + status = Column(Integer, default=0) + expired_at = Column(DateTime) created_at = Column(DateTime, default=datetime.now()) updated_at = Column(DateTime, default=datetime.now(), onupdate=datetime.now()) @@ -21,6 +26,11 @@ # 'id': self.id, 'title': self.title, 'desc': self.desc, + 'version': self.version, + 'machine_id': self.machine_id, + 'license_code': self.license_code, + 'status': self.status, + 'expired_at': self.expired_at.strftime('%Y-%m-%d %H:%M') if self.expired_at else '', } def __repr__(self): @@ -32,3 +42,7 @@ title: str desc: str logo: str + +class SystemLicense(BaseModel): + licenseCode: str + diff --git a/app/service/pom/public_key.pem b/app/service/pom/public_key.pem new file mode 100644 index 0000000..746caa3 --- /dev/null +++ b/app/service/pom/public_key.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8Jak4kGIkOE2kyI0oTcb +w3yk7OfZ78g1RGvxKlYbKWz93Prxi1pvywXHOnrL/IYDCaNFOybFy5aMbqqvqXOx +0LBCqwmB9F07AiEysmhH5m5OxlS9XsxGZb1WeRmobRbge3Hxl59DmUKvD/7Gdsre +JnDeSWxeaS/zIqLVUsvV3301B08biIywMAKamBQyuJNTPK1ir8iy6peSLPi022zk +Nl+Rm4ToOrF00oqwB8z5BOTdDcJW/eFlieOyTnWSAFBTIXAB9uqZSjn37kyLKYDh +yVqB71T/wQvMRip4PPFpCE4UCGGhLHHsKPhtCgxHj6YqE7vUCuGBXP/aagzpWC/H +ywIDAQAB +-----END PUBLIC KEY----- diff --git a/app/service/system.py b/app/service/system.py index 4df1e03..086fbae 100644 --- a/app/service/system.py +++ b/app/service/system.py @@ -1,15 +1,20 @@ import os import shutil import uuid -from datetime import datetime - import yaml -from fastapi import UploadFile +from datetime import datetime +from fastapi import UploadFile from Log import logger -from app.api import pwd_context -from app.config.const import SYSTEM_ID, ENV_CONF_PATH, APP_STATIC_PATH +from app.config.const import SYSTEM_ID, ENV_CONF_PATH, APP_STATIC_PATH, APP_SERVICE_PATH, SYSTEM_STATUS_ON from app.models.system import SystemDataModel +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives import hashes +import base64 +import json + +from app.utils.common import get_machine_id async def services_get_system_data(db): @@ -18,7 +23,7 @@ with open(os.path.join(ENV_CONF_PATH, "system.yaml"), 'r', encoding='utf-8') as file: # 鍔犺浇JSON鏁版嵁 config = yaml.safe_load(file) - system = SystemDataModel(id=SYSTEM_ID, title=config["smart_system"]["title"], desc=config["smart_system"]["desc"]) + system = SystemDataModel(id=SYSTEM_ID, title=config["smart_system"]["title"], desc=config["smart_system"]["desc"], version=config["smart_system"]["version"]) db.add(system) db.commit() db.refresh(system) @@ -53,3 +58,37 @@ except Exception as e: logger.error(f"淇濆瓨澶辫触: {str(e)}") return "" + + +async def services_update_system_license(db, license_code): + try: + with open(os.path.join(APP_SERVICE_PATH, "pom/public_key.pem"), "rb") as f: + public_key = serialization.load_pem_public_key(f.read()) + license_data, signature = base64.b64decode(license_code).split(b"-----", 1) + # print(license_data) + public_key.verify( + signature, + license_data, + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA256() + ) + license_dict = json.loads(license_data.decode('utf-8')) + # print(license_dict) + expiration_date = datetime.fromisoformat(license_dict['expiration_date']) + if expiration_date < datetime.now(): + return "鎺堟潈鐮佸凡杩囨湡" + system = db.query(SystemDataModel).filter_by(id=SYSTEM_ID).first() + if license_dict['machine_id'] != get_machine_id() or system.machine_id != license_dict['machine_id']: + return "鎺堟潈鐮佹棤鏁�" + system.license_code = license_code + system.expired_at = expiration_date + system.status = SYSTEM_STATUS_ON + system.updated_at = datetime.now() + # db.query(SystemDataModel).filter_by(id=SYSTEM_ID).update({"license_code": license_code, "expired_at": expiration_date, "status": 1, "updated_at": datetime.now()}) + db.commit() + return "" + except Exception as e: + return f"楠岃瘉澶辫触: {str(e)}" diff --git a/app/service/v2/initialize_data.py b/app/service/v2/initialize_data.py index 1cfad66..28a9961 100644 --- a/app/service/v2/initialize_data.py +++ b/app/service/v2/initialize_data.py @@ -10,9 +10,9 @@ from app.config.agent_base_url import RG_APP_TOKEN_LIST, RG_APP_NEW_TOKEN, DF_CHAT_API_KEY # from app.api import pwd_context from app.config.const import DIFY, ENV_CONF_PATH, RAGFLOW, smart_server, chat_server, workflow_server, TMP_DICT, \ - rg_api_token, Dialog_STATSU_ON + rg_api_token, Dialog_STATSU_ON, SYSTEM_ID from app.models import MenuCapacityModel, WebMenuModel, GroupModel, RoleModel, DialogModel, UserModel, UserAppModel, \ - cipher_suite, UserTokenModel, ApiTokenModel, ComplexChatModel + cipher_suite, UserTokenModel, ApiTokenModel, ComplexChatModel, SystemDataModel from app.service.auth import UserAppDao from app.service.bisheng import BishengService from app.service.difyService import DifyService @@ -22,6 +22,7 @@ from app.service.v2.app_register import AppRegisterDao from app.config.config import settings from app.service.v2.chat import get_app_token +from app.utils.common import get_machine_id from app.utils.password_handle import generate_password, password_encrypted, password_decrypted pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") @@ -427,4 +428,21 @@ except Exception as e: print(e) - db.rollback() \ No newline at end of file + db.rollback() + + +async def system_license_sync(db): + with open(os.path.join(ENV_CONF_PATH, "system.yaml") , 'r', encoding='utf-8') as file: + # 鍔犺浇JSON鏁版嵁 + config = json.load(file) + try: + system = db.query(SystemDataModel).filter_by(id=SYSTEM_ID).first() + if system: + system.version = config["smart_system"].get("version") + else: + system = SystemDataModel(id=SYSTEM_ID, version=config["smart_system"].get("version"), title=config["smart_system"].get("title"), desc=config["smart_system"].get("desc"), machine_id=get_machine_id()) + db.add(system) + db.commit() + except Exception as e: + print(e) + db.rollback() diff --git a/app/task/sync_resources.py b/app/task/sync_resources.py deleted file mode 100644 index fca2ae8..0000000 --- a/app/task/sync_resources.py +++ /dev/null @@ -1,45 +0,0 @@ -import asyncio - -from fastapi import Depends - -from Log import logger -from app.config.config import settings -from app.models import AgentType -from app.models.public_api_model import AppRegisterModel -from app.models.base_model import get_db -from app.service.bisheng import BishengService -from app.service.ragflow import RagflowService - - -def sync_resource(): - asyncio.run(sync_knowledge()) - asyncio.run(sync_dialog()) - asyncio.run(sync_agent()) - asyncio.run(sync_llm()) - - -async def sync_knowledge(db=Depends(get_db)): - - token = "" - - register_app = db.query(AppRegisterModel).filter(AppRegisterModel.status.__eq__(1)).all() - for rapp in register_app: - if rapp.app_type == AgentType.RAGFLOW: - token = "" - ragflow_service = RagflowService(settings.fwr_base_url) - ragflow_service.get_knowledge_list() - elif rapp.app_type ==AgentType.BISHENG: - token = "" - bisheng_service = BishengService(settings.sgb_base_url) - else: - logger.error("娉ㄥ唽鏈煡搴旂敤锛歿}".format(rapp.app_type)) - - -async def sync_dialog(): - ... - -async def sync_agent(): - ... - -async def sync_llm(): - ... \ No newline at end of file diff --git a/app/task/sync_system.py b/app/task/sync_system.py new file mode 100644 index 0000000..4d3c3d0 --- /dev/null +++ b/app/task/sync_system.py @@ -0,0 +1,68 @@ +import asyncio +import base64 +import json +import os + +from datetime import datetime +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives import hashes +from app.config.const import SYSTEM_ID, SYSTEM_STATUS_EXPIRED, SYSTEM_STATUS_ON, APP_SERVICE_PATH +from app.models import SystemDataModel +from app.models.base_model import SessionLocal +from app.utils.common import get_machine_id + + +async def sync_system_license(): + print("-------------------------------------------") + db = SessionLocal() + try: + system = db.query(SystemDataModel).filter_by(id=SYSTEM_ID).first() + if not system: + return + machine_id = get_machine_id() + if system.machine_id: + if system.machine_id != machine_id: + system.machine_id = machine_id + if system.status == SYSTEM_STATUS_ON: + system.status = SYSTEM_STATUS_EXPIRED + else: + if system.status == SYSTEM_STATUS_ON: + if not system.license_code or system.expired_at < datetime.now(): + system.status = SYSTEM_STATUS_EXPIRED + else: + try: + with open(os.path.join(APP_SERVICE_PATH, "pom/public_key.pem"), "rb") as f: + public_key = serialization.load_pem_public_key(f.read()) + license_data, signature = base64.b64decode(system.license_code).split(b"-----", 1) + # print(license_data) + public_key.verify( + signature, + license_data, + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA256() + ) + license_dict = json.loads(license_data.decode('utf-8')) + # print(license_dict) + expiration_date = datetime.fromisoformat(license_dict['expiration_date']) + system.expired_at = expiration_date + if expiration_date < datetime.now(): + system.status = SYSTEM_STATUS_EXPIRED + except Exception as e: + print(e) + system.status = SYSTEM_STATUS_EXPIRED + else: + if system.status == SYSTEM_STATUS_ON: + system.status = SYSTEM_STATUS_EXPIRED + db.commit() + except Exception as e: + print(e) + finally: + db.close() + + +def sync_resource(): + asyncio.run(sync_system_license()) diff --git a/app/utils/common.py b/app/utils/common.py index e3ac4a6..cacda03 100644 --- a/app/utils/common.py +++ b/app/utils/common.py @@ -1,6 +1,31 @@ import pytz +import platform +import subprocess + from datetime import datetime + def current_time(): tz = pytz.timezone('Asia/Shanghai') return datetime.now(tz) + + +def get_machine_id(): + """鑾峰彇鏈哄櫒鐨勫敮涓�鏍囪瘑""" + if platform.system() == "Windows": + # Windows 绯荤粺 + command = "wmic csproduct get UUID" + process = subprocess.Popen(command.split(), stdout=subprocess.PIPE) + output, _ = process.communicate() + machine_id = output.decode().strip().split("\n")[1].strip() + elif platform.system() == "Darwin": + # macOS 绯荤粺 + command = "system_profiler SPHardwareDataType | grep 'Hardware UUID' | awk '{print $4}'" + process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + output, _ = process.communicate() + machine_id = output.decode().strip() + else: + # Linux 绯荤粺 + machine_id = open("/etc/machine-id").read().strip() + # print(machine_id) + return machine_id \ No newline at end of file diff --git a/main.py b/main.py index 3ec7a9d..1743d2c 100644 --- a/main.py +++ b/main.py @@ -27,6 +27,7 @@ sync_resources_from_json from app.init_config.init_run_data import sync_default_data from app.task.sync_account_token import start_sync_token_task +from app.task.sync_system import sync_resource init_db() @@ -56,6 +57,7 @@ scheduler = BackgroundScheduler() scheduler.add_job(sync_agents_v2, 'interval', minutes=60, id="sync_resource_data") scheduler.add_job(start_sync_token_task, 'interval', minutes=5, id="sync_token_1") +scheduler.add_job(sync_resource, 'interval', minutes=5, id="sync_resource_1") scheduler.start() -- Gitblit v1.8.0