ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
Plugin.php
Go to the documentation of this file.
1<?php
2
3namespace Sabre\DAVACL;
4
5use Sabre\DAV;
15use Sabre\Uri;
16
31class Plugin extends DAV\ServerPlugin {
32
38 const R_PARENT = 1;
39
45 const R_RECURSIVE = 2;
46
53
59 protected $server;
60
68 'principals',
69 ];
70
80 public $hideNodesFromListings = false;
81
91 '{DAV:}displayname' => 'Display name',
92 '{http://sabredav.org/ns}email-address' => 'Email address',
93 ];
94
102 public $adminPrincipals = [];
103
115
123 function getFeatures() {
124
125 return ['access-control', 'calendarserver-principal-property-search'];
126
127 }
128
135 function getMethods($uri) {
136
137 return ['ACL'];
138
139 }
140
149 function getPluginName() {
150
151 return 'acl';
152
153 }
154
165 function getSupportedReportSet($uri) {
166
167 return [
168 '{DAV:}expand-property',
169 '{DAV:}principal-match',
170 '{DAV:}principal-property-search',
171 '{DAV:}principal-search-property-set',
172 ];
173
174 }
175
176
192 function checkPrivileges($uri, $privileges, $recursion = self::R_PARENT, $throwExceptions = true) {
193
194 if (!is_array($privileges)) $privileges = [$privileges];
195
196 $acl = $this->getCurrentUserPrivilegeSet($uri);
197
198 $failed = [];
199 foreach ($privileges as $priv) {
200
201 if (!in_array($priv, $acl)) {
202 $failed[] = $priv;
203 }
204
205 }
206
207 if ($failed) {
208 if ($this->allowUnauthenticatedAccess && is_null($this->getCurrentUserPrincipal())) {
209 // We are not authenticated. Kicking in the Auth plugin.
210 $authPlugin = $this->server->getPlugin('auth');
211 $reasons = $authPlugin->getLoginFailedReasons();
212 $authPlugin->challenge(
213 $this->server->httpRequest,
214 $this->server->httpResponse
215 );
216 throw new notAuthenticated(implode(', ', $reasons) . '. Login was needed for privilege: ' . implode(', ', $failed) . ' on ' . $uri);
217 }
218 if ($throwExceptions) {
219
220 throw new NeedPrivileges($uri, $failed);
221 } else {
222 return false;
223 }
224 }
225 return true;
226
227 }
228
237 function getCurrentUserPrincipal() {
238
240 $authPlugin = $this->server->getPlugin('auth');
241 if (!$authPlugin) {
242 return null;
243 }
244 return $authPlugin->getCurrentPrincipal();
245
246 }
247
248
256
257 $currentUser = $this->getCurrentUserPrincipal();
258
259 if (is_null($currentUser)) return [];
260
261 return array_merge(
262 [$currentUser],
263 $this->getPrincipalMembership($currentUser)
264 );
265
266 }
267
276 function setDefaultAcl(array $acl) {
277
278 $this->defaultAcl = $acl;
279
280 }
281
289 function getDefaultAcl() {
290
291 return $this->defaultAcl;
292
293 }
294
304 protected $defaultAcl = [
305 [
306 'principal' => '{DAV:}authenticated',
307 'protected' => true,
308 'privilege' => '{DAV:}all',
309 ],
310 ];
311
319
320
327 function getPrincipalMembership($mainPrincipal) {
328
329 // First check our cache
330 if (isset($this->principalMembershipCache[$mainPrincipal])) {
331 return $this->principalMembershipCache[$mainPrincipal];
332 }
333
334 $check = [$mainPrincipal];
335 $principals = [];
336
337 while (count($check)) {
338
339 $principal = array_shift($check);
340
341 $node = $this->server->tree->getNodeForPath($principal);
342 if ($node instanceof IPrincipal) {
343 foreach ($node->getGroupMembership() as $groupMember) {
344
345 if (!in_array($groupMember, $principals)) {
346
347 $check[] = $groupMember;
348 $principals[] = $groupMember;
349
350 }
351
352 }
353
354 }
355
356 }
357
358 // Store the result in the cache
359 $this->principalMembershipCache[$mainPrincipal] = $principals;
360
361 return $principals;
362
363 }
364
386 function principalMatchesPrincipal($checkPrincipal, $currentPrincipal = null) {
387
388 if (is_null($currentPrincipal)) {
389 $currentPrincipal = $this->getCurrentUserPrincipal();
390 }
391 if ($currentPrincipal === $checkPrincipal) {
392 return true;
393 }
394 return in_array(
395 $checkPrincipal,
396 $this->getPrincipalMembership($currentPrincipal)
397 );
398
399 }
400
401
441 function getSupportedPrivilegeSet($node) {
442
443 if (is_string($node)) {
444 $node = $this->server->tree->getNodeForPath($node);
445 }
446
447 $supportedPrivileges = null;
448 if ($node instanceof IACL) {
449 $supportedPrivileges = $node->getSupportedPrivilegeSet();
450 }
451
452 if (is_null($supportedPrivileges)) {
453
454 // Default
455 $supportedPrivileges = [
456 '{DAV:}read' => [
457 'abstract' => false,
458 'aggregates' => [
459 '{DAV:}read-acl' => [
460 'abstract' => false,
461 'aggregates' => [],
462 ],
463 '{DAV:}read-current-user-privilege-set' => [
464 'abstract' => false,
465 'aggregates' => [],
466 ],
467 ],
468 ],
469 '{DAV:}write' => [
470 'abstract' => false,
471 'aggregates' => [
472 '{DAV:}write-properties' => [
473 'abstract' => false,
474 'aggregates' => [],
475 ],
476 '{DAV:}write-content' => [
477 'abstract' => false,
478 'aggregates' => [],
479 ],
480 '{DAV:}unlock' => [
481 'abstract' => false,
482 'aggregates' => [],
483 ],
484 ],
485 ],
486 ];
487 if ($node instanceof DAV\ICollection) {
488 $supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}bind'] = [
489 'abstract' => false,
490 'aggregates' => [],
491 ];
492 $supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}unbind'] = [
493 'abstract' => false,
494 'aggregates' => [],
495 ];
496 }
497 if ($node instanceof IACL) {
498 $supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}write-acl'] = [
499 'abstract' => false,
500 'aggregates' => [],
501 ];
502 }
503
504 }
505
506 $this->server->emit(
507 'getSupportedPrivilegeSet',
508 [$node, &$supportedPrivileges]
509 );
510
511 return $supportedPrivileges;
512
513 }
514
529 final function getFlatPrivilegeSet($node) {
530
531 $privs = [
532 'abstract' => false,
533 'aggregates' => $this->getSupportedPrivilegeSet($node)
534 ];
535
536 $fpsTraverse = null;
537 $fpsTraverse = function($privName, $privInfo, $concrete, &$flat) use (&$fpsTraverse) {
538
539 $myPriv = [
540 'privilege' => $privName,
541 'abstract' => isset($privInfo['abstract']) && $privInfo['abstract'],
542 'aggregates' => [],
543 'concrete' => isset($privInfo['abstract']) && $privInfo['abstract'] ? $concrete : $privName,
544 ];
545
546 if (isset($privInfo['aggregates'])) {
547
548 foreach ($privInfo['aggregates'] as $subPrivName => $subPrivInfo) {
549
550 $myPriv['aggregates'][] = $subPrivName;
551
552 }
553
554 }
555
556 $flat[$privName] = $myPriv;
557
558 if (isset($privInfo['aggregates'])) {
559
560 foreach ($privInfo['aggregates'] as $subPrivName => $subPrivInfo) {
561
562 $fpsTraverse($subPrivName, $subPrivInfo, $myPriv['concrete'], $flat);
563
564 }
565
566 }
567
568 };
569
570 $flat = [];
571 $fpsTraverse('{DAV:}all', $privs, null, $flat);
572
573 return $flat;
574
575 }
576
587 function getAcl($node) {
588
589 if (is_string($node)) {
590 $node = $this->server->tree->getNodeForPath($node);
591 }
592 if (!$node instanceof IACL) {
593 return $this->getDefaultAcl();
594 }
595 $acl = $node->getACL();
596 foreach ($this->adminPrincipals as $adminPrincipal) {
597 $acl[] = [
598 'principal' => $adminPrincipal,
599 'privilege' => '{DAV:}all',
600 'protected' => true,
601 ];
602 }
603 return $acl;
604
605 }
606
619
620 if (is_string($node)) {
621 $node = $this->server->tree->getNodeForPath($node);
622 }
623
624 $acl = $this->getACL($node);
625
626 $collected = [];
627
628 $isAuthenticated = $this->getCurrentUserPrincipal() !== null;
629
630 foreach ($acl as $ace) {
631
632 $principal = $ace['principal'];
633
634 switch ($principal) {
635
636 case '{DAV:}owner' :
637 $owner = $node->getOwner();
638 if ($owner && $this->principalMatchesPrincipal($owner)) {
639 $collected[] = $ace;
640 }
641 break;
642
643
644 // 'all' matches for every user
645 case '{DAV:}all' :
646 $collected[] = $ace;
647 break;
648
649 case '{DAV:}authenticated' :
650 // Authenticated users only
651 if ($isAuthenticated) {
652 $collected[] = $ace;
653 }
654 break;
655
656 case '{DAV:}unauthenticated' :
657 // Unauthenticated users only
658 if (!$isAuthenticated) {
659 $collected[] = $ace;
660 }
661 break;
662
663 default :
664 if ($this->principalMatchesPrincipal($ace['principal'])) {
665 $collected[] = $ace;
666 }
667 break;
668
669 }
670
671
672 }
673
674 // Now we deduct all aggregated privileges.
675 $flat = $this->getFlatPrivilegeSet($node);
676
677 $collected2 = [];
678 while (count($collected)) {
679
680 $current = array_pop($collected);
681 $collected2[] = $current['privilege'];
682
683 if (!isset($flat[$current['privilege']])) {
684 // Ignoring privileges that are not in the supported-privileges list.
685 $this->server->getLogger()->debug('A node has the "' . $current['privilege'] . '" in its ACL list, but this privilege was not reported in the supportedPrivilegeSet list. This will be ignored.');
686 continue;
687 }
688 foreach ($flat[$current['privilege']]['aggregates'] as $subPriv) {
689 $collected2[] = $subPriv;
690 $collected[] = $flat[$subPriv];
691 }
692
693 }
694
695 return array_values(array_unique($collected2));
696
697 }
698
699
708 function getPrincipalByUri($uri) {
709
710 $result = null;
711 $collections = $this->principalCollectionSet;
712 foreach ($collections as $collection) {
713
714 try {
715 $principalCollection = $this->server->tree->getNodeForPath($collection);
716 } catch (NotFound $e) {
717 // Ignore and move on
718 continue;
719 }
720
721 if (!$principalCollection instanceof IPrincipalCollection) {
722 // Not a principal collection, we're simply going to ignore
723 // this.
724 continue;
725 }
726
727 $result = $principalCollection->findByUri($uri);
728 if ($result) {
729 return $result;
730 }
731
732 }
733
734 }
735
759 function principalSearch(array $searchProperties, array $requestedProperties, $collectionUri = null, $test = 'allof') {
760
761 if (!is_null($collectionUri)) {
762 $uris = [$collectionUri];
763 } else {
765 }
766
767 $lookupResults = [];
768 foreach ($uris as $uri) {
769
770 $principalCollection = $this->server->tree->getNodeForPath($uri);
771 if (!$principalCollection instanceof IPrincipalCollection) {
772 // Not a principal collection, we're simply going to ignore
773 // this.
774 continue;
775 }
776
777 $results = $principalCollection->searchPrincipals($searchProperties, $test);
778 foreach ($results as $result) {
779 $lookupResults[] = rtrim($uri, '/') . '/' . $result;
780 }
781
782 }
783
784 $matches = [];
785
786 foreach ($lookupResults as $lookupResult) {
787
788 list($matches[]) = $this->server->getPropertiesForPath($lookupResult, $requestedProperties, 0);
789
790 }
791
792 return $matches;
793
794 }
795
804 function initialize(DAV\Server $server) {
805
806 if ($this->allowUnauthenticatedAccess) {
807 $authPlugin = $server->getPlugin('auth');
808 if (!$authPlugin) {
809 throw new \Exception('The Auth plugin must be loaded before the ACL plugin if you want to allow unauthenticated access.');
810 }
811 $authPlugin->autoRequireLogin = false;
812 }
813
814 $this->server = $server;
815 $server->on('propFind', [$this, 'propFind'], 20);
816 $server->on('beforeMethod', [$this, 'beforeMethod'], 20);
817 $server->on('beforeBind', [$this, 'beforeBind'], 20);
818 $server->on('beforeUnbind', [$this, 'beforeUnbind'], 20);
819 $server->on('propPatch', [$this, 'propPatch']);
820 $server->on('beforeUnlock', [$this, 'beforeUnlock'], 20);
821 $server->on('report', [$this, 'report']);
822 $server->on('method:ACL', [$this, 'httpAcl']);
823 $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
824 $server->on('getPrincipalByUri', function($principal, &$uri) {
825
826 $uri = $this->getPrincipalByUri($principal);
827
828 // Break event chain
829 if ($uri) return false;
830
831 });
832
833 array_push($server->protectedProperties,
834 '{DAV:}alternate-URI-set',
835 '{DAV:}principal-URL',
836 '{DAV:}group-membership',
837 '{DAV:}principal-collection-set',
838 '{DAV:}current-user-principal',
839 '{DAV:}supported-privilege-set',
840 '{DAV:}current-user-privilege-set',
841 '{DAV:}acl',
842 '{DAV:}acl-restrictions',
843 '{DAV:}inherited-acl-set',
844 '{DAV:}owner',
845 '{DAV:}group'
846 );
847
848 // Automatically mapping nodes implementing IPrincipal to the
849 // {DAV:}principal resourcetype.
850 $server->resourceTypeMapping['Sabre\\DAVACL\\IPrincipal'] = '{DAV:}principal';
851
852 // Mapping the group-member-set property to the HrefList property
853 // class.
854 $server->xml->elementMap['{DAV:}group-member-set'] = 'Sabre\\DAV\\Xml\\Property\\Href';
855 $server->xml->elementMap['{DAV:}acl'] = 'Sabre\\DAVACL\\Xml\\Property\\Acl';
856 $server->xml->elementMap['{DAV:}acl-principal-prop-set'] = 'Sabre\\DAVACL\\Xml\\Request\\AclPrincipalPropSetReport';
857 $server->xml->elementMap['{DAV:}expand-property'] = 'Sabre\\DAVACL\\Xml\\Request\\ExpandPropertyReport';
858 $server->xml->elementMap['{DAV:}principal-property-search'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalPropertySearchReport';
859 $server->xml->elementMap['{DAV:}principal-search-property-set'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalSearchPropertySetReport';
860 $server->xml->elementMap['{DAV:}principal-match'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalMatchReport';
861
862 }
863
864 /* {{{ Event handlers */
865
874
875 $method = $request->getMethod();
876 $path = $request->getPath();
877
878 $exists = $this->server->tree->nodeExists($path);
879
880 // If the node doesn't exists, none of these checks apply
881 if (!$exists) return;
882
883 switch ($method) {
884
885 case 'GET' :
886 case 'HEAD' :
887 case 'OPTIONS' :
888 // For these 3 we only need to know if the node is readable.
889 $this->checkPrivileges($path, '{DAV:}read');
890 break;
891
892 case 'PUT' :
893 case 'LOCK' :
894 // This method requires the write-content priv if the node
895 // already exists, and bind on the parent if the node is being
896 // created.
897 // The bind privilege is handled in the beforeBind event.
898 $this->checkPrivileges($path, '{DAV:}write-content');
899 break;
900
901 case 'UNLOCK' :
902 // Unlock is always allowed at the moment.
903 break;
904
905 case 'PROPPATCH' :
906 $this->checkPrivileges($path, '{DAV:}write-properties');
907 break;
908
909 case 'ACL' :
910 $this->checkPrivileges($path, '{DAV:}write-acl');
911 break;
912
913 case 'COPY' :
914 case 'MOVE' :
915 // Copy requires read privileges on the entire source tree.
916 // If the target exists write-content normally needs to be
917 // checked, however, we're deleting the node beforehand and
918 // creating a new one after, so this is handled by the
919 // beforeUnbind event.
920 //
921 // The creation of the new node is handled by the beforeBind
922 // event.
923 //
924 // If MOVE is used beforeUnbind will also be used to check if
925 // the sourcenode can be deleted.
926 $this->checkPrivileges($path, '{DAV:}read', self::R_RECURSIVE);
927 break;
928
929 }
930
931 }
932
942 function beforeBind($uri) {
943
944 list($parentUri) = Uri\split($uri);
945 $this->checkPrivileges($parentUri, '{DAV:}bind');
946
947 }
948
958 function beforeUnbind($uri) {
959
960 list($parentUri) = Uri\split($uri);
961 $this->checkPrivileges($parentUri, '{DAV:}unbind', self::R_RECURSIVEPARENTS);
962
963 }
964
973 function beforeUnlock($uri, DAV\Locks\LockInfo $lock) {
974
975
976 }
977
986 function propFind(DAV\PropFind $propFind, DAV\INode $node) {
987
988 $path = $propFind->getPath();
989
990 // Checking the read permission
991 if (!$this->checkPrivileges($path, '{DAV:}read', self::R_PARENT, false)) {
992 // User is not allowed to read properties
993
994 // Returning false causes the property-fetching system to pretend
995 // that the node does not exist, and will cause it to be hidden
996 // from listings such as PROPFIND or the browser plugin.
997 if ($this->hideNodesFromListings) {
998 return false;
999 }
1000
1001 // Otherwise we simply mark every property as 403.
1002 foreach ($propFind->getRequestedProperties() as $requestedProperty) {
1003 $propFind->set($requestedProperty, null, 403);
1004 }
1005
1006 return;
1007
1008 }
1009
1010 /* Adding principal properties */
1011 if ($node instanceof IPrincipal) {
1012
1013 $propFind->handle('{DAV:}alternate-URI-set', function() use ($node) {
1014 return new Href($node->getAlternateUriSet());
1015 });
1016 $propFind->handle('{DAV:}principal-URL', function() use ($node) {
1017 return new Href($node->getPrincipalUrl() . '/');
1018 });
1019 $propFind->handle('{DAV:}group-member-set', function() use ($node) {
1020 $members = $node->getGroupMemberSet();
1021 foreach ($members as $k => $member) {
1022 $members[$k] = rtrim($member, '/') . '/';
1023 }
1024 return new Href($members);
1025 });
1026 $propFind->handle('{DAV:}group-membership', function() use ($node) {
1027 $members = $node->getGroupMembership();
1028 foreach ($members as $k => $member) {
1029 $members[$k] = rtrim($member, '/') . '/';
1030 }
1031 return new Href($members);
1032 });
1033 $propFind->handle('{DAV:}displayname', [$node, 'getDisplayName']);
1034
1035 }
1036
1037 $propFind->handle('{DAV:}principal-collection-set', function() {
1038
1040 // Ensuring all collections end with a slash
1041 foreach ($val as $k => $v) $val[$k] = $v . '/';
1042 return new Href($val);
1043
1044 });
1045 $propFind->handle('{DAV:}current-user-principal', function() {
1046 if ($url = $this->getCurrentUserPrincipal()) {
1047 return new Xml\Property\Principal(Xml\Property\Principal::HREF, $url . '/');
1048 } else {
1049 return new Xml\Property\Principal(Xml\Property\Principal::UNAUTHENTICATED);
1050 }
1051 });
1052 $propFind->handle('{DAV:}supported-privilege-set', function() use ($node) {
1054 });
1055 $propFind->handle('{DAV:}current-user-privilege-set', function() use ($node, $propFind, $path) {
1056 if (!$this->checkPrivileges($path, '{DAV:}read-current-user-privilege-set', self::R_PARENT, false)) {
1057 $propFind->set('{DAV:}current-user-privilege-set', null, 403);
1058 } else {
1059 $val = $this->getCurrentUserPrivilegeSet($node);
1060 return new Xml\Property\CurrentUserPrivilegeSet($val);
1061 }
1062 });
1063 $propFind->handle('{DAV:}acl', function() use ($node, $propFind, $path) {
1064 /* The ACL property contains all the permissions */
1065 if (!$this->checkPrivileges($path, '{DAV:}read-acl', self::R_PARENT, false)) {
1066 $propFind->set('{DAV:}acl', null, 403);
1067 } else {
1068 $acl = $this->getACL($node);
1069 return new Xml\Property\Acl($this->getACL($node));
1070 }
1071 });
1072 $propFind->handle('{DAV:}acl-restrictions', function() {
1073 return new Xml\Property\AclRestrictions();
1074 });
1075
1076 /* Adding ACL properties */
1077 if ($node instanceof IACL) {
1078 $propFind->handle('{DAV:}owner', function() use ($node) {
1079 return new Href($node->getOwner() . '/');
1080 });
1081 }
1082
1083 }
1084
1093 function propPatch($path, DAV\PropPatch $propPatch) {
1094
1095 $propPatch->handle('{DAV:}group-member-set', function($value) use ($path) {
1096 if (is_null($value)) {
1097 $memberSet = [];
1098 } elseif ($value instanceof Href) {
1099 $memberSet = array_map(
1100 [$this->server, 'calculateUri'],
1101 $value->getHrefs()
1102 );
1103 } else {
1104 throw new DAV\Exception('The group-member-set property MUST be an instance of Sabre\DAV\Property\HrefList or null');
1105 }
1106 $node = $this->server->tree->getNodeForPath($path);
1107 if (!($node instanceof IPrincipal)) {
1108 // Fail
1109 return false;
1110 }
1111
1112 $node->setGroupMemberSet($memberSet);
1113 // We must also clear our cache, just in case
1114
1115 $this->principalMembershipCache = [];
1116
1117 return true;
1118 });
1119
1120 }
1121
1130 function report($reportName, $report, $path) {
1131
1132 switch ($reportName) {
1133
1134 case '{DAV:}principal-property-search' :
1135 $this->server->transactionType = 'report-principal-property-search';
1136 $this->principalPropertySearchReport($path, $report);
1137 return false;
1138 case '{DAV:}principal-search-property-set' :
1139 $this->server->transactionType = 'report-principal-search-property-set';
1140 $this->principalSearchPropertySetReport($path, $report);
1141 return false;
1142 case '{DAV:}expand-property' :
1143 $this->server->transactionType = 'report-expand-property';
1144 $this->expandPropertyReport($path, $report);
1145 return false;
1146 case '{DAV:}principal-match' :
1147 $this->server->transactionType = 'report-principal-match';
1148 $this->principalMatchReport($path, $report);
1149 return false;
1150 case '{DAV:}acl-principal-prop-set' :
1151 $this->server->transactionType = 'acl-principal-prop-set';
1152 $this->aclPrincipalPropSetReport($path, $report);
1153 return false;
1154
1155 }
1156
1157 }
1158
1167
1168 $path = $request->getPath();
1169 $body = $request->getBodyAsString();
1170
1171 if (!$body) {
1172 throw new DAV\Exception\BadRequest('XML body expected in ACL request');
1173 }
1174
1175 $acl = $this->server->xml->expect('{DAV:}acl', $body);
1176 $newAcl = $acl->getPrivileges();
1177
1178 // Normalizing urls
1179 foreach ($newAcl as $k => $newAce) {
1180 $newAcl[$k]['principal'] = $this->server->calculateUri($newAce['principal']);
1181 }
1182 $node = $this->server->tree->getNodeForPath($path);
1183
1184 if (!$node instanceof IACL) {
1185 throw new DAV\Exception\MethodNotAllowed('This node does not support the ACL method');
1186 }
1187
1188 $oldAcl = $this->getACL($node);
1189
1190 $supportedPrivileges = $this->getFlatPrivilegeSet($node);
1191
1192 /* Checking if protected principals from the existing principal set are
1193 not overwritten. */
1194 foreach ($oldAcl as $oldAce) {
1195
1196 if (!isset($oldAce['protected']) || !$oldAce['protected']) continue;
1197
1198 $found = false;
1199 foreach ($newAcl as $newAce) {
1200 if (
1201 $newAce['privilege'] === $oldAce['privilege'] &&
1202 $newAce['principal'] === $oldAce['principal'] &&
1203 $newAce['protected']
1204 )
1205 $found = true;
1206 }
1207
1208 if (!$found)
1209 throw new Exception\AceConflict('This resource contained a protected {DAV:}ace, but this privilege did not occur in the ACL request');
1210
1211 }
1212
1213 foreach ($newAcl as $newAce) {
1214
1215 // Do we recognize the privilege
1216 if (!isset($supportedPrivileges[$newAce['privilege']])) {
1217 throw new Exception\NotSupportedPrivilege('The privilege you specified (' . $newAce['privilege'] . ') is not recognized by this server');
1218 }
1219
1220 if ($supportedPrivileges[$newAce['privilege']]['abstract']) {
1221 throw new Exception\NoAbstract('The privilege you specified (' . $newAce['privilege'] . ') is an abstract privilege');
1222 }
1223
1224 // Looking up the principal
1225 try {
1226 $principal = $this->server->tree->getNodeForPath($newAce['principal']);
1227 } catch (NotFound $e) {
1228 throw new Exception\NotRecognizedPrincipal('The specified principal (' . $newAce['principal'] . ') does not exist');
1229 }
1230 if (!($principal instanceof IPrincipal)) {
1231 throw new Exception\NotRecognizedPrincipal('The specified uri (' . $newAce['principal'] . ') is not a principal');
1232 }
1233
1234 }
1235 $node->setACL($newAcl);
1236
1237 $response->setStatus(200);
1238
1239 // Breaking the event chain, because we handled this method.
1240 return false;
1241
1242 }
1243
1244 /* }}} */
1245
1246 /* Reports {{{ */
1247
1259 protected function principalMatchReport($path, Xml\Request\PrincipalMatchReport $report) {
1260
1261 $depth = $this->server->getHTTPDepth(0);
1262 if ($depth !== 0) {
1263 throw new BadRequest('The principal-match report is only defined on Depth: 0');
1264 }
1265
1266 $currentPrincipals = $this->getCurrentUserPrincipals();
1267
1268 $result = [];
1269
1270 if ($report->type === Xml\Request\PrincipalMatchReport::SELF) {
1271
1272 // Finding all principals under the request uri that match the
1273 // current principal.
1274 foreach ($currentPrincipals as $currentPrincipal) {
1275
1276 if ($currentPrincipal === $path || strpos($currentPrincipal, $path . '/') === 0) {
1277 $result[] = $currentPrincipal;
1278 }
1279
1280 }
1281
1282 } else {
1283
1284 // We need to find all resources that have a property that matches
1285 // one of the current principals.
1286 $candidates = $this->server->getPropertiesForPath(
1287 $path,
1288 [$report->principalProperty],
1289 1
1290 );
1291
1292 foreach ($candidates as $candidate) {
1293
1294 if (!isset($candidate[200][$report->principalProperty])) {
1295 continue;
1296 }
1297
1298 $hrefs = $candidate[200][$report->principalProperty];
1299
1300 if (!$hrefs instanceof Href) {
1301 continue;
1302 }
1303
1304 foreach ($hrefs->getHrefs() as $href) {
1305 if (in_array(trim($href, '/'), $currentPrincipals)) {
1306 $result[] = $candidate['href'];
1307 continue 2;
1308 }
1309 }
1310 }
1311
1312 }
1313
1314 $responses = [];
1315
1316 foreach ($result as $item) {
1317
1318 $properties = [];
1319
1320 if ($report->properties) {
1321
1322 $foo = $this->server->getPropertiesForPath($item, $report->properties);
1323 $foo = $foo[0];
1324 $item = $foo['href'];
1325 unset($foo['href']);
1326 $properties = $foo;
1327
1328 }
1329
1330 $responses[] = new DAV\Xml\Element\Response(
1331 $item,
1332 $properties,
1333 '200'
1334 );
1335
1336 }
1337
1338 $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
1339 $this->server->httpResponse->setStatus(207);
1340 $this->server->httpResponse->setBody(
1341 $this->server->xml->write(
1342 '{DAV:}multistatus',
1343 $responses,
1344 $this->server->getBaseUri()
1345 )
1346 );
1347
1348
1349 }
1350
1366 protected function expandPropertyReport($path, $report) {
1367
1368 $depth = $this->server->getHTTPDepth(0);
1369
1370 $result = $this->expandProperties($path, $report->properties, $depth);
1371
1372 $xml = $this->server->xml->write(
1373 '{DAV:}multistatus',
1374 new DAV\Xml\Response\MultiStatus($result),
1375 $this->server->getBaseUri()
1376 );
1377 $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
1378 $this->server->httpResponse->setStatus(207);
1379 $this->server->httpResponse->setBody($xml);
1380
1381 }
1382
1392 protected function expandProperties($path, array $requestedProperties, $depth) {
1393
1394 $foundProperties = $this->server->getPropertiesForPath($path, array_keys($requestedProperties), $depth);
1395
1396 $result = [];
1397
1398 foreach ($foundProperties as $node) {
1399
1400 foreach ($requestedProperties as $propertyName => $childRequestedProperties) {
1401
1402 // We're only traversing if sub-properties were requested
1403 if (count($childRequestedProperties) === 0) continue;
1404
1405 // We only have to do the expansion if the property was found
1406 // and it contains an href element.
1407 if (!array_key_exists($propertyName, $node[200])) continue;
1408
1409 if (!$node[200][$propertyName] instanceof DAV\Xml\Property\Href) {
1410 continue;
1411 }
1412
1413 $childHrefs = $node[200][$propertyName]->getHrefs();
1414 $childProps = [];
1415
1416 foreach ($childHrefs as $href) {
1417 // Gathering the result of the children
1418 $childProps[] = [
1419 'name' => '{DAV:}response',
1420 'value' => $this->expandProperties($href, $childRequestedProperties, 0)[0]
1421 ];
1422 }
1423
1424 // Replacing the property with its expanded form.
1425 $node[200][$propertyName] = $childProps;
1426
1427 }
1428 $result[] = new DAV\Xml\Element\Response($node['href'], $node);
1429
1430 }
1431
1432 return $result;
1433
1434 }
1435
1448 protected function principalSearchPropertySetReport($path, $report) {
1449
1450 $httpDepth = $this->server->getHTTPDepth(0);
1451 if ($httpDepth !== 0) {
1452 throw new DAV\Exception\BadRequest('This report is only defined when Depth: 0');
1453 }
1454
1455 $writer = $this->server->xml->getWriter();
1456 $writer->openMemory();
1457 $writer->startDocument();
1458
1459 $writer->startElement('{DAV:}principal-search-property-set');
1460
1461 foreach ($this->principalSearchPropertySet as $propertyName => $description) {
1462
1463 $writer->startElement('{DAV:}principal-search-property');
1464 $writer->startElement('{DAV:}prop');
1465
1466 $writer->writeElement($propertyName);
1467
1468 $writer->endElement(); // prop
1469
1470 if ($description) {
1471 $writer->write([[
1472 'name' => '{DAV:}description',
1473 'value' => $description,
1474 'attributes' => ['xml:lang' => 'en']
1475 ]]);
1476 }
1477
1478 $writer->endElement(); // principal-search-property
1479
1480
1481 }
1482
1483 $writer->endElement(); // principal-search-property-set
1484
1485 $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
1486 $this->server->httpResponse->setStatus(200);
1487 $this->server->httpResponse->setBody($writer->outputMemory());
1488
1489 }
1490
1503 protected function principalPropertySearchReport($path, Xml\Request\PrincipalPropertySearchReport $report) {
1504
1505 if ($report->applyToPrincipalCollectionSet) {
1506 $path = null;
1507 }
1508 if ($this->server->getHttpDepth('0') !== 0) {
1509 throw new BadRequest('Depth must be 0');
1510 }
1511 $result = $this->principalSearch(
1512 $report->searchProperties,
1513 $report->properties,
1514 $path,
1515 $report->test
1516 );
1517
1518 $prefer = $this->server->getHTTPPrefer();
1519
1520 $this->server->httpResponse->setStatus(207);
1521 $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
1522 $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
1523 $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return'] === 'minimal'));
1524
1525 }
1526
1544 protected function aclPrincipalPropSetReport($path, Xml\Request\AclPrincipalPropSetReport $report) {
1545
1546 if ($this->server->getHTTPDepth(0) !== 0) {
1547 throw new BadRequest('The {DAV:}acl-principal-prop-set REPORT only supports Depth 0');
1548 }
1549
1550 // Fetching ACL rules for the given path. We're using the property
1551 // API and not the local getACL, because it will ensure that all
1552 // business rules and restrictions are applied.
1553 $acl = $this->server->getProperties($path, '{DAV:}acl');
1554
1555 if (!$acl || !isset($acl['{DAV:}acl'])) {
1556 throw new Forbidden('Could not fetch ACL rules for this path');
1557 }
1558
1559 $principals = [];
1560 foreach ($acl['{DAV:}acl']->getPrivileges() as $ace) {
1561
1562 if ($ace['principal'][0] === '{') {
1563 // It's not a principal, it's one of the special rules such as {DAV:}authenticated
1564 continue;
1565 }
1566
1567 $principals[] = $ace['principal'];
1568
1569 }
1570
1571 $properties = $this->server->getPropertiesForMultiplePaths(
1572 $principals,
1573 $report->properties
1574 );
1575
1576 $this->server->httpResponse->setStatus(207);
1577 $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
1578 $this->server->httpResponse->setBody(
1579 $this->server->generateMultiStatus($properties)
1580 );
1581
1582 }
1583
1584
1585 /* }}} */
1586
1596 function htmlActionsPanel(DAV\INode $node, &$output) {
1597
1598 if (!$node instanceof PrincipalCollection)
1599 return;
1600
1601 $output .= '<tr><td colspan="2"><form method="post" action="">
1602 <h3>Create new principal</h3>
1603 <input type="hidden" name="sabreAction" value="mkcol" />
1604 <input type="hidden" name="resourceType" value="{DAV:}principal" />
1605 <label>Name (uri):</label> <input type="text" name="name" /><br />
1606 <label>Display name:</label> <input type="text" name="{DAV:}displayname" /><br />
1607 <label>Email address:</label> <input type="text" name="{http://sabredav*DOT*org/ns}email-address" /><br />
1608 <input type="submit" value="create" />
1609 </form>
1610 </td></tr>';
1611
1612 return false;
1613
1614 }
1615
1627 function getPluginInfo() {
1628
1629 return [
1630 'name' => $this->getPluginName(),
1631 'description' => 'Adds support for WebDAV ACL (rfc3744)',
1632 'link' => 'http://sabre.io/dav/acl/',
1633 ];
1634
1635 }
1636}
$result
$failed
Definition: Utf8Test.php:85
$test
Definition: Utf8Test.php:84
$path
Definition: aliased.php:25
foreach($paths as $path) $request
Definition: asyncclient.php:32
$authPlugin
An exception for terminatinating execution or to throw for unit testing.
This exception is thrown when a client attempts to set conflicting permissions.
Definition: AceConflict.php:15
This exception is thrown when a user tries to set a privilege that's marked as abstract.
Definition: NoAbstract.php:15
If a client tried to set a privilege assigned to a non-existent principal, this exception will be thr...
If a client tried to set a privilege that doesn't exist, this exception will be thrown.
SabreDAV ACL Plugin.
Definition: Plugin.php:31
getFlatPrivilegeSet($node)
Returns the supported privilege set as a flat list.
Definition: Plugin.php:529
getCurrentUserPrincipals()
Returns a list of principals that's associated to the current user, either directly or through group ...
Definition: Plugin.php:255
propPatch($path, DAV\PropPatch $propPatch)
This method intercepts PROPPATCH methods and make sure the group-member-set is updated correctly.
Definition: Plugin.php:1093
propFind(DAV\PropFind $propFind, DAV\INode $node)
Triggered before properties are looked up in specific nodes.
Definition: Plugin.php:986
getPluginInfo()
Returns a bunch of meta-data about the plugin.
Definition: Plugin.php:1627
getPluginName()
Returns a plugin name.
Definition: Plugin.php:149
principalSearchPropertySetReport($path, $report)
principalSearchPropertySetReport
Definition: Plugin.php:1448
expandPropertyReport($path, $report)
The expand-property report is defined in RFC3253 section 3.8.
Definition: Plugin.php:1366
const R_RECURSIVEPARENTS
Recursion constants.
Definition: Plugin.php:52
getSupportedReportSet($uri)
Returns a list of reports this plugin supports.
Definition: Plugin.php:165
principalSearch(array $searchProperties, array $requestedProperties, $collectionUri=null, $test='allof')
Principal property search.
Definition: Plugin.php:759
getPrincipalMembership($mainPrincipal)
Returns all the principal groups the specified principal is a member of.
Definition: Plugin.php:327
getSupportedPrivilegeSet($node)
Returns a tree of supported privileges for a resource.
Definition: Plugin.php:441
beforeMethod(RequestInterface $request, ResponseInterface $response)
Triggered before any method is handled.
Definition: Plugin.php:873
getPrincipalByUri($uri)
Returns a principal based on its uri.
Definition: Plugin.php:708
principalPropertySearchReport($path, Xml\Request\PrincipalPropertySearchReport $report)
principalPropertySearchReport
Definition: Plugin.php:1503
beforeBind($uri)
Triggered before a new node is created.
Definition: Plugin.php:942
const R_RECURSIVE
Recursion constants.
Definition: Plugin.php:45
checkPrivileges($uri, $privileges, $recursion=self::R_PARENT, $throwExceptions=true)
Checks if the current user has the specified privilege(s).
Definition: Plugin.php:192
report($reportName, $report, $path)
This method handles HTTP REPORT requests.
Definition: Plugin.php:1130
principalMatchesPrincipal($checkPrincipal, $currentPrincipal=null)
Find out of a principal equals another principal.
Definition: Plugin.php:386
getMethods($uri)
Returns a list of available methods for a given url.
Definition: Plugin.php:135
expandProperties($path, array $requestedProperties, $depth)
This method expands all the properties and returns a list with property values.
Definition: Plugin.php:1392
getFeatures()
Returns a list of features added by this plugin.
Definition: Plugin.php:123
const R_PARENT
Recursion constants.
Definition: Plugin.php:38
htmlActionsPanel(DAV\INode $node, &$output)
This method is used to generate HTML output for the DAV\Browser\Plugin.
Definition: Plugin.php:1596
getDefaultAcl()
Returns the default ACL rules.
Definition: Plugin.php:289
principalMatchReport($path, Xml\Request\PrincipalMatchReport $report)
The principal-match report is defined in RFC3744, section 9.3.
Definition: Plugin.php:1259
initialize(DAV\Server $server)
Sets up the plugin.
Definition: Plugin.php:804
beforeUnlock($uri, DAV\Locks\LockInfo $lock)
Triggered before a node is unlocked.
Definition: Plugin.php:973
beforeUnbind($uri)
Triggered before a node is deleted.
Definition: Plugin.php:958
setDefaultAcl(array $acl)
Sets the default ACL rules.
Definition: Plugin.php:276
httpAcl(RequestInterface $request, ResponseInterface $response)
This method is responsible for handling the 'ACL' event.
Definition: Plugin.php:1166
getCurrentUserPrivilegeSet($node)
Returns a list of privileges the current user has on a particular node.
Definition: Plugin.php:618
aclPrincipalPropSetReport($path, Xml\Request\AclPrincipalPropSetReport $report)
aclPrincipalPropSet REPORT
Definition: Plugin.php:1544
getAcl($node)
Returns the full ACL list.
Definition: Plugin.php:587
This class represents the {DAV:}acl property.
Definition: Acl.php:28
Main Exception class.
Definition: Exception.php:18
This class holds all the information about a PROPFIND request.
Definition: PropFind.php:11
This class represents a set of properties that are going to be updated.
Definition: PropPatch.php:20
The baseclass for all server plugins.
Main DAV server class.
Definition: Server.php:23
WebDAV {DAV:}response parser.
Definition: Response.php:20
Href property.
Definition: Href.php:26
The Request class represents a single HTTP request.
Definition: Request.php:18
This class represents a single HTTP response.
Definition: Response.php:12
ACL-enabled node.
Definition: IACL.php:16
Principal Collection interface.
IPrincipal interface.
Definition: IPrincipal.php:16
The ICollection Interface.
Definition: ICollection.php:14
The INode interface is the base interface, and the parent class of both ICollection and IFile.
Definition: INode.php:12
The RequestInterface represents a HTTP request.
This interface represents a HTTP response.
split($path)
Returns the 'dirname' and 'basename' for a path.
Definition: functions.php:279
$url
$response
$results
Definition: svg-scanner.php:47