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