Fix implementation of per-directory restrictions

FossilOrigin-Name: d854b96bd18b5ca437cb9b32a95d810423a63c417caafd0659797488aeab9f9c
This commit is contained in:
bohwaz 2022-11-22 00:32:38 +00:00
parent 479dd6310c
commit cfeb7ed2a0
3 changed files with 182 additions and 42 deletions

View file

@ -6,6 +6,21 @@ If you drop the [`index.php`](./index.php) file in a directory of your web-serve
![Web UI screenshot](https://raw.githubusercontent.com/kd2org/webdav-manager.js/main/scr_desktop.png)
* Single-file WebDAV server!
* No database!
* Very fast and lightweight!
* Compatible with tons of apps!
* Manage files and directories from a web browser:
* Upload directly from browser, using paste or drag and drop
* Rename
* Delete
* Create and edit text files
* Create directories
* MarkDown live preview
* Preview of images, text, MarkDown and PDF
* Manage users and password with only a text file!
* Restrict users to some directories, control where they can write!
## WebDAV clients
You can use any WebDAV client, but we recommend these:
@ -70,19 +85,38 @@ All users have read access to everything by default.
#### Restricting users to some directories
You can also limit users in which directories and files they can access by using the `restrict` and `restrict_write` configuration directives:
If you want something more detailed, you can also limit users in which directories and files they can access by using the `restrict[]` and `restrict_write[]` configuration directives.
These are tables, so you can have more than one directory restriction, don't forget the `[]`!
In the following example, the user will only be able to read the `constitution` directory and not write anything:
```
[emusk]
password = youSuck
[olympe]
password = abcd
write = false
restrict[] = 'kill-twitter/'
restrict[] = 'constitution/'
```
[pouyane]
password = youArePaidWayTooMuch
write = false
restrict[] = 'total/'
restrict_write[] = 'total/kill-the-planet/'
Here the user will be able to only read and write in the `constitution` and `images` directories:
```
[olympe]
password = abcd
write = true
restrict[] = 'constitution/'
restrict[] = 'images/'
```
And here, she will be able to only read from the `constitution` directory and write in the `constitution/book` and `constitution/summary` directories:
```
[olympe]
password = abcd
write = true
restrict[] = 'constitution/'
restrict_write[] = 'constitution/book/'
restrict_write[] = 'constitution/summary/'
```
### Allow unrestricted access to everyone

View file

@ -725,7 +725,7 @@ namespace KD2\WebDAV
$uri = trim(rtrim($this->base_uri, '/') . '/' . ltrim($uri, '/'), '/');
$path = '/' . str_replace('%2F', '/', rawurlencode($uri));
if (($item['DAV::resourcetype'] ?? null) == 'collection') {
if (($item['DAV::resourcetype'] ?? null) == 'collection' && $path != '/') {
$path .= '/';
}
@ -1353,16 +1353,28 @@ namespace PicoDAV
return true;
}
if ($this->auth()) {
if (!$this->auth()) {
return false;
}
$restrict = $this->users[$this->user]['restrict'] ?? [];
if (!is_array($restrict) || empty($restrict)) {
return true;
}
foreach ($restrict as $match) {
if (0 === strpos($uri, $match)) {
return true;
}
}
return false;
}
public function canWrite(string $uri): bool
{
if (!$this->user && !ANONYMOUS_WRITE) {
if (!$this->auth() && !ANONYMOUS_WRITE) {
return false;
}
@ -1374,7 +1386,36 @@ namespace PicoDAV
return true;
}
if (!empty($this->users[$this->user]['write'])) {
if (!$this->auth() || empty($this->users[$this->user]['write'])) {
return false;
}
$restrict = $this->users[$this->user]['restrict_write'] ?? [];
if (!is_array($restrict) || empty($restrict)) {
return true;
}
foreach ($restrict as $match) {
if (0 === strpos($uri, $match)) {
return true;
}
}
return false;
}
public function canOnlyCreate(string $uri): bool
{
$restrict = $this->users[$this->user]['restrict_write'] ?? [];
if (in_array($uri, $restrict, true)) {
return true;
}
$restrict = $this->users[$this->user]['restrict'] ?? [];
if (in_array($uri, $restrict, true)) {
return true;
}
@ -1383,13 +1424,13 @@ namespace PicoDAV
public function list(string $uri, ?array $properties): iterable
{
if (!$this->canRead($uri)) {
throw new WebDAV_Exception('Access forbidden', 403);
if (!$this->canRead($uri . '/')) {
//throw new WebDAV_Exception('Access forbidden', 403);
}
$dirs = self::glob($this->path . $uri, '/*', \GLOB_ONLYDIR);
$dirs = array_map('basename', $dirs);
$dirs = array_filter($dirs, fn($a) => $this->canRead(ltrim($uri . '/' . $a, '/')));
$dirs = array_filter($dirs, fn($a) => $this->canRead(ltrim($uri . '/' . $a, '/') . '/'));
natcasesort($dirs);
$files = self::glob($this->path . $uri, '/*');
@ -1403,6 +1444,7 @@ namespace PicoDAV
$files = array_flip(array_merge($dirs, $files));
$files = array_map(fn($a) => null, $files);
return $files;
}
@ -1452,8 +1494,6 @@ namespace PicoDAV
}
return new \DateTime('@' . $mtime);
case 'DAV::displayname':
return basename($target);
case 'DAV::ishidden':
return basename($target)[0] == '.';
case 'DAV::getetag':
@ -1466,12 +1506,23 @@ namespace PicoDAV
case 'http://owncloud.org/ns:permissions':
$permissions = 'G';
if (is_dir($target)) {
$uri .= '/';
}
if (is_writeable($target) && $this->canWrite($uri)) {
$permissions .= 'DNVWCK';
// If the directory is one of the restricted paths,
// then we can only do stuff INSIDE, and not delete/rename the directory itself
if ($this->canOnlyCreate($uri)) {
$permissions .= 'CK';
}
else {
$permissions .= 'DNVWCK';
}
}
return $permissions;
case WebDAV::PROP_DIGEST_MD5:
case Server::PROP_DIGEST_MD5:
if (!is_file($target)) {
return null;
}
@ -1578,6 +1629,10 @@ namespace PicoDAV
throw new WebDAV_Exception('Access forbidden', 403);
}
if ($this->canOnlyCreate($uri)) {
throw new WebDAV_Exception('Access forbidden', 403);
}
$target = $this->path . $uri;
if (!file_exists($target)) {
@ -1602,11 +1657,9 @@ namespace PicoDAV
public function copymove(bool $move, string $uri, string $destination): bool
{
if (!$this->canWrite($uri)) {
throw new WebDAV_Exception('Access forbidden', 403);
}
if (!$this->canWrite($destination)) {
if (!$this->canWrite($uri)
|| !$this->canWrite($destination)
|| $this->canOnlyCreate($uri)) {
throw new WebDAV_Exception('Access forbidden', 403);
}
@ -1794,11 +1847,11 @@ RewriteRule ^.*$ /index.php [END]
$fp = fopen(__FILE__, 'r');
if ($relative_uri == 'webdav.js') {
fseek($fp, 48399, SEEK_SET);
fseek($fp, 49575, SEEK_SET);
echo fread($fp, 25889);
}
else {
fseek($fp, 48399 + 25889, SEEK_SET);
fseek($fp, 49575 + 25889, SEEK_SET);
echo fread($fp, 6760);
}

View file

@ -77,16 +77,28 @@ namespace PicoDAV
return true;
}
if ($this->auth()) {
if (!$this->auth()) {
return false;
}
$restrict = $this->users[$this->user]['restrict'] ?? [];
if (!is_array($restrict) || empty($restrict)) {
return true;
}
foreach ($restrict as $match) {
if (0 === strpos($uri, $match)) {
return true;
}
}
return false;
}
public function canWrite(string $uri): bool
{
if (!$this->user && !ANONYMOUS_WRITE) {
if (!$this->auth() && !ANONYMOUS_WRITE) {
return false;
}
@ -98,7 +110,36 @@ namespace PicoDAV
return true;
}
if (!empty($this->users[$this->user]['write'])) {
if (!$this->auth() || empty($this->users[$this->user]['write'])) {
return false;
}
$restrict = $this->users[$this->user]['restrict_write'] ?? [];
if (!is_array($restrict) || empty($restrict)) {
return true;
}
foreach ($restrict as $match) {
if (0 === strpos($uri, $match)) {
return true;
}
}
return false;
}
public function canOnlyCreate(string $uri): bool
{
$restrict = $this->users[$this->user]['restrict_write'] ?? [];
if (in_array($uri, $restrict, true)) {
return true;
}
$restrict = $this->users[$this->user]['restrict'] ?? [];
if (in_array($uri, $restrict, true)) {
return true;
}
@ -107,13 +148,13 @@ namespace PicoDAV
public function list(string $uri, ?array $properties): iterable
{
if (!$this->canRead($uri)) {
throw new WebDAV_Exception('Access forbidden', 403);
if (!$this->canRead($uri . '/')) {
//throw new WebDAV_Exception('Access forbidden', 403);
}
$dirs = self::glob($this->path . $uri, '/*', \GLOB_ONLYDIR);
$dirs = array_map('basename', $dirs);
$dirs = array_filter($dirs, fn($a) => $this->canRead(ltrim($uri . '/' . $a, '/')));
$dirs = array_filter($dirs, fn($a) => $this->canRead(ltrim($uri . '/' . $a, '/') . '/'));
natcasesort($dirs);
$files = self::glob($this->path . $uri, '/*');
@ -127,6 +168,7 @@ namespace PicoDAV
$files = array_flip(array_merge($dirs, $files));
$files = array_map(fn($a) => null, $files);
return $files;
}
@ -176,8 +218,6 @@ namespace PicoDAV
}
return new \DateTime('@' . $mtime);
case 'DAV::displayname':
return basename($target);
case 'DAV::ishidden':
return basename($target)[0] == '.';
case 'DAV::getetag':
@ -190,12 +230,23 @@ namespace PicoDAV
case 'http://owncloud.org/ns:permissions':
$permissions = 'G';
if (is_dir($target)) {
$uri .= '/';
}
if (is_writeable($target) && $this->canWrite($uri)) {
$permissions .= 'DNVWCK';
// If the directory is one of the restricted paths,
// then we can only do stuff INSIDE, and not delete/rename the directory itself
if ($this->canOnlyCreate($uri)) {
$permissions .= 'CK';
}
else {
$permissions .= 'DNVWCK';
}
}
return $permissions;
case WebDAV::PROP_DIGEST_MD5:
case Server::PROP_DIGEST_MD5:
if (!is_file($target)) {
return null;
}
@ -302,6 +353,10 @@ namespace PicoDAV
throw new WebDAV_Exception('Access forbidden', 403);
}
if ($this->canOnlyCreate($uri)) {
throw new WebDAV_Exception('Access forbidden', 403);
}
$target = $this->path . $uri;
if (!file_exists($target)) {
@ -326,11 +381,9 @@ namespace PicoDAV
public function copymove(bool $move, string $uri, string $destination): bool
{
if (!$this->canWrite($uri)) {
throw new WebDAV_Exception('Access forbidden', 403);
}
if (!$this->canWrite($destination)) {
if (!$this->canWrite($uri)
|| !$this->canWrite($destination)
|| $this->canOnlyCreate($uri)) {
throw new WebDAV_Exception('Access forbidden', 403);
}