N°10006 - Incorrect display of audit errors in iTOP (#1037)

* N°10006 - Incorrect display of audit errors in iTOP - fix export-v2 from audit

* N°10006 - Incorrect display of audit errors in iTOP - fix export-v2 from audit

* N°10006 - handle no class allowed in read corner case

* N°1006 - fix export-v2 with OQLRequest and without fields
This commit is contained in:
odain-cbd
2026-09-03 15:02:12 +02:00
committed by GitHub
parent da62814e67
commit ee17738e8e
2 changed files with 220 additions and 3 deletions

View File

@@ -325,7 +325,7 @@ EOF
$sQueryId = utils::ReadParam('query', null, true);
$sFields = utils::ReadParam('fields', null, true, 'raw_data');
if ((($sFields === null) || ($sFields === '')) && ($sQueryId === null)) {
throw new BulkExportMissingParameterException('fields');
$sFields = $this->GetFieldsFromQuery();
} else {
if (($sQueryId !== null) && ($sQueryId !== null)) {
$oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', ['query_id' => $sQueryId]);
@@ -336,11 +336,11 @@ EOF
// No 'fields' parameter supplied, take the fields from the query phrasebook definition
$sFields = trim($oQuery->Get('fields'));
if ($sFields === '') {
throw new BulkExportMissingParameterException('fields');
$sFields = $this->GetFieldsFromQuery();
}
}
} else {
throw BulkExportException('Invalid value for the parameter: query. There is no Query Phrasebook with id = '.$sQueryId, Dict::Format('Core:BulkExport:InvalidParameter_Query', $sQueryId));
throw new BulkExportException('Invalid value for the parameter: query. There is no Query Phrasebook with id = '.$sQueryId, Dict::Format('Core:BulkExport:InvalidParameter_Query', $sQueryId));
}
}
}
@@ -348,6 +348,39 @@ EOF
$this->SetFields($sFields);
}
public function GetFieldsFromQuery(): string
{
if (is_null($this->oSearch)) {
throw new BulkExportMissingParameterException("fields");
}
$aSelectedClasses = $this->oSearch->GetSelectedClasses();
$aAuthorizedClasses = [];
$aFields = [];
foreach ($aSelectedClasses as $sAlias => $sClassName) {
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_BULK_READ) == UR_ALLOWED_YES) {
$aAuthorizedClasses[$sAlias] = $sClassName;
}
}
foreach ($aAuthorizedClasses as $sAlias => $sClassName) {
foreach (MetaModel::GetZListItems($sClassName, 'details') as $sAttCode) {
//$oAttDef = Metamodel::GetAttributeDef($sClassName, $sAttCode);
if (utils::IsNullOrEmptyString($sAlias)) {
$aFields[] = "$sAttCode";
} else {
$aFields[] = "$sAlias.$sAttCode";
}
}
}
if (count($aFields) === 0) {
IssueLog::Error("User is not allowed to see any field for exported OQL");
throw new BulkExportMissingParameterException("fields");
}
return implode(',', $aFields);
}
public function SetFields($sFields)
{
// Interpret (and check) the list of fields

View File

@@ -0,0 +1,184 @@
<?php
namespace Combodo\iTop\Test\UnitTest\Webservices;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use MetaModel;
use QueryOQL;
class ExportTest extends ItopDataTestCase
{
public const USE_TRANSACTION = false;
private $sPassword = "abcDEF12345##";
private $sLogin;
private $sPortalLogin;
protected function setUp(): void
{
parent::setUp();
require_once(APPROOT.'application/startup.inc.php');
$sUid = date('dmYHis');
$this->sLogin = "import-".$sUid;
$this->CreateContactlessUser($this->sLogin, self::$aURP_Profiles['Administrator'], $this->sPassword);
$this->sPortalLogin = "import-portaluser-".$sUid;
$this->CreateContactlessUser($this->sPortalLogin, self::$aURP_Profiles['Portal user'], $this->sPassword);
}
public function testExportWithExpressionAndFields()
{
$aParams = [
'expression' => 'SELECT User',
'fields' => 'login',
'format' => 'csv',
'filename' => 'toto.csv',
'charset' => 'UTF-8',
];
$sOutput = $this->performExportTesting($aParams, $this->sLogin);
$sExpectedHeader = <<<HEADER
"Login"
HEADER;
$this->assertStringContainsString($sExpectedHeader, $sOutput, "Header ($sExpectedHeader)\n should be in export-v2 answer: \n$sOutput");
$this->assertStringContainsString($this->sLogin, $sOutput, "Login ({$this->sLogin})\n should be in export-v2 answer: \n$sOutput");
}
public function testExportWithExpressionAndWithoutFields()
{
$aParams = [
'expression' => 'SELECT User',
'format' => 'csv',
'filename' => 'toto.csv',
'charset' => 'UTF-8',
];
$sOutput = $this->performExportTesting($aParams, $this->sLogin);
$sExpectedHeader = <<<HEADER
"Person","Organization","Email","Login","Language","Status","Profiles","Allowed Organizations","log"
HEADER;
$this->assertStringContainsString($sExpectedHeader, $sOutput, "Header ($sExpectedHeader)\n should be in export-v2 answer: \n$sOutput");
$this->assertTrue(false !== strpos($sOutput, $this->sLogin), "Login ({$this->sLogin})\n should be in export-v2 answer: \n$sOutput");
}
public function testExportWithOQLRequestAndWithoutFields()
{
$oOuery = $this->createObject(
QueryOQL::class,
[
'name' => "TestExport-".uniqid(),
'description' => "blabla",
"oql" => "SELECT User",
]
);
$aParams = [
'query' => $oOuery->GetKey(),
'format' => 'spreadsheet',
'filename' => 'toto.csv',
'charset' => 'UTF-8',
];
$sOutput = $this->performExportTesting($aParams, $this->sLogin);
$sExpectedHeader = <<<HEADER
<td>Person</td>
<td>Organization</td>
<td>Email</td>
<td>Login</td>
<td>Language</td>
<td>Status</td>
<td>Profiles</td>
<td>Allowed Organizations</td>
<td>log</td>
HEADER;
$this->assertStringContainsString("$sExpectedHeader", $sOutput, "Header ($sExpectedHeader)\n should be in export-v2 answer: \n$sOutput");
$this->assertStringContainsString($this->sLogin, $sOutput, "Login ({$this->sLogin})\n should be in export-v2 answer: \n$sOutput");
}
public function testExportWithOqlRequestAndWithoutFieldsButNoReadRights()
{
$oOuery = $this->createObject(
QueryOQL::class,
[
'name' => "TestExport-".uniqid(),
'description' => "blabla",
"oql" => "SELECT User",
]
);
$aParams = [
'query' => $oOuery->GetKey(),
'format' => 'csv',
'filename' => 'toto.csv',
'charset' => 'UTF-8',
];
try {
$this->performExportTesting($aParams, $this->sPortalLogin);
} catch (\Exception $e) {
$this->assertStringContainsString("PHP Error (parsing, or runtime) ", $e->getMessage());
$this->assertStringContainsString("ERROR: Missing parameter: fields", $e->getMessage());
}
}
public function testExportWithOQLRequestAndFields()
{
$oOuery = $this->createObject(
QueryOQL::class,
[
'name' => "TestExport-".uniqid(),
'description' => "blabla",
"fields" => "login, status",
"oql" => "SELECT User",
]
);
$aParams = [
'query' => $oOuery->GetKey(),
'fields' => 'login',
'format' => 'spreadsheet',
'filename' => 'toto.csv',
'charset' => 'UTF-8',
];
$sOutput = $this->performExportTesting($aParams, $this->sLogin);
$sExpectedHeader = <<<HEADER
<td>Login</td>
HEADER;
$this->assertStringContainsString("$sExpectedHeader", $sOutput, "Header ($sExpectedHeader)\n should be in export-v2 answer: \n$sOutput");
$this->assertStringContainsString($this->sLogin, $sOutput, "Login ({$this->sLogin})\n should be in export-v2 answer: \n$sOutput");
}
public function testExportWithExpressionAndWithoutFieldsButNoReadRights()
{
$aParams = [
'expression' => 'SELECT User',
'format' => 'csv',
'filename' => 'toto.csv',
'charset' => 'UTF-8',
];
try {
$this->performExportTesting($aParams, $this->sPortalLogin);
} catch (\Exception $e) {
$this->assertStringContainsString("PHP Error (parsing, or runtime) ", $e->getMessage());
$this->assertStringContainsString("ERROR: Missing parameter: fields", $e->getMessage());
}
}
private function performExportTesting(array $aParams, $sLogin, $iExpectedExitCode = 0)
{
$aRes = \utils::ExecITopScript('webservices/export-v2.php', $aParams, $sLogin, $this->sPassword);
$aOutput = $aRes[1];
$sOutput = implode("\n", $aOutput);
$iRes = $aRes[0];
$this->assertEquals($iExpectedExitCode, $iRes, "exit code: $iRes | $sOutput");
return $sOutput;
}
}