4bcc5416f6eebe76c17f214580b239cfe4f57356
[tine20] / tine20 / Tinebase / User / Abstract.php
1 <?php
2 /**
3  * Tine 2.0
4  *
5  * @package     Tinebase
6  * @subpackage  User
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL3
8  * @copyright   Copyright (c) 2008 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Lars Kneschke <l.kneschke@metaways.de>
10  * 
11  * @deprecated  user backends should be refactored
12  * @todo        add searchCount function
13  */
14
15 /**
16  * abstract class for all user backends
17  *
18  * @package     Tinebase
19  * @subpackage  User
20  */
21  
22 abstract class Tinebase_User_Abstract implements Tinebase_User_Interface
23 {
24     /**
25      * des encryption
26      */
27     const ENCRYPT_DES = 'des';
28     
29     /**
30      * blowfish crypt encryption
31      */
32     const ENCRYPT_BLOWFISH_CRYPT = 'blowfish_crypt';
33     
34     /**
35      * md5 crypt encryption
36      */
37     const ENCRYPT_MD5_CRYPT = 'md5_crypt';
38     
39     /**
40      * ext crypt encryption
41      */
42     const ENCRYPT_EXT_CRYPT = 'ext_crypt';
43     
44     /**
45      * md5 encryption
46      */
47     const ENCRYPT_MD5 = 'md5';
48     
49     /**
50      * smd5 encryption
51      */
52     const ENCRYPT_SMD5 = 'smd5';
53
54     /**
55      * sha encryption
56      */
57     const ENCRYPT_SHA = 'sha';
58     
59     /**
60      * ssha encryption
61      */
62     const ENCRYPT_SSHA = 'ssha';
63     
64     /**
65      * ntpassword encryption
66      */
67     const ENCRYPT_NTPASSWORD = 'ntpassword';
68     
69     /**
70      * no encryption
71      */
72     const ENCRYPT_PLAIN = 'plain';
73     
74     /**
75      * user property for openid
76      */
77     const PROPERTY_OPENID = 'openid';
78     
79     /**
80      * list of plugins 
81      * 
82      * @var array
83      */
84     protected $_plugins = array();
85     
86     /**
87      * user block time
88      * 
89      * @var integer
90      */
91     protected $_blockTime        = 15;
92     
93     /**
94      * the constructor
95      */
96     public function __construct(array $_options = array())
97     {
98         if ((isset($_options['plugins']) || array_key_exists('plugins', $_options))) {
99             $this->registerPlugins($_options['plugins']);
100         }
101     }
102     
103     /**
104      * registerPlugins
105      * 
106      * @param array $plugins
107      */
108     public function registerPlugins($plugins)
109     {
110         foreach ($plugins as $plugin) {
111             $this->registerPlugin($plugin);
112         }
113     }
114     
115     /**
116      * (non-PHPdoc)
117      * @see Tinebase_User_Interface::registerPlugin()
118      */
119     public function registerPlugin(Tinebase_User_Plugin_Interface $_plugin)
120     {
121         $className = get_class($_plugin);
122         
123         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
124             . " Registering " . $className . ' plugin.');
125         
126         $this->_plugins[$className] = $_plugin;
127     }
128
129     /**
130      * unregisterAllPlugins
131      */
132     public function unregisterAllPlugins()
133     {
134         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
135             . ' Unregistering all user plugins.');
136         
137         $this->_plugins = array();
138     }
139     
140     /**
141      * returns all supported password encryptions types
142      *
143      * @return array
144      */
145     public static function getSupportedEncryptionTypes()
146     {
147         return array(
148             self::ENCRYPT_BLOWFISH_CRYPT,
149             self::ENCRYPT_EXT_CRYPT,
150             self::ENCRYPT_DES,
151             self::ENCRYPT_MD5,
152             self::ENCRYPT_MD5_CRYPT,
153             self::ENCRYPT_PLAIN,
154             self::ENCRYPT_SHA,
155             self::ENCRYPT_SMD5,
156             self::ENCRYPT_SSHA,
157             self::ENCRYPT_NTPASSWORD
158         );
159     }
160     
161     /**
162      * encryptes password
163      *
164      * @param string $_password
165      * @param string $_method
166      */
167     public static function encryptPassword($_password, $_method)
168     {
169         switch (strtolower($_method)) {
170             case self::ENCRYPT_BLOWFISH_CRYPT:
171                 if(@defined('CRYPT_BLOWFISH') && CRYPT_BLOWFISH == 1) {
172                     $salt = '$2$' . self::getRandomString(13);
173                     $password = '{CRYPT}' . crypt($_password, $salt);
174                 }
175                 break;
176                 
177             case self::ENCRYPT_EXT_CRYPT:
178                 if(@defined('CRYPT_EXT_DES') && CRYPT_EXT_DES == 1) {
179                     $salt = self::getRandomString(9);
180                     $password = '{CRYPT}' . crypt($_password, $salt);
181                 }
182                 break;
183                 
184             case self::ENCRYPT_MD5:
185                 $password = '{MD5}' . base64_encode(pack("H*", md5($_password)));
186                 break;
187                 
188             case self::ENCRYPT_MD5_CRYPT:
189                 if(@defined('CRYPT_MD5') && CRYPT_MD5 == 1) {
190                     $salt = '$1$' . self::getRandomString(9);
191                     $password = '{CRYPT}' . crypt($_password, $salt);
192                 }
193                 break;
194                 
195             case self::ENCRYPT_PLAIN:
196                 $password = $_password;
197                 break;
198                 
199             case self::ENCRYPT_SHA:
200                 if(function_exists('mhash')) {
201                     $password = '{SHA}' . base64_encode(mhash(MHASH_SHA1, $_password));
202                 }
203                 break;
204                 
205             case self::ENCRYPT_SMD5:
206                 if(function_exists('mhash')) {
207                     $salt = self::getRandomString(8);
208                     $hash = mhash(MHASH_MD5, $_password . $salt);
209                     $password = '{SMD5}' . base64_encode($hash . $salt);
210                 }
211                 break;
212                 
213             case self::ENCRYPT_SSHA:
214                 if(function_exists('mhash')) {
215                     $salt = self::getRandomString(8);
216                     $hash = mhash(MHASH_SHA1, $_password . $salt);
217                     $password = '{SSHA}' . base64_encode($hash . $salt);
218                 }
219                 break;
220                 
221             case self::ENCRYPT_NTPASSWORD:
222                 $password = strtoupper(hash('md4', iconv('UTF-8','UTF-16LE',$_password)));
223                 
224                 break;
225                 
226             default:
227                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " using default password encryption method " . self::ENCRYPT_DES);
228                 // fall through
229             case self::ENCRYPT_DES:
230                 $salt = self::getRandomString(2);
231                 $password  = '{CRYPT}'. crypt($_password, $salt);
232                 break;
233             
234         }
235         
236         if (! $password) {
237             throw new Tinebase_Exception_NotImplemented("$_method is not supported by your php version");
238         }
239         
240         return $password;
241     }    
242     
243     /**
244      * (non-PHPdoc)
245      * @see Tinebase_User_Interface::getPlugins()
246      */
247     public function getPlugins()
248     {
249        return $this->_plugins;
250     }
251     
252     /**
253      * generates a randomstrings of given length
254      *
255      * @param int $_length
256      */
257     public static function getRandomString($_length)
258     {
259         $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
260         
261         $randomString = '';
262         for ($i=0; $i<(int)$_length; $i++) {
263             $randomString .= $chars[mt_rand(1, strlen($chars)) -1];
264         }
265         
266         return $randomString;
267     }
268     
269     /**
270      * get list of users
271      *
272      * @param string $_filter
273      * @param string $_sort
274      * @param string $_dir
275      * @param int $_start
276      * @param int $_limit
277      * @return Tinebase_Record_RecordSet with record class Tinebase_Model_FullUser
278      */
279     public function getFullUsers($_filter = NULL, $_sort = NULL, $_dir = 'ASC', $_start = NULL, $_limit = NULL)
280     {
281         return $this->getUsers($_filter, $_sort, $_dir, $_start, $_limit, 'Tinebase_Model_FullUser');
282     }
283     
284     /**
285      * get full user by login name
286      *
287      * @param   string      $_loginName
288      * @return  Tinebase_Model_FullUser full user
289      */
290     public function getFullUserByLoginName($_loginName)
291     {
292         return $this->getUserByLoginName($_loginName, 'Tinebase_Model_FullUser');
293     }
294     
295     /**
296      * get full user by id
297      *
298      * @param   int         $_accountId
299      * @return  Tinebase_Model_FullUser full user
300      */
301     public function getFullUserById($_accountId)
302     {
303         return $this->getUserById($_accountId, 'Tinebase_Model_FullUser');
304     }
305     
306     /**
307      * get dummy user record
308      *
309      * @param string $_accountClass Tinebase_Model_User|Tinebase_Model_FullUser
310      * @param integer $_id [optional]
311      * @return Tinebase_Model_User|Tinebase_Model_FullUser
312      */
313     public function getNonExistentUser($_accountClass = 'Tinebase_Model_User', $_id = 0) 
314     {
315         $translate = Tinebase_Translation::getTranslation('Tinebase');
316         
317         $data = array(
318             'accountId'             => ($_id !== NULL) ? $_id : 0,
319             'accountLoginName'      => $translate->_('unknown'),
320             'accountDisplayName'    => $translate->_('unknown'),
321             'accountLastName'       => $translate->_('unknown'),
322             'accountFirstName'      => $translate->_('unknown'),
323             'accountFullName'       => $translate->_('unknown'),
324             'accountStatus'         => $translate->_('unknown'),
325         );
326         
327         if ($_accountClass === 'Tinebase_Model_FullUser') {
328             $defaultUserGroup = Tinebase_Group::getInstance()->getDefaultGroup();
329             $data['accountPrimaryGroup'] = $defaultUserGroup->getId();
330         }
331         
332         $result = new $_accountClass($data, TRUE);
333         
334         return $result;
335     }
336     
337     /**
338      * account name generation
339      *
340      * @param Tinebase_Model_FullUser $_account
341      * @param integer $_schema 0 = lastname (10 chars) / 1 = lastname + 2 chars of firstname / 2 = 1-x chars of firstname + lastname 
342      * @return string
343      */
344     public function generateUserName($_account, $_schema = 1)
345     {
346         if (! empty($_account->accountFirstName) && $_schema > 0 && method_exists($this, '_generateUserWithSchema' . $_schema)) {
347             $userName = call_user_func_array(array($this, '_generateUserWithSchema' . $_schema), array($_account));
348         } else {
349             $userName = strtolower(substr(Tinebase_Helper::replaceSpecialChars($_account->accountLastName), 0, 10));
350         }
351
352         if (empty($userName)) {
353             // try email address
354             $userName = strtolower(substr(Tinebase_Helper::replaceSpecialChars($_account->accountEmailAddress), 0, 19));
355         }
356         
357         $userName = $this->_addSuffixToNameIfExists('accountLoginName', $userName);
358         
359         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . '  generated username: ' . $userName . ' with schema: '. $_schema);
360         
361         return $userName;
362     }
363     
364     /**
365      * Full name generation for user with the same name
366      * this is needed for active directory because accountFullName is used for the dn
367      *
368      * @param Tinebase_Model_FullUser $_account
369      * @return string
370      */
371     public function generateAccountFullName($_account)
372     {
373         return $this->_addSuffixToNameIfExists('accountFullName', $_account->accountFullName, NULL);
374     }
375     
376     /**
377      * schema 1 = lastname + 2 chars of firstname
378      * 
379      * @param Tinebase_Model_FullUser $_account
380      * @return string
381      */
382     protected function _generateUserWithSchema1($_account)
383     {
384         $result = strtolower(substr(Tinebase_Helper::replaceSpecialChars($_account->accountLastName), 0, 10) . substr(Tinebase_Helper::replaceSpecialChars($_account->accountFirstName), 0, 2));
385         return $result;
386     }
387     
388     /**
389      * schema 2 = 1-x chars of firstname + lastname
390      * 
391      * @param Tinebase_Model_FullUser $_account
392      * @return string
393      */
394     protected function _generateUserWithSchema2($_account)
395     {
396         $result = $_account->accountLastName;
397         for ($i=0; $i < strlen($_account->accountFirstName); $i++) {
398         
399             $userName = strtolower(substr(Tinebase_Helper::replaceSpecialChars($_account->accountFirstName), 0, $i+1) . Tinebase_Helper::replaceSpecialChars($_account->accountLastName));
400             if (! $this->nameExists('accountLoginName', $userName)) {
401                 $result = $userName;
402                 break;
403             }
404         }
405         
406         return $result;
407     }
408     
409     /**
410      * schema 3 = 1-x chars of firstname . lastname
411      * 
412      * @param Tinebase_Model_FullUser $_account
413      * @return string
414      */
415     protected function _generateUserWithSchema3($_account)
416     {
417         $result = $_account->accountLastName;
418         for ($i=0; $i < strlen($_account->accountFirstName); $i++) {
419         
420             $userName = strtolower(substr(Tinebase_Helper::replaceSpecialChars($_account->accountFirstName), 0, $i+1) . '.' . Tinebase_Helper::replaceSpecialChars($_account->accountLastName));
421             if (! $this->nameExists('accountLoginName', $userName)) {
422                 $result = $userName;
423                 break;
424             }
425         }
426         
427         return $result;
428     }
429     
430     /**
431      * add a suffix to username and AccountFullName
432      * 
433      * @param string $_property
434      * @param string $_name
435      * @return string
436      */
437     protected function _addSuffixToNameIfExists($_property, $_name)
438     {
439         $result = $_name;
440         if ($this->nameExists($_property, $_name)) {
441             $numSuffix = 0;
442         
443             while ($numSuffix < 100) {
444                 $suffix = sprintf('%02d', $numSuffix);
445                 
446                 if (! $this->nameExists($_property, $_name . $suffix)) {
447                     $result = $_name . $suffix;
448                     break;
449                 }
450         
451                 $numSuffix++;
452             }
453         }
454         
455         return $result;
456     }
457     
458     /**
459      * resolves users of given record
460      * 
461      * @param Tinebase_Record_Abstract $_record
462      * @param string|array             $_userProperties
463      * @param bool                     $_addNonExistingUsers
464      * @return void
465      */
466     public function resolveUsers(Tinebase_Record_Abstract $_record, $_userProperties, $_addNonExistingUsers = FALSE)
467     {
468         $recordSet = new Tinebase_Record_RecordSet('Tinebase_Record_Abstract', array($_record));
469         $this->resolveMultipleUsers($recordSet, $_userProperties, $_addNonExistingUsers);
470     }
471     
472     /**
473      * resolves users of given record
474      * 
475      * @param Tinebase_Record_RecordSet $_records
476      * @param string|array              $_userProperties
477      * @param bool                      $_addNonExistingUsers
478      * @return void
479      */
480     public function resolveMultipleUsers(Tinebase_Record_RecordSet $_records, $_userProperties, $_addNonExistingUsers = FALSE)
481     {
482         $userIds = array();
483         foreach ((array)$_userProperties as $property) {
484             // don't break if property is not in record
485             try {
486                 $userIds = array_merge($userIds, $_records->$property);
487             } catch (Exception $e) {
488                 if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Records of class ' . get_class($_records->getFirstRecord()) . ' does not have property ' . $property);
489             }
490         }
491         
492         $userIds = array_unique($userIds);
493         foreach ($userIds as $index => $userId) {
494             if (empty($userId)) {
495                 unset ($userIds[$index]);
496             }
497         }
498                 
499         // if no data return
500         if (empty($userIds)) {
501             return;
502         }
503         
504         $users = $this->getMultiple($userIds);
505         $nonExistingUser = $this->getNonExistentUser();
506         
507         foreach ($_records as $record) {
508             foreach ((array)$_userProperties as $property) {
509                 if ($record->$property && is_string($record->$property)) {
510                     $idx = $users->getIndexById($record->$property);
511                     $user = $idx !== false ? $users[$idx] : NULL;
512                     
513                     if (!$user && $_addNonExistingUsers) {
514                         $user = $nonExistingUser;
515                     }
516                     
517                     if ($user) {
518                         $record->$property = $user;
519                     }
520                 }
521             }
522         }
523     }
524     
525     /**
526      * checks if accountLoginName/accountFullName already exists
527      *
528      * @param   string  $_property
529      * @param   string  $_value
530      * @return  bool    
531      * 
532      */
533     public function nameExists($_property, $_value)
534     {
535         try {
536             $this->getUserByProperty($_property, $_value)->getId();
537         } catch (Tinebase_Exception_NotFound $e) {
538             // username or full name not found
539             return false;
540         }
541         
542         return true;
543     }
544
545     /**
546      * get user by login name
547      *
548      * @param   string  $_loginName
549      * @param   string  $_accountClass  type of model to return
550      * @return  Tinebase_Model_User full user
551      */
552     public function getUserByLoginName($_loginName, $_accountClass = 'Tinebase_Model_User')
553     {
554         return $this->getUserByProperty('accountLoginName', $_loginName, $_accountClass);
555     }
556
557     /**
558      * get user by id
559      *
560      * @param   string  $_accountId
561      * @param   string  $_accountClass  type of model to return
562      * @return  Tinebase_Model_User user
563      */
564     public function getUserById($_accountId, $_accountClass = 'Tinebase_Model_User')
565     {
566         $userId = $_accountId instanceof Tinebase_Model_User ? $_accountId->getId() : $_accountId;
567
568         return $this->getUserByProperty('accountId', $userId, $_accountClass);
569     }
570
571     /**
572      * returns active users
573      *
574      * @return int
575      */
576     public function getActiveUserCount()
577     {
578         $backend = new Tinebase_Backend_Sql(array(
579             'modelName' => 'Tinebase_Model_User',
580             'tableName' => 'accounts',
581             'modlogActive' => true,
582         ));
583
584         // TODO allow to set this as param
585         $afterDate = Tinebase_DateTime::now()->subMonth(1);
586         $filter = new Tinebase_Model_FullUserFilter(array(
587             array('field' => 'last_login', 'operator' => 'after', 'value' => $afterDate),
588             array('field' => 'status', 'operator' => 'equals', 'value' => Tinebase_Model_User::ACCOUNT_STATUS_ENABLED),
589         ));
590
591         return $backend->searchCount($filter);
592     }
593
594     /**
595      * check admin group membership
596      *
597      * @param Tinebase_Model_FullUser $user
598      */
599     public function assertAdminGroupMembership($user)
600     {
601         $adminGroup = Tinebase_Group::getInstance()->getDefaultAdminGroup();
602         $memberships = Tinebase_Group::getInstance()->getGroupMemberships($user);
603         if (! in_array($adminGroup->getId(), $memberships)) {
604             Tinebase_Group::getInstance()->addGroupMember($adminGroup, $user);
605         }
606     }
607
608     /******************* abstract functions *********************/
609     
610     /**
611      * setPassword() - sets / updates the password in the account backend
612      *
613      * @param  string  $_userId
614      * @param  string  $_password
615      * @param  bool    $_encrypt encrypt password
616      * @param  bool    $_mustChange
617      * @return void
618      */
619     abstract public function setPassword($_userId, $_password, $_encrypt = TRUE, $_mustChange = null);
620     
621     /**
622      * update user status
623      *
624      * @param   int         $_accountId
625      * @param   string      $_status
626      */
627     abstract public function setStatus($_accountId, $_status);
628
629     /**
630      * sets/unsets expiry date (calls backend class with the same name)
631      *
632      * @param   int         $_accountId
633      * @param   Tinebase_DateTime   $_expiryDate
634     */
635     abstract public function setExpiryDate($_accountId, $_expiryDate);
636
637     /**
638      * set login time for user (with ip address)
639      *
640      * @param int $_accountId
641      * @param string $_ipAddress
642      */
643     abstract public function setLoginTime($_accountId, $_ipAddress);
644     
645     /**
646      * updates an existing user
647      *
648      * @param   Tinebase_Model_FullUser  $_user
649      * @return  Tinebase_Model_FullUser
650      */
651     abstract public function updateUser(Tinebase_Model_FullUser $_user);
652
653     /**
654      * update contact data(first name, last name, ...) of user
655      * 
656      * @param Addressbook_Model_Contact $contact
657      */
658     abstract public function updateContact(Addressbook_Model_Contact $_contact);
659     
660     /**
661      * adds a new user
662      *
663      * @param   Tinebase_Model_FullUser  $_user
664      * @return  Tinebase_Model_FullUser
665      */
666     abstract public function addUser(Tinebase_Model_FullUser $_user);
667     
668     /**
669      * delete an user
670      *
671      * @param  mixed  $_userId
672      */
673     abstract public function deleteUser($_userId);
674
675     /**
676      * delete multiple users
677      *
678      * @param array $_accountIds
679      */
680     abstract public function deleteUsers(array $_accountIds);
681     
682     /**
683      * Get multiple users
684      *
685      * @param string|array     $_id Ids
686      * @param string          $_accountClass  type of model to return
687      * @return Tinebase_Record_RecordSet
688      */
689     abstract public function getMultiple($_id, $_accountClass = 'Tinebase_Model_User');
690 }