**Ä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>
252 lines
7.2 KiB
Python
252 lines
7.2 KiB
Python
#!/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)") |