EU-Utility/core/settings_secure.py

256 lines
8.3 KiB
Python

"""
EU-Utility - Settings Manager (Security Hardened)
User preferences and configuration management with input validation.
"""
import json
from pathlib import Path
from PyQt6.QtCore import QObject, pyqtSignal
from core.security_utils import InputValidator, DataValidator, SecurityError
class Settings(QObject):
"""Application settings manager with security validation."""
setting_changed = pyqtSignal(str, object)
# Default settings
DEFAULTS = {
# Overlay
'overlay_enabled': True,
'overlay_opacity': 0.9,
'overlay_theme': 'dark',
# Hotkeys
'hotkey_toggle': 'ctrl+shift+u',
'hotkey_search': 'ctrl+shift+f',
'hotkey_calculator': 'ctrl+shift+c',
'hotkey_music': 'ctrl+shift+m',
'hotkey_scan': 'ctrl+shift+r',
'hotkey_skills': 'ctrl+shift+s',
# Plugins
'enabled_plugins': [
'universal_search',
'calculator',
'spotify_controller',
'nexus_search',
'game_reader',
'skill_scanner',
],
'disabled_plugins': [],
# Dashboard
'dashboard_widgets': [
{'type': 'QuickActions', 'row': 0, 'col': 0},
{'type': 'Spotify', 'row': 0, 'col': 1},
{'type': 'PEDTracker', 'row': 1, 'col': 0},
{'type': 'SkillProgress', 'row': 1, 'col': 1},
],
# Overlay widgets
'overlay_widgets': {
'spotify': {'enabled': True, 'x': 0, 'y': 100},
'skillgain': {'enabled': False, 'x': 0, 'y': 200},
},
# Game Reader
'ocr_engine': 'easyocr',
'auto_capture': False,
'capture_region': 'full',
# Skill Scanner
'auto_track_gains': True,
'skill_alert_threshold': 100,
# Appearance
'icon_size': 24,
'accent_color': '#4a9eff',
'show_tooltips': True,
# Updates
'check_updates': True,
'auto_update_plugins': False,
# Data
'data_retention_days': 30,
'auto_export': False,
}
# Settings that require type validation
TYPED_SETTINGS = {
'overlay_enabled': bool,
'overlay_opacity': (int, float),
'overlay_theme': str,
'icon_size': int,
'accent_color': str,
'data_retention_days': int,
'skill_alert_threshold': (int, float),
}
def __init__(self, config_file="data/settings.json"):
super().__init__()
self.config_file = Path(config_file)
self._settings = {}
self._load()
def _load(self):
"""Load settings from file with validation."""
self._settings = self.DEFAULTS.copy()
if self.config_file.exists():
try:
with open(self.config_file, 'r', encoding='utf-8') as f:
saved = json.load(f)
# Validate loaded data structure
if not isinstance(saved, dict):
print("[Settings] Invalid settings format, using defaults")
return
# Validate and merge settings
for key, value in saved.items():
# Validate key
if not InputValidator.validate_json_key(key):
print(f"[Settings] Rejecting invalid key: {key}")
continue
# Type validation
if key in self.TYPED_SETTINGS:
expected_types = self.TYPED_SETTINGS[key]
if not isinstance(expected_types, tuple):
expected_types = (expected_types,)
if not isinstance(value, expected_types):
print(f"[Settings] Type mismatch for {key}, using default")
continue
# Validate data structure
try:
DataValidator.validate_data_structure(value)
except SecurityError as e:
print(f"[Settings] Security error for {key}: {e}")
continue
self._settings[key] = value
except json.JSONDecodeError as e:
print(f"[Settings] JSON parse error: {e}")
except Exception as e:
print(f"[Settings] Error loading settings: {e}")
def save(self):
"""Save settings to file."""
try:
self.config_file.parent.mkdir(parents=True, exist_ok=True)
# Validate before saving
DataValidator.validate_data_structure(self._settings)
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(self._settings, f, indent=2)
except Exception as e:
print(f"[Settings] Error saving settings: {e}")
def get(self, key, default=None):
"""Get a setting value."""
return self._settings.get(key, default)
def set(self, key, value):
"""Set a setting value with validation."""
# Validate key
if not InputValidator.validate_json_key(key):
print(f"[Settings] Invalid key: {key}")
return
# Type validation
if key in self.TYPED_SETTINGS:
expected_types = self.TYPED_SETTINGS[key]
if not isinstance(expected_types, tuple):
expected_types = (expected_types,)
if not isinstance(value, expected_types):
print(f"[Settings] Type mismatch for {key}")
return
# Validate value structure
try:
DataValidator.validate_data_structure(value)
except SecurityError as e:
print(f"[Settings] Security error: {e}")
return
old_value = self._settings.get(key)
self._settings[key] = value
self.save()
self.setting_changed.emit(key, value)
def reset(self, key=None):
"""Reset setting(s) to default."""
if key:
if key in self.DEFAULTS:
self._settings[key] = self.DEFAULTS[key]
self.save()
self.setting_changed.emit(key, self._settings[key])
else:
self._settings = self.DEFAULTS.copy()
self.save()
def is_plugin_enabled(self, plugin_id):
"""Check if a plugin is enabled."""
# Validate plugin_id
if not isinstance(plugin_id, str):
return False
return plugin_id in self._settings.get('enabled_plugins', [])
def enable_plugin(self, plugin_id):
"""Enable a plugin."""
if not isinstance(plugin_id, str):
print(f"[Settings] Invalid plugin_id type")
return
enabled = self._settings.get('enabled_plugins', [])
disabled = self._settings.get('disabled_plugins', [])
if plugin_id not in enabled:
enabled.append(plugin_id)
if plugin_id in disabled:
disabled.remove(plugin_id)
self.set('enabled_plugins', enabled)
self.set('disabled_plugins', disabled)
def disable_plugin(self, plugin_id):
"""Disable a plugin."""
if not isinstance(plugin_id, str):
print(f"[Settings] Invalid plugin_id type")
return
enabled = self._settings.get('enabled_plugins', [])
disabled = self._settings.get('disabled_plugins', [])
if plugin_id in enabled:
enabled.remove(plugin_id)
if plugin_id not in disabled:
disabled.append(plugin_id)
self.set('enabled_plugins', enabled)
self.set('disabled_plugins', disabled)
def all_settings(self):
"""Get all settings."""
return self._settings.copy()
# Global settings instance
_settings_instance = None
def get_settings():
"""Get global settings instance."""
global _settings_instance
if _settings_instance is None:
_settings_instance = Settings()
return _settings_instance