3ba925096a0ec51f1b9dc628b31de817ce02db86
[tine20] / tine20 / Tinebase / WebDav / PrincipalBackend.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Tinebase
6  * @subpackage  WebDav
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2011-2014 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Lars Kneschke <l.kneschke@metaways.de>
10  */
11
12 /**
13  * principal backend class
14  * 
15  * @package     Tinebase
16  * @subpackage  WebDav
17  */
18 class Tinebase_WebDav_PrincipalBackend implements \Sabre\DAVACL\PrincipalBackend\BackendInterface
19 {
20     const PREFIX_USERS  = 'principals/users';
21     const PREFIX_GROUPS = 'principals/groups';
22     const SHARED        = 'shared';
23     
24     /**
25      * (non-PHPdoc)
26      * @see Sabre\DAVACL\IPrincipalBackend::getPrincipalsByPrefix()
27      */
28     public function getPrincipalsByPrefix($prefixPath) 
29     {
30         $principals = array();
31         
32         switch ($prefixPath) {
33             case self::PREFIX_GROUPS:
34                 $filter = new Addressbook_Model_ListFilter(array(
35                     array(
36                         'field'     => 'type',
37                         'operator'  => 'equals',
38                         'value'     => Addressbook_Model_List::LISTTYPE_GROUP
39                     )
40                 ));
41                 
42                 $lists = Addressbook_Controller_List::getInstance()->search($filter);
43                 
44                 foreach ($lists as $list) {
45                     $principals[] = $this->_listToPrincipal($list);
46                 }
47                 
48                 break;
49                 
50             case self::PREFIX_USERS:
51                 $filter = new Addressbook_Model_ContactFilter(array(
52                     array(
53                         'field'     => 'type',
54                         'operator'  => 'equals',
55                         'value'     => Addressbook_Model_Contact::CONTACTTYPE_USER
56                     )
57                 ));
58                 
59                 $contacts = Addressbook_Controller_Contact::getInstance()->search($filter);
60                 
61                 foreach ($contacts as $contact) {
62                     $principals[] = $this->_contactToPrincipal($contact);
63                 }
64                 
65                 $principals[] = $this->_contactForSharedPrincipal();
66                 
67                 break;
68         }
69         
70         return $principals;
71     }
72     
73     /**
74      * (non-PHPdoc)
75      * @see Sabre\DAVACL\IPrincipalBackend::getPrincipalByPath()
76      * @todo resolve real $path
77      */
78     public function getPrincipalByPath($path) 
79     {
80         $principal = null;
81         
82         list($prefix, $id) = \Sabre\DAV\URLUtil::splitPath($path);
83         
84         // special handling for calendar proxy principals
85         // they are groups in the user namespace
86         if (in_array($id, array('calendar-proxy-read', 'calendar-proxy-write'))) {
87             $path = $prefix;
88             
89             // set prefix to calendar-proxy-read or calendar-proxy-write
90             $prefix = $id;
91             
92             list(, $id) = \Sabre\DAV\URLUtil::splitPath($path);
93         }
94         
95         switch ($prefix) {
96             case 'calendar-proxy-read':
97                 return null;
98                 
99                 break;
100                 
101             case 'calendar-proxy-write':
102                 // does the account exist
103                 $contactPrincipal = $this->getPrincipalByPath(self::PREFIX_USERS . '/' . $id);
104                 
105                 if (!$contactPrincipal) {
106                     return null;
107                 }
108                 
109                 $principal = array(
110                     'uri'                     => $contactPrincipal['uri'] . '/' . $prefix,
111                     '{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}calendar-user-type'  => 'GROUP',
112                     '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}record-type' => 'groups'
113                 );
114                 
115                 break;
116                 
117             case self::PREFIX_GROUPS:
118                 $filter = new Addressbook_Model_ListFilter(array(
119                     array(
120                         'field'     => 'type',
121                         'operator'  => 'equals',
122                         'value'     => Addressbook_Model_List::LISTTYPE_GROUP
123                     ),
124                     array(
125                         'field'     => 'id',
126                         'operator'  => 'equals',
127                         'value'     => $id
128                     ),
129                 ));
130                 
131                 $list = Addressbook_Controller_List::getInstance()->search($filter)->getFirstRecord();
132                 
133                 if (!$list) {
134                     return null;
135                 }
136                 
137                 $principal = $this->_listToPrincipal($list);
138                 
139                 break;
140                 
141             case self::PREFIX_USERS:
142                 if ($id === self::SHARED) {
143                     $principal = $this->_contactForSharedPrincipal();
144                     
145                 } else {
146                     $filter = new Addressbook_Model_ContactFilter(array(
147                         array(
148                             'field'     => 'type',
149                             'operator'  => 'equals',
150                             'value'     => Addressbook_Model_Contact::CONTACTTYPE_USER
151                         ),
152                         array(
153                             'field'     => 'id',
154                             'operator'  => 'equals',
155                             'value'     => $id
156                         ),
157                     ));
158                     
159                     $contact = Addressbook_Controller_Contact::getInstance()->search($filter)->getFirstRecord();
160                     
161                     if (!$contact) {
162                         return null;
163                     }
164                     
165                     $principal = $this->_contactToPrincipal($contact);
166                 }
167                 
168                 break;
169         }
170         
171         return $principal;
172     }
173     
174     /**
175      * (non-PHPdoc)
176      * @see Sabre\DAVACL\IPrincipalBackend::getGroupMemberSet()
177      */
178     public function getGroupMemberSet($principal) 
179     {
180         $result = array();
181         
182         list($prefix, $id) = \Sabre\DAV\URLUtil::splitPath($principal);
183         
184         // special handling for calendar proxy principals
185         // they are groups in the user namespace
186         if (in_array($id, array('calendar-proxy-read', 'calendar-proxy-write'))) {
187             $path = $prefix;
188             
189             // set prefix to calendar-proxy-read or calendar-proxy-write
190             $prefix = $id;
191             
192             list(, $id) = \Sabre\DAV\URLUtil::splitPath($path);
193         }
194         
195         switch ($prefix) {
196             case 'calendar-proxy-read':
197                 return array();
198                 
199                 break;
200                 
201             case 'calendar-proxy-write':
202                 if ($id === self::SHARED) {
203                     // check if account has the right to run the calendar at all
204                     if (!Tinebase_Acl_Roles::getInstance()->hasRight('Calendar', Tinebase_Core::getUser(), Tinebase_Acl_Rights::RUN)) {
205                         return array();
206                     }
207                     
208                     // collect all users which have access to any of the calendars of this user
209                     $sharedContainerSync = Tinebase_Container::getInstance()->getSharedContainer(Tinebase_Core::getUser(), 'Calendar_Model_Event', Tinebase_Model_Grants::GRANT_SYNC);
210                     
211                     if ($sharedContainerSync->count() > 0) {
212                         $sharedContainerRead = Tinebase_Container::getInstance()->getSharedContainer(Tinebase_Core::getUser(), 'Calendar_Model_Event', Tinebase_Model_Grants::GRANT_READ);
213                         
214                         $sharedContainerIds = array_intersect($sharedContainerSync->getArrayOfIds(), $sharedContainerRead->getArrayOfIds());
215                         
216                         $result = $this->_containerGrantsToPrincipals($sharedContainerSync->filter('id', $sharedContainerIds));
217                     }
218                     
219                 } else {
220                     $filter = new Addressbook_Model_ContactFilter(array(
221                         array(
222                             'field'     => 'type',
223                             'operator'  => 'equals',
224                             'value'     => Addressbook_Model_Contact::CONTACTTYPE_USER
225                         ),
226                         array(
227                             'field'     => 'id',
228                             'operator'  => 'equals',
229                             'value'     => $id
230                         ),
231                     ));
232                     
233                     $contact = Addressbook_Controller_Contact::getInstance()->search($filter)->getFirstRecord();
234                     
235                     if (!$contact instanceof Addressbook_Model_Contact || !$contact->account_id) {
236                         return array();
237                     }
238                     
239                     // check if account has the right to run the calendar at all
240                     if (!Tinebase_Acl_Roles::getInstance()->hasRight('Calendar', $contact->account_id, Tinebase_Acl_Rights::RUN)) {
241                         return array();
242                     }
243                     
244                     // collect all users which have access to any of the calendars of this user
245                     $personalContainerSync = Tinebase_Container::getInstance()->getPersonalContainer(Tinebase_Core::getUser(), 'Calendar_Model_Event', $contact->account_id, Tinebase_Model_Grants::GRANT_SYNC);
246                     
247                     if ($personalContainerSync->count() > 0) {
248                         $personalContainerRead = Tinebase_Container::getInstance()->getPersonalContainer(Tinebase_Core::getUser(), 'Calendar_Model_Event', $contact->account_id, Tinebase_Model_Grants::GRANT_READ);
249                         
250                         $personalContainerIds = array_intersect($personalContainerSync->getArrayOfIds(), $personalContainerRead->getArrayOfIds());
251                         
252                         $result = $this->_containerGrantsToPrincipals($personalContainerSync->filter('id', $personalContainerIds));
253                     }
254                 }
255                 
256                 break;
257                 
258             case self::PREFIX_GROUPS:
259                 $filter = new Addressbook_Model_ListFilter(array(
260                     array(
261                         'field'     => 'type',
262                         'operator'  => 'equals',
263                         'value'     => Addressbook_Model_List::LISTTYPE_GROUP
264                     ),
265                     array(
266                         'field'     => 'id',
267                         'operator'  => 'equals',
268                         'value'     => $id
269                     ),
270                 ));
271                 
272                 $list = Addressbook_Controller_List::getInstance()->search($filter)->getFirstRecord();
273                 
274                 if (!$list) {
275                     return array();
276                 }
277                 
278                 foreach ($list->members as $member) {
279                     $result[] = self::PREFIX_USERS . '/' . $member;
280                 }
281                 
282                 break;
283         }
284         
285         return $result;
286     }
287     
288     /**
289      * (non-PHPdoc)
290      * @see Sabre\DAVACL\IPrincipalBackend::getGroupMembership()
291      */
292     public function getGroupMembership($principal) 
293     {
294         $result = array();
295         
296         list($prefix, $contactId) = \Sabre\DAV\URLUtil::splitPath($principal);
297         
298         switch ($prefix) {
299             case self::PREFIX_GROUPS:
300                 // @TODO implement?
301                 break;
302         
303             case self::PREFIX_USERS:
304                 if ($contactId !== self::SHARED) {
305                     $user = Tinebase_User::getInstance()->getUserByProperty('contactId', $contactId);
306                     
307                     $groupIds = Tinebase_Group::getInstance()->getGroupMemberships($user);
308                     $groups   = Tinebase_Group::getInstance()->getMultiple($groupIds);
309                     
310                     foreach ($groups as $group) {
311                         if ($group->list_id && $group->visibility == Tinebase_Model_Group::VISIBILITY_DISPLAYED) {
312                             $result[] = self::PREFIX_GROUPS . '/' . $group->list_id;
313                         }
314                     }
315                     
316                     // return user only, if the containers have the sync AND read grant set
317                     $otherUsersSync = Tinebase_Container::getInstance()->getOtherUsers($user, 'Calendar', Tinebase_Model_Grants::GRANT_SYNC);
318                     
319                     if ($otherUsersSync->count() > 0) {
320                         $otherUsersRead = Tinebase_Container::getInstance()->getOtherUsers($user, 'Calendar', Tinebase_Model_Grants::GRANT_READ);
321                         
322                         $otherUsersIds = array_intersect($otherUsersSync->getArrayOfIds(), $otherUsersRead->getArrayOfIds());
323                         
324                         foreach ($otherUsersIds as $userId) {
325                             if ($otherUsersSync->getById($userId)->contact_id && $otherUsersSync->getById($userId)->visibility == Tinebase_Model_User::VISIBILITY_DISPLAYED) {
326                                 $result[] = self::PREFIX_USERS . '/' . $otherUsersSync->getById($userId)->contact_id . '/calendar-proxy-write';
327                             }
328                         }
329                     }
330                     
331                     // return user only, if the containers have the sync AND read grant set
332                     $sharedContainersSync = Tinebase_Container::getInstance()->getSharedContainer($user, 'Calendar', Tinebase_Model_Grants::GRANT_SYNC);
333                     
334                     if ($sharedContainersSync->count() > 0) {
335                         $sharedContainersRead = Tinebase_Container::getInstance()->getSharedContainer($user, 'Calendar', Tinebase_Model_Grants::GRANT_READ);
336                         
337                         $sharedContainerIds = array_intersect($sharedContainersSync->getArrayOfIds(), $sharedContainersRead->getArrayOfIds());
338                         
339                         if (count($sharedContainerIds) > 0) {
340                             $result[] = self::PREFIX_USERS . '/' . self::SHARED . '/calendar-proxy-write';
341                         }
342                     }
343                 }
344                 
345                 break;
346         }
347         
348         return $result;
349     }
350     
351     public function setGroupMemberSet($principal, array $members) 
352     {
353         // do nothing
354     }
355     
356     public function updatePrincipal($path, $mutations)
357     {
358         return false;
359     }
360     
361     /**
362      * This method is used to search for principals matching a set of
363      * properties.
364      *
365      * This search is specifically used by RFC3744's principal-property-search
366      * REPORT. You should at least allow searching on
367      * http://sabredav.org/ns}email-address.
368      *
369      * The actual search should be a unicode-non-case-sensitive search. The
370      * keys in searchProperties are the WebDAV property names, while the values
371      * are the property values to search on.
372      *
373      * If multiple properties are being searched on, the search should be
374      * AND'ed.
375      *
376      * This method should simply return an array with full principal uri's.
377      *
378      * If somebody attempted to search on a property the backend does not
379      * support, you should simply return 0 results.
380      *
381      * You can also just return 0 results if you choose to not support
382      * searching at all, but keep in mind that this may stop certain features
383      * from working.
384      *
385      * @param string $prefixPath
386      * @param array $searchProperties
387      * @todo implement handling for shared pseudo user
388      * @return array
389      */
390     public function searchPrincipals($prefixPath, array $searchProperties)
391     {
392         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
393             __METHOD__ . '::' . __LINE__ . ' path: ' . $prefixPath . ' properties: ' . print_r($searchProperties, true));
394         
395         $principalUris = array();
396         
397         switch ($prefixPath) {
398             case self::PREFIX_GROUPS:
399                 $filter = new Addressbook_Model_ListFilter(array(
400                     array(
401                         'field'     => 'type',
402                         'operator'  => 'equals',
403                         'value'     => Addressbook_Model_List::LISTTYPE_GROUP
404                     )
405                 ));
406                 
407                 if (!empty($searchProperties['{http://calendarserver.org/ns/}search-token'])) {
408                     $filter->addFilter($filter->createFilter(array(
409                         'field'     => 'query',
410                         'operator'  => 'contains',
411                         'value'     => $searchProperties['{http://calendarserver.org/ns/}search-token']
412                     )));
413                 }
414                 
415                 if (!empty($searchProperties['{DAV:}displayname'])) {
416                     $filter->addFilter($filter->createFilter(array(
417                         'field'     => 'name',
418                         'operator'  => 'contains',
419                         'value'     => $searchProperties['{DAV:}displayname']
420                     )));
421                 }
422                 
423                 $result = Addressbook_Controller_List::getInstance()->search($filter, null, false, true);
424                 
425                 foreach ($result as $listId) {
426                     $principalUris[] = $prefixPath . '/' . $listId;
427                 }
428                 
429                 break;
430                 
431             case self::PREFIX_USERS:
432                 $filter = new Addressbook_Model_ContactFilter(array(
433                     array(
434                         'field'     => 'type',
435                         'operator'  => 'equals',
436                         'value'     => Addressbook_Model_Contact::CONTACTTYPE_USER
437                     )
438                 ));
439                 
440                 if (!empty($searchProperties['{http://calendarserver.org/ns/}search-token'])) {
441                     $filter->addFilter($filter->createFilter(array(
442                         'field'     => 'query',
443                         'operator'  => 'contains',
444                         'value'     => $searchProperties['{http://calendarserver.org/ns/}search-token']
445                     )));
446                 }
447                 
448                 if (!empty($searchProperties['{http://sabredav.org/ns}email-address'])) {
449                     $filter->addFilter($filter->createFilter(array(
450                         'field'     => 'email_query',
451                         'operator'  => 'contains',
452                         'value'     => $searchProperties['{http://sabredav.org/ns}email-address']
453                     )));
454                 }
455                 
456                 if (!empty($searchProperties['{DAV:}displayname'])) {
457                     $filter->addFilter($filter->createFilter(array(
458                         'field'     => 'query',
459                         'operator'  => 'contains',
460                         'value'     => $searchProperties['{DAV:}displayname']
461                     )));
462                 }
463                 
464                 if (!empty($searchProperties['{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}first-name'])) {
465                     $filter->addFilter($filter->createFilter(array(
466                         'field'     => 'n_given',
467                         'operator'  => 'contains',
468                         'value'     => $searchProperties['{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}first-name']
469                     )));
470                 }
471                 
472                 if (!empty($searchProperties['{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}last-name'])) {
473                     $filter->addFilter($filter->createFilter(array(
474                         'field'     => 'n_family',
475                         'operator'  => 'contains',
476                         'value'     => $searchProperties['{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}last-name']
477                     )));
478                 }
479                 
480                 #if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
481                 #    __METHOD__ . '::' . __LINE__ . ' path: ' . $prefixPath . ' properties: ' . print_r($filter->toArray(), true));
482                 
483                 $result = Addressbook_Controller_Contact::getInstance()->search($filter, null, false, true);
484                 
485                 foreach ($result as $contactId) {
486                     $principalUris[] = $prefixPath . '/' . $contactId;
487                 }
488                 
489                 break;
490         }
491         
492         return $principalUris;
493     }
494     
495     /**
496      * return shared pseudo principal (principal for the shared containers) 
497      */
498     protected function _contactForSharedPrincipal()
499     {
500         $translate = Tinebase_Translation::getTranslation('Tinebase');
501         
502         $principal = array(
503             'uri'                     => self::PREFIX_USERS . '/' . self::SHARED,
504             '{DAV:}displayname'       => $translate->_('Shared folders'),
505             
506             '{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}calendar-user-type'  => 'INDIVIDUAL',
507             
508             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}record-type' => 'users',
509             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}first-name'  => 'Folders',
510             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}last-name'   => 'Shared'
511         );
512         
513         return $principal;
514         
515     }
516     
517     /**
518      * convert contact model to principal array
519      * 
520      * @param Addressbook_Model_Contact $contact
521      * @return array
522      */
523     protected function _contactToPrincipal(Addressbook_Model_Contact $contact)
524     {
525         $principal = array(
526             'uri'                     => self::PREFIX_USERS . '/' . $contact->getId(),
527             '{DAV:}displayname'       => $contact->n_fileas,
528             '{DAV:}alternate-URI-set' => array('urn:uuid:' . $contact->getId()),
529             
530             '{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}calendar-user-type'  => 'INDIVIDUAL',
531             
532             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}record-type' => 'users',
533             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}first-name'  => $contact->n_given,
534             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}last-name'   => $contact->n_family
535         );
536         
537         if (!empty(Tinebase_Core::getUser()->accountEmailAddress)) {
538             $principal['{http://sabredav.org/ns}email-address'] = $contact->email;
539         }
540         
541         return $principal;
542     }
543     
544     protected function _containerGrantsToPrincipals(Tinebase_Record_RecordSet $containers)
545     {
546         $result = array();
547         
548         foreach ($containers as $container) {
549             $grants = Tinebase_Container::getInstance()->getGrantsOfContainer($container);
550             
551             foreach ($grants as $grant) {
552                 switch ($grant->account_type) {
553                     case 'group':
554                         $group = Tinebase_Group::getInstance()->getGroupById($grant->account_id);
555                         if ($group->list_id) {
556                             $result[] = self::PREFIX_GROUPS . '/' . $group->list_id;
557                         }
558                         break;
559                         
560                     case 'user':
561                         // skip if grant belongs to the owner of the calendar
562                         if ($contact->account_id == $grant->account_id) {
563                             continue;
564                         }
565                         $user = Tinebase_User::getInstance()->getUserById($grant->account_id);
566                         if ($user->contact_id) {
567                             $result[] = self::PREFIX_USERS . '/' . $user->contact_id;
568                         }
569                         
570                         break;
571                 }
572             }
573         }
574         
575         // users and groups can be duplicate
576         $result = array_unique($result);
577         
578         return $result;
579     }
580     
581     /**
582      * convert list model to principal array
583      * 
584      * @param Addressbook_Model_List $list
585      * @return array
586      */
587     protected function _listToPrincipal(Addressbook_Model_List $list)
588     {
589         $principal = array(
590             'uri'                     => self::PREFIX_GROUPS . '/' . $list->getId(),
591             '{DAV:}displayname'       => $list->name . ' (Group)',
592             '{DAV:}alternate-URI-set' => array('urn:uuid:' . $list->getId()),
593             
594             '{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}calendar-user-type'  => 'GROUP',
595             
596             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}record-type' => 'groups',
597             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}first-name'  => 'Group',
598             '{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}last-name'   => $list->name,
599         );
600         
601         return $principal;
602     }
603 }