Files
Projektarbeit-MYP/backend/utils/ip_security.py
Till Tomczak 956c24d8ca 🔧 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>
2025-06-15 22:45:20 +02:00

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)")