From b6a5f90fd3465b4807fb745dee3ccef2e1146061 Mon Sep 17 00:00:00 2001 From: Aina Sitraka <35221835+aynsix@users.noreply.github.com> Date: Tue, 3 Oct 2023 17:28:33 +0300 Subject: [PATCH] PHRAS-3857 Check CSRF token on Prod and Admin forms (#4361) * csrf token form * add csrf token * add csrf * add csrf * add csrf * test * test * test * add form token in report * csrf token upload * lazaret csrf form * upload test * lazaret test * add csrf token * fix test * fix set cover publication --------- Co-authored-by: jygaulier --- Phraseanet-production-client/dist/lightbox.js | 3 +- .../dist/lightbox.min.js | 3 +- .../dist/production.js | 36 ++++++++++++---- .../dist/production.min.js | 36 ++++++++++++---- .../src/components/lightbox/index.js | 3 +- .../src/components/record/delete.js | 11 ++++- .../src/components/record/move.js | 3 +- .../src/components/thesaurus/index.js | 8 +++- .../src/components/ui/workzone/index.js | 11 ++++- .../src/components/uploader/index.js | 2 + lib/Alchemy/Phrasea/Controller/Controller.php | 31 ++++++++++++++ .../Phrasea/Controller/LightboxController.php | 6 +++ .../Controller/Prod/BasketController.php | 18 ++++++++ .../Controller/Prod/DownloadController.php | 4 ++ .../Controller/Prod/ExportController.php | 13 ++++++ .../Controller/Prod/LazaretController.php | 22 +++++++++- .../Prod/MoveCollectionController.php | 6 +++ .../Controller/Prod/PrinterController.php | 6 +++ .../Controller/Prod/PropertyController.php | 12 ++++++ .../Controller/Prod/PushController.php | 12 ++++++ .../Controller/Prod/QueryController.php | 5 +++ .../Controller/Prod/RecordController.php | 6 +++ .../Controller/Prod/RootController.php | 4 ++ .../Controller/Prod/StoryController.php | 12 ++++++ .../Controller/Prod/ToolsController.php | 21 ++++++++++ .../Controller/Prod/UploadController.php | 9 ++++ .../Controller/Report/RootController.php | 5 +++ .../Thesaurus/ThesaurusXmlHttpController.php | 4 ++ .../Order/Controller/ProdOrderController.php | 4 ++ .../Controller/PSExposeController.php | 29 ++++++++++++- .../Controller/ProdReportController.php | 21 ++++++++-- templates/web/common/dialog_export.html.twig | 12 ++++-- templates/web/lightbox/sc_note.html.twig | 1 + templates/web/prod/Baskets/Create.html.twig | 1 + templates/web/prod/Baskets/Reorder.html.twig | 33 ++++++++------- templates/web/prod/Baskets/Update.html.twig | 1 + templates/web/prod/Story/Create.html.twig | 2 +- templates/web/prod/Story/Reorder.html.twig | 1 + templates/web/prod/User/Add.html.twig | 1 + .../web/prod/WorkZone/ExposeEdit.html.twig | 5 ++- .../WorkZone/ExposeFieldMapping.html.twig | 2 + .../web/prod/WorkZone/ExposeNew.html.twig | 5 ++- .../prod/WorkZone/ExposeOauthLogin.html.twig | 1 + .../ExposePublicationAssets.html.twig | 1 + .../web/prod/actions/Property/index.html.twig | 1 + .../web/prod/actions/Property/type.html.twig | 1 + templates/web/prod/actions/Push.html.twig | 1 + .../web/prod/actions/Tools/index.html.twig | 9 +++- .../prod/actions/collection_default.html.twig | 1 + .../delete_records_confirm_form.html.twig | 1 + .../prod/actions/printer_default.html.twig | 3 +- templates/web/prod/index.html.twig | 1 + templates/web/prod/tab_thesaurus.html.twig | 1 + templates/web/prod/upload/lazaret.html.twig | 4 ++ templates/web/prod/upload/upload.html.twig | 2 +- .../web/report/report_layout_child.html.twig | 3 ++ .../Phrasea/Application/LightboxTest.php | 6 ++- .../Phrasea/Controller/Prod/BasketTest.php | 20 +++++++-- .../Phrasea/Controller/Prod/DownloadTest.php | 18 ++++++-- .../Phrasea/Controller/Prod/ExportTest.php | 16 +++++-- .../Phrasea/Controller/Prod/LazaretTest.php | 42 +++++++++++++++---- .../Phrasea/Controller/Prod/OrderTest.php | 8 +++- .../Phrasea/Controller/Prod/PrinterTest.php | 5 ++- .../Phrasea/Controller/Prod/PropertyTest.php | 9 +++- .../Phrasea/Controller/Prod/QueryTest.php | 3 +- .../Phrasea/Controller/Prod/RecordsTest.php | 5 ++- .../Phrasea/Controller/Prod/StoryTest.php | 21 +++++++--- .../Phrasea/Controller/Prod/ToolsTest.php | 7 ++++ .../Phrasea/Controller/Prod/UploadTest.php | 33 +++++++++++---- tests/classes/PhraseanetTestCase.php | 8 ++++ tests/classes/record/adapterTest.php | 6 ++- 71 files changed, 567 insertions(+), 100 deletions(-) diff --git a/Phraseanet-production-client/dist/lightbox.js b/Phraseanet-production-client/dist/lightbox.js index e2968fcd1f..aac0a3a98f 100644 --- a/Phraseanet-production-client/dist/lightbox.js +++ b/Phraseanet-production-client/dist/lightbox.js @@ -1064,7 +1064,8 @@ var lightbox = function lightbox(services) { url: '/lightbox/ajax/SET_NOTE/' + sselcont_id + '/', dataType: 'json', data: { - note: note + note: note, + lightbox_token: (0, _jquery2.default)(button).closest('form').find('input[name=lightbox_token]').val() }, success: function success(datas) { _hideNotes(container); diff --git a/Phraseanet-production-client/dist/lightbox.min.js b/Phraseanet-production-client/dist/lightbox.min.js index e2968fcd1f..aac0a3a98f 100644 --- a/Phraseanet-production-client/dist/lightbox.min.js +++ b/Phraseanet-production-client/dist/lightbox.min.js @@ -1064,7 +1064,8 @@ var lightbox = function lightbox(services) { url: '/lightbox/ajax/SET_NOTE/' + sselcont_id + '/', dataType: 'json', data: { - note: note + note: note, + lightbox_token: (0, _jquery2.default)(button).closest('form').find('input[name=lightbox_token]').val() }, success: function success(datas) { _hideNotes(container); diff --git a/Phraseanet-production-client/dist/production.js b/Phraseanet-production-client/dist/production.js index 72e148cdfd..0ea0d75acd 100644 --- a/Phraseanet-production-client/dist/production.js +++ b/Phraseanet-production-client/dist/production.js @@ -10135,6 +10135,10 @@ var workzone = function workzone(services) { data: formData, success: function success(data) { (0, _jquery2.default)('#DIALOG-field-mapping').dialog('close'); + }, + error: function error(xhr, status, _error) { + var err = JSON.parse(xhr.responseText); + alert(err.message); } }); }); @@ -10221,6 +10225,10 @@ var workzone = function workzone(services) { data: formData, success: function success(data) { (0, _jquery2.default)('#DIALOG-field-mapping').dialog('close'); + }, + error: function error(xhr, status, _error2) { + var err = JSON.parse(xhr.responseText); + alert(err.message); } }); }); @@ -10981,7 +10989,8 @@ var workzone = function workzone(services) { dataType: 'json', data: { exposeName: '' + exposeName, - publicationData: publicationData + publicationData: publicationData, + prodExposeEdit_token: (0, _jquery2.default)(this).find('input[name="prodExposeEdit_token"]').val() }, success: function success(data) { if (data.success) { @@ -12282,7 +12291,9 @@ var thesaurusService = function thesaurusService(services) { sbas[i].seeker = _jquery2.default.ajax({ url: _zurl, type: 'POST', - data: [], + data: { + prodTabThesaurus_token: (0, _jquery2.default)('form.thesaurus-filter-submit-action input[name=prodTabThesaurus_token]').val() + }, dataType: 'json', success: function success(j) { var z = '#TX_P\\.' + j.parm.sbid + '\\.T'; @@ -12319,7 +12330,9 @@ var thesaurusService = function thesaurusService(services) { sbas[i].seeker = _jquery2.default.ajax({ url: zurl, type: 'POST', - data: [], + data: { + prodTabThesaurus_token: (0, _jquery2.default)('form.thesaurus-filter-submit-action input[name=prodTabThesaurus_token]').val() + }, dataType: 'json', success: function success(j) { var z = '#TX_P\\.' + j.parm.sbid + '\\.T'; @@ -22040,7 +22053,8 @@ var moveRecord = function moveRecord(services) { var datas = { lst: (0, _jquery2.default)('input[name="lst"]', $form).val(), base_id: (0, _jquery2.default)('select[name="base_id"]', $form).val(), - chg_coll_son: coll_son + chg_coll_son: coll_son, + prodMoveCollection_token: (0, _jquery2.default)('input[name="prodMoveCollection_token"]', $form).val() }; var buttonPanel = $dialog.getDomElement().closest('.ui-dialog').find('.ui-dialog-buttonpane'); @@ -62298,6 +62312,7 @@ var deleteRecord = function deleteRecord(services) { $trash_counter = $form.find(".to_trash_count"), $loader = $form.find(".form-action-loader"); var lst = (0, _jquery2.default)("input[name='lst']", $form).val().split(';'); + var csrfToken = (0, _jquery2.default)("input[name='prodDeleteRecord_token']", $form).val(); /** * same parameters for every delete call, except the list of (CHUNKSIZE) records @@ -62307,9 +62322,10 @@ var deleteRecord = function deleteRecord(services) { type: $form.attr("method"), url: $form.attr("action"), data: { - 'lst': "" // set in f + lst: '', // set in f + prodDeleteRecord_token: csrfToken }, - dataType: "json" + dataType: 'json' }; var runningTasks = 0, @@ -62335,7 +62351,11 @@ var deleteRecord = function deleteRecord(services) { } // pop & truncate ajaxParms.data.lst = lst.splice(0, CHUNKSIZE).join(';'); - _jquery2.default.ajax(ajaxParms).success(function (data) { + _jquery2.default.ajax(ajaxParms).error(function (data) { + fCancel(); + $dialog.close(); + alert('invalid csrf token delete form'); + }).success(function (data) { // prod feedback only if result ok _jquery2.default.each(data, function (i, n) { var imgt = (0, _jquery2.default)('#IMGT_' + n), @@ -68256,6 +68276,8 @@ var uploader = function uploader(services) { params.push((0, _jquery2.default)('input', (0, _jquery2.default)('.collection-status:visible', uploaderInstance.getSettingsBox())).serializeArray()); params.push((0, _jquery2.default)('select', uploaderInstance.getSettingsBox()).serializeArray()); + params.push([{ name: 'prodUpload_token', value: (0, _jquery2.default)('input[name=prodUpload_token]').val() }]); + _jquery2.default.each(params, function (i, p) { _jquery2.default.each(p, function (i, f) { data.formData.push(f); diff --git a/Phraseanet-production-client/dist/production.min.js b/Phraseanet-production-client/dist/production.min.js index 72e148cdfd..0ea0d75acd 100644 --- a/Phraseanet-production-client/dist/production.min.js +++ b/Phraseanet-production-client/dist/production.min.js @@ -10135,6 +10135,10 @@ var workzone = function workzone(services) { data: formData, success: function success(data) { (0, _jquery2.default)('#DIALOG-field-mapping').dialog('close'); + }, + error: function error(xhr, status, _error) { + var err = JSON.parse(xhr.responseText); + alert(err.message); } }); }); @@ -10221,6 +10225,10 @@ var workzone = function workzone(services) { data: formData, success: function success(data) { (0, _jquery2.default)('#DIALOG-field-mapping').dialog('close'); + }, + error: function error(xhr, status, _error2) { + var err = JSON.parse(xhr.responseText); + alert(err.message); } }); }); @@ -10981,7 +10989,8 @@ var workzone = function workzone(services) { dataType: 'json', data: { exposeName: '' + exposeName, - publicationData: publicationData + publicationData: publicationData, + prodExposeEdit_token: (0, _jquery2.default)(this).find('input[name="prodExposeEdit_token"]').val() }, success: function success(data) { if (data.success) { @@ -12282,7 +12291,9 @@ var thesaurusService = function thesaurusService(services) { sbas[i].seeker = _jquery2.default.ajax({ url: _zurl, type: 'POST', - data: [], + data: { + prodTabThesaurus_token: (0, _jquery2.default)('form.thesaurus-filter-submit-action input[name=prodTabThesaurus_token]').val() + }, dataType: 'json', success: function success(j) { var z = '#TX_P\\.' + j.parm.sbid + '\\.T'; @@ -12319,7 +12330,9 @@ var thesaurusService = function thesaurusService(services) { sbas[i].seeker = _jquery2.default.ajax({ url: zurl, type: 'POST', - data: [], + data: { + prodTabThesaurus_token: (0, _jquery2.default)('form.thesaurus-filter-submit-action input[name=prodTabThesaurus_token]').val() + }, dataType: 'json', success: function success(j) { var z = '#TX_P\\.' + j.parm.sbid + '\\.T'; @@ -22040,7 +22053,8 @@ var moveRecord = function moveRecord(services) { var datas = { lst: (0, _jquery2.default)('input[name="lst"]', $form).val(), base_id: (0, _jquery2.default)('select[name="base_id"]', $form).val(), - chg_coll_son: coll_son + chg_coll_son: coll_son, + prodMoveCollection_token: (0, _jquery2.default)('input[name="prodMoveCollection_token"]', $form).val() }; var buttonPanel = $dialog.getDomElement().closest('.ui-dialog').find('.ui-dialog-buttonpane'); @@ -62298,6 +62312,7 @@ var deleteRecord = function deleteRecord(services) { $trash_counter = $form.find(".to_trash_count"), $loader = $form.find(".form-action-loader"); var lst = (0, _jquery2.default)("input[name='lst']", $form).val().split(';'); + var csrfToken = (0, _jquery2.default)("input[name='prodDeleteRecord_token']", $form).val(); /** * same parameters for every delete call, except the list of (CHUNKSIZE) records @@ -62307,9 +62322,10 @@ var deleteRecord = function deleteRecord(services) { type: $form.attr("method"), url: $form.attr("action"), data: { - 'lst': "" // set in f + lst: '', // set in f + prodDeleteRecord_token: csrfToken }, - dataType: "json" + dataType: 'json' }; var runningTasks = 0, @@ -62335,7 +62351,11 @@ var deleteRecord = function deleteRecord(services) { } // pop & truncate ajaxParms.data.lst = lst.splice(0, CHUNKSIZE).join(';'); - _jquery2.default.ajax(ajaxParms).success(function (data) { + _jquery2.default.ajax(ajaxParms).error(function (data) { + fCancel(); + $dialog.close(); + alert('invalid csrf token delete form'); + }).success(function (data) { // prod feedback only if result ok _jquery2.default.each(data, function (i, n) { var imgt = (0, _jquery2.default)('#IMGT_' + n), @@ -68256,6 +68276,8 @@ var uploader = function uploader(services) { params.push((0, _jquery2.default)('input', (0, _jquery2.default)('.collection-status:visible', uploaderInstance.getSettingsBox())).serializeArray()); params.push((0, _jquery2.default)('select', uploaderInstance.getSettingsBox()).serializeArray()); + params.push([{ name: 'prodUpload_token', value: (0, _jquery2.default)('input[name=prodUpload_token]').val() }]); + _jquery2.default.each(params, function (i, p) { _jquery2.default.each(p, function (i, f) { data.formData.push(f); diff --git a/Phraseanet-production-client/src/components/lightbox/index.js b/Phraseanet-production-client/src/components/lightbox/index.js index 4312cdb8bd..cf9875906f 100644 --- a/Phraseanet-production-client/src/components/lightbox/index.js +++ b/Phraseanet-production-client/src/components/lightbox/index.js @@ -1036,7 +1036,8 @@ const lightbox = services => { url: '/lightbox/ajax/SET_NOTE/' + sselcont_id + '/', dataType: 'json', data: { - note: note + note: note, + lightbox_token: $(button).closest('form').find('input[name=lightbox_token]').val() }, success: function (datas) { _hideNotes(container); diff --git a/Phraseanet-production-client/src/components/record/delete.js b/Phraseanet-production-client/src/components/record/delete.js index 10f2aefe8a..e6ed7eddc3 100644 --- a/Phraseanet-production-client/src/components/record/delete.js +++ b/Phraseanet-production-client/src/components/record/delete.js @@ -76,6 +76,7 @@ const deleteRecord = (services) => { $trash_counter = $form.find(".to_trash_count"), $loader = $form.find(".form-action-loader"); let lst = $("input[name='lst']", $form).val().split(';'); + let csrfToken = $("input[name='prodDeleteRecord_token']", $form).val(); /** * same parameters for every delete call, except the list of (CHUNKSIZE) records @@ -85,9 +86,10 @@ const deleteRecord = (services) => { type: $form.attr("method"), url: $form.attr("action"), data: { - 'lst': "" // set in f + lst: '', // set in f + prodDeleteRecord_token: csrfToken }, - dataType: "json" + dataType: 'json' }; let runningTasks = 0, // number of running tasks @@ -113,6 +115,11 @@ const deleteRecord = (services) => { // pop & truncate ajaxParms.data.lst = lst.splice(0, CHUNKSIZE).join(';'); $.ajax(ajaxParms) + .error(function (data) { + fCancel(); + $dialog.close(); + alert('invalid csrf token delete form'); + }) .success(function (data) { // prod feedback only if result ok $.each(data, function (i, n) { let imgt = $('#IMGT_' + n), diff --git a/Phraseanet-production-client/src/components/record/move.js b/Phraseanet-production-client/src/components/record/move.js index 77ed60b9a5..3e2d840b34 100644 --- a/Phraseanet-production-client/src/components/record/move.js +++ b/Phraseanet-production-client/src/components/record/move.js @@ -48,7 +48,8 @@ const moveRecord = (services) => { var datas = { lst: $('input[name="lst"]', $form).val(), base_id: $('select[name="base_id"]', $form).val(), - chg_coll_son: coll_son + chg_coll_son: coll_son, + prodMoveCollection_token: $('input[name="prodMoveCollection_token"]', $form).val() }; var buttonPanel = $dialog.getDomElement() diff --git a/Phraseanet-production-client/src/components/thesaurus/index.js b/Phraseanet-production-client/src/components/thesaurus/index.js index 37a6493a8a..c9f9d6c9e8 100644 --- a/Phraseanet-production-client/src/components/thesaurus/index.js +++ b/Phraseanet-production-client/src/components/thesaurus/index.js @@ -636,7 +636,9 @@ const thesaurusService = services => { sbas[i].seeker = $.ajax({ url: zurl, type: 'POST', - data: [], + data: { + prodTabThesaurus_token: $('form.thesaurus-filter-submit-action input[name=prodTabThesaurus_token]').val() + }, dataType: 'json', success: function (j) { var z = '#TX_P\\.' + j.parm.sbid + '\\.T'; @@ -680,7 +682,9 @@ const thesaurusService = services => { sbas[i].seeker = $.ajax({ url: zurl, type: 'POST', - data: [], + data: { + prodTabThesaurus_token: $('form.thesaurus-filter-submit-action input[name=prodTabThesaurus_token]').val() + }, dataType: 'json', success: function (j) { var z = '#TX_P\\.' + j.parm.sbid + '\\.T'; diff --git a/Phraseanet-production-client/src/components/ui/workzone/index.js b/Phraseanet-production-client/src/components/ui/workzone/index.js index e0d3392f30..83bf5b083e 100644 --- a/Phraseanet-production-client/src/components/ui/workzone/index.js +++ b/Phraseanet-production-client/src/components/ui/workzone/index.js @@ -210,6 +210,10 @@ const workzone = (services) => { data: formData, success: function (data) { $('#DIALOG-field-mapping').dialog('close'); + }, + error: function (xhr, status, error) { + let err = JSON.parse(xhr.responseText); + alert(err.message); } }); }); @@ -296,6 +300,10 @@ const workzone = (services) => { data: formData, success: function (data) { $('#DIALOG-field-mapping').dialog('close'); + }, + error: function (xhr, status, error) { + let err = JSON.parse(xhr.responseText); + alert(err.message); } }); @@ -1068,7 +1076,8 @@ const workzone = (services) => { dataType: 'json', data: { exposeName: `${exposeName}`, - publicationData: publicationData + publicationData: publicationData, + prodExposeEdit_token: $(this).find('input[name="prodExposeEdit_token"]').val() }, success: function (data) { if (data.success) { diff --git a/Phraseanet-production-client/src/components/uploader/index.js b/Phraseanet-production-client/src/components/uploader/index.js index a82b56208d..007caf1fa5 100644 --- a/Phraseanet-production-client/src/components/uploader/index.js +++ b/Phraseanet-production-client/src/components/uploader/index.js @@ -417,6 +417,8 @@ const uploader = (services) => { params.push($('input', $('.collection-status:visible', uploaderInstance.getSettingsBox())).serializeArray()); params.push($('select', uploaderInstance.getSettingsBox()).serializeArray()); + params.push([{name: 'prodUpload_token', value: $('input[name=prodUpload_token]').val()}]); + $.each(params, function (i, p) { $.each(p, function (i, f) { data.formData.push(f); diff --git a/lib/Alchemy/Phrasea/Controller/Controller.php b/lib/Alchemy/Phrasea/Controller/Controller.php index 79890b3501..82b6c291e2 100644 --- a/lib/Alchemy/Phrasea/Controller/Controller.php +++ b/lib/Alchemy/Phrasea/Controller/Controller.php @@ -15,7 +15,9 @@ use Alchemy\Phrasea\Authentication\Authenticator; use Alchemy\Phrasea\Core\Configuration\PropertyAccess; use Alchemy\Phrasea\Model\Entities\User; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Session\Session; class Controller { @@ -112,6 +114,35 @@ public function getAuthenticatedUser() return $this->getAuthenticator()->getUser(); } + public function setSessionFormToken($formName) + { + $randomValue = bin2hex(random_bytes(35)); + $this->app['session']->set($formName.'_token', $randomValue); + + return $randomValue; + } + + public function getSessionFormToken($formName) + { + return $this->app['session']->get($formName.'_token'); + } + + public function isCrsfValid(Request $request, $formName) + { + if (!$request->isMethod("POST") && !$request->isMethod("PUT")) { + return false; + } + + $formTokenName = $formName . '_token'; + $formToken = (string) $request->request->get($formTokenName); + + if (empty($formToken) || $formToken != $this->getSessionFormToken($formName)) { + return false; + } + + return true; + } + /** * @return PropertyAccess */ diff --git a/lib/Alchemy/Phrasea/Controller/LightboxController.php b/lib/Alchemy/Phrasea/Controller/LightboxController.php index f17c036ab7..8347903f52 100644 --- a/lib/Alchemy/Phrasea/Controller/LightboxController.php +++ b/lib/Alchemy/Phrasea/Controller/LightboxController.php @@ -264,6 +264,8 @@ private function markBasketRead(Basket $basket) */ private function getValidationTemplate() { + $this->setSessionFormToken('lightbox'); + return 'lightbox/validate.html.twig'; } @@ -338,6 +340,10 @@ public function ajaxReportAction(Basket $basket) */ public function ajaxSetNoteAction(Request $request, $sselcont_id) { + if (!$this->isCrsfValid($request, 'lightbox')) { + return new Response('invalid crsf token form', 403); + } + $note = $request->request->get('note'); if (is_null($note)) { diff --git a/lib/Alchemy/Phrasea/Controller/Prod/BasketController.php b/lib/Alchemy/Phrasea/Controller/Prod/BasketController.php index 57c22d0dbe..843d1d476d 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/BasketController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/BasketController.php @@ -203,6 +203,10 @@ private function getEntityManager() public function createBasket(Request $request) { + if (!$this->isCrsfValid($request, 'prodCreateBasket')) { + return $this->app->json(['success' => false , 'message' => 'invalid form'], 403); + } + $basket = new Basket(); $basket->setName($request->request->get('name', '')); @@ -271,6 +275,10 @@ public function removeBasketElement(Request $request, Basket $basket, $basket_el public function updateBasket(Request $request, Basket $basket) { + if (!$this->isCrsfValid($request, 'prodBasketRename')) { + return $this->app->json(['success' => false , 'message' => 'invalid form'], 403); + } + $success = false; try { @@ -305,16 +313,24 @@ public function updateBasket(Request $request, Basket $basket) public function displayUpdateForm(Basket $basket) { + $this->setSessionFormToken('prodBasketRename'); + return $this->render('prod/Baskets/Update.html.twig', ['basket' => $basket]); } public function displayReorderForm(Basket $basket) { + $this->setSessionFormToken('prodBasketReorder'); + return $this->render('prod/Baskets/Reorder.html.twig', ['basket' => $basket]); } public function reorder(Request $request, Basket $basket) { + if (!$this->isCrsfValid($request, 'prodBasketReorder')) { + return $this->app->json(['success' => false , 'message' => 'invalid form'], 403); + } + $ret = ['success' => false, 'message' => $this->app->trans('An error occured')]; try { $order = $request->request->get('element'); @@ -417,6 +433,8 @@ public function stealElements(Request $request, Basket $basket) public function displayCreateForm() { + $this->setSessionFormToken('prodCreateBasket'); + return $this->render('prod/Baskets/Create.html.twig'); } diff --git a/lib/Alchemy/Phrasea/Controller/Prod/DownloadController.php b/lib/Alchemy/Phrasea/Controller/Prod/DownloadController.php index 9d19d7297c..c575c86431 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/DownloadController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/DownloadController.php @@ -29,6 +29,10 @@ class DownloadController extends Controller */ public function checkDownload(Request $request) { + if (!$this->isCrsfValid($request, 'prodExportDownload')) { + $this->app->abort(403); + } + $lst = $request->request->get('lst'); $ssttid = $request->request->get('ssttid', ''); $subdefs = $request->request->get('obj', []); diff --git a/lib/Alchemy/Phrasea/Controller/Prod/ExportController.php b/lib/Alchemy/Phrasea/Controller/Prod/ExportController.php index 0b69f551a1..4ef91b65c5 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/ExportController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/ExportController.php @@ -44,6 +44,11 @@ public function displayMultiExport(Request $request) $request->request->get('story') ); + $this->setSessionFormToken('prodExportDownload'); + $this->setSessionFormToken('prodExportEmail'); + $this->setSessionFormToken('prodExportFTP'); + $this->setSessionFormToken('prodExportOrder'); + return new Response($this->render('common/dialog_export.html.twig', [ 'download' => $download, 'ssttid' => $request->request->get('ssel'), @@ -90,6 +95,10 @@ public function testFtpConnexion(Request $request) */ public function exportFtp(Request $request) { + if (!$this->isCrsfValid($request, 'prodExportFTP')) { + return $this->app->json(['message' => 'invalid export ftp form'], 403); + } + $download = new \set_exportftp($this->app, $request->request->get('lst'), $request->request->get('ssttid')); $mandatoryParameters = ['address', 'login', 'obj']; @@ -153,6 +162,10 @@ public function exportFtp(Request $request) */ public function exportMail(Request $request) { + if (!$this->isCrsfValid($request, 'prodExportEmail')) { + return $this->app->json(['message' => 'invalid export mail form'], 403); + } + set_time_limit(0); session_write_close(); ignore_user_abort(true); diff --git a/lib/Alchemy/Phrasea/Controller/Prod/LazaretController.php b/lib/Alchemy/Phrasea/Controller/Prod/LazaretController.php index 7fcddb0b28..85cf5195d0 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/LazaretController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/LazaretController.php @@ -53,6 +53,8 @@ public function listElement(Request $request) $lazaretFiles = $this->getLazaretFileRepository()->findPerPage($baseIds, $offset, $perPage); } + $this->setSessionFormToken('prodLazaret'); + return $this->render('prod/upload/lazaret.html.twig', [ 'lazaretFiles' => $lazaretFiles, 'currentPage' => $page, @@ -110,6 +112,12 @@ public function addElement(Request $request, $file_id) { $ret = ['success' => false, 'message' => '', 'result' => []]; + if (!$this->isCrsfValid($request, 'prodLazaret')) { + $ret['message'] = 'invalid prodLazaret token'; + + return $this->app->json($ret, 403); + } + //Mandatory parameter if (null === $request->request->get('bas_id')) { $ret['message'] = $this->app->trans('You must give a destination collection'); @@ -146,8 +154,14 @@ public function addElement(Request $request, $file_id) * * @return Response */ - public function denyElement($file_id) + public function denyElement(Request $request, $file_id) { + if (!$this->isCrsfValid($request, 'prodLazaret')) { + $ret['message'] = 'invalid prodLazaret token'; + + return $this->app->json($ret, 403); + } + /** @var LazaretManipulator $lazaretManipulator */ $lazaretManipulator = $this->app['manipulator.lazaret']; @@ -193,6 +207,12 @@ public function acceptElement(Request $request, $file_id) { $ret = ['success' => false, 'message' => '', 'result' => []]; + if (!$this->isCrsfValid($request, 'prodLazaret')) { + $ret['message'] = 'invalid prodLazaret token'; + + return $this->app->json($ret, 403); + } + //Mandatory parameter if (null === $recordId = $request->request->get('record_id')) { $ret['message'] = $this->app->trans('You must give a destination record'); diff --git a/lib/Alchemy/Phrasea/Controller/Prod/MoveCollectionController.php b/lib/Alchemy/Phrasea/Controller/Prod/MoveCollectionController.php index 274ab2ac17..23ff3c2b83 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/MoveCollectionController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/MoveCollectionController.php @@ -62,6 +62,8 @@ public function displayForm(Request $request) // sort the collections array_multisort($aName, $uorder, SORT_REGULAR, $collections); + $this->setSessionFormToken('prodMoveCollection'); + $parameters = [ 'records' => $records, 'message' => '', @@ -81,6 +83,10 @@ public function displayForm(Request $request) public function apply(Request $request) { + if (!$this->isCrsfValid($request, 'prodMoveCollection')) { + return $this->app->json(['success' => false, 'message' => 'invalid move collection form']); + } + /** @var \record_adapter[] $records */ $records = RecordsRequest::fromRequest($this->app, $request, false, [\ACL::CANDELETERECORD]); diff --git a/lib/Alchemy/Phrasea/Controller/Prod/PrinterController.php b/lib/Alchemy/Phrasea/Controller/Prod/PrinterController.php index edd01bba04..8a2f48e38f 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/PrinterController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/PrinterController.php @@ -48,6 +48,8 @@ public function postPrinterAction(Request $request) $storyId = $r->singleStory()->getId(); } + $this->setSessionFormToken('prodPrint'); + return $this->render('prod/actions/printer_default.html.twig', [ 'printer' => $printer, 'message' => '', @@ -59,6 +61,10 @@ public function postPrinterAction(Request $request) public function printAction(Request $request) { + if (!$this->isCrsfValid($request, 'prodPrint')) { + $this->app->abort(403); + } + $printer = new RecordHelper\Printer($this->app, $request); $printer->setThumbnailName($request->request->get('thumbnail-chosen')); $printer->setPreviewName($request->request->get('preview-chosen')); diff --git a/lib/Alchemy/Phrasea/Controller/Prod/PropertyController.php b/lib/Alchemy/Phrasea/Controller/Prod/PropertyController.php index 74022f1422..a67a221613 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/PropertyController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/PropertyController.php @@ -33,6 +33,8 @@ public function displayStatusProperty(Request $request) $records = RecordsRequest::fromRequest($this->app, $request, false, [\ACL::CHGSTATUS]); + $this->setSessionFormToken('prodPropertyStatus'); + $databoxes = $records->databoxes(); if (count($databoxes) > 1) { return new Response($this->render('prod/actions/Property/index.html.twig', [ @@ -103,6 +105,8 @@ public function displayTypeProperty(Request $request) $recordsType[$sbasId][$record->getType()][] = $record; } + $this->setSessionFormToken('prodPropertyType'); + return new Response($this->render('prod/actions/Property/type.html.twig', [ 'records' => $records, 'recordsType' => $recordsType, @@ -117,6 +121,10 @@ public function displayTypeProperty(Request $request) */ public function changeStatus(Request $request) { + if (!$this->isCrsfValid($request, 'prodPropertyStatus')) { + return $this->app->json(['message' => 'invalid change status form'], 403); + } + $applyStatusToChildren = $request->request->get('apply_to_children', []); $records = RecordsRequest::fromRequest($this->app, $request, false, [\ACL::CHGSTATUS]); $updated = []; @@ -151,6 +159,10 @@ public function changeStatus(Request $request) */ public function changeType(Request $request) { + if (!$this->isCrsfValid($request, 'prodPropertyType')) { + return $this->app->json(['message' => 'invalid change type form'], 403); + } + $typeLst = $request->request->get('types', []); $records = RecordsRequest::fromRequest($this->app, $request, false, [\ACL::CANMODIFRECORD]); $mimeLst = $request->request->get('mimes', []); diff --git a/lib/Alchemy/Phrasea/Controller/Prod/PushController.php b/lib/Alchemy/Phrasea/Controller/Prod/PushController.php index 796b4f80ba..ca0d585655 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/PushController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/PushController.php @@ -215,6 +215,10 @@ public function sendAction(Request $request) */ public function sharebasketAction(Request $request) { + if (!$this->isCrsfValid($request, 'prodShareBasket')) { + return $this->app->json(['success' => false, 'message' => 'invalid form']); + } + $ret = [ 'success' => false, 'message' => $this->app->trans('Unable to send the documents') @@ -393,6 +397,10 @@ public function getListAction($list_id) */ public function addUserAction(Request $request) { + if (!$this->isCrsfValid($request, 'prodShareAddUser')) { + return $this->app->json(['success' => false , 'message' => 'invalid add user form'], 403); + } + $result = ['success' => false, 'message' => '', 'user' => null]; try { @@ -470,6 +478,8 @@ public function getAddUserFormAction(Request $request) { $params = ['callback' => $request->query->get('callback')]; + $this->setSessionFormToken('prodShareAddUser'); + return $this->render('prod/User/Add.html.twig', $params); } @@ -817,6 +827,8 @@ public function renderPushTemplate(Request $request, $context) $repository = $this->getUserListRepository(); $recommendedUsers = $this->getUsersInSelectionExtractor($push->get_elements()); + $this->setSessionFormToken('prodShareBasket'); + return $this->render( 'prod/actions/Push.html.twig', [ diff --git a/lib/Alchemy/Phrasea/Controller/Prod/QueryController.php b/lib/Alchemy/Phrasea/Controller/Prod/QueryController.php index d055206d18..9ae6b379e4 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/QueryController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/QueryController.php @@ -21,6 +21,7 @@ use Alchemy\Phrasea\Utilities\StringHelper; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use unicode; class QueryController extends Controller @@ -121,6 +122,10 @@ public function completion(Request $request) */ public function query(Request $request) { + if (!$this->isCrsfValid($request, 'searchForm')) { + return $this->app->json(['message' => 'invalid search token'], 403); + } + $query = (string) $request->request->get('qry'); // since the query comes from a submited form, normalize crlf,cr,lf ... diff --git a/lib/Alchemy/Phrasea/Controller/Prod/RecordController.php b/lib/Alchemy/Phrasea/Controller/Prod/RecordController.php index 3c4e09ad75..3671a9e40a 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/RecordController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/RecordController.php @@ -224,6 +224,10 @@ public function getRecordById($sbasId, $recordId) */ public function doDeleteRecords(Request $request) { + if (!$this->isCrsfValid($request, 'prodDeleteRecord')) { + return $this->app->json(['success' => false , 'message' => 'invalid delete form'], 403); + } + $records = RecordsRequest::fromRequest( $this->app, $request, @@ -351,6 +355,8 @@ public function whatCanIDelete(Request $request) 'deletableCount' => count($filteredRecords['delete']) ]; + $this->setSessionFormToken('prodDeleteRecord'); + return $this->render( 'prod/actions/delete_records_confirm.html.twig', $viewParms diff --git a/lib/Alchemy/Phrasea/Controller/Prod/RootController.php b/lib/Alchemy/Phrasea/Controller/Prod/RootController.php index b89c919ed9..3f5b6d2673 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/RootController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/RootController.php @@ -104,6 +104,10 @@ public function indexAction(Request $request) { 'actionbar' => $filter('actionbar'), ]; + $this->setSessionFormToken('searchForm'); + $this->setSessionFormToken('prodExposeNew'); + $this->setSessionFormToken('prodTabThesaurus'); + return $this->render('prod/index.html.twig', [ 'module_name' => 'Production', 'WorkZone' => new WorkzoneHelper($this->app, $request), diff --git a/lib/Alchemy/Phrasea/Controller/Prod/StoryController.php b/lib/Alchemy/Phrasea/Controller/Prod/StoryController.php index dc5ec6bd5a..01c7528088 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/StoryController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/StoryController.php @@ -45,6 +45,8 @@ public function displayCreateFormAction(Request $request) } } + $this->setSessionFormToken('prodCreateStory'); + return $this->render('prod/Story/Create.html.twig', [ 'isMultipleDataboxes' => count($databoxes) > 1 ? 1 : 0, 'isMultipleCollections' => count($collections) > 1 ? 1 : 0, @@ -56,6 +58,10 @@ public function displayCreateFormAction(Request $request) public function postCreateFormAction(Request $request) { + if (!$this->isCrsfValid($request, 'prodCreateStory')) { + return $this->app->json(['success' => false , 'message' => 'invalid form'], 403); + } + $collection = \collection::getByBaseId($this->app, $request->request->get('base_id')); if (!$this->getAclForUser()->has_right_on_base($collection->get_base_id(), \ACL::CANADDRECORD)) { @@ -209,6 +215,8 @@ public function displayReorderFormAction($sbas_id, $record_id) throw new \Exception('This is not a story'); } + $this->setSessionFormToken('prodStoryReorder'); + return $this->renderResponse('prod/Story/Reorder.html.twig', [ 'story' => $story, ]); @@ -216,6 +224,10 @@ public function displayReorderFormAction($sbas_id, $record_id) public function reorderAction(Request $request, $sbas_id, $record_id) { + if (!$this->isCrsfValid($request, 'prodStoryReorder')) { + return $this->app->json(['success' => false , 'message' => 'invalid form'], 403); + } + try { $story = new \record_adapter($this->app, $sbas_id, $record_id); $previousDescription = $story->getRecordDescriptionAsArray(); diff --git a/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php b/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php index 3987296e0c..6a30a0c055 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php @@ -113,6 +113,11 @@ public function indexAction(Request $request) } } + $this->setSessionFormToken('prodToolsSubdef'); + $this->setSessionFormToken('prodToolsRotate'); + $this->setSessionFormToken('prodToolsHDSubstitution'); + $this->setSessionFormToken('prodToolsThumbSubstitution'); + return $this->render('prod/actions/Tools/index.html.twig', [ 'records' => $records, 'record' => $record, @@ -127,6 +132,10 @@ public function indexAction(Request $request) public function rotateAction(Request $request) { + if (!$this->isCrsfValid($request, 'prodToolsRotate')) { + return $this->app->json(['success' => false , 'message' => 'invalid rotate form'], 403); + } + $records = RecordsRequest::fromRequest($this->app, $request, false); $rotation = (int)$request->request->get('rotation', 90); $rotation %= 360; @@ -165,6 +174,10 @@ public function rotateAction(Request $request) public function imageAction(Request $request) { + if (!$this->isCrsfValid($request, 'prodToolsSubdef')) { + return $this->app->json(['success' => false , 'message' => 'invalid create subview form'], 403); + } + $return = ['success' => true]; $force = $request->request->get('force_substitution') == '1'; @@ -211,6 +224,10 @@ public function imageAction(Request $request) public function hddocAction(Request $request) { + if (!$this->isCrsfValid($request, 'prodToolsHDSubstitution')) { + return $this->app->json(['success' => false , 'message' => 'invalid document substitution form'], 403); + } + $success = false; $message = $this->app->trans('An error occured'); @@ -269,6 +286,10 @@ public function hddocAction(Request $request) public function changeThumbnailAction(Request $request) { + if (!$this->isCrsfValid($request, 'prodToolsThumbSubstitution')) { + return $this->app->json(['success' => false , 'message' => 'invalid thumbnail substitution form'], 403); + } + $file = $request->files->get('newThumb'); if (empty($file)) { diff --git a/lib/Alchemy/Phrasea/Controller/Prod/UploadController.php b/lib/Alchemy/Phrasea/Controller/Prod/UploadController.php index cfe3147eba..6e1967b5c9 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/UploadController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/UploadController.php @@ -28,6 +28,7 @@ use DataURI\Parser; use GuzzleHttp\Client as Guzzle; use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -56,6 +57,8 @@ public function getFlashUploadForm() public function getHtml5UploadForm() { + $this->setSessionFormToken('prodUpload'); + $maxFileSize = $this->getUploadMaxFileSize(); return $this->render('prod/upload/upload.html.twig', [ @@ -68,6 +71,8 @@ public function getHtml5UploadForm() public function getUploadForm() { + $this->setSessionFormToken('prodUpload'); + $maxFileSize = $this->getUploadMaxFileSize(); return $this->render('prod/upload/upload.html.twig', [ @@ -126,6 +131,10 @@ public function upload(Request $request) 'id' => '', ]; + if (!$this->isCrsfValid($request, 'prodUpload')) { + throw new AccessDeniedHttpException('Invalid prodUpload token'); + } + if (null === $request->files->get('files')) { throw new BadRequestHttpException('Missing file parameter'); } diff --git a/lib/Alchemy/Phrasea/Controller/Report/RootController.php b/lib/Alchemy/Phrasea/Controller/Report/RootController.php index 146043240b..c085def0b9 100644 --- a/lib/Alchemy/Phrasea/Controller/Report/RootController.php +++ b/lib/Alchemy/Phrasea/Controller/Report/RootController.php @@ -90,6 +90,11 @@ public function getDashboard(Request $request) $conf = $this->getConf(); + $this->setSessionFormToken('reportConnection'); + $this->setSessionFormToken('reportDownload'); + $this->setSessionFormToken('reportRecord'); + + return $this->render('report/report_layout_child.html.twig', [ 'ajax_dash' => true, 'dashboard' => null, diff --git a/lib/Alchemy/Phrasea/Controller/Thesaurus/ThesaurusXmlHttpController.php b/lib/Alchemy/Phrasea/Controller/Thesaurus/ThesaurusXmlHttpController.php index 8677fc9d13..5983d4e14b 100644 --- a/lib/Alchemy/Phrasea/Controller/Thesaurus/ThesaurusXmlHttpController.php +++ b/lib/Alchemy/Phrasea/Controller/Thesaurus/ThesaurusXmlHttpController.php @@ -1440,6 +1440,10 @@ public function replaceCandidateJson(Request $request) public function searchTermJson(Request $request) { + if (!$this->isCrsfValid($request, 'prodTabThesaurus')) { + return $this->app->json(['success' => false , 'message' => 'invalid form token'], 403); + } + $lng = $request->get('lng'); $html = ''; diff --git a/lib/Alchemy/Phrasea/Order/Controller/ProdOrderController.php b/lib/Alchemy/Phrasea/Order/Controller/ProdOrderController.php index ac176bab42..976ab8611b 100644 --- a/lib/Alchemy/Phrasea/Order/Controller/ProdOrderController.php +++ b/lib/Alchemy/Phrasea/Order/Controller/ProdOrderController.php @@ -37,6 +37,10 @@ class ProdOrderController extends BaseOrderController */ public function createOrder(Request $request) { + if (!$this->isCrsfValid($request, 'prodExportOrder')) { + return $this->app->json(['message' => 'invalid export order form'], 403); + } + $records = RecordsRequest::fromRequest($this->app, $request, true, [\ACL::CANCMD]); try { diff --git a/lib/Alchemy/Phrasea/PhraseanetService/Controller/PSExposeController.php b/lib/Alchemy/Phrasea/PhraseanetService/Controller/PSExposeController.php index 037425a957..9df6ada7b6 100644 --- a/lib/Alchemy/Phrasea/PhraseanetService/Controller/PSExposeController.php +++ b/lib/Alchemy/Phrasea/PhraseanetService/Controller/PSExposeController.php @@ -25,6 +25,10 @@ class PSExposeController extends Controller */ public function authenticateAction(PhraseaApplication $app, Request $request) { + if (!$this->isCrsfValid($request, 'prodExposeLogin')) { + return $this->app->json(['success' => false , 'error_description' => 'invalid csrf form']); + } + $exposeConfiguration = $app['conf']->get(['phraseanet-service', 'expose-service', 'exposes'], []); $exposeConfiguration = $exposeConfiguration[$request->request->get('exposeName')]; @@ -205,7 +209,9 @@ public function listPublicationAction(PhraseaApplication $app, Request $request) } if (!$session->has($passSessionName) && $exposeConfiguration['connection_kind'] == 'password' && $request->get('format') != 'json') { - return $app->json([ + $this->setSessionFormToken('prodExposeLogin'); + + return $app->json([ 'twig' => $this->render("prod/WorkZone/ExposeOauthLogin.html.twig", [ 'exposeName' => $exposeName ]), @@ -296,6 +302,8 @@ public function listPublicationAction(PhraseaApplication $app, Request $request) ]); } + $this->setSessionFormToken('prodExposeEdit'); + $exposeListTwig = $this->render("prod/WorkZone/ExposeList.html.twig", [ 'publications' => $publications, 'exposeFrontBasePath' => $exposeFrontBasePath @@ -614,6 +622,10 @@ public function listProfileAction(PhraseaApplication $app, Request $request) */ public function createPublicationAction(PhraseaApplication $app, Request $request) { + if (!$this->isCrsfValid($request, 'prodExposeNew')) { + return $this->app->json(['success' => false , 'message' => 'invalid crsf token form']); + } + $exposeName = $request->get('exposeName'); if ( $exposeName == null) { return $app->json([ @@ -682,6 +694,10 @@ public function createPublicationAction(PhraseaApplication $app, Request $reques */ public function updatePublicationAction(PhraseaApplication $app, Request $request) { + if (!$this->isCrsfValid($request, 'prodExposeEdit')) { + return $this->app->json(['success' => false , 'message' => 'invalid crsf token form']); + } + $exposeName = $request->get('exposeName'); $exposeClient = $this->getExposeClient($exposeName); if ($exposeClient == null) { @@ -966,6 +982,9 @@ public function getSubdefsListAction(PhraseaApplication $app, Request $request) public function getFieldMappingAction(PhraseaApplication $app, Request $request) { + $this->setSessionFormToken('prodExposeFieldMapping'); + $this->setSessionFormToken('prodExposeSubdefMapping'); + return $this->render('prod/WorkZone/ExposeFieldMapping.html.twig', [ 'exposeName' => $request->get('exposeName') ]); @@ -979,6 +998,10 @@ public function getFieldMappingAction(PhraseaApplication $app, Request $request) */ public function saveFieldMappingAction(PhraseaApplication $app, Request $request) { + if (!$this->isCrsfValid($request, 'prodExposeFieldMapping')) { + return $this->app->json(['success' => false , 'message' => 'invalid crsf token form'], 403); + } + $exposeName = $request->get('exposeName'); $profile = $request->request->get('profile'); $sendGeolocField = !empty($request->request->get('sendGeolocField')) ? array_keys($request->request->get('sendGeolocField')): []; @@ -1055,6 +1078,10 @@ public function saveFieldMappingAction(PhraseaApplication $app, Request $request public function saveSubdefMappingAction(PhraseaApplication $app, Request $request) { + if (!$this->isCrsfValid($request, 'prodExposeSubdefMapping')) { + return $this->app->json(['success' => false , 'message' => 'invalid crsf token form'], 403); + } + $exposeName = $request->get('exposeName'); $profile = $request->request->get('profile'); $exposeClient = $this->getExposeClient($exposeName); diff --git a/lib/Alchemy/Phrasea/Report/Controller/ProdReportController.php b/lib/Alchemy/Phrasea/Report/Controller/ProdReportController.php index cb82797751..eed1a8979a 100644 --- a/lib/Alchemy/Phrasea/Report/Controller/ProdReportController.php +++ b/lib/Alchemy/Phrasea/Report/Controller/ProdReportController.php @@ -15,6 +15,7 @@ use Alchemy\Phrasea\Report\ReportActions; use Alchemy\Phrasea\Report\ReportFactory; use Alchemy\Phrasea\Report\ReportRecords; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -57,6 +58,8 @@ public function __construct(ReportFactory $reportFactory, $anonymousReport, \ACL $this->anonymousReport = $anonymousReport; $this->acl = $acl; $this->appbox = $appbox; + + parent::__construct($appbox->getPhraseApplication()); } /** @@ -77,11 +80,15 @@ public function indexAction(Request $request) * * @param Request $request * @param $sbasId - * @return RedirectResponse|StreamedResponse + * @return RedirectResponse|StreamedResponse|JsonResponse */ public function connectionsAction(Request $request, $sbasId) { if ($request->isMethod("POST")) { + if (!$this->isCrsfValid($request, 'reportConnection')) { + return new JsonResponse(['message' => 'invalid report connection token'], 403); + } + if (!($extension = $request->get('format'))) { $extension = 'csv'; } @@ -123,11 +130,15 @@ public function connectionsAction(Request $request, $sbasId) * * @param Request $request * @param $sbasId - * @return RedirectResponse|StreamedResponse + * @return RedirectResponse|StreamedResponse|JsonResponse */ public function downloadsAction(Request $request, $sbasId) { if ($request->isMethod("POST")) { + if (!$this->isCrsfValid($request, 'reportDownload')) { + return new JsonResponse(['message' => 'invalid report download token'], 403); + } + if(!($extension = $request->get('format'))) { $extension = 'csv'; } @@ -172,11 +183,15 @@ public function downloadsAction(Request $request, $sbasId) * * @param Request $request * @param $sbasId - * @return RedirectResponse|StreamedResponse + * @return RedirectResponse|StreamedResponse|JsonResponse */ public function recordsAction(Request $request, $sbasId) { if ($request->isMethod("POST")) { + if (!$this->isCrsfValid($request, 'reportRecord')) { + return new JsonResponse(['message' => 'invalid report record token'], 403); + } + if (!($extension = $request->get('format'))) { $extension = 'csv'; } diff --git a/templates/web/common/dialog_export.html.twig b/templates/web/common/dialog_export.html.twig index b4faf0029f..b99a837708 100644 --- a/templates/web/common/dialog_export.html.twig +++ b/templates/web/common/dialog_export.html.twig @@ -108,7 +108,7 @@

{{ 'export:: telechargement' | trans }}

-
+ {% for name, values in download.get_display_download() %} @@ -168,13 +168,14 @@
+

{{ 'export:: envoi par mail' | trans }}

-
+
@@ -257,6 +258,7 @@
+
@@ -296,7 +298,7 @@ {% endif %} {% endfor %}
-
+ @@ -404,6 +406,7 @@ +
{% endif %} @@ -430,7 +433,7 @@ {% endfor %} -
+
@@ -479,6 +482,7 @@
{% endif %} +
diff --git a/templates/web/lightbox/sc_note.html.twig b/templates/web/lightbox/sc_note.html.twig index 2265371c3d..bb03ff4f54 100644 --- a/templates/web/lightbox/sc_note.html.twig +++ b/templates/web/lightbox/sc_note.html.twig @@ -26,6 +26,7 @@ {{ 'boutton::enregistrer' | trans }}
+ diff --git a/templates/web/prod/Baskets/Create.html.twig b/templates/web/prod/Baskets/Create.html.twig index a758c16e61..d8207647bf 100644 --- a/templates/web/prod/Baskets/Create.html.twig +++ b/templates/web/prod/Baskets/Create.html.twig @@ -9,4 +9,5 @@ {{ 'Ajouter ma selection courrante' | trans }} + diff --git a/templates/web/prod/Baskets/Reorder.html.twig b/templates/web/prod/Baskets/Reorder.html.twig index dbaa113496..f594af2925 100644 --- a/templates/web/prod/Baskets/Reorder.html.twig +++ b/templates/web/prod/Baskets/Reorder.html.twig @@ -2,22 +2,23 @@
- - - - -
-
- {% for element in basket.getElements() %} - - {% endfor %} - + + + + +
+
+ {% for element in basket.getElements() %} + + {% endfor %} + +
diff --git a/templates/web/prod/Baskets/Update.html.twig b/templates/web/prod/Baskets/Update.html.twig index c5b577252d..06f68fa601 100644 --- a/templates/web/prod/Baskets/Update.html.twig +++ b/templates/web/prod/Baskets/Update.html.twig @@ -5,5 +5,6 @@ +
diff --git a/templates/web/prod/Story/Create.html.twig b/templates/web/prod/Story/Create.html.twig index 6eeb5682bf..4c7344a2fe 100644 --- a/templates/web/prod/Story/Create.html.twig +++ b/templates/web/prod/Story/Create.html.twig @@ -43,5 +43,5 @@ {{ 'Ajouter ma selection courrante' | trans }} - + diff --git a/templates/web/prod/Story/Reorder.html.twig b/templates/web/prod/Story/Reorder.html.twig index 39d6a33b5c..0146fd41a9 100644 --- a/templates/web/prod/Story/Reorder.html.twig +++ b/templates/web/prod/Story/Reorder.html.twig @@ -18,6 +18,7 @@ {% endfor %} +
diff --git a/templates/web/prod/User/Add.html.twig b/templates/web/prod/User/Add.html.twig index 8661ac97da..4c92fc3656 100644 --- a/templates/web/prod/User/Add.html.twig +++ b/templates/web/prod/User/Add.html.twig @@ -35,6 +35,7 @@ +
diff --git a/templates/web/prod/WorkZone/ExposeEdit.html.twig b/templates/web/prod/WorkZone/ExposeEdit.html.twig index ddf75ead50..9e207466b3 100644 --- a/templates/web/prod/WorkZone/ExposeEdit.html.twig +++ b/templates/web/prod/WorkZone/ExposeEdit.html.twig @@ -175,7 +175,7 @@ - + @@ -382,7 +382,8 @@ dataType: 'json', data: { exposeName: "{{ exposeName }}", - publicationData: advancedSetting.val() + publicationData: advancedSetting.val(), + prodExposeEdit_token: $('#publication-json input[name="prodExposeEdit_token"]').val() }, success: function (data) { if (data.success) { diff --git a/templates/web/prod/WorkZone/ExposeFieldMapping.html.twig b/templates/web/prod/WorkZone/ExposeFieldMapping.html.twig index cf2d75b329..3e938b3a00 100644 --- a/templates/web/prod/WorkZone/ExposeFieldMapping.html.twig +++ b/templates/web/prod/WorkZone/ExposeFieldMapping.html.twig @@ -41,6 +41,7 @@ + {% else %}

{{ 'expose::setting You have to select an expose to continue' | trans }}

@@ -76,6 +77,7 @@ + diff --git a/templates/web/prod/WorkZone/ExposeNew.html.twig b/templates/web/prod/WorkZone/ExposeNew.html.twig index d4d45c6484..200eaae2a3 100644 --- a/templates/web/prod/WorkZone/ExposeNew.html.twig +++ b/templates/web/prod/WorkZone/ExposeNew.html.twig @@ -131,7 +131,7 @@ - + @@ -373,7 +373,8 @@ dataType: 'json', data: { exposeName: $('#expose_list').val(), - publicationData: $('#DIALOG-expose-add').find('#advancedSetting').val() + publicationData: $('#DIALOG-expose-add').find('#advancedSetting').val(), + prodExposeNew_token: $('#publication-json input[name="prodExposeNew_token"]').val() }, success: function (data) { if (data.success) { diff --git a/templates/web/prod/WorkZone/ExposeOauthLogin.html.twig b/templates/web/prod/WorkZone/ExposeOauthLogin.html.twig index 9dd073923d..f8e0f9f9b0 100644 --- a/templates/web/prod/WorkZone/ExposeOauthLogin.html.twig +++ b/templates/web/prod/WorkZone/ExposeOauthLogin.html.twig @@ -17,6 +17,7 @@
+ diff --git a/templates/web/prod/WorkZone/ExposePublicationAssets.html.twig b/templates/web/prod/WorkZone/ExposePublicationAssets.html.twig index 9b71f21340..d12dc5ceaa 100644 --- a/templates/web/prod/WorkZone/ExposePublicationAssets.html.twig +++ b/templates/web/prod/WorkZone/ExposePublicationAssets.html.twig @@ -73,6 +73,7 @@ {% if capabilitiesEdit %}
+
diff --git a/templates/web/prod/actions/Property/index.html.twig b/templates/web/prod/actions/Property/index.html.twig index f3abc7d6c3..1525aca1f0 100644 --- a/templates/web/prod/actions/Property/index.html.twig +++ b/templates/web/prod/actions/Property/index.html.twig @@ -116,6 +116,7 @@ +
diff --git a/templates/web/prod/actions/Property/type.html.twig b/templates/web/prod/actions/Property/type.html.twig index 4ce19322a0..e33c5f640c 100644 --- a/templates/web/prod/actions/Property/type.html.twig +++ b/templates/web/prod/actions/Property/type.html.twig @@ -52,4 +52,5 @@ + diff --git a/templates/web/prod/actions/Push.html.twig b/templates/web/prod/actions/Push.html.twig index 86a22989b9..4eb20d39b0 100644 --- a/templates/web/prod/actions/Push.html.twig +++ b/templates/web/prod/actions/Push.html.twig @@ -508,6 +508,7 @@ {% endif %} + {# subdef section #}
-
+
 {{ "Reconstruire les sous definitions" | trans }}  {% if nbSubdefSubstitute > 0 %} @@ -107,7 +107,9 @@
{{ 'prod::tool:recreatesubviews: warning for rebuild sub-definitions' | trans }} -
+ +
+ @@ -136,6 +138,7 @@ + @@ -176,6 +179,7 @@ +
@@ -202,6 +206,7 @@ +
diff --git a/templates/web/prod/actions/collection_default.html.twig b/templates/web/prod/actions/collection_default.html.twig index cadc0eada2..9846079a70 100644 --- a/templates/web/prod/actions/collection_default.html.twig +++ b/templates/web/prod/actions/collection_default.html.twig @@ -38,5 +38,6 @@ + diff --git a/templates/web/prod/actions/delete_records_confirm_form.html.twig b/templates/web/prod/actions/delete_records_confirm_form.html.twig index 91beb1b15f..fc20b73862 100644 --- a/templates/web/prod/actions/delete_records_confirm_form.html.twig +++ b/templates/web/prod/actions/delete_records_confirm_form.html.twig @@ -37,6 +37,7 @@ + {% elseif records.received().count() == 0 %}
diff --git a/templates/web/prod/actions/printer_default.html.twig b/templates/web/prod/actions/printer_default.html.twig index daf7e198d8..c880703e41 100644 --- a/templates/web/prod/actions/printer_default.html.twig +++ b/templates/web/prod/actions/printer_default.html.twig @@ -191,7 +191,8 @@
- + +