Fix implementation of per-directory restrictions

This commit is contained in:
bohwaz 2022-11-22 01:32:39 +01:00
parent 3adcc56962
commit 456a14f21e
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) ![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 ## WebDAV clients
You can use any WebDAV client, but we recommend these: 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 #### 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] [olympe]
password = youSuck password = abcd
write = false write = false
restrict[] = 'kill-twitter/' restrict[] = 'constitution/'
```
[pouyane] Here the user will be able to only read and write in the `constitution` and `images` directories:
password = youArePaidWayTooMuch
write = false ```
restrict[] = 'total/' [olympe]
restrict_write[] = 'total/kill-the-planet/' 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 ### Allow unrestricted access to everyone

View file

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

View file

@ -77,16 +77,28 @@ namespace PicoDAV
return true; return true;
} }
if ($this->auth()) { if (!$this->auth()) {
return false;
}
$restrict = $this->users[$this->user]['restrict'] ?? [];
if (!is_array($restrict) || empty($restrict)) {
return true; return true;
} }
foreach ($restrict as $match) {
if (0 === strpos($uri, $match)) {
return true;
}
}
return false; return false;
} }
public function canWrite(string $uri): bool public function canWrite(string $uri): bool
{ {
if (!$this->user && !ANONYMOUS_WRITE) { if (!$this->auth() && !ANONYMOUS_WRITE) {
return false; return false;
} }
@ -98,7 +110,36 @@ namespace PicoDAV
return true; 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; return true;
} }
@ -107,13 +148,13 @@ namespace PicoDAV
public function list(string $uri, ?array $properties): iterable public function list(string $uri, ?array $properties): iterable
{ {
if (!$this->canRead($uri)) { if (!$this->canRead($uri . '/')) {
throw new WebDAV_Exception('Access forbidden', 403); //throw new WebDAV_Exception('Access forbidden', 403);
} }
$dirs = self::glob($this->path . $uri, '/*', \GLOB_ONLYDIR); $dirs = self::glob($this->path . $uri, '/*', \GLOB_ONLYDIR);
$dirs = array_map('basename', $dirs); $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); natcasesort($dirs);
$files = self::glob($this->path . $uri, '/*'); $files = self::glob($this->path . $uri, '/*');
@ -127,6 +168,7 @@ namespace PicoDAV
$files = array_flip(array_merge($dirs, $files)); $files = array_flip(array_merge($dirs, $files));
$files = array_map(fn($a) => null, $files); $files = array_map(fn($a) => null, $files);
return $files; return $files;
} }
@ -176,8 +218,6 @@ namespace PicoDAV
} }
return new \DateTime('@' . $mtime); return new \DateTime('@' . $mtime);
case 'DAV::displayname':
return basename($target);
case 'DAV::ishidden': case 'DAV::ishidden':
return basename($target)[0] == '.'; return basename($target)[0] == '.';
case 'DAV::getetag': case 'DAV::getetag':
@ -190,12 +230,23 @@ namespace PicoDAV
case 'http://owncloud.org/ns:permissions': case 'http://owncloud.org/ns:permissions':
$permissions = 'G'; $permissions = 'G';
if (is_dir($target)) {
$uri .= '/';
}
if (is_writeable($target) && $this->canWrite($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; return $permissions;
case WebDAV::PROP_DIGEST_MD5: case Server::PROP_DIGEST_MD5:
if (!is_file($target)) { if (!is_file($target)) {
return null; return null;
} }
@ -302,6 +353,10 @@ namespace PicoDAV
throw new WebDAV_Exception('Access forbidden', 403); throw new WebDAV_Exception('Access forbidden', 403);
} }
if ($this->canOnlyCreate($uri)) {
throw new WebDAV_Exception('Access forbidden', 403);
}
$target = $this->path . $uri; $target = $this->path . $uri;
if (!file_exists($target)) { if (!file_exists($target)) {
@ -326,11 +381,9 @@ namespace PicoDAV
public function copymove(bool $move, string $uri, string $destination): bool public function copymove(bool $move, string $uri, string $destination): bool
{ {
if (!$this->canWrite($uri)) { if (!$this->canWrite($uri)
throw new WebDAV_Exception('Access forbidden', 403); || !$this->canWrite($destination)
} || $this->canOnlyCreate($uri)) {
if (!$this->canWrite($destination)) {
throw new WebDAV_Exception('Access forbidden', 403); throw new WebDAV_Exception('Access forbidden', 403);
} }