Completed
Push — master ( ebab99...72632f )
by Robin
42:06 queued 08:16
created
apps/dav/lib/Connector/Sabre/SharesPlugin.php 1 patch
Indentation   +246 added lines, -246 removed lines patch added patch discarded remove patch
@@ -27,250 +27,250 @@
 block discarded – undo
27 27
  * Sabre Plugin to provide share-related properties
28 28
  */
29 29
 class SharesPlugin extends \Sabre\DAV\ServerPlugin {
30
-	public const NS_OWNCLOUD = 'http://owncloud.org/ns';
31
-	public const NS_NEXTCLOUD = 'http://nextcloud.org/ns';
32
-	public const SHARETYPES_PROPERTYNAME = '{http://owncloud.org/ns}share-types';
33
-	public const SHAREES_PROPERTYNAME = '{http://nextcloud.org/ns}sharees';
34
-
35
-	/**
36
-	 * Reference to main server object
37
-	 *
38
-	 * @var \Sabre\DAV\Server
39
-	 */
40
-	private $server;
41
-	private string $userId;
42
-
43
-	/** @var IShare[][] */
44
-	private array $cachedShares = [];
45
-
46
-	/**
47
-	 * Tracks which folders have been cached.
48
-	 * When a folder is cached, it will appear with its path as key and true
49
-	 * as value.
50
-	 *
51
-	 * @var bool[]
52
-	 */
53
-	private array $cachedFolders = [];
54
-
55
-	public function __construct(
56
-		private Tree $tree,
57
-		private IUserSession $userSession,
58
-		private Folder $userFolder,
59
-		private IManager $shareManager,
60
-	) {
61
-		$this->userId = $userSession->getUser()->getUID();
62
-	}
63
-
64
-	/**
65
-	 * This initializes the plugin.
66
-	 *
67
-	 * This function is called by \Sabre\DAV\Server, after
68
-	 * addPlugin is called.
69
-	 *
70
-	 * This method should set up the required event subscriptions.
71
-	 *
72
-	 * @return void
73
-	 */
74
-	public function initialize(Server $server) {
75
-		$server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
76
-		$server->xml->elementMap[self::SHARETYPES_PROPERTYNAME] = ShareTypeList::class;
77
-		$server->protectedProperties[] = self::SHARETYPES_PROPERTYNAME;
78
-		$server->protectedProperties[] = self::SHAREES_PROPERTYNAME;
79
-
80
-		$this->server = $server;
81
-		$this->server->on('preloadCollection', $this->preloadCollection(...));
82
-		$this->server->on('propFind', $this->handleGetProperties(...));
83
-		$this->server->on('beforeCopy', $this->validateMoveOrCopy(...));
84
-		$this->server->on('beforeMove', $this->validateMoveOrCopy(...));
85
-	}
86
-
87
-	/**
88
-	 * @param Node $node
89
-	 * @return IShare[]
90
-	 */
91
-	private function getShare(Node $node): array {
92
-		$result = [];
93
-		$requestedShareTypes = [
94
-			IShare::TYPE_USER,
95
-			IShare::TYPE_GROUP,
96
-			IShare::TYPE_LINK,
97
-			IShare::TYPE_REMOTE,
98
-			IShare::TYPE_EMAIL,
99
-			IShare::TYPE_ROOM,
100
-			IShare::TYPE_CIRCLE,
101
-			IShare::TYPE_DECK,
102
-		];
103
-
104
-		foreach ($requestedShareTypes as $requestedShareType) {
105
-			$result[] = $this->shareManager->getSharesBy(
106
-				$this->userId,
107
-				$requestedShareType,
108
-				$node,
109
-				false,
110
-				-1
111
-			);
112
-
113
-			// Also check for shares where the user is the recipient
114
-			try {
115
-				$result[] = $this->shareManager->getSharedWith(
116
-					$this->userId,
117
-					$requestedShareType,
118
-					$node,
119
-					-1
120
-				);
121
-			} catch (BackendError $e) {
122
-				// ignore
123
-			}
124
-		}
125
-
126
-		return array_merge(...$result);
127
-	}
128
-
129
-	/**
130
-	 * @param Folder $node
131
-	 * @return IShare[][]
132
-	 */
133
-	private function getSharesFolder(Folder $node): array {
134
-		return $this->shareManager->getSharesInFolder(
135
-			$this->userId,
136
-			$node,
137
-			true
138
-		);
139
-	}
140
-
141
-	/**
142
-	 * @param DavNode $sabreNode
143
-	 * @return IShare[]
144
-	 */
145
-	private function getShares(DavNode $sabreNode): array {
146
-		if (isset($this->cachedShares[$sabreNode->getId()])) {
147
-			return $this->cachedShares[$sabreNode->getId()];
148
-		}
149
-
150
-		[$parentPath,] = \Sabre\Uri\split($sabreNode->getPath());
151
-		if ($parentPath === '') {
152
-			$parentPath = '/';
153
-		}
154
-
155
-		// if we already cached the folder containing this file
156
-		// then we already know there are no shares here.
157
-		if (!isset($this->cachedFolders[$parentPath])) {
158
-			try {
159
-				$node = $sabreNode->getNode();
160
-			} catch (NotFoundException $e) {
161
-				return [];
162
-			}
163
-
164
-			$shares = $this->getShare($node);
165
-			$this->cachedShares[$sabreNode->getId()] = $shares;
166
-			return $shares;
167
-		}
168
-
169
-		return [];
170
-	}
171
-
172
-	private function preloadCollection(PropFind $propFind, ICollection $collection): void {
173
-		if (!$collection instanceof Directory
174
-			|| isset($this->cachedFolders[$collection->getPath()])
175
-			|| (
176
-				$propFind->getStatus(self::SHARETYPES_PROPERTYNAME) === null
177
-				&& $propFind->getStatus(self::SHAREES_PROPERTYNAME) === null
178
-			)
179
-		) {
180
-			return;
181
-		}
182
-
183
-		// If the node is a directory and we are requesting share types or sharees
184
-		// then we get all the shares in the folder and cache them.
185
-		// This is more performant than iterating each files afterwards.
186
-		$folderNode = $collection->getNode();
187
-		$this->cachedFolders[$collection->getPath()] = true;
188
-		foreach ($this->getSharesFolder($folderNode) as $id => $shares) {
189
-			$this->cachedShares[$id] = $shares;
190
-		}
191
-	}
192
-
193
-	/**
194
-	 * Adds shares to propfind response
195
-	 *
196
-	 * @param PropFind $propFind propfind object
197
-	 * @param \Sabre\DAV\INode $sabreNode sabre node
198
-	 */
199
-	public function handleGetProperties(
200
-		PropFind $propFind,
201
-		\Sabre\DAV\INode $sabreNode,
202
-	) {
203
-		if (!($sabreNode instanceof DavNode)) {
204
-			return;
205
-		}
206
-
207
-		$propFind->handle(self::SHARETYPES_PROPERTYNAME, function () use ($sabreNode): ShareTypeList {
208
-			$shares = $this->getShares($sabreNode);
209
-
210
-			$shareTypes = array_unique(array_map(function (IShare $share) {
211
-				return $share->getShareType();
212
-			}, $shares));
213
-
214
-			return new ShareTypeList($shareTypes);
215
-		});
216
-
217
-		$propFind->handle(self::SHAREES_PROPERTYNAME, function () use ($sabreNode): ShareeList {
218
-			$shares = $this->getShares($sabreNode);
219
-
220
-			return new ShareeList($shares);
221
-		});
222
-	}
223
-
224
-	/**
225
-	 * Ensure that when copying or moving a node it is not transferred from one share to another,
226
-	 * if the user is neither the owner nor has re-share permissions.
227
-	 * For share creation we already ensure this in the share manager.
228
-	 */
229
-	public function validateMoveOrCopy(string $source, string $target): bool {
230
-		try {
231
-			$targetNode = $this->tree->getNodeForPath($target);
232
-		} catch (NotFound) {
233
-			[$targetPath,] = \Sabre\Uri\split($target);
234
-			$targetNode = $this->tree->getNodeForPath($targetPath);
235
-		}
236
-
237
-		$sourceNode = $this->tree->getNodeForPath($source);
238
-		if ((!$sourceNode instanceof DavNode) || (!$targetNode instanceof DavNode)) {
239
-			return true;
240
-		}
241
-
242
-		$sourceNode = $sourceNode->getNode();
243
-		if ($sourceNode->isShareable()) {
244
-			return true;
245
-		}
246
-
247
-		$targetShares = $this->getShare($targetNode->getNode());
248
-		if (empty($targetShares)) {
249
-			// Target is not a share so no re-sharing inprogress
250
-			return true;
251
-		}
252
-
253
-		$sourceStorage = $sourceNode->getStorage();
254
-		if ($sourceStorage->instanceOfStorage(ISharedStorage::class)) {
255
-			// source is also a share - check if it is the same share
256
-
257
-			/** @var ISharedStorage $sourceStorage */
258
-			$sourceShare = $sourceStorage->getShare();
259
-			foreach ($targetShares as $targetShare) {
260
-				if ($targetShare->getId() === $sourceShare->getId()) {
261
-					return true;
262
-				}
263
-			}
264
-
265
-			// if the share recipient is allow to delete from the share, they are allowed to move the file out of the share
266
-			// the user moving the file out of the share to their home storage would give them share permissions and allow moving into the share
267
-			//
268
-			// since the 2-step move is allowed, we also allow both steps at once
269
-			if ($sourceNode->isDeletable()) {
270
-				return true;
271
-			}
272
-		}
273
-
274
-		throw new Forbidden('You cannot move a non-shareable node into a share');
275
-	}
30
+    public const NS_OWNCLOUD = 'http://owncloud.org/ns';
31
+    public const NS_NEXTCLOUD = 'http://nextcloud.org/ns';
32
+    public const SHARETYPES_PROPERTYNAME = '{http://owncloud.org/ns}share-types';
33
+    public const SHAREES_PROPERTYNAME = '{http://nextcloud.org/ns}sharees';
34
+
35
+    /**
36
+     * Reference to main server object
37
+     *
38
+     * @var \Sabre\DAV\Server
39
+     */
40
+    private $server;
41
+    private string $userId;
42
+
43
+    /** @var IShare[][] */
44
+    private array $cachedShares = [];
45
+
46
+    /**
47
+     * Tracks which folders have been cached.
48
+     * When a folder is cached, it will appear with its path as key and true
49
+     * as value.
50
+     *
51
+     * @var bool[]
52
+     */
53
+    private array $cachedFolders = [];
54
+
55
+    public function __construct(
56
+        private Tree $tree,
57
+        private IUserSession $userSession,
58
+        private Folder $userFolder,
59
+        private IManager $shareManager,
60
+    ) {
61
+        $this->userId = $userSession->getUser()->getUID();
62
+    }
63
+
64
+    /**
65
+     * This initializes the plugin.
66
+     *
67
+     * This function is called by \Sabre\DAV\Server, after
68
+     * addPlugin is called.
69
+     *
70
+     * This method should set up the required event subscriptions.
71
+     *
72
+     * @return void
73
+     */
74
+    public function initialize(Server $server) {
75
+        $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
76
+        $server->xml->elementMap[self::SHARETYPES_PROPERTYNAME] = ShareTypeList::class;
77
+        $server->protectedProperties[] = self::SHARETYPES_PROPERTYNAME;
78
+        $server->protectedProperties[] = self::SHAREES_PROPERTYNAME;
79
+
80
+        $this->server = $server;
81
+        $this->server->on('preloadCollection', $this->preloadCollection(...));
82
+        $this->server->on('propFind', $this->handleGetProperties(...));
83
+        $this->server->on('beforeCopy', $this->validateMoveOrCopy(...));
84
+        $this->server->on('beforeMove', $this->validateMoveOrCopy(...));
85
+    }
86
+
87
+    /**
88
+     * @param Node $node
89
+     * @return IShare[]
90
+     */
91
+    private function getShare(Node $node): array {
92
+        $result = [];
93
+        $requestedShareTypes = [
94
+            IShare::TYPE_USER,
95
+            IShare::TYPE_GROUP,
96
+            IShare::TYPE_LINK,
97
+            IShare::TYPE_REMOTE,
98
+            IShare::TYPE_EMAIL,
99
+            IShare::TYPE_ROOM,
100
+            IShare::TYPE_CIRCLE,
101
+            IShare::TYPE_DECK,
102
+        ];
103
+
104
+        foreach ($requestedShareTypes as $requestedShareType) {
105
+            $result[] = $this->shareManager->getSharesBy(
106
+                $this->userId,
107
+                $requestedShareType,
108
+                $node,
109
+                false,
110
+                -1
111
+            );
112
+
113
+            // Also check for shares where the user is the recipient
114
+            try {
115
+                $result[] = $this->shareManager->getSharedWith(
116
+                    $this->userId,
117
+                    $requestedShareType,
118
+                    $node,
119
+                    -1
120
+                );
121
+            } catch (BackendError $e) {
122
+                // ignore
123
+            }
124
+        }
125
+
126
+        return array_merge(...$result);
127
+    }
128
+
129
+    /**
130
+     * @param Folder $node
131
+     * @return IShare[][]
132
+     */
133
+    private function getSharesFolder(Folder $node): array {
134
+        return $this->shareManager->getSharesInFolder(
135
+            $this->userId,
136
+            $node,
137
+            true
138
+        );
139
+    }
140
+
141
+    /**
142
+     * @param DavNode $sabreNode
143
+     * @return IShare[]
144
+     */
145
+    private function getShares(DavNode $sabreNode): array {
146
+        if (isset($this->cachedShares[$sabreNode->getId()])) {
147
+            return $this->cachedShares[$sabreNode->getId()];
148
+        }
149
+
150
+        [$parentPath,] = \Sabre\Uri\split($sabreNode->getPath());
151
+        if ($parentPath === '') {
152
+            $parentPath = '/';
153
+        }
154
+
155
+        // if we already cached the folder containing this file
156
+        // then we already know there are no shares here.
157
+        if (!isset($this->cachedFolders[$parentPath])) {
158
+            try {
159
+                $node = $sabreNode->getNode();
160
+            } catch (NotFoundException $e) {
161
+                return [];
162
+            }
163
+
164
+            $shares = $this->getShare($node);
165
+            $this->cachedShares[$sabreNode->getId()] = $shares;
166
+            return $shares;
167
+        }
168
+
169
+        return [];
170
+    }
171
+
172
+    private function preloadCollection(PropFind $propFind, ICollection $collection): void {
173
+        if (!$collection instanceof Directory
174
+            || isset($this->cachedFolders[$collection->getPath()])
175
+            || (
176
+                $propFind->getStatus(self::SHARETYPES_PROPERTYNAME) === null
177
+                && $propFind->getStatus(self::SHAREES_PROPERTYNAME) === null
178
+            )
179
+        ) {
180
+            return;
181
+        }
182
+
183
+        // If the node is a directory and we are requesting share types or sharees
184
+        // then we get all the shares in the folder and cache them.
185
+        // This is more performant than iterating each files afterwards.
186
+        $folderNode = $collection->getNode();
187
+        $this->cachedFolders[$collection->getPath()] = true;
188
+        foreach ($this->getSharesFolder($folderNode) as $id => $shares) {
189
+            $this->cachedShares[$id] = $shares;
190
+        }
191
+    }
192
+
193
+    /**
194
+     * Adds shares to propfind response
195
+     *
196
+     * @param PropFind $propFind propfind object
197
+     * @param \Sabre\DAV\INode $sabreNode sabre node
198
+     */
199
+    public function handleGetProperties(
200
+        PropFind $propFind,
201
+        \Sabre\DAV\INode $sabreNode,
202
+    ) {
203
+        if (!($sabreNode instanceof DavNode)) {
204
+            return;
205
+        }
206
+
207
+        $propFind->handle(self::SHARETYPES_PROPERTYNAME, function () use ($sabreNode): ShareTypeList {
208
+            $shares = $this->getShares($sabreNode);
209
+
210
+            $shareTypes = array_unique(array_map(function (IShare $share) {
211
+                return $share->getShareType();
212
+            }, $shares));
213
+
214
+            return new ShareTypeList($shareTypes);
215
+        });
216
+
217
+        $propFind->handle(self::SHAREES_PROPERTYNAME, function () use ($sabreNode): ShareeList {
218
+            $shares = $this->getShares($sabreNode);
219
+
220
+            return new ShareeList($shares);
221
+        });
222
+    }
223
+
224
+    /**
225
+     * Ensure that when copying or moving a node it is not transferred from one share to another,
226
+     * if the user is neither the owner nor has re-share permissions.
227
+     * For share creation we already ensure this in the share manager.
228
+     */
229
+    public function validateMoveOrCopy(string $source, string $target): bool {
230
+        try {
231
+            $targetNode = $this->tree->getNodeForPath($target);
232
+        } catch (NotFound) {
233
+            [$targetPath,] = \Sabre\Uri\split($target);
234
+            $targetNode = $this->tree->getNodeForPath($targetPath);
235
+        }
236
+
237
+        $sourceNode = $this->tree->getNodeForPath($source);
238
+        if ((!$sourceNode instanceof DavNode) || (!$targetNode instanceof DavNode)) {
239
+            return true;
240
+        }
241
+
242
+        $sourceNode = $sourceNode->getNode();
243
+        if ($sourceNode->isShareable()) {
244
+            return true;
245
+        }
246
+
247
+        $targetShares = $this->getShare($targetNode->getNode());
248
+        if (empty($targetShares)) {
249
+            // Target is not a share so no re-sharing inprogress
250
+            return true;
251
+        }
252
+
253
+        $sourceStorage = $sourceNode->getStorage();
254
+        if ($sourceStorage->instanceOfStorage(ISharedStorage::class)) {
255
+            // source is also a share - check if it is the same share
256
+
257
+            /** @var ISharedStorage $sourceStorage */
258
+            $sourceShare = $sourceStorage->getShare();
259
+            foreach ($targetShares as $targetShare) {
260
+                if ($targetShare->getId() === $sourceShare->getId()) {
261
+                    return true;
262
+                }
263
+            }
264
+
265
+            // if the share recipient is allow to delete from the share, they are allowed to move the file out of the share
266
+            // the user moving the file out of the share to their home storage would give them share permissions and allow moving into the share
267
+            //
268
+            // since the 2-step move is allowed, we also allow both steps at once
269
+            if ($sourceNode->isDeletable()) {
270
+                return true;
271
+            }
272
+        }
273
+
274
+        throw new Forbidden('You cannot move a non-shareable node into a share');
275
+    }
276 276
 }
Please login to merge, or discard this patch.