From 7660820a38d539ab06eafdfbf757014169baac40 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Noack?= <noack@data-quest.de>
Date: Tue, 21 Dec 2021 11:08:51 +0000
Subject: [PATCH] Resolve "OpenID Connect als SSO AuthPlugin"

---
 composer.json                                 |   3 +-
 composer.lock                                 |  44 +-
 config/config_defaults.inc.php                |  20 +
 .../auth_plugins/StudipAuthAbstract.class.php | 422 +++++++++---------
 .../auth_plugins/StudipAuthCAS.class.php      |  70 +--
 .../auth_plugins/StudipAuthLdap.class.php     | 146 +++---
 .../StudipAuthLdapReadAndBind.class.php       |  64 ++-
 .../auth_plugins/StudipAuthOIDC.class.php     | 112 +++++
 .../auth_plugins/StudipAuthSSO.class.php      |  10 +
 .../auth_plugins/StudipAuthShib.class.php     |  64 +--
 .../auth_plugins/StudipAuthStandard.class.php |  26 +-
 lib/navigation/LoginNavigation.php            |  16 +-
 12 files changed, 578 insertions(+), 419 deletions(-)
 create mode 100644 lib/classes/auth_plugins/StudipAuthOIDC.class.php

diff --git a/composer.json b/composer.json
index ba9f55ed8f9..569f700e9f7 100644
--- a/composer.json
+++ b/composer.json
@@ -47,6 +47,7 @@
         "slim/slim": "4.7.1",
         "php-di/php-di": "6.3.4",
         "symfony/console": "5.3.6",
-        "symfony/process": "^5.4"
+        "symfony/process": "^5.4",
+        "jumbojett/openid-connect-php": "^0.9.2"
     }
 }
diff --git a/composer.lock b/composer.lock
index 83fc9e51262..3ec63574b50 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "59006c71d43d6f32f0445636c803208d",
+    "content-hash": "2fa6a856bbe442274874aeabe26f054b",
     "packages": [
         {
             "name": "algo26-matthias/idna-convert",
@@ -545,6 +545,48 @@
             },
             "time": "2019-08-18T20:01:55+00:00"
         },
+        {
+            "name": "jumbojett/openid-connect-php",
+            "version": "v0.9.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/jumbojett/OpenID-Connect-PHP.git",
+                "reference": "14991f706675b13dd1f72291bfd779f144454d64"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/jumbojett/OpenID-Connect-PHP/zipball/14991f706675b13dd1f72291bfd779f144454d64",
+                "reference": "14991f706675b13dd1f72291bfd779f144454d64",
+                "shasum": ""
+            },
+            "require": {
+                "ext-curl": "*",
+                "ext-json": "*",
+                "paragonie/random_compat": ">=2",
+                "php": ">=5.4",
+                "phpseclib/phpseclib": "~2.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8",
+                "roave/security-advisories": "dev-master"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "description": "Bare-bones OpenID Connect client",
+            "support": {
+                "issues": "https://github.com/jumbojett/OpenID-Connect-PHP/issues",
+                "source": "https://github.com/jumbojett/OpenID-Connect-PHP/tree/v0.9.5"
+            },
+            "time": "2021-11-24T16:11:49+00:00"
+        },
         {
             "name": "kub-at/php-simple-html-dom-parser",
             "version": "1.9.1",
diff --git a/config/config_defaults.inc.php b/config/config_defaults.inc.php
index 14c1757e69a..2b6f2cae6d9 100644
--- a/config/config_defaults.inc.php
+++ b/config/config_defaults.inc.php
@@ -326,6 +326,26 @@ $STUDIP_AUTH_CONFIG_CAS = array("host" => "cas.studip.de",
                                                         "auth_user_md5.Email" => array("callback" => "getUserData", "map_args" => "email"),
                                                         "auth_user_md5.perms" => array("callback" => "getUserData", "map_args" => "status")));
 
+//example of OpenID Connect
+$STUDIP_AUTH_CONFIG_GOOGLE = [
+        'provider_url' => 'https://accounts.google.com',
+        'client_id'    => '',
+        'client_secret'     => '',
+        'plugin_class'      => 'StudipAuthOIDC',
+        'plugin_name'       => 'google',
+        'domain'            => 'google',
+        'plugin_fullname'   => 'Google',
+        'login_description' => 'Login with Google',
+        'ssl_options'       => ['certPath' => null, 'verifyPeer' => true, 'verifyHost' => true],
+        'user_data_mapping' =>
+            ['auth_user_md5.username' => ['callback' => 'dummy', 'map_args' => ''],
+             'auth_user_md5.password' => ['callback' => 'dummy', 'map_args' => ''],
+             'auth_user_md5.Email'    => ['callback' => 'getUserData', 'map_args' => 'email'],
+             'auth_user_md5.Nachname' => ['callback' => 'getUserData', 'map_args' => 'family_name'],
+             'auth_user_md5.Vorname'  => ['callback' => 'getUserData', 'map_args' => 'given_name']
+            ]
+    ];
+
 $STUDIP_AUTH_CONFIG_LTI = [
     'consumer_keys' => [
         // 'domain' is optional, default is value of consumer_key
diff --git a/lib/classes/auth_plugins/StudipAuthAbstract.class.php b/lib/classes/auth_plugins/StudipAuthAbstract.class.php
index 3af55c56d16..008ed870967 100644
--- a/lib/classes/auth_plugins/StudipAuthAbstract.class.php
+++ b/lib/classes/auth_plugins/StudipAuthAbstract.class.php
@@ -1,8 +1,4 @@
 <?php
-# Lifter003: TEST
-# Lifter007: TODO
-# Lifter010: DONE - no html
-
 // +---------------------------------------------------------------------------+
 // This file is part of Stud.IP
 // StudipAuthAbstract.class.php
@@ -26,39 +22,37 @@
 // +---------------------------------------------------------------------------+
 
 /**
-* abstract base class for authentication plugins
-*
-* abstract base class for authentication plugins
-* to write your own authentication plugin, derive it from this class and
-* implement the following abstract methods: isUsedUsername($username) and
-* isAuthenticated($username, $password, $jscript)
-* don't forget to call the parents constructor if you implement your own, php
-* won't do that for you !
-*
-* @abstract
-* @access   public
-* @author   André Noack <noack@data-quest.de>
-* @package
-*/
-class StudipAuthAbstract {
+ * abstract base class for authentication plugins
+ *
+ * abstract base class for authentication plugins
+ * to write your own authentication plugin, derive it from this class and
+ * implement the following abstract methods: isUsedUsername($username) and
+ * isAuthenticated($username, $password, $jscript)
+ * don't forget to call the parents constructor if you implement your own, php
+ * won't do that for you !
+ *
+ * @abstract
+ * @author   André Noack <noack@data-quest.de>
+ * @package
+ */
+class StudipAuthAbstract
+{
 
     /**
-    * contains error message, if authentication fails
-    *
-    *
-    * @access   public
-    * @var      string
-    */
-    var $error_msg;
+     * contains error message, if authentication fails
+     *
+     *
+     * @var      string $error_msg
+     */
+    public $error_msg;
 
     /**
-    * indicates whether the authenticated user logs in for the first time
-    *
-    *
-    * @access   public
-    * @var      bool
-    */
-    var $is_new_user = false;
+     * indicates whether the authenticated user logs in for the first time
+     *
+     *
+     * @var      bool $is_new_user
+     */
+    public $is_new_user = false;
 
     /**
      * array of user domains to assign to each user, can be set in local.inc
@@ -66,76 +60,75 @@ class StudipAuthAbstract {
      * @access  public
      * @var     array $user_domains
      */
-    var $user_domains;
+    public $user_domains;
 
     /**
-    * associative array with mapping for database fields
-    *
-    * associative array with mapping for database fields,
-    * should be set in local.inc
-    * structure :
-    * array("<table name>.<field name>" => array(   "callback" => "<name of callback method used for data retrieval>",
-    *                                               "map_args" => "<arguments passed to callback method>"))
-    * @access   public
-    * @var      array $user_data_mapping
-    */
-    var $user_data_mapping = null;
+     * associative array with mapping for database fields
+     *
+     * associative array with mapping for database fields,
+     * should be set in local.inc
+     * structure :
+     * array('<table name>.<field name>' => array(   'callback' => '<name of callback method used for data retrieval>',
+     *                                               'map_args' => '<arguments passed to callback method>'))
+     * @var      array $user_data_mapping
+     */
+    public $user_data_mapping = null;
 
     /**
-    * name of the plugin
-    *
-    * name of the plugin (last part of class name) is set in the constructor
-    * @access   public
-    * @var      string
-    */
-    var $plugin_name;
+     * name of the plugin
+     *
+     * name of the plugin (last part of class name) is set in the constructor
+     * @var      string $plugin_name
+     */
+    public $plugin_name;
 
     /**
-    * text, which precedes error message for the plugin
-    *
-    *
-    * @access   public
-    * @var      string
-    */
-    var $error_head;
+     * text, which precedes error message for the plugin
+     *
+     *
+     * @var      string $error_head
+     */
+    public $error_head;
 
+    /**
+     * @var $plugin_instances
+     */
     private static $plugin_instances;
 
     /**
-    * static method to instantiate and retrieve a reference to an object (singleton)
-    *
-    * use always this method to instantiate a plugin object, it will ensure that only one object of each
-    * plugin will exist
-    * @access public
-    * @static
-    * @param    string  name of plugin, if omitted an array with all plugin objects will be returned
-    * @return   mixed   either a reference to the plugin with the passed name, or an array with references to all plugins
-    */
-    static function getInstance($plugin_name = false)
+     * static method to instantiate and retrieve a reference to an object (singleton)
+     *
+     * always use this method to instantiate a plugin object, it will ensure that only one object of each
+     * plugin will exist
+     * @param string $plugin_name name of plugin, if omitted an array with all plugin objects will be returned
+     * @return   mixed   either a reference to the plugin with the passed name, or an array with references to all plugins
+     */
+    public static function getInstance($plugin_name = false)
     {
         if (!is_array(self::$plugin_instances)) {
-            foreach($GLOBALS['STUDIP_AUTH_PLUGIN'] as $plugin) {
-                $plugin = "StudipAuth" . $plugin;
-                include_once "lib/classes/auth_plugins/" . $plugin . ".class.php";
-                self::$plugin_instances[mb_strtoupper($plugin)] = new $plugin;
+            foreach ($GLOBALS['STUDIP_AUTH_PLUGIN'] as $plugin) {
+                $config = $GLOBALS['STUDIP_AUTH_CONFIG_' . strtoupper($plugin)];
+                $plugin_class = $config['plugin_class'] ?? 'StudipAuth' . $plugin;
+                if (empty($config['plugin_name'])) {
+                    $config['plugin_name'] = strtolower($plugin);
+                }
+                self::$plugin_instances[strtoupper($plugin)] = new $plugin_class($config);
             }
         }
-        return ($plugin_name) ? self::$plugin_instances[mb_strtoupper("StudipAuth" . $plugin_name)] : self::$plugin_instances;
+        return ($plugin_name) ? self::$plugin_instances[strtoupper($plugin_name)] : self::$plugin_instances;
     }
 
     /**
-    * static method to check authentication in all plugins
-    *
-    * if authentication fails in one plugin, the error message is stored and the next plugin is used
-    * if authentication succeeds, the uid element in the returned array will contain the Stud.IP user id
-    *
-    * @access public
-    * @static
-    * @param    string  the username to check
-    * @param    string  the password to check
-    * @return   array   structure: array('uid'=>'string <Stud.IP user id>','error'=>'string <error message>','is_new_user'=>'bool')
-    */
-    static function CheckAuthentication($username, $password)
+     * static method to check authentication in all plugins
+     *
+     * if authentication fails in one plugin, the error message is stored and the next plugin is used
+     * if authentication succeeds, the uid element in the returned array will contain the Stud.IP user id
+     *
+     * @param string $username the username to check
+     * @param string $password the password to check
+     * @return   array   structure: array('uid'=>'string <Stud.IP user id>','error'=>'string <error message>','is_new_user'=>'bool')
+     */
+    public static function CheckAuthentication($username, $password)
     {
 
         $plugins = StudipAuthAbstract::GetInstance();
@@ -157,63 +150,60 @@ class StudipAuthAbstract {
                     $exp_d = UserConfig::get($user['user_id'])->EXPIRATION_DATE;
 
                     if ($exp_d > 0 && $exp_d < time()) {
-                        $error .= _("Dieses Benutzerkonto ist abgelaufen.<br> Wenden Sie sich bitte an die Administration.") . "<BR>";
+                        $error .= _('Dieses Benutzerkonto ist abgelaufen.<br> Wenden Sie sich bitte an die Administration.') . '<BR>';
                         return ['uid' => false, 'error' => $error];
-                    } else if ($locked == "1") {
-                        $error .= _("Dieser Benutzer ist gesperrt! Wenden Sie sich bitte an die Administration.") . "<BR>";
+                    } else if ($locked) {
+                        $error .= _('Dieser Benutzer ist gesperrt! Wenden Sie sich bitte an die Administration.') . '<BR>';
                         return ['uid' => false, 'error' => $error];
                     } else if ($key != '') {
                         return ['uid' => $uid, 'user' => $user, 'error' => $error, 'need_email_activation' => $uid];
                     } else if ($checkIPRange && !self::CheckIPRange()) {
-                        $error .= _("Der Login in Ihren Account ist aus diesem Netzwerk nicht erlaubt.") . "<BR>";
+                        $error .= _('Der Login in Ihren Account ist aus diesem Netzwerk nicht erlaubt.') . '<BR>';
                         return ['uid' => false, 'error' => $error];
                     }
                 }
                 return ['uid' => $uid, 'user' => $user, 'error' => $error, 'is_new_user' => $object->is_new_user];
             } else {
-                $error .= (($object->error_head) ? ("<b>" . $object->error_head . ":</b> ") : "") . $object->error_msg . "<br>";
+                $error .= (($object->error_head) ? ('<b>' . $object->error_head . ':</b> ') : '') . $object->error_msg . '<br>';
             }
         }
         return ['uid' => $uid, 'error' => $error];
     }
 
     /**
-    * static method to check if passed username is used in external data sources
-    *
-    * all plugins are checked, the error messages are stored and returned
-    *
-    * @access public
-    * @static
-    * @param    string the username
-    * @return   array
-    */
-    static function CheckUsername($username)
+     * static method to check if passed username is used in external data sources
+     *
+     * all plugins are checked, the error messages are stored and returned
+     *
+     * @param string $username the username
+     * @return   array
+     */
+    public static function CheckUsername($username)
     {
         $plugins = StudipAuthAbstract::GetInstance();
         $error = false;
         $found = false;
         foreach ($plugins as $object) {
             if ($found = $object->isUsedUsername($username)) {
-                return ['found' => $found,'error' => $error];
+                return ['found' => $found, 'error' => $error];
             } else {
-                $error .= (($object->error_head) ? ("<b>" . $object->error_head . ":</b> ") : "") . $object->error_msg . "<br>";
+                $error .= (($object->error_head) ? ('<b>' . $object->error_head . ':</b> ') : '') . $object->error_msg . '<br>';
             }
         }
-        return ['found' => $found,'error' => $error];
+        return ['found' => $found, 'error' => $error];
     }
+
     /**
-    * static method to check for a mapped field
-    *
-    * this method checks in the plugin with the passed name, if the passed
-    * Stud.IP DB field is mapped to an external data source
-    *
-    * @access public
-    * @static
-    * @param    string  the name of the db field must be in form '<table name>.<field name>'
-    * @param    string  the name of the plugin to check
-    * @return   bool    true if the field is mapped, else false
-    */
-    static function CheckField($field_name,$plugin_name)
+     * static method to check for a mapped field
+     *
+     * this method checks in the plugin with the passed name, if the passed
+     * Stud.IP DB field is mapped to an external data source
+     *
+     * @param string  the name of the db field must be in form '<table name>.<field name>'
+     * @param string  the name of the plugin to check
+     * @return   bool    true if the field is mapped, else false
+     */
+    public static function CheckField($field_name, $plugin_name)
     {
         if (!$plugin_name) {
             return false;
@@ -230,7 +220,7 @@ class StudipAuthAbstract {
     public static function CheckIPRange()
     {
         $ip = $_SERVER['REMOTE_ADDR'];
-        $version = mb_substr_count($ip, ':') > 1 ? 'V6' : 'V4'; // valid ip v6 addresses have atleast two colons
+        $version = substr_count($ip, ':') > 1 ? 'V6' : 'V4'; // valid ip v6 addresses have atleast two colons
         $method = 'CheckIPRange' . $version;
         if (is_array($GLOBALS['LOGIN_IP_RANGES'][$version])) {
             foreach ($GLOBALS['LOGIN_IP_RANGES'][$version] as $range) {
@@ -275,53 +265,51 @@ class StudipAuthAbstract {
         $start = inet_pton($range['start']);
         $end = inet_pton($range['end']);
 
-        return mb_strlen($ipv6) === mb_strlen($start)
-        && $ipv6 >= $start && $ipv6 <= $end;
+        return strlen($ipv6) === strlen($start)
+            && $ipv6 >= $start && $ipv6 <= $end;
     }
 
     /**
-    * Constructor
-    *
-    * the constructor is private, you should use StudipAuthAbstract::GetInstance($plugin_name)
-    * to get a reference to a plugin object. Make sure the constructor in the base class is called
-    * when deriving your own plugin class, it assigns the settings from local.inc as members of the plugin
-    * each key of the $STUDIP_AUTH_CONFIG_<plugin name> array will become a member of the object
-    *
-    * @access private
-    *
-    */
-    function __construct()
+     * Constructor
+     *
+     * you should use StudipAuthAbstract::GetInstance($plugin_name)
+     * to get a reference to a plugin object. Make sure the constructor in the base class is called
+     * when deriving your own plugin class, it assigns the settings from local.inc as members of the plugin
+     * each key of the $STUDIP_AUTH_CONFIG_<plugin name> array will become a member of the object
+     *
+     * @param array $config
+     */
+    public function __construct($config = [])
     {
-        $this->plugin_name = mb_strtolower(mb_substr(get_class($this),10));
         //get configuration array set in local inc
-        $config_var = $GLOBALS["STUDIP_AUTH_CONFIG_" . mb_strtoupper($this->plugin_name)];
+        if (empty($config)) {
+            $this->plugin_name = strtolower(substr(get_class($this), 10));
+            $config = $GLOBALS['STUDIP_AUTH_CONFIG_' . strtoupper($this->plugin_name)];
+        }
         //assign each key in the config array as a member of the plugin object
-        if (isset($config_var)) {
-            foreach ($config_var as $key => $value) {
-                $this->$key = $value;
-            }
+        foreach ($config as $key => $value) {
+            $this->$key = $value;
         }
     }
 
     /**
-    * authentication method
-    *
-    * this method authenticates the passed username, it is used by StudipAuthAbstract::CheckAuthentication()
-    * if authentication succeeds it calls StudipAuthAbstract::doDataMapping() to map data fields
-    * if the authenticated user logs in for the first time it calls StudipAuthAbstract::doNewUserInit() to
-    * initialize the new user
-    * @access private
-    * @param    string  the username to check
-    * @param    string  the password to check
-    * @return   string  if authentication succeeds the Stud.IP user , else false
-    */
-    function authenticateUser($username, $password)
+     * authentication method
+     *
+     * this method authenticates the passed username, it is used by StudipAuthAbstract::CheckAuthentication()
+     * if authentication succeeds it calls StudipAuthAbstract::doDataMapping() to map data fields
+     * if the authenticated user logs in for the first time it calls StudipAuthAbstract::doNewUserInit() to
+     * initialize the new user
+     * @param string $username the username to check
+     * @param string $password the password to check
+     * @return   string  if authentication succeeds the Stud.IP user , else false
+     */
+    public function authenticateUser($username, $password)
     {
         $username = $this->verifyUsername($username);
         if ($this->isAuthenticated($username, $password)) {
             if ($user = $this->getStudipUser($username)) {
                 $this->doDataMapping($user);
-                if ($this->is_new_user){
+                if ($this->is_new_user) {
                     $this->doNewUserInit($user);
                 }
                 $this->setUserDomains($user);
@@ -333,24 +321,24 @@ class StudipAuthAbstract {
     }
 
     /**
-    * method to retrieve the Stud.IP user id to a given username
-    *
-    *
-    * @access   private
-    * @param    string  the username
-    * @return   User  the Stud.IP or false if an error occurs
-    */
+     * method to retrieve the Stud.IP user id to a given username
+     *
+     *
+     * @access   private
+     * @param string  the username
+     * @return   User  the Stud.IP or false if an error occurs
+     */
     function getStudipUser($username)
     {
         $user = User::findByUsername($username);
         if ($user) {
             $auth_plugin = $user->auth_plugin;
             if ($auth_plugin === null) {
-                $this->error_msg = _("Dies ist ein vorläufiger Benutzer.") . "<br>";
+                $this->error_msg = _('Dies ist ein vorläufiger Benutzer.') . '<br>';
                 return false;
             }
-            if ($auth_plugin != $this->plugin_name){
-                $this->error_msg = sprintf(_("Dieser Benutzername wird bereits über %s authentifiziert!"),$auth_plugin) . "<br>";
+            if ($auth_plugin != $this->plugin_name) {
+                $this->error_msg = sprintf(_('Dieser Benutzername wird bereits über %s authentifiziert!'), $auth_plugin) . '<br>';
                 return false;
             }
             return $user;
@@ -387,9 +375,10 @@ class StudipAuthAbstract {
      * This method sets the user domains for the current user.
      *
      * @access  private
-     * @param   User  the user object
+     * @param User  the user object
      */
-    function setUserDomains ($user) {
+    function setUserDomains($user)
+    {
         $user_domains = $this->getUserDomains();
         $uid = $user->id;
         if (isset($user_domains)) {
@@ -419,37 +408,43 @@ class StudipAuthAbstract {
     /**
      * Get the user domains to assign to the current user.
      */
-    function getUserDomains ()
+    function getUserDomains()
     {
         return $this->user_domains;
     }
 
     /**
-    * this method handles the data mapping
-    *
-    * for each entry in $this->user_data_mapping the according callback will be invoked
-    * the return value of the callback method is then written to the db field, which is specified
-    * in the key of the array
-    *
-    * @access   private
-    * @param    User  the user object
-    * @return   bool
-    */
+     * this method handles the data mapping
+     *
+     * for each entry in $this->user_data_mapping the according callback will be invoked
+     * the return value of the callback method is then written to the db field, which is specified
+     * in the key of the array
+     *
+     * @access   private
+     * @param User  the user object
+     * @return   bool
+     */
     function doDataMapping($user)
     {
         if ($user && is_array($this->user_data_mapping)) {
-            foreach($this->user_data_mapping as $key => $value){
+            foreach ($this->user_data_mapping as $key => $value) {
+                $callback = null;
                 if (method_exists($this, $value['callback'])) {
-                    $split = explode(".",$key);
+                    $callback = [$this, $value['callback']];
+                } else if (is_callable($value['callback'])) {
+                    $callback = $value['callback'];
+                }
+                if ($callback) {
+                    $split = explode('.', $key);
                     $table = $split[0];
                     $field = $split[1];
-                    if ($table == 'auth_user_md5' || $table == 'user_info') {
-                        $mapped_value = call_user_func([$this, $value['callback']],$value['map_args']);
+                    if ($table === 'auth_user_md5' || $table === 'user_info') {
+                        $mapped_value = call_user_func($callback, $value['map_args']);
                         if (isset($mapped_value)) {
                             $user->setValue($field, $mapped_value);
                         }
                     } else {
-                        call_user_func([$this, $value['callback']],[$table,$field,$user,$value['map_args']]);
+                        call_user_func($callback, [$table, $field, $user, $value['map_args']]);
                     }
                 }
             }
@@ -459,30 +454,30 @@ class StudipAuthAbstract {
     }
 
     /**
-    * method to check, if a given db field is mapped by the plugin
-    *
-    *
-    * @access   private
-    * @param    string  the name of the db field (<table_name>.<field_name>)
-    * @return   bool    true if the field is mapped
-    */
+     * method to check, if a given db field is mapped by the plugin
+     *
+     *
+     * @access   private
+     * @param string  the name of the db field (<table_name>.<field_name>)
+     * @return   bool    true if the field is mapped
+     */
     function isMappedField($name)
     {
         return isset($this->user_data_mapping[$name]);
     }
 
     /**
-    * method to eliminate bad characters in the given username
-    *
-    *
-    * @access   private
-    * @param    string  the username
-    * @return   string  the username
-    */
+     * method to eliminate bad characters in the given username
+     *
+     *
+     * @access   private
+     * @param string  the username
+     * @return   string  the username
+     */
     function verifyUsername($username)
     {
-        if($this->username_case_insensitiv) $username = mb_strtolower($username);
-        if ($this->bad_char_regex){
+        if ($this->username_case_insensitiv) $username = mb_strtolower($username);
+        if ($this->bad_char_regex) {
             return preg_replace($this->bad_char_regex, '', $username);
         } else {
             return trim($username);
@@ -490,32 +485,33 @@ class StudipAuthAbstract {
     }
 
     /**
-    * method to check, if username is used
-    *
-    * abstract MUST be realized
-    *
-    * @access   private
-    * @param    string  the username
-    * @return   bool    true if the username exists
-    */
+     * method to check, if username is used
+     *
+     * abstract MUST be realized
+     *
+     * @access   private
+     * @param string  the username
+     * @return   bool    true if the username exists
+     */
     function isUsedUsername($username)
     {
-        $this->error_msg = sprintf(_("Methode %s nicht implementiert!"),get_class($this) . "::isUsedUsername()");
+        $this->error_msg = sprintf(_('Methode %s nicht implementiert!'), get_class($this) . '::isUsedUsername()');
         return false;
     }
 
     /**
-    * method to check the authentication of a given username and a given password
-    *
-    * abstract, MUST be realized
-    *
-    * @access private
-    * @param    string  the username
-    * @param    string  the password
-    * @return   bool    true if authentication succeeds
-    */
-    function isAuthenticated($username, $password) {
-        $this->error = sprintf(_("Methode %s nicht implementiert!"),get_class($this) . "::isAuthenticated()");
+     * method to check the authentication of a given username and a given password
+     *
+     * abstract, MUST be realized
+     *
+     * @access private
+     * @param string  the username
+     * @param string  the password
+     * @return   bool    true if authentication succeeds
+     */
+    function isAuthenticated($username, $password)
+    {
+        $this->error = sprintf(_('Methode %s nicht implementiert!'), get_class($this) . '::isAuthenticated()');
         return false;
     }
 }
diff --git a/lib/classes/auth_plugins/StudipAuthCAS.class.php b/lib/classes/auth_plugins/StudipAuthCAS.class.php
index d28cc3e7711..620f12e19ce 100644
--- a/lib/classes/auth_plugins/StudipAuthCAS.class.php
+++ b/lib/classes/auth_plugins/StudipAuthCAS.class.php
@@ -3,37 +3,43 @@
 # Lifter003: TODO
 # Lifter010: TODO
 /**
-* Stud.IP authentication against CAS Server
-*
-* @access   public
-* @author   Dennis Reil <dennis.reil@offis.de>
-* @package
-*/
+ * Stud.IP authentication against CAS Server
+ *
+ * @access   public
+ * @author   Dennis Reil <dennis.reil@offis.de>
+ * @package
+ */
 
 require_once 'composer/jasig/phpcas/CAS.php';
 require_once 'lib/classes/cas/CAS_PGTStorage_Cache.php';
 
-class StudipAuthCAS extends StudipAuthSSO {
+class StudipAuthCAS extends StudipAuthSSO
+{
 
-    var $host;
-    var $port;
-    var $uri;
-    var $cacert;
+    public $host;
+    public $port;
+    public $uri;
+    public $cacert;
 
-    var $cas;
-    var $userdata;
+    public $cas;
+    public $userdata;
 
     /**
-    * Constructor
-    *
-    *
-    * @access public
-    *
-    */
-    function __construct() {
-        parent::__construct();
-
-        if (Request::option('sso')) {
+     * Constructor
+     *
+     *
+     *
+     */
+    public function __construct($config = [])
+    {
+        parent::__construct($config);
+        if (!isset($this->plugin_fullname)) {
+            $this->plugin_fullname = _('CAS');
+        }
+        if (!isset($this->login_description)) {
+            $this->login_description = _('für Single Sign On mit CAS');
+        }
+        if (Request::get('sso') === $this->plugin_name) {
             $this->cas = new CAS_Client(CAS_VERSION_2_0, $this->proxy, $this->host, $this->port, $this->uri, false);
 
             if ($this->proxy) {
@@ -68,22 +74,22 @@ class StudipAuthCAS extends StudipAuthSSO {
         return $this->getUser();
     }
 
-    function getUserData($key){
-        $userdataclassname = $GLOBALS["STUDIP_AUTH_CONFIG_CAS"]["user_data_mapping_class"];
-        if (empty($userdataclassname)){
-            echo ("ERROR: no userdataclassname specified.");
+    function getUserData($key)
+    {
+        $userdataclassname = $this->user_data_mapping_class;
+        if (!class_exists($userdataclassname)) {
+            Log::ERROR($this->plugin_name . ': no userdataclassname specified or found.');
             return;
         }
-        require_once($userdataclassname . ".class.php");
         // get the userdata
-        if (empty($this->userdata)){
+        if (empty($this->userdata)) {
             $this->userdata = new $userdataclassname();
         }
-        $result = $this->userdata->getUserData($key, $this->cas->getUser());
-        return $result;
+        return $this->userdata->getUserData($key, $this->cas->getUser());
     }
 
-    function logout(){
+    function logout()
+    {
         // do a global cas logout
         $this->cas = new CAS_Client(CAS_VERSION_2_0, false, $this->host, $this->port, $this->uri, false);
         $this->cas->logout();
diff --git a/lib/classes/auth_plugins/StudipAuthLdap.class.php b/lib/classes/auth_plugins/StudipAuthLdap.class.php
index 3958560e738..5721cf44e87 100644
--- a/lib/classes/auth_plugins/StudipAuthLdap.class.php
+++ b/lib/classes/auth_plugins/StudipAuthLdap.class.php
@@ -1,7 +1,4 @@
 <?php
-# Lifter007: TODO
-# Lifter003: TODO
-# Lifter010: TODO
 // +---------------------------------------------------------------------------+
 // This file is part of Stud.IP
 // StudipAuthLdap.class.php
@@ -25,39 +22,27 @@
 // +---------------------------------------------------------------------------+
 
 /**
-* Stud.IP authentication against LDAP Server
-*
-* Stud.IP authentication against LDAP Server
-*
-* @access   public
-* @author   André Noack <noack@data-quest.de>
-* @package
-*/
-class StudipAuthLdap extends StudipAuthAbstract {
-
-    var $anonymous_bind = true;
-
-    var $host;
-    var $base_dn;
-    var $username_attribute = 'uid';
-    var $ldap_filter;
-    var $bad_char_regex =  '/[^0-9_a-zA-Z]/';
-
-    var $conn = null;
-    var $user_data = null;
+ * Stud.IP authentication against LDAP Server
+ *
+ * Stud.IP authentication against LDAP Server
+ *
+ * @access   public
+ * @author   André Noack <noack@data-quest.de>
+ * @package
+ */
+class StudipAuthLdap extends StudipAuthAbstract
+{
 
-    /**
-    * Constructor
-    *
-    *
-    * @access public
-    *
-    */
-    function __construct()
-    {
-        //calling the baseclass constructor
-        parent::__construct();
-    }
+    public $anonymous_bind = true;
+
+    public $host;
+    public $base_dn;
+    public $username_attribute = 'uid';
+    public $ldap_filter;
+    public $bad_char_regex = '/[^0-9_a-zA-Z]/';
+
+    public $conn = null;
+    public $user_data = null;
 
 
     function getLdapFilter($username)
@@ -76,16 +61,16 @@ class StudipAuthLdap extends StudipAuthAbstract {
     function doLdapConnect()
     {
         if (!($this->conn = ldap_connect($this->host))) {
-            $this->error_msg = _("Keine Verbindung zum LDAP Server möglich.");
+            $this->error_msg = _('Keine Verbindung zum LDAP Server möglich.');
             return false;
         }
-        if (!($r = ldap_set_option($this->conn, LDAP_OPT_PROTOCOL_VERSION, 3))){
-            $this->error_msg = _("Setzen der LDAP Protokolversion fehlgeschlagen.");
+        if (!($r = ldap_set_option($this->conn, LDAP_OPT_PROTOCOL_VERSION, 3))) {
+            $this->error_msg = _('Setzen der LDAP Protokolversion fehlgeschlagen.');
             return false;
         }
         if ($this->start_tls) {
             if (!ldap_start_tls($this->conn)) {
-                $this->error_msg = _("\"Start TLS\" fehlgeschlagen.");
+                $this->error_msg = _('"Start TLS" fehlgeschlagen.');
                 return false;
             }
         }
@@ -94,60 +79,60 @@ class StudipAuthLdap extends StudipAuthAbstract {
 
     function getUserDn($username)
     {
-        $user_dn = "";
+        $user_dn = '';
 
-        if ($this->anonymous_bind){
-            if (!($r = @ldap_bind($this->conn))){
-                $this->error_msg =_("Anonymer Bind fehlgeschlagen.") . $this->getLdapError();
+        if ($this->anonymous_bind) {
+            if (!($r = @ldap_bind($this->conn))) {
+                $this->error_msg = _('Anonymer Bind fehlgeschlagen.') . $this->getLdapError();
                 return false;
             }
-            if (!($result = @ldap_search($this->conn, $this->base_dn, $this->getLdapFilter($username), ['dn']))){
-                $this->error_msg = _("Anonymes Durchsuchen des LDAP Baumes fehlgeschlagen.") .$this->getLdapError();
+            if (!($result = @ldap_search($this->conn, $this->base_dn, $this->getLdapFilter($username), ['dn']))) {
+                $this->error_msg = _('Anonymes Durchsuchen des LDAP Baumes fehlgeschlagen.') . $this->getLdapError();
                 return false;
             }
-            if (!ldap_count_entries($this->conn, $result)){
-                $this->error_msg = sprintf(_("%s wurde nicht unterhalb von %s gefunden."), $username, $this->base_dn);
+            if (!ldap_count_entries($this->conn, $result)) {
+                $this->error_msg = sprintf(_('%s wurde nicht unterhalb von %s gefunden.'), $username, $this->base_dn);
                 return false;
             }
-            if (!($entry = @ldap_first_entry($this->conn, $result))){
+            if (!($entry = @ldap_first_entry($this->conn, $result))) {
                 $this->error_msg = $this->getLdapError();
                 return false;
             }
-            if (!($user_dn = @ldap_get_dn($this->conn, $entry))){
+            if (!($user_dn = @ldap_get_dn($this->conn, $entry))) {
                 $this->error_msg = $this->getLdapError();
                 return false;
             }
         } else {
-            $user_dn = $this->username_attribute . "=" . $username . "," . $this->base_dn;
+            $user_dn = $this->username_attribute . '=' . $username . ',' . $this->base_dn;
         }
         return $user_dn;
     }
 
     function doLdapBind($username, $password)
     {
-        if (!$this->doLdapConnect()){
+        if (!$this->doLdapConnect()) {
             return false;
         }
-        if (!($user_dn = $this->getUserDn($username))){
+        if (!($user_dn = $this->getUserDn($username))) {
             return false;
         }
-        if (!$password){
-            $this->error_msg = _("Kein Passwort eingegeben."); //some ldap servers seem to allow binding with a user dn and  without a password, if anonymous bind is enabled
+        if (!$password) {
+            $this->error_msg = _('Kein Passwort eingegeben.'); //some ldap servers seem to allow binding with a user dn and  without a password, if anonymous bind is enabled
             return false;
         }
-        if (!($r = @ldap_bind($this->conn, $user_dn, $password))){
-            if(ldap_errno($this->conn) == 49) {
-                $this->error_msg = _("Bitte überprüfen Sie ihre Zugangsdaten.");
+        if (!($r = @ldap_bind($this->conn, $user_dn, $password))) {
+            if (ldap_errno($this->conn) == 49) {
+                $this->error_msg = _('Bitte überprüfen Sie ihre Zugangsdaten.');
             }
-            $this->error_msg = _("Anmeldung fehlgeschlagen.") . $this->getLdapError();
+            $this->error_msg = _('Anmeldung fehlgeschlagen.') . $this->getLdapError();
             return false;
         }
-        if (!($result = @ldap_search($this->conn, $user_dn, "objectclass=*"))){
-            $this->error_msg = _("Abholen der Benutzer Attribute fehlgeschlagen.") .$this->getLdapError();
+        if (!($result = @ldap_search($this->conn, $user_dn, 'objectclass=*'))) {
+            $this->error_msg = _('Abholen der Benutzer Attribute fehlgeschlagen.') . $this->getLdapError();
             return false;
         }
-        if (@ldap_count_entries($this->conn, $result)){
-            if (!($info = @ldap_get_entries($this->conn, $result))){
+        if (@ldap_count_entries($this->conn, $result)) {
+            if (!($info = @ldap_get_entries($this->conn, $result))) {
                 $this->error_msg = $this->getLdapError();
                 return false;
             }
@@ -157,15 +142,15 @@ class StudipAuthLdap extends StudipAuthAbstract {
     }
 
     /**
-    *
-    *
-    *
-    * @access private
-    *
-    */
+     *
+     *
+     *
+     * @access private
+     *
+     */
     function isAuthenticated($username, $password)
     {
-        if (!$this->doLdapBind($username,$password)){
+        if (!$this->doLdapBind($username, $password)) {
             ldap_unbind($this->conn);
             return false;
         }
@@ -174,12 +159,11 @@ class StudipAuthLdap extends StudipAuthAbstract {
     }
 
 
-
     function doLdapMap($map_params)
     {
         if (isset($this->user_data[$map_params][0])) {
             $ret = $this->user_data[$map_params][0];
-            if ($ret[0] == ':') {
+            if ($ret[0] === ':') {
                 $ret = base64_decode($ret);
             }
         }
@@ -202,23 +186,23 @@ class StudipAuthLdap extends StudipAuthAbstract {
 
     function isUsedUsername($username)
     {
-        if (!$this->anonymous_bind){
-            $this->error = _("Kann den Benutzernamen nicht überprüfen, anonymous_bind ist ausgeschaltet!");
+        if (!$this->anonymous_bind) {
+            $this->error = _('Kann den Benutzernamen nicht überprüfen, anonymous_bind ist ausgeschaltet!');
             return false;
         }
-        if (!$this->doLdapConnect()){
+        if (!$this->doLdapConnect()) {
             return false;
         }
-        if (!($r = @ldap_bind($this->conn))){
-            $this->error = _("Anonymer Bind fehlgeschlagen.") . $this->getLdapError();
+        if (!($r = @ldap_bind($this->conn))) {
+            $this->error = _('Anonymer Bind fehlgeschlagen.') . $this->getLdapError();
             return false;
         }
-        if (!($result = @ldap_search($this->conn, $this->base_dn, $this->getLdapFilter($username), ['dn']))){
-            $this->error =  _("Anonymes Durchsuchen des LDAP Baumes fehlgeschlagen.") .$this->getLdapError();
+        if (!($result = @ldap_search($this->conn, $this->base_dn, $this->getLdapFilter($username), ['dn']))) {
+            $this->error = _('Anonymes Durchsuchen des LDAP Baumes fehlgeschlagen.') . $this->getLdapError();
             return false;
         }
-        if (!ldap_count_entries($this->conn, $result)){
-            $this->error_msg = _("Der Benutzername wurde nicht gefunden.");
+        if (!ldap_count_entries($this->conn, $result)) {
+            $this->error_msg = _('Der Benutzername wurde nicht gefunden.');
             return false;
         }
         return true;
@@ -226,6 +210,6 @@ class StudipAuthLdap extends StudipAuthAbstract {
 
     function getLdapError()
     {
-            return _("<br>LDAP Fehler: ") . ldap_error($this->conn) ." (#" . ldap_errno($this->conn) . ")";
+        return _('<br>LDAP Fehler: ') . ldap_error($this->conn) . ' (#' . ldap_errno($this->conn) . ')';
     }
 }
diff --git a/lib/classes/auth_plugins/StudipAuthLdapReadAndBind.class.php b/lib/classes/auth_plugins/StudipAuthLdapReadAndBind.class.php
index 25e87399d64..742f0cba4a6 100644
--- a/lib/classes/auth_plugins/StudipAuthLdapReadAndBind.class.php
+++ b/lib/classes/auth_plugins/StudipAuthLdapReadAndBind.class.php
@@ -26,61 +26,51 @@
 // +---------------------------------------------------------------------------+
 
 /**
-* Stud.IP authentication against LDAP Server
-*
-* Stud.IP authentication against LDAP Server using read-only account and
-* following user bind
-*
-* @access   public
-* @author   André Noack <noack@data-quest.de>
-* @package
-*/
-class StudipAuthLdapReadAndBind extends StudipAuthLdap {
+ * Stud.IP authentication against LDAP Server
+ *
+ * Stud.IP authentication against LDAP Server using read-only account and
+ * following user bind
+ *
+ * @access   public
+ * @author   André Noack <noack@data-quest.de>
+ * @package
+ */
+class StudipAuthLdapReadAndBind extends StudipAuthLdap
+{
 
-    var $anonymous_bind = false;
+    public $anonymous_bind = false;
 
-    var $reader_dn;
-    var $reader_password;
+    public $reader_dn;
+    public $reader_password;
 
-    /**
-    * Constructor
-    *
-    *
-    * @access public
-    *
-    */
-    function __construct() {
-        //calling the baseclass constructor
-        parent::__construct();
-    }
-
-
-    function getUserDn($username){
-        $user_dn = "";
-        if (!($r = @ldap_bind($this->conn, $this->reader_dn, $this->reader_password))){
-            $this->error_msg = sprintf(_("Anmeldung von %s fehlgeschlagen."),$this->reader_dn) . $this->getLdapError();
+    function getUserDn($username)
+    {
+        $user_dn = '';
+        if (!($r = @ldap_bind($this->conn, $this->reader_dn, $this->reader_password))) {
+            $this->error_msg = sprintf(_('Anmeldung von %s fehlgeschlagen.'), $this->reader_dn) . $this->getLdapError();
             return false;
         }
-        if (!($result = @ldap_search($this->conn, $this->base_dn, $this->getLdapFilter($username), ['dn']))){
-            $this->error_msg = _("Durchsuchen des LDAP Baumes fehlgeschlagen.") .$this->getLdapError();
+        if (!($result = @ldap_search($this->conn, $this->base_dn, $this->getLdapFilter($username), ['dn']))) {
+            $this->error_msg = _('Durchsuchen des LDAP Baumes fehlgeschlagen.') . $this->getLdapError();
             return false;
         }
-        if (!ldap_count_entries($this->conn, $result)){
-            $this->error_msg = sprintf(_("%s wurde nicht unterhalb von %s gefunden."), $username, $this->base_dn);
+        if (!ldap_count_entries($this->conn, $result)) {
+            $this->error_msg = sprintf(_('%s wurde nicht unterhalb von %s gefunden.'), $username, $this->base_dn);
             return false;
         }
-        if (!($entry = @ldap_first_entry($this->conn, $result))){
+        if (!($entry = @ldap_first_entry($this->conn, $result))) {
             $this->error_msg = $this->getLdapError();
             return false;
         }
-        if (!($user_dn = @ldap_get_dn($this->conn, $entry))){
+        if (!($user_dn = @ldap_get_dn($this->conn, $entry))) {
             $this->error_msg = $this->getLdapError();
             return false;
         }
         return $user_dn;
     }
 
-    function isUsedUsername($username){
+    function isUsedUsername($username)
+    {
         if (!$this->doLdapConnect()) {
             return false;
         }
diff --git a/lib/classes/auth_plugins/StudipAuthOIDC.class.php b/lib/classes/auth_plugins/StudipAuthOIDC.class.php
new file mode 100644
index 00000000000..adfe9c9a4b0
--- /dev/null
+++ b/lib/classes/auth_plugins/StudipAuthOIDC.class.php
@@ -0,0 +1,112 @@
+<?php
+/*
+ * StudipAuthOpenID.class.php - Stud.IP authentication using OpenID Connect
+ * Copyright (c) 2021  André Noack <noack@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.
+ */
+
+use Jumbojett\OpenIDConnectClient;
+use Jumbojett\OpenIDConnectClientException;
+
+class StudipAuthOIDC extends StudipAuthSSO
+{
+    /**
+     * @var OpenIDConnectClient
+     */
+    private $oidc;
+
+    /**
+     * @var string
+     */
+    public $provider_url;
+    /**
+     * @var string
+     */
+    public $client_id;
+    /**
+     * @var string
+     */
+    public $client_secret;
+
+
+    /**
+     * @param array $config
+     */
+    public function __construct($config = [])
+    {
+        parent::__construct($config);
+        if (Request::get('sso') === $this->plugin_name) {
+            $this->oidc = new OpenIDConnectClient($this->provider_url, $this->client_id, $this->client_secret);
+            if (isset($this->ssl_options)) {
+                foreach ($this->ssl_options as $option_key => $option_value) {
+                    if (isset($option_value)) {
+                        $this->oidc->{'set' . $option_key}($option_value);
+                    }
+                }
+                if (Config::get()->HTTP_PROXY) {
+                    $this->oidc->setHttpProxy(Config::get()->HTTP_PROXY);
+                }
+                $return_url = URLHelper::getScriptURL($GLOBALS['ABSOLUTE_URI_STUDIP'] . 'index.php', ['sso' => $this->plugin_name, 'again' => 'yes']);
+                $this->oidc->setRedirectURL($return_url);
+                $this->oidc->addScope(['openid', 'email', 'profile']);
+            }
+        }
+    }
+
+    /**
+     * Validate the username passed to the auth plugin.
+     *
+     * @param string $username
+     *
+     * @return  string  username openid attribute user_id@domain
+     *
+     * @throws OpenIDConnectClientException
+     */
+    public function verifyUsername($username)
+    {
+
+        $this->oidc->authenticate();
+        $this->userdata = (array)$this->oidc->requestUserInfo();
+        if (isset($this->userdata['sub'])) {
+            return $this->userdata['username'] = $this->userdata['sub'] . '@' . $this->domain;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Return the current username of the pending authentication request.
+     */
+    public function getUser()
+    {
+        return $this->userdata['username'];
+    }
+
+    /**
+     * Get the user domains to assign to the current user (if any).
+     *
+     * @return array    array of user domain names
+     */
+    public function getUserDomains()
+    {
+        return $this->domain ? [$this->domain] : null;
+    }
+
+    /**
+     * Callback that can be used in user_data_mapping array.
+     *
+     * @see https://openid.net/specs/openid-connect-basic-1_0.html#StandardClaims
+     *
+     * @param string  key
+     *
+     * @return  string  parameter value (null if not set)
+     */
+    public function getUserData($key)
+    {
+        return $this->userdata[$key];
+    }
+}
diff --git a/lib/classes/auth_plugins/StudipAuthSSO.class.php b/lib/classes/auth_plugins/StudipAuthSSO.class.php
index 65e8cd1c14f..752fa59a1b4 100644
--- a/lib/classes/auth_plugins/StudipAuthSSO.class.php
+++ b/lib/classes/auth_plugins/StudipAuthSSO.class.php
@@ -17,6 +17,16 @@
  */
 abstract class StudipAuthSSO extends StudipAuthAbstract
 {
+    /**
+     * @var string the descriptive name of the authentication plugin
+     */
+    public $plugin_fullname;
+
+    /**
+     * @var string a short description, when present it is shown on the login page
+     */
+    public $login_description;
+
     /**
      * Return the current username.
      */
diff --git a/lib/classes/auth_plugins/StudipAuthShib.class.php b/lib/classes/auth_plugins/StudipAuthShib.class.php
index 5ac00ed365a..8f31eef5237 100644
--- a/lib/classes/auth_plugins/StudipAuthShib.class.php
+++ b/lib/classes/auth_plugins/StudipAuthShib.class.php
@@ -14,28 +14,39 @@
 
 class StudipAuthShib extends StudipAuthSSO
 {
-    var $env_remote_user = 'HTTP_REMOTE_USER';
-    var $local_domain;
-    var $session_initiator;
-    var $validate_url;
-    var $userdata;
+    public $env_remote_user = 'HTTP_REMOTE_USER';
+    public $local_domain;
+    public $session_initiator;
+    public $validate_url;
+    public $userdata;
+    public $username_attribute = 'username';
 
     /**
      * Constructor: read auth information from remote SP.
      */
-    function __construct()
+    public function __construct($config = [])
     {
-        parent::__construct();
+        parent::__construct($config);
 
-        if (Request::option('sso') && isset($this->validate_url) && isset($_REQUEST['token'])) {
+        if (!isset($this->plugin_fullname)) {
+            $this->plugin_fullname = _('Shibboleth');
+        }
+        if (!isset($this->login_description)) {
+            $this->login_description = _('für Single Sign On mit Shibboleth');
+        }
+
+        if (Request::get('sso') === $this->plugin_name && isset($this->validate_url) && isset($_REQUEST['token'])) {
             $context = get_default_http_stream_context($this->validate_url);
-            $auth = file_get_contents($this->validate_url.'/'.$_REQUEST['token'], false, $context);
+            $auth = file_get_contents($this->validate_url . '/' . $_REQUEST['token'], false, $context);
 
             $this->userdata = json_decode($auth, true);
 
+            if ($this->username_attribute !== 'username') {
+                $this->userdata['username'] = $this->userdata[$this->username_attribute];
+            }
             if (isset($this->local_domain)) {
                 $this->userdata['username'] =
-                    str_replace('@'.$this->local_domain, '', $this->userdata['username']);
+                    str_replace('@' . $this->local_domain, '', $this->userdata['username']);
             }
         }
     }
@@ -43,7 +54,7 @@ class StudipAuthShib extends StudipAuthSSO
     /**
      * Return the current username.
      */
-    function getUser ()
+    function getUser()
     {
         return $this->userdata['username'];
     }
@@ -51,7 +62,7 @@ class StudipAuthShib extends StudipAuthSSO
     /**
      * Return the current URL (including parameters).
      */
-    function getURL ()
+    function getURL()
     {
         $url = $_SERVER['HTTPS'] == 'on' ? 'https' : 'http';
         $url .= '://';
@@ -64,7 +75,7 @@ class StudipAuthShib extends StudipAuthSSO
 
         if ($_SERVER['HTTPS'] == 'on' && $_SERVER['SERVER_PORT'] != 443 ||
             $_SERVER['HTTPS'] != 'on' && $_SERVER['SERVER_PORT'] != 80) {
-            $url .= ':'.$_SERVER['SERVER_PORT'];
+            $url .= ':' . $_SERVER['SERVER_PORT'];
         }
 
         $url .= $_SERVER['REQUEST_URI'];
@@ -75,7 +86,7 @@ class StudipAuthShib extends StudipAuthSSO
      * Validate the username passed to the auth plugin.
      * Note: This triggers authentication if needed.
      */
-    function verifyUsername ($username)
+    function verifyUsername($username)
     {
         if (isset($this->userdata)) {
             // use cached user information
@@ -89,15 +100,15 @@ class StudipAuthShib extends StudipAuthSSO
         }
 
         if (empty($remote_user) || isset($this->validate_url)) {
-            if ($_REQUEST['sso'] == 'shib') {
+            if (Request::get('sso') === $this->plugin_name) {
                 // force Shibboleth authentication (lazy session)
                 $shib_url = $this->session_initiator;
-                $shib_url .= mb_strpos($shib_url, '?') === false ? '?' : '&';
-                $shib_url .= 'target='.urlencode($this->getURL());
+                $shib_url .= strpos($shib_url, '?') === false ? '?' : '&';
+                $shib_url .= 'target=' . urlencode($this->getURL());
 
                 // break redirection loop in case of misconfiguration
-                if (mb_strstr($_SERVER['HTTP_REFERER'], 'target=') == false) {
-                    header('Location: '.$shib_url);
+                if (strstr($_SERVER['HTTP_REFERER'], 'target=') === false) {
+                    header('Location: ' . $shib_url);
                     echo '<html></html>';
                     exit();
                 }
@@ -107,10 +118,6 @@ class StudipAuthShib extends StudipAuthSSO
             return NULL;
         }
 
-        if (isset($this->local_domain)) {
-            $remote_user = str_replace('@'.$this->local_domain, '', $remote_user);
-        }
-
         // import authentication information
         $this->userdata['username'] = $remote_user;
 
@@ -121,13 +128,20 @@ class StudipAuthShib extends StudipAuthSSO
             }
         }
 
+        if ($this->username_attribute !== 'username') {
+            $this->userdata['username'] = $this->userdata[$this->username_attribute];
+        }
+        if (isset($this->local_domain)) {
+            $this->userdata['username'] =
+                str_replace('@' . $this->local_domain, '', $this->userdata['username']);
+        }
         return $this->getUser();
     }
 
     /**
      * Get the user domains to assign to the current user.
      */
-    function getUserDomains ()
+    function getUserDomains()
     {
         $user = $this->getUser();
         $pos = mb_strpos($user, '@');
@@ -142,7 +156,7 @@ class StudipAuthShib extends StudipAuthSSO
     /**
      * Callback that can be used in user_data_mapping array.
      */
-    function getUserData ($key)
+    function getUserData($key)
     {
         $data = explode(';', $this->userdata[$key]);
 
diff --git a/lib/classes/auth_plugins/StudipAuthStandard.class.php b/lib/classes/auth_plugins/StudipAuthStandard.class.php
index c195053cfc9..1a2c2bae556 100644
--- a/lib/classes/auth_plugins/StudipAuthStandard.class.php
+++ b/lib/classes/auth_plugins/StudipAuthStandard.class.php
@@ -38,18 +38,6 @@ class StudipAuthStandard extends StudipAuthAbstract
 
     var $bad_char_regex =  false;
 
-    /**
-     * Constructor
-     *
-     *
-     * @access public
-     *        
-     */
-    function __construct()
-    {
-        parent::__construct();
-    }
-
     /**
     *
     *
@@ -61,19 +49,19 @@ class StudipAuthStandard extends StudipAuthAbstract
     {
         $user = User::findByUsername($username);
         if (!$user || !$password || mb_strlen($password) > 72) {
-            $this->error_msg= _("Ungültige Benutzername/Passwort-Kombination!") ;
+            $this->error_msg= _('Ungültige Benutzername/Passwort-Kombination!') ;
             return false;
-        } elseif ($user->username != $username) {
-            $this->error_msg = _("Bitte achten Sie auf korrekte Gro&szlig;-Kleinschreibung beim Username!");
+        } elseif ($user->username !== $username) {
+            $this->error_msg = _('Bitte achten Sie auf korrekte Groß-Kleinschreibung beim Username!');
             return false;
-        } elseif (!is_null($user->auth_plugin) && $user->auth_plugin != "standard") {
-            $this->error_msg = sprintf(_("Dieser Benutzername wird bereits über %s authentifiziert!"),$user->auth_plugin) ;
+        } elseif (!is_null($user->auth_plugin) && $user->auth_plugin !== 'standard') {
+            $this->error_msg = sprintf(_('Dieser Benutzername wird bereits über %s authentifiziert!'),$user->auth_plugin) ;
             return false;
         } else {
             $pass = $user->password;   // Password is stored as a md5 hash
         }
         $hasher = UserManagement::getPwdHasher();
-        $old_style_check = (mb_strlen($pass) == 32 && md5($password) == $pass);
+        $old_style_check = (strlen($pass) === 32 && md5($password) === $pass);
         $migrated_check = $hasher->CheckPassword(md5($password), $pass);
         $check = $hasher->CheckPassword($password, $pass);
         $old_encoding_check = $hasher->CheckPassword(legacy_studip_utf8decode($password), $pass);
@@ -85,7 +73,7 @@ class StudipAuthStandard extends StudipAuthAbstract
         }
 
         if (!($check || $migrated_check || $old_style_check || $old_encoding_check)) {
-            $this->error_msg= _("Das Passwort ist falsch!");
+            $this->error_msg= _('Das Passwort ist falsch!');
             return false;
         } else {
             return true;
diff --git a/lib/navigation/LoginNavigation.php b/lib/navigation/LoginNavigation.php
index cc6372bace3..db212364ccc 100644
--- a/lib/navigation/LoginNavigation.php
+++ b/lib/navigation/LoginNavigation.php
@@ -27,16 +27,12 @@ class LoginNavigation extends Navigation
         $navigation->setDescription(_('für registrierte NutzerInnen'));
         $this->addSubNavigation('login', $navigation);
 
-        if (in_array('CAS', $GLOBALS['STUDIP_AUTH_PLUGIN'])) {
-            $navigation = new Navigation(_('Login'), 'index.php?again=yes&sso=cas');
-            $navigation->setDescription(_('für Single Sign On mit CAS'));
-            $this->addSubNavigation('login_cas', $navigation);
-        }
-
-        if (in_array('Shib', $GLOBALS['STUDIP_AUTH_PLUGIN'])) {
-            $navigation = new Navigation(_('Shibboleth Login'), 'index.php?again=yes&sso=shib');
-            $navigation->setDescription(_('für Single Sign On mit Shibboleth'));
-            $this->addSubNavigation('login_shib', $navigation);
+        foreach (StudipAuthAbstract::getInstance() as $auth_plugin) {
+            if ($auth_plugin instanceof StudipAuthSSO && isset($auth_plugin->login_description)) {
+                $navigation = new Navigation($auth_plugin->plugin_fullname . ' ' . _('Login'), 'index.php?again=yes&sso=' . $auth_plugin->plugin_name);
+                $navigation->setDescription($auth_plugin->login_description);
+                $this->addSubNavigation('login_' . $auth_plugin->plugin_name, $navigation);
+            }
         }
 
         if (Config::get()->ENABLE_SELF_REGISTRATION) {
-- 
GitLab