diff --git a/datamodels/2.x/itop-portal-base/portal/templates/layout.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/layout.html.twig index 2b1780082..a584b168e 100644 --- a/datamodels/2.x/itop-portal-base/portal/templates/layout.html.twig +++ b/datamodels/2.x/itop-portal-base/portal/templates/layout.html.twig @@ -87,6 +87,7 @@ {% block pPageScripts %} + {% if is_development_environment() %} {% else %} diff --git a/js/ajax_hook.js b/js/ajax_hook.js new file mode 100644 index 000000000..aa91d51e2 --- /dev/null +++ b/js/ajax_hook.js @@ -0,0 +1,5 @@ +// add X-Combodo-Ajax for all request (just after jaquery is loaded) +// mandatory for ajax requests with JQuery (CSRF protection) +$(document).ajaxSend(function (event, jqxhr, options) { + jqxhr.setRequestHeader('X-Combodo-Ajax', 'true'); +}); \ No newline at end of file diff --git a/js/ckeditor.feeds.js b/js/ckeditor.feeds.js index b06279602..0eed695d4 100644 --- a/js/ckeditor.feeds.js +++ b/js/ckeditor.feeds.js @@ -16,11 +16,7 @@ const CombodoCKEditorFeeds = { return async function(queryText) { return new Promise( resolve => { setTimeout( () => { - fetch(options.url + queryText, { - headers: { - 'X-Combodo-Ajax': true - } - }) + CombodoHTTP.Fetch(options.url + queryText) .then(response => { return response.json(); }) diff --git a/js/ckeditor.handler.js b/js/ckeditor.handler.js index 390cd29a5..7f7cfc836 100644 --- a/js/ckeditor.handler.js +++ b/js/ckeditor.handler.js @@ -112,12 +112,9 @@ const CombodoCKEditorHandler = { const formData = new FormData(); formData.append('upload', file); - fetch(uploadUrl, { + CombodoHTTP.Fetch(uploadUrl, { method: 'POST', - body: formData, - headers: { - 'X-Combodo-Ajax': true - }, + body: formData }) .then(response => response.json()) .then(responseData => { diff --git a/js/icon_select.js b/js/icon_select.js index 5eaa590b7..a9e6bfcfb 100644 --- a/js/icon_select.js +++ b/js/icon_select.js @@ -247,12 +247,8 @@ $(function() var file = event.target.files[0]; var formData = new FormData(); formData.append('file', file); - - fetch(this.options.post_upload_to, { + CombodoHTTP.Fetch(this.options.post_upload_to, { method: 'POST', - headers: { - 'X-Combodo-Ajax': true - }, body: formData }) .then(response => { diff --git a/js/pages/backoffice/on-ready.js b/js/pages/backoffice/on-ready.js index e477de897..1d7f54192 100644 --- a/js/pages/backoffice/on-ready.js +++ b/js/pages/backoffice/on-ready.js @@ -8,11 +8,6 @@ */ $(document).ready(function () { - // AJAX calls handling - // - Custom headers - $(document).ajaxSend(function (event, jqxhr, options) { - jqxhr.setRequestHeader('X-Combodo-Ajax', 'true'); - }); // - Error messages regarding the error code $(document).ajaxError(function (event, jqxhr, options) { // User is not logged in diff --git a/js/utils.js b/js/utils.js index 5bb9fc24f..4d51b4ee0 100644 --- a/js/utils.js +++ b/js/utils.js @@ -1296,6 +1296,23 @@ const CombodoInlineImage = { } }; +/** + * Abstract Fetch API wrapper to manage AJAX requests in iTop. + */ +const CombodoHTTP = { + /** + * @param {string} sUrl URL to fetch + * @param {Object} oOptions Fetch options + * @return {Promise} + */ + Fetch: function(sUrl, oOptions) { + oOptions = oOptions || {}; + oOptions.headers = oOptions.headers || {}; + oOptions.headers['X-Combodo-Ajax'] = true; + return fetch(sUrl, oOptions); + } +} + /** * Abstract wrapper to manage modal dialogs in iTop. * Implementations for the various GUIs may vary but APIs are the same. diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 4a194a869..89135c9db 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -29,12 +29,12 @@ require_once('../approot.inc.php'); // check if header contains X-Combodo-Ajax for POST request (CSRF protection for ajax calls) -/*if (!isset($_SERVER['HTTP_X_COMBODO_AJAX']) && $_SERVER['REQUEST_METHOD'] === 'POST') { +if (!isset($_SERVER['HTTP_X_COMBODO_AJAX']) && $_SERVER['REQUEST_METHOD'] === 'POST') { $sReferer = utils::HtmlEntities($_SERVER['HTTP_REFERER']); IssueLog::Error("Unprotected ajax call from: $sReferer", 'SECURITY'); header('HTTP/1.1 401 Unauthorized'); die('Unauthorized access. Please see https://www.itophub.io/wiki/page?id=3_2_0:release:developer#checking_for_the_presence_of_specific_header_in_the_post_to_enhance_protection_against_csrf_attacks'); -}*/ +} function LogErrorMessage($sMsgPrefix, $aContextInfo) { diff --git a/sources/Application/WebPage/NiceWebPage.php b/sources/Application/WebPage/NiceWebPage.php index e8e0051ef..4e105a5dc 100644 --- a/sources/Application/WebPage/NiceWebPage.php +++ b/sources/Application/WebPage/NiceWebPage.php @@ -153,6 +153,7 @@ JS // Used throughout the app. $this->LinkScriptFromAppRoot('node_modules/jquery/dist/jquery.min.js'); + $this->LinkScriptFromAppRoot('js/ajax_hook.js'); $this->LinkScriptFromAppRoot('js/jquery.blockUI.js'); if (utils::IsDevelopmentEnvironment()) // Needed since many other plugins still rely on oldies like $.browser { diff --git a/sources/Controller/OAuth/OAuthLandingController.php b/sources/Controller/OAuth/OAuthLandingController.php index fd0e03807..a6429738c 100644 --- a/sources/Controller/OAuth/OAuthLandingController.php +++ b/sources/Controller/OAuth/OAuthLandingController.php @@ -10,6 +10,7 @@ class OAuthLandingController extends Controller public function OperationLanding() { $this->AddLinkedScript(utils::GetAbsoluteUrlAppRoot().'node_modules/jquery/dist/jquery.min.js'); + $this->AddLinkedScript(utils::GetAbsoluteUrlAppRoot().'js/ajax_hook.js'); $this->DisplayPage([], null, static::ENUM_PAGE_TYPE_BASIC_HTML); } } \ No newline at end of file