Initial commit
Dieser Commit ist enthalten in:
534
v2_lizenzserver/client_examples/csharp_client.cs
Normale Datei
534
v2_lizenzserver/client_examples/csharp_client.cs
Normale Datei
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
331
v2_lizenzserver/client_examples/python_client.py
Normale Datei
331
v2_lizenzserver/client_examples/python_client.py
Normale Datei
@ -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()
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren