Compare commits

...

24 Commits

Author SHA1 Message Date
odain
37351d6b3e N°3464: fix ci 2020-12-07 16:23:17 +01:00
odain
57a085eec1 N°3464: move fix in itop-fence + fix/enhance rest api test 2020-12-07 15:56:35 +01:00
odain
0019595923 N°3464: fix ci 2020-12-07 00:44:39 +01:00
odain
4d61c14f80 N°3464 add test in phpunit.xml.dit to validate the fix 2020-12-07 00:12:31 +01:00
odain
cf1b613923 N°3464 REST comment field not working anymore 2020-12-06 23:54:27 +01:00
Pierre Goiffon
1304e2eb2d N°3416 Updates after code review v2 :) 2020-12-04 08:51:07 +01:00
Pierre Goiffon
3cf16627c1 Merge remote-tracking branch 'origin/support/2.6' into support/2.7
# Conflicts:
#	pages/ajax.render.php
2020-12-03 18:18:36 +01:00
Pierre Goiffon
4aaa237bf9 🔖 Prepare 2.7.3 version 2020-12-03 18:15:58 +01:00
Pierre Goiffon
cece15d10c N°3416 Updates after code review
Many thanks @bruno-ds !
* add comments to explain intentions
* fix indentations
2020-12-03 17:45:44 +01:00
Pierre Goiffon
aa15e009cb 🔖 Prepare 2.7.2-2 version 2020-12-03 10:05:37 +01:00
Pierre Goiffon
b9ca2ac13d N°3416 Fix DocumentFile preview not working anymore
Was caused by X-Frame-Options http header added with N°3317

(cherry picked from commit 35d77ff642)

# Conflicts:
#	pages/ajax.render.php
2020-12-03 08:20:51 +01:00
Pierre Goiffon
80e1e0e61a N°3426 Fix no navigation menu on User object creation
Caused by a typo in js/forms-json-utils.js
Thanks @Molkobain !
2020-12-02 18:02:00 +01:00
Pierre Goiffon
ecebe4ecd5 N°3416 XFrame and cache headers optimizations
* Remove XFrame header set in \WebPage::no_cache : not this method responsability, was confusing :/
* Remove no_cache() calls when already set in page constructor (ajax_page mainly)
* Also calls everywhere the \WebPage::no_cache method instead of setting headers manually
2020-12-02 17:19:05 +01:00
Pierre Goiffon
8bfcb14d0c N°3416 XFrame-Options header is now set using a config parameter, defaults to SAMEORIGIN
Also adds an indirection (\WebPage::add_xframe_options) to set header
2020-12-02 17:17:11 +01:00
Molkobain
1cf1473d6b N°3469 - Fix variable declaration (let => var) 🤭 2020-12-02 17:01:00 +01:00
Molkobain
aa43425df3 N°3469 - Portal: Fix modal created without an ID 2020-12-02 16:59:39 +01:00
Pierre Goiffon
35d77ff642 N°3416 Fix DocumentFile preview not working anymore
Was caused by X-Frame-Options http header added with N°3317
2020-12-02 15:44:58 +01:00
acognet
539fa43503 N°3461 - Setup Broken with Chrome v87 2020-11-30 18:27:25 +01:00
acognet
eb537f45f4 N°3421 - Attributes of class Person are not accessible from :current_contact in portal anymore. Only attributes of class Contact are. 2020-11-30 09:24:35 +01:00
acognet
a2a4cd4e7a N°3426 - Wrong tab is displayed when a creation or modification form is invalidated 2020-11-27 15:20:20 +01:00
Pierre Goiffon
35215cf62f 🌐 Fix typo in comma (2 "m" !!) 2020-11-26 18:34:07 +01:00
Pierre Goiffon
66273ebd39 Merge remote-tracking branch 'origin/support/2.7.2' into support/2.7 2020-10-30 18:08:01 +01:00
Pierre Goiffon
512b415bd6 N°3065 add test case in comment 2020-10-30 11:30:22 +01:00
Pierre Goiffon
906c8855b0 🔊 When error during CoreUpdate, show full file path instead of only basename 2020-10-28 18:32:49 +01:00
36 changed files with 498 additions and 159 deletions

View File

@@ -41,10 +41,8 @@ class ajax_page extends WebPage implements iTabbedPage
parent::__construct($s_title, $bPrintable);
$this->m_sReadyScript = "";
//$this->add_header("Content-type: text/html; charset=utf-8");
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
$this->no_cache();
$this->add_xframe_options();
$this->m_oTabs = new TabManager();
$this->sContentType = 'text/html';
$this->sContentDisposition = 'inline';

View File

@@ -32,10 +32,8 @@ class CSVPage extends WebPage
function __construct($s_title) {
parent::__construct($s_title);
$this->add_header("Content-type: text/plain; charset=".self::PAGES_CHARSET);
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
$this->no_cache();
$this->add_xframe_options();
//$this->add_header("Content-Transfer-Encoding: binary");
}

View File

@@ -60,8 +60,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
// Create a breadcrumb entry for the current page, but get its title as late as possible (page title could be changed later)
$this->bBreadCrumbEnabled = true;
}
else
{
else {
$this->bBreadCrumbEnabled = false;
}
@@ -71,10 +70,8 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
$this->m_aMessages = array();
$this->SetRootUrl(utils::GetAbsoluteUrlAppRoot());
$this->add_header("Content-type: text/html; charset=".self::PAGES_CHARSET);
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
$this->no_cache();
$this->add_xframe_options();
$this->add_linked_stylesheet("../css/jquery.treeview.css");
$this->add_linked_stylesheet("../css/jquery.autocomplete.css");
$this->add_linked_stylesheet("../css/jquery-ui-timepicker-addon.css");

View File

@@ -84,10 +84,8 @@ class LoginWebPage extends NiceWebPage
parent::__construct($sTitle);
$this->SetStyleSheet();
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
$this->no_cache();
$this->add_xframe_options();
}
public function SetStyleSheet()

View File

@@ -482,6 +482,23 @@ class WebPage implements Page
$this->a_headers[] = $s_header;
}
/**
* @param string|null $sHeaderValue for example `SAMESITE`. If null will set the header using the config parameter value.
*
* @since 2.7.2-2 3.0.0 N°3416
* @uses security_header_xframe config parameter
* @uses \utils::GetConfig()
* @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
*/
public function add_xframe_options($sHeaderValue = null)
{
if (is_null($sHeaderValue)) {
$sHeaderValue = utils::GetConfig()->Get('security_header_xframe');
}
$this->add_header('X-Frame-Options: '.$sHeaderValue);
}
/**
* Add needed headers to the page so that it will no be cached
*/
@@ -490,7 +507,6 @@ class WebPage implements Page
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
}
/**

View File

@@ -43,10 +43,8 @@ class XMLPage extends WebPage
$this->m_bPassThrough = $bPassThrough;
$this->m_bHeaderSent = false;
$this->add_header("Content-type: text/xml; charset=".self::PAGES_CHARSET);
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
$this->no_cache();
$this->add_xframe_options();
$this->add_header("Content-location: export.xml");
}

View File

@@ -1249,6 +1249,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'security_header_xframe' => [
'type' => 'string',
'description' => 'Value of the X-Frame-Options HTTP header sent by iTop. Possible values : DENY, SAMEORIGIN, or empty string.',
'default' => 'SAMEORIGIN',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
],
);

View File

@@ -4166,11 +4166,7 @@ abstract class MetaModel
}
if (count($aCurrentUser) > 0)
{
$oSearch = DBObjectSearch::FromOQL("SELECT User WHERE id = :id");
$oSearch->AllowAllData();
$oSet = new DBObjectSet($oSearch, array(), array('id' => UserRights::GetUserId()));
$oSet->OptimizeColumnLoad($aCurrentUser);
$oUser = $oSet->fetch();
$oUser = MetaModel::GetObject("User", UserRights::GetUserId(),true,true);
$aPlaceholders['current_user->object()'] = $oUser;
foreach ($aCurrentUser as $sField)
{
@@ -4179,10 +4175,7 @@ abstract class MetaModel
}
if (count($aCurrentContact) > 0)
{
$oSearch = DBObjectSearch::FromOQL("SELECT Contact WHERE id = :id");
$oSet = new DBObjectSet($oSearch, array(), array('id' => UserRights::GetContactId()));
$oSet->OptimizeColumnLoad(['Contact' => $aCurrentContact]);
$oUser = $oSet->fetch();
$oUser = MetaModel::GetObject("Person", UserRights::GetContactId(),true,true);
foreach ($aCurrentContact as $sField)
{
$aPlaceholders['current_contact->'.$sField] = $oUser->Get($sField);

View File

@@ -199,8 +199,8 @@ EOF
// Integration within MS-Excel web queries + HTTPS + IIS:
// MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS
// Then the fix is to force the reset of header values Pragma and Cache-control
$oPage->add_header("Pragma:", true);
$oPage->add_header("Cache-control:", true);
$oPage->add_header("Pragma:");
$oPage->add_header("Cache-control:");
}
public function GetHeader()

View File

@@ -17,17 +17,17 @@
*/
// Beware the version number MUST be enclosed with quotes otherwise v2.3.0 becomes v2 0.3 .0
$version: "v2.7.2";
$version: "v2.7.3";
$approot-relative: "../../../../../" !default; // relative to env-***/branding/themes/***/main.css
// Base colors
$gray-base: #000 !default;
$gray-darker: lighten($gray-base, 13.5%) !default; // #222
$gray-dark: #444 !default;
$gray: #777 !default;
$gray-light: #808080 !default;
$gray-lighter: #ddd !default;
$gray-extra-light: #F1F1F1 !default;
$gray-base: #000 !default;
$gray-darker: lighten($gray-base, 13.5%) !default; // #222
$gray-dark: #444 !default;
$gray: #777 !default;
$gray-light: #808080 !default;
$gray-lighter: #ddd !default;
$gray-extra-light: #F1F1F1 !default;
$white: #FFFFFF !default;

View File

@@ -212,9 +212,9 @@ function DisplayInconsistenciesReport($aResults)
header('Content-Description: File Transfer');
header('Content-Type: multipart/x-zip');
header('Content-Disposition: inline; filename="'.basename($sZipReport).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Expires: 0');
header('Content-Length: '.filesize($sZipReport));
readfile($sZipReport);
unlink($sZipReport);

View File

@@ -61,7 +61,6 @@ try
LoginWebPage::DoLoginEx(null /* any portal */, false);
$oPage = new ajax_page("");
$oPage->no_cache();
$sOperation = utils::ReadParam('operation', '');

View File

@@ -51,7 +51,6 @@ function DisplayErrorAndDie($oPage, $sHtmlErrorMessage, $exitCode = null)
$sOperation = utils::ReadParam('operation', '');
$oPage = new ajax_page('');
$oPage->no_cache();
$oPage->SetContentType('text/html');

View File

@@ -103,7 +103,7 @@ class FilesIntegrity
$sChecksum = md5($sContent);
if (($iSize != $aFileInfo['size']) || ($sChecksum != $aFileInfo['md5']))
{
throw new FileIntegrityException(Dict::Format('FilesInformation:Error:CorruptedFile', basename($sFile)));
throw new FileIntegrityException(Dict::Format('FilesInformation:Error:CorruptedFile', $sFile));
}
}
// Packed with missing files...

View File

@@ -111,7 +111,6 @@ function DoBackup($sTargetFile)
function ReportStatus($sMessage, $bSuccess, $iErrorCode = 0, $aMoreFields = array())
{
$oPage = new ajax_page("");
$oPage->no_cache();
$oPage->SetContentType('application/json');
$aResult = array(
'code' => $iErrorCode,

View File

@@ -4,20 +4,18 @@ class HubConnectorPage extends NiceWebPage
{
public function __construct($sTitle)
{
parent::__construct($sTitle);
parent::__construct($sTitle);
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
$this->no_cache();
$this->add_xframe_options();
$sImagesDir = utils::GetAbsoluteUrlAppRoot().'images';
$sModuleImagesDir = utils::GetAbsoluteUrlModulesRoot().'itop-hub-connector/images';
$sUserPrefs = appUserPreferences::GetAsJSON();
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/utils.js');
$this->add_script(
<<<EOF
$sImagesDir = utils::GetAbsoluteUrlAppRoot().'images';
$sModuleImagesDir = utils::GetAbsoluteUrlModulesRoot().'itop-hub-connector/images';
$sUserPrefs = appUserPreferences::GetAsJSON();
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/utils.js');
$this->add_script(
<<<EOF
var oUserPreferences = $sUserPrefs;
EOF
);

View File

@@ -106,7 +106,11 @@ var CombodoPortalToolbox = {
if (oOptions.base_modal.usage === 'clone')
{
oModalElem = oSelectorElem.clone();
oModalElem.attr('id', oOptions.id)
// Force modal to have an HTML ID, otherwise it can lead to complications, especially with the portal_leave_handle.js
// See N°3469
var sModalID = (oOptions.id !== null) ? oOptions.id : 'modal-with-generated-id-'+Date.now();
oModalElem.attr('id', sModalID)
.appendTo('body');
}
// - Get an existing modal in the DOM

View File

@@ -89,7 +89,7 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:Query/Attribute:description' => 'Description',
'Class:Query/Attribute:description+' => 'Long description for the query (purpose, usage, etc.)',
'Class:QueryOQL/Attribute:fields' => 'Fields',
'Class:QueryOQL/Attribute:fields+' => 'Coma separated list of attributes (or alias.attribute) to export',
'Class:QueryOQL/Attribute:fields+' => 'Comma separated list of attributes (or alias.attribute) to export',
'Class:QueryOQL' => 'OQL Query',
'Class:QueryOQL+' => 'A query based on the Object Query Language',
'Class:QueryOQL/Attribute:oql' => 'Expression',

View File

@@ -72,7 +72,7 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
'Class:Query/Attribute:description' => 'Description~~',
'Class:Query/Attribute:description+' => 'Long description for the query (purpose, usage, etc.)~~',
'Class:QueryOQL/Attribute:fields' => 'Fields~~',
'Class:QueryOQL/Attribute:fields+' => 'Coma separated list of attributes (or alias.attribute) to export~~',
'Class:QueryOQL/Attribute:fields+' => 'Comma separated list of attributes (or alias.attribute) to export~~',
'Class:QueryOQL' => 'OQL Query~~',
'Class:QueryOQL+' => 'A query based on the Object Query Language~~',
'Class:QueryOQL/Attribute:oql' => 'Expression~~',

View File

@@ -71,7 +71,7 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', array(
'Class:Query/Attribute:description' => 'Popis',
'Class:Query/Attribute:description+' => '',
'Class:QueryOQL/Attribute:fields' => 'Polia',
'Class:QueryOQL/Attribute:fields+' => 'Coma separated list of attributes (or alias.attribute) to export~~',
'Class:QueryOQL/Attribute:fields+' => 'Comma separated list of attributes (or alias.attribute) to export~~',
'Class:QueryOQL' => 'OQL Dopyt',
'Class:QueryOQL+' => '',
'Class:QueryOQL/Attribute:oql' => 'Výraz',

View File

@@ -86,7 +86,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
'Class:Query/Attribute:description' => 'Description~~',
'Class:Query/Attribute:description+' => 'Long description for the query (purpose, usage, etc.)~~',
'Class:QueryOQL/Attribute:fields' => 'Fields~~',
'Class:QueryOQL/Attribute:fields+' => 'Coma separated list of attributes (or alias.attribute) to export~~',
'Class:QueryOQL/Attribute:fields+' => 'Comma separated list of attributes (or alias.attribute) to export~~',
'Class:QueryOQL' => 'OQL Query~~',
'Class:QueryOQL+' => 'A query based on the Object Query Language~~',
'Class:QueryOQL/Attribute:oql' => 'Expression~~',

View File

@@ -192,7 +192,7 @@ function activateFirstTabWithError(sFormId) {
if ($fieldsWithError.length > 0)
{
$tabsContainer.tabs("option", "active", index);
return;
return false;
}
});
}

View File

@@ -242,7 +242,6 @@ try
{
case 'parser_preview':
$oPage = new ajax_page("");
$oPage->no_cache();
$oPage->SetContentType('text/html');
$sSeparator = utils::ReadParam('separator', ',', false, 'raw_data');
if ($sSeparator == 'tab') $sSeparator = "\t";

View File

@@ -38,7 +38,6 @@ try
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
$oPage = new ajax_page("");
$oPage->no_cache();
$operation = utils::ReadParam('operation', '');
$sClass = utils::ReadParam('class', 'MissingAjaxParam', false, 'class');
@@ -62,9 +61,14 @@ try
ormDocument::DownloadDocument($oPage, $sClass, $id, $sField, 'attachment');
if ($iCacheSec > 0)
{
$oPage->add_header("Expires: "); // Reset the value set in ajax_page
$oPage->add_header("Cache-Control: no-transform,public,max-age=$iCacheSec,s-maxage=$iCacheSec");
$oPage->add_header("Pragma: cache"); // Reset the value set .... where ?
$oPage->add_header("Expires: "); // Reset the value set in ajax_page
// X-Frame http header : set in page constructor, but we need to allow frame integration for this specific page
// so we're resetting its value ! (see N°3416)
$oPage->add_xframe_options('');
$oPage->add_header("Last-Modified: Wed, 15 Jun 2015 13:21:15 GMT"); // An arbitrary date in the past is ok
}
}
@@ -76,12 +80,16 @@ try
$id = utils::ReadParam('id', '');
$sSecret = utils::ReadParam('s', '');
$iCacheSec = 31556926; // One year ahead: an inline image cannot change
if (!empty($id) && !empty($sSecret))
{
if (!empty($id) && !empty($sSecret)) {
ormDocument::DownloadDocument($oPage, 'InlineImage', $id, 'contents', 'inline', 'secret', $sSecret);
$oPage->add_header("Expires: "); // Reset the value set in ajax_page
$oPage->add_header("Cache-Control: no-transform,public,max-age=$iCacheSec,s-maxage=$iCacheSec");
$oPage->add_header("Pragma: cache"); // Reset the value set .... where ?
$oPage->add_header("Expires: "); // Reset the value set in ajax_page
// X-Frame http header : set in page constructor, but we need to allow frame integration for this specific page
// so we're resetting its value ! (see N°3416)
$oPage->add_xframe_options('');
$oPage->add_header("Last-Modified: Wed, 15 Jun 2016 13:21:15 GMT"); // An arbitrary date in the past is ok
}
break;
@@ -92,6 +100,11 @@ try
$oPage->SetContentType('text/javascript');
$oPage->add_header('Cache-control: public, max-age=86400'); // Cache for 24 hours
$oPage->add_header("Pragma: cache"); // Reset the value set .... where ?
// X-Frame http header : set in page constructor, but we need to allow frame integration for this specific page
// so we're resetting its value ! (see N°3416)
$oPage->add_xframe_options('');
$oPage->add(file_get_contents(Utils::GetCachePath().$sSignature.'.js'));
break;

View File

@@ -68,8 +68,6 @@ try
LoginWebPage::DoLoginEx($sRequestedPortalId, false);
$oPage = new ajax_page("");
$oPage->no_cache();
$sFilter = utils::ReadParam('filter', '', false, 'raw_data');
$sEncoding = utils::ReadParam('encoding', 'serialize');
@@ -889,13 +887,12 @@ try
case 'chart':
// Workaround for IE8 + IIS + HTTPS
// See TRAC #363, fix described here: http://forums.codecharge.com/posts.php?post_id=97771
$oPage->add_header("Expires: Fri, 17 Jul 1970 05:00:00 GMT");
$oPage->add_header("Cache-Control: cache, must-revalidate");
$oPage->add_header("Pragma: public");
$oPage->add_header("Expires: Fri, 17 Jul 1970 05:00:00 GMT");
$aParams = utils::ReadParam('params', array(), false, 'raw_data');
if ($sFilter != '')
{
if ($sFilter != '') {
$oFilter = DBSearch::unserialize($sFilter);
$oKPI = new ExecutionKPI();
$oDisplayBlock = new DisplayBlock($oFilter, 'chart_ajax', false);
@@ -961,6 +958,11 @@ try
if (!empty($sClass) && ($sClass != 'InlineImage') && !empty($id) && !empty($sField))
{
$oKPI = new ExecutionKPI();
// X-Frame http header : set in page constructor, but we need to allow frame integration for this specific page
// so we're resetting its value ! (see N°3416)
$oPage->add_xframe_options('');
ormDocument::DownloadDocument($oPage, $sClass, $id, $sField, 'inline');
$oKPI->ComputeAndReport('Data fetch and format');
}

View File

@@ -49,7 +49,6 @@ try
}
$oPage = new ajax_page("");
$oPage->no_cache();
$oPage->SetContentType('text/html');
$sListParams = utils::ReadParam('list_params', '{}', false, 'raw_data');

View File

@@ -33,10 +33,14 @@ LoginWebPage::DoLogin(true); // Check user rights and prompt if needed (must be
$sOperation = Utils::ReadParam('operation', 'step1');
$oP = new SetupPage('iTop email test utility');
// Although this page doesn't expose sensitive info, with it we can send multiple emails
// So we're adding this http header to reduce CSRF exposure...
$oP->add_xframe_options('DENY');
/**
* Helper to check server setting required to send an email
*/
*/
function CheckEmailSetting($oP)
{
$bRet = true;
@@ -255,11 +259,11 @@ try
break;
case 'step2':
$oP->no_cache();
$sTo = Utils::ReadParam('to', '', false, 'raw_data');
$sFrom = Utils::ReadParam('from', '', false, 'raw_data');
DisplayStep2($oP, $sFrom, $sTo);
break;
$oP->no_cache();
$sTo = Utils::ReadParam('to', '', false, 'raw_data');
$sFrom = Utils::ReadParam('from', '', false, 'raw_data');
DisplayStep2($oP, $sFrom, $sTo);
break;
default:
$oP->error("Error: unsupported operation '$sOperation'");

View File

@@ -716,7 +716,7 @@ class WizStepLicense extends WizardStep
$aLicenses = SetupUtils::GetLicenses();
$oPage->add_style(
<<<EOF
fieldset {
fieldset ul{
max-height: 18em;
overflow: auto;
}

View File

@@ -435,18 +435,16 @@ abstract class Controller
$sFileMimeType = utils::GetFileMimeType($sFilePath);
header('Content-Type: '.$sFileMimeType);
if ($bFileTransfer)
{
if ($bFileTransfer) {
header('Content-Description: File Transfer');
header('Content-Disposition: inline; filename="'.$sDownloadArchiveName);
}
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Expires: 0');
foreach ($aHeaders as $sKey => $sValue)
{
foreach ($aHeaders as $sKey => $sValue) {
header($sKey.': '.$sValue);
}
@@ -558,7 +556,7 @@ abstract class Controller
{
case 'html':
$this->m_oPage = new iTopWebPage($this->GetOperationTitle());
$this->m_oPage->add_header('X-Frame-Options: deny');
$this->m_oPage->add_xframe_options();
break;
case 'ajax':

View File

@@ -73,7 +73,7 @@ class ItopDataTestCase extends ItopTestCase
protected function setUp()
{
parent::setUp();
//require_once(APPROOT.'/application/startup.inc.php');
require_once(APPROOT.'/application/startup.inc.php');
require_once(APPROOT.'application/utils.inc.php');
@@ -408,8 +408,12 @@ class ItopDataTestCase extends ItopTestCase
* @return \DBObject
* @throws Exception
*/
protected function CreateUser($sLogin, $iProfileId)
protected function CreateUser($sLogin, $iProfileId, $sPassword=null)
{
if (empty($sPassword)){
$sPassword = $sLogin;
}
$oUserProfile = new URP_UserProfile();
$oUserProfile->Set('profileid', $iProfileId);
$oUserProfile->Set('reason', 'UNIT Tests');
@@ -417,7 +421,7 @@ class ItopDataTestCase extends ItopTestCase
$oUser = $this->createObject('UserLocal', array(
'contactid' => 2,
'login' => $sLogin,
'password' => $sLogin,
'password' => $sPassword,
'language' => 'EN US',
'profile_list' => $oSet,
));
@@ -426,6 +430,29 @@ class ItopDataTestCase extends ItopTestCase
return $oUser;
}
/**
* @param \DBObject $oUser
* @param int $iProfileId
*
* @return \DBObject
* @throws Exception
*/
protected function AddProfileToUser($oUser, $iProfileId)
{
$oUserProfile = new URP_UserProfile();
$oUserProfile->Set('profileid', $iProfileId);
$oUserProfile->Set('reason', 'UNIT Tests');
/** @var DBObjectSet $oSet */
$oSet = $oUser->Get('profile_list');
$oSet->AddObject($oUserProfile);
$oUser = $this->updateObject('UserLocal', $oUser->GetKey(), array(
'profile_list' => $oSet,
));
$this->debug("Updated {$oUser->GetName()} ({$oUser->GetKey()})");
return $oUser;
}
/**
* Create a Hypervisor in database

View File

@@ -781,15 +781,16 @@ try
break;
case 'create_structure':
$oP->no_cache();
$iPlannedContacts = Utils::ReadParam('plannedcontacts');
$iPlannedContracts = Utils::ReadParam('plannedcontracts');
$oP->no_cache();
$oP->add_xframe_options('DENY');
$iPlannedContacts = Utils::ReadParam('plannedcontacts');
$iPlannedContracts = Utils::ReadParam('plannedcontracts');
$oDataCreation = new BenchmarkDataCreation();
$oDataCreation->PlanStructure($iPlannedContacts, $iPlannedContracts);
$oDataCreation->ShowPlans($oP);
$oDataCreation->ShowForm($oP, 'create_structure_go');
break;
$oDataCreation = new BenchmarkDataCreation();
$oDataCreation->PlanStructure($iPlannedContacts, $iPlannedContracts);
$oDataCreation->ShowPlans($oP);
$oDataCreation->ShowForm($oP, 'create_structure_go');
break;
case 'create_structure_go':
$oP->no_cache();

View File

@@ -102,10 +102,16 @@ class CMDBSourceTest extends ItopTestCase
"enum('1','2','3') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '1'",
),
'ENUM with values containing parenthesis' => array(
true,
true, // see N°3065 : if having distinct values having parenthesis in enum values will cause comparison to be inexact
"ENUM('CSP A','CSP M','NA','OEM(ROC)','OPEN(VL)','RETAIL (Boite)') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci",
"enum('CSP A','CSP M','NA','OEM(ROC)','OPEN(VL)','RETAIL (Boite)') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci",
),
//FIXME N°3065 before the fix this returns true :(
// 'ENUM with different values, containing parenthesis' => array(
// false,
// "ENUM('value 1 (with parenthesis)','value 2') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci",
// "enum('value 1 (with parenthesis)','value 3') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci",
// ),
);
}
}

View File

@@ -47,6 +47,9 @@
<testsuite name="Core">
<directory>core</directory>
</testsuite>
<testsuite name="Webservices">
<directory>webservices</directory>
</testsuite>
<testsuite name="Tickets">
<directory>itop-tickets</directory>
</testsuite>

View File

@@ -0,0 +1,281 @@
<?php
namespace Combodo\iTop\Test\UnitTest\Webservices;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use Exception;
/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
*/
class RestTest extends ItopDataTestCase
{
const USE_TRANSACTION = false;
private $sTmpFile = "";
private $bPassJsonDataAsFile = false;
private $sUrl;
private $sLogin;
private $sPassword = "Iuytrez9876543ç_è-(";
/**
* @throws Exception
*/
protected function setUp()
{
parent::setUp();
require_once(APPROOT.'application/startup.inc.php');
$this->sLogin = "rest-user-" . date('dmYHis');
$this->CreateTestOrganization();
if (!empty($this->sTmpFile)){
unlink($this->sTmpFile);
}
$sConfigFile = \utils::GetConfig()->GetLoadedFile();
@chmod($sConfigFile, 0770);
$this->sUrl = \MetaModel::GetConfig()->Get('app_root_url');
@chmod($sConfigFile, 0444); // Read-only
$oRestProfile = \MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => 'REST Services User'), true);
$oAdminProfile = \MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => 'Administrator'), true);
if (is_object($oRestProfile) && is_object($oAdminProfile))
{
$oUser = $this->CreateUser($this->sLogin, $oRestProfile->GetKey(), $this->sPassword);
$this->AddProfileToUser($oUser, $oAdminProfile->GetKey());
}
}
/**
* @dataProvider BasicProvider
* @param bool $bPassJsonDataAsFile
*/
public function testCreateApi($bPassJsonDataAsFile)
{
$this->bPassJsonDataAsFile = $bPassJsonDataAsFile;
//create ticket
$description = date('dmY H:i:s');
$sOuputJson = $this->CreateTicketViaApi($description);
$aJson = json_decode($sOuputJson, true);
$this->assertContains("0", "".$aJson['code'], $sOuputJson);
$sUserRequestKey = $this->array_key_first($aJson['objects']);
$this->assertContains('UserRequest::', $sUserRequestKey);
$iId = $aJson['objects'][$sUserRequestKey]['key'];
$sExpectedJsonOuput=<<<JSON
{"objects":{"UserRequest::$iId":{"code":0,"message":"created","class":"UserRequest","key":"$iId","fields":{"id":"$iId"}}},"code":0,"message":null}
JSON;
$this->assertEquals($sExpectedJsonOuput, $sOuputJson);
$sExpectedJsonOuput=<<<JSON
{"objects":{"UserRequest::$iId":{"code":0,"message":"","class":"UserRequest","key":"$iId","fields":{"id":"$iId","description":"<p>$description<\/p>"}}},"code":0,"message":"Found: 1"}
JSON;
$this->assertEquals($sExpectedJsonOuput, $this->GetTicketViaRest($iId));
$aCmdbChangeUserInfo = $this->GetCmdbChangeUserInfo($iId);
$this->assertEquals(['CMDBChangeOpCreate' => 'test'], $aCmdbChangeUserInfo);
//delete ticket
$this->DeleteTicketFromApi($iId);
}
/**
* array_key_first comes with PHP7.3
* itop should also work with previous PHP versions
*/
private function array_key_first($aTab){
if (!is_array($aTab) || empty($aTab)){
return false;
}
foreach ($aTab as $sKey => $sVal){
return $sKey;
}
}
/**
* @dataProvider BasicProvider
* @param bool $bPassJsonDataAsFile
*/
public function testUpdateApi($bPassJsonDataAsFile)
{
$this->bPassJsonDataAsFile = $bPassJsonDataAsFile;
//create ticket
$description = date('dmY H:i:s');
$sOuputJson = $this->CreateTicketViaApi($description);
$aJson = json_decode($sOuputJson, true);
$this->assertContains("0", "".$aJson['code'], $sOuputJson);
$sUserRequestKey = $this->array_key_first($aJson['objects']);
$this->assertContains('UserRequest::', $sUserRequestKey);
$iId = $aJson['objects'][$sUserRequestKey]['key'];
//update ticket
$description = date('Ymd H:i:s');
$sExpectedJsonOuput=<<<JSON
{"objects":{"UserRequest::$iId":{"code":0,"message":"updated","class":"UserRequest","key":"$iId","fields":{"description":"<p>$description<\/p>"}}},"code":0,"message":null}
JSON;
$this->assertEquals($sExpectedJsonOuput, $this->UpdateTicketViaApi($iId, $description));
$aCmdbChangeUserInfo = $this->GetCmdbChangeUserInfo($iId);
$this->assertEquals(['CMDBChangeOpCreate' => 'test', 'CMDBChangeOpSetAttributeHTML' => 'test'], $aCmdbChangeUserInfo);
//delete ticket
$this->DeleteTicketFromApi($iId);
}
/**
* @dataProvider BasicProvider
* @param bool $bPassJsonDataAsFile
*/
public function testDeleteApi($bPassJsonDataAsFile)
{
$this->bPassJsonDataAsFile = $bPassJsonDataAsFile;
//create ticket
$description = date('dmY H:i:s');
$sOuputJson = $this->CreateTicketViaApi($description);
$aJson = json_decode($sOuputJson, true);
$this->assertContains("0", "".$aJson['code'], $sOuputJson);
$sUserRequestKey = $this->array_key_first($aJson['objects']);
$this->assertContains('UserRequest::', $sUserRequestKey);
$iId = $aJson['objects'][$sUserRequestKey]['key'];
//delete ticket
$sExpectedJsonOuput=<<<JSON
{"objects":{"UserRequest::$iId"
JSON;
$this->assertContains($sExpectedJsonOuput, $this->DeleteTicketFromApi($iId));
$sExpectedJsonOuput=<<<JSON
{"objects":null,"code":0,"message":"Found: 0"}
JSON;
$this->assertEquals($sExpectedJsonOuput, $this->GetTicketViaRest($iId));
}
private function GetTicketViaRest($iId){
$sJsonGetContent = <<<JSON
{
"operation": "core/get",
"class": "UserRequest",
"key": "SELECT UserRequest WHERE id=$iId",
"output_fields": "id, description"
}
JSON;
return $this->CallRestApi($sJsonGetContent);
}
public function BasicProvider(){
return [
'call rest call' => [ 'bCallApiViaFile' => false],
//'pass json_data as file' => [ 'bCallApiViaFile' => true]
];
}
private function UpdateTicketViaApi($iId, $description){
$sJsonUpdateContent = <<<JSON
{"operation": "core/update","comment": "test","class": "UserRequest","key":"$iId","output_fields": "description","fields":{"description": "$description"}}
JSON;
return $this->CallRestApi($sJsonUpdateContent);
}
private function CreateTicketViaApi($description){
$sJsonCreateContent = <<<JSON
{
"operation": "core/create",
"comment": "test",
"class": "UserRequest",
"output_fields": "id",
"fields":
{
"org_id": "SELECT Organization WHERE name = \"Demo\"",
"title": "Houston, got a problem",
"description": "$description"
}
}
JSON;
return $this->CallRestApi($sJsonCreateContent);
}
private function DeleteTicketFromApi($iId){
$sJson = <<<JSON
{
"operation": "core/delete",
"comment": "Cleanup",
"class": "UserRequest",
"key":$iId,
"simulate": false
}
JSON;
return $this->CallRestApi($sJson);
}
private function CallRestApi($sJsonDataContent){
$ch = curl_init();
$aPostFields = [
'version' => '1.3',
'auth_user' => $this->sLogin,
'auth_pwd' => $this->sPassword,
];
if ($this->bPassJsonDataAsFile){
$this->sTmpFile = tempnam(sys_get_temp_dir(), 'jsondata_');
file_put_contents($this->sTmpFile, $sJsonDataContent);
$oCurlFile = curl_file_create($this->sTmpFile);
$aPostFields['json_data'] = $oCurlFile;
}else{
$aPostFields['json_data'] = $sJsonDataContent;
}
curl_setopt($ch, CURLOPT_URL, "$this->sUrl/webservices/rest.php");
curl_setopt($ch, CURLOPT_POST, 1);// set post data to true
curl_setopt($ch, CURLOPT_POSTFIELDS, $aPostFields);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$sJson = curl_exec($ch);
curl_close ($ch);
return $sJson;
}
/**
* @param $iId
* Get CMDBChangeOp info to test
* @return array
*/
private function GetCmdbChangeUserInfo($iId){
$sJsonGetContent = <<<JSON
{
"operation": "core/get",
"class": "CMDBChangeOp",
"key": "SELECT CMDBChangeOp WHERE objclass='UserRequest' AND objkey=$iId",
"output_fields": "userinfo"
}
JSON;
$aUserInfo = [];
$sOutput = $this->CallRestApi($sJsonGetContent);
$aJson = json_decode($sOutput, true);
if (is_array($aJson) && array_key_exists('objects', $aJson)){
$aObjects = $aJson['objects'];
if (!empty($aObjects)){
foreach ($aObjects as $aObject){
$sClass = $aObject['class'];
$sUserInfo = $aObject['fields']['userinfo'];
$aUserInfo[$sClass] = $sUserInfo;
}
}
}
return $aUserInfo;
}
}

View File

@@ -44,7 +44,7 @@ function ReportErrorAndExit($sErrorMessage)
else
{
$oP = new WebPage("iTop - Export");
$oP->add_header('X-Frame-Options: deny');
$oP->add_xframe_options();
$oP->p('ERROR: '.$sErrorMessage);
$oP->output();
exit(-1);
@@ -61,10 +61,9 @@ function ReportErrorAndUsage($sErrorMessage)
$oP->output();
exit(-1);
}
else
{
else {
$oP = new WebPage("iTop - Export");
$oP->add_header('X-Frame-Options: deny');
$oP->add_xframe_options();
$oP->p('ERROR: '.$sErrorMessage);
Usage($oP);
$oP->output();
@@ -728,19 +727,17 @@ try
if ($sMimeType == 'text/html')
{
// Note: Using NiceWebPage only for HTML export as it includes JS scripts & files, which makes no sense in other export formats. More over, it breaks Excel spreadsheet import.
if($oExporter instanceof HTMLBulkExport)
{
if($oExporter instanceof HTMLBulkExport) {
$oP = new NiceWebPage('iTop export');
$oP->add_header('X-Frame-Options: deny');
$oP->add_xframe_options();
$oP->add_ready_script("$('table.listResults').tablesorter({widgets: ['MyZebra']});");
$oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/all.min.css');
$oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/v4-shims.min.css');
}
else
{
else {
$oP = new WebPage('iTop export');
$oP->add_header('X-Frame-Options: deny');
$oP->add_style("table br { mso-data-placement:same-cell; }"); // Trick for Excel: keep line breaks inside the same cell !
$oP->add_xframe_options();
$oP->add_style("table br { mso-data-placement:same-cell; }"); // Trick for Excel: keep line breaks inside the same cell !
}
$oP->add_style("body { overflow: auto; }");
}
@@ -760,10 +757,9 @@ catch (BulkExportMissingParameterException $e)
Usage($oP);
$oP->output();
}
catch (Exception $e)
{
catch (Exception $e) {
$oP = new WebPage('iTop Export');
$oP->add_header('X-Frame-Options: deny');
$oP->add_xframe_options();
$oP->add('Error: '.$e->getMessage());
IssueLog::Error($e->getMessage()."\n".$e->getTraceAsString());
$oP->output();

View File

@@ -189,47 +189,55 @@ if (!empty($sExpression))
switch($sFormat)
{
case 'html':
$oP = new NiceWebPage("iTop - Export");
$oP->add_style('body { overflow: auto; }'); // Show scroll bars if needed
$oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/all.min.css');
$oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/v4-shims.min.css');
// Integration within MS-Excel web queries + HTTPS + IIS:
// MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS
// Then the fix is to force the reset of header values Pragma and Cache-control
header("Pragma:", true);
header("Cache-control:", true);
$oP = new NiceWebPage("iTop - Export");
$oP->add_style('body { overflow: auto; }'); // Show scroll bars if needed
$oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/all.min.css');
$oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/v4-shims.min.css');
// The HTML output is made for pages located in the /pages/ folder
// since this page is in a different folder, let's adjust the HTML 'base' attribute
// to make the relative hyperlinks in the page work
$sUrl = utils::GetAbsoluteUrlAppRoot();
$oP->set_base($sUrl.'pages/');
// Integration within MS-Excel web queries + HTTPS + IIS:
// MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS
// Then the fix is to force the reset of header values Pragma and Cache-control
header("Cache-control:", true);
header("Pragma:", true);
if(count($aFields) > 0)
{
$iSearch = array_search('id', $aFields);
if ($iSearch !== false)
{
$bViewLink = true;
unset($aFields[$iSearch]);
// The HTML output is made for pages located in the /pages/ folder
// since this page is in a different folder, let's adjust the HTML 'base' attribute
// to make the relative hyperlinks in the page work
$sUrl = utils::GetAbsoluteUrlAppRoot();
$oP->set_base($sUrl.'pages/');
if (count($aFields) > 0) {
$iSearch = array_search('id', $aFields);
if ($iSearch !== false) {
$bViewLink = true;
unset($aFields[$iSearch]);
} else {
$bViewLink = false;
}
$sFields = implode(',', $aFields);
$aExtraParams = array(
'menu' => false,
'toolkit_menu' => false,
'display_limit' => false,
'localize_values' => $bLocalize,
'zlist' => false,
'extra_fields' => $sFields,
'view_link' => $bViewLink,
);
} else {
$aExtraParams = array(
'menu' => false,
'toolkit_menu' => false,
'display_limit' => false,
'localize_values' => $bLocalize,
'zlist' => 'details',
);
}
else
{
$bViewLink = false;
}
$sFields = implode(',', $aFields);
$aExtraParams = array('menu' => false, 'toolkit_menu' => false, 'display_limit' => false, 'localize_values' => $bLocalize, 'zlist' => false, 'extra_fields' => $sFields, 'view_link' => $bViewLink);
}
else
{
$aExtraParams = array('menu' => false, 'toolkit_menu' => false, 'display_limit' => false, 'localize_values' => $bLocalize, 'zlist' => 'details');
}
$oResultBlock = new DisplayBlock($oFilter, 'list', false, $aExtraParams);
$oResultBlock->Display($oP, 'expresult');
break;
$oResultBlock = new DisplayBlock($oFilter, 'list', false, $aExtraParams);
$oResultBlock->Display($oP, 'expresult');
break;
case 'csv':
$oP = new CSVPage("iTop - Export");
$sFields = implode(',', $aFields);
@@ -336,17 +344,14 @@ if (!$oP)
$oP->p("Parameters:");
$oP->p(" * expression: an OQL expression (URL encoded if needed)");
$oP->p(" * query: (alternative to 'expression') the id of an entry from the query phrasebook");
if (Utils::IsModeCLI())
{
if (Utils::IsModeCLI()) {
$oP->p(" * with_archive: (optional, defaults to 0) if set to 1 then the result set will include archived objects");
}
else
{
} else {
$oP->p(" * with_archive: (optional, defaults to the current mode) if set to 1 then the result set will include archived objects");
}
$oP->p(" * arg_xxx: (needed if the query has parameters) the value of the parameter 'xxx'");
$oP->p(" * format: (optional, default is html) the desired output format. Can be one of 'html', 'spreadsheet', 'csv', 'xlsx' or 'xml'");
$oP->p(" * fields: (optional, no effect on XML format) list of fields (attribute codes, or alias.attcode) separated by a coma");
$oP->p(" * fields: (optional, no effect on XML format) list of fields (attribute codes, or alias.attcode) separated by a comma");
$oP->p(" * fields_advanced: (optional, no effect on XML/HTML formats ; ignored is fields is specified) If set to 1, the default list of fields will include the external keys and their reconciliation keys");
$oP->p(" * filename: (optional, no effect in CLI mode) if set then the results will be downloaded as a file");
}