WIP increase content Sequence with ldap
[tine20] / tine20 / Tinebase / User.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Tinebase
6  * @subpackage  User
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2007-2013 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Lars Kneschke <l.kneschke@metaways.de>
10  */
11
12 /**
13  * User Class
14  *
15  * @package     Tinebase
16  * @subpackage  User
17  */
18 class Tinebase_User
19 {
20     /**
21      * backend constants
22      * 
23      * @var string
24      */
25     const ACTIVEDIRECTORY = 'ActiveDirectory';
26     const LDAP   = 'Ldap';
27     const SQL    = 'Sql';
28     const TYPO3  = 'Typo3';
29     
30     /**
31      * user status constants
32      * 
33      * @var string
34      * 
35      * @todo use constants from model
36      */
37     const STATUS_BLOCKED  = 'blocked';
38     const STATUS_DISABLED = 'disabled';
39     const STATUS_ENABLED  = 'enabled';
40     const STATUS_EXPIRED  = 'expired';
41     
42     /**
43      * Key under which the default user group name setting will be stored/retrieved
44      *
45      */
46     const DEFAULT_USER_GROUP_NAME_KEY = 'defaultUserGroupName';
47     
48     /**
49      * Key under which the default admin group name setting will be stored/retrieved
50      *
51      */
52     const DEFAULT_ADMIN_GROUP_NAME_KEY = 'defaultAdminGroupName';
53
54     /**
55      * the constructor
56      *
57      * don't use the constructor. use the singleton 
58      */
59     private function __construct() {}
60     
61     /**
62      * don't clone. Use the singleton.
63      */
64     private function __clone() {}
65
66     /**
67      * holds the instance of the singleton
68      *
69      * @var Tinebase_User_Interface
70      */
71     private static $_instance = NULL;
72
73     /**
74      * Holds the accounts backend type (e.g. Ldap or Sql.
75      * Property is lazy loaded on first access via getter {@see getConfiguredBackend()}
76      * 
77      * @var array | optional
78      */
79     private static $_backendType;
80     
81     /**
82      * Holds the backend configuration options.
83      * Property is lazy loaded from {@see Tinebase_Config} on first access via
84      * getter {@see getBackendConfiguration()}
85      * 
86      * @var array | optional
87      */
88     private static $_backendConfiguration;
89     
90     /**
91      * Holds the backend configuration options.
92      * Property is lazy loaded from {@see Tinebase_Config} on first access via
93      * getter {@see getBackendConfiguration()}
94      * 
95      * @var array | optional
96      */
97     private static $_backendConfigurationDefaults = array(
98         self::SQL => array(
99             self::DEFAULT_USER_GROUP_NAME_KEY  => Tinebase_Group::DEFAULT_USER_GROUP,
100             self::DEFAULT_ADMIN_GROUP_NAME_KEY => Tinebase_Group::DEFAULT_ADMIN_GROUP,
101         ),
102         self::LDAP => array(
103             'host' => 'localhost',
104             'username' => '',
105             'password' => '',
106             'bindRequiresDn' => true,
107             'useRfc2307bis' => false,
108             'userDn' => '',
109             'userFilter' => 'objectclass=posixaccount',
110             'userSearchScope' => Zend_Ldap::SEARCH_SCOPE_SUB,
111             'groupsDn' => '',
112             'groupFilter' => 'objectclass=posixgroup',
113             'groupSearchScope' => Zend_Ldap::SEARCH_SCOPE_SUB,
114             'pwEncType' => 'SSHA',
115             'minUserId' => '10000',
116             'maxUserId' => '29999',
117             'minGroupId' => '11000',
118             'maxGroupId' => '11099',
119             'groupUUIDAttribute' => 'entryUUID',
120             'userUUIDAttribute' => 'entryUUID',
121             self::DEFAULT_USER_GROUP_NAME_KEY  => Tinebase_Group::DEFAULT_USER_GROUP,
122             self::DEFAULT_ADMIN_GROUP_NAME_KEY => Tinebase_Group::DEFAULT_ADMIN_GROUP,
123             'readonly' => false,
124         ),
125         self::ACTIVEDIRECTORY => array(
126             'host' => 'localhost',
127             'username' => '',
128             'password' => '',
129             'bindRequiresDn' => true,
130             'useRfc2307' => false,
131             'userDn' => '',
132             'userFilter' => 'objectclass=user',
133             'userSearchScope' => Zend_Ldap::SEARCH_SCOPE_SUB,
134             'groupsDn' => '',
135             'groupFilter' => 'objectclass=group',
136             'groupSearchScope' => Zend_Ldap::SEARCH_SCOPE_SUB,
137             'minUserId' => '10000',
138             'maxUserId' => '29999',
139             'minGroupId' => '11000',
140             'maxGroupId' => '11099',
141             'groupUUIDAttribute' => 'objectGUID',
142             'userUUIDAttribute' => 'objectGUID',
143             self::DEFAULT_USER_GROUP_NAME_KEY  => 'Domain Users',
144             self::DEFAULT_ADMIN_GROUP_NAME_KEY => 'Domain Admins',
145             'readonly' => false,
146          )
147     );
148     
149     /**
150      * the singleton pattern
151      *
152      * @return Tinebase_User_Abstract
153      */
154     public static function getInstance() 
155     {
156         $backendType = self::getConfiguredBackend();
157         
158         if (self::$_instance === NULL) {
159             $backendType = self::getConfiguredBackend();
160             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .' accounts backend: ' . $backendType);
161             
162             self::$_instance = self::factory($backendType);
163         }
164         
165         return self::$_instance;
166     }
167         
168     /**
169      * return an instance of the current user backend
170      *
171      * @param   string $backendType name of the user backend
172      * @return  Tinebase_User_Abstract
173      * @throws  Tinebase_Exception_InvalidArgument
174      */
175     public static function factory($backendType) 
176     {
177         $options = self::getBackendConfiguration();
178         
179         // this is a dangerous TRACE as there might be passwords in here!
180         //if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' 
181         //    . print_r($options, TRUE));
182         
183         $options['plugins'] = array();
184         
185         // manage email user settings
186         if (Tinebase_EmailUser::manages(Tinebase_Config::IMAP)) {
187             try {
188                 $options['plugins'][] = Tinebase_EmailUser::getInstance(Tinebase_Config::IMAP);
189             } catch (Exception $e) {
190                 if (Tinebase_Core::isLogLevel(Zend_Log::ERR)) Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__
191                     . ' Could not add IMAP EmailUser plugin: ' . $e);
192             }
193         }
194         if (Tinebase_EmailUser::manages(Tinebase_Config::SMTP)) {
195             try {
196                 $options['plugins'][] = Tinebase_EmailUser::getInstance(Tinebase_Config::SMTP);
197                         } catch (Exception $e) {
198             if (Tinebase_Core::isLogLevel(Zend_Log::ERR)) Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__
199                     . ' Could not add SMTP EmailUser plugin: ' . $e);
200             }
201         }
202         
203         switch ($backendType) {
204             case self::ACTIVEDIRECTORY:
205                 $result  = new Tinebase_User_ActiveDirectory($options);
206                 
207                 break;
208                 
209             case self::LDAP:
210                 // manage samba sam?
211                 if (isset(Tinebase_Core::getConfig()->samba) && Tinebase_Core::getConfig()->samba->get('manageSAM', FALSE) == true) {
212                     $options['plugins'][] = new Tinebase_User_Plugin_Samba(Tinebase_Core::getConfig()->samba->toArray());
213                 }
214                 
215                 $result  = new Tinebase_User_Ldap($options);
216                 
217                 break;
218                 
219             case self::SQL:
220                 $result = new Tinebase_User_Sql($options);
221                 
222                 break;
223             
224             case self::TYPO3:
225                 $result = new Tinebase_User_Typo3($options);
226                 
227                 break;
228                 
229             default:
230                 throw new Tinebase_Exception_InvalidArgument("User backend type $backendType not implemented.");
231         }
232         
233         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ 
234             . ' Created user backend of type ' . $backendType);
235         
236         return $result;
237     }
238     
239     /**
240      * returns the configured rs backend
241      * 
242      * @return string
243      */
244     public static function getConfiguredBackend()
245     {
246         if (!isset(self::$_backendType)) {
247             if (Setup_Controller::getInstance()->isInstalled('Tinebase')) {
248                 self::setBackendType(Tinebase_Config::getInstance()->get(Tinebase_Config::USERBACKENDTYPE, self::SQL));
249             } else {
250                 self::setBackendType(self::SQL);
251             }
252         }
253         
254         return self::$_backendType;
255     }
256     
257     /**
258      * setter for {@see $_backendType}
259      * 
260      * @todo persist in db
261      * 
262      * @param string $backendType
263      * @return void
264      */
265     public static function setBackendType($backendType)
266     {
267         if (empty($backendType)) {
268             throw new Tinebase_Exception_InvalidArgument('Backend type can not be empty!');
269         }
270         
271         $newBackendType = ucfirst($backendType);
272         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ .
273             ' Setting backend type to ' . $newBackendType);
274         
275         self::$_backendType = $newBackendType;
276     }
277     
278     /**
279      * Setter for {@see $_backendConfiguration}
280      * 
281      * NOTE:
282      * Setting will not be written to Database or Filesystem.
283      * To persist the change call {@see saveBackendConfiguration()}
284      * 
285      * @param mixed $_value
286      * @param string $_key
287      * @param boolean $_applyDefaults
288      * @return void
289      * 
290      * @todo generalize this (see Tinebase_Auth::setBackendConfiguration)
291      */
292     public static function setBackendConfiguration($_value, $_key = null, $_applyDefaults = false)
293     {
294         $defaultValues = self::$_backendConfigurationDefaults[self::getConfiguredBackend()];
295         
296         if (is_null($_key) && !is_array($_value)) {
297             throw new Tinebase_Exception_InvalidArgument('To set backend configuration either a key and value '
298                 . 'parameter are required or the value parameter should be a hash');
299         } elseif (is_null($_key) && is_array($_value)) {
300             $configToSet = $_applyDefaults ? array_merge($defaultValues, $_value) : $_value;
301             foreach ($configToSet as $key => $value) {
302                 self::setBackendConfiguration($value, $key);
303             }
304         } else {
305             if ( ! (isset($defaultValues[$_key]) || array_key_exists($_key, $defaultValues))) {
306                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ .
307                     " Cannot set backend configuration option '$_key' for accounts storage " . self::getConfiguredBackend());
308                 return;
309             }
310             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
311                 ' Setting backend key ' . $_key . ' to ' . (preg_match('/password|pwd|pass|passwd/i', $_key) ? '********' : $_value));
312             
313             self::$_backendConfiguration[$_key] = $_value;
314         }
315     }
316     
317     /**
318      * Delete the given config setting or all config settings if {@param $_key} is not specified
319      * 
320      * @param string | optional $_key
321      * @return void
322      */
323     public static function deleteBackendConfiguration($_key = null)
324     {
325         if (is_null($_key)) {
326             self::$_backendConfiguration = array();
327         } elseif ((isset(self::$_backendConfiguration[$_key]) || array_key_exists($_key, self::$_backendConfiguration))) {
328             unset(self::$_backendConfiguration[$_key]);
329         } else {
330             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' configuration option does not exist: ' . $_key);
331         }
332     }
333     
334     /**
335      * Write backend configuration setting {@see $_backendConfigurationSettings} and {@see $_backendType} to
336      * db config table.
337      * 
338      * @return void
339      */
340     public static function saveBackendConfiguration()
341     {
342         Tinebase_Config::getInstance()->set(Tinebase_Config::USERBACKEND, self::getBackendConfiguration());
343         Tinebase_Config::getInstance()->set(Tinebase_Config::USERBACKENDTYPE, self::getConfiguredBackend());
344     }
345     
346     /**
347      * Getter for {@see $_backendConfiguration}
348      * 
349      * @param String | optional $_key
350      * @return mixed [If {@param $_key} is set then only the specified option is returned, otherwise the whole options hash]
351      */
352     public static function getBackendConfiguration($_key = null, $_default = null)
353     {
354         //lazy loading for $_backendConfiguration
355         if (!isset(self::$_backendConfiguration)) {
356             if (Setup_Controller::getInstance()->isInstalled('Tinebase')) {
357                 $rawBackendConfiguration = Tinebase_Config::getInstance()->get(Tinebase_Config::USERBACKEND, new Tinebase_Config_Struct())->toArray();
358             } else {
359                 $rawBackendConfiguration = array();
360             }
361             self::$_backendConfiguration = is_array($rawBackendConfiguration) ? $rawBackendConfiguration : Zend_Json::decode($rawBackendConfiguration);
362         }
363
364         if (isset($_key)) {
365             return (isset(self::$_backendConfiguration[$_key]) || array_key_exists($_key, self::$_backendConfiguration)) ? self::$_backendConfiguration[$_key] : $_default;
366         } else {
367             return self::$_backendConfiguration;
368         }
369     }
370     
371     /**
372      * Returns default configuration for all supported backends 
373      * and overrides the defaults with concrete values stored in this configuration 
374      * 
375      * @param boolean $_getConfiguredBackend
376      * @return mixed [If {@param $_key} is set then only the specified option is returned, otherwise the whole options hash]
377      */
378     public static function getBackendConfigurationWithDefaults($_getConfiguredBackend = TRUE)
379     {
380         $config = array();
381         $defaultConfig = self::getBackendConfigurationDefaults();
382         foreach ($defaultConfig as $backendType => $backendConfig) {
383             $config[$backendType] = ($_getConfiguredBackend && $backendType == self::getConfiguredBackend() ? self::getBackendConfiguration() : array());
384             if (is_array($config[$backendType])) {
385                 foreach ($backendConfig as $key => $value) {
386                     if (! (isset($config[$backendType][$key]) || array_key_exists($key, $config[$backendType]))) {
387                         $config[$backendType][$key] = $value;
388                     }
389                 }
390             } else {
391                 $config[$backendType] = $backendConfig;
392             }
393         }
394         return $config;
395     }
396     
397     /**
398      * Getter for {@see $_backendConfigurationDefaults}
399      * @param String | optional $_backendType
400      * @return array
401      */
402     public static function getBackendConfigurationDefaults($_backendType = null) {
403         if ($_backendType) {
404             if (!(isset(self::$_backendConfigurationDefaults[$_backendType]) || array_key_exists($_backendType, self::$_backendConfigurationDefaults))) {
405                 throw new Tinebase_Exception_InvalidArgument("Unknown backend type '$_backendType'");
406             }
407             return self::$_backendConfigurationDefaults[$_backendType];
408         } else {
409             return self::$_backendConfigurationDefaults;
410         }
411     }
412     
413     /**
414      * syncronize user from syncbackend to local sql backend
415      * 
416      * @param  mixed  $username  the login id of the user to synchronize
417      * @param  array $options
418      * @return Tinebase_Model_FullUser
419      * @throws Tinebase_Exception
420      * 
421      * @todo make use of dbmail plugin configurable (should be false by default)
422      * @todo switch to new primary group if it could not be found
423      * @todo write a test and refactor this ... :(
424      */
425     public static function syncUser($username, $options = array())
426     {
427         if ($username instanceof Tinebase_Model_FullUser) {
428             $username = $username->accountLoginName;
429         } else {
430             $username = $username;
431         }
432         
433         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . "  sync user data for: " . $username);
434         
435         $userBackend  = Tinebase_User::getInstance();
436         if (isset($options['ldapplugins']) && is_array($options['ldapplugins'])) {
437             foreach ($options['ldapplugins'] as $plugin) {
438                 $userBackend->registerLdapPlugin($plugin);
439             }
440         }
441         
442         $user = $userBackend->getUserByPropertyFromSyncBackend('accountLoginName', $username, 'Tinebase_Model_FullUser');
443         $user->accountPrimaryGroup = Tinebase_Group::getInstance()->resolveGIdNumberToUUId($user->accountPrimaryGroup);
444         
445         $userProperties = method_exists($userBackend, 'getLastUserProperties') ? $userBackend->getLastUserProperties() : array();
446         
447         $hookResult = self::_syncUserHook($user, $userProperties);
448         if (! $hookResult) {
449             return null;
450         }
451         
452         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' 
453             . print_r($user->toArray(), TRUE));
454         
455         $group = self::getPrimaryGroupForUser($user);
456         
457         try {
458             $currentUser = $userBackend->getUserByProperty('accountId', $user, 'Tinebase_Model_FullUser');
459         
460             $currentUser->accountLoginName          = $user->accountLoginName;
461             $currentUser->accountLastPasswordChange = $user->accountLastPasswordChange;
462             $currentUser->accountExpires            = $user->accountExpires;
463             $currentUser->accountPrimaryGroup       = $user->accountPrimaryGroup;
464             $currentUser->accountDisplayName        = $user->accountDisplayName;
465             $currentUser->accountLastName           = $user->accountLastName;
466             $currentUser->accountFirstName          = $user->accountFirstName;
467             $currentUser->accountFullName           = $user->accountFullName;
468             $currentUser->accountEmailAddress       = $user->accountEmailAddress;
469             $currentUser->accountHomeDirectory      = $user->accountHomeDirectory;
470             $currentUser->accountLoginShell         = $user->accountLoginShell;
471             if (! empty($user->visibility) && $currentUser->visibility !== $user->visibility) {
472                 $currentUser->visibility            = $user->visibility;
473                 if (empty($currentUser->contact_id) && $currentUser->visibility == Tinebase_Model_FullUser::VISIBILITY_DISPLAYED) {
474                     self::createContactForSyncedUser($currentUser);
475                 }
476             }
477         
478             $syncedUser = $userBackend->updateUserInSqlBackend($currentUser);
479             if (! empty($user->container_id)) {
480                 $syncedUser->container_id = $user->container_id;
481             }
482             $userBackend->updatePluginUser($syncedUser, $user);
483             
484         } catch (Tinebase_Exception_NotFound $ten) {
485             try {
486                 $invalidUser = $userBackend->getUserByPropertyFromSqlBackend('accountLoginName', $username, 'Tinebase_Model_FullUser');
487                 if (Tinebase_Core::isLogLevel(Zend_Log::CRIT)) Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__ 
488                     . " Remove invalid user: " . $username);
489                 $userBackend->deleteUserInSqlBackend($invalidUser);
490             } catch (Tinebase_Exception_NotFound $ten) {
491                 // do nothing
492             }
493             
494             if ($user->visibility !== Tinebase_Model_FullUser::VISIBILITY_HIDDEN) {
495                 self::createContactForSyncedUser($user);
496             }
497             $syncedUser = $userBackend->addUserInSqlBackend($user);
498             $userBackend->addPluginUser($syncedUser, $user);
499         }
500         
501         self::syncContactData($syncedUser, $options);
502         
503         // sync group memberships
504         Tinebase_Group::syncMemberships($syncedUser);
505         
506         return $syncedUser;
507     }
508     
509     /**
510      * import contactdata(phone, address, fax, birthday. photo)
511      * 
512      * @param Tinebase_Model_FullUser $syncedUser
513      * @param array $options
514      */
515     public static function syncContactData($syncedUser, $options)
516     {
517         if (! Tinebase_Config::getInstance()->get(Tinebase_Config::SYNC_USER_CONTACT_DATA, true)
518                 || ! isset($options['syncContactData'])
519                 || ! $options['syncContactData']
520                 || ! Tinebase_Application::getInstance()->isInstalled('Addressbook')
521                 ||   $syncedUser->visibility === Tinebase_Model_FullUser::VISIBILITY_HIDDEN
522         ) {
523             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
524                 . ' Contact data sync disabled');
525             return;
526         }
527         
528         $addressbook = Addressbook_Backend_Factory::factory(Addressbook_Backend_Factory::SQL);
529         
530         try {
531             $contact = $addressbook->getByUserId($syncedUser->getId());
532             
533             Tinebase_User::getInstance()->updateContactFromSyncBackend($syncedUser, $contact);
534             if (! empty($syncedUser->container_id)) {
535                 $contact->container_id = $syncedUser->container_id;
536             }
537             
538             // add modlog info
539             // Tinebase_Timemachine_ModificationLog::setRecordMetaData($contact, 'update');
540             // Tinebase_Container::getInstance()->increaseContentSequence($contact->container_id);
541             
542             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
543                 . ' Updating contact data for user ' . $syncedUser->accountLoginName);
544             
545             $addressbook->update($contact);
546         } catch (Addressbook_Exception_NotFound $aenf) {
547             self::createContactForSyncedUser($syncedUser);
548             $syncedUser = Tinebase_User::getInstance()->updateUserInSqlBackend($syncedUser);
549         }
550     }
551     
552     /**
553      * get primary group for user and make sure that group exists
554      * 
555      * @param Tinebase_Model_FullUser $user
556      * @throws Tinebase_Exception
557      * @return Tinebase_Model_Group
558      */
559     public static function getPrimaryGroupForUser($user)
560     {
561         $groupBackend = Tinebase_Group::getInstance();
562         
563         try {
564             $group = $groupBackend->getGroupById($user->accountPrimaryGroup);
565         } catch (Tinebase_Exception_Record_NotDefined $tern) {
566             if ($groupBackend->isDisabledBackend()) {
567                 // groups are sql only
568                 $group = $groupBackend->getDefaultGroup();
569                 $user->accountPrimaryGroup = $group->getId();
570             } else {
571                 try {
572                     $group = $groupBackend->getGroupByIdFromSyncBackend($user->accountPrimaryGroup);
573                 } catch (Tinebase_Exception_Record_NotDefined $ternd) {
574                     throw new Tinebase_Exception('Primary group ' . $user->accountPrimaryGroup . ' not found in sync backend.');
575                 }
576                 try {
577                     $sqlGgroup = $groupBackend->getGroupByName($group->name);
578                     throw new Tinebase_Exception('Group already exists but it has a different ID: ' . $group->name);
579         
580                 } catch (Tinebase_Exception_Record_NotDefined $tern) {
581                     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
582                         . " Adding group " . $group->name);
583                     $group = $groupBackend->addGroupInSqlBackend($group);
584                 }
585             }
586         }
587         
588         return $group;
589     }
590     
591     /**
592      * call configured hooks for adjusting synced user data
593      * 
594      * @param Tinebase_Model_FullUser $user
595      * @param array $userProperties
596      * @return boolean if false, user is skipped
597      */
598     protected static function _syncUserHook(Tinebase_Model_FullUser $user, $userProperties)
599     {
600         $result = true;
601         $hookClass = Tinebase_Config::getInstance()->get(Tinebase_Config::SYNC_USER_HOOK_CLASS);
602         if ($hookClass && class_exists($hookClass)) {
603             $hook = new $hookClass();
604             if (method_exists($hook, 'syncUser')) {
605                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
606                     . ' Calling ' . $hookClass . '::syncUser() ...');
607                 
608                 try {
609                     $result = call_user_func_array(array($hook, 'syncUser'), array($user, $userProperties));
610                 } catch (Tinebase_Exception $te) {
611                     Tinebase_Exception::log($te);
612                     return false;
613                 }
614             }
615         }
616         
617         return $result;
618     }
619     
620     /**
621      * create contact in addressbook
622      * 
623      * @param Tinebase_Model_FullUser $user
624      */
625     public static function createContactForSyncedUser($user)
626     {
627         if (! Tinebase_Application::getInstance()->isInstalled('Addressbook')) {
628             return;
629         }
630         
631         $addressbook = Addressbook_Backend_Factory::factory(Addressbook_Backend_Factory::SQL);
632         $internalAddressbook = Tinebase_Container::getInstance()->getContainerByName('Addressbook', 'Internal Contacts', Tinebase_Model_Container::TYPE_SHARED);
633     
634         $contact = new Addressbook_Model_Contact(array(
635             'n_family'      => $user->accountLastName,
636             'n_given'       => $user->accountFirstName,
637             'n_fn'          => $user->accountFullName,
638             'n_fileas'      => $user->accountDisplayName,
639             'email'         => $user->accountEmailAddress,
640             'type'          => Addressbook_Model_Contact::CONTACTTYPE_USER,
641             'container_id'  => (! empty($user->container_id)) ? $user->container_id : $internalAddressbook->getId()
642         ));
643     
644         // add modlog info
645         Tinebase_Timemachine_ModificationLog::setRecordMetaData($contact, 'create');
646     
647         $contact = $addressbook->create($contact);
648         
649         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
650             . " Added contact " . $contact->n_given);
651     
652         $user->contact_id = $contact->getId();
653     }
654     
655     /**
656      * import users from sync backend
657      * 
658      * @param array $options
659      */
660     public static function syncUsers($options)
661     {
662         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ 
663             .' Start synchronizing users ...');
664         
665         $users = Tinebase_User::getInstance()->getUsersFromSyncBackend(NULL, NULL, 'ASC', NULL, NULL, 'Tinebase_Model_FullUser');
666         
667         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
668             . ' About to sync ' . count($users) . ' users from sync backend ...');
669         
670         foreach ($users as $user) {
671             try {
672                 self::syncUser($user, $options);
673             } catch (Tinebase_Exception_NotFound $ten) {
674                 Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__ . " User {$user->accountLoginName} not synced: "
675                     . $ten->getMessage());
676             }
677         }
678         
679         // @todo this should be improved: only the cache of synced users + group memberships should be cleaned
680         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
681             . ' Finished synchronizing users. Clearing cache after user sync ...');
682         Tinebase_Core::getCache()->clean();
683     }
684
685     /**
686      * get all user passwords from ldap
687      * - set pw for user (in sql and sql plugins)
688      * - do not encrypt the pw again as it is encrypted in LDAP
689      * 
690      * @throws Tinebase_Exception_Backend
691      */
692     public static function syncLdapPasswords()
693     {
694         $userBackend = Tinebase_User::getInstance();
695         if (! $userBackend instanceof Tinebase_User_Ldap) {
696             throw new Tinebase_Exception_Backend('Needs LDAP accounts backend');
697         }
698         
699         $result = $userBackend->getUserAttributes(array('entryUUID', 'userPassword'));
700         
701         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
702             . ' About to sync ' . count($result) . ' user passwords from LDAP to Tine 2.0.');
703         
704         $sqlBackend = Tinebase_User::factory(self::SQL);
705         foreach ($result as $user) {
706             try {
707                 $sqlBackend->setPassword($user['entryUUID'], $user['userPassword'], FALSE);
708             } catch (Tinebase_Exception_NotFound $tenf) {
709                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
710                     . ' Could not find user with id ' . $user['entryUUID'] . ' in SQL backend.');
711             }
712         }
713     }
714     
715     /**
716      * create initial admin account
717      * 
718      * Method is called during Setup Initialization
719      *
720      * $_options may contain the following keys:
721      * <code>
722      * $options = array(
723      *  'adminLoginName'    => 'admin',
724      *  'adminPassword'     => 'lars',
725      *  'adminFirstName'    => 'Tine 2.0',
726      *  'adminLastName'     => 'Admin Account',
727      *  'adminEmailAddress' => 'admin@tine20domain.org',
728      *  'expires'            => Tinebase_DateTime object
729      * );
730      * </code>
731      *
732      * @param array $_options [hash that may contain override values for admin user name and password]
733      * @return void
734      */
735     public static function createInitialAccounts($_options)
736     {
737         if (! isset($_options['adminPassword']) || ! isset($_options['adminLoginName'])) {
738             throw new Tinebase_Exception_InvalidArgument('Admin password and login name have to be set when creating initial account.', 503);
739         }
740         
741         $adminLoginName     = $_options['adminLoginName'];
742         $adminPassword      = $_options['adminPassword'];
743         $adminFirstName     = isset($_options['adminFirstName'])    ? $_options['adminFirstName'] : 'Tine 2.0';
744         $adminLastName      = isset($_options['adminLastName'])     ? $_options['adminLastName']  : 'Admin Account';
745         $adminEmailAddress  = ((isset($_options['adminEmailAddress']) || array_key_exists('adminEmailAddress', $_options))) ? $_options['adminEmailAddress'] : NULL;
746
747         // get admin & user groups
748         $userBackend   = Tinebase_User::getInstance();
749         $groupsBackend = Tinebase_Group::getInstance();
750         
751         $adminGroup = $groupsBackend->getDefaultAdminGroup();
752         $userGroup  = $groupsBackend->getDefaultGroup();
753         
754         Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Creating initial admin user (login: ' . $adminLoginName . ' / email: ' . $adminEmailAddress . ')');
755
756         $user = new Tinebase_Model_FullUser(array(
757             'accountLoginName'      => $adminLoginName,
758             'accountStatus'         => 'enabled',
759             'accountPrimaryGroup'   => $userGroup->getId(),
760             'accountLastName'       => $adminLastName,
761             'accountDisplayName'    => $adminLastName . ', ' . $adminFirstName,
762             'accountFirstName'      => $adminFirstName,
763             'accountExpires'        => (isset($_options['expires'])) ? $_options['expires'] : NULL,
764             'accountEmailAddress'   => $adminEmailAddress
765         ));
766         
767         if ($adminEmailAddress !== NULL) {
768             $user->imapUser = new Tinebase_Model_EmailUser(array(
769                 'emailPassword' => $adminPassword
770             ));
771             $user->smtpUser = new Tinebase_Model_EmailUser(array(
772                 'emailPassword' => $adminPassword
773             ));
774         }
775
776         // update or create user in local sql backend
777         try {
778             $userBackend->getUserByProperty('accountLoginName', $adminLoginName);
779             $user = $userBackend->updateUserInSqlBackend($user);
780         } catch (Tinebase_Exception_NotFound $ten) {
781             // call addUser here to make sure, sql user plugins (email, ...) are triggered
782             $user = $userBackend->addUser($user);
783         }
784         
785         // set the password for the account
786         Tinebase_User::getInstance()->setPassword($user, $adminPassword);
787
788         // add the admin account to all groups
789         Tinebase_Group::getInstance()->addGroupMember($adminGroup, $user);
790         Tinebase_Group::getInstance()->addGroupMember($userGroup, $user);
791     }
792 }