403 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			403 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| /**
 | |
|  * Tiny Tiny RSS plugin for LDAP authentication 
 | |
|  * @author tsmgeek (tsmgeek@gmail.com)
 | |
|  * @author hydrian (ben.tyger@tygerclan.net)
 | |
|  * @copyright GPL2
 | |
|  *  Requires php-ldap 
 | |
|  * @version 2.00
 | |
|  */
 | |
| /**
 | |
|  *  Configuration
 | |
|  *  Put the following options in config.php and customize them for your environment
 | |
|  *
 | |
|  * 	define('LDAP_AUTH_SERVER_URI', 'ldaps://LDAPServerHostname:port/');
 | |
|  * 	define('LDAP_AUTH_USETLS', FALSE); // Enable TLS Support for ldaps://
 | |
|  * 	define('LDAP_AUTH_ALLOW_UNTRUSTED_CERT', TRUE); // Allows untrusted certificate
 | |
|  * 	define('LDAP_AUTH_BASEDN', 'dc=example,dc=com');
 | |
|  * 	define('LDAP_AUTH_ANONYMOUSBEFOREBIND', FALSE);
 | |
|  * 	// ??? will be replaced with the entered username(escaped) at login 
 | |
|  * 	define('LDAP_AUTH_SEARCHFILTER', '(&(objectClass=person)(uid=???))');
 | |
|  * 	// Optional configuration
 | |
|  *      define('LDAP_AUTH_BINDDN', 'cn=serviceaccount,dc=example,dc=com');
 | |
|  *      define('LDAP_AUTH_BINDPW', 'ServiceAccountsPassword');
 | |
|  *      define('LDAP_AUTH_LOGIN_ATTRIB', 'uid');
 | |
|  *  define('LDAP_AUTH_LOG_ATTEMPTS', FALSE);
 | |
|  *    Enable Debug Logging
 | |
|  *  define('LDAP_AUTH_DEBUG', FALSE);
 | |
|  *    
 | |
|  *    
 | |
|  *    
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * 	Notes -
 | |
|  * 	LDAP search does not support follow ldap referals. Referals are disabled to 
 | |
|  * 	allow proper login.  This is particular to Active Directory.  
 | |
|  * 
 | |
|  * 	Also group membership can be supported if the user object contains the
 | |
|  * 	the group membership via attributes.  The following LDAP servers can 
 | |
|  * 	support this.   
 | |
|  * 	 * Active Directory
 | |
|  *   * OpenLDAP support with MemberOf Overlay
 | |
|  *
 | |
|  */
 | |
| class Auth_Ldap extends Plugin implements IAuthModule {
 | |
| 
 | |
|     private $link;
 | |
|     private $host;
 | |
|     private $base;
 | |
|     private $logClass;
 | |
|     private $ldapObj = NULL;
 | |
|     private $_debugMode;
 | |
|     private $_serviceBindDN;
 | |
|     private $_serviceBindPass;
 | |
|     private $_baseDN;
 | |
|     private $_useTLS;
 | |
|     private $_host;
 | |
|     private $_port;
 | |
|     private $_scheme;
 | |
|     private $_schemaCacheEnabled;
 | |
|     private $_anonBeforeBind;
 | |
|     private $_allowUntrustedCerts;
 | |
|     private $_ldapLoginAttrib;
 | |
| 
 | |
|     function about() {
 | |
|         return array(0.05,
 | |
|             "Authenticates against an LDAP server (configured in config.php)",
 | |
|             "hydrian",
 | |
|             true);
 | |
|     }
 | |
| 
 | |
|     function init($host) {
 | |
|         $this->link = $host->get_link();
 | |
|         $this->host = $host;
 | |
|         $this->base = new Auth_Base($this->link);
 | |
| 
 | |
|         $host->add_hook($host::HOOK_AUTH_USER, $this);
 | |
|     }
 | |
| 
 | |
|     private function _log($msg, $level = E_USER_NOTICE, $file = '', $line = 0, $context = '') {
 | |
|         $loggerFunction = Logger::get();
 | |
|         if (is_object($loggerFunction)) {
 | |
|             $loggerFunction->log_error($level, $msg, $file, $line, $context);
 | |
|         } else {
 | |
|             trigger_error($msg, $level);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Logs login attempts
 | |
|      * @param string $username Given username that attempts to log in to TTRSS
 | |
|      * @param string $result "Logging message for type of result. (Success / Fail)"
 | |
|      * @return boolean
 | |
|      * @deprecated
 | |
|      * 
 | |
|      * Now that _log support syslog and log levels and graceful fallback user.  
 | |
|      */
 | |
|     private function _logAttempt($username, $result) {
 | |
| 
 | |
| 
 | |
|         return trigger_error('TT-RSS Login Attempt: user ' . (string) $username .
 | |
|                 ' attempted to login (' . (string) $result . ') from ' . (string) $ip, E_USER_NOTICE
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param string $subject The subject string
 | |
|      * @param string $ignore Set of characters to leave untouched
 | |
|      * @param int $flags Any combination of LDAP_ESCAPE_* flags to indicate the
 | |
|      *                   set(s) of characters to escape.
 | |
|      * @return string
 | |
|      **/
 | |
|     function ldap_escape($subject, $ignore = '', $flags = 0)
 | |
|     {
 | |
|         if (!function_exists('ldap_escape')) {
 | |
|             define('LDAP_ESCAPE_FILTER', 0x01);
 | |
|             define('LDAP_ESCAPE_DN',     0x02);
 | |
|             
 | |
|             static $charMaps = array(
 | |
|                 LDAP_ESCAPE_FILTER => array('\\', '*', '(', ')', "\x00"),
 | |
|                 LDAP_ESCAPE_DN     => array('\\', ',', '=', '+', '<', '>', ';', '"', '#'),
 | |
|             );
 | |
| 
 | |
|             // Pre-process the char maps on first call
 | |
|             if (!isset($charMaps[0])) {
 | |
|                 $charMaps[0] = array();
 | |
|                 for ($i = 0; $i < 256; $i++) {
 | |
|                     $charMaps[0][chr($i)] = sprintf('\\%02x', $i);;
 | |
|                 }
 | |
| 
 | |
|                 for ($i = 0, $l = count($charMaps[LDAP_ESCAPE_FILTER]); $i < $l; $i++) {
 | |
|                     $chr = $charMaps[LDAP_ESCAPE_FILTER][$i];
 | |
|                     unset($charMaps[LDAP_ESCAPE_FILTER][$i]);
 | |
|                     $charMaps[LDAP_ESCAPE_FILTER][$chr] = $charMaps[0][$chr];
 | |
|                 }
 | |
| 
 | |
|                 for ($i = 0, $l = count($charMaps[LDAP_ESCAPE_DN]); $i < $l; $i++) {
 | |
|                     $chr = $charMaps[LDAP_ESCAPE_DN][$i];
 | |
|                     unset($charMaps[LDAP_ESCAPE_DN][$i]);
 | |
|                     $charMaps[LDAP_ESCAPE_DN][$chr] = $charMaps[0][$chr];
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // Create the base char map to escape
 | |
|             $flags = (int)$flags;
 | |
|             $charMap = array();
 | |
|             if ($flags & LDAP_ESCAPE_FILTER) {
 | |
|                 $charMap += $charMaps[LDAP_ESCAPE_FILTER];
 | |
|             }
 | |
|             if ($flags & LDAP_ESCAPE_DN) {
 | |
|                 $charMap += $charMaps[LDAP_ESCAPE_DN];
 | |
|             }
 | |
|             if (!$charMap) {
 | |
|                 $charMap = $charMaps[0];
 | |
|             }
 | |
| 
 | |
|             // Remove any chars to ignore from the list
 | |
|             $ignore = (string)$ignore;
 | |
|             for ($i = 0, $l = strlen($ignore); $i < $l; $i++) {
 | |
|                 unset($charMap[$ignore[$i]]);
 | |
|             }
 | |
| 
 | |
|             // Do the main replacement
 | |
|             $result = strtr($subject, $charMap);
 | |
| 
 | |
|             // Encode leading/trailing spaces if LDAP_ESCAPE_DN is passed
 | |
|             if ($flags & LDAP_ESCAPE_DN) {
 | |
|                 if ($result[0] === ' ') {
 | |
|                     $result = '\\20' . substr($result, 1);
 | |
|                 }
 | |
|                 if ($result[strlen($result) - 1] === ' ') {
 | |
|                     $result = substr($result, 0, -1) . '\\20';
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return $result;
 | |
|         }else{
 | |
|             return ldap_escape($subject, $ignore, $flags);
 | |
|         }    
 | |
|     }
 | |
|         
 | |
|     /**
 | |
|      * Finds client's IP address
 | |
|      * @return string
 | |
|      */
 | |
|     private function _getClientIP() {
 | |
|         if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
 | |
|             //check ip from share internet
 | |
| 
 | |
|             $ip = $_SERVER['HTTP_CLIENT_IP'];
 | |
|         } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
 | |
|             //to check ip is pass from proxy
 | |
|             $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
 | |
|         } else {
 | |
|             $ip = $_SERVER['REMOTE_ADDR'];
 | |
|         }
 | |
| 
 | |
|         return $ip;
 | |
|     }
 | |
| 
 | |
|     private function _getBindDNWord() {
 | |
|         return (strlen($this->_serviceBindDN) > 0 ) ? $this->_serviceBindDN : 'anonymous DN';
 | |
|     }
 | |
| 
 | |
|     private function _getTempDir() {
 | |
|         if (!sys_get_temp_dir()) {
 | |
|             $tmpFile = tempnam();
 | |
|             $tmpDir = dirname($tmpFile);
 | |
|             unlink($tmpFile);
 | |
|             unset($tmpFile);
 | |
|             return $tmpDir;
 | |
|         } else {
 | |
|             return sys_get_temp_dir();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Main Authentication method
 | |
|      * Required for plugin interface 
 | |
|      * @param unknown $login  User's username
 | |
|      * @param unknown $password User's password
 | |
|      * @return boolean
 | |
|      */
 | |
|     function authenticate($login, $password) {
 | |
|         if ($login && $password) {
 | |
| 
 | |
|             if (!function_exists('ldap_connect')) {
 | |
|                 trigger_error('auth_ldap requires PHP\'s PECL LDAP package installed.');
 | |
|                 return FALSE;
 | |
|             }
 | |
| 
 | |
|             //Loading configuration
 | |
|             $this->_debugMode = defined('LDAP_AUTH_DEBUG') ?
 | |
|                     LDAP_AUTH_DEBUG : FALSE;
 | |
| 
 | |
|             $this->_anonBeforeBind = defined('LDAP_AUTH_ANONYMOUSBEFOREBIND') ?
 | |
|                     LDAP_AUTH_ANONYMOUSBEFOREBIND : FALSE;
 | |
| 
 | |
|             $this->_serviceBindDN = defined('LDAP_AUTH_BINDDN') ? LDAP_AUTH_BINDDN : null;
 | |
|             $this->_serviceBindPass = defined('LDAP_AUTH_BINDPW') ? LDAP_AUTH_BINDPW : null;
 | |
|             $this->_baseDN = defined('LDAP_AUTH_BASEDN') ? LDAP_AUTH_BASEDN : null;
 | |
|             if (!defined('LDAP_AUTH_BASEDN')) {
 | |
|                 $this->_log('LDAP_AUTH_BASEDN is required and not defined.', E_USER_ERROR);
 | |
|                 return FALSE;
 | |
|             } else {
 | |
|                 $this->_baseDN = LDAP_AUTH_BASEDN;
 | |
|             }
 | |
| 
 | |
|             $parsedURI = parse_url(LDAP_AUTH_SERVER_URI);
 | |
|             if ($parsedURI === FALSE) {
 | |
|                 $this->_log('Could not parse LDAP_AUTH_SERVER_URI in config.php', E_USER_ERROR);
 | |
|                 return FALSE;
 | |
|             }
 | |
|             $this->_host = $parsedURI['host'];
 | |
|             $this->_scheme = $parsedURI['scheme'];
 | |
| 
 | |
|             if (is_int($parsedURI['port'])) {
 | |
|                 $this->_port = $parsedURI['port'];
 | |
|             } else {
 | |
|                 $this->_port = ($this->_scheme === 'ldaps') ? 636 : 389;
 | |
|             }
 | |
| 
 | |
|             $this->_useTLS = defined('LDAP_AUTH_USETLS') ? LDAP_AUTH_USETLS : FALSE;
 | |
| 
 | |
|             $this->_logAttempts = defined('LDAP_AUTH_LOG_ATTEMPTS') ?
 | |
|                     LDAP_AUTH_LOG_ATTEMPTS : FALSE;
 | |
| 
 | |
|             $this->_ldapLoginAttrib = defined('LDAP_AUTH_LOGIN_ATTRIB') ?
 | |
|                     LDAP_AUTH_LOGIN_ATTRIB : null;
 | |
| 
 | |
| 
 | |
|             /**
 | |
|               Building LDAP connection
 | |
|              * */
 | |
|             $ldapConnParams = array(
 | |
|                 'host' => $this->_host,
 | |
|                 'basedn' => $this->_baseDN,
 | |
|                 'port' => $this->_port,
 | |
|                 'starttls' => $this->_useTLS
 | |
|             );
 | |
| 
 | |
|             if ($this->_debugMode)
 | |
|                 $this->_log(print_r($ldapConnParams, TRUE), E_USER_NOTICE);
 | |
|             $ldapConn = @ldap_connect($this->_host, $this->_port);
 | |
|             if ($ldapConn === FALSE) {
 | |
|                 $this->_log('Could not connect to LDAP Server: \'' . $this->_host . '\'', E_USER_ERROR);
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             /* Enable LDAP protocol version 3. */
 | |
|             if (!@ldap_set_option($ldapConn, LDAP_OPT_PROTOCOL_VERSION, 3)) {
 | |
|                 $this->_log('Failed to set LDAP Protocol version (LDAP_OPT_PROTOCOL_VERSION) to 3', E_USER_ERROR);
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             /* Set referral option */
 | |
|             if (!@ldap_set_option($ldapConn, LDAP_OPT_REFERRALS, FALSE)) {
 | |
|                 $this->_log('Failed to set LDAP Referrals (LDAP_OPT_REFERRALS) to TRUE', E_USER_ERROR);
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             if (stripos($this->_host, "ldaps:") === FALSE and $this->_useTLS) {
 | |
|                 if (!@ldap_start_tls($ldapConn)) {
 | |
|                     $this->_log('Unable to force TLS', E_USER_ERROR);
 | |
|                     return false;
 | |
|                 }
 | |
|             }
 | |
|             $error = @ldap_bind($ldapConn, $this->_serviceBindDN, $this->_serviceBindPass);
 | |
|             if ($error === FALSE) {
 | |
|                 $this->_log(
 | |
|                         'LDAP bind(): Bind failed (' . $error . ')with DN ' . $this->_serviceBindDN, E_USER_ERROR
 | |
|                 );
 | |
|                 return FALSE;
 | |
|             } else {
 | |
|                 $this->_log(
 | |
|                         'Connected to LDAP Server: ' . LDAP_AUTH_SERVER_URI . ' with ' . $this->_getBindDNWord());
 | |
|             }
 | |
| 
 | |
|             // Bind with service account if orignal connexion was anonymous
 | |
|             /* if (($this->_anonBeforeBind) && (strlen($this->_bindDN > 0))) {
 | |
|               $binding=$this->ldapObj->bind($this->_serviceBindDN, $this->_serviceBindPass);
 | |
|               if (get_class($binding) !== 'Net_LDAP2') {
 | |
|               $this->_log(
 | |
|               'Cound not bind service account: '.$binding->getMessage(),E_USER_ERROR);
 | |
|               return FALSE;
 | |
|               } else {
 | |
|               $this->_log('Bind with '.$this->_serviceBindDN.' successful.',E_USER_NOTICE);
 | |
|               }
 | |
|               } */
 | |
| 
 | |
|             //Searching for user
 | |
|             $filterObj = str_replace('???', $this->ldap_escape($login), LDAP_AUTH_SEARCHFILTER);
 | |
|             $searchResults = @ldap_search($ldapConn, $this->_baseDN, $filterObj, array('displayName', 'title', 'sAMAccountName', $this->_ldapLoginAttrib), 0, 0, 0);
 | |
|             if ($searchResults === FALSE) {
 | |
|                 $this->_log('LDAP Search Failed on base \'' . $this->_baseDN . '\' for \'' . $filterObj . '\'', E_USER_ERROR);
 | |
|                 return FALSE;
 | |
|             }
 | |
|             $count = @ldap_count_entries($ldapConn, $searchResults);
 | |
|             if ($count === FALSE) {
 | |
|                 
 | |
|             } elseif ($count > 1) {
 | |
|                 $this->_log('Multiple DNs found for username ' . (string) $login, E_USER_WARNING);
 | |
|                 return FALSE;
 | |
|             } elseif ($count === 0) {
 | |
|                 $this->_log((string) $login, 'Unknown User', E_USER_NOTICE);
 | |
|                 return FALSE;
 | |
|             }
 | |
| 
 | |
|             //Getting user's DN from search
 | |
|             $userEntry = @ldap_first_entry($ldapConn, $searchResults);
 | |
|             if ($userEntry === FALSE) {
 | |
|                 $this->_log('LDAP search(): Unable to retrieve result after searching base \'' . $this->_baseDN . '\' for \'' . $filterObj . '\'', E_USER_WARNING);
 | |
|                 return false;
 | |
|             }
 | |
|             $userAttributes = @ldap_get_attributes($ldapConn, $userEntry);
 | |
|             $userDN = @ldap_get_dn($ldapConn, $userEntry);
 | |
|             if ($userDN == FALSE) {
 | |
|                 $this->_log('LDAP search(): Unable to get DN after searching base \'' . $this->_baseDN . '\' for \'' . $filterObj . '\'', E_USER_WARNING);
 | |
|                 return false;
 | |
|             }
 | |
|             //Binding with user's DN. 
 | |
|             if ($this->_debugMode)
 | |
|                 $this->_log('Try to bind with user\'s DN: ' . $userDN);
 | |
|             $loginAttempt = @ldap_bind($ldapConn, $userDN, $password);
 | |
|             if ($loginAttempt === TRUE) {
 | |
|                 $this->_log('User: ' . (string) $login . ' authentication successful');
 | |
|                 if (strlen($this->_ldapLoginAttrib) > 0) {
 | |
|                     if ($this->_debugMode)
 | |
|                         $this->_log('Looking up TT-RSS username attribute in ' . $this->_ldapLoginAttrib);
 | |
|                     $ttrssUsername = $userAttributes[$this->_ldapLoginAttrib][0];
 | |
|                     ;
 | |
|                     @ldap_close($ldapConn);
 | |
|                     if (!is_string($ttrssUsername)) {
 | |
|                         $this->_log('Could not find user name attribute ' . $this->_ldapLoginAttrib . ' in LDAP entry', E_USER_WARNING);
 | |
|                         return FALSE;
 | |
|                     }
 | |
|                     return $this->base->auto_create_user($ttrssUsername);
 | |
|                 } else {
 | |
|                     @ldap_close($ldapConn);
 | |
|                     return $this->base->auto_create_user($login);
 | |
|                 }
 | |
|             } else {
 | |
|                 @ldap_close($ldapConn);
 | |
|                 $this->_log('User: ' . (string) $login . ' authentication failed');
 | |
|                 return FALSE;
 | |
|             }
 | |
|         }
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns plugin API version
 | |
|      * Required for plugin interface
 | |
|      * @return number
 | |
|      */
 | |
|     function api_version() {
 | |
|         return 2;
 | |
|     }
 | |
| 
 | |
| }
 | |
| 
 | |
| ?>
 | 
