diff options
| author | Jan-Hendrik Willms <tleilax+github@gmail.com> | 2021-07-22 16:07:19 +0200 |
|---|---|---|
| committer | Jan-Hendrik Willms <tleilax+github@gmail.com> | 2021-07-22 16:19:12 +0200 |
| commit | a3da1483a9e689846179159355badfec8073dbec (patch) | |
| tree | 770dcca6bdf5f6f2a11b0e7fcbbeda6919a3fc52 /lib/classes/auth_plugins | |
current code from svn, revision 62608
Diffstat (limited to 'lib/classes/auth_plugins')
| -rw-r--r-- | lib/classes/auth_plugins/CASUserDataMapping.php | 13 | ||||
| -rw-r--r-- | lib/classes/auth_plugins/StudipAuthAbstract.class.php | 521 | ||||
| -rw-r--r-- | lib/classes/auth_plugins/StudipAuthCAS.class.php | 90 | ||||
| -rw-r--r-- | lib/classes/auth_plugins/StudipAuthIP.class.php | 19 | ||||
| -rw-r--r-- | lib/classes/auth_plugins/StudipAuthLTI.class.php | 142 | ||||
| -rw-r--r-- | lib/classes/auth_plugins/StudipAuthLdap.class.php | 231 | ||||
| -rw-r--r-- | lib/classes/auth_plugins/StudipAuthLdapReadAndBind.class.php | 91 | ||||
| -rw-r--r-- | lib/classes/auth_plugins/StudipAuthSSO.class.php | 41 | ||||
| -rw-r--r-- | lib/classes/auth_plugins/StudipAuthShib.class.php | 151 | ||||
| -rw-r--r-- | lib/classes/auth_plugins/StudipAuthStandard.class.php | 100 |
10 files changed, 1399 insertions, 0 deletions
diff --git a/lib/classes/auth_plugins/CASUserDataMapping.php b/lib/classes/auth_plugins/CASUserDataMapping.php new file mode 100644 index 0000000..03c9cd2 --- /dev/null +++ b/lib/classes/auth_plugins/CASUserDataMapping.php @@ -0,0 +1,13 @@ +<? +# Lifter007: TODO +# Lifter003: TODO +# Lifter010: TODO +/** + * Interface for the user mapping used by StudIPAuthCAS + */ +interface CASUserDataMapping { + + // reads one attribute identified by a key of a given user + function getUserData ($key, $username); +} +?>
\ No newline at end of file diff --git a/lib/classes/auth_plugins/StudipAuthAbstract.class.php b/lib/classes/auth_plugins/StudipAuthAbstract.class.php new file mode 100644 index 0000000..3af55c5 --- /dev/null +++ b/lib/classes/auth_plugins/StudipAuthAbstract.class.php @@ -0,0 +1,521 @@ +<?php +# Lifter003: TEST +# Lifter007: TODO +# Lifter010: DONE - no html + +// +---------------------------------------------------------------------------+ +// This file is part of Stud.IP +// StudipAuthAbstract.class.php +// Abstract class, used as a template for authentication plugins +// +// Copyright (c) 2003 André Noack <noack@data-quest.de> +// Suchi & Berg GmbH <info@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 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. +// +---------------------------------------------------------------------------+ + +/** +* 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 { + + /** + * contains error message, if authentication fails + * + * + * @access public + * @var string + */ + var $error_msg; + + /** + * indicates whether the authenticated user logs in for the first time + * + * + * @access public + * @var bool + */ + var $is_new_user = false; + + /** + * array of user domains to assign to each user, can be set in local.inc + * + * @access public + * @var array $user_domains + */ + var $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; + + /** + * 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; + + /** + * text, which precedes error message for the plugin + * + * + * @access public + * @var string + */ + var $error_head; + + 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) + { + 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; + } + } + return ($plugin_name) ? self::$plugin_instances[mb_strtoupper("StudipAuth" . $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) + { + + $plugins = StudipAuthAbstract::GetInstance(); + $error = false; + $uid = false; + foreach ($plugins as $object) { + // SSO plugins can't be used + if ($object instanceof StudipAuthSSO) { + continue; + } + if ($user = $object->authenticateUser($username, $password)) { + if ($user) { + $uid = $user->id; + $locked = $user['locked']; + $key = $user['validation_key']; + $checkIPRange = ($GLOBALS['ENABLE_ADMIN_IP_CHECK'] && $user['perms'] === 'admin') + || ($GLOBALS['ENABLE_ROOT_IP_CHECK'] && $user['perms'] === 'root'); + + $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>"; + return ['uid' => false, 'error' => $error]; + } else if ($locked == "1") { + $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>"; + 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>"; + } + } + 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) + { + $plugins = StudipAuthAbstract::GetInstance(); + $error = false; + $found = false; + foreach ($plugins as $object) { + if ($found = $object->isUsedUsername($username)) { + return ['found' => $found,'error' => $error]; + } else { + $error .= (($object->error_head) ? ("<b>" . $object->error_head . ":</b> ") : "") . $object->error_msg . "<br>"; + } + } + 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) + { + if (!$plugin_name) { + return false; + } + $plugin = StudipAuthAbstract::GetInstance($plugin_name); + return (is_object($plugin) ? $plugin->isMappedField($field_name) : false); + } + + /** + * static method to check if ip address belongs to allowed range + * + * @return bool true if the client ip address is within the valid range + */ + public static function CheckIPRange() + { + $ip = $_SERVER['REMOTE_ADDR']; + $version = mb_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) { + if (self::$method($ip, $range)) { + return true; + } + } + } + return false; + } + + /** + * @param $ip string IPv4 adress + * @param $range array assoc array with [start] & [end] + * @return bool + */ + public static function CheckIPRangeV4($ip, $range) + { + $ipv4 = ip2long($ip); + if ($ipv4 === false) { + return false; // invalid ip address + } + + $start = ip2long($range['start']); + $end = ip2long($range['end']); + + return $ipv4 >= $start && $ipv4 <= $end; + } + + /** + * @param $ip string IPv6 address + * @param $range array assoc array with [start] & [end] + * @return bool + */ + public static function CheckIPRangeV6($ip, $range) + { + $ipv6 = inet_pton($ip); + if ($ipv6 === false) { + return false; // invalid ip address + } + + $start = inet_pton($range['start']); + $end = inet_pton($range['end']); + + return mb_strlen($ipv6) === mb_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() + { + $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)]; + //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; + } + } + } + + /** + * 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) + { + $username = $this->verifyUsername($username); + if ($this->isAuthenticated($username, $password)) { + if ($user = $this->getStudipUser($username)) { + $this->doDataMapping($user); + if ($this->is_new_user){ + $this->doNewUserInit($user); + } + $this->setUserDomains($user); + } + return $user; + } else { + return false; + } + } + + /** + * 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>"; + return false; + } + if ($auth_plugin != $this->plugin_name){ + $this->error_msg = sprintf(_("Dieser Benutzername wird bereits über %s authentifiziert!"),$auth_plugin) . "<br>"; + return false; + } + return $user; + } + $new_user = new User(); + $new_user->username = $username; + $new_user->perms = 'autor'; + $new_user->auth_plugin = $this->plugin_name; + $new_user->preferred_language = $_SESSION['_language']; + if ($new_user->store()) { + $this->is_new_user = true; + return $new_user; + } + } + + /** + * initialize a new user + * + * this method is invoked for one time, if a new user logs in ($this->is_new_user is true) + * place special treatment of new users here + * + * @access private + * @param + * User the user object + * @return bool + */ + function doNewUserInit($user) + { + // auto insertion of new users, according to $AUTO_INSERT_SEM[] (defined in local.inc) + AutoInsert::instance()->saveUser($user->id, $user->perms); + } + + /** + * This method sets the user domains for the current user. + * + * @access private + * @param User the user object + */ + function setUserDomains ($user) { + $user_domains = $this->getUserDomains(); + $uid = $user->id; + if (isset($user_domains)) { + $old_domains = UserDomain::getUserDomainsForUser($uid); + + foreach ($old_domains as $domain) { + if (!in_array($domain->id, $user_domains)) { + $domain->removeUser($uid); + } + } + + foreach ($user_domains as $user_domain) { + $domain = new UserDomain($user_domain); + + if ($domain->isNew()) { + $domain->name = $user_domain; + $domain->store(); + } + + if (!in_array($domain, $old_domains)) { + $domain->addUser($uid); + } + } + } + } + + /** + * Get the user domains to assign to the current user. + */ + 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 + */ + function doDataMapping($user) + { + if ($user && is_array($this->user_data_mapping)) { + foreach($this->user_data_mapping as $key => $value){ + if (method_exists($this, $value['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 (isset($mapped_value)) { + $user->setValue($field, $mapped_value); + } + } else { + call_user_func([$this, $value['callback']],[$table,$field,$user,$value['map_args']]); + } + } + } + return $user->store(); + } + return false; + } + + /** + * 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 + */ + function verifyUsername($username) + { + 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); + } + } + + /** + * 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()"); + 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()"); + return false; + } +} diff --git a/lib/classes/auth_plugins/StudipAuthCAS.class.php b/lib/classes/auth_plugins/StudipAuthCAS.class.php new file mode 100644 index 0000000..408da78 --- /dev/null +++ b/lib/classes/auth_plugins/StudipAuthCAS.class.php @@ -0,0 +1,90 @@ +<?php +# Lifter007: TODO +# Lifter003: TODO +# Lifter010: TODO +/** +* Stud.IP authentication against CAS Server +* +* @access public +* @author Dennis Reil <dennis.reil@offis.de> +* @package +*/ + +require_once 'lib/classes/cas/CAS_PGTStorage_Cache.php'; + +class StudipAuthCAS extends StudipAuthSSO { + + var $host; + var $port; + var $uri; + var $cacert; + + var $cas; + var $userdata; + + /** + * Constructor + * + * + * @access public + * + */ + function __construct() { + parent::__construct(); + + if (Request::option('sso')) { + $this->cas = new CAS_Client(CAS_VERSION_2_0, $this->proxy, $this->host, $this->port, $this->uri, false); + + if ($this->proxy) { + URLHelper::setBaseUrl($GLOBALS['ABSOLUTE_URI_STUDIP']); + $this->cas->setPGTStorage(new CAS_PGTStorage_Cache($this->cas)); + $this->cas->setCallbackURL(URLHelper::getURL('dispatch.php/cas/proxy')); + } + + if (isset($this->cacert)) { + $this->cas->setCasServerCACert($this->cacert); + } else { + $this->cas->setNoCasServerValidation(); + } + } + } + + /** + * Return the current username. + */ + function getUser() + { + return $this->cas->getUser(); + } + + /** + * Validate the username passed to the auth plugin. + * Note: This triggers authentication if needed. + */ + function verifyUsername($username) + { + $this->cas->forceAuthentication(); + return $this->getUser(); + } + + function getUserData($key){ + $userdataclassname = $GLOBALS["STUDIP_AUTH_CONFIG_CAS"]["user_data_mapping_class"]; + if (empty($userdataclassname)){ + echo ("ERROR: no userdataclassname specified."); + return; + } + require_once($userdataclassname . ".class.php"); + // get the userdata + if (empty($this->userdata)){ + $this->userdata = new $userdataclassname(); + } + $result = $this->userdata->getUserData($key, $this->cas->getUser()); + return $result; + } + + 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/StudipAuthIP.class.php b/lib/classes/auth_plugins/StudipAuthIP.class.php new file mode 100644 index 0000000..9c1a177 --- /dev/null +++ b/lib/classes/auth_plugins/StudipAuthIP.class.php @@ -0,0 +1,19 @@ +<?php +/* + * StudipAuthIP.class.php - Stud.IP authentication with user ip + * Copyright (c) 2014 Florian Bieringer, Uni Passau + * + * 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 StudipAuthIP extends StudipAuthAbstract { + + /** + * {@inheritdoc} + */ + function isAuthenticated($username, $password) { + return $this->allowed_users[$username] && in_array($_SERVER['REMOTE_ADDR'], $this->allowed_users[$username]); + } +} diff --git a/lib/classes/auth_plugins/StudipAuthLTI.class.php b/lib/classes/auth_plugins/StudipAuthLTI.class.php new file mode 100644 index 0000000..e8c316f --- /dev/null +++ b/lib/classes/auth_plugins/StudipAuthLTI.class.php @@ -0,0 +1,142 @@ +<?php +/* + * StudipAuthLTI.class.php - Stud.IP authentication against LTI 1.1 consumer + * Copyright (c) 2018 Elmar Ludwig + * + * 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 StudipAuthLTI extends StudipAuthSSO +{ + public $consumer_keys; + public $username; + public $domain; + + /** + * Validate the username passed to the auth plugin. Note: This implementation + * ignores the username parameter and always uses the data passed via the LTI + * parameters "lis_person_sourcedid" or "user_id". + * + * @param string $username (ignored) + * + * @return string username derived from LTI parameters + * + * @throws InvalidArgumentException if no username can be determined + */ + public function verifyUsername($username) + { + $consumer_key = Request::get('oauth_consumer_key'); + $username = Request::get('lis_person_sourcedid', Request::get('user_id')); + $override = $this->consumer_keys[$consumer_key]['allow_domain_override']; + $domain = $this->consumer_keys[$consumer_key]['domain']; + + if (!$username) { + throw new InvalidArgumentException('user_id must not be empty'); + } + + if ($domain === null) { + $domain = $consumer_key; + } + + if ($override && strpos($username, '@') !== false) { + list($username, $domain) = explode('@', $username); + } + + if ($domain !== '') { + $username .= '@' . $domain; + $this->domain = $domain; + } + + return $this->username = parent::verifyUsername($username); + } + + /** + * Check whether this user can be authenticated. Since we trust the user + * information sent by the LTI consumer, only the OAuth signature is checked. + * + * @param string $username account name + * @param string $password (ignored) + * + * @return bool true if authentication succeeds + * + * @throws OAuthException2 if the signature verification failed + * + */ + public function isAuthenticated($username, $password) + { + require_once 'vendor/oauth-php/library/OAuthRequestVerifier.php'; + + OAuthStore::instance('PDO', [ + 'dsn' => 'mysql:host=' . $GLOBALS['DB_STUDIP_HOST'] . ';dbname=' . $GLOBALS['DB_STUDIP_DATABASE'], + 'username' => $GLOBALS['DB_STUDIP_USER'], + 'password' => $GLOBALS['DB_STUDIP_PASSWORD'] + ]); + + $consumer_key = Request::get('oauth_consumer_key'); + $consumer_secret = $this->consumer_keys[$consumer_key]['consumer_secret']; + + $oarv = new OAuthRequestVerifier(); + $oarv->verifySignature($consumer_secret, false, false); + + return parent::isAuthenticated($username, $password); + } + + /** + * Authenticate this user and handle auto enrollment. If the URL parameter + * "sem_id" is set, the user is automatically redircted to the enrollment + * action for this course. + * + * @param string $username the username to check + * @param string $password the password (ignored) + * + * @return mixed if authentication succeeds: the Stud.IP user, else false + * + * @throws OAuthException2 if the signature verification failed + */ + public function authenticateUser($username, $password) + { + $user = parent::authenticateUser($username, $password); + $course_id = Request::option('sem_id'); + + if ($user && $course_id) { + header('Location: ' . URLHelper::getURL('dispatch.php/lti/index/' . $course_id)); + } + + return $user; + } + + /** + * Return the current username of the pending authentication request. + */ + public function getUser() + { + return $this->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. For LTI, this is + * equivalent to Request::get(), since all launch data is POST parameters. + * @see http://www.imsglobal.org/specs/ltiv1p1/implementation-guide + * + * @param string key (e.g. "lis_person_contact_email_primary") + * + * @return string parameter value (null if not set) + */ + public function getUserData($key) + { + return Request::get($key); + } +} diff --git a/lib/classes/auth_plugins/StudipAuthLdap.class.php b/lib/classes/auth_plugins/StudipAuthLdap.class.php new file mode 100644 index 0000000..3958560 --- /dev/null +++ b/lib/classes/auth_plugins/StudipAuthLdap.class.php @@ -0,0 +1,231 @@ +<?php +# Lifter007: TODO +# Lifter003: TODO +# Lifter010: TODO +// +---------------------------------------------------------------------------+ +// This file is part of Stud.IP +// StudipAuthLdap.class.php +// Stud.IP authentication against LDAP Server +// +// Copyright (c) 2003 André Noack <noack@data-quest.de> +// Suchi & Berg GmbH <info@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 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. +// +---------------------------------------------------------------------------+ + +/** +* 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; + + /** + * Constructor + * + * + * @access public + * + */ + function __construct() + { + //calling the baseclass constructor + parent::__construct(); + } + + + function getLdapFilter($username) + { + if (isset($this->ldap_filter)) { + list($user, $domain) = explode('@', $username); + $search = ['%u', '%U', '%d', '%%']; + $replace = [$username, $user, $domain, '%']; + + return str_replace($search, $replace, $this->ldap_filter); + } + + return $this->username_attribute . '=' . $username; + } + + function doLdapConnect() + { + if (!($this->conn = ldap_connect($this->host))) { + $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."); + return false; + } + if ($this->start_tls) { + if (!ldap_start_tls($this->conn)) { + $this->error_msg = _("\"Start TLS\" fehlgeschlagen."); + return false; + } + } + return true; + } + + function getUserDn($username) + { + $user_dn = ""; + + 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(); + return false; + } + 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))){ + $this->error_msg = $this->getLdapError(); + return false; + } + 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; + } + return $user_dn; + } + + function doLdapBind($username, $password) + { + if (!$this->doLdapConnect()){ + return false; + } + 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 + 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."); + } + $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(); + return false; + } + if (@ldap_count_entries($this->conn, $result)){ + if (!($info = @ldap_get_entries($this->conn, $result))){ + $this->error_msg = $this->getLdapError(); + return false; + } + } + $this->user_data = $info[0]; + return true; + } + + /** + * + * + * + * @access private + * + */ + function isAuthenticated($username, $password) + { + if (!$this->doLdapBind($username,$password)){ + ldap_unbind($this->conn); + return false; + } + ldap_unbind($this->conn); + return true; + } + + + + function doLdapMap($map_params) + { + if (isset($this->user_data[$map_params][0])) { + $ret = $this->user_data[$map_params][0]; + if ($ret[0] == ':') { + $ret = base64_decode($ret); + } + } + return $ret; + } + + function doLdapMapDatafield($params) + { + $datafield_id = $params[1]; + $user = $params[2]; + $ldap_field = $this->doLdapMap($params[3]); + if (isset($ldap_field)) { + $df = $user->datafields->findOneBy('datafield_id', $datafield_id); + if ($df) { + $df->content = $ldap_field; + return true; + } + } + } + + function isUsedUsername($username) + { + if (!$this->anonymous_bind){ + $this->error = _("Kann den Benutzernamen nicht überprüfen, anonymous_bind ist ausgeschaltet!"); + return false; + } + if (!$this->doLdapConnect()){ + return false; + } + 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(); + return false; + } + if (!ldap_count_entries($this->conn, $result)){ + $this->error_msg = _("Der Benutzername wurde nicht gefunden."); + return false; + } + return true; + } + + function getLdapError() + { + 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 new file mode 100644 index 0000000..25e8739 --- /dev/null +++ b/lib/classes/auth_plugins/StudipAuthLdapReadAndBind.class.php @@ -0,0 +1,91 @@ +<?php +# Lifter007: TODO +# Lifter003: TODO +# Lifter010: TODO +// +---------------------------------------------------------------------------+ +// This file is part of Stud.IP +// StudipAuthLdapReadAndBind.class.php +// Stud.IP authentication against LDAP Server using read-only account and +// user bind +// +// Copyright (c) 2006 André Noack <noack@data-quest.de> +// Suchi & Berg GmbH <info@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 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. +// +---------------------------------------------------------------------------+ + +/** +* 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; + + var $reader_dn; + var $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(); + 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(); + return false; + } + 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))){ + $this->error_msg = $this->getLdapError(); + return false; + } + if (!($user_dn = @ldap_get_dn($this->conn, $entry))){ + $this->error_msg = $this->getLdapError(); + return false; + } + return $user_dn; + } + + function isUsedUsername($username){ + if (!$this->doLdapConnect()) { + return false; + } + $ret = (bool)$this->getUserDn($username); + ldap_unbind($this->conn); + return $ret; + } +} diff --git a/lib/classes/auth_plugins/StudipAuthSSO.class.php b/lib/classes/auth_plugins/StudipAuthSSO.class.php new file mode 100644 index 0000000..65e8cd1 --- /dev/null +++ b/lib/classes/auth_plugins/StudipAuthSSO.class.php @@ -0,0 +1,41 @@ +<?php +# Lifter007: TODO +# Lifter003: TODO +# Lifter010: TODO +/* + * StudipAuthSSO.class.php - abstract base class for SSO auth plugins + * Copyright (c) 2007 Elmar Ludwig, Universitaet Osnabrueck + * + * 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. + */ + +/* + * Abstract base class for SSO authentication plugins. + */ +abstract class StudipAuthSSO extends StudipAuthAbstract +{ + /** + * Return the current username. + */ + abstract function getUser (); + + /** + * Check whether this user can be authenticated. The default + * implementation just checks whether $username is not empty. + */ + function isAuthenticated ($username, $password) + { + return !empty($username); + } + + /** + * SSO auth plugins cannot determine if a username is used. + */ + function isUsedUsername ($username) + { + return false; + } +} diff --git a/lib/classes/auth_plugins/StudipAuthShib.class.php b/lib/classes/auth_plugins/StudipAuthShib.class.php new file mode 100644 index 0000000..5ac00ed --- /dev/null +++ b/lib/classes/auth_plugins/StudipAuthShib.class.php @@ -0,0 +1,151 @@ +<?php +# Lifter007: TODO +# Lifter003: TODO +# Lifter010: TODO +/* + * StudipAuthShib.class.php - Stud.IP authentication against Shibboleth server + * Copyright (c) 2007 Elmar Ludwig, Universitaet Osnabrueck + * + * 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 StudipAuthShib extends StudipAuthSSO +{ + var $env_remote_user = 'HTTP_REMOTE_USER'; + var $local_domain; + var $session_initiator; + var $validate_url; + var $userdata; + + /** + * Constructor: read auth information from remote SP. + */ + function __construct() + { + parent::__construct(); + + if (Request::option('sso') && 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); + + $this->userdata = json_decode($auth, true); + + if (isset($this->local_domain)) { + $this->userdata['username'] = + str_replace('@'.$this->local_domain, '', $this->userdata['username']); + } + } + } + + /** + * Return the current username. + */ + function getUser () + { + return $this->userdata['username']; + } + + /** + * Return the current URL (including parameters). + */ + function getURL () + { + $url = $_SERVER['HTTPS'] == 'on' ? 'https' : 'http'; + $url .= '://'; + + if (empty($_SERVER['SERVER_NAME'])) { + $url .= $_SERVER['HTTP_HOST']; + } else { + $url .= $_SERVER['SERVER_NAME']; + } + + if ($_SERVER['HTTPS'] == 'on' && $_SERVER['SERVER_PORT'] != 443 || + $_SERVER['HTTPS'] != 'on' && $_SERVER['SERVER_PORT'] != 80) { + $url .= ':'.$_SERVER['SERVER_PORT']; + } + + $url .= $_SERVER['REQUEST_URI']; + return $url; + } + + /** + * Validate the username passed to the auth plugin. + * Note: This triggers authentication if needed. + */ + function verifyUsername ($username) + { + if (isset($this->userdata)) { + // use cached user information + return $this->getUser(); + } + + $remote_user = $_SERVER[$this->env_remote_user]; + + if (empty($remote_user)) { + $remote_user = $_SERVER['REMOTE_USER']; + } + + if (empty($remote_user) || isset($this->validate_url)) { + if ($_REQUEST['sso'] == 'shib') { + // force Shibboleth authentication (lazy session) + $shib_url = $this->session_initiator; + $shib_url .= mb_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); + echo '<html></html>'; + exit(); + } + } + + // not authenticated + return NULL; + } + + if (isset($this->local_domain)) { + $remote_user = str_replace('@'.$this->local_domain, '', $remote_user); + } + + // import authentication information + $this->userdata['username'] = $remote_user; + + foreach ($_SERVER as $key => $value) { + if (mb_substr($key, 0, 10) == 'HTTP_SHIB_') { + $key = mb_strtolower(mb_substr($key, 10)); + $this->userdata[$key] = $value; + } + } + + return $this->getUser(); + } + + /** + * Get the user domains to assign to the current user. + */ + function getUserDomains () + { + $user = $this->getUser(); + $pos = mb_strpos($user, '@'); + + if ($pos !== false) { + return [mb_substr($user, $pos + 1)]; + } + + return NULL; + } + + /** + * Callback that can be used in user_data_mapping array. + */ + function getUserData ($key) + { + $data = explode(';', $this->userdata[$key]); + + return $data[0]; + } +} diff --git a/lib/classes/auth_plugins/StudipAuthStandard.class.php b/lib/classes/auth_plugins/StudipAuthStandard.class.php new file mode 100644 index 0000000..c195053 --- /dev/null +++ b/lib/classes/auth_plugins/StudipAuthStandard.class.php @@ -0,0 +1,100 @@ +<?php +# Lifter007: TODO +# Lifter003: TODO +# Lifter010: TODO +// +---------------------------------------------------------------------------+ +// This file is part of Stud.IP +// StudipAuthStandard.class.php +// Basic Stud.IP authentication, using the Stud.IP database +// +// Copyright (c) 2003 André Noack <noack@data-quest.de> +// Suchi & Berg GmbH <info@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 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. +// +---------------------------------------------------------------------------+ + +/** +* Basic Stud.IP authentication, using the Stud.IP database +* +* Basic Stud.IP authentication, using the Stud.IP database +* +* @access public +* @author André Noack <noack@data-quest.de> +* @package +*/ +class StudipAuthStandard extends StudipAuthAbstract +{ + + var $bad_char_regex = false; + + /** + * Constructor + * + * + * @access public + * + */ + function __construct() + { + parent::__construct(); + } + + /** + * + * + * + * @access public + * + */ + function isAuthenticated($username, $password) + { + $user = User::findByUsername($username); + if (!$user || !$password || mb_strlen($password) > 72) { + $this->error_msg= _("Ungültige Benutzername/Passwort-Kombination!") ; + return false; + } 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) ; + 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); + $migrated_check = $hasher->CheckPassword(md5($password), $pass); + $check = $hasher->CheckPassword($password, $pass); + $old_encoding_check = $hasher->CheckPassword(legacy_studip_utf8decode($password), $pass); + + if (($migrated_check || $old_style_check || $old_encoding_check) && !$check) { + // time to convert the password + $user->password = $hasher->HashPassword($password); + $user->store(); + } + + if (!($check || $migrated_check || $old_style_check || $old_encoding_check)) { + $this->error_msg= _("Das Passwort ist falsch!"); + return false; + } else { + return true; + } + } + + function isUsedUsername($username) + { + return User::findByUsername($username) ? true : false; + } + +} |
