implement investigation engine, plugin manager and result aggregation

This commit is contained in:
authentik Default Admin 2026-02-21 00:00:00 +00:00
parent 214ef8ad1b
commit 2456047f8c
3 changed files with 219 additions and 0 deletions

104
eirescope/core/engine.py Normal file
View file

@ -0,0 +1,104 @@
"""Investigation Engine — orchestrates OSINT modules and aggregates results."""
import logging
from typing import List, Optional, Dict
from datetime import datetime
from eirescope.core.entity import Entity, EntityType, Investigation
from eirescope.core.plugin_manager import PluginManager
from eirescope.utils.validators import EntityValidator
from eirescope.utils.exceptions import ValidationError, ModuleError
logger = logging.getLogger("eirescope.core.engine")
class InvestigationEngine:
"""Main orchestration engine for OSINT investigations."""
def __init__(self, config: Dict = None):
self.config = config or {}
self.plugin_manager = PluginManager(config=self.config)
def investigate(self, query: str, entity_type: str = None,
module_filter: List[str] = None) -> Investigation:
"""Run a full OSINT investigation.
Args:
query: The search value (email, username, IP, etc.)
entity_type: Entity type string (auto-detected if None)
module_filter: Optional list of module names to run (runs all if None)
Returns:
Investigation with all discovered entities and relationships.
"""
# 1. Validate and detect entity type
if entity_type:
try:
etype = EntityType(entity_type)
except ValueError:
raise ValidationError(f"Unknown entity type: {entity_type}")
else:
etype = EntityValidator.detect_type(query)
if not etype:
raise ValidationError(
f"Could not auto-detect entity type for: {query}. "
"Please specify the entity type."
)
# 2. Validate and normalize input
is_valid, normalized = EntityValidator.validate_and_normalize(query, etype)
if not is_valid:
raise ValidationError(f"Invalid {etype.value}: {query}")
logger.info(f"Starting investigation: {normalized} (type: {etype.value})")
# 3. Create investigation
investigation = Investigation(
initial_query=normalized,
initial_type=etype,
status="running",
)
# 4. Create the seed entity
seed_entity = Entity(
entity_type=etype,
value=normalized,
source_module="user_input",
confidence=1.0,
)
investigation.add_entity(seed_entity)
# 5. Get applicable modules
modules = self.plugin_manager.get_modules_for_entity(etype)
if module_filter:
modules = [m for m in modules if m.name in module_filter]
if not modules:
logger.warning(f"No modules available for entity type: {etype.value}")
investigation.complete()
return investigation
# 6. Execute each module sequentially
for module in modules:
try:
logger.info(f"Running module: {module.name}")
new_entities = module.execute(seed_entity, investigation)
investigation.modules_run.append(module.name)
logger.info(f"Module {module.name} found {len(new_entities)} entities")
except Exception as e:
logger.error(f"Module {module.name} failed: {e}")
investigation.modules_run.append(f"{module.name} (FAILED)")
# 7. Complete investigation
investigation.complete()
logger.info(
f"Investigation complete: {len(investigation.entities)} entities, "
f"{len(investigation.relationships)} relationships"
)
return investigation
def get_available_modules(self) -> List[Dict]:
"""List all available OSINT modules."""
return self.plugin_manager.list_modules()
def get_supported_types(self) -> List[str]:
"""Get entity types that have at least one module."""
return self.plugin_manager.get_supported_types()

View file

@ -0,0 +1,62 @@
"""Plugin manager — auto-discovers and loads OSINT modules."""
import logging
from typing import List, Dict, Optional
from eirescope.core.entity import EntityType
from eirescope.modules.base import BaseOSINTModule
from eirescope.modules.username_module import UsernameModule
from eirescope.modules.email_module import EmailModule
from eirescope.modules.phone_module import PhoneModule
from eirescope.modules.ip_module import IPModule
from eirescope.modules.domain_module import DomainModule
from eirescope.modules.social_module import SocialMediaModule
logger = logging.getLogger("eirescope.core.plugins")
# Registry of all available modules
AVAILABLE_MODULES = [
UsernameModule,
EmailModule,
PhoneModule,
IPModule,
DomainModule,
SocialMediaModule,
]
class PluginManager:
"""Manages OSINT module loading and discovery."""
def __init__(self, config: Dict = None):
self.config = config or {}
self.modules: Dict[str, BaseOSINTModule] = {}
self._load_modules()
def _load_modules(self):
"""Load all registered OSINT modules."""
for module_class in AVAILABLE_MODULES:
try:
module = module_class(config=self.config)
self.modules[module.name] = module
logger.info(f"Loaded module: {module.name}")
except Exception as e:
logger.error(f"Failed to load module {module_class.__name__}: {e}")
def get_modules_for_entity(self, entity_type: EntityType) -> List[BaseOSINTModule]:
"""Get all modules that can handle a given entity type."""
return [m for m in self.modules.values() if m.can_handle(entity_type)]
def get_module(self, name: str) -> Optional[BaseOSINTModule]:
"""Get a specific module by name."""
return self.modules.get(name)
def list_modules(self) -> List[Dict]:
"""List all available modules with metadata."""
return [m.get_metadata() for m in self.modules.values()]
def get_supported_types(self) -> List[str]:
"""Get all entity types supported by at least one module."""
types = set()
for module in self.modules.values():
for t in module.supported_entity_types:
types.add(t.value)
return sorted(types)

53
eirescope/core/results.py Normal file
View file

@ -0,0 +1,53 @@
"""Result aggregation and analysis utilities."""
from typing import Dict, List
from collections import Counter
from eirescope.core.entity import Investigation, EntityType
def summarize_investigation(investigation: Investigation) -> Dict:
"""Generate a summary of an investigation for display."""
entity_counts = Counter(e.entity_type.value for e in investigation.entities)
rel_counts = Counter(r.relationship_type for r in investigation.relationships)
# Build graph data for D3.js visualization
nodes = []
for e in investigation.entities:
nodes.append({
"id": e.id,
"label": _truncate(e.value, 40),
"type": e.entity_type.value,
"confidence": e.confidence,
"source": e.source_module,
})
links = []
for r in investigation.relationships:
links.append({
"source": r.source_entity_id,
"target": r.target_entity_id,
"type": r.relationship_type,
"confidence": r.confidence,
})
return {
"id": investigation.id,
"query": investigation.initial_query,
"query_type": investigation.initial_type.value,
"status": investigation.status,
"created_at": investigation.created_at,
"completed_at": investigation.completed_at,
"total_entities": len(investigation.entities),
"total_relationships": len(investigation.relationships),
"entity_counts": dict(entity_counts),
"relationship_counts": dict(rel_counts),
"modules_run": investigation.modules_run,
"graph": {"nodes": nodes, "links": links},
"entities": [e.to_dict() for e in investigation.entities],
"relationships": [r.to_dict() for r in investigation.relationships],
}
def _truncate(text: str, max_len: int) -> str:
if len(text) <= max_len:
return text
return text[:max_len - 3] + "..."