mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-31 10:12:21 +01:00
Merge pull request #6904 from dxrpl/fix/calendar-config-object-body
fix(calendar): answer 400 when a CalDAV config body is not a JSON object (#6903)
This commit is contained in:
+16
-16
@@ -80,6 +80,18 @@ def _require_user(request: Request) -> str:
|
|||||||
return FALLBACK_OWNER
|
return FALLBACK_OWNER
|
||||||
|
|
||||||
|
|
||||||
|
async def _json_object_body(request: Request) -> dict:
|
||||||
|
"""Parse a JSON object body. An unreadable body counts as empty; a body
|
||||||
|
that is valid JSON but not an object (a list, string or number) is a 400."""
|
||||||
|
try:
|
||||||
|
body = await request.json()
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
if not isinstance(body, dict):
|
||||||
|
raise HTTPException(400, "Request body must be a JSON object")
|
||||||
|
return body
|
||||||
|
|
||||||
|
|
||||||
def _get_or_404_calendar(db, cal_id: str, owner: str) -> CalendarCal:
|
def _get_or_404_calendar(db, cal_id: str, owner: str) -> CalendarCal:
|
||||||
cal = db.query(CalendarCal).filter(CalendarCal.id == cal_id).first()
|
cal = db.query(CalendarCal).filter(CalendarCal.id == cal_id).first()
|
||||||
if not cal:
|
if not cal:
|
||||||
@@ -994,10 +1006,7 @@ def setup_calendar_routes(upload_handler=None) -> APIRouter:
|
|||||||
async def save_config(request: Request):
|
async def save_config(request: Request):
|
||||||
"""Legacy single-account endpoint — upserts the first account."""
|
"""Legacy single-account endpoint — upserts the first account."""
|
||||||
owner = _require_user(request)
|
owner = _require_user(request)
|
||||||
try:
|
body = await _json_object_body(request)
|
||||||
body = await request.json()
|
|
||||||
except Exception:
|
|
||||||
body = {}
|
|
||||||
accounts = _get_caldav_accounts(owner)
|
accounts = _get_caldav_accounts(owner)
|
||||||
if not (body.get("url") or "").strip():
|
if not (body.get("url") or "").strip():
|
||||||
_save_caldav_accounts(owner, [])
|
_save_caldav_accounts(owner, [])
|
||||||
@@ -1052,10 +1061,7 @@ def setup_calendar_routes(upload_handler=None) -> APIRouter:
|
|||||||
"""Add a new CalDAV account."""
|
"""Add a new CalDAV account."""
|
||||||
import uuid as _uuid
|
import uuid as _uuid
|
||||||
owner = _require_user(request)
|
owner = _require_user(request)
|
||||||
try:
|
body = await _json_object_body(request)
|
||||||
body = await request.json()
|
|
||||||
except Exception:
|
|
||||||
body = {}
|
|
||||||
from src.caldav_sync import validate_caldav_url
|
from src.caldav_sync import validate_caldav_url
|
||||||
try:
|
try:
|
||||||
url = validate_caldav_url(body.get("url", ""))
|
url = validate_caldav_url(body.get("url", ""))
|
||||||
@@ -1080,10 +1086,7 @@ def setup_calendar_routes(upload_handler=None) -> APIRouter:
|
|||||||
async def update_caldav_account(account_id: str, request: Request):
|
async def update_caldav_account(account_id: str, request: Request):
|
||||||
"""Update an existing CalDAV account by id."""
|
"""Update an existing CalDAV account by id."""
|
||||||
owner = _require_user(request)
|
owner = _require_user(request)
|
||||||
try:
|
body = await _json_object_body(request)
|
||||||
body = await request.json()
|
|
||||||
except Exception:
|
|
||||||
body = {}
|
|
||||||
accounts = _get_caldav_accounts(owner)
|
accounts = _get_caldav_accounts(owner)
|
||||||
idx = next((i for i, a in enumerate(accounts) if a.get("id") == account_id), None)
|
idx = next((i for i, a in enumerate(accounts) if a.get("id") == account_id), None)
|
||||||
if idx is None:
|
if idx is None:
|
||||||
@@ -1124,10 +1127,7 @@ def setup_calendar_routes(upload_handler=None) -> APIRouter:
|
|||||||
test an already-saved account. Falls back to the first saved account
|
test an already-saved account. Falls back to the first saved account
|
||||||
when nothing is provided."""
|
when nothing is provided."""
|
||||||
owner = _require_user(request)
|
owner = _require_user(request)
|
||||||
try:
|
body = await _json_object_body(request)
|
||||||
body = await request.json()
|
|
||||||
except Exception:
|
|
||||||
body = {}
|
|
||||||
url = (body.get("url") or "").strip()
|
url = (body.get("url") or "").strip()
|
||||||
user = (body.get("username") or "").strip()
|
user = (body.get("username") or "").strip()
|
||||||
pw = body.get("password") or ""
|
pw = body.get("password") or ""
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
"""The CalDAV config routes answer 400 for a body that is JSON but not an object."""
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from fastapi import HTTPException
|
||||||
|
|
||||||
|
from routes.calendar_routes import _json_object_body
|
||||||
|
|
||||||
|
|
||||||
|
def _request(raw: bytes):
|
||||||
|
async def _json():
|
||||||
|
return json.loads(raw)
|
||||||
|
return SimpleNamespace(json=_json)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse(raw: bytes):
|
||||||
|
return asyncio.run(_json_object_body(_request(raw)))
|
||||||
|
|
||||||
|
|
||||||
|
def test_object_body_is_returned():
|
||||||
|
assert _parse(b'{"url": "https://dav.example/"}') == {"url": "https://dav.example/"}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("raw", [b"", b"not json"])
|
||||||
|
def test_unreadable_body_counts_as_empty(raw):
|
||||||
|
assert _parse(raw) == {}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("raw", [b"[]", b'"x"', b"5", b"null"])
|
||||||
|
def test_non_object_body_is_a_400(raw):
|
||||||
|
with pytest.raises(HTTPException) as exc:
|
||||||
|
_parse(raw)
|
||||||
|
assert exc.value.status_code == 400
|
||||||
Reference in New Issue
Block a user