*/ private static $default_user_attributes = array( 'uid' => array( 'name' => 'login', 'type' => 'string', 'multivalued' => false, 'default' => null, ), 'mail' => array( 'type' => 'string', 'multivalued' => false, 'default' => null, ), 'cn' => array( 'name' => 'name', 'type' => 'string', 'multivalued' => false, 'default' => null, ), ); /** * Initialize * @return bool */ public static function init() { if (!class_exists('Net_LDAP2')) { $path = App::get('auth.ldap.netldap2_path', 'Net/LDAP2.php', 'string'); if (!@include($path)) { Log::error('Fail to load Net_LDAP2 (%s)', $path); return false; } } foreach(array('host', 'basedn') as $param) { if (!App::get("auth.ldap.$param")) { Log :: error('LDAP %s not configured. Check your configuration!', $param); return false; } } self :: $ldap_config = array ( 'host' => implode(' ', App :: get('auth.ldap.host', array(), 'array')), 'basedn' => App :: get('auth.ldap.basedn', null, 'string'), 'binddn' => App :: get('auth.ldap.bind_dn', null, 'string'), 'bindpw' => App :: get('auth.ldap.bind_password', null, 'string'), 'starttls' => App :: get('starttls', false, 'bool'), ); if ($port = App :: get('auth.ldap.port', null, 'int')) self :: $ldap_config['port'] = $port; return true; } /** * Connect on the LDAP directory * @return bool */ private static function connect() { if (is_a(self :: $connection, 'Net_LDAP2')) return true; Log :: debug( 'Connect on LDAP host "%s" as %s (base DN="%s")', self :: $ldap_config['host'], isset(self :: $ldap_config['binddn'])?self :: $ldap_config['binddn']:"anonymous", self :: $ldap_config['basedn'] ); // @phpstan-ignore-next-line self :: $connection = Net_LDAP2::connect(self :: $ldap_config); // @phpstan-ignore-next-line if (PEAR::isError(self :: $connection)) { Log :: error( 'Could not connect to LDAP server (%s): %s', self :: $ldap_config['host'], self :: $connection->getMessage()); self :: $connection = null; return false; } return true; } /** * Make a search in the LDAP directory * @param string $filter The LDAP filter string * @param array|null $attrs Expected attributes (optional, default: all existing attributes) * @param string|null $basedn The base DN of the search (optional, default: configured root base * DN of the LDAP connection) * @param string|array|null $sorted If defined, sort return objects by specified attribute(s) * @param array|null $options Search options as expected by NetLDAP::search() (optional, default: null) */ public static function search($filter, $attrs=null, $basedn=null, $sorted=null, $options=null) { if (!self :: connect()) return false; $options = is_array($options)?$options:array(); if (!is_null($attrs)) $options['attributes'] = $attrs; Log :: debug( 'Run search in LDAP directory with filter "%s" on base DN "%s"', $filter, $basedn?$basedn:"unset"); $search = self :: $connection -> search($basedn, $filter, $options); // @phpstan-ignore-next-line if (PEAR::isError($search)) { Log :: error( 'Error occured searching in LDAP with filter "%s" on base DN "%s": %s', $filter, $basedn?$basedn:"unset", $search->getMessage() ); return false; } $entries = ( $sorted? $search -> sorted(ensure_is_array($sorted)): $search -> entries() ); $result = array(); foreach ($entries as $entry) $result[$entry->dn()] = $entry -> getValues(); return $result; } /** * Cast an LDAP value * @param mixed $value The raw LDAP value * @param string $type The expected type: see cast() for supported types, but boolean value will * be casted as LDAP boolean string. * @return mixed The casted value */ public static function cast($value, $type) { switch($type) { case 'bool': case 'boolean': return $value == 'TRUE'; case 'array_of_bool': case 'array_of_boolean': $values = array(); foreach(ensure_is_array($value) as $value) $values[] = $value == 'TRUE'; return $values; default: return cast($value, $type); } } /** * Retreive LDAP attribute value(s) from LDAP entry * @param array $entry The LDAP entry * @param string $attr The LDAP attribute name * @param bool $all_values Return all values or just the first one (optional, default: false) * @param mixed $default The default value to return if the LDAP attribute is undefined * (optional, default: an empty array if $all_values, null otherwise) * @param string|null $cast The expected type of value (optional, default: string) */ public static function get_attr($entry, $attr, $all_values=False, $default=null, $cast=null) { $values = self :: cast( isset($entry[$attr])?ensure_is_array($entry[$attr]):array(), "array_of_".($cast?$cast:'string') ); if ($values) return $all_values?$values:$values[0]; if ($all_values) return !is_null($default)?$default:array(); return $default; } /** * Retreive a user by its username * @param string $username * @return \EesyPHP\Auth\User|null|false The user object if found, null it not, false in case of error */ public static function get_user($username) { $attrs = App::get('auth.ldap.user_attributes', self :: $default_user_attributes, 'array'); $users = self :: search( str_replace( '[username]', Net_LDAP2_Filter::escape($username), App::get('auth.ldap.user_filter_by_uid', 'uid=[username]', 'string') ), array_keys($attrs), App::get('auth.ldap.user_basedn', null, 'string') ); if (!is_array($users)) { Log::warning('An error occured looking for user "%s" in LDAP directory', $username); return false; } if (!$users) { Log::debug('User "%s" not found in LDAP directory', $username); return null; } if (count($users) > 1) { Log::warning( 'More than on users found with username "%s": %s', $username, implode(' / ', array_keys($users)) ); } $dn = key($users); $info = array('dn' => $dn); foreach($attrs as $attr => $attr_config) { $info[Config::get("name", $attr, 'string', false, $attr_config)] = self :: get_attr( $users[$dn], $attr, Config::get("multivalued", false, 'bool', false, $attr_config), Config::get("default", null, null, false, $attr_config) ); } Log::debug('User "%s" found in LDAP directory (%s):\n%s', $username, $dn, vardump($info)); return new User($username, '\\EesyPHP\\Auth\\LDAP', $info); } /** * Check a user password * @param \EesyPHP\Auth\User $user The user object * @param string $password The password to check * @return boolean */ public static function check_password($user, $password) { $config = self :: $ldap_config; $config['binddn'] = ( App::get('auth.ldap.bind_with_username', false, 'bool')? $user->username: $user->dn ); $config['bindpw'] = $password; $result = Net_LDAP2::connect($config); // @phpstan-ignore-next-line return !PEAR::isError($result); } }