🔧 Update: Enhanced error handling and logging across various modules
**Änderungen:** - ✅ app.py: Hinzugefügt, um CSRF-Fehler zu behandeln - ✅ models.py: Fehlerprotokollierung bei der Suche nach Gastanfragen per OTP - ✅ api.py: Fehlerprotokollierung beim Markieren von Benachrichtigungen als gelesen - ✅ calendar.py: Fallback-Daten zurückgeben, wenn keine Kalenderereignisse vorhanden sind - ✅ guest.py: Status-Check-Seite für Gäste aktualisiert - ✅ hardware_integration.py: Debugging-Informationen für erweiterte Geräteinformationen hinzugefügt - ✅ tapo_status_manager.py: Rückgabewert für Statusabfrage hinzugefügt **Ergebnis:** - Verbesserte Fehlerbehandlung und Protokollierung für eine robustere Anwendung - Bessere Nachverfolgbarkeit von Fehlern und Systemverhalten 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
252
backend/utils/ip_security.py
Normal file
252
backend/utils/ip_security.py
Normal file
@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env python3.11
|
||||
"""
|
||||
IP Security Module für MYP Platform
|
||||
===================================
|
||||
|
||||
Implementiert IP-Beschränkungen für Steckdosen-Zugriff
|
||||
Erlaubt nur IPs von 192.168.0.100 bis 192.168.0.106, mit Ausnahme von 192.168.0.105
|
||||
|
||||
Author: Till Tomczak
|
||||
"""
|
||||
|
||||
import ipaddress
|
||||
from typing import List, Optional, Set
|
||||
from flask import request, abort
|
||||
from functools import wraps
|
||||
|
||||
from utils.logging_config import get_logger
|
||||
|
||||
logger = get_logger("ip_security")
|
||||
|
||||
class IPSecurityManager:
|
||||
"""Verwaltet IP-Beschränkungen für Steckdosen-Zugriff"""
|
||||
|
||||
def __init__(self):
|
||||
# Erlaubte IP-Adressen für Steckdosen-Steuerung
|
||||
self.allowed_ips: Set[str] = {
|
||||
"192.168.0.100",
|
||||
"192.168.0.101",
|
||||
"192.168.0.102",
|
||||
"192.168.0.103",
|
||||
"192.168.0.104",
|
||||
"192.168.0.106" # 192.168.0.105 ist ausgeschlossen
|
||||
}
|
||||
|
||||
# Zusätzliche erlaubte Netzwerke für Admin-Zugriff
|
||||
self.admin_networks: List[str] = [
|
||||
"127.0.0.0/8", # Localhost
|
||||
"192.168.0.0/24", # Lokales Netzwerk
|
||||
"10.0.0.0/8", # Private Netzwerke
|
||||
"172.16.0.0/12" # Private Netzwerke
|
||||
]
|
||||
|
||||
logger.info(f"✅ IP-Sicherheit initialisiert: {len(self.allowed_ips)} erlaubte Steckdosen-IPs")
|
||||
logger.info(f"🚫 Gesperrte IP: 192.168.0.105")
|
||||
|
||||
def is_plug_ip_allowed(self, ip: str) -> bool:
|
||||
"""
|
||||
Prüft, ob eine IP-Adresse für Steckdosen-Zugriff erlaubt ist
|
||||
|
||||
Args:
|
||||
ip: IP-Adresse der Steckdose
|
||||
|
||||
Returns:
|
||||
bool: True wenn erlaubt, False wenn gesperrt
|
||||
"""
|
||||
if not ip:
|
||||
return False
|
||||
|
||||
is_allowed = ip in self.allowed_ips
|
||||
|
||||
if not is_allowed:
|
||||
logger.warning(f"🚫 Steckdosen-IP {ip} ist nicht erlaubt")
|
||||
|
||||
return is_allowed
|
||||
|
||||
def is_client_ip_allowed(self, client_ip: str) -> bool:
|
||||
"""
|
||||
Prüft, ob eine Client-IP für Admin-Zugriff erlaubt ist
|
||||
|
||||
Args:
|
||||
client_ip: IP-Adresse des Clients
|
||||
|
||||
Returns:
|
||||
bool: True wenn erlaubt
|
||||
"""
|
||||
if not client_ip:
|
||||
return False
|
||||
|
||||
try:
|
||||
client_addr = ipaddress.ip_address(client_ip)
|
||||
|
||||
# Prüfe gegen erlaubte Netzwerke
|
||||
for network_str in self.admin_networks:
|
||||
network = ipaddress.ip_network(network_str, strict=False)
|
||||
if client_addr in network:
|
||||
return True
|
||||
|
||||
logger.warning(f"🚫 Client-IP {client_ip} ist nicht in erlaubten Netzwerken")
|
||||
return False
|
||||
|
||||
except ValueError as e:
|
||||
logger.error(f"❌ Ungültige IP-Adresse: {client_ip} - {e}")
|
||||
return False
|
||||
|
||||
def get_client_ip(self) -> Optional[str]:
|
||||
"""
|
||||
Ermittelt die echte Client-IP-Adresse
|
||||
|
||||
Returns:
|
||||
Optional[str]: Client-IP oder None
|
||||
"""
|
||||
# Prüfe verschiedene Headers für Proxy-Setups
|
||||
headers_to_check = [
|
||||
'X-Forwarded-For',
|
||||
'X-Real-IP',
|
||||
'X-Forwarded-Proto',
|
||||
'HTTP_X_FORWARDED_FOR',
|
||||
'HTTP_X_REAL_IP'
|
||||
]
|
||||
|
||||
for header in headers_to_check:
|
||||
ip = request.headers.get(header)
|
||||
if ip:
|
||||
# X-Forwarded-For kann mehrere IPs enthalten
|
||||
if ',' in ip:
|
||||
ip = ip.split(',')[0].strip()
|
||||
return ip
|
||||
|
||||
# Fallback auf remote_addr
|
||||
return request.remote_addr
|
||||
|
||||
def validate_plug_access(self, plug_ip: str) -> bool:
|
||||
"""
|
||||
Validiert Zugriff auf Steckdose basierend auf IP-Adresse
|
||||
|
||||
Args:
|
||||
plug_ip: IP-Adresse der Steckdose
|
||||
|
||||
Returns:
|
||||
bool: True wenn Zugriff erlaubt
|
||||
|
||||
Raises:
|
||||
SecurityError: Wenn Zugriff verweigert wird
|
||||
"""
|
||||
if not self.is_plug_ip_allowed(plug_ip):
|
||||
logger.security(f"🚨 SICHERHEITSVERSTOSSE: Zugriff auf gesperrte Steckdose {plug_ip} verweigert")
|
||||
raise SecurityError(f"Zugriff auf Steckdose {plug_ip} ist nicht erlaubt")
|
||||
|
||||
return True
|
||||
|
||||
def get_allowed_plug_ips(self) -> List[str]:
|
||||
"""
|
||||
Gibt Liste der erlaubten Steckdosen-IPs zurück
|
||||
|
||||
Returns:
|
||||
List[str]: Erlaubte IP-Adressen
|
||||
"""
|
||||
return sorted(list(self.allowed_ips))
|
||||
|
||||
def is_ip_blocked(self, ip: str) -> bool:
|
||||
"""
|
||||
Prüft, ob eine IP explizit gesperrt ist
|
||||
|
||||
Args:
|
||||
ip: IP-Adresse
|
||||
|
||||
Returns:
|
||||
bool: True wenn gesperrt
|
||||
"""
|
||||
# 192.168.0.105 ist explizit gesperrt
|
||||
return ip == "192.168.0.105"
|
||||
|
||||
class SecurityError(Exception):
|
||||
"""Fehler bei Sicherheitsüberprüfung"""
|
||||
pass
|
||||
|
||||
# Globale Instanz
|
||||
ip_security = IPSecurityManager()
|
||||
|
||||
def require_plug_ip_access(func):
|
||||
"""
|
||||
Decorator für Steckdosen-Zugriff der IP-Beschränkungen durchsetzt
|
||||
|
||||
Args:
|
||||
func: Zu schützende Funktion
|
||||
|
||||
Returns:
|
||||
Wrapper-Funktion
|
||||
"""
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
# Extrahiere plug_ip aus Argumenten
|
||||
plug_ip = None
|
||||
|
||||
# Versuche plug_ip aus kwargs zu extrahieren
|
||||
if 'plug_ip' in kwargs:
|
||||
plug_ip = kwargs['plug_ip']
|
||||
elif 'ip' in kwargs:
|
||||
plug_ip = kwargs['ip']
|
||||
# Versuche aus Request-Daten
|
||||
elif request.is_json:
|
||||
data = request.get_json()
|
||||
plug_ip = data.get('plug_ip') or data.get('ip')
|
||||
elif request.form:
|
||||
plug_ip = request.form.get('plug_ip') or request.form.get('ip')
|
||||
|
||||
if plug_ip:
|
||||
try:
|
||||
ip_security.validate_plug_access(plug_ip)
|
||||
except SecurityError as e:
|
||||
logger.error(f"🚨 IP-Sicherheitsverstoß: {e}")
|
||||
abort(403, description=str(e))
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
def require_admin_ip_access(func):
|
||||
"""
|
||||
Decorator für Admin-Zugriff mit IP-Beschränkungen
|
||||
|
||||
Args:
|
||||
func: Zu schützende Funktion
|
||||
|
||||
Returns:
|
||||
Wrapper-Funktion
|
||||
"""
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
client_ip = ip_security.get_client_ip()
|
||||
|
||||
if not ip_security.is_client_ip_allowed(client_ip):
|
||||
logger.security(f"🚨 Admin-Zugriff von unerlaubter IP verweigert: {client_ip}")
|
||||
abort(403, description=f"Zugriff von IP {client_ip} ist nicht erlaubt")
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
def get_allowed_plug_ips() -> List[str]:
|
||||
"""
|
||||
Convenience-Function für erlaubte Steckdosen-IPs
|
||||
|
||||
Returns:
|
||||
List[str]: Erlaubte IP-Adressen
|
||||
"""
|
||||
return ip_security.get_allowed_plug_ips()
|
||||
|
||||
def is_plug_ip_allowed(ip: str) -> bool:
|
||||
"""
|
||||
Convenience-Function für IP-Validierung
|
||||
|
||||
Args:
|
||||
ip: IP-Adresse
|
||||
|
||||
Returns:
|
||||
bool: True wenn erlaubt
|
||||
"""
|
||||
return ip_security.is_plug_ip_allowed(ip)
|
||||
|
||||
logger.info("✅ IP Security Module initialisiert")
|
||||
logger.info("🛡️ Erlaubte Steckdosen-IPs: 192.168.0.100-106 (außer .105)")
|
Reference in New Issue
Block a user