""" 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