FIX accounting export: enhance file metadata return and secure access checks (#39218)

Co-authored-by: gedeonts <gedeontshobowa1@gmail.com>
This commit is contained in:
Ushindi Gedeon 2026-07-20 02:50:31 +02:00 committed by GitHub
parent fa5fe7ebf8
commit 2fb564d438
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 59 additions and 3 deletions

View file

@ -78,7 +78,7 @@ class Accountancy extends DolibarrApi
* @param int $alreadyexport [=0] by default export data only if it's not yet exported or 1 already exported (always export data even if 'date_export" is set)
* @param int $notnotifiedasexport [=0] by default notified as exported or 1 not notified as exported (when the export is done, notified or not the column 'date_export')
*
* @return string
* @return array{modulepart:string,relative_path:string,filename:string,mimetype:string} Generated file metadata
*
* @url GET exportdata
*
@ -271,7 +271,24 @@ class Accountancy extends DolibarrApi
throw new RestException(500, 'Error accountancy export : '.implode(',', $accountancyexport->errors));
} else {
$this->db->commit();
exit();
$filedata = $accountancyexport->generatedfiledata;
if (empty($filedata['downloadFilePath']) || empty($filedata['downloadFileFullName'])) {
throw new RestException(500, 'Accounting export generated no downloadable file');
}
$outputdir = !empty($conf->accounting->multidir_output[$conf->entity]) ? $conf->accounting->multidir_output[$conf->entity] : $conf->accounting->dir_output;
$outputdir = rtrim($outputdir, '/').'/';
if (strpos($filedata['downloadFilePath'], $outputdir) !== 0) {
throw new RestException(500, 'Accounting export generated a file outside the accounting output directory');
}
return array(
'modulepart' => 'export_compta',
'relative_path' => substr($filedata['downloadFilePath'], strlen($outputdir)),
'filename' => basename($filedata['downloadFileFullName']),
'mimetype' => $filedata['downloadFileMimeType'],
);
}
}
}

View file

@ -3570,7 +3570,7 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity,
}
} elseif ($modulepart == 'export_compta' && !empty($conf->accounting->dir_output)) {
// Wrapping for accounting exports
if ($fuser->hasRight('accounting', 'bind', 'write') || preg_match('/^specimen/i', $original_file)) {
if ($fuser->hasRight('accounting', 'bind', 'write') || $fuser->hasRight('accounting', 'mouvements', 'export') || preg_match('/^specimen/i', $original_file)) {
$accessallowed = 1;
}
$original_file = $conf->accounting->dir_output.'/'.$original_file;

View file

@ -488,6 +488,45 @@ class FilesLibTest extends CommonClassTest
$user->rights->facture->creer = $savpermcreer;
}
/**
* Check that a user allowed to export the ledger can download the generated accounting export.
*
* @return void
*/
public function testDolCheckSecureAccessAccountingExport()
{
global $conf, $user, $langs, $db;
$conf = $this->savconf;
$user = $this->savuser;
$langs = $this->savlangs;
$db = $this->savdb;
$savpermbindwrite = $user->hasRight('accounting', 'bind', 'write');
$savpermexport = $user->hasRight('accounting', 'mouvements', 'export');
if (empty($user->rights->accounting)) {
$user->rights->accounting = new stdClass();
}
if (empty($user->rights->accounting->bind)) {
$user->rights->accounting->bind = new stdClass();
}
if (empty($user->rights->accounting->mouvements)) {
$user->rights->accounting->mouvements = new stdClass();
}
$user->rights->accounting->bind->write = 0;
$user->rights->accounting->mouvements->export = 1;
$result = dol_check_secure_access_document('export_compta', 'export/1/general_ledger.csv', 0, $user, '', 'read');
$this->assertEquals(1, $result['accessallowed']);
$user->rights->accounting->mouvements->export = 0;
$result = dol_check_secure_access_document('export_compta', 'export/1/general_ledger.csv', 0, $user, '', 'read');
$this->assertEquals(0, $result['accessallowed']);
$user->rights->accounting->bind->write = $savpermbindwrite;
$user->rights->accounting->mouvements->export = $savpermexport;
}
/**
* testDolDirMove
*