implement investigation engine, plugin manager and result aggregation
This commit is contained in:
parent
214ef8ad1b
commit
2456047f8c
3 changed files with 219 additions and 0 deletions
104
eirescope/core/engine.py
Normal file
104
eirescope/core/engine.py
Normal 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()
|
||||
62
eirescope/core/plugin_manager.py
Normal file
62
eirescope/core/plugin_manager.py
Normal 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
53
eirescope/core/results.py
Normal 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] + "..."
|
||||
Loading…
Add table
Add a link
Reference in a new issue