// 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: '', port: 9999, useProxy: true } }; 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; } function getLicenseServerUrl() { const config = loadAppConfigForLicense(); const hostname = window.location.hostname; const protocol = window.location.protocol; if (config.api && config.api.url) { let url = config.api.url; if (!config.api.useProxy && config.api.port) { url = url.replace(/\/$/, '') + ':' + config.api.port; } return url; } 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'; } const LICENSE_CONFIG = { storageKey: 'git_pusher_license', usageKey: 'git_pusher_usage', version: '2.1.0', serverUrl: getLicenseServerUrl() }; 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; } 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; } } 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; } } 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); } } function getCurrentHostname() { return window.location.hostname.toLowerCase(); } async function getSplunkHostname() { try { 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 { 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); } console.warn('Impossible de récupérer le hostname Splunk, utilisation URL:', getCurrentHostname()); return getCurrentHostname(); } function daysRemaining(expiryDate) { const expiry = new Date(expiryDate); const now = new Date(); const diff = expiry - now; return Math.ceil(diff / (1000 * 60 * 60 * 24)); } 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' }; } const payloadJson = base64Decode(payloadB64); const payload = JSON.parse(payloadJson); if (!payload.license || !payload.signature) { return { error: 'Format de licence invalide' }; } 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' }; } } async function validateLicense(licenseContent = null) { try { 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; 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); 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'); const expectedHostname = (licenseData.hostname || '').toLowerCase(); const currentHostname = await getSplunkHostname(); console.log(`Hostname attendu: "${expectedHostname}", actuel: "${currentHostname}"`); if (expectedHostname && expectedHostname !== currentHostname) { 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)'); } 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)`); } 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' }; } } async function hasFeature(featureName) { const validation = await validateLicense(); if (!validation.valid) return false; return validation.features.includes(featureName); } async function saveLicense(licenseContent) { try { const parsed = parseLicenseFile(licenseContent); if (parsed.error) { return { success: false, error: parsed.error }; } const validation = await validateLicense(licenseContent); if (!validation.valid) { return { success: false, error: validation.error, error_code: validation.error_code }; } localStorage.setItem(LICENSE_CONFIG.storageKey, JSON.stringify(parsed)); console.log('✓ Licence sauvegardée dans localStorage'); 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); } } catch (serverError) { console.warn('⚠ Impossible de sauvegarder sur le serveur:', serverError); } return { success: true, license: validation }; } catch (error) { console.error('Erreur sauvegarde licence:', error); return { success: false, error: error.message }; } } 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...'); const parsed = parseLicenseFile(result.content); if (parsed.error) { console.warn('Erreur parsing licence serveur:', parsed.error); return false; } const validation = await validateLicense(result.content); if (validation.valid) { 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; } } async function removeLicense() { localStorage.removeItem(LICENSE_CONFIG.storageKey); localStorage.removeItem(LICENSE_CONFIG.usageKey); 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'); } 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; } } 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 }; } function incrementUsage() { const stats = getUsageStats(); const today = new Date().toISOString().split('T')[0]; 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; } 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]; 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 }; } 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; } function showLicenseModal(message = null, errorCode = null) { 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 = `

🔐 Activation de Licence

${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); getSplunkHostname().then(hostname => { const hostnameDisplay = document.getElementById('current-hostname-display'); if (hostnameDisplay) { hostnameDisplay.textContent = hostname; } }); setupLicenseUpload(); } function setupLicenseUpload() { const dropZone = document.getElementById('license-upload-zone'); const fileInput = document.getElementById('license-file-input'); if (!dropZone || !fileInput) return; dropZone.addEventListener('click', () => fileInput.click()); 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]); } }); fileInput.addEventListener('change', (e) => { if (e.target.files.length > 0) { handleLicenseFile(e.target.files[0]); } }); } async function handleLicenseFile(file) { const resultDiv = document.getElementById('license-validation-result'); if (!resultDiv) return; if (!file.name.endsWith('.lic')) { resultDiv.innerHTML = `
❌ Le fichier doit avoir l'extension .lic
`; return; } resultDiv.innerHTML = `
⏳ Validation en cours...
`; try { const content = await file.text(); 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'}
`; updateLicenseBadge(); setTimeout(() => { closeLicenseModal(); }, 3000); } else { resultDiv.innerHTML = `
❌ ${result.error}
`; } } catch (error) { resultDiv.innerHTML = `
❌ Erreur: ${error.message}
`; } } function closeLicenseModal() { const modal = document.getElementById('license-modal'); if (modal) modal.remove(); } async function showLicenseDetails() { const validation = await validateLicense(); if (!validation.valid) { showLicenseModal(validation.error, validation.error_code); return; } 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 = `

📋 Détails de la Licence

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); } function confirmRemoveLicense() { if (confirm('Êtes-vous sûr de vouloir supprimer cette licence ?')) { removeLicense(); const detailsModal = document.getElementById('license-details-modal'); if (detailsModal) detailsModal.remove(); updateLicenseBadge(); alert('Licence supprimée.'); } } async function checkLicenseBeforePush() { const result = await checkLimits(); if (!result.allowed) { showLicenseModal(result.error, result.error_code); return false; } return true; } const licenseStyles = ` `; async function initializeLicense() { console.log('Git Pusher License System v' + LICENSE_CONFIG.version + ' (RSA)'); if (!document.getElementById('license-styles')) { const styleEl = document.createElement('div'); styleEl.id = 'license-styles'; styleEl.innerHTML = licenseStyles; document.head.appendChild(styleEl); } const stored = localStorage.getItem(LICENSE_CONFIG.storageKey); if (!stored) { console.log('Aucune licence en cache, tentative de chargement 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'); } updateLicenseBadge(); } 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; if (document.readyState === 'complete') { setTimeout(initializeLicense, 100); } else { window.addEventListener('load', function() { setTimeout(initializeLicense, 100); }); }