Add Android forensics, IOC threat-intel DB, Compose companion; scrub secrets from configs
This commit is contained in:
159
core/user_profile.py
Normal file
159
core/user_profile.py
Normal file
@@ -0,0 +1,159 @@
|
||||
"""
|
||||
AUTARCH User Profile Manager
|
||||
Handles user identity, preferences, and session management.
|
||||
|
||||
On first launch, creates a default profile in the user data directory.
|
||||
Supports multiple users for shared deployments.
|
||||
|
||||
The profile stores non-sensitive user info (username, display name, role, etc.)
|
||||
Sensitive data (API keys, passwords) goes in the keystore.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional
|
||||
|
||||
_log = logging.getLogger('autarch.user_profile')
|
||||
|
||||
|
||||
class UserProfile:
|
||||
"""User profile and preferences."""
|
||||
|
||||
def __init__(self, profile_path: Path = None):
|
||||
from core.paths import get_user_profile_path
|
||||
self._path = profile_path or get_user_profile_path()
|
||||
self._data: Dict = {}
|
||||
self._load()
|
||||
|
||||
def _load(self):
|
||||
"""Load profile from disk, or create default."""
|
||||
if self._path.exists():
|
||||
try:
|
||||
with open(self._path) as f:
|
||||
self._data = json.load(f)
|
||||
_log.info(f'Loaded profile for {self._data.get("username", "unknown")}')
|
||||
return
|
||||
except Exception as e:
|
||||
_log.error(f'Failed to load profile: {e}')
|
||||
|
||||
# Create default profile
|
||||
self._data = self._create_default()
|
||||
self._save()
|
||||
_log.info('Created default user profile')
|
||||
|
||||
def _create_default(self) -> Dict:
|
||||
"""Create the default user profile."""
|
||||
username = os.getenv('USER', os.getenv('USERNAME', 'operator'))
|
||||
return {
|
||||
'username': username,
|
||||
'display_name': username.capitalize(),
|
||||
'role': 'admin',
|
||||
'created': int(time.time()),
|
||||
'last_login': int(time.time()),
|
||||
'preferences': {
|
||||
'theme': 'dark',
|
||||
'llm_backend': 'local',
|
||||
'hal_auto_analyze': True,
|
||||
'hal_max_steps': 200,
|
||||
'verbose': False,
|
||||
'default_scan_target': 'local', # 'local' scans user device, 'server' scans server
|
||||
'chat_memory_enabled': True,
|
||||
},
|
||||
'session': {
|
||||
'last_module': '',
|
||||
'last_target': '',
|
||||
'last_scan_type': '',
|
||||
},
|
||||
}
|
||||
|
||||
def _save(self):
|
||||
"""Save profile to disk."""
|
||||
try:
|
||||
self._path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(self._path, 'w') as f:
|
||||
json.dump(self._data, f, indent=2)
|
||||
except Exception as e:
|
||||
_log.error(f'Failed to save profile: {e}')
|
||||
|
||||
@property
|
||||
def username(self) -> str:
|
||||
return self._data.get('username', 'default')
|
||||
|
||||
@property
|
||||
def display_name(self) -> str:
|
||||
return self._data.get('display_name', self.username)
|
||||
|
||||
@property
|
||||
def role(self) -> str:
|
||||
return self._data.get('role', 'user')
|
||||
|
||||
def get_preference(self, key: str, default=None):
|
||||
"""Get a user preference value."""
|
||||
return self._data.get('preferences', {}).get(key, default)
|
||||
|
||||
def set_preference(self, key: str, value):
|
||||
"""Set a user preference and save."""
|
||||
if 'preferences' not in self._data:
|
||||
self._data['preferences'] = {}
|
||||
self._data['preferences'][key] = value
|
||||
self._save()
|
||||
|
||||
def get_session(self, key: str, default='') -> str:
|
||||
"""Get a session value (non-persistent state hints)."""
|
||||
return self._data.get('session', {}).get(key, default)
|
||||
|
||||
def set_session(self, key: str, value: str):
|
||||
"""Set a session value."""
|
||||
if 'session' not in self._data:
|
||||
self._data['session'] = {}
|
||||
self._data['session'][key] = value
|
||||
self._save()
|
||||
|
||||
def update_last_login(self):
|
||||
"""Record login timestamp."""
|
||||
self._data['last_login'] = int(time.time())
|
||||
self._save()
|
||||
|
||||
def to_dict(self) -> Dict:
|
||||
"""Export profile as a dict (for API/display)."""
|
||||
return {
|
||||
'username': self.username,
|
||||
'display_name': self.display_name,
|
||||
'role': self.role,
|
||||
'created': self._data.get('created', 0),
|
||||
'last_login': self._data.get('last_login', 0),
|
||||
'preferences': self._data.get('preferences', {}),
|
||||
}
|
||||
|
||||
|
||||
# Singleton
|
||||
_profile_instance: Optional[UserProfile] = None
|
||||
|
||||
|
||||
def get_profile() -> UserProfile:
|
||||
"""Get the global user profile instance."""
|
||||
global _profile_instance
|
||||
if _profile_instance is None:
|
||||
_profile_instance = UserProfile()
|
||||
return _profile_instance
|
||||
|
||||
|
||||
def init_profile_on_launch():
|
||||
"""Called at app startup to ensure profile exists and update login time."""
|
||||
profile = get_profile()
|
||||
profile.update_last_login()
|
||||
|
||||
# Migrate old vault secrets to new keystore if needed
|
||||
from core.paths import get_keystore_path
|
||||
ks_path = get_keystore_path()
|
||||
if not ks_path.exists():
|
||||
from core.keystore import get_keystore
|
||||
ks = get_keystore()
|
||||
migrated = ks.migrate_from_vault()
|
||||
if migrated:
|
||||
_log.info(f'Migrated {migrated} secrets from vault to keystore on first run')
|
||||
|
||||
return profile
|
||||
Reference in New Issue
Block a user