mirror of
https://github.com/apidoorman/doorman.git
synced 2026-01-28 04:38:28 -06:00
169 lines
5.0 KiB
Python
169 lines
5.0 KiB
Python
"""
|
|
Utilities to manage security-related settings and schedule auto-save of memory dumps.
|
|
"""
|
|
|
|
import asyncio
|
|
import logging
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from .database import database, db
|
|
from .memory_dump_util import dump_memory_to_file
|
|
|
|
logger = logging.getLogger('doorman.gateway')
|
|
|
|
_CACHE: dict[str, Any] = {}
|
|
_AUTO_TASK: asyncio.Task | None = None
|
|
_STOP_EVENT: asyncio.Event | None = None
|
|
|
|
_PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
|
_GEN_DIR = _PROJECT_ROOT / 'generated'
|
|
|
|
DEFAULTS = {
|
|
'type': 'security_settings',
|
|
'enable_auto_save': False,
|
|
'auto_save_frequency_seconds': 900,
|
|
'dump_path': os.getenv('MEM_DUMP_PATH', str(_GEN_DIR / 'memory_dump.bin')),
|
|
'ip_whitelist': [],
|
|
'ip_blacklist': [],
|
|
'trust_x_forwarded_for': False,
|
|
'xff_trusted_proxies': [],
|
|
'allow_localhost_bypass': (os.getenv('LOCAL_HOST_IP_BYPASS', 'false').lower() == 'true'),
|
|
}
|
|
|
|
SETTINGS_FILE = os.getenv('SECURITY_SETTINGS_FILE', str(_GEN_DIR / 'security_settings.json'))
|
|
|
|
|
|
def _get_collection():
|
|
return db.settings if not database.memory_only else database.db.settings
|
|
|
|
|
|
def _merge_settings(doc: dict[str, Any]) -> dict[str, Any]:
|
|
merged = DEFAULTS.copy()
|
|
if doc:
|
|
merged.update({k: v for k, v in doc.items() if v is not None})
|
|
return merged
|
|
|
|
|
|
def get_cached_settings() -> dict[str, Any]:
|
|
global _CACHE
|
|
if not _CACHE:
|
|
_CACHE = DEFAULTS.copy()
|
|
return _CACHE
|
|
|
|
|
|
def _load_from_file() -> dict[str, Any] | None:
|
|
try:
|
|
if not os.path.exists(SETTINGS_FILE):
|
|
return None
|
|
with open(SETTINGS_FILE, encoding='utf-8') as f:
|
|
data = f.read().strip()
|
|
if not data:
|
|
return None
|
|
import json
|
|
|
|
obj = json.loads(data)
|
|
|
|
if isinstance(obj, dict):
|
|
return obj
|
|
except Exception as e:
|
|
logger.error('Failed to read settings file %s: %s', SETTINGS_FILE, e)
|
|
return None
|
|
|
|
|
|
def _save_to_file(settings: dict[str, Any]) -> None:
|
|
try:
|
|
os.makedirs(os.path.dirname(SETTINGS_FILE) or '.', exist_ok=True)
|
|
import json
|
|
|
|
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
|
f.write(json.dumps(settings, separators=(',', ':')))
|
|
except Exception as e:
|
|
logger.error('Failed to write settings file %s: %s', SETTINGS_FILE, e)
|
|
|
|
|
|
async def load_settings() -> dict[str, Any]:
|
|
coll = _get_collection()
|
|
doc = coll.find_one({'type': 'security_settings'})
|
|
|
|
if not doc and database.memory_only:
|
|
file_obj = _load_from_file()
|
|
if file_obj:
|
|
try:
|
|
to_set = _merge_settings(file_obj)
|
|
coll.update_one({'type': 'security_settings'}, {'$set': to_set})
|
|
doc = to_set
|
|
except Exception:
|
|
doc = file_obj
|
|
settings = _merge_settings(doc or {})
|
|
_CACHE.update(settings)
|
|
return settings
|
|
|
|
|
|
async def save_settings(partial: dict[str, Any]) -> dict[str, Any]:
|
|
coll = _get_collection()
|
|
current = _merge_settings(coll.find_one({'type': 'security_settings'}) or {})
|
|
current.update({k: v for k, v in partial.items() if v is not None})
|
|
result = coll.update_one({'type': 'security_settings'}, {'$set': current})
|
|
|
|
try:
|
|
modified = getattr(result, 'modified_count', 0)
|
|
except Exception:
|
|
modified = 0
|
|
if not modified and not coll.find_one({'type': 'security_settings'}):
|
|
coll.insert_one(current)
|
|
_CACHE.update(current)
|
|
|
|
_save_to_file(_CACHE)
|
|
await restart_auto_save_task()
|
|
return current
|
|
|
|
|
|
async def _auto_save_loop(stop_event: asyncio.Event):
|
|
while not stop_event.is_set():
|
|
try:
|
|
settings = get_cached_settings()
|
|
|
|
freq = int(settings.get('auto_save_frequency_seconds', 0) or 0)
|
|
if database.memory_only and freq > 0:
|
|
try:
|
|
dump_memory_to_file(settings.get('dump_path'))
|
|
logger.info('Auto-saved memory dump to %s', settings.get('dump_path'))
|
|
except Exception as e:
|
|
logger.error('Auto-save memory dump failed: %s', e)
|
|
|
|
await asyncio.wait_for(stop_event.wait(), timeout=max(freq, 60) if freq > 0 else 60)
|
|
except TimeoutError:
|
|
continue
|
|
except Exception as e:
|
|
logger.error('Auto-save loop error: %s', e)
|
|
await asyncio.sleep(60)
|
|
|
|
|
|
async def start_auto_save_task():
|
|
global _AUTO_TASK, _STOP_EVENT
|
|
if _AUTO_TASK and not _AUTO_TASK.done():
|
|
return
|
|
_STOP_EVENT = asyncio.Event()
|
|
_AUTO_TASK = asyncio.create_task(_auto_save_loop(_STOP_EVENT))
|
|
logger.info('Security auto-save task started')
|
|
|
|
|
|
async def stop_auto_save_task():
|
|
global _AUTO_TASK, _STOP_EVENT
|
|
if _STOP_EVENT:
|
|
_STOP_EVENT.set()
|
|
if _AUTO_TASK:
|
|
try:
|
|
await asyncio.wait_for(_AUTO_TASK, timeout=5)
|
|
except Exception:
|
|
pass
|
|
_AUTO_TASK = None
|
|
_STOP_EVENT = None
|
|
|
|
|
|
async def restart_auto_save_task():
|
|
await stop_auto_save_task()
|
|
await start_auto_save_task()
|