* Improved LDAP access and error handling. Tested aganist OpenLDAP with ldap and ldaps on Debian. More info at Wiki http://wiki.syspass.org/doku.php/en:ldap

This commit is contained in:
nuxsmin
2014-02-03 00:39:20 +01:00
parent a3b2731fec
commit 341dcc68ac
5 changed files with 304 additions and 112 deletions

View File

@@ -36,18 +36,18 @@ $userLogin = SP_Common::parseParams('p', 'user');
$userPass = SP_Common::parseParams('p', 'pass');
$masterPass = SP_Common::parseParams('p', 'mpass');
if (!$userLogin OR !$userPass) {
if (!$userLogin || !$userPass) {
SP_Common::printJSON(_('Usuario/Clave no introducidos'));
}
$resLdap = SP_Auth::authUserLDAP($userLogin,$userPass);
$objUser = new SP_Users;
$objUser->userLogin = SP_Auth::$userLogin = $userLogin;
$objUser->userPass = SP_Auth::$userPass = $userPass;
$objUser->userLogin = $userLogin;
$objUser->userPass = $userPass;
$objUser->userName = SP_Auth::$userName;
$objUser->userEmail = SP_Auth::$userEmail;
$resLdap = SP_Auth::authUserLDAP();
// Autentificamos por LDAP
if ($resLdap == 1) {
$message['action'] = _('Inicio sesión (LDAP)');
@@ -71,6 +71,7 @@ if ($resLdap == 1) {
}
}
} else if ($resLdap == 49) {
$message['action'] = _('Inicio sesión (LDAP)');
$message['text'][] = _('Login incorrecto');
$message['text'][] = _('Usuario') . ": " . $userLogin;
$message['text'][] = _('IP') . ": " . $_SERVER['REMOTE_ADDR'];
@@ -81,7 +82,7 @@ if ($resLdap == 1) {
$message['action'] = _('Inicio sesión (MySQL)');
// Autentificamos con la BBDD
if (!SP_Auth::authUserMySQL()) {
if (!SP_Auth::authUserMySQL($userLogin,$userPass)) {
$message['text'][] = _('Login incorrecto');
$message['text'][] = _('Usuario') . ": " . $userLogin;
$message['text'][] = _('IP') . ": " . $_SERVER['REMOTE_ADDR'];
@@ -92,7 +93,7 @@ if ($resLdap == 1) {
}
// Comprobar si el usuario está deshabilitado
if (SP_Auth::checkUserIsDisabled()) {
if (SP_Auth::checkUserIsDisabled($userLogin)) {
$message['text'][] = _('Usuario deshabilitado');
$message['text'][] = _('Usuario') . ": " . $userLogin;
$message['text'][] = _('IP') . ": " . $_SERVER['REMOTE_ADDR'];
@@ -139,9 +140,7 @@ if ($objUser->getUserMPass()) {
}
}
if ( isset($params) ){
$urlParams = '?'.implode('&', $params);
}
$urlParams = isset($params) ? '?'.implode('&', $params) : '';
SP_Common::printJSON('index.php'.$urlParams, 0);
}

View File

@@ -30,134 +30,101 @@ defined('APP_ROOT') || die(_('No es posible acceder directamente a este archivo'
*/
class SP_Auth {
static $userLogin;
static $userPass;
static $userName;
static $userEmail;
/**
* @brief Autentificación de usuarios con LDAP
* @param string $userLogin con el login del usuario
* @param string $userPass con la clave del usuario
* @return bool
*/
public static function authUserLDAP() {
if (SP_Config::getValue('ldapenabled', 0) === 0 || !SP_Util::ldapIsAvailable()) {
return FALSE;
}
$searchBase = SP_Config::getValue('ldapbase');
$ldapserver = SP_Config::getValue('ldapserver');
$ldapgroup = SP_Config::getValue('ldapgroup');
$bindDN = SP_Config::getValue('ldapbinduser');
$bindPass = SP_Config::getValue('ldapbindpass');
if (!$searchBase || !$ldapserver || !$ldapgroup || !$bindDN || !$bindPass) {
public static function authUserLDAP($userLogin, $userPass) {
if (!SP_Util::ldapIsAvailable()
|| !SP_Config::getValue('ldapenabled', FALSE)
|| !SP_LDAP::checkLDAPParams()) {
return FALSE;
}
$ldapAccess = FALSE;
$message['action'] = __FUNCTION__;
// Conexión al servidor LDAP
if (!$ldapConn = @ldap_connect($ldapserver)) {
$message['text'][] = _('No es posible conectar con el servidor de LDAP') . " '" . $ldapserver . "'";
$message['text'][] = 'LDAP ERROR: ' . ldap_error($ldapConn) . '(' . ldap_errno($ldapConn) . ')';
// Conectamos al servidor realizamos la conexión con el usuario proxy
try {
SP_LDAP::connect();
SP_LDAP::bind();
SP_LDAP::getUserDN($userLogin);
} catch (Exception $e) {
return FALSE;
}
$userDN = SP_LDAP::$ldapSearchData[0]['dn'];
// Mapeo de los atributos
$attribsMap = array(
'groupmembership' => 'group',
'memberof' => 'group',
'displayname' => 'name',
'fullname' => 'name',
'mail' => 'mail',
'lockouttime' => 'expire');
// Realizamos la conexión con el usuario real y obtenemos los atributos
try{
SP_LDAP::bind($userDN, $userPass);
SP_LDAP::unbind();
$attribs = SP_LDAP::getLDAPAttr($attribsMap);
} catch (Exception $e) {
return ldap_errno(SP_LDAP::getConn());
}
// Comprobamos si la cuenta está bloqueada o expirada
if ( isset($attribs['expire']) && $attribs['expire'] > 0){
return FALSE;
}
if ( !isset($attribs['group']) ){
$message['text'][] = _('El usuario no tiene grupos asociados');
SP_Common::wrLogInfo($message);
return FALSE;
}
if (is_array($attribs['group'])){
foreach ($attribs['group'] as $group) {
if (is_int($group)) {
continue;
}
@ldap_set_option($ldapConn, LDAP_OPT_NETWORK_TIMEOUT, 10); // Set timeout
@ldap_set_option($ldapConn, LDAP_OPT_PROTOCOL_VERSION, 3); // Set LDAP version
if (!@ldap_bind($ldapConn, $bindDN, $bindPass)) {
$message['text'][] = _('Error al conectar (bind)');
$message['text'][] = 'LDAP ERROR: ' . ldap_error($ldapConn) . '(' . ldap_errno($ldapConn) . ')';
SP_Common::wrLogInfo($message);
return FALSE;
}
$filter = '(&(|(samaccountname=' . self::$userLogin . ')(cn=' . self::$userLogin . '))(|(objectClass=inetOrgPerson)(objectClass=person)))';
$filterAttr = array("dn", "displayname", "samaccountname", "mail", "memberof", "lockouttime", "fullname", "groupmembership", "mail");
$searchRes = @ldap_search($ldapConn, $searchBase, $filter, $filterAttr);
if (!$searchRes) {
$message['text'][] = _('Error al buscar el DN del usuario');
$message['text'][] = 'LDAP ERROR: ' . ldap_error($ldapConn) . '(' . ldap_errno($ldapConn) . ')';
SP_Common::wrLogInfo($message);
return FALSE;
}
if (@ldap_count_entries($ldapConn, $searchRes) === 1) {
$searchUser = @ldap_get_entries($ldapConn, $searchRes);
if (!$searchUser) {
$message['text'][] = _('Error al localizar el usuario en LDAP');
$message['text'][] = 'LDAP ERROR: ' . ldap_error($ldapConn) . '(' . ldap_errno($ldapConn) . ')';
SP_Common::wrLogInfo($message);
return FALSE;
}
$userDN = $searchUser[0]["dn"];
}
if (@ldap_bind($ldapConn, $userDN, self::$userPass)) {
@ldap_unbind($ldapConn);
foreach ($searchUser as $entryValue) {
if (is_array($entryValue)) {
foreach ($entryValue as $entryAttr => $attrValue) {
if (is_array($attrValue)) {
if ($entryAttr == "groupmembership" || $entryAttr == "memberof") {
foreach ($attrValue as $group) {
if (is_int($group)) {
continue;
}
preg_match('/^cn=([\w\s-]+),.*/i', $group, $groupName);
// Comprobamos que el usuario está en el grupo indicado
if ($groupName[1] == $ldapgroup || $group == $ldapgroup) {
$ldapAccess = TRUE;
break;
}
}
} elseif ($entryAttr == "displayname" | $entryAttr == "fullname") {
self::$userName = $attrValue[0];
} elseif ($entryAttr == "mail") {
self::$userEmail = $attrValue[0];
} elseif ($entryAttr == "lockouttime") {
if ($attrValue[0] > 0)
return FALSE;
}
}
}
// Comprobamos que el usuario está en el grupo indicado
if ( self::checkLDAPGroup($group) ) {
$ldapAccess = TRUE;
break;
}
}
} else {
$message['text'][] = _('Error al conectar con el usuario');
$message['text'][] = 'LDAP ERROR: ' . ldap_error($ldapConn) . '(' . ldap_errno($ldapConn) . ')';
SP_Common::wrLogInfo($message);
return ldap_errno($ldapConn);
} else{
// Comprobamos que el usuario está en el grupo indicado
if ( self::checkLDAPGroup($attribs['group']) ) {
$ldapAccess = TRUE;
}
}
self::$userName = $attribs['name'];
self::$userEmail = $attribs['mail'];
return $ldapAccess;
}
/**
* @brief Autentificación de usuarios con MySQL
* @param string $userLogin con el login del usuario
* @param string $userPass con la clave del usuario
* @return bool
*
* Esta función comprueba la clave del usuario. Si el usuario necesita ser migrado desde phpPMS,
* se ejecuta el proceso para actualizar la clave.
*/
public static function authUserMySQL() {
if (SP_Users::checkUserIsMigrate(self::$userLogin)) {
if (!SP_Users::migrateUser(self::$userLogin, self::$userPass)) {
public static function authUserMySQL($userLogin, $userPass) {
if (SP_Users::checkUserIsMigrate($userLogin)) {
if (!SP_Users::migrateUser($userLogin, $userPass)) {
return FALSE;
}
}
@@ -165,9 +132,9 @@ class SP_Auth {
$query = "SELECT user_login,"
. "user_pass "
. "FROM usrData "
. "WHERE user_login = '" . DB::escape(self::$userLogin) . "' "
. "WHERE user_login = '" . DB::escape($userLogin) . "' "
. "AND user_isMigrate = 0 "
. "AND user_pass = SHA1(CONCAT(user_hashSalt,'" . DB::escape(self::$userPass) . "')) LIMIT 1";
. "AND user_pass = SHA1(CONCAT(user_hashSalt,'" . DB::escape($userPass) . "')) LIMIT 1";
if (DB::doQuery($query, __FUNCTION__) === FALSE) {
return FALSE;
@@ -182,12 +149,13 @@ class SP_Auth {
/**
* @brief Comprobar si un usuario está deshabilitado
* @param string $userLogin con el login del usuario
* @return bool
*/
public static function checkUserIsDisabled() {
public static function checkUserIsDisabled($userLogin) {
$query = "SELECT user_isDisabled "
. "FROM usrData "
. "WHERE user_login = '" . DB::escape(self::$userLogin) . "' LIMIT 1";
. "WHERE user_login = '" . DB::escape($userLogin) . "' LIMIT 1";
$queryRes = DB::getResults($query, __FUNCTION__);
if ($queryRes === FALSE) {
@@ -200,4 +168,21 @@ class SP_Auth {
return TRUE;
}
/**
* @brief Comprobar si el grupo de LDAP está habilitado
* @param string $group con el nombre del grupo
* @return bool
*/
private static function checkLDAPGroup($group){
$ldapgroup = SP_Config::getValue('ldapgroup');
preg_match('/^cn=([\w\s-]+),.*/i', $group, $groupName);
if ($groupName[1] == $ldapgroup || $group == $ldapgroup) {
return TRUE;
}
return FALSE;
}
}

206
inc/ldap.class.php Normal file
View File

@@ -0,0 +1,206 @@
<?php
/**
* sysPass
*
* @author nuxsmin
* @link http://syspass.org
* @copyright 2012 Rubén Domínguez nuxsmin@syspass.org
*
* This file is part of sysPass.
*
* sysPass 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 3 of the License, or
* (at your option) any later version.
*
* sysPass 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 sysPass. If not, see <http://www.gnu.org/licenses/>.
*
*/
defined('APP_ROOT') || die(_('No es posible acceder directamente a este archivo'));
/**
* Esta clase es la encargada de realizar la autentificación de usuarios de sysPass.
*/
class SP_LDAP {
private static $ldapConn;
private static $ldapServer;
private static $searchBase;
private static $bindDN;
private static $bindPass;
private static $ldapGroup;
public static $ldapSearchData;
/**
* @brief Obtener el recurso de conexión a LDAP
* @return resource
*/
public static function getConn(){
if (is_resource(self::$ldapConn)){
return self::$ldapConn;
}
}
/**
* @brief Comprobar la conexión al servidor de LDAP
* @return bool
*/
public static function checkLDAPConn(){
}
/**
* @brief Comprobar si los parámetros necesario de LDAP están establecidos
* @return bool
*/
public static function checkLDAPParams(){
self::$searchBase = SP_Config::getValue('ldapbase');
self::$ldapServer = SP_Config::getValue('ldapserver');
self::$bindDN = SP_Config::getValue('ldapbinduser');
self::$bindPass = SP_Config::getValue('ldapbindpass');
self::$ldapGroup = SP_Config::getValue('ldapgroup');
if (!self::$searchBase || !self::$ldapServer || !self::$ldapGroup || !self::$bindDN || !self::$bindPass) {
$message['action'] = __FUNCTION__;
$message['text'][] = _('Los parámetros de LDAP no están configurados');
SP_Common::wrLogInfo($message);
return FALSE;
}
return TRUE;
}
/**
* @brief Realizar la conexión al servidor de LDAP
* @param string $server con la dirección del servidor
* @return bool
*/
public static function connect(){
$message['action'] = __FUNCTION__;
// Conexión al servidor LDAP
if (!self::$ldapConn = @ldap_connect(self::$ldapServer)) {
$message['text'][] = _('No es posible conectar con el servidor de LDAP') . " '" . self::$ldapServer . "'";
$message['text'][] = 'LDAP ERROR: ' . ldap_error(self::$ldapConn) . '(' . ldap_errno(self::$ldapConn) . ')';
SP_Common::wrLogInfo($message);
throw new Exception(_('No es posible conectar con el servidor de LDAP'));
}
@ldap_set_option(self::$ldapConn, LDAP_OPT_NETWORK_TIMEOUT, 10); // Set timeout
@ldap_set_option(self::$ldapConn, LDAP_OPT_PROTOCOL_VERSION, 3); // Set LDAP version
return TRUE;
}
/**
* @brief Realizar la autentificación con el servidor de LDAP
* @param string $dn con el DN del usuario
* @param string $pass con la clave del usuario
* @return bool
*/
public static function bind($userDN = '', $userPass = ''){
$message['action'] = __FUNCTION__;
$dn = ( $userDN ) ? $userDN : self::$bindDN;
$pass = ( $userPass ) ? $userPass : self::$bindPass;
if (!@ldap_bind(self::$ldapConn, $dn, $pass)) {
$message['text'][] = _('Error al conectar (BIND)');
$message['text'][] = 'LDAP ERROR: ' . ldap_error(self::$ldapConn) . '(' . ldap_errno(self::$ldapConn) . ')';
$message['text'][] = 'LDAP DN: ' . $dn;
SP_Common::wrLogInfo($message);
throw new Exception(_('Error al conectar (BIND)'));
}
return TRUE;
}
/**
* @brief Obtener el RDN del usuario que realiza el login
* @param string $userLogin con el login del usuario
* @return none
*/
public static function getUserDN($userLogin){
$message['action'] = __FUNCTION__;
$filter = '(&(|(samaccountname=' . $userLogin . ')(cn=' . $userLogin . ')(uid=' . $userLogin . '))(|(objectClass=inetOrgPerson)(objectClass=person)(objectClass=simpleSecurityObject)))';
$filterAttr = array("dn", "displayname", "samaccountname", "mail", "memberof", "lockouttime", "fullname", "groupmembership", "mail");
$searchRes = @ldap_search(self::$ldapConn, self::$searchBase, $filter, $filterAttr);
if (!$searchRes) {
$message['text'][] = _('Error al buscar el DN del usuario');
$message['text'][] = 'LDAP ERROR: ' . ldap_error(self::$ldapConn) . '(' . ldap_errno(self::$ldapConn) . ')';
$message['text'][] = 'LDAP FILTER: ' . $filter;
SP_Common::wrLogInfo($message);
throw new Exception(_('Error al buscar el DN del usuario'));
}
if (@ldap_count_entries(self::$ldapConn, $searchRes) === 1) {
self::$ldapSearchData = @ldap_get_entries(self::$ldapConn, $searchRes);
if (!self::$ldapSearchData) {
$message['text'][] = _('Error al localizar el usuario en LDAP');
$message['text'][] = 'LDAP ERROR: ' . ldap_error(self::$ldapConn) . '(' . ldap_errno(self::$ldapConn) . ')';
SP_Common::wrLogInfo($message);
throw new Exception(_('Error al localizar el usuario en LDAP'));
}
//return $searchUser[0]["dn"];
} else {
$message['text'][] = _('Error al buscar el DN del usuario');
$message['text'][] = 'LDAP FILTER: ' . $filter;
SP_Common::wrLogInfo($message);
throw new Exception(_('Error al buscar el DN del usuario'));
}
}
/**
* @brief Realizar la desconexión del servidor de LDAP
* @return none
*/
public static function unbind(){
@ldap_unbind(self::$ldapConn);
}
public static function getLDAPAttr($attribs){
$res = array();
foreach (self::$ldapSearchData as $entryValue) {
if (is_array($entryValue)) {
foreach ($entryValue as $entryAttr => $attrValue) {
if (is_array($attrValue)) {
if (array_key_exists($entryAttr, $attribs)){
if ( $attrValue['count'] > 1 ){
$res[$attribs[$entryAttr]] = $attrValue;
} else{
$res[$attribs[$entryAttr]] = $attrValue[0];
}
}
}
}
}
}
return $res;
}
}

View File

@@ -482,7 +482,7 @@ class SP_Users {
public function newUserLDAP() {
$passdata = SP_Users::makeUserPass($this->userPass);
$query = "INSERT INTO usrData SET"
$query = "INSERT INTO usrData SET "
. "user_name = '" . DB::escape($this->userName) . "',"
. "user_groupId = 0,"
. "user_login = '" . DB::escape($this->userLogin) . "',"
@@ -614,6 +614,8 @@ class SP_Users {
$query = "UPDATE usrData SET "
. "user_pass = '" . $passdata['pass'] . "',"
. "user_hashSalt = '" . $passdata['salt'] . "',"
. "user_name = '" . DB::escape($this->userName) . "',"
. "user_email = '" . DB::escape($this->userEmail) . "',"
. "user_lastUpdate = NOW() "
. "WHERE user_id = " . $this->getUserIdByLogin($this->userLogin) . " LIMIT 1";