2013-10-21 22:46:31 +00:00
< ? php
namespace Sabre\DAVACL ;
2016-05-11 00:26:44 +00:00
2013-10-21 22:46:31 +00:00
use Sabre\DAV ;
2016-05-11 00:26:44 +00:00
use Sabre\DAV\INode ;
2016-05-28 15:46:24 +00:00
use Sabre\DAV\Xml\Property\Href ;
2016-05-11 00:26:44 +00:00
use Sabre\DAV\Exception\BadRequest ;
2016-05-28 15:46:24 +00:00
use Sabre\DAV\Exception\NotFound ;
use Sabre\DAV\Exception\NotAuthenticated ;
use Sabre\DAVACL\Exception\NeedPrivileges ;
2016-05-11 00:26:44 +00:00
use Sabre\HTTP\RequestInterface ;
use Sabre\HTTP\ResponseInterface ;
use Sabre\Uri ;
2013-10-21 22:46:31 +00:00
/**
* SabreDAV ACL Plugin
*
* This plugin provides functionality to enforce ACL permissions .
* ACL is defined in RFC3744 .
*
* In addition it also provides support for the { DAV : } current - user - principal
* property , defined in RFC5397 and the { DAV : } expand - property report , as
* defined in RFC3253 .
*
2016-05-11 00:26:44 +00:00
* @ copyright Copyright ( C ) fruux GmbH ( https :// fruux . com / )
2013-10-21 22:46:31 +00:00
* @ author Evert Pot ( http :// evertpot . com / )
2014-06-28 20:28:08 +00:00
* @ license http :// sabre . io / license / Modified BSD License
2013-10-21 22:46:31 +00:00
*/
class Plugin extends DAV\ServerPlugin {
/**
* Recursion constants
*
* This only checks the base node
*/
const R_PARENT = 1 ;
/**
* Recursion constants
*
* This checks every node in the tree
*/
const R_RECURSIVE = 2 ;
/**
* Recursion constants
*
* This checks every parentnode in the tree , but not leaf - nodes .
*/
const R_RECURSIVEPARENTS = 3 ;
/**
* Reference to server object .
*
* @ var Sabre\DAV\Server
*/
protected $server ;
/**
* List of urls containing principal collections .
* Modify this if your principals are located elsewhere .
*
* @ var array
*/
2016-05-11 00:26:44 +00:00
public $principalCollectionSet = [
2013-10-21 22:46:31 +00:00
'principals' ,
2016-05-11 00:26:44 +00:00
];
2013-10-21 22:46:31 +00:00
/**
* By default nodes that are inaccessible by the user , can still be seen
* in directory listings ( PROPFIND on parent with Depth : 1 )
*
* In certain cases it ' s desirable to hide inaccessible nodes . Setting this
* to true will cause these nodes to be hidden from directory listings .
*
* @ var bool
*/
public $hideNodesFromListings = false ;
/**
* This list of properties are the properties a client can search on using
* the { DAV : } principal - property - search report .
*
* The keys are the property names , values are descriptions .
*
* @ var array
*/
2016-05-11 00:26:44 +00:00
public $principalSearchPropertySet = [
'{DAV:}displayname' => 'Display name' ,
2013-10-21 22:46:31 +00:00
'{http://sabredav.org/ns}email-address' => 'Email address' ,
2016-05-11 00:26:44 +00:00
];
2013-10-21 22:46:31 +00:00
/**
* Any principal uri ' s added here , will automatically be added to the list
* of ACL ' s . They will effectively receive { DAV : } all privileges , as a
* protected privilege .
*
* @ var array
*/
2016-05-11 00:26:44 +00:00
public $adminPrincipals = [];
2013-10-21 22:46:31 +00:00
2016-05-28 15:46:24 +00:00
/**
* 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 ;
2013-10-21 22:46:31 +00:00
/**
* Returns a list of features added by this plugin .
*
* This list is used in the response of a HTTP OPTIONS request .
*
* @ return array
*/
2016-05-11 00:26:44 +00:00
function getFeatures () {
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
return [ 'access-control' , 'calendarserver-principal-property-search' ];
2013-10-21 22:46:31 +00:00
}
/**
* Returns a list of available methods for a given url
*
* @ param string $uri
* @ return array
*/
2016-05-11 00:26:44 +00:00
function getMethods ( $uri ) {
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
return [ 'ACL' ];
2013-10-21 22:46:31 +00:00
}
/**
* Returns a plugin name .
*
* Using this name other plugins will be able to access other plugins
* using Sabre\DAV\Server :: getPlugin
*
* @ return string
*/
2016-05-11 00:26:44 +00:00
function getPluginName () {
2013-10-21 22:46:31 +00:00
return 'acl' ;
}
/**
* Returns a list of reports this plugin supports .
*
* This will be used in the { DAV : } supported - report - set property .
* Note that you still need to subscribe to the 'report' event to actually
* implement them
*
* @ param string $uri
* @ return array
*/
2016-05-11 00:26:44 +00:00
function getSupportedReportSet ( $uri ) {
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
return [
2013-10-21 22:46:31 +00:00
'{DAV:}expand-property' ,
2016-05-28 15:46:24 +00:00
'{DAV:}principal-match' ,
2013-10-21 22:46:31 +00:00
'{DAV:}principal-property-search' ,
'{DAV:}principal-search-property-set' ,
2016-05-11 00:26:44 +00:00
];
2013-10-21 22:46:31 +00:00
}
/**
* Checks if the current user has the specified privilege ( s ) .
*
* You can specify a single privilege , or a list of privileges .
* This method will throw an exception if the privilege is not available
* and return true otherwise .
*
* @ param string $uri
* @ param array | string $privileges
* @ param int $recursion
* @ param bool $throwExceptions if set to false , this method won ' t throw exceptions .
2016-05-28 15:46:24 +00:00
* @ throws NeedPrivileges
* @ throws NotAuthenticated
2013-10-21 22:46:31 +00:00
* @ return bool
*/
2016-05-11 00:26:44 +00:00
function checkPrivileges ( $uri , $privileges , $recursion = self :: R_PARENT , $throwExceptions = true ) {
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
if ( ! is_array ( $privileges )) $privileges = [ $privileges ];
2013-10-21 22:46:31 +00:00
$acl = $this -> getCurrentUserPrivilegeSet ( $uri );
2016-05-11 00:26:44 +00:00
$failed = [];
foreach ( $privileges as $priv ) {
2013-10-21 22:46:31 +00:00
if ( ! in_array ( $priv , $acl )) {
$failed [] = $priv ;
}
}
if ( $failed ) {
2016-05-28 15:46:24 +00:00
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 {
2013-10-21 22:46:31 +00:00
return false ;
2016-05-28 15:46:24 +00:00
}
2013-10-21 22:46:31 +00:00
}
return true ;
}
/**
* Returns the standard users ' principal .
*
* This is one authorative principal url for the current user .
* This method will return null if the user wasn ' t logged in .
*
* @ return string | null
*/
2016-05-11 00:26:44 +00:00
function getCurrentUserPrincipal () {
2013-10-21 22:46:31 +00:00
/** @var $authPlugin Sabre\DAV\Auth\Plugin */
2016-05-28 15:46:24 +00:00
$authPlugin = $this -> server -> getPlugin ( 'auth' );
if ( ! $authPlugin ) {
return null ;
}
2016-05-11 00:26:44 +00:00
return $authPlugin -> getCurrentPrincipal ();
2013-10-21 22:46:31 +00:00
}
/**
* Returns a list of principals that ' s associated to the current
* user , either directly or through group membership .
*
* @ return array
*/
2016-05-11 00:26:44 +00:00
function getCurrentUserPrincipals () {
2013-10-21 22:46:31 +00:00
$currentUser = $this -> getCurrentUserPrincipal ();
2016-05-11 00:26:44 +00:00
if ( is_null ( $currentUser )) return [];
2013-10-21 22:46:31 +00:00
return array_merge (
2016-05-11 00:26:44 +00:00
[ $currentUser ],
2013-10-21 22:46:31 +00:00
$this -> getPrincipalMembership ( $currentUser )
);
}
2016-05-28 15:46:24 +00:00
/**
* 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' ,
],
];
2013-10-21 22:46:31 +00:00
/**
* This array holds a cache for all the principals that are associated with
* a single principal .
*
* @ var array
*/
2016-05-11 00:26:44 +00:00
protected $principalMembershipCache = [];
2013-10-21 22:46:31 +00:00
/**
* Returns all the principal groups the specified principal is a member of .
*
* @ param string $principal
* @ return array
*/
2016-05-11 00:26:44 +00:00
function getPrincipalMembership ( $mainPrincipal ) {
2013-10-21 22:46:31 +00:00
// First check our cache
if ( isset ( $this -> principalMembershipCache [ $mainPrincipal ])) {
return $this -> principalMembershipCache [ $mainPrincipal ];
}
2016-05-11 00:26:44 +00:00
$check = [ $mainPrincipal ];
$principals = [];
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
while ( count ( $check )) {
2013-10-21 22:46:31 +00:00
$principal = array_shift ( $check );
$node = $this -> server -> tree -> getNodeForPath ( $principal );
if ( $node instanceof IPrincipal ) {
2016-05-11 00:26:44 +00:00
foreach ( $node -> getGroupMembership () as $groupMember ) {
2013-10-21 22:46:31 +00:00
if ( ! in_array ( $groupMember , $principals )) {
$check [] = $groupMember ;
$principals [] = $groupMember ;
}
}
}
}
// Store the result in the cache
$this -> principalMembershipCache [ $mainPrincipal ] = $principals ;
return $principals ;
}
/**
2016-05-28 15:46:24 +00:00
* 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 .
2013-10-21 22:46:31 +00:00
*
2016-05-28 15:46:24 +00:00
* 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 .
2013-10-21 22:46:31 +00:00
*
2016-05-28 15:46:24 +00:00
* 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' => [],
* ]
* ]
* ]
* ]
2013-10-21 22:46:31 +00:00
*
2016-05-11 00:26:44 +00:00
* @ param string | INode $node
2013-10-21 22:46:31 +00:00
* @ return array
*/
2016-05-11 00:26:44 +00:00
function getSupportedPrivilegeSet ( $node ) {
2013-10-21 22:46:31 +00:00
if ( is_string ( $node )) {
$node = $this -> server -> tree -> getNodeForPath ( $node );
}
2016-05-28 15:46:24 +00:00
$supportedPrivileges = null ;
2013-10-21 22:46:31 +00:00
if ( $node instanceof IACL ) {
2016-05-28 15:46:24 +00:00
$supportedPrivileges = $node -> getSupportedPrivilegeSet ();
2013-10-21 22:46:31 +00:00
}
2016-05-28 15:46:24 +00:00
if ( is_null ( $supportedPrivileges )) {
2013-10-21 22:46:31 +00:00
2016-05-28 15:46:24 +00:00
// Default
$supportedPrivileges = [
'{DAV:}read' => [
'abstract' => false ,
2016-05-11 00:26:44 +00:00
'aggregates' => [
2016-05-28 15:46:24 +00:00
'{DAV:}read-acl' => [
'abstract' => false ,
'aggregates' => [],
2016-05-11 00:26:44 +00:00
],
2016-05-28 15:46:24 +00:00
'{DAV:}read-current-user-privilege-set' => [
'abstract' => false ,
'aggregates' => [],
2016-05-11 00:26:44 +00:00
],
],
2016-05-28 15:46:24 +00:00
],
'{DAV:}write' => [
'abstract' => false ,
2016-05-11 00:26:44 +00:00
'aggregates' => [
2016-05-28 15:46:24 +00:00
'{DAV:}write-properties' => [
'abstract' => false ,
'aggregates' => [],
2016-05-11 00:26:44 +00:00
],
2016-05-28 15:46:24 +00:00
'{DAV:}write-content' => [
'abstract' => false ,
'aggregates' => [],
2016-05-11 00:26:44 +00:00
],
2016-05-28 15:46:24 +00:00
'{DAV:}unlock' => [
'abstract' => false ,
'aggregates' => [],
2016-05-11 00:26:44 +00:00
],
],
2016-05-28 15:46:24 +00:00
],
];
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 ;
2013-10-21 22:46:31 +00:00
}
/**
* Returns the supported privilege set as a flat list
*
* This is much easier to parse .
*
* The returned list will be index by privilege name .
* The value is a struct containing the following properties :
* - aggregates
* - abstract
* - concrete
*
2016-05-11 00:26:44 +00:00
* @ param string | INode $node
2013-10-21 22:46:31 +00:00
* @ return array
*/
2016-05-11 00:26:44 +00:00
final function getFlatPrivilegeSet ( $node ) {
2013-10-21 22:46:31 +00:00
2016-05-28 15:46:24 +00:00
$privs = [
'abstract' => false ,
'aggregates' => $this -> getSupportedPrivilegeSet ( $node )
];
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
$fpsTraverse = null ;
2016-05-28 15:46:24 +00:00
$fpsTraverse = function ( $privName , $privInfo , $concrete , & $flat ) use ( & $fpsTraverse ) {
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
$myPriv = [
2016-05-28 15:46:24 +00:00
'privilege' => $privName ,
'abstract' => isset ( $privInfo [ 'abstract' ]) && $privInfo [ 'abstract' ],
2016-05-11 00:26:44 +00:00
'aggregates' => [],
2016-05-28 15:46:24 +00:00
'concrete' => isset ( $privInfo [ 'abstract' ]) && $privInfo [ 'abstract' ] ? $concrete : $privName ,
2016-05-11 00:26:44 +00:00
];
2013-10-21 22:46:31 +00:00
2016-05-28 15:46:24 +00:00
if ( isset ( $privInfo [ 'aggregates' ])) {
2013-10-21 22:46:31 +00:00
2016-05-28 15:46:24 +00:00
foreach ( $privInfo [ 'aggregates' ] as $subPrivName => $subPrivInfo ) {
2013-10-21 22:46:31 +00:00
2016-05-28 15:46:24 +00:00
$myPriv [ 'aggregates' ][] = $subPrivName ;
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
}
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
}
2016-05-28 15:46:24 +00:00
$flat [ $privName ] = $myPriv ;
2013-10-21 22:46:31 +00:00
2016-05-28 15:46:24 +00:00
if ( isset ( $privInfo [ 'aggregates' ])) {
2013-10-21 22:46:31 +00:00
2016-05-28 15:46:24 +00:00
foreach ( $privInfo [ 'aggregates' ] as $subPrivName => $subPrivInfo ) {
2013-10-21 22:46:31 +00:00
2016-05-28 15:46:24 +00:00
$fpsTraverse ( $subPrivName , $subPrivInfo , $myPriv [ 'concrete' ], $flat );
2016-05-11 00:26:44 +00:00
}
2013-10-21 22:46:31 +00:00
}
2016-05-11 00:26:44 +00:00
};
$flat = [];
2016-05-28 15:46:24 +00:00
$fpsTraverse ( '{DAV:}all' , $privs , null , $flat );
2016-05-11 00:26:44 +00:00
return $flat ;
2013-10-21 22:46:31 +00:00
}
/**
* Returns the full ACL list .
*
2016-05-11 00:26:44 +00:00
* Either a uri or a INode may be passed .
2013-10-21 22:46:31 +00:00
*
* null will be returned if the node doesn ' t support ACLs .
*
* @ param string | DAV\INode $node
* @ return array
*/
2016-05-28 15:46:24 +00:00
function getAcl ( $node ) {
2013-10-21 22:46:31 +00:00
if ( is_string ( $node )) {
$node = $this -> server -> tree -> getNodeForPath ( $node );
}
if ( ! $node instanceof IACL ) {
2016-05-28 15:46:24 +00:00
return $this -> getDefaultAcl ();
2013-10-21 22:46:31 +00:00
}
$acl = $node -> getACL ();
2016-05-11 00:26:44 +00:00
foreach ( $this -> adminPrincipals as $adminPrincipal ) {
$acl [] = [
2013-10-21 22:46:31 +00:00
'principal' => $adminPrincipal ,
'privilege' => '{DAV:}all' ,
'protected' => true ,
2016-05-11 00:26:44 +00:00
];
2013-10-21 22:46:31 +00:00
}
return $acl ;
}
/**
* Returns a list of privileges the current user has
* on a particular node .
*
* Either a uri or a DAV\INode may be passed .
*
* null will be returned if the node doesn ' t support ACLs .
*
* @ param string | DAV\INode $node
* @ return array
*/
2016-05-11 00:26:44 +00:00
function getCurrentUserPrivilegeSet ( $node ) {
2013-10-21 22:46:31 +00:00
if ( is_string ( $node )) {
$node = $this -> server -> tree -> getNodeForPath ( $node );
}
$acl = $this -> getACL ( $node );
2016-05-11 00:26:44 +00:00
$collected = [];
2013-10-21 22:46:31 +00:00
2016-05-28 15:46:24 +00:00
$isAuthenticated = $this -> getCurrentUserPrincipal () !== null ;
2016-05-11 00:26:44 +00:00
foreach ( $acl as $ace ) {
2013-10-21 22:46:31 +00:00
$principal = $ace [ 'principal' ];
2016-05-11 00:26:44 +00:00
switch ( $principal ) {
2013-10-21 22:46:31 +00:00
case '{DAV:}owner' :
$owner = $node -> getOwner ();
2016-05-28 15:46:24 +00:00
if ( $owner && $this -> principalMatchesPrincipal ( $owner )) {
2013-10-21 22:46:31 +00:00
$collected [] = $ace ;
}
break ;
// 'all' matches for every user
case '{DAV:}all' :
2016-05-28 15:46:24 +00:00
$collected [] = $ace ;
break ;
2013-10-21 22:46:31 +00:00
case '{DAV:}authenticated' :
2016-05-28 15:46:24 +00:00
// Authenticated users only
if ( $isAuthenticated ) {
$collected [] = $ace ;
}
2013-10-21 22:46:31 +00:00
break ;
case '{DAV:}unauthenticated' :
2016-05-28 15:46:24 +00:00
// Unauthenticated users only
if ( ! $isAuthenticated ) {
$collected [] = $ace ;
}
2013-10-21 22:46:31 +00:00
break ;
default :
2016-05-28 15:46:24 +00:00
if ( $this -> principalMatchesPrincipal ( $ace [ 'principal' ])) {
2013-10-21 22:46:31 +00:00
$collected [] = $ace ;
}
break ;
}
}
// Now we deduct all aggregated privileges.
$flat = $this -> getFlatPrivilegeSet ( $node );
2016-05-11 00:26:44 +00:00
$collected2 = [];
while ( count ( $collected )) {
2013-10-21 22:46:31 +00:00
$current = array_pop ( $collected );
$collected2 [] = $current [ 'privilege' ];
2016-05-28 15:46:24 +00:00
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 ;
}
2016-05-11 00:26:44 +00:00
foreach ( $flat [ $current [ 'privilege' ]][ 'aggregates' ] as $subPriv ) {
2013-10-21 22:46:31 +00:00
$collected2 [] = $subPriv ;
$collected [] = $flat [ $subPriv ];
}
}
return array_values ( array_unique ( $collected2 ));
}
2016-05-11 00:26:44 +00:00
/**
* Returns a principal based on its uri .
*
* Returns null if the principal could not be found .
*
* @ param string $uri
* @ return null | string
*/
function getPrincipalByUri ( $uri ) {
$result = null ;
$collections = $this -> principalCollectionSet ;
foreach ( $collections as $collection ) {
2016-05-28 15:46:24 +00:00
try {
$principalCollection = $this -> server -> tree -> getNodeForPath ( $collection );
} catch ( NotFound $e ) {
// Ignore and move on
continue ;
}
2016-05-11 00:26:44 +00:00
if ( ! $principalCollection instanceof IPrincipalCollection ) {
// Not a principal collection, we're simply going to ignore
// this.
continue ;
}
$result = $principalCollection -> findByUri ( $uri );
if ( $result ) {
return $result ;
}
}
}
2013-10-21 22:46:31 +00:00
/**
* Principal property search
*
* This method can search for principals matching certain values in
* properties .
*
* This method will return a list of properties for the matched properties .
*
* @ param array $searchProperties The properties to search on . This is a
* key - value list . The keys are property
* names , and the values the strings to
* match them on .
* @ param array $requestedProperties This is the list of properties to
* return for every match .
* @ param string $collectionUri The principal collection to search on .
* If this is ommitted , the standard
* principal collection - set will be used .
2016-05-11 00:26:44 +00:00
* @ param string $test " allof " to use AND to search the
* properties . 'anyof' for OR .
2013-10-21 22:46:31 +00:00
* @ return array This method returns an array structure similar to
* Sabre\DAV\Server :: getPropertiesForPath . Returned
* properties are index by a HTTP status code .
*/
2016-05-11 00:26:44 +00:00
function principalSearch ( array $searchProperties , array $requestedProperties , $collectionUri = null , $test = 'allof' ) {
2013-10-21 22:46:31 +00:00
if ( ! is_null ( $collectionUri )) {
2016-05-11 00:26:44 +00:00
$uris = [ $collectionUri ];
2013-10-21 22:46:31 +00:00
} else {
$uris = $this -> principalCollectionSet ;
}
2016-05-11 00:26:44 +00:00
$lookupResults = [];
foreach ( $uris as $uri ) {
2013-10-21 22:46:31 +00:00
$principalCollection = $this -> server -> tree -> getNodeForPath ( $uri );
if ( ! $principalCollection instanceof IPrincipalCollection ) {
// Not a principal collection, we're simply going to ignore
// this.
continue ;
}
2016-05-11 00:26:44 +00:00
$results = $principalCollection -> searchPrincipals ( $searchProperties , $test );
foreach ( $results as $result ) {
$lookupResults [] = rtrim ( $uri , '/' ) . '/' . $result ;
2013-10-21 22:46:31 +00:00
}
}
2016-05-11 00:26:44 +00:00
$matches = [];
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
foreach ( $lookupResults as $lookupResult ) {
2013-10-21 22:46:31 +00:00
list ( $matches []) = $this -> server -> getPropertiesForPath ( $lookupResult , $requestedProperties , 0 );
}
return $matches ;
}
/**
* Sets up the plugin
*
* This method is automatically called by the server class .
*
* @ param DAV\Server $server
* @ return void
*/
2016-05-11 00:26:44 +00:00
function initialize ( DAV\Server $server ) {
2013-10-21 22:46:31 +00:00
2016-05-28 15:46:24 +00:00
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 ;
}
2013-10-21 22:46:31 +00:00
$this -> server = $server ;
2016-05-11 00:26:44 +00:00
$server -> on ( 'propFind' , [ $this , 'propFind' ], 20 );
$server -> on ( 'beforeMethod' , [ $this , 'beforeMethod' ], 20 );
$server -> on ( 'beforeBind' , [ $this , 'beforeBind' ], 20 );
$server -> on ( 'beforeUnbind' , [ $this , 'beforeUnbind' ], 20 );
$server -> on ( 'propPatch' , [ $this , 'propPatch' ]);
$server -> on ( 'beforeUnlock' , [ $this , 'beforeUnlock' ], 20 );
$server -> on ( 'report' , [ $this , 'report' ]);
$server -> on ( 'method:ACL' , [ $this , 'httpAcl' ]);
$server -> on ( 'onHTMLActionsPanel' , [ $this , 'htmlActionsPanel' ]);
2016-05-28 15:46:24 +00:00
$server -> on ( 'getPrincipalByUri' , function ( $principal , & $uri ) {
$uri = $this -> getPrincipalByUri ( $principal );
// Break event chain
if ( $uri ) return false ;
});
2013-10-21 22:46:31 +00:00
array_push ( $server -> protectedProperties ,
'{DAV:}alternate-URI-set' ,
'{DAV:}principal-URL' ,
'{DAV:}group-membership' ,
'{DAV:}principal-collection-set' ,
'{DAV:}current-user-principal' ,
'{DAV:}supported-privilege-set' ,
'{DAV:}current-user-privilege-set' ,
'{DAV:}acl' ,
'{DAV:}acl-restrictions' ,
'{DAV:}inherited-acl-set' ,
'{DAV:}owner' ,
'{DAV:}group'
);
// Automatically mapping nodes implementing IPrincipal to the
// {DAV:}principal resourcetype.
$server -> resourceTypeMapping [ 'Sabre\\DAVACL\\IPrincipal' ] = '{DAV:}principal' ;
// Mapping the group-member-set property to the HrefList property
// class.
2016-05-11 00:26:44 +00:00
$server -> xml -> elementMap [ '{DAV:}group-member-set' ] = 'Sabre\\DAV\\Xml\\Property\\Href' ;
$server -> xml -> elementMap [ '{DAV:}acl' ] = 'Sabre\\DAVACL\\Xml\\Property\\Acl' ;
2016-05-28 15:46:24 +00:00
$server -> xml -> elementMap [ '{DAV:}acl-principal-prop-set' ] = 'Sabre\\DAVACL\\Xml\\Request\\AclPrincipalPropSetReport' ;
2016-05-11 00:26:44 +00:00
$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' ;
2016-05-28 15:46:24 +00:00
$server -> xml -> elementMap [ '{DAV:}principal-match' ] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalMatchReport' ;
2013-10-21 22:46:31 +00:00
}
/* {{{ Event handlers */
/**
* Triggered before any method is handled
*
2016-05-11 00:26:44 +00:00
* @ param RequestInterface $request
* @ param ResponseInterface $response
2013-10-21 22:46:31 +00:00
* @ return void
*/
2016-05-11 00:26:44 +00:00
function beforeMethod ( RequestInterface $request , ResponseInterface $response ) {
$method = $request -> getMethod ();
$path = $request -> getPath ();
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
$exists = $this -> server -> tree -> nodeExists ( $path );
2013-10-21 22:46:31 +00:00
// If the node doesn't exists, none of these checks apply
if ( ! $exists ) return ;
2016-05-11 00:26:44 +00:00
switch ( $method ) {
2013-10-21 22:46:31 +00:00
case 'GET' :
case 'HEAD' :
case 'OPTIONS' :
// For these 3 we only need to know if the node is readable.
2016-05-11 00:26:44 +00:00
$this -> checkPrivileges ( $path , '{DAV:}read' );
2013-10-21 22:46:31 +00:00
break ;
case 'PUT' :
case 'LOCK' :
// This method requires the write-content priv if the node
// already exists, and bind on the parent if the node is being
// created.
// The bind privilege is handled in the beforeBind event.
2016-05-11 00:26:44 +00:00
$this -> checkPrivileges ( $path , '{DAV:}write-content' );
2013-10-21 22:46:31 +00:00
break ;
2016-05-28 15:46:24 +00:00
case 'UNLOCK' :
// Unlock is always allowed at the moment.
break ;
2013-10-21 22:46:31 +00:00
case 'PROPPATCH' :
2016-05-11 00:26:44 +00:00
$this -> checkPrivileges ( $path , '{DAV:}write-properties' );
2013-10-21 22:46:31 +00:00
break ;
case 'ACL' :
2016-05-11 00:26:44 +00:00
$this -> checkPrivileges ( $path , '{DAV:}write-acl' );
2013-10-21 22:46:31 +00:00
break ;
case 'COPY' :
case 'MOVE' :
// Copy requires read privileges on the entire source tree.
// If the target exists write-content normally needs to be
// checked, however, we're deleting the node beforehand and
// creating a new one after, so this is handled by the
// beforeUnbind event.
//
// The creation of the new node is handled by the beforeBind
// event.
//
// If MOVE is used beforeUnbind will also be used to check if
// the sourcenode can be deleted.
2016-05-11 00:26:44 +00:00
$this -> checkPrivileges ( $path , '{DAV:}read' , self :: R_RECURSIVE );
2013-10-21 22:46:31 +00:00
break ;
}
}
/**
* Triggered before a new node is created .
*
* This allows us to check permissions for any operation that creates a
* new node , such as PUT , MKCOL , MKCALENDAR , LOCK , COPY and MOVE .
*
* @ param string $uri
* @ return void
*/
2016-05-11 00:26:44 +00:00
function beforeBind ( $uri ) {
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
list ( $parentUri ) = Uri\split ( $uri );
$this -> checkPrivileges ( $parentUri , '{DAV:}bind' );
2013-10-21 22:46:31 +00:00
}
/**
* Triggered before a node is deleted
*
* This allows us to check permissions for any operation that will delete
* an existing node .
*
* @ param string $uri
* @ return void
*/
2016-05-11 00:26:44 +00:00
function beforeUnbind ( $uri ) {
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
list ( $parentUri ) = Uri\split ( $uri );
$this -> checkPrivileges ( $parentUri , '{DAV:}unbind' , self :: R_RECURSIVEPARENTS );
2013-10-21 22:46:31 +00:00
}
/**
* Triggered before a node is unlocked .
*
* @ param string $uri
* @ param DAV\Locks\LockInfo $lock
* @ TODO : not yet implemented
* @ return void
*/
2016-05-11 00:26:44 +00:00
function beforeUnlock ( $uri , DAV\Locks\LockInfo $lock ) {
2013-10-21 22:46:31 +00:00
}
/**
* Triggered before properties are looked up in specific nodes .
*
2016-05-11 00:26:44 +00:00
* @ param DAV\PropFind $propFind
2013-10-21 22:46:31 +00:00
* @ param DAV\INode $node
* @ param array $requestedProperties
* @ param array $returnedProperties
* @ TODO really should be broken into multiple methods , or even a class .
* @ return bool
*/
2016-05-11 00:26:44 +00:00
function propFind ( DAV\PropFind $propFind , DAV\INode $node ) {
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
$path = $propFind -> getPath ();
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
// Checking the read permission
if ( ! $this -> checkPrivileges ( $path , '{DAV:}read' , self :: R_PARENT , false )) {
2013-10-21 22:46:31 +00:00
// User is not allowed to read properties
2016-05-11 00:26:44 +00:00
// Returning false causes the property-fetching system to pretend
// that the node does not exist, and will cause it to be hidden
// from listings such as PROPFIND or the browser plugin.
2013-10-21 22:46:31 +00:00
if ( $this -> hideNodesFromListings ) {
return false ;
}
2016-05-11 00:26:44 +00:00
// Otherwise we simply mark every property as 403.
foreach ( $propFind -> getRequestedProperties () as $requestedProperty ) {
$propFind -> set ( $requestedProperty , null , 403 );
2013-10-21 22:46:31 +00:00
}
2016-05-11 00:26:44 +00:00
2013-10-21 22:46:31 +00:00
return ;
}
/* Adding principal properties */
if ( $node instanceof IPrincipal ) {
2016-05-11 00:26:44 +00:00
$propFind -> handle ( '{DAV:}alternate-URI-set' , function () use ( $node ) {
2016-05-28 15:46:24 +00:00
return new Href ( $node -> getAlternateUriSet ());
2016-05-11 00:26:44 +00:00
});
$propFind -> handle ( '{DAV:}principal-URL' , function () use ( $node ) {
2016-05-28 15:46:24 +00:00
return new Href ( $node -> getPrincipalUrl () . '/' );
2016-05-11 00:26:44 +00:00
});
$propFind -> handle ( '{DAV:}group-member-set' , function () use ( $node ) {
$members = $node -> getGroupMemberSet ();
foreach ( $members as $k => $member ) {
$members [ $k ] = rtrim ( $member , '/' ) . '/' ;
}
2016-05-28 15:46:24 +00:00
return new Href ( $members );
2016-05-11 00:26:44 +00:00
});
$propFind -> handle ( '{DAV:}group-membership' , function () use ( $node ) {
$members = $node -> getGroupMembership ();
foreach ( $members as $k => $member ) {
$members [ $k ] = rtrim ( $member , '/' ) . '/' ;
}
2016-05-28 15:46:24 +00:00
return new Href ( $members );
2016-05-11 00:26:44 +00:00
});
$propFind -> handle ( '{DAV:}displayname' , [ $node , 'getDisplayName' ]);
2013-10-21 22:46:31 +00:00
}
2016-05-11 00:26:44 +00:00
$propFind -> handle ( '{DAV:}principal-collection-set' , function () {
2013-10-21 22:46:31 +00:00
$val = $this -> principalCollectionSet ;
// Ensuring all collections end with a slash
2016-05-11 00:26:44 +00:00
foreach ( $val as $k => $v ) $val [ $k ] = $v . '/' ;
2016-05-28 15:46:24 +00:00
return new Href ( $val );
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
});
$propFind -> handle ( '{DAV:}current-user-principal' , function () {
2013-10-21 22:46:31 +00:00
if ( $url = $this -> getCurrentUserPrincipal ()) {
2016-05-11 00:26:44 +00:00
return new Xml\Property\Principal ( Xml\Property\Principal :: HREF , $url . '/' );
2013-10-21 22:46:31 +00:00
} else {
2016-05-11 00:26:44 +00:00
return new Xml\Property\Principal ( Xml\Property\Principal :: UNAUTHENTICATED );
2013-10-21 22:46:31 +00:00
}
2016-05-11 00:26:44 +00:00
});
$propFind -> handle ( '{DAV:}supported-privilege-set' , function () use ( $node ) {
return new Xml\Property\SupportedPrivilegeSet ( $this -> getSupportedPrivilegeSet ( $node ));
});
$propFind -> handle ( '{DAV:}current-user-privilege-set' , function () use ( $node , $propFind , $path ) {
if ( ! $this -> checkPrivileges ( $path , '{DAV:}read-current-user-privilege-set' , self :: R_PARENT , false )) {
$propFind -> set ( '{DAV:}current-user-privilege-set' , null , 403 );
2013-10-21 22:46:31 +00:00
} else {
$val = $this -> getCurrentUserPrivilegeSet ( $node );
2016-05-28 15:46:24 +00:00
return new Xml\Property\CurrentUserPrivilegeSet ( $val );
2013-10-21 22:46:31 +00:00
}
2016-05-11 00:26:44 +00:00
});
$propFind -> handle ( '{DAV:}acl' , function () use ( $node , $propFind , $path ) {
/* The ACL property contains all the permissions */
if ( ! $this -> checkPrivileges ( $path , '{DAV:}read-acl' , self :: R_PARENT , false )) {
$propFind -> set ( '{DAV:}acl' , null , 403 );
2013-10-21 22:46:31 +00:00
} else {
$acl = $this -> getACL ( $node );
2016-05-28 15:46:24 +00:00
return new Xml\Property\Acl ( $this -> getACL ( $node ));
2013-10-21 22:46:31 +00:00
}
2016-05-11 00:26:44 +00:00
});
$propFind -> handle ( '{DAV:}acl-restrictions' , function () {
return new Xml\Property\AclRestrictions ();
});
2013-10-21 22:46:31 +00:00
/* Adding ACL properties */
if ( $node instanceof IACL ) {
2016-05-11 00:26:44 +00:00
$propFind -> handle ( '{DAV:}owner' , function () use ( $node ) {
2016-05-28 15:46:24 +00:00
return new Href ( $node -> getOwner () . '/' );
2016-05-11 00:26:44 +00:00
});
2013-10-21 22:46:31 +00:00
}
}
/**
* This method intercepts PROPPATCH methods and make sure the
* group - member - set is updated correctly .
*
2016-05-11 00:26:44 +00:00
* @ param string $path
* @ param DAV\PropPatch $propPatch
* @ return void
2013-10-21 22:46:31 +00:00
*/
2016-05-11 00:26:44 +00:00
function propPatch ( $path , DAV\PropPatch $propPatch ) {
$propPatch -> handle ( '{DAV:}group-member-set' , function ( $value ) use ( $path ) {
if ( is_null ( $value )) {
$memberSet = [];
2016-05-28 15:46:24 +00:00
} elseif ( $value instanceof Href ) {
2016-05-11 00:26:44 +00:00
$memberSet = array_map (
[ $this -> server , 'calculateUri' ],
$value -> getHrefs ()
);
} else {
throw new DAV\Exception ( 'The group-member-set property MUST be an instance of Sabre\DAV\Property\HrefList or null' );
}
$node = $this -> server -> tree -> getNodeForPath ( $path );
if ( ! ( $node instanceof IPrincipal )) {
// Fail
return false ;
}
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
$node -> setGroupMemberSet ( $memberSet );
// We must also clear our cache, just in case
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
$this -> principalMembershipCache = [];
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
return true ;
});
2013-10-21 22:46:31 +00:00
}
/**
* This method handles HTTP REPORT requests
*
* @ param string $reportName
2016-05-11 00:26:44 +00:00
* @ param mixed $report
* @ param mixed $path
2013-10-21 22:46:31 +00:00
* @ return bool
*/
2016-05-11 00:26:44 +00:00
function report ( $reportName , $report , $path ) {
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
switch ( $reportName ) {
2013-10-21 22:46:31 +00:00
case '{DAV:}principal-property-search' :
2016-05-11 00:26:44 +00:00
$this -> server -> transactionType = 'report-principal-property-search' ;
2016-05-28 15:46:24 +00:00
$this -> principalPropertySearchReport ( $path , $report );
2013-10-21 22:46:31 +00:00
return false ;
case '{DAV:}principal-search-property-set' :
2016-05-11 00:26:44 +00:00
$this -> server -> transactionType = 'report-principal-search-property-set' ;
2016-05-28 15:46:24 +00:00
$this -> principalSearchPropertySetReport ( $path , $report );
2013-10-21 22:46:31 +00:00
return false ;
case '{DAV:}expand-property' :
2016-05-11 00:26:44 +00:00
$this -> server -> transactionType = 'report-expand-property' ;
2016-05-28 15:46:24 +00:00
$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 );
2013-10-21 22:46:31 +00:00
return false ;
}
}
/**
2016-05-11 00:26:44 +00:00
* This method is responsible for handling the 'ACL' event .
2013-10-21 22:46:31 +00:00
*
2016-05-11 00:26:44 +00:00
* @ param RequestInterface $request
* @ param ResponseInterface $response
2013-10-21 22:46:31 +00:00
* @ return bool
*/
2016-05-11 00:26:44 +00:00
function httpAcl ( RequestInterface $request , ResponseInterface $response ) {
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
$path = $request -> getPath ();
$body = $request -> getBodyAsString ();
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
if ( ! $body ) {
throw new DAV\Exception\BadRequest ( 'XML body expected in ACL request' );
}
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
$acl = $this -> server -> xml -> expect ( '{DAV:}acl' , $body );
$newAcl = $acl -> getPrivileges ();
2013-10-21 22:46:31 +00:00
// Normalizing urls
2016-05-11 00:26:44 +00:00
foreach ( $newAcl as $k => $newAce ) {
2013-10-21 22:46:31 +00:00
$newAcl [ $k ][ 'principal' ] = $this -> server -> calculateUri ( $newAce [ 'principal' ]);
}
2016-05-11 00:26:44 +00:00
$node = $this -> server -> tree -> getNodeForPath ( $path );
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
if ( ! $node instanceof IACL ) {
2013-10-21 22:46:31 +00:00
throw new DAV\Exception\MethodNotAllowed ( 'This node does not support the ACL method' );
}
$oldAcl = $this -> getACL ( $node );
$supportedPrivileges = $this -> getFlatPrivilegeSet ( $node );
/* Checking if protected principals from the existing principal set are
not overwritten . */
2016-05-11 00:26:44 +00:00
foreach ( $oldAcl as $oldAce ) {
2013-10-21 22:46:31 +00:00
if ( ! isset ( $oldAce [ 'protected' ]) || ! $oldAce [ 'protected' ]) continue ;
$found = false ;
2016-05-11 00:26:44 +00:00
foreach ( $newAcl as $newAce ) {
2013-10-21 22:46:31 +00:00
if (
$newAce [ 'privilege' ] === $oldAce [ 'privilege' ] &&
$newAce [ 'principal' ] === $oldAce [ 'principal' ] &&
$newAce [ 'protected' ]
)
$found = true ;
}
if ( ! $found )
throw new Exception\AceConflict ( 'This resource contained a protected {DAV:}ace, but this privilege did not occur in the ACL request' );
}
2016-05-11 00:26:44 +00:00
foreach ( $newAcl as $newAce ) {
2013-10-21 22:46:31 +00:00
// Do we recognize the privilege
if ( ! isset ( $supportedPrivileges [ $newAce [ 'privilege' ]])) {
throw new Exception\NotSupportedPrivilege ( 'The privilege you specified (' . $newAce [ 'privilege' ] . ') is not recognized by this server' );
}
if ( $supportedPrivileges [ $newAce [ 'privilege' ]][ 'abstract' ]) {
throw new Exception\NoAbstract ( 'The privilege you specified (' . $newAce [ 'privilege' ] . ') is an abstract privilege' );
}
// Looking up the principal
try {
$principal = $this -> server -> tree -> getNodeForPath ( $newAce [ 'principal' ]);
2016-05-28 15:46:24 +00:00
} catch ( NotFound $e ) {
2013-10-21 22:46:31 +00:00
throw new Exception\NotRecognizedPrincipal ( 'The specified principal (' . $newAce [ 'principal' ] . ') does not exist' );
}
if ( ! ( $principal instanceof IPrincipal )) {
throw new Exception\NotRecognizedPrincipal ( 'The specified uri (' . $newAce [ 'principal' ] . ') is not a principal' );
}
}
$node -> setACL ( $newAcl );
2016-05-11 00:26:44 +00:00
$response -> setStatus ( 200 );
// Breaking the event chain, because we handled this method.
return false ;
2013-10-21 22:46:31 +00:00
}
/* }}} */
/* Reports {{{ */
/**
2016-05-28 15:46:24 +00:00
* 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 .
2013-10-21 22:46:31 +00:00
*
* This report is very similar to a standard PROPFIND . The difference is
* that it has the additional ability to look at properties containing a
* { DAV : } href element , follow that property and grab additional elements
* there .
*
* Other rfc ' s , such as ACL rely on this report , so it made sense to put
* it in this plugin .
*
2016-05-28 15:46:24 +00:00
* @ param string $path
2016-05-11 00:26:44 +00:00
* @ param Xml\Request\ExpandPropertyReport $report
2013-10-21 22:46:31 +00:00
* @ return void
*/
2016-05-28 15:46:24 +00:00
protected function expandPropertyReport ( $path , $report ) {
2013-10-21 22:46:31 +00:00
$depth = $this -> server -> getHTTPDepth ( 0 );
2016-05-28 15:46:24 +00:00
$result = $this -> expandProperties ( $path , $report -> properties , $depth );
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
$xml = $this -> server -> xml -> write (
'{DAV:}multistatus' ,
new DAV\Xml\Response\MultiStatus ( $result ),
$this -> server -> getBaseUri ()
);
$this -> server -> httpResponse -> setHeader ( 'Content-Type' , 'application/xml; charset=utf-8' );
$this -> server -> httpResponse -> setStatus ( 207 );
$this -> server -> httpResponse -> setBody ( $xml );
2013-10-21 22:46:31 +00:00
}
/**
* This method expands all the properties and returns
* a list with property values
*
* @ param array $path
* @ param array $requestedProperties the list of required properties
* @ param int $depth
* @ return array
*/
protected function expandProperties ( $path , array $requestedProperties , $depth ) {
$foundProperties = $this -> server -> getPropertiesForPath ( $path , array_keys ( $requestedProperties ), $depth );
2016-05-11 00:26:44 +00:00
$result = [];
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
foreach ( $foundProperties as $node ) {
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
foreach ( $requestedProperties as $propertyName => $childRequestedProperties ) {
2013-10-21 22:46:31 +00:00
// We're only traversing if sub-properties were requested
2016-05-11 00:26:44 +00:00
if ( count ( $childRequestedProperties ) === 0 ) continue ;
2013-10-21 22:46:31 +00:00
// We only have to do the expansion if the property was found
// and it contains an href element.
2016-05-11 00:26:44 +00:00
if ( ! array_key_exists ( $propertyName , $node [ 200 ])) continue ;
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
if ( ! $node [ 200 ][ $propertyName ] instanceof DAV\Xml\Property\Href ) {
continue ;
2013-10-21 22:46:31 +00:00
}
2016-05-11 00:26:44 +00:00
$childHrefs = $node [ 200 ][ $propertyName ] -> getHrefs ();
$childProps = [];
foreach ( $childHrefs as $href ) {
// Gathering the result of the children
$childProps [] = [
'name' => '{DAV:}response' ,
'value' => $this -> expandProperties ( $href , $childRequestedProperties , 0 )[ 0 ]
];
2013-10-21 22:46:31 +00:00
}
2016-05-11 00:26:44 +00:00
// Replacing the property with its expannded form.
$node [ 200 ][ $propertyName ] = $childProps ;
2013-10-21 22:46:31 +00:00
}
2016-05-11 00:26:44 +00:00
$result [] = new DAV\Xml\Element\Response ( $node [ 'href' ], $node );
2013-10-21 22:46:31 +00:00
}
return $result ;
}
/**
* principalSearchPropertySetReport
*
* This method responsible for handing the
* { DAV : } principal - search - property - set report . This report returns a list
* of properties the client may search on , using the
* { DAV : } principal - property - search report .
*
2016-05-28 15:46:24 +00:00
* @ param string $path
2016-05-11 00:26:44 +00:00
* @ param Xml\Request\PrincipalSearchPropertySetReport $report
2013-10-21 22:46:31 +00:00
* @ return void
*/
2016-05-28 15:46:24 +00:00
protected function principalSearchPropertySetReport ( $path , $report ) {
2013-10-21 22:46:31 +00:00
$httpDepth = $this -> server -> getHTTPDepth ( 0 );
2016-05-11 00:26:44 +00:00
if ( $httpDepth !== 0 ) {
2013-10-21 22:46:31 +00:00
throw new DAV\Exception\BadRequest ( 'This report is only defined when Depth: 0' );
}
2016-05-11 00:26:44 +00:00
$writer = $this -> server -> xml -> getWriter ();
$writer -> openMemory ();
$writer -> startDocument ();
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
$writer -> startElement ( '{DAV:}principal-search-property-set' );
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
foreach ( $this -> principalSearchPropertySet as $propertyName => $description ) {
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
$writer -> startElement ( '{DAV:}principal-search-property' );
$writer -> startElement ( '{DAV:}prop' );
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
$writer -> writeElement ( $propertyName );
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
$writer -> endElement (); // prop
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
if ( $description ) {
$writer -> write ([[
'name' => '{DAV:}description' ,
'value' => $description ,
'attributes' => [ 'xml:lang' => 'en' ]
]]);
}
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
$writer -> endElement (); // principal-search-property
2013-10-21 22:46:31 +00:00
}
2016-05-11 00:26:44 +00:00
$writer -> endElement (); // principal-search-property-set
$this -> server -> httpResponse -> setHeader ( 'Content-Type' , 'application/xml; charset=utf-8' );
$this -> server -> httpResponse -> setStatus ( 200 );
$this -> server -> httpResponse -> setBody ( $writer -> outputMemory ());
2013-10-21 22:46:31 +00:00
}
/**
* principalPropertySearchReport
*
* This method is responsible for handing the
* { DAV : } principal - property - search report . This report can be used for
* clients to search for groups of principals , based on the value of one
* or more properties .
*
2016-05-28 15:46:24 +00:00
* @ param string $path
2016-05-11 00:26:44 +00:00
* @ param Xml\Request\PrincipalPropertySearchReport $report
2013-10-21 22:46:31 +00:00
* @ return void
*/
2016-05-28 15:46:24 +00:00
protected function principalPropertySearchReport ( $path , Xml\Request\PrincipalPropertySearchReport $report ) {
2013-10-21 22:46:31 +00:00
2016-05-28 15:46:24 +00:00
if ( $report -> applyToPrincipalCollectionSet ) {
$path = null ;
2013-10-21 22:46:31 +00:00
}
2016-05-11 00:26:44 +00:00
if ( $this -> server -> getHttpDepth ( '0' ) !== 0 ) {
throw new BadRequest ( 'Depth must be 0' );
}
$result = $this -> principalSearch (
$report -> searchProperties ,
$report -> properties ,
2016-05-28 15:46:24 +00:00
$path ,
2016-05-11 00:26:44 +00:00
$report -> test
);
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
$prefer = $this -> server -> getHTTPPrefer ();
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
$this -> server -> httpResponse -> setStatus ( 207 );
$this -> server -> httpResponse -> setHeader ( 'Content-Type' , 'application/xml; charset=utf-8' );
$this -> server -> httpResponse -> setHeader ( 'Vary' , 'Brief,Prefer' );
$this -> server -> httpResponse -> setBody ( $this -> server -> generateMultiStatus ( $result , $prefer [ 'return' ] === 'minimal' ));
2013-10-21 22:46:31 +00:00
}
2016-05-28 15:46:24 +00:00
/**
* 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 )
);
}
2016-05-11 00:26:44 +00:00
/* }}} */
2013-10-21 22:46:31 +00:00
/**
2016-05-11 00:26:44 +00:00
* This method is used to generate HTML output for the
* DAV\Browser\Plugin . This allows us to generate an interface users
* can use to create new calendars .
2013-10-21 22:46:31 +00:00
*
2016-05-11 00:26:44 +00:00
* @ param DAV\INode $node
* @ param string $output
* @ return bool
2013-10-21 22:46:31 +00:00
*/
2016-05-11 00:26:44 +00:00
function htmlActionsPanel ( DAV\INode $node , & $output ) {
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
if ( ! $node instanceof PrincipalCollection )
return ;
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
$output .= ' < tr >< td colspan = " 2 " >< form method = " post " action = " " >
< h3 > Create new principal </ h3 >
< input type = " hidden " name = " sabreAction " value = " mkcol " />
< input type = " hidden " name = " resourceType " value = " { DAV:}principal " />
< label > Name ( uri ) :</ label > < input type = " text " name = " name " />< br />
< label > Display name :</ label > < input type = " text " name = " { DAV:}displayname " />< br />
< label > Email address :</ label > < input type = " text " name = " { http://sabredav*DOT*org/ns}email-address " />< br />
< input type = " submit " value = " create " />
</ form >
</ td ></ tr > ' ;
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
return false ;
2013-10-21 22:46:31 +00:00
}
2016-05-11 00:26:44 +00:00
/**
* Returns a bunch of meta - data about the plugin .
*
* Providing this information is optional , and is mainly displayed by the
* Browser plugin .
*
* The description key in the returned array may contain html and will not
* be sanitized .
*
* @ return array
*/
function getPluginInfo () {
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
return [
'name' => $this -> getPluginName (),
'description' => 'Adds support for WebDAV ACL (rfc3744)' ,
'link' => 'http://sabre.io/dav/acl/' ,
];
2013-10-21 22:46:31 +00:00
2016-05-11 00:26:44 +00:00
}
2013-10-21 22:46:31 +00:00
}