From 66effbfe0827fc61fff6d248797a894213ad20d6 Mon Sep 17 00:00:00 2001 From: Mario Vavti Date: Sat, 28 May 2016 17:46:24 +0200 Subject: upgrade to sabre32 --- vendor/sabre/dav/lib/DAVACL/ACLTrait.php | 100 ++++ .../dav/lib/DAVACL/AbstractPrincipalCollection.php | 2 +- vendor/sabre/dav/lib/DAVACL/FS/Collection.php | 52 +- vendor/sabre/dav/lib/DAVACL/FS/File.php | 49 +- vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php | 72 +-- vendor/sabre/dav/lib/DAVACL/IACL.php | 1 - vendor/sabre/dav/lib/DAVACL/Plugin.php | 614 ++++++++++++++++----- vendor/sabre/dav/lib/DAVACL/Principal.php | 71 +-- .../sabre/dav/lib/DAVACL/PrincipalBackend/PDO.php | 2 +- .../sabre/dav/lib/DAVACL/PrincipalCollection.php | 57 +- .../DAVACL/Xml/Property/SupportedPrivilegeSet.php | 20 +- .../Xml/Request/AclPrincipalPropSetReport.php | 67 +++ .../DAVACL/Xml/Request/PrincipalMatchReport.php | 107 ++++ 13 files changed, 771 insertions(+), 443 deletions(-) create mode 100644 vendor/sabre/dav/lib/DAVACL/ACLTrait.php create mode 100644 vendor/sabre/dav/lib/DAVACL/Xml/Request/AclPrincipalPropSetReport.php create mode 100644 vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalMatchReport.php (limited to 'vendor/sabre/dav/lib/DAVACL') diff --git a/vendor/sabre/dav/lib/DAVACL/ACLTrait.php b/vendor/sabre/dav/lib/DAVACL/ACLTrait.php new file mode 100644 index 000000000..602654a2e --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/ACLTrait.php @@ -0,0 +1,100 @@ + '{DAV:}all', + 'principal' => '{DAV:}owner', + 'protected' => true, + ] + ]; + + } + + /** + * Updates the ACL + * + * This method will receive a list of new ACE's as an array argument. + * + * @param array $acl + * @return void + */ + function setACL(array $acl) { + + throw new \Sabre\DAV\Exception\Forbidden('Setting ACL is not supported on this node'); + } + + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + function getSupportedPrivilegeSet() { + + return null; + + } + +} diff --git a/vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php b/vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php index 460f78981..9d2026380 100644 --- a/vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php +++ b/vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php @@ -110,7 +110,7 @@ abstract class AbstractPrincipalCollection extends DAV\Collection implements IPr * * @param string $name * @throws DAV\Exception\NotFound - * @return IPrincipal + * @return DAV\INode */ function getChild($name) { diff --git a/vendor/sabre/dav/lib/DAVACL/FS/Collection.php b/vendor/sabre/dav/lib/DAVACL/FS/Collection.php index 5fab4768c..1c08b43b1 100644 --- a/vendor/sabre/dav/lib/DAVACL/FS/Collection.php +++ b/vendor/sabre/dav/lib/DAVACL/FS/Collection.php @@ -3,6 +3,7 @@ namespace Sabre\DAVACL\FS; use Sabre\DAV\FSExt\Directory as BaseCollection; +use Sabre\DAVACL\ACLTrait; use Sabre\DAVACL\IACL; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; @@ -16,6 +17,8 @@ use Sabre\DAV\Exception\NotFound; */ class Collection extends BaseCollection implements IACL { + use ACLTrait; + /** * A list of ACL rules. * @@ -52,8 +55,8 @@ class Collection extends BaseCollection implements IACL { * exist. * * @param string $name - * @throws DAV\Exception\NotFound - * @return DAV\INode + * @throws NotFound + * @return \Sabre\DAV\INode */ function getChild($name) { @@ -87,19 +90,6 @@ class Collection extends BaseCollection implements IACL { } - /** - * Returns a group principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - function getGroup() { - - return null; - - } - /** * Returns a list of ACE's for this node. * @@ -118,36 +108,4 @@ class Collection extends BaseCollection implements IACL { } - /** - * Updates the ACL - * - * This method will receive a list of new ACE's as an array argument. - * - * @param array $acl - * @return void - */ - function setACL(array $acl) { - - throw new Forbidden('Setting ACL is not allowed here'); - - } - - /** - * Returns the list of supported privileges for this node. - * - * The returned data structure is a list of nested privileges. - * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple - * standard structure. - * - * If null is returned from this method, the default privilege set is used, - * which is fine for most common usecases. - * - * @return array|null - */ - function getSupportedPrivilegeSet() { - - return null; - - } - } diff --git a/vendor/sabre/dav/lib/DAVACL/FS/File.php b/vendor/sabre/dav/lib/DAVACL/FS/File.php index 0d549528b..387597bf7 100644 --- a/vendor/sabre/dav/lib/DAVACL/FS/File.php +++ b/vendor/sabre/dav/lib/DAVACL/FS/File.php @@ -4,7 +4,7 @@ namespace Sabre\DAVACL\FS; use Sabre\DAV\FSExt\File as BaseFile; use Sabre\DAVACL\IACL; -use Sabre\DAV\Exception\Forbidden; +use Sabre\DAVACL\ACLTrait; /** * This is an ACL-enabled file node. @@ -15,6 +15,8 @@ use Sabre\DAV\Exception\Forbidden; */ class File extends BaseFile implements IACL { + use ACLTrait; + /** * A list of ACL rules. * @@ -57,19 +59,6 @@ class File extends BaseFile implements IACL { } - /** - * Returns a group principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - function getGroup() { - - return null; - - } - /** * Returns a list of ACE's for this node. * @@ -88,36 +77,4 @@ class File extends BaseFile implements IACL { } - /** - * Updates the ACL - * - * This method will receive a list of new ACE's as an array argument. - * - * @param array $acl - * @return void - */ - function setACL(array $acl) { - - throw new Forbidden('Setting ACL is not allowed here'); - - } - - /** - * Returns the list of supported privileges for this node. - * - * The returned data structure is a list of nested privileges. - * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple - * standard structure. - * - * If null is returned from this method, the default privilege set is used, - * which is fine for most common usecases. - * - * @return array|null - */ - function getSupportedPrivilegeSet() { - - return null; - - } - } diff --git a/vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php b/vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php index c27616770..9e21353ea 100644 --- a/vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php +++ b/vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php @@ -2,8 +2,8 @@ namespace Sabre\DAVACL\FS; -use Sabre\DAV\Exception\Forbidden; use Sabre\DAVACL\AbstractPrincipalCollection; +use Sabre\DAVACL\ACLTrait; use Sabre\DAVACL\IACL; use Sabre\DAVACL\PrincipalBackend\BackendInterface; use Sabre\Uri; @@ -21,6 +21,8 @@ use Sabre\Uri; */ class HomeCollection extends AbstractPrincipalCollection implements IACL { + use ACLTrait; + /** * Name of this collection. * @@ -70,20 +72,15 @@ class HomeCollection extends AbstractPrincipalCollection implements IACL { * supplied by the authentication backend. * * @param array $principalInfo - * @return void + * @return \Sabre\DAVACL\INode */ function getChildForPrincipal(array $principalInfo) { $owner = $principalInfo['uri']; $acl = [ [ - 'privilege' => '{DAV:}read', - 'principal' => $owner, - 'protected' => true, - ], - [ - 'privilege' => '{DAV:}write', - 'principal' => $owner, + 'privilege' => '{DAV:}all', + 'principal' => '{DAV:}owner', 'protected' => true, ], ]; @@ -103,31 +100,6 @@ class HomeCollection extends AbstractPrincipalCollection implements IACL { } - /** - * Returns the owner principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - function getOwner() { - - return null; - - } - - /** - * Returns a group principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - function getGroup() { - - return null; - - } /** * Returns a list of ACE's for this node. @@ -153,36 +125,4 @@ class HomeCollection extends AbstractPrincipalCollection implements IACL { } - /** - * Updates the ACL - * - * This method will receive a list of new ACE's as an array argument. - * - * @param array $acl - * @return void - */ - function setACL(array $acl) { - - throw new Forbidden('Setting ACL is not allowed here'); - - } - - /** - * Returns the list of supported privileges for this node. - * - * The returned data structure is a list of nested privileges. - * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple - * standard structure. - * - * If null is returned from this method, the default privilege set is used, - * which is fine for most common usecases. - * - * @return array|null - */ - function getSupportedPrivilegeSet() { - - return null; - - } - } diff --git a/vendor/sabre/dav/lib/DAVACL/IACL.php b/vendor/sabre/dav/lib/DAVACL/IACL.php index 81908d08f..f7a138665 100644 --- a/vendor/sabre/dav/lib/DAVACL/IACL.php +++ b/vendor/sabre/dav/lib/DAVACL/IACL.php @@ -71,5 +71,4 @@ interface IACL extends DAV\INode { */ function getSupportedPrivilegeSet(); - } diff --git a/vendor/sabre/dav/lib/DAVACL/Plugin.php b/vendor/sabre/dav/lib/DAVACL/Plugin.php index 601dffecc..59a7b0922 100644 --- a/vendor/sabre/dav/lib/DAVACL/Plugin.php +++ b/vendor/sabre/dav/lib/DAVACL/Plugin.php @@ -4,7 +4,11 @@ namespace Sabre\DAVACL; use Sabre\DAV; use Sabre\DAV\INode; +use Sabre\DAV\Xml\Property\Href; use Sabre\DAV\Exception\BadRequest; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\Exception\NotAuthenticated; +use Sabre\DAVACL\Exception\NeedPrivileges; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; use Sabre\Uri; @@ -63,18 +67,6 @@ class Plugin extends DAV\ServerPlugin { 'principals', ]; - /** - * By default ACL is only enforced for nodes that have ACL support (the - * ones that implement IACL). For any other node, access is - * always granted. - * - * To override this behaviour you can turn this setting off. This is useful - * if you plan to fully support ACL in the entire tree. - * - * @var bool - */ - public $allowAccessToNodesWithoutACL = true; - /** * By default nodes that are inaccessible by the user, can still be seen * in directory listings (PROPFIND on parent with Depth: 1) @@ -108,6 +100,18 @@ class Plugin extends DAV\ServerPlugin { */ public $adminPrincipals = []; + /** + * The ACL plugin allows privileges to be assigned to users that are not + * logged in. To facilitate that, it modifies the auth plugin's behavior + * to only require login when a privileged operation was denied. + * + * Unauthenticated access can be considered a security concern, so it's + * possible to turn this feature off to harden the server's security. + * + * @var bool + */ + public $allowUnauthenticatedAccess = true; + /** * Returns a list of features added by this plugin. * @@ -161,6 +165,7 @@ class Plugin extends DAV\ServerPlugin { return [ '{DAV:}expand-property', + '{DAV:}principal-match', '{DAV:}principal-property-search', '{DAV:}principal-search-property-set', ]; @@ -179,7 +184,8 @@ class Plugin extends DAV\ServerPlugin { * @param array|string $privileges * @param int $recursion * @param bool $throwExceptions if set to false, this method won't throw exceptions. - * @throws Sabre\DAVACL\Exception\NeedPrivileges + * @throws NeedPrivileges + * @throws NotAuthenticated * @return bool */ function checkPrivileges($uri, $privileges, $recursion = self::R_PARENT, $throwExceptions = true) { @@ -188,18 +194,6 @@ class Plugin extends DAV\ServerPlugin { $acl = $this->getCurrentUserPrivilegeSet($uri); - if (is_null($acl)) { - if ($this->allowAccessToNodesWithoutACL) { - return true; - } else { - if ($throwExceptions) - throw new Exception\NeedPrivileges($uri, $privileges); - else - return false; - - } - } - $failed = []; foreach ($privileges as $priv) { @@ -210,10 +204,22 @@ class Plugin extends DAV\ServerPlugin { } if ($failed) { - if ($throwExceptions) - throw new Exception\NeedPrivileges($uri, $failed); - else + if ($this->allowUnauthenticatedAccess && is_null($this->getCurrentUserPrincipal())) { + // We are not authenticated. Kicking in the Auth plugin. + $authPlugin = $this->server->getPlugin('auth'); + $reasons = $authPlugin->getLoginFailedReasons(); + $authPlugin->challenge( + $this->server->httpRequest, + $this->server->httpResponse + ); + throw new notAuthenticated(implode(', ', $reasons) . '. Login was needed for privilege: ' . implode(', ', $failed) . ' on ' . $uri); + } + if ($throwExceptions) { + + throw new NeedPrivileges($uri, $failed); + } else { return false; + } } return true; @@ -229,10 +235,11 @@ class Plugin extends DAV\ServerPlugin { */ function getCurrentUserPrincipal() { - $authPlugin = $this->server->getPlugin('auth'); - if (is_null($authPlugin)) return null; /** @var $authPlugin Sabre\DAV\Auth\Plugin */ - + $authPlugin = $this->server->getPlugin('auth'); + if (!$authPlugin) { + return null; + } return $authPlugin->getCurrentPrincipal(); } @@ -257,6 +264,56 @@ class Plugin extends DAV\ServerPlugin { } + /** + * Sets the default ACL rules. + * + * These rules are used for all nodes that don't implement the IACL interface. + * + * @param array $acl + * @return void + */ + function setDefaultAcl(array $acl) { + + $this->defaultAcl = $acl; + + } + + /** + * Returns the default ACL rules. + * + * These rules are used for all nodes that don't implement the IACL interface. + * + * @param array $acl + * @return void + */ + function getDefaultAcl() { + + return $this->defaultAcl; + + } + + /** + * The default ACL rules. + * + * These rules are used for nodes that don't implement IACL. These default + * set of rules allow anyone to do anything, as long as they are + * authenticated. + * + * var array + */ + protected $defaultAcl = [ + [ + 'principal' => '{DAV:}authenticated', + 'protected' => true, + 'privilege' => '{DAV:}all', + ], + [ + 'principal' => '{DAV:}unauthenticated', + 'protected' => true, + 'privilege' => '{DAV:}read', + ], + ]; + /** * This array holds a cache for all the principals that are associated with * a single principal. @@ -311,13 +368,77 @@ class Plugin extends DAV\ServerPlugin { } /** - * Returns the supported privilege structure for this ACL plugin. + * Find out of a principal equals another principal. + * + * This is a quick way to find out wether a principal URI is part of a + * group, or any subgroups. * - * See RFC3744 for more details. Currently we default on a simple, - * standard structure. + * The first argument is the principal URI you want to check against. For + * example the principal group, and the second argument is the principal of + * which you want to find out of it is the same as the first principal, or + * in a member of the first principal's group or subgroups. * - * You can either get the list of privileges by a uri (path) or by - * specifying a Node. + * So the arguments are not interchangable. If principal A is in group B, + * passing 'B', 'A' will yield true, but 'A', 'B' is false. + * + * If the sceond argument is not passed, we will use the current user + * principal. + * + * @param string $checkPrincipal + * @param string $currentPrincipal + * @return bool + */ + function principalMatchesPrincipal($checkPrincipal, $currentPrincipal = null) { + + if (is_null($currentPrincipal)) { + $currentPrincipal = $this->getCurrentUserPrincipal(); + } + if ($currentPrincipal === $checkPrincipal) { + return true; + } + return in_array( + $checkPrincipal, + $this->getPrincipalMembership($currentPrincipal) + ); + + } + + + /** + * Returns a tree of supported privileges for a resource. + * + * The returned array structure should be in this form: + * + * [ + * [ + * 'privilege' => '{DAV:}read', + * 'abstract' => false, + * 'aggregates' => [] + * ] + * ] + * + * Privileges can be nested using "aggregrates". Doing so means that + * if you assign someone the aggregrating privilege, all the + * sub-privileges will automatically be granted. + * + * Marking a privilege as abstract means that the privilege cannot be + * directly assigned, but must be assigned via the parent privilege. + * + * So a more complex version might look like this: + * + * [ + * [ + * 'privilege' => '{DAV:}read', + * 'abstract' => false, + * 'aggregates' => [ + * [ + * 'privilege' => '{DAV:}read-acl', + * 'abstract' => false, + * 'aggregates' => [], + * ] + * ] + * ] + * ] * * @param string|INode $node * @return array @@ -328,73 +449,71 @@ class Plugin extends DAV\ServerPlugin { $node = $this->server->tree->getNodeForPath($node); } + $supportedPrivileges = null; if ($node instanceof IACL) { - $result = $node->getSupportedPrivilegeSet(); - - if ($result) - return $result; + $supportedPrivileges = $node->getSupportedPrivilegeSet(); } - return self::getDefaultSupportedPrivilegeSet(); + if (is_null($supportedPrivileges)) { - } - - /** - * Returns a fairly standard set of privileges, which may be useful for - * other systems to use as a basis. - * - * @return array - */ - static function getDefaultSupportedPrivilegeSet() { - - return [ - 'privilege' => '{DAV:}all', - 'abstract' => true, - 'aggregates' => [ - [ - 'privilege' => '{DAV:}read', + // Default + $supportedPrivileges = [ + '{DAV:}read' => [ + 'abstract' => false, 'aggregates' => [ - [ - 'privilege' => '{DAV:}read-acl', - 'abstract' => false, + '{DAV:}read-acl' => [ + 'abstract' => false, + 'aggregates' => [], ], - [ - 'privilege' => '{DAV:}read-current-user-privilege-set', - 'abstract' => false, + '{DAV:}read-current-user-privilege-set' => [ + 'abstract' => false, + 'aggregates' => [], ], ], - ], // {DAV:}read - [ - 'privilege' => '{DAV:}write', + ], + '{DAV:}write' => [ + 'abstract' => false, 'aggregates' => [ - [ - 'privilege' => '{DAV:}write-acl', - 'abstract' => false, - ], - [ - 'privilege' => '{DAV:}write-properties', - 'abstract' => false, - ], - [ - 'privilege' => '{DAV:}write-content', - 'abstract' => false, + '{DAV:}write-properties' => [ + 'abstract' => false, + 'aggregates' => [], ], - [ - 'privilege' => '{DAV:}bind', - 'abstract' => false, + '{DAV:}write-content' => [ + 'abstract' => false, + 'aggregates' => [], ], - [ - 'privilege' => '{DAV:}unbind', - 'abstract' => false, - ], - [ - 'privilege' => '{DAV:}unlock', - 'abstract' => false, + '{DAV:}unlock' => [ + 'abstract' => false, + 'aggregates' => [], ], ], - ], // {DAV:}write - ], - ]; // {DAV:}all + ], + ]; + if ($node instanceof \Sabre\DAV\ICollection) { + $supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}bind'] = [ + 'abstract' => false, + 'aggregates' => [], + ]; + $supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}unbind'] = [ + 'abstract' => false, + 'aggregates' => [], + ]; + } + if ($node instanceof \Sabre\DAVACL\IACL) { + $supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}write-acl'] = [ + 'abstract' => false, + 'aggregates' => [], + ]; + } + + } + + $this->server->emit( + 'getSupportedPrivilegeSet', + [$node, &$supportedPrivileges] + ); + + return $supportedPrivileges; } @@ -414,35 +533,38 @@ class Plugin extends DAV\ServerPlugin { */ final function getFlatPrivilegeSet($node) { - $privs = $this->getSupportedPrivilegeSet($node); + $privs = [ + 'abstract' => false, + 'aggregates' => $this->getSupportedPrivilegeSet($node) + ]; $fpsTraverse = null; - $fpsTraverse = function($priv, $concrete, &$flat) use (&$fpsTraverse) { + $fpsTraverse = function($privName, $privInfo, $concrete, &$flat) use (&$fpsTraverse) { $myPriv = [ - 'privilege' => $priv['privilege'], - 'abstract' => isset($priv['abstract']) && $priv['abstract'], + 'privilege' => $privName, + 'abstract' => isset($privInfo['abstract']) && $privInfo['abstract'], 'aggregates' => [], - 'concrete' => isset($priv['abstract']) && $priv['abstract'] ? $concrete : $priv['privilege'], + 'concrete' => isset($privInfo['abstract']) && $privInfo['abstract'] ? $concrete : $privName, ]; - if (isset($priv['aggregates'])) { + if (isset($privInfo['aggregates'])) { - foreach ($priv['aggregates'] as $subPriv) { + foreach ($privInfo['aggregates'] as $subPrivName => $subPrivInfo) { - $myPriv['aggregates'][] = $subPriv['privilege']; + $myPriv['aggregates'][] = $subPrivName; } } - $flat[$priv['privilege']] = $myPriv; + $flat[$privName] = $myPriv; - if (isset($priv['aggregates'])) { + if (isset($privInfo['aggregates'])) { - foreach ($priv['aggregates'] as $subPriv) { + foreach ($privInfo['aggregates'] as $subPrivName => $subPrivInfo) { - $fpsTraverse($subPriv, $myPriv['concrete'], $flat); + $fpsTraverse($subPrivName, $subPrivInfo, $myPriv['concrete'], $flat); } @@ -451,7 +573,7 @@ class Plugin extends DAV\ServerPlugin { }; $flat = []; - $fpsTraverse($privs, null, $flat); + $fpsTraverse('{DAV:}all', $privs, null, $flat); return $flat; @@ -467,13 +589,13 @@ class Plugin extends DAV\ServerPlugin { * @param string|DAV\INode $node * @return array */ - function getACL($node) { + function getAcl($node) { if (is_string($node)) { $node = $this->server->tree->getNodeForPath($node); } if (!$node instanceof IACL) { - return null; + return $this->getDefaultAcl(); } $acl = $node->getACL(); foreach ($this->adminPrincipals as $adminPrincipal) { @@ -506,12 +628,10 @@ class Plugin extends DAV\ServerPlugin { $acl = $this->getACL($node); - if (is_null($acl)) return null; - - $principals = $this->getCurrentUserPrincipals(); - $collected = []; + $isAuthenticated = $this->getCurrentUserPrincipal() !== null; + foreach ($acl as $ace) { $principal = $ace['principal']; @@ -520,7 +640,7 @@ class Plugin extends DAV\ServerPlugin { case '{DAV:}owner' : $owner = $node->getOwner(); - if ($owner && in_array($owner, $principals)) { + if ($owner && $this->principalMatchesPrincipal($owner)) { $collected[] = $ace; } break; @@ -528,21 +648,25 @@ class Plugin extends DAV\ServerPlugin { // 'all' matches for every user case '{DAV:}all' : + $collected[] = $ace; + break; - // 'authenticated' matched for every user that's logged in. - // Since it's not possible to use ACL while not being logged - // in, this is also always true. case '{DAV:}authenticated' : - $collected[] = $ace; + // Authenticated users only + if ($isAuthenticated) { + $collected[] = $ace; + } break; - // 'unauthenticated' can never occur either, so we simply - // ignore these. case '{DAV:}unauthenticated' : + // Unauthenticated users only + if (!$isAuthenticated) { + $collected[] = $ace; + } break; default : - if (in_array($ace['principal'], $principals)) { + if ($this->principalMatchesPrincipal($ace['principal'])) { $collected[] = $ace; } break; @@ -561,6 +685,11 @@ class Plugin extends DAV\ServerPlugin { $current = array_pop($collected); $collected2[] = $current['privilege']; + if (!isset($flat[$current['privilege']])) { + // Ignoring privileges that are not in the supported-privileges list. + $this->server->getLogger()->debug('A node has the "' . $current['privilege'] . '" in its ACL list, but this privilege was not reported in the supportedPrivilegeSet list. This will be ignored.'); + continue; + } foreach ($flat[$current['privilege']]['aggregates'] as $subPriv) { $collected2[] = $subPriv; $collected[] = $flat[$subPriv]; @@ -587,7 +716,13 @@ class Plugin extends DAV\ServerPlugin { $collections = $this->principalCollectionSet; foreach ($collections as $collection) { - $principalCollection = $this->server->tree->getNodeForPath($collection); + try { + $principalCollection = $this->server->tree->getNodeForPath($collection); + } catch (NotFound $e) { + // Ignore and move on + continue; + } + if (!$principalCollection instanceof IPrincipalCollection) { // Not a principal collection, we're simply going to ignore // this. @@ -673,6 +808,14 @@ class Plugin extends DAV\ServerPlugin { */ function initialize(DAV\Server $server) { + if ($this->allowUnauthenticatedAccess) { + $authPlugin = $server->getPlugin('auth'); + if (!$authPlugin) { + throw new \Exception('The Auth plugin must be loaded before the ACL plugin if you want to allow unauthenticated access.'); + } + $authPlugin->autoRequireLogin = false; + } + $this->server = $server; $server->on('propFind', [$this, 'propFind'], 20); $server->on('beforeMethod', [$this, 'beforeMethod'], 20); @@ -683,6 +826,14 @@ class Plugin extends DAV\ServerPlugin { $server->on('report', [$this, 'report']); $server->on('method:ACL', [$this, 'httpAcl']); $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']); + $server->on('getPrincipalByUri', function($principal, &$uri) { + + $uri = $this->getPrincipalByUri($principal); + + // Break event chain + if ($uri) return false; + + }); array_push($server->protectedProperties, '{DAV:}alternate-URI-set', @@ -707,9 +858,11 @@ class Plugin extends DAV\ServerPlugin { // class. $server->xml->elementMap['{DAV:}group-member-set'] = 'Sabre\\DAV\\Xml\\Property\\Href'; $server->xml->elementMap['{DAV:}acl'] = 'Sabre\\DAVACL\\Xml\\Property\\Acl'; + $server->xml->elementMap['{DAV:}acl-principal-prop-set'] = 'Sabre\\DAVACL\\Xml\\Request\\AclPrincipalPropSetReport'; $server->xml->elementMap['{DAV:}expand-property'] = 'Sabre\\DAVACL\\Xml\\Request\\ExpandPropertyReport'; $server->xml->elementMap['{DAV:}principal-property-search'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalPropertySearchReport'; $server->xml->elementMap['{DAV:}principal-search-property-set'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalSearchPropertySetReport'; + $server->xml->elementMap['{DAV:}principal-match'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalMatchReport'; } @@ -743,7 +896,6 @@ class Plugin extends DAV\ServerPlugin { case 'PUT' : case 'LOCK' : - case 'UNLOCK' : // This method requires the write-content priv if the node // already exists, and bind on the parent if the node is being // created. @@ -751,6 +903,9 @@ class Plugin extends DAV\ServerPlugin { $this->checkPrivileges($path, '{DAV:}write-content'); break; + case 'UNLOCK' : + // Unlock is always allowed at the moment. + break; case 'PROPPATCH' : $this->checkPrivileges($path, '{DAV:}write-properties'); @@ -774,7 +929,6 @@ class Plugin extends DAV\ServerPlugin { // If MOVE is used beforeUnbind will also be used to check if // the sourcenode can be deleted. $this->checkPrivileges($path, '{DAV:}read', self::R_RECURSIVE); - break; } @@ -864,24 +1018,24 @@ class Plugin extends DAV\ServerPlugin { if ($node instanceof IPrincipal) { $propFind->handle('{DAV:}alternate-URI-set', function() use ($node) { - return new DAV\Xml\Property\Href($node->getAlternateUriSet()); + return new Href($node->getAlternateUriSet()); }); $propFind->handle('{DAV:}principal-URL', function() use ($node) { - return new DAV\Xml\Property\Href($node->getPrincipalUrl() . '/'); + return new Href($node->getPrincipalUrl() . '/'); }); $propFind->handle('{DAV:}group-member-set', function() use ($node) { $members = $node->getGroupMemberSet(); foreach ($members as $k => $member) { $members[$k] = rtrim($member, '/') . '/'; } - return new DAV\Xml\Property\Href($members); + return new Href($members); }); $propFind->handle('{DAV:}group-membership', function() use ($node) { $members = $node->getGroupMembership(); foreach ($members as $k => $member) { $members[$k] = rtrim($member, '/') . '/'; } - return new DAV\Xml\Property\Href($members); + return new Href($members); }); $propFind->handle('{DAV:}displayname', [$node, 'getDisplayName']); @@ -892,7 +1046,7 @@ class Plugin extends DAV\ServerPlugin { $val = $this->principalCollectionSet; // Ensuring all collections end with a slash foreach ($val as $k => $v) $val[$k] = $v . '/'; - return new DAV\Xml\Property\Href($val); + return new Href($val); }); $propFind->handle('{DAV:}current-user-principal', function() { @@ -910,9 +1064,7 @@ class Plugin extends DAV\ServerPlugin { $propFind->set('{DAV:}current-user-privilege-set', null, 403); } else { $val = $this->getCurrentUserPrivilegeSet($node); - if (!is_null($val)) { - return new Xml\Property\CurrentUserPrivilegeSet($val); - } + return new Xml\Property\CurrentUserPrivilegeSet($val); } }); $propFind->handle('{DAV:}acl', function() use ($node, $propFind, $path) { @@ -921,9 +1073,7 @@ class Plugin extends DAV\ServerPlugin { $propFind->set('{DAV:}acl', null, 403); } else { $acl = $this->getACL($node); - if (!is_null($acl)) { - return new Xml\Property\Acl($this->getACL($node)); - } + return new Xml\Property\Acl($this->getACL($node)); } }); $propFind->handle('{DAV:}acl-restrictions', function() { @@ -933,7 +1083,7 @@ class Plugin extends DAV\ServerPlugin { /* Adding ACL properties */ if ($node instanceof IACL) { $propFind->handle('{DAV:}owner', function() use ($node) { - return new DAV\Xml\Property\Href($node->getOwner() . '/'); + return new Href($node->getOwner() . '/'); }); } @@ -952,7 +1102,7 @@ class Plugin extends DAV\ServerPlugin { $propPatch->handle('{DAV:}group-member-set', function($value) use ($path) { if (is_null($value)) { $memberSet = []; - } elseif ($value instanceof DAV\Xml\Property\Href) { + } elseif ($value instanceof Href) { $memberSet = array_map( [$this->server, 'calculateUri'], $value->getHrefs() @@ -990,15 +1140,23 @@ class Plugin extends DAV\ServerPlugin { case '{DAV:}principal-property-search' : $this->server->transactionType = 'report-principal-property-search'; - $this->principalPropertySearchReport($report); + $this->principalPropertySearchReport($path, $report); return false; case '{DAV:}principal-search-property-set' : $this->server->transactionType = 'report-principal-search-property-set'; - $this->principalSearchPropertySetReport($report); + $this->principalSearchPropertySetReport($path, $report); return false; case '{DAV:}expand-property' : $this->server->transactionType = 'report-expand-property'; - $this->expandPropertyReport($report); + $this->expandPropertyReport($path, $report); + return false; + case '{DAV:}principal-match' : + $this->server->transactionType = 'report-principal-match'; + $this->principalMatchReport($path, $report); + return false; + case '{DAV:}acl-principal-prop-set' : + $this->server->transactionType = 'acl-principal-prop-set'; + $this->aclPrincipalPropSetReport($path, $report); return false; } @@ -1073,7 +1231,7 @@ class Plugin extends DAV\ServerPlugin { // Looking up the principal try { $principal = $this->server->tree->getNodeForPath($newAce['principal']); - } catch (DAV\Exception\NotFound $e) { + } catch (NotFound $e) { throw new Exception\NotRecognizedPrincipal('The specified principal (' . $newAce['principal'] . ') does not exist'); } if (!($principal instanceof IPrincipal)) { @@ -1095,7 +1253,110 @@ class Plugin extends DAV\ServerPlugin { /* Reports {{{ */ /** - * The expand-property report is defined in RFC3253 section 3-8. + * The principal-match report is defined in RFC3744, section 9.3. + * + * This report allows a client to figure out based on the current user, + * or a principal URL, the principal URL and principal URLs of groups that + * principal belongs to. + * + * @param string $path + * @param Xml\Request\PrincipalMatchReport $report + * @return void + */ + protected function principalMatchReport($path, Xml\Request\PrincipalMatchReport $report) { + + $depth = $this->server->getHTTPDepth(0); + if ($depth !== 0) { + throw new BadRequest('The principal-match report is only defined on Depth: 0'); + } + + $currentPrincipals = $this->getCurrentUserPrincipals(); + + $result = []; + + if ($report->type === Xml\Request\PrincipalMatchReport::SELF) { + + // Finding all principals under the request uri that match the + // current principal. + foreach ($currentPrincipals as $currentPrincipal) { + + if ($currentPrincipal === $path || strpos($currentPrincipal, $path . '/') === 0) { + $result[] = $currentPrincipal; + } + + } + + } else { + + // We need to find all resources that have a property that matches + // one of the current principals. + $candidates = $this->server->getPropertiesForPath( + $path, + [$report->principalProperty], + 1 + ); + + foreach ($candidates as $candidate) { + + if (!isset($candidate[200][$report->principalProperty])) { + continue; + } + + $hrefs = $candidate[200][$report->principalProperty]; + + if (!$hrefs instanceof Href) { + continue; + } + + foreach ($hrefs->getHrefs() as $href) { + if (in_array(trim($href, '/'), $currentPrincipals)) { + $result[] = $candidate['href']; + continue 2; + } + } + } + + } + + $responses = []; + + foreach ($result as $item) { + + $properties = []; + + if ($report->properties) { + + $foo = $this->server->getPropertiesForPath($item, $report->properties); + $foo = $foo[0]; + $item = $foo['href']; + unset($foo['href']); + $properties = $foo; + + } + + $responses[] = new DAV\Xml\Element\Response( + $item, + $properties, + '200' + ); + + } + + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setBody( + $this->server->xml->write( + '{DAV:}multistatus', + $responses, + $this->server->getBaseUri() + ) + ); + + + } + + /** + * The expand-property report is defined in RFC3253 section 3.8. * * This report is very similar to a standard PROPFIND. The difference is * that it has the additional ability to look at properties containing a @@ -1105,15 +1366,15 @@ class Plugin extends DAV\ServerPlugin { * Other rfc's, such as ACL rely on this report, so it made sense to put * it in this plugin. * + * @param string $path * @param Xml\Request\ExpandPropertyReport $report * @return void */ - protected function expandPropertyReport($report) { + protected function expandPropertyReport($path, $report) { $depth = $this->server->getHTTPDepth(0); - $requestUri = $this->server->getRequestUri(); - $result = $this->expandProperties($requestUri, $report->properties, $depth); + $result = $this->expandProperties($path, $report->properties, $depth); $xml = $this->server->xml->write( '{DAV:}multistatus', @@ -1187,10 +1448,11 @@ class Plugin extends DAV\ServerPlugin { * of properties the client may search on, using the * {DAV:}principal-property-search report. * + * @param string $path * @param Xml\Request\PrincipalSearchPropertySetReport $report * @return void */ - protected function principalSearchPropertySetReport($report) { + protected function principalSearchPropertySetReport($path, $report) { $httpDepth = $this->server->getHTTPDepth(0); if ($httpDepth !== 0) { @@ -1241,14 +1503,14 @@ class Plugin extends DAV\ServerPlugin { * clients to search for groups of principals, based on the value of one * or more properties. * + * @param string $path * @param Xml\Request\PrincipalPropertySearchReport $report * @return void */ - protected function principalPropertySearchReport($report) { + protected function principalPropertySearchReport($path, Xml\Request\PrincipalPropertySearchReport $report) { - $uri = null; - if (!$report->applyToPrincipalCollectionSet) { - $uri = $this->server->httpRequest->getPath(); + if ($report->applyToPrincipalCollectionSet) { + $path = null; } if ($this->server->getHttpDepth('0') !== 0) { throw new BadRequest('Depth must be 0'); @@ -1256,7 +1518,7 @@ class Plugin extends DAV\ServerPlugin { $result = $this->principalSearch( $report->searchProperties, $report->properties, - $uri, + $path, $report->test ); @@ -1269,6 +1531,64 @@ class Plugin extends DAV\ServerPlugin { } + /** + * aclPrincipalPropSet REPORT + * + * This method is responsible for handling the {DAV:}acl-principal-prop-set + * REPORT, as defined in: + * + * https://tools.ietf.org/html/rfc3744#section-9.2 + * + * This REPORT allows a user to quickly fetch information about all + * principals specified in the access control list. Most commonly this + * is used to for example generate a UI with ACL rules, allowing you + * to show names for principals for every entry. + * + * @param string $path + * @param Xml\Request\AclPrincipalPropSetReport $report + * @return void + */ + protected function aclPrincipalPropSetReport($path, Xml\Request\AclPrincipalPropSetReport $report) { + + if ($this->server->getHTTPDepth(0) !== 0) { + throw new BadRequest('The {DAV:}acl-principal-prop-set REPORT only supports Depth 0'); + } + + // Fetching ACL rules for the given path. We're using the property + // API and not the local getACL, because it will ensure that all + // business rules and restrictions are applied. + $acl = $this->server->getProperties($path, '{DAV:}acl'); + + if (!$acl || !isset($acl['{DAV:}acl'])) { + throw new Forbidden('Could not fetch ACL rules for this path'); + } + + $principals = []; + foreach ($acl['{DAV:}acl']->getPrivileges() as $ace) { + + if ($ace['principal'][0] === '{') { + // It's not a principal, it's one of the special rules such as {DAV:}authenticated + continue; + } + + $principals[] = $ace['principal']; + + } + + $properties = $this->server->getPropertiesForMultiplePaths( + $principals, + $report->properties + ); + + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setBody( + $this->server->generateMultiStatus($properties) + ); + + } + + /* }}} */ /** diff --git a/vendor/sabre/dav/lib/DAVACL/Principal.php b/vendor/sabre/dav/lib/DAVACL/Principal.php index 16375d3fc..6ebb30907 100644 --- a/vendor/sabre/dav/lib/DAVACL/Principal.php +++ b/vendor/sabre/dav/lib/DAVACL/Principal.php @@ -22,6 +22,8 @@ use Sabre\HTTP\URLUtil; */ class Principal extends DAV\Node implements IPrincipal, DAV\IProperties, IACL { + use ACLTrait; + /** * Struct with principal information. * @@ -216,73 +218,4 @@ class Principal extends DAV\Node implements IPrincipal, DAV\IProperties, IACL { } - /** - * Returns a group principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - function getGroup() { - - return null; - - } - - /** - * Returns a list of ACE's for this node. - * - * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are - * currently the only supported privileges - * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array - */ - function getACL() { - - return [ - [ - 'privilege' => '{DAV:}read', - 'principal' => '{DAV:}authenticated', - 'protected' => true, - ], - ]; - - } - - /** - * Updates the ACL - * - * This method will receive a list of new ACE's. - * - * @param array $acl - * @return void - */ - function setACL(array $acl) { - - throw new DAV\Exception\MethodNotAllowed('Updating ACLs is not allowed here'); - - } - - /** - * Returns the list of supported privileges for this node. - * - * The returned data structure is a list of nested privileges. - * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple - * standard structure. - * - * If null is returned from this method, the default privilege set is used, - * which is fine for most common usecases. - * - * @return array|null - */ - function getSupportedPrivilegeSet() { - - return null; - - } - } diff --git a/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/PDO.php b/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/PDO.php index 01b7a175c..a491dc88f 100644 --- a/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/PDO.php +++ b/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/PDO.php @@ -300,7 +300,7 @@ class PDO extends AbstractBackend implements CreatePrincipalSupport { $value = null; $scheme = null; list($scheme, $value) = explode(":", $uri, 2); - if ($value == null) return null; + if (empty($value)) return null; $uri = null; switch ($scheme){ diff --git a/vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php b/vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php index 54911e7b5..d8a90153a 100644 --- a/vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php +++ b/vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php @@ -3,7 +3,6 @@ namespace Sabre\DAVACL; use Sabre\DAV\Exception\InvalidResourceType; -use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\IExtendedCollection; use Sabre\DAV\MkCol; @@ -19,6 +18,8 @@ use Sabre\DAV\MkCol; */ class PrincipalCollection extends AbstractPrincipalCollection implements IExtendedCollection, IACL { + use ACLTrait; + /** * This method returns a node for a principal. * @@ -72,28 +73,6 @@ class PrincipalCollection extends AbstractPrincipalCollection implements IExtend } - /** - * Returns the owner principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - function getOwner() { - return null; - } - - /** - * Returns a group principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - function getGroup() { - return null; - } - /** * Returns a list of ACE's for this node. * @@ -116,36 +95,4 @@ class PrincipalCollection extends AbstractPrincipalCollection implements IExtend ]; } - /** - * Updates the ACL - * - * This method will receive a list of new ACE's as an array argument. - * - * @param array $acl - * @return void - */ - function setACL(array $acl) { - - throw new Forbidden('Updating ACLs is not allowed on this node'); - - } - - /** - * Returns the list of supported privileges for this node. - * - * The returned data structure is a list of nested privileges. - * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple - * standard structure. - * - * If null is returned from this method, the default privilege set is used, - * which is fine for most common usecases. - * - * @return array|null - */ - function getSupportedPrivilegeSet() { - - return null; - - } - } diff --git a/vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php b/vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php index 572bed4dd..55e7783ae 100644 --- a/vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php +++ b/vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php @@ -73,7 +73,7 @@ class SupportedPrivilegeSet implements XmlSerializable, HtmlOutput { */ function xmlSerialize(Writer $writer) { - $this->serializePriv($writer, $this->privileges); + $this->serializePriv($writer, '{DAV:}all', [ 'aggregates' => $this->privileges]); } @@ -93,9 +93,9 @@ class SupportedPrivilegeSet implements XmlSerializable, HtmlOutput { */ function toHtml(HtmlOutputHelper $html) { - $traverse = function($priv) use (&$traverse, $html) { + $traverse = function($privName, $priv) use (&$traverse, $html) { echo "
  • "; - echo $html->xmlName($priv['privilege']); + echo $html->xmlName($privName); if (isset($priv['abstract']) && $priv['abstract']) { echo " (abstract)"; } @@ -104,8 +104,8 @@ class SupportedPrivilegeSet implements XmlSerializable, HtmlOutput { } if (isset($priv['aggregates'])) { echo "\n"; } @@ -114,7 +114,7 @@ class SupportedPrivilegeSet implements XmlSerializable, HtmlOutput { ob_start(); echo "\n"; return ob_get_clean(); @@ -132,12 +132,12 @@ class SupportedPrivilegeSet implements XmlSerializable, HtmlOutput { * @param array $privilege * @return void */ - private function serializePriv(Writer $writer, $privilege) { + private function serializePriv(Writer $writer, $privName, $privilege) { $writer->startElement('{DAV:}supported-privilege'); $writer->startElement('{DAV:}privilege'); - $writer->writeElement($privilege['privilege']); + $writer->writeElement($privName); $writer->endElement(); // privilege if (!empty($privilege['abstract'])) { @@ -147,8 +147,8 @@ class SupportedPrivilegeSet implements XmlSerializable, HtmlOutput { $writer->writeElement('{DAV:}description', $privilege['description']); } if (isset($privilege['aggregates'])) { - foreach ($privilege['aggregates'] as $subPrivilege) { - $this->serializePriv($writer, $subPrivilege); + foreach ($privilege['aggregates'] as $subPrivName => $subPrivilege) { + $this->serializePriv($writer, $subPrivName, $subPrivilege); } } diff --git a/vendor/sabre/dav/lib/DAVACL/Xml/Request/AclPrincipalPropSetReport.php b/vendor/sabre/dav/lib/DAVACL/Xml/Request/AclPrincipalPropSetReport.php new file mode 100644 index 000000000..f01c1e6ab --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/Xml/Request/AclPrincipalPropSetReport.php @@ -0,0 +1,67 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $reader->pushContext(); + $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Deserializer\enum'; + + $elems = Deserializer\keyValue( + $reader, + 'DAV:' + ); + + $reader->popContext(); + + $report = new self(); + + if (!empty($elems['prop'])) { + $report->properties = $elems['prop']; + } + + return $report; + + } + +} diff --git a/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalMatchReport.php b/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalMatchReport.php new file mode 100644 index 000000000..5c4e88189 --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalMatchReport.php @@ -0,0 +1,107 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $reader->pushContext(); + $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Deserializer\enum'; + + $elems = Deserializer\keyValue( + $reader, + 'DAV:' + ); + + $reader->popContext(); + + $principalMatch = new self(); + + if (array_key_exists('self', $elems)) { + $principalMatch->type = self::SELF; + } + + if (array_key_exists('principal-property', $elems)) { + $principalMatch->type = self::PRINCIPAL_PROPERTY; + $principalMatch->principalProperty = $elems['principal-property'][0]['name']; + } + + if (!empty($elems['prop'])) { + $principalMatch->properties = $elems['prop']; + } + + return $principalMatch; + + } + +} -- cgit v1.2.3