diff --git a/CHANGELOG.md b/CHANGELOG.md index 605e32e9e28..d8c1a719e3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ The present file will list all changes made to the project; according to the ### API changes +#### Changed + +- All POST request made to `/ajax/` scripts are now requiring a valid CSRF token in their `X-Glpi-Csrf-Token` header. +Requests done using jQuery are automatically including this header, from the moment that the page header is built using +`Html::includeHeader()` method and the `js/common.js` script is loaded. + #### Deprecated - Usage of "followups" option in `CommonITILObject::showShort()` diff --git a/ajax/dashboard.php b/ajax/dashboard.php index 8e8613ce651..0a77aaf8889 100644 --- a/ajax/dashboard.php +++ b/ajax/dashboard.php @@ -51,30 +51,30 @@ $dashboard = new Glpi\Dashboard\Dashboard($_REQUEST['dashboard'] ?? ""); -switch ($_REQUEST['action']) { +switch ($_POST['action'] ?? null) { case 'save_new_dashboard': echo $dashboard->saveNew( - $_REQUEST['title'] ?? "", - $_REQUEST['context'] ?? "" + $_POST['title'] ?? "", + $_POST['context'] ?? "" ); exit; case 'save_items': - $dashboard->saveitems($_REQUEST['items'] ?? []); - $dashboard->saveTitle($_REQUEST['title'] ?? ""); + $dashboard->saveitems($_POST['items'] ?? []); + $dashboard->saveTitle($_POST['title'] ?? ""); exit; case 'save_rights': - echo $dashboard->saveRights($_REQUEST['rights'] ?? []); + echo $dashboard->saveRights($_POST['rights'] ?? []); exit; case 'delete_dashboard': - echo $dashboard->delete(['key' => $_REQUEST['dashboard']]); + echo $dashboard->delete(['key' => $_POST['dashboard']]); exit; case 'set_last_dashboard': - $grid = new Grid($_REQUEST['dashboard'] ?? ""); - echo $grid->setLastDashboard($_REQUEST['page'], $_REQUEST['dashboard']); + $grid = new Grid($_POST['dashboard'] ?? ""); + echo $grid->setLastDashboard($_POST['page'], $_POST['dashboard']); exit; case 'clone_dashboard': diff --git a/ajax/fileupload.php b/ajax/fileupload.php index ade47258ac1..78ff95bdeb2 100644 --- a/ajax/fileupload.php +++ b/ajax/fileupload.php @@ -37,4 +37,4 @@ include ('../inc/includes.php'); Session::checkLoginUser(); -GLPIUploadHandler::uploadFiles($_REQUEST); +GLPIUploadHandler::uploadFiles($_POST); diff --git a/ajax/kanban.php b/ajax/kanban.php index 64524cc8301..46b597d3953 100644 --- a/ajax/kanban.php +++ b/ajax/kanban.php @@ -101,25 +101,25 @@ }; // Action Processing -if ($_REQUEST['action'] == 'update') { +if (($_POST['action'] ?? null) == 'update') { $checkParams(['column_field', 'column_value']); // Update project or task based on changes made in the Kanban $item->update([ - 'id' => $_REQUEST['items_id'], - $_REQUEST['column_field'] => $_REQUEST['column_value'] + 'id' => $_POST['items_id'], + $_POST['column_field'] => $_POST['column_value'] ]); -} else if ($_REQUEST['action'] == 'add_item') { +} else if (($_POST['action'] ?? null) == 'add_item') { $checkParams(['inputs']); $item = new $itemtype(); $inputs = []; - parse_str($_REQUEST['inputs'], $inputs); + parse_str($_POST['inputs'], $inputs); $item->add(Toolbox::clean_cross_side_scripting_deep($inputs)); -} else if ($_REQUEST['action'] == 'bulk_add_item') { +} else if (($_POST['action'] ?? null) == 'bulk_add_item') { $checkParams(['inputs']); $item = new $itemtype(); $inputs = []; - parse_str($_REQUEST['inputs'], $inputs); + parse_str($_POST['inputs'], $inputs); $bulk_item_list = preg_split('/\r\n|[\r\n]/', $inputs['bulk_item_list']); if (!empty($bulk_item_list)) { @@ -131,31 +131,31 @@ } } } -} else if ($_REQUEST['action'] == 'move_item') { +} else if (($_POST['action'] ?? null) == 'move_item') { $checkParams(['card', 'column', 'position', 'kanban']); /** @var Kanban|CommonDBTM $kanban */ - $kanban = new $_REQUEST['kanban']['itemtype']; - $can_move = $kanban->canOrderKanbanCard($_REQUEST['kanban']['items_id']); + $kanban = new $_POST['kanban']['itemtype']; + $can_move = $kanban->canOrderKanbanCard($_POST['kanban']['items_id']); if ($can_move) { - Item_Kanban::moveCard($_REQUEST['kanban']['itemtype'], $_REQUEST['kanban']['items_id'], - $_REQUEST['card'], $_REQUEST['column'], $_REQUEST['position']); + Item_Kanban::moveCard($_POST['kanban']['itemtype'], $_POST['kanban']['items_id'], + $_POST['card'], $_POST['column'], $_POST['position']); } -} else if ($_REQUEST['action'] == 'show_column') { +} else if (($_POST['action'] ?? null) == 'show_column') { $checkParams(['column', 'kanban']); - Item_Kanban::showColumn($_REQUEST['kanban']['itemtype'], $_REQUEST['kanban']['items_id'], $_REQUEST['column']); -} else if ($_REQUEST['action'] == 'hide_column') { + Item_Kanban::showColumn($_POST['kanban']['itemtype'], $_POST['kanban']['items_id'], $_POST['column']); +} else if (($_POST['action'] ?? null) == 'hide_column') { $checkParams(['column', 'kanban']); - Item_Kanban::hideColumn($_REQUEST['kanban']['itemtype'], $_REQUEST['kanban']['items_id'], $_REQUEST['column']); -} else if ($_REQUEST['action'] == 'collapse_column') { + Item_Kanban::hideColumn($_POST['kanban']['itemtype'], $_POST['kanban']['items_id'], $_POST['column']); +} else if (($_POST['action'] ?? null) == 'collapse_column') { $checkParams(['column', 'kanban']); - Item_Kanban::collapseColumn($_REQUEST['kanban']['itemtype'], $_REQUEST['kanban']['items_id'], $_REQUEST['column']); -} else if ($_REQUEST['action'] == 'expand_column') { + Item_Kanban::collapseColumn($_POST['kanban']['itemtype'], $_POST['kanban']['items_id'], $_POST['column']); +} else if (($_POST['action'] ?? null) == 'expand_column') { $checkParams(['column', 'kanban']); - Item_Kanban::expandColumn($_REQUEST['kanban']['itemtype'], $_REQUEST['kanban']['items_id'], $_REQUEST['column']); -} else if ($_REQUEST['action'] == 'move_column') { + Item_Kanban::expandColumn($_POST['kanban']['itemtype'], $_POST['kanban']['items_id'], $_POST['column']); +} else if (($_POST['action'] ?? null) == 'move_column') { $checkParams(['column', 'kanban', 'position']); - Item_Kanban::moveColumn($_REQUEST['kanban']['itemtype'], $_REQUEST['kanban']['items_id'], - $_REQUEST['column'], $_REQUEST['position']); + Item_Kanban::moveColumn($_POST['kanban']['itemtype'], $_POST['kanban']['items_id'], + $_POST['column'], $_POST['position']); } else if ($_REQUEST['action'] == 'refresh') { $checkParams(['column_field']); // Get all columns to refresh the kanban @@ -183,26 +183,26 @@ return; } echo $itemtype::getFormURLWithID($_REQUEST['items_id'], true)."&forcetab={$tab_id}"; -} else if ($_REQUEST['action'] == 'create_column') { +} else if (($_POST['action'] ?? null) == 'create_column') { $checkParams(['column_field', 'items_id', 'column_name']); - $column_field = $_REQUEST['column_field']; + $column_field = $_POST['column_field']; $column_itemtype = getItemtypeForForeignKeyField($column_field); if (!$column_itemtype::canCreate() || !$column_itemtype::canView()) { // Missing rights http_response_code(403); return; } - $params = $_REQUEST['params'] ?? []; + $params = $_POST['params'] ?? []; $column_item = new $column_itemtype(); $column_id = $column_item->add([ - 'name' => $_REQUEST['column_name'] + 'name' => $_POST['column_name'] ] + $params); header("Content-Type: application/json; charset=UTF-8", true); - $column = $itemtype::getKanbanColumns($_REQUEST['items_id'], $column_field, [$column_id]); + $column = $itemtype::getKanbanColumns($_POST['items_id'], $column_field, [$column_id]); echo json_encode($column); -} else if ($_REQUEST['action'] == 'save_column_state') { +} else if (($_POST['action'] ?? null) == 'save_column_state') { $checkParams(['items_id', 'state']); - Item_Kanban::saveStateForItem($_REQUEST['itemtype'], $_REQUEST['items_id'], $_REQUEST['state']); + Item_Kanban::saveStateForItem($_POST['itemtype'], $_POST['items_id'], $_POST['state']); } else if ($_REQUEST['action'] == 'load_column_state') { $checkParams(['items_id', 'last_load']); header("Content-Type: application/json; charset=UTF-8", true); diff --git a/ajax/marketplace.php b/ajax/marketplace.php index 358c8f9d3e9..579c77dc575 100644 --- a/ajax/marketplace.php +++ b/ajax/marketplace.php @@ -34,7 +34,7 @@ // follow download progress of a plugin with a minimal loading of files // So we get a ajax answer in 5ms instead 100ms -if (isset($_REQUEST["action"]) && $_REQUEST["action"] == "get_dl_progress") { +if (($_GET["action"] ?? null) == "get_dl_progress") { if (!defined('GLPI_ROOT')) { define('GLPI_ROOT', dirname(__DIR__)); } @@ -43,14 +43,14 @@ Session::setPath(); Session::start(); - echo $_SESSION['marketplace_dl_progress'][$_REQUEST['key']] ?? 0; + echo $_SESSION['marketplace_dl_progress'][$_GET['key']] ?? 0; exit; } -if ($_REQUEST["action"] == "download_plugin" || $_REQUEST["action"] == "update_plugin") { +if (in_array($_POST["action"] ?? null, ['download_plugin', 'update_plugin'])) { // Do not load plugin that will be updated, to be able to load its new informations // by redefining its plugin_version_ function after files replacement. - $PLUGINS_EXCLUDED = [$_REQUEST['key']]; + $PLUGINS_EXCLUDED = [$_POST['key']]; } @@ -62,56 +62,52 @@ use Glpi\Marketplace\Controller as MarketplaceController; use Glpi\Marketplace\View as MarketplaceView; -if (isset($_REQUEST['key'])) { - $marketplace_ctrl = new MarketplaceController($_REQUEST['key']); - if ($_REQUEST["action"] == "download_plugin" - || $_REQUEST["action"] == "update_plugin") { +if (isset($_POST['key']) && isset($_POST["action"])) { + $marketplace_ctrl = new MarketplaceController($_POST['key']); + if ($_POST["action"] == "download_plugin" + || $_POST["action"] == "update_plugin") { $marketplace_ctrl->downloadPlugin(); } - if ($_REQUEST["action"] == "clean_plugin") { + if ($_POST["action"] == "clean_plugin") { if ($marketplace_ctrl->cleanPlugin()) { echo "cleaned"; } } - if ($_REQUEST["action"] == "install_plugin") { + if ($_POST["action"] == "install_plugin") { $marketplace_ctrl->installPlugin(); } - if ($_REQUEST["action"] == "uninstall_plugin") { + if ($_POST["action"] == "uninstall_plugin") { $marketplace_ctrl->uninstallPlugin(); } - if ($_REQUEST["action"] == "enable_plugin") { + if ($_POST["action"] == "enable_plugin") { $marketplace_ctrl->enablePlugin(); } - if ($_REQUEST["action"] == "disable_plugin") { + if ($_POST["action"] == "disable_plugin") { $marketplace_ctrl->disablePlugin(); } - echo MarketplaceView::getButtons($_REQUEST['key']); -} - -if ($_REQUEST["action"] == "refresh_plugin_list") { - switch ($_REQUEST['tab']) { + echo MarketplaceView::getButtons($_POST['key']); +} else if (($_GET["action"] ?? null) == "refresh_plugin_list") { + switch ($_GET['tab']) { default: case 'discover': echo MarketplaceView::discover( - $_REQUEST['force'] ?? false, + $_GET['force'] ?? false, true, - $_REQUEST['tag'] ?? "", - $_REQUEST['filter'] ?? "", - $_REQUEST['page'] ?? 1, - $_REQUEST['sort'] ?? "sort-alpha-asc" + $_GET['tag'] ?? "", + $_GET['filter'] ?? "", + $_GET['page'] ?? 1, + $_GET['sort'] ?? "sort-alpha-asc" ); break; case 'installed': - echo MarketplaceView::installed(true, true, $_REQUEST['filter'] ?? ""); + echo MarketplaceView::installed(true, true, $_GET['filter'] ?? ""); break; } -} - -if ($_REQUEST["action"] == "getPagination") { +} else if (($_GET["action"] ?? null) == "getPagination") { echo MarketplaceView::getPaginationHtml( - $_REQUEST['page'] ?? 1, - $_REQUEST['total'] ?? 1, + $_GET['page'] ?? 1, + $_GET['total'] ?? 1, true ); } diff --git a/ajax/planning.php b/ajax/planning.php index df61316cc4c..a3c015550d7 100644 --- a/ajax/planning.php +++ b/ajax/planning.php @@ -44,23 +44,23 @@ exit; } -if ($_REQUEST["action"] == "update_event_times") { - echo Planning::updateEventTimes($_REQUEST); +if (($_POST["action"] ?? null) == "update_event_times") { + echo Planning::updateEventTimes($_POST); exit; } -if ($_REQUEST["action"] == "view_changed") { - echo Planning::viewChanged($_REQUEST['view']); +if (($_POST["action"] ?? null) == "view_changed") { + echo Planning::viewChanged($_POST['view']); exit; } -if ($_REQUEST["action"] == "clone_event") { - echo Planning::cloneEvent($_REQUEST['event']); +if (($_POST["action"] ?? null) == "clone_event") { + echo Planning::cloneEvent($_POST['event']); exit; } -if ($_REQUEST["action"] == "delete_event") { - echo Planning::deleteEvent($_REQUEST['event']); +if (($_POST["action"] ?? null) == "delete_event") { + echo Planning::deleteEvent($_POST['event']); exit; } @@ -122,16 +122,16 @@ Planning::showPlanningFilter(); } -if ($_REQUEST["action"] == "toggle_filter") { - Planning::toggleFilter($_REQUEST); +if (($_POST["action"] ?? null) == "toggle_filter") { + Planning::toggleFilter($_POST); } -if ($_REQUEST["action"] == "color_filter") { - Planning::colorFilter($_REQUEST); +if (($_POST["action"] ?? null) == "color_filter") { + Planning::colorFilter($_POST); } -if ($_REQUEST["action"] == "delete_filter") { - Planning::deleteFilter($_REQUEST); +if (($_POST["action"] ?? null) == "delete_filter") { + Planning::deleteFilter($_POST); } Html::ajaxFooter(); diff --git a/ajax/rack.php b/ajax/rack.php index 774f26ee847..03a024cd39b 100644 --- a/ajax/rack.php +++ b/ajax/rack.php @@ -41,10 +41,16 @@ http_response_code(403); die; } -if (isset($_REQUEST['action'])) { - $answer = []; +if (!isset($_REQUEST['action'])) { + exit(); +} - switch ($_REQUEST['action']) { +$answer = []; +if (($_GET['action'] ?? null) === 'show_pdu_form') { + PDU_Rack::showFirstForm((int) $_GET['racks_id']); +} else if (isset($_POST['action'])) { + header("Content-Type: application/json; charset=UTF-8", true); + switch ($_POST['action']) { case 'move_item': $item_rack = new Item_Rack; $item_rack->getFromDB((int) $_POST['id']); @@ -73,10 +79,6 @@ 'position' => (int) $_POST['x'].",".(int) $_POST['y'], ]); break; - - case 'show_pdu_form': - PDU_Rack::showFirstForm((int) $_REQUEST['racks_id']); - exit; } echo json_encode($answer); diff --git a/ajax/timeline.php b/ajax/timeline.php index c9aabda2b88..9a23b3930a1 100644 --- a/ajax/timeline.php +++ b/ajax/timeline.php @@ -34,111 +34,102 @@ Session::checkLoginUser(); -if (!isset($_REQUEST['action'])) { - exit; -} - -if ($_REQUEST['action'] == 'change_task_state') { +if (($_POST['action'] ?? null) === 'change_task_state') { header("Content-Type: application/json; charset=UTF-8"); -} else { + + if (!isset($_POST['tasks_id']) + || !isset($_POST['parenttype']) || ($parent = getItemForItemtype($_POST['parenttype'])) === false) { + exit(); + } + + $taskClass = $parent->getType()."Task"; + $task = new $taskClass; + $task->getFromDB(intval($_POST['tasks_id'])); + if (!in_array($task->fields['state'], [0, Planning::INFO])) { + $new_state = ($task->fields['state'] == Planning::DONE) + ? Planning::TODO + : Planning::DONE; + $new_label = Planning::getState($new_state); + echo json_encode([ + 'state' => $new_state, + 'label' => $new_label + ]); + + $foreignKey = $parent->getForeignKeyField(); + $task->update([ + 'id' => intval($_POST['tasks_id']), + $foreignKey => intval($_POST[$foreignKey]), + 'state' => $new_state + ]); + } +} else if (($_REQUEST['action'] ?? null) === 'viewsubitem') { header("Content-Type: text/html; charset=UTF-8"); -} + Html::header_nocache(); + + if (!isset($_REQUEST['type'])) { + exit(); + } + if (!isset($_REQUEST['parenttype'])) { + exit(); + } -$objType = $_REQUEST['parenttype']::getType(); -$foreignKey = $_REQUEST['parenttype']::getForeignKeyField(); + $item = getItemForItemtype($_REQUEST['type']); + $parent = getItemForItemtype($_REQUEST['parenttype']); -switch ($_REQUEST['action']) { - case "change_task_state": - if (!isset($_REQUEST['tasks_id'])) { - exit(); + if ($_REQUEST['type'] == "Solution") { + $parent->getFromDB($_REQUEST[$parent->getForeignKeyField()]); + + if (!isset($_REQUEST['load_kb_sol'])) { + $_REQUEST['load_kb_sol'] = 0; } - $taskClass = $objType."Task"; - $task = new $taskClass; - $task->getFromDB(intval($_REQUEST['tasks_id'])); - if (!in_array($task->fields['state'], [0, Planning::INFO])) { - $new_state = ($task->fields['state'] == Planning::DONE) - ? Planning::TODO - : Planning::DONE; - $new_label = Planning::getState($new_state); - echo json_encode([ - 'state' => $new_state, - 'label' => $new_label - ]); - - $task->update([ - 'id' => intval($_REQUEST['tasks_id']), - $foreignKey => intval($_REQUEST[$foreignKey]), - 'state' => $new_state - ]); + $sol_params = [ + 'item' => $parent, + 'kb_id_toload' => $_REQUEST['load_kb_sol'] + ]; + + $solution = new ITILSolution(); + $id = isset($_REQUEST['id']) && (int)$_REQUEST['id'] > 0 ? $_REQUEST['id'] : null; + if ($id) { + $solution->getFromDB($id); } - break; - case "viewsubitem": - Html::header_nocache(); - if (!isset($_REQUEST['type'])) { - exit(); + $solution->showForm($id, $sol_params); + } else if ($_REQUEST['type'] == "ITILFollowup") { + $parent->getFromDB($_REQUEST[$parent->getForeignKeyField()]); + + $fup_params = [ + 'item' => $parent + ]; + + $fup = new ITILFollowup(); + $id = isset($_REQUEST['id']) && (int)$_REQUEST['id'] > 0 ? $_REQUEST['id'] : null; + if ($id) { + $fup->getFromDB($id); } - if (!isset($_REQUEST['parenttype'])) { - exit(); + $fup->showForm($id, $fup_params); + } else if (substr_compare($_REQUEST['type'], 'Validation', -10) === 0) { + $parent->getFromDB($_REQUEST[$parent->getForeignKeyField()]); + $validation = new $_REQUEST['type'](); + $id = isset($_REQUEST['id']) && (int)$_REQUEST['id'] > 0 ? $_REQUEST['id'] : null; + if ($id) { + $validation->getFromDB($id); } - - $item = getItemForItemtype($_REQUEST['type']); - $parent = getItemForItemtype($_REQUEST['parenttype']); - - if ($_REQUEST['type'] == "Solution") { - $parent->getFromDB($_REQUEST[$parent->getForeignKeyField()]); - - if (!isset($_REQUEST['load_kb_sol'])) { - $_REQUEST['load_kb_sol'] = 0; - } - - $sol_params = [ - 'item' => $parent, - 'kb_id_toload' => $_REQUEST['load_kb_sol'] - ]; - - $solution = new ITILSolution(); - $id = isset($_REQUEST['id']) && (int)$_REQUEST['id'] > 0 ? $_REQUEST['id'] : null; - if ($id) { - $solution->getFromDB($id); - } - $solution->showForm($id, $sol_params); - } else if ($_REQUEST['type'] == "ITILFollowup") { - $parent->getFromDB($_REQUEST[$parent->getForeignKeyField()]); - - $fup_params = [ - 'item' => $parent - ]; - - $fup = new ITILFollowup(); - $id = isset($_REQUEST['id']) && (int)$_REQUEST['id'] > 0 ? $_REQUEST['id'] : null; - if ($id) { - $fup->getFromDB($id); - } - $fup->showForm($id, $fup_params); - } else if (substr_compare($_REQUEST['type'], 'Validation', -10) === 0) { - $parent->getFromDB($_REQUEST[$parent->getForeignKeyField()]); - $validation = new $_REQUEST['type'](); - $id = isset($_REQUEST['id']) && (int)$_REQUEST['id'] > 0 ? $_REQUEST['id'] : null; - if ($id) { - $validation->getFromDB($id); - } - $validation->showForm($id, ['parent' => $parent]); - } else if (isset($_REQUEST[$parent->getForeignKeyField()]) - && isset($_REQUEST["id"]) - && $parent->getFromDB($_REQUEST[$parent->getForeignKeyField()])) { - - $ol = ObjectLock::isLocked( $_REQUEST['parenttype'], $parent->getID() ); - if ($ol && (Session::getLoginUserID() != $ol->fields['users_id'])) { - ObjectLock::setReadOnlyProfile( ); - } - - $parent::showSubForm($item, $_REQUEST["id"], ['parent' => $parent, - $foreignKey => $_REQUEST[$foreignKey]]); - } else { - echo __('Access denied'); + $validation->showForm($id, ['parent' => $parent]); + } else if (isset($_REQUEST[$parent->getForeignKeyField()]) + && isset($_REQUEST["id"]) + && $parent->getFromDB($_REQUEST[$parent->getForeignKeyField()])) { + + $ol = ObjectLock::isLocked( $_REQUEST['parenttype'], $parent->getID() ); + if ($ol && (Session::getLoginUserID() != $ol->fields['users_id'])) { + ObjectLock::setReadOnlyProfile( ); } - Html::ajaxFooter(); - break; + $foreignKey = $parent->getForeignKeyField(); + $parent::showSubForm($item, $_REQUEST["id"], ['parent' => $parent, + $foreignKey => $_REQUEST[$foreignKey]]); + } else { + echo __('Access denied'); + } + + Html::ajaxFooter(); } diff --git a/ajax/unlockobject.php b/ajax/unlockobject.php index f5e7f011e53..5c533fc53df 100644 --- a/ajax/unlockobject.php +++ b/ajax/unlockobject.php @@ -46,23 +46,23 @@ Session::checkLoginUser(); $ret = 0; -if (isset($_GET['unlock']) && isset($_GET["id"])) { +if (isset($_POST['unlock']) && isset($_POST["id"])) { // then we may have something to unlock $ol = new ObjectLock(); - if ($ol->getFromDB($_GET["id"]) + if ($ol->getFromDB($_POST["id"]) && $ol->deleteFromDB(1)) { - if (isset($_GET['force'])) { + if (isset($_POST['force'])) { Log::history($ol->fields['items_id'], $ol->fields['itemtype'], [0, '', ''], 0, Log::HISTORY_UNLOCK_ITEM); } $ret = 1; } -} else if (isset($_GET['requestunlock']) - && isset($_GET["id"])) { +} else if (isset($_POST['requestunlock']) + && isset($_POST["id"])) { // the we must ask for unlock $ol = new ObjectLock(); - if ($ol->getFromDB( $_GET["id"])) { + if ($ol->getFromDB($_POST["id"])) { NotificationEvent::raiseEvent( 'unlock', $ol ); $ret = 1; } diff --git a/css/styles.scss b/css/styles.scss index 4039c21dba8..a3cbb386f93 100644 --- a/css/styles.scss +++ b/css/styles.scss @@ -4195,6 +4195,8 @@ a.copyright { .edit_document, .delete_document { font-size: 1.5em !important; +} +.edit_document { margin-left: .5em; } diff --git a/front/change.form.php b/front/change.form.php index 7c62d122f9d..a9036bb12d9 100644 --- a/front/change.form.php +++ b/front/change.form.php @@ -120,10 +120,10 @@ //TRANS: %s is the user login sprintf(__('%s adds an actor'), $_SESSION["glpiname"])); Html::redirect(Change::getFormURLWithID($_POST['changes_id'])); -} else if (isset($_REQUEST['delete_document'])) { - $change->getFromDB((int)$_REQUEST['changes_id']); +} else if (isset($_POST['delete_document'])) { + $change->getFromDB((int)$_POST['changes_id']); $doc = new Document(); - $doc->getFromDB(intval($_REQUEST['documents_id'])); + $doc->getFromDB(intval($_POST['documents_id'])); if ($doc->can($doc->getID(), UPDATE)) { $document_item = new Document_Item; $found_document_items = $document_item->find([ diff --git a/front/config.form.php b/front/config.form.php index 866f122ef46..548f161b5f7 100644 --- a/front/config.form.php +++ b/front/config.form.php @@ -60,16 +60,16 @@ $config->update($_POST); Html::redirect(Toolbox::getItemTypeFormURL('Config')); } -if (!empty($_GET['reset_opcache'])) { +if (!empty($_POST['reset_opcache'])) { $config->checkGlobal(UPDATE); if (opcache_reset()) { Session::addMessageAfterRedirect(__('Cache reset successful')); } Html::redirect(Toolbox::getItemTypeFormURL('Config')); } -if (!empty($_GET['reset_cache'])) { +if (!empty($_POST['reset_cache'])) { $config->checkGlobal(UPDATE); - $cache = isset($_GET['optname']) ? Config::getCache($_GET['optname']) : $GLPI_CACHE; + $cache = isset($_POST['optname']) ? Config::getCache($_POST['optname']) : $GLPI_CACHE; if ($cache->clear()) { Session::addMessageAfterRedirect(__('Cache reset successful')); } diff --git a/front/problem.form.php b/front/problem.form.php index 32b00d3ec25..73c48ec3345 100644 --- a/front/problem.form.php +++ b/front/problem.form.php @@ -123,10 +123,10 @@ //TRANS: %s is the user login sprintf(__('%s adds an actor'), $_SESSION["glpiname"])); Html::redirect($problem->getFormURLWithID($_POST['problems_id'])); -} else if (isset($_REQUEST['delete_document'])) { - $problem->getFromDB((int)$_REQUEST['problems_id']); +} else if (isset($_POST['delete_document'])) { + $problem->getFromDB((int)$_POST['problems_id']); $doc = new Document(); - $doc->getFromDB(intval($_REQUEST['documents_id'])); + $doc->getFromDB(intval($_POST['documents_id'])); if ($doc->can($doc->getID(), UPDATE)) { $document_item = new Document_Item; $found_document_items = $document_item->find([ diff --git a/front/ticket.form.php b/front/ticket.form.php index e98c1d0df68..5795bfd4df0 100644 --- a/front/ticket.form.php +++ b/front/ticket.form.php @@ -184,10 +184,10 @@ //TRANS: %s is the user login sprintf(__('%s adds an actor'), $_SESSION["glpiname"])); Html::redirect(Ticket::getFormURLWithID($_POST['tickets_id'])); -} else if (isset($_REQUEST['delete_document'])) { - $track->getFromDB((int)$_REQUEST['tickets_id']); +} else if (isset($_POST['delete_document'])) { + $track->getFromDB((int)$_POST['tickets_id']); $doc = new Document(); - $doc->getFromDB(intval($_REQUEST['documents_id'])); + $doc->getFromDB(intval($_POST['documents_id'])); if ($doc->can($doc->getID(), UPDATE)) { $document_item = new Document_Item; $found_document_items = $document_item->find([ diff --git a/inc/commonitilobject.class.php b/inc/commonitilobject.class.php index c453200d77c..f1384e4c000 100644 --- a/inc/commonitilobject.class.php +++ b/inc/commonitilobject.class.php @@ -7428,11 +7428,16 @@ function showTimeline($rand) { $doc = new Document(); $doc->getFromDB($item_i['id']); if ($doc->can($item_i['id'], UPDATE)) { - echo ""; - echo "" . _sx('button', 'Delete permanently') . ""; + echo '
'; + echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]); + echo Html::hidden('delete_document', ['value' => 1]); + echo Html::hidden('documents_id', ['value' => $item_i['id']]); + echo Html::hidden($foreignKey, ['value' => $this->getID()]); + echo '
'; + echo ''; + echo ""; } } else { $msg = sprintf(__s('%s extension is not present'), $ext); @@ -1730,9 +1735,15 @@ function showPerformanceInformations() { if ($GLPI_CACHE instanceof FlushableInterface) { echo ""; - echo ""; + echo '
'; + echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]); + echo Html::hidden('reset_cache', ['value' => 1]); + echo Html::hidden('optname', ['value' => 'cache_db']); + echo ''; + echo '
'; + echo ""; } echo "" . __('Translation cache') . ""; @@ -1745,9 +1756,15 @@ function showPerformanceInformations() { if ($translation_cache instanceof FlushableInterface) { echo ""; - echo ""; + echo '
'; + echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]); + echo Html::hidden('reset_cache', ['value' => 1]); + echo Html::hidden('optname', ['value' => 'cache_trans']); + echo ''; + echo '
'; + echo ""; } echo "\n"; diff --git a/inc/html.class.php b/inc/html.class.php index bffaa4b8115..9131a4bbbca 100644 --- a/inc/html.class.php +++ b/inc/html.class.php @@ -1231,6 +1231,11 @@ static function includeHeader($title = '', $sector = 'none', $item = 'none', $op // auto desktop / mobile viewport echo ""; + // CSRF token used for AJAX calls + // Ensure this token is not shared with the page, as result would be that some AJAX request will consume + // the token that would have been used by a form submitted from the same page. + echo ''; + //detect theme $theme = isset($_SESSION['glpipalette']) ? $_SESSION['glpipalette'] : 'auror'; diff --git a/inc/includes.php b/inc/includes.php index ea98092141f..3be5f26c3eb 100644 --- a/inc/includes.php +++ b/inc/includes.php @@ -147,8 +147,14 @@ if (GLPI_USE_CSRF_CHECK && !isAPI() && isset($_POST) && is_array($_POST) && count($_POST)) { - // No ajax pages - if (!preg_match(':'.$CFG_GLPI['root_doc'].'(/(plugins|marketplace)/[^/]*|)/ajax/:', $_SERVER['REQUEST_URI'])) { + if (preg_match(':'.$CFG_GLPI['root_doc'].'(/(plugins|marketplace)/[^/]*|)/ajax/:', $_SERVER['REQUEST_URI']) === 1) { + // Keep CSRF token as many AJAX requests may be made at the same time. + // This is due to the fact that read operations are often made using POST method (see #277). + define('GLPI_KEEP_CSRF_TOKEN', true); + + // For AJAX requests, check CSRF token located into "X-Glpi-Csrf-Token" header. + Session::checkCSRF(['_glpi_csrf_token' => $_SERVER['HTTP_X_GLPI_CSRF_TOKEN'] ?? '']); + } else { Session::checkCSRF($_POST); } } diff --git a/inc/objectlock.class.php b/inc/objectlock.class.php index 01ba3039664..2f59918aef2 100644 --- a/inc/objectlock.class.php +++ b/inc/objectlock.class.php @@ -112,9 +112,9 @@ static function getLockableObjects() { **/ private function autoLockMode() { // if !autolock mode then we are going to view the item with read-only profile - // if isset($_REQUEST['lockwrite']) then will behave like if automode was true but for this object only and for the lifetime of the session + // if isset($_POST['lockwrite']) then will behave like if automode was true but for this object only and for the lifetime of the session // look for lockwrite request - if (isset($_REQUEST['lockwrite'])) { + if (isset($_POST['lockwrite'])) { $_SESSION['glpilock_autolock_items'][ $this->itemtype ][$this->itemid] = 1; } @@ -139,10 +139,15 @@ private function getScriptToUnlock() { $ret = Html::scriptBlock(" function unlockIt(obj) { function callUnlock( ) { - $.ajax({ + $.post({ url: '".$CFG_GLPI['root_doc']."/ajax/unlockobject.php', cache: false, - data: 'unlock=1&force=1&id=".$this->fields['id']."', + data: { + unlock: 1, + force: 1, + id: {$this->fields['id']} + }, + dataType: 'json', success: function( data, textStatus, jqXHR ) { ". Html::jsConfirmCallback(__('Reload page?'), __('Item unlocked!'), "function() { window.location.reload(true); @@ -222,10 +227,14 @@ private function setLockedByMessage() { $ret = Html::scriptBlock(" function askUnlock() { ". Html::jsConfirmCallback( __('Ask for unlock this item?'), $this->itemtypename." #".$this->itemid, "function() { - $.ajax({ + $.post({ url: '".$CFG_GLPI['root_doc']."/ajax/unlockobject.php', cache: false, - data: 'requestunlock=1&id=".$this->fields['id']."', + data: { + requestunlock: 1, + id: {$this->fields['id']} + }, + dataType: 'json', success: function( data, textStatus, jqXHR ) { ".Html::jsAlertCallback($userdata['name'], __('Request sent to') )." } @@ -242,7 +251,7 @@ function askUnlock() { $('#alertMe').change(function( eventObject ){ if( this.checked ) { lockStatusTimer = setInterval( function() { - $.ajax({ + $.get({ url: '".$CFG_GLPI['root_doc']."/ajax/unlockobject.php', cache: false, data: 'lockstatus=1&id=".$this->fields['id']."', @@ -289,16 +298,15 @@ function askUnlock() { **/ private function setReadOnlyMessage() { - echo Html::scriptBlock(" - function requestLock() { - window.location.assign( window.location.href + '&lockwrite=1'); - } - "); - $msg = ""; $msg .= __('Warning: read-only!').""; - $msg .= "". - __('Request write on ').$this->itemtypename." #".$this->itemid.""; + $msg .= '
'; + $msg .= Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]); + $msg .= Html::hidden('lockwrite', ['value' => 1]); + $msg .= ''; + $msg .= '
'; + $this->displayLockMessage($msg); } @@ -323,17 +331,28 @@ private function lockObject() { echo Html::scriptBlock( "$(function() { $(window).on('beforeunload', function() { var fallback_request = function() { - $.ajax({ + $.post({ url: '".$CFG_GLPI['root_doc']."/ajax/unlockobject.php', async: false, cache: false, - data: 'unlock=1&id=$id' + data: { + unlock: 1, + id: $id + }, + dataType: 'json' }); }; if (typeof window.fetch !== 'undefined') { - fetch('".$CFG_GLPI['root_doc']."/ajax/unlockobject.php?unlock=1&id=$id', { + fetch('".$CFG_GLPI['root_doc']."/ajax/unlockobject.php', { + method: 'POST', cache: 'no-cache', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'X-Glpi-Csrf-Token': getAjaxCsrfToken() + }, + body: JSON.stringify({unlock: 1, id: {$id}}) }).catch(function(error) { //fallback if fetch fails fallback_request(); diff --git a/inc/session.class.php b/inc/session.class.php index 5342166fc50..3c1da8bc659 100644 --- a/inc/session.class.php +++ b/inc/session.class.php @@ -1208,24 +1208,34 @@ static function isReadOnlyAccount() { /** * Get new CSRF token * + * @param bool $standalone + * Generates a standalone token that will not be shared with other component of current request. + * * @since 0.83.3 * * @return string **/ - static public function getNewCSRFToken() { + static public function getNewCSRFToken(bool $standalone = false) { global $CURRENTCSRFTOKEN; - if (empty($CURRENTCSRFTOKEN)) { + $token = $standalone ? '' : $CURRENTCSRFTOKEN; + + if (empty($token)) { do { - $CURRENTCSRFTOKEN = bin2hex(random_bytes(32)); - } while ($CURRENTCSRFTOKEN == ''); + $token = bin2hex(random_bytes(32)); + } while ($token == ''); } if (!isset($_SESSION['glpicsrftokens'])) { $_SESSION['glpicsrftokens'] = []; } - $_SESSION['glpicsrftokens'][$CURRENTCSRFTOKEN] = time() + GLPI_CSRF_EXPIRES; - return $CURRENTCSRFTOKEN; + $_SESSION['glpicsrftokens'][$token] = time() + GLPI_CSRF_EXPIRES; + + if (!$standalone) { + $CURRENTCSRFTOKEN = $token; + } + + return $token; } @@ -1300,6 +1310,12 @@ static public function checkCSRF($data) { if (GLPI_USE_CSRF_CHECK && (!Session::validateCSRF($data))) { + // Output JSON if requested by client + if (strpos($_SERVER['HTTP_ACCEPT'] ?? '', 'application/json') !== false) { + http_response_code(403); + die(json_encode(["message" => __("The action you have requested is not allowed.")])); + } + Html::displayErrorAndDie(__("The action you have requested is not allowed."), true); } } diff --git a/js/common.js b/js/common.js index e1d108212e5..8a915042230 100644 --- a/js/common.js +++ b/js/common.js @@ -1214,3 +1214,26 @@ function getFlatPickerLocale(language, region) { return language; } } + +/* + * Sends the CSRF token in ajax POST requests headers. + */ +$(document).ajaxSend( + function(event, xhr, settings) { + if (settings.type !== 'POST') { + return; + } + + xhr.setRequestHeader('X-Glpi-Csrf-Token', getAjaxCsrfToken()); + } +); + +/** + * Returns CSRF token that can be used for AJAX requests. + * + * @returns {string|null} + */ +function getAjaxCsrfToken() { + const meta = document.querySelector('meta[property="glpi:csrf_token"]'); + return meta !== null ? meta.getAttribute('content') : null; +} diff --git a/js/dashboard.js b/js/dashboard.js index 95a5bb72244..f3019a9dc6c 100644 --- a/js/dashboard.js +++ b/js/dashboard.js @@ -248,8 +248,7 @@ var Dashboard = { form_data[right_name].push(value); }); - $.ajax({ - method: 'POST', + $.post({ url: CFG_GLPI.root_doc+"/ajax/dashboard.php", data: { action: 'save_rights', @@ -542,8 +541,7 @@ var Dashboard = { var widget = Dashboard.addWidget(form_data); // get the html of the new card and save dashboard - $.ajax({ - method: 'GET', + $.get({ url: CFG_GLPI.root_doc+"/ajax/dashboard.php", data: { action: 'get_card', @@ -608,8 +606,7 @@ var Dashboard = { }); // get the html of the new card and save dashboard - $.ajax({ - method: 'GET', + $.get({ url: CFG_GLPI.root_doc+"/ajax/dashboard.php", data: { action: 'get_filter', @@ -645,8 +642,7 @@ var Dashboard = { }, setLastDashboard: function() { - $.ajax({ - method: 'POST', + $.post({ url: CFG_GLPI.root_doc+"/ajax/dashboard.php", data: { dashboard: Dashboard.current_name, @@ -701,8 +697,7 @@ var Dashboard = { } : null; }); - $.ajax({ - method: 'POST', + $.post({ url: CFG_GLPI.root_doc+"/ajax/dashboard.php", data: { action: 'save_items', diff --git a/js/fileupload.js b/js/fileupload.js index 6e715938dc3..6f73dee58e5 100644 --- a/js/fileupload.js +++ b/js/fileupload.js @@ -40,8 +40,7 @@ function uploadFile(file, editor, input_name) { formdata.append('name', 'filename'); // upload file with ajax - $.ajax({ - type: 'POST', + $.post({ url: CFG_GLPI.root_doc+'/ajax/fileupload.php', data: formdata, processData: false, diff --git a/js/marketplace.js b/js/marketplace.js index e719c97a431..61b02dd89f9 100644 --- a/js/marketplace.js +++ b/js/marketplace.js @@ -58,7 +58,7 @@ $(document).ready(function() { } ajax_done = false; - $.get(ajax_url, { + $.post(ajax_url, { 'action': action, 'key': plugin_key }).done(function(html) {