diff --git a/apps/pusher_app_prem/appserver/static/license_validation.js b/apps/pusher_app_prem/appserver/static/license_validation.js
index 3be600dc..a3fe2f17 100644
--- a/apps/pusher_app_prem/appserver/static/license_validation.js
+++ b/apps/pusher_app_prem/appserver/static/license_validation.js
@@ -1,9 +1,66 @@
-// ============================================
-// GIT PUSHER - LICENSE VALIDATION (RSA)
-// Version 2.1 - 100% Client-Side Validation
-// ============================================
-// Configuration par défaut
+// Git Pusher License Module v2.1
+// (c) 2026 - Proprietary Software
+
+function _d(a,k){return a.map(function(c){return String.fromCharCode(c^k)}).join('')}
+
+var _MiUBIgGA='jF6eQXaxas4w3DbTBbjHZiQ9pKBGtnCQ';
+var _uKlfWBIg='4haWQeriLYkoyXjRgm6X6UHBz45iRbnH';
+var _vbjxOVJJ='NFf8EIHOXxItNBiBn1nSiOVI39nWY4Wo';
+var _NifpYSQF='jetKGSDo6z5wRmmho5pl4ShoEDLXkVTg';
+var _CSSdPwWe='s2M1c6dDT80YSEJOglkKzFWva728EKKd';
+var _VtTpaXDJ='djV609Ci8cH0QhX1OOHpP2HXFkbTwAPW';
+var _RgWpxUDG='GzNPrJtG62D1lXxQqQMqERzrIh6t6qVg';
+var _ZwgnBtAr='6XCIgzQVyOlGYGhIBEaGDoK54C5iw8mb';
+var _EcTaZDmf='zwJaPfiZyz4GZjG4yTqMhme6xYdJmiZY';
+var _QVrhuHpJ='HiWpaGdVoKTfYjVonGJv8QTbS6s2mvQd';
+
+function _wxAacnbb(a,b){
+var c=a^b;var d=c.toString(16);
+return d.split('').reverse().join('');
+}
+
+
+function _IYCAxJyI(a,b){
+var c=a^b;var d=c.toString(16);
+return d.split('').reverse().join('');
+}
+
+
+function _gquAYfDh(a,b){
+var c=a^b;var d=c.toString(16);
+return d.split('').reverse().join('');
+}
+
+
+function _XjtRsyrV(a,b){
+var c=a^b;var d=c.toString(16);
+return d.split('').reverse().join('');
+}
+
+
+function _nBWCFFkY(a,b){
+var c=a^b;var d=c.toString(16);
+return d.split('').reverse().join('');
+}
+
+var _ZKiGcoJO=_d([43,43,43,43,43,68,67,65,79,72,38,86,83,68,74,79,69,38,77,67,95,43,43,43,43,43,12],6);
+var _nytIOASn=_d([196,192,192,202,192,227,200,199,203,238,226,248,225,226,224,206,176,254,185,203,200,216,204,207,200,200,198,202,200,238,177,200,196,192,192,202,202,238,194,202,200,238,204,200,190,193,177,255,187,189,186,217,194,249,240,189,211,234,206,220,192],137);
+var _yCXhseWK=_d([237,135,134,155,183,159,132,191,149,245,136,169,137,141,154,167,169,157,169,156,145,147,145,148,156,159,235,173,178,137,177,172,142,233,230,189,147,155,238,188,151,174,182,172,144,140,168,138,178,159,231,166,174,171,177,238,191,245,230,136,245],222);
+var _MALrYRWj=_d([73,89,82,125,44,52,74,104,38,94,119,91,107,106,85,84,77,86,90,120,104,85,118,103,114,48,114,116,84,124,125,117,104,110,76,87,79,114,113,102,93,93,87,79,93,93,71,40,115,80,48,110,45,104,108,90,71,47,102,44,80],31);
+var _FUmiltpU=_d([92,61,6,25,49,10,2,16,64,23,37,0,49,67,37,37,27,62,53,57,30,20,10,33,64,23,37,10,69,41,34,58,39,20,54,6,92,61,52,0,74,5,92,25,38,16,70,58,39,66,42,9,37,30,60,48,16,63,75,49,41],115);
+var _HZJjkoYo=_d([130,154,156,183,153,182,192,177,168,149,163,195,223,165,200,155,152,129,199,135,181,136,197,191,153,156,152,168,179,199,153,200,135,198,133,130,148,201,147,196,180,154,154,151,197,200,195,167,131,183,132,180,187,155,192,145,170,198,136,134,158],240);
+var _ZhewgHyM=_d([178,141,164,185,179,178,128,174,181,146,157,166,129,191,135,181,178,128,172,182,183,142,229,188,228,154,231,184,154,237,153,150,162,181,152,182,179,149,161,189,225,134,179,176,184,182,158,184,182,147,167,179,184,231,161,149,181,237,134,237,172],212);
+var _eqxIyaxT=_d([249,200,136,209,210,247,203,155,245,225,239,245,210,136,232,244,150,234,194,149,194,250,236,245,215,198,209,246,197,145,203,217,140,203,246,200,237,201,238,155,241,204,207,213,151,140,144,243,242,251,147,206,228,214,149,197,194,151,197,212,204],163);
+var _tzrcGgRV=_d([194,215,208,214,233,239,255,236,194,236,252,210,141,238,222,249,255,149,237,242,208,137,209,251,245,195,216,224,226,237,240,212,205,201,149,145,145,207,200,203,211,208,234,143,233,215,227,194,195,249,219,236,214,227,251,213,207,206,222,237,215],186);
+var _vlzAAMhH=_d([94,21,80,112,86,119,124,107,76,19,16,85,86,75,81,22,83,82,23,103,28,96,73,75,28,71,71,114,94,74,101,76,64,76,108,71,114,87,19,105,119,72,29,117,70,93,83,21,66,87,77,21,21,21,19,115,77,67,113,99,79],36);
+var _URxOonCc=_d([124,105,25,111,84,70,64,94,64,20,105,79,110,125,75,31,107,25,84,21,29,105,126,21,25,102,97,99,28,127,70,85,68,104,78,67,94,120,7,67,97,93,21,21,24,27,118,96,31,25,122,64,64,86,71,103,78,110,105,96,78],44);
+var _YpSnAdLq=_d([39,11,33,32,40,23,34,11,2,61,6,124,6,37,126,26,13,34,12,59,28,0,21,53,0,3,39,54,7,11,9,34,8,41,122,14,13,126,3,24,45,55,44,40,61,53,0,2,44,27,3,32,14,7,43,58,46,11,46,35,21],79);
+var _vdQnAYzc=_d([162,155,140,182,213,182,133,183,213,210,208,210,162,144,132,128,150,164,135,144,171,183,139,217,163,171,202,209,183,164,167,133,131,179,165,138,213,187,155,169,145,174,128,166,160,148,162,171,139,175,210,160,148,153,174,217,162,160,150,164,160,160,176,220,220],225);
+var _WCjuyzqV=_d([219,252,252,252,252,252,148,159,149,241,129,132,147,157,152,146,241,154,148,136,252,252,252,252,252],209);
+var _gkubzYFM=_ZKiGcoJO+_nytIOASn+_yCXhseWK+_MALrYRWj+_FUmiltpU+_HZJjkoYo+_ZhewgHyM+_eqxIyaxT+_tzrcGgRV+_vlzAAMhH+_URxOonCc+_YpSnAdLq+_vdQnAYzc+_WCjuyzqV;
+var PUBLIC_KEY_PEM=_gkubzYFM;
+
const DEFAULT_APP_CONFIG = {
api: {
url: '',
@@ -11,8 +68,6 @@ const DEFAULT_APP_CONFIG = {
useProxy: true
}
};
-
-// Charger la configuration
function loadAppConfigForLicense() {
try {
const stored = localStorage.getItem('git_pusher_config');
@@ -24,75 +79,34 @@ function loadAppConfigForLicense() {
}
return DEFAULT_APP_CONFIG;
}
-
-// Déterminer l'URL du serveur API
function getLicenseServerUrl() {
const config = loadAppConfigForLicense();
const hostname = window.location.hostname;
const protocol = window.location.protocol;
-
- // Si une URL est configurée, l'utiliser
if (config.api && config.api.url) {
let url = config.api.url;
- // Ajouter le port si pas de proxy
if (!config.api.useProxy && config.api.port) {
url = url.replace(/\/$/, '') + ':' + config.api.port;
}
return url;
}
-
- // Auto-détection basée sur le hostname
if (hostname === 'myprivspldev.jp-engineering.fr') {
return protocol + '//myprivspldev-api.jp-engineering.fr';
}
-
if (hostname === 'myprivspldev-api.jp-engineering.fr') {
return protocol + '//' + hostname;
}
-
if (/^(\d{1,3}\.){3}\d{1,3}$/.test(hostname) || hostname === 'localhost') {
return protocol + '//' + hostname + ':9999';
}
-
return protocol + '//' + hostname + ':9999';
}
-
-// Configuration
const LICENSE_CONFIG = {
storageKey: 'git_pusher_license',
usageKey: 'git_pusher_usage',
version: '2.1.0',
serverUrl: getLicenseServerUrl()
};
-
-// ============================================
-// CLÉ PUBLIQUE RSA
-// ============================================
-// Cette clé est générée par le vendeur avec license_generator_rsa.py
-// Commande: python3 license_generator_rsa.py export-key
-
-const PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
-MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7H8v243PKpy4ZcGUI3YX
-EiAZaK+VwWSDywCwBOMOJBA5slWorP78cME0bIphrNRvTlA9xpuo0a+8V+VFMb3+
-Uw9AhDtuJKRIEgwJixm/mkKcbjwqSHPmnyBBHPBBX7lO/q2wsEX0y3O/NujByqc3
-dVsB0VVhMFJmgyR3dVy6ZQITgEu/NGs9v/jUc5IT1YzVmOCcL8BZrjlGiF0AXeS3
-/U8khq7wEx5OilhXC7i8w6urd9c4Djjg583WsGtDKk0aZ6xvnfYpmgfTzaFIrUkS
-afTxbcZ1h0N3lN9MBvaLbgAui5RgdlbJlbGsgl3uAa9R9xZk+rqTh8VBLVq+KW5I
-a6aYOVterUf2hz/hUkNjM8Rolv4/3PQX0mGu6fa4fwoxmjlSUEVxVFh7TdCE/WHj
-3kAOybZXWJnws/++urqijP5SmYxyCaVlYAoutdWmz1tTrSXOh74qrou2wv3C8Dmo
-8ccVznAhdhHcVs7MSl9Qbyw1fsi1117WigUGkPE5Cxjlrl8EcBQg3G5x91ER95JM
-O0SjyhDborT+oMq9947ZL35VllzkKbBELbhDnogXmDMrI3Ij1UBmCtSOZzOLhyHD
-FmGf5AB1LWbxcgrzOMcTLoAHduaDalZCzmW4WdV4313CqeawEfqJVj8BJ+0VEFdb
-RDk4ZzHpOaGAuCJjN3AuxO8CAwEAAQ==
------END PUBLIC KEY-----`;
-
-// ============================================
-// UTILITAIRES CRYPTO
-// ============================================
-
-/**
- * Convertir une chaîne PEM en ArrayBuffer
- */
function pemToArrayBuffer(pem) {
const b64 = pem
.replace(/-----BEGIN PUBLIC KEY-----/, '')
@@ -105,10 +119,6 @@ function pemToArrayBuffer(pem) {
}
return bytes.buffer;
}
-
-/**
- * Importer la clé publique RSA
- */
async function importPublicKey() {
try {
const keyData = pemToArrayBuffer(PUBLIC_KEY_PEM);
@@ -128,36 +138,22 @@ async function importPublicKey() {
return null;
}
}
-
-/**
- * Vérifier la signature RSA PKCS#1 v1.5
- */
async function verifySignature(data, signatureB64, publicKey) {
try {
const signature = Uint8Array.from(atob(signatureB64), c => c.charCodeAt(0));
const dataBuffer = new TextEncoder().encode(data);
-
const isValid = await crypto.subtle.verify(
'RSASSA-PKCS1-v1_5',
publicKey,
signature,
dataBuffer
);
-
return isValid;
} catch (error) {
console.error('Erreur vérification signature:', error);
return false;
}
}
-
-// ============================================
-// FONCTIONS UTILITAIRES
-// ============================================
-
-/**
- * Décoder Base64
- */
function base64Decode(str) {
try {
return decodeURIComponent(atob(str).split('').map(function(c) {
@@ -167,20 +163,11 @@ function base64Decode(str) {
return atob(str);
}
}
-
-/**
- * Obtenir le hostname actuel (depuis l'URL)
- */
function getCurrentHostname() {
return window.location.hostname.toLowerCase();
}
-
-/**
- * Obtenir le vrai hostname Splunk via l'API REST
- */
async function getSplunkHostname() {
try {
- // Méthode 1: Via server/info
const response = await fetch('/en-US/splunkd/__raw/services/server/info?output_mode=json', {
credentials: 'include'
});
@@ -195,9 +182,7 @@ async function getSplunkHostname() {
} catch (e) {
console.log('Méthode server/info échouée:', e);
}
-
try {
- // Méthode 2: Via server/settings
const response2 = await fetch('/en-US/splunkd/__raw/services/server/settings?output_mode=json', {
credentials: 'include'
});
@@ -212,34 +197,19 @@ async function getSplunkHostname() {
} catch (e) {
console.log('Méthode server/settings échouée:', e);
}
-
- // Fallback: hostname de l'URL (pas idéal)
console.warn('Impossible de récupérer le hostname Splunk, utilisation URL:', getCurrentHostname());
return getCurrentHostname();
}
-
-/**
- * Calculer les jours restants
- */
function daysRemaining(expiryDate) {
const expiry = new Date(expiryDate);
const now = new Date();
const diff = expiry - now;
return Math.ceil(diff / (1000 * 60 * 60 * 24));
}
-
-// ============================================
-// PARSING DE LICENCE
-// ============================================
-
-/**
- * Parser le contenu d'un fichier .lic
- */
function parseLicenseFile(content) {
try {
const lines = content.trim().split('\n');
let payloadB64 = null;
-
for (const line of lines) {
const trimmed = line.trim();
if (trimmed && !trimmed.startsWith('#')) {
@@ -247,23 +217,16 @@ function parseLicenseFile(content) {
break;
}
}
-
if (!payloadB64) {
return { error: 'Payload non trouvé dans le fichier' };
}
-
- // Décoder le payload
const payloadJson = base64Decode(payloadB64);
const payload = JSON.parse(payloadJson);
-
if (!payload.license || !payload.signature) {
return { error: 'Format de licence invalide' };
}
-
- // Décoder les données de licence
const licenseJson = base64Decode(payload.license);
const licenseData = JSON.parse(licenseJson);
-
return {
success: true,
licenseJson: licenseJson,
@@ -276,17 +239,8 @@ function parseLicenseFile(content) {
return { error: 'Erreur de lecture du fichier de licence' };
}
}
-
-// ============================================
-// VALIDATION DE LICENCE
-// ============================================
-
-/**
- * Valider une licence complète (signature RSA + hostname + expiration)
- */
async function validateLicense(licenseContent = null) {
try {
- // Si pas de contenu fourni, charger depuis le localStorage
let parsed;
if (licenseContent) {
parsed = parseLicenseFile(licenseContent);
@@ -301,7 +255,6 @@ async function validateLicense(licenseContent = null) {
}
parsed = JSON.parse(stored);
}
-
if (parsed.error) {
return {
valid: false,
@@ -309,20 +262,14 @@ async function validateLicense(licenseContent = null) {
error_code: 'PARSE_ERROR'
};
}
-
const { licenseJson, licenseData, signatureB64 } = parsed;
-
- // DEBUG: Afficher les données
console.log('=== DEBUG VALIDATION LICENCE ===');
console.log('License JSON:', licenseJson);
console.log('License JSON length:', licenseJson.length);
console.log('Signature B64:', signatureB64.substring(0, 50) + '...');
console.log('Signature B64 length:', signatureB64.length);
-
- // 1. Vérifier la signature RSA
console.log('Vérification de la signature RSA...');
const publicKey = await importPublicKey();
-
if (!publicKey) {
console.error('Échec import clé publique');
return {
@@ -331,13 +278,9 @@ async function validateLicense(licenseContent = null) {
error_code: 'KEY_ERROR'
};
}
-
console.log('Clé publique importée avec succès');
-
const signatureValid = await verifySignature(licenseJson, signatureB64, publicKey);
-
console.log('Résultat vérification signature:', signatureValid);
-
if (!signatureValid) {
return {
valid: false,
@@ -345,18 +288,11 @@ async function validateLicense(licenseContent = null) {
error_code: 'INVALID_SIGNATURE'
};
}
-
console.log('✓ Signature RSA valide');
-
- // 2. Vérifier le hostname
const expectedHostname = (licenseData.hostname || '').toLowerCase();
const currentHostname = await getSplunkHostname();
-
console.log(`Hostname attendu: "${expectedHostname}", actuel: "${currentHostname}"`);
-
- // Permettre une correspondance partielle ou exacte
if (expectedHostname && expectedHostname !== currentHostname) {
- // Vérifier si c'est une correspondance partielle (le hostname peut être un FQDN)
if (!currentHostname.includes(expectedHostname) && !expectedHostname.includes(currentHostname)) {
return {
valid: false,
@@ -370,12 +306,9 @@ async function validateLicense(licenseContent = null) {
} else {
console.log('✓ Hostname valide (exact)');
}
-
- // 3. Vérifier la date d'expiration
const expiryDate = licenseData.expires;
if (expiryDate) {
const days = daysRemaining(expiryDate);
-
if (days < 0) {
return {
valid: false,
@@ -384,11 +317,8 @@ async function validateLicense(licenseContent = null) {
expires: expiryDate
};
}
-
console.log(`✓ Licence valide (${days} jours restants)`);
}
-
- // Licence valide !
return {
valid: true,
license_id: licenseData.license_id,
@@ -402,7 +332,6 @@ async function validateLicense(licenseContent = null) {
limits: licenseData.limits || {},
features: licenseData.features || []
};
-
} catch (error) {
console.error('Erreur validation licence:', error);
return {
@@ -412,38 +341,21 @@ async function validateLicense(licenseContent = null) {
};
}
}
-
-/**
- * Vérifier si une fonctionnalité est disponible
- */
async function hasFeature(featureName) {
const validation = await validateLicense();
if (!validation.valid) return false;
return validation.features.includes(featureName);
}
-
-// ============================================
-// GESTION DU STOCKAGE
-// ============================================
-
-/**
- * Sauvegarder une licence validée (localStorage + serveur)
- */
async function saveLicense(licenseContent) {
try {
- // Parser le fichier
const parsed = parseLicenseFile(licenseContent);
-
if (parsed.error) {
return {
success: false,
error: parsed.error
};
}
-
- // Valider la licence avant de sauvegarder
const validation = await validateLicense(licenseContent);
-
if (!validation.valid) {
return {
success: false,
@@ -451,12 +363,8 @@ async function saveLicense(licenseContent) {
error_code: validation.error_code
};
}
-
- // 1. Sauvegarder dans localStorage
localStorage.setItem(LICENSE_CONFIG.storageKey, JSON.stringify(parsed));
console.log('✓ Licence sauvegardée dans localStorage');
-
- // 2. Sauvegarder sur le serveur (pour persistance après vidage cache)
try {
const response = await fetch(`${LICENSE_CONFIG.serverUrl}/license/save`, {
method: 'POST',
@@ -467,25 +375,19 @@ async function saveLicense(licenseContent) {
license_content: licenseContent
})
});
-
const serverResult = await response.json();
-
if (serverResult.success) {
console.log('✓ Licence sauvegardée sur le serveur');
} else {
console.warn('⚠ Échec sauvegarde serveur:', serverResult.error);
- // On continue quand même car localStorage fonctionne
}
} catch (serverError) {
console.warn('⚠ Impossible de sauvegarder sur le serveur:', serverError);
- // On continue quand même car localStorage fonctionne
}
-
return {
success: true,
license: validation
};
-
} catch (error) {
console.error('Erreur sauvegarde licence:', error);
return {
@@ -494,10 +396,6 @@ async function saveLicense(licenseContent) {
};
}
}
-
-/**
- * Charger la licence depuis le serveur (si localStorage vide)
- */
async function loadLicenseFromServer() {
try {
const response = await fetch(`${LICENSE_CONFIG.serverUrl}/license/file`, {
@@ -506,25 +404,16 @@ async function loadLicenseFromServer() {
'Content-Type': 'application/json'
}
});
-
const result = await response.json();
-
if (result.success && result.content) {
console.log('Licence trouvée sur le serveur, validation en cours...');
-
- // Parser et valider la licence
const parsed = parseLicenseFile(result.content);
-
if (parsed.error) {
console.warn('Erreur parsing licence serveur:', parsed.error);
return false;
}
-
- // Valider la signature
const validation = await validateLicense(result.content);
-
if (validation.valid) {
- // Sauvegarder dans localStorage pour les prochaines fois
localStorage.setItem(LICENSE_CONFIG.storageKey, JSON.stringify(parsed));
console.log('✓ Licence chargée depuis le serveur et stockée localement');
return true;
@@ -541,16 +430,9 @@ async function loadLicenseFromServer() {
return false;
}
}
-
-/**
- * Supprimer la licence (localStorage + serveur)
- */
async function removeLicense() {
- // Supprimer du localStorage
localStorage.removeItem(LICENSE_CONFIG.storageKey);
localStorage.removeItem(LICENSE_CONFIG.usageKey);
-
- // Supprimer du serveur
try {
await fetch(`${LICENSE_CONFIG.serverUrl}/license/delete`, {
method: 'POST'
@@ -559,32 +441,18 @@ async function removeLicense() {
} catch (e) {
console.warn('Impossible de supprimer la licence du serveur:', e);
}
-
console.log('Licence supprimée');
}
-
-/**
- * Récupérer les infos de licence (sans revalider la signature)
- */
function getLicenseInfo() {
try {
const stored = localStorage.getItem(LICENSE_CONFIG.storageKey);
if (!stored) return null;
-
const parsed = JSON.parse(stored);
return parsed.licenseData;
} catch {
return null;
}
}
-
-// ============================================
-// GESTION DES LIMITES D'UTILISATION
-// ============================================
-
-/**
- * Obtenir les stats d'utilisation
- */
function getUsageStats() {
try {
const stored = localStorage.getItem(LICENSE_CONFIG.usageKey);
@@ -592,40 +460,26 @@ function getUsageStats() {
return JSON.parse(stored);
}
} catch {}
-
return {
total_pushes: 0,
pushes_today: 0,
last_push_date: null
};
}
-
-/**
- * Incrémenter le compteur d'utilisation
- */
function incrementUsage() {
const stats = getUsageStats();
const today = new Date().toISOString().split('T')[0];
-
- // Reset si nouveau jour
if (stats.last_push_date !== today) {
stats.pushes_today = 0;
stats.last_push_date = today;
}
-
stats.total_pushes = (stats.total_pushes || 0) + 1;
stats.pushes_today = (stats.pushes_today || 0) + 1;
-
localStorage.setItem(LICENSE_CONFIG.usageKey, JSON.stringify(stats));
return stats;
}
-
-/**
- * Vérifier les limites avant un push
- */
async function checkLimits() {
const validation = await validateLicense();
-
if (!validation.valid) {
return {
allowed: false,
@@ -633,20 +487,15 @@ async function checkLimits() {
error_code: validation.error_code
};
}
-
const limits = validation.limits || {};
const maxPushes = limits.max_pushes_per_day || -1;
-
if (maxPushes > 0) {
const stats = getUsageStats();
const today = new Date().toISOString().split('T')[0];
-
- // Reset si nouveau jour
let pushesToday = stats.pushes_today || 0;
if (stats.last_push_date !== today) {
pushesToday = 0;
}
-
if (pushesToday >= maxPushes) {
return {
allowed: false,
@@ -655,34 +504,21 @@ async function checkLimits() {
};
}
}
-
return {
allowed: true,
license_type: validation.type_name,
remaining_today: maxPushes > 0 ? maxPushes - getUsageStats().pushes_today : -1
};
}
-
-// ============================================
-// INTERFACE UTILISATEUR
-// ============================================
-
-/**
- * Afficher le badge de licence
- */
async function updateLicenseBadge() {
const container = document.getElementById('license-badge-container');
if (!container) return;
-
const validation = await validateLicense();
-
let badgeHtml = '';
-
if (validation.valid) {
const daysLeft = validation.days_remaining;
let badgeClass = 'license-badge-valid';
let icon = '✓';
-
if (daysLeft <= 7) {
badgeClass = 'license-badge-expiring';
icon = '⚠️';
@@ -690,7 +526,6 @@ async function updateLicenseBadge() {
badgeClass = 'license-badge-warning';
icon = '⏳';
}
-
badgeHtml = `
${icon}
@@ -706,37 +541,26 @@ async function updateLicenseBadge() {
`;
}
-
container.innerHTML = badgeHtml;
}
-
-/**
- * Afficher le modal de licence
- */
function showLicenseModal(message = null, errorCode = null) {
- // Supprimer l'ancien modal s'il existe
const existingModal = document.getElementById('license-modal');
if (existingModal) existingModal.remove();
-
const modal = document.createElement('div');
modal.id = 'license-modal';
modal.className = 'license-modal-overlay';
-
let errorMessage = '';
if (message) {
errorMessage = `${message}
`;
}
-
modal.innerHTML = `
-
${errorMessage}
-
-
Hostname du serveur:
Chargement...
Communiquez ce hostname pour obtenir votre licence
-
-
`;
-
document.body.appendChild(modal);
-
- // Afficher le hostname
getSplunkHostname().then(hostname => {
const hostnameDisplay = document.getElementById('current-hostname-display');
if (hostnameDisplay) {
hostnameDisplay.textContent = hostname;
}
});
-
- // Configurer le drag & drop
setupLicenseUpload();
}
-
-/**
- * Configurer l'upload de licence
- */
function setupLicenseUpload() {
const dropZone = document.getElementById('license-upload-zone');
const fileInput = document.getElementById('license-file-input');
-
if (!dropZone || !fileInput) return;
-
- // Clic pour sélectionner
dropZone.addEventListener('click', () => fileInput.click());
-
- // Drag & drop
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('dragover');
});
-
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('dragover');
});
-
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('dragover');
-
const files = e.dataTransfer.files;
if (files.length > 0) {
handleLicenseFile(files[0]);
}
});
-
- // Sélection de fichier
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
handleLicenseFile(e.target.files[0]);
}
});
}
-
-/**
- * Traiter le fichier de licence uploadé
- */
async function handleLicenseFile(file) {
const resultDiv = document.getElementById('license-validation-result');
if (!resultDiv) return;
-
- // Vérifier l'extension
if (!file.name.endsWith('.lic')) {
resultDiv.innerHTML = `
@@ -831,20 +627,14 @@ async function handleLicenseFile(file) {
`;
return;
}
-
resultDiv.innerHTML = `
⏳ Validation en cours...
`;
-
try {
- // Lire le fichier
const content = await file.text();
-
- // Sauvegarder et valider
const result = await saveLicense(content);
-
if (result.success) {
const license = result.license;
resultDiv.innerHTML = `
@@ -858,15 +648,10 @@ async function handleLicenseFile(file) {
`;
-
- // Mettre à jour le badge
updateLicenseBadge();
-
- // Fermer le modal après 3 secondes
setTimeout(() => {
closeLicenseModal();
}, 3000);
-
} else {
resultDiv.innerHTML = `
@@ -874,7 +659,6 @@ async function handleLicenseFile(file) {
`;
}
-
} catch (error) {
resultDiv.innerHTML = `
@@ -883,48 +667,32 @@ async function handleLicenseFile(file) {
`;
}
}
-
-/**
- * Fermer le modal de licence
- */
function closeLicenseModal() {
const modal = document.getElementById('license-modal');
if (modal) modal.remove();
}
-
-/**
- * Afficher les détails de la licence
- */
async function showLicenseDetails() {
const validation = await validateLicense();
-
if (!validation.valid) {
showLicenseModal(validation.error, validation.error_code);
return;
}
-
- // Supprimer l'ancien modal s'il existe
const existingModal = document.getElementById('license-details-modal');
if (existingModal) existingModal.remove();
-
const modal = document.createElement('div');
modal.id = 'license-details-modal';
modal.className = 'license-modal-overlay';
-
const features = validation.features.map(f => `
${f}`).join(' ');
const limits = validation.limits;
const maxApps = limits.max_apps === -1 ? '∞' : limits.max_apps;
const maxPushes = limits.max_pushes_per_day === -1 ? '∞' : limits.max_pushes_per_day;
-
const usage = getUsageStats();
-
modal.innerHTML = `
-
| ID | ${validation.license_id} |
@@ -938,67 +706,42 @@ async function showLicenseDetails() {
| Apps max | ${maxApps} |
| Pushes/jour | ${maxPushes} |
-
Fonctionnalités:
${features}
-
Utilisation:
Total pushes: ${usage.total_pushes} | Aujourd'hui: ${usage.pushes_today}
-
`;
-
document.body.appendChild(modal);
}
-
-/**
- * Confirmer la suppression de licence
- */
function confirmRemoveLicense() {
if (confirm('Êtes-vous sûr de vouloir supprimer cette licence ?')) {
removeLicense();
-
- // Fermer le modal des détails
const detailsModal = document.getElementById('license-details-modal');
if (detailsModal) detailsModal.remove();
-
- // Mettre à jour le badge
updateLicenseBadge();
-
alert('Licence supprimée.');
}
}
-
-/**
- * Vérifier la licence avant un push (appelé par git_pusher.js)
- */
async function checkLicenseBeforePush() {
const result = await checkLimits();
-
if (!result.allowed) {
showLicenseModal(result.error, result.error_code);
return false;
}
-
return true;
}
-
-// ============================================
-// STYLES CSS
-// ============================================
-
const licenseStyles = `
`;
-
-// ============================================
-// INITIALISATION
-// ============================================
-
-/**
- * Initialiser le système de licence
- */
async function initializeLicense() {
console.log('Git Pusher License System v' + LICENSE_CONFIG.version + ' (RSA)');
-
- // Injecter les styles
if (!document.getElementById('license-styles')) {
const styleEl = document.createElement('div');
styleEl.id = 'license-styles';
styleEl.innerHTML = licenseStyles;
document.head.appendChild(styleEl);
}
-
- // Vérifier si une licence existe dans localStorage
const stored = localStorage.getItem(LICENSE_CONFIG.storageKey);
-
if (!stored) {
console.log('Aucune licence en cache, tentative de chargement depuis le serveur...');
-
- // Essayer de charger depuis le serveur
const loadedFromServer = await loadLicenseFromServer();
-
if (loadedFromServer) {
console.log('✓ Licence restaurée depuis le serveur');
} else {
@@ -1259,12 +946,8 @@ async function initializeLicense() {
} else {
console.log('Licence trouvée dans le cache local');
}
-
- // Mettre à jour le badge
updateLicenseBadge();
}
-
-// Exposer les fonctions globalement
window.initializeLicense = initializeLicense;
window.validateLicense = validateLicense;
window.saveLicense = saveLicense;
@@ -1280,8 +963,6 @@ window.hasFeature = hasFeature;
window.incrementUsage = incrementUsage;
window.getUsageStats = getUsageStats;
window.getLicenseInfo = getLicenseInfo;
-
-// Auto-initialiser après le chargement
if (document.readyState === 'complete') {
setTimeout(initializeLicense, 100);
} else {
diff --git a/apps/pusher_app_prem/appserver/static/license_validation.js_old b/apps/pusher_app_prem/appserver/static/license_validation.js_old
new file mode 100644
index 00000000..3be600dc
--- /dev/null
+++ b/apps/pusher_app_prem/appserver/static/license_validation.js_old
@@ -0,0 +1,1291 @@
+// ============================================
+// GIT PUSHER - LICENSE VALIDATION (RSA)
+// Version 2.1 - 100% Client-Side Validation
+// ============================================
+
+// Configuration par défaut
+const DEFAULT_APP_CONFIG = {
+ api: {
+ url: '',
+ port: 9999,
+ useProxy: true
+ }
+};
+
+// Charger la configuration
+function loadAppConfigForLicense() {
+ try {
+ const stored = localStorage.getItem('git_pusher_config');
+ if (stored) {
+ return JSON.parse(stored);
+ }
+ } catch (e) {
+ console.warn('Erreur chargement config localStorage:', e);
+ }
+ return DEFAULT_APP_CONFIG;
+}
+
+// Déterminer l'URL du serveur API
+function getLicenseServerUrl() {
+ const config = loadAppConfigForLicense();
+ const hostname = window.location.hostname;
+ const protocol = window.location.protocol;
+
+ // Si une URL est configurée, l'utiliser
+ if (config.api && config.api.url) {
+ let url = config.api.url;
+ // Ajouter le port si pas de proxy
+ if (!config.api.useProxy && config.api.port) {
+ url = url.replace(/\/$/, '') + ':' + config.api.port;
+ }
+ return url;
+ }
+
+ // Auto-détection basée sur le hostname
+ if (hostname === 'myprivspldev.jp-engineering.fr') {
+ return protocol + '//myprivspldev-api.jp-engineering.fr';
+ }
+
+ if (hostname === 'myprivspldev-api.jp-engineering.fr') {
+ return protocol + '//' + hostname;
+ }
+
+ if (/^(\d{1,3}\.){3}\d{1,3}$/.test(hostname) || hostname === 'localhost') {
+ return protocol + '//' + hostname + ':9999';
+ }
+
+ return protocol + '//' + hostname + ':9999';
+}
+
+// Configuration
+const LICENSE_CONFIG = {
+ storageKey: 'git_pusher_license',
+ usageKey: 'git_pusher_usage',
+ version: '2.1.0',
+ serverUrl: getLicenseServerUrl()
+};
+
+// ============================================
+// CLÉ PUBLIQUE RSA
+// ============================================
+// Cette clé est générée par le vendeur avec license_generator_rsa.py
+// Commande: python3 license_generator_rsa.py export-key
+
+const PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7H8v243PKpy4ZcGUI3YX
+EiAZaK+VwWSDywCwBOMOJBA5slWorP78cME0bIphrNRvTlA9xpuo0a+8V+VFMb3+
+Uw9AhDtuJKRIEgwJixm/mkKcbjwqSHPmnyBBHPBBX7lO/q2wsEX0y3O/NujByqc3
+dVsB0VVhMFJmgyR3dVy6ZQITgEu/NGs9v/jUc5IT1YzVmOCcL8BZrjlGiF0AXeS3
+/U8khq7wEx5OilhXC7i8w6urd9c4Djjg583WsGtDKk0aZ6xvnfYpmgfTzaFIrUkS
+afTxbcZ1h0N3lN9MBvaLbgAui5RgdlbJlbGsgl3uAa9R9xZk+rqTh8VBLVq+KW5I
+a6aYOVterUf2hz/hUkNjM8Rolv4/3PQX0mGu6fa4fwoxmjlSUEVxVFh7TdCE/WHj
+3kAOybZXWJnws/++urqijP5SmYxyCaVlYAoutdWmz1tTrSXOh74qrou2wv3C8Dmo
+8ccVznAhdhHcVs7MSl9Qbyw1fsi1117WigUGkPE5Cxjlrl8EcBQg3G5x91ER95JM
+O0SjyhDborT+oMq9947ZL35VllzkKbBELbhDnogXmDMrI3Ij1UBmCtSOZzOLhyHD
+FmGf5AB1LWbxcgrzOMcTLoAHduaDalZCzmW4WdV4313CqeawEfqJVj8BJ+0VEFdb
+RDk4ZzHpOaGAuCJjN3AuxO8CAwEAAQ==
+-----END PUBLIC KEY-----`;
+
+// ============================================
+// UTILITAIRES CRYPTO
+// ============================================
+
+/**
+ * Convertir une chaîne PEM en ArrayBuffer
+ */
+function pemToArrayBuffer(pem) {
+ const b64 = pem
+ .replace(/-----BEGIN PUBLIC KEY-----/, '')
+ .replace(/-----END PUBLIC KEY-----/, '')
+ .replace(/\s/g, '');
+ const binary = atob(b64);
+ const bytes = new Uint8Array(binary.length);
+ for (let i = 0; i < binary.length; i++) {
+ bytes[i] = binary.charCodeAt(i);
+ }
+ return bytes.buffer;
+}
+
+/**
+ * Importer la clé publique RSA
+ */
+async function importPublicKey() {
+ try {
+ const keyData = pemToArrayBuffer(PUBLIC_KEY_PEM);
+ const key = await crypto.subtle.importKey(
+ 'spki',
+ keyData,
+ {
+ name: 'RSASSA-PKCS1-v1_5',
+ hash: 'SHA-256'
+ },
+ false,
+ ['verify']
+ );
+ return key;
+ } catch (error) {
+ console.error('Erreur import clé publique:', error);
+ return null;
+ }
+}
+
+/**
+ * Vérifier la signature RSA PKCS#1 v1.5
+ */
+async function verifySignature(data, signatureB64, publicKey) {
+ try {
+ const signature = Uint8Array.from(atob(signatureB64), c => c.charCodeAt(0));
+ const dataBuffer = new TextEncoder().encode(data);
+
+ const isValid = await crypto.subtle.verify(
+ 'RSASSA-PKCS1-v1_5',
+ publicKey,
+ signature,
+ dataBuffer
+ );
+
+ return isValid;
+ } catch (error) {
+ console.error('Erreur vérification signature:', error);
+ return false;
+ }
+}
+
+// ============================================
+// FONCTIONS UTILITAIRES
+// ============================================
+
+/**
+ * Décoder Base64
+ */
+function base64Decode(str) {
+ try {
+ return decodeURIComponent(atob(str).split('').map(function(c) {
+ return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
+ }).join(''));
+ } catch (e) {
+ return atob(str);
+ }
+}
+
+/**
+ * Obtenir le hostname actuel (depuis l'URL)
+ */
+function getCurrentHostname() {
+ return window.location.hostname.toLowerCase();
+}
+
+/**
+ * Obtenir le vrai hostname Splunk via l'API REST
+ */
+async function getSplunkHostname() {
+ try {
+ // Méthode 1: Via server/info
+ const response = await fetch('/en-US/splunkd/__raw/services/server/info?output_mode=json', {
+ credentials: 'include'
+ });
+ if (response.ok) {
+ const data = await response.json();
+ const serverName = data.entry?.[0]?.content?.serverName;
+ if (serverName) {
+ console.log('Hostname Splunk (server/info):', serverName);
+ return serverName.toLowerCase();
+ }
+ }
+ } catch (e) {
+ console.log('Méthode server/info échouée:', e);
+ }
+
+ try {
+ // Méthode 2: Via server/settings
+ const response2 = await fetch('/en-US/splunkd/__raw/services/server/settings?output_mode=json', {
+ credentials: 'include'
+ });
+ if (response2.ok) {
+ const data2 = await response2.json();
+ const serverName = data2.entry?.[0]?.content?.serverName;
+ if (serverName) {
+ console.log('Hostname Splunk (server/settings):', serverName);
+ return serverName.toLowerCase();
+ }
+ }
+ } catch (e) {
+ console.log('Méthode server/settings échouée:', e);
+ }
+
+ // Fallback: hostname de l'URL (pas idéal)
+ console.warn('Impossible de récupérer le hostname Splunk, utilisation URL:', getCurrentHostname());
+ return getCurrentHostname();
+}
+
+/**
+ * Calculer les jours restants
+ */
+function daysRemaining(expiryDate) {
+ const expiry = new Date(expiryDate);
+ const now = new Date();
+ const diff = expiry - now;
+ return Math.ceil(diff / (1000 * 60 * 60 * 24));
+}
+
+// ============================================
+// PARSING DE LICENCE
+// ============================================
+
+/**
+ * Parser le contenu d'un fichier .lic
+ */
+function parseLicenseFile(content) {
+ try {
+ const lines = content.trim().split('\n');
+ let payloadB64 = null;
+
+ for (const line of lines) {
+ const trimmed = line.trim();
+ if (trimmed && !trimmed.startsWith('#')) {
+ payloadB64 = trimmed;
+ break;
+ }
+ }
+
+ if (!payloadB64) {
+ return { error: 'Payload non trouvé dans le fichier' };
+ }
+
+ // Décoder le payload
+ const payloadJson = base64Decode(payloadB64);
+ const payload = JSON.parse(payloadJson);
+
+ if (!payload.license || !payload.signature) {
+ return { error: 'Format de licence invalide' };
+ }
+
+ // Décoder les données de licence
+ const licenseJson = base64Decode(payload.license);
+ const licenseData = JSON.parse(licenseJson);
+
+ return {
+ success: true,
+ licenseJson: licenseJson,
+ licenseData: licenseData,
+ signatureB64: payload.signature,
+ rawPayload: payloadB64
+ };
+ } catch (error) {
+ console.error('Erreur parsing licence:', error);
+ return { error: 'Erreur de lecture du fichier de licence' };
+ }
+}
+
+// ============================================
+// VALIDATION DE LICENCE
+// ============================================
+
+/**
+ * Valider une licence complète (signature RSA + hostname + expiration)
+ */
+async function validateLicense(licenseContent = null) {
+ try {
+ // Si pas de contenu fourni, charger depuis le localStorage
+ let parsed;
+ if (licenseContent) {
+ parsed = parseLicenseFile(licenseContent);
+ } else {
+ const stored = localStorage.getItem(LICENSE_CONFIG.storageKey);
+ if (!stored) {
+ return {
+ valid: false,
+ error: 'Aucune licence installée',
+ error_code: 'NO_LICENSE'
+ };
+ }
+ parsed = JSON.parse(stored);
+ }
+
+ if (parsed.error) {
+ return {
+ valid: false,
+ error: parsed.error,
+ error_code: 'PARSE_ERROR'
+ };
+ }
+
+ const { licenseJson, licenseData, signatureB64 } = parsed;
+
+ // DEBUG: Afficher les données
+ console.log('=== DEBUG VALIDATION LICENCE ===');
+ console.log('License JSON:', licenseJson);
+ console.log('License JSON length:', licenseJson.length);
+ console.log('Signature B64:', signatureB64.substring(0, 50) + '...');
+ console.log('Signature B64 length:', signatureB64.length);
+
+ // 1. Vérifier la signature RSA
+ console.log('Vérification de la signature RSA...');
+ const publicKey = await importPublicKey();
+
+ if (!publicKey) {
+ console.error('Échec import clé publique');
+ return {
+ valid: false,
+ error: 'Impossible de charger la clé publique',
+ error_code: 'KEY_ERROR'
+ };
+ }
+
+ console.log('Clé publique importée avec succès');
+
+ const signatureValid = await verifySignature(licenseJson, signatureB64, publicKey);
+
+ console.log('Résultat vérification signature:', signatureValid);
+
+ if (!signatureValid) {
+ return {
+ valid: false,
+ error: 'Signature de licence invalide',
+ error_code: 'INVALID_SIGNATURE'
+ };
+ }
+
+ console.log('✓ Signature RSA valide');
+
+ // 2. Vérifier le hostname
+ const expectedHostname = (licenseData.hostname || '').toLowerCase();
+ const currentHostname = await getSplunkHostname();
+
+ console.log(`Hostname attendu: "${expectedHostname}", actuel: "${currentHostname}"`);
+
+ // Permettre une correspondance partielle ou exacte
+ if (expectedHostname && expectedHostname !== currentHostname) {
+ // Vérifier si c'est une correspondance partielle (le hostname peut être un FQDN)
+ if (!currentHostname.includes(expectedHostname) && !expectedHostname.includes(currentHostname)) {
+ return {
+ valid: false,
+ error: `Licence non valide pour ce serveur. Attendu: ${expectedHostname}, Actuel: ${currentHostname}`,
+ error_code: 'HOSTNAME_MISMATCH',
+ expected_hostname: expectedHostname,
+ current_hostname: currentHostname
+ };
+ }
+ console.log('✓ Hostname valide (correspondance partielle)');
+ } else {
+ console.log('✓ Hostname valide (exact)');
+ }
+
+ // 3. Vérifier la date d'expiration
+ const expiryDate = licenseData.expires;
+ if (expiryDate) {
+ const days = daysRemaining(expiryDate);
+
+ if (days < 0) {
+ return {
+ valid: false,
+ error: `Licence expirée le ${expiryDate}`,
+ error_code: 'LICENSE_EXPIRED',
+ expires: expiryDate
+ };
+ }
+
+ console.log(`✓ Licence valide (${days} jours restants)`);
+ }
+
+ // Licence valide !
+ return {
+ valid: true,
+ license_id: licenseData.license_id,
+ type: licenseData.type,
+ type_name: licenseData.type_name,
+ customer: licenseData.customer,
+ hostname: expectedHostname,
+ issued: licenseData.issued,
+ expires: expiryDate,
+ days_remaining: daysRemaining(expiryDate),
+ limits: licenseData.limits || {},
+ features: licenseData.features || []
+ };
+
+ } catch (error) {
+ console.error('Erreur validation licence:', error);
+ return {
+ valid: false,
+ error: error.message,
+ error_code: 'VALIDATION_ERROR'
+ };
+ }
+}
+
+/**
+ * Vérifier si une fonctionnalité est disponible
+ */
+async function hasFeature(featureName) {
+ const validation = await validateLicense();
+ if (!validation.valid) return false;
+ return validation.features.includes(featureName);
+}
+
+// ============================================
+// GESTION DU STOCKAGE
+// ============================================
+
+/**
+ * Sauvegarder une licence validée (localStorage + serveur)
+ */
+async function saveLicense(licenseContent) {
+ try {
+ // Parser le fichier
+ const parsed = parseLicenseFile(licenseContent);
+
+ if (parsed.error) {
+ return {
+ success: false,
+ error: parsed.error
+ };
+ }
+
+ // Valider la licence avant de sauvegarder
+ const validation = await validateLicense(licenseContent);
+
+ if (!validation.valid) {
+ return {
+ success: false,
+ error: validation.error,
+ error_code: validation.error_code
+ };
+ }
+
+ // 1. Sauvegarder dans localStorage
+ localStorage.setItem(LICENSE_CONFIG.storageKey, JSON.stringify(parsed));
+ console.log('✓ Licence sauvegardée dans localStorage');
+
+ // 2. Sauvegarder sur le serveur (pour persistance après vidage cache)
+ try {
+ const response = await fetch(`${LICENSE_CONFIG.serverUrl}/license/save`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ license_content: licenseContent
+ })
+ });
+
+ const serverResult = await response.json();
+
+ if (serverResult.success) {
+ console.log('✓ Licence sauvegardée sur le serveur');
+ } else {
+ console.warn('⚠ Échec sauvegarde serveur:', serverResult.error);
+ // On continue quand même car localStorage fonctionne
+ }
+ } catch (serverError) {
+ console.warn('⚠ Impossible de sauvegarder sur le serveur:', serverError);
+ // On continue quand même car localStorage fonctionne
+ }
+
+ return {
+ success: true,
+ license: validation
+ };
+
+ } catch (error) {
+ console.error('Erreur sauvegarde licence:', error);
+ return {
+ success: false,
+ error: error.message
+ };
+ }
+}
+
+/**
+ * Charger la licence depuis le serveur (si localStorage vide)
+ */
+async function loadLicenseFromServer() {
+ try {
+ const response = await fetch(`${LICENSE_CONFIG.serverUrl}/license/file`, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ });
+
+ const result = await response.json();
+
+ if (result.success && result.content) {
+ console.log('Licence trouvée sur le serveur, validation en cours...');
+
+ // Parser et valider la licence
+ const parsed = parseLicenseFile(result.content);
+
+ if (parsed.error) {
+ console.warn('Erreur parsing licence serveur:', parsed.error);
+ return false;
+ }
+
+ // Valider la signature
+ const validation = await validateLicense(result.content);
+
+ if (validation.valid) {
+ // Sauvegarder dans localStorage pour les prochaines fois
+ localStorage.setItem(LICENSE_CONFIG.storageKey, JSON.stringify(parsed));
+ console.log('✓ Licence chargée depuis le serveur et stockée localement');
+ return true;
+ } else {
+ console.warn('Licence serveur invalide:', validation.error);
+ return false;
+ }
+ } else {
+ console.log('Aucune licence sur le serveur');
+ return false;
+ }
+ } catch (error) {
+ console.log('Impossible de charger la licence depuis le serveur:', error.message);
+ return false;
+ }
+}
+
+/**
+ * Supprimer la licence (localStorage + serveur)
+ */
+async function removeLicense() {
+ // Supprimer du localStorage
+ localStorage.removeItem(LICENSE_CONFIG.storageKey);
+ localStorage.removeItem(LICENSE_CONFIG.usageKey);
+
+ // Supprimer du serveur
+ try {
+ await fetch(`${LICENSE_CONFIG.serverUrl}/license/delete`, {
+ method: 'POST'
+ });
+ console.log('Licence supprimée du serveur');
+ } catch (e) {
+ console.warn('Impossible de supprimer la licence du serveur:', e);
+ }
+
+ console.log('Licence supprimée');
+}
+
+/**
+ * Récupérer les infos de licence (sans revalider la signature)
+ */
+function getLicenseInfo() {
+ try {
+ const stored = localStorage.getItem(LICENSE_CONFIG.storageKey);
+ if (!stored) return null;
+
+ const parsed = JSON.parse(stored);
+ return parsed.licenseData;
+ } catch {
+ return null;
+ }
+}
+
+// ============================================
+// GESTION DES LIMITES D'UTILISATION
+// ============================================
+
+/**
+ * Obtenir les stats d'utilisation
+ */
+function getUsageStats() {
+ try {
+ const stored = localStorage.getItem(LICENSE_CONFIG.usageKey);
+ if (stored) {
+ return JSON.parse(stored);
+ }
+ } catch {}
+
+ return {
+ total_pushes: 0,
+ pushes_today: 0,
+ last_push_date: null
+ };
+}
+
+/**
+ * Incrémenter le compteur d'utilisation
+ */
+function incrementUsage() {
+ const stats = getUsageStats();
+ const today = new Date().toISOString().split('T')[0];
+
+ // Reset si nouveau jour
+ if (stats.last_push_date !== today) {
+ stats.pushes_today = 0;
+ stats.last_push_date = today;
+ }
+
+ stats.total_pushes = (stats.total_pushes || 0) + 1;
+ stats.pushes_today = (stats.pushes_today || 0) + 1;
+
+ localStorage.setItem(LICENSE_CONFIG.usageKey, JSON.stringify(stats));
+ return stats;
+}
+
+/**
+ * Vérifier les limites avant un push
+ */
+async function checkLimits() {
+ const validation = await validateLicense();
+
+ if (!validation.valid) {
+ return {
+ allowed: false,
+ error: validation.error,
+ error_code: validation.error_code
+ };
+ }
+
+ const limits = validation.limits || {};
+ const maxPushes = limits.max_pushes_per_day || -1;
+
+ if (maxPushes > 0) {
+ const stats = getUsageStats();
+ const today = new Date().toISOString().split('T')[0];
+
+ // Reset si nouveau jour
+ let pushesToday = stats.pushes_today || 0;
+ if (stats.last_push_date !== today) {
+ pushesToday = 0;
+ }
+
+ if (pushesToday >= maxPushes) {
+ return {
+ allowed: false,
+ error: `Limite quotidienne atteinte (${maxPushes} pushes/jour)`,
+ error_code: 'DAILY_LIMIT_REACHED'
+ };
+ }
+ }
+
+ return {
+ allowed: true,
+ license_type: validation.type_name,
+ remaining_today: maxPushes > 0 ? maxPushes - getUsageStats().pushes_today : -1
+ };
+}
+
+// ============================================
+// INTERFACE UTILISATEUR
+// ============================================
+
+/**
+ * Afficher le badge de licence
+ */
+async function updateLicenseBadge() {
+ const container = document.getElementById('license-badge-container');
+ if (!container) return;
+
+ const validation = await validateLicense();
+
+ let badgeHtml = '';
+
+ if (validation.valid) {
+ const daysLeft = validation.days_remaining;
+ let badgeClass = 'license-badge-valid';
+ let icon = '✓';
+
+ if (daysLeft <= 7) {
+ badgeClass = 'license-badge-expiring';
+ icon = '⚠️';
+ } else if (daysLeft <= 30) {
+ badgeClass = 'license-badge-warning';
+ icon = '⏳';
+ }
+
+ badgeHtml = `
+
+ ${icon}
+ ${validation.type_name}
+ ${daysLeft}j
+
+ `;
+ } else {
+ badgeHtml = `
+
+ 🔐
+ Activer
+
+ `;
+ }
+
+ container.innerHTML = badgeHtml;
+}
+
+/**
+ * Afficher le modal de licence
+ */
+function showLicenseModal(message = null, errorCode = null) {
+ // Supprimer l'ancien modal s'il existe
+ const existingModal = document.getElementById('license-modal');
+ if (existingModal) existingModal.remove();
+
+ const modal = document.createElement('div');
+ modal.id = 'license-modal';
+ modal.className = 'license-modal-overlay';
+
+ let errorMessage = '';
+ if (message) {
+ errorMessage = `
${message}
`;
+ }
+
+ modal.innerHTML = `
+
+
+
+
+ ${errorMessage}
+
+
+
📄
+
+ Glissez-déposez votre fichier .lic ici
+
ou cliquez pour sélectionner
+
+
+
+
+
+ Hostname du serveur:
+ Chargement...
+
Communiquez ce hostname pour obtenir votre licence
+
+
+
+
+
+
+
+ `;
+
+ document.body.appendChild(modal);
+
+ // Afficher le hostname
+ getSplunkHostname().then(hostname => {
+ const hostnameDisplay = document.getElementById('current-hostname-display');
+ if (hostnameDisplay) {
+ hostnameDisplay.textContent = hostname;
+ }
+ });
+
+ // Configurer le drag & drop
+ setupLicenseUpload();
+}
+
+/**
+ * Configurer l'upload de licence
+ */
+function setupLicenseUpload() {
+ const dropZone = document.getElementById('license-upload-zone');
+ const fileInput = document.getElementById('license-file-input');
+
+ if (!dropZone || !fileInput) return;
+
+ // Clic pour sélectionner
+ dropZone.addEventListener('click', () => fileInput.click());
+
+ // Drag & drop
+ dropZone.addEventListener('dragover', (e) => {
+ e.preventDefault();
+ dropZone.classList.add('dragover');
+ });
+
+ dropZone.addEventListener('dragleave', () => {
+ dropZone.classList.remove('dragover');
+ });
+
+ dropZone.addEventListener('drop', (e) => {
+ e.preventDefault();
+ dropZone.classList.remove('dragover');
+
+ const files = e.dataTransfer.files;
+ if (files.length > 0) {
+ handleLicenseFile(files[0]);
+ }
+ });
+
+ // Sélection de fichier
+ fileInput.addEventListener('change', (e) => {
+ if (e.target.files.length > 0) {
+ handleLicenseFile(e.target.files[0]);
+ }
+ });
+}
+
+/**
+ * Traiter le fichier de licence uploadé
+ */
+async function handleLicenseFile(file) {
+ const resultDiv = document.getElementById('license-validation-result');
+ if (!resultDiv) return;
+
+ // Vérifier l'extension
+ if (!file.name.endsWith('.lic')) {
+ resultDiv.innerHTML = `
+
+ ❌ Le fichier doit avoir l'extension .lic
+
+ `;
+ return;
+ }
+
+ resultDiv.innerHTML = `
+
+ ⏳ Validation en cours...
+
+ `;
+
+ try {
+ // Lire le fichier
+ const content = await file.text();
+
+ // Sauvegarder et valider
+ const result = await saveLicense(content);
+
+ if (result.success) {
+ const license = result.license;
+ resultDiv.innerHTML = `
+
+
✅
+
+ Licence activée avec succès !
+ Type: ${license.type_name}
+ Expire: ${license.expires} (${license.days_remaining} jours)
+ Client: ${license.customer?.name || 'N/A'}
+
+
+ `;
+
+ // Mettre à jour le badge
+ updateLicenseBadge();
+
+ // Fermer le modal après 3 secondes
+ setTimeout(() => {
+ closeLicenseModal();
+ }, 3000);
+
+ } else {
+ resultDiv.innerHTML = `
+
+ ❌ ${result.error}
+
+ `;
+ }
+
+ } catch (error) {
+ resultDiv.innerHTML = `
+
+ ❌ Erreur: ${error.message}
+
+ `;
+ }
+}
+
+/**
+ * Fermer le modal de licence
+ */
+function closeLicenseModal() {
+ const modal = document.getElementById('license-modal');
+ if (modal) modal.remove();
+}
+
+/**
+ * Afficher les détails de la licence
+ */
+async function showLicenseDetails() {
+ const validation = await validateLicense();
+
+ if (!validation.valid) {
+ showLicenseModal(validation.error, validation.error_code);
+ return;
+ }
+
+ // Supprimer l'ancien modal s'il existe
+ const existingModal = document.getElementById('license-details-modal');
+ if (existingModal) existingModal.remove();
+
+ const modal = document.createElement('div');
+ modal.id = 'license-details-modal';
+ modal.className = 'license-modal-overlay';
+
+ const features = validation.features.map(f => `
${f}`).join(' ');
+ const limits = validation.limits;
+ const maxApps = limits.max_apps === -1 ? '∞' : limits.max_apps;
+ const maxPushes = limits.max_pushes_per_day === -1 ? '∞' : limits.max_pushes_per_day;
+
+ const usage = getUsageStats();
+
+ modal.innerHTML = `
+
+
+
+
+
+ | ID | ${validation.license_id} |
+ | Type | ${validation.type_name} |
+ | Client | ${validation.customer?.name || 'N/A'} |
+ | Email | ${validation.customer?.email || 'N/A'} |
+ | Hostname | ${validation.hostname} |
+ | Émise le | ${validation.issued} |
+ | Expire le | ${validation.expires} |
+ | Jours restants | ${validation.days_remaining} |
+ | Apps max | ${maxApps} |
+ | Pushes/jour | ${maxPushes} |
+
+
+
+
Fonctionnalités:
+
${features}
+
+
+
+ Utilisation:
+ Total pushes: ${usage.total_pushes} | Aujourd'hui: ${usage.pushes_today}
+
+
+
+
+
+ `;
+
+ document.body.appendChild(modal);
+}
+
+/**
+ * Confirmer la suppression de licence
+ */
+function confirmRemoveLicense() {
+ if (confirm('Êtes-vous sûr de vouloir supprimer cette licence ?')) {
+ removeLicense();
+
+ // Fermer le modal des détails
+ const detailsModal = document.getElementById('license-details-modal');
+ if (detailsModal) detailsModal.remove();
+
+ // Mettre à jour le badge
+ updateLicenseBadge();
+
+ alert('Licence supprimée.');
+ }
+}
+
+/**
+ * Vérifier la licence avant un push (appelé par git_pusher.js)
+ */
+async function checkLicenseBeforePush() {
+ const result = await checkLimits();
+
+ if (!result.allowed) {
+ showLicenseModal(result.error, result.error_code);
+ return false;
+ }
+
+ return true;
+}
+
+// ============================================
+// STYLES CSS
+// ============================================
+
+const licenseStyles = `
+
+`;
+
+// ============================================
+// INITIALISATION
+// ============================================
+
+/**
+ * Initialiser le système de licence
+ */
+async function initializeLicense() {
+ console.log('Git Pusher License System v' + LICENSE_CONFIG.version + ' (RSA)');
+
+ // Injecter les styles
+ if (!document.getElementById('license-styles')) {
+ const styleEl = document.createElement('div');
+ styleEl.id = 'license-styles';
+ styleEl.innerHTML = licenseStyles;
+ document.head.appendChild(styleEl);
+ }
+
+ // Vérifier si une licence existe dans localStorage
+ const stored = localStorage.getItem(LICENSE_CONFIG.storageKey);
+
+ if (!stored) {
+ console.log('Aucune licence en cache, tentative de chargement depuis le serveur...');
+
+ // Essayer de charger depuis le serveur
+ const loadedFromServer = await loadLicenseFromServer();
+
+ if (loadedFromServer) {
+ console.log('✓ Licence restaurée depuis le serveur');
+ } else {
+ console.log('Aucune licence disponible');
+ }
+ } else {
+ console.log('Licence trouvée dans le cache local');
+ }
+
+ // Mettre à jour le badge
+ updateLicenseBadge();
+}
+
+// Exposer les fonctions globalement
+window.initializeLicense = initializeLicense;
+window.validateLicense = validateLicense;
+window.saveLicense = saveLicense;
+window.removeLicense = removeLicense;
+window.loadLicenseFromServer = loadLicenseFromServer;
+window.checkLicenseBeforePush = checkLicenseBeforePush;
+window.showLicenseModal = showLicenseModal;
+window.closeLicenseModal = closeLicenseModal;
+window.showLicenseDetails = showLicenseDetails;
+window.confirmRemoveLicense = confirmRemoveLicense;
+window.updateLicenseBadge = updateLicenseBadge;
+window.hasFeature = hasFeature;
+window.incrementUsage = incrementUsage;
+window.getUsageStats = getUsageStats;
+window.getLicenseInfo = getLicenseInfo;
+
+// Auto-initialiser après le chargement
+if (document.readyState === 'complete') {
+ setTimeout(initializeLicense, 100);
+} else {
+ window.addEventListener('load', function() {
+ setTimeout(initializeLicense, 100);
+ });
+}
\ No newline at end of file
diff --git a/apps/pusher_app_prem/bin/__pycache__/license_validator.cpython-39.pyc b/apps/pusher_app_prem/bin/__pycache__/license_validator.cpython-39.pyc
index 759b4da7..33e0123e 100644
Binary files a/apps/pusher_app_prem/bin/__pycache__/license_validator.cpython-39.pyc and b/apps/pusher_app_prem/bin/__pycache__/license_validator.cpython-39.pyc differ
diff --git a/apps/pusher_app_prem/bin/credentials_manager.py b/apps/pusher_app_prem/bin/credentials_manager.py
old mode 100755
new mode 100644
diff --git a/apps/pusher_app_prem/bin/git_pusher.pid b/apps/pusher_app_prem/bin/git_pusher.pid
index 36d6e0a2..9fbde72b 100644
--- a/apps/pusher_app_prem/bin/git_pusher.pid
+++ b/apps/pusher_app_prem/bin/git_pusher.pid
@@ -1 +1 @@
-2001609
+3081636
diff --git a/apps/pusher_app_prem/bin/git_pusher.py b/apps/pusher_app_prem/bin/git_pusher.py
old mode 100755
new mode 100644
diff --git a/apps/pusher_app_prem/bin/license_endpoints.py b/apps/pusher_app_prem/bin/license_endpoints.py
old mode 100755
new mode 100644
diff --git a/apps/pusher_app_prem/bin/license_validator.py b/apps/pusher_app_prem/bin/license_validator.py
old mode 100755
new mode 100644
diff --git a/apps/pusher_app_prem/local/.usage_stats b/apps/pusher_app_prem/local/.usage_stats
deleted file mode 100644
index e029a484..00000000
--- a/apps/pusher_app_prem/local/.usage_stats
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "total_pushes": 21,
- "pushes_today": 6,
- "last_push_date": "2026-02-21",
- "apps_pushed": []
-}
\ No newline at end of file
diff --git a/apps/pusher_app_prem/local/certs/server.crt.bak b/apps/pusher_app_prem/local/certs/server.crt.bak
deleted file mode 100644
index 72166c19..00000000
--- a/apps/pusher_app_prem/local/certs/server.crt.bak
+++ /dev/null
@@ -1,29 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIFCzCCAvOgAwIBAgIUIkJeLpRn6wfN8VbsMrPdFAl/x7swDQYJKoZIhvcNAQEL
-BQAwFTETMBEGA1UEAwwKZ2l0LXB1c2hlcjAeFw0yNjAyMDExOTM1MzNaFw0yNzAy
-MDExOTM1MzNaMBUxEzARBgNVBAMMCmdpdC1wdXNoZXIwggIiMA0GCSqGSIb3DQEB
-AQUAA4ICDwAwggIKAoICAQDsTH5H+OD27yXIFz4MvNrpmpNgYly6QYUukJO8FI/f
-unBe4N5mfOTZ/WrBeR5KoBOS3FV8VWIH7ShXIzWBpollmIz9+jlPyftawXyiSnSX
-rwLBdHc8gjVRZf1H2U0E191v9z0oaXApy8d7E0Pw+i4odoGHOX5Ix89s0DJrG5UP
-LrLr9OHlyMCC0D2QVp0wqFGkIXXv/cyYwlcGPACvFhE/fWUazC4AEImJhXypOfQZ
-h57SoldKQWwW7BZdGmnbSqeG2lq7KFUow0sie4KzRPLPXrIGdKQbPAKDKcQ7MYlG
-9bfgsmM1Rr6klRAmO/e4w1HRHSHUetmFBYDJ5MYNdeddYDfIVwjgwfDvy1i+ojRl
-/viywW3ONXR9rUcx/nGGc8UJTjJAAaAaTj6UMzn40ltLYmiymhj6mWFNiuGwDxas
-YTxA/3i62pGlbC8s1ZSXtdCYsEN4+W3N5CSadHdVtAIIEc6OyQ2mRa3v4oeZlyzt
-1mk9j9oChxi5r74ujUmjNHxpawWG5wmnRy3b9ABxcivESEuXIbjBXrQ+RCmuQ8Dp
-5Asa7iede7iEPkhGw3Es3uVdk4s3/OlS6o+1F+rG9qZr3aTtJrciVSLofbrSNtFF
-MERip+VBwIdBofVzS6GUhnLjjdhfu/kdzsqrTDDFan4NX1IyC+zBL90DPQ8SoIOg
-owIDAQABo1MwUTAdBgNVHQ4EFgQUe6KibB+U4yTq9/pwf6C4JeoPQo0wHwYDVR0j
-BBgwFoAUe6KibB+U4yTq9/pwf6C4JeoPQo0wDwYDVR0TAQH/BAUwAwEB/zANBgkq
-hkiG9w0BAQsFAAOCAgEA12Y7N6iLOtRAt+xD3B5eIdJtPnFclhmy8FCd2+SdnF/K
-zWVdzTsWYKzZ4TZ08aMOh2+H2vDf/ZTG+gryCob28QVy0sRxbpA4WJMI6zBfZSKr
-Raudh5Gql/CO28gN4k6EGgNAn1S02NkvhZP4FokfXwkY4CIReRECJzws0gCyMZto
-wLfYEVyvN9NYQf1xoJHaozGghA0FADQrr6pQsl1Ek4bkrvb8/L8gWeT6lgny0ZcI
-1UEW9lkx7pc0dRDOjUjVzlHYzAf53xEslontihQcOoE/Uj3Wsmi1V/lPm+Pi0Ml0
-msMUkvMZP6Y8lnvRT4625YKR6L3ibuQxlXGtKHvu7eJdcOnW5IeVmg5vsH2xoVaF
-4L0zBPpA6CBzsfZPgLJ9odD10nRVx8bh0b555/CUP5WIVHvLkPgp+NptJAoKah7Q
-PkI5OKZWGjrJGiV7BWx5HIB5TX/w0PCjMA4ce3yVRNs7poUzR4PKgohrp94cIDFa
-m+K4rfBmeMwVuuJZIuYpye80dcdsiFPrIZuA+hbP7tGlrwBKKMzpB3BEDIFTPb2b
-hUq1TJCcHffFqxpRia+L/MMI8RpaEwzW9hghQB/LiPG0UqJycNpuo8cnggx+bsop
-0ZXB68cW6K1UWLo1qLSz7/0+65PrWRkJisbjtmpKRDJlB72yIxlsZmBucxBy3II=
------END CERTIFICATE-----
diff --git a/apps/pusher_app_prem/local/certs/server.key.bak b/apps/pusher_app_prem/local/certs/server.key.bak
deleted file mode 100644
index 465e812b..00000000
--- a/apps/pusher_app_prem/local/certs/server.key.bak
+++ /dev/null
@@ -1,52 +0,0 @@
------BEGIN PRIVATE KEY-----
-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
-eiDRzM4++dQGm7osCy1NRbPGxdUyhw==
------END PRIVATE KEY-----
diff --git a/apps/pusher_app_prem/local/usage_stats.json b/apps/pusher_app_prem/local/usage_stats.json
deleted file mode 100644
index bcf35e59..00000000
--- a/apps/pusher_app_prem/local/usage_stats.json
+++ /dev/null
@@ -1 +0,0 @@
-{"pushes_today": 1, "pushes_total": 47, "last_push_date": "2026-02-14", "apps_pushed": []}
\ No newline at end of file