Fix implementation of per-directory restrictions
This commit is contained in:
parent
3adcc56962
commit
456a14f21e
3 changed files with 182 additions and 42 deletions
52
README.md
52
README.md
|
@ -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
|
||||
|
|
89
index.php
89
index.php
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
83
server.php
83
server.php
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue