mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 06:15:09 +00:00
fix(web): plugin dir resolver in routes, nmcli AP detection, daemon config reload, upload safety, BDF preview (#655)
* fix(web): plugin dir resolver in routes, nmcli AP detection, daemon config reload, upload safety - Route plugin lookups (installed list, update, recorded version, config form, web UI pages) through the plugin manager's resolver so plugins in ledmatrix-<id> directories work. - Captive-portal detection also sees the nmcli fallback AP (cached). - WiFi monitor daemon re-reads wifi_config.json when its mtime changes. - Drop the AP check in disconnect_from_network that could never fire. - LED status file per WiFiManager; config path falls back to this checkout. - BDF font preview via src.common.bdf_font. - Asset uploads validate every file before saving; metadata and calendar credentials written atomically; no absolute path in the response; asset delete answers 400 for a missing body. - Coerce string booleans in plugin toggle, on-demand start and AP force. - SSE broadcaster clears its thread handle before exiting. - start.py log filter handles every exc_info form. - Cleanups: unused plugins/fonts partial work, duplicate backup catch-alls, raw-config error helper, update-route tidy, redundant imports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web): request BDF font previews now that the server renders them The Fonts tab skipped the preview request for .bdf files because the server used to refuse them; /fonts/preview now draws BDF with the shared loader. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web): take the update route's plugin directory from a directory listing CodeQL flagged the path built from the request's plugin_id (the id was already validated with safe_path_component, which CodeQL doesn't model; the same flow on main is alerts 738/739). The directory is now the entry of plugins_dir matched by name, so nothing built from user input reaches the filesystem; an id with nothing installed goes to the store manager, which reports it not found as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web): read the blueprint's plugin_manager defensively in _plugin_directory _get_plugin_version now goes through _plugin_directory, which read api_v3.plugin_manager directly; the attribute exists only once the app sets it, so test_path_traversal_guards::test_a_real_manifest_is_read failed when run on its own (order-dependent in the full suite). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -170,9 +170,16 @@ def _get_plugin_version(plugin_id: str) -> str:
|
||||
that arrived in a request body, so the name is validated here rather
|
||||
than relying on each of them to have done it.
|
||||
"""
|
||||
manifest_path = resolve_under(
|
||||
api_v3.plugin_store_manager.plugins_dir, plugin_id, "manifest.json"
|
||||
)
|
||||
# The resolver first: a plugin installed as ledmatrix-<id>, or whose
|
||||
# directory is named differently from its manifest id, is not at
|
||||
# plugins_dir/<id>, and recording '' as its version hid that it worked.
|
||||
plugin_dir = _plugin_directory(plugin_id)
|
||||
if plugin_dir is not None:
|
||||
manifest_path = plugin_dir / "manifest.json"
|
||||
else:
|
||||
manifest_path = resolve_under(
|
||||
api_v3.plugin_store_manager.plugins_dir, plugin_id, "manifest.json"
|
||||
)
|
||||
if manifest_path is None:
|
||||
logger.warning("[PluginVersion] Rejected unsafe plugin id %r", plugin_id)
|
||||
return ''
|
||||
@@ -1420,9 +1427,11 @@ def _plugin_directory(plugin_id: str) -> Optional[Path]:
|
||||
is no fallback to the legacy plugins/ directory: the loader never scans
|
||||
it, so a plugin found only there is one that never runs.
|
||||
"""
|
||||
if not api_v3.plugin_manager:
|
||||
# getattr: the blueprint only has plugin_manager once the app has set it.
|
||||
manager = getattr(api_v3, 'plugin_manager', None)
|
||||
if not manager:
|
||||
return None
|
||||
plugin_dir = api_v3.plugin_manager.get_plugin_directory(plugin_id)
|
||||
plugin_dir = manager.get_plugin_directory(plugin_id)
|
||||
if not plugin_dir or not Path(plugin_dir).exists():
|
||||
return None
|
||||
return Path(plugin_dir)
|
||||
|
||||
Reference in New Issue
Block a user