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