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