Completed
Branch develop (456163)
by
unknown
23:40
created
htdocs/includes/sabre/sabre/dav/lib/DAV/Locks/Plugin.php 1 patch
Indentation   +523 added lines, -523 removed lines patch added patch discarded remove patch
@@ -24,527 +24,527 @@
 block discarded – undo
24 24
  */
25 25
 class Plugin extends DAV\ServerPlugin
26 26
 {
27
-    /**
28
-     * locksBackend.
29
-     *
30
-     * @var Backend\BackendInterface
31
-     */
32
-    protected $locksBackend;
33
-
34
-    /**
35
-     * server.
36
-     *
37
-     * @var DAV\Server
38
-     */
39
-    protected $server;
40
-
41
-    /**
42
-     * __construct.
43
-     */
44
-    public function __construct(Backend\BackendInterface $locksBackend)
45
-    {
46
-        $this->locksBackend = $locksBackend;
47
-    }
48
-
49
-    /**
50
-     * Initializes the plugin.
51
-     *
52
-     * This method is automatically called by the Server class after addPlugin.
53
-     */
54
-    public function initialize(DAV\Server $server)
55
-    {
56
-        $this->server = $server;
57
-
58
-        $this->server->xml->elementMap['{DAV:}lockinfo'] = 'Sabre\\DAV\\Xml\\Request\\Lock';
59
-
60
-        $server->on('method:LOCK', [$this, 'httpLock']);
61
-        $server->on('method:UNLOCK', [$this, 'httpUnlock']);
62
-        $server->on('validateTokens', [$this, 'validateTokens']);
63
-        $server->on('propFind', [$this, 'propFind']);
64
-        $server->on('afterUnbind', [$this, 'afterUnbind']);
65
-    }
66
-
67
-    /**
68
-     * Returns a plugin name.
69
-     *
70
-     * Using this name other plugins will be able to access other plugins
71
-     * using Sabre\DAV\Server::getPlugin
72
-     *
73
-     * @return string
74
-     */
75
-    public function getPluginName()
76
-    {
77
-        return 'locks';
78
-    }
79
-
80
-    /**
81
-     * This method is called after most properties have been found
82
-     * it allows us to add in any Lock-related properties.
83
-     */
84
-    public function propFind(DAV\PropFind $propFind, DAV\INode $node)
85
-    {
86
-        $propFind->handle('{DAV:}supportedlock', function () {
87
-            return new DAV\Xml\Property\SupportedLock();
88
-        });
89
-        $propFind->handle('{DAV:}lockdiscovery', function () use ($propFind) {
90
-            return new DAV\Xml\Property\LockDiscovery(
91
-                $this->getLocks($propFind->getPath())
92
-            );
93
-        });
94
-    }
95
-
96
-    /**
97
-     * Use this method to tell the server this plugin defines additional
98
-     * HTTP methods.
99
-     *
100
-     * This method is passed a uri. It should only return HTTP methods that are
101
-     * available for the specified uri.
102
-     *
103
-     * @param string $uri
104
-     *
105
-     * @return array
106
-     */
107
-    public function getHTTPMethods($uri)
108
-    {
109
-        return ['LOCK', 'UNLOCK'];
110
-    }
111
-
112
-    /**
113
-     * Returns a list of features for the HTTP OPTIONS Dav: header.
114
-     *
115
-     * In this case this is only the number 2. The 2 in the Dav: header
116
-     * indicates the server supports locks.
117
-     *
118
-     * @return array
119
-     */
120
-    public function getFeatures()
121
-    {
122
-        return [2];
123
-    }
124
-
125
-    /**
126
-     * Returns all lock information on a particular uri.
127
-     *
128
-     * This function should return an array with Sabre\DAV\Locks\LockInfo objects. If there are no locks on a file, return an empty array.
129
-     *
130
-     * Additionally there is also the possibility of locks on parent nodes, so we'll need to traverse every part of the tree
131
-     * If the $returnChildLocks argument is set to true, we'll also traverse all the children of the object
132
-     * for any possible locks and return those as well.
133
-     *
134
-     * @param string $uri
135
-     * @param bool   $returnChildLocks
136
-     *
137
-     * @return array
138
-     */
139
-    public function getLocks($uri, $returnChildLocks = false)
140
-    {
141
-        return $this->locksBackend->getLocks($uri, $returnChildLocks);
142
-    }
143
-
144
-    /**
145
-     * Locks an uri.
146
-     *
147
-     * The WebDAV lock request can be operated to either create a new lock on a file, or to refresh an existing lock
148
-     * If a new lock is created, a full XML body should be supplied, containing information about the lock such as the type
149
-     * of lock (shared or exclusive) and the owner of the lock
150
-     *
151
-     * If a lock is to be refreshed, no body should be supplied and there should be a valid If header containing the lock
152
-     *
153
-     * Additionally, a lock can be requested for a non-existent file. In these case we're obligated to create an empty file as per RFC4918:S7.3
154
-     *
155
-     * @return bool
156
-     */
157
-    public function httpLock(RequestInterface $request, ResponseInterface $response)
158
-    {
159
-        $uri = $request->getPath();
160
-
161
-        $existingLocks = $this->getLocks($uri);
162
-
163
-        if ($body = $request->getBodyAsString()) {
164
-            // This is a new lock request
165
-
166
-            $existingLock = null;
167
-            // Checking if there's already non-shared locks on the uri.
168
-            foreach ($existingLocks as $existingLock) {
169
-                if (LockInfo::EXCLUSIVE === $existingLock->scope) {
170
-                    throw new DAV\Exception\ConflictingLock($existingLock);
171
-                }
172
-            }
173
-
174
-            $lockInfo = $this->parseLockRequest($body);
175
-            $lockInfo->depth = $this->server->getHTTPDepth();
176
-            $lockInfo->uri = $uri;
177
-            if ($existingLock && LockInfo::SHARED != $lockInfo->scope) {
178
-                throw new DAV\Exception\ConflictingLock($existingLock);
179
-            }
180
-        } else {
181
-            // Gonna check if this was a lock refresh.
182
-            $existingLocks = $this->getLocks($uri);
183
-            $conditions = $this->server->getIfConditions($request);
184
-            $found = null;
185
-
186
-            foreach ($existingLocks as $existingLock) {
187
-                foreach ($conditions as $condition) {
188
-                    foreach ($condition['tokens'] as $token) {
189
-                        if ($token['token'] === 'opaquelocktoken:'.$existingLock->token) {
190
-                            $found = $existingLock;
191
-                            break 3;
192
-                        }
193
-                    }
194
-                }
195
-            }
196
-
197
-            // If none were found, this request is in error.
198
-            if (is_null($found)) {
199
-                if ($existingLocks) {
200
-                    throw new DAV\Exception\Locked(reset($existingLocks));
201
-                } else {
202
-                    throw new DAV\Exception\BadRequest('An xml body is required for lock requests');
203
-                }
204
-            }
205
-
206
-            // This must have been a lock refresh
207
-            $lockInfo = $found;
208
-
209
-            // The resource could have been locked through another uri.
210
-            if ($uri != $lockInfo->uri) {
211
-                $uri = $lockInfo->uri;
212
-            }
213
-        }
214
-
215
-        if ($timeout = $this->getTimeoutHeader()) {
216
-            $lockInfo->timeout = $timeout;
217
-        }
218
-
219
-        $newFile = false;
220
-
221
-        // If we got this far.. we should go check if this node actually exists. If this is not the case, we need to create it first
222
-        try {
223
-            $this->server->tree->getNodeForPath($uri);
224
-
225
-            // We need to call the beforeWriteContent event for RFC3744
226
-            // Edit: looks like this is not used, and causing problems now.
227
-            //
228
-            // See Issue 222
229
-            // $this->server->emit('beforeWriteContent',array($uri));
230
-        } catch (DAV\Exception\NotFound $e) {
231
-            // It didn't, lets create it
232
-            $this->server->createFile($uri, fopen('php://memory', 'r'));
233
-            $newFile = true;
234
-        }
235
-
236
-        $this->lockNode($uri, $lockInfo);
237
-
238
-        $response->setHeader('Content-Type', 'application/xml; charset=utf-8');
239
-        $response->setHeader('Lock-Token', '<opaquelocktoken:'.$lockInfo->token.'>');
240
-        $response->setStatus($newFile ? 201 : 200);
241
-        $response->setBody($this->generateLockResponse($lockInfo));
242
-
243
-        // Returning false will interrupt the event chain and mark this method
244
-        // as 'handled'.
245
-        return false;
246
-    }
247
-
248
-    /**
249
-     * Unlocks a uri.
250
-     *
251
-     * This WebDAV method allows you to remove a lock from a node. The client should provide a valid locktoken through the Lock-token http header
252
-     * The server should return 204 (No content) on success
253
-     */
254
-    public function httpUnlock(RequestInterface $request, ResponseInterface $response)
255
-    {
256
-        $lockToken = $request->getHeader('Lock-Token');
257
-
258
-        // If the locktoken header is not supplied, we need to throw a bad request exception
259
-        if (!$lockToken) {
260
-            throw new DAV\Exception\BadRequest('No lock token was supplied');
261
-        }
262
-        $path = $request->getPath();
263
-        $locks = $this->getLocks($path);
264
-
265
-        // Windows sometimes forgets to include < and > in the Lock-Token
266
-        // header
267
-        if ('<' !== $lockToken[0]) {
268
-            $lockToken = '<'.$lockToken.'>';
269
-        }
270
-
271
-        foreach ($locks as $lock) {
272
-            if ('<opaquelocktoken:'.$lock->token.'>' == $lockToken) {
273
-                $this->unlockNode($path, $lock);
274
-                $response->setHeader('Content-Length', '0');
275
-                $response->setStatus(204);
276
-
277
-                // Returning false will break the method chain, and mark the
278
-                // method as 'handled'.
279
-                return false;
280
-            }
281
-        }
282
-
283
-        // If we got here, it means the locktoken was invalid
284
-        throw new DAV\Exception\LockTokenMatchesRequestUri();
285
-    }
286
-
287
-    /**
288
-     * This method is called after a node is deleted.
289
-     *
290
-     * We use this event to clean up any locks that still exist on the node.
291
-     *
292
-     * @param string $path
293
-     */
294
-    public function afterUnbind($path)
295
-    {
296
-        $locks = $this->getLocks($path, $includeChildren = true);
297
-        foreach ($locks as $lock) {
298
-            // don't delete a lock on a parent dir
299
-            if (0 !== strpos($lock->uri, $path)) {
300
-                continue;
301
-            }
302
-            $this->unlockNode($path, $lock);
303
-        }
304
-    }
305
-
306
-    /**
307
-     * Locks a uri.
308
-     *
309
-     * All the locking information is supplied in the lockInfo object. The object has a suggested timeout, but this can be safely ignored
310
-     * It is important that if the existing timeout is ignored, the property is overwritten, as this needs to be sent back to the client
311
-     *
312
-     * @param string $uri
313
-     *
314
-     * @return bool
315
-     */
316
-    public function lockNode($uri, LockInfo $lockInfo)
317
-    {
318
-        if (!$this->server->emit('beforeLock', [$uri, $lockInfo])) {
319
-            return;
320
-        }
321
-
322
-        return $this->locksBackend->lock($uri, $lockInfo);
323
-    }
324
-
325
-    /**
326
-     * Unlocks a uri.
327
-     *
328
-     * This method removes a lock from a uri. It is assumed all the supplied information is correct and verified
329
-     *
330
-     * @param string $uri
331
-     *
332
-     * @return bool
333
-     */
334
-    public function unlockNode($uri, LockInfo $lockInfo)
335
-    {
336
-        if (!$this->server->emit('beforeUnlock', [$uri, $lockInfo])) {
337
-            return;
338
-        }
339
-
340
-        return $this->locksBackend->unlock($uri, $lockInfo);
341
-    }
342
-
343
-    /**
344
-     * Returns the contents of the HTTP Timeout header.
345
-     *
346
-     * The method formats the header into an integer.
347
-     *
348
-     * @return int
349
-     */
350
-    public function getTimeoutHeader()
351
-    {
352
-        $header = $this->server->httpRequest->getHeader('Timeout');
353
-
354
-        if ($header) {
355
-            if (0 === stripos($header, 'second-')) {
356
-                $header = (int) (substr($header, 7));
357
-            } elseif (0 === stripos($header, 'infinite')) {
358
-                $header = LockInfo::TIMEOUT_INFINITE;
359
-            } else {
360
-                throw new DAV\Exception\BadRequest('Invalid HTTP timeout header');
361
-            }
362
-        } else {
363
-            $header = 0;
364
-        }
365
-
366
-        return $header;
367
-    }
368
-
369
-    /**
370
-     * Generates the response for successful LOCK requests.
371
-     *
372
-     * @return string
373
-     */
374
-    protected function generateLockResponse(LockInfo $lockInfo)
375
-    {
376
-        return $this->server->xml->write('{DAV:}prop', [
377
-            '{DAV:}lockdiscovery' => new DAV\Xml\Property\LockDiscovery([$lockInfo]),
378
-        ], $this->server->getBaseUri());
379
-    }
380
-
381
-    /**
382
-     * The validateTokens event is triggered before every request.
383
-     *
384
-     * It's a moment where this plugin can check all the supplied lock tokens
385
-     * in the If: header, and check if they are valid.
386
-     *
387
-     * In addition, it will also ensure that it checks any missing lokens that
388
-     * must be present in the request, and reject requests without the proper
389
-     * tokens.
390
-     *
391
-     * @param mixed $conditions
392
-     */
393
-    public function validateTokens(RequestInterface $request, &$conditions)
394
-    {
395
-        // First we need to gather a list of locks that must be satisfied.
396
-        $mustLocks = [];
397
-        $method = $request->getMethod();
398
-
399
-        // Methods not in that list are operations that doesn't alter any
400
-        // resources, and we don't need to check the lock-states for.
401
-        switch ($method) {
402
-            case 'DELETE':
403
-                $mustLocks = array_merge($mustLocks, $this->getLocks(
404
-                    $request->getPath(),
405
-                    true
406
-                ));
407
-                break;
408
-            case 'MKCOL':
409
-            case 'MKCALENDAR':
410
-            case 'PROPPATCH':
411
-            case 'PUT':
412
-            case 'PATCH':
413
-                $mustLocks = array_merge($mustLocks, $this->getLocks(
414
-                    $request->getPath(),
415
-                    false
416
-                ));
417
-                break;
418
-            case 'MOVE':
419
-                $mustLocks = array_merge($mustLocks, $this->getLocks(
420
-                    $request->getPath(),
421
-                    true
422
-                ));
423
-                $mustLocks = array_merge($mustLocks, $this->getLocks(
424
-                    $this->server->calculateUri($request->getHeader('Destination')),
425
-                    false
426
-                ));
427
-                break;
428
-            case 'COPY':
429
-                $mustLocks = array_merge($mustLocks, $this->getLocks(
430
-                    $this->server->calculateUri($request->getHeader('Destination')),
431
-                    false
432
-                ));
433
-                break;
434
-            case 'LOCK':
435
-                //Temporary measure.. figure out later why this is needed
436
-                // Here we basically ignore all incoming tokens...
437
-                foreach ($conditions as $ii => $condition) {
438
-                    foreach ($condition['tokens'] as $jj => $token) {
439
-                        $conditions[$ii]['tokens'][$jj]['validToken'] = true;
440
-                    }
441
-                }
442
-
443
-                return;
444
-        }
445
-
446
-        // It's possible that there's identical locks, because of shared
447
-        // parents. We're removing the duplicates here.
448
-        $tmp = [];
449
-        foreach ($mustLocks as $lock) {
450
-            $tmp[$lock->token] = $lock;
451
-        }
452
-        $mustLocks = array_values($tmp);
453
-
454
-        foreach ($conditions as $kk => $condition) {
455
-            foreach ($condition['tokens'] as $ii => $token) {
456
-                // Lock tokens always start with opaquelocktoken:
457
-                if ('opaquelocktoken:' !== substr($token['token'], 0, 16)) {
458
-                    continue;
459
-                }
460
-
461
-                $checkToken = substr($token['token'], 16);
462
-                // Looping through our list with locks.
463
-                foreach ($mustLocks as $jj => $mustLock) {
464
-                    if ($mustLock->token == $checkToken) {
465
-                        // We have a match!
466
-                        // Removing this one from mustlocks
467
-                        unset($mustLocks[$jj]);
468
-
469
-                        // Marking the condition as valid.
470
-                        $conditions[$kk]['tokens'][$ii]['validToken'] = true;
471
-
472
-                        // Advancing to the next token
473
-                        continue 2;
474
-                    }
475
-                }
476
-
477
-                // If we got here, it means that there was a
478
-                // lock-token, but it was not in 'mustLocks'.
479
-                //
480
-                // This is an edge-case, as it could mean that token
481
-                // was specified with a url that was not 'required' to
482
-                // check. So we're doing one extra lookup to make sure
483
-                // we really don't know this token.
484
-                //
485
-                // This also gets triggered when the user specified a
486
-                // lock-token that was expired.
487
-                $oddLocks = $this->getLocks($condition['uri']);
488
-                foreach ($oddLocks as $oddLock) {
489
-                    if ($oddLock->token === $checkToken) {
490
-                        // We have a hit!
491
-                        $conditions[$kk]['tokens'][$ii]['validToken'] = true;
492
-                        continue 2;
493
-                    }
494
-                }
495
-
496
-                // If we get all the way here, the lock-token was
497
-                // really unknown.
498
-            }
499
-        }
500
-
501
-        // If there's any locks left in the 'mustLocks' array, it means that
502
-        // the resource was locked and we must block it.
503
-        if ($mustLocks) {
504
-            throw new DAV\Exception\Locked(reset($mustLocks));
505
-        }
506
-    }
507
-
508
-    /**
509
-     * Parses a webdav lock xml body, and returns a new Sabre\DAV\Locks\LockInfo object.
510
-     *
511
-     * @param string $body
512
-     *
513
-     * @return LockInfo
514
-     */
515
-    protected function parseLockRequest($body)
516
-    {
517
-        $result = $this->server->xml->expect(
518
-            '{DAV:}lockinfo',
519
-            $body
520
-        );
521
-
522
-        $lockInfo = new LockInfo();
523
-
524
-        $lockInfo->owner = $result->owner;
525
-        $lockInfo->token = DAV\UUIDUtil::getUUID();
526
-        $lockInfo->scope = $result->scope;
527
-
528
-        return $lockInfo;
529
-    }
530
-
531
-    /**
532
-     * Returns a bunch of meta-data about the plugin.
533
-     *
534
-     * Providing this information is optional, and is mainly displayed by the
535
-     * Browser plugin.
536
-     *
537
-     * The description key in the returned array may contain html and will not
538
-     * be sanitized.
539
-     *
540
-     * @return array
541
-     */
542
-    public function getPluginInfo()
543
-    {
544
-        return [
545
-            'name' => $this->getPluginName(),
546
-            'description' => 'The locks plugin turns this server into a class-2 WebDAV server and adds support for LOCK and UNLOCK',
547
-            'link' => 'http://sabre.io/dav/locks/',
548
-        ];
549
-    }
27
+	/**
28
+	 * locksBackend.
29
+	 *
30
+	 * @var Backend\BackendInterface
31
+	 */
32
+	protected $locksBackend;
33
+
34
+	/**
35
+	 * server.
36
+	 *
37
+	 * @var DAV\Server
38
+	 */
39
+	protected $server;
40
+
41
+	/**
42
+	 * __construct.
43
+	 */
44
+	public function __construct(Backend\BackendInterface $locksBackend)
45
+	{
46
+		$this->locksBackend = $locksBackend;
47
+	}
48
+
49
+	/**
50
+	 * Initializes the plugin.
51
+	 *
52
+	 * This method is automatically called by the Server class after addPlugin.
53
+	 */
54
+	public function initialize(DAV\Server $server)
55
+	{
56
+		$this->server = $server;
57
+
58
+		$this->server->xml->elementMap['{DAV:}lockinfo'] = 'Sabre\\DAV\\Xml\\Request\\Lock';
59
+
60
+		$server->on('method:LOCK', [$this, 'httpLock']);
61
+		$server->on('method:UNLOCK', [$this, 'httpUnlock']);
62
+		$server->on('validateTokens', [$this, 'validateTokens']);
63
+		$server->on('propFind', [$this, 'propFind']);
64
+		$server->on('afterUnbind', [$this, 'afterUnbind']);
65
+	}
66
+
67
+	/**
68
+	 * Returns a plugin name.
69
+	 *
70
+	 * Using this name other plugins will be able to access other plugins
71
+	 * using Sabre\DAV\Server::getPlugin
72
+	 *
73
+	 * @return string
74
+	 */
75
+	public function getPluginName()
76
+	{
77
+		return 'locks';
78
+	}
79
+
80
+	/**
81
+	 * This method is called after most properties have been found
82
+	 * it allows us to add in any Lock-related properties.
83
+	 */
84
+	public function propFind(DAV\PropFind $propFind, DAV\INode $node)
85
+	{
86
+		$propFind->handle('{DAV:}supportedlock', function () {
87
+			return new DAV\Xml\Property\SupportedLock();
88
+		});
89
+		$propFind->handle('{DAV:}lockdiscovery', function () use ($propFind) {
90
+			return new DAV\Xml\Property\LockDiscovery(
91
+				$this->getLocks($propFind->getPath())
92
+			);
93
+		});
94
+	}
95
+
96
+	/**
97
+	 * Use this method to tell the server this plugin defines additional
98
+	 * HTTP methods.
99
+	 *
100
+	 * This method is passed a uri. It should only return HTTP methods that are
101
+	 * available for the specified uri.
102
+	 *
103
+	 * @param string $uri
104
+	 *
105
+	 * @return array
106
+	 */
107
+	public function getHTTPMethods($uri)
108
+	{
109
+		return ['LOCK', 'UNLOCK'];
110
+	}
111
+
112
+	/**
113
+	 * Returns a list of features for the HTTP OPTIONS Dav: header.
114
+	 *
115
+	 * In this case this is only the number 2. The 2 in the Dav: header
116
+	 * indicates the server supports locks.
117
+	 *
118
+	 * @return array
119
+	 */
120
+	public function getFeatures()
121
+	{
122
+		return [2];
123
+	}
124
+
125
+	/**
126
+	 * Returns all lock information on a particular uri.
127
+	 *
128
+	 * This function should return an array with Sabre\DAV\Locks\LockInfo objects. If there are no locks on a file, return an empty array.
129
+	 *
130
+	 * Additionally there is also the possibility of locks on parent nodes, so we'll need to traverse every part of the tree
131
+	 * If the $returnChildLocks argument is set to true, we'll also traverse all the children of the object
132
+	 * for any possible locks and return those as well.
133
+	 *
134
+	 * @param string $uri
135
+	 * @param bool   $returnChildLocks
136
+	 *
137
+	 * @return array
138
+	 */
139
+	public function getLocks($uri, $returnChildLocks = false)
140
+	{
141
+		return $this->locksBackend->getLocks($uri, $returnChildLocks);
142
+	}
143
+
144
+	/**
145
+	 * Locks an uri.
146
+	 *
147
+	 * The WebDAV lock request can be operated to either create a new lock on a file, or to refresh an existing lock
148
+	 * If a new lock is created, a full XML body should be supplied, containing information about the lock such as the type
149
+	 * of lock (shared or exclusive) and the owner of the lock
150
+	 *
151
+	 * If a lock is to be refreshed, no body should be supplied and there should be a valid If header containing the lock
152
+	 *
153
+	 * Additionally, a lock can be requested for a non-existent file. In these case we're obligated to create an empty file as per RFC4918:S7.3
154
+	 *
155
+	 * @return bool
156
+	 */
157
+	public function httpLock(RequestInterface $request, ResponseInterface $response)
158
+	{
159
+		$uri = $request->getPath();
160
+
161
+		$existingLocks = $this->getLocks($uri);
162
+
163
+		if ($body = $request->getBodyAsString()) {
164
+			// This is a new lock request
165
+
166
+			$existingLock = null;
167
+			// Checking if there's already non-shared locks on the uri.
168
+			foreach ($existingLocks as $existingLock) {
169
+				if (LockInfo::EXCLUSIVE === $existingLock->scope) {
170
+					throw new DAV\Exception\ConflictingLock($existingLock);
171
+				}
172
+			}
173
+
174
+			$lockInfo = $this->parseLockRequest($body);
175
+			$lockInfo->depth = $this->server->getHTTPDepth();
176
+			$lockInfo->uri = $uri;
177
+			if ($existingLock && LockInfo::SHARED != $lockInfo->scope) {
178
+				throw new DAV\Exception\ConflictingLock($existingLock);
179
+			}
180
+		} else {
181
+			// Gonna check if this was a lock refresh.
182
+			$existingLocks = $this->getLocks($uri);
183
+			$conditions = $this->server->getIfConditions($request);
184
+			$found = null;
185
+
186
+			foreach ($existingLocks as $existingLock) {
187
+				foreach ($conditions as $condition) {
188
+					foreach ($condition['tokens'] as $token) {
189
+						if ($token['token'] === 'opaquelocktoken:'.$existingLock->token) {
190
+							$found = $existingLock;
191
+							break 3;
192
+						}
193
+					}
194
+				}
195
+			}
196
+
197
+			// If none were found, this request is in error.
198
+			if (is_null($found)) {
199
+				if ($existingLocks) {
200
+					throw new DAV\Exception\Locked(reset($existingLocks));
201
+				} else {
202
+					throw new DAV\Exception\BadRequest('An xml body is required for lock requests');
203
+				}
204
+			}
205
+
206
+			// This must have been a lock refresh
207
+			$lockInfo = $found;
208
+
209
+			// The resource could have been locked through another uri.
210
+			if ($uri != $lockInfo->uri) {
211
+				$uri = $lockInfo->uri;
212
+			}
213
+		}
214
+
215
+		if ($timeout = $this->getTimeoutHeader()) {
216
+			$lockInfo->timeout = $timeout;
217
+		}
218
+
219
+		$newFile = false;
220
+
221
+		// If we got this far.. we should go check if this node actually exists. If this is not the case, we need to create it first
222
+		try {
223
+			$this->server->tree->getNodeForPath($uri);
224
+
225
+			// We need to call the beforeWriteContent event for RFC3744
226
+			// Edit: looks like this is not used, and causing problems now.
227
+			//
228
+			// See Issue 222
229
+			// $this->server->emit('beforeWriteContent',array($uri));
230
+		} catch (DAV\Exception\NotFound $e) {
231
+			// It didn't, lets create it
232
+			$this->server->createFile($uri, fopen('php://memory', 'r'));
233
+			$newFile = true;
234
+		}
235
+
236
+		$this->lockNode($uri, $lockInfo);
237
+
238
+		$response->setHeader('Content-Type', 'application/xml; charset=utf-8');
239
+		$response->setHeader('Lock-Token', '<opaquelocktoken:'.$lockInfo->token.'>');
240
+		$response->setStatus($newFile ? 201 : 200);
241
+		$response->setBody($this->generateLockResponse($lockInfo));
242
+
243
+		// Returning false will interrupt the event chain and mark this method
244
+		// as 'handled'.
245
+		return false;
246
+	}
247
+
248
+	/**
249
+	 * Unlocks a uri.
250
+	 *
251
+	 * This WebDAV method allows you to remove a lock from a node. The client should provide a valid locktoken through the Lock-token http header
252
+	 * The server should return 204 (No content) on success
253
+	 */
254
+	public function httpUnlock(RequestInterface $request, ResponseInterface $response)
255
+	{
256
+		$lockToken = $request->getHeader('Lock-Token');
257
+
258
+		// If the locktoken header is not supplied, we need to throw a bad request exception
259
+		if (!$lockToken) {
260
+			throw new DAV\Exception\BadRequest('No lock token was supplied');
261
+		}
262
+		$path = $request->getPath();
263
+		$locks = $this->getLocks($path);
264
+
265
+		// Windows sometimes forgets to include < and > in the Lock-Token
266
+		// header
267
+		if ('<' !== $lockToken[0]) {
268
+			$lockToken = '<'.$lockToken.'>';
269
+		}
270
+
271
+		foreach ($locks as $lock) {
272
+			if ('<opaquelocktoken:'.$lock->token.'>' == $lockToken) {
273
+				$this->unlockNode($path, $lock);
274
+				$response->setHeader('Content-Length', '0');
275
+				$response->setStatus(204);
276
+
277
+				// Returning false will break the method chain, and mark the
278
+				// method as 'handled'.
279
+				return false;
280
+			}
281
+		}
282
+
283
+		// If we got here, it means the locktoken was invalid
284
+		throw new DAV\Exception\LockTokenMatchesRequestUri();
285
+	}
286
+
287
+	/**
288
+	 * This method is called after a node is deleted.
289
+	 *
290
+	 * We use this event to clean up any locks that still exist on the node.
291
+	 *
292
+	 * @param string $path
293
+	 */
294
+	public function afterUnbind($path)
295
+	{
296
+		$locks = $this->getLocks($path, $includeChildren = true);
297
+		foreach ($locks as $lock) {
298
+			// don't delete a lock on a parent dir
299
+			if (0 !== strpos($lock->uri, $path)) {
300
+				continue;
301
+			}
302
+			$this->unlockNode($path, $lock);
303
+		}
304
+	}
305
+
306
+	/**
307
+	 * Locks a uri.
308
+	 *
309
+	 * All the locking information is supplied in the lockInfo object. The object has a suggested timeout, but this can be safely ignored
310
+	 * It is important that if the existing timeout is ignored, the property is overwritten, as this needs to be sent back to the client
311
+	 *
312
+	 * @param string $uri
313
+	 *
314
+	 * @return bool
315
+	 */
316
+	public function lockNode($uri, LockInfo $lockInfo)
317
+	{
318
+		if (!$this->server->emit('beforeLock', [$uri, $lockInfo])) {
319
+			return;
320
+		}
321
+
322
+		return $this->locksBackend->lock($uri, $lockInfo);
323
+	}
324
+
325
+	/**
326
+	 * Unlocks a uri.
327
+	 *
328
+	 * This method removes a lock from a uri. It is assumed all the supplied information is correct and verified
329
+	 *
330
+	 * @param string $uri
331
+	 *
332
+	 * @return bool
333
+	 */
334
+	public function unlockNode($uri, LockInfo $lockInfo)
335
+	{
336
+		if (!$this->server->emit('beforeUnlock', [$uri, $lockInfo])) {
337
+			return;
338
+		}
339
+
340
+		return $this->locksBackend->unlock($uri, $lockInfo);
341
+	}
342
+
343
+	/**
344
+	 * Returns the contents of the HTTP Timeout header.
345
+	 *
346
+	 * The method formats the header into an integer.
347
+	 *
348
+	 * @return int
349
+	 */
350
+	public function getTimeoutHeader()
351
+	{
352
+		$header = $this->server->httpRequest->getHeader('Timeout');
353
+
354
+		if ($header) {
355
+			if (0 === stripos($header, 'second-')) {
356
+				$header = (int) (substr($header, 7));
357
+			} elseif (0 === stripos($header, 'infinite')) {
358
+				$header = LockInfo::TIMEOUT_INFINITE;
359
+			} else {
360
+				throw new DAV\Exception\BadRequest('Invalid HTTP timeout header');
361
+			}
362
+		} else {
363
+			$header = 0;
364
+		}
365
+
366
+		return $header;
367
+	}
368
+
369
+	/**
370
+	 * Generates the response for successful LOCK requests.
371
+	 *
372
+	 * @return string
373
+	 */
374
+	protected function generateLockResponse(LockInfo $lockInfo)
375
+	{
376
+		return $this->server->xml->write('{DAV:}prop', [
377
+			'{DAV:}lockdiscovery' => new DAV\Xml\Property\LockDiscovery([$lockInfo]),
378
+		], $this->server->getBaseUri());
379
+	}
380
+
381
+	/**
382
+	 * The validateTokens event is triggered before every request.
383
+	 *
384
+	 * It's a moment where this plugin can check all the supplied lock tokens
385
+	 * in the If: header, and check if they are valid.
386
+	 *
387
+	 * In addition, it will also ensure that it checks any missing lokens that
388
+	 * must be present in the request, and reject requests without the proper
389
+	 * tokens.
390
+	 *
391
+	 * @param mixed $conditions
392
+	 */
393
+	public function validateTokens(RequestInterface $request, &$conditions)
394
+	{
395
+		// First we need to gather a list of locks that must be satisfied.
396
+		$mustLocks = [];
397
+		$method = $request->getMethod();
398
+
399
+		// Methods not in that list are operations that doesn't alter any
400
+		// resources, and we don't need to check the lock-states for.
401
+		switch ($method) {
402
+			case 'DELETE':
403
+				$mustLocks = array_merge($mustLocks, $this->getLocks(
404
+					$request->getPath(),
405
+					true
406
+				));
407
+				break;
408
+			case 'MKCOL':
409
+			case 'MKCALENDAR':
410
+			case 'PROPPATCH':
411
+			case 'PUT':
412
+			case 'PATCH':
413
+				$mustLocks = array_merge($mustLocks, $this->getLocks(
414
+					$request->getPath(),
415
+					false
416
+				));
417
+				break;
418
+			case 'MOVE':
419
+				$mustLocks = array_merge($mustLocks, $this->getLocks(
420
+					$request->getPath(),
421
+					true
422
+				));
423
+				$mustLocks = array_merge($mustLocks, $this->getLocks(
424
+					$this->server->calculateUri($request->getHeader('Destination')),
425
+					false
426
+				));
427
+				break;
428
+			case 'COPY':
429
+				$mustLocks = array_merge($mustLocks, $this->getLocks(
430
+					$this->server->calculateUri($request->getHeader('Destination')),
431
+					false
432
+				));
433
+				break;
434
+			case 'LOCK':
435
+				//Temporary measure.. figure out later why this is needed
436
+				// Here we basically ignore all incoming tokens...
437
+				foreach ($conditions as $ii => $condition) {
438
+					foreach ($condition['tokens'] as $jj => $token) {
439
+						$conditions[$ii]['tokens'][$jj]['validToken'] = true;
440
+					}
441
+				}
442
+
443
+				return;
444
+		}
445
+
446
+		// It's possible that there's identical locks, because of shared
447
+		// parents. We're removing the duplicates here.
448
+		$tmp = [];
449
+		foreach ($mustLocks as $lock) {
450
+			$tmp[$lock->token] = $lock;
451
+		}
452
+		$mustLocks = array_values($tmp);
453
+
454
+		foreach ($conditions as $kk => $condition) {
455
+			foreach ($condition['tokens'] as $ii => $token) {
456
+				// Lock tokens always start with opaquelocktoken:
457
+				if ('opaquelocktoken:' !== substr($token['token'], 0, 16)) {
458
+					continue;
459
+				}
460
+
461
+				$checkToken = substr($token['token'], 16);
462
+				// Looping through our list with locks.
463
+				foreach ($mustLocks as $jj => $mustLock) {
464
+					if ($mustLock->token == $checkToken) {
465
+						// We have a match!
466
+						// Removing this one from mustlocks
467
+						unset($mustLocks[$jj]);
468
+
469
+						// Marking the condition as valid.
470
+						$conditions[$kk]['tokens'][$ii]['validToken'] = true;
471
+
472
+						// Advancing to the next token
473
+						continue 2;
474
+					}
475
+				}
476
+
477
+				// If we got here, it means that there was a
478
+				// lock-token, but it was not in 'mustLocks'.
479
+				//
480
+				// This is an edge-case, as it could mean that token
481
+				// was specified with a url that was not 'required' to
482
+				// check. So we're doing one extra lookup to make sure
483
+				// we really don't know this token.
484
+				//
485
+				// This also gets triggered when the user specified a
486
+				// lock-token that was expired.
487
+				$oddLocks = $this->getLocks($condition['uri']);
488
+				foreach ($oddLocks as $oddLock) {
489
+					if ($oddLock->token === $checkToken) {
490
+						// We have a hit!
491
+						$conditions[$kk]['tokens'][$ii]['validToken'] = true;
492
+						continue 2;
493
+					}
494
+				}
495
+
496
+				// If we get all the way here, the lock-token was
497
+				// really unknown.
498
+			}
499
+		}
500
+
501
+		// If there's any locks left in the 'mustLocks' array, it means that
502
+		// the resource was locked and we must block it.
503
+		if ($mustLocks) {
504
+			throw new DAV\Exception\Locked(reset($mustLocks));
505
+		}
506
+	}
507
+
508
+	/**
509
+	 * Parses a webdav lock xml body, and returns a new Sabre\DAV\Locks\LockInfo object.
510
+	 *
511
+	 * @param string $body
512
+	 *
513
+	 * @return LockInfo
514
+	 */
515
+	protected function parseLockRequest($body)
516
+	{
517
+		$result = $this->server->xml->expect(
518
+			'{DAV:}lockinfo',
519
+			$body
520
+		);
521
+
522
+		$lockInfo = new LockInfo();
523
+
524
+		$lockInfo->owner = $result->owner;
525
+		$lockInfo->token = DAV\UUIDUtil::getUUID();
526
+		$lockInfo->scope = $result->scope;
527
+
528
+		return $lockInfo;
529
+	}
530
+
531
+	/**
532
+	 * Returns a bunch of meta-data about the plugin.
533
+	 *
534
+	 * Providing this information is optional, and is mainly displayed by the
535
+	 * Browser plugin.
536
+	 *
537
+	 * The description key in the returned array may contain html and will not
538
+	 * be sanitized.
539
+	 *
540
+	 * @return array
541
+	 */
542
+	public function getPluginInfo()
543
+	{
544
+		return [
545
+			'name' => $this->getPluginName(),
546
+			'description' => 'The locks plugin turns this server into a class-2 WebDAV server and adds support for LOCK and UNLOCK',
547
+			'link' => 'http://sabre.io/dav/locks/',
548
+		];
549
+	}
550 550
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/DAV/Locks/Backend/BackendInterface.php 1 patch
Indentation   +31 added lines, -31 removed lines patch added patch discarded remove patch
@@ -16,37 +16,37 @@
 block discarded – undo
16 16
  */
17 17
 interface BackendInterface
18 18
 {
19
-    /**
20
-     * Returns a list of Sabre\DAV\Locks\LockInfo objects.
21
-     *
22
-     * This method should return all the locks for a particular uri, including
23
-     * locks that might be set on a parent uri.
24
-     *
25
-     * If returnChildLocks is set to true, this method should also look for
26
-     * any locks in the subtree of the uri for locks.
27
-     *
28
-     * @param string $uri
29
-     * @param bool   $returnChildLocks
30
-     *
31
-     * @return array
32
-     */
33
-    public function getLocks($uri, $returnChildLocks);
19
+	/**
20
+	 * Returns a list of Sabre\DAV\Locks\LockInfo objects.
21
+	 *
22
+	 * This method should return all the locks for a particular uri, including
23
+	 * locks that might be set on a parent uri.
24
+	 *
25
+	 * If returnChildLocks is set to true, this method should also look for
26
+	 * any locks in the subtree of the uri for locks.
27
+	 *
28
+	 * @param string $uri
29
+	 * @param bool   $returnChildLocks
30
+	 *
31
+	 * @return array
32
+	 */
33
+	public function getLocks($uri, $returnChildLocks);
34 34
 
35
-    /**
36
-     * Locks a uri.
37
-     *
38
-     * @param string $uri
39
-     *
40
-     * @return bool
41
-     */
42
-    public function lock($uri, Locks\LockInfo $lockInfo);
35
+	/**
36
+	 * Locks a uri.
37
+	 *
38
+	 * @param string $uri
39
+	 *
40
+	 * @return bool
41
+	 */
42
+	public function lock($uri, Locks\LockInfo $lockInfo);
43 43
 
44
-    /**
45
-     * Removes a lock from a uri.
46
-     *
47
-     * @param string $uri
48
-     *
49
-     * @return bool
50
-     */
51
-    public function unlock($uri, Locks\LockInfo $lockInfo);
44
+	/**
45
+	 * Removes a lock from a uri.
46
+	 *
47
+	 * @param string $uri
48
+	 *
49
+	 * @return bool
50
+	 */
51
+	public function unlock($uri, Locks\LockInfo $lockInfo);
52 52
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/DAV/Locks/Backend/File.php 1 patch
Indentation   +158 added lines, -158 removed lines patch added patch discarded remove patch
@@ -21,162 +21,162 @@
 block discarded – undo
21 21
  */
22 22
 class File extends AbstractBackend
23 23
 {
24
-    /**
25
-     * The storage file.
26
-     *
27
-     * @var string
28
-     */
29
-    private $locksFile;
30
-
31
-    /**
32
-     * Constructor.
33
-     *
34
-     * @param string $locksFile path to file
35
-     */
36
-    public function __construct($locksFile)
37
-    {
38
-        $this->locksFile = $locksFile;
39
-    }
40
-
41
-    /**
42
-     * Returns a list of Sabre\DAV\Locks\LockInfo objects.
43
-     *
44
-     * This method should return all the locks for a particular uri, including
45
-     * locks that might be set on a parent uri.
46
-     *
47
-     * If returnChildLocks is set to true, this method should also look for
48
-     * any locks in the subtree of the uri for locks.
49
-     *
50
-     * @param string $uri
51
-     * @param bool   $returnChildLocks
52
-     *
53
-     * @return array
54
-     */
55
-    public function getLocks($uri, $returnChildLocks)
56
-    {
57
-        $newLocks = [];
58
-
59
-        $locks = $this->getData();
60
-
61
-        foreach ($locks as $lock) {
62
-            if ($lock->uri === $uri ||
63
-                //deep locks on parents
64
-                (0 != $lock->depth && 0 === strpos($uri, $lock->uri.'/')) ||
65
-
66
-                // locks on children
67
-                ($returnChildLocks && (0 === strpos($lock->uri, $uri.'/')))) {
68
-                $newLocks[] = $lock;
69
-            }
70
-        }
71
-
72
-        // Checking if we can remove any of these locks
73
-        foreach ($newLocks as $k => $lock) {
74
-            if (time() > $lock->timeout + $lock->created) {
75
-                unset($newLocks[$k]);
76
-            }
77
-        }
78
-
79
-        return $newLocks;
80
-    }
81
-
82
-    /**
83
-     * Locks a uri.
84
-     *
85
-     * @param string $uri
86
-     *
87
-     * @return bool
88
-     */
89
-    public function lock($uri, LockInfo $lockInfo)
90
-    {
91
-        // We're making the lock timeout 30 minutes
92
-        $lockInfo->timeout = 1800;
93
-        $lockInfo->created = time();
94
-        $lockInfo->uri = $uri;
95
-
96
-        $locks = $this->getData();
97
-
98
-        foreach ($locks as $k => $lock) {
99
-            if (
100
-                ($lock->token == $lockInfo->token) ||
101
-                (time() > $lock->timeout + $lock->created)
102
-            ) {
103
-                unset($locks[$k]);
104
-            }
105
-        }
106
-        $locks[] = $lockInfo;
107
-        $this->putData($locks);
108
-
109
-        return true;
110
-    }
111
-
112
-    /**
113
-     * Removes a lock from a uri.
114
-     *
115
-     * @param string $uri
116
-     *
117
-     * @return bool
118
-     */
119
-    public function unlock($uri, LockInfo $lockInfo)
120
-    {
121
-        $locks = $this->getData();
122
-        foreach ($locks as $k => $lock) {
123
-            if ($lock->token == $lockInfo->token) {
124
-                unset($locks[$k]);
125
-                $this->putData($locks);
126
-
127
-                return true;
128
-            }
129
-        }
130
-
131
-        return false;
132
-    }
133
-
134
-    /**
135
-     * Loads the lockdata from the filesystem.
136
-     *
137
-     * @return array
138
-     */
139
-    protected function getData()
140
-    {
141
-        if (!file_exists($this->locksFile)) {
142
-            return [];
143
-        }
144
-
145
-        // opening up the file, and creating a shared lock
146
-        $handle = fopen($this->locksFile, 'r');
147
-        flock($handle, LOCK_SH);
148
-
149
-        // Reading data until the eof
150
-        $data = stream_get_contents($handle);
151
-
152
-        // We're all good
153
-        flock($handle, LOCK_UN);
154
-        fclose($handle);
155
-
156
-        // Unserializing and checking if the resource file contains data for this file
157
-        $data = unserialize($data);
158
-        if (!$data) {
159
-            return [];
160
-        }
161
-
162
-        return $data;
163
-    }
164
-
165
-    /**
166
-     * Saves the lockdata.
167
-     */
168
-    protected function putData(array $newData)
169
-    {
170
-        // opening up the file, and creating an exclusive lock
171
-        $handle = fopen($this->locksFile, 'a+');
172
-        flock($handle, LOCK_EX);
173
-
174
-        // We can only truncate and rewind once the lock is acquired.
175
-        ftruncate($handle, 0);
176
-        rewind($handle);
177
-
178
-        fwrite($handle, serialize($newData));
179
-        flock($handle, LOCK_UN);
180
-        fclose($handle);
181
-    }
24
+	/**
25
+	 * The storage file.
26
+	 *
27
+	 * @var string
28
+	 */
29
+	private $locksFile;
30
+
31
+	/**
32
+	 * Constructor.
33
+	 *
34
+	 * @param string $locksFile path to file
35
+	 */
36
+	public function __construct($locksFile)
37
+	{
38
+		$this->locksFile = $locksFile;
39
+	}
40
+
41
+	/**
42
+	 * Returns a list of Sabre\DAV\Locks\LockInfo objects.
43
+	 *
44
+	 * This method should return all the locks for a particular uri, including
45
+	 * locks that might be set on a parent uri.
46
+	 *
47
+	 * If returnChildLocks is set to true, this method should also look for
48
+	 * any locks in the subtree of the uri for locks.
49
+	 *
50
+	 * @param string $uri
51
+	 * @param bool   $returnChildLocks
52
+	 *
53
+	 * @return array
54
+	 */
55
+	public function getLocks($uri, $returnChildLocks)
56
+	{
57
+		$newLocks = [];
58
+
59
+		$locks = $this->getData();
60
+
61
+		foreach ($locks as $lock) {
62
+			if ($lock->uri === $uri ||
63
+				//deep locks on parents
64
+				(0 != $lock->depth && 0 === strpos($uri, $lock->uri.'/')) ||
65
+
66
+				// locks on children
67
+				($returnChildLocks && (0 === strpos($lock->uri, $uri.'/')))) {
68
+				$newLocks[] = $lock;
69
+			}
70
+		}
71
+
72
+		// Checking if we can remove any of these locks
73
+		foreach ($newLocks as $k => $lock) {
74
+			if (time() > $lock->timeout + $lock->created) {
75
+				unset($newLocks[$k]);
76
+			}
77
+		}
78
+
79
+		return $newLocks;
80
+	}
81
+
82
+	/**
83
+	 * Locks a uri.
84
+	 *
85
+	 * @param string $uri
86
+	 *
87
+	 * @return bool
88
+	 */
89
+	public function lock($uri, LockInfo $lockInfo)
90
+	{
91
+		// We're making the lock timeout 30 minutes
92
+		$lockInfo->timeout = 1800;
93
+		$lockInfo->created = time();
94
+		$lockInfo->uri = $uri;
95
+
96
+		$locks = $this->getData();
97
+
98
+		foreach ($locks as $k => $lock) {
99
+			if (
100
+				($lock->token == $lockInfo->token) ||
101
+				(time() > $lock->timeout + $lock->created)
102
+			) {
103
+				unset($locks[$k]);
104
+			}
105
+		}
106
+		$locks[] = $lockInfo;
107
+		$this->putData($locks);
108
+
109
+		return true;
110
+	}
111
+
112
+	/**
113
+	 * Removes a lock from a uri.
114
+	 *
115
+	 * @param string $uri
116
+	 *
117
+	 * @return bool
118
+	 */
119
+	public function unlock($uri, LockInfo $lockInfo)
120
+	{
121
+		$locks = $this->getData();
122
+		foreach ($locks as $k => $lock) {
123
+			if ($lock->token == $lockInfo->token) {
124
+				unset($locks[$k]);
125
+				$this->putData($locks);
126
+
127
+				return true;
128
+			}
129
+		}
130
+
131
+		return false;
132
+	}
133
+
134
+	/**
135
+	 * Loads the lockdata from the filesystem.
136
+	 *
137
+	 * @return array
138
+	 */
139
+	protected function getData()
140
+	{
141
+		if (!file_exists($this->locksFile)) {
142
+			return [];
143
+		}
144
+
145
+		// opening up the file, and creating a shared lock
146
+		$handle = fopen($this->locksFile, 'r');
147
+		flock($handle, LOCK_SH);
148
+
149
+		// Reading data until the eof
150
+		$data = stream_get_contents($handle);
151
+
152
+		// We're all good
153
+		flock($handle, LOCK_UN);
154
+		fclose($handle);
155
+
156
+		// Unserializing and checking if the resource file contains data for this file
157
+		$data = unserialize($data);
158
+		if (!$data) {
159
+			return [];
160
+		}
161
+
162
+		return $data;
163
+	}
164
+
165
+	/**
166
+	 * Saves the lockdata.
167
+	 */
168
+	protected function putData(array $newData)
169
+	{
170
+		// opening up the file, and creating an exclusive lock
171
+		$handle = fopen($this->locksFile, 'a+');
172
+		flock($handle, LOCK_EX);
173
+
174
+		// We can only truncate and rewind once the lock is acquired.
175
+		ftruncate($handle, 0);
176
+		rewind($handle);
177
+
178
+		fwrite($handle, serialize($newData));
179
+		flock($handle, LOCK_UN);
180
+		fclose($handle);
181
+	}
182 182
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/DAV/Locks/Backend/PDO.php 1 patch
Indentation   +151 added lines, -151 removed lines patch added patch discarded remove patch
@@ -18,155 +18,155 @@
 block discarded – undo
18 18
  */
19 19
 class PDO extends AbstractBackend
20 20
 {
21
-    /**
22
-     * The PDO tablename this backend uses.
23
-     *
24
-     * @var string
25
-     */
26
-    public $tableName = 'locks';
27
-
28
-    /**
29
-     * The PDO connection object.
30
-     *
31
-     * @var pdo
32
-     */
33
-    protected $pdo;
34
-
35
-    /**
36
-     * Constructor.
37
-     */
38
-    public function __construct(\PDO $pdo)
39
-    {
40
-        $this->pdo = $pdo;
41
-    }
42
-
43
-    /**
44
-     * Returns a list of Sabre\DAV\Locks\LockInfo objects.
45
-     *
46
-     * This method should return all the locks for a particular uri, including
47
-     * locks that might be set on a parent uri.
48
-     *
49
-     * If returnChildLocks is set to true, this method should also look for
50
-     * any locks in the subtree of the uri for locks.
51
-     *
52
-     * @param string $uri
53
-     * @param bool   $returnChildLocks
54
-     *
55
-     * @return array
56
-     */
57
-    public function getLocks($uri, $returnChildLocks)
58
-    {
59
-        // NOTE: the following 10 lines or so could be easily replaced by
60
-        // pure sql. MySQL's non-standard string concatenation prevents us
61
-        // from doing this though.
62
-        $query = 'SELECT owner, token, timeout, created, scope, depth, uri FROM '.$this->tableName.' WHERE (created > (? - timeout)) AND ((uri = ?)';
63
-        $params = [time(), $uri];
64
-
65
-        // We need to check locks for every part in the uri.
66
-        $uriParts = explode('/', $uri);
67
-
68
-        // We already covered the last part of the uri
69
-        array_pop($uriParts);
70
-
71
-        $currentPath = '';
72
-
73
-        foreach ($uriParts as $part) {
74
-            if ($currentPath) {
75
-                $currentPath .= '/';
76
-            }
77
-            $currentPath .= $part;
78
-
79
-            $query .= ' OR (depth!=0 AND uri = ?)';
80
-            $params[] = $currentPath;
81
-        }
82
-
83
-        if ($returnChildLocks) {
84
-            $query .= ' OR (uri LIKE ?)';
85
-            $params[] = $uri.'/%';
86
-        }
87
-        $query .= ')';
88
-
89
-        $stmt = $this->pdo->prepare($query);
90
-        $stmt->execute($params);
91
-        $result = $stmt->fetchAll();
92
-
93
-        $lockList = [];
94
-        foreach ($result as $row) {
95
-            $lockInfo = new LockInfo();
96
-            $lockInfo->owner = $row['owner'];
97
-            $lockInfo->token = $row['token'];
98
-            $lockInfo->timeout = $row['timeout'];
99
-            $lockInfo->created = $row['created'];
100
-            $lockInfo->scope = $row['scope'];
101
-            $lockInfo->depth = $row['depth'];
102
-            $lockInfo->uri = $row['uri'];
103
-            $lockList[] = $lockInfo;
104
-        }
105
-
106
-        return $lockList;
107
-    }
108
-
109
-    /**
110
-     * Locks a uri.
111
-     *
112
-     * @param string $uri
113
-     *
114
-     * @return bool
115
-     */
116
-    public function lock($uri, LockInfo $lockInfo)
117
-    {
118
-        // We're making the lock timeout 30 minutes
119
-        $lockInfo->timeout = 30 * 60;
120
-        $lockInfo->created = time();
121
-        $lockInfo->uri = $uri;
122
-
123
-        $locks = $this->getLocks($uri, false);
124
-        $exists = false;
125
-        foreach ($locks as $lock) {
126
-            if ($lock->token == $lockInfo->token) {
127
-                $exists = true;
128
-            }
129
-        }
130
-
131
-        if ($exists) {
132
-            $stmt = $this->pdo->prepare('UPDATE '.$this->tableName.' SET owner = ?, timeout = ?, scope = ?, depth = ?, uri = ?, created = ? WHERE token = ?');
133
-            $stmt->execute([
134
-                $lockInfo->owner,
135
-                $lockInfo->timeout,
136
-                $lockInfo->scope,
137
-                $lockInfo->depth,
138
-                $uri,
139
-                $lockInfo->created,
140
-                $lockInfo->token,
141
-            ]);
142
-        } else {
143
-            $stmt = $this->pdo->prepare('INSERT INTO '.$this->tableName.' (owner,timeout,scope,depth,uri,created,token) VALUES (?,?,?,?,?,?,?)');
144
-            $stmt->execute([
145
-                $lockInfo->owner,
146
-                $lockInfo->timeout,
147
-                $lockInfo->scope,
148
-                $lockInfo->depth,
149
-                $uri,
150
-                $lockInfo->created,
151
-                $lockInfo->token,
152
-            ]);
153
-        }
154
-
155
-        return true;
156
-    }
157
-
158
-    /**
159
-     * Removes a lock from a uri.
160
-     *
161
-     * @param string $uri
162
-     *
163
-     * @return bool
164
-     */
165
-    public function unlock($uri, LockInfo $lockInfo)
166
-    {
167
-        $stmt = $this->pdo->prepare('DELETE FROM '.$this->tableName.' WHERE uri = ? AND token = ?');
168
-        $stmt->execute([$uri, $lockInfo->token]);
169
-
170
-        return 1 === $stmt->rowCount();
171
-    }
21
+	/**
22
+	 * The PDO tablename this backend uses.
23
+	 *
24
+	 * @var string
25
+	 */
26
+	public $tableName = 'locks';
27
+
28
+	/**
29
+	 * The PDO connection object.
30
+	 *
31
+	 * @var pdo
32
+	 */
33
+	protected $pdo;
34
+
35
+	/**
36
+	 * Constructor.
37
+	 */
38
+	public function __construct(\PDO $pdo)
39
+	{
40
+		$this->pdo = $pdo;
41
+	}
42
+
43
+	/**
44
+	 * Returns a list of Sabre\DAV\Locks\LockInfo objects.
45
+	 *
46
+	 * This method should return all the locks for a particular uri, including
47
+	 * locks that might be set on a parent uri.
48
+	 *
49
+	 * If returnChildLocks is set to true, this method should also look for
50
+	 * any locks in the subtree of the uri for locks.
51
+	 *
52
+	 * @param string $uri
53
+	 * @param bool   $returnChildLocks
54
+	 *
55
+	 * @return array
56
+	 */
57
+	public function getLocks($uri, $returnChildLocks)
58
+	{
59
+		// NOTE: the following 10 lines or so could be easily replaced by
60
+		// pure sql. MySQL's non-standard string concatenation prevents us
61
+		// from doing this though.
62
+		$query = 'SELECT owner, token, timeout, created, scope, depth, uri FROM '.$this->tableName.' WHERE (created > (? - timeout)) AND ((uri = ?)';
63
+		$params = [time(), $uri];
64
+
65
+		// We need to check locks for every part in the uri.
66
+		$uriParts = explode('/', $uri);
67
+
68
+		// We already covered the last part of the uri
69
+		array_pop($uriParts);
70
+
71
+		$currentPath = '';
72
+
73
+		foreach ($uriParts as $part) {
74
+			if ($currentPath) {
75
+				$currentPath .= '/';
76
+			}
77
+			$currentPath .= $part;
78
+
79
+			$query .= ' OR (depth!=0 AND uri = ?)';
80
+			$params[] = $currentPath;
81
+		}
82
+
83
+		if ($returnChildLocks) {
84
+			$query .= ' OR (uri LIKE ?)';
85
+			$params[] = $uri.'/%';
86
+		}
87
+		$query .= ')';
88
+
89
+		$stmt = $this->pdo->prepare($query);
90
+		$stmt->execute($params);
91
+		$result = $stmt->fetchAll();
92
+
93
+		$lockList = [];
94
+		foreach ($result as $row) {
95
+			$lockInfo = new LockInfo();
96
+			$lockInfo->owner = $row['owner'];
97
+			$lockInfo->token = $row['token'];
98
+			$lockInfo->timeout = $row['timeout'];
99
+			$lockInfo->created = $row['created'];
100
+			$lockInfo->scope = $row['scope'];
101
+			$lockInfo->depth = $row['depth'];
102
+			$lockInfo->uri = $row['uri'];
103
+			$lockList[] = $lockInfo;
104
+		}
105
+
106
+		return $lockList;
107
+	}
108
+
109
+	/**
110
+	 * Locks a uri.
111
+	 *
112
+	 * @param string $uri
113
+	 *
114
+	 * @return bool
115
+	 */
116
+	public function lock($uri, LockInfo $lockInfo)
117
+	{
118
+		// We're making the lock timeout 30 minutes
119
+		$lockInfo->timeout = 30 * 60;
120
+		$lockInfo->created = time();
121
+		$lockInfo->uri = $uri;
122
+
123
+		$locks = $this->getLocks($uri, false);
124
+		$exists = false;
125
+		foreach ($locks as $lock) {
126
+			if ($lock->token == $lockInfo->token) {
127
+				$exists = true;
128
+			}
129
+		}
130
+
131
+		if ($exists) {
132
+			$stmt = $this->pdo->prepare('UPDATE '.$this->tableName.' SET owner = ?, timeout = ?, scope = ?, depth = ?, uri = ?, created = ? WHERE token = ?');
133
+			$stmt->execute([
134
+				$lockInfo->owner,
135
+				$lockInfo->timeout,
136
+				$lockInfo->scope,
137
+				$lockInfo->depth,
138
+				$uri,
139
+				$lockInfo->created,
140
+				$lockInfo->token,
141
+			]);
142
+		} else {
143
+			$stmt = $this->pdo->prepare('INSERT INTO '.$this->tableName.' (owner,timeout,scope,depth,uri,created,token) VALUES (?,?,?,?,?,?,?)');
144
+			$stmt->execute([
145
+				$lockInfo->owner,
146
+				$lockInfo->timeout,
147
+				$lockInfo->scope,
148
+				$lockInfo->depth,
149
+				$uri,
150
+				$lockInfo->created,
151
+				$lockInfo->token,
152
+			]);
153
+		}
154
+
155
+		return true;
156
+	}
157
+
158
+	/**
159
+	 * Removes a lock from a uri.
160
+	 *
161
+	 * @param string $uri
162
+	 *
163
+	 * @return bool
164
+	 */
165
+	public function unlock($uri, LockInfo $lockInfo)
166
+	{
167
+		$stmt = $this->pdo->prepare('DELETE FROM '.$this->tableName.' WHERE uri = ? AND token = ?');
168
+		$stmt->execute([$uri, $lockInfo->token]);
169
+
170
+		return 1 === $stmt->rowCount();
171
+	}
172 172
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/DAV/Locks/LockInfo.php 1 patch
Indentation   +54 added lines, -54 removed lines patch added patch discarded remove patch
@@ -16,67 +16,67 @@
 block discarded – undo
16 16
  */
17 17
 class LockInfo
18 18
 {
19
-    /**
20
-     * A shared lock.
21
-     */
22
-    const SHARED = 1;
19
+	/**
20
+	 * A shared lock.
21
+	 */
22
+	const SHARED = 1;
23 23
 
24
-    /**
25
-     * An exclusive lock.
26
-     */
27
-    const EXCLUSIVE = 2;
24
+	/**
25
+	 * An exclusive lock.
26
+	 */
27
+	const EXCLUSIVE = 2;
28 28
 
29
-    /**
30
-     * A never expiring timeout.
31
-     */
32
-    const TIMEOUT_INFINITE = -1;
29
+	/**
30
+	 * A never expiring timeout.
31
+	 */
32
+	const TIMEOUT_INFINITE = -1;
33 33
 
34
-    /**
35
-     * The owner of the lock.
36
-     *
37
-     * @var string
38
-     */
39
-    public $owner;
34
+	/**
35
+	 * The owner of the lock.
36
+	 *
37
+	 * @var string
38
+	 */
39
+	public $owner;
40 40
 
41
-    /**
42
-     * The locktoken.
43
-     *
44
-     * @var string
45
-     */
46
-    public $token;
41
+	/**
42
+	 * The locktoken.
43
+	 *
44
+	 * @var string
45
+	 */
46
+	public $token;
47 47
 
48
-    /**
49
-     * How long till the lock is expiring.
50
-     *
51
-     * @var int
52
-     */
53
-    public $timeout;
48
+	/**
49
+	 * How long till the lock is expiring.
50
+	 *
51
+	 * @var int
52
+	 */
53
+	public $timeout;
54 54
 
55
-    /**
56
-     * UNIX Timestamp of when this lock was created.
57
-     *
58
-     * @var int
59
-     */
60
-    public $created;
55
+	/**
56
+	 * UNIX Timestamp of when this lock was created.
57
+	 *
58
+	 * @var int
59
+	 */
60
+	public $created;
61 61
 
62
-    /**
63
-     * Exclusive or shared lock.
64
-     *
65
-     * @var int
66
-     */
67
-    public $scope = self::EXCLUSIVE;
62
+	/**
63
+	 * Exclusive or shared lock.
64
+	 *
65
+	 * @var int
66
+	 */
67
+	public $scope = self::EXCLUSIVE;
68 68
 
69
-    /**
70
-     * Depth of lock, can be 0 or Sabre\DAV\Server::DEPTH_INFINITY.
71
-     */
72
-    public $depth = 0;
69
+	/**
70
+	 * Depth of lock, can be 0 or Sabre\DAV\Server::DEPTH_INFINITY.
71
+	 */
72
+	public $depth = 0;
73 73
 
74
-    /**
75
-     * The uri this lock locks.
76
-     *
77
-     * TODO: This value is not always set
78
-     *
79
-     * @var mixed
80
-     */
81
-    public $uri;
74
+	/**
75
+	 * The uri this lock locks.
76
+	 *
77
+	 * TODO: This value is not always set
78
+	 *
79
+	 * @var mixed
80
+	 */
81
+	public $uri;
82 82
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/DAV/IProperties.php 1 patch
Indentation   +27 added lines, -27 removed lines patch added patch discarded remove patch
@@ -15,32 +15,32 @@
 block discarded – undo
15 15
  */
16 16
 interface IProperties extends INode
17 17
 {
18
-    /**
19
-     * Updates properties on this node.
20
-     *
21
-     * This method received a PropPatch object, which contains all the
22
-     * information about the update.
23
-     *
24
-     * To update specific properties, call the 'handle' method on this object.
25
-     * Read the PropPatch documentation for more information.
26
-     */
27
-    public function propPatch(PropPatch $propPatch);
18
+	/**
19
+	 * Updates properties on this node.
20
+	 *
21
+	 * This method received a PropPatch object, which contains all the
22
+	 * information about the update.
23
+	 *
24
+	 * To update specific properties, call the 'handle' method on this object.
25
+	 * Read the PropPatch documentation for more information.
26
+	 */
27
+	public function propPatch(PropPatch $propPatch);
28 28
 
29
-    /**
30
-     * Returns a list of properties for this nodes.
31
-     *
32
-     * The properties list is a list of propertynames the client requested,
33
-     * encoded in clark-notation {xmlnamespace}tagname
34
-     *
35
-     * If the array is empty, it means 'all properties' were requested.
36
-     *
37
-     * Note that it's fine to liberally give properties back, instead of
38
-     * conforming to the list of requested properties.
39
-     * The Server class will filter out the extra.
40
-     *
41
-     * @param array $properties
42
-     *
43
-     * @return array
44
-     */
45
-    public function getProperties($properties);
29
+	/**
30
+	 * Returns a list of properties for this nodes.
31
+	 *
32
+	 * The properties list is a list of propertynames the client requested,
33
+	 * encoded in clark-notation {xmlnamespace}tagname
34
+	 *
35
+	 * If the array is empty, it means 'all properties' were requested.
36
+	 *
37
+	 * Note that it's fine to liberally give properties back, instead of
38
+	 * conforming to the list of requested properties.
39
+	 * The Server class will filter out the extra.
40
+	 *
41
+	 * @param array $properties
42
+	 *
43
+	 * @return array
44
+	 */
45
+	public function getProperties($properties);
46 46
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/DAV/Auth/Plugin.php 1 patch
Indentation   +226 added lines, -226 removed lines patch added patch discarded remove patch
@@ -26,230 +26,230 @@
 block discarded – undo
26 26
  */
27 27
 class Plugin extends ServerPlugin
28 28
 {
29
-    /**
30
-     * By default this plugin will require that the user is authenticated,
31
-     * and refuse any access if the user is not authenticated.
32
-     *
33
-     * If this setting is set to false, we let the user through, whether they
34
-     * are authenticated or not.
35
-     *
36
-     * This is useful if you want to allow both authenticated and
37
-     * unauthenticated access to your server.
38
-     *
39
-     * @param bool
40
-     */
41
-    public $autoRequireLogin = true;
42
-
43
-    /**
44
-     * authentication backends.
45
-     */
46
-    protected $backends;
47
-
48
-    /**
49
-     * The currently logged in principal. Will be `null` if nobody is currently
50
-     * logged in.
51
-     *
52
-     * @var string|null
53
-     */
54
-    protected $currentPrincipal;
55
-
56
-    /**
57
-     * Creates the authentication plugin.
58
-     *
59
-     * @param Backend\BackendInterface $authBackend
60
-     */
61
-    public function __construct(Backend\BackendInterface $authBackend = null)
62
-    {
63
-        if (!is_null($authBackend)) {
64
-            $this->addBackend($authBackend);
65
-        }
66
-    }
67
-
68
-    /**
69
-     * Adds an authentication backend to the plugin.
70
-     */
71
-    public function addBackend(Backend\BackendInterface $authBackend)
72
-    {
73
-        $this->backends[] = $authBackend;
74
-    }
75
-
76
-    /**
77
-     * Initializes the plugin. This function is automatically called by the server.
78
-     */
79
-    public function initialize(Server $server)
80
-    {
81
-        $server->on('beforeMethod:*', [$this, 'beforeMethod'], 10);
82
-    }
83
-
84
-    /**
85
-     * Returns a plugin name.
86
-     *
87
-     * Using this name other plugins will be able to access other plugins
88
-     * using DAV\Server::getPlugin
89
-     *
90
-     * @return string
91
-     */
92
-    public function getPluginName()
93
-    {
94
-        return 'auth';
95
-    }
96
-
97
-    /**
98
-     * Returns the currently logged-in principal.
99
-     *
100
-     * This will return a string such as:
101
-     *
102
-     * principals/username
103
-     * principals/users/username
104
-     *
105
-     * This method will return null if nobody is logged in.
106
-     *
107
-     * @return string|null
108
-     */
109
-    public function getCurrentPrincipal()
110
-    {
111
-        return $this->currentPrincipal;
112
-    }
113
-
114
-    /**
115
-     * This method is called before any HTTP method and forces users to be authenticated.
116
-     */
117
-    public function beforeMethod(RequestInterface $request, ResponseInterface $response)
118
-    {
119
-        if ($this->currentPrincipal) {
120
-            // We already have authentication information. This means that the
121
-            // event has already fired earlier, and is now likely fired for a
122
-            // sub-request.
123
-            //
124
-            // We don't want to authenticate users twice, so we simply don't do
125
-            // anything here. See Issue #700 for additional reasoning.
126
-            //
127
-            // This is not a perfect solution, but will be fixed once the
128
-            // "currently authenticated principal" is information that's not
129
-            // not associated with the plugin, but rather per-request.
130
-            //
131
-            // See issue #580 for more information about that.
132
-            return;
133
-        }
134
-
135
-        $authResult = $this->check($request, $response);
136
-
137
-        if ($authResult[0]) {
138
-            // Auth was successful
139
-            $this->currentPrincipal = $authResult[1];
140
-            $this->loginFailedReasons = null;
141
-
142
-            return;
143
-        }
144
-
145
-        // If we got here, it means that no authentication backend was
146
-        // successful in authenticating the user.
147
-        $this->currentPrincipal = null;
148
-        $this->loginFailedReasons = $authResult[1];
149
-
150
-        if ($this->autoRequireLogin) {
151
-            $this->challenge($request, $response);
152
-            throw new NotAuthenticated(implode(', ', $authResult[1]));
153
-        }
154
-    }
155
-
156
-    /**
157
-     * Checks authentication credentials, and logs the user in if possible.
158
-     *
159
-     * This method returns an array. The first item in the array is a boolean
160
-     * indicating if login was successful.
161
-     *
162
-     * If login was successful, the second item in the array will contain the
163
-     * current principal url/path of the logged in user.
164
-     *
165
-     * If login was not successful, the second item in the array will contain a
166
-     * an array with strings. The strings are a list of reasons why login was
167
-     * unsuccessful. For every auth backend there will be one reason, so usually
168
-     * there's just one.
169
-     *
170
-     * @return array
171
-     */
172
-    public function check(RequestInterface $request, ResponseInterface $response)
173
-    {
174
-        if (!$this->backends) {
175
-            throw new \Sabre\DAV\Exception('No authentication backends were configured on this server.');
176
-        }
177
-        $reasons = [];
178
-        foreach ($this->backends as $backend) {
179
-            $result = $backend->check(
180
-                $request,
181
-                $response
182
-            );
183
-
184
-            if (!is_array($result) || 2 !== count($result) || !is_bool($result[0]) || !is_string($result[1])) {
185
-                throw new \Sabre\DAV\Exception('The authentication backend did not return a correct value from the check() method.');
186
-            }
187
-
188
-            if ($result[0]) {
189
-                $this->currentPrincipal = $result[1];
190
-                // Exit early
191
-                return [true, $result[1]];
192
-            }
193
-            $reasons[] = $result[1];
194
-        }
195
-
196
-        return [false, $reasons];
197
-    }
198
-
199
-    /**
200
-     * This method sends authentication challenges to the user.
201
-     *
202
-     * This method will for example cause a HTTP Basic backend to set a
203
-     * WWW-Authorization header, indicating to the client that it should
204
-     * authenticate.
205
-     */
206
-    public function challenge(RequestInterface $request, ResponseInterface $response)
207
-    {
208
-        foreach ($this->backends as $backend) {
209
-            $backend->challenge($request, $response);
210
-        }
211
-    }
212
-
213
-    /**
214
-     * List of reasons why login failed for the last login operation.
215
-     *
216
-     * @var string[]|null
217
-     */
218
-    protected $loginFailedReasons;
219
-
220
-    /**
221
-     * Returns a list of reasons why login was unsuccessful.
222
-     *
223
-     * This method will return the login failed reasons for the last login
224
-     * operation. One for each auth backend.
225
-     *
226
-     * This method returns null if the last authentication attempt was
227
-     * successful, or if there was no authentication attempt yet.
228
-     *
229
-     * @return string[]|null
230
-     */
231
-    public function getLoginFailedReasons()
232
-    {
233
-        return $this->loginFailedReasons;
234
-    }
235
-
236
-    /**
237
-     * Returns a bunch of meta-data about the plugin.
238
-     *
239
-     * Providing this information is optional, and is mainly displayed by the
240
-     * Browser plugin.
241
-     *
242
-     * The description key in the returned array may contain html and will not
243
-     * be sanitized.
244
-     *
245
-     * @return array
246
-     */
247
-    public function getPluginInfo()
248
-    {
249
-        return [
250
-            'name' => $this->getPluginName(),
251
-            'description' => 'Generic authentication plugin',
252
-            'link' => 'http://sabre.io/dav/authentication/',
253
-        ];
254
-    }
29
+	/**
30
+	 * By default this plugin will require that the user is authenticated,
31
+	 * and refuse any access if the user is not authenticated.
32
+	 *
33
+	 * If this setting is set to false, we let the user through, whether they
34
+	 * are authenticated or not.
35
+	 *
36
+	 * This is useful if you want to allow both authenticated and
37
+	 * unauthenticated access to your server.
38
+	 *
39
+	 * @param bool
40
+	 */
41
+	public $autoRequireLogin = true;
42
+
43
+	/**
44
+	 * authentication backends.
45
+	 */
46
+	protected $backends;
47
+
48
+	/**
49
+	 * The currently logged in principal. Will be `null` if nobody is currently
50
+	 * logged in.
51
+	 *
52
+	 * @var string|null
53
+	 */
54
+	protected $currentPrincipal;
55
+
56
+	/**
57
+	 * Creates the authentication plugin.
58
+	 *
59
+	 * @param Backend\BackendInterface $authBackend
60
+	 */
61
+	public function __construct(Backend\BackendInterface $authBackend = null)
62
+	{
63
+		if (!is_null($authBackend)) {
64
+			$this->addBackend($authBackend);
65
+		}
66
+	}
67
+
68
+	/**
69
+	 * Adds an authentication backend to the plugin.
70
+	 */
71
+	public function addBackend(Backend\BackendInterface $authBackend)
72
+	{
73
+		$this->backends[] = $authBackend;
74
+	}
75
+
76
+	/**
77
+	 * Initializes the plugin. This function is automatically called by the server.
78
+	 */
79
+	public function initialize(Server $server)
80
+	{
81
+		$server->on('beforeMethod:*', [$this, 'beforeMethod'], 10);
82
+	}
83
+
84
+	/**
85
+	 * Returns a plugin name.
86
+	 *
87
+	 * Using this name other plugins will be able to access other plugins
88
+	 * using DAV\Server::getPlugin
89
+	 *
90
+	 * @return string
91
+	 */
92
+	public function getPluginName()
93
+	{
94
+		return 'auth';
95
+	}
96
+
97
+	/**
98
+	 * Returns the currently logged-in principal.
99
+	 *
100
+	 * This will return a string such as:
101
+	 *
102
+	 * principals/username
103
+	 * principals/users/username
104
+	 *
105
+	 * This method will return null if nobody is logged in.
106
+	 *
107
+	 * @return string|null
108
+	 */
109
+	public function getCurrentPrincipal()
110
+	{
111
+		return $this->currentPrincipal;
112
+	}
113
+
114
+	/**
115
+	 * This method is called before any HTTP method and forces users to be authenticated.
116
+	 */
117
+	public function beforeMethod(RequestInterface $request, ResponseInterface $response)
118
+	{
119
+		if ($this->currentPrincipal) {
120
+			// We already have authentication information. This means that the
121
+			// event has already fired earlier, and is now likely fired for a
122
+			// sub-request.
123
+			//
124
+			// We don't want to authenticate users twice, so we simply don't do
125
+			// anything here. See Issue #700 for additional reasoning.
126
+			//
127
+			// This is not a perfect solution, but will be fixed once the
128
+			// "currently authenticated principal" is information that's not
129
+			// not associated with the plugin, but rather per-request.
130
+			//
131
+			// See issue #580 for more information about that.
132
+			return;
133
+		}
134
+
135
+		$authResult = $this->check($request, $response);
136
+
137
+		if ($authResult[0]) {
138
+			// Auth was successful
139
+			$this->currentPrincipal = $authResult[1];
140
+			$this->loginFailedReasons = null;
141
+
142
+			return;
143
+		}
144
+
145
+		// If we got here, it means that no authentication backend was
146
+		// successful in authenticating the user.
147
+		$this->currentPrincipal = null;
148
+		$this->loginFailedReasons = $authResult[1];
149
+
150
+		if ($this->autoRequireLogin) {
151
+			$this->challenge($request, $response);
152
+			throw new NotAuthenticated(implode(', ', $authResult[1]));
153
+		}
154
+	}
155
+
156
+	/**
157
+	 * Checks authentication credentials, and logs the user in if possible.
158
+	 *
159
+	 * This method returns an array. The first item in the array is a boolean
160
+	 * indicating if login was successful.
161
+	 *
162
+	 * If login was successful, the second item in the array will contain the
163
+	 * current principal url/path of the logged in user.
164
+	 *
165
+	 * If login was not successful, the second item in the array will contain a
166
+	 * an array with strings. The strings are a list of reasons why login was
167
+	 * unsuccessful. For every auth backend there will be one reason, so usually
168
+	 * there's just one.
169
+	 *
170
+	 * @return array
171
+	 */
172
+	public function check(RequestInterface $request, ResponseInterface $response)
173
+	{
174
+		if (!$this->backends) {
175
+			throw new \Sabre\DAV\Exception('No authentication backends were configured on this server.');
176
+		}
177
+		$reasons = [];
178
+		foreach ($this->backends as $backend) {
179
+			$result = $backend->check(
180
+				$request,
181
+				$response
182
+			);
183
+
184
+			if (!is_array($result) || 2 !== count($result) || !is_bool($result[0]) || !is_string($result[1])) {
185
+				throw new \Sabre\DAV\Exception('The authentication backend did not return a correct value from the check() method.');
186
+			}
187
+
188
+			if ($result[0]) {
189
+				$this->currentPrincipal = $result[1];
190
+				// Exit early
191
+				return [true, $result[1]];
192
+			}
193
+			$reasons[] = $result[1];
194
+		}
195
+
196
+		return [false, $reasons];
197
+	}
198
+
199
+	/**
200
+	 * This method sends authentication challenges to the user.
201
+	 *
202
+	 * This method will for example cause a HTTP Basic backend to set a
203
+	 * WWW-Authorization header, indicating to the client that it should
204
+	 * authenticate.
205
+	 */
206
+	public function challenge(RequestInterface $request, ResponseInterface $response)
207
+	{
208
+		foreach ($this->backends as $backend) {
209
+			$backend->challenge($request, $response);
210
+		}
211
+	}
212
+
213
+	/**
214
+	 * List of reasons why login failed for the last login operation.
215
+	 *
216
+	 * @var string[]|null
217
+	 */
218
+	protected $loginFailedReasons;
219
+
220
+	/**
221
+	 * Returns a list of reasons why login was unsuccessful.
222
+	 *
223
+	 * This method will return the login failed reasons for the last login
224
+	 * operation. One for each auth backend.
225
+	 *
226
+	 * This method returns null if the last authentication attempt was
227
+	 * successful, or if there was no authentication attempt yet.
228
+	 *
229
+	 * @return string[]|null
230
+	 */
231
+	public function getLoginFailedReasons()
232
+	{
233
+		return $this->loginFailedReasons;
234
+	}
235
+
236
+	/**
237
+	 * Returns a bunch of meta-data about the plugin.
238
+	 *
239
+	 * Providing this information is optional, and is mainly displayed by the
240
+	 * Browser plugin.
241
+	 *
242
+	 * The description key in the returned array may contain html and will not
243
+	 * be sanitized.
244
+	 *
245
+	 * @return array
246
+	 */
247
+	public function getPluginInfo()
248
+	{
249
+		return [
250
+			'name' => $this->getPluginName(),
251
+			'description' => 'Generic authentication plugin',
252
+			'link' => 'http://sabre.io/dav/authentication/',
253
+		];
254
+	}
255 255
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/DAV/Auth/Backend/Apache.php 1 patch
Indentation   +66 added lines, -66 removed lines patch added patch discarded remove patch
@@ -21,73 +21,73 @@
 block discarded – undo
21 21
  */
22 22
 class Apache implements BackendInterface
23 23
 {
24
-    /**
25
-     * This is the prefix that will be used to generate principal urls.
26
-     *
27
-     * @var string
28
-     */
29
-    protected $principalPrefix = 'principals/';
24
+	/**
25
+	 * This is the prefix that will be used to generate principal urls.
26
+	 *
27
+	 * @var string
28
+	 */
29
+	protected $principalPrefix = 'principals/';
30 30
 
31
-    /**
32
-     * When this method is called, the backend must check if authentication was
33
-     * successful.
34
-     *
35
-     * The returned value must be one of the following
36
-     *
37
-     * [true, "principals/username"]
38
-     * [false, "reason for failure"]
39
-     *
40
-     * If authentication was successful, it's expected that the authentication
41
-     * backend returns a so-called principal url.
42
-     *
43
-     * Examples of a principal url:
44
-     *
45
-     * principals/admin
46
-     * principals/user1
47
-     * principals/users/joe
48
-     * principals/uid/123457
49
-     *
50
-     * If you don't use WebDAV ACL (RFC3744) we recommend that you simply
51
-     * return a string such as:
52
-     *
53
-     * principals/users/[username]
54
-     *
55
-     * @return array
56
-     */
57
-    public function check(RequestInterface $request, ResponseInterface $response)
58
-    {
59
-        $remoteUser = $request->getRawServerValue('REMOTE_USER');
60
-        if (is_null($remoteUser)) {
61
-            $remoteUser = $request->getRawServerValue('REDIRECT_REMOTE_USER');
62
-        }
63
-        if (is_null($remoteUser)) {
64
-            $remoteUser = $request->getRawServerValue('PHP_AUTH_USER');
65
-        }
66
-        if (is_null($remoteUser)) {
67
-            return [false, 'No REMOTE_USER, REDIRECT_REMOTE_USER, or PHP_AUTH_USER property was found in the PHP $_SERVER super-global. This likely means your server is not configured correctly'];
68
-        }
31
+	/**
32
+	 * When this method is called, the backend must check if authentication was
33
+	 * successful.
34
+	 *
35
+	 * The returned value must be one of the following
36
+	 *
37
+	 * [true, "principals/username"]
38
+	 * [false, "reason for failure"]
39
+	 *
40
+	 * If authentication was successful, it's expected that the authentication
41
+	 * backend returns a so-called principal url.
42
+	 *
43
+	 * Examples of a principal url:
44
+	 *
45
+	 * principals/admin
46
+	 * principals/user1
47
+	 * principals/users/joe
48
+	 * principals/uid/123457
49
+	 *
50
+	 * If you don't use WebDAV ACL (RFC3744) we recommend that you simply
51
+	 * return a string such as:
52
+	 *
53
+	 * principals/users/[username]
54
+	 *
55
+	 * @return array
56
+	 */
57
+	public function check(RequestInterface $request, ResponseInterface $response)
58
+	{
59
+		$remoteUser = $request->getRawServerValue('REMOTE_USER');
60
+		if (is_null($remoteUser)) {
61
+			$remoteUser = $request->getRawServerValue('REDIRECT_REMOTE_USER');
62
+		}
63
+		if (is_null($remoteUser)) {
64
+			$remoteUser = $request->getRawServerValue('PHP_AUTH_USER');
65
+		}
66
+		if (is_null($remoteUser)) {
67
+			return [false, 'No REMOTE_USER, REDIRECT_REMOTE_USER, or PHP_AUTH_USER property was found in the PHP $_SERVER super-global. This likely means your server is not configured correctly'];
68
+		}
69 69
 
70
-        return [true, $this->principalPrefix.$remoteUser];
71
-    }
70
+		return [true, $this->principalPrefix.$remoteUser];
71
+	}
72 72
 
73
-    /**
74
-     * This method is called when a user could not be authenticated, and
75
-     * authentication was required for the current request.
76
-     *
77
-     * This gives you the opportunity to set authentication headers. The 401
78
-     * status code will already be set.
79
-     *
80
-     * In this case of Basic Auth, this would for example mean that the
81
-     * following header needs to be set:
82
-     *
83
-     * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
84
-     *
85
-     * Keep in mind that in the case of multiple authentication backends, other
86
-     * WWW-Authenticate headers may already have been set, and you'll want to
87
-     * append your own WWW-Authenticate header instead of overwriting the
88
-     * existing one.
89
-     */
90
-    public function challenge(RequestInterface $request, ResponseInterface $response)
91
-    {
92
-    }
73
+	/**
74
+	 * This method is called when a user could not be authenticated, and
75
+	 * authentication was required for the current request.
76
+	 *
77
+	 * This gives you the opportunity to set authentication headers. The 401
78
+	 * status code will already be set.
79
+	 *
80
+	 * In this case of Basic Auth, this would for example mean that the
81
+	 * following header needs to be set:
82
+	 *
83
+	 * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
84
+	 *
85
+	 * Keep in mind that in the case of multiple authentication backends, other
86
+	 * WWW-Authenticate headers may already have been set, and you'll want to
87
+	 * append your own WWW-Authenticate header instead of overwriting the
88
+	 * existing one.
89
+	 */
90
+	public function challenge(RequestInterface $request, ResponseInterface $response)
91
+	{
92
+	}
93 93
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php 1 patch
Indentation   +45 added lines, -45 removed lines patch added patch discarded remove patch
@@ -16,50 +16,50 @@
 block discarded – undo
16 16
  */
17 17
 interface BackendInterface
18 18
 {
19
-    /**
20
-     * When this method is called, the backend must check if authentication was
21
-     * successful.
22
-     *
23
-     * The returned value must be one of the following
24
-     *
25
-     * [true, "principals/username"]
26
-     * [false, "reason for failure"]
27
-     *
28
-     * If authentication was successful, it's expected that the authentication
29
-     * backend returns a so-called principal url.
30
-     *
31
-     * Examples of a principal url:
32
-     *
33
-     * principals/admin
34
-     * principals/user1
35
-     * principals/users/joe
36
-     * principals/uid/123457
37
-     *
38
-     * If you don't use WebDAV ACL (RFC3744) we recommend that you simply
39
-     * return a string such as:
40
-     *
41
-     * principals/users/[username]
42
-     *
43
-     * @return array
44
-     */
45
-    public function check(RequestInterface $request, ResponseInterface $response);
19
+	/**
20
+	 * When this method is called, the backend must check if authentication was
21
+	 * successful.
22
+	 *
23
+	 * The returned value must be one of the following
24
+	 *
25
+	 * [true, "principals/username"]
26
+	 * [false, "reason for failure"]
27
+	 *
28
+	 * If authentication was successful, it's expected that the authentication
29
+	 * backend returns a so-called principal url.
30
+	 *
31
+	 * Examples of a principal url:
32
+	 *
33
+	 * principals/admin
34
+	 * principals/user1
35
+	 * principals/users/joe
36
+	 * principals/uid/123457
37
+	 *
38
+	 * If you don't use WebDAV ACL (RFC3744) we recommend that you simply
39
+	 * return a string such as:
40
+	 *
41
+	 * principals/users/[username]
42
+	 *
43
+	 * @return array
44
+	 */
45
+	public function check(RequestInterface $request, ResponseInterface $response);
46 46
 
47
-    /**
48
-     * This method is called when a user could not be authenticated, and
49
-     * authentication was required for the current request.
50
-     *
51
-     * This gives you the opportunity to set authentication headers. The 401
52
-     * status code will already be set.
53
-     *
54
-     * In this case of Basic Auth, this would for example mean that the
55
-     * following header needs to be set:
56
-     *
57
-     * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
58
-     *
59
-     * Keep in mind that in the case of multiple authentication backends, other
60
-     * WWW-Authenticate headers may already have been set, and you'll want to
61
-     * append your own WWW-Authenticate header instead of overwriting the
62
-     * existing one.
63
-     */
64
-    public function challenge(RequestInterface $request, ResponseInterface $response);
47
+	/**
48
+	 * This method is called when a user could not be authenticated, and
49
+	 * authentication was required for the current request.
50
+	 *
51
+	 * This gives you the opportunity to set authentication headers. The 401
52
+	 * status code will already be set.
53
+	 *
54
+	 * In this case of Basic Auth, this would for example mean that the
55
+	 * following header needs to be set:
56
+	 *
57
+	 * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
58
+	 *
59
+	 * Keep in mind that in the case of multiple authentication backends, other
60
+	 * WWW-Authenticate headers may already have been set, and you'll want to
61
+	 * append your own WWW-Authenticate header instead of overwriting the
62
+	 * existing one.
63
+	 */
64
+	public function challenge(RequestInterface $request, ResponseInterface $response);
65 65
 }
Please login to merge, or discard this patch.