1ff049298e0f673a3fc8f7e279979ef0c4c86cbd
[tine20] / tine20 / Admin / Controller / User.php
1 <?php
2 /**
3  * Tine 2.0
4  *
5  * @package     Admin
6  * @subpackage  Controller
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @author      Philipp Schüle <p.schuele@metaways.de>
9  * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
10  * 
11  * @todo        catch error (and show alert) if postfix email already exists
12  * @todo        extend Tinebase_Controller_Record_Abstract
13  */
14
15 /**
16  * User Controller for Admin application
17  *
18  * @package     Admin
19  * @subpackage  Controller
20  */
21 class Admin_Controller_User extends Tinebase_Controller_Abstract
22 {
23     /**
24      * @var Tinebase_User_Abstract
25      */
26     protected $_userBackend = NULL;
27     
28     /**
29      * @var Tinebase_SambaSAM_Ldap
30      */
31     protected $_samBackend = NULL;
32
33     /**
34      * the constructor
35      *
36      * don't use the constructor. use the singleton 
37      */
38     private function __construct() 
39     {
40         $this->_applicationName = 'Admin';
41         
42         $this->_userBackend = Tinebase_User::getInstance();
43     }
44
45     /**
46      * don't clone. Use the singleton.
47      *
48      */
49     private function __clone() 
50     {
51     }
52
53     /**
54      * holds the instance of the singleton
55      *
56      * @var Admin_Controller_User
57      */
58     private static $_instance = NULL;
59
60     /**
61      * the singleton pattern
62      *
63      * @return Admin_Controller_User
64      */
65     public static function getInstance() 
66     {
67         if (self::$_instance === NULL) {
68             self::$_instance = new Admin_Controller_User;
69         }
70         
71         return self::$_instance;
72     }
73
74     /**
75      * get list of full accounts -> renamed to search full users
76      *
77      * @param string $_filter string to search accounts for
78      * @param string $_sort
79      * @param string $_dir
80      * @param int $_start
81      * @param int $_limit
82      * @return Tinebase_Record_RecordSet with record class Tinebase_Model_FullUser
83      */
84     public function searchFullUsers($_filter, $_sort = NULL, $_dir = 'ASC', $_start = NULL, $_limit = NULL)
85     {
86         $this->checkRight('VIEW_ACCOUNTS');
87         
88         $result = $this->_userBackend->getUsers($_filter, $_sort, $_dir, $_start, $_limit, 'Tinebase_Model_FullUser');
89         
90         return $result;
91     }
92     
93     /**
94      * count users
95      *
96      * @param string $_filter string to search user accounts for
97      * @return int total user count
98      */
99     public function searchCount($_filter)
100     {
101         $this->checkRight('VIEW_ACCOUNTS');
102         
103         $result = $this->_userBackend->getUsersCount($_filter);
104         
105         return $result;
106     }
107     
108     /**
109      * get account
110      *
111      * @param   string  $_accountId  account id to get
112      * @return  Tinebase_Model_FullUser
113      */
114     public function get($_userId)
115     {
116         $this->checkRight('VIEW_ACCOUNTS');
117         
118         $user = $this->_userBackend->getUserById($_userId, 'Tinebase_Model_FullUser');
119         
120         return $user;
121     }
122     
123     /**
124      * set account status
125      *
126      * @param   string $_accountId  account id
127      * @param   string $_status     status to set
128      * @return  int
129      */
130     public function setAccountStatus($_accountId, $_status)
131     {
132         $this->checkRight('MANAGE_ACCOUNTS');
133         
134         $result = $this->_userBackend->setStatus($_accountId, $_status);
135         
136         if ($_status === Tinebase_Model_FullUser::ACCOUNT_STATUS_DISABLED) {
137             // TODO send this for blocked/expired, too? allow to configure this?
138             Tinebase_User::getInstance()->sendDeactivationNotification($_accountId);
139         }
140         
141         return $result;
142     }
143     
144     /**
145      * set the password for a given account
146      *
147      * @param  Tinebase_Model_FullUser  $_account the account
148      * @param  string                   $_password the new password
149      * @param  string                   $_passwordRepeat the new password again
150      * @param  bool                     $_mustChange
151      * @return void
152      * 
153      * @todo add must change pwd info to normal tine user accounts
154      */
155     public function setAccountPassword(Tinebase_Model_FullUser $_account, $_password, $_passwordRepeat, $_mustChange = null)
156     {
157         $this->checkRight('MANAGE_ACCOUNTS');
158         
159         if ($_password != $_passwordRepeat) {
160             throw new Admin_Exception("Passwords don't match.");
161         }
162         
163         $this->_userBackend->setPassword($_account, $_password, true, $_mustChange);
164         
165         Tinebase_Core::getLogger()->info(
166             __METHOD__ . '::' . __LINE__ . 
167             ' Set new password for user ' . $_account->accountLoginName . '. Must change:' . $_mustChange
168         );
169     }
170     
171     /**
172      * update user
173      *
174      * @param  Tinebase_Model_FullUser    $_user            the user
175      * @param  string                     $_password        the new password
176      * @param  string                     $_passwordRepeat  the new password again
177      * 
178      * @return Tinebase_Model_FullUser
179      */
180     public function update(Tinebase_Model_FullUser $_user, $_password, $_passwordRepeat)
181     {
182         $this->checkRight('MANAGE_ACCOUNTS');
183         
184         $oldUser = $this->_userBackend->getUserByProperty('accountId', $_user, 'Tinebase_Model_FullUser');
185         
186         if ($oldUser->accountLoginName !== $_user->accountLoginName) {
187             $this->_checkLoginNameExistance($_user);
188         }
189         $this->_checkLoginNameLength($_user);
190         $this->_checkPrimaryGroupExistance($_user);
191         
192         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Update user ' . $_user->accountLoginName);
193         
194         try {
195             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
196             
197             if (Tinebase_Application::getInstance()->isInstalled('Addressbook') === true) {
198                 $_user->contact_id = $oldUser->contact_id;
199                 $contact = $this->createOrUpdateContact($_user);
200                 $_user->contact_id = $contact->getId();
201             }
202             
203             Tinebase_Timemachine_ModificationLog::setRecordMetaData($_user, 'update', $oldUser);
204             
205             $user = $this->_userBackend->updateUser($_user);
206             
207             // make sure primary groups is in the list of groupmemberships
208             $groups = array_unique(array_merge(array($user->accountPrimaryGroup), (array) $_user->groups));
209             Admin_Controller_Group::getInstance()->setGroupMemberships($user, $groups);
210             
211             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
212             
213         } catch (Exception $e) {
214             Tinebase_TransactionManager::getInstance()->rollBack();
215             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $e);
216             
217             if ($e instanceof Zend_Db_Statement_Exception && preg_match('/Lock wait timeout exceeded/', $e->getMessage())) {
218                 throw new Tinebase_Exception_Backend_Database_LockTimeout($e->getMessage());
219             }
220             
221             throw $e;
222         }
223         
224         // fire needed events
225         $event = new Admin_Event_UpdateAccount;
226         $event->account = $user;
227         $event->oldAccount = $oldUser;
228         Tinebase_Event::fireEvent($event);
229         
230         if (!empty($_password) && !empty($_passwordRepeat)) {
231             $this->setAccountPassword($_user, $_password, $_passwordRepeat, FALSE);
232         }
233
234         return $user;
235     }
236     
237     /**
238      * create user
239      *
240      * @param  Tinebase_Model_FullUser  $_account           the account
241      * @param  string                     $_password           the new password
242      * @param  string                     $_passwordRepeat  the new password again
243      * @return Tinebase_Model_FullUser
244      */
245     public function create(Tinebase_Model_FullUser $_user, $_password, $_passwordRepeat)
246     {
247         $this->checkRight('MANAGE_ACCOUNTS');
248         
249         // avoid forging accountId, gets created in backend
250         unset($_user->accountId);
251         
252         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Create new user ' . $_user->accountLoginName);
253         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_user->toArray(), TRUE));
254         
255         $this->_checkLoginNameExistance($_user);
256         $this->_checkLoginNameLength($_user);
257         $this->_checkPrimaryGroupExistance($_user);
258         
259         if ($_password != $_passwordRepeat) {
260             throw new Admin_Exception("Passwords don't match.");
261         } else if (empty($_password)) {
262             $_password = '';
263             $_passwordRepeat = '';
264         }
265         Tinebase_User::getInstance()->checkPasswordPolicy($_password, $_user);
266         
267         try {
268             $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
269             
270             if (Tinebase_Application::getInstance()->isInstalled('Addressbook') === true) {
271                 $contact = $this->createOrUpdateContact($_user);
272                 $_user->contact_id = $contact->getId();
273             }
274             
275             Tinebase_Timemachine_ModificationLog::setRecordMetaData($_user, 'create');
276             
277             $user = $this->_userBackend->addUser($_user);
278             
279             // make sure primary groups is in the list of groupmemberships
280             $groups = array_unique(array_merge(array($user->accountPrimaryGroup), (array) $_user->groups));
281             Admin_Controller_Group::getInstance()->setGroupMemberships($user, $groups);
282             
283             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
284
285         } catch (Exception $e) {
286             Tinebase_TransactionManager::getInstance()->rollBack();
287             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $e->getMessage());
288             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . $e->getTraceAsString());
289             throw $e;
290         }
291         
292         $event = new Admin_Event_AddAccount(array(
293             'account' => $user
294         ));
295         Tinebase_Event::fireEvent($event);
296         
297         $this->setAccountPassword($user, $_password, $_passwordRepeat);
298
299         return $user;
300     }
301     
302     /**
303      * look for user with the same login name
304      * 
305      * @param Tinebase_Model_FullUser $user
306      * @return boolean
307      * @throws Tinebase_Exception_SystemGeneric
308      */
309     protected function _checkLoginNameExistance(Tinebase_Model_FullUser $user)
310     {
311         try {
312             $existing = Tinebase_User::getInstance()->getUserByLoginName($user->accountLoginName);
313             if ($user->getId() === NULL || $existing->getId() !== $user->getId()) {
314                 throw new Tinebase_Exception_SystemGeneric('Login name already exists. Please choose another one.');
315             }
316         } catch (Tinebase_Exception_NotFound $tenf) {
317         }
318         
319         return TRUE;
320     }
321     
322     /**
323      * Check login name length
324      *
325      * @param Tinebase_Model_FullUser $user
326      * @return boolean
327      * @throws Tinebase_Exception_SystemGeneric
328      */
329     protected function _checkLoginNameLength(Tinebase_Model_FullUser $user)
330     {
331         $maxLoginNameLength = Tinebase_Config::getInstance()->get(Tinebase_Config::MAX_USERNAME_LENGTH);
332         if (!empty($maxLoginNameLength) && strlen($user->accountLoginName) > $maxLoginNameLength) {
333             throw new Tinebase_Exception_SystemGeneric('The login name exceeds the maximum length of  ' . $maxLoginNameLength . ' characters. Please choose another one.');
334         }
335         return TRUE;
336     }
337     
338     /**
339      * look for primary group, if it does not exist, fallback to default user group
340      * 
341      * @param Tinebase_Model_FullUser $user
342      * @throws Tinebase_Exception_SystemGeneric
343      */
344     protected function _checkPrimaryGroupExistance(Tinebase_Model_FullUser $user)
345     {
346         try {
347             $group = Tinebase_Group::getInstance()->getGroupById($user->accountPrimaryGroup);
348         } catch (Tinebase_Exception_Record_NotDefined $ternd) {
349             $defaultUserGroup = Tinebase_Group::getInstance()->getDefaultGroup();
350             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
351                 . ' Group with id ' . $user->accountPrimaryGroup . ' not found. Use default group (' . $defaultUserGroup->name
352                 . ') as primary group for ' . $user->accountLoginName);
353             
354             $user->accountPrimaryGroup = $defaultUserGroup->getId();
355         }
356     }
357     
358     /**
359      * delete accounts
360      *
361      * @param   mixed $_accountIds  array of account ids
362      * @return  array with success flag
363      * @throws  Tinebase_Exception_Record_NotAllowed
364      */
365     public function delete($_accountIds)
366     {
367         $this->checkRight('MANAGE_ACCOUNTS');
368         
369         $groupsController = Admin_Controller_Group::getInstance();
370         
371         foreach ((array)$_accountIds as $accountId) {
372             if ($accountId === Tinebase_Core::getUser()->getId()) {
373                 $message = 'You are not allowed to delete yourself!';
374                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' ' . $message);
375                 throw new Tinebase_Exception_AccessDenied($message);
376             }
377             
378             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " about to remove user with id: {$accountId}");
379             
380             $oldUser = $this->get($accountId);
381             
382             $memberships = $groupsController->getGroupMemberships($accountId);
383             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " removing user from groups: " . print_r($memberships, true));
384             
385             foreach ((array)$memberships as $groupId) {
386                 $groupsController->removeGroupMember($groupId, $accountId);
387             }
388             
389             $this->_userBackend->deleteUser($accountId);
390         }
391     }
392     
393     /**
394      * returns all shared addressbooks
395      * 
396      * @return Tinebase_Record_RecordSet of shared addressbooks
397      * 
398      * @todo do we need to fetch ALL shared containers here (even if admin user has NO grants for them)?
399      */
400     public function searchSharedAddressbooks()
401     {
402         $this->checkRight('MANAGE_ACCOUNTS');
403         
404         return Tinebase_Container::getInstance()->getSharedContainer(Tinebase_Core::getUser(), 'Addressbook', Tinebase_Model_Grants::GRANT_READ, TRUE);
405     }
406     
407     /**
408      * returns default internal addressbook container
409      * 
410      * @return string|int ID
411      */
412     public function getDefaultInternalAddressbook()
413     {
414         $appConfigDefaults = Admin_Controller::getInstance()->getConfigSettings();
415         if (empty($appConfigDefaults[Admin_Model_Config::DEFAULTINTERNALADDRESSBOOK])) {
416             $internalAdb = Addressbook_Setup_Initialize::setDefaultInternalAddressbook();
417             $internalAdbId = $internalAdb->getId();
418         } else {
419             $internalAdbId = $appConfigDefaults[Admin_Model_Config::DEFAULTINTERNALADDRESSBOOK];
420         }
421         return $internalAdbId;
422     }
423     
424     /**
425      * create or update contact in addressbook backend
426      * 
427      * @param  Tinebase_Model_FullUser $_user
428      * @return Addressbook_Model_Contact
429      */
430     public function createOrUpdateContact(Tinebase_Model_FullUser $_user)
431     {
432         $contactsBackend = Addressbook_Backend_Factory::factory(Addressbook_Backend_Factory::SQL);
433         $contactsBackend->setGetDisabledContacts(true);
434         
435         if (empty($_user->container_id)) {
436             $_user->container_id = $this->getDefaultInternalAddressbook();
437         }
438         
439         try {
440             if (empty($_user->contact_id)) { // jump to catch block
441                 throw new Tinebase_Exception_NotFound('contact_id is empty');
442             }
443             
444             $contact = $contactsBackend->get($_user->contact_id);
445             
446             // update exisiting contact
447             $contact->n_family   = $_user->accountLastName;
448             $contact->n_given    = $_user->accountFirstName;
449             $contact->n_fn       = $_user->accountFullName;
450             $contact->n_fileas   = $_user->accountDisplayName;
451             $contact->email      = $_user->accountEmailAddress;
452             $contact->type       = Addressbook_Model_Contact::CONTACTTYPE_USER;
453             $contact->container_id = $_user->container_id;
454             
455             // add modlog info
456             Tinebase_Timemachine_ModificationLog::setRecordMetaData($contact, 'update');
457             
458             $contact = $contactsBackend->update($contact);
459             
460         } catch (Tinebase_Exception_NotFound $tenf) {
461             // add new contact
462             $contact = new Addressbook_Model_Contact(array(
463                 'n_family'      => $_user->accountLastName,
464                 'n_given'       => $_user->accountFirstName,
465                 'n_fn'          => $_user->accountFullName,
466                 'n_fileas'      => $_user->accountDisplayName,
467                 'email'         => $_user->accountEmailAddress,
468                 'type'          => Addressbook_Model_Contact::CONTACTTYPE_USER,
469                 'container_id'  => $_user->container_id
470             ));
471             
472             // add modlog info
473             Tinebase_Timemachine_ModificationLog::setRecordMetaData($contact, 'create');
474     
475             $contact = $contactsBackend->create($contact);
476         }
477         
478         return $contact;
479     }
480 }