fix(security): close backend merge blockers
This commit is contained in:
@@ -7,6 +7,21 @@ from fastapi.testclient import TestClient
|
||||
|
||||
from app.infra.audit import middleware as audit_middleware
|
||||
from app.infra.audit.middleware import AuditMiddleware
|
||||
from app.core.audit import sanitize_sensitive_data
|
||||
|
||||
|
||||
def test_sanitize_sensitive_data_redacts_database_dsn() -> None:
|
||||
raw_dsn = "postgresql://alice:supersecret@db.internal/project"
|
||||
|
||||
sanitized = sanitize_sensitive_data(
|
||||
{"dsn": raw_dsn, "database": {"readonly_dsn": raw_dsn}}
|
||||
)
|
||||
|
||||
assert sanitized == {
|
||||
"dsn": "***REDACTED***",
|
||||
"database": {"readonly_dsn": "***REDACTED***"},
|
||||
}
|
||||
assert raw_dsn not in str(sanitized)
|
||||
|
||||
|
||||
def test_post_streaming_response_survives_audit_body_capture(monkeypatch):
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
@@ -11,8 +12,11 @@ from fastapi.testclient import TestClient
|
||||
|
||||
from app.api.v1.endpoints import schemes as schemes_endpoint
|
||||
from app.api.v1.endpoints import simulation as simulation_endpoint
|
||||
from app.api.v1.endpoints import cache as cache_endpoint
|
||||
from app.api.pagination import PaginatedList
|
||||
from app.api.v1.rest_router import api_router, build_rest_router
|
||||
from app.api.v1.router import api_router as source_api_router
|
||||
from app.auth.metadata_dependencies import get_current_metadata_user
|
||||
from app.auth.project_dependencies import ProjectContext, get_project_context
|
||||
from scripts.check_openapi import current_contract_bytes, validate
|
||||
|
||||
@@ -130,6 +134,12 @@ def test_rest_contract_uses_header_project_context() -> None:
|
||||
assert "network" not in schema.get("properties", {})
|
||||
assert "network_name" not in schema.get("properties", {})
|
||||
|
||||
placement_schema = document["components"]["schemas"][
|
||||
"PressureSensorPlacementRest"
|
||||
]
|
||||
assert "name" not in placement_schema["properties"]
|
||||
assert "username" not in placement_schema["properties"]
|
||||
|
||||
assert "/api/v1/burst-analysis" not in document["paths"]
|
||||
assert "/api/v1/getpipeproperties/" not in document["paths"]
|
||||
|
||||
@@ -280,7 +290,7 @@ def test_rest_runtime_wraps_handler_paginated_list() -> None:
|
||||
limit: int = Query(2, ge=1, le=10),
|
||||
) -> list[int]:
|
||||
records = [10, 20, 30, 40]
|
||||
return records[skip : skip + limit]
|
||||
return PaginatedList(records[skip : skip + limit], total=len(records))
|
||||
|
||||
app = FastAPI(redirect_slashes=False)
|
||||
app.include_router(build_rest_router(source_router.routes), prefix="/api/v1")
|
||||
@@ -293,12 +303,76 @@ def test_rest_runtime_wraps_handler_paginated_list() -> None:
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"items": [20, 30],
|
||||
"total": 3,
|
||||
"total": 4,
|
||||
"limit": 2,
|
||||
"offset": 1,
|
||||
}
|
||||
|
||||
|
||||
def test_sensor_placement_body_uses_authenticated_project_and_user(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
captured: dict[str, object] = {}
|
||||
|
||||
def fake_pressure_sensor_placement_kmeans(**kwargs):
|
||||
captured.update(kwargs)
|
||||
|
||||
monkeypatch.setattr(
|
||||
simulation_endpoint,
|
||||
"pressure_sensor_placement_kmeans",
|
||||
fake_pressure_sensor_placement_kmeans,
|
||||
)
|
||||
app = FastAPI(redirect_slashes=False)
|
||||
app.include_router(api_router, prefix="/api/v1")
|
||||
app.dependency_overrides[get_project_context] = lambda: ProjectContext(
|
||||
project_id=uuid4(),
|
||||
project_code="project_a",
|
||||
user_id=uuid4(),
|
||||
project_role="member",
|
||||
)
|
||||
app.dependency_overrides[get_current_metadata_user] = lambda: type(
|
||||
"User", (), {"username": "alice"}
|
||||
)()
|
||||
|
||||
response = TestClient(app, raise_server_exceptions=False).post(
|
||||
"/api/v1/pressure-sensor-placement-kmeans",
|
||||
json={
|
||||
"scheme_name": "placement_01",
|
||||
"sensor_number": 5,
|
||||
"min_diameter": 100,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert captured == {
|
||||
"name": "project_a",
|
||||
"scheme_name": "placement_01",
|
||||
"sensor_number": 5,
|
||||
"min_diameter": 100,
|
||||
"username": "alice",
|
||||
}
|
||||
|
||||
|
||||
def test_cache_management_requires_environment_permission(monkeypatch) -> None:
|
||||
flushdb = Mock(return_value=True)
|
||||
monkeypatch.setattr(cache_endpoint.redis_client, "flushdb", flushdb)
|
||||
app = FastAPI(redirect_slashes=False)
|
||||
app.include_router(api_router, prefix="/api/v1")
|
||||
app.dependency_overrides[get_project_context] = lambda: ProjectContext(
|
||||
project_id=uuid4(),
|
||||
project_code="project_a",
|
||||
user_id=uuid4(),
|
||||
project_role="member",
|
||||
)
|
||||
|
||||
response = TestClient(app, raise_server_exceptions=False).delete(
|
||||
"/api/v1/all-redis"
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
flushdb.assert_not_called()
|
||||
|
||||
|
||||
def test_rest_runtime_json_encodes_untyped_datetime_response() -> None:
|
||||
source_router = APIRouter()
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
from app.native.wndb import s2_junctions
|
||||
|
||||
|
||||
def test_get_junction_binds_untrusted_identifier(monkeypatch) -> None:
|
||||
calls: list[tuple[str, str, tuple[str, ...]]] = []
|
||||
malicious_id = "J-1'; DELETE FROM junctions; --"
|
||||
|
||||
def fake_try_read(name, statement, params):
|
||||
calls.append((name, statement, params))
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(s2_junctions, "try_read", fake_try_read)
|
||||
|
||||
assert s2_junctions.get_junction("project_a", malicious_id) == {}
|
||||
assert calls == [
|
||||
(
|
||||
"project_a",
|
||||
"select * from junctions where id = %s",
|
||||
(malicious_id,),
|
||||
)
|
||||
]
|
||||
Reference in New Issue
Block a user