Consolidate settings into 8 hubs and fix security vulnerabilities

Settings Consolidation (37+ pages to 8 hubs):
- Asset Type Hub: Unified type management with plugin descriptors
- Model Catalog Hub: Vendors, types, models, operating systems
- Asset Configuration Hub: Statuses, relationships, fields, identifiers
- Security Hub: Authentication, tokens, users, audit
- Site Configuration Hub: Site settings, floor map, branding, imports
- Integrations Hub: ServiceNow, Zabbix, Dell warranty
- Locations Hub: Locations, business units, employees
- Network Infrastructure Hub: VLANs, subnets

Plugin Descriptor Pattern:
- Add get_asset_type_descriptor hook to BasePlugin
- Implement descriptors in 6 plugins (printers, computers, machines, network, measuringtools, locations)
- Add /api/pluginui/asset-type-descriptors and core-type-descriptors endpoints
- Generic SubtypesTab component renders CRUD UI from descriptor schema

Security Fixes:
- Fix stored XSS via javascript: URIs in Models and Support Teams
- Add URL scheme validation (allow only http/https) in create/update endpoints
- Fix plugin contract violations in endpoints plugin (use shopdb.api imports)
- Add missing requiresAdmin guard to printer model supplies route
- Add user-facing error messages to API token load failures

Integration Improvements:
- Add persistent tab navigation to ServiceNow, Zabbix, and Dell Warranty settings
- Standardize integration settings UI across all three pages
- Fix tab visibility when navigating between integrations

Tested with 94-agent orchestrated review for ADR compliance and security.
This commit is contained in:
Proudlock
2026-09-11 10:45:12 -04:00
parent 9f78d0bfc7
commit f9f1d39f08
34 changed files with 2485 additions and 166 deletions

View File

@@ -2,6 +2,7 @@
import glob
import os
from urllib.parse import urlparse
from flask import Blueprint, request, current_app
from flask_jwt_extended import jwt_required
@@ -39,6 +40,16 @@ def _modelimage_dir():
return os.path.join(current_app.instance_path, 'modelimages')
def _validate_url_scheme(url):
"""Validate that URL uses safe scheme (prevents XSS via javascript: URIs)."""
if not url:
return True
parsed = urlparse(url)
if parsed.scheme and parsed.scheme not in ['http', 'https']:
raise ValueError(f"Invalid URL scheme '{parsed.scheme}'. Only http:// and https:// are allowed.")
return True
@models_bp.route('', methods=['GET'])
@require_permission('assets.view')
def list_models():
@@ -127,6 +138,13 @@ def create_model():
http_code=409
)
# Validate URL scheme to prevent XSS
try:
_validate_url_scheme(data.get('documentationurl'))
_validate_url_scheme(data.get('imageurl'))
except ValueError as e:
return error_response(ErrorCodes.VALIDATION_ERROR, str(e))
m = Model(
modelnumber=data['modelnumber'],
vendorid=data.get('vendorid'),
@@ -162,6 +180,15 @@ def update_model(model_id: int):
if not data:
return error_response(ErrorCodes.VALIDATION_ERROR, 'No data provided')
# Validate URL schemes to prevent XSS
try:
if 'documentationurl' in data:
_validate_url_scheme(data['documentationurl'])
if 'imageurl' in data:
_validate_url_scheme(data['imageurl'])
except ValueError as e:
return error_response(ErrorCodes.VALIDATION_ERROR, str(e))
for key in ['modelnumber', 'vendorid', 'modeltypeid', 'description', 'imageurl', 'documentationurl', 'notes', 'isactive']:
if key in data:
setattr(m, key, data[key])