Dev control archive integrity

This commit is contained in:
Laurent Destailleur 2026-02-16 15:57:00 +01:00
parent adc251a053
commit db521258ea
4 changed files with 184 additions and 48 deletions

View file

@ -751,92 +751,222 @@ print "</div>\n";
if ($action == 'check' || $action == 'checkconfirmed') {
print '<br>';
print '<div class="formconsumeproduce paddingleft paddingright">';
print '<div class="formconsumeproduce">';
print '<b>'.$langs->trans("File").'</b> : '.GETPOST('urlfile').'<br>';
$fullpath = $upload_dir.'/'.GETPOST('urlfile');
$handle = fopen($fullpath, "r");
$line = fgets($handle);
fclose($handle);
$reg = array();
$period = '';
$formatexport = '';
if (preg_match('/\speriod=([^\s]+)/', $line, $reg)) {
$period = $reg[1]; // Get period on first line
}
if (preg_match('/\sregnumber=([^\s]+)/', $line, $reg)) {
$regnumber = str_replace(array('.', '…'), '', $reg[1]); // Get period on first line
}
if (preg_match('/\sformatexport=([^\s]+)/', $line, $reg)) {
$formatexport = $reg[1]; // Get export format (VE1, VE2...)
}
print '<b>'.$langs->trans("Period").'</b> : '.$period.'<br>';
print '<br>';
$registrationnumber = getHashUniqueIdOfRegistration();
$secretkey = $registrationnumber;
// Prepare to create a temporary file
$fullpathtmp = $upload_dir.'/tmp/'.GETPOST('urlfile').'.tmp';
dol_mkdir($upload_dir.'/tmp');
$result = dol_copy($fullpath, $fullpathtmp);
// Generate tmp file content without the last line
// TODO Move this into removeLastLine() function into files.lib.php
$fp = fopen($fullpathtmp, "r");
fseek($fp, -1, SEEK_END);
$pos = -1;
$char = fgetc($fp);
while ($char === "\n" || $char === "\r") { // Go to last real char of last line
fseek($fp, $pos--, SEEK_END);
$char = fgetc($fp);
}
while ($char !== "\n" && $char !== false) {
fseek($fp, $pos--, SEEK_END);
$char = fgetc($fp);
}
/*
while ($char === "\n" || $char === "\r") { // Go to last real char of last-1 line
fseek($fp, $pos--, SEEK_END);
$char = fgetc($fp);
}
*/
$truncatePos = ftell($fp);
fclose($fp);
// Truncate the tmp file to remove the last line
$fp = fopen($fullpathtmp, "c+");
ftruncate($fp, $truncatePos);
fclose($fp);
print $langs->trans("FileHasBeenEncodedWithASecretKeyStartingWith").' : '.$regnumber.'...<br>';
if (preg_match('/^'.$regnumber.'/', $secretkey)) {
print 'As this matches the 10 first characters of the full registration number of this instance, we will use this full registration number to control the archive file...';
print 'As this matches the 10 first characters of the full registration number of this instance, we will use the full registration number to control the archive file...';
} else {
print 'This archive file was not generated by this instance. The control of authenticity is possible only if you know the full registration number.';
print '<input type="text" name="inputregistrationnumber" placeholder="'.$langs->trans("FullRegistrationNumber").'">';
}
print '<br>';
print '<a class="button small nomarginleft" href="'.$_SERVER["PHP_SELF"].'?action=checkconfirmed&urlfile='.urlencode(GETPOST('urlfile')).'">'.$langs->trans("ControlFile").'</a>';
print '<br><br>';
print '<center><a class="button small nomarginleft" href="'.$_SERVER["PHP_SELF"].'?action=checkconfirmed&urlfile='.urlencode(GETPOST('urlfile')).'">'.$langs->trans("ControlFile").'</a></center>';
//<input type="text" name="inputregistrationnumber" placeholder="'.$langs->trans("RegistrationNumber").'">';
print '</div>';
if ($action == 'checkconfirmed') {
// TODO
print '<br><br>';
print '<b>File integrity</b>: '.img_picto('', 'tick').'<br>';
print '<b>File authenticity</b>: '.img_picto('', 'tick').'<br>';
print '<b>Detection of database restoration or not allowed line deletion in period</b>: This feature is available only from www.dolibarr.org/onlinecheckarchive.php<br>';
$totalamount = array(
'BILL_VALIDATE' => 0,
'PAYMENT_CUSTOMER_CREATE' => 0
);
$reg = array();
$refinvoicefound = array();
$recalculatedhashsign = '';
$recalculatedhashauth = '';
$hashsign = '';
$hashauth = '';
$algosign = '';
$algoauth = '';
foreach ($totalamount as $key => $totalamountperref) {
if ($key == 'BILL_VALIDATE' || $key == 'PAYMENT_CUSTOMER_CREATE') {
$totalhttoshow = 0;
foreach ($totalhtamount[$key] as $value) { // Loop on each module
$totalhttoshow += $value;
}
$totalvattoshow = 0;
foreach ($totalvatamount[$key] as $value) {
$totalvattoshow += $value;
}
$totaltoshow = 0;
foreach ($totalamountperref as $value) {
$totaltoshow += $value;
}
$handle = fopen($fullpath, "r");
if ($handle) {
$numline=0;
while ($line = fgetcsv($handle, 10000, ';', '"', '')) {
$numline++;
print '<b>'.dolPrintHTML($langs->trans("TotalForAction").' '.$langs->trans('log'.$key)).'</b>: ';
$linetech = $linerowid = $linedatecreation = $lineactioncode = $lineorigin = '';
$lineamountht = $lineamountttc = 0;
$lineref = $linedate = $lineuser = $linelinkto = $linelinktype = $linecontent = '';
$lineversiondoli = $lineversionsign = $linesignaturedb = $linesignatureexport = '';
if ($key == 'PAYMENT_CUSTOMER_CREATE') {
print price($totaltoshow);
if ($formatexport == 'VE1') {
$linetech = $line[0];
$linerowid = $line[1];
$linedatecreation = $line[2];
$lineactioncode = $line[3];
$lineorigin = $line[4];
$lineamountht = $line[5];
$lineamountttc = $line[6];
$lineref = $line[7];
$linedate = $line[8];
$lineuser = $line[9];
$linelinkto = $line[10];
$linelinktype = $line[11];
$linecontent = $line[12];
$lineversiondoli = $line[13];
$lineversionsign = $line[14];
$linesignaturedb = $line[15];
$linesignatureexport = $line[16];
} else {
print $langs->trans("HT").': ';
print price($totalhttoshow);
print ' - ';
print $langs->trans("VAT").': ';
print price($totalvattoshow);
print ' - ';
print $langs->trans("TTC").': ';
print price($totaltoshow);
print 'Line '.$numline.' has format '.$formatexport.' that is not supported';
}
if ($lineactioncode == 'BILL_VALIDATE' || $lineactioncode == 'PAYMENT_CUSTOMER_CREATE') {
// For action = BILL_VALIDATE, we keep only first invoice found, but this should not happen because edition of invoice is never possible on
// certified version and very difficult on other version.
if ($lineactioncode != 'BILL_VALIDATE' || empty($refinvoicefound[$lineref])) {
$totalhtamount[$lineactioncode] += $lineamountht;
$totalvatamount[$lineactioncode] += ($lineamountttc - $lineamountht);
$totalamount[$lineactioncode] += $lineamountttc;
}
if ($lineactioncode == 'BILL_VALIDATE') {
$refinvoicefound[$lineref] = 1;
}
}
if (preg_match('/END - ([a-z0-9_]+)=([a-z0-9]+) - ([a-z0-9_]+)=([a-z0-9]+)$/', $linetech, $reg)) {
$algosign=$reg[1];
$hashsign=$reg[2];
$algoauth=$reg[3];
$hashauth=$reg[4];
if ($algosign == 'sha256') {
$algo = 'sha256';
$recalculatedhashsign = hash_file($algo, $fullpathtmp);
}
if ($algoauth == 'hmac_sha256') {
$algo = 'sha256';
$recalculatedhashauth = hash_hmac_file($algo, $fullpathtmp, $secretkey);
}
}
print '<br>';
}
fclose($handle);
} else {
print 'Failed to open file '.GETPOST('urlfile');
}
print '<br><br>';
print '<b>'.$langs->trans("FileIntegrity").'</b>: ';
if ($recalculatedhashsign && $recalculatedhashsign == $hashsign) {
print img_picto('', 'tick', 'class="valignmiddle"');
print ' '.$form->textwithpicto('', $algosign.' = '.$recalculatedhashsign);
} else {
print img_picto('', 'cross', 'class="error valignmiddle"');
print ' '.$form->textwithpicto('', $langs->trans("FileHasBeenCorrupted").'<br>Recalculated '.$recalculatedhashsign.' != Found in file '.$hashsign);
}
print '<br><br>';
print '<b>'.$langs->trans("FileAuthenticity").'</b>: ';
if ($recalculatedhashauth && $recalculatedhashauth == $hashauth) {
print img_picto('', 'tick', 'class="valignmiddle"');
print ' - <span class="opacitymedium">'.$langs->trans("FileWasGeneratedByThisInstance").'</span>';
print ' '.$form->textwithpicto('', $algoauth.' = '.$recalculatedhashauth);
} elseif ($recalculatedhashsign == $hashsign) {
print img_picto('', 'cross', 'class="error valignmiddle"');
print ' '.$form->textwithpicto('', $langs->trans("FileNotFromInstance").'<br><br>Recalculated '.$recalculatedhashauth.' != Found in file '.$hashauth);
} else {
print img_picto('', 'cross', 'class="error valignmiddle"');
print ' '.$form->textwithpicto('', $langs->trans("FileHasBeenCorruptedOrNotFromInstance").'<br><br>Recalculated '.$recalculatedhashauth.' != Found in file '.$hashauth);
}
print '<br><br>';
print '<b>Detection of database restoration or not allowed line deletion in period</b>: ';
print 'This feature is for the moment available only from https://www.dolibarr.org/onlinecheckarchive.php<br>';
print '<br>';
$arraykeys = array('BILL_VALIDATE', 'PAYMENT_CUSTOMER_CREATE');
foreach ($arraykeys as $key) {
$totalhttoshow = $totalhtamount[$key];
$totalvattoshow = $totalvatamount[$key];
$totaltoshow = $totalamount[$key];
print '<b>'.dolPrintHTML($langs->trans("TotalForAction").' '.$langs->trans('log'.$key)).'</b>: ';
if ($key == 'PAYMENT_CUSTOMER_CREATE') {
print price($totaltoshow);
} else {
print $langs->trans("HT").': ';
print price($totalhttoshow);
print ' - ';
print $langs->trans("VAT").': ';
print price($totalvattoshow);
print ' - ';
print $langs->trans("TTC").': ';
print price($totaltoshow);
}
print '<br><br>';
}
}
print '</div>';
print '<br><br>';
print '<center><a href="'.$_SERVER["PHP_SELF"].'">'.$langs->trans("BackToList").'</a></center>';

View file

@ -30,8 +30,8 @@ ListOfTrackedEvents=List of tracked events
Fingerprint=Signature
FingerprintInBlockedLog=Signature in Unalterable Log table
FingerprintDatabase=Signature in database
FingerprintExport=Signature export
FingerprintExportHMAC=Signature export HMAC
FingerprintExport=Signature of export
FingerprintExportHMAC=Signature of export HMAC
DownloadLogCSV=Export unalterable logs (CSV)
DataOfArchivedEvent=Complete data of archived event
DataOfArchivedEventHelp=This field contains the complementary data that was archived on real time. Even if some parent business event could have been canceled or modified, the data stored here is the original data, and it can't be modified.
@ -89,7 +89,13 @@ MAIN_INFO_ITPROVIDER_TOWN=IT supplier town
RegistrationDoneAndModuleEnabled=The new registration information have been saved and registered. The module %s is enabled.
UserRegistration=User registration
RegistrationRequired=Registration expected
FileHasBeenEncodedWithASecretKeyStartingWith=The archive file content says it has been encoded with a secret key starting with
FileHasBeenEncodedWithASecretKeyStartingWith=The archive file content says it has been signed with a secret key starting with
FileIntegrity=File integrity
FileAuthenticity=File authenticity
FileWasGeneratedByThisInstance=File was generated by this instance
FileHasBeenCorrupted=The file integrity is wrong. It may have been altered (by a disk problem or manual corruption).
FileHasBeenCorruptedOrNotFromInstance=The file integrity/authenticity is wrong. File may have been altered (by a disk problem or a manual corruption.
FileNotFromInstance=The file integrity seems good but authenticity is wrong. File has not been generated by this instance.
## logTypes
logBILL_DELETE=Customer invoice logically deleted
@ -122,7 +128,7 @@ DebugInfo=Debug info
PreviousFingerprint=Previous signature
PaymentOf=Payment of
ReplacedBy=Replaced by
VersionSignature=Version signature
VersionSignature=Version of signature
DebugTools=Debug tools
URLToForceRegistration=URL to force the registration
URLToForcePushOfBlockedLogCounter=URL to force the push of the last unalterable record counter

View file

@ -883,7 +883,7 @@ input#onlinepaymenturl, input#directdownloadlink {
background: #efefef;
/* background: #f3f3f3; */
padding: 20px 0px 20px 0px;
padding: 20px 20px 20px 20px;
border-radius: 8px;
}

View file

@ -1046,7 +1046,7 @@ input#onlinepaymenturl, input#directdownloadlink {
.formconsumeproduce {
background: #f0f0f0;
padding: 20px 0px 20px 0px;
padding: 20px 20px 20px 20px;
border-radius: 8px;
}