Skip to content
Snippets Groups Projects
Select Git revision
  • 24377df5463c10696077822a81a8fa20790a4749
  • main default protected
  • studip-rector
  • ci-opt
  • course-members-export-as-word
  • data-vue-app
  • pipeline-improvements
  • webpack-optimizations
  • rector
  • icon-renewal
  • http-client-and-factories
  • jsonapi-atomic-operations
  • vueify-messages
  • tic-2341
  • 135-translatable-study-areas
  • extensible-sorm-action-parameters
  • sorm-configuration-trait
  • jsonapi-mvv-routes
  • docblocks-for-magic-methods
19 results

AvatarClassTest.php

Blame
  • Forked from Stud.IP / Stud.IP
    Source project has a limited visibility.
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    StudipAuthAbstract.class.php 19.34 KiB
    <?php
    // +---------------------------------------------------------------------------+
    // 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
     * @author   André Noack <noack@data-quest.de>
     * @package
     */
    class StudipAuthAbstract
    {
    
        /**
         * contains error message, if authentication fails
         *
         *
         * @var      string $error_msg
         */
        public $error_msg;
    
        /**
         * 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
         *
         * @access  public
         * @var     array $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>'))
         * @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
         * @var      string $plugin_name
         */
        public $plugin_name;
    
        /**
         * text, which precedes error message for the plugin
         *
         *
         * @var      string $error_head
         */
        public $error_head;
    
        /**
         * toggles display of standard login
         *
         *
         * @var      bool $show_login
         */
        public $show_login;
    
        /**
         * @var $plugin_instances
         */
        private static $plugin_instances;
    
        private $config_data = [];
    
        /**
         * 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) {
                    $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[strtoupper($plugin_name)]??null : self::$plugin_instances;
        }
    
        /**
         * static method to check if SSO login is enabled
         *
         * @return bool
         */
        public static function isSSOEnabled(): bool
        {
            self::getInstance();
            foreach (self::$plugin_instances as $auth_plugin) {
                if ($auth_plugin instanceof StudipAuthSSO) {
                    return true;
                }
            }
            return false;
        }
    
        /**
         * static method to check if standard login is enabled
         *
         * @return bool
         */
        public static function isLoginEnabled(): bool
        {
            self::getInstance();
            foreach (self::$plugin_instances as $auth_plugin) {
                if ($auth_plugin->show_login === true) {
                    return true;
                }
            }
            return false;
        }
    
        /**
         * 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();
            $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');
    
                        if ($user->isExpired()) {
                            $error .= _('Dieses Benutzerkonto ist abgelaufen.<br> Wenden Sie sich bitte an die Administration.') . '<BR>';
                            return ['uid' => false, 'error' => $error];
                        } 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>';
                            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
         *
         * @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];
                } 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
         *
         * @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;
            }
            $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 = 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 strlen($ipv6) === strlen($start)
                && $ipv6 >= $start && $ipv6 <= $end;
        }
    
        /**
         * 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 = [])
        {
            //get configuration array set in local inc
            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
            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
         * @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) {
                        $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 $user the user object
         */
        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) {
                    $callback = null;
                    if (is_string($value['callback']) && method_exists($this, $value['callback'])) {
                        $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($callback, $value['map_args']);
                            if (isset($mapped_value)) {
                                $user->setValue($field, $mapped_value);
                            }
                        } else {
                            call_user_func($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!'),
                __METHOD__
            );
            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_msg = sprintf(
                _('Methode %s nicht implementiert!'),
                __METHOD__
            );
            return false;
        }
    
        // Store dynamically set dynamically created properties in $config_data
        public function __isset($offset)
        {
            return isset($this->config_data[$offset]);
        }
    
        public function __set($offset, $value)
        {
            $this->config_data[$offset] = $value;
        }
    
        public function __get($offset)
        {
            return $this->config_data[$offset] ?? null;
        }
    
        public function __unset($offset)
        {
            unset($this->config_data[$offset]);
        }
    }