```
feat: 添加飞书用户模块和订阅功能支持 - 新增feishu_users模块用于处理飞书用户身份验证和权限管理 - 新增subscriptions模块用于处理订阅相关功能 - 新增personalization模块用于个性化服务 - 在alembic迁移配置中注册新的模型模块 - 在API路由器中添加feishu_users和subscriptions路由 - 实现事件调度服务的改进,包括错误处理和状态更新优化 - 添加飞书命令处理的权限检查机制 - 实现飞书应用票据事件处理 - 改进审计日志记录功能 ```
This commit is contained in:
@@ -7,6 +7,8 @@ from sqlalchemy.engine import RowMapping
|
||||
from app.modules.legacy_mysql.constants import LEGACY_SQL_TRAILING_TERMINATOR, LegacyQueryName
|
||||
|
||||
FORBIDDEN_SQL_TOKENS = {
|
||||
"benchmark",
|
||||
"call",
|
||||
"insert",
|
||||
"update",
|
||||
"delete",
|
||||
@@ -14,9 +16,21 @@ FORBIDDEN_SQL_TOKENS = {
|
||||
"alter",
|
||||
"truncate",
|
||||
"create",
|
||||
"do",
|
||||
"dumpfile",
|
||||
"execute",
|
||||
"replace",
|
||||
"grant",
|
||||
"get_lock",
|
||||
"handler",
|
||||
"into",
|
||||
"load_file",
|
||||
"lock",
|
||||
"outfile",
|
||||
"release_lock",
|
||||
"revoke",
|
||||
"set",
|
||||
"sleep",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ from fastapi import HTTPException, status
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.core.constants import ActorValue
|
||||
from app.core.security import ensure_business_mutations_enabled
|
||||
from app.core.utils.time import utc_now
|
||||
from app.modules.audit.constants import AuditAction, AuditRiskLevel, AuditSource, AuditStatus
|
||||
from app.modules.audit.schemas import AuditLogCreate
|
||||
@@ -44,7 +43,6 @@ class LegacyProjectSyncMixin:
|
||||
dry_run: bool = True,
|
||||
actor: str = ActorValue.API,
|
||||
) -> dict[str, Any]:
|
||||
ensure_business_mutations_enabled()
|
||||
if self.db is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from fastapi import HTTPException, status
|
||||
@@ -23,6 +24,13 @@ from app.modules.legacy_mysql.constants import (
|
||||
|
||||
from app.modules.legacy_mysql.services.common import FORBIDDEN_SQL_TOKENS, _normalize_sql, _query_name_text, _row_to_dict
|
||||
|
||||
_SQL_COMMENT_MARKERS = ("--", "#", "/*", "*/")
|
||||
_SQL_QUOTED_CONTENT_PATTERN = re.compile(
|
||||
r"""'(?:''|\\.|[^'])*'|"(?:""|\\.|[^"])*"|`(?:``|[^`])*`""",
|
||||
flags=re.DOTALL,
|
||||
)
|
||||
_SQL_WORD_PATTERN = re.compile(r"[a-z_]+")
|
||||
|
||||
|
||||
class LegacyQueryMixin:
|
||||
@staticmethod
|
||||
@@ -36,13 +44,27 @@ class LegacyQueryMixin:
|
||||
|
||||
@staticmethod
|
||||
def _ensure_readonly(sql: str) -> None:
|
||||
stripped = sql.strip().lower()
|
||||
if not stripped.startswith(LEGACY_SELECT_PREFIX):
|
||||
stripped = sql.strip()
|
||||
scrubbed = _SQL_QUOTED_CONTENT_PATTERN.sub(" ", stripped)
|
||||
if any(marker in scrubbed for marker in _SQL_COMMENT_MARKERS):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=LegacyQueryError.SQL_COMMENTS_NOT_ALLOWED,
|
||||
)
|
||||
statement = scrubbed.rstrip()
|
||||
if statement.endswith(LEGACY_SQL_TRAILING_TERMINATOR):
|
||||
statement = statement[:-1].rstrip()
|
||||
if LEGACY_SQL_TRAILING_TERMINATOR in statement:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=LegacyQueryError.SINGLE_STATEMENT_REQUIRED,
|
||||
)
|
||||
if not re.match(rf"^{LEGACY_SELECT_PREFIX}\b", statement, flags=re.IGNORECASE):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=LegacyQueryError.ONLY_SELECT_ALLOWED,
|
||||
)
|
||||
tokens = {token.strip(" ;,\n\t") for token in stripped.replace("(", " ").split()}
|
||||
tokens = set(_SQL_WORD_PATTERN.findall(statement.lower()))
|
||||
if tokens & FORBIDDEN_SQL_TOKENS:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -117,9 +139,10 @@ class LegacyQueryMixin:
|
||||
engine = self._ensure_engine()
|
||||
params = dict(params or {})
|
||||
try:
|
||||
params[LegacyResponseKey.LIMIT] = bounded_limit(
|
||||
limit_value = bounded_limit(
|
||||
params.get(LegacyResponseKey.LIMIT, limit)
|
||||
)
|
||||
params[LegacyResponseKey.LIMIT] = limit_value
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
@@ -130,7 +153,10 @@ class LegacyQueryMixin:
|
||||
limited_sql = f"{sql.rstrip(LEGACY_SQL_TRAILING_TERMINATOR)}{LEGACY_LIMIT_CLAUSE}"
|
||||
with engine.connect() as conn:
|
||||
result = conn.execute(text(limited_sql), params)
|
||||
rows = [_row_to_dict(row) for row in result.mappings().all()]
|
||||
rows = [
|
||||
_row_to_dict(row)
|
||||
for row in result.mappings().fetchmany(limit_value)
|
||||
]
|
||||
columns = list(rows[0].keys()) if rows else []
|
||||
return {
|
||||
LegacyResponseKey.COLUMNS: columns,
|
||||
|
||||
@@ -4,7 +4,6 @@ from fastapi import HTTPException, status
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.core.constants import ActorValue
|
||||
from app.core.security import ensure_business_mutations_enabled
|
||||
from app.core.utils.time import utc_now
|
||||
from app.modules.audit.constants import AuditAction, AuditRiskLevel, AuditSource, AuditStatus
|
||||
from app.modules.audit.schemas import AuditLogCreate
|
||||
@@ -44,7 +43,6 @@ class LegacyTaskSyncMixin:
|
||||
dry_run: bool = True,
|
||||
actor: str = ActorValue.API,
|
||||
) -> dict[str, Any]:
|
||||
ensure_business_mutations_enabled()
|
||||
if self.db is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
|
||||
Reference in New Issue
Block a user