7 * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
8 * @copyright Copyright (c) 2007-2014 Metaways Infosystems GmbH (http://www.metaways.de)
9 * @author Lars Kneschke <l.kneschke@metaways.de>
18 class Tinebase_User_Ldap extends Tinebase_User_Sql implements Tinebase_User_Interface_SyncAble
23 protected $_options = array();
28 protected $_ldap = NULL;
31 * name of the ldap attribute which identifies a group uniquely
32 * for example gidNumber, entryUUID, objectGUID
35 protected $_groupUUIDAttribute;
38 * name of the ldap attribute which identifies a user uniquely
39 * for example uidNumber, entryUUID, objectGUID
42 protected $_userUUIDAttribute;
45 * mapping of ldap attributes to class properties
49 protected $_rowNameMapping = array(
50 'accountDisplayName' => 'displayname',
51 'accountFullName' => 'cn',
52 'accountFirstName' => 'givenname',
53 'accountLastName' => 'sn',
54 'accountLoginName' => 'uid',
55 'accountLastPasswordChange' => 'shadowlastchange',
56 'accountExpires' => 'shadowexpire',
57 'accountPrimaryGroup' => 'gidnumber',
58 'accountEmailAddress' => 'mail',
59 'accountHomeDirectory' => 'homedirectory',
60 'accountLoginShell' => 'loginshell',
61 'accountStatus' => 'shadowinactive'
65 * objectclasses required by this backend
69 protected $_requiredObjectClass = array(
77 * the base dn to work on (defaults to to userDn, but can also be machineDn)
84 * the basic group ldap filter (for example the objectclass)
88 protected $_groupBaseFilter = 'objectclass=posixgroup';
91 * the basic user ldap filter (for example the objectclass)
95 protected $_userBaseFilter = 'objectclass=posixaccount';
98 * the basic user search scope
102 protected $_userSearchScope = Zend_Ldap::SEARCH_SCOPE_SUB;
104 protected $_ldapPlugins = array();
106 protected $_isReadOnlyBackend = false;
109 * used to save the last user properties from ldap backend
111 protected $_lastLdapProperties = array();
116 * @param array $_options Options used in connecting, binding, etc.
117 * @throws Tinebase_Exception_Backend_Ldap
119 public function __construct(array $_options = array())
121 parent::__construct($_options);
123 if(empty($_options['userUUIDAttribute'])) {
124 $_options['userUUIDAttribute'] = 'entryUUID';
126 if(empty($_options['groupUUIDAttribute'])) {
127 $_options['groupUUIDAttribute'] = 'entryUUID';
129 if(empty($_options['baseDn'])) {
130 $_options['baseDn'] = $_options['userDn'];
132 if(empty($_options['userFilter'])) {
133 $_options['userFilter'] = 'objectclass=posixaccount';
135 if(empty($_options['userSearchScope'])) {
136 $_options['userSearchScope'] = Zend_Ldap::SEARCH_SCOPE_SUB;
138 if(empty($_options['groupFilter'])) {
139 $_options['groupFilter'] = 'objectclass=posixgroup';
142 if (isset($_options['requiredObjectClass'])) {
143 $this->_requiredObjectClass = (array)$_options['requiredObjectClass'];
145 if ((isset($_options['readonly']) || array_key_exists('readonly', $_options))) {
146 $this->_isReadOnlyBackend = (bool)$_options['readonly'];
148 if ((isset($_options['ldap']) || array_key_exists('ldap', $_options))) {
149 $this->_ldap = $_options['ldap'];
152 $this->_options = $_options;
154 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE))
155 Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . " Registering " . print_r($this->_options, true));
157 $this->_userUUIDAttribute = strtolower($this->_options['userUUIDAttribute']);
158 $this->_groupUUIDAttribute = strtolower($this->_options['groupUUIDAttribute']);
159 $this->_baseDn = $this->_options['baseDn'];
160 $this->_userBaseFilter = $this->_options['userFilter'];
161 $this->_userSearchScope = $this->_options['userSearchScope'];
162 $this->_groupBaseFilter = $this->_options['groupFilter'];
164 $this->_rowNameMapping['accountId'] = $this->_userUUIDAttribute;
166 if (! $this->_ldap instanceof Tinebase_Ldap) {
167 $this->_ldap = new Tinebase_Ldap($this->_options);
169 $this->_ldap->bind();
170 } catch (Zend_Ldap_Exception $zle) {
171 // @todo move this to Tinebase_Ldap?
172 throw new Tinebase_Exception_Backend_Ldap('Could not bind to LDAP: ' . $zle->getMessage());
176 foreach ($this->_plugins as $plugin) {
177 if ($plugin instanceof Tinebase_User_Plugin_LdapInterface) {
178 $this->registerLdapPlugin($plugin);
184 * register ldap plugin
186 * @param Tinebase_User_Plugin_LdapInterface $plugin
188 public function registerLdapPlugin(Tinebase_User_Plugin_LdapInterface $plugin)
190 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG))
191 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " Registering " . get_class($plugin) . ' LDAP plugin.');
193 $plugin->setLdap($this->_ldap);
194 $this->_ldapPlugins[] = $plugin;
200 * @param string $_filter
201 * @param string $_sort
202 * @param string $_dir
205 * @param string $_accountClass the type of subclass for the Tinebase_Record_RecordSet to return
206 * @return Tinebase_Record_RecordSet with record class Tinebase_Model_User
208 public function getUsersFromSyncBackend($_filter = NULL, $_sort = NULL, $_dir = 'ASC', $_start = NULL, $_limit = NULL, $_accountClass = 'Tinebase_Model_User')
210 $filter = $this->_getBaseFilter();
212 if (!empty($_filter)) {
213 $filter = $filter->addFilter(Zend_Ldap_Filter::orFilter(
214 Zend_Ldap_Filter::contains($this->_rowNameMapping['accountFirstName'], Zend_Ldap::filterEscape($_filter)),
215 Zend_Ldap_Filter::contains($this->_rowNameMapping['accountLastName'], Zend_Ldap::filterEscape($_filter)),
216 Zend_Ldap_Filter::contains($this->_rowNameMapping['accountLoginName'], Zend_Ldap::filterEscape($_filter))
220 $accounts = $this->_ldap->search(
223 $this->_userSearchScope,
224 array_values($this->_rowNameMapping),
225 $_sort !== null ? $this->_rowNameMapping[$_sort] : null
228 $result = new Tinebase_Record_RecordSet($_accountClass);
230 // nothing to be done anymore
231 if (count($accounts) == 0) {
235 foreach ($accounts as $account) {
236 $accountObject = $this->_ldap2User($account, $_accountClass);
238 if ($accountObject) {
239 $result->addRecord($accountObject);
246 // @todo implement limit, start, dir and status
247 // $select = $this->_getUserSelectObject()
248 // ->limit($_limit, $_start);
250 // if ($_sort !== NULL) {
251 // $select->order($this->rowNameMapping[$_sort] . ' ' . $_dir);
254 // // return only active users, when searching for simple users
255 // if ($_accountClass == 'Tinebase_Model_User') {
256 // $select->where($this->_db->quoteInto($this->_db->quoteIdentifier('status') . ' = ?', 'enabled'));
261 * returns user base filter
263 * @return Zend_Ldap_Filter_And
265 protected function _getBaseFilter()
267 return Zend_Ldap_Filter::andFilter(
268 Zend_Ldap_Filter::string($this->_userBaseFilter)
273 * search for user attributes
275 * @param array $attributes
278 * @todo allow multi value attributes
279 * @todo generalize this for usage in other Tinebase_User_Ldap fns?
281 public function getUserAttributes($attributes)
283 $ldapCollection = $this->_ldap->search(
284 $this->_getBaseFilter(),
286 $this->_userSearchScope,
291 foreach ($ldapCollection as $data) {
292 $row = array('dn' => $data['dn']);
293 foreach ($attributes as $key) {
294 $lowerKey = strtolower($key);
295 if (isset($data[$lowerKey]) && isset($data[$lowerKey][0])) {
296 $row[$key] = $data[$lowerKey][0];
302 return (array)$result;
308 * @return Tinebase_Ldap
310 public function getLdap()
316 * get user by login name
318 * @param string $_property
319 * @param string $_accountId
320 * @return Tinebase_Model_User the user object
322 public function getUserByPropertyFromSyncBackend($_property, $_accountId, $_accountClass = 'Tinebase_Model_User')
324 if (!(isset($this->_rowNameMapping[$_property]) || array_key_exists($_property, $this->_rowNameMapping))) {
325 throw new Tinebase_Exception_NotFound("can't get user by property $_property. property not supported by ldap backend.");
328 $ldapEntry = $this->_getLdapEntry($_property, $_accountId);
330 $user = $this->_ldap2User($ldapEntry, $_accountClass);
332 // append data from ldap plugins
333 foreach ($this->_ldapPlugins as $class => $plugin) {
334 $plugin->inspectGetUserByProperty($user, $ldapEntry);
341 * set the password for given account
343 * @param string $_userId
344 * @param string $_password
345 * @param bool $_encrypt encrypt password
346 * @param bool $_mustChange
348 * @throws Tinebase_Exception_InvalidArgument
350 public function setPassword($_userId, $_password, $_encrypt = TRUE, $_mustChange = null)
352 if ($this->_isReadOnlyBackend) {
356 $user = $_userId instanceof Tinebase_Model_FullUser ? $_userId : $this->getFullUserById($_userId);
358 $this->checkPasswordPolicy($_password, $user);
360 $metaData = $this->_getMetaData($user);
362 $encryptionType = isset($this->_options['pwEncType']) ? $this->_options['pwEncType'] : Tinebase_User_Abstract::ENCRYPT_SSHA;
363 $userpassword = $_encrypt ? Hash_Password::generate($encryptionType, $_password) : $_password;
366 'userpassword' => $userpassword,
367 'shadowlastchange' => floor(Tinebase_DateTime::now()->getTimestamp() / 86400)
370 foreach ($this->_ldapPlugins as $plugin) {
371 $plugin->inspectSetPassword($user, $_password, $_encrypt, $_mustChange, $ldapData);
374 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' $dn: ' . $metaData['dn']);
375 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' $ldapData: ' . print_r($ldapData, true));
377 $this->_ldap->update($metaData['dn'], $ldapData);
379 // update last modify timestamp in sql backend too
381 'last_password_change' => Tinebase_DateTime::now()->get(Tinebase_Record_Abstract::ISO8601LONG),
385 $this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' = ?', $user->getId())
388 $this->_db->update(SQL_TABLE_PREFIX . 'accounts', $values, $where);
390 $this->_setPluginsPassword($user->getId(), $_password, $_encrypt);
394 * update user status (enabled or disabled)
396 * @param mixed $_accountId
397 * @param string $_status
399 public function setStatusInSyncBackend($_accountId, $_status)
401 if ($this->_isReadOnlyBackend) {
405 $metaData = $this->_getMetaData($_accountId);
407 if ($_status == 'disabled') {
410 'shadowInactive' => 1
414 'shadowMax' => 999999,
415 'shadowInactive' => array()
419 foreach ($this->_ldapPlugins as $plugin) {
420 $plugin->inspectStatus($_status, $ldapData);
423 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . " {$metaData['dn']} $ldapData: " . print_r($ldapData, true));
425 $this->_ldap->update($metaData['dn'], $ldapData);
429 * sets/unsets expiry date in ldap backend
431 * expiryDate is the number of days since Jan 1, 1970
433 * @param mixed $_accountId
434 * @param Tinebase_DateTime $_expiryDate
436 public function setExpiryDateInSyncBackend($_accountId, $_expiryDate)
438 if ($this->_isReadOnlyBackend) {
442 $metaData = $this->_getMetaData($_accountId);
444 if ($_expiryDate instanceof DateTime) {
445 // days since Jan 1, 1970
446 $ldapData = array('shadowexpire' => floor($_expiryDate->getTimestamp() / 86400));
448 $ldapData = array('shadowexpire' => array());
451 foreach ($this->_ldapPlugins as $plugin) {
452 $plugin->inspectExpiryDate($_expiryDate, $ldapData);
455 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . " {$metaData['dn']} $ldapData: " . print_r($ldapData, true));
457 $this->_ldap->update($metaData['dn'], $ldapData);
461 * updates an existing user
463 * @todo check required objectclasses?
465 * @param Tinebase_Model_FullUser $_account
466 * @return Tinebase_Model_FullUser
468 public function updateUserInSyncBackend(Tinebase_Model_FullUser $_account)
470 if ($this->_isReadOnlyBackend) {
474 $ldapEntry = $this->_getLdapEntry('accountId', $_account);
476 $ldapData = $this->_user2ldap($_account, $ldapEntry);
478 foreach ($this->_ldapPlugins as $plugin) {
479 $plugin->inspectUpdateUser($_account, $ldapData, $ldapEntry);
482 // no need to update this attribute, it's not allowed to change and even might not be updateable
483 unset($ldapData[$this->_userUUIDAttribute]);
485 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' $dn: ' . $ldapEntry['dn']);
486 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' $ldapData: ' . print_r($ldapData, true));
488 $this->_ldap->update($ldapEntry['dn'], $ldapData);
490 $dn = Zend_Ldap_Dn::factory($ldapEntry['dn'], null);
491 $rdn = $dn->getRdn();
493 // do we need to rename the entry?
494 if (isset($ldapData[key($rdn)]) && $rdn[key($rdn)] != $ldapData[key($rdn)]) {
495 $groupsBackend = Tinebase_Group::factory(Tinebase_Group::LDAP);
497 // get the current groupmemberships
498 $memberships = $groupsBackend->getGroupMembershipsFromSyncBackend($_account);
500 // remove the user from current groups, because the dn/uid has changed
501 foreach ($memberships as $groupId) {
502 $groupsBackend->removeGroupMemberInSyncBackend($groupId, $_account);
505 $newDN = $this->_generateDn($_account);
506 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' rename ldap entry to: ' . $newDN);
507 $this->_ldap->rename($dn, $newDN);
509 // add the user to current groups again
510 foreach ($memberships as $groupId) {
511 $groupsBackend->addGroupMemberInSyncBackend($groupId, $_account);
515 // refetch user from ldap backend
516 $user = $this->getUserByPropertyFromSyncBackend('accountId', $_account, 'Tinebase_Model_FullUser');
524 * @param Tinebase_Model_FullUser $user
525 * @return Tinebase_Model_FullUser|null
527 public function addUserToSyncBackend(Tinebase_Model_FullUser $user)
529 if ($this->_isReadOnlyBackend) {
533 $ldapData = $this->_user2ldap($user);
535 $ldapData['uidnumber'] = $this->_generateUidNumber();
536 $ldapData['objectclass'] = $this->_requiredObjectClass;
538 foreach ($this->_ldapPlugins as $plugin) {
539 $plugin->inspectAddUser($user, $ldapData);
542 $dn = $this->_generateDn($user);
544 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE))
545 Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ldapData: ' . print_r($ldapData, true));
547 $this->_ldap->add($dn, $ldapData);
549 $userId = $this->_ldap->getEntry($dn, array($this->_userUUIDAttribute));
551 $userId = $userId[$this->_userUUIDAttribute][0];
553 $user = $this->getUserByPropertyFromSyncBackend('accountId', $userId, 'Tinebase_Model_FullUser');
559 * delete an user in ldap backend
561 * @param int $_userId
563 public function deleteUserInSyncBackend($_userId)
565 if ($this->_isReadOnlyBackend) {
570 $metaData = $this->_getMetaData($_userId);
572 if (! empty($metaData['dn'])) {
573 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
574 . ' delete user ' . $metaData['dn'] .' from sync backend (LDAP)');
575 $this->_ldap->delete($metaData['dn']);
577 } catch (Tinebase_Exception_NotFound $tenf) {
578 if (Tinebase_Core::isLogLevel(Zend_Log::CRIT)) Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__
579 . ' user not found in sync backend: ' . $_userId);
584 * delete multiple users from ldap only
586 * @param array $_accountIds
588 public function deleteUsersInSyncBackend(array $_accountIds)
590 if ($this->_isReadOnlyBackend) {
594 foreach ($_accountIds as $accountId) {
595 $this->deleteUserInSyncBackend($accountId);
600 * return ldap entry of user
602 * @param string $_uid
605 protected function _getLdapEntry($_property, $_userId)
609 $value = $this->_encodeAccountId(Tinebase_Model_User::convertUserIdToInt($_userId));
612 $value = Zend_Ldap::filterEscape($_userId);
616 $filter = Zend_Ldap_Filter::andFilter(
617 Zend_Ldap_Filter::string($this->_userBaseFilter),
618 Zend_Ldap_Filter::equals($this->_rowNameMapping[$_property], $value)
621 $attributes = array_values($this->_rowNameMapping);
622 foreach ($this->_ldapPlugins as $plugin) {
623 $attributes = array_merge($attributes, $plugin->getSupportedAttributes());
625 $attributes[] = 'objectclass';
626 $attributes[] = 'uidnumber';
627 $attributes[] = 'useraccountcontrol';
629 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG))
630 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' filter ' . $filter);
631 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE))
632 Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' requested attributes ' . print_r($attributes, true));
634 $accounts = $this->_ldap->search(
637 $this->_userSearchScope,
641 if (count($accounts) !== 1) {
642 throw new Tinebase_Exception_NotFound('User with ' . $_property . ' = ' . $value . ' not found.');
645 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE))
646 Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' current ldap values ' . print_r($accounts->getFirst(), true));
648 return $accounts->getFirst();
652 * get metatada of existing user
654 * @param string $_userId
657 protected function _getMetaData($_userId)
659 $userId = $this->_encodeAccountId(Tinebase_Model_User::convertUserIdToInt($_userId));
661 $filter = Zend_Ldap_Filter::equals(
662 $this->_rowNameMapping['accountId'], $userId
665 $result = $this->_ldap->search(
668 $this->_userSearchScope
671 if (count($result) !== 1) {
672 throw new Tinebase_Exception_NotFound("user with userid $_userId not found");
675 return $result->getFirst();
679 * generates dn for new user
681 * @param Tinebase_Model_FullUser $_account
684 protected function _generateDn(Tinebase_Model_FullUser $_account)
686 $baseDn = $this->_baseDn;
688 $uidProperty = array_search('uid', $this->_rowNameMapping);
689 $newDn = "uid={$_account->$uidProperty},{$baseDn}";
695 * generates a uidnumber
697 * @todo add a persistent registry which id has been generated lastly to
698 * reduce amount of userid to be transfered
702 protected function _generateUidNumber()
704 $allUidNumbers = array();
707 $filter = Zend_Ldap_Filter::equals(
708 'objectclass', 'posixAccount'
711 $accounts = $this->_ldap->search(
713 $this->_options['userDn'],
714 Zend_Ldap::SEARCH_SCOPE_SUB,
718 foreach ($accounts as $userData) {
719 $allUidNumbers[$userData['uidnumber'][0]] = $userData['uidnumber'][0];
722 // fetch also the uidnumbers of machine accounts, if needed
723 // @todo move this to samba plugin
724 if (isset(Tinebase_Core::getConfig()->samba) && Tinebase_Core::getConfig()->samba->get('manageSAM', FALSE) == true) {
725 $accounts = $this->_ldap->search(
727 Tinebase_Core::getConfig()->samba->get('machineDn'),
728 Zend_Ldap::SEARCH_SCOPE_SUB,
732 foreach ($accounts as $userData) {
733 $allUidNumbers[$userData['uidnumber'][0]] = $userData['uidnumber'][0];
736 sort($allUidNumbers);
738 #if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " Existing uidnumbers " . print_r($allUidNumbers, true));
739 $minUidNumber = isset($this->_options['minUserId']) ? $this->_options['minUserId'] : 1000;
740 $maxUidNumber = isset($this->_options['maxUserId']) ? $this->_options['maxUserId'] : 65000;
742 $numUsers = count($allUidNumbers);
743 if ($numUsers == 0 || $allUidNumbers[$numUsers-1] < $minUidNumber) {
744 $uidNumber = $minUidNumber;
745 } elseif ($allUidNumbers[$numUsers-1] < $maxUidNumber) {
746 $uidNumber = ++$allUidNumbers[$numUsers-1];
747 } elseif (count($allUidNumbers) < ($maxUidNumber - $minUidNumber)) {
748 // maybe there is a gap
749 for($i = $minUidNumber; $i <= $maxUidNumber; $i++) {
750 if (!in_array($i, $allUidNumbers)) {
757 if ($uidNumber === NULL) {
758 throw new Tinebase_Exception_NotImplemented('Max User Id is reached');
765 * return contact information for user
767 * @param Tinebase_Model_FullUser $_user
768 * @param Addressbook_Model_Contact $_contact
770 public function updateContactFromSyncBackend(Tinebase_Model_FullUser $_user, Addressbook_Model_Contact $_contact)
772 $userData = $this->_getMetaData($_user);
774 $userData = $this->_ldap->getEntry($userData['dn']);
776 $this->_ldap2Contact($userData, $_contact);
778 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
779 . " Synced user object: " . print_r($_contact->toArray(), true));
783 * update contact data(first name, last name, ...) of user
785 * @param Addressbook_Model_Contact $contact
786 * @todo implement logic
788 public function updateContactInSyncBackend($_contact)
794 * Returns a user obj with raw data from ldap
796 * @param array $_userData
797 * @param string $_accountClass
798 * @return Tinebase_Record_Abstract
800 protected function _ldap2User(array $_userData, $_accountClass)
804 $this->_lastLdapProperties = $_userData;
806 foreach ($_userData as $key => $value) {
810 $keyMapping = array_search($key, $this->_rowNameMapping);
811 if ($keyMapping !== FALSE) {
812 switch($keyMapping) {
813 case 'accountLastPasswordChange':
814 case 'accountExpires':
815 $shadowExpire = $value[0];
816 if ($shadowExpire < 0) {
817 // account does not expire
818 $accountArray[$keyMapping] = null;
820 $accountArray[$keyMapping] = new Tinebase_DateTime(($shadowExpire < 100000) ? $shadowExpire * 86400 : $shadowExpire);
824 case 'accountStatus':
825 if ((isset($_userData['shadowmax']) || array_key_exists('shadowmax', $_userData)) && (isset($_userData['shadowinactive']) || array_key_exists('shadowinactive', $_userData))) {
826 $lastChange = (isset($_userData['shadowlastchange']) || array_key_exists('shadowlastchange', $_userData)) ? $_userData['shadowlastchange'] : 0;
827 if (($lastChange + $_userData['shadowmax'] + $_userData['shadowinactive']) * 86400 <= Tinebase_DateTime::now()->getTimestamp()) {
828 $accountArray[$keyMapping] = 'enabled';
830 $accountArray[$keyMapping] = 'disabled';
833 $accountArray[$keyMapping] = 'enabled';
838 $accountArray[$keyMapping] = $this->_decodeAccountId($value[0]);
843 $accountArray[$keyMapping] = $value[0];
849 if (empty($accountArray['accountLastName']) && !empty($accountArray['accountFullName'])) {
850 $accountArray['accountLastName'] = $accountArray['accountFullName'];
853 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Could not instantiate account object for ldap user ' . print_r($_userData, 1));
854 $accountObject = null;
856 $accountObject = new $_accountClass($accountArray, TRUE);
859 return $accountObject;
863 * returns properties of last user fetched from sync backend
867 public function getLastUserProperties()
869 return $this->_lastLdapProperties;
873 * helper function to be overwriten in subclasses
875 * @param string $accountId
878 protected function _decodeAccountId($accountId)
884 * helper function to be overwriten in subclasses
886 * @param string $accountId
889 protected function _encodeAccountId($accountId)
895 * parse ldap result set and update Addressbook_Model_Contact
897 * @param array $_userData
898 * @param Addressbook_Model_Contact $_contact
900 protected function _ldap2Contact($_userData, Addressbook_Model_Contact $_contact)
902 $rowNameMapping = array(
903 'bday' => 'birthdate',
904 'tel_cell' => 'mobile',
905 'tel_work' => 'telephonenumber',
906 'tel_home' => 'homephone',
907 'tel_fax' => 'facsimiletelephonenumber',
910 'email_home' => 'mozillasecondemail',
911 'jpegphoto' => 'jpegphoto',
912 'adr_two_locality' => 'mozillahomelocalityname',
913 'adr_two_postalcode' => 'mozillahomepostalcode',
914 'adr_two_region' => 'mozillahomestate',
915 'adr_two_street' => 'mozillahomestreet',
916 'adr_one_region' => 'l',
917 'adr_one_postalcode' => 'postalcode',
918 'adr_one_street' => 'street',
919 'adr_one_region' => 'st',
922 foreach ($_userData as $key => $value) {
927 $keyMapping = array_search($key, $rowNameMapping);
929 if ($keyMapping !== FALSE) {
930 switch($keyMapping) {
932 $_contact->$keyMapping = Tinebase_DateTime::createFromFormat('Y-m-d', $value[0]);
935 $_contact->$keyMapping = $value[0];
943 * returns array of ldap data
945 * @param Tinebase_Model_FullUser $_user
948 protected function _user2ldap(Tinebase_Model_FullUser $_user, array $_ldapEntry = array())
952 foreach ($_user as $key => $value) {
953 $ldapProperty = (isset($this->_rowNameMapping[$key]) || array_key_exists($key, $this->_rowNameMapping)) ? $this->_rowNameMapping[$key] : false;
957 case 'accountLastPasswordChange':
960 case 'accountExpires':
961 $ldapData[$ldapProperty] = $value instanceof DateTime ? floor($value->getTimestamp() / 86400) : array();
963 case 'accountStatus':
964 if ($value == 'enabled') {
965 $ldapData['shadowMax'] = 999999;
966 $ldapData['shadowInactive'] = array();
968 $ldapData['shadowMax'] = 1;
969 $ldapData['shadowInactive'] = 1;
972 case 'accountPrimaryGroup':
973 $ldapData[$ldapProperty] = Tinebase_Group::getInstance()->resolveUUIdToGIdNumber($value);
976 $ldapData[$ldapProperty] = $value;
982 // homedir is an required attribute
983 if (empty($ldapData['homedirectory'])) {
984 $ldapData['homedirectory'] = '/dev/null';
987 $ldapData['objectclass'] = isset($_ldapEntry['objectclass']) ? $_ldapEntry['objectclass'] : array();
989 // check if user has all required object classes. This is needed
990 // when updating users which where created using different requirements
991 foreach ($this->_requiredObjectClass as $className) {
992 if (! in_array($className, $ldapData['objectclass'])) {
993 // merge all required classes at once
994 $ldapData['objectclass'] = array_unique(array_merge($ldapData['objectclass'], $this->_requiredObjectClass));
999 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' LDAP data ' . print_r($ldapData, true));
1004 public function resolveUIdNumberToUUId($_uidNumber)
1006 if ($this->_userUUIDAttribute == 'uidnumber') {
1010 $filter = Zend_Ldap_Filter::equals(
1011 'uidnumber', Zend_Ldap::filterEscape($_uidNumber)
1014 $userId = $this->_ldap->search(
1017 $this->_userSearchScope,
1018 array($this->_userUUIDAttribute)
1021 return $userId[$this->_userUUIDAttribute][0];
1025 * resolve UUID(for example entryUUID) to uidnumber
1027 * @param string $_uuid
1030 public function resolveUUIdToUIdNumber($_uuid)
1032 if ($this->_userUUIDAttribute == 'uidnumber') {
1036 $filter = Zend_Ldap_Filter::equals(
1037 $this->_userUUIDAttribute, $this->_encodeAccountId($_uuid)
1040 $groupId = $this->_ldap->search(
1042 $this->_options['userDn'],
1043 $this->_userSearchScope,
1047 return $groupId['uidnumber'][0];