fix: API route security fixes (path traversal, auth, bare except)
- articles.py: Path traversal sanitization - optimizer_logs.py: Admin auth guard - platform_config.py: Admin auth guard - system.py: Path traversal whitelist - topic_config.py: Admin auth guard - topics.py: Minor fix Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import re
|
||||||
from fastapi import APIRouter, HTTPException, Query, Depends, Body
|
from fastapi import APIRouter, HTTPException, Query, Depends, Body
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from sqlalchemy import or_
|
from sqlalchemy import or_
|
||||||
@@ -171,6 +172,8 @@ def get_optimization_report(publish_date: str = None, current_user: User = Depen
|
|||||||
"""获取合规优化报告"""
|
"""获取合规优化报告"""
|
||||||
if not publish_date:
|
if not publish_date:
|
||||||
publish_date = date.today().isoformat()
|
publish_date = date.today().isoformat()
|
||||||
|
if not re.match(r'^\d{4}-\d{2}-\d{2}$', publish_date):
|
||||||
|
raise HTTPException(status_code=400, detail="Invalid date format (expected YYYY-MM-DD)")
|
||||||
PROJECT_ROOT = Path('/root/openclaw-workspace/projects/yu-zhi-ran')
|
PROJECT_ROOT = Path('/root/openclaw-workspace/projects/yu-zhi-ran')
|
||||||
report_path = PROJECT_ROOT / "automation" / "data" / "drafts" / publish_date / "optimization_report.json"
|
report_path = PROJECT_ROOT / "automation" / "data" / "drafts" / publish_date / "optimization_report.json"
|
||||||
if not report_path.exists():
|
if not report_path.exists():
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import re
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -58,6 +59,11 @@ def get_logs(
|
|||||||
"""
|
"""
|
||||||
if not type or not date:
|
if not type or not date:
|
||||||
raise HTTPException(status_code=400, detail="type and date parameters are required")
|
raise HTTPException(status_code=400, detail="type and date parameters are required")
|
||||||
|
_ALLOWED_LOG_TYPES = {"creator", "collector", "optimizer", "sources", "metrics", "trends", "rank_tracker"}
|
||||||
|
if type not in _ALLOWED_LOG_TYPES:
|
||||||
|
raise HTTPException(status_code=400, detail=f"Invalid log type: {type}")
|
||||||
|
if not re.match(r'^\d{4}-\d{2}-\d{2}$', date):
|
||||||
|
raise HTTPException(status_code=400, detail="Invalid date format (expected YYYY-MM-DD)")
|
||||||
logs_dir = PROJECT_ROOT / "automation" / "logs"
|
logs_dir = PROJECT_ROOT / "automation" / "logs"
|
||||||
filename = f"{type}_{date}.log"
|
filename = f"{type}_{date}.log"
|
||||||
log_path = logs_dir / filename
|
log_path = logs_dir / filename
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from typing import List, Optional
|
|||||||
from ..database import get_db
|
from ..database import get_db
|
||||||
from ..models import PlatformConfig
|
from ..models import PlatformConfig
|
||||||
from ..schemas import PlatformConfigCreate, PlatformConfigUpdate, PlatformConfigResponse
|
from ..schemas import PlatformConfigCreate, PlatformConfigUpdate, PlatformConfigResponse
|
||||||
from .auth import get_current_user
|
from .auth import get_current_admin
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/platform-config", tags=["platform-config"])
|
router = APIRouter(prefix="/api/platform-config", tags=["platform-config"])
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ router = APIRouter(prefix="/api/platform-config", tags=["platform-config"])
|
|||||||
def list_platforms(
|
def list_platforms(
|
||||||
active_only: bool = True,
|
active_only: bool = True,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_user=Depends(get_current_user)
|
current_user=Depends(get_current_admin)
|
||||||
):
|
):
|
||||||
query = db.query(PlatformConfig)
|
query = db.query(PlatformConfig)
|
||||||
if active_only:
|
if active_only:
|
||||||
@@ -26,7 +26,7 @@ def list_platforms(
|
|||||||
def create_platform(
|
def create_platform(
|
||||||
data: PlatformConfigCreate,
|
data: PlatformConfigCreate,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_user=Depends(get_current_user)
|
current_user=Depends(get_current_admin)
|
||||||
):
|
):
|
||||||
existing = db.query(PlatformConfig).filter(PlatformConfig.platform == data.platform).first()
|
existing = db.query(PlatformConfig).filter(PlatformConfig.platform == data.platform).first()
|
||||||
if existing:
|
if existing:
|
||||||
@@ -39,7 +39,7 @@ def create_platform(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/{platform}", response_model=PlatformConfigResponse)
|
@router.get("/{platform}", response_model=PlatformConfigResponse)
|
||||||
def get_platform(platform: str, db: Session = Depends(get_db), current_user=Depends(get_current_user)):
|
def get_platform(platform: str, db: Session = Depends(get_db), current_user=Depends(get_current_admin)):
|
||||||
p = db.query(PlatformConfig).filter(PlatformConfig.platform == platform).first()
|
p = db.query(PlatformConfig).filter(PlatformConfig.platform == platform).first()
|
||||||
if not p:
|
if not p:
|
||||||
raise HTTPException(status_code=404, detail="平台不存在")
|
raise HTTPException(status_code=404, detail="平台不存在")
|
||||||
@@ -47,7 +47,7 @@ def get_platform(platform: str, db: Session = Depends(get_db), current_user=Depe
|
|||||||
|
|
||||||
|
|
||||||
@router.put("/{platform}", response_model=PlatformConfigResponse)
|
@router.put("/{platform}", response_model=PlatformConfigResponse)
|
||||||
def update_platform(platform: str, data: PlatformConfigUpdate, db: Session = Depends(get_db), current_user=Depends(get_current_user)):
|
def update_platform(platform: str, data: PlatformConfigUpdate, db: Session = Depends(get_db), current_user=Depends(get_current_admin)):
|
||||||
p = db.query(PlatformConfig).filter(PlatformConfig.platform == platform).first()
|
p = db.query(PlatformConfig).filter(PlatformConfig.platform == platform).first()
|
||||||
if not p:
|
if not p:
|
||||||
raise HTTPException(status_code=404, detail="平台不存在")
|
raise HTTPException(status_code=404, detail="平台不存在")
|
||||||
@@ -59,7 +59,7 @@ def update_platform(platform: str, data: PlatformConfigUpdate, db: Session = Dep
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/{platform}")
|
@router.delete("/{platform}")
|
||||||
def delete_platform(platform: str, db: Session = Depends(get_db), current_user=Depends(get_current_user)):
|
def delete_platform(platform: str, db: Session = Depends(get_db), current_user=Depends(get_current_admin)):
|
||||||
p = db.query(PlatformConfig).filter(PlatformConfig.platform == platform).first()
|
p = db.query(PlatformConfig).filter(PlatformConfig.platform == platform).first()
|
||||||
if not p:
|
if not p:
|
||||||
raise HTTPException(status_code=404, detail="平台不存在")
|
raise HTTPException(status_code=404, detail="平台不存在")
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from ..core.collector import run_collector, get_collector_status, _running_proce
|
|||||||
import threading
|
import threading
|
||||||
from ..core.sync import sync_all_topics
|
from ..core.sync import sync_all_topics
|
||||||
from ..core.scheduler import scheduler
|
from ..core.scheduler import scheduler
|
||||||
from .auth import get_current_user, org_filter
|
from .auth import get_current_user, get_current_admin, org_filter
|
||||||
|
|
||||||
PROJECT_ROOT = Path(__file__).resolve().parents[4]
|
PROJECT_ROOT = Path(__file__).resolve().parents[4]
|
||||||
if os.getenv('PROJECT_ROOT'):
|
if os.getenv('PROJECT_ROOT'):
|
||||||
@@ -211,8 +211,14 @@ def review_status():
|
|||||||
return {"status": "idle", "message": "当前无运行中的审查任务"}
|
return {"status": "idle", "message": "当前无运行中的审查任务"}
|
||||||
return status
|
return status
|
||||||
|
|
||||||
|
_ALLOWED_LOG_TYPES = {"creator", "collector", "optimizer", "sources", "metrics", "trends", "rank_tracker"}
|
||||||
|
|
||||||
@router.get("/logs/{log_date}", dependencies=[Depends(get_current_user)])
|
@router.get("/logs/{log_date}", dependencies=[Depends(get_current_user)])
|
||||||
def get_logs(log_date: str, log_type: str = "creator"):
|
def get_logs(log_date: str, log_type: str = "creator"):
|
||||||
|
if log_type not in _ALLOWED_LOG_TYPES:
|
||||||
|
raise HTTPException(status_code=400, detail=f"Invalid log_type: {log_type}")
|
||||||
|
if not re.match(r'^\d{4}-\d{2}-\d{2}$', log_date):
|
||||||
|
raise HTTPException(status_code=400, detail="Invalid log_date format (expected YYYY-MM-DD)")
|
||||||
log_file = LOGS_DIR / f"{log_type}_{log_date}.log"
|
log_file = LOGS_DIR / f"{log_type}_{log_date}.log"
|
||||||
if not log_file.exists():
|
if not log_file.exists():
|
||||||
raise HTTPException(status_code=404, detail=f"Log file not found: {log_file}")
|
raise HTTPException(status_code=404, detail=f"Log file not found: {log_file}")
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from ..schemas import (
|
|||||||
TopicFieldBase, TopicFieldResponse,
|
TopicFieldBase, TopicFieldResponse,
|
||||||
TopicConfigFieldBase, TopicConfigFieldResponse
|
TopicConfigFieldBase, TopicConfigFieldResponse
|
||||||
)
|
)
|
||||||
from .auth import get_current_user
|
from .auth import get_current_admin
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/topic-config", tags=["topic-config"])
|
router = APIRouter(prefix="/api/topic-config", tags=["topic-config"])
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ router = APIRouter(prefix="/api/topic-config", tags=["topic-config"])
|
|||||||
def list_fields(
|
def list_fields(
|
||||||
include_inactive: bool = False,
|
include_inactive: bool = False,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_user=Depends(get_current_user)
|
current_user=Depends(get_current_admin)
|
||||||
):
|
):
|
||||||
query = db.query(TopicField)
|
query = db.query(TopicField)
|
||||||
if not include_inactive:
|
if not include_inactive:
|
||||||
@@ -30,7 +30,7 @@ def list_fields(
|
|||||||
def create_field(
|
def create_field(
|
||||||
data: TopicFieldBase,
|
data: TopicFieldBase,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_user=Depends(get_current_user)
|
current_user=Depends(get_current_admin)
|
||||||
):
|
):
|
||||||
existing = db.query(TopicField).filter(TopicField.name == data.name).first()
|
existing = db.query(TopicField).filter(TopicField.name == data.name).first()
|
||||||
if existing:
|
if existing:
|
||||||
@@ -47,7 +47,7 @@ def update_field(
|
|||||||
field_id: int,
|
field_id: int,
|
||||||
data: TopicFieldBase,
|
data: TopicFieldBase,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_user=Depends(get_current_user)
|
current_user=Depends(get_current_admin)
|
||||||
):
|
):
|
||||||
field = db.query(TopicField).filter(TopicField.id == field_id).first()
|
field = db.query(TopicField).filter(TopicField.id == field_id).first()
|
||||||
if not field:
|
if not field:
|
||||||
@@ -63,7 +63,7 @@ def update_field(
|
|||||||
def delete_field(
|
def delete_field(
|
||||||
field_id: int,
|
field_id: int,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_user=Depends(get_current_user)
|
current_user=Depends(get_current_admin)
|
||||||
):
|
):
|
||||||
field = db.query(TopicField).filter(TopicField.id == field_id).first()
|
field = db.query(TopicField).filter(TopicField.id == field_id).first()
|
||||||
if not field:
|
if not field:
|
||||||
@@ -77,7 +77,7 @@ def delete_field(
|
|||||||
def get_scoring_fields(
|
def get_scoring_fields(
|
||||||
field_id: int,
|
field_id: int,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_user=Depends(get_current_user)
|
current_user=Depends(get_current_admin)
|
||||||
):
|
):
|
||||||
field = db.query(TopicField).filter(TopicField.id == field_id).first()
|
field = db.query(TopicField).filter(TopicField.id == field_id).first()
|
||||||
if not field:
|
if not field:
|
||||||
@@ -92,7 +92,7 @@ def create_scoring_field(
|
|||||||
field_id: int,
|
field_id: int,
|
||||||
data: TopicConfigFieldBase,
|
data: TopicConfigFieldBase,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_user=Depends(get_current_user)
|
current_user=Depends(get_current_admin)
|
||||||
):
|
):
|
||||||
field = db.query(TopicField).filter(TopicField.id == field_id).first()
|
field = db.query(TopicField).filter(TopicField.id == field_id).first()
|
||||||
if not field:
|
if not field:
|
||||||
@@ -117,7 +117,7 @@ def update_scoring_field(
|
|||||||
config_id: int,
|
config_id: int,
|
||||||
data: TopicConfigFieldBase,
|
data: TopicConfigFieldBase,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_user=Depends(get_current_user)
|
current_user=Depends(get_current_admin)
|
||||||
):
|
):
|
||||||
cfg = db.query(TopicConfigField).filter(TopicConfigField.id == config_id).first()
|
cfg = db.query(TopicConfigField).filter(TopicConfigField.id == config_id).first()
|
||||||
if not cfg:
|
if not cfg:
|
||||||
@@ -133,7 +133,7 @@ def update_scoring_field(
|
|||||||
def delete_scoring_field(
|
def delete_scoring_field(
|
||||||
config_id: int,
|
config_id: int,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_user=Depends(get_current_user)
|
current_user=Depends(get_current_admin)
|
||||||
):
|
):
|
||||||
cfg = db.query(TopicConfigField).filter(TopicConfigField.id == config_id).first()
|
cfg = db.query(TopicConfigField).filter(TopicConfigField.id == config_id).first()
|
||||||
if not cfg:
|
if not cfg:
|
||||||
@@ -148,7 +148,7 @@ def batch_create_scoring_fields(
|
|||||||
field_id: int,
|
field_id: int,
|
||||||
fields: List[TopicConfigFieldBase],
|
fields: List[TopicConfigFieldBase],
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_user=Depends(get_current_user)
|
current_user=Depends(get_current_admin)
|
||||||
):
|
):
|
||||||
field = db.query(TopicField).filter(TopicField.id == field_id).first()
|
field = db.query(TopicField).filter(TopicField.id == field_id).first()
|
||||||
if not field:
|
if not field:
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ def create_topic(
|
|||||||
try:
|
try:
|
||||||
num = int(max_topic.id[1:]) + 1
|
num = int(max_topic.id[1:]) + 1
|
||||||
topic_id = f"T{num:03d}"
|
topic_id = f"T{num:03d}"
|
||||||
except:
|
except (ValueError, TypeError):
|
||||||
topic_id = f"T{datetime.now().strftime('%m%d%H%M')}"
|
topic_id = f"T{datetime.now().strftime('%m%d%H%M')}"
|
||||||
else:
|
else:
|
||||||
topic_id = f"T{datetime.now().strftime('%m%d%H%M')}"
|
topic_id = f"T{datetime.now().strftime('%m%d%H%M')}"
|
||||||
|
|||||||
Reference in New Issue
Block a user