mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-18 04:49:13 +00:00
fix(api-v3): reject MQTT bridge bodies the endpoint cannot apply
Two CodeRabbit findings on the bridge settings endpoint, both of which returned
200 while doing something other than what the caller asked.
`request.get_json(silent=True) or {}` turned a missing or unparseable body --
and the JSON literals null, [] and false -- into an empty dict, which then
satisfied the isinstance(data, dict) guard on the very next line. The guard was
there to reject exactly those bodies. Dropping the `or {}` lets None fail it.
The same `or {}` on /errors/clear is left alone: its docstring documents the
body as optional, so an absent body legitimately means "use the defaults". The
difference is that saving settings has nothing sensible to do with no body.
`if data.get('clear_password'):` accepted any truthy value, and the string
"false" is truthy in Python -- so a client echoing the field back as a string
wiped a password it meant to keep. Now coerced through the package's existing
_coerce_to_bool, which already maps 'true'/'on'/'1'/'yes' and nothing else.
test_mqtt_bridge_config_endpoint.py covers both: five unusable body shapes plus
a missing body, and clear_password across truthy and falsy spellings. Verified
against the unfixed code -- reverting the body guard fails 5, reverting the
coercion fails 3.
Not changed here: CodeRabbit also asks this endpoint to reject MQTT credentials
when TLS is off (CWE-319). That is a policy decision about the feature rather
than a defect -- unencrypted MQTT on a trusted LAN is common and often
deliberate -- so it is raised on the PR for a maintainer call instead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ Routes decorate the shared `api_v3` Blueprint from ._common, so their
|
||||
endpoint names are unchanged by living here.
|
||||
"""
|
||||
from web_interface.blueprints.api_v3 import (
|
||||
_coerce_to_bool,
|
||||
ErrorCode, Path, _JOURNALCTL, _MQTT_BRIDGE_CONFIG, _MQTT_BRIDGE_DEFAULTS,
|
||||
_MQTT_BRIDGE_DIR, _SUDO, _coerce_mqtt_bridge_value,
|
||||
_get_display_service_status, _mqtt_bridge_service_state,
|
||||
@@ -436,7 +437,11 @@ def update_mqtt_bridge_config():
|
||||
have to blank it on every save.
|
||||
"""
|
||||
try:
|
||||
data = request.get_json(silent=True) or {}
|
||||
# No `or {}` here: get_json(silent=True) returns None for a missing or
|
||||
# unparseable body, and `None or {}` produced an empty dict that then
|
||||
# satisfied the isinstance check below -- so malformed JSON, `null`,
|
||||
# `[]` and `false` all reported success while applying nothing.
|
||||
data = request.get_json(silent=True)
|
||||
if not isinstance(data, dict):
|
||||
return jsonify({'status': 'error', 'message': 'Body must be a JSON object'}), 400
|
||||
|
||||
@@ -454,7 +459,10 @@ def update_mqtt_bridge_config():
|
||||
|
||||
config.update(updates)
|
||||
|
||||
if data.get('clear_password'):
|
||||
# Coerced, not merely truthy: the string "false" is truthy in Python,
|
||||
# so a client echoing the field back as a string would have wiped a
|
||||
# stored password it meant to keep.
|
||||
if _coerce_to_bool(data.get('clear_password')):
|
||||
config['mqtt_password'] = None
|
||||
elif 'mqtt_password' in data and str(data['mqtt_password']) != '':
|
||||
new_password = str(data['mqtt_password'])
|
||||
|
||||
Reference in New Issue
Block a user