Plugin.php 42 KB

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