Source code for django_honeyguard.services

"""Business logic for HoneyGuard honeypot triggers."""

from typing import Any, Dict, Optional

from django.core.mail import send_mail
from django.http import HttpRequest

from .conf import settings as honeyguard_settings
from .constants import CONSOLE_LOG_FORMAT, EMAIL_ALERT_BODY
from .loggers import get_logger
from .models import HoneyGuardLog, TimingIssue
from .utils import check_timing_attack, get_request_metadata, sanitize_password

logger = get_logger(__name__)


[docs] class HoneyGuardService: """Encapsulates core business logic for honeypot event processing."""
[docs] def __init__( self, request: HttpRequest, data: Optional[Dict[str, Any]] = None ) -> None: self.request: HttpRequest = request self.metadata: Dict[str, str] = get_request_metadata(request) elapsed_time = 0.0 timing_issue = TimingIssue.VALID if data: render_time = data.get("render_time") if render_time: timing_issue, elapsed_time = check_timing_attack(render_time) honeypot_triggered = bool(data.get("hp", "").strip()) data["honeypot_triggered"] = honeypot_triggered data["timing_issue"] = timing_issue data["elapsed_time"] = elapsed_time self.data: Dict[str, Any] = data else: self.data = { "timing_issue": timing_issue, "elapsed_time": elapsed_time, "honeypot_triggered": False, }
def _format_log_data(self) -> Dict[str, Any]: """Format data for logging/email alerts.""" password_sanitized = sanitize_password(self.data.get("password", "")) return { **self.metadata, "username": self.data.get("username", ""), "password": password_sanitized, "elapsed_time": self.data.get("elapsed_time", 0), "timing_issue": self.data.get("timing_issue", ""), "honeypot_triggered": self.data.get("honeypot_triggered", False), "raw_metadata": str(self.metadata), }
[docs] def log_trigger(self) -> None: """Log honeypot trigger to the database.""" HoneyGuardLog.objects.create( path=self.metadata["path"], raw_metadata=self.metadata, method=self.metadata["method"], ip_address=self.metadata["ip_address"], user_agent=self.metadata["user_agent"], referer=self.metadata["referer"], accept_language=self.metadata["accept_language"], accept_encoding=self.metadata["accept_encoding"], username=self.data.get("username", ""), password=sanitize_password(self.data.get("password")), honeypot_triggered=self.data.get("honeypot_triggered", False), timing_issue=self.data.get("timing_issue"), elapsed_time=self.data.get("elapsed_time"), )
[docs] def log_to_console(self) -> None: """Log honeypot trigger details to console or file.""" if not honeyguard_settings.ENABLE_CONSOLE_LOGGING: return log_text = CONSOLE_LOG_FORMAT.format(**self._format_log_data()) logger.warning(log_text)
[docs] def send_email_alert(self) -> None: """ Send email alert to configured recipients. This method handles email sending with proper error handling. Email failures will not raise exceptions by default to prevent disrupting the honeypot detection flow. """ recipients = honeyguard_settings.EMAIL_RECIPIENTS or [] if not recipients: logger.debug("No email recipients configured; skipping email alert.") return subject_prefix = honeyguard_settings.EMAIL_SUBJECT_PREFIX subject = f"{subject_prefix} - {self.metadata['path']}" try: message = EMAIL_ALERT_BODY.format(**self._format_log_data()) except KeyError as e: logger.error( f"Error formatting email message: missing key {e}", exc_info=True, ) return # Get fail_silently setting (defaults to True for resilience) fail_silently = honeyguard_settings.EMAIL_FAIL_SILENTLY or True try: from_email = honeyguard_settings.EMAIL_FROM if not from_email: from_email = None # Let Django use DEFAULT_FROM_EMAIL send_mail( subject=subject, message=message, from_email=from_email, recipient_list=recipients, fail_silently=fail_silently, ) logger.info(f"Sent email alert to {len(recipients)} recipient(s)") except Exception as e: logger.error( f"Error sending email alert to {len(recipients)} recipient(s): {e}", exc_info=True, ) # If fail_silently is False and we still got an exception, log critical if not fail_silently: logger.critical( "Email sending failed with fail_silently=False. " "Check email configuration immediately." )