mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-04-04 01:01:35 -07:00
Coderabbit fixes:
- Mac - Flask debug - Threaded flask - propagate token in GET requests - enhance spec docs - normalize MAC x2 - mcp disablement redundant private attribute - run all tests imports
This commit is contained in:
@@ -13,7 +13,7 @@ INSTALL_PATH = os.getenv("NETALERTX_APP", "/app")
|
||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||
|
||||
from logger import mylog # noqa: E402 [flake8 lint suppression]
|
||||
from helper import get_setting_value # noqa: E402 [flake8 lint suppression]
|
||||
from helper import get_setting_value, get_env_setting_value # noqa: E402 [flake8 lint suppression]
|
||||
from db.db_helper import get_date_from_period # noqa: E402 [flake8 lint suppression]
|
||||
from app_state import updateState # noqa: E402 [flake8 lint suppression]
|
||||
|
||||
@@ -1693,10 +1693,26 @@ def start_server(graphql_port, app_state):
|
||||
if app_state.graphQLServerStarted == 0:
|
||||
mylog("verbose", [f"[graphql endpoint] Starting on port: {graphql_port}"])
|
||||
|
||||
# First check environment variable override (direct env like FLASK_DEBUG)
|
||||
env_val = get_env_setting_value("FLASK_DEBUG", None)
|
||||
if env_val is not None:
|
||||
flask_debug = bool(env_val)
|
||||
mylog("verbose", [f"[graphql endpoint] Flask debug mode: {flask_debug} (FLASK_DEBUG env override)"])
|
||||
else:
|
||||
# Fall back to configured setting `FLASK_DEBUG` (from app.conf / overrides)
|
||||
flask_debug = get_setting_value("FLASK_DEBUG")
|
||||
# Normalize value to boolean in case it's stored as a string
|
||||
if isinstance(flask_debug, str):
|
||||
flask_debug = flask_debug.strip().lower() in ("1", "true", "yes", "on")
|
||||
else:
|
||||
flask_debug = bool(flask_debug)
|
||||
|
||||
mylog("verbose", [f"[graphql endpoint] Flask debug mode: {flask_debug} (FLASK_DEBUG setting)"])
|
||||
|
||||
# Start Flask app in a separate thread
|
||||
thread = threading.Thread(
|
||||
target=lambda: app.run(
|
||||
host="0.0.0.0", port=graphql_port, debug=False, use_reloader=False
|
||||
host="0.0.0.0", port=graphql_port, threaded=True,debug=flask_debug, use_reloader=False
|
||||
)
|
||||
)
|
||||
thread.start()
|
||||
|
||||
@@ -642,6 +642,11 @@ def _execute_tool(route: Dict[str, Any], args: Dict[str, Any]) -> Dict[str, Any]
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if "Authorization" in request.headers:
|
||||
headers["Authorization"] = request.headers["Authorization"]
|
||||
else:
|
||||
# Propagate query token or fallback to configured API token for internal loopback
|
||||
token = request.args.get("token") or get_setting_value('API_TOKEN')
|
||||
if token:
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
|
||||
filtered_body_args = {k: v for k, v in args.items() if f"{{{k}}}" not in route['path']}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Dict, Any, Optional, Type, List
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
def pydantic_to_json_schema(model: Type[BaseModel]) -> Dict[str, Any]:
|
||||
def pydantic_to_json_schema(model: Type[BaseModel], mode: str = "validation") -> Dict[str, Any]:
|
||||
"""
|
||||
Convert a Pydantic model to JSON Schema (OpenAPI 3.1 compatible).
|
||||
|
||||
@@ -13,12 +13,13 @@ def pydantic_to_json_schema(model: Type[BaseModel]) -> Dict[str, Any]:
|
||||
|
||||
Args:
|
||||
model: Pydantic BaseModel class
|
||||
mode: Schema mode - "validation" (for inputs) or "serialization" (for outputs)
|
||||
|
||||
Returns:
|
||||
JSON Schema dictionary
|
||||
"""
|
||||
# Pydantic v2 uses model_json_schema()
|
||||
schema = model.model_json_schema(mode="serialization")
|
||||
schema = model.model_json_schema(mode=mode)
|
||||
|
||||
# Remove $defs if empty (cleaner output)
|
||||
if "$defs" in schema and not schema["$defs"]:
|
||||
@@ -169,7 +170,7 @@ def build_responses(
|
||||
# Success response (200)
|
||||
if response_model:
|
||||
# Strip validation from response schema to save tokens
|
||||
schema = strip_validation(pydantic_to_json_schema(response_model))
|
||||
schema = strip_validation(pydantic_to_json_schema(response_model, mode="serialization"))
|
||||
schema = extract_definitions(schema, definitions)
|
||||
responses["200"] = {
|
||||
"description": "Successful response",
|
||||
|
||||
@@ -361,6 +361,42 @@ def setting_value_to_python_type(set_type, set_value):
|
||||
return value
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------------
|
||||
# Environment helper
|
||||
def get_env_setting_value(key, default=None):
|
||||
"""Return a typed value from environment variable if present.
|
||||
|
||||
- Parses booleans (1/0, true/false, yes/no, on/off).
|
||||
- Tries to parse ints and JSON literals where sensible.
|
||||
- Returns `default` when env var is not set.
|
||||
"""
|
||||
val = os.environ.get(key)
|
||||
if val is None:
|
||||
return default
|
||||
|
||||
v = val.strip()
|
||||
# Booleans
|
||||
low = v.lower()
|
||||
if low in ("1", "true", "yes", "on"):
|
||||
return True
|
||||
if low in ("0", "false", "no", "off"):
|
||||
return False
|
||||
|
||||
# Integer
|
||||
try:
|
||||
if re.fullmatch(r"-?\d+", v):
|
||||
return int(v)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# JSON-like (list/object/true/false/null/number)
|
||||
try:
|
||||
return json.loads(v)
|
||||
except Exception:
|
||||
# Fallback to raw string
|
||||
return v
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------------
|
||||
def updateSubnets(scan_subnets):
|
||||
"""
|
||||
|
||||
@@ -334,15 +334,6 @@ def importConfigs(pm, db, all_plugins):
|
||||
"[]",
|
||||
"General",
|
||||
)
|
||||
conf.FLASK_DEBUG = ccd(
|
||||
"FLASK_DEBUG",
|
||||
False,
|
||||
c_d,
|
||||
"Flask debug mode - SECURITY WARNING: Enabling enables interactive debugger with RCE risk. Configure via environment only, not exposed in UI.",
|
||||
'{"dataType": "boolean","elements": []}',
|
||||
"[]",
|
||||
"system",
|
||||
)
|
||||
conf.VERSION = ccd(
|
||||
"VERSION",
|
||||
"",
|
||||
|
||||
@@ -4,7 +4,7 @@ import re
|
||||
import sqlite3
|
||||
import csv
|
||||
from io import StringIO
|
||||
from front.plugins.plugin_helper import is_mac
|
||||
from front.plugins.plugin_helper import is_mac, normalize_mac
|
||||
from logger import mylog
|
||||
from models.plugin_object_instance import PluginObjectInstance
|
||||
from database import get_temp_db_connection
|
||||
@@ -500,6 +500,9 @@ class DeviceInstance:
|
||||
|
||||
def setDeviceData(self, mac, data):
|
||||
"""Update or create a device."""
|
||||
normalized_mac = normalize_mac(mac)
|
||||
normalized_parent_mac = normalize_mac(data.get("devParentMAC") or "")
|
||||
|
||||
conn = None
|
||||
try:
|
||||
if data.get("createNew", False):
|
||||
@@ -517,7 +520,7 @@ class DeviceInstance:
|
||||
"""
|
||||
|
||||
values = (
|
||||
mac,
|
||||
normalized_mac,
|
||||
data.get("devName") or "",
|
||||
data.get("devOwner") or "",
|
||||
data.get("devType") or "",
|
||||
@@ -527,7 +530,7 @@ class DeviceInstance:
|
||||
data.get("devGroup") or "",
|
||||
data.get("devLocation") or "",
|
||||
data.get("devComments") or "",
|
||||
data.get("devParentMAC") or "",
|
||||
normalized_parent_mac,
|
||||
data.get("devParentPort") or "",
|
||||
data.get("devSSID") or "",
|
||||
data.get("devSite") or "",
|
||||
@@ -569,7 +572,7 @@ class DeviceInstance:
|
||||
data.get("devGroup") or "",
|
||||
data.get("devLocation") or "",
|
||||
data.get("devComments") or "",
|
||||
data.get("devParentMAC") or "",
|
||||
normalized_parent_mac,
|
||||
data.get("devParentPort") or "",
|
||||
data.get("devSSID") or "",
|
||||
data.get("devSite") or "",
|
||||
@@ -583,7 +586,7 @@ class DeviceInstance:
|
||||
data.get("devIsNew") or 0,
|
||||
data.get("devIsArchived") or 0,
|
||||
data.get("devCustomProps") or "",
|
||||
mac,
|
||||
normalized_mac,
|
||||
)
|
||||
|
||||
conn = get_temp_db_connection()
|
||||
|
||||
Reference in New Issue
Block a user