From 03c5927fa48eb86f44042a12c4d8cfb9eb7e41f3 Mon Sep 17 00:00:00 2001
From: Jan-Hendrik Willms <tleilax+studip@gmail.com>
Date: Thu, 29 Aug 2024 14:38:15 +0000
Subject: [PATCH] use csrf protection correctly, fixes #4545

Closes #4545

Merge request studip/studip!3341
---
 app/controllers/admin/login_style.php         |   7 +-
 app/controllers/admin/user.php                |   2 +-
 app/controllers/contact.php                   |   4 +-
 app/controllers/course/timesrooms.php         |   6 +-
 app/controllers/course/wiki.php               |   9 +-
 app/controllers/materialien/files.php         |  31 +--
 app/controllers/questionnaire.php             |   5 +-
 app/controllers/settings/deputies.php         |   2 +-
 app/controllers/shared/contacts.php           |   4 +-
 .../studiengaenge/studiengaenge.php           |  50 ++--
 app/views/materialien/files/index.php         | 236 +++++++++---------
 app/views/materialien/files/range.php         | 206 +++++++--------
 app/views/shared/contacts/index.php           | 171 +++++++------
 lib/classes/forms/Form.php                    |   5 +-
 14 files changed, 364 insertions(+), 374 deletions(-)

diff --git a/app/controllers/admin/login_style.php b/app/controllers/admin/login_style.php
index 35cd7d820ff..6f721aaf741 100644
--- a/app/controllers/admin/login_style.php
+++ b/app/controllers/admin/login_style.php
@@ -64,7 +64,8 @@ class Admin_LoginStyleController extends AuthenticatedController
      */
     public function add_pic_action()
     {
-        CSRFProtection::verifyRequest();
+        CSRFProtection::verifyUnsafeRequest();
+
         $success = 0;
         foreach ($_FILES['pictures']['name'] as $index => $filename) {
             if ($_FILES['pictures']['error'][$index] !== UPLOAD_ERR_OK) {
@@ -174,7 +175,7 @@ class Admin_LoginStyleController extends AuthenticatedController
 
     public function store_faq_action(LoginFaq $entry = null)
     {
-        CSRFProtection::verifyRequest();
+        CSRFProtection::verifyUnsafeRequest();
 
         $entry->setData([
             'title' => Request::i18n('title'),
@@ -190,7 +191,7 @@ class Admin_LoginStyleController extends AuthenticatedController
 
     public function delete_faq_action(LoginFaq $entry)
     {
-        CSRFProtection::verifyRequest();
+        CSRFProtection::verifyUnsafeRequest();
 
         if ($entry->delete()) {
             PageLayout::postSuccess(_('Der Hinweistext wurde gelöscht.'));
diff --git a/app/controllers/admin/user.php b/app/controllers/admin/user.php
index a9a04cfd354..49b5f61408c 100644
--- a/app/controllers/admin/user.php
+++ b/app/controllers/admin/user.php
@@ -1093,7 +1093,7 @@ class Admin_UserController extends AuthenticatedController
      */
     public function store_user_institute_action($user_id, $institute_id)
     {
-        CSRFProtection::verifyRequest();
+        CSRFProtection::verifyUnsafeRequest();
 
         $inst_membership = InstituteMember::findOneBySQL('user_id = ? AND institut_id = ?', [$user_id, $institute_id]);
 
diff --git a/app/controllers/contact.php b/app/controllers/contact.php
index 2148777c360..7820ce6cc50 100644
--- a/app/controllers/contact.php
+++ b/app/controllers/contact.php
@@ -166,7 +166,7 @@ class ContactController extends AuthenticatedController
             $this->group->owner_id = User::findCurrent()->id;
         }
         if (Request::submitted('store')) {
-            CSRFProtection::verifyRequest();
+            CSRFProtection::verifyUnsafeRequest();
             $this->group->name = Request::get('name');
             $this->group->store();
             $this->redirect('contact/index/' . $this->group->id);
@@ -175,7 +175,7 @@ class ContactController extends AuthenticatedController
 
     public function deleteGroup_action()
     {
-        CSRFProtection::verifyRequest();
+        CSRFProtection::verifyUnsafeRequest();
         $this->group->delete();
         $this->redirect('contact/index');
     }
diff --git a/app/controllers/course/timesrooms.php b/app/controllers/course/timesrooms.php
index 9b124697cf6..4d4dbff1c5d 100644
--- a/app/controllers/course/timesrooms.php
+++ b/app/controllers/course/timesrooms.php
@@ -1239,7 +1239,8 @@ class Course_TimesroomsController extends AuthenticatedController
      */
     public function saveCycle_action()
     {
-        CSRFProtection::verifyRequest();
+        CSRFProtection::verifyUnsafeRequest();
+
         $start = strtotime(Request::get('start_time'));
         $end   = strtotime(Request::get('end_time'));
 
@@ -1390,7 +1391,8 @@ class Course_TimesroomsController extends AuthenticatedController
      */
     public function deleteCycle_action($cycle_id)
     {
-        CSRFProtection::verifyRequest();
+        CSRFProtection::verifyUnsafeRequest();
+
         $cycle = SeminarCycleDate::find($cycle_id);
         if ($cycle === null) {
             $message = sprintf(_('Es gibt keinen regelmäßigen Eintrag "%s".'), $cycle_id);
diff --git a/app/controllers/course/wiki.php b/app/controllers/course/wiki.php
index ac997e8e828..970ea4695df 100644
--- a/app/controllers/course/wiki.php
+++ b/app/controllers/course/wiki.php
@@ -299,9 +299,12 @@ class Course_WikiController extends AuthenticatedController
 
     public function delete_action(WikiPage $page)
     {
-        if (!Request::isPost() || !$page->isEditable() || !CSRFProtection::verifyRequest()) {
+        CSRFProtection::verifyUnsafeRequest();
+
+        if (!$page->isEditable()) {
             throw new AccessDeniedException();
         }
+
         $name = $page->name;
         $page->delete();
         PageLayout::postSuccess(sprintf(_('Die Seite %s wurde gelöscht.'), htmlReady($name)));
@@ -310,7 +313,9 @@ class Course_WikiController extends AuthenticatedController
 
     public function deleteversion_action(WikiPage $page)
     {
-        if (!Request::isPost() || !$page->isEditable() || !CSRFProtection::verifyRequest()) {
+        CSRFProtection::verifyUnsafeRequest();
+
+        if (!$page->isEditable()) {
             throw new AccessDeniedException();
         }
 
diff --git a/app/controllers/materialien/files.php b/app/controllers/materialien/files.php
index 804668ef603..acdc14cdda4 100644
--- a/app/controllers/materialien/files.php
+++ b/app/controllers/materialien/files.php
@@ -435,7 +435,7 @@ class Materialien_FilesController extends MVVController
 
     public function delete_range_action($mvvfile_id, $range_id)
     {
-        CSRFProtection::verifyRequest();
+        CSRFProtection::verifyUnsafeRequest();
 
         if ($mvvfile_range = MvvFileRange::find([$mvvfile_id, $range_id])) {
             $vacant = $mvvfile_range->position;
@@ -460,36 +460,9 @@ class Materialien_FilesController extends MVVController
         }
     }
 
-    public function delete_fileref_action($mvvfile_id, $fileref_id)
-    {
-        CSRFProtection::verifyRequest();
-
-        if ($mvv_file = MvvFile::find($mvvfile_id)) {
-            $vacant = $mvv_file->position;
-            $range_id = $mvv_file->range_id;
-            if ($mvv_file->delete()) {
-                foreach (MvvFile::findBySQL('range_id = ? ORDER BY position ASC',[$range_id]) as $other_file) {
-                    if ($other_file->position > $vacant) {
-                        $tmp = $other_file->position;
-                        $other_file->position = $vacant;
-                        $other_file->store();
-                        $vacant = $tmp;
-                    }
-                }
-                PageLayout::postSuccess(_('Das Dokument wurde gelöscht.'));
-            }
-        }
-        $this->range_id = $range_id;
-        if (Request::isXhr()) {
-            $this->response->add_header('X-Dialog-Execute', 'STUDIP.MVV.Document.reload_documenttable("' . $range_id . '")');
-            $this->response->add_header('X-Dialog-Close', 1);
-            $this->render_nothing();
-        }
-    }
-
     public function delete_all_dokument_action($mvvfile_id)
     {
-        CSRFProtection::verifyRequest();
+        CSRFProtection::verifyUnsafeRequest();
 
         MvvFile::deleteBySQL('mvvfile_id =?', [$mvvfile_id]);
         MvvFileRange::deleteBySQL('mvvfile_id =?', [$mvvfile_id]);
diff --git a/app/controllers/questionnaire.php b/app/controllers/questionnaire.php
index 07e28a1399c..d81b6cda508 100644
--- a/app/controllers/questionnaire.php
+++ b/app/controllers/questionnaire.php
@@ -381,9 +381,12 @@ class QuestionnaireController extends AuthenticatedController
 
     public function reset_action(Questionnaire $questionnaire)
     {
-        if (!Request::isPost() || !$questionnaire->isEditable() || !CSRFProtection::verifyRequest()) {
+        CSRFProtection::verifyUnsafeRequest();
+
+        if (!$questionnaire->isEditable()) {
             throw new AccessDeniedException();
         }
+
         foreach ($questionnaire->anonymousanswers as $anonymous) {
             $anonymous->delete();
         }
diff --git a/app/controllers/settings/deputies.php b/app/controllers/settings/deputies.php
index 3476692cfbb..8ce4555e7d7 100644
--- a/app/controllers/settings/deputies.php
+++ b/app/controllers/settings/deputies.php
@@ -101,7 +101,7 @@ class Settings_DeputiesController extends Settings_SettingsController
 
     public function add_member_action()
     {
-        CSRFProtection::verifyRequest();
+        CSRFProtection::verifyUnsafeRequest();
 
         $mp = MultiPersonSearch::load('settings_add_deputy');
         $msg = [
diff --git a/app/controllers/shared/contacts.php b/app/controllers/shared/contacts.php
index 62dd7a88816..963e60bcb5e 100644
--- a/app/controllers/shared/contacts.php
+++ b/app/controllers/shared/contacts.php
@@ -641,7 +641,7 @@ class Shared_ContactsController extends MVVController
 
     public function delete_all_ranges_action($contact_id = null)
     {
-        CSRFProtection::verifyRequest();
+        CSRFProtection::verifyUnsafeRequest();
 
         $contact = MvvContact::find($contact_id);
         if (!($contact && MvvPerm::get($contact)->haveFieldPerm('ranges', MvvPerm::PERM_CREATE))) {
@@ -662,7 +662,7 @@ class Shared_ContactsController extends MVVController
 
     public function delete_extern_contact_action($user_id = null)
     {
-        CSRFProtection::verifyRequest();
+        CSRFProtection::verifyUnsafeRequest();
 
         if ($mvv_ext_contact = MvvExternContact::find($user_id)) {
             $mvv_ext_contact->delete();
diff --git a/app/controllers/studiengaenge/studiengaenge.php b/app/controllers/studiengaenge/studiengaenge.php
index d053f482915..5134ceb68c6 100644
--- a/app/controllers/studiengaenge/studiengaenge.php
+++ b/app/controllers/studiengaenge/studiengaenge.php
@@ -288,17 +288,13 @@ class Studiengaenge_StudiengaengeController extends MVVController
         $studiengang = Studiengang::find($studiengang_id);
         if (!$studiengang) {
              PageLayout::postError(_('Unbekannter Studiengang'));
-        } else {
-            if (Request::isPost()) {
-                if (Request::submitted('delete')) {
-                    CSRFProtection::verifyRequest();
-                    PageLayout::postSuccess(sprintf(
-                        _('Studiengang "%s" gelöscht!'),
-                        htmlReady($studiengang->name)
-                    ));
-                    $studiengang->delete();
-                }
-            }
+        } else if (Request::submitted('delete')) {
+            CSRFProtection::verifyUnsafeRequest();
+            PageLayout::postSuccess(sprintf(
+                _('Studiengang "%s" gelöscht!'),
+                htmlReady($studiengang->name)
+            ));
+            $studiengang->delete();
         }
         $this->redirect($this->action_url('index'));
     }
@@ -538,23 +534,21 @@ class Studiengaenge_StudiengaengeController extends MVVController
             if ($this->stg_stgteil->isNew()) {
                 PageLayout::postError(_('Unbekannter Studiengangteil'));
             } else {
-                if (Request::isPost()) {
-                    CSRFProtection::verifyRequest();
-                    if (!MvvPerm::haveFieldPermStudiengangteile($studiengang, MvvPerm::PERM_CREATE)) {
-                        throw new Trails_Exception(403);
-                    }
-                    $stgteil_name = $this->stg_stgteil->stgteil_name;
-                    $stgbez_name = $this->stg_stgteil->stgbez_name;
-                    if ($this->stg_stgteil->delete()) {
-                        PageLayout::postSuccess(sprintf(
-                            _('Die Zuordnung des Studiengangteils "%s" als "%s" zum Studiengang "%s" wurde gelöscht.'),
-                            htmlReady($stgteil_name),
-                            htmlReady($stgbez_name),
-                            htmlReady($studiengang->name)
-                        ));
-                    } else {
-                        PageLayout::postError(_('Der Studiengangteil konnte nicht gelöscht werden.'));
-                    }
+                CSRFProtection::verifyUnsafeRequest();
+                if (!MvvPerm::haveFieldPermStudiengangteile($studiengang, MvvPerm::PERM_CREATE)) {
+                    throw new Trails_Exception(403);
+                }
+                $stgteil_name = $this->stg_stgteil->stgteil_name;
+                $stgbez_name = $this->stg_stgteil->stgbez_name;
+                if ($this->stg_stgteil->delete()) {
+                    PageLayout::postSuccess(sprintf(
+                        _('Die Zuordnung des Studiengangteils "%s" als "%s" zum Studiengang "%s" wurde gelöscht.'),
+                        htmlReady($stgteil_name),
+                        htmlReady($stgbez_name),
+                        htmlReady($studiengang->name)
+                    ));
+                } else {
+                    PageLayout::postError(_('Der Studiengangteil konnte nicht gelöscht werden.'));
                 }
             }
             $this->redirect(
diff --git a/app/views/materialien/files/index.php b/app/views/materialien/files/index.php
index 538d97a38c0..d1eb03c5828 100644
--- a/app/views/materialien/files/index.php
+++ b/app/views/materialien/files/index.php
@@ -1,123 +1,127 @@
-<table id="mvv_files" class="default collapsable">
-    <caption>
-        <?= _('Verlinkte Dokumente') ?>
-        <span class="actions"><? printf(ngettext('%s Dokument', '%s Dokumente', $count), $count) ?></span>
-    </caption>
-    <thead>
-        <tr class="sortable">
-            <?= $controller->renderSortLink('materialien/files/', _('Name'), 'mvv_files_filerefs.name') ?>
-            <?= $controller->renderSortLink('materialien/files/', _('Dateiname'), 'file_refs.name') ?>
-            <?= $controller->renderSortLInk('materialien/files/', _('Sichtbarkeit'), 'extern_visible') ?>
-            <?= $controller->renderSortLInk('materialien/files/', _('Sprache'), 'file_language') ?>
-            <?= $controller->renderSortLink('materialien/files/', _('Art der Datei'), 'type') ?>
-            <?= $controller->renderSortLink('materialien/files/', _('Datum'), 'mkdate') ?>
-            <th><?= _('Dateityp') ?></th>
-            <?= $controller->renderSortLInk('materialien/files/', _('Kategorie'), 'category') ?>
-            <?= $controller->renderSortLink('materialien/files/', _('Zuordnungen'), 'count_relations') ?>
-            <th class="actions" style="width: 5%"><?= _('Aktionen') ?></th>
-        </tr>
-    </thead>
-<? foreach ($dokumente as $mvv_file) : ?>
-    <tbody class="<?= (in_array($range_id, $mvv_file->getRangesArray()) && ($fileref_id && in_array($fileref_id, $mvv_file->getFileRefsArray())) ? 'not-collapsed' : 'collapsed') ?>">
-        <tr class="header-row">
-            <td class="toggle-indicator">
-                <a class="mvv-load-in-new-row"
-                    href="<?= $controller->link_for('materialien/files/details', $mvv_file->mvvfile_id) ?>">
-                    <?= htmlReady($mvv_file->getDisplayName()) ?>
-                </a>
-            </td>
-            <td class="dont-hide">
-            <? if ($mvv_file->getFiletypes()[0] === 'Link'): ?>
-                <a href="<?= htmlReady($mvv_file->file_refs[0]->file_ref->file->metadata['url']); ?>" target="_blank">
-                    <?= Icon::create('link-extern')->asImg(['class' => 'text-bottom']) ?>
+<form method="post">
+    <?= CSRFProtection::tokenTag() ?>
+    <table id="mvv_files" class="default collapsable">
+        <caption>
+            <?= _('Verlinkte Dokumente') ?>
+            <span class="actions"><? printf(ngettext('%s Dokument', '%s Dokumente', $count), $count) ?></span>
+        </caption>
+        <thead>
+            <tr class="sortable">
+                <?= $controller->renderSortLink('materialien/files/', _('Name'), 'mvv_files_filerefs.name') ?>
+                <?= $controller->renderSortLink('materialien/files/', _('Dateiname'), 'file_refs.name') ?>
+                <?= $controller->renderSortLInk('materialien/files/', _('Sichtbarkeit'), 'extern_visible') ?>
+                <?= $controller->renderSortLInk('materialien/files/', _('Sprache'), 'file_language') ?>
+                <?= $controller->renderSortLink('materialien/files/', _('Art der Datei'), 'type') ?>
+                <?= $controller->renderSortLink('materialien/files/', _('Datum'), 'mkdate') ?>
+                <th><?= _('Dateityp') ?></th>
+                <?= $controller->renderSortLInk('materialien/files/', _('Kategorie'), 'category') ?>
+                <?= $controller->renderSortLink('materialien/files/', _('Zuordnungen'), 'count_relations') ?>
+                <th class="actions" style="width: 5%"><?= _('Aktionen') ?></th>
+            </tr>
+        </thead>
+    <? foreach ($dokumente as $mvv_file) : ?>
+        <tbody class="<?= (in_array($range_id, $mvv_file->getRangesArray()) && ($fileref_id && in_array($fileref_id, $mvv_file->getFileRefsArray())) ? 'not-collapsed' : 'collapsed') ?>">
+            <tr class="header-row">
+                <td class="toggle-indicator">
+                    <a class="mvv-load-in-new-row"
+                        href="<?= $controller->link_for('materialien/files/details', $mvv_file->mvvfile_id) ?>">
+                        <?= htmlReady($mvv_file->getDisplayName()) ?>
+                    </a>
+                </td>
+                <td class="dont-hide">
+                <? if ($mvv_file->getFiletypes()[0] === 'Link'): ?>
+                    <a href="<?= htmlReady($mvv_file->file_refs[0]->file_ref->file->metadata['url']); ?>" target="_blank">
+                        <?= Icon::create('link-extern')->asImg(['class' => 'text-bottom']) ?>
+                        <?= htmlReady($mvv_file->getFilenames()[0]); ?>
+                    </a>
+                <? else: ?>
                     <?= htmlReady($mvv_file->getFilenames()[0]); ?>
-                </a>
-            <? else: ?>
-                <?= htmlReady($mvv_file->getFilenames()[0]); ?>
-            <? endif; ?>
-            </td>
-            <td class="dont-hide" style="text-align: center;">
-                <?= Icon::create(
-                    $mvv_file->extern_visible?'visibility-visible':'visibility-invisible',
-                    Icon::ROLE_INFO
-                )->asImg([
-                    'class' => 'text-bottom',
-                    'title' => $mvv_file->extern_visible?_('sichtbar'):_('unsichtbar')
-                ]) ?>
-            </td>
-            <td class="dont-hide">
-            <? foreach ($mvv_file->file_refs as $fileref) : ?>
-                <?= Assets::img(MVV::getContentLanguageImagePath($fileref->file_language), ['size' => '24']) ?>
-            <? endforeach; ?>
-            </td>
-            <td class="dont-hide"><?= htmlReady($GLOBALS['MVV_DOCUMENTS']['TYPE']['values'][$mvv_file->type]['name']) ?></td>
-            <td class="dont-hide"><?= strftime('%x', $mvv_file->mkdate) ?></td>
-            <td class="dont-hide"><?= htmlReady($mvv_file->getFiletypes()[0]) ?></td>
-            <td class="dont-hide"><?= htmlReady($GLOBALS['MVV_DOCUMENTS']['CATEGORY']['values'][$mvv_file->category]['name']) ?></td>
-            <td class="dont-hide" style="text-align: center;"><?= htmlReady($mvv_file->count_relations) ?></td>
-            <td class="dont-hide actions">
-            <?
-                $actions = ActionMenu::get()->setContext($mvv_file->getFilenames()[0]);
-                $actions->addLink(
-                    $controller->url_for('materialien/files/add_dokument', 'index', $mvv_file->getRangeType()?:0, 0, $mvv_file->mvvfile_id),
-                    _('Dokument bearbeiten'),
-                    Icon::create('edit'),
-                    ['data-dialog' => 'size=auto']
-                );
-                $actions->addLink(
-                    $controller->url_for('materialien/files/add_ranges_to_file',$mvv_file->mvvfile_id),
-                    _('Dokument zuordnen'),
-                    Icon::create('add'),
-                    ['data-dialog' => 'size=auto']
-                );
-                foreach ($mvv_file->file_refs as $fileref) {
+                <? endif; ?>
+                </td>
+                <td class="dont-hide" style="text-align: center;">
+                    <?= Icon::create(
+                        $mvv_file->extern_visible?'visibility-visible':'visibility-invisible',
+                        Icon::ROLE_INFO
+                    )->asImg([
+                        'class' => 'text-bottom',
+                        'title' => $mvv_file->extern_visible?_('sichtbar'):_('unsichtbar')
+                    ]) ?>
+                </td>
+                <td class="dont-hide">
+                <? foreach ($mvv_file->file_refs as $fileref) : ?>
+                    <?= Assets::img(MVV::getContentLanguageImagePath($fileref->file_language), ['size' => '24']) ?>
+                <? endforeach; ?>
+                </td>
+                <td class="dont-hide"><?= htmlReady($GLOBALS['MVV_DOCUMENTS']['TYPE']['values'][$mvv_file->type]['name']) ?></td>
+                <td class="dont-hide"><?= strftime('%x', $mvv_file->mkdate) ?></td>
+                <td class="dont-hide"><?= htmlReady($mvv_file->getFiletypes()[0]) ?></td>
+                <td class="dont-hide"><?= htmlReady($GLOBALS['MVV_DOCUMENTS']['CATEGORY']['values'][$mvv_file->category]['name']) ?></td>
+                <td class="dont-hide" style="text-align: center;"><?= htmlReady($mvv_file->count_relations) ?></td>
+                <td class="dont-hide actions">
+                <?
+                    $actions = ActionMenu::get()->setContext($mvv_file->getFilenames()[0]);
                     $actions->addLink(
-                        $fileref->file_ref->getDownloadURL('force_download'),
-                        _('Datei herunterladen') . ' (' . $fileref->file_language . ')',
-                        Icon::create('download'),
-                        ['target' => '_blank']
+                        $controller->url_for('materialien/files/add_dokument', 'index', $mvv_file->getRangeType()?:0, 0, $mvv_file->mvvfile_id),
+                        _('Dokument bearbeiten'),
+                        Icon::create('edit'),
+                        ['data-dialog' => 'size=auto']
                     );
-                }
-                $actions->addLink(
-                    $controller->url_for("materialien/files/delete_all_dokument/{$mvv_file->mvvfile_id}"),
-                    _('Dokument löschen'),
-                    Icon::create('trash'),
-                    [
-                        'data-confirm' => _('Wollen Sie das Dokument wirklich entfernen?'),
-                        'data-dialog' => 'size=auto'
-                    ]
-                );
-                echo $actions->render();
-            ?>
-            </td>
-        </tr>
-    <? if (in_array($range_id, $mvv_file->getRangesArray()) && ($fileref_id && in_array($fileref_id, $mvv_file->getFileRefsArray()))) : ?>
-        <tr class="loaded-details nohover">
-            <?= $this->render_partial('materialien/files/details', compact('mvv_file')) ?>
-        </tr>
-    <? endif; ?>
-    </tbody>
-<? endforeach; ?>
-<? if ($count > MVVController::$items_per_page) : ?>
-    <tfoot>
-        <tr>
-            <td colspan="10" style="text-align: right">
-                <?
-                $pagination = $GLOBALS['template_factory']->open('shared/pagechooser');
-                $pagination->clear_attributes();
-                $pagination->set_attribute('perPage', MVVController::$items_per_page);
-                $pagination->set_attribute('num_postings', $count);
-                $pagination->set_attribute('page', $page);
-                // ARGH!
-                $page_link = explode('?', $controller->action_url('index'))[0] . '?page_files=%s';
-                $pagination->set_attribute('pagelink', $page_link);
-                echo $pagination->render("shared/pagechooser");
+                    $actions->addLink(
+                        $controller->url_for('materialien/files/add_ranges_to_file',$mvv_file->mvvfile_id),
+                        _('Dokument zuordnen'),
+                        Icon::create('add'),
+                        ['data-dialog' => 'size=auto']
+                    );
+                    foreach ($mvv_file->file_refs as $fileref) {
+                        $actions->addLink(
+                            $fileref->file_ref->getDownloadURL('force_download'),
+                            _('Datei herunterladen') . ' (' . $fileref->file_language . ')',
+                            Icon::create('download'),
+                            ['target' => '_blank']
+                        );
+                    }
+                    $actions->addButton(
+                        'delete',
+                        _('Dokument löschen'),
+                        Icon::create('trash'),
+                        [
+                            'data-confirm' => _('Wollen Sie das Dokument wirklich entfernen?'),
+                            'data-dialog' => 'size=auto',
+                            'formaction' => $controller->url_for("materialien/files/delete_all_dokument/{$mvv_file->mvvfile_id}"),
+                        ]
+                    );
+                    echo $actions->render();
                 ?>
-            </td>
-        </tr>
-    </tfoot>
-<? endif; ?>
-</table>
+                </td>
+            </tr>
+        <? if (in_array($range_id, $mvv_file->getRangesArray()) && ($fileref_id && in_array($fileref_id, $mvv_file->getFileRefsArray()))) : ?>
+            <tr class="loaded-details nohover">
+                <?= $this->render_partial('materialien/files/details', compact('mvv_file')) ?>
+            </tr>
+        <? endif; ?>
+        </tbody>
+    <? endforeach; ?>
+    <? if ($count > MVVController::$items_per_page) : ?>
+        <tfoot>
+            <tr>
+                <td colspan="10" style="text-align: right">
+                    <?
+                    $pagination = $GLOBALS['template_factory']->open('shared/pagechooser');
+                    $pagination->clear_attributes();
+                    $pagination->set_attribute('perPage', MVVController::$items_per_page);
+                    $pagination->set_attribute('num_postings', $count);
+                    $pagination->set_attribute('page', $page);
+                    // ARGH!
+                    $page_link = explode('?', $controller->action_url('index'))[0] . '?page_files=%s';
+                    $pagination->set_attribute('pagelink', $page_link);
+                    echo $pagination->render("shared/pagechooser");
+                    ?>
+                </td>
+            </tr>
+        </tfoot>
+    <? endif; ?>
+    </table>
+</form>
 <script type="text/javascript">
 jQuery(function ($) {
     $(document).on('dialog-close', function(event) {
diff --git a/app/views/materialien/files/range.php b/app/views/materialien/files/range.php
index bbf24755dd7..0bdbc15a200 100644
--- a/app/views/materialien/files/range.php
+++ b/app/views/materialien/files/range.php
@@ -5,109 +5,113 @@
     <? endforeach ?>
 <? endif; ?>
 </div>
-<table id="mvv_files" class="default sortable-table" data-sortlist="[[0, 0]]">
-    <caption>
-        <span class="actions">
-            <a href="<?= $controller->url_for('materialien/files/add_dokument', 'range', $range_type, $range_id);?>" data-dialog="">
-                <?= Icon::create('upload', Icon::ROLE_CLICKABLE, ['title' => _('neues Dokument hinzufügen')]); ?>
-            </a>
-            <a href="<?= $controller->url_for('materialien/files/add_files_to_range',$range_type, $range_id);?>" data-dialog="">
-                <?= Icon::create('add', Icon::ROLE_CLICKABLE, ['title' => _('vorhandenes Dokument hinzufügen')]); ?>
-            </a>
-            <a href="<?= $controller->url_for('materialien/files/sort', $range_type, $range_id);?>" data-dialog="size=auto">
-                <?= Icon::create('arr_2up', Icon::ROLE_CLICKABLE, ['title' => _('Reihenfolge der Dokumente ändern')]); ?>
-            </a>
-        </span>
-    </caption>
-    <thead>
-        <tr class="sortable">
-            <th data-sorter="digit"><?= _('Pos.') ?></th>
-            <th data-sorter="text"><?= _('Name') ?></th>
-            <th data-sorter="text"><?= _('Dateiname') ?></th>
-            <th data-sorter="digit"><?= _('Sichtbarkeit') ?></th>
-            <th><?= _('Sprache') ?></th>
-            <th data-sorter="text"><?= _('Art der Datei') ?></th>
-            <th data-sorter="digit"><?= _('Datum') ?></th>
-            <th data-sorter="text"><?= _('Dateityp') ?></th>
-            <th data-sorter="text"><?= _('Kategorie') ?></th>
-            <th data-sorter="digit"><?= _('Zuordnungen') ?></th>
-            <th data-sorter="false" style="text-align: right;"><?= _('Aktionen') ?></th>
-        </tr>
-    </thead>
-<? if($dokumente): ?>
-    <tbody>
-    <? foreach($dokumente as $mvv_file): ?>
-        <tr>
-            <td><?= htmlReady($mvv_file->getPositionInRange($range_id)); ?></td>
-            <td><?= htmlReady($mvv_file->getDisplayName()) ?></td>
-            <td data-sort-value="<?= htmlReady($mvv_file->getFilenames()[0]); ?>">
-                <? if($mvv_file->getFiletypes()[0] == 'Link'): ?>
-                    <a href="<?= htmlReady($mvv_file->getFilenames()[0]); ?>" target="_blank">
-                        <?= Icon::create('link-extern', Icon::ROLE_CLICKABLE, ['class' => 'text-bottom']); ?>
+<form method="post">
+    <?= CSRFProtection::tokenTag() ?>
+    <table id="mvv_files" class="default sortable-table" data-sortlist="[[0, 0]]">
+        <caption>
+            <span class="actions">
+                <a href="<?= $controller->url_for('materialien/files/add_dokument', 'range', $range_type, $range_id);?>" data-dialog="">
+                    <?= Icon::create('upload', Icon::ROLE_CLICKABLE, ['title' => _('neues Dokument hinzufügen')]); ?>
+                </a>
+                <a href="<?= $controller->url_for('materialien/files/add_files_to_range',$range_type, $range_id);?>" data-dialog="">
+                    <?= Icon::create('add', Icon::ROLE_CLICKABLE, ['title' => _('vorhandenes Dokument hinzufügen')]); ?>
+                </a>
+                <a href="<?= $controller->url_for('materialien/files/sort', $range_type, $range_id);?>" data-dialog="size=auto">
+                    <?= Icon::create('arr_2up', Icon::ROLE_CLICKABLE, ['title' => _('Reihenfolge der Dokumente ändern')]); ?>
+                </a>
+            </span>
+        </caption>
+        <thead>
+            <tr class="sortable">
+                <th data-sorter="digit"><?= _('Pos.') ?></th>
+                <th data-sorter="text"><?= _('Name') ?></th>
+                <th data-sorter="text"><?= _('Dateiname') ?></th>
+                <th data-sorter="digit"><?= _('Sichtbarkeit') ?></th>
+                <th><?= _('Sprache') ?></th>
+                <th data-sorter="text"><?= _('Art der Datei') ?></th>
+                <th data-sorter="digit"><?= _('Datum') ?></th>
+                <th data-sorter="text"><?= _('Dateityp') ?></th>
+                <th data-sorter="text"><?= _('Kategorie') ?></th>
+                <th data-sorter="digit"><?= _('Zuordnungen') ?></th>
+                <th data-sorter="false" style="text-align: right;"><?= _('Aktionen') ?></th>
+            </tr>
+        </thead>
+    <? if($dokumente): ?>
+        <tbody>
+        <? foreach($dokumente as $mvv_file): ?>
+            <tr>
+                <td><?= htmlReady($mvv_file->getPositionInRange($range_id)); ?></td>
+                <td><?= htmlReady($mvv_file->getDisplayName()) ?></td>
+                <td data-sort-value="<?= htmlReady($mvv_file->getFilenames()[0]); ?>">
+                    <? if($mvv_file->getFiletypes()[0] == 'Link'): ?>
+                        <a href="<?= htmlReady($mvv_file->getFilenames()[0]); ?>" target="_blank">
+                            <?= Icon::create('link-extern', Icon::ROLE_CLICKABLE, ['class' => 'text-bottom']); ?>
+                            <?= htmlReady($mvv_file->getFilenames()[0]); ?>
+                        </a>
+                    <? else: ?>
                         <?= htmlReady($mvv_file->getFilenames()[0]); ?>
-                    </a>
-                <? else: ?>
-                    <?= htmlReady($mvv_file->getFilenames()[0]); ?>
-                <? endif; ?>
-            </td>
-            <td style="text-align: center;" data-sort-value="<?= $mvv_file->extern_visible?'1':'0' ?>">
-                <?= Icon::create(
-                        $mvv_file->extern_visible?'visibility-visible':'visibility-invisible',
-                        Icon::ROLE_INFO,
+                    <? endif; ?>
+                </td>
+                <td style="text-align: center;" data-sort-value="<?= $mvv_file->extern_visible?'1':'0' ?>">
+                    <?= Icon::create(
+                            $mvv_file->extern_visible?'visibility-visible':'visibility-invisible',
+                            Icon::ROLE_INFO,
+                            [
+                                'class' => 'text-bottom',
+                                'title' => $mvv_file->extern_visible?_('sichtbar'):_('unsichtbar')
+                            ]
+                        );
+                    ?>
+                </td>
+                <td>
+                    <? foreach ($mvv_file->file_refs as $fileref) : ?>
+                        <?= Assets::img(MVV::getContentLanguageImagePath($fileref->file_language), ['size' => 24]) ?>
+                    <? endforeach; ?>
+                </td>
+                <td><?= htmlReady($GLOBALS['MVV_DOCUMENTS']['TYPE']['values'][$mvv_file->type]['name']); ?></td>
+                <td data-sort-value="<?= htmlReady($mvv_file->mkdate); ?>"><?= htmlReady(date('d.m.Y', $mvv_file->mkdate)); ?></td>
+                <td style="text-align: center;"><?= htmlReady($mvv_file->getFiletypes()[0]); ?></td>
+                <td><?= htmlReady($GLOBALS['MVV_DOCUMENTS']['CATEGORY']['values'][$mvv_file->category]['name']); ?></td>
+                <td style="text-align: center;"><?= htmlReady($mvv_file->count_relations); ?></td>
+                <td class="actions">
+                <?
+                    $actions = ActionMenu::get()->setContext($mvv_file->getDisplayName());
+                    $actions->addLink(
+                        $controller->url_for('materialien/files/details',$mvv_file->mvvfile_id),
+                        _('Details'),
+                        Icon::create('info-circle'),
+                        ['data-dialog' => 'size=auto']
+                    );
+                    $actions->addLink(
+                        $controller->url_for('materialien/files/add_dokument','range', $range_type, $range_id, $mvv_file->mvvfile_id),
+                        _('Dokument bearbeiten'),
+                        Icon::create('edit'),
+                        ['data-dialog' => '']
+                    );
+                    foreach ($mvv_file->file_refs as $fileref) {
+                        $actions->addLink(
+                            $fileref->file_ref->getDownloadURL('force_download'),
+                            _('Datei herunterladen') . ' (' . $fileref->file_language . ')',
+                            Icon::create('download', 'clickable', ['size' => 20]),
+                            ['target' => '_blank']
+                        );
+                    }
+                    $actions->addButton(
+                        'delete',
+                        _('Dokument-Zuordnung löschen'),
+                        Icon::create('trash'),
                         [
-                            'class' => 'text-bottom',
-                            'title' => $mvv_file->extern_visible?_('sichtbar'):_('unsichtbar')
+                            'data-confirm' => _('Wollen Sie die Zuordnung des Dokuments wirklich entfernen?'),
+                            'data-dialog'  => 'size=auto',
+                            'formaction'   => $controller->url_for('materialien/files/delete_range', $mvv_file->mvvfile_id, $range_id),
                         ]
                     );
+                    echo $actions;
                 ?>
-            </td>
-            <td>
-                <? foreach ($mvv_file->file_refs as $fileref) : ?>
-                    <?= Assets::img(MVV::getContentLanguageImagePath($fileref->file_language), ['size' => 24]) ?>
-                <? endforeach; ?>
-            </td>
-            <td><?= htmlReady($GLOBALS['MVV_DOCUMENTS']['TYPE']['values'][$mvv_file->type]['name']); ?></td>
-            <td data-sort-value="<?= htmlReady($mvv_file->mkdate); ?>"><?= htmlReady(date('d.m.Y', $mvv_file->mkdate)); ?></td>
-            <td style="text-align: center;"><?= htmlReady($mvv_file->getFiletypes()[0]); ?></td>
-            <td><?= htmlReady($GLOBALS['MVV_DOCUMENTS']['CATEGORY']['values'][$mvv_file->category]['name']); ?></td>
-            <td style="text-align: center;"><?= htmlReady($mvv_file->count_relations); ?></td>
-            <td class="actions">
-            <?
-                $actions = ActionMenu::get()->setContext($mvv_file->getDisplayName());
-                $actions->addLink(
-                    $controller->url_for('materialien/files/details',$mvv_file->mvvfile_id),
-                    _('Details'),
-                    Icon::create('info-circle'),
-                    ['data-dialog' => 'size=auto']
-                );
-                $actions->addLink(
-                    $controller->url_for('materialien/files/add_dokument','range', $range_type, $range_id, $mvv_file->mvvfile_id),
-                    _('Dokument bearbeiten'),
-                    Icon::create('edit'),
-                    ['data-dialog' => '']
-                );
-                foreach ($mvv_file->file_refs as $fileref) {
-                    $actions->addLink(
-                        $fileref->file_ref->getDownloadURL('force_download'),
-                        _('Datei herunterladen') . ' (' . $fileref->file_language . ')',
-                        Icon::create('download', 'clickable', ['size' => 20]),
-                        ['target' => '_blank']
-                    );
-                }
-                $actions->addLink(
-                    $controller->url_for('materialien/files/delete_range', $mvv_file->mvvfile_id, $range_id),
-                    _('Dokument-Zuordnung löschen'),
-                    Icon::create('trash'),
-                    [
-                        'data-confirm' => _('Wollen Sie die Zuordnung des Dokuments wirklich entfernen?'),
-                        'data-dialog' => 'size=auto'
-                    ]
-                );
-                echo $actions;
-            ?>
-            </td>
-        </tr>
-    <? endforeach; ?>
-    </tbody>
-<? endif; ?>
-</table>
+                </td>
+            </tr>
+        <? endforeach; ?>
+        </tbody>
+    <? endif; ?>
+    </table>
+</form>
diff --git a/app/views/shared/contacts/index.php b/app/views/shared/contacts/index.php
index 297de31b7bd..496aeaadf71 100644
--- a/app/views/shared/contacts/index.php
+++ b/app/views/shared/contacts/index.php
@@ -5,93 +5,98 @@
     <? endforeach ?>
 <? endif; ?>
 </div>
-<table id="mvv_contacts" class="default collapsable">
-    <caption>
-        <span class="actions"><? printf('%s Ansprechpartner', $count) ?></span>
-    </caption>
-    <thead>
-        <tr>
-            <?= $controller->renderSortLink('shared/contacts/', _('Name/Institution'), 'name', ['style' => 'width: 40%;']) ?>
-            <?= $controller->renderSortLink('shared/contacts/', _('Alternative Kontaktmail'), 'alt_mail', ['style' => 'width: 20%;']) ?>
-            <?= $controller->renderSortLink('shared/contacts/', _('Status'), 'contact_status', ['style' => 'width: 15%;']) ?>
-            <?= $controller->renderSortLink('shared/contacts/', _('Zuordnungen'), 'count_relations', ['style' => 'width: 20%;']) ?>
-            <th style="width: 5%; text-align: right;"><?= _('Aktionen') ?></th>
-        </tr>
-    </thead>
-<? if ($contacts) : ?>
-    <? foreach ($contacts as $mvv_contact) : ?>
-    <? $perm = new MvvPerm($mvv_contact) ?>
-    <tbody class="<?= ($contact_id == $mvv_contact->contact_id ? 'not-collapsed' : 'collapsed') ?>">
-        <tr class="header-row">
-            <td class="toggle-indicator">
-                <a class="mvv-load-in-new-row"
-                    href="<?= $controller->url_for('shared/contacts/details/index', $mvv_contact->contact_id) ?>"><?= htmlReady($mvv_contact->getContactName()) ?></a>
-            </td>
-            <td class="dont-hide"><?= htmlReady($mvv_contact->alt_mail); ?></td>
-            <td class="dont-hide"><?= htmlReady($GLOBALS['MVV_CONTACTS']['STATUS']['values'][$mvv_contact->contact_status]['name'] ?? '') ?></td>
-            <td class="dont-hide"><?= htmlReady($mvv_contact->count_relations); ?></td>
-            <td class="dont-hide actions">
-            <?
-                $actions = ActionMenu::get()->setContext($mvv_contact->getContactName());
-                if ($perm->haveFieldPerm('ranges', MvvPerm::PERM_CREATE)) {
-                    $actions->addLink(
-                        $controller->url_for('shared/contacts/add_ranges_to_contact', $mvv_contact->contact_id),
-                        _('Ansprechpartner zuordnen'),
-                        Icon::create('person'),
-                        ['data-dialog' => 'size=auto']
-                    );
-                    $actions->addLink(
-                        $controller->url_for('shared/contacts/delete_all_ranges', $mvv_contact->contact_id),
-                        _('Alle Zuordnungen löschen'),
-                        Icon::create('trash'),
-                        [
-                            'data-confirm' => _('Wollen Sie wirklich alle Zuordnungen entfernen?'),
-                            'data-dialog' => 'size=auto'
-                        ]
-                    );
-                }
-                if ($mvv_contact->contact_status === 'extern' && $perm->havePerm(MvvPerm::PERM_CREATE)) {
-                    $actions->addLink(
-                        $controller->url_for('shared/contacts/delete_extern_contact', $mvv_contact->contact_id),
-                        _('Externe Person löschen'),
-                        Icon::create('trash'),
-                        [
-                            'data-confirm' => _('Wollen Sie die externe Person wirklich löschen?'),
-                            'data-dialog' => 'size=auto'
-                        ]
-                    );
-                }
-                echo $actions;
-            ?>
-            </td>
-        </tr>
-        <? if ($contact_id == $mvv_contact->contact_id) : ?>
-            <tr class="loaded-details nohover">
-                <?= $this->render_partial('shared/contacts/details', compact('mvv_contact')) ?>
-            </tr>
-        <? endif; ?>
-    </tbody>
-    <? endforeach; ?>
-    <? if ($count > MVVController::$items_per_page) : ?>
-        <tfoot>
+<form method="post">
+    <?= CSRFProtection::tokenTag() ?>
+    <table id="mvv_contacts" class="default collapsable">
+        <caption>
+            <span class="actions"><? printf('%s Ansprechpartner', $count) ?></span>
+        </caption>
+        <thead>
             <tr>
-                <td colspan="10" style="text-align: right">
-                    <?
-                    $pagination = $GLOBALS['template_factory']->open('shared/pagechooser');
-                    $pagination->clear_attributes();
-                    $pagination->set_attribute('perPage', MVVController::$items_per_page);
-                    $pagination->set_attribute('num_postings', $count);
-                    $pagination->set_attribute('page', $page);
-                    $page_link = explode('?', $controller->action_url('index'))[0] . '?page_contacts=%s';
-                    $pagination->set_attribute('pagelink', $page_link);
-                    echo $pagination->render('shared/pagechooser');
-                    ?>
+                <?= $controller->renderSortLink('shared/contacts/', _('Name/Institution'), 'name', ['style' => 'width: 40%;']) ?>
+                <?= $controller->renderSortLink('shared/contacts/', _('Alternative Kontaktmail'), 'alt_mail', ['style' => 'width: 20%;']) ?>
+                <?= $controller->renderSortLink('shared/contacts/', _('Status'), 'contact_status', ['style' => 'width: 15%;']) ?>
+                <?= $controller->renderSortLink('shared/contacts/', _('Zuordnungen'), 'count_relations', ['style' => 'width: 20%;']) ?>
+                <th style="width: 5%; text-align: right;"><?= _('Aktionen') ?></th>
+            </tr>
+        </thead>
+    <? if ($contacts) : ?>
+        <? foreach ($contacts as $mvv_contact) : ?>
+        <? $perm = new MvvPerm($mvv_contact) ?>
+        <tbody class="<?= ($contact_id == $mvv_contact->contact_id ? 'not-collapsed' : 'collapsed') ?>">
+            <tr class="header-row">
+                <td class="toggle-indicator">
+                    <a class="mvv-load-in-new-row"
+                        href="<?= $controller->url_for('shared/contacts/details/index', $mvv_contact->contact_id) ?>"><?= htmlReady($mvv_contact->getContactName()) ?></a>
+                </td>
+                <td class="dont-hide"><?= htmlReady($mvv_contact->alt_mail); ?></td>
+                <td class="dont-hide"><?= htmlReady($GLOBALS['MVV_CONTACTS']['STATUS']['values'][$mvv_contact->contact_status]['name'] ?? '') ?></td>
+                <td class="dont-hide"><?= htmlReady($mvv_contact->count_relations); ?></td>
+                <td class="dont-hide actions">
+                <?
+                    $actions = ActionMenu::get()->setContext($mvv_contact->getContactName());
+                    if ($perm->haveFieldPerm('ranges', MvvPerm::PERM_CREATE)) {
+                        $actions->addLink(
+                            $controller->url_for('shared/contacts/add_ranges_to_contact', $mvv_contact->contact_id),
+                            _('Ansprechpartner zuordnen'),
+                            Icon::create('person'),
+                            ['data-dialog' => 'size=auto']
+                        );
+                        $actions->addButton(
+                            'delete_all_ranges',
+                            _('Alle Zuordnungen löschen'),
+                            Icon::create('trash'),
+                            [
+                                'data-confirm' => _('Wollen Sie wirklich alle Zuordnungen entfernen?'),
+                                'data-dialog'  => 'size=auto',
+                                'formaction'   => $controller->url_for('shared/contacts/delete_all_ranges', $mvv_contact->contact_id),
+                            ]
+                        );
+                    }
+                    if ($mvv_contact->contact_status === 'extern' && $perm->havePerm(MvvPerm::PERM_CREATE)) {
+                        $actions->addButton(
+                            'delete_extern_contact',
+                            _('Externe Person löschen'),
+                            Icon::create('trash'),
+                            [
+                                'data-confirm' => _('Wollen Sie die externe Person wirklich löschen?'),
+                                'data-dialog'  => 'size=auto',
+                                'formaction'   => $controller->url_for('shared/contacts/delete_extern_contact', $mvv_contact->contact_id),
+                            ]
+                        );
+                    }
+                    echo $actions;
+                ?>
                 </td>
             </tr>
-        </tfoot>
+            <? if ($contact_id == $mvv_contact->contact_id) : ?>
+                <tr class="loaded-details nohover">
+                    <?= $this->render_partial('shared/contacts/details', compact('mvv_contact')) ?>
+                </tr>
+            <? endif; ?>
+        </tbody>
+        <? endforeach; ?>
+        <? if ($count > MVVController::$items_per_page) : ?>
+            <tfoot>
+                <tr>
+                    <td colspan="10" style="text-align: right">
+                        <?
+                        $pagination = $GLOBALS['template_factory']->open('shared/pagechooser');
+                        $pagination->clear_attributes();
+                        $pagination->set_attribute('perPage', MVVController::$items_per_page);
+                        $pagination->set_attribute('num_postings', $count);
+                        $pagination->set_attribute('page', $page);
+                        $page_link = explode('?', $controller->action_url('index'))[0] . '?page_contacts=%s';
+                        $pagination->set_attribute('pagelink', $page_link);
+                        echo $pagination->render('shared/pagechooser');
+                        ?>
+                    </td>
+                </tr>
+            </tfoot>
+        <? endif; ?>
     <? endif; ?>
-<? endif; ?>
-</table>
+    </table>
+</form>
 <script type="text/javascript">
 jQuery(function ($) {
     $(document).on('dialog-close', function(event) {
diff --git a/lib/classes/forms/Form.php b/lib/classes/forms/Form.php
index fa0422e1a92..fbedc1fb676 100644
--- a/lib/classes/forms/Form.php
+++ b/lib/classes/forms/Form.php
@@ -388,9 +388,8 @@ class Form extends Part
      */
     public function store()
     {
-        if (!\CSRFProtection::verifyRequest()) {
-            throw new \AccessDeniedException();
-        }
+        \CSRFProtection::verifyUnsafeRequest();
+
         \NotificationCenter::postNotification('FormWillStore', $this);
 
         $stored = 0;
-- 
GitLab