Browse Source

Fix implementation of per-directory restrictions

bohwaz 2 years ago
parent
commit
456a14f21e
3 changed files with 182 additions and 42 deletions
  1. 43 9
      README.md
  2. 71 18
      index.php
  3. 68 15
      server.php

+ 43 - 9
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

+ 71 - 18
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);
 		}
 

+ 68 - 15
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);
 			}