aboutsummaryrefslogtreecommitdiff
path: root/lib/classes/auth_plugins
diff options
context:
space:
mode:
authorJan-Hendrik Willms <tleilax+github@gmail.com>2021-07-22 16:07:19 +0200
committerJan-Hendrik Willms <tleilax+github@gmail.com>2021-07-22 16:19:12 +0200
commita3da1483a9e689846179159355badfec8073dbec (patch)
tree770dcca6bdf5f6f2a11b0e7fcbbeda6919a3fc52 /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.php13
-rw-r--r--lib/classes/auth_plugins/StudipAuthAbstract.class.php521
-rw-r--r--lib/classes/auth_plugins/StudipAuthCAS.class.php90
-rw-r--r--lib/classes/auth_plugins/StudipAuthIP.class.php19
-rw-r--r--lib/classes/auth_plugins/StudipAuthLTI.class.php142
-rw-r--r--lib/classes/auth_plugins/StudipAuthLdap.class.php231
-rw-r--r--lib/classes/auth_plugins/StudipAuthLdapReadAndBind.class.php91
-rw-r--r--lib/classes/auth_plugins/StudipAuthSSO.class.php41
-rw-r--r--lib/classes/auth_plugins/StudipAuthShib.class.php151
-rw-r--r--lib/classes/auth_plugins/StudipAuthStandard.class.php100
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&szlig;-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;
+ }
+
+}