Dieser Commit ist enthalten in:
2025-06-16 00:37:14 +02:00
Ursprung ff935204d5
Commit 262de2839e
26 geänderte Dateien mit 2128 neuen und 28 gelöschten Zeilen

Dateidiff unterdrückt, weil mindestens eine Zeile zu lang ist

Dateidiff unterdrückt, weil mindestens eine Zeile zu lang ist

Datei anzeigen

@@ -2743,6 +2743,7 @@ def export_licenses():
# Alle Lizenzen mit Kundeninformationen abrufen (ohne Testdaten, außer explizit gewünscht)
include_test = request.args.get('include_test', 'false').lower() == 'true'
customer_id = request.args.get('customer_id', type=int)
query = """
SELECT l.id, l.license_key, c.name as customer_name, c.email as customer_email,
@@ -2757,12 +2758,23 @@ def export_licenses():
JOIN customers c ON l.customer_id = c.id
"""
# Build WHERE clause
where_conditions = []
params = []
if not include_test:
query += " WHERE l.is_test = FALSE"
where_conditions.append("l.is_test = FALSE")
if customer_id:
where_conditions.append("l.customer_id = %s")
params.append(customer_id)
if where_conditions:
query += " WHERE " + " AND ".join(where_conditions)
query += " ORDER BY l.id"
cur.execute(query)
cur.execute(query, params)
# Spaltennamen
columns = ['ID', 'Lizenzschlüssel', 'Kunde', 'E-Mail', 'Typ',
@@ -2963,20 +2975,40 @@ def export_customers():
conn = get_connection()
cur = conn.cursor()
# Alle Kunden mit Lizenzstatistiken (ohne Testdaten)
cur.execute("""
SELECT c.id, c.name, c.email, c.created_at,
COUNT(CASE WHEN l.is_test = FALSE THEN 1 END) as total_licenses,
COUNT(CASE WHEN l.is_active = TRUE AND l.valid_until >= CURRENT_DATE AND l.is_test = FALSE THEN 1 END) as active_licenses,
COUNT(CASE WHEN l.valid_until < CURRENT_DATE AND l.is_test = FALSE THEN 1 END) as expired_licenses
FROM customers c
LEFT JOIN licenses l ON c.id = l.customer_id
GROUP BY c.id, c.name, c.email, c.created_at
ORDER BY c.id
""")
# Check if test data should be included
include_test = request.args.get('include_test', 'false').lower() == 'true'
# Build query based on test data filter
if include_test:
# Include all customers
query = """
SELECT c.id, c.name, c.email, c.created_at, c.is_test,
COUNT(l.id) as total_licenses,
COUNT(CASE WHEN l.is_active = TRUE AND l.valid_until >= CURRENT_DATE THEN 1 END) as active_licenses,
COUNT(CASE WHEN l.valid_until < CURRENT_DATE THEN 1 END) as expired_licenses
FROM customers c
LEFT JOIN licenses l ON c.id = l.customer_id
GROUP BY c.id, c.name, c.email, c.created_at, c.is_test
ORDER BY c.id
"""
else:
# Exclude test customers and test licenses
query = """
SELECT c.id, c.name, c.email, c.created_at, c.is_test,
COUNT(CASE WHEN l.is_test = FALSE THEN 1 END) as total_licenses,
COUNT(CASE WHEN l.is_active = TRUE AND l.valid_until >= CURRENT_DATE AND l.is_test = FALSE THEN 1 END) as active_licenses,
COUNT(CASE WHEN l.valid_until < CURRENT_DATE AND l.is_test = FALSE THEN 1 END) as expired_licenses
FROM customers c
LEFT JOIN licenses l ON c.id = l.customer_id
WHERE c.is_test = FALSE
GROUP BY c.id, c.name, c.email, c.created_at, c.is_test
ORDER BY c.id
"""
cur.execute(query)
# Spaltennamen
columns = ['ID', 'Name', 'E-Mail', 'Erstellt am',
columns = ['ID', 'Name', 'E-Mail', 'Erstellt am', 'Testdaten',
'Lizenzen gesamt', 'Aktive Lizenzen', 'Abgelaufene Lizenzen']
# Daten in DataFrame
@@ -2986,6 +3018,9 @@ def export_customers():
# Datumsformatierung
df['Erstellt am'] = pd.to_datetime(df['Erstellt am']).dt.strftime('%d.%m.%Y %H:%M')
# Testdaten formatting
df['Testdaten'] = df['Testdaten'].replace({True: 'Ja', False: 'Nein'})
cur.close()
conn.close()

4
v2_lizenzserver/.env Normale Datei
Datei anzeigen

@@ -0,0 +1,4 @@
SECRET_KEY=your-super-secret-key-change-this-in-production-12345
DATABASE_URL=postgresql://adminuser:supergeheimespasswort@db:5432/meinedatenbank
REDIS_URL=redis://redis:6379
DEBUG=False

Datei anzeigen

@@ -1,18 +1,21 @@
FROM python:3.11-slim
# Zeitzone setzen
ENV TZ=Europe/Berlin
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y tzdata \
&& ln -sf /usr/share/zoneinfo/Europe/Berlin /etc/localtime \
&& echo "Europe/Berlin" > /etc/timezone \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Placeholder für Lizenzserver
RUN echo "Lizenzserver noch nicht implementiert" > info.txt
RUN apt-get update && apt-get install -y \
gcc \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*
CMD ["python", "-c", "print('Lizenzserver Container läuft, aber noch keine Implementierung vorhanden'); import time; time.sleep(86400)"]
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app app/
COPY init_db.py .
COPY .env* ./
ENV PYTHONPATH=/app
EXPOSE 8443
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8443"]

Datei anzeigen

@@ -0,0 +1,335 @@
# Lizenzserver Nutzungsanleitung
## Übersicht
Der Lizenzserver läuft unter: `https://api-software-undso.z5m7q9dk3ah2v1plx6ju.com`
## API Endpoints
### 1. Lizenz aktivieren
**POST** `/api/license/activate`
Aktiviert eine Lizenz für eine bestimmte Maschine.
```json
{
"license_key": "XXXX-XXXX-XXXX-XXXX",
"machine_id": "UNIQUE-MACHINE-ID",
"hardware_hash": "SHA256-HARDWARE-FINGERPRINT",
"os_info": {
"os": "Windows",
"version": "10.0.19041"
},
"app_version": "1.0.0"
}
```
**Antwort bei Erfolg:**
```json
{
"success": true,
"message": "License activated successfully",
"activation_id": 123,
"expires_at": "2025-12-31T23:59:59",
"features": {
"all_features": true
}
}
```
### 2. Lizenz verifizieren (Heartbeat)
**POST** `/api/license/verify`
Sollte alle 15 Minuten aufgerufen werden.
```json
{
"license_key": "XXXX-XXXX-XXXX-XXXX",
"machine_id": "UNIQUE-MACHINE-ID",
"hardware_hash": "SHA256-HARDWARE-FINGERPRINT",
"activation_id": 123
}
```
**Antwort:**
```json
{
"valid": true,
"message": "License is valid",
"expires_at": "2025-12-31T23:59:59",
"features": {
"all_features": true
},
"requires_update": false,
"update_url": null
}
```
### 3. Version prüfen
**POST** `/api/version/check`
```json
{
"current_version": "1.0.0",
"license_key": "XXXX-XXXX-XXXX-XXXX"
}
```
**Antwort:**
```json
{
"latest_version": "1.1.0",
"current_version": "1.0.0",
"update_available": true,
"is_mandatory": false,
"download_url": "https://download.example.com/v1.1.0",
"release_notes": "Bug fixes and improvements"
}
```
### 4. Lizenzinfo abrufen
**GET** `/api/license/info/{license_key}`
Liefert detaillierte Informationen über eine Lizenz.
## Authentifizierung
Alle API-Anfragen benötigen einen API-Key im Authorization Header:
```
Authorization: Bearer YOUR-API-KEY-HERE
```
## Client-Integration Beispiel (Python)
```python
import requests
import hashlib
import platform
import uuid
import json
from datetime import datetime, timedelta
class LicenseClient:
def __init__(self, api_key, server_url="https://api-software-undso.z5m7q9dk3ah2v1plx6ju.com"):
self.api_key = api_key
self.server_url = server_url
self.headers = {"Authorization": f"Bearer {api_key}"}
def get_machine_id(self):
"""Eindeutige Maschinen-ID generieren"""
return str(uuid.getnode()) # MAC-Adresse
def get_hardware_hash(self):
"""Hardware-Fingerprint erstellen"""
machine_id = self.get_machine_id()
cpu_info = platform.processor()
system_info = f"{platform.system()}-{platform.machine()}"
combined = f"{machine_id}-{cpu_info}-{system_info}"
return hashlib.sha256(combined.encode()).hexdigest()
def activate_license(self, license_key):
"""Lizenz aktivieren"""
data = {
"license_key": license_key,
"machine_id": self.get_machine_id(),
"hardware_hash": self.get_hardware_hash(),
"os_info": {
"os": platform.system(),
"version": platform.version(),
"machine": platform.machine()
},
"app_version": "1.0.0"
}
response = requests.post(
f"{self.server_url}/api/license/activate",
headers=self.headers,
json=data,
verify=True # SSL-Zertifikat prüfen
)
return response.json()
def verify_license(self, license_key, activation_id):
"""Lizenz verifizieren (Heartbeat)"""
data = {
"license_key": license_key,
"machine_id": self.get_machine_id(),
"hardware_hash": self.get_hardware_hash(),
"activation_id": activation_id
}
response = requests.post(
f"{self.server_url}/api/license/verify",
headers=self.headers,
json=data,
verify=True
)
return response.json()
def check_version(self, license_key, current_version):
"""Version prüfen"""
data = {
"current_version": current_version,
"license_key": license_key
}
response = requests.post(
f"{self.server_url}/api/version/check",
headers=self.headers,
json=data,
verify=True
)
return response.json()
# Verwendungsbeispiel
if __name__ == "__main__":
client = LicenseClient("YOUR-API-KEY")
# Lizenz aktivieren
result = client.activate_license("USER-LICENSE-KEY")
if result["success"]:
activation_id = result["activation_id"]
print(f"Lizenz aktiviert! Activation ID: {activation_id}")
# Lizenz verifizieren
verify_result = client.verify_license("USER-LICENSE-KEY", activation_id)
if verify_result["valid"]:
print("Lizenz ist gültig!")
```
## Client-Integration Beispiel (C#)
```csharp
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Security.Cryptography;
using System.Management;
public class LicenseClient
{
private readonly HttpClient httpClient;
private readonly string apiKey;
private readonly string serverUrl;
public LicenseClient(string apiKey, string serverUrl = "https://api-software-undso.z5m7q9dk3ah2v1plx6ju.com")
{
this.apiKey = apiKey;
this.serverUrl = serverUrl;
httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
}
private string GetMachineId()
{
// CPU ID abrufen
string cpuId = "";
ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT ProcessorId FROM Win32_Processor");
foreach (ManagementObject obj in searcher.Get())
{
cpuId = obj["ProcessorId"].ToString();
break;
}
return cpuId;
}
private string GetHardwareHash()
{
string machineId = GetMachineId();
string systemInfo = Environment.MachineName + Environment.OSVersion;
using (SHA256 sha256 = SHA256.Create())
{
byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(machineId + systemInfo));
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
}
}
public async Task<LicenseActivationResponse> ActivateLicenseAsync(string licenseKey)
{
var requestData = new
{
license_key = licenseKey,
machine_id = GetMachineId(),
hardware_hash = GetHardwareHash(),
os_info = new
{
os = "Windows",
version = Environment.OSVersion.Version.ToString()
},
app_version = "1.0.0"
};
var json = JsonSerializer.Serialize(requestData);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync($"{serverUrl}/api/license/activate", content);
var responseContent = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<LicenseActivationResponse>(responseContent);
}
}
public class LicenseActivationResponse
{
public bool success { get; set; }
public string message { get; set; }
public int? activation_id { get; set; }
public DateTime? expires_at { get; set; }
}
```
## Wichtige Hinweise
### Sicherheit
- API-Keys niemals im Quellcode hartcodieren
- SSL-Zertifikat immer prüfen (verify=True)
- Hardware-Hash lokal zwischenspeichern
### Rate Limiting
- Max. 100 Anfragen pro Minute pro API-Key
- Heartbeat alle 15 Minuten (nicht öfter)
### Offline-Betrieb
- 7 Tage Grace Period bei Verbindungsproblemen
- Letzte gültige Lizenz lokal cachen
- Bei Hardware-Änderung: Grace Period nutzen
### Fehlerbehandlung
```python
try:
result = client.verify_license(license_key, activation_id)
except requests.exceptions.ConnectionError:
# Offline-Modus: Gecachte Lizenz prüfen
if cached_license_valid():
continue_execution()
else:
show_error("Keine Internetverbindung und Lizenz abgelaufen")
except requests.exceptions.HTTPError as e:
if e.response.status_code == 401:
show_error("Ungültiger API-Key")
elif e.response.status_code == 403:
show_error("Lizenz ungültig oder abgelaufen")
```
## Test-Zugangsdaten
Für Entwicklungszwecke:
- Test API-Key: `test-api-key-12345`
- Test Lizenz: `TEST-LICENSE-KEY-12345`
**WICHTIG:** Für Produktion eigene API-Keys und Lizenzen erstellen!
## Support
Bei Problemen:
1. Logs prüfen: `docker logs license-server`
2. API-Status: https://api-software-undso.z5m7q9dk3ah2v1plx6ju.com/health
3. Datenbank prüfen: Lizenzen und Aktivierungen

29
v2_lizenzserver/README.md Normale Datei
Datei anzeigen

@@ -0,0 +1,29 @@
# License Server für v2-Docker
Dieser License Server ersetzt den Dummy-Container und bietet vollständige Lizenzmanagement-Funktionalität.
## Features
- Lizenzaktivierung mit Hardware-Binding
- Heartbeat/Verifizierung alle 15 Minuten
- Versionskontrolle mit Update-Erzwingung
- Offline Grace Period (7 Tage)
- Multi-Aktivierung Support
## API Endpoints
- POST `/api/license/activate` - Lizenz aktivieren
- POST `/api/license/verify` - Lizenz verifizieren (Heartbeat)
- POST `/api/version/check` - Version prüfen
- GET `/api/license/info/{license_key}` - Lizenzinfo abrufen
## Deployment
Der Server läuft im v2-Docker Stack und ist über nginx erreichbar:
- Extern: https://api-software-undso.z5m7q9dk3ah2v1plx6ju.com
- Intern: http://license-server:8443
## Konfiguration
Die Konfiguration erfolgt über die `.env` Datei:
- `DATABASE_URL` - Verbindung zur PostgreSQL Datenbank
- `SECRET_KEY` - JWT Secret Key für Token-Signierung
## Integration
Siehe `LIZENZSERVER_ANLEITUNG.md` und die Beispiele in `client_examples/` für die Client-Integration.

90
v2_lizenzserver/TODO.md Normale Datei
Datei anzeigen

@@ -0,0 +1,90 @@
# TODO - License Server & Admin Panel Erweiterungen
## 🎯 Phase 1: Admin Panel Integration (Priorität: Hoch)
### Neue Lizenz-Verwaltung im Admin Panel
- [ ] Neuer Menüpunkt "Lizenzen" im Admin Panel
- [ ] Dashboard mit Lizenz-Statistiken
- [ ] Anzahl aktive/abgelaufene Lizenzen
- [ ] Aktivierungen pro Lizenz
- [ ] Grafische Auswertungen
- [ ] Lizenz-Erstellung Formular
- [ ] Lizenzschlüssel generieren
- [ ] Ablaufdatum festlegen
- [ ] Max. Aktivierungen definieren
- [ ] Kundeninformationen speichern
- [ ] Lizenz-Übersicht Tabelle
- [ ] Suche/Filter-Funktionen
- [ ] Status anzeigen (aktiv/abgelaufen/gesperrt)
- [ ] Aktivierungen einsehen
- [ ] Lizenzen deaktivieren/reaktivieren
- [ ] API-Key Verwaltung
- [ ] Neue API-Keys generieren
- [ ] Bestehende Keys anzeigen/deaktivieren
- [ ] Berechtigungen festlegen
## 🔒 Phase 2: Sicherheits-Optimierungen (Priorität: Mittel)
### Rate Limiting in Nginx
- [ ] API Rate Limiting implementieren (10 Requests/Minute)
- [ ] Burst-Handling konfigurieren
- [ ] IP-basierte Limits für API-Endpoints
### Datenbank-Sicherheit
- [ ] Separaten Read-Only DB-User für API erstellen
- [ ] Admin-User mit eingeschränkten Rechten
- [ ] Connection Pooling optimieren
### GeoIP-Blocking
- [ ] GeoIP-Modul in Nginx installieren
- [ ] Länder-Blacklist konfigurieren
- [ ] VPN-Erkennung implementieren
- [ ] Logging verdächtiger Zugriffe
## 📊 Phase 3: Monitoring & Backup (Priorität: Mittel)
### Monitoring-System
- [ ] Prometheus einrichten
- [ ] Grafana Dashboards erstellen
- [ ] API-Zugriffe
- [ ] Lizenz-Aktivierungen
- [ ] System-Ressourcen
- [ ] Alerting bei Anomalien
- [ ] Zu viele fehlgeschlagene Aktivierungen
- [ ] Verdächtige Zugriffsmuster
- [ ] System-Ausfälle
### Backup-Strategie
- [ ] Automatische tägliche DB-Backups
- [ ] Backup-Rotation (30 Tage aufbewahren)
- [ ] Verschlüsselung der Backups
- [ ] Offsite-Backup zu Cloud-Storage
- [ ] Restore-Tests durchführen
## 🚀 Phase 4: Performance & Features (Priorität: Niedrig)
### Performance-Optimierungen
- [ ] Redis Cache für häufige Queries
- [ ] Database Indexing optimieren
- [ ] API Response Caching
### Erweiterte Features
- [ ] Export-Funktionen (CSV/Excel)
- [ ] Email-Benachrichtigungen
- [ ] Bei Lizenz-Ablauf
- [ ] Bei verdächtigen Aktivitäten
- [ ] Lizenz-Templates für häufige Konfigurationen
- [ ] Bulk-Operationen (mehrere Lizenzen gleichzeitig)
## 📝 Dokumentation
- [ ] API-Dokumentation erweitern
- [ ] Admin Panel Benutzerhandbuch
- [ ] Deployment-Guide aktualisieren
- [ ] Troubleshooting-Guide erstellen
## Prioritäten:
1. **Sofort**: Phase 1 - Admin Panel Integration
2. **Nächste Woche**: Phase 2 - Sicherheits-Optimierungen
3. **Nächster Monat**: Phase 3 - Monitoring & Backup
4. **Bei Bedarf**: Phase 4 - Performance & Features

Datei anzeigen

@@ -0,0 +1 @@
from . import license, version

Datei anzeigen

@@ -0,0 +1,209 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from datetime import datetime, timedelta
from typing import Dict, Any
from app.db.database import get_db
from app.models.models import License, Activation, Version
from app.schemas.license import (
LicenseActivationRequest,
LicenseActivationResponse,
LicenseVerificationRequest,
LicenseVerificationResponse
)
from app.core.security import get_api_key
from app.core.config import settings
router = APIRouter()
@router.post("/activate", response_model=LicenseActivationResponse)
async def activate_license(
request: LicenseActivationRequest,
db: Session = Depends(get_db),
api_key = Depends(get_api_key)
):
license = db.query(License).filter(
License.license_key == request.license_key,
License.is_active == True
).first()
if not license:
return LicenseActivationResponse(
success=False,
message="Invalid license key"
)
if license.expires_at and license.expires_at < datetime.utcnow():
return LicenseActivationResponse(
success=False,
message="License has expired"
)
existing_activations = db.query(Activation).filter(
Activation.license_id == license.id,
Activation.is_active == True
).all()
existing_machine = next(
(a for a in existing_activations if a.machine_id == request.machine_id),
None
)
if existing_machine:
if existing_machine.hardware_hash != request.hardware_hash:
return LicenseActivationResponse(
success=False,
message="Hardware mismatch for this machine"
)
existing_machine.last_heartbeat = datetime.utcnow()
existing_machine.app_version = request.app_version
existing_machine.os_info = request.os_info
db.commit()
return LicenseActivationResponse(
success=True,
message="License reactivated successfully",
activation_id=existing_machine.id,
expires_at=license.expires_at,
features={"all_features": True}
)
if len(existing_activations) >= license.max_activations:
return LicenseActivationResponse(
success=False,
message=f"Maximum activations ({license.max_activations}) reached"
)
new_activation = Activation(
license_id=license.id,
machine_id=request.machine_id,
hardware_hash=request.hardware_hash,
os_info=request.os_info,
app_version=request.app_version
)
db.add(new_activation)
db.commit()
db.refresh(new_activation)
return LicenseActivationResponse(
success=True,
message="License activated successfully",
activation_id=new_activation.id,
expires_at=license.expires_at,
features={"all_features": True}
)
@router.post("/verify", response_model=LicenseVerificationResponse)
async def verify_license(
request: LicenseVerificationRequest,
db: Session = Depends(get_db),
api_key = Depends(get_api_key)
):
activation = db.query(Activation).filter(
Activation.id == request.activation_id,
Activation.machine_id == request.machine_id,
Activation.is_active == True
).first()
if not activation:
return LicenseVerificationResponse(
valid=False,
message="Invalid activation"
)
license = activation.license
if not license.is_active:
return LicenseVerificationResponse(
valid=False,
message="License is no longer active"
)
if license.license_key != request.license_key:
return LicenseVerificationResponse(
valid=False,
message="License key mismatch"
)
if activation.hardware_hash != request.hardware_hash:
grace_period = datetime.utcnow() - timedelta(days=settings.OFFLINE_GRACE_PERIOD_DAYS)
if activation.last_heartbeat < grace_period:
return LicenseVerificationResponse(
valid=False,
message="Hardware mismatch and grace period expired"
)
else:
return LicenseVerificationResponse(
valid=True,
message="Hardware mismatch but within grace period",
expires_at=license.expires_at,
features={"all_features": True}
)
if license.expires_at and license.expires_at < datetime.utcnow():
return LicenseVerificationResponse(
valid=False,
message="License has expired"
)
activation.last_heartbeat = datetime.utcnow()
db.commit()
latest_version = db.query(Version).order_by(Version.release_date.desc()).first()
requires_update = False
update_url = None
if latest_version and activation.app_version:
if latest_version.version_number > activation.app_version:
requires_update = True
update_url = latest_version.download_url
return LicenseVerificationResponse(
valid=True,
message="License is valid",
expires_at=license.expires_at,
features={"all_features": True},
requires_update=requires_update,
update_url=update_url
)
@router.get("/info/{license_key}")
async def get_license_info(
license_key: str,
db: Session = Depends(get_db),
api_key = Depends(get_api_key)
):
license = db.query(License).filter(
License.license_key == license_key
).first()
if not license:
raise HTTPException(status_code=404, detail="License not found")
activations = db.query(Activation).filter(
Activation.license_id == license.id,
Activation.is_active == True
).all()
return {
"license_key": license.license_key,
"product_id": license.product_id,
"customer_email": license.customer_email,
"customer_name": license.customer_name,
"is_active": license.is_active,
"expires_at": license.expires_at,
"max_activations": license.max_activations,
"current_activations": len(activations),
"activations": [
{
"id": a.id,
"machine_id": a.machine_id,
"activation_date": a.activation_date,
"last_heartbeat": a.last_heartbeat,
"app_version": a.app_version
}
for a in activations
]
}

Datei anzeigen

@@ -0,0 +1,84 @@
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from packaging import version
from app.db.database import get_db
from app.models.models import Version, License
from app.schemas.license import VersionCheckRequest, VersionCheckResponse
from app.core.security import get_api_key
router = APIRouter()
@router.post("/check", response_model=VersionCheckResponse)
async def check_version(
request: VersionCheckRequest,
db: Session = Depends(get_db),
api_key = Depends(get_api_key)
):
license = db.query(License).filter(
License.license_key == request.license_key,
License.is_active == True
).first()
if not license:
return VersionCheckResponse(
latest_version=request.current_version,
current_version=request.current_version,
update_available=False,
is_mandatory=False
)
latest_version = db.query(Version).order_by(Version.release_date.desc()).first()
if not latest_version:
return VersionCheckResponse(
latest_version=request.current_version,
current_version=request.current_version,
update_available=False,
is_mandatory=False
)
current_ver = version.parse(request.current_version)
latest_ver = version.parse(latest_version.version_number)
update_available = latest_ver > current_ver
is_mandatory = False
if update_available and latest_version.is_mandatory:
if latest_version.min_version:
min_ver = version.parse(latest_version.min_version)
is_mandatory = current_ver < min_ver
else:
is_mandatory = True
return VersionCheckResponse(
latest_version=latest_version.version_number,
current_version=request.current_version,
update_available=update_available,
is_mandatory=is_mandatory,
download_url=latest_version.download_url if update_available else None,
release_notes=latest_version.release_notes if update_available else None
)
@router.get("/latest")
async def get_latest_version(
db: Session = Depends(get_db),
api_key = Depends(get_api_key)
):
latest_version = db.query(Version).order_by(Version.release_date.desc()).first()
if not latest_version:
return {
"version": "1.0.0",
"release_date": None,
"release_notes": "Initial release"
}
return {
"version": latest_version.version_number,
"release_date": latest_version.release_date,
"is_mandatory": latest_version.is_mandatory,
"min_version": latest_version.min_version,
"download_url": latest_version.download_url,
"release_notes": latest_version.release_notes
}

Datei anzeigen

@@ -0,0 +1,32 @@
from pydantic_settings import BaseSettings
from typing import List
class Settings(BaseSettings):
PROJECT_NAME: str = "License Server"
VERSION: str = "1.0.0"
API_PREFIX: str = "/api"
SECRET_KEY: str = "your-secret-key-change-this-in-production"
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
DATABASE_URL: str = "postgresql://license_user:license_password@db:5432/license_db"
REDIS_URL: str = "redis://redis:6379"
ALLOWED_ORIGINS: List[str] = [
"https://api-software-undso.z5m7q9dk3ah2v1plx6ju.com",
"https://admin-panel-undso.z5m7q9dk3ah2v1plx6ju.com"
]
DEBUG: bool = False
MAX_ACTIVATIONS_PER_LICENSE: int = 5
HEARTBEAT_INTERVAL_MINUTES: int = 15
OFFLINE_GRACE_PERIOD_DAYS: int = 7
class Config:
env_file = ".env"
case_sensitive = True
settings = Settings()

Datei anzeigen

@@ -0,0 +1,52 @@
from datetime import datetime, timedelta
from typing import Optional
from jose import JWTError, jwt
from passlib.context import CryptContext
from fastapi import HTTPException, Security, Depends
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.orm import Session
from app.db.database import get_db
from app.models.models import ApiKey
from app.core.config import settings
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
security = HTTPBearer()
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
return encoded_jwt
def verify_token(credentials: HTTPAuthorizationCredentials = Security(security)):
token = credentials.credentials
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
return payload
except JWTError:
raise HTTPException(status_code=403, detail="Invalid token")
def verify_api_key(api_key: str, db: Session):
key = db.query(ApiKey).filter(
ApiKey.key == api_key,
ApiKey.is_active == True
).first()
if not key:
raise HTTPException(status_code=401, detail="Invalid API key")
key.last_used = datetime.utcnow()
db.commit()
return key
def get_api_key(
credentials: HTTPAuthorizationCredentials = Security(security),
db: Session = Depends(get_db)
):
api_key = credentials.credentials
return verify_api_key(api_key, db)

Datei anzeigen

@@ -0,0 +1,16 @@
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from app.core.config import settings
engine = create_engine(settings.DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

65
v2_lizenzserver/app/main.py Normale Datei
Datei anzeigen

@@ -0,0 +1,65 @@
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import uvicorn
import logging
from datetime import datetime
from app.api import license, version
from app.core.config import settings
from app.db.database import engine, Base
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
Base.metadata.create_all(bind=engine)
app = FastAPI(
title="License Server API",
description="API for software license management",
version="1.0.0",
docs_url="/docs" if settings.DEBUG else None,
redoc_url="/redoc" if settings.DEBUG else None,
)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
logger.error(f"Global exception: {str(exc)}", exc_info=True)
return JSONResponse(
status_code=500,
content={"detail": "Internal server error"}
)
@app.get("/")
async def root():
return {
"status": "online",
"service": "License Server",
"timestamp": datetime.utcnow().isoformat()
}
@app.get("/health")
async def health_check():
return {
"status": "healthy",
"timestamp": datetime.utcnow().isoformat()
}
app.include_router(license.router, prefix="/api/license", tags=["license"])
app.include_router(version.router, prefix="/api/version", tags=["version"])
if __name__ == "__main__":
uvicorn.run(
"app.main:app",
host="0.0.0.0",
port=8443,
reload=settings.DEBUG
)

Datei anzeigen

@@ -0,0 +1 @@
from .models import License, Activation, Version, ApiKey

Datei anzeigen

@@ -0,0 +1,65 @@
from sqlalchemy import Column, String, Integer, Boolean, DateTime, ForeignKey, Text, JSON
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.db.database import Base
import uuid
class License(Base):
__tablename__ = "licenses"
id = Column(Integer, primary_key=True, index=True)
license_key = Column(String, unique=True, index=True, default=lambda: str(uuid.uuid4()))
product_id = Column(String, nullable=False)
customer_email = Column(String, nullable=False)
customer_name = Column(String)
max_activations = Column(Integer, default=1)
is_active = Column(Boolean, default=True)
expires_at = Column(DateTime, nullable=True)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, onupdate=func.now())
activations = relationship("Activation", back_populates="license")
class Activation(Base):
__tablename__ = "activations"
id = Column(Integer, primary_key=True, index=True)
license_id = Column(Integer, ForeignKey("licenses.id"))
machine_id = Column(String, nullable=False)
hardware_hash = Column(String, nullable=False)
activation_date = Column(DateTime, server_default=func.now())
last_heartbeat = Column(DateTime, server_default=func.now())
is_active = Column(Boolean, default=True)
os_info = Column(JSON)
app_version = Column(String)
license = relationship("License", back_populates="activations")
class Version(Base):
__tablename__ = "versions"
id = Column(Integer, primary_key=True, index=True)
version_number = Column(String, unique=True, nullable=False)
release_date = Column(DateTime, server_default=func.now())
is_mandatory = Column(Boolean, default=False)
min_version = Column(String)
download_url = Column(String)
release_notes = Column(Text)
created_at = Column(DateTime, server_default=func.now())
class ApiKey(Base):
__tablename__ = "api_keys"
id = Column(Integer, primary_key=True, index=True)
key = Column(String, unique=True, index=True, default=lambda: str(uuid.uuid4()))
name = Column(String, nullable=False)
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, server_default=func.now())
last_used = Column(DateTime)

Datei anzeigen

@@ -0,0 +1,8 @@
from .license import (
LicenseActivationRequest,
LicenseActivationResponse,
LicenseVerificationRequest,
LicenseVerificationResponse,
VersionCheckRequest,
VersionCheckResponse
)

Datei anzeigen

@@ -0,0 +1,43 @@
from pydantic import BaseModel, EmailStr
from datetime import datetime
from typing import Optional, Dict, Any
class LicenseActivationRequest(BaseModel):
license_key: str
machine_id: str
hardware_hash: str
os_info: Optional[Dict[str, Any]] = None
app_version: Optional[str] = None
class LicenseActivationResponse(BaseModel):
success: bool
message: str
activation_id: Optional[int] = None
expires_at: Optional[datetime] = None
features: Optional[Dict[str, Any]] = None
class LicenseVerificationRequest(BaseModel):
license_key: str
machine_id: str
hardware_hash: str
activation_id: int
class LicenseVerificationResponse(BaseModel):
valid: bool
message: str
expires_at: Optional[datetime] = None
features: Optional[Dict[str, Any]] = None
requires_update: bool = False
update_url: Optional[str] = None
class VersionCheckRequest(BaseModel):
current_version: str
license_key: str
class VersionCheckResponse(BaseModel):
latest_version: str
current_version: str
update_available: bool
is_mandatory: bool
download_url: Optional[str] = None
release_notes: Optional[str] = None

Datei anzeigen

@@ -0,0 +1,534 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Management;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace LicenseClient
{
/// <summary>
/// Vollständige Lizenzserver-Integration für .NET-Anwendungen
/// </summary>
public class LicenseManager : IDisposable
{
private readonly HttpClient httpClient;
private readonly string apiKey;
private readonly string appVersion;
private readonly string serverUrl = "https://api-software-undso.z5m7q9dk3ah2v1plx6ju.com";
private readonly string cacheFilePath;
private string licenseKey;
private int? activationId;
private DateTime? expiresAt;
private bool isValid;
private Timer heartbeatTimer;
private readonly SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
public LicenseManager(string apiKey, string appVersion = "1.0.0")
{
this.apiKey = apiKey;
this.appVersion = appVersion;
// HttpClient Setup
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true // Für Entwicklung
};
httpClient = new HttpClient(handler);
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
httpClient.Timeout = TimeSpan.FromSeconds(30);
// Cache-Verzeichnis
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
string appFolder = Path.Combine(appDataPath, "MyApp", "License");
Directory.CreateDirectory(appFolder);
cacheFilePath = Path.Combine(appFolder, "license.json");
}
/// <summary>
/// Eindeutige Maschinen-ID generieren
/// </summary>
private string GetMachineId()
{
try
{
// CPU-ID abrufen
string cpuId = "";
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT ProcessorId FROM Win32_Processor"))
{
foreach (ManagementObject obj in searcher.Get())
{
cpuId = obj["ProcessorId"]?.ToString() ?? "";
break;
}
}
// Motherboard Serial Number
string motherboardId = "";
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT SerialNumber FROM Win32_BaseBoard"))
{
foreach (ManagementObject obj in searcher.Get())
{
motherboardId = obj["SerialNumber"]?.ToString() ?? "";
break;
}
}
return $"{cpuId}-{motherboardId}";
}
catch
{
// Fallback: Machine Name + User
return $"{Environment.MachineName}-{Environment.UserName}";
}
}
/// <summary>
/// Hardware-Fingerprint erstellen
/// </summary>
private string GetHardwareHash()
{
var components = new List<string>
{
GetMachineId(),
Environment.MachineName,
Environment.OSVersion.ToString(),
Environment.ProcessorCount.ToString()
};
// MAC-Adressen hinzufügen
try
{
using (var searcher = new ManagementObjectSearcher("SELECT MACAddress FROM Win32_NetworkAdapter WHERE MACAddress IS NOT NULL"))
{
foreach (ManagementObject obj in searcher.Get())
{
string mac = obj["MACAddress"]?.ToString();
if (!string.IsNullOrEmpty(mac))
components.Add(mac);
}
}
}
catch { }
string combined = string.Join("-", components);
using (SHA256 sha256 = SHA256.Create())
{
byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(combined));
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
}
}
/// <summary>
/// Lizenz-Cache speichern
/// </summary>
private async Task SaveLicenseCacheAsync()
{
var cacheData = new
{
license_key = licenseKey,
activation_id = activationId,
expires_at = expiresAt?.ToString("O"),
hardware_hash = GetHardwareHash(),
last_verified = DateTime.UtcNow.ToString("O")
};
string json = JsonSerializer.Serialize(cacheData, new JsonSerializerOptions { WriteIndented = true });
await File.WriteAllTextAsync(cacheFilePath, json);
}
/// <summary>
/// Lizenz-Cache laden
/// </summary>
private async Task<LicenseCache> LoadLicenseCacheAsync()
{
if (!File.Exists(cacheFilePath))
return null;
try
{
string json = await File.ReadAllTextAsync(cacheFilePath);
return JsonSerializer.Deserialize<LicenseCache>(json);
}
catch
{
return null;
}
}
/// <summary>
/// Lizenz aktivieren
/// </summary>
public async Task<(bool Success, string Message)> ActivateLicenseAsync(string licenseKey)
{
await semaphore.WaitAsync();
try
{
var requestData = new
{
license_key = licenseKey,
machine_id = GetMachineId(),
hardware_hash = GetHardwareHash(),
os_info = new
{
os = "Windows",
version = Environment.OSVersion.Version.ToString(),
platform = Environment.OSVersion.Platform.ToString(),
service_pack = Environment.OSVersion.ServicePack
},
app_version = appVersion
};
var json = JsonSerializer.Serialize(requestData);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync($"{serverUrl}/api/license/activate", content);
var responseContent = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
var result = JsonSerializer.Deserialize<ActivationResponse>(responseContent);
if (result.success)
{
this.licenseKey = licenseKey;
this.activationId = result.activation_id;
this.isValid = true;
if (!string.IsNullOrEmpty(result.expires_at))
this.expiresAt = DateTime.Parse(result.expires_at);
await SaveLicenseCacheAsync();
StartHeartbeat();
return (true, result.message ?? "Lizenz erfolgreich aktiviert");
}
else
{
return (false, result.message ?? "Aktivierung fehlgeschlagen");
}
}
else
{
return (false, $"Server-Fehler: {response.StatusCode}");
}
}
catch (HttpRequestException ex)
{
return (false, $"Verbindungsfehler: {ex.Message}");
}
catch (Exception ex)
{
return (false, $"Fehler: {ex.Message}");
}
finally
{
semaphore.Release();
}
}
/// <summary>
/// Lizenz verifizieren (Heartbeat)
/// </summary>
public async Task<(bool Valid, string Message)> VerifyLicenseAsync()
{
if (string.IsNullOrEmpty(licenseKey) || !activationId.HasValue)
return (false, "Keine aktive Lizenz");
await semaphore.WaitAsync();
try
{
var requestData = new
{
license_key = licenseKey,
machine_id = GetMachineId(),
hardware_hash = GetHardwareHash(),
activation_id = activationId.Value
};
var json = JsonSerializer.Serialize(requestData);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync($"{serverUrl}/api/license/verify", content);
var responseContent = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
var result = JsonSerializer.Deserialize<VerificationResponse>(responseContent);
isValid = result.valid;
if (isValid)
{
await SaveLicenseCacheAsync();
if (result.requires_update)
{
OnUpdateAvailable?.Invoke(result.update_url);
}
}
return (isValid, result.message ?? "");
}
else
{
return (false, $"Server-Fehler: {response.StatusCode}");
}
}
catch (HttpRequestException)
{
// Offline-Verifizierung
return await VerifyOfflineAsync();
}
catch (Exception ex)
{
return (false, $"Fehler: {ex.Message}");
}
finally
{
semaphore.Release();
}
}
/// <summary>
/// Offline-Verifizierung mit Grace Period
/// </summary>
private async Task<(bool Valid, string Message)> VerifyOfflineAsync()
{
var cache = await LoadLicenseCacheAsync();
if (cache == null)
return (false, "Keine gecachte Lizenz vorhanden");
// Hardware-Hash prüfen
if (cache.hardware_hash != GetHardwareHash())
{
// Grace Period bei Hardware-Änderung
var lastVerified = DateTime.Parse(cache.last_verified);
var gracePeriod = TimeSpan.FromDays(7);
if (DateTime.UtcNow - lastVerified > gracePeriod)
return (false, "Hardware geändert - Grace Period abgelaufen");
}
// Ablaufdatum prüfen
if (!string.IsNullOrEmpty(cache.expires_at))
{
var expiresAt = DateTime.Parse(cache.expires_at);
if (DateTime.UtcNow > expiresAt)
return (false, "Lizenz abgelaufen");
}
licenseKey = cache.license_key;
activationId = cache.activation_id;
isValid = true;
return (true, "Offline-Modus (gecachte Lizenz)");
}
/// <summary>
/// Nach Updates suchen
/// </summary>
public async Task<UpdateInfo> CheckForUpdatesAsync()
{
if (string.IsNullOrEmpty(licenseKey))
return null;
try
{
var requestData = new
{
current_version = appVersion,
license_key = licenseKey
};
var json = JsonSerializer.Serialize(requestData);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync($"{serverUrl}/api/version/check", content);
if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<UpdateInfo>(responseContent);
}
}
catch { }
return null;
}
/// <summary>
/// Heartbeat starten
/// </summary>
private void StartHeartbeat()
{
heartbeatTimer?.Dispose();
// Alle 15 Minuten
heartbeatTimer = new Timer(async _ =>
{
var (valid, message) = await VerifyLicenseAsync();
if (!valid)
{
OnLicenseInvalid?.Invoke(message);
}
}, null, TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(15));
}
/// <summary>
/// Lizenz-Informationen abrufen
/// </summary>
public LicenseInfo GetLicenseInfo()
{
return new LicenseInfo
{
IsValid = isValid,
LicenseKey = string.IsNullOrEmpty(licenseKey) ? null : licenseKey.Substring(0, 4) + "****",
ExpiresAt = expiresAt,
MachineId = GetMachineId()
};
}
// Events
public event Action<string> OnUpdateAvailable;
public event Action<string> OnLicenseInvalid;
public void Dispose()
{
heartbeatTimer?.Dispose();
httpClient?.Dispose();
semaphore?.Dispose();
}
}
// Hilfsklassen
public class LicenseCache
{
public string license_key { get; set; }
public int? activation_id { get; set; }
public string expires_at { get; set; }
public string hardware_hash { get; set; }
public string last_verified { get; set; }
}
public class ActivationResponse
{
public bool success { get; set; }
public string message { get; set; }
public int? activation_id { get; set; }
public string expires_at { get; set; }
}
public class VerificationResponse
{
public bool valid { get; set; }
public string message { get; set; }
public string expires_at { get; set; }
public bool requires_update { get; set; }
public string update_url { get; set; }
}
public class UpdateInfo
{
public string latest_version { get; set; }
public string current_version { get; set; }
public bool update_available { get; set; }
public bool is_mandatory { get; set; }
public string download_url { get; set; }
public string release_notes { get; set; }
}
public class LicenseInfo
{
public bool IsValid { get; set; }
public string LicenseKey { get; set; }
public DateTime? ExpiresAt { get; set; }
public string MachineId { get; set; }
}
// Beispiel-Anwendung
class Program
{
static async Task Main(string[] args)
{
// API-Key aus Umgebungsvariable oder Konfiguration
string apiKey = Environment.GetEnvironmentVariable("LICENSE_API_KEY") ?? "your-api-key-here";
using (var licenseManager = new LicenseManager(apiKey, "1.0.0"))
{
// Event-Handler registrieren
licenseManager.OnUpdateAvailable += url => Console.WriteLine($"Update verfügbar: {url}");
licenseManager.OnLicenseInvalid += msg => Console.WriteLine($"Lizenz ungültig: {msg}");
// Lizenz prüfen/aktivieren
var cache = await licenseManager.LoadLicenseCacheAsync();
if (cache != null)
{
Console.WriteLine("Gecachte Lizenz gefunden, verifiziere...");
var (valid, message) = await licenseManager.VerifyLicenseAsync();
if (!valid)
{
Console.WriteLine($"Lizenz ungültig: {message}");
await ActivateNewLicense(licenseManager);
}
else
{
Console.WriteLine($"✓ Lizenz gültig: {message}");
}
}
else
{
await ActivateNewLicense(licenseManager);
}
// Update-Check
var updateInfo = await licenseManager.CheckForUpdatesAsync();
if (updateInfo?.update_available == true)
{
Console.WriteLine($"Update verfügbar: {updateInfo.latest_version}");
if (updateInfo.is_mandatory)
Console.WriteLine("⚠️ Dies ist ein Pflicht-Update!");
}
// Lizenz-Info anzeigen
var info = licenseManager.GetLicenseInfo();
Console.WriteLine($"\nLizenz-Status:");
Console.WriteLine($"- Gültig: {info.IsValid}");
Console.WriteLine($"- Ablauf: {info.ExpiresAt}");
Console.WriteLine($"- Maschine: {info.MachineId}");
// App läuft...
Console.WriteLine("\n✓ Anwendung gestartet");
Console.WriteLine("Drücken Sie eine Taste zum Beenden...");
Console.ReadKey();
}
}
static async Task ActivateNewLicense(LicenseManager licenseManager)
{
Console.Write("Bitte Lizenzschlüssel eingeben: ");
string licenseKey = Console.ReadLine();
var (success, message) = await licenseManager.ActivateLicenseAsync(licenseKey);
if (success)
{
Console.WriteLine($"✓ {message}");
}
else
{
Console.WriteLine($"✗ {message}");
Environment.Exit(1);
}
}
}
}

Datei anzeigen

@@ -0,0 +1,331 @@
#!/usr/bin/env python3
"""
Vollständiges Beispiel für die Integration des Lizenzservers in eine Python-Anwendung
"""
import requests
import hashlib
import platform
import uuid
import json
import os
import time
import threading
from datetime import datetime, timedelta
from pathlib import Path
class LicenseManager:
def __init__(self, api_key, app_version="1.0.0"):
self.api_key = api_key
self.app_version = app_version
self.server_url = "https://api-software-undso.z5m7q9dk3ah2v1plx6ju.com"
self.headers = {"Authorization": f"Bearer {api_key}"}
# Cache-Verzeichnis
self.cache_dir = Path.home() / ".myapp" / "license"
self.cache_dir.mkdir(parents=True, exist_ok=True)
self.cache_file = self.cache_dir / "license.json"
# Lizenz-Status
self.license_key = None
self.activation_id = None
self.is_valid = False
self.expires_at = None
# Heartbeat Thread
self.heartbeat_thread = None
self.stop_heartbeat = False
def get_machine_id(self):
"""Eindeutige Maschinen-ID basierend auf MAC-Adresse"""
mac = uuid.getnode()
return f"MAC-{mac:012X}"
def get_hardware_hash(self):
"""Hardware-Fingerprint aus verschiedenen Systeminfos"""
components = [
self.get_machine_id(),
platform.processor(),
platform.system(),
platform.machine(),
platform.node()
]
combined = "-".join(components)
return hashlib.sha256(combined.encode()).hexdigest()
def save_license_cache(self):
"""Lizenzinfo lokal speichern für Offline-Betrieb"""
cache_data = {
"license_key": self.license_key,
"activation_id": self.activation_id,
"expires_at": self.expires_at.isoformat() if self.expires_at else None,
"hardware_hash": self.get_hardware_hash(),
"last_verified": datetime.now().isoformat()
}
with open(self.cache_file, 'w') as f:
json.dump(cache_data, f)
def load_license_cache(self):
"""Gespeicherte Lizenz laden"""
if not self.cache_file.exists():
return None
try:
with open(self.cache_file, 'r') as f:
return json.load(f)
except:
return None
def activate_license(self, license_key):
"""Neue Lizenz aktivieren"""
data = {
"license_key": license_key,
"machine_id": self.get_machine_id(),
"hardware_hash": self.get_hardware_hash(),
"os_info": {
"os": platform.system(),
"version": platform.version(),
"release": platform.release(),
"machine": platform.machine()
},
"app_version": self.app_version
}
try:
response = requests.post(
f"{self.server_url}/api/license/activate",
headers=self.headers,
json=data,
timeout=10,
verify=True
)
if response.status_code == 200:
result = response.json()
if result.get("success"):
self.license_key = license_key
self.activation_id = result.get("activation_id")
self.is_valid = True
if result.get("expires_at"):
self.expires_at = datetime.fromisoformat(
result["expires_at"].replace("Z", "+00:00")
)
self.save_license_cache()
self.start_heartbeat()
return True, result.get("message", "Lizenz aktiviert")
else:
return False, result.get("message", "Aktivierung fehlgeschlagen")
else:
return False, f"Server-Fehler: {response.status_code}"
except requests.exceptions.RequestException as e:
return False, f"Verbindungsfehler: {str(e)}"
def verify_license(self):
"""Lizenz verifizieren (Heartbeat)"""
if not self.license_key or not self.activation_id:
return False, "Keine aktive Lizenz"
data = {
"license_key": self.license_key,
"machine_id": self.get_machine_id(),
"hardware_hash": self.get_hardware_hash(),
"activation_id": self.activation_id
}
try:
response = requests.post(
f"{self.server_url}/api/license/verify",
headers=self.headers,
json=data,
timeout=10,
verify=True
)
if response.status_code == 200:
result = response.json()
self.is_valid = result.get("valid", False)
if self.is_valid:
self.save_license_cache()
# Update-Check
if result.get("requires_update"):
print(f"Update verfügbar: {result.get('update_url')}")
return self.is_valid, result.get("message", "")
else:
return False, f"Server-Fehler: {response.status_code}"
except requests.exceptions.RequestException:
# Offline-Modus: Cache prüfen
return self.verify_offline()
def verify_offline(self):
"""Offline-Verifizierung mit Grace Period"""
cache = self.load_license_cache()
if not cache:
return False, "Keine gecachte Lizenz vorhanden"
# Hardware-Hash prüfen
if cache.get("hardware_hash") != self.get_hardware_hash():
# Grace Period bei Hardware-Änderung
last_verified = datetime.fromisoformat(cache.get("last_verified"))
grace_period = timedelta(days=7)
if datetime.now() - last_verified > grace_period:
return False, "Hardware geändert - Grace Period abgelaufen"
# Ablaufdatum prüfen
if cache.get("expires_at"):
expires_at = datetime.fromisoformat(cache.get("expires_at"))
if datetime.now() > expires_at:
return False, "Lizenz abgelaufen"
self.license_key = cache.get("license_key")
self.activation_id = cache.get("activation_id")
self.is_valid = True
return True, "Offline-Modus (gecachte Lizenz)"
def check_for_updates(self):
"""Nach Updates suchen"""
if not self.license_key:
return None
data = {
"current_version": self.app_version,
"license_key": self.license_key
}
try:
response = requests.post(
f"{self.server_url}/api/version/check",
headers=self.headers,
json=data,
timeout=10,
verify=True
)
if response.status_code == 200:
return response.json()
except:
pass
return None
def heartbeat_worker(self):
"""Background-Thread für regelmäßige Lizenzprüfung"""
while not self.stop_heartbeat:
time.sleep(900) # 15 Minuten
if self.stop_heartbeat:
break
valid, message = self.verify_license()
if not valid:
print(f"Lizenz-Warnung: {message}")
# Hier könnte die App reagieren (z.B. Features deaktivieren)
def start_heartbeat(self):
"""Heartbeat-Thread starten"""
if self.heartbeat_thread and self.heartbeat_thread.is_alive():
return
self.stop_heartbeat = False
self.heartbeat_thread = threading.Thread(
target=self.heartbeat_worker,
daemon=True
)
self.heartbeat_thread.start()
def stop_heartbeat_thread(self):
"""Heartbeat-Thread beenden"""
self.stop_heartbeat = True
if self.heartbeat_thread:
self.heartbeat_thread.join(timeout=1)
def get_license_info(self):
"""Aktuelle Lizenzinformationen"""
return {
"valid": self.is_valid,
"license_key": self.license_key[:4] + "****" if self.license_key else None,
"expires_at": self.expires_at.isoformat() if self.expires_at else None,
"machine_id": self.get_machine_id()
}
# Beispiel-Anwendung
def main():
# API-Key sollte sicher gespeichert werden (z.B. verschlüsselt)
API_KEY = os.environ.get("LICENSE_API_KEY", "your-api-key-here")
# License Manager initialisieren
license_mgr = LicenseManager(API_KEY, app_version="1.0.0")
# Versuche gecachte Lizenz zu laden
cache = license_mgr.load_license_cache()
if cache:
print("Gecachte Lizenz gefunden, verifiziere...")
valid, message = license_mgr.verify_license()
if valid:
print(f"✓ Lizenz gültig: {message}")
else:
print(f"✗ Lizenz ungültig: {message}")
# Neue Lizenz erforderlich
license_key = input("Bitte Lizenzschlüssel eingeben: ")
success, message = license_mgr.activate_license(license_key)
if success:
print(f"{message}")
else:
print(f"{message}")
return
else:
# Erste Aktivierung
print("Keine Lizenz gefunden.")
license_key = input("Bitte Lizenzschlüssel eingeben: ")
success, message = license_mgr.activate_license(license_key)
if success:
print(f"{message}")
else:
print(f"{message}")
return
# Update-Check
print("\nPrüfe auf Updates...")
update_info = license_mgr.check_for_updates()
if update_info and update_info.get("update_available"):
print(f"Update verfügbar: {update_info.get('latest_version')}")
if update_info.get("is_mandatory"):
print("⚠️ Dies ist ein Pflicht-Update!")
# Lizenzinfo anzeigen
info = license_mgr.get_license_info()
print(f"\nLizenz-Status:")
print(f"- Gültig: {info['valid']}")
print(f"- Ablauf: {info['expires_at']}")
print(f"- Maschine: {info['machine_id']}")
# App läuft...
print("\n✓ Anwendung gestartet mit gültiger Lizenz")
try:
# Simuliere App-Laufzeit
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\nBeende Anwendung...")
license_mgr.stop_heartbeat_thread()
if __name__ == "__main__":
main()

Datei anzeigen

@@ -0,0 +1,48 @@
version: '3.8'
services:
license-server:
build: .
container_name: license-server
restart: unless-stopped
ports:
- "8443:8443"
environment:
- DATABASE_URL=postgresql://license_user:license_password@db:5432/license_db
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
networks:
- license-network
db:
image: postgres:15-alpine
container_name: license-db
restart: unless-stopped
environment:
POSTGRES_USER: license_user
POSTGRES_PASSWORD: license_password
POSTGRES_DB: license_db
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- license-network
redis:
image: redis:7-alpine
container_name: license-redis
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
- redis_data:/data
networks:
- license-network
volumes:
postgres_data:
redis_data:
networks:
license-network:
driver: bridge

44
v2_lizenzserver/init_db.py Normale Datei
Datei anzeigen

@@ -0,0 +1,44 @@
import sys
sys.path.append('/app')
from app.db.database import engine, Base
from app.models.models import License, Activation, Version, ApiKey
from sqlalchemy.orm import Session
import uuid
from datetime import datetime, timedelta
print("Creating database tables...")
Base.metadata.create_all(bind=engine)
with Session(engine) as db:
# Create a test API key
api_key = ApiKey(
key="test-api-key-12345",
name="Test API Key",
is_active=True
)
db.add(api_key)
# Create a test license
test_license = License(
license_key="TEST-LICENSE-KEY-12345",
product_id="software-v1",
customer_email="test@example.com",
customer_name="Test Customer",
max_activations=5,
expires_at=datetime.utcnow() + timedelta(days=365)
)
db.add(test_license)
# Create initial version
initial_version = Version(
version_number="1.0.0",
release_notes="Initial release",
is_mandatory=False
)
db.add(initial_version)
db.commit()
print("Database initialized successfully!")
print(f"Test API Key: test-api-key-12345")
print(f"Test License Key: TEST-LICENSE-KEY-12345")

Datei anzeigen

@@ -0,0 +1,14 @@
fastapi==0.104.1
uvicorn[standard]==0.24.0
sqlalchemy==2.0.23
psycopg2-binary==2.9.9
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
python-multipart==0.0.6
pydantic==2.5.0
pydantic-settings==2.1.0
alembic==1.12.1
python-dotenv==1.0.0
httpx==0.25.2
redis==5.0.1
packaging==23.2

39
v2_lizenzserver/test_api.py Normale Datei
Datei anzeigen

@@ -0,0 +1,39 @@
import httpx
import asyncio
API_URL = "https://api-software-undso.z5m7q9dk3ah2v1plx6ju.com"
API_KEY = "test-api-key-12345"
async def test_license_server():
async with httpx.AsyncClient(verify=False) as client:
# Test root endpoint
print("Testing root endpoint...")
response = await client.get(f"{API_URL}/")
print(f"Status: {response.status_code}")
print(f"Response: {response.json()}\n")
# Test health endpoint
print("Testing health endpoint...")
response = await client.get(f"{API_URL}/health")
print(f"Status: {response.status_code}")
print(f"Response: {response.json()}\n")
# Test license activation (will fail without valid API key)
print("Testing license activation...")
headers = {"Authorization": f"Bearer {API_KEY}"}
data = {
"license_key": "TEST-LICENSE-KEY-12345",
"machine_id": "MACHINE001",
"hardware_hash": "abc123def456",
"app_version": "1.0.0"
}
response = await client.post(
f"{API_URL}/api/license/activate",
headers=headers,
json=data
)
print(f"Status: {response.status_code}")
print(f"Response: {response.json()}\n")
if __name__ == "__main__":
asyncio.run(test_license_server())

Datei anzeigen

@@ -0,0 +1,18 @@
FROM python:3.11-slim
# Zeitzone setzen
ENV TZ=Europe/Berlin
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y tzdata \
&& ln -sf /usr/share/zoneinfo/Europe/Berlin /etc/localtime \
&& echo "Europe/Berlin" > /etc/timezone \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Placeholder für Lizenzserver
RUN echo "Lizenzserver noch nicht implementiert" > info.txt
CMD ["python", "-c", "print('Lizenzserver Container läuft, aber noch keine Implementierung vorhanden'); import time; time.sleep(86400)"]