mirror of
https://github.com/Combodo/iTop.git
synced 2026-10-15 21:08:55 +02:00
Compare commits
16 Commits
designer-3
...
support/3.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23619ec6cd | ||
|
|
0ba1bf829c | ||
|
|
16cac91c8a | ||
|
|
01bf120da3 | ||
|
|
6477468572 | ||
|
|
4639d06a0d | ||
|
|
09574d90d5 | ||
|
|
0a4bbfe5a9 | ||
|
|
31a4e1a0a0 | ||
|
|
de74103118 | ||
|
|
a2a00cb582 | ||
|
|
4d699f79e8 | ||
|
|
873cea1a76 | ||
|
|
adc2596b4a | ||
|
|
2ac4229ee0 | ||
|
|
4a6af15237 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -33,7 +33,6 @@ tests/*/vendor/*
|
||||
!/data/index.php
|
||||
!/data/web.config
|
||||
!/data/exclude.txt
|
||||
!/data/.compilation-symlinks
|
||||
|
||||
# iTop extensions
|
||||
/extensions/**
|
||||
|
||||
@@ -61,7 +61,7 @@ foreach ($aTcpdfFontsDirContent as $sTcpdfFontResourceName) {
|
||||
* 2) Then adding the DroidSansFallback font (useful for CJK data for example)
|
||||
*/
|
||||
echo $sCurrentScriptFileName.": ---2) Copying font files to TCPDF ($sTcPdfFontsFolder)...\n";
|
||||
$aFontFilesToCopy = glob(__DIR__.'\droidsansfallback.*');
|
||||
$aFontFilesToCopy = glob(__DIR__.DIRECTORY_SEPARATOR.'droidsansfallback.*');
|
||||
foreach ($aFontFilesToCopy as $sFontFileToCopy) {
|
||||
$sFontFileName = basename($sFontFileToCopy);
|
||||
echo $sCurrentScriptFileName.': copying '.$sFontFileName."\n";
|
||||
|
||||
@@ -2075,17 +2075,8 @@ class MenuBlock extends DisplayBlock
|
||||
$aAllowedProfiles = MetaModel::GetConfig()->Get('concurrent_lock_override_profiles');
|
||||
$bCanKill = false;
|
||||
|
||||
$oUser = UserRights::GetUserObject();
|
||||
$aUserProfiles = [];
|
||||
if (!is_null($oUser)) {
|
||||
$oProfileSet = $oUser->Get('profile_list');
|
||||
while ($oProfile = $oProfileSet->Fetch()) {
|
||||
$aUserProfiles[$oProfile->Get('profile')] = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($aAllowedProfiles as $sProfile) {
|
||||
if (array_key_exists($sProfile, $aUserProfiles)) {
|
||||
if (UserRights::HasProfile($sProfile)) {
|
||||
$bCanKill = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1202,7 +1202,6 @@ class DesignerComboField extends DesignerFormField
|
||||
{
|
||||
$sId = $this->oForm->GetFieldId($this->sCode);
|
||||
$sName = $this->oForm->GetFieldName($this->sCode);
|
||||
$sChecked = $this->defaultValue ? 'checked' : '';
|
||||
$sMandatory = $this->bMandatory ? 'true' : 'false';
|
||||
$sReadOnly = $this->IsReadOnly() ? 'disabled="disabled"' : '';
|
||||
if ($this->IsSorted()) {
|
||||
@@ -1245,8 +1244,7 @@ class DesignerComboField extends DesignerFormField
|
||||
} else {
|
||||
$sSelected = ($sKey == $this->defaultValue) ? 'selected' : '';
|
||||
}
|
||||
// Quick and dirty: display the menu parents as a tree
|
||||
$sHtmlValue = str_replace(' ', ' ', $sDisplayValue);
|
||||
$sHtmlValue = $this->PrepareValueForRendering($sDisplayValue);
|
||||
$sHtml .= "<option value=\"".utils::EscapeHtml($sKey)."\" $sSelected>$sHtmlValue</option>";
|
||||
}
|
||||
$sHtml .= "</select></span>";
|
||||
@@ -1263,6 +1261,14 @@ EOF
|
||||
|
||||
}
|
||||
|
||||
protected function PrepareValueForRendering($sValue = null): ?string
|
||||
{
|
||||
if (is_null($sValue)) {
|
||||
$sValue = $this->defaultValue;
|
||||
}
|
||||
return $sValue;
|
||||
}
|
||||
|
||||
public function ReadParam(&$aValues)
|
||||
{
|
||||
parent::ReadParam($aValues);
|
||||
|
||||
@@ -109,10 +109,18 @@ $('#shortcut_rename_dlg').dialog({
|
||||
modal: true,
|
||||
title: '$sDialogTitle',
|
||||
buttons: [
|
||||
{ text: "$sOkButtonLabel", click: ShortcutRenameOK},
|
||||
{ text: "$sCancelButtonLabel", click: function() {
|
||||
$(this).dialog( "close" ); $(this).remove();
|
||||
} },
|
||||
{
|
||||
text: "$sCancelButtonLabel",
|
||||
click: function() {
|
||||
$(this).dialog( "close" ); $(this).remove();
|
||||
},
|
||||
'class': 'ibo-button ibo-is-alternative ibo-is-neutral action cancel'
|
||||
},
|
||||
{
|
||||
text: "$sOkButtonLabel",
|
||||
click: ShortcutRenameOK,
|
||||
'class': 'ibo-is-regular ibo-is-primary'
|
||||
},
|
||||
],
|
||||
close: function() { $(this).remove(); }
|
||||
});
|
||||
|
||||
@@ -1315,8 +1315,8 @@ class utils
|
||||
}
|
||||
|
||||
if (is_null($sAuthUser)) {
|
||||
$sAuthUser = self::ReadParam('auth_user', '', 'raw_data');
|
||||
$sAuthPwd = self::ReadParam('auth_pwd', '', 'raw_data');
|
||||
$sAuthUser = self::ReadParam('auth_user', '', true, 'raw_data');
|
||||
$sAuthPwd = self::ReadParam('auth_pwd', '', true, 'raw_data');
|
||||
}
|
||||
$sParamFile = self::GetParamSourceFile('auth_user');
|
||||
if (is_null($sParamFile)) {
|
||||
@@ -1886,6 +1886,9 @@ SQL;
|
||||
$aHTTPHeaders[] = trim($sHeaderString);
|
||||
}
|
||||
// Default options, can be overloaded/extended with the 4th parameter of this method, see above $aCurlOptions
|
||||
$iCurlAllowedProtocols = self::ConvertProtocolsToCurlOption(
|
||||
self::GetConfig()->Get('security.post_requests.allowed_protocols')
|
||||
);
|
||||
$aOptions = [
|
||||
CURLOPT_RETURNTRANSFER => true, // return the content of the request
|
||||
CURLOPT_HEADER => false, // don't return the headers in the output
|
||||
@@ -1908,6 +1911,9 @@ SQL;
|
||||
$aAllOptions = $aCurlOptions + $aOptions;
|
||||
$ch = curl_init($sUrl);
|
||||
curl_setopt_array($ch, $aAllOptions);
|
||||
if (!curl_setopt($ch, CURLOPT_PROTOCOLS, $iCurlAllowedProtocols)) {
|
||||
throw new Exception('Unable to enforce allowed protocols for cURL request');
|
||||
}
|
||||
$response = curl_exec($ch);
|
||||
$iErr = curl_errno($ch);
|
||||
$sErrMsg = curl_error($ch);
|
||||
@@ -1929,6 +1935,40 @@ SQL;
|
||||
return $response;
|
||||
}
|
||||
|
||||
private static function ConvertProtocolsToCurlOption(string $sProtocols): int
|
||||
{
|
||||
$aProtocols = explode(',', $sProtocols);
|
||||
|
||||
$iCurlProtocols = 0;
|
||||
foreach ($aProtocols as $sProtocol) {
|
||||
$iCurlProtocols |= match (strtolower(trim($sProtocol))) {
|
||||
'dict' => defined('CURLPROTO_DICT') ? CURLPROTO_DICT : 0,
|
||||
'file' => defined('CURLPROTO_FILE') ? CURLPROTO_FILE : 0,
|
||||
'ftp' => defined('CURLPROTO_FTP') ? CURLPROTO_FTP : 0,
|
||||
'ftps' => defined('CURLPROTO_FTPS') ? CURLPROTO_FTPS : 0,
|
||||
'gopher' => defined('CURLPROTO_GOPHER') ? CURLPROTO_GOPHER : 0,
|
||||
'http' => defined('CURLPROTO_HTTP') ? CURLPROTO_HTTP : 0,
|
||||
'https' => defined('CURLPROTO_HTTPS') ? CURLPROTO_HTTPS : 0,
|
||||
'imap' => defined('CURLPROTO_IMAP') ? CURLPROTO_IMAP : 0,
|
||||
'imaps' => defined('CURLPROTO_IMAPS') ? CURLPROTO_IMAPS : 0,
|
||||
'ldap' => defined('CURLPROTO_LDAP') ? CURLPROTO_LDAP : 0,
|
||||
'ldaps' => defined('CURLPROTO_LDAPS') ? CURLPROTO_LDAPS : 0,
|
||||
'mqtt' => defined('CURLPROTO_MQTT') ? CURLPROTO_MQTT : 0,
|
||||
'pop3' => defined('CURLPROTO_POP3') ? CURLPROTO_POP3 : 0,
|
||||
'pop3s' => defined('CURLPROTO_POP3S') ? CURLPROTO_POP3S : 0,
|
||||
'rtsp' => defined('CURLPROTO_RTSP') ? CURLPROTO_RTSP : 0,
|
||||
'scp' => defined('CURLPROTO_SCP') ? CURLPROTO_SCP : 0,
|
||||
'sftp' => defined('CURLPROTO_SFTP') ? CURLPROTO_SFTP : 0,
|
||||
'smtp' => defined('CURLPROTO_SMTP') ? CURLPROTO_SMTP : 0,
|
||||
'smtps' => defined('CURLPROTO_SMTPS') ? CURLPROTO_SMTPS : 0,
|
||||
'telnet' => defined('CURLPROTO_TELNET') ? CURLPROTO_TELNET : 0,
|
||||
'tftp' => defined('CURLPROTO_TFTP') ? CURLPROTO_TFTP : 0,
|
||||
default => throw new Exception("Unsupported protocol: $sProtocol"),
|
||||
};
|
||||
}
|
||||
return $iCurlProtocols;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a standard list of character sets
|
||||
*
|
||||
|
||||
@@ -1750,8 +1750,8 @@ class Config
|
||||
'security.disable_joined_classes_filter' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If true, scope filters aren\'t applied to joined classes or union classes not directly listed in the SELECT clause.',
|
||||
'default' => true,
|
||||
'value' => true,
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
@@ -1766,8 +1766,16 @@ class Config
|
||||
'security.disable_exec_forced_login_for_all_enpoints' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If true, when no delegated authentication module is defined, no login will be forced on modules exec endpoints',
|
||||
'default' => true,
|
||||
'value' => true,
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'security.post_requests.allowed_protocols' => [
|
||||
'type' => 'string',
|
||||
'description' => 'List of allowed protocols that will be used for post requests. Allowed values: dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, ldaps, mqtt, pop3, pop3s, rtsp, scp, sftp, smtp, smtps, telnet, tftp',
|
||||
'default' => 'http,https,ftp,ftps,sftp',
|
||||
'value' => 'http,https,ftp,ftps,sftp',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
|
||||
@@ -1704,7 +1704,7 @@ class DBObjectSearch extends DBSearch
|
||||
* @return array|mixed|\SQLObjectQuery|null
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null)
|
||||
public function GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null, $bSelectOnlyIds = false)
|
||||
{
|
||||
// Hide objects that are not visible to the current user
|
||||
//
|
||||
@@ -1775,6 +1775,8 @@ class DBObjectSearch extends DBSearch
|
||||
$aContextData['aSelectExpr'] = $aSelectExpr;
|
||||
$sRawId .= $bGetCount;
|
||||
$aContextData['bGetCount'] = $bGetCount;
|
||||
$sRawId .= 'ids:'.($bSelectOnlyIds ? '1' : '0');
|
||||
$aContextData['bSelectOnlyIds'] = $bSelectOnlyIds;
|
||||
if (is_array($aSelectedClasses)) {
|
||||
$sRawId .= implode(',', $aSelectedClasses); // Unions may alter the list of selected columns
|
||||
}
|
||||
@@ -1828,7 +1830,7 @@ class DBObjectSearch extends DBSearch
|
||||
if (!isset($oSQLQuery)) {
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oSQLObjectQueryBuilder = new SQLObjectQueryBuilder($oSearch);
|
||||
$oSQLQuery = $oSQLObjectQueryBuilder->BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr, $aSelectedClasses, $aSelectExpr);
|
||||
$oSQLQuery = $oSQLObjectQueryBuilder->BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr, $aSelectedClasses, $aSelectExpr, $bSelectOnlyIds);
|
||||
$oKPI->ComputeStats('BuildSQLQueryStruct', $sOqlQuery);
|
||||
|
||||
if (self::$m_bQueryCacheEnabled) {
|
||||
@@ -1938,7 +1940,7 @@ class DBObjectSearch extends DBSearch
|
||||
$oSearch = $this;
|
||||
$aClassesToFilter = $this->GetSelectedClasses();
|
||||
|
||||
// Opt-in for joined classes filtering, otherwise only filter the selected class(es)
|
||||
// Joined classes filtering can be disabled through the configuration.
|
||||
if (MetaModel::GetConfig()->Get('security.disable_joined_classes_filter') === false) {
|
||||
$aClassesToFilter = $this->GetJoinedClasses();
|
||||
}
|
||||
|
||||
@@ -995,7 +995,7 @@ abstract class DBSearch
|
||||
* @internal
|
||||
*
|
||||
*/
|
||||
public function MakeSelectQuery($aOrderBy = [], $aArgs = [], $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false, $bBeautifulSQL = true)
|
||||
public function MakeSelectQuery($aOrderBy = [], $aArgs = [], $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false, $bBeautifulSQL = true, $bSelectOnlyIds = false)
|
||||
{
|
||||
// Check the order by specification, and prefix with the class alias
|
||||
// and make sure that the ordering columns are going to be selected
|
||||
@@ -1036,7 +1036,7 @@ abstract class DBSearch
|
||||
}
|
||||
}
|
||||
|
||||
$oSQLQuery = $this->GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount);
|
||||
$oSQLQuery = $this->GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, null, null, $bSelectOnlyIds);
|
||||
|
||||
if ($this->m_bNoContextParameters) {
|
||||
// Only internal parameters
|
||||
@@ -1120,7 +1120,7 @@ abstract class DBSearch
|
||||
* @internal
|
||||
*
|
||||
*/
|
||||
protected function GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $aGroupByExpr = null, $aSelectExpr = null)
|
||||
protected function GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $aGroupByExpr = null, $aSelectExpr = null, $bSelectOnlyIds = false)
|
||||
{
|
||||
$oSearch = $this->ApplyDataFilters();
|
||||
|
||||
@@ -1142,7 +1142,7 @@ abstract class DBSearch
|
||||
}
|
||||
}
|
||||
|
||||
$oSQLQuery = $oSearch->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr, null, $aSelectExpr);
|
||||
$oSQLQuery = $oSearch->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr, null, $aSelectExpr, $bSelectOnlyIds);
|
||||
$oSQLQuery->SetSourceOQL($oSearch->ToOQL());
|
||||
|
||||
// Join to an additional table, if required...
|
||||
@@ -1177,7 +1177,8 @@ abstract class DBSearch
|
||||
$bGetCount,
|
||||
$aGroupByExpr = null,
|
||||
$aSelectedClasses = null,
|
||||
$aSelectExpr = null
|
||||
$aSelectExpr = null,
|
||||
$bSelectOnlyIds = false
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -527,10 +527,10 @@ class DBUnionSearch extends DBSearch
|
||||
throw new Exception('MakeUpdateQuery is not implemented for the unions!');
|
||||
}
|
||||
|
||||
public function GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null)
|
||||
public function GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null, $bSelectOnlyIds = false)
|
||||
{
|
||||
if (count($this->aSearches) == 1) {
|
||||
return $this->aSearches[0]->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr, $aSelectedClasses, $aSelectExpr);
|
||||
return $this->aSearches[0]->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr, $aSelectedClasses, $aSelectExpr, $bSelectOnlyIds);
|
||||
}
|
||||
|
||||
$aSQLQueries = [];
|
||||
@@ -610,7 +610,7 @@ class DBUnionSearch extends DBSearch
|
||||
}
|
||||
}
|
||||
}
|
||||
$oSubQuery = $oSearch->GetSQLQueryStructure($aQueryAttToLoad, false, $aQueryGroupByExpr, $aSearchSelectedClasses, $aQuerySelectExpr);
|
||||
$oSubQuery = $oSearch->GetSQLQueryStructure($aQueryAttToLoad, false, $aQueryGroupByExpr, $aSearchSelectedClasses, $aQuerySelectExpr, $bSelectOnlyIds);
|
||||
if (count($aSearchAliases) > 1) {
|
||||
// Necessary to make sure that selected columns will match throughout all the queries
|
||||
// (default order of selected fields depending on the order of JOINS)
|
||||
@@ -683,7 +683,7 @@ class DBUnionSearch extends DBSearch
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Opt-in for joined classes filtering, otherwise fallback on DBSearch filtering
|
||||
// Joined classes filtering can be disabled through the configuration.
|
||||
if (MetaModel::GetConfig()->Get('security.disable_joined_classes_filter') === true) {
|
||||
return parent::ApplyDataFilters();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,13 +29,14 @@ class SQLObjectQueryBuilder
|
||||
* @param array $aGroupByExpr
|
||||
* @param array $aSelectedClasses
|
||||
* @param array $aSelectExpr
|
||||
* @param bool $bSelectOnlyIds
|
||||
*
|
||||
* @return null|SQLObjectQuery
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null)
|
||||
public function BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null, $bSelectOnlyIds = false)
|
||||
{
|
||||
if ($bGetCount || !is_null($aGroupByExpr)) {
|
||||
if ($bGetCount || !is_null($aGroupByExpr) || $bSelectOnlyIds) {
|
||||
// Avoid adding all the fields for counts or "group by" requests
|
||||
$aAttToLoad = [];
|
||||
foreach ($this->oDBObjetSearch->GetSelectedClasses() as $sClassAlias => $sClass) {
|
||||
|
||||
@@ -56,7 +56,7 @@ function RenderAttachments(AjaxPage $oPage, $iTransactionId)
|
||||
try {
|
||||
require_once APPROOT.'/application/startup.inc.php';
|
||||
require_once APPROOT.'/application/loginwebpage.class.inc.php';
|
||||
LoginWebPage::DoLoginEx(null /* any portal */, false);
|
||||
LoginWebPage::DoLogin(); // No user portal should access this endpoint.
|
||||
|
||||
$oPage = new AjaxPage("");
|
||||
|
||||
@@ -105,15 +105,6 @@ try {
|
||||
$oPage->SetData($aResult);
|
||||
break;
|
||||
|
||||
case 'remove':
|
||||
$iAttachmentId = utils::ReadParam('att_id', '');
|
||||
$oSearch = DBObjectSearch::FromOQL("SELECT Attachment WHERE id = :id");
|
||||
$oSet = new DBObjectSet($oSearch, [], ['id' => $iAttachmentId]);
|
||||
while ($oAttachment = $oSet->Fetch()) {
|
||||
$oAttachment->DBDelete();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'refresh_attachments_render':
|
||||
$sTempId = utils::ReadParam('temp_id', '', false, 'transaction_id');
|
||||
RenderAttachments($oPage, $sTempId);
|
||||
|
||||
@@ -742,11 +742,9 @@
|
||||
{
|
||||
$oAddon = UserRights::GetModuleInstance();
|
||||
$aOrgs = $oAddon->GetUserOrgs($oUser,'Organization');
|
||||
$oSet = $oUser->Get('profile_list');
|
||||
$aProfiles = $oSet->GetColumnAsArray('profile');
|
||||
|
||||
// User is not allowed on the new Organization and has 'Portal user' Profile and is enabled
|
||||
if ((count($aOrgs) > 0) && !in_array($this->Get('org_id'), $aOrgs) && in_array('Portal user',$aProfiles) && ($oUser->Get('status') === 'enabled'))
|
||||
if ((count($aOrgs) > 0) && !in_array($this->Get('org_id'), $aOrgs) && UserRights::HasProfile('Portal user', $oUser) && ($oUser->Get('status') === 'enabled'))
|
||||
{ // Let's block the Person modification,
|
||||
// replace by $this->AddCheckWarning(...) if you don't want to block the modification
|
||||
$this->AddCheckIssue(Dict::Format('Class:Person/Error:ChangingOrgDenied', $this->Get('org_id_friendlyname')));
|
||||
|
||||
19
js/pages/backoffice/dialog.js
Normal file
19
js/pages/backoffice/dialog.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2024 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
// Overload of the default dialog widget
|
||||
$.widget('ui.dialog', $.ui.dialog, {
|
||||
_allowInteraction: function (oEvent) {
|
||||
const oTarget = $(oEvent.target);
|
||||
// If we interact with a CKEditor instance in fullscreen mode, we need to allow it
|
||||
// We could check if the current instance is in the dialog, but it's easier to always allow it in fullscreen
|
||||
if (oTarget.closest('.ck.ck-fullscreen__main-wrapper, .ck-body-wrapper').length > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If that's not a specific case, fall back to the default behavior
|
||||
return this._super(oEvent);
|
||||
}
|
||||
});
|
||||
BIN
lib/tecnickcom/tcpdf/fonts/droidsansfallback.ctg.z
Normal file
BIN
lib/tecnickcom/tcpdf/fonts/droidsansfallback.ctg.z
Normal file
Binary file not shown.
2975
lib/tecnickcom/tcpdf/fonts/droidsansfallback.php
Normal file
2975
lib/tecnickcom/tcpdf/fonts/droidsansfallback.php
Normal file
File diff suppressed because it is too large
Load Diff
BIN
lib/tecnickcom/tcpdf/fonts/droidsansfallback.z
Normal file
BIN
lib/tecnickcom/tcpdf/fonts/droidsansfallback.z
Normal file
Binary file not shown.
@@ -768,7 +768,7 @@ try {
|
||||
|
||||
// N°4129 - Prevent XSS attacks & other script executions
|
||||
if (utils::GetConfig()->Get('security.disable_inline_documents_sandbox') === false) {
|
||||
$oPage->add_header('Content-Security-Policy: sandbox;');
|
||||
$oPage->add_header('Content-Security-Policy: sandbox; default-src \'none\'');
|
||||
}
|
||||
|
||||
ormDocument::DownloadDocument($oPage, $sClass, $id, $sField, 'inline');
|
||||
@@ -1239,13 +1239,32 @@ JS
|
||||
$oSearch = new DBObjectSearch('Shortcut');
|
||||
$aShortcuts = utils::ReadMultipleSelection($oSearch);
|
||||
$iShortcut = $aShortcuts[0];
|
||||
$oShortcut = MetaModel::GetObject('Shortcut', $iShortcut);
|
||||
$oShortcutSearch = new DBObjectSearch('Shortcut');
|
||||
$oShortcutSearch->AddCondition('user_id', UserRights::GetUserId(), '=');
|
||||
$oShortcutSearch->AddCondition('id', $iShortcut, '=');
|
||||
$oShortcutSet = new CMDBObjectSet($oShortcutSearch);
|
||||
|
||||
$oShortcut = $oShortcutSet->Fetch();
|
||||
|
||||
if ($oShortcut === null) {
|
||||
throw new SecurityException(Dict::S('UI:ObjectDoesNotExist'));
|
||||
}
|
||||
|
||||
$oShortcut->StartRenameDialog($oPage);
|
||||
break;
|
||||
|
||||
case 'shortcut_rename_go':
|
||||
$iShortcut = utils::ReadParam('id', 0);
|
||||
$oShortcut = MetaModel::GetObject('Shortcut', $iShortcut);
|
||||
|
||||
$oShortcutSearch = new DBObjectSearch('Shortcut');
|
||||
$oShortcutSearch->AddCondition('user_id', UserRights::GetUserId(), '=');
|
||||
$oShortcutSearch->AddCondition('id', $iShortcut, '=');
|
||||
$oShortcutSet = new CMDBObjectSet($oShortcutSearch);
|
||||
$oShortcut = $oShortcutSet->Fetch();
|
||||
|
||||
if ($oShortcut === null) {
|
||||
throw new SecurityException(Dict::S('UI:ObjectDoesNotExist'));
|
||||
}
|
||||
|
||||
$sName = utils::ReadParam('attr_name', '', false, 'raw_data');
|
||||
if (strlen($sName) > 0) {
|
||||
|
||||
@@ -243,7 +243,7 @@ JS
|
||||
);
|
||||
$oShortcutsToolBar->AddSubBlock($oShortcutsRenameButton);
|
||||
// - Delete button
|
||||
$oShortcutsDeleteButton = ButtonUIBlockFactory::MakeForSecondaryAction(
|
||||
$oShortcutsDeleteButton = ButtonUIBlockFactory::MakeForDestructiveAction(
|
||||
Dict::S('UI:Button:Delete'),
|
||||
null,
|
||||
null,
|
||||
|
||||
@@ -346,12 +346,4 @@ class AjaxPage extends WebPage implements iTabbedPage
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function FilterXSS($sHTML)
|
||||
{
|
||||
return str_ireplace(['<script', '</script>'], ['<!-- <removed-script', '</removed-script> -->'], $sHTML);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,15 +47,9 @@ class DownloadPage extends AjaxPage
|
||||
header($s_header);
|
||||
}
|
||||
|
||||
if (($this->sContentType == 'text/html') && ($this->sContentDisposition == 'inline')) {
|
||||
// inline content != attachment && html => filter all scripts for malicious XSS scripts
|
||||
$sContent = self::FilterXSS($this->sContent);
|
||||
} else {
|
||||
$sContent = $this->sContent;
|
||||
}
|
||||
$oKpi->ComputeAndReport(get_class($this).' output');
|
||||
echo $sContent;
|
||||
$oKpi->ComputeAndReport('Echoing ('.round(strlen($sContent) / 1024).' Kb)');
|
||||
echo $this->sContent;
|
||||
$oKpi->ComputeAndReport('Echoing ('.round(strlen($this->sContent) / 1024).' Kb)');
|
||||
ExecutionKPI::ReportStats();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,6 +209,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
$this->LinkScriptFromAppRoot('js/pages/backoffice/keyboard-shortcuts.js');
|
||||
|
||||
// Used throughout the app.
|
||||
$this->LinkScriptFromAppRoot('js/pages/backoffice/dialog.js');
|
||||
$this->LinkScriptFromAppRoot('js/pages/backoffice/toolbox.js');
|
||||
$this->LinkScriptFromAppRoot('js/pages/backoffice/on-ready.js');
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
{% endif %}
|
||||
<span class="ibo-object-details--object-class" data-role="ibo-object-details--object-class">
|
||||
{% if is_backoffice_menu_enabled('DataModelMenu') %}
|
||||
<a href="{{ get_absolute_url_app_root() }}pages/schema.php?operation=details_class&class={{ oUIBlock.GetClassName() }}" target="_blank" data-tooltip-content="{{ 'UI:Layout:ObjectDetails:DatamodelSchemaLink:Tooltip' | dict_s }}" data-tooltip-html-enabled="true">{{ oUIBlock.GetClassLabel() }}</a>
|
||||
<a class="ibo-object-details--object-class--label" href="{{ get_absolute_url_app_root() }}pages/schema.php?operation=details_class&class={{ oUIBlock.GetClassName() }}" target="_blank" data-tooltip-content="{{ 'UI:Layout:ObjectDetails:DatamodelSchemaLink:Tooltip' | dict_s }}" data-tooltip-html-enabled="true">{{ oUIBlock.GetClassLabel() }}</a>
|
||||
{% else %}
|
||||
{{ oUIBlock.GetClassLabel() }}
|
||||
<span class="ibo-object-details--object-class--label">{{ oUIBlock.GetClassLabel() }}</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
{{ parent() }}
|
||||
|
||||
@@ -111,7 +111,7 @@ class LoginWebPageTest extends ItopDataTestCase
|
||||
$this->assertStringContainsString('<title>iTop login</title>', $sPageContent, 'if itop is configured to force login when no there is no delegated authentication endpoints list, then login should be required.');
|
||||
}
|
||||
|
||||
public function testWithoutDelegatedAuthenticationEndpointsListWithDefaultConfiguration()
|
||||
public function testWithoutDelegatedAuthenticationEndpointsListRequiresLoginByDefault()
|
||||
{
|
||||
$sPageContent = $this->CallItopUri(
|
||||
"pages/exec.php?exec_module=extension-without-delegated-authentication-endpoints-list&exec_page=src/Controller/File.php",
|
||||
@@ -120,7 +120,24 @@ class LoginWebPageTest extends ItopDataTestCase
|
||||
true
|
||||
);
|
||||
|
||||
$this->assertStringContainsString('Yo', $sPageContent, 'by default (until N°9343) if no delegated authentication endpoints list is defined, not logged in persons should access pages');
|
||||
$this->assertStringContainsString('<title>iTop login</title>', $sPageContent, 'by default, login should be required when no delegated authentication endpoints list is defined');
|
||||
}
|
||||
|
||||
public function testWithoutDelegatedAuthenticationEndpointsListWithCompatibilityOptOut()
|
||||
{
|
||||
@chmod($this->oConfig->GetLoadedFile(), 0770);
|
||||
$this->oConfig->Set('security.disable_exec_forced_login_for_all_enpoints', true, 'AnythingButEmptyOrUnknownValue');
|
||||
$this->oConfig->WriteToFile();
|
||||
@chmod($this->oConfig->GetLoadedFile(), 0444);
|
||||
|
||||
$sPageContent = $this->CallItopUri(
|
||||
"pages/exec.php?exec_module=extension-without-delegated-authentication-endpoints-list&exec_page=src/Controller/File.php",
|
||||
[],
|
||||
[],
|
||||
true
|
||||
);
|
||||
|
||||
$this->assertStringContainsString('Yo !', $sPageContent, 'the compatibility opt-out should allow anonymous access when no delegated authentication endpoints list is defined');
|
||||
}
|
||||
|
||||
public function testNotInDelegatedAuthenticationEndpointsList()
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\DotMake\Dependencies\Composer\Tcpdf;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
|
||||
/**
|
||||
* @coversNothing
|
||||
*/
|
||||
class TcpdfUpdateFontsTest extends ItopTestCase
|
||||
{
|
||||
public function testThatDroidSansFallbackFilesAreCopiedToTcpdfFontsFolderAfterLibraryUpdate(): void
|
||||
{
|
||||
$sSourcePattern = APPROOT
|
||||
.'.make'.DIRECTORY_SEPARATOR.'dependencies'.DIRECTORY_SEPARATOR.'composer'.DIRECTORY_SEPARATOR.'tcpdf'.DIRECTORY_SEPARATOR.'droidsansfallback.*';
|
||||
$aSourceFiles = glob($sSourcePattern);
|
||||
$this->assertIsArray($aSourceFiles, 'Unable to read source TCPDF custom font files.');
|
||||
$this->assertNotEmpty($aSourceFiles, 'No source files found for pattern droidsansfallback.*');
|
||||
|
||||
foreach ($aSourceFiles as $sSourceFilePath) {
|
||||
$sFontFileName = basename($sSourceFilePath);
|
||||
$sDestinationFilePath = APPROOT
|
||||
.'lib'.DIRECTORY_SEPARATOR.'tecnickcom'.DIRECTORY_SEPARATOR.'tcpdf'.DIRECTORY_SEPARATOR.'fonts'.DIRECTORY_SEPARATOR.$sFontFileName;
|
||||
|
||||
$this->assertFileExists($sDestinationFilePath, "Missing copied font file: {$sFontFileName}");
|
||||
$this->assertSame(
|
||||
hash_file('sha256', $sSourceFilePath),
|
||||
hash_file('sha256', $sDestinationFilePath),
|
||||
"Copied font file content mismatch: {$sFontFileName}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,11 @@ XML,
|
||||
</root>
|
||||
HTML
|
||||
],
|
||||
'DesignerComboField should not escape html characters to preserve indentation made with ' => [
|
||||
'\\DesignerComboField',
|
||||
' Foo & Bar',
|
||||
' Foo & Bar',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,30 @@ class utilsTest extends ItopTestCase
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider DoPostRequestAllowedProtocolsProvider
|
||||
*/
|
||||
public function testDoPostRequestRespectsAllowedProtocolsConfig(string $sAllowedProtocols, bool $bFileProtocolAllowed): void
|
||||
{
|
||||
// Config::Set changes the in-memory setting only; the test framework reloads config after this test class.
|
||||
utils::GetConfig()->Set('security.post_requests.allowed_protocols', $sAllowedProtocols);
|
||||
if (!$bFileProtocolAllowed) {
|
||||
$this->expectException(\Exception::class);
|
||||
$this->expectExceptionMessage('Problem opening URL');
|
||||
} else {
|
||||
$this->expectNotToPerformAssertions();
|
||||
}
|
||||
utils::DoPostRequest('file:///tmp', ['payload' => 'test']);
|
||||
}
|
||||
|
||||
public function DoPostRequestAllowedProtocolsProvider(): array
|
||||
{
|
||||
return [
|
||||
'file protocol is allowed' => ['file,http,https', true],
|
||||
'file protocol is not allowed' => ['http,https', false],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider realPathDataProvider
|
||||
* @covers utils::RealPath()
|
||||
|
||||
@@ -419,6 +419,7 @@ class UserLocalTest extends ItopDataTestCase
|
||||
$this->assertInstanceOf(ormLinkSet::class, $oProfilesSet);
|
||||
$this->assertEquals(0, $oProfilesSet->Count());
|
||||
MetaModel::GetConfig()->Set('security.hide_administrators', false);
|
||||
MetaModel::GetConfig()->Set('security.disable_joined_classes_filter', true);
|
||||
$oProfilesSet = $this->GetAdminUserProfileList();
|
||||
$this->assertIsObject($oProfilesSet);
|
||||
$this->assertInstanceOf(ormLinkSet::class, $oProfilesSet);
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Module\ItopAttachment;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
|
||||
class AttachmentAjaxEndpointTest extends ItopDataTestCase
|
||||
{
|
||||
public const USE_TRANSACTION = false;
|
||||
private const AUTHENTICATION_PASSWORD = 'tagada-Secret,007';
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->BackupConfiguration();
|
||||
$this->AddLoginModeAndSaveConfiguration('url');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider AjaxEndpointAccessProvider
|
||||
*/
|
||||
public function testAjaxEndpointAccess(string $sProfile, int $iExpectedHttpCode): void
|
||||
{
|
||||
$sLogin = 'user-'.uniqid();
|
||||
$this->CreateUser($sLogin, self::$aURP_Profiles[$sProfile], self::AUTHENTICATION_PASSWORD);
|
||||
|
||||
$iHttpCode = $this->CallAttachmentEndpointAs($sLogin);
|
||||
|
||||
$this->assertSame($iExpectedHttpCode, $iHttpCode);
|
||||
}
|
||||
|
||||
public function AjaxEndpointAccessProvider(): array
|
||||
{
|
||||
return [
|
||||
'console user' => ['Service Desk Agent', 200],
|
||||
'portal user' => ['Portal user', 302], // redirect to portal
|
||||
];
|
||||
}
|
||||
|
||||
private function CallAttachmentEndpointAs(string $sLogin): int
|
||||
{
|
||||
$this->CallItopUri(
|
||||
'env-production/itop-attachments/ajax.itop-attachment.php?operation=add&'.http_build_query([
|
||||
'auth_user' => $sLogin,
|
||||
'auth_pwd' => self::AUTHENTICATION_PASSWORD,
|
||||
]),
|
||||
[],
|
||||
[
|
||||
CURLOPT_HTTPHEADER => ['X-Combodo-Ajax:1'],
|
||||
CURLOPT_POST => 0,
|
||||
],
|
||||
true
|
||||
);
|
||||
|
||||
return $this->aLastCurlGetInfo['http_code'];
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace Combodo\iTop\Test\UnitTest\Pages;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use Dict;
|
||||
use ormDocument;
|
||||
use UserLocal;
|
||||
use UserRequest;
|
||||
|
||||
@@ -132,6 +133,47 @@ class AjaxRenderTest extends ItopDataTestCase
|
||||
return $this->AcquireLockAsUser(self::$sLogin, self::$iTicketId);
|
||||
}
|
||||
|
||||
public function testDisplayDocumentHasSandboxWhenConfigurationDoesNotDisableIt(): void
|
||||
{
|
||||
$sLogin = uniqid('AjaxRenderTest');
|
||||
$this->CreateContactlessUser($sLogin, self::$aURP_Profiles['Administrator'], self::AUTHENTICATION_PASSWORD);
|
||||
$iDocumentId = $this->GivenObjectInDB('DocumentFile', [
|
||||
'name' => 'AjaxRenderTest_'.uniqid(),
|
||||
'org_id' => $this->getTestOrgId(),
|
||||
'file' => new ormDocument('<svg xmlns="http://www.w3.org/2000/svg"></svg>', 'image/svg+xml', 'test.svg'),
|
||||
]);
|
||||
|
||||
$this->oiTopConfig->Set('security.disable_inline_documents_sandbox', false);
|
||||
$this->SaveItopConfFile();
|
||||
$sResponseHeaders = $this->GetDocumentResponseHeaders($sLogin, $iDocumentId);
|
||||
$this->assertStringContainsString(
|
||||
'sandbox',
|
||||
$sResponseHeaders
|
||||
);
|
||||
}
|
||||
|
||||
private function GetDocumentResponseHeaders(string $sLogin, int $iDocumentId): string
|
||||
{
|
||||
$sResponse = $this->CallItopUri(
|
||||
'pages/ajax.render.php?'.http_build_query([
|
||||
'operation' => 'display_document',
|
||||
'class' => 'DocumentFile',
|
||||
'id' => $iDocumentId,
|
||||
'field' => 'file',
|
||||
'auth_user' => $sLogin,
|
||||
'auth_pwd' => self::AUTHENTICATION_PASSWORD,
|
||||
]),
|
||||
[],
|
||||
[
|
||||
CURLOPT_HTTPHEADER => ['X-Combodo-Ajax:1'],
|
||||
CURLOPT_HEADER => true,
|
||||
CURLOPT_POST => 0,
|
||||
]
|
||||
);
|
||||
|
||||
return substr($sResponse, 0, $this->aLastCurlGetInfo['header_size']);
|
||||
}
|
||||
|
||||
// Helper method to create a user with a specific profile
|
||||
private function CreateUserWithProfile(int $iProfileId): UserLocal
|
||||
{
|
||||
|
||||
@@ -116,7 +116,7 @@ class DataSynchroTest extends ItopDataTestCase
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function RunDataSynchroTest($aUserLoginUsecase)
|
||||
public function RunDataSynchroTest($aUserLoginUsecase, array $aAdditionalImportParams = [])
|
||||
{
|
||||
$sDescription = $aUserLoginUsecase['desc'];
|
||||
$sTargetClass = $aUserLoginUsecase['target_class'];
|
||||
@@ -273,13 +273,13 @@ class DataSynchroTest extends ItopDataTestCase
|
||||
$sCSVTmpFile = tempnam(sys_get_temp_dir(), "CSV");
|
||||
file_put_contents($sCSVTmpFile, $sCsvData);
|
||||
|
||||
$aParams = [
|
||||
$aParams = array_merge($aAdditionalImportParams, [
|
||||
'csvfile' => $sCSVTmpFile,
|
||||
'data_source_id' => $iDataSourceId,
|
||||
'separator' => ';',
|
||||
'simulate' => 0,
|
||||
'output' => 'details',
|
||||
];
|
||||
]);
|
||||
list($iRetCode, $aOutputLines) = static::ExecSynchroImport($aParams, $bSynchroByHttp);
|
||||
|
||||
unlink($sCSVTmpFile);
|
||||
@@ -390,6 +390,11 @@ class DataSynchroTest extends ItopDataTestCase
|
||||
$this->RunDataSynchroTest($this->GetNominalUsecaseData());
|
||||
}
|
||||
|
||||
public function testDataSynchroByCliWithMaxChunkSize()
|
||||
{
|
||||
$this->RunDataSynchroTest($this->GetNominalUsecaseData(), ['max_chunk_size' => 1 ]);
|
||||
}
|
||||
|
||||
public function testDataSynchroByHttp()
|
||||
{
|
||||
$aUserLoginUsecase = $this->GetNominalUsecaseData();
|
||||
|
||||
Reference in New Issue
Block a user