diff --git a/app/controllers/admin/courses.php b/app/controllers/admin/courses.php
index 53fdbc4b4c3fefd3d94af6a6dc7995def7c0c54d..2ff2a9e2f5cf15214c80e3c62b9220a22469b757 100644
--- a/app/controllers/admin/courses.php
+++ b/app/controllers/admin/courses.php
@@ -706,7 +706,7 @@ class Admin_CoursesController extends AuthenticatedController
 
             foreach ($icons as $icon) {
                 $d['contents'] .= '<li class="my-courses-navigation-item '. ($icon->getImage()->signalsAttention() ? 'my-courses-navigation-important' : '').'">
-                        <a href="'. URLHelper::getLink('seminar_main.php', ['auswahl' => $course->id, 'redirect_to' => $icon->getURL()]).'"'. ($icon->getTitle() ? ' title="'.htmlReady($icon->getTitle()).'"' : '') .'>
+                        <a href="'. URLHelper::getLink('dispatch.php/course/go', ['to' => $course->id, 'redirect_to' => $icon->getURL()]).'"'. ($icon->getTitle() ? ' title="'.htmlReady($icon->getTitle()).'"' : '') .'>
                             '. $icon->getImage()->asImg() .'
                         </a>
                     </li>';
diff --git a/app/controllers/api/oauth2/oauth2_controller.php b/app/controllers/api/oauth2/oauth2_controller.php
index 6b3dacd37158609030c11bcd9fd21bd432bcea4c..e9208fce3aa26ec60aeeff7f9a9ec1d7b0d96123 100644
--- a/app/controllers/api/oauth2/oauth2_controller.php
+++ b/app/controllers/api/oauth2/oauth2_controller.php
@@ -15,13 +15,6 @@ abstract class OAuth2Controller extends StudipController
     {
         parent::before_filter($action, $args);
 
-        page_open([
-            'sess' => 'Seminar_Session',
-            'auth' => 'Seminar_Default_Auth',
-            'perm' => 'Seminar_Perm',
-            'user' => 'Seminar_User',
-        ]);
-
         $this->set_layout(null);
 
         $this->container = new Studip\OAuth2\Container();
diff --git a/app/controllers/blubber.php b/app/controllers/blubber.php
index 8ede39ee478140a8f797d433b6e23b425282bac9..550770bbe23efc8061df19f91885928bbfb40b8c 100644
--- a/app/controllers/blubber.php
+++ b/app/controllers/blubber.php
@@ -423,7 +423,7 @@ class BlubberController extends AuthenticatedController
             );
 
             PageLayout::postSuccess(sprintf(_('Studiengruppe "%s" wurde angelegt.'), htmlReady($course['name'])));
-            $this->redirect(URLHelper::getURL('seminar_main.php', ['auswahl' => $course->getId()]));
+            $this->redirect(URLHelper::getURL('dispatch.php/course/go', ['to' => $course->getId()]));
         }
     }
 
diff --git a/app/controllers/consultation/admin.php b/app/controllers/consultation/admin.php
index 21507edb0418176d60288f835322003f09b43e97..011e42462d1a13e54f9309d2499dcad50a1e6806 100644
--- a/app/controllers/consultation/admin.php
+++ b/app/controllers/consultation/admin.php
@@ -790,7 +790,7 @@ class Consultation_AdminController extends ConsultationController
 
         // Redirect to message write
         $_SESSION['sms_data'] = compact('p_rec');
-        page_close();
+        sess()->save();
         $this->redirect(URLHelper::getURL(
             'dispatch.php/messages/write',
             compact('default_subject')
diff --git a/app/controllers/course/basicdata.php b/app/controllers/course/basicdata.php
index f463a043cc7a8d6e47a68213dd431fc641dc0cde..4524153feca47ca5beeb2168ba658fa73fba685f 100644
--- a/app/controllers/course/basicdata.php
+++ b/app/controllers/course/basicdata.php
@@ -368,7 +368,7 @@ class Course_BasicdataController extends AuthenticatedController
                     _('Veranstaltung kopieren'),
                     $this->url_for(
                          'course/wizard/copy/' . $this->course_id,
-                         ['studip_ticket' => Seminar_Session::get_ticket()]
+                         ['studip_ticket' => get_ticket()]
                     ),
                     Icon::create('seminar')
                 );
@@ -381,7 +381,7 @@ class Course_BasicdataController extends AuthenticatedController
                 _('Sperrebene ändern') . ' (' . ($is_locked ? _('gesperrt') : _('nicht gesperrt')) . ')',
                 $this->url_for(
                     'course/management/lock',
-                    ['studip_ticket' => Seminar_Session::get_ticket()]
+                    ['studip_ticket' => get_ticket()]
                 ),
                 Icon::create('lock-' . ($is_locked ? 'locked' : 'unlocked'))
             )->asDialog('size=auto');
@@ -397,7 +397,7 @@ class Course_BasicdataController extends AuthenticatedController
                     $is_visible ? _('Veranstaltung verstecken') : _('Veranstaltung sichtbar schalten'),
                     $this->url_for(
                         'course/management/change_visibility',
-                        ['studip_ticket' => Seminar_Session::get_ticket()]
+                        ['studip_ticket' => get_ticket()]
                     ),
                     Icon::create('visibility-' . ($is_visible ? 'visible' : 'invisible'))
                 );
@@ -428,7 +428,7 @@ class Course_BasicdataController extends AuthenticatedController
                 _('Veranstaltung löschen'),
                 $this->url_for(
                     'course/archive/confirm',
-                    ['studip_ticket' => Seminar_Session::get_ticket()]
+                    ['studip_ticket' => get_ticket()]
                 ),
                 Icon::create('trash')
             )->asDialog('size=auto');
diff --git a/app/controllers/course/details.php b/app/controllers/course/details.php
index 707b0ed990d30387f18e89bee0c07e526e38b04e..ab1c2a6921c1c8e5530567291bcdc9c028626260 100644
--- a/app/controllers/course/details.php
+++ b/app/controllers/course/details.php
@@ -39,7 +39,7 @@ class Course_DetailsController extends AuthenticatedController
         }
         $this->send_from_search_page = Request::get('send_from_search_page');
 
-        if ($GLOBALS['SessionSeminar'] != $this->course->id
+        if (isset($GLOBALS['SessionSeminar']) && $GLOBALS['SessionSeminar'] != $this->course->id
             && !(int)$this->course->visible
             && !($GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM)
                 || $GLOBALS['perm']->have_studip_perm('user', $this->course->id))) {
@@ -52,7 +52,7 @@ class Course_DetailsController extends AuthenticatedController
 
         if ($this->course->getSemClass()->offsetGet('studygroup_mode')) {
             if ($GLOBALS['perm']->have_studip_perm('autor', $this->course->id)) { // participants may see seminar_main
-                $link = URLHelper::getUrl('seminar_main.php', ['auswahl' => $this->course->id]);
+                $link = URLHelper::getUrl('dispatch.php/course/go', ['to' => $this->course->id]);
             } else {
                 $link = URLHelper::getUrl('dispatch.php/course/studygroup/details/' . $this->course->id, [
                     'send_from_search_page' => $this->send_from_search_page,
@@ -202,7 +202,7 @@ class Course_DetailsController extends AuthenticatedController
 
             $enrolment_info = null;
 
-            if ($GLOBALS['SessionSeminar'] === $this->course->id) {
+            if (isset($GLOBALS['SessionSeminar']) && $GLOBALS['SessionSeminar'] === $this->course->id) {
                 Navigation::activateItem('/course/main/details');
             } else {
                 $enrolment_info = $this->course->getEnrolmentInformation($GLOBALS['user']->id);
diff --git a/app/controllers/course/enrolment.php b/app/controllers/course/enrolment.php
index e2c0e76b0444dc714f7d41283a2c0c7f78c6e15a..97aa62e863bc94c8829b60a6f2c9a291307022bf 100644
--- a/app/controllers/course/enrolment.php
+++ b/app/controllers/course/enrolment.php
@@ -49,7 +49,7 @@ class Course_EnrolmentController extends AuthenticatedController
                 || ($enrolment_info->getCodeword() === 'free_access' && !User::findCurrent())
             )
         ) {
-            $redirect_url = URLHelper::getUrl('seminar_main.php', ['auswahl' => $this->course_id]);
+            $redirect_url = URLHelper::getUrl('dispatch.php/course/go', ['to' => $this->course_id]);
             if (Request::isXhr()) {
                 $this->response->add_header('X-Location', $redirect_url);
                 $this->render_nothing();
@@ -252,7 +252,7 @@ class Course_EnrolmentController extends AuthenticatedController
             if (!empty($course) && $course->admission_prelim) {
                 $this->relocate(URLHelper::getLink('dispatch.php/course/details', ['sem_id' => $this->course_id]));
             } else {
-                $this->relocate(URLHelper::getLink('seminar_main.php', ['auswahl' => $this->course_id]));
+                $this->relocate(URLHelper::getLink('dispatch.php/course/go', ['to' => $this->course_id]));
             }
         } elseif ($enrol_user) {
 
diff --git a/app/controllers/course/go.php b/app/controllers/course/go.php
new file mode 100644
index 0000000000000000000000000000000000000000..03ae1dfa7f649de5e7800032acc5f87af81dcefc
--- /dev/null
+++ b/app/controllers/course/go.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * dispatch.php/course/go - startpage for course controller, formerly known as seminar_main.php
+ *
+ * @author  André Noack <noack@data-quest.de>
+ * @license GPL2 or any later version
+ * @since   6.0
+ */
+
+class Course_GoController extends AuthenticatedController
+{
+    protected $allow_nobody = true;
+
+    public function __construct(\Trails\Dispatcher $dispatcher)
+    {
+        if (Request::option('to')) {
+            Request::set('cid', Request::option('to'));
+        }
+        parent::__construct($dispatcher);
+    }
+
+    public function index_action()
+    {
+        $course_id = Context::getId();
+
+        if (!$course_id && Request::get('cid')) {
+            $archive_id = Request::get('cid');
+            $archived = ArchivedCourse::find($archive_id);
+            if ($archived) {
+                $this->redirect(URLHelper::getURL('dispatch.php/search/archive', [
+                    'criteria' => $archived->name,
+                ]));
+                return;
+            }
+        }
+
+        if (!$course_id) {
+            throw new CheckObjectException(_('Sie haben kein Objekt gewählt.'));
+        }
+
+        //set visitdate for course, when coming from my_courses
+        if (Request::get('to')) {
+            object_set_visit($course_id, 0);
+        }
+
+
+        // gibt es eine Anweisung zur Umleitung?
+        $redirect_to = Request::get('redirect_to');
+        if ($redirect_to) {
+            if (!is_internal_url($redirect_to)) {
+                throw new Exception('Invalid redirection');
+            }
+
+            $this->redirect(URLHelper::getURL($redirect_to, ['cid' => $course_id]));
+            return;
+        }
+
+        // der Nutzer zum ersten
+        //Reiter der Veranstaltung weiter geleitet.
+        if (Navigation::hasItem("/course")) {
+            foreach (Navigation::getItem("/course")->getSubNavigation() as $index => $navigation) {
+                if ($index !== 'admin') {
+                    $this->redirect(URLHelper::getURL($navigation->getURL()));
+                    return;
+                }
+            }
+        }
+    }
+}
diff --git a/app/controllers/course/members.php b/app/controllers/course/members.php
index 822f4a4df48addc304d841cbb463bf21edeb87e5..df36820c501811a3479094b517c50fee196662ce 100644
--- a/app/controllers/course/members.php
+++ b/app/controllers/course/members.php
@@ -143,7 +143,7 @@ class Course_MembersController extends AuthenticatedController
         $this->tutoren = $filtered_members['tutor']->toArray();
         $this->autoren = $filtered_members['autor']->toArray();
         $this->users = $filtered_members['user']->toArray();
-        $this->studipticket = Seminar_Session::get_ticket();
+        $this->studipticket = get_ticket();
         $this->subject = $this->getSubject();
         $this->groups = $this->status_groups;
         $this->semAdmissionEnabled = false;
diff --git a/app/controllers/course/statusgroups.php b/app/controllers/course/statusgroups.php
index cbd80ad40c50f10b637ae6f765a88ad3f4b63016..24efa022278273f917fe4bcb10294a82523407f6 100644
--- a/app/controllers/course/statusgroups.php
+++ b/app/controllers/course/statusgroups.php
@@ -1582,7 +1582,7 @@ class Course_StatusgroupsController extends AuthenticatedController
             _('Die Gruppen wurden in die Veranstaltung %s kopiert.'),
             sprintf(
                 '<a href="%s">%s</a>',
-                URLHelper::getLink('seminar_main.php', ['auswahl' => $target_course_id], true),
+                URLHelper::getLink('dispatch.php/course/go', ['to' => $target_course_id], true),
                 htmlReady($target_course->getFullName())
             ),
         ));
diff --git a/app/controllers/course/studygroup.php b/app/controllers/course/studygroup.php
index e9a0f02e377255810951e2dd733400dfe1d023f7..981a152f2a5c2e9f7ca6c773755ef7175a7edb9a 100644
--- a/app/controllers/course/studygroup.php
+++ b/app/controllers/course/studygroup.php
@@ -108,7 +108,7 @@ class Course_StudygroupController extends AuthenticatedController
                     $icon = $icon->copyWithRole('info');
                     $infotext = _('Mitgliedschaft bereits beantragt!');
                 } else {
-                    $infolink = URLHelper::getURL('seminar_main.php', ['auswahl' => $studygroup->id]);
+                    $infolink = URLHelper::getURL('dispatch.php/course/go', ['to' => $studygroup->id]);
                     $infotext = _('Direkt zur Studiengruppe');
                 }
             } else if ($GLOBALS['perm']->have_perm('admin')) {
@@ -205,7 +205,7 @@ class Course_StudygroupController extends AuthenticatedController
             Sidebar::get()->addWidget($actions);
         } // ... otherwise redirect us to the seminar
         else {
-            $this->redirect(URLHelper::getURL('seminar_main.php?auswahl=' . $id));
+            $this->redirect(URLHelper::getURL('dispatch.php/course/go?to=' . $id));
         }
     }
 
@@ -408,7 +408,7 @@ class Course_StudygroupController extends AuthenticatedController
         }
 
         if (!$perm->have_studip_perm('tutor', $id)) {
-            $this->redirect(URLHelper::getURL('seminar_main.php', ['auswahl' => $id]));
+            $this->redirect(URLHelper::getURL('dispatch.php/course/go', ['to' => $id]));
             return;
         }
 
@@ -570,7 +570,7 @@ class Course_StudygroupController extends AuthenticatedController
         $id = Context::getId();
 
         if (!$perm->have_studip_perm('tutor', $id)) {
-            $this->redirect(URLHelper::getURL('seminar_main.php', ['auswahl' => $id]));
+            $this->redirect(URLHelper::getURL('dispatch.php/course/go', ['to' => $id]));
             exit;
         }
 
diff --git a/app/controllers/debugbar.php b/app/controllers/debugbar.php
index 9fc5286c693d189d38a47970c054257accedd5ab..3f43bd28feb9bfa0243341f4692805373b45744a 100644
--- a/app/controllers/debugbar.php
+++ b/app/controllers/debugbar.php
@@ -1,5 +1,5 @@
 <?php
-final class DebugbarController extends Trails\Controller
+final class DebugbarController extends StudipController
 {
     public function __construct(
         Trails\Dispatcher $dispatcher,
@@ -10,15 +10,23 @@ final class DebugbarController extends Trails\Controller
 
     public function css_action(): void
     {
-        $this->set_content_type('text/css;charset=utf-8');
-        $this->render_nothing();
+        $this->response->add_header('Content-Type', 'text/css;charset=utf-8');
+
+        ob_start();
         $this->debugbar->getJavascriptRenderer()->dumpCssAssets();
+        $content = ob_get_contents();
+        ob_end_clean();
+        $this->render_text($content);
+
     }
 
     public function js_action(): void
     {
-        $this->set_content_type('text/javascript;charset=utf-8');
-        $this->render_nothing();
+        $this->response->add_header('Content-Type', 'text/javascript;charset=utf-8');
+        ob_start();
         $this->debugbar->getJavascriptRenderer()->setIncludeVendors(false)->dumpJsAssets();
+        $content = ob_get_contents();
+        ob_end_clean();
+        $this->render_text($content);
     }
 }
diff --git a/app/controllers/login.php b/app/controllers/login.php
new file mode 100644
index 0000000000000000000000000000000000000000..09a7c6b07fb1573fe470e2af68271219c86e1253
--- /dev/null
+++ b/app/controllers/login.php
@@ -0,0 +1,173 @@
+<?php
+/**
+ * login.php - login
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * @author      André Noack <noack@data-quest.de>
+ * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category    Stud.IP
+ */
+class LoginController extends AuthenticatedController
+{
+    protected $allow_nobody = true;
+
+    public function __construct(\Trails\Dispatcher $dispatcher)
+    {
+        Config::get()->USER_VISIBILITY_CHECK = false;
+        parent::__construct($dispatcher);
+    }
+
+    public function index_action()
+    {
+        if ($GLOBALS['user']->id !== 'nobody') {
+            $this->redirect(URLHelper::getURL('dispatch.php/start'));
+            return;
+        }
+
+        if (Request::isXhr()) {
+            if (Request::isDialog()) {
+                $this->relocate(URLHelper::getURL($_SERVER['REQUEST_URI']));
+                return;
+            }
+            throw new AccessDeniedException();
+        }
+
+        if (Request::submitted('user_config_submitted')) {
+            CSRFProtection::verifyUnsafeRequest();
+            if (Request::submitted('unset_contrast')) {
+                $_SESSION['contrast'] = 0;
+                $this->redirect('login/index'); //we're too late to remove the high contrast mode, so we reload the page
+                return;
+            }
+            if (Request::submitted('set_contrast')) {
+                $_SESSION['contrast'] = 1;
+            }
+
+
+            foreach (array_keys($GLOBALS['INSTALLED_LANGUAGES']) as $language_key) {
+                if (Request::submitted('set_language_' . $language_key)) {
+                    $_SESSION['forced_language'] = $language_key;
+                    $_SESSION['_language'] = $language_key;
+                    init_i18n($_SESSION['_language']);
+                }
+            }
+            if (!empty($_SESSION['contrast'])) {
+                \PageLayout::addStylesheet('accessibility.css');
+            }
+
+        }
+        if (Request::isPost()) {
+            CSRFProtection::verifyUnsafeRequest();
+
+            $check_auth = StudipAuthAbstract::CheckAuthentication(Request::get('loginname'), Request::get('password'));
+
+            if ($check_auth['uid']) {
+                $uid = $check_auth['uid'];
+                if (isset($check_auth['need_email_activation']) && $check_auth['need_email_activation'] == $uid) {
+                    $this->need_email_activation = $uid;
+                    $_SESSION['semi_logged_in'] = $uid;
+                    $this->redirect('login/activate_email', ['uid' => $uid]);
+                    return;
+                } else {
+                    auth()->setAuthenticatedUser($check_auth['user']);
+                    Metrics::increment('core.login.succeeded');
+                    sess()->regenerateId(['auth', '_language', 'phpCAS', 'contrast']);
+                    if (isset($_SESSION['redirect_after_login'] )) {
+                        $this->redirect($_SESSION['redirect_after_login']);
+                        return;
+                    }
+                    $this->redirect('start/index');
+                    return;
+                }
+            } else {
+                Metrics::increment('core.login.failed');
+                $this->error_msg = $check_auth['error'];
+            }
+        }
+
+
+        if ($this->error_msg) {
+            PageLayout::postException(_('Bei der Anmeldung trat ein Fehler auf!'), $this->error_msg);
+        }
+        $this->uname =  (isset($this->auth["uname"]) ? $this->auth["uname"] : Request::username('loginname'));
+        $this->self_registration_activated = Config::get()->ENABLE_SELF_REGISTRATION;
+
+        $news_entries = StudipNews::GetNewsByRange('login', true, false);
+        if (class_exists('LoginFaq')) {
+            $this->faq_entries = LoginFaq::findBySQL("1 ORDER BY `faq_id` ASC");
+        }
+        $this->news_entries = array_values($news_entries);
+        PageLayout::setHelpKeyword('Basis.AnmeldungLogin');
+        PageLayout::disableSidebar();
+        PageLayout::setBodyElementId('login');
+    }
+
+    public function activate_email_action()
+    {
+        PageLayout::setTitle(_('E-Mail Aktivierung'));
+        $uid = Request::option('uid');
+        $user = User::find($uid);
+
+        if (!$user) {
+            throw new \Trails\Exception(400);
+        }
+        if (Request::get('key')) {
+            $key = $user->validation_key;
+
+            if (Request::get('key') === $key) {
+                $user->validation_key = '';
+                $user->store();
+                unset($_SESSION['semi_logged_in']);
+                PageLayout::postSuccess(_('Ihre E-Mail-Adresse wurde erfolgreich geändert.'));
+                $this->redirect(URLHelper::getURL('dispatch.php/start'));
+                return;
+            } else if ($key == '') {
+                PageLayout::postInfo(_('Ihre E-Mail-Adresse ist bereits geändert.'));
+                $this->redirect(URLHelper::getURL('dispatch.php/start'));
+                return;
+            } else {
+                if (Request::get('key')) {
+                    PageLayout::postError(_("Falscher Bestätigungscode."));
+                }
+                $this->mail_explain = true;
+                if ($_SESSION['semi_logged_in'] == Request::option('uid')) {
+                    $this->reenter_mail = true;
+                } else {
+                    PageLayout::postInfo(_('Sie können sich einloggen und sich den Bestätigungscode neu oder an eine andere E-Mail-Adresse schicken lassen.'));
+                    $this->redirect(URLHelper::getURL('dispatch.php/start'));
+                    return;
+                }
+            }
+
+        // checking semi_logged_in is important to avoid abuse
+        } else if (Request::get('email1') && Request::get('email2') && $_SESSION['semi_logged_in'] == Request::option('uid')) {
+            if (Request::get('email1') == Request::get('email2')) {
+                // change mail
+                $tmp_user = User::find(Request::option('uid'));
+                if ($tmp_user && $tmp_user->changeEmail(Request::get('email1'), true)) {
+                    $_SESSION['semi_logged_in'] = false;
+                }
+
+            } else {
+                PageLayout::postError(_('Die eingegebenen E-Mail-Adressen stimmen nicht überein. Bitte überprüfen Sie Ihre Eingabe.'));
+            }
+            $this->mail_explain = true;
+            $this->reenter_mail = true;
+        } else {
+            $this->mail_explain = true;
+        }
+    }
+
+    public function privacy_info_action()
+    {
+        // this page must be accessible during visibility decision
+        Config::get()->USER_VISIBILITY_CHECK = false;
+
+        PageLayout::setTitle(_('Erläuterungen zum Datenschutz'));
+    }
+}
diff --git a/app/controllers/logout.php b/app/controllers/logout.php
new file mode 100644
index 0000000000000000000000000000000000000000..22a93f09bb11cadd53168a7e6e2b6bc76f9bb317
--- /dev/null
+++ b/app/controllers/logout.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ * logout.php - logout
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * @author      André Noack <noack@data-quest.de>
+ * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category    Stud.IP
+ */
+class LogoutController extends AuthenticatedController
+{
+    protected $allow_nobody = true;
+
+    public function index_action()
+    {
+        if ($GLOBALS['user']->id !== 'nobody') {
+            $my_messaging_settings = $GLOBALS['user']->cfg->MESSAGING_SETTINGS;
+
+            //Wenn Option dafuer gewaehlt, alle ungelsesenen Nachrichten als gelesen speichern
+            if ($my_messaging_settings["logout_markreaded"]) {
+                Message::markAllAs();
+            }
+
+            $logout_user = $GLOBALS['user']->id;
+            $_language = $_SESSION['_language'];
+            $contrast = UserConfig::get($GLOBALS['user']->id)->USER_HIGH_CONTRAST;
+
+            // Get auth plugin of user before logging out since the $auth object will
+            // be modified by the logout
+            $auth_plugin = StudipAuthAbstract::getInstance($GLOBALS['user']->auth_plugin);
+
+            sess()->destroy();
+            //Session changed zuruecksetzen
+            $timeout=(time()-(15 * 60));
+            $GLOBALS['user']->set_last_action($timeout);
+
+            // Perform logout from auth plugin (if possible)
+            if ($auth_plugin instanceof StudipAuthSSO) {
+                $auth_plugin->logout();
+            }
+
+            sess()->start();
+            $_SESSION['_language'] = $_language;
+            if ($contrast) {
+                $_SESSION['contrast'] = $contrast;
+            }
+            NotificationCenter::addObserver(function() {
+                throw new NotificationVetoException();
+            }, '__invoke', 'PageCloseWillExecute');
+            PageLayout::postSuccess(
+                _('Sie sind nun aus dem System abgemeldet.'),
+                array_filter([$GLOBALS['UNI_LOGOUT_ADD']])
+            );
+        }
+
+        $this->redirect(URLHelper::getURL('dispatch.php/start'));
+    }
+}
diff --git a/app/controllers/media_proxy.php b/app/controllers/media_proxy.php
index b98b49c9c738f1139c26aba1e36828b3d009181e..7b37544fc373f6f915417eff1e3d2c25438121dd 100644
--- a/app/controllers/media_proxy.php
+++ b/app/controllers/media_proxy.php
@@ -34,7 +34,7 @@ class MediaProxyController extends StudipController
         $config = Config::GetInstance();
         $modified_since = NULL;
 
-        if (!Seminar_Session::is_current_session_authenticated() ||
+        if (!sess()->isCurrentSessionAuthenticated() ||
             $config->getValue('LOAD_EXTERNAL_MEDIA') != 'proxy') {
             throw new AccessDeniedException();
         }
diff --git a/app/controllers/my_courses.php b/app/controllers/my_courses.php
index 187d6c6e438956d52b6092331b562ec0920ee305..c7a6771aa6faa2eaae3931cf4b72a30dacdc90c9 100644
--- a/app/controllers/my_courses.php
+++ b/app/controllers/my_courses.php
@@ -334,7 +334,7 @@ class MyCoursesController extends AuthenticatedController
     public function decline_action($course_id, $waiting = null)
     {
         $course = Course::find($course_id);
-        $ticket_check    = Seminar_Session::check_ticket(Request::option('studipticket'));
+        $ticket_check    = check_ticket(Request::option('studipticket'));
         if (LockRules::Check($course_id, 'participants')) {
             $lockdata = LockRules::getObjectRule($course_id);
             PageLayout::postError(sprintf(
@@ -419,8 +419,8 @@ class MyCoursesController extends AuthenticatedController
 
             PageLayout::postQuestion(
                 $message,
-                $this->declineURL($course_id, ['cmd' => $cmd, 'studipticket' => Seminar_Session::get_ticket()]),
-                $this->declineURL($course_id, ['cmd' => 'back', 'studipticket' => Seminar_Session::get_ticket()])
+                $this->declineURL($course_id, ['cmd' => $cmd, 'studipticket' => get_ticket()]),
+                $this->declineURL($course_id, ['cmd' => 'back', 'studipticket' => get_ticket()])
             );
             $this->redirect('my_courses/index');
             return;
diff --git a/app/controllers/my_institutes.php b/app/controllers/my_institutes.php
index 0e15489aeb85255fee4ac759d7455c1f1d304e27..6093e72f8467bb1f450d32764050a39ba4d70d49 100644
--- a/app/controllers/my_institutes.php
+++ b/app/controllers/my_institutes.php
@@ -33,13 +33,13 @@ class MyInstitutesController extends AuthenticatedController
     public function decline_inst_action($inst_id)
     {
         $institut     = Institute::find($inst_id);
-        $ticket_check = Seminar_Session::check_ticket(Request::option('studipticket'));
+        $ticket_check = check_ticket(Request::option('studipticket'));
 
         if (Request::option('cmd') !== 'kill' && Request::get('cmd') !== 'back') {
             $this->flash['decline_inst'] = true;
             $this->flash['inst_id']      = $inst_id;
             $this->flash['name']         = $institut->name;
-            $this->flash['studipticket'] = Seminar_Session::get_ticket();
+            $this->flash['studipticket'] = get_ticket();
         } elseif (Request::get('cmd') === 'kill' && $ticket_check && Request::get('cmd') !== 'back') {
             $changed = InstituteMember::deleteBySQL(
                 "user_id = ? AND Institut_id = ? AND inst_perms = 'user'",
diff --git a/app/controllers/oer/endpoints.php b/app/controllers/oer/endpoints.php
index 1ce9626e891d2d08bf3187c1788a1586c79b38e9..bf398bf79e12555712f3f4fb56758ad811da18f4 100644
--- a/app/controllers/oer/endpoints.php
+++ b/app/controllers/oer/endpoints.php
@@ -322,7 +322,7 @@ class Oer_EndpointsController extends StudipController
         while (ob_get_level()) {
             ob_end_clean();
         }
-        page_close();
+        sess()->save();
 
         if (!file_exists($this->material->getFilePath())) {
             throw new Exception(_('Die gewünschte Datei konnte nicht gefunden werden.'));
diff --git a/app/controllers/registration.php b/app/controllers/registration.php
index 4c550ceae8d8d1d72b9ab9f3d5c8b7aa69c04b1a..5ca4cfd01d0cb7051c044d5d7354c20d2c178c2a 100644
--- a/app/controllers/registration.php
+++ b/app/controllers/registration.php
@@ -124,22 +124,85 @@ class RegistrationController extends AuthenticatedController
         $this->registrationform->addStoreCallback(
             function (Form $form) {
                 $new_user = $form->getLastPart()->getContextObject();
-
-                $GLOBALS['sess']->regenerate_session_id(['auth']);
-                $GLOBALS['auth']->unauth();
-                $GLOBALS['auth']->auth['jscript'] = true;
-                $GLOBALS['auth']->auth['perm']  = $new_user['perms'];
-                $GLOBALS['auth']->auth['uname'] = $new_user['username'];
-                $GLOBALS['auth']->auth['auth_plugin']  = $new_user['auth_plugin'];
-                $GLOBALS['auth']->auth_set_user_settings($new_user->user_id);
-                $GLOBALS['auth']->auth['uid'] = $new_user['user_id'];
-
-                Seminar_Register_Auth::sendValidationMail($new_user);
-
+                sess()->regenerateId();
+                auth()->setAuthenticatedUser($new_user);
+                auth()->sendValidationMail($new_user);
                 return 1;
             }
         );
 
         $this->registrationform->autoStore()->setURL(URLHelper::getURL('dispatch.php/start'));
     }
+
+    public function email_validation_action()
+    {
+        if (!User::findCurrent()) {
+            $_SESSION['redirect_after_login'] = Request::url();
+            sess()->save();
+            $this->redirect(URLHelper::getURL('dispatch.php/login'));
+            return;
+        }
+        // hier wird noch mal berechnet, welches secret in der Bestaetigungsmail uebergeben wurde
+        $secret = Request::option('secret');
+        PageLayout::setHelpKeyword('Basis.AnmeldungMail');
+        PageLayout::setTitle(_('Bestätigung der E-Mail-Adresse'));
+        //user bereits vorhanden
+        if ($GLOBALS['perm']->have_perm('autor')) {
+            $info = sprintf(_('Sie haben schon den Status <b>%s</b> im System.
+                       Eine Aktivierung des Accounts ist nicht mehr nötig, um Schreibrechte zu bekommen'), $GLOBALS['user']->perms);
+            $details = [];
+            $details[] = sprintf('<a href="%s">%s</a>', URLHelper::getLink('index.php'), _('zurück zur Startseite'));
+            $message = MessageBox::info($info, $details);
+        }
+
+        //  So, wer bis hier hin gekommen ist gehoert zur Zielgruppe...
+        // Volltrottel (oder abuse)
+        elseif (empty($secret)) {
+            $message = MessageBox::error(_('Sie müssen den vollständigen Link aus der Bestätigungsmail in die Adresszeile Ihres Browsers kopieren.'));
+        }
+
+        // abuse (oder Volltrottel)
+        else {
+            if (!Token::isValid($secret, User::findCurrent()->id)) {
+                $error = _('Der übergebene <em>Secret-Code</em> ist nicht korrekt.');
+                $details = [];
+                $details[] = _('Sie müssen unter dem Benutzernamen eingeloggt sein, für den Sie die Bestätigungsmail erhalten haben.');
+                $details[] = _('Und Sie müssen den vollständigen Link aus der Bestätigungsmail in die Adresszeile Ihres Browsers kopieren.');
+                $message = MessageBox::error($error, $details);
+
+                // Mail an abuse
+                $REMOTE_ADDR = $_SERVER['REMOTE_ADDR'];
+                $Zeit = date("H:i:s, d.m.Y", time());
+                $username = User::findCurrent()->username;
+                StudipMail::sendAbuseMessage("Validation", "Secret falsch\n\nUser: $username\n\nIP: $REMOTE_ADDR\nZeit: $Zeit\n");
+            } // alles paletti, Status ändern
+            else {
+                $studip_user = User::findCurrent();
+                $studip_user->perms = 'autor';
+                if (!$studip_user->store()) {
+                    $error = _('Fehler! Bitte wenden Sie sich an den Systemadministrator.');
+                    $message = MessageBox::error($error);
+                } else {
+                    $success = _('Ihr Status wurde erfolgreich auf <em>autor</em> gesetzt.<br>
+                      Damit dürfen Sie in den meisten Veranstaltungen schreiben, für die Sie sich anmelden.');
+                    $details = [];
+                    $details[] = _('Einige Veranstaltungen erfordern allerdings bei der Anmeldung die Eingabe eines Passwortes.
+                        Dieses Passwort erfahren Sie von den Lehrenden der Veranstaltung.');
+                    $message = MessageBox::success($success, $details);
+
+                    // Auto-Inserts
+                    AutoInsert::instance()->saveUser($studip_user->id, "autor");
+
+                    auth()->setAuthenticatedUser(\User::build(['user_id' => 'nobody', 'perms' => null]));
+
+                    $info = sprintf(_('Die Statusänderung wird erst nach einem erneuten %sLogin%s wirksam!<br>
+                          Deshalb wurden Sie jetzt automatisch ausgeloggt.'),
+                        '<a href="' . URLHelper::getLink('index.php') . '"><em>',
+                        '</em></a>');
+                    $message .= MessageBox::info($info);
+                }
+                $this->message = $message;
+            }
+        }
+    }
 }
diff --git a/app/controllers/search/globalsearch.php b/app/controllers/search/globalsearch.php
index 05fcda294353eab1e66630036758c91adf2958ef..6901f483e78adfe2b2a7bfbdcc40701c1710cc45 100644
--- a/app/controllers/search/globalsearch.php
+++ b/app/controllers/search/globalsearch.php
@@ -31,7 +31,7 @@ class Search_GlobalsearchController extends AuthenticatedController
 
         PageLayout::addHeadElement('meta', [
             'name'    => 'studip-cache-prefix',
-            'content' => md5("{$_COOKIE[Seminar_Session::class]}-{$GLOBALS['user']->id}"),
+            'content' => md5("{$_COOKIE[$GLOBALS['SESSION_OPTIONS']['name']]}-{$GLOBALS['user']->id}"),
         ]);
 
         PageLayout::setBodyElementId('globalsearch-page');
diff --git a/app/controllers/settings/avatar.php b/app/controllers/settings/avatar.php
index 31bd7050233ff41eedb3f603242a83b4db533e56..067e346bbc3df54de967529fe6ba0894a5a542dd 100644
--- a/app/controllers/settings/avatar.php
+++ b/app/controllers/settings/avatar.php
@@ -5,8 +5,6 @@ class Settings_AvatarController extends AuthenticatedController
     public function before_filter(&$action, &$args)
     {
         parent::before_filter($action, $args);
-        // Ensure user is logged in
-        $GLOBALS['auth']->login_if($action !== 'logout' && $GLOBALS['auth']->auth['uid'] === 'nobody');
 
         if (!$GLOBALS['perm']->have_profile_perm('user', User::findCurrent()->id)) {
             throw new AccessDeniedException(_('Sie dürfen dieses Profil nicht bearbeiten'));
@@ -20,4 +18,4 @@ class Settings_AvatarController extends AuthenticatedController
         $avatar = Avatar::getAvatar($this->user_id);
         $this->avatar_url = $avatar->getURL(Avatar::NORMAL);
     }
-}
\ No newline at end of file
+}
diff --git a/app/controllers/settings/settings.php b/app/controllers/settings/settings.php
index 9cd00c09d1f6f7d804e0aa3aa682de075b67d339..294b2881e0c84d8409f09e39ceb14c5c5a8ffae5 100644
--- a/app/controllers/settings/settings.php
+++ b/app/controllers/settings/settings.php
@@ -35,9 +35,6 @@ abstract class Settings_SettingsController extends AuthenticatedController
 
         parent::before_filter($action, $args);
 
-        // Ensure user is logged in
-        $GLOBALS['auth']->login_if($action !== 'logout' && $GLOBALS['auth']->auth['uid'] === 'nobody');
-
         // extract username
         $username   = Request::username('username', $GLOBALS['user']->username);
         $this->user = User::findByUsername($username);
@@ -95,23 +92,6 @@ abstract class Settings_SettingsController extends AuthenticatedController
         }
     }
 
-    /**
-     * Adjust url_for so it imitates the parameters behaviour of URLHelper.
-     * This way you can add parameters by adding an associative array as last
-     * argument.
-     *
-     * @param mixed $to Path segments of the url (String) or url parameters
-     *                  (Array)
-     * @return String Generated url
-     */
-    public function url_for($to = ''/*, ...*/)
-    {
-        $arguments  = func_get_args();
-        $parameters = is_array(end($arguments)) ? array_pop($arguments) : [];
-        $url        = call_user_func_array('parent::url_for', $arguments);
-        return URLHelper::getURL($url, $parameters);
-    }
-
     /**
      * Gets the default template for an action.
      *
@@ -224,14 +204,9 @@ abstract class Settings_SettingsController extends AuthenticatedController
         $should_logout = $action === 'logout' && $this->flash['logout-token'] === Request::get('token');
 
         if ($should_logout) {
-            $GLOBALS['sess']->delete();
-            $GLOBALS['auth']->logout();
+            $this->redirect('dispatch.php/logout');
         }
 
         parent::after_filter($action, $args);
-
-        if ($should_logout) {
-            $GLOBALS['user']->set_last_action(time() - 15 * 60);
-        }
     }
 }
diff --git a/app/controllers/start.php b/app/controllers/start.php
index 82310959fcccc89652105766a7ecc0d342c67832..b655fc01b25fafb91051170d27a2af221ce38f70 100644
--- a/app/controllers/start.php
+++ b/app/controllers/start.php
@@ -317,7 +317,7 @@ class StartController extends AuthenticatedController
     public function resend_validation_mail_action()
     {
         if ($GLOBALS['perm']->get_perm() === 'user') {
-            Seminar_Register_Auth::sendValidationMail($GLOBALS['user']);
+            auth()->sendValidationMail();
             PageLayout::postSuccess(
                 _('Die Bestätigungsmail wurde erneut verschickt.')
             );
@@ -360,13 +360,11 @@ class StartController extends AuthenticatedController
                 $this->redirect('start/edit_mail_address');
                 return;
             }
-            $user = new User($GLOBALS['user']->id);
+            $user = \User::findCurrent();
             $user->Email = $email1;
             $user->store();
 
-            $GLOBALS['user']->Email = $user->Email;
-
-            Seminar_Register_Auth::sendValidationMail($user);
+            auth()->sendValidationMail($user);
             PageLayout::postMessage(MessageBox::success(
                 _('Ihre Mailadresse wurde geändert und die Bestätigungsmail erneut verschickt.')
             ));
diff --git a/app/controllers/terms.php b/app/controllers/terms.php
index 679a34774774e7b20ff40c69dd0451da03cf1341..d3a829ef1f62e37ffb4674befb476eb3a153e118 100644
--- a/app/controllers/terms.php
+++ b/app/controllers/terms.php
@@ -33,7 +33,7 @@ class TermsController extends AuthenticatedController
                 $this->redirectUser();
             } else {
                 $_SESSION['logout_ticket'] = get_ticket();
-                $this->redirectUser('logout.php');
+                $this->redirectUser('dispatch.php/logout');
             }
         } elseif (Request::get('action') === 'denied') {
             if (trim(Config::get()->TERMS_CONFIG['denial_message'])) {
diff --git a/app/controllers/web_migrate.php b/app/controllers/web_migrate.php
index 3553f9041a438d02b9dcd28c44ff5f020c82bc41..bf24e32a75ea9551c01e84f97dafe7215913eb69 100644
--- a/app/controllers/web_migrate.php
+++ b/app/controllers/web_migrate.php
@@ -12,7 +12,6 @@ class WebMigrateController extends StudipController
 
     public function before_filter(&$action, &$args)
     {
-        $GLOBALS['auth']->login_if(!$GLOBALS['perm']->have_perm('root'));
         $GLOBALS['perm']->check('root');
 
         parent::before_filter($action, $args);
diff --git a/app/views/admin/autoinsert/index.php b/app/views/admin/autoinsert/index.php
index a4a999e32bd4599e9bb271c5473095ede2d1cc78..33dfdcfff86c950748d03e5df66971dc4cd784f4 100644
--- a/app/views/admin/autoinsert/index.php
+++ b/app/views/admin/autoinsert/index.php
@@ -83,7 +83,7 @@
         <? foreach ($auto_sems as $auto_sem): ?>
             <tr>
                 <td>
-                    <a href="<?= URLHelper::getLink('seminar_main.php', ['auswahl' => $auto_sem['seminar_id']]) ?>">
+                    <a href="<?= URLHelper::getLink('dispatch.php/course/go', ['to' => $auto_sem['seminar_id']]) ?>">
                         <?= htmlReady($auto_sem['Name']) ?>
                     </a>
                 </td>
diff --git a/app/views/admin/user/_course_files.php b/app/views/admin/user/_course_files.php
index caac81ad8d3e2d95a6718675d1356154495a26ad..f2a9eae1e9ebd639d9f4f96430f557babbc7d5be 100644
--- a/app/views/admin/user/_course_files.php
+++ b/app/views/admin/user/_course_files.php
@@ -43,12 +43,12 @@
                         <? foreach ($file_data as $data): ?>
                             <tr>
                                 <td>
-                                    <a href="<?= URLHelper::getLink('seminar_main.php', ['auswahl' => $data['course']->id]) ?>">
+                                    <a href="<?= URLHelper::getLink('dispatch.php/course/go', ['to' => $data['course']->id]) ?>">
                                         <?= htmlReady($data['course']->veranstaltungsnummer) ?>
                                     </a>
                                 </td>
                                 <td>
-                                    <a href="<?= URLHelper::getLink('seminar_main.php', ['auswahl' => $data['course']->id]) ?>">
+                                    <a href="<?= URLHelper::getLink('dispatch.php/course/go', ['to' => $data['course']->id]) ?>">
                                         <?= htmlReady($data['course']->name) ?>
                                     </a>
                                 </td>
diff --git a/app/views/admin/user/_course_list.php b/app/views/admin/user/_course_list.php
index a7e090bfb553ff1e1b572171ba126f5028c0c534..9250d7cb3c9ae295d85ccee963f1e2d9b681f760 100644
--- a/app/views/admin/user/_course_list.php
+++ b/app/views/admin/user/_course_list.php
@@ -38,12 +38,12 @@
                             <? foreach ($_memberships as $membership): ?>
                                 <tr>
                                     <td>
-                                        <a href="<?= URLHelper::getLink('seminar_main.php', ['auswahl' => $membership->course->id]) ?>">
+                                        <a href="<?= URLHelper::getLink('dispatch.php/course/go', ['to' => $membership->course->id]) ?>">
                                             <?= htmlReady($membership->course->veranstaltungsnummer) ?>
                                         </a>
                                     </td>
                                     <td>
-                                        <a href="<?= URLHelper::getLink('seminar_main.php', ['auswahl' => $membership->course->id]) ?>">
+                                        <a href="<?= URLHelper::getLink('dispatch.php/course/go', ['to' => $membership->course->id]) ?>">
                                             <?= htmlReady($membership->course->name) ?>
                                         </a>
                                     </td>
diff --git a/app/views/admin/user/_priority_list.php b/app/views/admin/user/_priority_list.php
index e835a88309d3647d517e38b4d896702f742de66d..acb2ca09b649e75c169bde3d40897d9fbc76a3ef 100644
--- a/app/views/admin/user/_priority_list.php
+++ b/app/views/admin/user/_priority_list.php
@@ -31,12 +31,12 @@
                         <? $course = Course::find($priority['seminar_id']) ?>
                         <tr>
                             <td>
-                                <a href="<?= URLHelper::getLink('seminar_main.php', ['auswahl' => $course->id]) ?>">
+                                <a href="<?= URLHelper::getLink('dispatch.php/course/go', ['to' => $course->id]) ?>">
                                     <?= htmlReady($course->veranstaltungsnummer) ?>
                                 </a>
                             </td>
                             <td>
-                                <a href="<?= URLHelper::getLink('seminar_main.php', ['auswahl' => $course->id]) ?>">
+                                <a href="<?= URLHelper::getLink('dispatch.php/course/go', ['to' => $course->id]) ?>">
                                     <?= sprintf('%s (%s)', htmlReady($course->getFullName('type-name')), htmlReady($course->getFullName('sem-duration-name'))) ?>
                                 </a>
                             </td>
diff --git a/app/views/admin/user/_waiting_list.php b/app/views/admin/user/_waiting_list.php
index 1fd07242e6b4f2a1f3abd7f454e1bce17b98a214..d4b306eabf4e927fcb2756b8ccb1d35659deb47f 100644
--- a/app/views/admin/user/_waiting_list.php
+++ b/app/views/admin/user/_waiting_list.php
@@ -32,12 +32,12 @@
                     <? foreach ($memberships as $membership): ?>
                         <tr>
                             <td>
-                                <a href="<?= URLHelper::getLink('seminar_main.php', ['auswahl' => $membership->course->id]) ?>">
+                                <a href="<?= URLHelper::getLink('dispatch.php/course/go', ['to' => $membership->course->id]) ?>">
                                     <?= htmlReady($membership->course->veranstaltungsnummer) ?>
                                 </a>
                             </td>
                             <td>
-                                <a href="<?= URLHelper::getLink('seminar_main.php', ['auswahl' => $membership->course->id]) ?>">
+                                <a href="<?= URLHelper::getLink('dispatch.php/course/go', ['to' => $membership->course->id]) ?>">
                                     <?= sprintf('%s (%s)', htmlReady($membership->course->getFullName('type-name')), htmlReady($membership->course->getFullName('sem-duration-name'))) ?>
                                 </a>
                             </td>
diff --git a/app/views/api/oauth2/authorize.php b/app/views/api/oauth2/authorize.php
index 57d4ef61bde7ce3006bb3af1439dd8722a246273..f1bf2b0ef44949dd6e48c8c43d02b765d2c11a88 100644
--- a/app/views/api/oauth2/authorize.php
+++ b/app/views/api/oauth2/authorize.php
@@ -50,7 +50,7 @@
             ) ?><br>
     </p>
 
-    <form action="<?= URLHelper::getLink('logout.php') ?>" method="post">
+    <form action="<?= URLHelper::getLink('dispatch.php/logout') ?>" method="post">
         <button class="as-link">
             <small>
                 <?= sprintf(
diff --git a/app/views/course/enrolment/apply.php b/app/views/course/enrolment/apply.php
index 8d4ecb98e68a0d92c0cadc85ac669bdf83e6570f..53397a8fb0d28d6e264a755ae92ff939f5f075b5 100644
--- a/app/views/course/enrolment/apply.php
+++ b/app/views/course/enrolment/apply.php
@@ -55,7 +55,7 @@
     <div data-dialog-button>
         <?=Studip\LinkButton::createAccept(
             _('Zur Veranstaltung'),
-            URLHelper::getLink('seminar_main.php', ['auswahl' => $course_id])
+            URLHelper::getLink('dispatch.php/course/go', ['to' => $course_id])
         ) ?>
     </div>
 <? endif ?>
diff --git a/app/views/course/scm/edit.php b/app/views/course/scm/edit.php
index ea054c8332ea727ede1d7495262837a9d3c6bce9..8c5d11a032c7d491ed823b45eb24c57ce10c9867 100644
--- a/app/views/course/scm/edit.php
+++ b/app/views/course/scm/edit.php
@@ -54,7 +54,7 @@
     <footer data-dialog-button>
         <?= Studip\Button::createAccept(_('Speichern'), 'submit') ?>
     <? if (!empty($first_entry)): ?>
-        <?= Studip\LinkButton::createCancel(_('Abbrechen'), URLHelper::getLink('seminar_main.php')) ?>
+        <?= Studip\LinkButton::createCancel(_('Abbrechen'), URLHelper::getLink('dispatch.php/course/go')) ?>
     <? else: ?>
         <?= Studip\LinkButton::createCancel(_('Abbrechen'), $controller->url_for('course/scm/' . $scm->id)) ?>
     <? endif; ?>
diff --git a/app/views/course/studygroup/edit.php b/app/views/course/studygroup/edit.php
index 28a8cc5901862c8e5f3c2d1f389487fb4113aad3..e3189f746e5f1f97180d627fee1b7fd67c7cd240 100644
--- a/app/views/course/studygroup/edit.php
+++ b/app/views/course/studygroup/edit.php
@@ -43,6 +43,6 @@
 
     <footer>
         <?= Studip\Button::createAccept(_('Übernehmen'), ['title' => _("Änderungen übernehmen")]); ?>
-        <?= Studip\LinkButton::createCancel(_('Abbrechen'), URLHelper::getURL('seminar_main.php')); ?>
+        <?= Studip\LinkButton::createCancel(_('Abbrechen'), URLHelper::getURL('dispatch.php/course/go')); ?>
     </footer>
 </form>
diff --git a/templates/login/_login_faq.php b/app/views/login/_login_faq.php
similarity index 100%
rename from templates/login/_login_faq.php
rename to app/views/login/_login_faq.php
diff --git a/templates/login/_login_news.php b/app/views/login/_login_news.php
similarity index 100%
rename from templates/login/_login_news.php
rename to app/views/login/_login_news.php
diff --git a/templates/login/_standard_loginform.php b/app/views/login/_standard_loginform.php
similarity index 98%
rename from templates/login/_standard_loginform.php
rename to app/views/login/_standard_loginform.php
index e111453fd3d8165a2c1af970e94dbb30fa49ebf3..4221dcdb7d1ada142d7cd7f2b294615c793a15bf 100644
--- a/templates/login/_standard_loginform.php
+++ b/app/views/login/_standard_loginform.php
@@ -60,10 +60,10 @@ $password_tooltip_text = (string) Config::get()->PASSWORD_TOOLTIP_TEXT;
         </a>
 
     <?= CSRFProtection::tokenTag() ?>
-    <input type="hidden" name="login_ticket" value="<?= htmlReady(Seminar_Session::get_ticket()) ?>">
+    <input type="hidden" name="login_ticket" value="<?= htmlReady(get_ticket()) ?>">
     <input type="hidden" name="resolution" value="">
 
     <div class="login-button-wrapper">
         <?= Button::create(_('Anmelden'), _('Login'), ['id' => 'submit_login']); ?>
     </div>
-</form>
\ No newline at end of file
+</form>
diff --git a/app/views/login/activate_email.php b/app/views/login/activate_email.php
new file mode 100644
index 0000000000000000000000000000000000000000..366990a10087edddbc35f95a0c2d3d2b0dba80d2
--- /dev/null
+++ b/app/views/login/activate_email.php
@@ -0,0 +1,39 @@
+<?php if (isset($mail_explain)) : ?>
+    <form action="<?= URLHelper::getLink() ?>" method="post" class="default">
+        <fieldset>
+        <legend>
+            <?= _('Sie haben Ihre E-Mail-Adresse geändert.
+        Um diese frei zu schalten müssen Sie den Ihnen an Ihre neue Adresse zugeschickten Aktivierungs Schlüssel im unten stehenden Eingabefeld eintragen.') ?>
+        </legend>
+        <?= CSRFProtection::tokenTag() ?>
+            <label>
+                <?=_('Aktivierungs Schlüssel')?>
+                <input type="text" name="key">
+            </label>
+            <input name="uid" type="hidden" value="<?= htmlReady(Request::option('uid')) ?>">
+        </fieldset>
+
+        <footer><?= Studip\Button::createAccept() ?></footer>
+    </form>
+<?php endif; ?>
+<?php if (isset($reenter_mail)) : ?>
+    <form action="<?= URLHelper::getLink() ?>" method="post" class="default">
+        <fieldset>
+            <legend>
+                <?= _('Sollten Sie keine E-Mail erhalten haben, können Sie sich einen neuen Aktivierungsschlüssel zuschicken lassen. Geben Sie dazu Ihre gewünschte E-Mail-Adresse unten an.') ?>
+            </legend>
+            <?= CSRFProtection::tokenTag() ?>
+            <label>
+                <?= _('Email') ?>
+                <input type="email" name="email1" required>
+            </label>
+            <label>
+                <?= _('Wiederholung') ?>
+                <input type="email" name="email2" required>
+            </label>
+            <input name="uid" type="hidden" value="<?= htmlReady(Request::option('uid')) ?>">
+        </fieldset>
+
+        <footer><?= Studip\Button::createAccept() ?></footer>
+    </form>
+<?php endif; ?>
diff --git a/templates/loginform.php b/app/views/login/index.php
similarity index 59%
rename from templates/loginform.php
rename to app/views/login/index.php
index ec2289956f5573ddaad74e0fbdea851d95437d69..b1b3dfab5b6f3fd7226d30de7e00f54e2becd3d3 100644
--- a/templates/loginform.php
+++ b/app/views/login/index.php
@@ -2,7 +2,6 @@
 /**
  * @var array $loginerror
  * @var string $error_msg
- * @var LoginFaq[] $faq_entries
  */
 
 // Get background images (this should be resolved differently since mobile
@@ -26,7 +25,7 @@ if (!match_route('web_migrate.php')) {
 }
 $show_login = !(current(StudipAuthAbstract::getInstance()) instanceof StudipAuthSSO) && StudipAuthAbstract::isLoginEnabled();
 $show_hidden_login = !$show_login && StudipAuthAbstract::isLoginEnabled();
-$enable_faq = count($faq_entries) > 0;
+$enable_faq = Config::get()->LOGIN_FAQ_VISIBILITY && count($faq_entries) > 0;
 $enable_news = Config::get()->LOGIN_NEWS_VISIBILITY && count($news_entries) > 0;
 ?>
 <main id="content" class="loginpage">
@@ -34,13 +33,7 @@ $enable_news = Config::get()->LOGIN_NEWS_VISIBILITY && count($news_entries) > 0;
     <div id="background-mobile" style="background: url(<?= $bg_mobile ?>) no-repeat top left/cover;"></div>
 
     <div id="login-wrapper">
-        <?= Studip\VueApp::create('SystemNotificationManager')
-                ->withProps([
-                    'id'                    => 'system-notifications',
-                    'class'                 => 'system-notifications-login',
-                    'notifications'         => PageLayout::getMessages(MessageBox::class),
-                ])
-        ?>
+
         <div id="login-content-wrapper">
             <div id="loginbox">
                 <header>
@@ -54,28 +47,28 @@ $enable_news = Config::get()->LOGIN_NEWS_VISIBILITY && count($news_entries) > 0;
                 <? endif ?>
                 <nav class="<?= $show_hidden_login ? 'login-bottom' : '' ?>">
                     <? foreach (Navigation::getItem('/login') as $key => $nav): ?>
-                        <? if ($nav->isVisible()): ?>
-                            <? if ($key === 'standard_login' && $show_login)
-                                continue; ?>
-                        <? endif ?>
-                        <? $name_and_title = explode(' - ', $nav->getTitle()) ?>
-                        <? if (is_internal_url($url = $nav->getURL())): ?>
-                            <? SkipLinks::addLink($name_and_title[0], URLHelper::getLink($url, ['cancel_login' => 1])) ?>
-                            <a href="<?= URLHelper::getLink($url, ['cancel_login' => 1]) ?>"
-                                <?= arrayToHtmlAttributes($nav->getLinkAttributes()) ?>>
-                            <? else: ?>
-                                <a href="<?= htmlReady($url) ?>" target="_blank" rel="noopener noreferrer">
-                                <? endif ?>
-                                <p class="title"><?= htmlReady($name_and_title[0]) ?></p>
-                                <p class="description">
-                                    <?= htmlReady(!empty($name_and_title[1]) ? $name_and_title[1] : $nav->getDescription()) ?>
-                                </p>
+                    <? if ($nav->isVisible()): ?>
+                        <? if ($key === 'standard_login' && $show_login)
+                            continue; ?>
+                    <? endif ?>
+                    <? $name_and_title = explode(' - ', $nav->getTitle()) ?>
+                    <? if (is_internal_url($url = $nav->getURL())): ?>
+                    <? SkipLinks::addLink($name_and_title[0], URLHelper::getLink($url, ['cancel_login' => 1])) ?>
+                    <a href="<?= URLHelper::getLink($url, ['cancel_login' => 1]) ?>"
+                        <?= arrayToHtmlAttributes($nav->getLinkAttributes()) ?>>
+                        <? else: ?>
+                        <a href="<?= htmlReady($url) ?>" target="_blank" rel="noopener noreferrer">
+                            <? endif ?>
+                            <p class="title"><?= htmlReady($name_and_title[0]) ?></p>
+                            <p class="description">
+                                <?= htmlReady(!empty($name_and_title[1]) ? $name_and_title[1] : $nav->getDescription()) ?>
+                            </p>
                         </a>
                         <? endforeach ?>
 
                         <? if (Config::get()->ENABLE_SELF_REGISTRATION): ?>
                             <a href="<?= URLHelper::getLink('dispatch.php/registration', ['cancel_login' => 1]) ?>"
-                                title="<?= _('Registrieren, um das System erstmalig zu nutzen') ?>" class="link-registration">
+                               title="<?= _('Registrieren, um das System erstmalig zu nutzen') ?>" class="link-registration">
                                 <?= _('Kein Zugang? Jetzt registrieren') ?>
                             </a>
                         <? endif; ?>
@@ -137,49 +130,27 @@ $enable_news = Config::get()->LOGIN_NEWS_VISIBILITY && count($news_entries) > 0;
     });
     // -->
 
-    <? if ($enable_faq) : ?>
-        STUDIP.domReady(() => {
-            const loginBox = document.getElementById('loginbox');
-            const faqContent = document.getElementById('login-faq-content-wrapper');
-            const htmlTag = document.documentElement;
-
-            const adjustFaqHeight = () => {
-                if (!htmlTag.classList.contains('responsive-display')) {
-                    const loginBoxHeight = loginBox.offsetHeight;
-
-                    const maxAllowedHeight = loginBoxHeight - 100;
-
-                    faqContent.style.maxHeight = `${Math.max(maxAllowedHeight, 0)}px`;
-                }
-            };
-
-            adjustFaqHeight();
-
-            window.addEventListener('resize', adjustFaqHeight);
-        });
-    <? endif ?>
-
     <? if ($enable_faq && $enable_news): ?>
-        const faqButton = document.getElementById('show-faq');
-        const newsButton = document.getElementById('hide-faq');
-
-        faqButton.addEventListener('click', e => {
-            const faqBox = document.getElementById('login-faq-box');
-            const newsBox = document.getElementById('login-news-box');
-            newsBox.classList.add('hidden');
-            faqBox.classList.remove('hidden');
-            faqButton.classList.add('selected');
-            newsButton.classList.remove('selected');
-        });
+    const faqButton = document.getElementById('show-faq');
+    const newsButton = document.getElementById('hide-faq');
+
+    faqButton.addEventListener('click', e => {
+        const faqBox = document.getElementById('login-faq-box');
+        const newsBox = document.getElementById('login-news-box');
+        newsBox.classList.add('hidden');
+        faqBox.classList.remove('hidden');
+        faqButton.classList.add('selected');
+        newsButton.classList.remove('selected');
+    });
 
-        newsButton.addEventListener('click', e => {
-            const faqBox = document.getElementById('login-faq-box');
-            const newsBox = document.getElementById('login-news-box');
-            faqBox.classList.add('hidden');
-            newsBox.classList.remove('hidden');
-            newsButton.classList.add('selected');
-            faqButton.classList.remove('selected');
-        });
+    newsButton.addEventListener('click', e => {
+        const faqBox = document.getElementById('login-faq-box');
+        const newsBox = document.getElementById('login-news-box');
+        faqBox.classList.add('hidden');
+        newsBox.classList.remove('hidden');
+        newsButton.classList.add('selected');
+        faqButton.classList.remove('selected');
+    });
     <? endif ?>
 
 </script>
diff --git a/templates/privacy.php b/app/views/login/privacy_info.php
similarity index 100%
rename from templates/privacy.php
rename to app/views/login/privacy_info.php
diff --git a/app/views/my_courses/groups.php b/app/views/my_courses/groups.php
index 689f51b9d96200853ecbb2dcbc8d6aac6ae3a0c5..ee9b96498562842c22f267e8cab79102d904f809 100644
--- a/app/views/my_courses/groups.php
+++ b/app/views/my_courses/groups.php
@@ -47,7 +47,7 @@
             <? foreach ($group_members as $member): ?>
                 <tr>
                     <td>
-                        <a href="<?= URLHelper::getLink('seminar_main.php?auswahl=' . $member['seminar_id']) ?>">
+                        <a href="<?= URLHelper::getLink('dispatch.php/course/go?to=' . $member['seminar_id']) ?>">
                             <?= htmlReady(Config::get()->IMPORTANT_SEMNUMBER ? $my_sem[$member['seminar_id']]['veranstaltungsnummer'] : '') ?>
                             <?= htmlReady($my_sem[$member['seminar_id']]['name']) ?>
                         </a>
diff --git a/app/views/my_studygroups/_course.php b/app/views/my_studygroups/_course.php
index 87af045625ece3eafdd0d752ef0cea21199959af..37c081e14b2b3a7df1dbf547c06b46f9686a4084 100644
--- a/app/views/my_studygroups/_course.php
+++ b/app/views/my_studygroups/_course.php
@@ -5,7 +5,7 @@
             <?= StudygroupAvatar::getAvatar($group['seminar_id'])->getImageTag(Avatar::SMALL, ['title' => $group['name']]) ?>
         </td>
         <td style="text-align: left">
-            <a href="<?= URLHelper::getLink('seminar_main.php', ['auswahl' => $group['seminar_id']]) ?>"
+            <a href="<?= URLHelper::getLink('dispatch.php/course/go', ['to' => $group['seminar_id']]) ?>"
                 <?= $group['last_visitdate'] < $group['chdate'] ? 'style="color: red;"' : '' ?>>
                 <?= htmlReady($group['name']) ?>
             </a>
@@ -28,8 +28,8 @@
                     <? if (isset($nav) && $nav->isVisible(true)) : ?>
                         <li class="my-courses-navigation-item <? if ($nav->getImage()->signalsAttention()) echo 'my-courses-navigation-important'; ?>">
                             <a href="<?=
-                            URLHelper::getLink('seminar_main.php',
-                                ['auswahl'     => $group['seminar_id'],
+                            URLHelper::getLink('dispatch.php/course/go',
+                                ['to'     => $group['seminar_id'],
                                       'redirect_to' => $nav->getURL()]) ?>" <?= $nav->hasBadgeNumber() ? 'class="badge" data-badge-number="' . intval($nav->getBadgeNumber()) . '"' : '' ?>>
                                 <?= $nav->getImage()->asImg($nav->getLinkAttributes()) ?>
                             </a>
@@ -56,7 +56,7 @@
                 <? endif ?>
 
             <? elseif (!empty($group['binding'])) : ?>
-                <a href="<?= URLHelper::getLink('', ['auswahl' => $group['seminar_id'], 'cmd' => 'no_kill']) ?>">
+                <a href="<?= URLHelper::getLink('', ['to' => $group['seminar_id'], 'cmd' => 'no_kill']) ?>">
                     <?= Icon::create('door-leave', Icon::ROLE_INACTIVE)->asImg(['title' => _('Die Teilnahme ist bindend. Bitte wenden Sie sich an die Lehrenden.')]) ?>
                 </a>
             <?
diff --git a/app/views/public_courses/index.php b/app/views/public_courses/index.php
index 066ef7ae6ee3459b4db7871b041a738702c52b6e..8ecd0abfb4e0c597659754be4c961018f3420783 100644
--- a/app/views/public_courses/index.php
+++ b/app/views/public_courses/index.php
@@ -49,7 +49,7 @@
         <tr>
             <td class="<?= $values['Schreibzugriff'] ? 'gruppe6' : 'gruppe2' ?>">&nbsp;</td>
             <td>
-                <a href="<?= URLHelper::getLink('seminar_main.php?auswahl=' . $id) ?>">
+                <a href="<?= URLHelper::getLink('dispatch.php/course/go?to=' . $id) ?>">
                     <?= htmlReady($values['name']) ?>
                 </a>
             </td>
@@ -68,7 +68,7 @@
                   $badge = ' class="badge" data-badge-number="' . intval($navigation->getBadgeNumber())  . '"';
                 }
             ?>
-                <a href="<?= URLHelper::getLink('seminar_main.php?auswahl='. $id . '&redirect_to=' . str_replace('?', '&', $navigation->getURL())) ?>"<?= $badge ?>>
+                <a href="<?= URLHelper::getLink('dispatch.php/course/go?to='. $id . '&redirect_to=' . str_replace('?', '&', $navigation->getURL())) ?>"<?= $badge ?>>
                     <?= $navigation->getImage()->asImg($navigation->getLinkAttributes()) ?>
                 </a>
             <? else: ?>
diff --git a/app/views/questionnaire/context.php b/app/views/questionnaire/context.php
index 96190d963c145536a6b6bc2c596272a4e4f65529..36953a666cf935f4626696c76b5d5815a2814be7 100644
--- a/app/views/questionnaire/context.php
+++ b/app/views/questionnaire/context.php
@@ -38,7 +38,7 @@
                     <label>
                         <input type="checkbox" name="remove_sem[]" value="<?= htmlReady($assignment['range_id']) ?>" style="display: none;">
                         <span>
-                            <a href="<?= URLHelper::getLink("seminar_main.php", ['auswahl' => $course->getId()]) ?>">
+                            <a href="<?= URLHelper::getLink("dispatch.php/course/go", ['to' => $course->getId()]) ?>">
                                 <?= htmlReady((Config::get()->IMPORTANT_SEMNUMBER ? $course->veranstaltungsnummer." " : "").$course->name. ' ('.$course->semester_text.')') ?>
                             </a>
                             <?= Icon::create('trash')->asimg(['class' => 'text-bottom', 'title' => _('Zuweisung zur Veranstaltung aufheben.')]) ?>
@@ -60,7 +60,7 @@
                             <input type="checkbox" name="remove_statusgruppe[]" value="<?= htmlReady($assignment['range_id']) ?>" style="display: none;">
                             <? $statusgruppe = Statusgruppen::find($assignment['range_id']) ?>
                             <span>
-                                <a href="<?= URLHelper::getLink("seminar_main.php", ['auswahl' => $statusgruppe->getId()]) ?>">
+                                <a href="<?= URLHelper::getLink("dispatch.php/course/go", ['to' => $statusgruppe->getId()]) ?>">
                                     <?= htmlReady($statusgruppe->course['name'].": ".$statusgruppe->name) ?>
                                 </a>
                                 <?= Icon::create('trash')->asimg(['class' => 'text-bottom', 'title' => _('Zuweisung zur Veranstaltung aufheben.')]) ?>
diff --git a/templates/email-validation.php b/app/views/registration/email_validation.php
similarity index 100%
rename from templates/email-validation.php
rename to app/views/registration/email_validation.php
diff --git a/app/views/settings/notification.php b/app/views/settings/notification.php
index 54288197133c6d2c463a80f6ef4d1ef64ccaf8c3..1ee98883aa355833822b4c2c1c6e5a36a7baa354 100644
--- a/app/views/settings/notification.php
+++ b/app/views/settings/notification.php
@@ -69,7 +69,7 @@
                         <tr>
                             <td class="gruppe<?= $seminars[$member['seminar_id']]['gruppe'] ?>">&nbsp;</td>
                             <td>
-                                <a href="<?= URLHelper::getLink('seminar_main.php', ['auswahl' => $member['seminar_id']]) ?>">
+                                <a href="<?= URLHelper::getLink('dispatch.php/course/go', ['to' => $member['seminar_id']]) ?>">
                                     <?= Config::get()->IMPORTANT_SEMNUMBER ? htmlReady($seminars[$member['seminar_id']]['sem_nr']) : '' ?>
                                     <?= htmlReady(my_substr($seminars[$member['seminar_id']]['name'], 0, 70)) ?>
                                 </a>
diff --git a/app/views/studygroup/browse.php b/app/views/studygroup/browse.php
index 6d0ebc4a3de94d0c891489e2938041f1ad046951..6362d6af7ece0c8ec04f31467ebbe22c4e6654b4 100644
--- a/app/views/studygroup/browse.php
+++ b/app/views/studygroup/browse.php
@@ -60,7 +60,7 @@ $headers = [
                 </td>
                 <td class="studygroup-title">
                     <? if ($is_member): ?>
-                    <a href="<?= URLHelper::getlink("seminar_main.php?auswahl=" . $group['Seminar_id']) ?>">
+                    <a href="<?= URLHelper::getlink("dispatch.php/course/go?to=" . $group['Seminar_id']) ?>">
                         <? else: ?>
                         <a href="<?= URLHelper::getlink("dispatch.php/course/studygroup/details/" . $group['Seminar_id'], ['cid' => null]) ?>">
                             <? endif; ?>
diff --git a/cli/studip_cli_env.inc.php b/cli/studip_cli_env.inc.php
index a5ef8f46dd6a10e9978f82e362226446a8eb3ef1..b939e5d8ca2115ab29cd02603bfcf5348c6753d3 100644
--- a/cli/studip_cli_env.inc.php
+++ b/cli/studip_cli_env.inc.php
@@ -63,12 +63,6 @@ $CACHING_ENABLE        = false;
 // set base url for URLHelper class
 URLHelper::setBaseUrl($ABSOLUTE_URI_STUDIP);
 
-//cli scripts run always as faked (Stud.IP) root
-$auth = new Seminar_Auth();
-$auth->auth = ['uid' => 'cli',
-                    'uname' => 'cli',
-                    'perm' => 'root'];
-
 $faked_root = new User();
 $faked_root->user_id = 'cli';
 $faked_root->username = 'cli';
diff --git a/composer.json b/composer.json
index 9c6118b7ceba77cba81d503c8c49e7f2905894df..7aef546f92209ea029b67eb42571a7aa76f08921 100644
--- a/composer.json
+++ b/composer.json
@@ -21,6 +21,9 @@
                 "lib/exceptions/",
                 "lib/exceptions/course/"
             ],
+            "Studip\\Middleware\\": "lib/middleware/",
+            "Studip\\Session\\": "lib/session/",
+            "Studip\\Authentication\\": "lib/authentication/",
             "Trails\\": "lib/trails/",
             "": [
                 "lib/classes/",
@@ -50,7 +53,6 @@
                 "lib/models/resources/",
                 "lib/modules/",
                 "lib/navigation/",
-                "lib/phplib/",
                 "lib/plugins/core/",
                 "lib/plugins/db/",
                 "lib/plugins/engine/",
@@ -58,7 +60,6 @@
             ]
         },
         "classmap": [
-            "lib/phplib/email_validation.php",
             "lib/messaging.inc.php",
             "lib/plugins/core/StudIPPlugin.php",
             "app/controllers/module/mvv_controller.php",
diff --git a/config/config_defaults.inc.php b/config/config_defaults.inc.php
index 2f83fefc583fddad9474bc360b8cea3aed86a4dc..66b6900826dd4252731591791384bcbfeadb867a 100644
--- a/config/config_defaults.inc.php
+++ b/config/config_defaults.inc.php
@@ -70,6 +70,10 @@ $CACHING_ENABLE = $_ENV['STUDIP_CACHING_ENABLE'] ?? true;
 $CACHING_FILECACHE_PATH = $TMP_PATH . '/studip_cache';
 $CACHE_IS_SESSION_STORAGE = $_ENV['STUDIP_CACHE_IS_SESSION_STORAGE'] ?? false; //store session data in cache
 
+$SESSION_OPTIONS = [];
+$SESSION_OPTIONS['lifetime'] = 7200; // session lifetime in seconds
+
+
 /*Stud.IP modules
 ----------------------------------------------------------------
 enable or disable the Stud.IP internal modules, set and basic settings*/
diff --git a/lib/admin_search.inc.php b/lib/admin_search.inc.php
index a6b2260d365b607128fdce96a1d26d1c9ba66d9b..93a45385e156f05af1e091713a63ec33f8b1f3d6 100644
--- a/lib/admin_search.inc.php
+++ b/lib/admin_search.inc.php
@@ -18,6 +18,6 @@ if (!Institute::findCurrent()) {
     $template->institutes = Institute::getMyInstitutes($GLOBALS['user']->id);
     echo $template->render();
 
-    page_close();
+    sess()->save();
     die;
 }
diff --git a/lib/authentication/Manager.php b/lib/authentication/Manager.php
new file mode 100644
index 0000000000000000000000000000000000000000..64419cac723d36a5fe61955a29a181178d29d617
--- /dev/null
+++ b/lib/authentication/Manager.php
@@ -0,0 +1,152 @@
+<?php
+/**
+ * Authentication Manager
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * @author      André Noack <noack@data-quest.de>
+ */
+namespace Studip\Authentication;
+
+class Manager
+{
+    private $auth = [];
+    public function __construct(private $nobody = false)
+    {
+    }
+
+    /**
+     * @return false|mixed
+     */
+    public function getNobody(): mixed
+    {
+        return $this->nobody;
+    }
+
+    public function setNobody($allow_nobody = false): void
+    {
+        $this->nobody = $allow_nobody;
+    }
+
+
+    public function start()
+    {
+        $this->auth =& $_SESSION['auth'];
+
+        if (!$this->isAuthenticated()) {
+            $user = null;
+            if (($provider = \Request::option('sso'))) {
+                \Metrics::increment('core.sso_login.attempted');
+                // then do login
+                $authplugin = \StudipAuthAbstract::GetInstance($provider);
+                if ($authplugin) {
+                    $authplugin->authenticateUser('', '');
+                    if ($authplugin->getUser()) {
+                        $user = $authplugin->getStudipUser($authplugin->getUser());
+                        $exp_d = \UserConfig::get($user->id)->EXPIRATION_DATE;
+                        if ($exp_d > 0 && $exp_d < time()) {
+                            throw new \AccessDeniedException(
+                                _('Dieses Benutzerkonto ist abgelaufen. Wenden Sie sich bitte an die Administration.')
+                            );
+                        }
+                        if ($user->locked == 1) {
+                            throw new \AccessDeniedException(
+                                _('Dieser Benutzer ist gesperrt! Wenden Sie sich bitte an die Administration.')
+                            );
+                        }
+                        \Metrics::increment('core.sso_login.succeeded');
+                        sess()->regenerateId(['auth', '_language', 'phpCAS', 'contrast']);
+                    }
+                }
+            }
+            if (!$user) {
+                if ($this->nobody && !\Request::get('again')) {
+                    $this->setAuthenticatedUser(\User::build(['user_id' => 'nobody', 'perms' => null]));
+                }
+                if (!match_route('dispatch.php/login')) {
+                    return false;
+                }
+            }
+        } else {
+            if ($this->auth['uid'] !== 'nobody' && \Request::get('again') && !match_route('dispatch.php/login')) {
+                return false;
+            }
+            $this->setAuthenticatedUser($this->auth['uid'] !== 'nobody' ? \User::find($this->auth['uid']) : \User::build(['user_id' => 'nobody', 'perms' => null]));
+        }
+        return true;
+    }
+
+    public function isAuthenticated()
+    {
+        if (!is_array($this->auth)) {
+            $this->auth = [];
+        }
+        if (isset($this->auth['uid']) && $this->auth['uid'] === 'nobody' && (!$this->nobody || \Request::option('again'))) {
+            $this->auth['uid'] = null;
+        }
+        $cfg = \Config::GetInstance();
+        //check if the user got kicked meanwhile, or if user is locked out
+        if (!empty($this->auth['uid']) && !in_array($this->auth['uid'], ['nobody'])) {
+            $user = null;
+            if (isset($GLOBALS['user']) && $GLOBALS['user']->id == $this->auth['uid']) {
+                $user = $GLOBALS['user'];
+            } else {
+                $user = \User::find($this->auth['uid']);
+            }
+            $exp_d = $user->username ? \UserConfig::get($user->id)->EXPIRATION_DATE : 0;
+            if (!$user->username || $user->locked || ($exp_d > 0 && $exp_d < time())) {
+                $this->auth = [];
+            }
+        } elseif ($cfg->getValue('MAINTENANCE_MODE_ENABLE') && \Request::username('loginname')) {
+            $user = \User::findByUsername(\Request::username('loginname'));
+        }
+        if ($cfg->getValue('MAINTENANCE_MODE_ENABLE') && $user->perms != 'root') {
+            $this->auth = [];
+            throw new \AccessDeniedException(_("Das System befindet sich im Wartungsmodus. Zur Zeit ist kein Zugriff möglich."));
+        }
+        return @$this->auth['uid'] ? : false;
+    }
+
+    public function setAuthenticatedUser(\User $user): void
+    {
+        $this->auth['uid'] = $user->id;
+        $GLOBALS['user'] = new \Seminar_User($user);
+        $GLOBALS['perm'] = new \Seminar_Perm();
+    }
+
+    public function sendValidationMail(\User $user = null): void
+    {
+        if (is_null($user)) {
+            $user = \User::findCurrent();
+        }
+
+        // template-variables for the include partial
+        $Zeit     = date('H:i:s, d.m.Y', $user->mkdate);
+        $username = $user->username;
+        $Vorname  = $user->vorname;
+        $Nachname = $user->nachname;
+        $Email    = $user->email;
+
+        // (re-)send the confirmation mail
+        $to     = $user->email;
+        $token  = \Token::create(7 * 24 * 60 * 60, $user->id); // Link is valid for 1 week
+        $url    = $GLOBALS['ABSOLUTE_URI_STUDIP'] . 'dispatch.php/registration/email_validation?secret=' . $token;
+        $mail   = new \StudipMail();
+        $abuse  = $mail->getReplyToEmail();
+
+        $lang_path = getUserLanguagePath($user->id);
+
+        // include language-specific subject and mailbody
+        // TODO: This should be refactored so that the included file returns an array
+        include "locale/{$lang_path}/LC_MAILS/register_mail.inc.php"; // Defines $subject and $mailbody
+
+        // send the mail
+        $mail->setSubject($subject ?? '')
+            ->addRecipient($to)
+            ->setBodyText($mailbody ?? '')
+            ->send();
+    }
+}
diff --git a/lib/bootstrap-definitions.php b/lib/bootstrap-definitions.php
index 7d4f93bd613c5202b223fdc1992fa95e320cc428..06380a830d5e3559aefdfbcf757a029d7ad60679 100644
--- a/lib/bootstrap-definitions.php
+++ b/lib/bootstrap-definitions.php
@@ -71,6 +71,19 @@ return [
     }),
     PluginManager::class => DI\factory([PluginManager::class, 'getInstance']),
 
+    Studip\Session\Manager::class =>  DI\factory(function () {
+        if (Config::get()->CACHING_ENABLE && $GLOBALS['CACHE_IS_SESSION_STORAGE']) {
+            $session_handler = new Studip\Session\CacheSessionHandler($GLOBALS['SESSION_OPTIONS']['lifetime'] ?? null);
+        } else {
+            $session_handler = new Studip\Session\DbSessionHandler();
+        }
+        $GLOBALS['SESSION_OPTIONS']['path'] = $GLOBALS['CANONICAL_RELATIVE_PATH_STUDIP'];
+        $GLOBALS['SESSION_OPTIONS']['secure'] = Request::protocol() == 'https';
+        return new Studip\Session\Manager($session_handler, $GLOBALS['SESSION_OPTIONS']);
+
+    }),
+    Studip\Authentication\Manager::class => DI\create(),
+
     // PSR-17 HTTP Factories
     \Psr\Http\Message\RequestFactoryInterface::class => DI\get(Psr17Factory::class),
     \Psr\Http\Message\ResponseFactoryInterface::class => DI\get(Psr17Factory::class),
diff --git a/lib/bootstrap.php b/lib/bootstrap.php
index 2f8448ddf7a9382839141db652bf7a460ec5d78a..2d4e2e83da8b0a1c389d28a472511784d75eb029 100644
--- a/lib/bootstrap.php
+++ b/lib/bootstrap.php
@@ -115,7 +115,6 @@ if ($GLOBALS['ASSETS_URL'][0] === '/') {
 require $GLOBALS['STUDIP_BASE_PATH'] . '/config/config.inc.php';
 
 require 'lib/helpers.php';
-require 'lib/phplib/page_open.php';
 require_once 'lib/functions.php';
 require_once 'lib/language.inc.php';
 require_once 'lib/visual.inc.php';
@@ -204,21 +203,6 @@ register_shutdown_function(function ($timer) {
 
 //include 'tools/debug/StudipDebugPDO.class.php';
 
-/**
- * @deprecated
- */
-class DB_Seminar extends DB_Sql
-{
-    public function __construct($query = false)
-    {
-        $this->Host = $GLOBALS['DB_STUDIP_HOST'];
-        $this->Database = $GLOBALS['DB_STUDIP_DATABASE'];
-        $this->User = $GLOBALS['DB_STUDIP_USER'];
-        $this->Password = $GLOBALS['DB_STUDIP_PASSWORD'];
-        parent::__construct($query);
-    }
-}
-
 if (Config::get()->CALENDAR_ENABLE) {
     require_once 'lib/calendar_functions.inc.php';
 }
diff --git a/lib/classes/CSRFProtection.php b/lib/classes/CSRFProtection.php
index 05532d462e69f61a1521fc0cf740a353f2ef704c..036aa932884ccf7dfb2accaccf5f2f2fb6e552dc 100644
--- a/lib/classes/CSRFProtection.php
+++ b/lib/classes/CSRFProtection.php
@@ -66,7 +66,7 @@ class CSRFProtection
         if (!isset(self::$storage)) {
             // w/o a session, throw an exception since we cannot use it
             if (session_id() === '') {
-                throw new SessionRequiredException();
+               throw new SessionRequiredException();
             }
 
             self::$storage =& $_SESSION;
@@ -180,4 +180,38 @@ class CSRFProtection
             arrayToHtmlAttributes($attributes)
         );
     }
+
+    /**
+     * returns a random string token for XSRF prevention
+     * the string is stored in the session
+     *
+     * @static
+     * @return string
+     */
+    public static function sessionticket()
+    {
+        $storage = &self::getStorage();
+
+        if (empty($storage['studipticket'])) {
+            $storage['studipticket'] = md5(uniqid('studipticket', 1));
+        }
+        return $storage['studipticket'];
+    }
+
+    /**
+     * checks the given string token against the one stored
+     * in the session
+     *
+     * @static
+     * @param string $studipticket
+     * @return bool
+     */
+    public static function verifySessionticket($studipticket)
+    {
+        $storage = &self::getStorage();
+
+        $check = (isset($storage['studipticket']) && $storage['studipticket'] === $studipticket);
+        $storage['studipticket'] = null;
+        return $check;
+    }
 }
diff --git a/lib/classes/Context.php b/lib/classes/Context.php
index 389a8cc276170514b74dc191798b71ac50f3d9e3..e7b2e0146abb5440895f27525a403e6173ac7c48 100644
--- a/lib/classes/Context.php
+++ b/lib/classes/Context.php
@@ -246,7 +246,7 @@ class Context
                 );
                 if (!$count) {
                     header('Location: ' . URLHelper::getURL('dispatch.php/course/members/additional_input'));
-                    page_close();
+                    sess()->save();
                     die;
                 }
             }
diff --git a/lib/classes/JsonApi/Middlewares/Auth/SessionStrategy.php b/lib/classes/JsonApi/Middlewares/Auth/SessionStrategy.php
index 26c0bc22fe621d33a3c596f6cfa43be284a04359..a0f14f8645babe578ba6309a2aec2a94707fb958 100644
--- a/lib/classes/JsonApi/Middlewares/Auth/SessionStrategy.php
+++ b/lib/classes/JsonApi/Middlewares/Auth/SessionStrategy.php
@@ -24,7 +24,7 @@ class SessionStrategy implements Strategy
         }
 
         $isAuthenticated =
-            isset($GLOBALS['auth']) && $GLOBALS['auth']->is_authenticated() && 'nobody' !== $GLOBALS['user']->id;
+            isset($GLOBALS['user']) && 'nobody' !== $GLOBALS['user']->id;
 
         if ($isAuthenticated) {
             $this->user = $GLOBALS['user']->getAuthenticatedUser();
diff --git a/lib/classes/JsonApi/Middlewares/Authentication.php b/lib/classes/JsonApi/Middlewares/Authentication.php
index 19da101535eedfcc33463bfc204782333c0306d0..ed534ad47baa9a8e561eb031aed89c08dc0d8399 100644
--- a/lib/classes/JsonApi/Middlewares/Authentication.php
+++ b/lib/classes/JsonApi/Middlewares/Authentication.php
@@ -80,17 +80,8 @@ class Authentication
             || 'nobody' === $GLOBALS['user']->id
         ) {
             $GLOBALS['user'] = new \Seminar_User($user);
-            $GLOBALS['auth'] = new \Seminar_Auth();
-            $GLOBALS['auth']->auth = [
-                'uid' => $user->id,
-                'uname' => $user->username,
-                'perm' => $user->perms,
-            ];
-            $GLOBALS['perm'] = new \Seminar_Perm();
             $GLOBALS['MAIL_VALIDATE_BOX'] = false;
-            if (isset($GLOBALS['sess'])) {
-                $GLOBALS['sess']->delete();
-            }
+            sess()->destroy();
             setTempLanguage($user->id);
         }
 
diff --git a/lib/classes/Markup.php b/lib/classes/Markup.php
index 404bcb9418bd102f19a625b2e17f008864e66152..08a25ba8975472107c282bc65ee40fc6995858a2 100644
--- a/lib/classes/Markup.php
+++ b/lib/classes/Markup.php
@@ -584,8 +584,9 @@ function getMediaUrl($url) {
 
     // handle external media links
     $external_media = \Config::get()->LOAD_EXTERNAL_MEDIA;
+
     if ($external_media === 'proxy' &&
-        \Seminar_Session::is_current_session_authenticated()
+        sess()->isCurrentSessionAuthenticated()
     ) {
         // media proxy must be accessed by an internal link
         return encodeMediaProxyUrl($url);
diff --git a/lib/classes/ModulesNotification.php b/lib/classes/ModulesNotification.php
index f16ea1201cb09be9fcdaf0128a59a1b5dcd84007..96aa660e82a6eaaf7c2471604877308edd751e86 100644
--- a/lib/classes/ModulesNotification.php
+++ b/lib/classes/ModulesNotification.php
@@ -168,7 +168,7 @@ class ModulesNotification
         $base_url = URLHelper::setBaseURL('');
         URLHelper::setBaseURl($base_url);
         if ($nav instanceof Navigation && $nav->isVisible(true)) {
-            $url = 'seminar_main.php?again=yes&auswahl=' . $seminar_id . '&redirect_to=' . strtr($nav->getURL(), '?', '&');
+            $url = 'dispatch.php/course/go?again=yes&to=' . $seminar_id . '&redirect_to=' . strtr($nav->getURL(), '?', '&');
             $icon = $nav->getImage();
             $text = $nav->getTitle();
             if (!$text) {
diff --git a/lib/classes/ObjectdisplayHelper.php b/lib/classes/ObjectdisplayHelper.php
index 145647ef36d7ca5060ab1cbeb0d8ec6381480f2f..b11f90d6eb155d9e16f8d7fd4909e7b7ac93a2d6 100644
--- a/lib/classes/ObjectdisplayHelper.php
+++ b/lib/classes/ObjectdisplayHelper.php
@@ -63,7 +63,7 @@ class ObjectdisplayHelper {
             ],
             'Course' => [
                 'link' => function($obj) {
-            return URLHelper::getLink('seminar_main.php', ['auswahl' => $obj->id]);
+            return URLHelper::getLink('dispatch.php/course/go', ['to' => $obj->id]);
         },
                 'name' => function($obj) {
             return htmlReady($obj->name);
diff --git a/lib/classes/PluginController.php b/lib/classes/PluginController.php
index 867d6697e12c4067b630ef416f912a58893e9627..67fd7f4c121d483b9c60cc15c03ad25b68b06af5 100644
--- a/lib/classes/PluginController.php
+++ b/lib/classes/PluginController.php
@@ -15,6 +15,7 @@ class PluginController extends StudipController
 {
     public function __construct(PluginDispatcher $dispatcher)
     {
+        $this->with_session = false; //session for plugin is always initialized in plugins.php
         parent::__construct($dispatcher);
 
         $this->plugin = $dispatcher->current_plugin;
diff --git a/lib/phplib/Seminar_Perm.php b/lib/classes/Seminar_Perm.php
similarity index 100%
rename from lib/phplib/Seminar_Perm.php
rename to lib/classes/Seminar_Perm.php
diff --git a/lib/phplib/Seminar_User.php b/lib/classes/Seminar_User.php
similarity index 100%
rename from lib/phplib/Seminar_User.php
rename to lib/classes/Seminar_User.php
diff --git a/lib/classes/StudipController.php b/lib/classes/StudipController.php
index d103621b3e5a2bde805ed8c164a1a51944caeecf..33b7e38de3328e3041899e08cfd793cca9bf42b0 100644
--- a/lib/classes/StudipController.php
+++ b/lib/classes/StudipController.php
@@ -25,6 +25,21 @@ abstract class StudipController extends Trails\Controller
     protected $allow_nobody = true; //should 'nobody' allowed for this controller or redirected to login?
     protected $_autobind = false;
 
+    public function __construct(\Trails\Dispatcher $dispatcher)
+    {
+        parent::__construct($dispatcher);
+        if ($this->with_session) {
+            $slimapp = app()->get(Slim\App::class);
+            if ($slimapp) {
+                $slimapp->add(Studip\Middleware\SeminarOpenMiddleware::class);
+                $slimapp->add(Studip\Middleware\AuthenticationMiddleware::class);
+                auth()->setNobody($this->allow_nobody);
+                $slimapp->add(Studip\Middleware\SessionMiddleware::class);
+            }
+        }
+    }
+
+
     /**
      * @return false|void
      */
@@ -37,22 +52,8 @@ abstract class StudipController extends Trails\Controller
         parent::before_filter($action, $args);
 
         if ($this->with_session) {
-            # open session
-            page_open([
-                'sess' => 'Seminar_Session',
-                'auth' => $this->allow_nobody ? 'Seminar_Default_Auth' : 'Seminar_Auth',
-                'perm' => 'Seminar_Perm',
-                'user' => 'Seminar_User'
-            ]);
-
-            // show login-screen, if authentication is "nobody"
-            $GLOBALS['auth']->login_if((Request::get('again') || !$this->allow_nobody) && $GLOBALS['user']->id == 'nobody');
-
             // Setup flash instance
             $this->flash = Trails\Flash::instance();
-
-            // set up user session
-            include 'lib/seminar_open.php';
         }
 
         // Set generic attribute that indicates whether the request was sent
@@ -85,13 +86,11 @@ abstract class StudipController extends Trails\Controller
     }
 
     /**
-     * Extended method to inject extended response object.
+     *  method to inject extended response object.
      */
-    public function erase_response()
+    public function injectResponse(Psr\Http\Message\ResponseInterface $response)
     {
-        parent::erase_response();
-
-        $this->response = new StudipResponse();
+        $this->response = new StudipResponse($response);
     }
 
     /**
@@ -140,9 +139,6 @@ abstract class StudipController extends Trails\Controller
             $this->response->add_header('X-WikiLink', format_help_url(PageLayout::getHelpKeyword()));
         }
 
-        if ($this->with_session) {
-            page_close();
-        }
     }
 
     /**
@@ -644,7 +640,7 @@ abstract class StudipController extends Trails\Controller
         // If the relayed action should perform a redirect, do so
         if (isset($response->headers['Location'])) {
             header("Location: {$response->headers['Location']}");
-            page_close();
+            sess()->save();
             die;
         }
 
diff --git a/lib/classes/StudipCoreFormat.php b/lib/classes/StudipCoreFormat.php
index bc3ded3096b9f767c1bb9530f13430acee6c7f93..fa3817b8e7f064eceb848c96a96b805c317d2f96 100644
--- a/lib/classes/StudipCoreFormat.php
+++ b/lib/classes/StudipCoreFormat.php
@@ -595,7 +595,7 @@ class StudipCoreFormat extends TextFormat
         }
 
         //Mediaproxy?
-        if (!$intern && $LOAD_EXTERNAL_MEDIA === "proxy" && Seminar_Session::is_current_session_authenticated()) {
+        if (!$intern && $LOAD_EXTERNAL_MEDIA === 'proxy' && sess()->isCurrentSessionAuthenticated()) {
             $media_url = $GLOBALS['ABSOLUTE_URI_STUDIP'] . 'dispatch.php/media_proxy?url=' . urlencode(decodeHTML(idna_link($url)));
         } else {
             $media_url = idna_link($url);
diff --git a/lib/classes/StudipDispatcher.php b/lib/classes/StudipDispatcher.php
index a41635a9034e30ef9a00c07b4e07712046d525ce..ea66c1c718e1d18ef26d77dc25879d319194bb3c 100644
--- a/lib/classes/StudipDispatcher.php
+++ b/lib/classes/StudipDispatcher.php
@@ -1,6 +1,8 @@
 <?php
 
 use Psr\Container\ContainerInterface;
+use Slim\Routing\RouteContext;
+use Trails\Exception;
 
 /**
  * StudipDispatcher.php - create the default Trails dispatcher
@@ -87,4 +89,16 @@ class StudipDispatcher extends Trails\Dispatcher
 
         return $this->container->make($class, ['dispatcher' => $this]);
     }
+
+    public function getRouteCallable($uri)
+    {
+        $uri = $this->clean_request_uri((string) $uri);
+        [$controller_path, $unconsumed] = '' === $uri ? $this->default_route() : $this->parse($uri);
+        $controller = $this->load_controller($controller_path);
+        return function ($request, $response, array $args) use ($controller, $unconsumed) {
+            $controller->injectResponse($response);
+            $response = $controller->perform($unconsumed);
+            return $response->getPsrResponse();
+        } ;
+    }
 }
diff --git a/lib/classes/StudipResponse.php b/lib/classes/StudipResponse.php
index a9f1a4c36997643edafe7ba358079fcc4946efb7..3031d7c72f7ca3709538169cb30b46fe883b92c4 100644
--- a/lib/classes/StudipResponse.php
+++ b/lib/classes/StudipResponse.php
@@ -1,55 +1,103 @@
 <?php
 class StudipResponse extends Trails\Response
 {
+
+    /**
+     * Constructor.
+     * @return void
+     */
+    public function __construct(protected Psr\Http\Message\ResponseInterface $psr_response)
+    {
+        parent::__construct();
+    }
+
+    /**
+     * @param $name
+     * @param $value
+     * @return mixed
+     */
+    public function __call($name, $value)
+    {
+        return $this->psr_response->$name($value);
+    }
+
+    /**
+     * @return \Psr\Http\Message\ResponseInterface
+     */
+    public function getPsrResponse(): \Psr\Http\Message\ResponseInterface
+    {
+        return $this->psr_response;
+    }
+
     /**
-     * Outputs this response to the client using "echo" and "header".
      *
-     * This extension allows the body to be a callable and handles generators
-     * by outputting the chunks yielded by the generator.
+     * @return void
      */
     public function output()
     {
-        if (isset($this->status)) {
-            $this->send_header(
-                "{$_SERVER['SERVER_PROTOCOL']} {$this->status} {$this->reason}",
-                true,
-                $this->status
-            );
-        }
+        $status = sprintf('HTTP/%s %s %s'
+            , $this->psr_response->getProtocolVersion()
+            , $this->psr_response->getStatusCode()
+            , $this->psr_response->getReasonPhrase()
+        );
+        header($status);
 
-        // Send headers
-        foreach ($this->headers as $k => $v) {
-            $this->send_header("{$k}: {$v}");
+        foreach ($this->psr_response->getHeaders() as $name => $values) {
+            $responseHeader = sprintf('%s: %s'
+                , $name
+                , $this->psr_response->getHeaderLine($name)
+            );
+            header($responseHeader, false);
         }
+        echo $this->psr_response->getBody();
+    }
 
-        // Determine output
-        if (is_callable($this->body)) {
-            $output = call_user_func($this->body);
+    /**
+     * Sets the body of the response.
+     *
+     * @param string|Psr\Http\Message\StreamInterface $body the body
+     *
+     * @return static   this response object. Useful for cascading method calls.
+     */
+    public function set_body($body)
+    {
+        if ($body instanceof Psr\Http\Message\StreamInterface) {
+            $this->psr_response = $this->psr_response->withBody($body);
         } else {
-            $output = $this->body;
+            $this->psr_response->getBody()->write($body);
         }
+        return $this;
+    }
 
-        if ($output instanceof Generator) {
-            // Clear output buffer
-            while (ob_get_level()) {
-                ob_end_clean();
-            }
-
-            // Ensure generator will run to the end
-            $abort = ignore_user_abort(true);
-
-            // Output chunks yielded by generator
-            foreach ($output as $chunk) {
-                if (!connection_aborted()) {
-                    echo $chunk;
-                    flush();
-                }
-            }
-
-            // Reset user abort to previous state
-            ignore_user_abort($abort);
-        } else {
-            echo $output;
-        }
+
+    /**
+     * Sets the status code and an optional custom reason. If none is given, the
+     * standard reason phrase as of RFC 2616 is used.
+     *
+     * @param integer  the status code
+     * @param string   the custom reason, defaulting to the one given in RFC 2616
+     *
+     * @return static    this response object. Useful for cascading method calls.
+     */
+    public function set_status($status, $reason = null)
+    {
+        $this->psr_response = $this->psr_response->withStatus($status, $reason ?? self::get_reason($status));
+        return $this;
+    }
+
+
+
+    /**
+     * Adds an additional header to the response.
+     *
+     * @param string  the left hand key part
+     * @param string  the right hand value part
+     *
+     * @return static   this response object. Useful for cascading method calls.
+     */
+    function add_header($key, $value)
+    {
+        $this->psr_response = $this->psr_response->withHeader($key, $value);
+        return $this;
     }
 }
diff --git a/lib/classes/TwoFactorAuth.php b/lib/classes/TwoFactorAuth.php
index b67f4befa2684458b70c8379669716d75fc4dc63..73e9f5ec015ff3aa63ca366ec624497932d2060f 100644
--- a/lib/classes/TwoFactorAuth.php
+++ b/lib/classes/TwoFactorAuth.php
@@ -240,7 +240,7 @@ final class TwoFactorAuth
             ],
             'layouts/base.php'
         );
-        page_close();
+        sess()->save();
         die;
     }
 
diff --git a/lib/classes/UserManagement.php b/lib/classes/UserManagement.php
index f4c47108e55ed3bf259494a54bb37a3423917950..961a872b00b3058c222276f3161a0b917cfc86df 100644
--- a/lib/classes/UserManagement.php
+++ b/lib/classes/UserManagement.php
@@ -1280,7 +1280,7 @@ class UserManagement
                       WHERE a.user_id = ? AND a.inst_perms = 'admin'";
             $statement = DBManager::get()->prepare($query);
             $statement->execute([
-                $GLOBALS['auth']->auth['uid'],
+                $GLOBALS['user']->id,
                 $this->user_data['auth_user_md5.user_id'],
             ]);
             $ok = $statement->fetchColumn();
diff --git a/lib/classes/auth_plugins/StudipAuthOAuth2.php b/lib/classes/auth_plugins/StudipAuthOAuth2.php
index 2ed2a0f58bad48db8f315d9fad0d2fa574401c61..a67006777c2ee31c656f7a8048ba85c50072c302 100644
--- a/lib/classes/auth_plugins/StudipAuthOAuth2.php
+++ b/lib/classes/auth_plugins/StudipAuthOAuth2.php
@@ -75,7 +75,7 @@ final class StudipAuthOAuth2 extends StudipAuthSSO
                 'redirect' => Request::url(),
             ];
 
-            page_close();
+            sess()->save();
             header('Location: ' . $authorizationUrl);
             die;
         } elseif (
diff --git a/lib/phplib/email_validation.php b/lib/classes/email_validation_class.php
similarity index 100%
rename from lib/phplib/email_validation.php
rename to lib/classes/email_validation_class.php
diff --git a/lib/classes/forms/Form.php b/lib/classes/forms/Form.php
index 576ea43702a5214ca7d1014ca3f5451c5dc9dad0..e4d49379f5d11fbb05c6a0c4c3a50411938adcfa 100644
--- a/lib/classes/forms/Form.php
+++ b/lib/classes/forms/Form.php
@@ -300,7 +300,7 @@ class Form extends Part
                 if ($this->success_message) {
                     \PageLayout::postSuccess($this->success_message);
                 }
-                page_close();
+                sess()->save();
                 //This indicates that the form has been stored successfully.
                 echo "STUDIPFORM_STORE_SUCCESS";
                 die();
@@ -330,7 +330,7 @@ class Form extends Part
             }
             header('Content-Type: application/json');
             echo json_encode($output);
-            page_close();
+            sess()->save();
             die();
         }
         return $this;
diff --git a/lib/cronjobs/session_gc.php b/lib/cronjobs/session_gc.php
index 54763ee0c4fe036f6b50d74675ec0a2e0dd3ecae..25d5f6c6f88b923030f5e3b417f2844ebd3a2155 100644
--- a/lib/cronjobs/session_gc.php
+++ b/lib/cronjobs/session_gc.php
@@ -22,8 +22,6 @@ class SessionGcJob extends CronJob
 
     public function execute($last_result, $parameters = [])
     {
-        $sess = new Seminar_Session();
-        $sess->set_container();
-        return $sess->gc();
+        return sess()->doGarbageCollect();
     }
 }
diff --git a/lib/functions.php b/lib/functions.php
index 39fb96655df9f5a4e60e9f9e5b538198744b07e1..29264fbf0ba705fdb34536b098a13ac1165fd1ac 100644
--- a/lib/functions.php
+++ b/lib/functions.php
@@ -345,10 +345,10 @@ function get_fullname($user_id = "", $format = "full" , $htmlready = false)
 function get_fullname_from_uname($uname = "", $format = "full", $htmlready = false)
 {
     static $cache;
-    global $auth, $_fullname_sql;
+    global $user, $_fullname_sql;
 
     if (!$uname) {
-        $uname = $auth->auth['uname'];
+        $uname = $user->username;
     }
 
     $hash = md5($uname . $format);
@@ -379,10 +379,10 @@ function get_fullname_from_uname($uname = "", $format = "full", $htmlready = fal
 function get_username($user_id = "")
 {
     static $cache = [];
-    global $auth;
+    global $user;
 
-    if (!$user_id || $user_id === $auth->auth['uid']) {
-        return $auth->auth['uname'] ?? '';
+    if (!$user_id || $user_id === $user->id) {
+        return $user->username ?? '';
     }
 
     if (!isset($cache[$user_id])) {
@@ -400,7 +400,7 @@ function get_username($user_id = "")
  *
  * uses global $online array if user is online
  *
- * @global object $auth
+ * @global object user
  * @staticvar array $cache
  *
  * @param string $username if omitted, current user_id will be returned
@@ -410,10 +410,10 @@ function get_username($user_id = "")
 function get_userid($username = "")
 {
     static $cache = [];
-    global $auth;
+    global $user;
 
-    if (!$username || $username == $auth->auth['uname']) {
-        return $auth->auth['uid'];
+    if (!$username || $username == $user->username) {
+        return $user->id;
     }
 
     // Read id from database if no cached version is available
@@ -513,7 +513,6 @@ function StringToFloat($str)
  * passed archived seminar
  *
  * @global array $perm
- * @global object $auth
  * @staticvar array $archiv_perms
  *
  * @param string $seminar_id the seminar in the archive
@@ -641,7 +640,7 @@ function get_users_online_count($active_time = 10)
  */
 function get_ticket()
 {
-    return Seminar_Session::get_ticket();
+    return CSRFProtection::sessionticket();
 }
 
 /**
@@ -653,7 +652,7 @@ function get_ticket()
  */
 function check_ticket($studipticket)
 {
-    return Seminar_Session::check_ticket($studipticket);
+    return CSRFProtection::verifySessionticket($studipticket);
 }
 
 /**
@@ -1124,7 +1123,7 @@ function studip_default_exception_handler($exception) {
         $status = 403;
         $template = 'check_object_exception';
     } elseif ($exception instanceof LoginException) {
-        $GLOBALS['auth']->login_if(true);
+
     } else {
         if ($exception instanceOf Trails\Exception) {
             $status = $exception->getCode();
diff --git a/lib/helpers.php b/lib/helpers.php
index bffed4a9acb47aff2c794f275c1dc870b506fef2..9bfedb1b17366628f397807beb6f14683a66e4bb 100644
--- a/lib/helpers.php
+++ b/lib/helpers.php
@@ -31,3 +31,19 @@ function app($entryId = null, $parameters = [])
 
     return $container->make($entryId, $parameters);
 }
+
+/**
+ * @return \Studip\Session\Manager
+ */
+function sess() : Studip\Session\Manager
+{
+    return app()->get(Studip\Session\Manager::class);
+}
+
+/**
+ * @return \Studip\Authentication\Manager
+ */
+function auth() : Studip\Authentication\Manager
+{
+    return app()->get(Studip\Authentication\Manager::class);
+}
diff --git a/lib/middleware/AuthenticationMiddleware.php b/lib/middleware/AuthenticationMiddleware.php
new file mode 100644
index 0000000000000000000000000000000000000000..739ce894261ac9838e5daff5cfd9577e3ade7479
--- /dev/null
+++ b/lib/middleware/AuthenticationMiddleware.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * PSR 15 middleware Stud.IP Authentication
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * @author      André Noack <noack@data-quest.de>
+ * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category    Stud.IP
+ * @since       6.0
+ */
+namespace Studip\Middleware;
+
+use Psr\Http\Message\ResponseFactoryInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use Studip\Authentication\Manager;
+
+final class AuthenticationMiddleware implements MiddlewareInterface
+{
+    public function __construct(private Manager $auth_manager, private ResponseFactoryInterface $response_factory)
+    {
+    }
+
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        if ($this->auth_manager->start()) {
+            return $handler->handle($request);
+        } else {
+            $_SESSION['redirect_after_login'] = \Request::url();
+            $response = $this->response_factory->createResponse(302);
+            return $response->withHeader('Location', \URLHelper::getURL('dispatch.php/login'));
+        }
+    }
+}
diff --git a/lib/middleware/SeminarOpenMiddleware.php b/lib/middleware/SeminarOpenMiddleware.php
new file mode 100644
index 0000000000000000000000000000000000000000..108e0edbf3e604a41bb48d5ab03e2e78ac4178d3
--- /dev/null
+++ b/lib/middleware/SeminarOpenMiddleware.php
@@ -0,0 +1,295 @@
+<?php
+/**
+ * PSR 15 middleware Stud.IP initialization
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * @author      André Noack <noack@data-quest.de>
+ * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category    Stud.IP
+ * @since       6.0
+ */
+
+namespace Studip\Middleware;
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use Psr\Http\Message\ResponseFactoryInterface;
+
+
+final class SeminarOpenMiddleware implements MiddlewareInterface
+{
+
+    public function __construct(private ResponseFactoryInterface $response_factory)
+    {
+    }
+
+    /**
+     * @param $page_code
+     *
+     * @return ResponseInterface
+     */
+    public function startpageRedirect($page_code): ResponseInterface
+    {
+        switch ($page_code) {
+            case 1:
+            case 2:
+                $jump_page = 'dispatch.php/my_courses';
+                break;
+            case 3:
+                $jump_page = 'dispatch.php/calendar/schedule';
+                break;
+            case 4:
+                $jump_page = 'dispatch.php/contact';
+                break;
+            case 5:
+                $jump_page = 'dispatch.php/calendar/calendar';
+                break;
+            case 6:
+                // redirect to global blubberstream
+                // or no redirection if blubber isn't active
+                if (\Config::get()->BLUBBER_GLOBAL_MESSENGER_ACTIVATE) {
+                    $jump_page = 'dispatch.php/blubber';
+                }
+                break;
+            case 7:
+                $jump_page = 'dispatch.php/contents/overview';
+                break;
+        }
+        $response = $this->response_factory->createResponse(302);
+        return $response->withHeader('Location', \URLHelper::getURL($jump_page));
+    }
+
+    /**
+     * @param ServerRequestInterface  $request
+     * @param RequestHandlerInterface $handler
+     *
+     * @return ResponseInterface
+     */
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+
+        global $user, $perm, $_language_path;
+
+
+        //INITS
+        $seminar_open_redirected = false;
+        $user_did_login = false;
+
+        // session init starts here
+        if (empty($_SESSION['SessionStart'])) {
+            $_SESSION['SessionStart'] = time();
+            $_SESSION['object_cache'] = [];
+
+            // try to get accepted languages from browser
+            if (!isset($_SESSION['_language'])) {
+                $_SESSION['_language'] = get_accepted_languages();
+            }
+            if (!$_SESSION['_language']) {
+                $_SESSION['_language'] = \Config::get()->DEFAULT_LANGUAGE;
+            }
+        }
+
+        // user init starts here
+        if (is_object($user) && $user->id != "nobody") {
+            if ($_SESSION['SessionStart'] > \UserConfig::get(
+                    $user->id
+                )->CURRENT_LOGIN_TIMESTAMP
+            ) {      // just logged in
+                // store old CURRENT_LOGIN in LAST_LOGIN and set CURRENT_LOGIN to start of session
+                \UserConfig::get($user->id)->store(
+                    'LAST_LOGIN_TIMESTAMP', \UserConfig::get($user->id)->CURRENT_LOGIN_TIMESTAMP
+                );
+                \UserConfig::get($user->id)->store('CURRENT_LOGIN_TIMESTAMP', $_SESSION['SessionStart']);
+                //find current semester and store it in $_SESSION['_default_sem']
+                $current_sem = \Semester::findDefault();
+                $_SESSION['_default_sem'] = $current_sem->semester_id ?? null;
+                //redirect user to another page if he want to, redirect is deferred to allow plugins to catch the UserDidLogin notification
+                if (\UserConfig::get($user->id)->PERSONAL_STARTPAGE > 0 && !isset($_SESSION['redirect_after_login'])
+                    && !$perm->have_perm(
+                        "root"
+                    )
+                ) {
+                    $seminar_open_redirected = true;
+                }
+                unset($_SESSION['redirect_after_login']);
+                if (isset($_SESSION['contrast'])) {
+                    \UserConfig::get($GLOBALS['user']->id)->store('USER_HIGH_CONTRAST', $_SESSION['contrast']);
+                    unset($_SESSION['contrast']);
+                }
+                // store last language click
+                if (!empty($_SESSION['forced_language'])) {
+                    \User::findCurrent()->preferred_language = $_SESSION['forced_language'];
+                    \User::findCurrent()->store();
+                    $_SESSION['_language'] = $_SESSION['forced_language'];
+                }
+                $_SESSION['forced_language'] = null;
+                $user_did_login = true;
+            }
+
+            \TwoFactorAuth::get()->secureSession();
+        }
+
+        if (!empty($_SESSION['contrast']) || \UserConfig::get($GLOBALS['user']->id)->USER_HIGH_CONTRAST) {
+            \PageLayout::addStylesheet('accessibility.css');
+        }
+
+        // init of output via I18N
+        $_language_path = init_i18n($_SESSION['_language']);
+        //force reload of config to get translated data
+        include $GLOBALS['STUDIP_BASE_PATH'] . '/config/config.inc.php';
+
+
+        // Try to select the course or institute given by the parameter 'cid'
+        // in the current request.
+
+        $course_id = (\Request::int('cancel_login') && (!is_object($user) || $user->id === 'nobody'))
+            ? null
+            : \Request::option('cid');
+
+        // Select the current course or institute if we got one from 'cid' or session.
+        // This also binds Context::getId()
+        // to the URL parameter 'cid' for all generated links.
+        if (isset($course_id)) {
+            \Context::set($course_id);
+            unset($course_id);
+        }
+
+        if (\Request::int('disable_plugins') !== null && ($user->id === 'nobody' || $perm->have_perm('root'))) {
+            // deactivate non-core plugins
+            \PluginManager::getInstance()->setPluginsDisabled(\Request::int('disable_plugins'));
+        }
+
+        // load the default set of plugins
+        \PluginEngine::loadPlugins();
+
+        // add navigation item for profile: add modules
+        if (\Navigation::hasItem('/profile/edit')) {
+            $plus_nav = new \Navigation(_('Mehr …'), 'dispatch.php/profilemodules/index');
+            $plus_nav->setDescription(_('Mehr Stud.IP-Funktionen für Ihr Profil'));
+            \Navigation::addItem('/profile/modules', $plus_nav);
+        }
+
+        if ($user_did_login) {
+            if (isset($_SESSION[\StudipAuthOAuth2::class]['redirect'])) {
+                $redirect = $_SESSION[\StudipAuthOAuth2::class]['redirect'];
+                unset($_SESSION[\StudipAuthOAuth2::class]);
+                $response = $this->response_factory->createResponse(302);
+                return $response->withHeader('Location', \URLHelper::getURL($redirect));
+            }
+            \NotificationCenter::postNotification('UserDidLogin', $user->id);
+        }
+
+        if (!\Request::isXhr() && $perm->have_perm('root')) {
+            if (!isset($_SESSION['migration-check']) || $_SESSION['migration-check']['timestamp'] < time() - 5 * 60) {
+                $migrator = new \Migrator(
+                    "{$GLOBALS['STUDIP_BASE_PATH']}/db/migrations",
+                    new \DBSchemaVersion('studip')
+                );
+
+                $_SESSION['migration-check'] = [
+                    'disabled'  => $_SESSION['migration-check']['disabled'] ?? false,
+                    'timestamp' => time(),
+                    'count'     => $migrator->pendingMigrations()
+                ];
+            }
+
+            if (\Request::option('stop-migration-nag')) {
+                $_SESSION['migration-check']['disabled'] = true;
+            }
+
+            if (empty($_SESSION['migration-check']['disabled'])
+                && $_SESSION['migration-check']['count'] > 0
+            ) {
+                $info = sprintf(
+                    _('Es gibt %u noch nicht ausgeführte Migration(en).'),
+                    $_SESSION['migration-check']['count']
+                );
+
+                $message = \MessageBox::info($info, [
+                        sprintf(
+                            _('Zur %sMigrationsseite%s'),
+                            '<a class="link-intern" href="' . \URLHelper::getLink('web_migrate.php') . '">',
+                            '</a>'
+                        ),
+                        sprintf(
+                            '<small><a href="%s">%s</a></small>',
+                            \URLHelper::getLink('', ['stop-migration-nag' => true]),
+                            _('Diese Nachricht bis zum nächsten Login nicht mehr anzeigen')
+                        )
+                    ]
+                );
+                \PageLayout::postMessage($message, 'migration-info');
+            }
+        }
+        if (
+            $GLOBALS['perm']->have_perm('root')
+            && \Config::get()->MIGRATION_START_VERSION
+            && \Config::get()->MIGRATION_START_VERSION < \StudipVersion::getStudipVersion(true)
+            && !\Config::get()->UPDATE_NEWS_SEEN
+        ) {
+            $message = \MessageBox::info(
+                _('Sie haben ein Stud.IP-Update durchgeführt.'),
+                [
+                    sprintf(
+                        _('Zu den %sRelease-Notes%s'),
+                        '<a class="link-intern" href="' . \URLHelper::getLink('dispatch.php/root_assistant') . '" data-dialog>',
+                        '</a>'
+                    ),
+                ]
+            );
+            \PageLayout::postMessage($message, 'release-notes');
+        }
+
+        if ($seminar_open_redirected) {
+            $this->startpageRedirect(\UserConfig::get($user->id)->PERSONAL_STARTPAGE);
+        }
+
+        // Show terms on first login
+        if (is_object($GLOBALS['user'])
+            && $GLOBALS['user']->needsToAcceptTerms()
+            && !match_route('dispatch.php/terms')
+            && !match_route('dispatch.php/siteinfo/*')
+        ) {
+            if (!\Request::isXhr()) {
+                $response = $this->response_factory->createResponse(302);
+                return $response->withHeader(
+                    'Location', \URLHelper::getURL(
+                    'dispatch.php/terms',
+                    [
+                        'return_to'      => $_SERVER['REQUEST_URI'],
+                        'redirect_token' => \Token::create(600)
+                    ],
+                    true
+                )
+                );
+            } else {
+                throw new \Trails\Exception(400);
+            }
+        }
+
+        if (
+            \Config::get()->USER_VISIBILITY_CHECK
+            && is_object($GLOBALS['user'])
+            && $GLOBALS['user']->id !== 'nobody'
+            && !(
+                \Config::get()->DOZENT_ALWAYS_VISIBLE
+                && $perm->get_perm() === 'dozent'
+            )
+            && !match_route('dispatch.php/siteinfo/*')
+            && $GLOBALS['user']->visible === 'unknown'
+        ) {
+            require_once('lib/user_visible.inc.php');
+            $response = $this->response_factory->createResponse(200);
+            $response->getBody()->write((string)first_decision($GLOBALS['user']->id));
+            return $response;
+        }
+
+        return $handler->handle($request);
+    }
+}
diff --git a/lib/middleware/SessionMiddleware.php b/lib/middleware/SessionMiddleware.php
new file mode 100644
index 0000000000000000000000000000000000000000..7c4f242dc95ef439935408b8d47650e68becdb24
--- /dev/null
+++ b/lib/middleware/SessionMiddleware.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * PSR 15 middleware Stud.IP Session management
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * @author      André Noack <noack@data-quest.de>
+ * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category    Stud.IP
+ * @since       6.0
+ */
+namespace Studip\Middleware;
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use Studip\Session\Manager;
+
+final class SessionMiddleware implements MiddlewareInterface
+{
+    public function __construct(private Manager $session_manager)
+    {
+    }
+
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        $this->session_manager->start();
+        $response = $handler->handle($request);
+        try {
+            \NotificationCenter::postNotification('PageCloseWillExecute', null);
+            $this->session_manager->save();
+            if (isset($GLOBALS['user'])) {
+                $GLOBALS['user']->set_last_action();
+            }
+            \NotificationCenter::postNotification('PageCloseDidExecute', null);
+        } catch (\NotificationVetoException $e) {}
+        return $response;
+    }
+}
diff --git a/lib/navigation/AdminNavigation.php b/lib/navigation/AdminNavigation.php
index 1dab05bf22e29e3f2a2b56be0bdac1522fe13306..62796d7abcf274c9963ef0d1737ee0de4189cb08 100644
--- a/lib/navigation/AdminNavigation.php
+++ b/lib/navigation/AdminNavigation.php
@@ -223,7 +223,7 @@ class AdminNavigation extends Navigation
             $navigation = new Navigation($back_jump, 'dispatch.php/institute/overview?auswahl=' . Context::getId());
             $this->addSubNavigation('back_jump', $navigation);
         } else if (Context::isCourse() && !$archive_kill && !(isset($_SESSION['links_admin_data']['assi']) && $_SESSION['links_admin_data']['assi'])) {
-            $navigation = new Navigation($back_jump, 'seminar_main.php?auswahl=' . Context::getId());
+            $navigation = new Navigation($back_jump, 'dispatch.php/course/go?to=' . Context::getId());
             $this->addSubNavigation('back_jump', $navigation);
         }
 
diff --git a/lib/navigation/AvatarNavigation.php b/lib/navigation/AvatarNavigation.php
index 64bbdbc68a1db3be1adb31cbb5573b35bb350713..9e95bb0bd91bcaa1d12ceac55eeff866193584fa 100644
--- a/lib/navigation/AvatarNavigation.php
+++ b/lib/navigation/AvatarNavigation.php
@@ -46,7 +46,7 @@ class AvatarNavigation extends Navigation
         }
 
         // Link to logout
-        $navigation = new Navigation(_('Logout'), 'logout.php');
+        $navigation = new Navigation(_('Logout'), 'dispatch.php/logout');
         $navigation->setImage(Icon::create('door-leave'));
         $navigation->setRenderAsButton();
         $this->addSubNavigation('logout', $navigation);
diff --git a/lib/phplib/CT_Cache.php b/lib/phplib/CT_Cache.php
deleted file mode 100644
index d4eccfa0a2702b7055e5c7197010a1c8b9e95d1c..0000000000000000000000000000000000000000
--- a/lib/phplib/CT_Cache.php
+++ /dev/null
@@ -1,68 +0,0 @@
-<?php
-
-## Copyright (c) 2011 Elmar Ludwig, University of Osnabrueck
-##
-## PHPLIB Data Storage Container using Stud.IP cache
-## for use with Stud.IP and cache only!
-
-class CT_Cache
-{
-    protected const CACHE_KEY_PREFIX = 'session_data';
-    protected const SESSION_LIFETIME = 7200;
-
-    private $cache;
-
-    public function ac_start()
-    {
-        $this->cache = \Studip\Cache\Factory::getCache();
-    }
-
-    public function ac_get_lock()
-    {
-    }
-
-    public function ac_release_lock()
-    {
-    }
-
-    public function ac_newid($str, $name = null)
-    {
-        return $this->ac_get_value($str) === false ? $str : false;
-    }
-
-    public function ac_store($id, $name, $str)
-    {
-        $cache_key = self::CACHE_KEY_PREFIX . '/' . $id;
-        return $this->cache->write($cache_key, $str, ini_get('session.gc_maxlifetime') ?: self::SESSION_LIFETIME);
-    }
-
-    public function ac_delete($id, $name = null)
-    {
-        $cache_key = self::CACHE_KEY_PREFIX . '/' . $id;
-        $this->cache->expire($cache_key);
-    }
-
-    public function ac_gc($gc_time, $name = null)
-    {
-    }
-
-    public function ac_halt($s)
-    {
-        echo "<b>$s</b>";
-        exit;
-    }
-
-    public function ac_get_value($id, $name = null)
-    {
-        $cache_key = self::CACHE_KEY_PREFIX . '/' . $id;
-        return $this->cache->read($cache_key);
-    }
-
-    public function ac_get_changed($id, $name = null)
-    {
-    }
-
-    public function ac_set_changed($id, $name, $timestamp)
-    {
-    }
-}
diff --git a/lib/phplib/CT_Sql.php b/lib/phplib/CT_Sql.php
deleted file mode 100644
index 220af2241d8371541c5c6de0e1ccb1e566720160..0000000000000000000000000000000000000000
--- a/lib/phplib/CT_Sql.php
+++ /dev/null
@@ -1,101 +0,0 @@
-<?php
-
-##
-## Copyright (c) 1998-2000 NetUSE AG
-##                    Boris Erdmann, Kristian Koehntopp
-##
-## Copyright (c) 1998-2000 Sascha Schumann <sascha@schumann.cx>
-##
-## PHPLIB Data Storage Container using a SQL database
-## for use with Stud.IP and PDO only!
-
-class CT_Sql {
-    
-    ##
-    ## Define these parameters by overwriting or by
-    ## deriving your own class from it (recommened)
-    ##
-    
-    var $database_table = "session_data";
-    var $gzip_level = 0;
-    var $exists = '';
-    
-    ## end of configuration
-    
-    function ac_start() {
-    }
-    
-    function ac_get_lock() {
-        return true;
-    }
-    
-    function ac_release_lock() {
-        return true;
-    }
-    
-    function ac_gc($gc_time, $name = null) {
-        return DBManager::get()->exec(sprintf("DELETE FROM %s WHERE changed < FROM_UNIXTIME(%s) ",
-            $this->database_table,
-            (time() - ($gc_time * 60))
-            ));
-    }
-    
-    function ac_store($id, $name, $str) {
-        $db = DBManager::get();
-        if ($this->gzip_level){
-            $str = gzcompress($str, $this->gzip_level);
-        }
-        if ($this->exists === $id) {
-            $stmt = $db->prepare(sprintf("UPDATE %s SET val = ? WHERE sid = ?", $this->database_table));
-        } else {
-            $stmt = $db->prepare(sprintf("REPLACE INTO %s ( val, sid ) VALUES (?, ?)", $this->database_table));
-        }
-        $stmt->execute([$str, $id]);
-        return $stmt->rowCount();
-    }
-    
-    function ac_delete($id, $name = null) {
-        return DBManager::get()->exec(sprintf("DELETE FROM %s WHERE sid = '%s' LIMIT 1",
-            $this->database_table,
-            $id));
-    }
-    
-    function ac_get_value($id, $name = null) {
-        $rs = DBManager::get()->query(sprintf("SELECT val FROM %s where sid  = '%s'",
-            $this->database_table,
-            $id));
-        $str  = $rs->fetchColumn();
-        if ($this->gzip_level){
-            $str = @gzuncompress($str);
-        }
-        if ($str) $this->exists = $id;
-        return $str;
-    }
-    
-    function ac_get_changed($id, $name = null){
-        $rs = DBManager::get()->query(sprintf("SELECT UNIX_TIMESTAMP(changed) FROM %s WHERE sid  = '%s'",
-            $this->database_table,
-            $id));
-        return $rs->fetchColumn();
-    }
-    
-    function ac_set_changed($id, $name, $timestamp){
-        $db = DBManager::get();
-        $stmt = $db->prepare(sprintf("UPDATE %s SET changed = FROM_UNIXTIME(?) WHERE sid  = ?", $this->database_table));
-        $stmt->execute([$timestamp, $id]);
-        return $stmt->rowCount();
-    }
-    
-    function ac_newid($str, $name = null) {
-        $db = DBManager::get();
-        $query = "SELECT sid FROM " . $this->database_table . " WHERE sid = '$str'";
-        if (!$db->query($query)->fetchColumn()) {
-            return $str;
-        } else {
-            return FALSE;
-        }
-    }
-    
-    function ac_halt($s) {
-    }
-}
diff --git a/lib/phplib/DB_Sql.php b/lib/phplib/DB_Sql.php
deleted file mode 100644
index 1a6170308f3b84d17676a39b8e3e56d0a66d869a..0000000000000000000000000000000000000000
--- a/lib/phplib/DB_Sql.php
+++ /dev/null
@@ -1,260 +0,0 @@
-<?php
-
-/*
- * Copyright (C) 2007 - Marcus Lunzenauer <mlunzena@uos.de>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- */
-
-
-class DB_Sql {
-
-    /* public: connection parameters */
-    public $Host     = "";
-    public $Database = "";
-    public $User     = "";
-    public $Password = "";
-
-    /* public: configuration parameters */
-    public $Auto_Free     = 1;     ## Set to 1 for automatic mysql_free_result()
-    public $Debug         = 0;     ## Set to 1 for debugging messages.
-    public $Halt_On_Error = "yes"; ## "yes" (halt with message), "no" (ignore errors quietly), "report" (ignore errror, but spit a warning)
-
-    /* public: result array and current row number */
-    public $Record   = [];
-    public $Row;
-    public $RowCount;
-    public $ColumnCount;
-
-    /* public: current error number and error text */
-    public $Errno    = 0;
-    public $Error    = "";
-
-    protected $pdo;
-    protected $resultSet;
-
-    /* public: constructor */
-    function __construct($query = "") {
-        $this->pdo = DBManager::get();
-        $this->query($query);
-    }
-
-    function link_id() {
-        return TRUE;
-    }
-
-    function query_id() {
-        return !is_null($this->resultSet);
-    }
-
-    /* public: discard the query result */
-    function free() {
-        $this->resultSet = NULL;
-        $this->Errno     = NULL;
-        $this->Error     = NULL;
-        $this->Row       = 0;
-        $this->RowCount  = null;
-        $this->ColumnCount  = null;
-    }
-
-    /* public: perform a query */
-    function query($Query_String) {
-
-        $Query_String = trim($Query_String);
-        if ($Query_String == "") {
-            return 0;
-        }
-
-        # New query, discard previous result.
-        $this->free();
-
-        if ($this->Debug) {
-            printf("Debug: query = %s<br>\n", $Query_String);
-        }
-        if(mb_stripos($Query_String, 'select') === 0 || mb_stripos($Query_String, 'show') === 0){
-
-            $this->resultSet = $this->pdo->query($Query_String);
-
-            # Will return nada if it fails. That's fine.
-            return $this->resultSet;
-        } else {
-            $this->RowCount = $this->pdo->exec($Query_String);
-            return $this->RowCount;
-        }
-    }
-
-    /* public: walk result set */
-    function next_record() {
-        if (!$this->resultSet) {
-            //$this->halt("next_record called with no query pending.");
-            return 0;
-        }
-
-        $this->Record = $this->resultSet->fetch();
-        $this->Row   += 1;
-        /*
-        $this->Errno  = $this->resultSet->errorCode();
-        $this->Error  = $this->resultSet->errorInfo();
-        */
-        $stat = is_array($this->Record);
-        if ($this->Row == $this->num_rows()) {
-            $this->ColumnCount = $this->resultSet->ColumnCount();
-            $this->resultSet = null;
-        }
-        return $stat;
-    }
-
-    /* public: evaluate the result (size, width) */
-    function affected_rows() {
-        return $this->num_rows();
-    }
-
-    function num_rows() {
-        return (!is_null($this->RowCount) ? $this->RowCount : ($this->resultSet ? ($this->RowCount = $this->resultSet->rowCount()) : FALSE));
-    }
-
-    function num_fields() {
-        return (!is_null($this->ColumnCount) ? $this->ColumnCount : ($this->resultSet ? ($this->ColumnCount = $this->resultSet->ColumnCount()) : FALSE));
-    }
-
-    /* public: shorthand notation */
-    function nf() {
-        return $this->num_rows();
-    }
-
-    function np() {
-        print $this->num_rows();
-    }
-
-    function f($Name) {
-        return $this->Record[$Name];
-    }
-
-    function p($Name) {
-        print $this->Record[$Name];
-    }
-
-    /* public: return table metadata */
-    function metadata($table='',$full=false) {
-        $count = 0;
-        $id    = 0;
-        $res   = [];
-
-        /*
-        * Due to compatibility problems with Table we changed the behavior
-        * of metadata();
-        * depending on $full, metadata returns the following values:
-        *
-        * - full is false (default):
-        * $result[]:
-        *   [0]["table"]  table name
-        *   [0]["name"]   field name
-        *   [0]["type"]   field type
-        *   [0]["len"]    field length
-        *   [0]["flags"]  field flags
-        *
-        * - full is true
-        * $result[]:
-        *   ["num_fields"] number of metadata records
-        *   [0]["table"]  table name
-        *   [0]["name"]   field name
-        *   [0]["type"]   field type
-        *   [0]["len"]    field length
-        *   [0]["flags"]  field flags
-        *   ["meta"][field name]  index of field named "field name"
-        *   The last one is used, if you have a field name, but no index.
-        *   Test:  if (isset($result['meta']['myfield'])) { ...
-            */
-
-            // if no $table specified, assume that we are working with a query
-            // result
-            if ($table) {
-                throw new Exception('Not yet implemented.');
-                $this->connect();
-                $id = @mysql_list_fields($this->Database, $table);
-                if (!$id)
-                $this->halt("Metadata query failed.");
-            } else {
-                if (is_null($this->resultSet))
-                $this->halt("No query specified.");
-            }
-
-            $count = $this->resultSet->ColumnCount();
-
-            // made this IF due to performance (one if is faster than $count if's)
-            if (!$full) {
-                for ($i = 0; $i < $count; $i++) {
-                    $meta = $this->resultSet->getColumnMeta($i);
-                    $res[$i]["table"] = $meta['table'];
-                    $res[$i]["name"]  = $meta['name'];
-                    $res[$i]["type"]  = $meta['native_type'];
-                    $res[$i]["len"]   = $meta['len'];
-                    $res[$i]["flags"] = $meta['flags'];
-                }
-            } else { // full
-                throw new Exception('Not yet implemented.');
-                $res["num_fields"]= $count;
-
-                for ($i=0; $i<$count; $i++) {
-                    $res[$i]["table"] = @mysql_field_table ($id, $i);
-                    $res[$i]["name"]  = @mysql_field_name  ($id, $i);
-                    $res[$i]["type"]  = @mysql_field_type  ($id, $i);
-                    $res[$i]["len"]   = @mysql_field_len   ($id, $i);
-                    $res[$i]["flags"] = @mysql_field_flags ($id, $i);
-                    $res["meta"][$res[$i]["name"]] = $i;
-                }
-            }
-
-            // free the result only if we were called on a table
-            if ($table) {
-                throw new Exception('Not yet implemented.');
-                @mysql_free_result($id);
-            }
-
-            return $res;
-    }
-
-    /* private: error handling */
-    function halt($msg) {
-        if ($this->Halt_On_Error == "no")
-        return;
-
-        $this->haltmsg($msg);
-
-        if ($this->Halt_On_Error != "report")
-        die("Session halted.");
-    }
-
-    function haltmsg($msg) {
-        printf("</td></tr></table><b>Database error:</b> %s<br>\n", $msg);
-        printf("<b>MySQL Error</b>: %s (%s)<br>\n",
-        $this->Errno,
-        join(':',$this->Error));
-    }
-
-    /* public: perform a query using format string*/
-    function queryf($format /* , .. */) {
-
-        // get args
-        $args = func_get_args();
-
-        // get format string
-        $format = array_shift($args);
-
-        // do something
-        return $this->query(vsprintf($format, $args));
-    }
-
-    /**
-     * perform a query with caching directive for mysql
-     * @param  string $Query_String
-     * @return Resultset | int
-     */
-    public function cache_query($Query_String) {
-        trigger_error(__CLASS__ . ' no longer supports query caching - use query() instead.', E_USER_DEPRECATED);
-        return $this->query($Query_String);
-    }
-}
diff --git a/lib/phplib/Seminar_Auth.php b/lib/phplib/Seminar_Auth.php
deleted file mode 100644
index 4150df5fe2e139c1addb389cd94218a093857c68..0000000000000000000000000000000000000000
--- a/lib/phplib/Seminar_Auth.php
+++ /dev/null
@@ -1,455 +0,0 @@
-<?php
-
-/**
- * Seminar_Auth.php
- *
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * @author      André Noack <noack@data-quest.de>
- * @copyright   2000 Stud.IP Core-Group
- * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- */
-class Seminar_Auth
-{
-    /**
-     * @var string
-     */
-    public $classname;
-
-    /**
-     * @var string
-     */
-    public $error_msg = "";
-
-    /**
-     * @var array
-     */
-    protected $persistent_slots = ["auth", "classname"];
-
-    /**
-     * @var bool
-     */
-    protected $nobody = false; ## If true, a default auth is created...
-
-    /**
-     * @var string
-     */
-    protected $cancel_login = "cancel_login"; ## The name of a button that can be
-    ## used to cancel a login form
-    /**
-     * @var array
-     */
-    public $auth = []; ## Data array
-
-    public $need_email_activation = null;
-
-    /**
-     *
-     */
-    function __construct()
-    {
-        $this->classname = get_class($this);
-    }
-
-    /**
-     * @param $f
-     * @return $this
-     */
-    function check_feature($f)
-    {
-        if ($this->classname != $f) {
-            $clone = new $f;
-            $clone->auth = $this->auth;
-            return $clone;
-        } else {
-            return $this;
-        }
-    }
-
-    /**
-     * Check current auth state. Should be one of
-     * 1) Not logged in (no valid auth info or auth expired)
-     * 2) Logged in (valid auth info)
-     * 3) Login in progress (if $this->cancel_login, revert to state 1)
-
-     * @return int
-     */
-    protected function getState(): int
-    {
-        if ($this->is_authenticated()) {
-            $uid = $this->auth['uid'];
-            switch ($uid) {
-                case 'form':
-                    # Login in progress
-                    if (Request::option($this->cancel_login)) {
-                        # If $this->cancel_login is set, delete all auth info and set
-                        # state to "Not logged in", so eventually default or automatic
-                        # authentication may take place
-                        $this->unauth();
-                        $state = 1;
-                    } else {
-                        # Set state to "Login in progress"
-                        $state = 3;
-                    }
-                    break;
-                default:
-                    # User is authenticated and auth not expired
-                    $state = 2;
-                    break;
-            }
-        } else {
-            # User is not (yet) authenticated
-            $this->unauth();
-            $state = 1;
-        }
-
-        return $state;
-    }
-
-    /**
-     * @return bool
-     * @throws RuntimeException
-     */
-    public function start()
-    {
-        global $sess;
-
-        switch ($this->getState()) {
-            case 1:
-                # No valid auth info or auth is expired
-
-                # Check for user supplied automatic login procedure
-                if ($uid = $this->auth_preauth()) {
-                    $this->auth["uid"] = $uid;
-                    $sess->regenerate_session_id([
-                        '_language',
-                        'auth',
-                        'contrast',
-                        'phpCAS',
-                        StudipAuthOAuth2::class
-                    ]);
-                    $sess->freeze();
-                    $GLOBALS['user'] = new Seminar_User($this->auth['uid']);
-                    return true;
-                }
-
-                if ($this->nobody) {
-                    # Authenticate as nobody
-                    $this->auth["uid"] = "nobody";
-                    return true;
-                } else {
-                    # Show the login form
-                    $this->auth_loginform();
-                    $this->auth["uid"] = "form";
-                    $sess->freeze();
-                    exit;
-                }
-            case 2:
-                # Valid auth info
-                # do nothin
-                break;
-            case 3:
-                # Login in progress, check results and act accordingly
-                $uid = $this->auth_validatelogin();
-                if ($uid) {
-                    $this->auth["uid"] = $uid;
-                    $keep_session_vars = ['auth', 'forced_language', '_language', 'contrast', 'oauth2'];
-                    if ($this->auth['perm'] === 'root') {
-                        $keep_session_vars[] = 'plugins_disabled';
-                    }
-                    $sess->regenerate_session_id($keep_session_vars);
-                    $sess->freeze();
-                    $GLOBALS['user'] = new Seminar_User($this->auth['uid']);
-                    return true;
-                } else {
-                    $this->auth_loginform();
-                    $this->auth["uid"] = "form";
-                    $sess->freeze();
-                    exit;
-                }
-            default:
-                # This should never happen. Complain.
-                throw new RuntimeException("Error in auth handling: invalid state reached.");
-        }
-
-        return false;
-    }
-
-
-    /**
-     * @return array
-     */
-    function __sleep()
-    {
-        return $this->persistent_slots;
-    }
-
-
-    /**
-     *
-     */
-    function unauth()
-    {
-        $this->auth = [];
-        $this->auth["uid"] = "";
-        $this->auth["perm"] = "";
-    }
-
-
-    /**
-     *
-     */
-    function logout()
-    {
-        $_SESSION['auth'] = null;
-        $this->unauth();
-        $GLOBALS['auth'] = $this;
-    }
-
-    /**
-     * @param $ok
-     * @return bool
-     */
-    function login_if($ok)
-    {
-        if ($ok) {
-            $this->unauth(); # We have to relogin, so clear current auth info
-            $this->nobody = false; # We are forcing login, so default auth is
-            # disabled
-            $this->start(); # Call authentication code
-        }
-        return true;
-    }
-
-    /**
-     * @return bool
-     * @throws AccessDeniedException
-     */
-    function is_authenticated()
-    {
-        $cfg = Config::GetInstance();
-        //check if the user got kicked meanwhile, or if user is locked out
-        if (!empty($this->auth['uid']) && !in_array($this->auth['uid'], ['form', 'nobody'])) {
-            $user = null;
-            if (isset($GLOBALS['user']) && $GLOBALS['user']->id == $this->auth['uid']) {
-                $user = $GLOBALS['user']->getAuthenticatedUser();
-            } else {
-                $user = User::find($this->auth['uid']);
-            }
-            if (!$user->username || $user->isBlocked()) {
-                $this->unauth();
-            }
-        } elseif ($cfg->getValue('MAINTENANCE_MODE_ENABLE') && Request::username('loginname')) {
-            $user = User::findByUsername(Request::username('loginname'));
-        }
-        if ($cfg->getValue('MAINTENANCE_MODE_ENABLE') && $user->perms != 'root') {
-            $this->unauth();
-            throw new AccessDeniedException(_("Das System befindet sich im Wartungsmodus. Zur Zeit ist kein Zugriff möglich."));
-        }
-        return @$this->auth['uid'] ? : false;
-    }
-
-    /**
-     * @return bool
-     */
-    function auth_preauth()
-    {
-        // is Single Sign On activated?
-        if (($provider = Request::option('sso'))) {
-
-            $this->check_environment();
-
-            Metrics::increment('core.sso_login.attempted');
-
-            // then do login
-            if (($authplugin = StudipAuthAbstract::GetInstance($provider))) {
-                $user = $authplugin->authenticateUser('', '');
-                if ($user) {
-                    if ($user->isExpired()) {
-                        throw new AccessDeniedException(_('Dieses Benutzerkonto ist abgelaufen. Wenden Sie sich bitte an die Administration.'));
-                    }
-                    if ($user->locked == 1) {
-                        throw new AccessDeniedException(_('Dieser Benutzer ist gesperrt! Wenden Sie sich bitte an die Administration.'));
-                    }
-                    $this->auth["jscript"] = true;
-                    $this->auth["perm"] = $user->perms;
-                    $this->auth["uname"] = $user->username;
-                    $this->auth["auth_plugin"] = $user->auth_plugin;
-                    $this->auth_set_user_settings($user);
-
-                    Metrics::increment('core.sso_login.succeeded');
-
-                    return $user->id;
-                } else {
-                    PageLayout::postMessage(MessageBox::error($authplugin->plugin_name . ': ' . _('Login fehlgeschlagen'), $authplugin->error_msg ? [$authplugin->error_msg] : []),md5($authplugin->error_msg));
-                }
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     *
-     */
-    function auth_loginform()
-    {
-        if (Request::isXhr()) {
-            if (Request::isDialog()) {
-                header('X-Location: ' . URLHelper::getURL($_SERVER['REQUEST_URI']));
-                page_close();
-                die();
-            }
-            throw new AccessDeniedException();
-        }
-
-        if (Request::submitted('user_config_submitted')) {
-            CSRFProtection::verifyUnsafeRequest();
-            if (Request::submitted('unset_contrast')) {
-                $_SESSION['contrast'] = 0;
-            }
-            if (Request::submitted('set_contrast')) {
-                $_SESSION['contrast'] = 1;
-            }
-
-            foreach (array_keys($GLOBALS['INSTALLED_LANGUAGES']) as $language_key) {
-                if (Request::get('set_language') === $language_key) {
-                    $_SESSION['forced_language'] = $language_key;
-                    $_SESSION['_language'] = $language_key;
-                }
-            }
-        }
-        $this->check_environment();
-
-        PageLayout::setBodyElementId('login');
-
-        // load the default set of plugins
-        PluginEngine::loadPlugins();
-
-        if (Request::get('loginname') && empty($_COOKIE[get_class($GLOBALS['sess'])])) {
-            $login_template = $GLOBALS['template_factory']->open('nocookies');
-        } else if (isset($this->need_email_activation)) {
-            $this->unauth();
-            header('Location: ' . URLHelper::getURL('activate_email.php?cancel_login=1&key=&uid=' . $this->need_email_activation));
-            page_close();
-            die();
-        } else {
-            $news_entries = StudipNews::GetNewsByRange('login', true, false);
-            unset($_SESSION['semi_logged_in']); // used by email activation
-            $login_template = $GLOBALS['template_factory']->open('loginform');
-            if (isset($this->auth['uname']) && $this->error_msg) {
-                PageLayout::postError(_('Bei der Anmeldung trat ein Fehler auf!'), $this->error_msg);
-            }
-            $login_template->error_msg = $this->error_msg;
-            $login_template->uname = $this->auth['uname'] ?? Request::username('loginname');
-            $login_template->self_registration_activated = Config::get()->ENABLE_SELF_REGISTRATION;
-            $login_template->logout = Request::bool('logout', false);
-            $login_template->faq_entries = [];
-            $login_template->news_entries = array_values($news_entries);
-
-            if (Config::get()->getValue('LOGIN_FAQ_VISIBILITY')) {
-                $login_template->faq_entries = LoginFaq::findBySQL('1');
-            }
-        }
-        PageLayout::setHelpKeyword('Basis.AnmeldungLogin');
-        $header_template = $GLOBALS['template_factory']->open('header');
-        $header_template->current_page = _('Login');
-        $header_template->link_params = ['cancel_login' => 1];
-
-        include 'lib/include/html_head.inc.php';
-        echo $header_template->render();
-        echo $login_template->render();
-        include 'lib/include/html_end.inc.php';
-        page_close();
-    }
-
-    /**
-     * @return bool
-     */
-    function auth_validatelogin()
-    {
-        //prevent replay attack
-        if (!Seminar_Session::check_ticket(Request::option('login_ticket'))) {
-            return false;
-        }
-
-        $this->check_environment();
-
-        $this->auth["uname"] = Request::get('loginname'); // This provides access for "loginform.ihtml"
-        $this->auth["jscript"] = Request::get('resolution') != "";
-
-        $check_auth = StudipAuthAbstract::CheckAuthentication(Request::get('loginname'), Request::get('password'));
-
-        if ($check_auth['uid']) {
-            $uid = $check_auth['uid'];
-            if (isset($check_auth['need_email_activation']) && $check_auth['need_email_activation'] == $uid) {
-                $this->need_email_activation = $uid;
-                $_SESSION['semi_logged_in'] = $uid;
-                return false;
-            }
-            $user = $check_auth['user'];
-            $this->auth["perm"] = $user->perms;
-            $this->auth["uname"] = $user->username;
-            $this->auth["auth_plugin"] = $user->auth_plugin;
-            $this->auth_set_user_settings($user);
-
-            Metrics::increment('core.login.succeeded');
-
-            return $uid;
-        } else {
-            Metrics::increment('core.login.failed');
-            $this->error_msg = $check_auth['error'];
-            return false;
-        }
-    }
-
-    /**
-     * @param $user
-     */
-    function auth_set_user_settings($user)
-    {
-        $divided = explode('x', Request::get('resolution'));
-        $this->auth["xres" . ""] = !empty($divided[0]) ? (int) $divided[0] : 1024; //default
-        $this->auth['yres'] = !empty($divided[1]) ? (int)$divided[1] : 768; //default
-        // Change X-Resulotion on Multi-Screen Systems (as Matrox Graphic-Adapters are)
-        if ($this->auth['xres'] / $this->auth['yres'] > 2) {
-            $this->auth['xres'] = $this->auth['xres'] / 2;
-        }
-        $user = User::toObject($user);
-        //restore user-specific language preference
-        if ($user->preferred_language) {
-            // we found a stored setting for preferred language
-            $_SESSION['_language'] = $user->preferred_language;
-        }
-    }
-
-    /**
-     * setup dummy user environment
-     */
-    function check_environment()
-    {
-        global $_language_path;
-
-        if (!isset($GLOBALS['user']) || $GLOBALS['user']->id !== 'nobody') {
-            $GLOBALS['user'] = new Seminar_User('nobody');
-            $GLOBALS['perm'] = new Seminar_Perm();
-            $GLOBALS['auth'] = $this;
-        }
-
-        if (empty($_SESSION['_language'])) {
-            $_SESSION['_language'] = get_accepted_languages();
-        }
-
-        // init of output via I18N
-        $_language_path = init_i18n($_SESSION['_language']);
-        include $GLOBALS['STUDIP_BASE_PATH'] . '/config/config.inc.php';
-
-        if (!empty($_SESSION['contrast'])) {
-            PageLayout::addStylesheet('accessibility.css');
-        }
-    }
-}
diff --git a/lib/phplib/Seminar_Default_Auth.php b/lib/phplib/Seminar_Default_Auth.php
deleted file mode 100644
index 146c5888be46be6e38cff2d72178b68ce5529b29..0000000000000000000000000000000000000000
--- a/lib/phplib/Seminar_Default_Auth.php
+++ /dev/null
@@ -1,19 +0,0 @@
-<?php
-
-/**
- * Seminar_Default_Auth.php
- *
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * @author      André Noack <noack@data-quest.de>
- * @copyright   2000 Stud.IP Core-Group
- * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- */
-class Seminar_Default_Auth extends Seminar_Auth
-{
-    protected $nobody = true;
-}
diff --git a/lib/phplib/Seminar_Register_Auth.php b/lib/phplib/Seminar_Register_Auth.php
deleted file mode 100644
index 5bf10f188468a543f39f4c9be9528ba71272a7c5..0000000000000000000000000000000000000000
--- a/lib/phplib/Seminar_Register_Auth.php
+++ /dev/null
@@ -1,242 +0,0 @@
-<?php
-
-/**
- * Seminar_Register_Auth.php
- *
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * @author      André Noack <noack@data-quest.de>
- * @copyright   2000 Stud.IP Core-Group
- * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- */
-class Seminar_Register_Auth extends Seminar_Auth
-{
-    public function start()
-    {
-        global $sess;
-
-        switch ($this->getState()) {
-            # No valid auth info or auth is expired
-            case 1:
-
-                if ($this->nobody) {
-                    # Authenticate as nobody
-                    $this->auth['uid'] = 'nobody';
-                    return true;
-                } else {
-                    # Show the registration form
-                    $this->auth_registerform();
-                    $this->auth['uid'] = 'form';
-                    exit;
-                }
-            # Login in progress, check results and act accordingly
-            case 3:
-                $uid = $this->auth_doregister();
-                if ($uid) {
-                    $this->auth['uid'] = $uid;
-                    $GLOBALS['user'] = new Seminar_User($this->auth['uid']);
-                    return true;
-                } else {
-                    $this->auth_registerform();
-                    $this->auth['uid'] = 'form';
-                    $sess->freeze();
-                    exit;
-                }
-        }
-
-        return parent::start();
-    }
-
-    public function auth_registerform()
-    {
-        $this->check_environment();
-
-        // load the default set of plugins
-        PluginEngine::loadPlugins();
-
-        if (!$_COOKIE[get_class($GLOBALS['sess'])]) {
-            $register_template = $GLOBALS['template_factory']->open('nocookies');
-        } else {
-            $register_template = $GLOBALS['template_factory']->open('register/form');
-            $register_template->validator   = new email_validation_class();
-            $register_template->error_msg   = $this->error_msg;
-            $register_template->username    = Request::get('username');
-            $register_template->Vorname     = Request::get('Vorname');
-            $register_template->Nachname    = Request::get('Nachname');
-            $register_template->Email       = Request::get('Email');
-            $register_template->title_front = Request::get('title_front');
-            $register_template->title_rear  = Request::get('title_rear');
-            $register_template->geschlecht  = Request::int('geschlecht', 0);
-        }
-        PageLayout::setHelpKeyword('Basis.AnmeldungRegistrierung');
-        PageLayout::setTitle(_('Registrierung'));
-
-        echo $register_template->render(
-            [],
-            $GLOBALS['template_factory']->open('layouts/base.php')
-        );
-    }
-
-    /**
-     * @return bool|string
-     */
-    public function auth_doregister()
-    {
-        $this->check_environment();
-
-        $this->error_msg = '';
-
-        $this->auth['uname'] = Request::username('username'); // This provides access for "crcregister.ihtml"
-
-        $validator = new email_validation_class(); // Klasse zum Ueberpruefen der Eingaben
-        $validator->timeout = 10; // Wie lange warten wir auf eine Antwort des Mailservers?
-
-        if (!Seminar_Session::check_ticket(Request::option('login_ticket'))) {
-            return false;
-        }
-
-        $username = trim(Request::get('username'));
-        $Vorname  = trim(Request::get('Vorname'));
-        $Nachname = trim(Request::get('Nachname'));
-
-        // accept only registered domains if set
-        if (Config::get()->EMAIL_DOMAIN_RESTRICTION) {
-            $Email = trim(Request::get('Email')) . '@' . trim(Request::get('emaildomain'));
-        } else {
-            $Email = trim(Request::get('Email'));
-        }
-
-        if (!$validator->ValidateUsername($username)) {
-            $this->error_msg = $this->error_msg . _('Der gewählte Benutzername ist zu kurz!') . '<br>';
-            return false;
-        } // username syntaktisch falsch oder zu kurz
-        // auf doppelte Vergabe wird weiter unten getestet.
-
-        if (!$validator->ValidatePassword(Request::get('password'))) {
-            $this->error_msg = $this->error_msg . _('Das Passwort ist zu kurz, zu lang oder enthält nicht erlaubte Zeichen!') . '<br>';
-            return false;
-        }
-
-        if (!$validator->ValidateName($Vorname)) {
-            $this->error_msg = $this->error_msg . _('Der Vorname fehlt oder ist unsinnig!') . '<br>';
-            return false;
-        } // Vorname nicht korrekt oder fehlend
-        if (!$validator->ValidateName($Nachname)) {
-            $this->error_msg = $this->error_msg . _('Der Nachname fehlt oder ist unsinnig!') . '<br>';
-            return false; // Nachname nicht korrekt oder fehlend
-        }
-        if (!$validator->ValidateEmailAddress($Email)) {
-            $this->error_msg = $this->error_msg . _('Die E-Mail-Adresse fehlt oder ist falsch geschrieben!') . '<br>';
-            return false;
-        } // E-Mail syntaktisch nicht korrekt oder fehlend
-
-        $REMOTE_ADDR = $_SERVER['REMOTE_ADDR'];
-        $Zeit = date('H:i:s, d.m.Y');
-
-        if (!$validator->ValidateEmailHost($Email)) { // Mailserver nicht erreichbar, ablehnen
-            $this->error_msg = $this->error_msg . _('Der Mailserver ist nicht erreichbar, bitte überprüfen Sie, ob Sie E-Mails mit der angegebenen Adresse verschicken und empfangen können!') . '<br>';
-            return false;
-        } else { // Server ereichbar
-            if (!$validator->ValidateEmailBox($Email)) { // aber user unbekannt. Mail an abuse!
-                StudipMail::sendAbuseMessage('Register', "Emailbox unbekannt\n\nUser: $username\nEmail: $Email\n\nIP: $REMOTE_ADDR\nZeit: $Zeit\n");
-                $this->error_msg = $this->error_msg . _('Die angegebene E-Mail-Adresse ist nicht erreichbar, bitte überprüfen Sie Ihre Angaben!') . '<br>';
-                return false;
-            } else {
-                ; // Alles paletti, jetzt kommen die Checks gegen die Datenbank...
-            }
-        }
-
-        $check_uname = StudipAuthAbstract::CheckUsername($username);
-
-        if ($check_uname['found']) {
-            $this->error_msg = $this->error_msg . _('Der gewählte Benutzername ist bereits vorhanden!') . '<br>';
-            return false; // username schon vorhanden
-        }
-
-        if (User::countBySQL('Email = ?', [$Email])) {
-            $this->error_msg = $this->error_msg . _('Die angegebene E-Mail-Adresse wird bereits von einem anderen Benutzer verwendet. Sie müssen eine andere E-Mail-Adresse angeben!') . '<br>';
-            return false; // Email schon vorhanden
-        }
-
-        // alle Checks ok, Benutzer registrieren...
-        $hasher = UserManagement::getPwdHasher();
-        $new_user = new User();
-        $new_user->username = $username;
-        $new_user->perms = 'user';
-        $new_user->password = $hasher->HashPassword(Request::get('password'));
-        $new_user->vorname = $Vorname;
-        $new_user->nachname = $Nachname;
-        $new_user->email = $Email;
-        $new_user->geschlecht = Request::int('geschlecht');
-        $new_user->title_front = trim(Request::get('title_front', Request::get('title_front_chooser')));
-        $new_user->title_rear = trim(Request::get('title_rear', Request::get('title_rear_chooser')));
-        $new_user->auth_plugin = 'standard';
-        $new_user->store();
-
-        if (!$new_user->user_id) {
-            return false;
-        }
-
-        self::sendValidationMail($new_user);
-
-        $this->auth['perm'] = $new_user->perms;
-        $this->auth['uname'] = $new_user->username;
-        $this->auth['auth_plugin'] = $new_user->auth_plugin;
-
-        return $new_user->user_id;
-    }
-
-    /**
-     * Send a validation mail to the passed user
-     *
-     * @param User $user a user-object or id of the user
-     *                   to resend the validation mail for
-     */
-    public static function sendValidationMail($user){
-        // if no user-object is given interpret it as a user-id
-        if (is_string($user)) {
-            $user = new User($user);
-        }
-
-        // template-variables for the include partial
-        $Zeit     = date('H:i:s, d.m.Y', $user->mkdate);
-        $username = $user->username;
-        $Vorname  = $user->vorname;
-        $Nachname = $user->nachname;
-        $Email    = $user->email;
-
-        // (re-)send the confirmation mail
-        $to     = $user->email;
-        $token  = Token::create(7 * 24 * 60 * 60, $user->id); // Link is valid for 1 week
-        $url    = $GLOBALS['ABSOLUTE_URI_STUDIP'] . 'email_validation.php?secret=' . $token;
-        $mail   = new StudipMail();
-        $abuse  = StudipMail::getAbuseEmail();
-
-        $lang_path = getUserLanguagePath($user->id);
-
-        // include language-specific subject and mailbody
-        // TODO: This should be refactored so that the included file returns an array
-        include "locale/{$lang_path}/LC_MAILS/register_mail.inc.php"; // Defines $subject and $mailbody
-
-        // send the mail
-        $mail->setSubject($subject ?? '')
-            ->addRecipient($to)
-            ->setBodyText($mailbody ?? '')
-            ->send();
-    }
-
-    /**
-     * Validates a given hash for a given user id.
-     * @param  string $secret  Secret to validate
-     * @param  string $user_id User id
-     * @return bool
-     */
-    public static function validateSecret($secret, $user_id)
-    {
-        return Token::isValid($secret, $user_id);
-    }
-}
diff --git a/lib/phplib/Seminar_Session.php b/lib/phplib/Seminar_Session.php
deleted file mode 100644
index 8324062dbe175638febfa93ec0ab076f51c07ca3..0000000000000000000000000000000000000000
--- a/lib/phplib/Seminar_Session.php
+++ /dev/null
@@ -1,436 +0,0 @@
-<?php
-
-/**
- * PHPLib Sessions using PHP 4 build-in sessions and PHPLib storage container
- *
- * @copyright  (c) 1998,1999 NetUSE GmbH Boris Erdmann, Kristian Koehntopp,
- *             2000 Maxim Derkachev <kot@books.ru>,
- *             2000 Teodor Cimpoesu <teo@digiro.net>
- * @author     André Noack <noack@data-quest.de> Maxim Derkachev <kot@books.ru>,
- *               Teodor Cimpoesu <teo@digiro.net>,Ulf Wendel <uw@netuse.de>
- */
-class Seminar_Session
-{
-    /**
-     * Current session id.
-     *
-     * @var  string
-     * @see  id(), Session()
-     */
-    private $id;
-
-
-    /**
-     * Current session name also cookie name
-     *
-     * @var  string
-     * @see  name(), Session()
-     */
-    private $name;
-
-    /**
-     *
-     * @var  string
-     */
-    private $cookie_path;
-
-    /**
-     * If set, the domain for which the session cookie is set.
-     *
-     * @var  string
-     */
-    private $cookie_domain;
-
-    /**
-     * If set, the domain for which the session cookie is set.
-     *
-     * @var  bool
-     */
-    private $cookie_secure = false;
-
-    /**
-     * If set, the domain for which the session cookie is set.
-     *
-     * @var  bool
-     */
-    private $cookie_httponly = true;
-
-    /**
-     * session storage module - user, files or mm
-     *
-     * @var  string
-     */
-    private $module = 'user';
-
-
-    /**
-     * where to save session files if module == files
-     *
-     * @var string
-     */
-    private $save_path;
-
-
-    /**
-     * Name of data storage container
-     *
-     * var string
-     */
-    private $that_class = 'CT_Sql';
-
-    /**
-     *
-     * @var  object CT_*
-     */
-    private $that;
-
-    /**
-     * Purge all session data older than this.
-     *
-     * @var int
-     */
-    private $gc_time;
-
-
-    /**
-     * @var
-     */
-    private static $studipticket;
-    /**
-     * @var
-     */
-    private static $current_session_state;
-
-    /**
-     * Returns true, if the current session is valid and belongs to an
-     * authenticated user. Does not start a session.
-     *
-     * @static
-     * @return bool
-     */
-    public static function is_current_session_authenticated()
-    {
-        return self::get_current_session_state() == 'authenticated';
-    }
-
-    /**
-     * Returns the state of the current session. Does not start a session.
-     * possible return values:
-     * 'authenticated' - session is valid and user is authenticated
-     * 'nobody' - session is valid, but user is not authenticated
-     * false - no valid session
-     *
-     * @static
-     * @return string|false
-     */
-    public static function get_current_session_state()
-    {
-
-        if (!is_null(self::$current_session_state)) {
-            return self::$current_session_state;
-        }
-        $state = false;
-        if (isset($GLOBALS['user']) && is_object($GLOBALS['user'])) {
-            $state = in_array($GLOBALS['user']->id, ['nobody', 'form']) ? 'nobody' : 'authenticated';
-        } else {
-            $sid = $_COOKIE[__CLASS__];
-            if ($sid) {
-                $session_vars = self::get_session_vars($sid);
-                $session_auth = $session_vars['auth']->auth;
-                if ($session_auth['uid'] && !in_array($session_auth['uid'], ['nobody', 'form'])) {
-                    $state = 'authenticated';
-                } else {
-                    $state = in_array($session_auth['uid'], ['nobody', 'form']) ? 'nobody' : false;
-                }
-            }
-        }
-        return (self::$current_session_state = $state);
-    }
-
-    /**
-     * returns a SessionDecoder object containing the session variables
-     * for the given session id
-     *
-     * @static
-     * @param string $sid a session id
-     * @return SessionDecoder
-     */
-    public static function get_session_vars($sid)
-    {
-        $sess = $GLOBALS['sess'] ?? null;
-        if (!is_object($sess)) {
-            $sess = new self();
-        }
-        $storage_class = $sess->that_class;
-        $storage = new $storage_class();
-        $storage->ac_start();
-        return new SessionDecoder($storage->ac_get_value($sid));
-    }
-
-    /**
-     * returns a random string token for XSRF prevention
-     * the string is stored in the session
-     *
-     * @static
-     * @return string
-     */
-    public static function get_ticket()
-    {
-        if (!self::$studipticket) {
-            self::$studipticket = $_SESSION['last_ticket'] = md5(uniqid('studipticket', 1));
-        }
-        return self::$studipticket;
-    }
-
-    /**
-     * checks the given string token against the one stored
-     * in the session
-     *
-     * @static
-     * @param string $studipticket
-     * @return bool
-     */
-    public static function check_ticket($studipticket)
-    {
-        $check = (isset($_SESSION['last_ticket']) && $_SESSION['last_ticket'] == $studipticket);
-        $_SESSION['last_ticket'] = null;
-        return $check;
-    }
-
-
-    /**
-     *
-     */
-    function __construct()
-    {
-        if (Config::get()->CACHING_ENABLE && $GLOBALS['CACHE_IS_SESSION_STORAGE']) {
-            $this->that_class = 'CT_Cache';
-        }
-        $this->cookie_path = $this->cookie_path ?: $GLOBALS['CANONICAL_RELATIVE_PATH_STUDIP'];
-        $this->cookie_secure = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on';
-        $this->name(get_class($this));
-    }
-
-    /**
-     * Start a new session or recovers from an existing session
-     *
-     * @return boolean   session_start() return value
-     * @access public
-     */
-    function start()
-    {
-        $this->set_container();
-
-        session_set_cookie_params(
-            0,
-            implode('/', array_map('rawurlencode', explode('/', $this->cookie_path))),
-            $this->cookie_domain,
-            $this->cookie_secure,
-            $this->cookie_httponly
-        );
-        session_cache_limiter("nocache");
-        //check for illegal cookiename
-        if (
-            !isset($_COOKIE[$this->name])
-            || mb_strlen($_COOKIE[$this->name]) !== 32
-            || preg_match('/[^0-9a-f]+/', $_COOKIE[$this->name])
-        ) {
-            do {
-                $new_id = md5(bin2hex(random_bytes(128)));
-            } while (!$this->that->ac_newid($new_id));
-
-            session_id($new_id);
-        }
-
-        $ok = session_start();
-        $this->id = session_id();
-        return $ok;
-    }
-
-    /**
-     * Sets or returns the name of the current session
-     *
-     * @param  string  If given, sets the session name
-     * @return string  session_name() return value
-     * @access public
-     */
-    function name($name = '')
-    {
-        if ($name) {
-            $this->name = $name;
-            $ok = session_name($name);
-        } else {
-            $ok = session_name();
-        }
-        return $ok;
-    }
-
-    /**
-     * ?
-     *
-     */
-    function set_container()
-    {
-
-        switch ($this->module) {
-            case "user" :
-                $name = $this->that_class;
-                $this->that = new $name;
-                $this->that->ac_start();
-                // set custom session handlers
-                session_set_save_handler([$this, 'open'],
-                    [$this, 'close'],
-                    [$this, 'thaw'],
-                    [$this, 'freeze'],
-                    [$this, 'del'],
-                    [$this, 'gc']
-                );
-                break;
-
-            case "mm":
-                session_module_name('mm');
-                break;
-
-            case "files":
-            default:
-                if ($this->save_path) {
-                    session_save_path($this->save_path);
-                }
-                session_module_name('files');
-                break;
-        }
-    }
-
-    /**
-     * @param array $keep_session_vars
-     */
-    function regenerate_session_id($keep_session_vars = [])
-    {
-        $keep = [];
-        if (is_array($_SESSION)) {
-            foreach (array_keys($_SESSION) as $k) {
-                if (in_array($k, $keep_session_vars)) {
-                    $keep[$k] = $_SESSION[$k];
-                }
-            }
-            $_SESSION = [];
-        }
-        $this->delete();
-        $this->start();
-        foreach ($keep_session_vars as $k) {
-            $_SESSION[$k] = $keep[$k] ?? null;
-        }
-    }
-
-    /**
-     * Delete the current session destroying all registered data.
-     *
-     * Note that it does more but the PHP 4 session_destroy it also
-     * throws away a cookie is there's one.
-     *
-     * @return boolean session_destroy return value
-     * @access public
-     */
-    function delete()
-    {
-        $cookie_params = session_get_cookie_params();
-        setCookie(
-            $this->name,
-            '',
-            0,
-            implode('/', array_map('rawurlencode', explode('/', $cookie_params['path']))),
-            $cookie_params['domain'],
-            $cookie_params['secure'],
-            $cookie_params['httponly']
-        );
-        $_COOKIE[$this->name] = "";
-        $_SESSION = [];
-        return session_destroy();
-    }
-
-    // the following functions used in session_set_save_handler
-
-    /**
-     * Open callback
-     *
-     */
-    function open()
-    {
-        return true;
-    }
-
-
-    /**
-     * Close callback
-     *
-     */
-    function close()
-    {
-        return true;
-    }
-
-
-    /**
-     * Delete callback
-     */
-    function del()
-    {
-        if ($this->module == 'user') {
-            $this->that->ac_delete($this->id, $this->name);
-        }
-        return true;
-    }
-
-
-    /**
-     * Write callback.
-     *
-     */
-    function freeze($id = null, $sess_data = null)
-    {
-        if ($this->module == 'user') {
-            if (!isset($sess_data)) {
-                $sess_data = session_encode();
-            }
-            $r = $this->that->ac_store($this->id, $this->name, $sess_data);
-            if (!$r) {
-                $this->that->ac_halt("Session: freeze() failed.");
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Read callback.
-     */
-    function thaw()
-    {
-        if ($this->module == 'user') {
-            return $this->that->ac_get_value(session_id(), $this->name) ?: '';
-        }
-        return '';
-    }
-
-    /**
-     * @return bool
-     */
-    function gc()
-    {
-        if ($this->module === 'user') {
-            //bail out if cronjob activated and not called in cli context
-            if (
-                Config::getInstance()->getValue('CRONJOBS_ENABLE')
-                && ($task = CronjobTask::findOneByClass(SessionGcJob::class))
-                && count($task->schedules->findBy('active', 1))
-                && PHP_SAPI !== 'cli'
-            ) {
-                return false;
-            }
-            if (empty($this->gc_time)) {
-                $this->gc_time = ini_get("session.gc_maxlifetime");
-            }
-            return (bool)$this->that->ac_gc($this->gc_time, $this->name);
-        }
-        return true;
-    }
-}
diff --git a/lib/phplib/page_open.php b/lib/phplib/page_open.php
deleted file mode 100644
index 699d50d7314b82ee1af183201cc9b190c245171f..0000000000000000000000000000000000000000
--- a/lib/phplib/page_open.php
+++ /dev/null
@@ -1,62 +0,0 @@
-<?php
-/*
-* Session Management for PHP3
-*
-* Copyright (c) 1998-2000 NetUSE AG
-*                    Boris Erdmann, Kristian Koehntopp
-*
-*
-*/
-
-function page_open($feature) {
-
-    # enable sess and all dependent features.
-    if (isset($feature["sess"])) {
-        $GLOBALS['sess'] = new $feature["sess"];
-        $GLOBALS['sess']->start();
-
-        # the auth feature depends on sess
-        if (isset($feature["auth"])) {
-
-            if (isset($_SESSION['auth'])) {
-                $_SESSION['auth'] = $_SESSION['auth']->check_feature($feature["auth"]);
-            } else {
-                $_SESSION['auth'] = new $feature["auth"];
-            }
-            $_SESSION['auth']->start();
-
-            $GLOBALS['auth'] =& $_SESSION['auth'];
-
-            # the perm feature depends on auth and sess
-            if (isset($feature["perm"])) {
-
-                if (!isset($GLOBALS['perm'])) {
-                    $GLOBALS['perm'] = new $feature["perm"];
-                }
-            }
-
-            # the user feature depends on auth and sess
-            if (isset($feature["user"])) {
-                if (!isset($GLOBALS['user'])) {
-                    $GLOBALS['user'] = new $feature["user"]($GLOBALS['auth']->auth["uid"]);
-                }
-            }
-        }
-    }
-}
-
-function page_close() {
-    try {
-        NotificationCenter::postNotification('PageCloseWillExecute', null);
-    } catch (NotificationVetoException $e) {
-        return;
-    }
-    if (is_object($GLOBALS['sess'])) {
-        @session_write_close();
-    }
-
-    if (is_object($GLOBALS['user'])) {
-        $GLOBALS['user']->set_last_action();
-    }
-    NotificationCenter::postNotification('PageCloseDidExecute', null);
-}
diff --git a/lib/phplib/prepend4.php b/lib/phplib/prepend4.php
deleted file mode 100644
index abe332958246065c03bc534ec6e3e0d1bd38f2f4..0000000000000000000000000000000000000000
--- a/lib/phplib/prepend4.php
+++ /dev/null
@@ -1,78 +0,0 @@
-<!doctype html>
-<html lang="de">
-  <head>
-    <meta charset="utf-8">
-    <title>Stud.IP-Konfigurationsfehler</title>
-    <script>
-      document.createElement("mark");
-    </script>
-    <style>
-      body {
-        margin: auto;
-        width: 40em;
-        font-family: Futura, "Century Gothic", AppleGothic, sans-serif;
-      }
-      p {
-        line-height: 1.5em;
-        margin-top: 1.5em;
-        margin-bottom: 1.5em;
-      }
-      mark {
-        display: inline-block;
-        background: #ffff88;
-        border: 1px dotted #888;
-        padding: 0.5em;
-      }
-      code {
-        font-family: "Monaco", "Courier New", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", monospace;
-        background-color: #f8f8ff;
-        padding: 0.5em;
-        line-height: 1.5em;
-      }
-      p code {
-        padding: 0;
-        background: none;
-      }
-    </style>
-  </head>
-  <body>
-    <h1>Fehler in der PHP-Konfiguration Ihrer Stud.IP-Installation</h1>
-    <p>
-        Für den Betrieb von Stud.IP bis zur Version 2.0 war es erforderlich,
-        die Konfiguration des PHP-Moduls Ihres Webservers anzupassen, damit
-        folgende Zeile – evtl. leicht verändert – enthalten ist:
-    </p>
-
-    <code>
-      auto_prepend_file = /usr/local/studip/lib/phplib/prepend4.php
-    </code>
-
-    <p>
-      Diese Konfigurationsoption wird für Stud.IP ab der Version 2.0 nicht mehr
-      verwendet.
-    </p>
-
-    <p>
-      <mark>
-      Entfernen Sie daher bitte die genannte Zeile aus Ihrer Konfiguration,
-      um diese Fehlermeldung zu verhindern.
-      </mark>
-      Typischerweise finden Sie diese in einer der folgenden Dateien:
-      <code>php.ini</code>, <code>httpd.conf</code> oder in einer
-      <code>.htaccess</code> Datei.
-    </p>
-
-    <p>
-      Wenn Sie dafür weitere Hilfe benötigen, besuchen Sie bitte das
-      Forum im
-      <a href="http://develop.studip.de/studip/seminar_main.php?auswahl=a70c45ca747f0ab2ea4acbb17398d370">Developer-Board</a>.
-    </p>
-
-    <p>
-      Vielen Dank, Ihre Stud.IP-CoreGroup
-    </p>
-
-  </body>
-</html>
-<?
-exit();
diff --git a/lib/plugins/core/StudIPPlugin.php b/lib/plugins/core/StudIPPlugin.php
index cbcac648c136ddae7d7971c9001ebd6c2d53439d..a747119c8a14b51550648675c570a20498584752 100644
--- a/lib/plugins/core/StudIPPlugin.php
+++ b/lib/plugins/core/StudIPPlugin.php
@@ -184,20 +184,35 @@ abstract class StudIPPlugin
     }
 
     /**
-     * This method dispatches all actions.
-     *
-     * @param string   part of the dispatch path that was not consumed
+     * This method formerly dispatches all actions. Left in place for
+     * compatibility
+     * @param string $unconsumed_path part of the dispatch path that was not consumed
      *
      * @return void
      */
     public function perform($unconsumed_path)
     {
+
+    }
+
+    /**
+     * This method returns callable for slim app
+     *
+     * @param string $unconsumed_path part of the dispatch path that was not consumed
+     *
+     * @return Closure
+     * @throws \Trails\Exceptions\UnknownAction
+     */
+    public function getRouteCallable(string $unconsumed_path): Closure
+    {
+        $this->perform($unconsumed_path);
         $args = explode('/', $unconsumed_path);
         $action = $args[0] !== '' ? array_shift($args).'_action' : 'show_action';
 
         if (!method_exists($this, $action)) {
             try {
-                app(PluginDispatcher::class, ['plugin' => $this])->dispatch($unconsumed_path);
+                $dispatcher = app(PluginDispatcher::class, ['plugin' => $this]);
+                return $dispatcher->getRouteCallable($unconsumed_path);
             } catch (Trails\Exceptions\UnknownAction $exception) {
                 if (count($args) > 0) {
                     throw $exception;
@@ -206,10 +221,16 @@ abstract class StudIPPlugin
                 }
             }
         } else {
-            call_user_func_array([$this, $action], $args);
+            $that = $this;
+            return function ($request, $response, array $otherargs) use ($action, $args, $that) {
+                ob_start();
+                call_user_func_array([$that, $action], $args);
+                $content = ob_get_contents();
+                $response->getBody()->write($content);
+                return $response;
+            };
         }
     }
-
     /**
      * Callback function called after enabling a plugin.
      * The plugin's ID is transmitted for convenience.
diff --git a/lib/plugins/engine/PluginManager.php b/lib/plugins/engine/PluginManager.php
index 919e175f65ee20c675add6ecdf767a4b772a0209..6177e00e8b7dd499e64c9ae2bcbb00c1dd54fdeb 100644
--- a/lib/plugins/engine/PluginManager.php
+++ b/lib/plugins/engine/PluginManager.php
@@ -609,7 +609,7 @@ class PluginManager
      */
     public function getPlugin ($class)
     {
-        $user = $GLOBALS['user']->id;
+        $user = $GLOBALS['user']->id ?? 'nobody' ;
         $plugin_info = $this->getPluginInfo($class);
         $plugin = null;
 
diff --git a/lib/seminar_open.php b/lib/seminar_open.php
deleted file mode 100644
index 4fb856fc7248e185f0e39e1e56b60ff7273458df..0000000000000000000000000000000000000000
--- a/lib/seminar_open.php
+++ /dev/null
@@ -1,266 +0,0 @@
-<?php
-# Lifter002: TODO
-# Lifter007: TODO
-# Lifter003: TODO
-# Lifter010: TODO
-/*
-seminar_open.php - Initialises a Stud.IP sesssion
-Copyright (C) 2000 Stefan Suchi <suchi@data-quest.de>
-
-This program is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-*/
-
-/**
- * @addtogroup notifications
- *
- * Logging in triggers a UserDidLogin notification. The user's ID is
- * transmitted as subject of the notification.
- */
-
-//redirect the user where he want to go today....
-function startpage_redirect($page_code) {
-    switch ($page_code) {
-        case 1:
-        case 2:
-            $jump_page = "dispatch.php/my_courses";
-        break;
-        case 3:
-            $jump_page = "dispatch.php/calendar/schedule";
-        break;
-        case 4:
-            $jump_page = "dispatch.php/contact";
-        break;
-        case 5:
-            $jump_page = "dispatch.php/calendar/calendar";
-        break;
-        case 6:
-            // redirect to global blubberstream
-            // or no redirection if blubber isn't active
-            if (Config::get()->BLUBBER_GLOBAL_MESSENGER_ACTIVATE) {
-                $jump_page = "dispatch.php/blubber";
-            }
-            break;
-        case 7:
-            $jump_page = "dispatch.php/contents/overview";
-            break;
-    }
-
-    page_close();
-    header ('Location: ' . URLHelper::getURL($jump_page));
-    exit;
-}
-
-global $i_page,
-       $SessionSeminar,
-       $sess, $auth, $user, $perm, $_language_path;
-
-//get the name of the current page in $i_page
-$i_page = basename($_SERVER['PHP_SELF']);
-
-//INITS
-$seminar_open_redirected = false;
-$user_did_login = false;
-
-// session init starts here
-if (empty($_SESSION['SessionStart']) || $_SESSION['SessionStart'] == 0) {
-    $_SESSION['SessionStart'] = time();
-    $_SESSION['object_cache'] = [];
-
-    // try to get accepted languages from browser
-    if (!isset($_SESSION['_language'])) {
-        $_SESSION['_language'] = get_accepted_languages();
-    }
-    if (!$_SESSION['_language']) {
-        $_SESSION['_language'] = Config::get()->DEFAULT_LANGUAGE;
-    }
-}
-
-// user init starts here
-if ($auth->is_authenticated() && is_object($user) && $user->id != "nobody") {
-    if ($_SESSION['SessionStart'] > UserConfig::get($user->id)->CURRENT_LOGIN_TIMESTAMP) {      // just logged in
-        // store old CURRENT_LOGIN in LAST_LOGIN and set CURRENT_LOGIN to start of session
-        UserConfig::get($user->id)->store('LAST_LOGIN_TIMESTAMP', UserConfig::get($user->id)->CURRENT_LOGIN_TIMESTAMP);
-        UserConfig::get($user->id)->store('CURRENT_LOGIN_TIMESTAMP', $_SESSION['SessionStart']);
-        //find current semester and store it in $_SESSION['_default_sem']
-        $current_sem = Semester::findDefault();
-        $_SESSION['_default_sem'] = $current_sem->semester_id;
-        //redirect user to another page if he want to, redirect is deferred to allow plugins to catch the UserDidLogin notification
-        if (UserConfig::get($user->id)->PERSONAL_STARTPAGE > 0 && $i_page == "index.php" && !$perm->have_perm("root")) {
-            $seminar_open_redirected = TRUE;
-        }
-        if (isset($_SESSION['contrast'])) {
-            UserConfig::get($GLOBALS['user']->id)->store('USER_HIGH_CONTRAST', $_SESSION['contrast']);
-            unset($_SESSION['contrast']);
-        }
-        // store last language click
-        if (!empty($_SESSION['forced_language'])) {
-            User::findCurrent()->preferred_language = $_SESSION['forced_language'];
-            User::findCurrent()->store();
-            $_SESSION['_language'] = $_SESSION['forced_language'];
-        }
-        $_SESSION['forced_language'] = null;
-        $user_did_login = true;
-    }
-
-    TwoFactorAuth::get()->secureSession();
-}
-
-if (!empty($_SESSION['contrast']) || UserConfig::get($GLOBALS['user']->id)->USER_HIGH_CONTRAST) {
-    PageLayout::addStylesheet('accessibility.css');
-}
-
-// init of output via I18N
-$_language_path = init_i18n($_SESSION['_language']);
-//force reload of config to get translated data
-include $GLOBALS['STUDIP_BASE_PATH'] . '/config/config.inc.php';
-
-// Try to select the course or institute given by the parameter 'cid'
-// in the current request.
-
-$course_id = (Request::int('cancel_login') && (!is_object($user) || $user->id === 'nobody'))
-           ? null
-           : Request::option('cid');
-
-// Select the current course or institute if we got one from 'cid' or session.
-// This also binds Context::getId()
-// to the URL parameter 'cid' for all generated links.
-if (isset($course_id)) {
-    Context::set($course_id);
-    unset($course_id);
-}
-
-if (Request::int('disable_plugins') !== null && ($user->id === 'nobody' || $perm->have_perm('root'))) {
-    // deactivate non-core plugins
-    PluginManager::getInstance()->setPluginsDisabled(Request::int('disable_plugins'));
-}
-
-// load the default set of plugins
-PluginEngine::loadPlugins();
-
-// add navigation item for profile: add modules
-if (Navigation::hasItem('/profile/edit')) {
-    $plus_nav = new Navigation(_('Mehr …'), 'dispatch.php/profilemodules/index');
-    $plus_nav->setDescription(_("Mehr Stud.IP-Funktionen für Ihr Profil"));
-    Navigation::addItem('/profile/modules', $plus_nav);
-}
-
-if ($user_did_login) {
-    if (isset($_SESSION[StudipAuthOAuth2::class]['redirect'])) {
-        $redirect = $_SESSION[StudipAuthOAuth2::class]['redirect'];
-        unset($_SESSION[StudipAuthOAuth2::class]);
-
-        page_close();
-        header('Location: ' . $redirect);
-        die;
-    }
-
-    NotificationCenter::postNotification('UserDidLogin', $user->id);
-}
-
-if (!Request::isXhr() && $perm->have_perm('root')) {
-    if (!isset($_SESSION['migration-check']) || $_SESSION['migration-check']['timestamp'] < time() - 5 * 60) {
-        $migrator = new Migrator(
-            "{$GLOBALS['STUDIP_BASE_PATH']}/db/migrations",
-            new DBSchemaVersion('studip')
-        );
-
-        $_SESSION['migration-check'] = [
-            'disabled'  => $_SESSION['migration-check']['disabled'] ?? false,
-            'timestamp' => time(),
-            'count'     => $migrator->pendingMigrations()
-        ];
-    }
-
-    if (Request::option('stop-migration-nag')) {
-        $_SESSION['migration-check']['disabled'] = true;
-    }
-
-    if (empty($_SESSION['migration-check']['disabled'])
-        && $_SESSION['migration-check']['count'] > 0
-    ) {
-        $info = sprintf(
-            _('Es gibt %u noch nicht ausgeführte Migration(en).'),
-            $_SESSION['migration-check']['count']
-        );
-
-        $message = MessageBox::info($info,[
-            sprintf(
-                _('Zur %sMigrationsseite%s'),
-                '<a class="link-intern" href="' . URLHelper::getLink('web_migrate.php') . '">',
-                '</a>'
-            ),
-            sprintf(
-                '<small><a href="%s">%s</a></small>',
-                URLHelper::getLink('', ['stop-migration-nag' => true]),
-                _('Diese Nachricht bis zum nächsten Login nicht mehr anzeigen')
-            )
-        ]
-        );
-        PageLayout::postMessage($message, 'migration-info');
-    }
-}
-
-if (
-    $GLOBALS['perm']->have_perm('root')
-    && Config::get()->MIGRATION_START_VERSION
-    && Config::get()->MIGRATION_START_VERSION < StudipVersion::getStudipVersion(true)
-    && !Config::get()->UPDATE_NEWS_SEEN
-) {
-    $message = MessageBox::info(
-        _('Sie haben ein Stud.IP-Update durchgeführt.'),
-        [
-            sprintf(
-                _('Zu den %sRelease-Notes%s'),
-                '<a class="link-intern" href="' . URLHelper::getLink('dispatch.php/root_assistant') . '" data-dialog>',
-                '</a>'
-            ),
-        ]
-    );
-    PageLayout::postMessage($message, 'release-notes');
-}
-
-if ($seminar_open_redirected) {
-    startpage_redirect(UserConfig::get($user->id)->PERSONAL_STARTPAGE);
-}
-
-// Show terms on first login
-if (is_object($GLOBALS['user'])
-    && $GLOBALS['user']->needsToAcceptTerms()
-    && !match_route('dispatch.php/terms')
-    && !match_route('dispatch.php/siteinfo/*'))
-{
-    if (!Request::isXhr()) {
-        header('Location: ' . URLHelper::getURL('dispatch.php/terms', ['return_to' => $_SERVER['REQUEST_URI'], 'redirect_token' => Token::create(600)], true));
-    } else {
-        throw new Trails\Exception(400);
-    }
-    page_close();
-    die;
-}
-
-if (
-    Config::get()->USER_VISIBILITY_CHECK
-    && is_object($GLOBALS['user'])
-    && $GLOBALS['user']->id !== 'nobody'
-    && !(
-        Config::get()->DOZENT_ALWAYS_VISIBLE
-        && $perm->get_perm() === 'dozent'
-    )
-    && !match_route('dispatch.php/siteinfo/*')
-) {
-    require_once('lib/user_visible.inc.php');
-    first_decision($GLOBALS['user']->id);
-}
diff --git a/lib/session/CacheSessionHandler.php b/lib/session/CacheSessionHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..c416ceb348e152586a50e3ba8242dd19186dd31c
--- /dev/null
+++ b/lib/session/CacheSessionHandler.php
@@ -0,0 +1,100 @@
+<?php
+/**
+ * Session handler for using Stud.IP Cache as session storage
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * @author      André Noack <noack@data-quest.de>
+ */
+namespace Studip\Session;
+
+class CacheSessionHandler implements \SessionHandlerInterface, \SessionIdInterface, \SessionUpdateTimestampHandlerInterface
+{
+
+    const CACHE_KEY_PREFIX = 'session_data';
+
+    private $session_lifetime = 7200;
+
+    private $cache;
+
+    public function __construct($session_lifetime = null)
+    {
+        if ($session_lifetime) {
+            $this->session_lifetime = $session_lifetime;
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function close(): bool
+    {
+        return true;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function destroy($id): bool
+    {
+        $cache_key = self::CACHE_KEY_PREFIX . '/' . $id;
+        $this->cache->expire($cache_key);
+        return true;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function gc($max_lifetime): int|false
+    {
+        return false;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function open($path, $name): bool
+    {
+        $this->cache = \Studip\Cache\Factory::getCache();
+        return true;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function read($id): string|false
+    {
+        $cache_key = self::CACHE_KEY_PREFIX . '/' . $id;
+        return $this->cache->read($cache_key);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function write($id, $data): bool
+    {
+        $cache_key = self::CACHE_KEY_PREFIX . '/' . $id;
+        return (bool)$this->cache->write($cache_key, $data, $this->session_lifetime);
+    }
+
+    public function create_sid(): string
+    {
+        do {
+            $new_id = md5(bin2hex(random_bytes(128)));
+        } while (!$this->read($new_id));
+        return $new_id;
+    }
+
+    public function updateTimestamp(string $id, string $data): bool
+    {
+        return $this->write($id, $data);
+    }
+
+    public function validateId(string $id): bool
+    {
+        return (bool)$this->read($id);
+    }
+}
diff --git a/lib/session/DbSessionHandler.php b/lib/session/DbSessionHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..05a31a486e6c705b1f06388722662dd58942b73b
--- /dev/null
+++ b/lib/session/DbSessionHandler.php
@@ -0,0 +1,114 @@
+<?php
+/**
+ * Session handler for using Stud.IP database as session storage
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * @author      André Noack <noack@data-quest.de>
+ */
+
+namespace Studip\Session;
+use \DBManager, \Config, \CronjobTask;
+
+class DbSessionHandler implements \SessionHandlerInterface, \SessionIdInterface, \SessionUpdateTimestampHandlerInterface
+{
+
+    private $exists;
+
+    /**
+     * @inheritDoc
+     */
+    public function close(): bool
+    {
+        return true;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function destroy($id): bool
+    {
+        return (bool)DBManager::get()->execute("DELETE FROM session_data WHERE sid = ? LIMIT 1", [$id]);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function gc($max_lifetime): false|int
+    {
+        //bail out if cronjob activated and not called in cli context
+        if (Config::getInstance()->getValue('CRONJOBS_ENABLE')
+            && ($task = array_pop(CronjobTask::findByClass('SessionGcJob')))
+            && count($task->schedules->findBy('active', 1))
+            && PHP_SAPI !== 'cli'
+        ) {
+            return false;
+        }
+        return DBManager::get()->execute("DELETE FROM session_data WHERE changed < FROM_UNIXTIME(?) ", [time() - $max_lifetime]);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function open($path, $name): bool
+    {
+        return true;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    #[\ReturnTypeWillChange]
+    public function read($id)
+    {
+        $str = DBManager::get()->fetchColumn("SELECT val FROM session_data where sid  = ?", [$id]);
+        if ($str) {
+            $this->exists = $id;
+        }
+        return (string)$str;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function write($id, $data): bool
+    {
+        $db = DBManager::get();
+        if ($this->exists === $id) {
+            $stmt = $db->prepare("UPDATE session_data SET val = ? WHERE sid = ?");
+        } else {
+            $stmt = $db->prepare("REPLACE INTO session_data ( val, sid ) VALUES (?, ?)");
+        }
+        return (bool) $stmt->execute([$data, $id]);
+    }
+
+    public function exists($id)
+    {
+        return (bool)DBManager::get()->fetchColumn("SELECT 1 FROM session_data where sid  = ?", [$id]);
+    }
+
+    public function create_sid(): string
+    {
+        do {
+            $new_id = md5(bin2hex(random_bytes(128)));
+        } while ($this->exists($new_id));
+        $this->exists = null;
+        return $new_id;
+    }
+
+    public function updateTimestamp(string $id, string $data): bool
+    {
+        DBManager::get()->execute("UPDATE session_data SET changed = CURRENT_TIMESTAMP() WHERE sid = ?", [$id]);
+        return true;
+    }
+
+    public function validateId(string $id): bool
+    {
+        return (bool)$this->exists($id);
+    }
+
+
+}
diff --git a/lib/session/Manager.php b/lib/session/Manager.php
new file mode 100644
index 0000000000000000000000000000000000000000..ea95dbea469357ff61afee8ad246bd520fc396bc
--- /dev/null
+++ b/lib/session/Manager.php
@@ -0,0 +1,229 @@
+<?php
+/**
+ * Session manager for Stud.IP
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * @author      André Noack <noack@data-quest.de>
+ */
+namespace Studip\Session;
+
+
+class Manager
+{
+    /**
+     * @var \SessionHandlerInterface
+     */
+    protected \SessionHandlerInterface $handler;
+    /**
+     * @var array
+     */
+    protected array $options = [
+        'name' => 'Seminar_Session',
+        'lifetime' => 7200,
+        'path' => null,
+        'domain' => null,
+        'secure' => false,
+        'httponly' => true,
+        'samesite' => 'Lax',
+        'cache_limiter' => 'nocache'
+    ];
+    /**
+     * @var null
+     */
+    protected $current_session_state = null;
+
+
+    /**
+     * @param \SessionHandlerInterface $session_handler
+     * @param array $session_options
+     */
+    public function __construct(\SessionHandlerInterface $session_handler, array $session_options = [])
+    {
+        $this->handler = $session_handler;
+        $keys = array_keys($this->options);
+        foreach ($keys as $key) {
+            if (array_key_exists($key, $session_options)) {
+                $this->options[$key] = $session_options[$key];
+                if ($key === 'path') {
+                    $this->options[$key]  = implode('/', array_map('rawurlencode', explode('/', $this->options[$key] )));
+                }
+            }
+        }
+    }
+
+    /**
+     * @return void
+     */
+    public function start(): void
+    {
+        if (!$this->isStarted()) {
+
+            ini_set('session.use_strict_mode', 1);
+            $current = session_get_cookie_params();
+
+            $lifetime = (int)($this->options['lifetime'] ?: $current['lifetime']);
+            $path = $this->options['path'] ?: $current['path'];
+            $domain = $this->options['domain'] ?: $current['domain'];
+            $samesite = $this->options['samesite'] ?: $current['samesite'];
+            $secure = (bool)$this->options['secure'];
+            $httponly = (bool)$this->options['httponly'];
+
+            session_set_cookie_params(compact('lifetime', 'path', 'domain', 'secure', 'samesite', 'httponly'));
+            session_name($this->options['name']);
+            session_cache_limiter('nocache');
+            session_set_save_handler($this->handler, true);
+
+            session_start();
+        }
+    }
+
+    /**
+     * @return bool
+     */
+    public function isStarted(): bool
+    {
+        return session_status() === PHP_SESSION_ACTIVE;
+    }
+
+    /**
+     * @param array $keep_session_vars
+     * @return void
+     */
+    public function regenerateId(array $keep_session_vars = []): void
+    {
+        if (!$this->isStarted()) {
+            return;
+        }
+
+        $keep = [];
+        if (is_array($_SESSION)) {
+            foreach (array_keys($_SESSION) as $k) {
+                if (in_array($k, $keep_session_vars)) {
+                    $keep[$k] = $_SESSION[$k];
+                }
+            }
+            $_SESSION = [];
+        }
+        session_regenerate_id(true);
+
+        foreach ($keep_session_vars as $k) {
+            $_SESSION[$k] = $keep[$k] ?? null;
+        }
+    }
+
+    /**
+     * @return string
+     */
+    public function getName(): string
+    {
+        return $this->options['name'];
+    }
+
+    /**
+     * @return void
+     */
+    public function destroy(): void
+    {
+        if (!$this->isStarted()) {
+            return;
+        }
+
+        if (ini_get('session.use_cookies')) {
+            $params = session_get_cookie_params();
+            setcookie(
+                $this->getName(),
+                '',
+                time() - 42000,
+                $params['path'],
+                $params['domain'],
+                $params['secure'],
+                $params['httponly']
+            );
+        }
+        $_COOKIE[$this->getName()] = '';
+        session_unset();
+        session_destroy();
+    }
+
+    /**
+     * @return void
+     */
+    public function save() : void
+    {
+        session_write_close();
+    }
+
+    /**
+     * Returns true, if the current session is valid and belongs to an
+     * authenticated user. Does not start a session.
+     *
+     * @static
+     * @return bool
+     */
+    public function isCurrentSessionAuthenticated(): bool
+    {
+        return self::getCurrentSessionState() === 'authenticated';
+    }
+
+    /**
+     * Returns the state of the current session. Does not start a session.
+     * possible return values:
+     * 'authenticated' - session is valid and user is authenticated
+     * 'nobody' - session is valid, but user is not authenticated
+     * false - no valid session
+     *
+     * @static
+     * @return string|false
+     */
+    public function getCurrentSessionState(): false|string|null
+    {
+
+        if (!is_null($this->current_session_state)) {
+            return $this->current_session_state;
+        }
+        $state = false;
+        if (isset($GLOBALS['user']) && is_object($GLOBALS['user'])) {
+            $state = in_array($GLOBALS['user']->id, ['nobody', 'form']) ? 'nobody' : 'authenticated';
+        } else {
+            $sid = $_COOKIE[$this->getName()];
+            if ($sid) {
+                $session_vars = $this->getSessionVars($sid);
+                $session_auth = $session_vars['auth'];
+                if ($session_auth['uid'] && !in_array($session_auth['uid'], ['nobody', 'form'])) {
+                    $state = 'authenticated';
+                } else {
+                    $state = in_array($session_auth['uid'], ['nobody', 'form']) ? 'nobody' : false;
+                }
+            }
+        }
+        return ($this->current_session_state = $state);
+    }
+
+    /**
+     * returns a SessionDecoder object containing the session variables
+     * for the given session id
+     *
+     * @static
+     * @param string $sid a session id
+     * @return \SessionDecoder
+     */
+    public function getSessionVars($sid): \SessionDecoder
+    {
+        $data = $this->handler->read($sid);
+        return new \SessionDecoder($data);
+    }
+
+    /**
+     * force garbage collect
+     *
+     * @return void
+     */
+    public function doGarbageCollect(): void
+    {
+        $this->handler->gc($this->options['lifetime']);
+    }
+}
diff --git a/lib/user_visible.inc.php b/lib/user_visible.inc.php
index 5de2a073664568a2a9798aaa8c996876163c24df..3bfb7d9b0924e12d0062c279a7eb84b38e1d529e 100644
--- a/lib/user_visible.inc.php
+++ b/lib/user_visible.inc.php
@@ -255,9 +255,7 @@ function first_decision($userid) {
     $template = $GLOBALS['template_factory']->open("../locale/$user_language/LC_HELP/visibility_decision.php");
     $template->set_layout('layouts/base.php');
 
-    echo $template->render();
-    page_close();
-    die;
+    return  $template->render();
 }
 
 
diff --git a/locale/de/LC_HELP/visibility_decision.php b/locale/de/LC_HELP/visibility_decision.php
index 918370ec77105e7cf97ae35680135f18e86a2fb3..fb0db7e03b0bdf9c32376967e06b4b5596ff34ce 100644
--- a/locale/de/LC_HELP/visibility_decision.php
+++ b/locale/de/LC_HELP/visibility_decision.php
@@ -27,7 +27,7 @@
                     </ul>
                 </td>
             </tr>
-            
+
             <tr>
                 <td style="background:#ddffdd; border:1px solid #d0d7e3;" valign="top">
                     <p><b>Wenn Sie sichtbar sind, dann</b></p>
@@ -67,7 +67,7 @@
             <tr>
                 <td colspan="3">
                     <p>
-                        Weitere Informationen entnehme Sie bitte den 
+                        Weitere Informationen entnehme Sie bitte den
                         <a href="<?=URLHelper::getURL(Config::get()->PRIVACY_URL, ['cancel_login' => 1], true) ?>" target="_bank">Datenschutzerklärungen</a>.
                     </p>
                 </td>
diff --git a/public/activate_email.php b/public/activate_email.php
deleted file mode 100644
index 25e70f31b4a9939e55d20f18ada85fb41cae052f..0000000000000000000000000000000000000000
--- a/public/activate_email.php
+++ /dev/null
@@ -1,124 +0,0 @@
-<?php
-# Lifter007: TODO
-# Lifter003: TODO
-# Lifter010: TODO
-
-$_GET['cancel_login'] = '1';
-
-require '../lib/bootstrap.php';
-
-use Studip\Button, Studip\LinkButton;
-
-ob_start();
-
-page_open(['sess' => 'Seminar_Session', 'auth' => 'Seminar_Default_Auth', 'perm' => 'Seminar_Perm', 'user' => 'Seminar_User']);
-
-function head($headline, $red=False) {
-    echo sprintf('<h1>%s</h1>', $headline);
-}
-
-function footer() {
-}
-
-function reenter_mail() {
-    echo '<br>';
-    echo '<form action="' . URLHelper::getLink() . '" method="post" class="default">';
-    echo '<fieldset>';
-    echo '<legend>'._('Sollten Sie keine E-Mail erhalten haben, können Sie sich einen neuen Aktivierungsschlüssel zuschicken lassen. Geben Sie dazu Ihre gewünschte E-Mail-Adresse unten an') . '</legend>'
-        . CSRFProtection::tokenTag()
-        .'<input type="hidden" name="uid" value="'. htmlReady(Request::option('uid')) .'">'
-        .'<label>' . _('E-Mail')
-        .'<input type="email" name="email1" required>'
-        .'</label>'
-        .'<label>' . _('Wiederholung')
-        .'<input type="email" name="email2" required>'
-        .'</label>';
-    echo '</fieldset>';
-    echo '<footer>' . Button::createAccept() . '</footer>';
-    echo '</form>';
-}
-
-function mail_explain() {
-    echo '<form action="' . URLHelper::getLink() . '" method="post" class="default">';
-    echo '<fieldset>';
-    echo '<legend>' .  _('Sie haben Ihre E-Mail-Adresse geändert.
-    Um diese frei zu schalten müssen Sie den Ihnen an Ihre neue Adresse zugeschickten Aktivierungs Schlüssel im unten stehenden Eingabefeld eintragen.') . '</legend>';
-    echo CSRFProtection::tokenTag();
-    echo '<label>' . _('Aktivierungs Schlüssel')
-        .'<input type="text" name="key"><input name="uid" type="hidden" value="'.htmlReady(Request::option('uid')).'">';
-    echo '</fieldset>';
-    echo '<footer>' . Button::createAccept() . '</footer>';
-    echo '</form>';
-
-}
-
-if(!Request::option('uid'))
-    header("Location: index.php");
-
-URLHelper::addLinkParam('cancel_login', 1);
-
-// set up user session
-include 'lib/seminar_open.php';
-
-// display header
-PageLayout::setTitle(_('E-Mail Aktivierung'));
-
-$uid = Request::option('uid');
-if(Request::get('key') !== null) {
-
-    $db = DBManager::get();
-    $sth = $db->prepare("SELECT validation_key FROM auth_user_md5 WHERE user_id=?");
-    $sth->execute([$uid]);
-    $result = $sth->fetch();
-    $key = $result['validation_key'];
-
-    if(Request::get('key') == $key) {
-        $sth = $db->prepare("UPDATE auth_user_md5 SET validation_key='' WHERE user_id=?");
-        $sth->execute([$uid]);
-        unset($_SESSION['semi_logged_in']);
-        head(PageLayout::getTitle());
-        PageLayout::postSuccess(_('Ihre E-Mail-Adresse wurde erfolgreich geändert.'));
-        printf(' <a href="' . URLHelper::getLink('index.php') . '">%s</a>', _('Zum Login'));
-    } else if ($key == '') {
-        head(PageLayout::getTitle());
-        PageLayout::postInfo(_('Ihre E-Mail-Adresse ist bereits geändert.'));
-        printf(' <a href="' . URLHelper::getLink('index.php') . '">%s</a>', _('Zum Login'));
-    } else {
-        if (Request::get('key')) {
-            PageLayout::postError(_("Falscher Bestätigungscode."));
-        }
-        head(PageLayout::getTitle());
-        mail_explain();
-        if($_SESSION['semi_logged_in'] == Request::option('uid')) {
-            reenter_mail();
-        } else {
-            printf(_('Sie können sich %seinloggen%s und sich den Bestätigungscode neu oder an eine andere E-Mail-Adresse schicken lassen.'),
-                    '<a href="' . URLHelper::getLink('index.php?again=yes') . '">', '</a>');
-        }
-    }
-
-// checking semi_logged_in is important to avoid abuse
-} else if(Request::get('email1') && Request::get('email2') && $_SESSION['semi_logged_in'] == Request::option('uid')) {
-    if(Request::get('email1') == Request::get('email2')) {
-        // change mail
-        $tmp_user = User::find(Request::option('uid'));
-        if($tmp_user && $tmp_user->changeEmail(Request::get('email1'), true)) {
-            $_SESSION['semi_logged_in'] = False;
-        }
-
-    } else {
-        PageLayout::postError(_('Die eingegebenen E-Mail-Adressen stimmen nicht überein. Bitte überprüfen Sie Ihre Eingabe.'));
-    }
-    mail_explain();
-    reenter_mail();
-} else {
-    // this never happens unless someone manipulates urls (or the presented link within the mail is broken)
-    head(PageLayout::getTitle());
-    mail_explain();
-    reenter_mail();
-}
-
-$template = $GLOBALS['template_factory']->open('layouts/base.php');
-$template->content_for_layout = ob_get_clean();
-echo $template->render();
-page_close();
diff --git a/public/datenschutz.php b/public/datenschutz.php
deleted file mode 100644
index 0eb134f5c99da123f80b94164375868a560e6756..0000000000000000000000000000000000000000
--- a/public/datenschutz.php
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-/**
- * datenschutz.php
- *
- * privacy guidelines for Stud.IP
- *
- * PHP version 5
- *
- * @author      Elmar Ludwig
- * @author      Michael Riehemann <michael.riehemann@uni-oldenburg.de>
- * @copyright   2009 Stud.IP
- * @license     http://www.gnu.org/licenses/gpl.html GPL Licence 3
- * @package     studip_core
- * @access      public
- */
-
-require '../lib/bootstrap.php';
-
-page_open([
-    'sess' => 'Seminar_Session',
-    'auth' => 'Seminar_Default_Auth',
-    'perm' => 'Seminar_Perm',
-    'user' => 'Seminar_User'
-]);
-
-// set up user session
-include 'lib/seminar_open.php';
-
-// this page must be accessible during visibility decision
-Config::get()->USER_VISIBILITY_CHECK = false;
-
-PageLayout::setTitle(_('Erläuterungen zum Datenschutz'));
-
-$template = $template_factory->open('privacy');
-$template->set_layout('layouts/base.php');
-
-echo $template->render();
diff --git a/public/dispatch.php b/public/dispatch.php
index 77c593310f9d94edcab0c568fe5eb97583b63b2f..8bc346df8be84175455cbe60ba64585149ae4195 100644
--- a/public/dispatch.php
+++ b/public/dispatch.php
@@ -1,8 +1,6 @@
 <?php
-# Lifter002: TODO
-# Lifter007: TODO
-# Lifter003: TODO
-# Lifter010: TODO
+use Slim\App;
+use Slim\Factory\AppFactory;
 
 /*
  * index.php - <short-description>
@@ -21,5 +19,17 @@ require '../lib/bootstrap.php';
 // prepare environment
 URLHelper::setBaseUrl($GLOBALS['ABSOLUTE_URI_STUDIP']);
 
-$dispatcher = app(\Trails\Dispatcher::class);
-$dispatcher->dispatch(Request::pathInfo());
+// Build PHP_DI Container
+$container = app();
+
+// Instantiate the app
+AppFactory::setContainer($container);
+$app = AppFactory::create();
+$container->set(App::class, $app);
+$app->setBasePath($GLOBALS['CANONICAL_RELATIVE_PATH_STUDIP'] . 'dispatch.php');
+
+$studip_dispatcher = app(\Trails\Dispatcher::class);
+$route_callable = $studip_dispatcher->getRouteCallable(Request::pathInfo());
+$app->any(Request::pathInfo(), $route_callable);
+NotificationCenter::postNotification('SLIM_BEFORE_RUN', $app);
+$app->run();
diff --git a/public/email_validation.php b/public/email_validation.php
deleted file mode 100644
index f877aee0afc00c7b57de97c07bbfceff7b51616a..0000000000000000000000000000000000000000
--- a/public/email_validation.php
+++ /dev/null
@@ -1,108 +0,0 @@
-<?php
-# Lifter002: TEST
-# Lifter003: TEST
-# Lifter007: TEST
-# Lifter010: DONE - not applicable
-/*
-email_validation.php - Hochstufung eines user auf Status autor, wenn erfolgreich per Mail zurueckgemeldet
-Copyright (C) 2001 Stefan Suchi <suchi@gmx.de>
-
-This program is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-*/
-
-require '../lib/bootstrap.php';
-
-page_open([
-    'sess' => 'Seminar_Session',
-    'auth' => 'Seminar_Auth',
-    'perm' => 'Seminar_Perm',
-    'user' => 'Seminar_User'
-]);
-$auth->login_if($auth->auth['uid'] == 'nobody');
-$perm->check('user');
-// nobody hat hier nix zu suchen...
-
-include 'lib/seminar_open.php'; // initialise Stud.IP-Session
-
-// hier wird noch mal berechnet, welches secret in der Bestaetigungsmail uebergeben wurde
-$secret = Request::option('secret');
-PageLayout::setHelpKeyword('Basis.AnmeldungMail');
-PageLayout::setTitle(_('Bestätigung der E-Mail-Adresse'));
-
-//user bereits vorhanden
-if ($perm->have_perm('autor')) {
-    $info = sprintf(_('Sie haben schon den Status <b>%s</b> im System.
-                       Eine Aktivierung des Accounts ist nicht mehr nötig, um Schreibrechte zu bekommen'), $auth->auth['perm']);
-    $details = [];
-    $details[] = sprintf('<a href="%s">%s</a>', URLHelper::getLink('index.php'), _('zurück zur Startseite'));
-    $message = MessageBox::info($info, $details);
-}
-
-//  So, wer bis hier hin gekommen ist gehoert zur Zielgruppe...
-// Volltrottel (oder abuse)
-else if (empty($secret)) {
-    $message = MessageBox::error(_('Sie müssen den vollständigen Link aus der Bestätigungsmail in die Adresszeile Ihres Browsers kopieren.'));
-}
-
-// abuse (oder Volltrottel)
-else if (!Seminar_Register_Auth::validateSecret($secret, $user->id)) {
-    $error = _('Der übergebene <em>Secret-Code</em> ist nicht korrekt.');
-    $details = [];
-    $details[] = _('Sie müssen unter dem Benutzernamen eingeloggt sein, für den Sie die Bestätigungsmail erhalten haben.');
-    $details[] = _('Und Sie müssen den vollständigen Link aus der Bestätigungsmail in die Adresszeile Ihres Browsers kopieren.');
-    $message = MessageBox::error($error, $details);
-
-    // Mail an abuse
-    $REMOTE_ADDR=getenv("REMOTE_ADDR");
-    $Zeit=date("H:i:s, d.m.Y",time());
-    $username = $auth->auth["uname"];
-    StudipMail::sendAbuseMessage("Validation", "Secret falsch\n\nUser: $username\n\nIP: $REMOTE_ADDR\nZeit: $Zeit\n");
-}
-
-// alles paletti, Status ändern
-else {
-    $studip_user = User::findCurrent();
-    $studip_user->perms = 'autor';
-    if (!$studip_user->store()) {
-        $error = _('Fehler! Bitte wenden Sie sich an den Systemadministrator.');
-        $details = [$query];
-        $message = MessageBox::error($error, $details);
-    } else {
-        $success = _('Ihr Status wurde erfolgreich auf <em>autor</em> gesetzt.<br>
-                      Damit dürfen Sie in den meisten Veranstaltungen schreiben, für die Sie sich anmelden.');
-        $details = [];
-        $details[] = _('Einige Veranstaltungen erfordern allerdings bei der Anmeldung die Eingabe eines Passwortes.
-                        Dieses Passwort erfahren Sie von den Lehrenden der Veranstaltung.');
-        $message = MessageBox::success($success, $details);
-
-        // Auto-Inserts
-        AutoInsert::instance()->saveUser($user->id, "autor");
-
-        $auth->logout();    // einen Logout durchführen, um erneuten Login zu erzwingen
-
-        $info = sprintf(_('Die Statusänderung wird erst nach einem erneuten %sLogin%s wirksam!<br>
-                          Deshalb wurden Sie jetzt automatisch ausgeloggt.'),
-                        '<a href="index.php?again=yes"><em>',
-                        '</em></a>');
-        $message .= MessageBox::info($info);
-    }
-}
-
-$template = $GLOBALS['template_factory']->open('email-validation');
-$template->set_layout($GLOBALS['template_factory']->open('layouts/base.php'));
-$template->message = $message;
-echo $template->render();
-
-page_close();
diff --git a/public/index.php b/public/index.php
index abba9015187173857f3f442fd5e69e99c459983e..19772338f24fcf38f72c85e0946da61b29f4865d 100644
--- a/public/index.php
+++ b/public/index.php
@@ -19,12 +19,5 @@
 
 require '../lib/bootstrap.php';
 
-page_open(['sess' => 'Seminar_Session', 'auth' => 'Seminar_Default_Auth', 'perm' => 'Seminar_Perm', 'user' => 'Seminar_User']);
+header('Location: ' . URLHelper::getURL('dispatch.php/start'));
 
-$auth->login_if($user->id === 'nobody');
-include 'lib/seminar_open.php'; // initialise Stud.IP-Session
-
-// if new start page is in use, redirect there (if logged in)
-if ($auth->is_authenticated() && $user->id != 'nobody') {
-    header('Location: ' . URLHelper::getURL('dispatch.php/start'));
-}
diff --git a/public/jsonapi.php b/public/jsonapi.php
index 4b2ba4bfef3676f1efb9f90398568d8b15a7f2a1..8d69b5108e2e716a4edf53e7e39135f3c0ed9366 100644
--- a/public/jsonapi.php
+++ b/public/jsonapi.php
@@ -9,13 +9,6 @@ require '../lib/bootstrap.php';
 // Set base url for URLHelper class
 URLHelper::setBaseUrl($GLOBALS['CANONICAL_RELATIVE_PATH_STUDIP']);
 
-page_open([
-    'sess' => 'Seminar_Session',
-    'auth' => 'Seminar_Default_Auth',
-    'perm' => 'Seminar_Perm',
-    'user' => 'Seminar_User',
-]);
-
 // Instantiate the app
 $container = app();
 AppFactory::setContainer($container);
@@ -29,6 +22,11 @@ $app->setBasePath($GLOBALS['CANONICAL_RELATIVE_PATH_STUDIP'] . 'jsonapi.php');
 $middleware = require 'lib/classes/JsonApi/middleware.php';
 $middleware($app);
 
+//register stud.ip session/auth middleware
+$app->add(app(Studip\Middleware\AuthenticationMiddleware::class));
+auth()->setNobody(true);
+$app->add(app(Studip\Middleware\SessionMiddleware::class));
+
 // Register routes
 $routes = require 'lib/classes/JsonApi/routes.php';
 $routes($app);
diff --git a/public/logout.php b/public/logout.php
index 6f941949c4cc5cf585d92c726484a0d6859f94cc..aee2701eea893560a34fcd1247d455a2daf4d7ca 100644
--- a/public/logout.php
+++ b/public/logout.php
@@ -25,69 +25,4 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
 require '../lib/bootstrap.php';
 
-page_open(["sess" => "Seminar_Session", "auth" => "Seminar_Default_Auth", "perm" => "Seminar_Perm", "user" => "Seminar_User"]);
-
-require_once 'lib/messaging.inc.php';
-
-// Redirect to index page if request is not a post request or logout ticket is
-// missing
-if (
-    !Request::isPost()
-    && !(
-        isset($_SESSION['logout_ticket'])
-        && check_ticket($_SESSION['logout_ticket'])
-    )
-) {
-    header('Location: ' . URLHelper::getURL('index.php'));
-    page_close();
-    die;
-}
-
-//nur wenn wir angemeldet sind sollten wir dies tun!
-if ($auth->auth['uid'] !== 'nobody') {
-    $my_messaging_settings = $GLOBALS['user']->cfg->MESSAGING_SETTINGS;
-
-    //Wenn Option dafuer gewaehlt, alle ungelsesenen Nachrichten als gelesen speichern
-    if ($my_messaging_settings["logout_markreaded"]) {
-        Message::markAllAs();
-    }
-
-    $logout_user = $user->id;
-    $_language = $_SESSION['_language'];
-    $contrast = UserConfig::get($GLOBALS['user']->id)->USER_HIGH_CONTRAST;
-
-    // Get auth plugin of user before logging out since the $auth object will
-    // be modified by the logout
-    $auth_plugin = StudipAuthAbstract::getInstance($auth->auth['auth_plugin']);
-
-    //Logout aus dem Sessionmanagement
-    $auth->logout();
-    $sess->delete();
-
-    page_close();
-
-    //Session changed zuruecksetzen
-    $timeout=(time()-(15 * 60));
-    $user->set_last_action($timeout);
-
-    $sess->start();
-    $_SESSION['_language'] = $_language;
-    if ($contrast) {
-        $_SESSION['contrast'] = $contrast;
-    }
-
-    PageLayout::postSuccess(
-        _('Sie sind nun aus dem System abgemeldet.'),
-        array_filter([$GLOBALS['UNI_LOGOUT_ADD']])
-    );
-
-    // Perform logout from auth plugin (if possible)
-    if ($auth_plugin instanceof StudipAuthSSO) {
-        $auth_plugin->logout();
-    }
-} else {
-    $sess->delete();
-    page_close();
-}
-
-header('Location: ' . URLHelper::getURL('index.php?logout=1'));
+header('Location: ' . URLHelper::getURL('dispatch.php/logout'));
diff --git a/public/plugins.php b/public/plugins.php
index 99373a053fd58691eb6e4e2bf5dff09b3f1ba18b..176ccfa98f24ca14176ecffaa1022c054bfecc90 100644
--- a/public/plugins.php
+++ b/public/plugins.php
@@ -1,6 +1,4 @@
 <?php
-# Lifter007: TEST
-
 /*
  * Copyright (C) 2007 - Marcus Lunzenauer <mlunzena@uos.de>
  *
@@ -10,61 +8,74 @@
  * the License, or (at your option) any later version.
  */
 
+use Psr\Http\Message\ResponseFactoryInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Slim\App;
+use Slim\Factory\AppFactory;
+use Psr\Http\Server\RequestHandlerInterface;
+
 require '../lib/bootstrap.php';
 
-// set base url for URLHelper class
-URLHelper::setBaseUrl($GLOBALS['CANONICAL_RELATIVE_PATH_STUDIP']);
+// prepare environment
+URLHelper::setBaseUrl($GLOBALS['ABSOLUTE_URI_STUDIP']);
 
-// initialize Stud.IP-Session
-page_open([
-    'sess' => 'Seminar_Session',
-    'auth' => 'Seminar_Default_Auth',
-    'perm' => 'Seminar_Perm',
-    'user' => 'Seminar_User',
-]);
+// Build PHP_DI Container
+$container = app();
 
-try {
-    require_once 'lib/seminar_open.php';
+// Instantiate the app
+AppFactory::setContainer($container);
+$app = AppFactory::create();
+$container->set(App::class, $app);
+$app->setBasePath($GLOBALS['CANONICAL_RELATIVE_PATH_STUDIP'] . 'plugins.php');
+$plugin_dispatch = function (ServerRequestInterface $request, RequestHandlerInterface $handler) use ($app) {
+    $responseFactory = app(ResponseFactoryInterface::class);
+    try {
+        // get plugin class from request
+        $dispatch_to = Request::pathInfo();
+        list($plugin_class, $unconsumed) = PluginEngine::routeRequest($dispatch_to);
 
-    // get plugin class from request
-    $dispatch_to = Request::pathInfo();
-    list($plugin_class, $unconsumed) = PluginEngine::routeRequest($dispatch_to);
+        // handle legacy forum plugin URLs
+        if ($plugin_class === 'coreforum') {
+            $response = $responseFactory->createResponse(302);
+            return $response->withHeader('Location', URLHelper::getURL('dispatch.php/course/forum/' . $unconsumed));
+        }
 
-    // handle legacy forum plugin URLs
-    if ($plugin_class === 'coreforum') {
-        header('Location: ' . URLHelper::getURL('dispatch.php/course/forum/' . $unconsumed));
-        die();
-    }
+        // retrieve corresponding plugin info
+        $plugin_manager = PluginManager::getInstance();
+        $plugin_info = $plugin_manager->getPluginInfo($plugin_class);
 
-    // retrieve corresponding plugin info
-    $plugin_manager = PluginManager::getInstance();
-    $plugin_info = $plugin_manager->getPluginInfo($plugin_class);
+        // create an instance of the queried plugin
+        $plugin = PluginEngine::getPlugin($plugin_class);
 
-    // create an instance of the queried plugin
-    $plugin = PluginEngine::getPlugin($plugin_class);
+        // user is not permitted, show login screen
+        if (is_null($plugin)) {
+            // TODO (mlunzena) should not getPlugin throw this exception?
+            throw new AccessDeniedException(_('Sie besitzen keine Rechte zum Aufruf dieses Plugins.'));
+        }
 
-    // user is not permitted, show login screen
-    if (is_null($plugin)) {
-        // TODO (mlunzena) should not getPlugin throw this exception?
-        throw new AccessDeniedException(_('Sie besitzen keine Rechte zum Aufruf dieses Plugins.'));
-    }
+        // set default page title
+        PageLayout::setTitle($plugin->getPluginName());
 
-    // set default page title
-    PageLayout::setTitle($plugin->getPluginName());
+        // deprecated, the plugin should override perform() instead
+        if (is_callable([$plugin, 'initialize'])) {
+            $plugin->initialize();
+        }
 
-    // deprecated, the plugin should override perform() instead
-    if (is_callable([$plugin, 'initialize'])) {
-        $plugin->initialize();
+        $route_callable = $plugin->getRouteCallable($unconsumed);
+        $app->any(Request::pathInfo(), $route_callable);
+    } catch (AccessDeniedException $ade) {
+        $_SESSION['redirect_after_login'] = Request::url();
+        $response = $responseFactory->createResponse(302);
+        return $response->withHeader('Location', URLHelper::getURL('dispatch.php/login'));
     }
+    return $handler->handle($request);
+};
 
-    // let the show begin
-    $plugin->perform($unconsumed);
-} catch (AccessDeniedException $ade) {
-    global $auth;
-
-    $auth->login_if($auth->auth['uid'] == 'nobody');
-    throw $ade;
-}
+$app->add($plugin_dispatch);
+$app->add(app(Studip\Middleware\SeminarOpenMiddleware::class));
+$app->add(app(Studip\Middleware\AuthenticationMiddleware::class));
+auth()->setNobody(true);
+$app->add(app(Studip\Middleware\SessionMiddleware::class));
 
-// close the page
-page_close();
+NotificationCenter::postNotification('SLIM_BEFORE_RUN', $app);
+$app->run();
diff --git a/public/seminar_main.php b/public/seminar_main.php
index 831f1b47e4467972a1e5d025cc07f645c0e1093e..a4ac2fe0c9ad24910442b61601ea4283597ef1ac 100644
--- a/public/seminar_main.php
+++ b/public/seminar_main.php
@@ -23,63 +23,5 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
 
-
 require '../lib/bootstrap.php';
-
-ob_start();
-page_open(["sess" => "Seminar_Session", "auth" => "Seminar_Default_Auth", "perm" => "Seminar_Perm", "user" => "Seminar_User"]);
-$auth->login_if(Request::get('again') && ($auth->auth["uid"] == "nobody"));
-
-if (Request::option('auswahl')) {
-    Request::set('cid', Request::option('auswahl'));
-}
-
-include ('lib/seminar_open.php'); // initialise Stud.IP-Session
-
-// -- here you have to put initialisations for the current page
-
-$course_id = Context::getId();
-
-if (!$course_id && Request::get('cid')) {
-    $archive_id = Request::get('cid');
-    $archived = ArchivedCourse::find($archive_id);
-    if ($archived) {
-        header('Location: ' . URLHelper::getURL('dispatch.php/search/archive', [
-            'criteria' => $archived->name,
-        ]));
-        die;
-    }
-}
-
-if (!$course_id) {
-    throw new CheckObjectException(_('Sie haben kein Objekt gewählt.'));
-}
-
-//set visitdate for course, when coming from my_courses
-if (Request::get('auswahl')) {
-   object_set_visit($course_id, 0);
-}
-
-
-// gibt es eine Anweisung zur Umleitung?
-$redirect_to = Request::get('redirect_to');
-if ($redirect_to) {
-    if (!is_internal_url($redirect_to)) {
-        throw new Exception('Invalid redirection');
-    }
-
-    header('Location: '.URLHelper::getURL($redirect_to, ['cid' => $course_id]));
-    die;
-}
-
-// der Nutzer zum ersten
-//Reiter der Veranstaltung weiter geleitet.
-if (Navigation::hasItem("/course")) {
-    foreach (Navigation::getItem("/course")->getSubNavigation() as $index => $navigation) {
-        if ($index !== 'admin') {
-            header('Location: ' . URLHelper::getURL($navigation->getURL()));
-            die;
-        }
-    }
-}
-
+header('Location: ' . URLHelper::getURL('dispatch.php/course/go', $_GET));
diff --git a/public/sendfile.php b/public/sendfile.php
index 571713ef08ba1522d60cbfcb70520dd24b708fd8..6c5d120e30f0eed328bcda5e2e569a7b4590cad3 100644
--- a/public/sendfile.php
+++ b/public/sendfile.php
@@ -39,10 +39,10 @@
 ob_start();
 require '../lib/bootstrap.php';
 
-page_open(["sess" => "Seminar_Session",
-                "auth" => "Seminar_Default_Auth",
-                "perm" => "Seminar_Perm",
-                "user" => "Seminar_User"]);
+$sess = sess();
+$auth = auth();
+$auth->setNobody(true);
+$sess->start();
 
 //Load plugins, unless they are disabled via an URL parameter.
 if (Request::int('disable_plugins') !== null && ($GLOBALS['user']->id === 'nobody' || $GLOBALS['perm']->have_perm('root'))) {
@@ -113,8 +113,14 @@ if ($file_missing) {
 //if download not allowed throw exception to terminate script
 if ($no_access) {
     // redirect to login page if user is not logged in
-    $GLOBALS['auth']->login_if($GLOBALS['auth']->auth['uid'] === 'nobody');
-    throw new AccessDeniedException(_("Sie haben keine Zugriffsberechtigung für diesen Download!"));
+    if ($GLOBALS['user']->id === 'nobody') {
+        $_SESSION['redirect_after_login'] = Request::url();
+        $sess->save();
+        header('Location: ' . URLHelper::getURL('dispatch.php/login'));
+        die();
+    } else {
+        throw new AccessDeniedException(_("Sie haben keine Zugriffsberechtigung für diesen Download!"));
+    }
 }
 
 //replace bad charakters to avoid problems when saving the file
@@ -171,7 +177,7 @@ if (isset($file)) {
 }
 
 // close session, download will mostly be a parallel action
-page_close();
+$sess->save();
 
 // output_buffering may be explicitly or implicitly enabled
 while (ob_get_level()) {
@@ -209,7 +215,7 @@ if ($filesize && !parse_url($path_file, PHP_URL_SCHEME)) {
     if (isset($_SERVER['HTTP_RANGE'])) {
         $c_start = $start;
         $c_end   = $end;
-        list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
+        [, $range] = explode('=', $_SERVER['HTTP_RANGE'], 2);
         if (mb_strpos($range, ',') !== false) {
             header('HTTP/1.1 416 Requested Range Not Satisfiable');
             header("Content-Range: bytes $start-$end/$filesize");
diff --git a/public/web_migrate.php b/public/web_migrate.php
index 0ee4de554638e7dc40240a64ef450fcc407521f0..4f2102a8eb0f0e2707f4a30a385e89d2269c2ec2 100644
--- a/public/web_migrate.php
+++ b/public/web_migrate.php
@@ -16,12 +16,13 @@
 
 require __DIR__ . '/../lib/bootstrap.php';
 
-page_open([
-    'sess' => 'Seminar_Session',
-    'auth' => 'Seminar_Auth',
-    'perm' => 'Seminar_Perm',
-    'user' => 'Seminar_User',
-]);
+sess()->start();
+if (!auth()->start()) {
+    $_SESSION['redirect_after_login'] = Request::url();
+    sess()->save();
+    header('Location: ' . URLHelper::getURL('dispatch.php/login'));
+    die();
+}
 
 URLHelper::setBaseUrl($GLOBALS['ABSOLUTE_URI_STUDIP']);
 
diff --git a/resources/assets/stylesheets/scss/index.scss b/resources/assets/stylesheets/scss/index.scss
index b33d5f6f9b8cc5e17edacddcc670acc941766e77..f41d0d62ab82587fb368fbdb64f13cf144a74ae1 100644
--- a/resources/assets/stylesheets/scss/index.scss
+++ b/resources/assets/stylesheets/scss/index.scss
@@ -60,6 +60,7 @@ $gap-between-boxes: calc($login-page-margin / 2);
     min-height: 540px;
     display: flex;
     box-shadow: 0 0 8px rgba(0, 0, 0, 0.5);
+    margin-top: 100px;
 
     #login-infobox {
         max-width: 540px;
diff --git a/resources/assets/stylesheets/scss/layouts.scss b/resources/assets/stylesheets/scss/layouts.scss
index a101dac396cc8b42d4d6e7ce12c28e0135069b41..f9cb14334299e35e1706295b819b3117035cca4c 100644
--- a/resources/assets/stylesheets/scss/layouts.scss
+++ b/resources/assets/stylesheets/scss/layouts.scss
@@ -8,7 +8,7 @@ html {
 body {
     display: grid;
     background-color: var(--white);
-    width: 100%;    
+    width: 100%;
     grid-column-gap: 5px;
     grid-row-gap: $grid-gap;
     grid-template-columns: ($sidebar-width + $sidebar-padding) minmax(auto, calc(100vw - $sidebar-width - $sidebar-padding));
@@ -35,6 +35,10 @@ body {
     grid-row: 2 / 3;
 }
 
+body#login #content-wrapper {
+    background: unset;
+}
+
 #content-wrapper {
     background:
         linear-gradient(90deg, var(--white) 30%, hsla(0, 0%, 100%, 0)),
diff --git a/templates/blubber/course_context.php b/templates/blubber/course_context.php
index d13752eb5ab704675cccf9217fab8f27c227f7e1..0d6d2af62ca9d57f279ff0ea5f391b1890f2e881 100644
--- a/templates/blubber/course_context.php
+++ b/templates/blubber/course_context.php
@@ -1,14 +1,14 @@
 <div class="blubber_course_info indented">
     <div class="headline">
         <div class="side">
-            <a href="<?= URLHelper::getLink("seminar_main.php", ['auswahl' => $course->getId()]) ?>">
+            <a href="<?= URLHelper::getLink("dispatch.php/course/go", ['to' => $course->getId()]) ?>">
                 <?= htmlReady($course->name) ?>
             </a>
             <div class="icons">
                 <ul class="my-courses-navigation">
                 <? foreach ($icons as $icon) : ?>
                     <li class="my-courses-navigation-item <? if ($icon->getImage()->signalsAttention()) echo 'my-courses-navigation-important'; ?>">
-                        <a href="<?= URLHelper::getLink("seminar_main.php", ['auswahl' => $course->getId(), 'redirect_to' => $icon->getURL()]) ?>"<?= $icon->getTitle() ? ' title="'.htmlReady($icon->getTitle()).'"' : "" ?>>
+                        <a href="<?= URLHelper::getLink("dispatch.php/course/go", ['to' => $course->getId(), 'redirect_to' => $icon->getURL()]) ?>"<?= $icon->getTitle() ? ' title="'.htmlReady($icon->getTitle()).'"' : "" ?>>
                             <?= $icon->getImage() ?>
                         </a>
                     </li>
diff --git a/templates/dates/seminar_html.php b/templates/dates/seminar_html.php
index f227a57aab06a0db47767b2edd32e93e5e245706..4a95f4d35c19d04d5c931d154fe1527d691b072a 100644
--- a/templates/dates/seminar_html.php
+++ b/templates/dates/seminar_html.php
@@ -112,7 +112,7 @@ if (!$dates['regular']['turnus_data'] && empty($dates['irregular'])) {
         echo '<br>';
         printf(
             _('Details zu allen Terminen im %sAblaufplan%s'),
-            '<a href="' . URLHelper::getLink('seminar_main.php', array('auswahl' => $seminar_id, 'redirect_to' => 'dispatch.php/course/dates')) . '">',
+            '<a href="' . URLHelper::getLink('dispatch.php/course/go', array('to' => $seminar_id, 'redirect_to' => 'dispatch.php/course/dates')) . '">',
             '</a>'
         );
     }
diff --git a/templates/login_emailactivation.php b/templates/login_emailactivation.php
deleted file mode 100644
index cf8221ac47d7270165a7652a4d24035c73c5c74a..0000000000000000000000000000000000000000
--- a/templates/login_emailactivation.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<?
-# Lifter010: TODO
-use Studip\Button, Studip\LinkButton;
-?>
-<div align="center">
-<table width="70%" border=0 cellpadding=0 cellspacing=0>
-<tr><td class="table_header_bold" colspan=3 align="left">
- <?= Icon::create('mail', 'info_alt')->asImg(['class' => 'text-top']) ?>
- <b><?= _('E-Mail Aktivierung') ?></b>
-</td></tr>
-<tr><td style="background-color: #fff; padding: 1.5em;">
-<?= _('Sie haben Ihre E-Mail-Adresse geändert. Um diese frei zu schalten müssen Sie den Ihnen an Ihre neue Adresse zugeschickten Aktivierungs Schlüssel im folgenden Eingabefeld eintragen.'); ?>
-<br><form action="activate_email.php" method="post">
- <?= CSRFProtection::tokenTag() ?>
- <input name="key">
- <input name="uid" type="hidden" value="<?= $uid ?>">
- <?= Button::createAccept(_('Abschicken')) ?></form><br><br>
-</td></tr></table></div><br>
-
-
-<div align="center">
-<table width="70%" border=0 cellpadding=0 cellspacing=0>
-<tr><td class="table_header_bold" colspan=3 align="left">
- <?= Icon::create('mail', 'info_alt')->asImg(['class' => 'text-top']) ?>
- <b><?= _('E-Mail Aktivierung neu senden') ?></b>
-</td></tr>
-<tr><td style="background-color: #fff; padding: 1.5em;">
-<?= _('Sollten Sie keine E-Mail erhalten haben, können Sie sich einen neuen Aktivierungsschlüssel zuschicken lassen. Geben Sie dazu Ihre gewünschte E-Mail-Adresse im folgenden Formular an:'); ?>
-<form action="activate_email.php" method="post">
-<?= CSRFProtection::tokenTag() ?>
-<input type="hidden" name="uid" value="<?= $uid ?>">
-<table><tr><td><?= _('E-Mail') ?>:</td><td><input name="email1"></td></tr>
-<tr><td><?= _('Wiederholung') ?>:</td><td><input name="email2"></td></tr></table>
-<?= Button::createAccept(_('Abschicken'))  ?>
-</form>
-</td></tr></table></div><br>
diff --git a/templates/mail/notification_html.php b/templates/mail/notification_html.php
index 93bc1a81fbc9c8fdb6ac05f8a2f50d2567e01fb6..b1cbdee2c74dc07ab9b57a2506fe8f314cc760b6 100644
--- a/templates/mail/notification_html.php
+++ b/templates/mail/notification_html.php
@@ -104,7 +104,7 @@
               <? foreach ($news as $sem_titel => $data) : ?>
                 <tr class="table_header_bold">
                   <td style="font-weight: bold;">
-                    <a href="<?= URLHelper::getLink('seminar_main.php', ['again' => 'yes', 'sso' => $sso, 'auswahl' => $data[0]['seminar_id']]) ?>">
+                    <a href="<?= URLHelper::getLink('dispatch.php/course/go', ['again' => 'yes', 'sso' => $sso, 'to' => $data[0]['seminar_id']]) ?>">
                       <?= htmlReady($sem_titel) ?>
                       <?= (($semester = Course::find($data[0]['range_id'])->semester_text) ? ' ('.$semester.')' : '') ?>
                     </a>
diff --git a/templates/mail/notification_text.php b/templates/mail/notification_text.php
index 47b8a4aaf85eb1c4658ca2e6ffe035f5c7b3c509..3eb3b2e4e7fde695d775f4d104580e38f3652087 100644
--- a/templates/mail/notification_text.php
+++ b/templates/mail/notification_text.php
@@ -14,7 +14,7 @@
 <? foreach ($news as $sem_titel => $data) : ?>
 <?= sprintf(_("In der Veranstaltung \"%s\" gibt es folgende Neuigkeiten:"), $sem_titel) ?>
 
-<?= URLHelper::getURL('seminar_main.php', ['again' => 'yes', 'sso' => $sso, 'auswahl' => $data[0]['seminar_id']]) ?>
+<?= URLHelper::getURL('dispatch.php/course/go', ['again' => 'yes', 'sso' => $sso, 'to' => $data[0]['seminar_id']]) ?>
 
 
 <? foreach ($data as $module) : ?>
diff --git a/templates/shared/opengraphinfo_wide.php b/templates/shared/opengraphinfo_wide.php
index 0637232808ee343e530532ee52bbc1169708aae6..1957bc44da5ace18bdda13d9a6e84d1dd164645b 100644
--- a/templates/shared/opengraphinfo_wide.php
+++ b/templates/shared/opengraphinfo_wide.php
@@ -7,7 +7,7 @@
 $videofiles = $og->getVideoFiles();
 $audiofiles = $og->getAudioFiles();
 $og['image'] = filter_var($og['image'], FILTER_VALIDATE_URL) ? $og['image'] : '';
-if (Config::get()->LOAD_EXTERNAL_MEDIA === "proxy" && Seminar_Session::is_current_session_authenticated()) {
+if (Config::get()->LOAD_EXTERNAL_MEDIA === "proxy" && sess()->isCurrentSessionAuthenticated()) {
     $media_url_func = function ($url) {
         return $GLOBALS['ABSOLUTE_URI_STUDIP'] . 'dispatch.php/media_proxy?url=' . urlencode($url);
     };
diff --git a/tests/_support/Helper/Jsonapi.php b/tests/_support/Helper/Jsonapi.php
index cbb2216ed2344d37384725e8d164756de2d79d47..6a31d661ced0c2742edc9e14b91671c887980dba 100644
--- a/tests/_support/Helper/Jsonapi.php
+++ b/tests/_support/Helper/Jsonapi.php
@@ -129,12 +129,6 @@ class Jsonapi extends \Codeception\Module
                 ->add(function ($request, $handler) {
                     $user = $request->getAttribute(Authentication::USER_KEY, null);
 
-                    $GLOBALS['auth'] = new \Seminar_Auth();
-                    $GLOBALS['auth']->auth = [
-                        'uid' => $user->id,
-                        'uname' => $user->username,
-                        'perm' => $user->perms,
-                    ];
                     $GLOBALS['user'] = new \Seminar_User($user->id);
                     $GLOBALS['perm'] = new \Seminar_Perm();
                     $GLOBALS['MAIL_VALIDATE_BOX'] = false;
diff --git a/tests/jsonapi/SeminarCycleDatesShowTest.php b/tests/jsonapi/SeminarCycleDatesShowTest.php
index 46f8b9334d623e625b75c2a94d25786dc9503b50..0d2c559b8dc263e124cc38e591bbaac974784a98 100644
--- a/tests/jsonapi/SeminarCycleDatesShowTest.php
+++ b/tests/jsonapi/SeminarCycleDatesShowTest.php
@@ -51,8 +51,6 @@ class SeminarCycleDatesShowTest extends \Codeception\Test\Unit
         $GLOBALS['user'] = new \Seminar_User(
             \User::find($credentials['id'])
         );
-        $GLOBALS['auth'] = new \Seminar_Auth();
-        $GLOBALS['auth']->auth = ['uid' => $credentials['id']];
 
         $cycle = \SeminarCycleDate::create(
             [
diff --git a/tests/jsonapi/_bootstrap.php b/tests/jsonapi/_bootstrap.php
index 1fc5ba7ba0d71af6f124d8a675935a341f6fc6af..5a02ca2b60a92cdb96af0cd6b39851b3b693785e 100644
--- a/tests/jsonapi/_bootstrap.php
+++ b/tests/jsonapi/_bootstrap.php
@@ -2,13 +2,14 @@
 
 // Here you can initialize variables that will be available to your tests
 
-global $STUDIP_BASE_PATH, $ABSOLUTE_URI_STUDIP, $CACHING_ENABLE, $CACHING_FILECACHE_PATH, $SYMBOL_SHORT, $TMP_PATH, $UPLOAD_PATH, $DYNAMIC_CONTENT_PATH, $DYNAMIC_CONTENT_URL;
+global $STUDIP_BASE_PATH, $ABSOLUTE_URI_STUDIP, $CACHING_ENABLE, $CACHING_FILECACHE_PATH, $SYMBOL_SHORT, $TMP_PATH, $UPLOAD_PATH, $DYNAMIC_CONTENT_PATH, $DYNAMIC_CONTENT_URL, $CANONICAL_RELATIVE_PATH_STUDIP;
 
 // common set-up, usually done by lib/bootstraph.php and
 // config/config_local.inc.php when run on web server
 if (!isset($STUDIP_BASE_PATH)) {
     $STUDIP_BASE_PATH = dirname(dirname(__DIR__));
     $ABSOLUTE_PATH_STUDIP = $STUDIP_BASE_PATH.'/public/';
+    $CANONICAL_RELATIVE_PATH_STUDIP = '/public/';
     $UPLOAD_PATH = $STUDIP_BASE_PATH.'/data/upload_doc';
     $TMP_PATH = $TMP_PATH ?: '/tmp';
     $DYNAMIC_CONTENT_PATH = '';
@@ -50,17 +51,6 @@ $GLOBALS['_fullname_sql']['full_rev_username'] = "TRIM(CONCAT(Nachname,', ',Vorn
 
 SimpleORMap::expireTableScheme();
 
-/**
- * @deprecated
- */
-class DB_Seminar extends DB_Sql
-{
-    public function __construct($query = false)
-    {
-        parent::__construct($query);
-    }
-}
-
-require_once __DIR__ . '/../../composer/autoload.php';
+require_once __DIR__.'/../../composer/autoload.php';
 
 session_id("test-session");
diff --git a/tests/unit/_bootstrap.php b/tests/unit/_bootstrap.php
index 0cf0f7ec526c3903ce023b89dbc0b501d98ab28a..9bc68f5ea2f60ef3cd7a6be05448706f832ddc1f 100644
--- a/tests/unit/_bootstrap.php
+++ b/tests/unit/_bootstrap.php
@@ -51,7 +51,6 @@ require __DIR__ . '/../../composer/autoload.php';
 global $STUDIP_BASE_PATH;
 $STUDIP_BASE_PATH = realpath(dirname(__DIR__) . '/..');
 
-require 'lib/helpers.php';
 require 'lib/functions.php';
 require 'lib/visual.inc.php';
 
@@ -125,3 +124,48 @@ if (!class_exists('StudipTestHelper')) {
         }
     }
 }
+
+//fake DI
+class StudipFakeDIContainer
+{
+    private $storage;
+    private static $instance;
+
+    public static function getInstance()
+    {
+        if (is_null(self::$instance)) {
+            self::$instance = new self();
+        }
+        return self::$instance;
+    }
+
+    public function get($name)
+    {
+        return $this->storage[$name] ?? null;
+    }
+
+    public function set($name, $object)
+    {
+        $this->storage[$name] = $object;
+    }
+
+    public function make($name, $parameters)
+    {
+        return $this->get($name);
+    }
+}
+
+function app($entryId = null, $parameters = [])
+{
+    $container = StudipFakeDIContainer::getInstance();
+    if (is_null($entryId)) {
+        return $container;
+    }
+
+    return $container->make($entryId, $parameters);
+}
+
+function sess()
+{
+    return app()->get('Studip\Session\Manager');
+}
diff --git a/tests/unit/lib/classes/AvatarClassTest.php b/tests/unit/lib/classes/AvatarClassTest.php
index e09a3c260b571d9f3f94793f67165eb0c712cba8..51bfab8c5fd26910afef47751340ca78aa2fa038 100644
--- a/tests/unit/lib/classes/AvatarClassTest.php
+++ b/tests/unit/lib/classes/AvatarClassTest.php
@@ -9,7 +9,7 @@
  * the License, or (at your option) any later version.
  */
 
-require_once 'lib/phplib/Seminar_Perm.php';
+require_once 'lib/classes/Seminar_Perm.php';
 
 abstract class AvatarTest extends \Codeception\Test\Unit
 {
diff --git a/tests/unit/lib/classes/MarkupClassTest.php b/tests/unit/lib/classes/MarkupClassTest.php
index 8dad6e9954746c220a207cf96d34ba9f3c154c6c..7342a4a82097d29621eb1bc4e5d99e32742d0375 100644
--- a/tests/unit/lib/classes/MarkupClassTest.php
+++ b/tests/unit/lib/classes/MarkupClassTest.php
@@ -33,9 +33,9 @@ require_once 'lib/classes/Markup.php';
 # completely unneeded for testing the Markup class.
 # Instead, create a fake class.
 # => But note, this will fail if another test case does the same thing!
-class Seminar_Session
+class StudipSessionManager
 {
-    public static function is_current_session_authenticated()
+    public static function isCurrentSessionAuthenticated()
     {
         return true;
     }
@@ -77,7 +77,7 @@ class MarkupClassTest extends \Codeception\Test\Unit
         }));
 
         Config::set($configStub);
-
+        app()->set('Studip\Session\Manager', new StudipSessionManager());
         # exceptions
         $namespace = 'Studip\MarkupPrivate\MediaProxy\\';
         $invalidInternalLink = $namespace . 'InvalidInternalLinkException';
diff --git a/tests/unit/lib/classes/MigrationTest.php b/tests/unit/lib/classes/MigrationTest.php
index 12df1201c7ee64f52ef3f7406acf8434ddc31003..2e9db08a674550807721ed6213cbb1f2c71ceb4d 100644
--- a/tests/unit/lib/classes/MigrationTest.php
+++ b/tests/unit/lib/classes/MigrationTest.php
@@ -19,6 +19,11 @@ class MigrationTest extends \Codeception\Test\Unit
         require_once 'lib/migrations/Migration.php';
         require_once 'lib/migrations/Migrator.php';
         require_once 'lib/migrations/SchemaVersion.php';
+
+        $testconfig = new Config([
+            'LOG_ENABLE' => false,
+        ]);
+        Config::set($testconfig);
     }
 
     public function tearDown(): void
diff --git a/vendor/email_message/debug_message.php b/vendor/email_message/debug_message.php
index f8d4f02278f54dd82b50a015f037ca5d6a70c161..ea8bd6b4d5b6f80d16aa87d213790e89fa956d0e 100644
--- a/vendor/email_message/debug_message.php
+++ b/vendor/email_message/debug_message.php
@@ -15,15 +15,15 @@ class debug_message_class extends email_message_class
 	function __construct() {
 		$this->logfile = $GLOBALS['TMP_PATH'] . '/studip-mail-debug.log';
 	}
-	
+
 	function SendMail($to,$subject,$body,$headers,$return_path) {
 		if ($log = fopen($this->logfile, "a")){
 			if(strlen($headers)) $headers.="\n";
-			fwrite($log, "\n-- " . strftime("%x %X"). ' ' . $GLOBALS['auth']->auth['uname']);
+			fwrite($log, "\n-- " . strftime("%x %X"). ' ' . $GLOBALS['user']->username);
 			fwrite($log, "\nTo: ".$to."\nSubject: ".$subject."\n".$headers."\n");
 			fwrite($log,$body."\n");
 			fclose($log);
 		}
 	}
 }
-?>
\ No newline at end of file
+?>