11import json
22import os
3- from datetime import timedelta
3+ from datetime import datetime , timedelta , timezone
4+ from zoneinfo import ZoneInfo
45from typing import List , Optional
56
67from fastapi import APIRouter , Form , HTTPException , Path , Query , Request , Response , UploadFile
2223from common .core .security import create_access_token
2324from common .core .sqlbot_cache import clear_cache
2425from common .utils .utils import get_origin_from_referer , origin_match_domain
25-
2626router = APIRouter (tags = ["system_assistant" ], prefix = "/system/assistant" )
2727from common .audit .models .log_model import OperationType , OperationModules
2828from common .audit .schemas .logger_decorator import LogConfig , system_log
29-
29+ from sqlbot_xpack . core import decrypt_embedded_sign
3030
3131@router .get ("/info/{id}" , include_in_schema = False )
32- async def info (request : Request , response : Response , session : SessionDep , trans : Trans , id : int ) -> AssistantModel :
32+ async def info (request : Request , response : Response , session : SessionDep , trans : Trans , id : int , virtual : Optional [ int ] = Query ( None )) :
3333 if not id :
3434 raise Exception ('miss assistant id' )
3535 db_model = await get_assistant_info (session = session , assistant_id = id )
3636 if not db_model :
3737 raise RuntimeError (f"assistant application not exist" )
3838 db_model = AssistantModel .model_validate (db_model )
39-
40- origin = request .headers .get ("origin" ) or get_origin_from_referer (request )
41- if not origin :
42- raise RuntimeError (trans ('i18n_embedded.invalid_origin' , origin = origin or '' ))
43- origin = origin .rstrip ('/' )
44- if not origin_match_domain (origin , db_model .domain ):
45- raise RuntimeError (trans ('i18n_embedded.invalid_origin' , origin = origin or '' ))
46-
39+
40+ # 校验 SQLBOT-EMBEDDED-SIGN 请求头
41+ sign_header = request .headers .get ("SQLBOT-EMBEDDED-SIGN" )
42+ if not sign_header :
43+ raise RuntimeError (trans ('i18n_embedded.invalid_origin' , origin = '' ))
44+
45+ sign_data = await decrypt_embedded_sign (sign_header )
46+
47+ # 校验 assistant_id 与 id 参数一致
48+ if str (sign_data .get ("assistant_id" )) != str (id ):
49+ raise RuntimeError (trans ('i18n_embedded.invalid_origin' , origin = '' ))
50+
51+ # 校验 target(来源域名)是否合法
52+ target = sign_data .get ("target" , "" )
53+ if not origin_match_domain (target , db_model .domain ):
54+ raise RuntimeError (trans ('i18n_embedded.invalid_origin' , origin = target or '' ))
55+
56+ # 校验 sign_time 是否在 10 秒内
57+ sign_time_str = sign_data .get ("sign_time" , "" )
58+ sign_time = datetime .fromisoformat (sign_time_str )
59+ now_utc = datetime .now (timezone .utc )
60+ sign_time_utc = sign_time .astimezone (timezone .utc )
61+ if abs ((now_utc - sign_time_utc ).total_seconds ()) > 10 :
62+ raise RuntimeError (trans ('i18n_embedded.invalid_origin' , origin = target or '' ))
63+
64+ # 校验是否为真实浏览器请求(非自动化工具)
65+ if sign_data .get ("webdriver" , False ):
66+ raise RuntimeError (trans ('i18n_embedded.invalid_origin' , origin = target or '' ))
67+
68+ # 校验 User-Agent 一致性(签名中的 navigator.userAgent 与请求头一致)
69+ sign_user_agent = sign_data .get ("user_agent" , "" )
70+ request_user_agent = request .headers .get ("User-Agent" , "" )
71+ if sign_user_agent != request_user_agent :
72+ raise RuntimeError (trans ('i18n_embedded.invalid_origin' , origin = target or '' ))
73+
74+ # 校验 timezone 与 sign_time 偏移一致性(防时区伪造)
75+ tz_name = sign_data .get ("timezone" , "" )
76+ tz = ZoneInfo (tz_name )
77+ sign_time_naive = sign_time .replace (tzinfo = None )
78+ if tz .utcoffset (sign_time_naive ) != sign_time .utcoffset ():
79+ raise RuntimeError (trans ('i18n_embedded.invalid_origin' , origin = target or '' ))
80+
81+ origin = target .rstrip ('/' )
82+
4783 response .headers ["Access-Control-Allow-Origin" ] = origin
48- return db_model
84+
85+
86+ assistant_oid = 1
87+ if (db_model .type == 0 ):
88+ configuration = db_model .configuration
89+ config_obj = json .loads (configuration ) if configuration else {}
90+ assistant_oid = config_obj .get ('oid' , 1 )
91+
92+ access_token_expires = timedelta (minutes = settings .ACCESS_TOKEN_EXPIRE_MINUTES )
93+ assistantDict = {
94+ "id" : virtual , "account" : 'sqlbot-inner-assistant' , "oid" : assistant_oid , "assistant_id" : id
95+ }
96+ access_token = create_access_token (
97+ assistantDict , expires_delta = access_token_expires
98+ )
99+
100+ result = db_model .model_dump ()
101+ result ["token" ] = access_token
102+ return result
49103
50104
51105@router .get ("/app/{appId}" , include_in_schema = False )
@@ -67,7 +121,7 @@ async def getApp(request: Request, response: Response, session: SessionDep, tran
67121 return db_model
68122
69123
70- @router .get ("/validator" , response_model = AssistantValidator , include_in_schema = False )
124+ """ @router.get("/validator", response_model=AssistantValidator, include_in_schema=False)
71125async def validator(session: SessionDep, id: int, virtual: Optional[int] = Query(None)):
72126 if not id:
73127 raise Exception('miss assistant id')
@@ -89,7 +143,7 @@ async def validator(session: SessionDep, id: int, virtual: Optional[int] = Query
89143 access_token = create_access_token(
90144 assistantDict, expires_delta=access_token_expires
91145 )
92- return AssistantValidator (True , True , True , access_token )
146+ return AssistantValidator(True, True, True, access_token) """
93147
94148
95149@router .get ('/picture/{file_id}' , summary = f"{ PLACEHOLDER_PREFIX } assistant_picture_api" , description = f"{ PLACEHOLDER_PREFIX } assistant_picture_api" )
0 commit comments