"""多租户公共依赖 — 账套模式:token优先,query/header降级为Bot服务白名单""" from fastapi import Request, Header, Query, Depends, HTTPException from typing import Optional from sqlalchemy.orm import Session from app.database import get_db from app.auth_middleware import get_token_entity_id from app.models import Entity def get_entity_id( request: Request, x_entity_id: Optional[str] = Header(None, alias="X-Entity-Id"), entity_id: Optional[int] = Query(None, ge=1), db: Session = Depends(get_db), ) -> int: """解析当前企业ID(账套模式):token优先 → query/header(Bot白名单)→ 默认1 解析链(倒置后): 1. Authorization Bearer token → 优先返回 token.entity_id(唯一可信来源) 2. query参数 / X-Entity-Id header → 仅Bot服务通道使用(校验entity状态active) 3. body中的 entity_id(POST场景)→ Bot服务通道兼容 4. 兜底默认 1(酣客) 安全说明:登录用户必须通过token绑定账套,query/header传入的entity_id 在token存在时被忽略,防止越权传参(旧漏洞:query优先且无授权校验)。 """ # 1. token优先(账套模式唯一来源) auth = request.headers.get("Authorization", "") if auth.startswith("Bearer "): token_entity = get_token_entity_id(auth[7:]) if token_entity is not None: return token_entity # token存在但是旧格式/无entity → 账套模式下强制走白名单或默认(由require_auth拦截) # 这里不抛401:公开接口可能带旧token,交给require_auth统一处理 # 2. query/header → Bot服务通道白名单(校验entity状态active) candidate = None if entity_id is not None: candidate = entity_id elif x_entity_id and x_entity_id.isdigit(): candidate = int(x_entity_id) # 3. body 中的 entity_id(POST场景,Bot通道兼容) if candidate is None and request.method in ("POST", "PUT", "PATCH"): try: body = request.state.body_json or {} if body.get("entity_id"): candidate = int(body["entity_id"]) except Exception: pass if candidate is not None: ent = db.query(Entity).filter(Entity.id == candidate).first() if ent and ent.status == "active": return candidate # Bot通道校验不通过:不返回默认,直接拒绝(防越权注入无效entity) raise HTTPException(403, f"企业 entity_id={candidate} 不存在或未激活") return 1 # 默认酣客(无token、无参数时兜底,兼容存量公开接口)