91 '{DAV:}displayname' =>
'Display name',
92 '{http://sabredav.org/ns}email-address' =>
'Email address',
125 return [
'access-control',
'calendarserver-principal-property-search'];
168 '{DAV:}expand-property',
169 '{DAV:}principal-match',
170 '{DAV:}principal-property-search',
171 '{DAV:}principal-search-property-set',
192 function checkPrivileges($uri, $privileges, $recursion = self::R_PARENT, $throwExceptions =
true) {
194 if (!is_array($privileges)) $privileges = [$privileges];
199 foreach ($privileges as $priv) {
201 if (!in_array($priv, $acl)) {
208 if ($this->allowUnauthenticatedAccess && is_null($this->getCurrentUserPrincipal())) {
213 $this->server->httpRequest,
214 $this->server->httpResponse
216 throw new notAuthenticated(implode(
', ', $reasons) .
'. Login was needed for privilege: ' . implode(
', ',
$failed) .
' on ' . $uri);
218 if ($throwExceptions) {
237 function getCurrentUserPrincipal() {
257 $currentUser = $this->getCurrentUserPrincipal();
259 if (is_null($currentUser))
return [];
278 $this->defaultAcl = $acl;
306 'principal' =>
'{DAV:}authenticated',
308 'privilege' =>
'{DAV:}all',
330 if (isset($this->principalMembershipCache[$mainPrincipal])) {
331 return $this->principalMembershipCache[$mainPrincipal];
334 $check = [$mainPrincipal];
337 while (count($check)) {
339 $principal = array_shift($check);
341 $node = $this->server->tree->getNodeForPath($principal);
343 foreach ($node->getGroupMembership() as $groupMember) {
345 if (!in_array($groupMember, $principals)) {
347 $check[] = $groupMember;
348 $principals[] = $groupMember;
359 $this->principalMembershipCache[$mainPrincipal] = $principals;
388 if (is_null($currentPrincipal)) {
389 $currentPrincipal = $this->getCurrentUserPrincipal();
391 if ($currentPrincipal === $checkPrincipal) {
443 if (is_string($node)) {
444 $node = $this->server->tree->getNodeForPath($node);
447 $supportedPrivileges = null;
448 if ($node instanceof
IACL) {
449 $supportedPrivileges = $node->getSupportedPrivilegeSet();
452 if (is_null($supportedPrivileges)) {
455 $supportedPrivileges = [
459 '{DAV:}read-acl' => [
463 '{DAV:}read-current-user-privilege-set' => [
472 '{DAV:}write-properties' => [
476 '{DAV:}write-content' => [
488 $supportedPrivileges[
'{DAV:}write'][
'aggregates'][
'{DAV:}bind'] = [
492 $supportedPrivileges[
'{DAV:}write'][
'aggregates'][
'{DAV:}unbind'] = [
497 if ($node instanceof IACL) {
498 $supportedPrivileges[
'{DAV:}write'][
'aggregates'][
'{DAV:}write-acl'] = [
507 'getSupportedPrivilegeSet',
508 [$node, &$supportedPrivileges]
511 return $supportedPrivileges;
537 $fpsTraverse =
function($privName, $privInfo, $concrete, &$flat) use (&$fpsTraverse) {
540 'privilege' => $privName,
541 'abstract' => isset($privInfo[
'abstract']) && $privInfo[
'abstract'],
543 'concrete' => isset($privInfo[
'abstract']) && $privInfo[
'abstract'] ? $concrete : $privName,
546 if (isset($privInfo[
'aggregates'])) {
548 foreach ($privInfo[
'aggregates'] as $subPrivName => $subPrivInfo) {
550 $myPriv[
'aggregates'][] = $subPrivName;
556 $flat[$privName] = $myPriv;
558 if (isset($privInfo[
'aggregates'])) {
560 foreach ($privInfo[
'aggregates'] as $subPrivName => $subPrivInfo) {
562 $fpsTraverse($subPrivName, $subPrivInfo, $myPriv[
'concrete'], $flat);
571 $fpsTraverse(
'{DAV:}all', $privs, null, $flat);
589 if (is_string($node)) {
590 $node = $this->server->tree->getNodeForPath($node);
592 if (!$node instanceof
IACL) {
595 $acl = $node->getACL();
596 foreach ($this->adminPrincipals as $adminPrincipal) {
598 'principal' => $adminPrincipal,
599 'privilege' =>
'{DAV:}all',
620 if (is_string($node)) {
621 $node = $this->server->tree->getNodeForPath($node);
624 $acl = $this->getACL($node);
628 $isAuthenticated = $this->getCurrentUserPrincipal() !== null;
630 foreach ($acl as $ace) {
632 $principal = $ace[
'principal'];
634 switch ($principal) {
637 $owner = $node->getOwner();
649 case '{DAV:}authenticated' :
651 if ($isAuthenticated) {
656 case '{DAV:}unauthenticated' :
658 if (!$isAuthenticated) {
678 while (count($collected)) {
681 $collected2[] =
$current[
'privilege'];
683 if (!isset($flat[
$current[
'privilege']])) {
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.');
688 foreach ($flat[$current[
'privilege']][
'aggregates'] as $subPriv) {
689 $collected2[] = $subPriv;
690 $collected[] = $flat[$subPriv];
695 return array_values(array_unique($collected2));
712 foreach ($collections as $collection) {
715 $principalCollection = $this->server->tree->getNodeForPath($collection);
727 $result = $principalCollection->findByUri($uri);
759 function principalSearch(array $searchProperties, array $requestedProperties, $collectionUri = null,
$test =
'allof') {
761 if (!is_null($collectionUri)) {
762 $uris = [$collectionUri];
768 foreach ($uris as $uri) {
770 $principalCollection = $this->server->tree->getNodeForPath($uri);
777 $results = $principalCollection->searchPrincipals($searchProperties,
$test);
779 $lookupResults[] = rtrim($uri,
'/') .
'/' .
$result;
786 foreach ($lookupResults as $lookupResult) {
788 list($matches[]) = $this->server->getPropertiesForPath($lookupResult, $requestedProperties, 0);
806 if ($this->allowUnauthenticatedAccess) {
809 throw new \Exception(
'The Auth plugin must be loaded before the ACL plugin if you want to allow unauthenticated access.');
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) {
829 if ($uri)
return false;
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',
842 '{DAV:}acl-restrictions',
843 '{DAV:}inherited-acl-set',
850 $server->resourceTypeMapping[
'Sabre\\DAVACL\\IPrincipal'] =
'{DAV:}principal';
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';
878 $exists = $this->server->tree->nodeExists(
$path);
881 if (!$exists)
return;
961 $this->
checkPrivileges($parentUri,
'{DAV:}unbind', self::R_RECURSIVEPARENTS);
988 $path = $propFind->getPath();
997 if ($this->hideNodesFromListings) {
1002 foreach ($propFind->getRequestedProperties() as $requestedProperty) {
1003 $propFind->set($requestedProperty, null, 403);
1013 $propFind->handle(
'{DAV:}alternate-URI-set',
function() use ($node) {
1014 return new Href($node->getAlternateUriSet());
1016 $propFind->handle(
'{DAV:}principal-URL',
function() use ($node) {
1017 return new Href($node->getPrincipalUrl() .
'/');
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,
'/') .
'/';
1024 return new Href($members);
1026 $propFind->handle(
'{DAV:}group-membership',
function() use ($node) {
1027 $members = $node->getGroupMembership();
1028 foreach ($members as $k => $member) {
1029 $members[$k] = rtrim($member,
'/') .
'/';
1031 return new Href($members);
1033 $propFind->handle(
'{DAV:}displayname', [$node,
'getDisplayName']);
1037 $propFind->handle(
'{DAV:}principal-collection-set',
function() {
1041 foreach ($val as $k => $v) $val[$k] = $v .
'/';
1042 return new Href($val);
1045 $propFind->handle(
'{DAV:}current-user-principal',
function() {
1046 if (
$url = $this->getCurrentUserPrincipal()) {
1052 $propFind->handle(
'{DAV:}supported-privilege-set',
function() use ($node) {
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);
1063 $propFind->handle(
'{DAV:}acl',
function() use ($node, $propFind,
$path) {
1066 $propFind->set(
'{DAV:}acl', null, 403);
1068 $acl = $this->getACL($node);
1072 $propFind->handle(
'{DAV:}acl-restrictions',
function() {
1077 if ($node instanceof
IACL) {
1078 $propFind->handle(
'{DAV:}owner',
function() use ($node) {
1079 return new Href($node->getOwner() .
'/');
1095 $propPatch->handle(
'{DAV:}group-member-set',
function($value) use (
$path) {
1096 if (is_null($value)) {
1098 } elseif ($value instanceof
Href) {
1099 $memberSet = array_map(
1100 [$this->server,
'calculateUri'],
1104 throw new DAV\Exception(
'The group-member-set property MUST be an instance of Sabre\DAV\Property\HrefList or null');
1106 $node = $this->server->tree->getNodeForPath(
$path);
1112 $node->setGroupMemberSet($memberSet);
1115 $this->principalMembershipCache = [];
1132 switch ($reportName) {
1134 case '{DAV:}principal-property-search' :
1135 $this->server->transactionType =
'report-principal-property-search';
1138 case '{DAV:}principal-search-property-set' :
1139 $this->server->transactionType =
'report-principal-search-property-set';
1142 case '{DAV:}expand-property' :
1143 $this->server->transactionType =
'report-expand-property';
1146 case '{DAV:}principal-match' :
1147 $this->server->transactionType =
'report-principal-match';
1150 case '{DAV:}acl-principal-prop-set' :
1151 $this->server->transactionType =
'acl-principal-prop-set';
1172 throw new DAV\Exception\BadRequest(
'XML body expected in ACL request');
1175 $acl = $this->server->xml->expect(
'{DAV:}acl', $body);
1176 $newAcl = $acl->getPrivileges();
1179 foreach ($newAcl as $k => $newAce) {
1180 $newAcl[$k][
'principal'] = $this->server->calculateUri($newAce[
'principal']);
1182 $node = $this->server->tree->getNodeForPath(
$path);
1184 if (!$node instanceof
IACL) {
1185 throw new DAV\Exception\MethodNotAllowed(
'This node does not support the ACL method');
1188 $oldAcl = $this->getACL($node);
1194 foreach ($oldAcl as $oldAce) {
1196 if (!isset($oldAce[
'protected']) || !$oldAce[
'protected'])
continue;
1199 foreach ($newAcl as $newAce) {
1201 $newAce[
'privilege'] === $oldAce[
'privilege'] &&
1202 $newAce[
'principal'] === $oldAce[
'principal'] &&
1203 $newAce[
'protected']
1209 throw new Exception\AceConflict(
'This resource contained a protected {DAV:}ace, but this privilege did not occur in the ACL request');
1213 foreach ($newAcl as $newAce) {
1216 if (!isset($supportedPrivileges[$newAce[
'privilege']])) {
1220 if ($supportedPrivileges[$newAce[
'privilege']][
'abstract']) {
1221 throw new Exception\NoAbstract(
'The privilege you specified (' . $newAce[
'privilege'] .
') is an abstract privilege');
1226 $principal = $this->server->tree->getNodeForPath($newAce[
'principal']);
1235 $node->setACL($newAcl);
1261 $depth = $this->server->getHTTPDepth(0);
1263 throw new BadRequest(
'The principal-match report is only defined on Depth: 0');
1270 if ($report->type === Xml\
Request\PrincipalMatchReport::SELF) {
1274 foreach ($currentPrincipals as $currentPrincipal) {
1276 if ($currentPrincipal ===
$path || strpos($currentPrincipal,
$path .
'/') === 0) {
1277 $result[] = $currentPrincipal;
1286 $candidates = $this->server->getPropertiesForPath(
1288 [$report->principalProperty],
1292 foreach ($candidates as $candidate) {
1294 if (!isset($candidate[200][$report->principalProperty])) {
1298 $hrefs = $candidate[200][$report->principalProperty];
1300 if (!$hrefs instanceof
Href) {
1304 foreach ($hrefs->getHrefs() as $href) {
1305 if (in_array(trim($href,
'/'), $currentPrincipals)) {
1306 $result[] = $candidate[
'href'];
1320 if ($report->properties) {
1322 $foo = $this->server->getPropertiesForPath($item, $report->properties);
1324 $item = $foo[
'href'];
1325 unset($foo[
'href']);
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',
1344 $this->server->getBaseUri()
1368 $depth = $this->server->getHTTPDepth(0);
1372 $xml = $this->server->xml->write(
1373 '{DAV:}multistatus',
1375 $this->server->getBaseUri()
1377 $this->server->httpResponse->setHeader(
'Content-Type',
'application/xml; charset=utf-8');
1378 $this->server->httpResponse->setStatus(207);
1379 $this->server->httpResponse->setBody(
$xml);
1394 $foundProperties = $this->server->getPropertiesForPath(
$path, array_keys($requestedProperties), $depth);
1398 foreach ($foundProperties as $node) {
1400 foreach ($requestedProperties as $propertyName => $childRequestedProperties) {
1403 if (count($childRequestedProperties) === 0)
continue;
1407 if (!array_key_exists($propertyName, $node[200]))
continue;
1409 if (!$node[200][$propertyName] instanceof DAV\Xml\Property\
Href) {
1413 $childHrefs = $node[200][$propertyName]->getHrefs();
1416 foreach ($childHrefs as $href) {
1419 'name' =>
'{DAV:}response',
1425 $node[200][$propertyName] = $childProps;
1450 $httpDepth = $this->server->getHTTPDepth(0);
1451 if ($httpDepth !== 0) {
1452 throw new DAV\Exception\BadRequest(
'This report is only defined when Depth: 0');
1455 $writer = $this->server->xml->getWriter();
1456 $writer->openMemory();
1457 $writer->startDocument();
1459 $writer->startElement(
'{DAV:}principal-search-property-set');
1461 foreach ($this->principalSearchPropertySet as $propertyName =>
$description) {
1463 $writer->startElement(
'{DAV:}principal-search-property');
1464 $writer->startElement(
'{DAV:}prop');
1466 $writer->writeElement($propertyName);
1468 $writer->endElement();
1472 'name' =>
'{DAV:}description',
1474 'attributes' => [
'xml:lang' =>
'en']
1478 $writer->endElement();
1483 $writer->endElement();
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());
1505 if ($report->applyToPrincipalCollectionSet) {
1508 if ($this->server->getHttpDepth(
'0') !== 0) {
1512 $report->searchProperties,
1513 $report->properties,
1518 $prefer = $this->server->getHTTPPrefer();
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'));
1546 if ($this->server->getHTTPDepth(0) !== 0) {
1547 throw new BadRequest(
'The {DAV:}acl-principal-prop-set REPORT only supports Depth 0');
1553 $acl = $this->server->getProperties(
$path,
'{DAV:}acl');
1555 if (!$acl || !isset($acl[
'{DAV:}acl'])) {
1556 throw new Forbidden(
'Could not fetch ACL rules for this path');
1560 foreach ($acl[
'{DAV:}acl']->getPrivileges() as $ace) {
1562 if ($ace[
'principal'][0] ===
'{') {
1567 $principals[] = $ace[
'principal'];
1571 $properties = $this->server->getPropertiesForMultiplePaths(
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)
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" /> 1631 'description' =>
'Adds support for WebDAV ACL (rfc3744)',
1632 'link' =>
'http://sabre.io/dav/acl/',
This exception is thrown when a user tries to set a privilege that's marked as abstract.
This interface represents a HTTP response.
The RequestInterface represents a HTTP request.
getAcl($node)
Returns the full ACL list.
The baseclass for all server plugins.
This class represents a set of properties that are going to be updated.
foreach($paths as $path) $request
The Request class represents a single HTTP request.
getBodyAsString()
Returns the body as a string.
getPluginInfo()
Returns a bunch of meta-data about the plugin.
aclPrincipalPropSetReport($path, Xml\Request\AclPrincipalPropSetReport $report)
aclPrincipalPropSet REPORT
report($reportName, $report, $path)
This method handles HTTP REPORT requests.
split($path)
Returns the 'dirname' and 'basename' for a path.
Principal Collection interface.
const R_RECURSIVE
Recursion constants.
This class holds all the information about a PROPFIND request.
propFind(DAV\PropFind $propFind, DAV\INode $node)
Triggered before properties are looked up in specific nodes.
propPatch($path, DAV\PropPatch $propPatch)
This method intercepts PROPPATCH methods and make sure the group-member-set is updated correctly...
principalMatchesPrincipal($checkPrincipal, $currentPrincipal=null)
Find out of a principal equals another principal.
This exception is thrown when a client attempts to set conflicting permissions.
beforeMethod(RequestInterface $request, ResponseInterface $response)
Triggered before any method is handled.
$principalMembershipCache
The ICollection Interface.
getSupportedReportSet($uri)
Returns a list of reports this plugin supports.
httpAcl(RequestInterface $request, ResponseInterface $response)
This method is responsible for handling the 'ACL' event.
beforeUnlock($uri, DAV\Locks\LockInfo $lock)
Triggered before a node is unlocked.
getSupportedPrivilegeSet($node)
Returns a tree of supported privileges for a resource.
getDefaultAcl()
Returns the default ACL rules.
const R_RECURSIVEPARENTS
Recursion constants.
getFeatures()
Returns a list of features added by this plugin.
getCurrentUserPrincipals()
Returns a list of principals that's associated to the current user, either directly or through group ...
beforeUnbind($uri)
Triggered before a node is deleted.
setStatus($status)
Sets the HTTP status code.
beforeBind($uri)
Triggered before a new node is created.
$principalSearchPropertySet
getPrincipalByUri($uri)
Returns a principal based on its uri.
principalPropertySearchReport($path, Xml\Request\PrincipalPropertySearchReport $report)
principalPropertySearchReport
const R_PARENT
Recursion constants.
This class represents a single HTTP response.
expandProperties($path, array $requestedProperties, $depth)
This method expands all the properties and returns a list with property values.
expandPropertyReport($path, $report)
The expand-property report is defined in RFC3253 section 3.8.
getFlatPrivilegeSet($node)
Returns the supported privilege set as a flat list.
setDefaultAcl(array $acl)
Sets the default ACL rules.
The INode interface is the base interface, and the parent class of both ICollection and IFile...
WebDAV {DAV:}response parser.
principalSearchPropertySetReport($path, $report)
principalSearchPropertySetReport
getPluginName()
Returns a plugin name.
$allowUnauthenticatedAccess
getPath()
Returns the relative path.
getMethod()
Returns the current HTTP method.
principalMatchReport($path, Xml\Request\PrincipalMatchReport $report)
The principal-match report is defined in RFC3744, section 9.3.
getMethods($uri)
Returns a list of available methods for a given url.
checkPrivileges($uri, $privileges, $recursion=self::R_PARENT, $throwExceptions=true)
Checks if the current user has the specified privilege(s).
SupportedPrivilegeSet property.
principalSearch(array $searchProperties, array $requestedProperties, $collectionUri=null, $test='allof')
Principal property search.
If a client tried to set a privilege assigned to a non-existent principal, this exception will be thr...
initialize(DAV\Server $server)
Sets up the plugin.
getCurrentUserPrivilegeSet($node)
Returns a list of privileges the current user has on a particular node.
AclRestrictions property.
This class represents the {DAV:}acl property.
If a client tried to set a privilege that doesn't exist, this exception will be thrown.
htmlActionsPanel(DAV\INode $node, &$output)
This method is used to generate HTML output for the DAV.
getPrincipalMembership($mainPrincipal)
Returns all the principal groups the specified principal is a member of.