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