From 0d06770e80dae6ffda2c7cbeaefbeb461b49d841 Mon Sep 17 00:00:00 2001 From: Taras Machyshyn Date: Fri, 11 Jul 2014 13:57:03 +0300 Subject: [PATCH] LDAP changes --- .../Authentication/{Ldap.php => LDAP.php} | 81 +++++++-- .../{Ldap/Ldap.php => LDAP/LDAP.php} | 119 +++++++------- .../Core/Utils/Authentication/LDAP/Utils.php | 155 ++++++++++++++++++ 3 files changed, 285 insertions(+), 70 deletions(-) rename application/Espo/Core/Utils/Authentication/{Ldap.php => LDAP.php} (68%) rename application/Espo/Core/Utils/Authentication/{Ldap/Ldap.php => LDAP/LDAP.php} (60%) create mode 100644 application/Espo/Core/Utils/Authentication/LDAP/Utils.php diff --git a/application/Espo/Core/Utils/Authentication/Ldap.php b/application/Espo/Core/Utils/Authentication/LDAP.php similarity index 68% rename from application/Espo/Core/Utils/Authentication/Ldap.php rename to application/Espo/Core/Utils/Authentication/LDAP.php index 7392ec0678..abb4cf5d96 100644 --- a/application/Espo/Core/Utils/Authentication/Ldap.php +++ b/application/Espo/Core/Utils/Authentication/LDAP.php @@ -22,10 +22,17 @@ namespace Espo\Core\Utils\Authentication; -use \Espo\Core\Exceptions\Error; +use Espo\Core\Exceptions\Error, + Espo\Core\Utils\Config, + Espo\Core\ORM\EntityManager, + Espo\Core\Utils\Auth; -class Ldap extends Base +class LDAP extends Base { + private $utils; + + private $zendLdap; + /** * Espo => LDAP name * @@ -40,6 +47,24 @@ class Ldap extends Base 'phoneNumber' => 'telephonenumber', ); + public function __construct(Config $config, EntityManager $entityManager, Auth $auth) + { + parent::__construct($config, $entityManager, $auth); + + $this->zendLdap = new LDAP\LDAP(); + $this->utils = new LDAP\Utils($config); + } + + protected function getZendLdap() + { + return $this->zendLdap; + } + + protected function getUtils() + { + return $this->utils; + } + /** * LDAP login @@ -55,16 +80,28 @@ class Ldap extends Base return $this->loginByToken($username, $authToken); } - $options = $this->getConfig()->get('ldap'); - $ldap = new Ldap\Ldap($options); + $options = $this->getUtils()->getZendOptions(); + + $ldap = $this->getZendLdap(); + $ldap = $ldap->setOptions($options); try { $ldap->bind($username, $password); - $ldapUsername = $ldap->getCanonicalAccountName($username); + + $dn = $ldap->getDn($username); + + $loginFilter = $this->getUtils()->getOption('userLoginFilter'); + $userData = $ldap->searchByLoginFilter($loginFilter, $dn, 3); } catch (\Zend\Ldap\Exception\LdapException $zle) { - $GLOBALS['log']->info('LDAP Authentication: ' . $zle->getMessage()); - return null; + + $admin = $this->adminLogin($username, $password); + if (!isset($admin)) { + $GLOBALS['log']->info('LDAP Authentication: ' . $zle->getMessage()); + return null; + } + + $GLOBALS['log']->info('LDAP Authentication: Administrator login by username ['.$username.']'); } $user = $this->getEntityManager()->getRepository('User')->findOne(array( @@ -73,13 +110,9 @@ class Ldap extends Base ), )); - $isCreateUser = $ldap->getEspoOption('createEspoUser'); + $isCreateUser = $this->getUtils()->getOption('createEspoUser'); if (!isset($user) && $isCreateUser) { - $this->getAuth()->useNoAuth(); /** Required to fix Acl "isFetched()" error */ - - $dn = $ldap->getDn($username); - $userData = $ldap->getEntry($dn); $user = $this->createUser($userData); } @@ -117,6 +150,28 @@ class Ldap extends Base return $user; } + /** + * Login user with administrator rights + * + * @param string $username + * @param string $password + * @return \Espo\Entities\User | null + */ + protected function adminLogin($username, $password) + { + $hash = md5($password); + + $user = $this->getEntityManager()->getRepository('User')->findOne(array( + 'whereClause' => array( + 'userName' => $username, + 'password' => $hash, + 'isAdmin' => 1 + ), + )); + + return $user; + } + /** * Create Espo user with data gets from LDAP server * @@ -140,5 +195,7 @@ class Ldap extends Base return $user; } + + } diff --git a/application/Espo/Core/Utils/Authentication/Ldap/Ldap.php b/application/Espo/Core/Utils/Authentication/LDAP/LDAP.php similarity index 60% rename from application/Espo/Core/Utils/Authentication/Ldap/Ldap.php rename to application/Espo/Core/Utils/Authentication/LDAP/LDAP.php index 436c5ca3c2..0361d617e0 100644 --- a/application/Espo/Core/Utils/Authentication/Ldap/Ldap.php +++ b/application/Espo/Core/Utils/Authentication/LDAP/LDAP.php @@ -20,68 +20,12 @@ * along with EspoCRM. If not, see http://www.gnu.org/licenses/. ************************************************************************/ -namespace Espo\Core\Utils\Authentication\Ldap; +namespace Espo\Core\Utils\Authentication\LDAP; -class Ldap extends \Zend\Ldap\Ldap +class LDAP extends \Zend\Ldap\Ldap { protected $usernameAttribute = 'cn'; - protected $espoOptions = array(); - - /** - * Permitted Espo Options - * - * @var array - */ - protected $permittedEspoOptions = array( - /** Default Options (Zend LDAP): - 'host' => null, - 'port' => 0, - 'useSsl' => false, - 'username' => null, - 'password' => null, - 'bindRequiresDn' => false, - 'baseDn' => null, - 'accountCanonicalForm' => null, - 'accountDomainName' => null, - 'accountDomainNameShort' => null, - 'accountFilterFormat' => null, - 'allowEmptyPassword' => false, - 'useStartTls' => false, - 'optReferrals' => false, - 'tryUsernameSplit' => true, - 'networkTimeout' => null,*/ - - /** Espo Options */ - 'createEspoUser' => false, - ); - - - public function setOptions($options) - { - $espoOptionList = array_keys($this->permittedEspoOptions); - - $this->espoOptions = array_intersect_key($options, array_flip($espoOptionList)); - $options = array_diff_key($options, array_flip($espoOptionList)); - - return parent::setOptions($options); - } - - /** - * Get Espo Options - * - * @param string $name - * @param mixed $returns - * @return mixed - */ - public function getEspoOption($name, $returns = null) - { - if (isset($this->espoOptions[$name])) { - return $this->espoOptions[$name]; - } - - return $returns; - } /** * Get DN depends on options, ex. "cn=test,ou=People,dc=maxcrc,dc=com" @@ -117,4 +61,63 @@ class Ldap extends \Zend\Ldap\Ldap return parent::getAccountDn($acctname); } + + /** + * Search a user using userLoginFilter + * + * @param string $filter + * @param string $basedn + * @param int $scope + * @param array $attributes + * @return array + */ + public function searchByLoginFilter($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB, array $attributes = array()) + { + $filter = $this->getLoginFilter($filter); + + $result = $this->search($filter, $basedn, $scope, $attributes); + + if ($result->count() > 0) { + return $result->getFirst(); + } + + throw new \Zend\Ldap\Exception\LdapException($this, 'searching: ' . $filter); + } + + /** + * Get login filter in LDAP format + * + * @param string $filter + * @return string + */ + protected function getLoginFilter($filter) + { + $baseFilter = '(objectClass=*)'; + + if (!empty($filter)) { + $baseFilter = '(&' . $baseFilter . $this->convertToFilterFormat($filter). ')'; + } + + return $baseFilter; + } + + /** + * Check and convert filter item in LDAP format + * + * @param string $filter [description] + * @return string + */ + protected function convertToFilterFormat($filter) + { + $filter = trim($filter); + if (substr($filter, 0, 1) != '(') { + $filter = '(' . $filter; + } + + if (substr($filter, -1) != ')') { + $filter = $filter . ')'; + } + + return $filter; + } } \ No newline at end of file diff --git a/application/Espo/Core/Utils/Authentication/LDAP/Utils.php b/application/Espo/Core/Utils/Authentication/LDAP/Utils.php new file mode 100644 index 0000000000..35da2e3917 --- /dev/null +++ b/application/Espo/Core/Utils/Authentication/LDAP/Utils.php @@ -0,0 +1,155 @@ + 'ldapHost', + 'port' => 'ldapPort', + 'useSsl' => 'ldapSecurity', + 'useStartTls' => 'ldapSecurity', + 'username' => 'ldapUsername', + 'password' => 'ldapPassword', + 'bindRequiresDn' => 'ldapBindRequiresDn', + 'baseDn' => 'ldapBaseDn', + 'accountCanonicalForm' => 'ldapAccountCanonicalForm', + 'accountDomainName' => 'ldapAccountDomainName', + 'accountDomainNameShort' => 'ldapAccountDomainNameShort', + 'accountFilterFormat' => 'ldapAccountFilterFormat', + 'optReferrals' => 'ldapOptReferrals', + 'tryUsernameSplit' => 'ldapTryUsernameSplit', + 'networkTimeout' => 'ldapNetworkTimeout', + 'createEspoUser' => 'ldapCreateEspoUser', + 'userLoginFilter' => 'ldapUserLoginFilter', + ); + + /** + * Permitted Espo Options + * + * @var array + */ + protected $permittedEspoOptions = array( + 'createEspoUser' => false, + 'userLoginFilter' => null, + ); + + /** + * accountCanonicalForm Map between Espo and Zend value + * + * @var array + */ + protected $accountCanonicalFormMap = array( + 'Dn' => 1, + 'Username' => 2, + 'Backslash' => 3, + 'Principal' => 4, + ); + + + public function __construct(Config $config) + { + $this->config = $config; + } + + protected function getConfig() + { + return $this->config; + } + + /** + * Get Options from espo config according to $this->fieldMap + * + * @return array + */ + public function getOptions() + { + if (isset($this->options)) { + return $this->options; + } + + $options = array(); + foreach ($this->fieldMap as $ldapName => $espoName) { + + $option = $this->getConfig()->get($espoName); + if (isset($option)) { + $options[$ldapName] = $option; + } + } + + /** peculiar fields */ + $options['useSsl'] = (bool) ($options['useSsl'] == 'SSL'); + $options['useStartTls'] = (bool) ($options['useStartTls'] == 'TLS'); + $options['accountCanonicalForm'] = $this->accountCanonicalFormMap[ $options['accountCanonicalForm'] ]; + + $this->options = $options; + + return $this->options; + } + + /** + * Get an ldap option + * + * @param string $name + * @param mixed $returns Return value + * @return mixed + */ + public function getOption($name, $returns = null) + { + if (isset($this->options)) { + $this->getOptions(); + } + + if (isset($this->options[$name])) { + return $this->options[$name]; + } + + return $returns; + } + + /** + * Get Zend options for using Zend\Ldap + * + * @return array + */ + public function getZendOptions() + { + $options = $this->getOptions(); + $espoOptions = array_keys($this->permittedEspoOptions); + + $zendOptions = array_diff_key($options, array_flip($espoOptions)); + + return $zendOptions; + } + +} \ No newline at end of file