config = $config; $this->entityManager = $entityManager; $this->passwordHash = $passwordHash; $this->language = $defaultLanguage; $this->applicationUser = $applicationUser; $this->isPortal = $isPortal; $this->utils = new LDAPUtils($config); } protected $ldapFieldMap = [ 'userName' => 'userNameAttribute', 'firstName' => 'userFirstNameAttribute', 'lastName' => 'userLastNameAttribute', 'title' => 'userTitleAttribute', 'emailAddress' => 'userEmailAddressAttribute', 'phoneNumber' => 'userPhoneNumberAttribute', ]; protected $userFieldMap = [ 'teamsIds' => 'userTeamsIds', 'defaultTeamId' => 'userDefaultTeamId', ]; protected $portalUserFieldMap = [ 'portalsIds' => 'portalUserPortalsIds', 'portalRolesIds' => 'portalUserRolesIds', ]; public function login(?string $username, ?string $password, ?AuthToken $authToken = null, ?Request $request = null) : Result { $isPortal = $this->isPortal; if ($authToken) { $user = $this->loginByToken($username, $authToken); if ($user) { return Result::success($user); } else { return Result::fail(); } } if (!$password || $username == '**logout') { return Result::fail(); } if ($isPortal) { $useLdapAuthForPortalUser = $this->utils->getOption('portalUserLdapAuth'); if (!$useLdapAuthForPortalUser) { return parent::login($username, $password, $authToken, $request); } } $ldapClient = $this->getLdapClient(); /* Login LDAP system user (ldapUsername, ldapPassword) */ try { $ldapClient->bind(); } catch (Exception $e) { $options = $this->utils->getLdapClientOptions(); $GLOBALS['log']->error('LDAP: Could not connect to LDAP server ['.$options['host'].'], details: ' . $e->getMessage()); $adminUser = $this->adminLogin($username, $password); if (!isset($adminUser)) { return Result::fail(); } $GLOBALS['log']->info('LDAP: Administrator ['.$username.'] was logged in by Espo method.'); } if (!isset($adminUser)) { try { $userDn = $this->findLdapUserDnByUsername($username); } catch (Exception $e) { $GLOBALS['log']->error('Error while finding DN for ['.$username.'], details: ' . $e->getMessage() . '.'); } if (!isset($userDn)) { $GLOBALS['log']->error('LDAP: Authentication failed for user ['.$username.'], details: user is not found.'); $adminUser = $this->adminLogin($username, $password); if (!isset($adminUser)) { return Result::fail(); } $GLOBALS['log']->info('LDAP: Administrator ['.$username.'] was logged in by Espo method.'); } $GLOBALS['log']->debug('User ['.$username.'] is found with this DN ['.$userDn.'].'); try { $ldapClient->bind($userDn, $password); } catch ( Exception $e) { $GLOBALS['log']->error('LDAP: Authentication failed for user ['.$username.'], details: ' . $e->getMessage()); return Result::fail(); } } $user = $this->entityManager->getRepository('User')->findOne([ 'whereClause' => [ 'userName' => $username, 'type!=' => ['api', 'system'] ] ]); if (!isset($user)) { if (!$this->utils->getOption('createEspoUser')) { $this->useSystemUser(); throw new Error($this->language->translate('ldapUserInEspoNotFound', 'messages', 'User')); } $userData = $ldapClient->getEntry($userDn); $user = $this->createUser($userData, $isPortal); } if (!$user) { return Result::fail(); } return Result::success($user); } protected function useSystemUser() { $this->applicationUser->setupSystemUser(); } protected function getLdapClient() { if (!isset($this->ldapClient)) { $options = $this->utils->getLdapClientOptions(); try { $this->ldapClient = new LDAPClient($options); } catch (Exception $e) { $GLOBALS['log']->error('LDAP error: ' . $e->getMessage()); } } return $this->ldapClient; } /** * Login by authorization token. */ protected function loginByToken($username, AuthToken $authToken = null) { if (!isset($authToken)) { return null; } $userId = $authToken->get('userId'); $user = $this->entityManager->getEntity('User', $userId); $tokenUsername = $user->get('userName'); if (strtolower($username) != strtolower($tokenUsername)) { $ip = $_SERVER['REMOTE_ADDR'] ?? ''; $GLOBALS['log']->alert( 'Unauthorized access attempt for user ['.$username.'] from IP ['.$ip.']' ); return null; } $user = $this->entityManager->getRepository('User')->findOne([ 'whereClause' => [ 'userName' => $username, ] ]); return $user; } /** * Login user with administrator rights. */ protected function adminLogin($username, $password) { $hash = $this->passwordHash->hash($password); $user = $this->entityManager->getRepository('User')->findOne([ 'whereClause' => [ 'userName' => $username, 'password' => $hash, 'type' => ['admin', 'super-admin'] ] ]); return $user; } /** * Create Espo user with data gets from LDAP server. */ protected function createUser(array $userData, $isPortal = false) { $GLOBALS['log']->info('Creating new user ...'); $data = []; // show full array of the LDAP user $GLOBALS['log']->debug('LDAP: user data: ' .print_r($userData, true)); //set values from ldap server $ldapFields = $this->loadFields('ldap'); foreach ($ldapFields as $espo => $ldap) { $ldap = strtolower($ldap); if (isset($userData[$ldap][0])) { $GLOBALS['log']->debug('LDAP: Create a user wtih ['.$espo.'] = ['.$userData[$ldap][0].'].'); $data[$espo] = $userData[$ldap][0]; } } //set user fields if ($isPortal) { $userFields = $this->loadFields('portalUser'); $userFields['type'] = 'portal'; } else { $userFields = $this->loadFields('user'); } foreach ($userFields as $fieldName => $fieldValue) { $data[$fieldName] = $fieldValue; } $this->useSystemUser(); $user = $this->entityManager->getEntity('User'); $user->set($data); $this->entityManager->saveEntity($user); return $this->entityManager->getEntity('User', $user->id); } /** * Find LDAP user DN by his username. */ protected function findLdapUserDnByUsername($username) { $ldapClient = $this->getLdapClient(); $options = $this->utils->getOptions(); $loginFilterString = ''; if (!empty($options['userLoginFilter'])) { $loginFilterString = $this->convertToFilterFormat($options['userLoginFilter']); } $searchString = '(&(objectClass='.$options['userObjectClass'].')('.$options['userNameAttribute'].'='.$username.')'. $loginFilterString.')'; $result = $ldapClient->search($searchString, null, LDAPClient::SEARCH_SCOPE_SUB); $GLOBALS['log']->debug('LDAP: user search string: "' . $searchString . '"'); foreach ($result as $item) { return $item["dn"]; } } /** * Check and convert filter item into LDAP format. */ protected function convertToFilterFormat($filter) { $filter = trim($filter); if (substr($filter, 0, 1) != '(') { $filter = '(' . $filter; } if (substr($filter, -1) != ')') { $filter = $filter . ')'; } return $filter; } /** * Load fields for a user. */ protected function loadFields($type) { $options = $this->utils->getOptions(); $typeMap = $type . 'FieldMap'; $fields = []; foreach ($this->$typeMap as $fieldName => $fieldValue) { if (isset($options[$fieldValue])) { $fields[$fieldName] = $options[$fieldValue]; } } return $fields; } }