🎉 Verbesserte Installation und Konfiguration des Kiosk-Browsers mit Fallback-Mechanismus für Browser und optimierten Vollbildmodus. 📚

This commit is contained in:
2025-05-31 22:14:20 +02:00
parent f0692d1816
commit 91b1886dde
2366 changed files with 384929 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
"""
Configuration Package for MYP Platform
======================================
This package contains all configuration modules for the Mercedes-Benz 3D Printing Platform.
Modules:
- security: Security configuration and middleware
- database: Database configuration and settings
- logging: Logging configuration
- app_config: Main application configuration
"""
__version__ = "2.0.0"
__author__ = "MYP Development Team"
# Import main configuration modules
try:
from .security import SecurityConfig, get_security_headers
from .app_config import Config, DevelopmentConfig, ProductionConfig, TestingConfig
except ImportError as e:
print(f"Warning: Could not import configuration modules: {e}")
# Fallback configurations
SecurityConfig = None
get_security_headers = None
Config = None
# Export main configuration classes
__all__ = [
'SecurityConfig',
'get_security_headers',
'Config',
'DevelopmentConfig',
'ProductionConfig',
'TestingConfig'
]
def get_config(config_name='development'):
"""
Get configuration object based on environment name.
Args:
config_name (str): Configuration environment name
Returns:
Config: Configuration object
"""
configs = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'testing': TestingConfig
}
return configs.get(config_name, DevelopmentConfig)
def validate_config(config_obj):
"""
Validate configuration object.
Args:
config_obj: Configuration object to validate
Returns:
bool: True if valid, False otherwise
"""
required_attrs = ['SECRET_KEY', 'DATABASE_URL']
for attr in required_attrs:
if not hasattr(config_obj, attr):
print(f"Missing required configuration: {attr}")
return False
return True

View File

@@ -0,0 +1,181 @@
# -*- coding: utf-8 -*-
"""
Application Configuration Module for MYP Platform
================================================
Flask configuration classes for different environments.
"""
import os
from datetime import timedelta
# Base configuration directory
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
PROJECT_ROOT = os.path.abspath(os.path.join(BASE_DIR, '..', '..'))
class Config:
"""Base configuration class with common settings."""
# Secret key for Flask sessions and CSRF protection
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key-change-in-production-744563017196A'
# Session configuration
PERMANENT_SESSION_LIFETIME = timedelta(hours=24)
SESSION_COOKIE_SECURE = False # Set to True in production with HTTPS
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
# Database configuration
DATABASE_URL = os.environ.get('DATABASE_URL') or f'sqlite:///{os.path.join(PROJECT_ROOT, "data", "myp_platform.db")}'
SQLALCHEMY_DATABASE_URI = DATABASE_URL
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ENGINE_OPTIONS = {
'pool_pre_ping': True,
'pool_recycle': 300,
}
# Upload configuration
UPLOAD_FOLDER = os.path.join(PROJECT_ROOT, 'uploads')
MAX_CONTENT_LENGTH = 500 * 1024 * 1024 # 500MB max file size
ALLOWED_EXTENSIONS = {'gcode', 'stl', 'obj', '3mf', 'amf'}
# Security configuration
WTF_CSRF_ENABLED = True
WTF_CSRF_TIME_LIMIT = 3600 # 1 hour
# Mail configuration (optional)
MAIL_SERVER = os.environ.get('MAIL_SERVER')
MAIL_PORT = int(os.environ.get('MAIL_PORT') or 587)
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'true').lower() in ['true', 'on', '1']
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
# Logging configuration
LOG_LEVEL = os.environ.get('LOG_LEVEL', 'INFO')
LOG_FILE_MAX_BYTES = 10 * 1024 * 1024 # 10MB
LOG_BACKUP_COUNT = 5
# Application-specific settings
SCHEDULER_ENABLED = os.environ.get('SCHEDULER_ENABLED', 'true').lower() in ['true', 'on', '1']
SCHEDULER_INTERVAL = int(os.environ.get('SCHEDULER_INTERVAL', '60')) # seconds
# SSL/HTTPS configuration
SSL_ENABLED = os.environ.get('SSL_ENABLED', 'false').lower() in ['true', 'on', '1']
SSL_CERT_PATH = os.environ.get('SSL_CERT_PATH')
SSL_KEY_PATH = os.environ.get('SSL_KEY_PATH')
# Network configuration
DEFAULT_PORT = int(os.environ.get('PORT', '5000'))
DEFAULT_HOST = os.environ.get('HOST', '0.0.0.0')
@staticmethod
def init_app(app):
"""Initialize application with this configuration."""
pass
class DevelopmentConfig(Config):
"""Development environment configuration."""
DEBUG = True
TESTING = False
# More verbose logging in development
LOG_LEVEL = 'DEBUG'
# Disable some security features for easier development
SESSION_COOKIE_SECURE = False
WTF_CSRF_ENABLED = False # Disable CSRF for easier API testing
@staticmethod
def init_app(app):
Config.init_app(app)
# Development-specific initialization
import logging
logging.basicConfig(level=logging.DEBUG)
class TestingConfig(Config):
"""Testing environment configuration."""
TESTING = True
DEBUG = True
# Use in-memory database for testing
DATABASE_URL = 'sqlite:///:memory:'
SQLALCHEMY_DATABASE_URI = DATABASE_URL
# Disable CSRF for testing
WTF_CSRF_ENABLED = False
# Shorter session lifetime for testing
PERMANENT_SESSION_LIFETIME = timedelta(minutes=5)
@staticmethod
def init_app(app):
Config.init_app(app)
class ProductionConfig(Config):
"""Production environment configuration."""
DEBUG = False
TESTING = False
# Strict security settings for production
SESSION_COOKIE_SECURE = True # Requires HTTPS
WTF_CSRF_ENABLED = True
# Production logging
LOG_LEVEL = 'WARNING'
# SSL should be enabled in production
SSL_ENABLED = True
@staticmethod
def init_app(app):
Config.init_app(app)
# Production-specific initialization
import logging
from logging.handlers import RotatingFileHandler
# Set up file logging for production
log_dir = os.path.join(os.path.dirname(app.instance_path), 'logs')
if not os.path.exists(log_dir):
os.makedirs(log_dir)
file_handler = RotatingFileHandler(
os.path.join(log_dir, 'myp_platform.log'),
maxBytes=Config.LOG_FILE_MAX_BYTES,
backupCount=Config.LOG_BACKUP_COUNT
)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(logging.WARNING)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.WARNING)
# Configuration dictionary for easy access
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
def get_config_by_name(config_name):
"""
Get configuration class by name.
Args:
config_name (str): Name of the configuration ('development', 'testing', 'production')
Returns:
Config: Configuration class
"""
return config.get(config_name, config['default'])

View File

@@ -0,0 +1,81 @@
"""
Sicherheitskonfiguration für die MYP Platform
"""
# Sicherheits-Headers für HTTP-Responses
SECURITY_HEADERS = {
'Content-Security-Policy': (
"default-src 'self'; "
"script-src 'self' 'unsafe-eval' 'unsafe-inline'; "
"script-src-elem 'self' 'unsafe-inline'; "
"style-src 'self' 'unsafe-inline'; "
"font-src 'self'; "
"img-src 'self' data:; "
"connect-src 'self'; "
"worker-src 'self' blob:; "
"frame-src 'none'; "
"object-src 'none'; "
"base-uri 'self'; "
"form-action 'self'; "
"frame-ancestors 'none';"
),
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Permissions-Policy': 'geolocation=(), microphone=(), camera=()'
}
# Rate Limiting Konfiguration
RATE_LIMITS = {
'default': "200 per day, 50 per hour",
'login': "5 per minute",
'api': "100 per hour",
'admin': "500 per hour"
}
# Session-Sicherheit
SESSION_CONFIG = {
'SESSION_COOKIE_SECURE': False, # Für Offline-Betrieb auf False setzen
'SESSION_COOKIE_HTTPONLY': True,
'SESSION_COOKIE_SAMESITE': 'Lax',
'PERMANENT_SESSION_LIFETIME': 3600 # 1 Stunde
}
# CSRF-Schutz
CSRF_CONFIG = {
'CSRF_ENABLED': True,
'CSRF_SESSION_KEY': 'csrf_token',
'CSRF_TIME_LIMIT': 3600
}
class SecurityConfig:
"""Sicherheitskonfiguration für die Anwendung"""
def __init__(self):
self.headers = SECURITY_HEADERS
self.rate_limits = RATE_LIMITS
self.session_config = SESSION_CONFIG
self.csrf_config = CSRF_CONFIG
def get_headers(self):
"""Gibt die Sicherheits-Headers zurück"""
return self.headers
def get_rate_limits(self):
"""Gibt die Rate-Limiting-Konfiguration zurück"""
return self.rate_limits
def get_session_config(self):
"""Gibt die Session-Konfiguration zurück"""
return self.session_config
def get_csrf_config(self):
"""Gibt die CSRF-Konfiguration zurück"""
return self.csrf_config
def get_security_headers():
"""Gibt die Sicherheits-Headers zurück"""
return SECURITY_HEADERS

View File

@@ -0,0 +1,187 @@
import os
import json
from datetime import timedelta
def get_env_variable(name: str, default: str = None) -> str:
"""
Holt eine Umgebungsvariable oder gibt den Standardwert zurück.
Args:
name: Name der Umgebungsvariable
default: Standardwert, falls die Variable nicht gesetzt ist
Returns:
str: Wert der Umgebungsvariable oder Standardwert
"""
return os.environ.get(name, default)
# Hardcodierte Konfiguration
SECRET_KEY = "7445630171969DFAC92C53CEC92E67A9CB2E00B3CB2F"
# Dynamische Pfade basierend auf dem aktuellen Arbeitsverzeichnis
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # backend/app
PROJECT_ROOT = os.path.dirname(BASE_DIR) # backend
DATABASE_PATH = os.path.join(BASE_DIR, "database", "myp.db")
# ===== SMART PLUG KONFIGURATION =====
# TP-Link Tapo P110 Standardkonfiguration
TAPO_USERNAME = "till.tomczak@mercedes-benz.com"
TAPO_PASSWORD = "744563017196A"
# Automatische Steckdosen-Erkennung aktivieren
TAPO_AUTO_DISCOVERY = True
# Standard-Steckdosen-IPs (diese können später in der Datenbank überschrieben werden)
DEFAULT_TAPO_IPS = [
"192.168.0.103", # Erreichbare Steckdose laut Test
"192.168.0.104", # Erreichbare Steckdose laut Test
"192.168.0.100",
"192.168.0.101",
"192.168.0.102",
"192.168.0.105"
]
# Timeout-Konfiguration für Tapo-Verbindungen
TAPO_TIMEOUT = 10 # Sekunden
TAPO_RETRY_COUNT = 3 # Anzahl Wiederholungsversuche
# Drucker-Konfiguration
PRINTERS = {
"Printer 1": {"ip": "192.168.0.100"},
"Printer 2": {"ip": "192.168.0.101"},
"Printer 3": {"ip": "192.168.0.102"},
"Printer 4": {"ip": "192.168.0.103"},
"Printer 5": {"ip": "192.168.0.104"},
"Printer 6": {"ip": "192.168.0.106"}
}
# Logging-Konfiguration
LOG_DIR = os.path.join(BASE_DIR, "logs")
LOG_SUBDIRS = ["app", "scheduler", "auth", "jobs", "printers", "errors"]
LOG_LEVEL = "INFO"
LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
# Flask-Konfiguration
FLASK_HOST = "0.0.0.0"
FLASK_PORT = 443 # Geändert von 443 auf 8443 (nicht-privilegierter Port)
FLASK_FALLBACK_PORT = 8080 # Geändert von 80 auf 8080 (nicht-privilegierter Port)
FLASK_DEBUG = True
SESSION_LIFETIME = timedelta(hours=2) # Reduziert von 7 Tagen auf 2 Stunden für bessere Sicherheit
# Upload-Konfiguration
UPLOAD_FOLDER = os.path.join(BASE_DIR, "uploads")
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'gcode', '3mf', 'stl'}
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB Maximum-Dateigröße
# Umgebungskonfiguration
ENVIRONMENT = get_env_variable("MYP_ENVIRONMENT", "development")
# SSL-Konfiguration
SSL_ENABLED = get_env_variable("MYP_SSL_ENABLED", "True").lower() in ("true", "1", "yes")
SSL_CERT_PATH = os.path.join(BASE_DIR, "certs", "myp.crt")
SSL_KEY_PATH = os.path.join(BASE_DIR, "certs", "myp.key")
SSL_HOSTNAME = get_env_variable("MYP_SSL_HOSTNAME", "localhost")
# Scheduler-Konfiguration
SCHEDULER_INTERVAL = 60 # Sekunden
SCHEDULER_ENABLED = True
# Datenbank-Konfiguration
DB_ENGINE = f"sqlite:///{DATABASE_PATH}"
def get_log_file(category: str) -> str:
"""
Gibt den Pfad zur Log-Datei für eine bestimmte Kategorie zurück.
Args:
category: Log-Kategorie (app, scheduler, auth, jobs, printers, errors)
Returns:
str: Pfad zur Log-Datei
"""
if category not in LOG_SUBDIRS:
category = "app"
return os.path.join(LOG_DIR, category, f"{category}.log")
def ensure_log_directories():
"""Erstellt alle erforderlichen Log-Verzeichnisse."""
os.makedirs(LOG_DIR, exist_ok=True)
for subdir in LOG_SUBDIRS:
os.makedirs(os.path.join(LOG_DIR, subdir), exist_ok=True)
def ensure_database_directory():
"""Erstellt das Datenbank-Verzeichnis."""
db_dir = os.path.dirname(DATABASE_PATH)
if db_dir:
os.makedirs(db_dir, exist_ok=True)
def ensure_ssl_directory():
"""Erstellt das SSL-Verzeichnis, falls es nicht existiert."""
ssl_dir = os.path.dirname(SSL_CERT_PATH)
if ssl_dir and not os.path.exists(ssl_dir):
os.makedirs(ssl_dir, exist_ok=True)
def ensure_upload_directory():
"""Erstellt das Upload-Verzeichnis, falls es nicht existiert."""
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
def get_ssl_context():
"""
Gibt den SSL-Kontext für Flask zurück, wenn SSL aktiviert ist.
Returns:
tuple oder None: Tuple mit Zertifikat- und Schlüsselpfad, wenn SSL aktiviert ist, sonst None
"""
if not SSL_ENABLED:
return None
# Wenn Zertifikate nicht existieren, diese automatisch erstellen
if not os.path.exists(SSL_CERT_PATH) or not os.path.exists(SSL_KEY_PATH):
ensure_ssl_directory()
# Im Entwicklungsmodus versuchen wir, einfache Zertifikate zu erstellen
if FLASK_DEBUG:
print("SSL-Zertifikate nicht gefunden. Erstelle einfache selbstsignierte Zertifikate...")
try:
# Einfache Zertifikate mit Python erstellen
create_simple_ssl_cert()
# Prüfen, ob die Zertifikate erfolgreich erstellt wurden
if not os.path.exists(SSL_CERT_PATH) or not os.path.exists(SSL_KEY_PATH):
print("Konnte keine SSL-Zertifikate erstellen.")
return None
except Exception as e:
print(f"Fehler beim Erstellen der SSL-Zertifikate: {e}")
return None
else:
print("WARNUNG: SSL-Zertifikate nicht gefunden und Nicht-Debug-Modus. SSL wird deaktiviert.")
return None
return (SSL_CERT_PATH, SSL_KEY_PATH)
def create_simple_ssl_cert():
"""
Erstellt ein Mercedes-Benz SSL-Zertifikat mit dem neuen SSL-Manager.
"""
try:
# Verwende den neuen SSL-Manager
from utils.ssl_manager import ssl_manager
success = ssl_manager.generate_mercedes_certificate()
if success:
print(f"Mercedes-Benz SSL-Zertifikat erfolgreich erstellt: {SSL_CERT_PATH}")
return True
else:
print("Fehler beim Erstellen des Mercedes-Benz SSL-Zertifikats")
return None
except ImportError as e:
print(f"SSL-Manager nicht verfügbar: {e}")
return None
except Exception as e:
print(f"Fehler beim Erstellen der SSL-Zertifikate: {e}")
return None

View File

@@ -0,0 +1,116 @@
import os
import json
from datetime import timedelta
# Hardcodierte Konfiguration
SECRET_KEY = "7445630171969DFAC92C53CEC92E67A9CB2E00B3CB2F"
DATABASE_PATH = "database/myp.db"
TAPO_USERNAME = "till.tomczak@mercedes-benz.com"
TAPO_PASSWORD = "744563017196A"
# Drucker-Konfiguration
PRINTERS = {
"Printer 1": {"ip": "192.168.0.100"},
"Printer 2": {"ip": "192.168.0.101"},
"Printer 3": {"ip": "192.168.0.102"},
"Printer 4": {"ip": "192.168.0.103"},
"Printer 5": {"ip": "192.168.0.104"},
"Printer 6": {"ip": "192.168.0.106"}
}
# Logging-Konfiguration
LOG_DIR = "logs"
LOG_SUBDIRS = ["app", "scheduler", "auth", "jobs", "printers", "errors"]
LOG_LEVEL = "INFO"
LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
# Flask-Konfiguration
FLASK_HOST = "0.0.0.0"
FLASK_PORT = 443
FLASK_FALLBACK_PORT = 80
FLASK_DEBUG = True
SESSION_LIFETIME = timedelta(days=7)
# SSL-Konfiguration
SSL_ENABLED = True
SSL_CERT_PATH = "instance/ssl/myp.crt"
SSL_KEY_PATH = "instance/ssl/myp.key"
SSL_HOSTNAME = "raspberrypi"
# Scheduler-Konfiguration
SCHEDULER_INTERVAL = 60 # Sekunden
SCHEDULER_ENABLED = True
# Datenbank-Konfiguration
DB_ENGINE = f"sqlite:///{DATABASE_PATH}"
def get_log_file(category: str) -> str:
"""
Gibt den Pfad zur Log-Datei für eine bestimmte Kategorie zurück.
Args:
category: Log-Kategorie (app, scheduler, auth, jobs, printers, errors)
Returns:
str: Pfad zur Log-Datei
"""
if category not in LOG_SUBDIRS:
category = "app"
return os.path.join(LOG_DIR, category, f"{category}.log")
def ensure_log_directories():
"""Erstellt alle erforderlichen Log-Verzeichnisse."""
os.makedirs(LOG_DIR, exist_ok=True)
for subdir in LOG_SUBDIRS:
os.makedirs(os.path.join(LOG_DIR, subdir), exist_ok=True)
def ensure_database_directory():
"""Erstellt das Datenbank-Verzeichnis."""
db_dir = os.path.dirname(DATABASE_PATH)
if db_dir:
os.makedirs(db_dir, exist_ok=True)
def ensure_ssl_directory():
"""Erstellt das SSL-Verzeichnis, falls es nicht existiert."""
ssl_dir = os.path.dirname(SSL_CERT_PATH)
if ssl_dir and not os.path.exists(ssl_dir):
os.makedirs(ssl_dir, exist_ok=True)
def get_ssl_context():
"""
Gibt den SSL-Kontext für Flask zurück, wenn SSL aktiviert ist.
Returns:
tuple oder None: Tuple mit Zertifikat- und Schlüsselpfad, wenn SSL aktiviert ist, sonst None
"""
if not SSL_ENABLED:
return None
# Wenn Zertifikate nicht existieren, diese automatisch erstellen
if not os.path.exists(SSL_CERT_PATH) or not os.path.exists(SSL_KEY_PATH):
ensure_ssl_directory()
# Prüfen, ob wir uns im Entwicklungsmodus befinden
if FLASK_DEBUG:
print("SSL-Zertifikate nicht gefunden. Erstelle selbstsignierte Zertifikate...")
# Pfad zum create_ssl_cert.sh-Skript ermitteln
script_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
"install", "create_ssl_cert.sh")
# Ausführungsrechte setzen
if os.path.exists(script_path):
os.system(f"chmod +x {script_path}")
# Zertifikate erstellen mit spezifischem Hostnamen
os.system(f"{script_path} -c {SSL_CERT_PATH} -k {SSL_KEY_PATH} -h {SSL_HOSTNAME}")
else:
print(f"WARNUNG: SSL-Zertifikat-Generator nicht gefunden: {script_path}")
return None
else:
print("WARNUNG: SSL-Zertifikate nicht gefunden und Nicht-Debug-Modus. SSL wird deaktiviert.")
return None
return (SSL_CERT_PATH, SSL_KEY_PATH)