ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
Plugin.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Sabre\DAVACL;
4 
5 use Sabre\DAV;
10 use Sabre\DAV\INode;
15 use Sabre\Uri;
16 
31 class Plugin extends DAV\ServerPlugin {
32 
38  const R_PARENT = 1;
39 
45  const R_RECURSIVE = 2;
46 
52  const R_RECURSIVEPARENTS = 3;
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 
618  function getCurrentUserPrivilegeSet($node) {
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 }
This exception is thrown when a user tries to set a privilege that&#39;s marked as abstract.
Definition: NoAbstract.php:15
This interface represents a HTTP response.
The RequestInterface represents a HTTP request.
$path
Definition: aliased.php:25
getAcl($node)
Returns the full ACL list.
Definition: Plugin.php:587
The baseclass for all server plugins.
This class represents a set of properties that are going to be updated.
Definition: PropPatch.php:20
$result
foreach($paths as $path) $request
Definition: asyncclient.php:32
The Request class represents a single HTTP request.
Definition: Request.php:18
getBodyAsString()
Returns the body as a string.
getPluginInfo()
Returns a bunch of meta-data about the plugin.
Definition: Plugin.php:1627
aclPrincipalPropSetReport($path, Xml\Request\AclPrincipalPropSetReport $report)
aclPrincipalPropSet REPORT
Definition: Plugin.php:1544
report($reportName, $report, $path)
This method handles HTTP REPORT requests.
Definition: Plugin.php:1130
split($path)
Returns the &#39;dirname&#39; and &#39;basename&#39; for a path.
Definition: functions.php:279
Principal Collection interface.
const R_RECURSIVE
Recursion constants.
Definition: Plugin.php:45
This class holds all the information about a PROPFIND request.
Definition: PropFind.php:11
propFind(DAV\PropFind $propFind, DAV\INode $node)
Triggered before properties are looked up in specific nodes.
Definition: Plugin.php:986
propPatch($path, DAV\PropPatch $propPatch)
This method intercepts PROPPATCH methods and make sure the group-member-set is updated correctly...
Definition: Plugin.php:1093
principalMatchesPrincipal($checkPrincipal, $currentPrincipal=null)
Find out of a principal equals another principal.
Definition: Plugin.php:386
This exception is thrown when a client attempts to set conflicting permissions.
Definition: AceConflict.php:15
beforeMethod(RequestInterface $request, ResponseInterface $response)
Triggered before any method is handled.
Definition: Plugin.php:873
The ICollection Interface.
Definition: ICollection.php:14
getSupportedReportSet($uri)
Returns a list of reports this plugin supports.
Definition: Plugin.php:165
httpAcl(RequestInterface $request, ResponseInterface $response)
This method is responsible for handling the &#39;ACL&#39; event.
Definition: Plugin.php:1166
Href property.
Definition: Href.php:26
beforeUnlock($uri, DAV\Locks\LockInfo $lock)
Triggered before a node is unlocked.
Definition: Plugin.php:973
getSupportedPrivilegeSet($node)
Returns a tree of supported privileges for a resource.
Definition: Plugin.php:441
getDefaultAcl()
Returns the default ACL rules.
Definition: Plugin.php:289
const R_RECURSIVEPARENTS
Recursion constants.
Definition: Plugin.php:52
getFeatures()
Returns a list of features added by this plugin.
Definition: Plugin.php:123
getCurrentUserPrincipals()
Returns a list of principals that&#39;s associated to the current user, either directly or through group ...
Definition: Plugin.php:255
Main Exception class.
Definition: Exception.php:18
beforeUnbind($uri)
Triggered before a node is deleted.
Definition: Plugin.php:958
setStatus($status)
Sets the HTTP status code.
beforeBind($uri)
Triggered before a new node is created.
Definition: Plugin.php:942
getPrincipalByUri($uri)
Returns a principal based on its uri.
Definition: Plugin.php:708
principalPropertySearchReport($path, Xml\Request\PrincipalPropertySearchReport $report)
principalPropertySearchReport
Definition: Plugin.php:1503
ACL-enabled node.
Definition: IACL.php:16
const R_PARENT
Recursion constants.
Definition: Plugin.php:38
This class represents a single HTTP response.
Definition: Response.php:12
expandProperties($path, array $requestedProperties, $depth)
This method expands all the properties and returns a list with property values.
Definition: Plugin.php:1392
expandPropertyReport($path, $report)
The expand-property report is defined in RFC3253 section 3.8.
Definition: Plugin.php:1366
getFlatPrivilegeSet($node)
Returns the supported privilege set as a flat list.
Definition: Plugin.php:529
Main DAV server class.
Definition: Server.php:23
setDefaultAcl(array $acl)
Sets the default ACL rules.
Definition: Plugin.php:276
The INode interface is the base interface, and the parent class of both ICollection and IFile...
Definition: INode.php:12
$failed
Definition: Utf8Test.php:85
WebDAV {DAV:}response parser.
Definition: Response.php:20
principalSearchPropertySetReport($path, $report)
principalSearchPropertySetReport
Definition: Plugin.php:1448
getPluginName()
Returns a plugin name.
Definition: Plugin.php:149
getPath()
Returns the relative path.
$authPlugin
getMethod()
Returns the current HTTP method.
IPrincipal interface.
Definition: IPrincipal.php:16
principalMatchReport($path, Xml\Request\PrincipalMatchReport $report)
The principal-match report is defined in RFC3744, section 9.3.
Definition: Plugin.php:1259
getMethods($uri)
Returns a list of available methods for a given url.
Definition: Plugin.php:135
checkPrivileges($uri, $privileges, $recursion=self::R_PARENT, $throwExceptions=true)
Checks if the current user has the specified privilege(s).
Definition: Plugin.php:192
$results
Definition: svg-scanner.php:47
principalSearch(array $searchProperties, array $requestedProperties, $collectionUri=null, $test='allof')
Principal property search.
Definition: Plugin.php:759
If a client tried to set a privilege assigned to a non-existent principal, this exception will be thr...
$url
initialize(DAV\Server $server)
Sets up the plugin.
Definition: Plugin.php:804
getCurrentUserPrivilegeSet($node)
Returns a list of privileges the current user has on a particular node.
Definition: Plugin.php:618
This class represents the {DAV:}acl property.
Definition: Acl.php:28
$response
SabreDAV ACL Plugin.
Definition: Plugin.php:31
If a client tried to set a privilege that doesn&#39;t exist, this exception will be thrown.
$test
Definition: Utf8Test.php:84
htmlActionsPanel(DAV\INode $node, &$output)
This method is used to generate HTML output for the DAV.
Definition: Plugin.php:1596
getPrincipalMembership($mainPrincipal)
Returns all the principal groups the specified principal is a member of.
Definition: Plugin.php:327