perf: Multiple Domain Validation for Embedded Systems #388

This commit is contained in:
fit2cloud-chenyw
2025-11-25 21:43:21 +08:00
committed by fit2cloud-chenyw
parent a5ba14275e
commit c7693fc880
10 changed files with 86 additions and 27 deletions
+8 -5
View File
@@ -17,7 +17,7 @@ from common.core.config import settings
from common.core.deps import SessionDep, Trans
from common.core.security import create_access_token
from common.core.sqlbot_cache import clear_cache
from common.utils.utils import get_origin_from_referer
from common.utils.utils import get_origin_from_referer, origin_match_domain
router = APIRouter(tags=["system/assistant"], prefix="/system/assistant")
@@ -30,13 +30,15 @@ async def info(request: Request, response: Response, session: SessionDep, trans:
if not db_model:
raise RuntimeError(f"assistant application not exist")
db_model = AssistantModel.model_validate(db_model)
response.headers["Access-Control-Allow-Origin"] = db_model.domain
origin = request.headers.get("origin") or get_origin_from_referer(request)
if not origin:
raise RuntimeError(trans('i18n_embedded.invalid_origin', origin=origin or ''))
origin = origin.rstrip('/')
if origin != db_model.domain:
if not origin_match_domain(origin, db_model.domain):
raise RuntimeError(trans('i18n_embedded.invalid_origin', origin=origin or ''))
response.headers["Access-Control-Allow-Origin"] = origin
return db_model
@@ -48,13 +50,14 @@ async def getApp(request: Request, response: Response, session: SessionDep, tran
if not db_model:
raise RuntimeError(f"assistant application not exist")
db_model = AssistantModel.model_validate(db_model)
response.headers["Access-Control-Allow-Origin"] = db_model.domain
origin = request.headers.get("origin") or get_origin_from_referer(request)
if not origin:
raise RuntimeError(trans('i18n_embedded.invalid_origin', origin=origin or ''))
origin = origin.rstrip('/')
if origin != db_model.domain:
if not origin_match_domain(origin, db_model.domain):
raise RuntimeError(trans('i18n_embedded.invalid_origin', origin=origin or ''))
response.headers["Access-Control-Allow-Origin"] = origin
return db_model
+16
View File
@@ -99,17 +99,22 @@ class AssistantOutDs:
assistant: AssistantHeader
ds_list: Optional[list[AssistantOutDsSchema]] = None
certificate: Optional[str] = None
request_origin: Optional[str] = None
def __init__(self, assistant: AssistantHeader):
self.assistant = assistant
self.ds_list = None
self.certificate = assistant.certificate
self.request_origin = assistant.request_origin
self.get_ds_from_api()
# @cache(namespace=CacheNamespace.EMBEDDED_INFO, cacheName=CacheName.ASSISTANT_DS, keyExpression="current_user.id")
def get_ds_from_api(self):
config: dict[any] = json.loads(self.assistant.configuration)
endpoint: str = config['endpoint']
endpoint = self.get_complete_endpoint(endpoint=endpoint)
if not endpoint:
raise Exception(f"Failed to get datasource list from {config['endpoint']}, error: [Assistant domain or endpoint miss]")
certificateList: list[any] = json.loads(self.certificate)
header = {}
cookies = {}
@@ -137,6 +142,17 @@ class AssistantOutDs:
else:
raise Exception(f"Failed to get datasource list from {endpoint}, status code: {res.status_code}")
def get_complete_endpoint(self, endpoint: str) -> str | None:
if endpoint.startswith("http://") or endpoint.startswith("https://"):
return endpoint
domain_text = self.assistant.domain
if not domain_text:
return None
if ',' in domain_text:
return self.request_origin.strip('/') if self.request_origin else domain_text.split(',')[0].strip('/') + endpoint
else:
return f"{domain_text}{endpoint}"
def get_simple_ds_list(self):
if self.ds_list:
return [{'id': ds.id, 'name': ds.name, 'description': ds.comment} for ds in self.ds_list]
+4 -1
View File
@@ -16,7 +16,7 @@ from common.core import security
from common.core.config import settings
from common.core.schemas import TokenPayload
from common.utils.locale import I18n
from common.utils.utils import SQLBotLogUtil
from common.utils.utils import SQLBotLogUtil, get_origin_from_referer
from common.utils.whitelist import whiteUtils
from fastapi.security.utils import get_authorization_scheme_param
from common.core.deps import get_i18n
@@ -40,6 +40,9 @@ class TokenMiddleware(BaseHTTPMiddleware):
if validator[0]:
request.state.current_user = validator[1]
request.state.assistant = validator[2]
origin = request.headers.get("origin") or get_origin_from_referer(request)
if origin and validator[2]:
request.state.assistant.request_origin = origin
return await call_next(request)
message = trans('i18n_permission.authenticate_invalid', msg = validator[1])
return JSONResponse(message, status_code=401, headers={"Access-Control-Allow-Origin": "*"})
@@ -116,6 +116,7 @@ class AssistantHeader(AssistantDTO):
unique: Optional[str] = None
certificate: Optional[str] = None
online: bool = False
request_origin: Optional[str] = None
class AssistantValidator(BaseModel):
+6
View File
@@ -263,6 +263,12 @@ def get_origin_from_referer(request: Request):
SQLBotLogUtil.error(f"解析 Referer 出错: {e}")
return referer
def origin_match_domain(origin: str, domain: str) -> bool:
if not origin or not domain:
return False
origin_text = origin.rstrip('/')
domain_list = domain.replace(" ", "").split(',')
return origin_text in [d.rstrip('/') for d in domain_list]
def equals_ignore_case(str1: str, *args: str) -> bool:
if str1 is None: