Merge branch '2014.11' into 2015.11
[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         $userName = $this->_addSuffixToNameIfExists('accountLoginName', $userName);
353         
354         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . '  generated username: ' . $userName . ' with schema: '. $_schema);
355         
356         return $userName;
357     }
358     
359     /**
360      * Full name generation for user with the same name
361      * this is needed for active directory because accountFullName is used for the dn
362      *
363      * @param Tinebase_Model_FullUser $_account
364      * @return string
365      */
366     public function generateAccountFullName($_account)
367     {
368         return $this->_addSuffixToNameIfExists('accountFullName', $_account->accountFullName, NULL);
369     }
370     
371     /**
372      * schema 1 = lastname + 2 chars of firstname
373      * 
374      * @param Tinebase_Model_FullUser $_account
375      * @return string
376      */
377     protected function _generateUserWithSchema1($_account)
378     {
379         $result = strtolower(substr(Tinebase_Helper::replaceSpecialChars($_account->accountLastName), 0, 10) . substr(Tinebase_Helper::replaceSpecialChars($_account->accountFirstName), 0, 2));
380         return $result;
381     }
382     
383     /**
384      * schema 2 = 1-x chars of firstname + lastname
385      * 
386      * @param Tinebase_Model_FullUser $_account
387      * @return string
388      */
389     protected function _generateUserWithSchema2($_account)
390     {
391         $result = $_account->accountLastName;
392         for ($i=0; $i < strlen($_account->accountFirstName); $i++) {
393         
394             $userName = strtolower(substr(Tinebase_Helper::replaceSpecialChars($_account->accountFirstName), 0, $i+1) . Tinebase_Helper::replaceSpecialChars($_account->accountLastName));
395             if (! $this->nameExists('accountLoginName', $userName)) {
396                 $result = $userName;
397                 break;
398             }
399         }
400         
401         return $result;
402     }
403     
404     /**
405      * schema 3 = 1-x chars of firstname . lastname
406      * 
407      * @param Tinebase_Model_FullUser $_account
408      * @return string
409      */
410     protected function _generateUserWithSchema3($_account)
411     {
412         $result = $_account->accountLastName;
413         for ($i=0; $i < strlen($_account->accountFirstName); $i++) {
414         
415             $userName = strtolower(substr(Tinebase_Helper::replaceSpecialChars($_account->accountFirstName), 0, $i+1) . '.' . Tinebase_Helper::replaceSpecialChars($_account->accountLastName));
416             if (! $this->nameExists('accountLoginName', $userName)) {
417                 $result = $userName;
418                 break;
419             }
420         }
421         
422         return $result;
423     }
424     
425     /**
426      * add a suffix to username and AccountFullName
427      * 
428      * @param string $_property
429      * @param string $_name
430      * @return string
431      */
432     protected function _addSuffixToNameIfExists($_property, $_name)
433     {
434         $result = $_name;
435         if ($this->nameExists($_property, $_name)) {
436             $numSuffix = 0;
437         
438             while ($numSuffix < 100) {
439                 $suffix = sprintf('%02d', $numSuffix);
440                 
441                 if (! $this->nameExists($_property, $_name . $suffix)) {
442                     $result = $_name . $suffix;
443                     break;
444                 }
445         
446                 $numSuffix++;
447             }
448         }
449         
450         return $result;
451     }
452     
453     /**
454      * resolves users of given record
455      * 
456      * @param Tinebase_Record_Abstract $_record
457      * @param string|array             $_userProperties
458      * @param bool                     $_addNonExistingUsers
459      * @return void
460      */
461     public function resolveUsers(Tinebase_Record_Abstract $_record, $_userProperties, $_addNonExistingUsers = FALSE)
462     {
463         $recordSet = new Tinebase_Record_RecordSet('Tinebase_Record_Abstract', array($_record));
464         $this->resolveMultipleUsers($recordSet, $_userProperties, $_addNonExistingUsers);
465     }
466     
467     /**
468      * resolves users of given record
469      * 
470      * @param Tinebase_Record_RecordSet $_records
471      * @param string|array              $_userProperties
472      * @param bool                      $_addNonExistingUsers
473      * @return void
474      */
475     public function resolveMultipleUsers(Tinebase_Record_RecordSet $_records, $_userProperties, $_addNonExistingUsers = FALSE)
476     {
477         $userIds = array();
478         foreach ((array)$_userProperties as $property) {
479             // don't break if property is not in record
480             try {
481                 $userIds = array_merge($userIds, $_records->$property);
482             } catch (Exception $e) {
483                 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);
484             }
485         }
486         
487         $userIds = array_unique($userIds);
488         foreach ($userIds as $index => $userId) {
489             if (empty($userId)) {
490                 unset ($userIds[$index]);
491             }
492         }
493                 
494         // if no data return
495         if (empty($userIds)) {
496             return;
497         }
498         
499         $users = $this->getMultiple($userIds);
500         $nonExistingUser = $this->getNonExistentUser();
501         
502         foreach ($_records as $record) {
503             foreach ((array)$_userProperties as $property) {
504                 if ($record->$property && is_string($record->$property)) {
505                     $idx = $users->getIndexById($record->$property);
506                     $user = $idx !== false ? $users[$idx] : NULL;
507                     
508                     if (!$user && $_addNonExistingUsers) {
509                         $user = $nonExistingUser;
510                     }
511                     
512                     if ($user) {
513                         $record->$property = $user;
514                     }
515                 }
516             }
517         }
518     }
519     
520     /**
521      * checks if accountLoginName/accountFullName already exists
522      *
523      * @param   string  $_property
524      * @param   string  $_value
525      * @return  bool    
526      * 
527      */
528     public function nameExists($_property, $_value)
529     {
530         try {
531             $this->getUserByProperty($_property, $_value)->getId();
532         } catch (Tinebase_Exception_NotFound $e) {
533             // username or full name not found
534             return false;
535         }
536         
537         return true;
538     }
539
540     /**
541      * get user by login name
542      *
543      * @param   string  $_loginName
544      * @param   string  $_accountClass  type of model to return
545      * @return  Tinebase_Model_User full user
546      */
547     public function getUserByLoginName($_loginName, $_accountClass = 'Tinebase_Model_User')
548     {
549         return $this->getUserByProperty('accountLoginName', $_loginName, $_accountClass);
550     }
551
552     /**
553      * get user by id
554      *
555      * @param   string  $_accountId
556      * @param   string  $_accountClass  type of model to return
557      * @return  Tinebase_Model_User user
558      */
559     public function getUserById($_accountId, $_accountClass = 'Tinebase_Model_User')
560     {
561         $userId = $_accountId instanceof Tinebase_Model_User ? $_accountId->getId() : $_accountId;
562
563         return $this->getUserByProperty('accountId', $userId, $_accountClass);
564     }
565
566     /**
567      * returns active users
568      *
569      * @return int
570      */
571     public function getActiveUserCount()
572     {
573         $backend = new Tinebase_Backend_Sql(array(
574             'modelName' => 'Tinebase_Model_User',
575             'tableName' => 'accounts',
576         ));
577
578         // TODO allow to set this as param
579         $afterDate = Tinebase_DateTime::now()->subMonth(1);
580         $filter = new Tinebase_Model_FullUserFilter(array(
581             array('field' => 'last_login', 'operator' => 'after', 'value' => $afterDate),
582             array('field' => 'status', 'operator' => 'equals', 'value' => Tinebase_Model_User::ACCOUNT_STATUS_ENABLED),
583         ));
584
585         return $backend->searchCount($filter);
586     }
587
588     /******************* abstract functions *********************/
589     
590     /**
591      * setPassword() - sets / updates the password in the account backend
592      *
593      * @param  string  $_userId
594      * @param  string  $_password
595      * @param  bool    $_encrypt encrypt password
596      * @param  bool    $_mustChange
597      * @return void
598      */
599     abstract public function setPassword($_userId, $_password, $_encrypt = TRUE, $_mustChange = null);
600     
601     /**
602      * update user status
603      *
604      * @param   int         $_accountId
605      * @param   string      $_status
606      */
607     abstract public function setStatus($_accountId, $_status);
608
609     /**
610      * sets/unsets expiry date (calls backend class with the same name)
611      *
612      * @param   int         $_accountId
613      * @param   Tinebase_DateTime   $_expiryDate
614     */
615     abstract public function setExpiryDate($_accountId, $_expiryDate);
616
617     /**
618      * set login time for user (with ip address)
619      *
620      * @param int $_accountId
621      * @param string $_ipAddress
622      */
623     abstract public function setLoginTime($_accountId, $_ipAddress);
624     
625     /**
626      * updates an existing user
627      *
628      * @param   Tinebase_Model_FullUser  $_user
629      * @return  Tinebase_Model_FullUser
630      */
631     abstract public function updateUser(Tinebase_Model_FullUser $_user);
632
633     /**
634      * update contact data(first name, last name, ...) of user
635      * 
636      * @param Addressbook_Model_Contact $contact
637      */
638     abstract public function updateContact(Addressbook_Model_Contact $_contact);
639     
640     /**
641      * adds a new user
642      *
643      * @param   Tinebase_Model_FullUser  $_user
644      * @return  Tinebase_Model_FullUser
645      */
646     abstract public function addUser(Tinebase_Model_FullUser $_user);
647     
648     /**
649      * delete an user
650      *
651      * @param  mixed  $_userId
652      */
653     abstract public function deleteUser($_userId);
654
655     /**
656      * delete multiple users
657      *
658      * @param array $_accountIds
659      */
660     abstract public function deleteUsers(array $_accountIds);
661     
662     /**
663      * Get multiple users
664      *
665      * @param string|array     $_id Ids
666      * @param string          $_accountClass  type of model to return
667      * @return Tinebase_Record_RecordSet
668      */
669     abstract public function getMultiple($_id, $_accountClass = 'Tinebase_Model_User');
670 }