0013342: allow to configure default user/admin role names
[tine20] / tine20 / Tinebase / Acl / Roles.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Tinebase
6  * @subpackage  Acl
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2007-2017 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Philipp Schuele <p.schuele@metaways.de>
10  * 
11  * @todo        extend/use sql abstract backend
12  */
13
14 /**
15  * this class handles the roles
16  * 
17  * @package     Tinebase
18  * @subpackage  Acl
19  */
20 class Tinebase_Acl_Roles extends Tinebase_Controller_Record_Abstract
21 {
22     /**
23      * @var Zend_Db_Adapter_Abstract
24      */
25     protected $_db;
26     
27     /**
28      * @var Tinebase_Backend_Sql
29      */
30     protected $_rolesBackend;
31     
32     protected $_classCache = array(
33         'getRoleMemberships' => array(),
34         'hasRight'           => array(),
35     );
36     
37     /**
38      * holdes the _instance of the singleton
39      *
40      * @var Tinebase_Acl_Roles
41      */
42     private static $_instance = NULL;
43     
44     /**
45      * the clone function
46      *
47      * disabled. use the singleton
48      */
49     private function __clone() 
50     {
51     }
52     
53     /**
54      * the constructor
55      *
56      * disabled. use the singleton
57      */
58     protected function __construct()
59     {
60         $this->_applicationName = 'Tinebase';
61         $this->_modelName = 'Tinebase_Model_Role';
62         $this->_backend = new Tinebase_Backend_Sql(array(
63             'modelName' => 'Tinebase_Model_Role',
64             'tableName' => 'roles',
65         ), $this->_getDb());
66         //$this->_purgeRecords = TRUE;
67         //$this->_resolveCustomFields = FALSE;
68         $this->_updateMultipleValidateEachRecord = TRUE;
69         $this->_doContainerACLChecks = FALSE;
70     }
71     
72     /**
73      * the singleton pattern
74      *
75      * @return Tinebase_Acl_Roles
76      */
77     public static function getInstance() 
78     {
79         if (self::$_instance === NULL) {
80             self::$_instance = new Tinebase_Acl_Roles;
81         }
82         
83         return self::$_instance;
84     }
85
86     public static function unsetInstance()
87     {
88         self::$_instance = NULL;
89     }
90     
91     /**
92      * check if one of the roles the user is in has a given right for a given application
93      * 
94      * we read all right for the given user at once and cache them in the internal class cache
95      *
96      * @param   string|Tinebase_Model_Application $_application the application (one of: app name, id or record)
97      * @param   int $_accountId the numeric id of a user account
98      * @param   int $_right the right to check for
99      * @return  bool
100      */
101     public function hasRight($_application, $_accountId, $_right)
102     {
103         try {
104             $application = Tinebase_Application::getInstance()->getApplicationById($_application);
105         } catch (Tinebase_Exception_NotFound $tenf) {
106             return false;
107         }
108         
109         if ($application->status !== Tinebase_Application::ENABLED) {
110             return false;
111         }
112         
113         try {
114             $roleMemberships = $this->getRoleMemberships($_accountId);
115         } catch (Tinebase_Exception_NotFound $tenf) {
116             $roleMemberships = array();
117         }
118         
119         if (empty($roleMemberships)) {
120             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' ' . $_accountId . ' has no role/group memberships.');
121             if (is_object(Tinebase_Core::getUser()) && Tinebase_Core::getUser()->getId() === $_accountId) {
122                 // @todo throw exception in this case?
123                 Tinebase_Session::destroyAndRemoveCookie();
124             }
125             
126             return false;
127         }
128         
129         $classCacheId = Tinebase_Helper::convertCacheId(implode('', $roleMemberships));
130         
131         if (!isset($this->_classCache[__FUNCTION__][$classCacheId])) {
132             $select = $this->_getDb()->select()
133                 ->distinct()
134                 ->from(array('role_rights' => SQL_TABLE_PREFIX . 'role_rights'), array('application_id', 'right'))
135                 ->where($this->_getDb()->quoteIdentifier('role_id') . ' IN (?)', $roleMemberships);
136                 
137             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $select->__toString());
138             
139             $stmt = $this->_getDb()->query($select);
140             $rows = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
141             
142             $rights = array();
143             
144             foreach ($rows as $row) {
145                 $rights[$row['application_id']][$row['right']] = true;
146             }
147             
148             $this->_classCache[__FUNCTION__][$classCacheId] = $rights;
149         } else {
150             $rights = $this->_classCache[__FUNCTION__][$classCacheId];
151         }
152         
153         $applicationId = $application->getId();
154         
155         return isset($rights[$applicationId]) && (isset($rights[$applicationId][$_right]) || isset($rights[$applicationId][Tinebase_Acl_Rights::ADMIN]));
156     }
157     
158     /**
159      * returns list of applications the user is able to use
160      *
161      * this function takes group memberships into account. Applications the accounts is able to use
162      * must have any (was: the 'run') right set and the application must be enabled
163      * 
164      * @param   int $_accountId the numeric account id
165      * @param   boolean $_anyRight is any right enough to geht app?
166      * @return  Tinebase_Record_RecordSet list of enabled applications for this account
167      * @throws  Tinebase_Exception_AccessDenied if user has no role memberships
168      */
169     public function getApplications($_accountId, $_anyRight = FALSE)
170     {
171         $roleMemberships = $this->getRoleMemberships($_accountId);
172         
173         if (empty($roleMemberships)) {
174             return new Tinebase_Record_RecordSet('Tinebase_Model_Application');
175         }
176         
177         $select = $this->_getDb()->select()
178             ->distinct()
179             ->from(array('role_rights' => SQL_TABLE_PREFIX . 'role_rights'), array())
180             ->join(
181                 /* table  */ array('applications' => SQL_TABLE_PREFIX . 'applications'), 
182                 /* on     */ $this->_getDb()->quoteIdentifier('role_rights.application_id') . ' = ' . $this->_getDb()->quoteIdentifier('applications.id')
183             )
184             ->where($this->_getDb()->quoteIdentifier('role_id') . ' IN (?)',          $roleMemberships)
185             ->where($this->_getDb()->quoteIdentifier('applications.status') . ' = ?', Tinebase_Application::ENABLED)
186             ->order('order ASC');
187         
188         if ($_anyRight) {
189             $select->where($this->_getDb()->quoteIdentifier('role_rights.right') . " IS NOT NULL");
190         } else {
191             $select->where($this->_getDb()->quoteIdentifier('role_rights.right') . ' = ?', Tinebase_Acl_Rights::RUN);
192         }
193         
194         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
195             . ' ' . $select);
196         
197         $stmt = $this->_getDb()->query($select);
198         
199         $result = new Tinebase_Record_RecordSet('Tinebase_Model_Application', $stmt->fetchAll(Zend_Db::FETCH_ASSOC));
200         
201         return $result;
202     }
203
204     /**
205      * returns rights for given application and accountId
206      *
207      * @param   string $_application the name of the application
208      * @param   int $_accountId the numeric account id
209      * @return  array list of rights
210      * @throws  Tinebase_Exception_AccessDenied
211      */
212     public function getApplicationRights($_application, $_accountId) 
213     {
214         $application = Tinebase_Application::getInstance()->getApplicationByName($_application);
215         
216         if ($application->status !== Tinebase_Application::ENABLED) {
217             throw new Tinebase_Exception_AccessDenied('User has no rights. The application ' . $_application . ' is disabled.');
218         }
219         
220         $roleMemberships = $this->getRoleMemberships($_accountId);
221         
222         $select = $this->_getDb()->select()
223             ->distinct()
224             ->from(SQL_TABLE_PREFIX . 'role_rights', array('account_rights' => SQL_TABLE_PREFIX . 'role_rights.right'))
225             ->where($this->_getDb()->quoteIdentifier(SQL_TABLE_PREFIX . 'role_rights.application_id') . ' = ?', $application->getId())
226             ->where($this->_getDb()->quoteIdentifier('role_id') . ' IN (?)', $roleMemberships);
227         
228         $stmt = $this->_getDb()->query($select);
229         
230         return  $stmt->fetchAll(Zend_Db::FETCH_COLUMN);
231     }
232     
233     /**
234      * Searches roles according to filter and paging
235      * 
236      * @param  Tinebase_Model_RoleFilter  $_filter
237      * @param  Tinebase_Model_Pagination  $_paging
238      * @return Tinebase_Record_RecordSet  Set of Tinebase_Model_Role
239      */
240     public function searchRoles($_filter, $_paging)
241     {
242         return $this->search($_filter, $_paging);
243     }
244     
245     /**
246      * Returns role identified by its id
247      * 
248      * @param   string  $_roleId
249      * @return  Tinebase_Model_Role  
250      * @throws  Tinebase_Exception_InvalidArgument
251      * @throws  Tinebase_Exception_NotFound
252      */
253     public function getRoleById($_roleId)
254     {
255         /** @var Tinebase_Model_Role $role */
256         $role = $this->_getRolesBackend()->get((string)$_roleId);
257         return $role;
258     }
259     
260     /**
261      * Returns role identified by its name
262      * 
263      * @param   string $_roleName
264      * @return  Tinebase_Model_Role  
265      * @throws  Tinebase_Exception_NotFound
266      */
267     public function getRoleByName($_roleName)
268     {
269         /** @var Tinebase_Model_Role $role */
270         $role = $this->_getRolesBackend()->getByProperty($_roleName, 'name');
271         return $role;
272     }
273     
274     /**
275      * Get multiple roles
276      *
277      * @param string|array $_ids Ids
278      * @return Tinebase_Record_RecordSet
279      */
280     public function getMultiple($_ids)
281     {
282         return $this->_getRolesBackend()->getMultiple($_ids);
283     }
284     
285     /**
286      * Creates a single role
287      * 
288      * @param  Tinebase_Model_Role $role
289      * @return Tinebase_Model_Role
290      */
291     public function createRole(Tinebase_Model_Role $role)
292     {
293         $role = $this->create($role);
294         
295         $this->resetClassCache();
296         
297         return $role;
298     }
299     
300     /**
301      * updates a single role
302      * 
303      * @param  Tinebase_Model_Role $role
304      * @return Tinebase_Model_Role
305      */
306     public function updateRole(Tinebase_Model_Role $role)
307     {
308         $role = $this->update($role);
309         
310         $this->resetClassCache();
311         
312         return $role;
313     }
314     
315     /**
316      * Deletes roles identified by their identifiers
317      * 
318      * @param   string|array $ids to delete
319      * @return  void
320      * @throws  Tinebase_Exception_Backend
321      */
322     public function deleteRoles($ids)
323     {
324         try {
325             $this->delete($ids);
326             
327             $this->resetClassCache();
328             
329         } catch (Exception $e) {
330             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' error while deleting role ' . $e->__toString());
331             Tinebase_TransactionManager::getInstance()->rollBack();
332             throw new Tinebase_Exception_Backend($e->getMessage());
333         }
334     }
335     
336     /**
337      * Delete all Roles returned by {@see getRoles()} using {@see deleteRoles()}
338      * @return void
339      */
340     public function deleteAllRoles()
341     {
342         $roleIds = $this->_getRolesBackend()
343             ->getAll()
344             ->getArrayOfIds();
345         
346         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Deleting ' . count($roleIds) .' roles');
347         
348         if (count($roleIds) > 0) {
349             $this->deleteRoles($roleIds);
350         }
351     }
352     
353     /**
354      * get list of role members 
355      *
356      * @param   string $_roleId
357      * @return  array of array with account ids & types
358      * @throws  Tinebase_Exception_AccessDenied
359      */
360     public function getRoleMembers($_roleId)
361     {
362         $select = $this->_getDb()->select()
363             ->from(array('role_accounts' => SQL_TABLE_PREFIX . 'role_accounts'))
364             ->where($this->_getDb()->quoteIdentifier('role_id') . ' = ?', (string)$_roleId);
365         
366         $stmt = $this->_getDb()->query($select);
367         
368         $members = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
369         
370         return $members;
371     }
372
373     /**
374      * get list of role memberships
375      *
376      * @param int|Tinebase_Model_User $accountId
377      * @param string $type
378      * @return array of array with role ids
379      * @throws Tinebase_Exception_InvalidArgument
380      * @throws Tinebase_Exception_NotFound
381      */
382     public function getRoleMemberships($accountId, $type = Tinebase_Acl_Rights::ACCOUNT_TYPE_USER)
383     {
384         $groupMemberships = null;
385
386         if ($type === Tinebase_Acl_Rights::ACCOUNT_TYPE_USER) {
387             $accountId        = Tinebase_Model_User::convertUserIdToInt($accountId);
388             $groupMemberships = Tinebase_Group::getInstance()->getGroupMemberships($accountId);
389             
390             $classCacheId = Tinebase_Helper::convertCacheId ($accountId . implode('', $groupMemberships) . $type);
391         } else if ($type === Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP) {
392             $accountId = Tinebase_Model_Group::convertGroupIdToInt($accountId);
393             
394             $classCacheId = Tinebase_Helper::convertCacheId ($accountId . $type);
395         } else {
396             throw new Tinebase_Exception_InvalidArgument('Invalid type: ' . $type);
397         }
398         
399         if (isset($this->_classCache[__FUNCTION__][$classCacheId])) {
400             return $this->_classCache[__FUNCTION__][$classCacheId];
401         }
402         
403         $select = $this->_getDb()->select()
404             ->distinct()
405             ->from(array('role_accounts' => SQL_TABLE_PREFIX . 'role_accounts'), array('role_id'))
406             ->where($this->_getDb()->quoteInto($this->_getDb()->quoteIdentifier('account_id') . ' = ?', $accountId) . ' AND ' 
407                 . $this->_getDb()->quoteInto($this->_getDb()->quoteIdentifier('account_type') . ' = ?', $type));
408         
409         if ($type === Tinebase_Acl_Rights::ACCOUNT_TYPE_USER && !empty($groupMemberships)) {
410             $select->orWhere($this->_getDb()->quoteInto($this->_getDb()->quoteIdentifier('account_id') . ' IN (?)', $groupMemberships) . ' AND '
411                 .  $this->_getDb()->quoteInto($this->_getDb()->quoteIdentifier('account_type') . ' = ?', Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP));
412         }
413         
414         $stmt = $this->_getDb()->query($select);
415         
416         $memberships = $stmt->fetchAll(Zend_Db::FETCH_COLUMN);
417         
418         $this->_classCache[__FUNCTION__][$classCacheId] = $memberships;
419         
420         return $memberships;
421     }
422
423     /**
424      * set role members 
425      *
426      * @param   string $_roleId
427      * @param   array $_roleMembers with role members ("account_type" => account type, "account_id" => account id)
428      * @param   bool $_allowSetId
429      * @throws  Tinebase_Exception_InvalidArgument
430      */
431     public function setRoleMembers($_roleId, array $_roleMembers, $_allowSetId = false)
432     {
433         $_roleId = (string)$_roleId;
434         /** @var Tinebase_Model_Role $oldRole */
435         $oldRole = $this->get($_roleId);
436         
437         // remove old members
438         $where = array(
439             $this->_getDb()->quoteIdentifier('role_id') . ' = ?' => $_roleId
440         );
441         $this->_getDb()->delete(SQL_TABLE_PREFIX . 'role_accounts', $where);
442         
443         $validTypes = array( Tinebase_Acl_Rights::ACCOUNT_TYPE_USER, Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP, Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE);
444         foreach ($_roleMembers as $member) {
445             if (!in_array($member['type'], $validTypes)) {
446                 throw new Tinebase_Exception_InvalidArgument('account_type must be one of ' . 
447                     implode(', ', $validTypes) . ' (values given: ' . 
448                     print_r($member, true) . ')');
449             }
450             
451             $data = array(
452                 'role_id'       => $_roleId,
453                 'account_type'  => $member['type'],
454                 'account_id'    => $member['id'],
455             );
456             if (true === $_allowSetId && isset($member['dataId'])) {
457                 $data['id'] = $member['dataId'];
458             } else {
459                 $data['id'] = Tinebase_Record_Abstract::generateUID();
460             }
461             $this->_getDb()->insert(SQL_TABLE_PREFIX . 'role_accounts', $data);
462         }
463
464         $this->_writeModLogForRole($oldRole);
465         
466         $this->resetClassCache();
467     }
468
469     /**
470      * set all roles an user is member of
471      *
472      * @param  array $_account as role member ("account_type" => account type, "account_id" => account id)
473      * @param  mixed $_roleIds
474      * @return array
475      * @throws Tinebase_Exception_InvalidArgument
476      * @throws Tinebase_Exception_NotFound
477      */
478     public function setRoleMemberships($_account, $_roleIds)
479     {
480         if ($_roleIds instanceof Tinebase_Record_RecordSet) {
481             $_roleIds = $_roleIds->getArrayOfIds();
482         }
483         
484         if(count($_roleIds) === 0) {
485             throw new Tinebase_Exception_InvalidArgument('user must belong to at least one role');
486         }
487         
488         $validTypes = array( Tinebase_Acl_Rights::ACCOUNT_TYPE_USER, Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP, Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE);
489
490         if (! in_array($_account['type'], $validTypes)) {
491             throw new Tinebase_Exception_InvalidArgument('account_type must be one of ' . 
492                 implode(', ', $validTypes) . ' (values given: ' . 
493                 print_r($_account, true) . ')');
494         }
495         
496         $roleMemberships = $this->getRoleMemberships($_account['id']);
497         
498         $removeRoleMemberships = array_diff($roleMemberships, $_roleIds);
499         $addRoleMemberships    = array_diff($_roleIds, $roleMemberships);
500         
501         foreach ($addRoleMemberships as $roleId) {
502             $this->addRoleMember($roleId, $_account);
503         }
504         
505         foreach ($removeRoleMemberships as $roleId) {
506             $this->removeRoleMember($roleId, $_account);
507         }
508         
509         return $this->getRoleMemberships($_account['id']);
510     }
511
512     /**
513      * add a new member to a role
514      *
515      * @param  string $_roleId
516      * @param  array $_account as role member ("account_type" => account type, "account_id" => account id)
517      * @throws Tinebase_Exception_InvalidArgument
518      * @throws Zend_Db_Adapter_Exception
519      */
520     public function addRoleMember($_roleId, $_account)
521     {
522         $validTypes = array(Tinebase_Acl_Rights::ACCOUNT_TYPE_USER, Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP, Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE);
523
524         if (! in_array($_account['type'], $validTypes)) {
525             throw new Tinebase_Exception_InvalidArgument('account_type must be one of ' . 
526                 implode(', ', $validTypes) . ' (values given: ' . 
527                 print_r($_account, true) . ')');
528         }
529
530         /** @var Tinebase_Model_Role $oldRole */
531         $oldRole = $this->get($_roleId);
532         
533         $data = array(
534             'role_id'       => (string)$_roleId,
535             'account_type'  => $_account['type'],
536             'account_id'    => $_account['id'],
537             'id'            => Tinebase_Record_Abstract::generateUID(),
538         );
539         
540         try {
541             $this->_getDb()->insert(SQL_TABLE_PREFIX . 'role_accounts', $data);
542
543             $this->_writeModLogForRole($oldRole);
544         } catch (Zend_Db_Statement_Exception $e) {
545             // account is already member of this group
546         }
547         
548         $this->resetClassCache();
549     }
550
551     protected function _writeModLogForRole(Tinebase_Model_Role $_oldRole)
552     {
553         $seq = intval($_oldRole->seq);
554         $_oldRole->seq = $seq + 1;
555         $this->_getRolesBackend()->update($_oldRole);
556
557         $newRole = $this->get($_oldRole->getId());
558         $this->_writeModLog($newRole, $_oldRole);
559     }
560
561     /**
562      * remove one member from the role
563      *
564      * @param  mixed $_roleId
565      * @param  array $_account as role member ("type" => account type, "id" => account id)
566      * @throws Tinebase_Exception_InvalidArgument
567      */
568     public function removeRoleMember($_roleId, $_account)
569     {
570         /** @var Tinebase_Model_Role $oldRole */
571         $oldRole = $this->get($_roleId);
572         
573         $where = array(
574             $this->_getDb()->quoteIdentifier('role_id') . ' = ?'      => (string) $_roleId,
575             $this->_getDb()->quoteIdentifier('account_type') . ' = ?' => $_account['type'],
576             $this->_getDb()->quoteIdentifier('account_id') . ' = ?'   => (string) $_account['id']
577         );
578         
579         $this->_getDb()->delete(SQL_TABLE_PREFIX . 'role_accounts', $where);
580
581         $this->_writeModLogForRole($oldRole);
582         
583         $this->resetClassCache();
584     }
585     
586     /**
587      * reset class cache
588      * 
589      * @param string $key
590      * @return Tinebase_Acl_Roles
591      */
592     public function resetClassCache($key = null)
593     {
594         foreach ($this->_classCache as $cacheKey => $cacheValue) {
595             if ($key === null || $key === $cacheKey) {
596                 $this->_classCache[$cacheKey] = array();
597             }
598         }
599         
600         return $this;
601     }
602     
603     /**
604      * get list of role rights
605      *
606      * @param   string $_roleId
607      * @return  array of array with application ids & rights
608      * @throws  Tinebase_Exception_InvalidArgument
609      */
610     public function getRoleRights($_roleId)
611     {
612         $select = $this->_getDb()->select()
613             ->distinct()
614             ->from(array('role_rights' => SQL_TABLE_PREFIX . 'role_rights'), array('application_id', 'right'))
615             ->where($this->_getDb()->quoteIdentifier('role_id') . ' = ?', (string) $_roleId);
616         
617         $stmt = $this->_getDb()->query($select);
618         
619         $rights = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
620         
621         return $rights;
622     }
623
624     /**
625      * set role rights 
626      *
627      * @param   string    $roleId
628      * @param   array  $roleRights  with role rights array(("application_id" => app id, "right" => the right to set), (...))
629      * @throws  Tinebase_Exception_InvalidArgument
630      */
631     public function setRoleRights($roleId, array $roleRights)
632     {
633         $currentRights = $this->getRoleRights($roleId);
634         // change array key to string identifying right
635         foreach ($currentRights as $id => $right) {
636             $currentRights[$right['application_id'] . $right['right']] = $right;
637             unset($currentRights[$id]);
638         }
639         
640         // change array key to string identifying right
641         foreach ($roleRights as $id => $right) {
642             $roleRights[$right['application_id'] . $right['right']] = $right;
643             unset($roleRights[$id]);
644         }
645         
646         // compare array keys to calculate changes
647         $rightsToBeDeleted = array_diff_key($currentRights, $roleRights);
648         $rightsToBeAdded   = array_diff_key($roleRights, $currentRights);
649         
650         $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($this->_db);
651         
652         foreach ($rightsToBeDeleted as $right) {
653             $this->deleteRoleRight($roleId, $right['application_id'], $right['right']);
654         }
655         
656         foreach ($rightsToBeAdded as $right) {
657             $this->addRoleRight($roleId, $right['application_id'], $right['right']);
658         }
659         
660         Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
661         
662         $this->_invalidateRightsCache($roleId, array_merge($rightsToBeDeleted, $rightsToBeAdded));
663     }
664     
665     /**
666      * add one role right
667      * 
668      * @param string $roleId
669      * @param string $applicationId
670      * @param string $right
671      */
672     public function addRoleRight($roleId, $applicationId, $right)
673     {
674         /** @var Tinebase_Model_Role $oldRole */
675         $oldRole = $this->get($roleId);
676
677         $data = array(
678             'id'             => Tinebase_Record_Abstract::generateUID(),
679             'role_id'        => (string)$roleId,
680             'application_id' => $applicationId,
681             'right'          => $right,
682         );
683         
684         $this->_getDb()->insert(SQL_TABLE_PREFIX . 'role_rights', $data);
685
686         $this->_writeModLogForRole($oldRole);
687         
688         $this->resetClassCache();
689     }
690     
691     /**
692      * remove one role right
693      * 
694      * @param string $roleId
695      * @param string $applicationId
696      * @param string $right
697      */
698     public function deleteRoleRight($roleId, $applicationId, $right)
699     {
700         /** @var Tinebase_Model_Role $oldRole */
701         $oldRole = $this->get($roleId);
702
703         $where = array(
704             $this->_getDb()->quoteIdentifier('role_id') . ' = ?'        => (string) $roleId,
705             $this->_getDb()->quoteIdentifier('application_id') . ' = ?' => $applicationId,
706             $this->_getDb()->quoteIdentifier('right') . ' = ?'          => $right
707         );
708         
709         $this->_getDb()->delete(SQL_TABLE_PREFIX . 'role_rights', $where);
710
711         $this->_writeModLogForRole($oldRole);
712         
713         $this->resetClassCache();
714     }
715     
716     /**
717      * invalidate rights cache
718      * 
719      * @param string   $roleId
720      * @param array $roleRights  the role rights to purge from cache
721      */
722     protected function _invalidateRightsCache($roleId, $roleRights)
723     {
724         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
725             . ' Invalidating rights cache for role id ' . $roleId);
726         
727         $rightsInvalidateCache = array();
728         foreach ($roleRights as $right) {
729             $rightsInvalidateCache[] = strtoupper($right['right']) . Tinebase_Application::getInstance()->getApplicationById($right['application_id'])->name;
730         }
731         
732         // @todo can be further improved, by only selecting the users which are members of this role
733         $userIds = Tinebase_User::getInstance()->getUsers()->getArrayOfIds();
734         
735         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) 
736             Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($rightsInvalidateCache, TRUE));
737         
738         foreach ($rightsInvalidateCache as $rightData) {
739             foreach ($userIds as $userId) {
740                 $cacheId = Tinebase_Helper::convertCacheId('checkRight' . $userId . $rightData);
741                 
742                 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) 
743                     Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' Removing cache id ' . $cacheId);
744                 
745                 Tinebase_Core::getCache()->remove($cacheId);
746             }
747         }
748         
749         $this->resetClassCache();
750     }
751     
752     /**
753      * add single role rights 
754      *
755      * @param   string $_roleId
756      * @param   string $_applicationId
757      * @param   string $_right
758      * 
759      * @todo this function should be removed and setRoleRights should be used instead
760      */
761     public function addSingleRight($_roleId, $_applicationId, $_right)
762     {
763         // check if already in
764         $select = $this->_getDb()->select()
765             ->from(array('role_rights' => SQL_TABLE_PREFIX . 'role_rights'), array('id'))
766             ->where($this->_getDb()->quoteIdentifier('role_id')        . ' = ?', (string)$_roleId)
767             ->where($this->_getDb()->quoteIdentifier('application_id') . ' = ?', $_applicationId)
768             ->where($this->_getDb()->quoteIdentifier('right')          . ' = ?', $_right);
769         
770         $stmt = $this->_getDb()->query($select);
771         $rows = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
772         
773         if (empty($rows)) {
774             $this->addRoleRight($_roleId, $_applicationId, $_right);
775         }
776         
777         $this->resetClassCache();
778     }
779     
780     /**
781      * Create initial Roles
782      *
783      * @return void
784      */
785     public function createInitialRoles()
786     {
787         $groupsBackend = Tinebase_Group::getInstance();
788         /** @noinspection PhpUndefinedMethodInspection */
789         $oldValue = $groupsBackend->modlogActive(false);
790         
791         $adminGroup         = $groupsBackend->getDefaultAdminGroup();
792         $userGroup          = $groupsBackend->getDefaultGroup();
793         $replicationGroup   = $groupsBackend->getDefaultReplicationGroup();
794
795         $oldOmitModLog = $this->_omitModLog;
796         $oldSetNotes = $this->_setNotes;
797         $this->_omitModLog = true;
798         $this->_setNotes = false;
799
800         $userRoleName = Tinebase_Config::getInstance()->get(Tinebase_Config::DEFAULT_USER_ROLE_NAME);
801         $adminRoleName = Tinebase_Config::getInstance()->get(Tinebase_Config::DEFAULT_ADMIN_ROLE_NAME);
802         
803         // add roles and add the groups to the roles
804         $adminRole = new Tinebase_Model_Role(array(
805             'name'                  => $adminRoleName,
806             'description'           => 'admin role for tine. this role has all rights per default.',
807         ));
808         $adminRole = $this->createRole($adminRole);
809         $this->setRoleMembers($adminRole->getId(), array(
810             array(
811                 'id'    => $adminGroup->getId(),
812                 'type'  => Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP, 
813             )
814         ));
815         
816         $userRole = new Tinebase_Model_Role(array(
817             'name'                  => $userRoleName,
818             'description'           => 'userrole for tine. this role has only the run rights for all applications per default.',
819         ));
820         $userRole = $this->createRole($userRole);
821         $this->setRoleMembers($userRole->getId(), array(
822             array(
823                 'id'    => $userGroup->getId(),
824                 'type'  => Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP, 
825             )
826         ));
827
828         $replicationRole = new Tinebase_Model_Role(array(
829             'name'                  => 'replication role',
830             'description'           => 'replication role for tine. this role has only the right to access replication data per default.',
831         ));
832         $replicationRole = $this->createRole($replicationRole);
833         $this->addRoleRight($replicationRole->getId(), Tinebase_Core::getTinebaseId(), Tinebase_Acl_Rights::REPLICATION);
834         $this->setRoleMembers($replicationRole->getId(), array(
835             array(
836                 'id'    => $replicationGroup->getId(),
837                 'type'  => Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP,
838             )
839         ));
840
841         /** @noinspection PhpUndefinedMethodInspection */
842         $groupsBackend->modlogActive($oldValue);
843
844         $this->_setNotes = $oldSetNotes;
845         $this->_omitModLog = $oldOmitModLog;
846         $this->resetClassCache();
847     }
848     
849     /**
850      * create db instance
851      * 
852      * @return Zend_Db_Adapter_Abstract
853      */
854     protected function _getDb()
855     {
856         if (!$this->_db) {
857             $this->_db = Tinebase_Core::getDb();
858         }
859         
860         return $this->_db;
861     }
862     
863     /**
864      * create backend for roles table
865      * 
866      * @return Tinebase_Backend_Sql
867      */
868     protected function _getRolesBackend()
869     {
870         if (!$this->_rolesBackend) {
871             $this->_rolesBackend = new Tinebase_Backend_Sql(array(
872                 'modelName' => 'Tinebase_Model_Role', 
873                 'tableName' => 'roles',
874             ), $this->_getDb());
875         }
876         
877         return $this->_rolesBackend;
878     }
879
880     /**
881      * inspect creation of one record (after create)
882      *
883      * @param   Tinebase_Record_Interface $_createdRecord
884      * @param   Tinebase_Record_Interface $_record
885      * @return  void
886      */
887     protected function _inspectAfterCreate($_createdRecord, Tinebase_Record_Interface $_record)
888     {
889         $config = $_record::getConfiguration()->recordsFields;
890         foreach (array_keys($config) as $property) {
891             $this->_createDependentRecords($_createdRecord, $_record, $property, $config[$property]['config']);
892         }
893     }
894
895     /**
896      * inspect update of one record (before update)
897      *
898      * @param   Tinebase_Record_Interface $_record      the update record
899      * @param   Tinebase_Record_Interface $_oldRecord   the current persistent record
900      * @return  void
901      */
902     protected function _inspectBeforeUpdate($_record, $_oldRecord)
903     {
904         $config = $_record::getConfiguration()->recordsFields;
905
906         foreach (array_keys($config) as $p) {
907             $this->_updateDependentRecords($_record, $_oldRecord, $p, $config[$p]['config']);
908         }
909     }
910
911     /**
912      * get by id
913      *
914      * @param string $_id
915      * @param int $_containerId
916      * @param bool         $_getRelatedData
917      * @param bool $_getDeleted
918      * @return Tinebase_Record_Interface
919      * @throws Tinebase_Exception_AccessDenied
920      */
921     public function get($_id, $_containerId = NULL, $_getRelatedData = TRUE, $_getDeleted = FALSE)
922     {
923         $result = parent::get($_id, $_containerId, $_getRelatedData, $_getDeleted);
924         $modelName = $this->_modelName;
925         /** @noinspection PhpUndefinedMethodInspection */
926         $modelConf = $modelName::getConfiguration();
927         $rs = new Tinebase_Record_RecordSet($this->_modelName, array($result));
928         Tinebase_ModelConfiguration::resolveRecordsPropertiesForRecordSet($rs, $modelConf);
929         return $rs->getFirstRecord();
930     }
931
932     /**
933      * get dummy role record
934      *
935      * @param integer $_id [optional]
936      * @return Tinebase_Model_Role
937      */
938     public function getNonExistentRole($_id = NULL)
939     {
940         $translate = Tinebase_Translation::getTranslation('Tinebase');
941
942         $result = new Tinebase_Model_Role(array(
943             'id'        => $_id,
944             'name'      => $translate->_('unknown'),
945         ), TRUE);
946
947         return $result;
948     }
949 }