]> git.mxchange.org Git - friendica-addons.git/blob - dav/SabreDAV/lib/Sabre/DAVACL/Plugin.php
Revert to stable version 3.5.4
[friendica-addons.git] / dav / SabreDAV / lib / Sabre / DAVACL / Plugin.php
1 <?php
2
3 /**
4  * SabreDAV ACL Plugin
5  *
6  * This plugin provides functionality to enforce ACL permissions.
7  * ACL is defined in RFC3744.
8  *
9  * In addition it also provides support for the {DAV:}current-user-principal
10  * property, defined in RFC5397 and the {DAV:}expand-property report, as
11  * defined in RFC3253.
12  *
13  * @package Sabre
14  * @subpackage DAVACL
15  * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
16  * @author Evert Pot (http://www.rooftopsolutions.nl/)
17  * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
18  */
19 class Sabre_DAVACL_Plugin extends Sabre_DAV_ServerPlugin {
20
21     /**
22      * Recursion constants
23      *
24      * This only checks the base node
25      */
26     const R_PARENT = 1;
27
28     /**
29      * Recursion constants
30      *
31      * This checks every node in the tree
32      */
33     const R_RECURSIVE = 2;
34
35     /**
36      * Recursion constants
37      *
38      * This checks every parentnode in the tree, but not leaf-nodes.
39      */
40     const R_RECURSIVEPARENTS = 3;
41
42     /**
43      * Reference to server object.
44      *
45      * @var Sabre_DAV_Server
46      */
47     protected $server;
48
49     /**
50      * List of urls containing principal collections.
51      * Modify this if your principals are located elsewhere.
52      *
53      * @var array
54      */
55     public $principalCollectionSet = array(
56         'principals',
57     );
58
59     /**
60      * By default ACL is only enforced for nodes that have ACL support (the
61      * ones that implement Sabre_DAVACL_IACL). For any other node, access is
62      * always granted.
63      *
64      * To override this behaviour you can turn this setting off. This is useful
65      * if you plan to fully support ACL in the entire tree.
66      *
67      * @var bool
68      */
69     public $allowAccessToNodesWithoutACL = true;
70
71     /**
72      * By default nodes that are inaccessible by the user, can still be seen
73      * in directory listings (PROPFIND on parent with Depth: 1)
74      *
75      * In certain cases it's desirable to hide inaccessible nodes. Setting this
76      * to true will cause these nodes to be hidden from directory listings.
77      *
78      * @var bool
79      */
80     public $hideNodesFromListings = false;
81
82     /**
83      * This string is prepended to the username of the currently logged in
84      * user. This allows the plugin to determine the principal path based on
85      * the username.
86      *
87      * @var string
88      */
89     public $defaultUsernamePath = 'principals';
90
91     /**
92      * This list of properties are the properties a client can search on using
93      * the {DAV:}principal-property-search report.
94      *
95      * The keys are the property names, values are descriptions.
96      *
97      * @var array
98      */
99     public $principalSearchPropertySet = array(
100         '{DAV:}displayname' => 'Display name',
101         '{http://sabredav.org/ns}email-address' => 'Email address',
102     );
103
104     /**
105      * Any principal uri's added here, will automatically be added to the list
106      * of ACL's. They will effectively receive {DAV:}all privileges, as a
107      * protected privilege.
108      *
109      * @var array
110      */
111     public $adminPrincipals = array();
112
113     /**
114      * Returns a list of features added by this plugin.
115      *
116      * This list is used in the response of a HTTP OPTIONS request.
117      *
118      * @return array
119      */
120     public function getFeatures() {
121
122         return array('access-control');
123
124     }
125
126     /**
127      * Returns a list of available methods for a given url
128      *
129      * @param string $uri
130      * @return array
131      */
132     public function getMethods($uri) {
133
134         return array('ACL');
135
136     }
137
138     /**
139      * Returns a plugin name.
140      *
141      * Using this name other plugins will be able to access other plugins
142      * using Sabre_DAV_Server::getPlugin
143      *
144      * @return string
145      */
146     public function getPluginName() {
147
148         return 'acl';
149
150     }
151
152     /**
153      * Returns a list of reports this plugin supports.
154      *
155      * This will be used in the {DAV:}supported-report-set property.
156      * Note that you still need to subscribe to the 'report' event to actually
157      * implement them
158      *
159      * @param string $uri
160      * @return array
161      */
162     public function getSupportedReportSet($uri) {
163
164         return array(
165             '{DAV:}expand-property',
166             '{DAV:}principal-property-search',
167             '{DAV:}principal-search-property-set',
168         );
169
170     }
171
172
173     /**
174      * Checks if the current user has the specified privilege(s).
175      *
176      * You can specify a single privilege, or a list of privileges.
177      * This method will throw an exception if the privilege is not available
178      * and return true otherwise.
179      *
180      * @param string $uri
181      * @param array|string $privileges
182      * @param int $recursion
183      * @param bool $throwExceptions if set to false, this method won't through exceptions.
184      * @throws Sabre_DAVACL_Exception_NeedPrivileges
185      * @return bool
186      */
187     public function checkPrivileges($uri, $privileges, $recursion = self::R_PARENT, $throwExceptions = true) {
188
189         if (!is_array($privileges)) $privileges = array($privileges);
190
191         $acl = $this->getCurrentUserPrivilegeSet($uri);
192
193         if (is_null($acl)) {
194             if ($this->allowAccessToNodesWithoutACL) {
195                 return true;
196             } else {
197                 if ($throwExceptions)
198                     throw new Sabre_DAVACL_Exception_NeedPrivileges($uri,$privileges);
199                 else
200                     return false;
201
202             }
203         }
204
205         $failed = array();
206         foreach($privileges as $priv) {
207
208             if (!in_array($priv, $acl)) {
209                 $failed[] = $priv;
210             }
211
212         }
213
214         if ($failed) {
215             if ($throwExceptions)
216                 throw new Sabre_DAVACL_Exception_NeedPrivileges($uri,$failed);
217             else
218                 return false;
219         }
220         return true;
221
222     }
223
224     /**
225      * Returns the standard users' principal.
226      *
227      * This is one authorative principal url for the current user.
228      * This method will return null if the user wasn't logged in.
229      *
230      * @return string|null
231      */
232     public function getCurrentUserPrincipal() {
233
234         $authPlugin = $this->server->getPlugin('auth');
235         if (is_null($authPlugin)) return null;
236         /** @var $authPlugin Sabre_DAV_Auth_Plugin */
237
238         $userName = $authPlugin->getCurrentUser();
239         if (!$userName) return null;
240
241         return $this->defaultUsernamePath . '/' . $userName;
242
243     }
244
245     /**
246      * Returns a list of principals that's associated to the current
247      * user, either directly or through group membership.
248      *
249      * @return array
250      */
251     public function getCurrentUserPrincipals() {
252
253         $currentUser = $this->getCurrentUserPrincipal();
254
255         if (is_null($currentUser)) return array();
256
257         $check = array($currentUser);
258         $principals = array($currentUser);
259
260         while(count($check)) {
261
262             $principal = array_shift($check);
263
264             $node = $this->server->tree->getNodeForPath($principal);
265             if ($node instanceof Sabre_DAVACL_IPrincipal) {
266                 foreach($node->getGroupMembership() as $groupMember) {
267
268                     if (!in_array($groupMember, $principals)) {
269
270                         $check[] = $groupMember;
271                         $principals[] = $groupMember;
272
273                     }
274
275                 }
276
277             }
278
279         }
280
281         return $principals;
282
283     }
284
285     /**
286      * Returns the supported privilege structure for this ACL plugin.
287      *
288      * See RFC3744 for more details. Currently we default on a simple,
289      * standard structure.
290      *
291      * You can either get the list of privileges by a uri (path) or by
292      * specifying a Node.
293      *
294      * @param string|Sabre_DAV_INode $node
295      * @return array
296      */
297     public function getSupportedPrivilegeSet($node) {
298
299         if (is_string($node)) {
300             $node = $this->server->tree->getNodeForPath($node);
301         }
302
303         if ($node instanceof Sabre_DAVACL_IACL) {
304             $result = $node->getSupportedPrivilegeSet();
305
306             if ($result)
307                 return $result;
308         }
309
310         return self::getDefaultSupportedPrivilegeSet();
311
312     }
313
314     /**
315      * Returns a fairly standard set of privileges, which may be useful for
316      * other systems to use as a basis.
317      *
318      * @return array
319      */
320     static function getDefaultSupportedPrivilegeSet() {
321
322         return array(
323             'privilege'  => '{DAV:}all',
324             'abstract'   => true,
325             'aggregates' => array(
326                 array(
327                     'privilege'  => '{DAV:}read',
328                     'aggregates' => array(
329                         array(
330                             'privilege' => '{DAV:}read-acl',
331                             'abstract'  => true,
332                         ),
333                         array(
334                             'privilege' => '{DAV:}read-current-user-privilege-set',
335                             'abstract'  => true,
336                         ),
337                     ),
338                 ), // {DAV:}read
339                 array(
340                     'privilege'  => '{DAV:}write',
341                     'aggregates' => array(
342                         array(
343                             'privilege' => '{DAV:}write-acl',
344                             'abstract'  => true,
345                         ),
346                         array(
347                             'privilege' => '{DAV:}write-properties',
348                             'abstract'  => true,
349                         ),
350                         array(
351                             'privilege' => '{DAV:}write-content',
352                             'abstract'  => true,
353                         ),
354                         array(
355                             'privilege' => '{DAV:}bind',
356                             'abstract'  => true,
357                         ),
358                         array(
359                             'privilege' => '{DAV:}unbind',
360                             'abstract'  => true,
361                         ),
362                         array(
363                             'privilege' => '{DAV:}unlock',
364                             'abstract'  => true,
365                         ),
366                     ),
367                 ), // {DAV:}write
368             ),
369         ); // {DAV:}all
370
371     }
372
373     /**
374      * Returns the supported privilege set as a flat list
375      *
376      * This is much easier to parse.
377      *
378      * The returned list will be index by privilege name.
379      * The value is a struct containing the following properties:
380      *   - aggregates
381      *   - abstract
382      *   - concrete
383      *
384      * @param string|Sabre_DAV_INode $node
385      * @return array
386      */
387     final public function getFlatPrivilegeSet($node) {
388
389         $privs = $this->getSupportedPrivilegeSet($node);
390
391         $flat = array();
392         $this->getFPSTraverse($privs, null, $flat);
393
394         return $flat;
395
396     }
397
398     /**
399      * Traverses the privilege set tree for reordering
400      *
401      * This function is solely used by getFlatPrivilegeSet, and would have been
402      * a closure if it wasn't for the fact I need to support PHP 5.2.
403      *
404      * @param array $priv
405      * @param $concrete
406      * @param array $flat
407      * @return void
408      */
409     final private function getFPSTraverse($priv, $concrete, &$flat) {
410
411         $myPriv = array(
412             'privilege' => $priv['privilege'],
413             'abstract' => isset($priv['abstract']) && $priv['abstract'],
414             'aggregates' => array(),
415             'concrete' => isset($priv['abstract']) && $priv['abstract']?$concrete:$priv['privilege'],
416         );
417
418         if (isset($priv['aggregates']))
419             foreach($priv['aggregates'] as $subPriv) $myPriv['aggregates'][] = $subPriv['privilege'];
420
421         $flat[$priv['privilege']] = $myPriv;
422
423         if (isset($priv['aggregates'])) {
424
425             foreach($priv['aggregates'] as $subPriv) {
426
427                 $this->getFPSTraverse($subPriv, $myPriv['concrete'], $flat);
428
429             }
430
431         }
432
433     }
434
435     /**
436      * Returns the full ACL list.
437      *
438      * Either a uri or a Sabre_DAV_INode may be passed.
439      *
440      * null will be returned if the node doesn't support ACLs.
441      *
442      * @param string|Sabre_DAV_INode $node
443      * @return array
444      */
445     public function getACL($node) {
446
447         if (is_string($node)) {
448             $node = $this->server->tree->getNodeForPath($node);
449         }
450         if (!$node instanceof Sabre_DAVACL_IACL) {
451             return null;
452         }
453         $acl = $node->getACL();
454         foreach($this->adminPrincipals as $adminPrincipal) {
455             $acl[] = array(
456                 'principal' => $adminPrincipal,
457                 'privilege' => '{DAV:}all',
458                 'protected' => true,
459             );
460         }
461         return $acl;
462
463     }
464
465     /**
466      * Returns a list of privileges the current user has
467      * on a particular node.
468      *
469      * Either a uri or a Sabre_DAV_INode may be passed.
470      *
471      * null will be returned if the node doesn't support ACLs.
472      *
473      * @param string|Sabre_DAV_INode $node
474      * @return array
475      */
476     public function getCurrentUserPrivilegeSet($node) {
477
478         if (is_string($node)) {
479             $node = $this->server->tree->getNodeForPath($node);
480         }
481
482         $acl = $this->getACL($node);
483
484         if (is_null($acl)) return null;
485
486         $principals = $this->getCurrentUserPrincipals();
487
488         $collected = array();
489
490         foreach($acl as $ace) {
491
492             $principal = $ace['principal'];
493
494             switch($principal) {
495
496                 case '{DAV:}owner' :
497                     $owner = $node->getOwner();
498                     if ($owner && in_array($owner, $principals)) {
499                         $collected[] = $ace;
500                     }
501                     break;
502
503
504                 // 'all' matches for every user
505                 case '{DAV:}all' :
506
507                 // 'authenticated' matched for every user that's logged in.
508                 // Since it's not possible to use ACL while not being logged
509                 // in, this is also always true.
510                 case '{DAV:}authenticated' :
511                     $collected[] = $ace;
512                     break;
513
514                 // 'unauthenticated' can never occur either, so we simply
515                 // ignore these.
516                 case '{DAV:}unauthenticated' :
517                     break;
518
519                 default :
520                     if (in_array($ace['principal'], $principals)) {
521                         $collected[] = $ace;
522                     }
523                     break;
524
525             }
526
527
528
529         }
530
531         // Now we deduct all aggregated privileges.
532         $flat = $this->getFlatPrivilegeSet($node);
533
534         $collected2 = array();
535         while(count($collected)) {
536
537             $current = array_pop($collected);
538             $collected2[] = $current['privilege'];
539
540             foreach($flat[$current['privilege']]['aggregates'] as $subPriv) {
541                 $collected2[] = $subPriv;
542                 $collected[] = $flat[$subPriv];
543             }
544
545         }
546
547         return array_values(array_unique($collected2));
548
549     }
550
551     /**
552      * Principal property search
553      *
554      * This method can search for principals matching certain values in
555      * properties.
556      *
557      * This method will return a list of properties for the matched properties.
558      *
559      * @param array $searchProperties    The properties to search on. This is a
560      *                                   key-value list. The keys are property
561      *                                   names, and the values the strings to
562      *                                   match them on.
563      * @param array $requestedProperties This is the list of properties to
564      *                                   return for every match.
565      * @param string $collectionUri      The principal collection to search on.
566      *                                   If this is ommitted, the standard
567      *                                   principal collection-set will be used.
568      * @return array     This method returns an array structure similar to
569      *                  Sabre_DAV_Server::getPropertiesForPath. Returned
570      *                  properties are index by a HTTP status code.
571      *
572      */
573     public function principalSearch(array $searchProperties, array $requestedProperties, $collectionUri = null) {
574
575         if (!is_null($collectionUri)) {
576             $uris = array($collectionUri);
577         } else {
578             $uris = $this->principalCollectionSet;
579         }
580
581         $lookupResults = array();
582         foreach($uris as $uri) {
583
584             $principalCollection = $this->server->tree->getNodeForPath($uri);
585             if (!$principalCollection instanceof Sabre_DAVACL_AbstractPrincipalCollection) {
586                 // Not a principal collection, we're simply going to ignore
587                 // this.
588                 continue;
589             }
590
591             $results = $principalCollection->searchPrincipals($searchProperties);
592             foreach($results as $result) {
593                 $lookupResults[] = rtrim($uri,'/') . '/' . $result;
594             }
595
596         }
597
598         $matches = array();
599
600         foreach($lookupResults as $lookupResult) {
601
602             list($matches[]) = $this->server->getPropertiesForPath($lookupResult, $requestedProperties, 0);
603
604         }
605
606         return $matches;
607
608     }
609
610     /**
611      * Sets up the plugin
612      *
613      * This method is automatically called by the server class.
614      *
615      * @param Sabre_DAV_Server $server
616      * @return void
617      */
618     public function initialize(Sabre_DAV_Server $server) {
619
620         $this->server = $server;
621         $server->subscribeEvent('beforeGetProperties',array($this,'beforeGetProperties'));
622
623         $server->subscribeEvent('beforeMethod', array($this,'beforeMethod'),20);
624         $server->subscribeEvent('beforeBind', array($this,'beforeBind'),20);
625         $server->subscribeEvent('beforeUnbind', array($this,'beforeUnbind'),20);
626         $server->subscribeEvent('updateProperties',array($this,'updateProperties'));
627         $server->subscribeEvent('beforeUnlock', array($this,'beforeUnlock'),20);
628         $server->subscribeEvent('report',array($this,'report'));
629         $server->subscribeEvent('unknownMethod', array($this, 'unknownMethod'));
630
631         array_push($server->protectedProperties,
632             '{DAV:}alternate-URI-set',
633             '{DAV:}principal-URL',
634             '{DAV:}group-membership',
635             '{DAV:}principal-collection-set',
636             '{DAV:}current-user-principal',
637             '{DAV:}supported-privilege-set',
638             '{DAV:}current-user-privilege-set',
639             '{DAV:}acl',
640             '{DAV:}acl-restrictions',
641             '{DAV:}inherited-acl-set',
642             '{DAV:}owner',
643             '{DAV:}group'
644         );
645
646         // Automatically mapping nodes implementing IPrincipal to the
647         // {DAV:}principal resourcetype.
648         $server->resourceTypeMapping['Sabre_DAVACL_IPrincipal'] = '{DAV:}principal';
649
650         // Mapping the group-member-set property to the HrefList property
651         // class.
652         $server->propertyMap['{DAV:}group-member-set'] = 'Sabre_DAV_Property_HrefList';
653
654     }
655
656
657     /* {{{ Event handlers */
658
659     /**
660      * Triggered before any method is handled
661      *
662      * @param string $method
663      * @param string $uri
664      * @return void
665      */
666     public function beforeMethod($method, $uri) {
667
668         $exists = $this->server->tree->nodeExists($uri);
669
670         // If the node doesn't exists, none of these checks apply
671         if (!$exists) return;
672
673         switch($method) {
674
675             case 'GET' :
676             case 'HEAD' :
677             case 'OPTIONS' :
678                 // For these 3 we only need to know if the node is readable.
679                 $this->checkPrivileges($uri,'{DAV:}read');
680                 break;
681
682             case 'PUT' :
683             case 'LOCK' :
684             case 'UNLOCK' :
685                 // This method requires the write-content priv if the node
686                 // already exists, and bind on the parent if the node is being
687                 // created.
688                 // The bind privilege is handled in the beforeBind event.
689                 $this->checkPrivileges($uri,'{DAV:}write-content');
690                 break;
691
692
693             case 'PROPPATCH' :
694                 $this->checkPrivileges($uri,'{DAV:}write-properties');
695                 break;
696
697             case 'ACL' :
698                 $this->checkPrivileges($uri,'{DAV:}write-acl');
699                 break;
700
701             case 'COPY' :
702             case 'MOVE' :
703                 // Copy requires read privileges on the entire source tree.
704                 // If the target exists write-content normally needs to be
705                 // checked, however, we're deleting the node beforehand and
706                 // creating a new one after, so this is handled by the
707                 // beforeUnbind event.
708                 //
709                 // The creation of the new node is handled by the beforeBind
710                 // event.
711                 //
712                 // If MOVE is used beforeUnbind will also be used to check if
713                 // the sourcenode can be deleted.
714                 $this->checkPrivileges($uri,'{DAV:}read',self::R_RECURSIVE);
715
716                 break;
717
718         }
719
720     }
721
722     /**
723      * Triggered before a new node is created.
724      *
725      * This allows us to check permissions for any operation that creates a
726      * new node, such as PUT, MKCOL, MKCALENDAR, LOCK, COPY and MOVE.
727      *
728      * @param string $uri
729      * @return void
730      */
731     public function beforeBind($uri) {
732
733         list($parentUri,$nodeName) = Sabre_DAV_URLUtil::splitPath($uri);
734         $this->checkPrivileges($parentUri,'{DAV:}bind');
735
736     }
737
738     /**
739      * Triggered before a node is deleted
740      *
741      * This allows us to check permissions for any operation that will delete
742      * an existing node.
743      *
744      * @param string $uri
745      * @return void
746      */
747     public function beforeUnbind($uri) {
748
749         list($parentUri,$nodeName) = Sabre_DAV_URLUtil::splitPath($uri);
750         $this->checkPrivileges($parentUri,'{DAV:}unbind',self::R_RECURSIVEPARENTS);
751
752     }
753
754     /**
755      * Triggered before a node is unlocked.
756      *
757      * @param string $uri
758      * @param Sabre_DAV_Locks_LockInfo $lock
759      * @TODO: not yet implemented
760      * @return void
761      */
762     public function beforeUnlock($uri, Sabre_DAV_Locks_LockInfo $lock) {
763
764
765     }
766
767     /**
768      * Triggered before properties are looked up in specific nodes.
769      *
770      * @param string $uri
771      * @param Sabre_DAV_INode $node
772      * @param array $requestedProperties
773      * @param array $returnedProperties
774      * @TODO really should be broken into multiple methods, or even a class.
775      * @return bool
776      */
777     public function beforeGetProperties($uri, Sabre_DAV_INode $node, &$requestedProperties, &$returnedProperties) {
778
779         // Checking the read permission
780         if (!$this->checkPrivileges($uri,'{DAV:}read',self::R_PARENT,false)) {
781
782             // User is not allowed to read properties
783             if ($this->hideNodesFromListings) {
784                 return false;
785             }
786
787             // Marking all requested properties as '403'.
788             foreach($requestedProperties as $key=>$requestedProperty) {
789                 unset($requestedProperties[$key]);
790                 $returnedProperties[403][$requestedProperty] = null;
791             }
792             return;
793
794         }
795
796         /* Adding principal properties */
797         if ($node instanceof Sabre_DAVACL_IPrincipal) {
798
799             if (false !== ($index = array_search('{DAV:}alternate-URI-set', $requestedProperties))) {
800
801                 unset($requestedProperties[$index]);
802                 $returnedProperties[200]['{DAV:}alternate-URI-set'] = new Sabre_DAV_Property_HrefList($node->getAlternateUriSet());
803
804             }
805             if (false !== ($index = array_search('{DAV:}principal-URL', $requestedProperties))) {
806
807                 unset($requestedProperties[$index]);
808                 $returnedProperties[200]['{DAV:}principal-URL'] = new Sabre_DAV_Property_Href($node->getPrincipalUrl() . '/');
809
810             }
811             if (false !== ($index = array_search('{DAV:}group-member-set', $requestedProperties))) {
812
813                 unset($requestedProperties[$index]);
814                 $returnedProperties[200]['{DAV:}group-member-set'] = new Sabre_DAV_Property_HrefList($node->getGroupMemberSet());
815
816             }
817             if (false !== ($index = array_search('{DAV:}group-membership', $requestedProperties))) {
818
819                 unset($requestedProperties[$index]);
820                 $returnedProperties[200]['{DAV:}group-membership'] = new Sabre_DAV_Property_HrefList($node->getGroupMembership());
821
822             }
823
824             if (false !== ($index = array_search('{DAV:}displayname', $requestedProperties))) {
825
826                 $returnedProperties[200]['{DAV:}displayname'] = $node->getDisplayName();
827
828             }
829
830         }
831         if (false !== ($index = array_search('{DAV:}principal-collection-set', $requestedProperties))) {
832
833             unset($requestedProperties[$index]);
834             $val = $this->principalCollectionSet;
835             // Ensuring all collections end with a slash
836             foreach($val as $k=>$v) $val[$k] = $v . '/';
837             $returnedProperties[200]['{DAV:}principal-collection-set'] = new Sabre_DAV_Property_HrefList($val);
838
839         }
840         if (false !== ($index = array_search('{DAV:}current-user-principal', $requestedProperties))) {
841
842             unset($requestedProperties[$index]);
843             if ($url = $this->getCurrentUserPrincipal()) {
844                 $returnedProperties[200]['{DAV:}current-user-principal'] = new Sabre_DAVACL_Property_Principal(Sabre_DAVACL_Property_Principal::HREF, $url . '/');
845             } else {
846                 $returnedProperties[200]['{DAV:}current-user-principal'] = new Sabre_DAVACL_Property_Principal(Sabre_DAVACL_Property_Principal::UNAUTHENTICATED);
847             }
848
849         }
850         if (false !== ($index = array_search('{DAV:}supported-privilege-set', $requestedProperties))) {
851
852             unset($requestedProperties[$index]);
853             $returnedProperties[200]['{DAV:}supported-privilege-set'] = new Sabre_DAVACL_Property_SupportedPrivilegeSet($this->getSupportedPrivilegeSet($node));
854
855         }
856         if (false !== ($index = array_search('{DAV:}current-user-privilege-set', $requestedProperties))) {
857
858             if (!$this->checkPrivileges($uri, '{DAV:}read-current-user-privilege-set', self::R_PARENT, false)) {
859                 $returnedProperties[403]['{DAV:}current-user-privilege-set'] = null;
860                 unset($requestedProperties[$index]);
861             } else {
862                 $val = $this->getCurrentUserPrivilegeSet($node);
863                 if (!is_null($val)) {
864                     unset($requestedProperties[$index]);
865                     $returnedProperties[200]['{DAV:}current-user-privilege-set'] = new Sabre_DAVACL_Property_CurrentUserPrivilegeSet($val);
866                 }
867             }
868
869         }
870
871         /* The ACL property contains all the permissions */
872         if (false !== ($index = array_search('{DAV:}acl', $requestedProperties))) {
873
874             if (!$this->checkPrivileges($uri, '{DAV:}read-acl', self::R_PARENT, false)) {
875
876                 unset($requestedProperties[$index]);
877                 $returnedProperties[403]['{DAV:}acl'] = null;
878
879             } else {
880
881                 $acl = $this->getACL($node);
882                 if (!is_null($acl)) {
883                     unset($requestedProperties[$index]);
884                     $returnedProperties[200]['{DAV:}acl'] = new Sabre_DAVACL_Property_Acl($this->getACL($node));
885                 }
886
887             }
888
889         }
890
891         /* The acl-restrictions property contains information on how privileges
892          * must behave.
893          */
894         if (false !== ($index = array_search('{DAV:}acl-restrictions', $requestedProperties))) {
895             unset($requestedProperties[$index]);
896             $returnedProperties[200]['{DAV:}acl-restrictions'] = new Sabre_DAVACL_Property_AclRestrictions();
897         }
898
899     }
900
901     /**
902      * This method intercepts PROPPATCH methods and make sure the
903      * group-member-set is updated correctly.
904      *
905      * @param array $propertyDelta
906      * @param array $result
907      * @param Sabre_DAV_INode $node
908      * @return bool
909      */
910     public function updateProperties(&$propertyDelta, &$result, Sabre_DAV_INode $node) {
911
912         if (!array_key_exists('{DAV:}group-member-set', $propertyDelta))
913             return;
914
915         if (is_null($propertyDelta['{DAV:}group-member-set'])) {
916             $memberSet = array();
917         } elseif ($propertyDelta['{DAV:}group-member-set'] instanceof Sabre_DAV_Property_HrefList) {
918             $memberSet = $propertyDelta['{DAV:}group-member-set']->getHrefs();
919         } else {
920             throw new Sabre_DAV_Exception('The group-member-set property MUST be an instance of Sabre_DAV_Property_HrefList or null');
921         }
922
923         if (!($node instanceof Sabre_DAVACL_IPrincipal)) {
924             $result[403]['{DAV:}group-member-set'] = null;
925             unset($propertyDelta['{DAV:}group-member-set']);
926
927             // Returning false will stop the updateProperties process
928             return false;
929         }
930
931         $node->setGroupMemberSet($memberSet);
932
933         $result[200]['{DAV:}group-member-set'] = null;
934         unset($propertyDelta['{DAV:}group-member-set']);
935
936     }
937
938     /**
939      * This method handles HTTP REPORT requests
940      *
941      * @param string $reportName
942      * @param DOMNode $dom
943      * @return bool
944      */
945     public function report($reportName, $dom) {
946
947         switch($reportName) {
948
949             case '{DAV:}principal-property-search' :
950                 $this->principalPropertySearchReport($dom);
951                 return false;
952             case '{DAV:}principal-search-property-set' :
953                 $this->principalSearchPropertySetReport($dom);
954                 return false;
955             case '{DAV:}expand-property' :
956                 $this->expandPropertyReport($dom);
957                 return false;
958
959         }
960
961     }
962
963     /**
964      * This event is triggered for any HTTP method that is not known by the
965      * webserver.
966      *
967      * @param string $method
968      * @param string $uri
969      * @return bool
970      */
971     public function unknownMethod($method, $uri) {
972
973         if ($method!=='ACL') return;
974
975         $this->httpACL($uri);
976         return false;
977
978     }
979
980     /**
981      * This method is responsible for handling the 'ACL' event.
982      *
983      * @param string $uri
984      * @return void
985      */
986     public function httpACL($uri) {
987
988         $body = $this->server->httpRequest->getBody(true);
989         $dom = Sabre_DAV_XMLUtil::loadDOMDocument($body);
990
991         $newAcl =
992             Sabre_DAVACL_Property_Acl::unserialize($dom->firstChild)
993             ->getPrivileges();
994
995         // Normalizing urls
996         foreach($newAcl as $k=>$newAce) {
997             $newAcl[$k]['principal'] = $this->server->calculateUri($newAce['principal']);
998         }
999
1000         $node = $this->server->tree->getNodeForPath($uri);
1001
1002         if (!($node instanceof Sabre_DAVACL_IACL)) {
1003             throw new Sabre_DAV_Exception_MethodNotAllowed('This node does not support the ACL method');
1004         }
1005
1006         $oldAcl = $this->getACL($node);
1007
1008         $supportedPrivileges = $this->getFlatPrivilegeSet($node);
1009
1010         /* Checking if protected principals from the existing principal set are
1011            not overwritten. */
1012         foreach($oldAcl as $oldAce) {
1013
1014             if (!isset($oldAce['protected']) || !$oldAce['protected']) continue;
1015
1016             $found = false;
1017             foreach($newAcl as $newAce) {
1018                 if (
1019                     $newAce['privilege'] === $oldAce['privilege'] &&
1020                     $newAce['principal'] === $oldAce['principal'] &&
1021                     $newAce['protected']
1022                 )
1023                 $found = true;
1024             }
1025
1026             if (!$found)
1027                 throw new Sabre_DAVACL_Exception_AceConflict('This resource contained a protected {DAV:}ace, but this privilege did not occur in the ACL request');
1028
1029         }
1030
1031         foreach($newAcl as $newAce) {
1032
1033             // Do we recognize the privilege
1034             if (!isset($supportedPrivileges[$newAce['privilege']])) {
1035                 throw new Sabre_DAVACL_Exception_NotSupportedPrivilege('The privilege you specified (' . $newAce['privilege'] . ') is not recognized by this server');
1036             }
1037
1038             if ($supportedPrivileges[$newAce['privilege']]['abstract']) {
1039                 throw new Sabre_DAVACL_Exception_NoAbstract('The privilege you specified (' . $newAce['privilege'] . ') is an abstract privilege');
1040             }
1041
1042             // Looking up the principal
1043             try {
1044                 $principal = $this->server->tree->getNodeForPath($newAce['principal']);
1045             } catch (Sabre_DAV_Exception_NotFound $e) {
1046                 throw new Sabre_DAVACL_Exception_NotRecognizedPrincipal('The specified principal (' . $newAce['principal'] . ') does not exist');
1047             }
1048             if (!($principal instanceof Sabre_DAVACL_IPrincipal)) {
1049                 throw new Sabre_DAVACL_Exception_NotRecognizedPrincipal('The specified uri (' . $newAce['principal'] . ') is not a principal');
1050             }
1051
1052         }
1053         $node->setACL($newAcl);
1054
1055     }
1056
1057     /* }}} */
1058
1059     /* Reports {{{ */
1060
1061     /**
1062      * The expand-property report is defined in RFC3253 section 3-8.
1063      *
1064      * This report is very similar to a standard PROPFIND. The difference is
1065      * that it has the additional ability to look at properties containing a
1066      * {DAV:}href element, follow that property and grab additional elements
1067      * there.
1068      *
1069      * Other rfc's, such as ACL rely on this report, so it made sense to put
1070      * it in this plugin.
1071      *
1072      * @param DOMElement $dom
1073      * @return void
1074      */
1075     protected function expandPropertyReport($dom) {
1076
1077         $requestedProperties = $this->parseExpandPropertyReportRequest($dom->firstChild->firstChild);
1078         $depth = $this->server->getHTTPDepth(0);
1079         $requestUri = $this->server->getRequestUri();
1080
1081         $result = $this->expandProperties($requestUri,$requestedProperties,$depth);
1082
1083         $dom = new DOMDocument('1.0','utf-8');
1084         $dom->formatOutput = true;
1085         $multiStatus = $dom->createElement('d:multistatus');
1086         $dom->appendChild($multiStatus);
1087
1088         // Adding in default namespaces
1089         foreach($this->server->xmlNamespaces as $namespace=>$prefix) {
1090
1091             $multiStatus->setAttribute('xmlns:' . $prefix,$namespace);
1092
1093         }
1094
1095         foreach($result as $response) {
1096             $response->serialize($this->server, $multiStatus);
1097         }
1098
1099         $xml = $dom->saveXML();
1100         $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
1101         $this->server->httpResponse->sendStatus(207);
1102         $this->server->httpResponse->sendBody($xml);
1103
1104     }
1105
1106     /**
1107      * This method is used by expandPropertyReport to parse
1108      * out the entire HTTP request.
1109      *
1110      * @param DOMElement $node
1111      * @return array
1112      */
1113     protected function parseExpandPropertyReportRequest($node) {
1114
1115         $requestedProperties = array();
1116         do {
1117
1118             if (Sabre_DAV_XMLUtil::toClarkNotation($node)!=='{DAV:}property') continue;
1119
1120             if ($node->firstChild) {
1121
1122                 $children = $this->parseExpandPropertyReportRequest($node->firstChild);
1123
1124             } else {
1125
1126                 $children = array();
1127
1128             }
1129
1130             $namespace = $node->getAttribute('namespace');
1131             if (!$namespace) $namespace = 'DAV:';
1132
1133             $propName = '{'.$namespace.'}' . $node->getAttribute('name');
1134             $requestedProperties[$propName] = $children;
1135
1136         } while ($node = $node->nextSibling);
1137
1138         return $requestedProperties;
1139
1140     }
1141
1142     /**
1143      * This method expands all the properties and returns
1144      * a list with property values
1145      *
1146      * @param array $path
1147      * @param array $requestedProperties the list of required properties
1148      * @param int $depth
1149      * @return array
1150      */
1151     protected function expandProperties($path, array $requestedProperties, $depth) {
1152
1153         $foundProperties = $this->server->getPropertiesForPath($path, array_keys($requestedProperties), $depth);
1154
1155         $result = array();
1156
1157         foreach($foundProperties as $node) {
1158
1159             foreach($requestedProperties as $propertyName=>$childRequestedProperties) {
1160
1161                 // We're only traversing if sub-properties were requested
1162                 if(count($childRequestedProperties)===0) continue;
1163
1164                 // We only have to do the expansion if the property was found
1165                 // and it contains an href element.
1166                 if (!array_key_exists($propertyName,$node[200])) continue;
1167
1168                 if ($node[200][$propertyName] instanceof Sabre_DAV_Property_IHref) {
1169                     $hrefs = array($node[200][$propertyName]->getHref());
1170                 } elseif ($node[200][$propertyName] instanceof Sabre_DAV_Property_HrefList) {
1171                     $hrefs = $node[200][$propertyName]->getHrefs();
1172                 }
1173
1174                 $childProps = array();
1175                 foreach($hrefs as $href) {
1176                     $childProps = array_merge($childProps, $this->expandProperties($href, $childRequestedProperties, 0));
1177                 }
1178                 $node[200][$propertyName] = new Sabre_DAV_Property_ResponseList($childProps);
1179
1180             }
1181             $result[] = new Sabre_DAV_Property_Response($path, $node);
1182
1183         }
1184
1185         return $result;
1186
1187     }
1188
1189     /**
1190      * principalSearchPropertySetReport
1191      *
1192      * This method responsible for handing the
1193      * {DAV:}principal-search-property-set report. This report returns a list
1194      * of properties the client may search on, using the
1195      * {DAV:}principal-property-search report.
1196      *
1197      * @param DOMDocument $dom
1198      * @return void
1199      */
1200     protected function principalSearchPropertySetReport(DOMDocument $dom) {
1201
1202         $httpDepth = $this->server->getHTTPDepth(0);
1203         if ($httpDepth!==0) {
1204             throw new Sabre_DAV_Exception_BadRequest('This report is only defined when Depth: 0');
1205         }
1206
1207         if ($dom->firstChild->hasChildNodes())
1208             throw new Sabre_DAV_Exception_BadRequest('The principal-search-property-set report element is not allowed to have child elements');
1209
1210         $dom = new DOMDocument('1.0','utf-8');
1211         $dom->formatOutput = true;
1212         $root = $dom->createElement('d:principal-search-property-set');
1213         $dom->appendChild($root);
1214         // Adding in default namespaces
1215         foreach($this->server->xmlNamespaces as $namespace=>$prefix) {
1216
1217             $root->setAttribute('xmlns:' . $prefix,$namespace);
1218
1219         }
1220
1221         $nsList = $this->server->xmlNamespaces;
1222
1223         foreach($this->principalSearchPropertySet as $propertyName=>$description) {
1224
1225             $psp = $dom->createElement('d:principal-search-property');
1226             $root->appendChild($psp);
1227
1228             $prop = $dom->createElement('d:prop');
1229             $psp->appendChild($prop);
1230
1231             $propName = null;
1232             preg_match('/^{([^}]*)}(.*)$/',$propertyName,$propName);
1233
1234             $currentProperty = $dom->createElement($nsList[$propName[1]] . ':' . $propName[2]);
1235             $prop->appendChild($currentProperty);
1236
1237             $descriptionElem = $dom->createElement('d:description');
1238             $descriptionElem->setAttribute('xml:lang','en');
1239             $descriptionElem->appendChild($dom->createTextNode($description));
1240             $psp->appendChild($descriptionElem);
1241
1242
1243         }
1244
1245         $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
1246         $this->server->httpResponse->sendStatus(200);
1247         $this->server->httpResponse->sendBody($dom->saveXML());
1248
1249     }
1250
1251     /**
1252      * principalPropertySearchReport
1253      *
1254      * This method is responsible for handing the
1255      * {DAV:}principal-property-search report. This report can be used for
1256      * clients to search for groups of principals, based on the value of one
1257      * or more properties.
1258      *
1259      * @param DOMDocument $dom
1260      * @return void
1261      */
1262     protected function principalPropertySearchReport(DOMDocument $dom) {
1263
1264         list($searchProperties, $requestedProperties, $applyToPrincipalCollectionSet) = $this->parsePrincipalPropertySearchReportRequest($dom);
1265
1266         $uri = null;
1267         if (!$applyToPrincipalCollectionSet) {
1268             $uri = $this->server->getRequestUri();
1269         }
1270         $result = $this->principalSearch($searchProperties, $requestedProperties, $uri);
1271
1272         $xml = $this->server->generateMultiStatus($result);
1273         $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
1274         $this->server->httpResponse->sendStatus(207);
1275         $this->server->httpResponse->sendBody($xml);
1276
1277     }
1278
1279     /**
1280      * parsePrincipalPropertySearchReportRequest
1281      *
1282      * This method parses the request body from a
1283      * {DAV:}principal-property-search report.
1284      *
1285      * This method returns an array with two elements:
1286      *  1. an array with properties to search on, and their values
1287      *  2. a list of propertyvalues that should be returned for the request.
1288      *
1289      * @param DOMDocument $dom
1290      * @return array
1291      */
1292     protected function parsePrincipalPropertySearchReportRequest($dom) {
1293
1294         $httpDepth = $this->server->getHTTPDepth(0);
1295         if ($httpDepth!==0) {
1296             throw new Sabre_DAV_Exception_BadRequest('This report is only defined when Depth: 0');
1297         }
1298
1299         $searchProperties = array();
1300
1301         $applyToPrincipalCollectionSet = false;
1302
1303         // Parsing the search request
1304         foreach($dom->firstChild->childNodes as $searchNode) {
1305
1306             if (Sabre_DAV_XMLUtil::toClarkNotation($searchNode) == '{DAV:}apply-to-principal-collection-set') {
1307                 $applyToPrincipalCollectionSet = true;
1308             }
1309
1310             if (Sabre_DAV_XMLUtil::toClarkNotation($searchNode)!=='{DAV:}property-search')
1311                 continue;
1312
1313             $propertyName = null;
1314             $propertyValue = null;
1315
1316             foreach($searchNode->childNodes as $childNode) {
1317
1318                 switch(Sabre_DAV_XMLUtil::toClarkNotation($childNode)) {
1319
1320                     case '{DAV:}prop' :
1321                         $property = Sabre_DAV_XMLUtil::parseProperties($searchNode);
1322                         reset($property);
1323                         $propertyName = key($property);
1324                         break;
1325
1326                     case '{DAV:}match' :
1327                         $propertyValue = $childNode->textContent;
1328                         break;
1329
1330                 }
1331
1332
1333             }
1334
1335             if (is_null($propertyName) || is_null($propertyValue))
1336                 throw new Sabre_DAV_Exception_BadRequest('Invalid search request. propertyname: ' . $propertyName . '. propertvvalue: ' . $propertyValue);
1337
1338             $searchProperties[$propertyName] = $propertyValue;
1339
1340         }
1341
1342         return array($searchProperties, array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild)), $applyToPrincipalCollectionSet);
1343
1344     }
1345
1346
1347     /* }}} */
1348
1349 }