1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @author Viktar Dubiniuk <[email protected]> |
4
|
|
|
* |
5
|
|
|
* @copyright Copyright (c) 2018, ownCloud GmbH |
6
|
|
|
* @license AGPL-3.0 |
7
|
|
|
* |
8
|
|
|
* This code is free software: you can redistribute it and/or modify |
9
|
|
|
* it under the terms of the GNU Affero General Public License, version 3, |
10
|
|
|
* as published by the Free Software Foundation. |
11
|
|
|
* |
12
|
|
|
* This program is distributed in the hope that it will be useful, |
13
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
14
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15
|
|
|
* GNU Affero General Public License for more details. |
16
|
|
|
* |
17
|
|
|
* You should have received a copy of the GNU Affero General Public License, version 3, |
18
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/> |
19
|
|
|
* |
20
|
|
|
*/ |
21
|
|
|
|
22
|
|
|
namespace OCA\Files_Sharing\Controller; |
23
|
|
|
|
24
|
|
|
use OC\OCS\Result; |
25
|
|
|
use OCP\AppFramework\OCSController; |
26
|
|
|
use OCP\Files\IRootFolder; |
27
|
|
|
use OCP\Files\NotFoundException; |
28
|
|
|
use OCP\IConfig; |
29
|
|
|
use OCP\IGroupManager; |
30
|
|
|
use OCP\IL10N; |
31
|
|
|
use OCP\IRequest; |
32
|
|
|
use OCP\IURLGenerator; |
33
|
|
|
use OCP\IUser; |
34
|
|
|
use OCP\IUserManager; |
35
|
|
|
use OCP\Lock\ILockingProvider; |
36
|
|
|
use OCP\Lock\LockedException; |
37
|
|
|
use OCP\Share; |
38
|
|
|
use OCP\Share\Exceptions\GenericShareException; |
39
|
|
|
use OCP\Share\Exceptions\ShareNotFound; |
40
|
|
|
use OCP\Share\IManager; |
41
|
|
|
use OCP\Share\IShare; |
42
|
|
|
use OCA\Files_Sharing\Service\NotificationPublisher; |
43
|
|
|
use OCA\Files_Sharing\Helper; |
44
|
|
|
use OCA\Files_Sharing\SharingBlacklist; |
45
|
|
|
use Symfony\Component\EventDispatcher\EventDispatcher; |
46
|
|
|
use Symfony\Component\EventDispatcher\GenericEvent; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* Class Share20OcsController |
50
|
|
|
* |
51
|
|
|
* @package OCA\Files_Sharing\Controller |
52
|
|
|
*/ |
53
|
|
|
class Share20OcsController extends OCSController { |
54
|
|
|
/** @var IManager */ |
55
|
|
|
private $shareManager; |
56
|
|
|
/** @var IGroupManager */ |
57
|
|
|
private $groupManager; |
58
|
|
|
/** @var IUserManager */ |
59
|
|
|
private $userManager; |
60
|
|
|
/** @var IRootFolder */ |
61
|
|
|
private $rootFolder; |
62
|
|
|
/** @var IURLGenerator */ |
63
|
|
|
private $urlGenerator; |
64
|
|
|
/** @var IUser */ |
65
|
|
|
private $currentUser; |
66
|
|
|
/** @var IL10N */ |
67
|
|
|
private $l; |
68
|
|
|
/** @var IConfig */ |
69
|
|
|
private $config; |
70
|
|
|
/** @var NotificationPublisher */ |
71
|
|
|
private $notificationPublisher; |
72
|
|
|
/** @var EventDispatcher */ |
73
|
|
|
private $eventDispatcher; |
74
|
|
|
/** @var SharingBlacklist */ |
75
|
|
|
private $sharingBlacklist; |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* @var string |
79
|
|
|
*/ |
80
|
|
|
private $additionalInfoField; |
81
|
|
|
|
82
|
|
View Code Duplication |
public function __construct( |
83
|
|
|
$appName, |
84
|
|
|
IRequest $request, |
85
|
|
|
IManager $shareManager, |
86
|
|
|
IGroupManager $groupManager, |
87
|
|
|
IUserManager $userManager, |
88
|
|
|
IRootFolder $rootFolder, |
89
|
|
|
IURLGenerator $urlGenerator, |
90
|
|
|
IUser $currentUser, |
91
|
|
|
IL10N $l10n, |
92
|
|
|
IConfig $config, |
93
|
|
|
NotificationPublisher $notificationPublisher, |
94
|
|
|
EventDispatcher $eventDispatcher, |
95
|
|
|
SharingBlacklist $sharingBlacklist |
96
|
|
|
) { |
97
|
|
|
parent::__construct($appName, $request); |
98
|
|
|
$this->request = $request; |
99
|
|
|
$this->shareManager = $shareManager; |
100
|
|
|
$this->groupManager = $groupManager; |
101
|
|
|
$this->userManager = $userManager; |
102
|
|
|
$this->rootFolder = $rootFolder; |
103
|
|
|
$this->urlGenerator = $urlGenerator; |
104
|
|
|
$this->currentUser = $currentUser; |
105
|
|
|
$this->l = $l10n; |
106
|
|
|
$this->config = $config; |
107
|
|
|
$this->notificationPublisher = $notificationPublisher; |
108
|
|
|
$this->eventDispatcher = $eventDispatcher; |
109
|
|
|
$this->sharingBlacklist = $sharingBlacklist; |
110
|
|
|
$this->additionalInfoField = $this->config->getAppValue('core', 'user_additional_info_field', ''); |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Returns the additional info to display behind the display name as configured. |
115
|
|
|
* |
116
|
|
|
* @param IUser $user user for which to retrieve the additional info |
117
|
|
|
* @return string|null additional info or null if none to be displayed |
118
|
|
|
*/ |
119
|
|
View Code Duplication |
private function getAdditionalUserInfo(IUser $user) { |
120
|
|
|
if ($this->additionalInfoField === 'email') { |
121
|
|
|
return $user->getEMailAddress(); |
122
|
|
|
} elseif ($this->additionalInfoField === 'id') { |
123
|
|
|
return $user->getUID(); |
124
|
|
|
} |
125
|
|
|
return null; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Convert an IShare to an array for OCS output |
130
|
|
|
* |
131
|
|
|
* @param IShare $share |
132
|
|
|
* @param bool $received whether it's formatting received shares |
133
|
|
|
* @return array |
134
|
|
|
* @throws NotFoundException In case the node can't be resolved. |
135
|
|
|
*/ |
136
|
|
|
protected function formatShare(IShare $share, $received = false) { |
137
|
|
|
$sharedBy = $this->userManager->get($share->getSharedBy()); |
138
|
|
|
$shareOwner = $this->userManager->get($share->getShareOwner()); |
139
|
|
|
|
140
|
|
|
$result = [ |
141
|
|
|
'id' => $share->getId(), |
142
|
|
|
'share_type' => $share->getShareType(), |
143
|
|
|
'uid_owner' => $share->getSharedBy(), |
144
|
|
|
'displayname_owner' => $sharedBy !== null ? $sharedBy->getDisplayName() : $share->getSharedBy(), |
145
|
|
|
'permissions' => $share->getPermissions(), |
146
|
|
|
'stime' => $share->getShareTime() ? $share->getShareTime()->getTimestamp() : null, |
147
|
|
|
'parent' => null, |
148
|
|
|
'expiration' => null, |
149
|
|
|
'token' => null, |
150
|
|
|
'uid_file_owner' => $share->getShareOwner(), |
151
|
|
|
'displayname_file_owner' => $shareOwner !== null ? $shareOwner->getDisplayName() : $share->getShareOwner() |
152
|
|
|
]; |
153
|
|
|
|
154
|
|
|
if ($received) { |
155
|
|
|
// also add state |
156
|
|
|
$result['state'] = $share->getState(); |
157
|
|
|
|
158
|
|
|
// can only fetch path info if mounted already or if owner |
159
|
|
|
if ($share->getState() === Share::STATE_ACCEPTED || $share->getShareOwner() === $this->currentUser->getUID()) { |
160
|
|
|
$userFolder = $this->rootFolder->getUserFolder($this->currentUser->getUID()); |
161
|
|
|
} else { |
162
|
|
|
// need to go through owner user for pending shares |
163
|
|
|
$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner()); |
164
|
|
|
} |
165
|
|
|
} else { |
166
|
|
|
$userFolder = $this->rootFolder->getUserFolder($this->currentUser->getUID()); |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
$nodes = $userFolder->getById($share->getNodeId()); |
170
|
|
|
|
171
|
|
|
if (empty($nodes)) { |
172
|
|
|
throw new NotFoundException(); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
$node = $nodes[0]; |
176
|
|
|
|
177
|
|
|
$result['path'] = $userFolder->getRelativePath($node->getPath()); |
178
|
|
|
if ($node instanceof \OCP\Files\Folder) { |
179
|
|
|
$result['item_type'] = 'folder'; |
180
|
|
|
} else { |
181
|
|
|
$result['item_type'] = 'file'; |
182
|
|
|
} |
183
|
|
|
$result['mimetype'] = $node->getMimeType(); |
184
|
|
|
$result['storage_id'] = $node->getStorage()->getId(); |
185
|
|
|
$result['storage'] = $node->getStorage()->getCache()->getNumericStorageId(); |
186
|
|
|
$result['item_source'] = \strval($node->getId()); |
187
|
|
|
$result['file_source'] = \strval($node->getId()); |
188
|
|
|
$result['file_parent'] = \strval($node->getParent()->getId()); |
189
|
|
|
$result['file_target'] = $share->getTarget(); |
190
|
|
|
|
191
|
|
|
if ($share->getShareType() === Share::SHARE_TYPE_USER) { |
192
|
|
|
$sharedWith = $this->userManager->get($share->getSharedWith()); |
193
|
|
|
$result['share_with'] = $share->getSharedWith(); |
194
|
|
|
$result['share_with_displayname'] = $sharedWith !== null ? $sharedWith->getDisplayName() : $share->getSharedWith(); |
195
|
|
|
if ($sharedWith !== null) { |
196
|
|
|
$result['share_with_additional_info'] = $this->getAdditionalUserInfo($sharedWith); |
197
|
|
|
} |
198
|
|
|
} elseif ($share->getShareType() === Share::SHARE_TYPE_GROUP) { |
199
|
|
|
$group = $this->groupManager->get($share->getSharedWith()); |
200
|
|
|
$result['share_with'] = $share->getSharedWith(); |
201
|
|
|
$result['share_with_displayname'] = $group !== null ? $group->getDisplayName() : $share->getSharedWith(); |
202
|
|
|
} elseif ($share->getShareType() === Share::SHARE_TYPE_LINK) { |
203
|
|
|
$result['share_with'] = $share->getPassword(); |
204
|
|
|
$result['share_with_displayname'] = $share->getPassword(); |
205
|
|
|
$result['name'] = $share->getName(); |
206
|
|
|
|
207
|
|
|
$result['token'] = $share->getToken(); |
208
|
|
|
if ($share->getToken() !== null) { |
209
|
|
|
$result['url'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $share->getToken()]); |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
$expiration = $share->getExpirationDate(); |
213
|
|
|
if ($expiration !== null) { |
214
|
|
|
$result['expiration'] = $expiration->format('Y-m-d 00:00:00'); |
215
|
|
|
} |
216
|
|
|
} elseif ($share->getShareType() === Share::SHARE_TYPE_REMOTE) { |
217
|
|
|
$result['share_with'] = $share->getSharedWith(); |
218
|
|
|
$result['share_with_displayname'] = $share->getSharedWith(); |
219
|
|
|
$result['token'] = $share->getToken(); |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
$result['mail_send'] = $share->getMailSend() ? 1 : 0; |
223
|
|
|
|
224
|
|
|
return $result; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Get a specific share by id |
229
|
|
|
* |
230
|
|
|
* @NoCSRFRequired |
231
|
|
|
* @NoAdminRequired |
232
|
|
|
* |
233
|
|
|
* @param string $id |
234
|
|
|
* @return Result |
235
|
|
|
*/ |
236
|
|
|
public function getShare($id) { |
237
|
|
|
if (!$this->shareManager->shareApiEnabled()) { |
238
|
|
|
return new Result(null, 404, $this->l->t('Share API is disabled')); |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
try { |
242
|
|
|
$share = $this->getShareById($id); |
243
|
|
|
} catch (ShareNotFound $e) { |
244
|
|
|
return new Result(null, 404, $this->l->t('Wrong share ID, share doesn\'t exist')); |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
if ($this->canAccessShare($share)) { |
248
|
|
|
try { |
249
|
|
|
$share = $this->formatShare($share); |
250
|
|
|
return new Result([$share]); |
251
|
|
|
} catch (NotFoundException $e) { |
252
|
|
|
//Fall trough |
253
|
|
|
} |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
return new Result(null, 404, $this->l->t('Wrong share ID, share doesn\'t exist')); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* Delete a share |
261
|
|
|
* |
262
|
|
|
* @NoCSRFRequired |
263
|
|
|
* @NoAdminRequired |
264
|
|
|
* |
265
|
|
|
* @param string $id |
266
|
|
|
* @return Result |
267
|
|
|
*/ |
268
|
|
|
public function deleteShare($id) { |
269
|
|
|
if (!$this->shareManager->shareApiEnabled()) { |
270
|
|
|
return new Result(null, 404, $this->l->t('Share API is disabled')); |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
try { |
274
|
|
|
$share = $this->getShareById($id); |
275
|
|
|
} catch (ShareNotFound $e) { |
276
|
|
|
return new Result(null, 404, $this->l->t('Wrong share ID, share doesn\'t exist')); |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
try { |
280
|
|
|
$share->getNode()->lock(ILockingProvider::LOCK_SHARED); |
281
|
|
|
} catch (LockedException $e) { |
282
|
|
|
return new Result(null, 404, 'could not delete share'); |
283
|
|
|
} |
284
|
|
|
|
285
|
|
View Code Duplication |
if (!$this->canAccessShare($share)) { |
|
|
|
|
286
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
287
|
|
|
return new Result(null, 404, $this->l->t('Could not delete share')); |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
$this->shareManager->deleteShare($share); |
291
|
|
|
|
292
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
293
|
|
|
|
294
|
|
|
return new Result(); |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
/** |
298
|
|
|
* @NoCSRFRequired |
299
|
|
|
* @NoAdminRequired |
300
|
|
|
* |
301
|
|
|
* @return Result |
302
|
|
|
*/ |
303
|
|
|
public function createShare() { |
304
|
|
|
$share = $this->shareManager->newShare(); |
305
|
|
|
|
306
|
|
|
if (!$this->shareManager->shareApiEnabled()) { |
307
|
|
|
return new Result(null, 404, $this->l->t('Share API is disabled')); |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
$name = $this->request->getParam('name', null); |
311
|
|
|
|
312
|
|
|
// Verify path |
313
|
|
|
$path = $this->request->getParam('path', null); |
314
|
|
|
if ($path === null) { |
315
|
|
|
return new Result(null, 404, $this->l->t('Please specify a file or folder path')); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
$userFolder = $this->rootFolder->getUserFolder($this->currentUser->getUID()); |
319
|
|
|
|
320
|
|
|
try { |
321
|
|
|
$path = $userFolder->get($path); |
322
|
|
|
} catch (NotFoundException $e) { |
323
|
|
|
return new Result(null, 404, $this->l->t('Wrong path, file/folder doesn\'t exist')); |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
$share->setNode($path); |
327
|
|
|
|
328
|
|
|
try { |
329
|
|
|
$share->getNode()->lock(ILockingProvider::LOCK_SHARED); |
330
|
|
|
} catch (LockedException $e) { |
331
|
|
|
return new Result(null, 404, 'Could not create share'); |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
$shareType = (int)$this->request->getParam('shareType', '-1'); |
335
|
|
|
|
336
|
|
|
// Parse permissions (if available) |
337
|
|
|
$permissions = $this->request->getParam('permissions', null); |
338
|
|
|
if ($permissions === null) { |
339
|
|
|
if ($shareType !== Share::SHARE_TYPE_LINK) { |
340
|
|
|
$permissions = $this->config->getAppValue('core', 'shareapi_default_permissions', \OCP\Constants::PERMISSION_ALL); |
341
|
|
|
$permissions |= \OCP\Constants::PERMISSION_READ; |
342
|
|
|
} else { |
343
|
|
|
$permissions = \OCP\Constants::PERMISSION_ALL; |
344
|
|
|
} |
345
|
|
|
} else { |
346
|
|
|
$permissions = (int)$permissions; |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
if ($permissions < 0 || $permissions > \OCP\Constants::PERMISSION_ALL) { |
350
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
351
|
|
|
return new Result(null, 404, 'invalid permissions'); |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
if ($permissions === 0) { |
355
|
|
|
return new Result(null, 400, $this->l->t('Cannot remove all permissions')); |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
// link shares can have create-only without read (anonymous upload) |
359
|
|
|
if ($shareType !== Share::SHARE_TYPE_LINK && $permissions !== \OCP\Constants::PERMISSION_CREATE) { |
360
|
|
|
// Shares always require read permissions |
361
|
|
|
$permissions |= \OCP\Constants::PERMISSION_READ; |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
if ($path instanceof \OCP\Files\File) { |
365
|
|
|
// Single file shares should never have delete or create permissions |
366
|
|
|
$permissions &= ~\OCP\Constants::PERMISSION_DELETE; |
367
|
|
|
$permissions &= ~\OCP\Constants::PERMISSION_CREATE; |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
/* |
371
|
|
|
* Hack for https://github.com/owncloud/core/issues/22587 |
372
|
|
|
* We check the permissions via webdav. But the permissions of the mount point |
373
|
|
|
* do not equal the share permissions. Here we fix that for federated mounts. |
374
|
|
|
*/ |
375
|
|
|
if ($path->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) { |
376
|
|
|
$permissions &= ~($permissions & ~$path->getPermissions()); |
377
|
|
|
} |
378
|
|
|
|
379
|
|
|
$shareWith = $this->request->getParam('shareWith', null); |
380
|
|
|
|
381
|
|
|
$autoAccept = $this->config->getAppValue('core', 'shareapi_auto_accept_share', 'yes') === 'yes'; |
382
|
|
|
if ($shareType === Share::SHARE_TYPE_USER) { |
383
|
|
|
// Valid user is required to share |
384
|
|
|
if ($shareWith === null || !$this->userManager->userExists($shareWith)) { |
385
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
386
|
|
|
return new Result(null, 404, $this->l->t('Please specify a valid user')); |
387
|
|
|
} |
388
|
|
|
$share->setSharedWith($shareWith); |
389
|
|
|
$share->setPermissions($permissions); |
390
|
|
|
if ($autoAccept) { |
391
|
|
|
$share->setState(Share::STATE_ACCEPTED); |
392
|
|
|
} else { |
393
|
|
|
$share->setState(Share::STATE_PENDING); |
394
|
|
|
} |
395
|
|
|
} elseif ($shareType === Share::SHARE_TYPE_GROUP) { |
396
|
|
|
if (!$this->shareManager->allowGroupSharing()) { |
397
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
398
|
|
|
return new Result(null, 404, $this->l->t('Group sharing is disabled by the administrator')); |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
// Valid group is required to share |
402
|
|
|
if ($shareWith === null || !$this->groupManager->groupExists($shareWith)) { |
403
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
404
|
|
|
return new Result(null, 404, $this->l->t('Please specify a valid group')); |
405
|
|
|
} |
406
|
|
|
if ($this->sharingBlacklist->isGroupBlacklisted($this->groupManager->get($shareWith))) { |
407
|
|
|
return new Result(null, 403, $this->l->t('The group is blacklisted for sharing')); |
408
|
|
|
} |
409
|
|
|
$share->setSharedWith($shareWith); |
410
|
|
|
$share->setPermissions($permissions); |
411
|
|
|
if ($autoAccept) { |
412
|
|
|
$share->setState(Share::STATE_ACCEPTED); |
413
|
|
|
} else { |
414
|
|
|
$share->setState(Share::STATE_PENDING); |
415
|
|
|
} |
416
|
|
|
} elseif ($shareType === Share::SHARE_TYPE_LINK) { |
417
|
|
|
//Can we even share links? |
418
|
|
|
if (!$this->shareManager->shareApiAllowLinks()) { |
419
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
420
|
|
|
return new Result(null, 404, $this->l->t('Public link sharing is disabled by the administrator')); |
421
|
|
|
} |
422
|
|
|
|
423
|
|
|
// legacy way, expecting that this won't be used together with "create-only" shares |
424
|
|
|
$publicUpload = $this->request->getParam('publicUpload', null); |
425
|
|
|
// a few permission checks |
426
|
|
View Code Duplication |
if ($publicUpload === 'true' || $permissions === \OCP\Constants::PERMISSION_CREATE) { |
|
|
|
|
427
|
|
|
// Check if public upload is allowed |
428
|
|
|
if (!$this->shareManager->shareApiLinkAllowPublicUpload()) { |
429
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
430
|
|
|
return new Result(null, 403, $this->l->t('Public upload disabled by the administrator')); |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
// Public upload can only be set for folders |
434
|
|
|
if ($path instanceof \OCP\Files\File) { |
435
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
436
|
|
|
return new Result(null, 404, $this->l->t('Public upload is only possible for publicly shared folders')); |
437
|
|
|
} |
438
|
|
|
} |
439
|
|
|
|
440
|
|
|
// convert to permissions |
441
|
|
|
if ($publicUpload === 'true') { |
442
|
|
|
$share->setPermissions( |
443
|
|
|
\OCP\Constants::PERMISSION_READ | |
444
|
|
|
\OCP\Constants::PERMISSION_CREATE | |
445
|
|
|
\OCP\Constants::PERMISSION_UPDATE | |
446
|
|
|
\OCP\Constants::PERMISSION_DELETE |
447
|
|
|
); |
448
|
|
|
} elseif ($permissions === \OCP\Constants::PERMISSION_CREATE || |
449
|
|
|
$permissions === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE)) { |
450
|
|
|
$share->setPermissions($permissions); |
451
|
|
|
} else { |
452
|
|
|
// because when "publicUpload" is passed usually no permissions are set, |
453
|
|
|
// which defaults to ALL. But in the case of link shares we default to READ... |
454
|
|
|
$share->setPermissions(\OCP\Constants::PERMISSION_READ); |
455
|
|
|
} |
456
|
|
|
|
457
|
|
|
// set name only if passed as parameter, empty string is allowed |
458
|
|
|
if ($name !== null) { |
459
|
|
|
$share->setName($name); |
460
|
|
|
} |
461
|
|
|
|
462
|
|
|
// Set password |
463
|
|
|
$password = $this->request->getParam('password', ''); |
464
|
|
|
|
465
|
|
|
if ($password !== '') { |
466
|
|
|
$share->setPassword($password); |
467
|
|
|
} |
468
|
|
|
|
469
|
|
|
//Expire date |
470
|
|
|
$expireDate = $this->request->getParam('expireDate', ''); |
471
|
|
|
|
472
|
|
|
if ($expireDate !== '') { |
473
|
|
|
try { |
474
|
|
|
$expireDate = $this->parseDate($expireDate); |
475
|
|
|
$share->setExpirationDate($expireDate); |
476
|
|
|
} catch (\Exception $e) { |
477
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
478
|
|
|
return new Result(null, 404, $this->l->t('Invalid date, date format must be YYYY-MM-DD')); |
479
|
|
|
} |
480
|
|
|
} |
481
|
|
|
} elseif ($shareType === Share::SHARE_TYPE_REMOTE) { |
482
|
|
|
if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) { |
483
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
484
|
|
|
return new Result(null, 403, $this->l->t('Sharing %s failed because the back end does not allow shares from type %s', [$path->getPath(), $shareType])); |
485
|
|
|
} |
486
|
|
|
|
487
|
|
|
$share->setSharedWith($shareWith); |
488
|
|
|
$share->setPermissions($permissions); |
489
|
|
View Code Duplication |
} else { |
|
|
|
|
490
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
491
|
|
|
return new Result(null, 400, $this->l->t('Unknown share type')); |
492
|
|
|
} |
493
|
|
|
|
494
|
|
|
$share->setShareType($shareType); |
495
|
|
|
$share->setSharedBy($this->currentUser->getUID()); |
496
|
|
|
|
497
|
|
|
try { |
498
|
|
|
$share = $this->shareManager->createShare($share); |
499
|
|
|
} catch (GenericShareException $e) { |
500
|
|
|
$code = $e->getCode() === 0 ? 403 : $e->getCode(); |
501
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
502
|
|
|
return new Result(null, $code, $e->getHint()); |
503
|
|
|
} catch (\Exception $e) { |
504
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
505
|
|
|
return new Result(null, 403, $e->getMessage()); |
506
|
|
|
} |
507
|
|
|
|
508
|
|
|
$share->getNode()->unlock(\OCP\Lock\ILockingProvider::LOCK_SHARED); |
509
|
|
|
|
510
|
|
|
$formattedShareAfterCreate = $this->formatShare($share); |
511
|
|
|
|
512
|
|
|
return new Result($formattedShareAfterCreate); |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
/** |
516
|
|
|
* @param \OCP\Files\File|\OCP\Files\Folder $node |
517
|
|
|
* @param boolean $includeTags include tags in response |
518
|
|
|
* @param int|null $stateFilter state filter or empty for all, defaults to 0 (accepted) |
519
|
|
|
* @return Result |
520
|
|
|
*/ |
521
|
|
|
private function getSharedWithMe($node = null, $includeTags, $stateFilter = 0) { |
522
|
|
|
$userShares = $this->shareManager->getSharedWith($this->currentUser->getUID(), Share::SHARE_TYPE_USER, $node, -1, 0); |
523
|
|
|
$groupShares = $this->shareManager->getSharedWith($this->currentUser->getUID(), Share::SHARE_TYPE_GROUP, $node, -1, 0); |
524
|
|
|
|
525
|
|
|
$shares = \array_merge($userShares, $groupShares); |
526
|
|
|
|
527
|
|
|
$shares = \array_filter($shares, function (IShare $share) { |
528
|
|
|
return $share->getShareOwner() !== $this->currentUser->getUID(); |
529
|
|
|
}); |
530
|
|
|
|
531
|
|
|
$formatted = []; |
532
|
|
|
foreach ($shares as $share) { |
533
|
|
|
if (($stateFilter === null || $share->getState() === $stateFilter) && |
534
|
|
|
$this->canAccessShare($share)) { |
535
|
|
|
try { |
536
|
|
|
$formatted[] = $this->formatShare($share, true); |
537
|
|
|
} catch (NotFoundException $e) { |
538
|
|
|
// Ignore this share |
539
|
|
|
} |
540
|
|
|
} |
541
|
|
|
} |
542
|
|
|
|
543
|
|
|
if ($includeTags) { |
544
|
|
|
$formatted = \OCA\Files\Helper::populateTags($formatted, 'file_source'); |
545
|
|
|
} |
546
|
|
|
|
547
|
|
|
return new Result($formatted); |
548
|
|
|
} |
549
|
|
|
|
550
|
|
|
/** |
551
|
|
|
* @param \OCP\Files\Folder $folder |
552
|
|
|
* @return Result |
553
|
|
|
*/ |
554
|
|
|
private function getSharesInDir($folder) { |
555
|
|
|
if (!($folder instanceof \OCP\Files\Folder)) { |
556
|
|
|
return new Result(null, 400, $this->l->t('Not a directory')); |
557
|
|
|
} |
558
|
|
|
|
559
|
|
|
$nodes = $folder->getDirectoryListing(); |
560
|
|
|
/** @var IShare[] $shares */ |
561
|
|
|
$shares = []; |
562
|
|
|
foreach ($nodes as $node) { |
563
|
|
|
$shares = \array_merge($shares, $this->shareManager->getSharesBy($this->currentUser->getUID(), Share::SHARE_TYPE_USER, $node, false, -1, 0)); |
564
|
|
|
$shares = \array_merge($shares, $this->shareManager->getSharesBy($this->currentUser->getUID(), Share::SHARE_TYPE_GROUP, $node, false, -1, 0)); |
565
|
|
|
$shares = \array_merge($shares, $this->shareManager->getSharesBy($this->currentUser->getUID(), Share::SHARE_TYPE_LINK, $node, false, -1, 0)); |
566
|
|
|
if ($this->shareManager->outgoingServer2ServerSharesAllowed()) { |
567
|
|
|
$shares = \array_merge($shares, $this->shareManager->getSharesBy($this->currentUser->getUID(), Share::SHARE_TYPE_REMOTE, $node, false, -1, 0)); |
568
|
|
|
} |
569
|
|
|
} |
570
|
|
|
|
571
|
|
|
$formatted = []; |
572
|
|
|
foreach ($shares as $share) { |
573
|
|
|
try { |
574
|
|
|
$formatted[] = $this->formatShare($share); |
575
|
|
|
} catch (NotFoundException $e) { |
576
|
|
|
//Ignore this share |
577
|
|
|
} |
578
|
|
|
} |
579
|
|
|
|
580
|
|
|
return new Result($formatted); |
581
|
|
|
} |
582
|
|
|
|
583
|
|
|
/** |
584
|
|
|
* The getShares function. |
585
|
|
|
* |
586
|
|
|
* @NoCSRFRequired |
587
|
|
|
* @NoAdminRequired |
588
|
|
|
* |
589
|
|
|
* - Get shares by the current user |
590
|
|
|
* - Get shares by the current user and reshares (?reshares=true) |
591
|
|
|
* - Get shares with the current user (?shared_with_me=true) |
592
|
|
|
* - Get shares for a specific path (?path=...) |
593
|
|
|
* - Get all shares in a folder (?subfiles=true&path=..) |
594
|
|
|
* |
595
|
|
|
* @return Result |
596
|
|
|
*/ |
597
|
|
|
public function getShares() { |
598
|
|
|
if (!$this->shareManager->shareApiEnabled()) { |
599
|
|
|
return new Result(); |
600
|
|
|
} |
601
|
|
|
|
602
|
|
|
$sharedWithMe = $this->request->getParam('shared_with_me', null); |
603
|
|
|
$reshares = $this->request->getParam('reshares', null); |
604
|
|
|
$subfiles = $this->request->getParam('subfiles'); |
605
|
|
|
$path = $this->request->getParam('path', null); |
606
|
|
|
|
607
|
|
|
$includeTags = $this->request->getParam('include_tags', false); |
608
|
|
|
|
609
|
|
|
if ($path !== null) { |
610
|
|
|
$userFolder = $this->rootFolder->getUserFolder($this->currentUser->getUID()); |
611
|
|
|
try { |
612
|
|
|
$path = $userFolder->get($path); |
613
|
|
|
$path->lock(ILockingProvider::LOCK_SHARED); |
614
|
|
|
} catch (\OCP\Files\NotFoundException $e) { |
615
|
|
|
return new Result(null, 404, $this->l->t('Wrong path, file/folder doesn\'t exist')); |
616
|
|
|
} catch (LockedException $e) { |
617
|
|
|
return new Result(null, 404, $this->l->t('Could not lock path')); |
618
|
|
|
} |
619
|
|
|
} |
620
|
|
|
|
621
|
|
|
if ($sharedWithMe === 'true') { |
622
|
|
|
$stateFilter = $this->request->getParam('state', Share::STATE_ACCEPTED); |
623
|
|
|
if ($stateFilter === '') { |
624
|
|
|
$stateFilter = Share::STATE_ACCEPTED; |
625
|
|
|
} elseif ($stateFilter === 'all') { |
626
|
|
|
$stateFilter = null; // which means all |
627
|
|
|
} else { |
628
|
|
|
$stateFilter = (int)$stateFilter; |
629
|
|
|
} |
630
|
|
|
$result = $this->getSharedWithMe($path, $includeTags, $stateFilter); |
|
|
|
|
631
|
|
|
if ($path !== null) { |
632
|
|
|
$path->unlock(ILockingProvider::LOCK_SHARED); |
633
|
|
|
} |
634
|
|
|
return $result; |
635
|
|
|
} |
636
|
|
|
|
637
|
|
|
if ($subfiles === 'true') { |
638
|
|
|
$result = $this->getSharesInDir($path); |
|
|
|
|
639
|
|
|
if ($path !== null) { |
640
|
|
|
$path->unlock(ILockingProvider::LOCK_SHARED); |
641
|
|
|
} |
642
|
|
|
return $result; |
643
|
|
|
} |
644
|
|
|
|
645
|
|
|
if ($reshares === 'true') { |
646
|
|
|
$reshares = true; |
647
|
|
|
} else { |
648
|
|
|
$reshares = false; |
649
|
|
|
} |
650
|
|
|
|
651
|
|
|
// Get all shares |
652
|
|
|
$userShares = $this->shareManager->getSharesBy($this->currentUser->getUID(), Share::SHARE_TYPE_USER, $path, $reshares, -1, 0); |
653
|
|
|
$groupShares = $this->shareManager->getSharesBy($this->currentUser->getUID(), Share::SHARE_TYPE_GROUP, $path, $reshares, -1, 0); |
654
|
|
|
$linkShares = $this->shareManager->getSharesBy($this->currentUser->getUID(), Share::SHARE_TYPE_LINK, $path, $reshares, -1, 0); |
655
|
|
|
$shares = \array_merge($userShares, $groupShares, $linkShares); |
656
|
|
|
|
657
|
|
|
if ($this->shareManager->outgoingServer2ServerSharesAllowed()) { |
658
|
|
|
$federatedShares = $this->shareManager->getSharesBy($this->currentUser->getUID(), Share::SHARE_TYPE_REMOTE, $path, $reshares, -1, 0); |
659
|
|
|
$shares = \array_merge($shares, $federatedShares); |
660
|
|
|
} |
661
|
|
|
|
662
|
|
|
$formatted = []; |
663
|
|
|
foreach ($shares as $share) { |
664
|
|
|
try { |
665
|
|
|
$formatted[] = $this->formatShare($share); |
666
|
|
|
} catch (NotFoundException $e) { |
667
|
|
|
//Ignore share |
668
|
|
|
} |
669
|
|
|
} |
670
|
|
|
|
671
|
|
|
if ($includeTags) { |
672
|
|
|
$formatted = \OCA\Files\Helper::populateTags($formatted, 'file_source'); |
673
|
|
|
} |
674
|
|
|
|
675
|
|
|
if ($path !== null) { |
676
|
|
|
$path->unlock(ILockingProvider::LOCK_SHARED); |
677
|
|
|
} |
678
|
|
|
|
679
|
|
|
return new Result($formatted); |
680
|
|
|
} |
681
|
|
|
|
682
|
|
|
/** |
683
|
|
|
* @NoCSRFRequired |
684
|
|
|
* @NoAdminRequired |
685
|
|
|
* |
686
|
|
|
* @param int $id |
687
|
|
|
* @return Result |
688
|
|
|
*/ |
689
|
|
|
public function updateShare($id) { |
690
|
|
|
if (!$this->shareManager->shareApiEnabled()) { |
691
|
|
|
return new Result(null, 404, $this->l->t('Share API is disabled')); |
692
|
|
|
} |
693
|
|
|
|
694
|
|
|
try { |
695
|
|
|
$share = $this->getShareById($id); |
696
|
|
|
} catch (ShareNotFound $e) { |
697
|
|
|
return new Result(null, 404, $this->l->t('Wrong share ID, share doesn\'t exist')); |
698
|
|
|
} |
699
|
|
|
|
700
|
|
|
$share->getNode()->lock(\OCP\Lock\ILockingProvider::LOCK_SHARED); |
701
|
|
|
|
702
|
|
View Code Duplication |
if (!$this->canAccessShare($share)) { |
|
|
|
|
703
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
704
|
|
|
return new Result(null, 404, $this->l->t('Wrong share ID, share doesn\'t exist')); |
705
|
|
|
} |
706
|
|
|
|
707
|
|
|
$permissions = $this->request->getParam('permissions', null); |
708
|
|
|
$password = $this->request->getParam('password', null); |
709
|
|
|
$publicUpload = $this->request->getParam('publicUpload', null); |
710
|
|
|
$expireDate = $this->request->getParam('expireDate', null); |
711
|
|
|
$name = $this->request->getParam('name', null); |
712
|
|
|
|
713
|
|
|
/* |
714
|
|
|
* expirationdate, password and publicUpload only make sense for link shares |
715
|
|
|
*/ |
716
|
|
|
if ($share->getShareType() === Share::SHARE_TYPE_LINK) { |
717
|
|
|
if ($permissions === null && $password === null && $publicUpload === null && $expireDate === null && $name === null) { |
718
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
719
|
|
|
return new Result(null, 400, 'Wrong or no update parameter given'); |
720
|
|
|
} |
721
|
|
|
|
722
|
|
|
$newPermissions = null; |
723
|
|
|
if ($publicUpload === 'true') { |
724
|
|
|
$newPermissions = \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE; |
725
|
|
|
} elseif ($publicUpload === 'false') { |
726
|
|
|
$newPermissions = \OCP\Constants::PERMISSION_READ; |
727
|
|
|
} |
728
|
|
|
|
729
|
|
|
if ($permissions !== null) { |
730
|
|
|
$newPermissions = (int)$permissions; |
731
|
|
|
} |
732
|
|
|
|
733
|
|
|
if ($newPermissions !== null && |
734
|
|
|
$newPermissions !== \OCP\Constants::PERMISSION_READ && |
735
|
|
|
$newPermissions !== \OCP\Constants::PERMISSION_CREATE && |
736
|
|
|
// legacy |
737
|
|
|
$newPermissions !== (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE) && |
738
|
|
|
// correct |
739
|
|
|
$newPermissions !== (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) |
740
|
|
|
) { |
741
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
742
|
|
|
return new Result(null, 400, $this->l->t('Can\'t change permissions for public share links')); |
743
|
|
|
} |
744
|
|
|
|
745
|
|
|
if ( |
746
|
|
|
// legacy |
747
|
|
|
$newPermissions === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE) || |
748
|
|
|
// correct |
749
|
|
|
$newPermissions === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) |
750
|
|
|
) { |
751
|
|
|
if (!$this->shareManager->shareApiLinkAllowPublicUpload()) { |
752
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
753
|
|
|
return new Result(null, 403, $this->l->t('Public upload disabled by the administrator')); |
754
|
|
|
} |
755
|
|
|
|
756
|
|
|
if (!($share->getNode() instanceof \OCP\Files\Folder)) { |
757
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
758
|
|
|
return new Result(null, 400, $this->l->t('Public upload is only possible for publicly shared folders')); |
759
|
|
|
} |
760
|
|
|
|
761
|
|
|
// normalize to correct public upload permissions |
762
|
|
|
$newPermissions = \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE; |
763
|
|
|
} |
764
|
|
|
|
765
|
|
|
// create-only (upload-only) |
766
|
|
View Code Duplication |
if ( |
|
|
|
|
767
|
|
|
$newPermissions === \OCP\Constants::PERMISSION_CREATE |
768
|
|
|
) { |
769
|
|
|
if (!$this->shareManager->shareApiLinkAllowPublicUpload()) { |
770
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
771
|
|
|
return new Result(null, 403, $this->l->t('Public upload disabled by the administrator')); |
772
|
|
|
} |
773
|
|
|
|
774
|
|
|
if (!($share->getNode() instanceof \OCP\Files\Folder)) { |
775
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
776
|
|
|
return new Result(null, 400, $this->l->t('Public upload is only possible for publicly shared folders')); |
777
|
|
|
} |
778
|
|
|
} |
779
|
|
|
|
780
|
|
|
// set name only if passed as parameter, empty string is allowed |
781
|
|
|
if ($name !== null) { |
782
|
|
|
$oldname = $share->getName(); |
783
|
|
|
$share->setName($name); |
784
|
|
|
} |
785
|
|
|
|
786
|
|
|
if ($newPermissions !== null) { |
787
|
|
|
$share->setPermissions($newPermissions); |
788
|
|
|
$permissions = $newPermissions; |
789
|
|
|
} |
790
|
|
|
|
791
|
|
|
if ($expireDate === '') { |
792
|
|
|
$share->setExpirationDate(null); |
|
|
|
|
793
|
|
|
} elseif ($expireDate !== null) { |
794
|
|
|
try { |
795
|
|
|
$expireDate = $this->parseDate($expireDate); |
796
|
|
|
} catch (\Exception $e) { |
797
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
798
|
|
|
return new Result(null, 400, $e->getMessage()); |
799
|
|
|
} |
800
|
|
|
$share->setExpirationDate($expireDate); |
801
|
|
|
} |
802
|
|
|
|
803
|
|
|
if ($password === '') { |
804
|
|
|
$share->setPassword(null); |
805
|
|
|
} elseif ($password !== null) { |
806
|
|
|
$share->setPassword($password); |
807
|
|
|
} |
808
|
|
|
} else { |
809
|
|
|
// For other shares only permissions is valid. |
810
|
|
|
if ($permissions === null) { |
811
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
812
|
|
|
return new Result(null, 400, $this->l->t('Wrong or no update parameter given')); |
813
|
|
|
} else { |
814
|
|
|
$permissions = (int)$permissions; |
815
|
|
|
$share->setPermissions($permissions); |
816
|
|
|
} |
817
|
|
|
} |
818
|
|
|
|
819
|
|
|
if ($permissions !== null && $share->getShareOwner() !== $this->currentUser->getUID()) { |
820
|
|
|
/* Check if this is an incoming share */ |
821
|
|
|
$incomingShares = $this->shareManager->getSharedWith($this->currentUser->getUID(), Share::SHARE_TYPE_USER, $share->getNode(), -1, 0); |
822
|
|
|
$incomingShares = \array_merge($incomingShares, $this->shareManager->getSharedWith($this->currentUser->getUID(), Share::SHARE_TYPE_GROUP, $share->getNode(), -1, 0)); |
823
|
|
|
|
824
|
|
|
if (!empty($incomingShares)) { |
825
|
|
|
$maxPermissions = 0; |
826
|
|
|
foreach ($incomingShares as $incomingShare) { |
827
|
|
|
$maxPermissions |= $incomingShare->getPermissions(); |
828
|
|
|
} |
829
|
|
|
|
830
|
|
|
if ($share->getPermissions() & ~$maxPermissions) { |
831
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
832
|
|
|
return new Result(null, 404, $this->l->t('Cannot increase permissions')); |
833
|
|
|
} |
834
|
|
|
} |
835
|
|
|
} |
836
|
|
|
|
837
|
|
|
if ($share->getPermissions() === 0) { |
838
|
|
|
return new Result(null, 400, $this->l->t('Cannot remove all permissions')); |
839
|
|
|
} |
840
|
|
|
|
841
|
|
|
try { |
842
|
|
|
$share = $this->shareManager->updateShare($share); |
843
|
|
|
} catch (\Exception $e) { |
844
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
845
|
|
|
return new Result(null, 400, $e->getMessage()); |
846
|
|
|
} |
847
|
|
|
|
848
|
|
|
$share->getNode()->unlock(\OCP\Lock\ILockingProvider::LOCK_SHARED); |
849
|
|
|
|
850
|
|
|
return new Result($this->formatShare($share)); |
851
|
|
|
} |
852
|
|
|
|
853
|
|
|
/** |
854
|
|
|
* @NoCSRFRequired |
855
|
|
|
* @NoAdminRequired |
856
|
|
|
* |
857
|
|
|
* @param int $id |
858
|
|
|
* @return Result |
859
|
|
|
*/ |
860
|
|
|
public function acceptShare($id) { |
861
|
|
|
return $this->updateShareState($id, Share::STATE_ACCEPTED); |
862
|
|
|
} |
863
|
|
|
|
864
|
|
|
/** |
865
|
|
|
* @NoCSRFRequired |
866
|
|
|
* @NoAdminRequired |
867
|
|
|
* |
868
|
|
|
* @param int $id |
869
|
|
|
* @return Result |
870
|
|
|
*/ |
871
|
|
|
public function declineShare($id) { |
872
|
|
|
return $this->updateShareState($id, Share::STATE_REJECTED); |
873
|
|
|
} |
874
|
|
|
|
875
|
|
|
/** |
876
|
|
|
* Send a notification to share recipient(s) |
877
|
|
|
* |
878
|
|
|
* @NoCSRFRequired |
879
|
|
|
* @NoAdminRequired |
880
|
|
|
* |
881
|
|
|
* @param int $itemSource |
882
|
|
|
* @param int $shareType |
883
|
|
|
* @param string $recipient |
884
|
|
|
* |
885
|
|
|
* @return Result |
886
|
|
|
*/ |
887
|
|
|
public function notifyRecipients($itemSource, $shareType, $recipient) { |
888
|
|
|
$recipientList = []; |
889
|
|
|
if ($shareType === Share::SHARE_TYPE_USER) { |
890
|
|
|
$recipientList[] = $this->userManager->get($recipient); |
891
|
|
|
} elseif ($shareType === Share::SHARE_TYPE_GROUP) { |
892
|
|
|
$group = \OC::$server->getGroupManager()->get($recipient); |
893
|
|
|
$recipientList = $group->searchUsers(''); |
894
|
|
|
} |
895
|
|
|
// don't send a mail to the user who shared the file |
896
|
|
|
$recipientList = \array_filter($recipientList, function ($user) { |
897
|
|
|
/** @var IUser $user */ |
898
|
|
|
return $user->getUID() !== $this->currentUser->getUID(); |
899
|
|
|
}); |
900
|
|
|
|
901
|
|
|
$defaults = new \OCP\Defaults(); |
902
|
|
|
$mailNotification = new \OC\Share\MailNotifications( |
903
|
|
|
$this->shareManager, |
904
|
|
|
$this->currentUser, |
905
|
|
|
\OC::$server->getL10N('lib'), |
906
|
|
|
\OC::$server->getMailer(), |
907
|
|
|
$this->config, |
908
|
|
|
\OC::$server->getLogger(), |
909
|
|
|
$defaults, |
910
|
|
|
$this->urlGenerator, |
911
|
|
|
$this->eventDispatcher |
912
|
|
|
); |
913
|
|
|
|
914
|
|
|
$userFolder = $this->rootFolder->getUserFolder($this->currentUser->getUID()); |
915
|
|
|
$nodes = $userFolder->getById($itemSource); |
916
|
|
|
$node = $nodes[0]; |
917
|
|
|
$result = $mailNotification->sendInternalShareMail($node, $shareType, $recipientList); |
|
|
|
|
918
|
|
|
|
919
|
|
|
// if we were able to send to at least one recipient, mark as sent |
920
|
|
|
// allowing the user to resend would spam users who already got a notification |
921
|
|
|
if (\count($result) < \count($recipientList)) { |
922
|
|
|
$items = $this->shareManager->getSharedWith($recipient, $shareType, $node); |
923
|
|
View Code Duplication |
if (\count($items) > 0) { |
|
|
|
|
924
|
|
|
$share = $items[0]; |
925
|
|
|
$share->setMailSend(true); |
926
|
|
|
$this->shareManager->updateShare($share); |
927
|
|
|
} |
928
|
|
|
} |
929
|
|
|
|
930
|
|
|
$message = empty($result) |
931
|
|
|
? null |
932
|
|
|
: $this->l->t( |
933
|
|
|
"Couldn't send mail to following recipient(s): %s ", |
934
|
|
|
\implode(', ', $result) |
|
|
|
|
935
|
|
|
); |
936
|
|
|
return new Result([], 200, $message); |
937
|
|
|
} |
938
|
|
|
|
939
|
|
|
/** |
940
|
|
|
* Just mark a notification to share recipient(s) as sent |
941
|
|
|
* |
942
|
|
|
* @NoCSRFRequired |
943
|
|
|
* @NoAdminRequired |
944
|
|
|
* |
945
|
|
|
* @param int $itemSource |
946
|
|
|
* @param int $shareType |
947
|
|
|
* @param string $recipient |
948
|
|
|
* |
949
|
|
|
* @return Result |
950
|
|
|
*/ |
951
|
|
|
public function notifyRecipientsDisabled($itemSource, $shareType, $recipient) { |
952
|
|
|
$userFolder = $this->rootFolder->getUserFolder($this->currentUser->getUID()); |
953
|
|
|
$nodes = $userFolder->getById($itemSource); |
954
|
|
|
$node = $nodes[0]; |
955
|
|
|
|
956
|
|
|
$items = $this->shareManager->getSharedWith($recipient, $shareType, $node); |
957
|
|
View Code Duplication |
if (\count($items) > 0) { |
|
|
|
|
958
|
|
|
$share = $items[0]; |
959
|
|
|
$share->setMailSend(true); |
960
|
|
|
$this->shareManager->updateShare($share); |
961
|
|
|
} |
962
|
|
|
return new Result(); |
963
|
|
|
} |
964
|
|
|
|
965
|
|
|
/** |
966
|
|
|
* @param $id |
967
|
|
|
* @param $state |
968
|
|
|
* @return Result |
969
|
|
|
*/ |
970
|
|
|
private function updateShareState($id, $state) { |
971
|
|
|
$eventName = ''; |
972
|
|
|
if ($state === Share::STATE_ACCEPTED) { |
973
|
|
|
$eventName = 'accept'; |
974
|
|
|
} elseif ($state === Share::STATE_REJECTED) { |
975
|
|
|
$eventName = 'reject'; |
976
|
|
|
} |
977
|
|
|
|
978
|
|
|
if (!$this->shareManager->shareApiEnabled()) { |
979
|
|
|
return new Result(null, 404, $this->l->t('Share API is disabled')); |
980
|
|
|
} |
981
|
|
|
|
982
|
|
|
try { |
983
|
|
|
$share = $this->getShareById($id, $this->currentUser->getUID()); |
984
|
|
|
$this->eventDispatcher->dispatch('share.before' . $eventName, new GenericEvent(null, ['share' => $share])); |
985
|
|
|
} catch (ShareNotFound $e) { |
986
|
|
|
return new Result(null, 404, $this->l->t('Wrong share ID, share doesn\'t exist')); |
987
|
|
|
} |
988
|
|
|
|
989
|
|
|
$node = $share->getNode(); |
990
|
|
|
$node->lock(\OCP\Lock\ILockingProvider::LOCK_SHARED); |
991
|
|
|
|
992
|
|
|
// this checks that we are either the owner or recipient |
993
|
|
View Code Duplication |
if (!$this->canAccessShare($share)) { |
|
|
|
|
994
|
|
|
$node->unlock(ILockingProvider::LOCK_SHARED); |
995
|
|
|
return new Result(null, 404, $this->l->t('Wrong share ID, share doesn\'t exist')); |
996
|
|
|
} |
997
|
|
|
|
998
|
|
|
// only recipient can accept/reject share |
999
|
|
|
if ($share->getShareOwner() === $this->currentUser->getUID() || |
1000
|
|
|
$share->getSharedBy() === $this->currentUser->getUID()) { |
1001
|
|
|
$node->unlock(ILockingProvider::LOCK_SHARED); |
1002
|
|
|
return new Result(null, 403, $this->l->t('Only recipient can change accepted state')); |
1003
|
|
|
} |
1004
|
|
|
|
1005
|
|
|
if ($share->getState() === $state) { |
1006
|
|
View Code Duplication |
if ($eventName !== '') { |
|
|
|
|
1007
|
|
|
$this->eventDispatcher->dispatch('share.after' . $eventName, new GenericEvent(null, ['share' => $share])); |
1008
|
|
|
} |
1009
|
|
|
// if there are no changes in the state, just return the share as if the change was successful |
1010
|
|
|
$node->unlock(\OCP\Lock\ILockingProvider::LOCK_SHARED); |
1011
|
|
|
return new Result([$this->formatShare($share, true)]); |
1012
|
|
|
} |
1013
|
|
|
|
1014
|
|
|
// we actually want to update all shares related to the node in case there are multiple |
1015
|
|
|
// incoming shares for the same node (ex: receiving simultaneously through group share and user share) |
1016
|
|
|
$allShares = $this->shareManager->getSharedWith($this->currentUser->getUID(), Share::SHARE_TYPE_USER, $node, -1, 0); |
1017
|
|
|
$allShares = \array_merge($allShares, $this->shareManager->getSharedWith($this->currentUser->getUID(), Share::SHARE_TYPE_GROUP, $node, -1, 0)); |
1018
|
|
|
|
1019
|
|
|
// resolve and deduplicate target if accepting |
1020
|
|
|
if ($state === Share::STATE_ACCEPTED) { |
1021
|
|
|
$share = $this->deduplicateShareTarget($share); |
1022
|
|
|
} |
1023
|
|
|
|
1024
|
|
|
$share->setState($state); |
1025
|
|
|
|
1026
|
|
|
try { |
1027
|
|
|
foreach ($allShares as $aShare) { |
1028
|
|
|
$aShare->setState($share->getState()); |
1029
|
|
|
$aShare->setTarget($share->getTarget()); |
1030
|
|
|
$this->shareManager->updateShareForRecipient($aShare, $this->currentUser->getUID()); |
1031
|
|
|
} |
1032
|
|
|
} catch (\Exception $e) { |
1033
|
|
|
$share->getNode()->unlock(ILockingProvider::LOCK_SHARED); |
1034
|
|
|
return new Result(null, 400, $e->getMessage()); |
1035
|
|
|
} |
1036
|
|
|
|
1037
|
|
|
$node->unlock(\OCP\Lock\ILockingProvider::LOCK_SHARED); |
1038
|
|
|
|
1039
|
|
|
// FIXME: needs public API! |
1040
|
|
|
\OC\Files\Filesystem::tearDown(); |
1041
|
|
|
// FIXME: trigger mount for user to make sure the new node is mounted already |
1042
|
|
|
// before formatShare resolves it |
1043
|
|
|
$this->rootFolder->getUserFolder($this->currentUser->getUID()); |
1044
|
|
|
|
1045
|
|
|
$this->notificationPublisher->discardNotificationForUser($share, $this->currentUser->getUID()); |
1046
|
|
|
|
1047
|
|
View Code Duplication |
if ($eventName !== '') { |
|
|
|
|
1048
|
|
|
$this->eventDispatcher->dispatch('share.after' . $eventName, new GenericEvent(null, ['share' => $share])); |
1049
|
|
|
} |
1050
|
|
|
return new Result([$this->formatShare($share, true)]); |
1051
|
|
|
} |
1052
|
|
|
|
1053
|
|
|
/** |
1054
|
|
|
* Deduplicate the share target in the current user home folder, |
1055
|
|
|
* based on configured share folder |
1056
|
|
|
* |
1057
|
|
|
* @param IShare $share share target to deduplicate |
1058
|
|
|
* @return IShare same share with target updated if necessary |
1059
|
|
|
*/ |
1060
|
|
|
private function deduplicateShareTarget(IShare $share) { |
1061
|
|
|
$userFolder = $this->rootFolder->getUserFolder($this->currentUser->getUID()); |
1062
|
|
|
$mountPoint = \basename($share->getTarget()); |
1063
|
|
|
$parentDir = \dirname($share->getTarget()); |
1064
|
|
|
if (!$userFolder->nodeExists($parentDir)) { |
1065
|
|
|
$parentDir = Helper::getShareFolder(); |
1066
|
|
|
$pathAttempt = \OC\Files\Filesystem::normalizePath($parentDir . '/' . $share->getTarget()); |
1067
|
|
|
} else { |
1068
|
|
|
$pathAttempt = \OC\Files\Filesystem::normalizePath($share->getTarget()); |
1069
|
|
|
} |
1070
|
|
|
|
1071
|
|
|
$pathinfo = \pathinfo($pathAttempt); |
1072
|
|
|
$ext = (isset($pathinfo['extension'])) ? '.'.$pathinfo['extension'] : ''; |
1073
|
|
|
$name = $pathinfo['filename']; |
1074
|
|
|
|
1075
|
|
|
$i = 2; |
1076
|
|
|
while ($userFolder->nodeExists($pathAttempt)) { |
1077
|
|
|
$pathAttempt = \OC\Files\Filesystem::normalizePath($parentDir . '/' . $name . ' ('.$i.')' . $ext); |
1078
|
|
|
$i++; |
1079
|
|
|
} |
1080
|
|
|
|
1081
|
|
|
$share->setTarget($pathAttempt); |
1082
|
|
|
|
1083
|
|
|
return $share; |
1084
|
|
|
} |
1085
|
|
|
|
1086
|
|
|
/** |
1087
|
|
|
* @param IShare $share |
1088
|
|
|
* @return bool |
1089
|
|
|
*/ |
1090
|
|
|
protected function canAccessShare(IShare $share) { |
1091
|
|
|
// A file with permissions 0 can't be accessed by us, |
1092
|
|
|
// unless it's a rejected sub-group share in which case we want it visible to let the user accept it again |
1093
|
|
|
if ($share->getPermissions() === 0 |
1094
|
|
|
&& !($share->getShareType() === Share::SHARE_TYPE_GROUP && $share->getState() === Share::STATE_REJECTED)) { |
1095
|
|
|
return false; |
1096
|
|
|
} |
1097
|
|
|
|
1098
|
|
|
// Owner of the file and the sharer of the file can always get share |
1099
|
|
|
if ($share->getShareOwner() === $this->currentUser->getUID() || |
1100
|
|
|
$share->getSharedBy() === $this->currentUser->getUID() |
1101
|
|
|
) { |
1102
|
|
|
return true; |
1103
|
|
|
} |
1104
|
|
|
|
1105
|
|
|
// If the share is shared with you (or a group you are a member of) |
1106
|
|
|
if ($share->getShareType() === Share::SHARE_TYPE_USER && |
1107
|
|
|
$share->getSharedWith() === $this->currentUser->getUID()) { |
1108
|
|
|
return true; |
1109
|
|
|
} |
1110
|
|
|
|
1111
|
|
|
if ($share->getShareType() === Share::SHARE_TYPE_GROUP) { |
1112
|
|
|
$sharedWith = $this->groupManager->get($share->getSharedWith()); |
1113
|
|
|
if ($sharedWith !== null && $sharedWith->inGroup($this->currentUser)) { |
1114
|
|
|
return true; |
1115
|
|
|
} |
1116
|
|
|
} |
1117
|
|
|
|
1118
|
|
|
return false; |
1119
|
|
|
} |
1120
|
|
|
|
1121
|
|
|
/** |
1122
|
|
|
* Make sure that the passed date is valid ISO 8601 |
1123
|
|
|
* So YYYY-MM-DD |
1124
|
|
|
* If not throw an exception |
1125
|
|
|
* |
1126
|
|
|
* @param string $expireDate |
1127
|
|
|
* |
1128
|
|
|
* @throws \Exception |
1129
|
|
|
* @return \DateTime |
1130
|
|
|
*/ |
1131
|
|
|
private function parseDate($expireDate) { |
1132
|
|
|
try { |
1133
|
|
|
$date = new \DateTime($expireDate); |
1134
|
|
|
} catch (\Exception $e) { |
1135
|
|
|
throw new \Exception('Invalid date. Format must be YYYY-MM-DD'); |
1136
|
|
|
} |
1137
|
|
|
|
1138
|
|
|
if ($date === false) { |
1139
|
|
|
throw new \Exception('Invalid date. Format must be YYYY-MM-DD'); |
1140
|
|
|
} |
1141
|
|
|
|
1142
|
|
|
$date->setTime(0, 0, 0); |
1143
|
|
|
|
1144
|
|
|
return $date; |
1145
|
|
|
} |
1146
|
|
|
|
1147
|
|
|
/** |
1148
|
|
|
* Since we have multiple providers but the OCS Share API v1 does |
1149
|
|
|
* not support this we need to check all backends. |
1150
|
|
|
* |
1151
|
|
|
* @param string $id |
1152
|
|
|
* @return IShare |
1153
|
|
|
* @throws ShareNotFound |
1154
|
|
|
*/ |
1155
|
|
|
private function getShareById($id, $recipient = null) { |
1156
|
|
|
$share = null; |
1157
|
|
|
|
1158
|
|
|
// First check if it is an internal share. |
1159
|
|
|
try { |
1160
|
|
|
$share = $this->shareManager->getShareById('ocinternal:'.$id, $recipient); |
1161
|
|
|
} catch (ShareNotFound $e) { |
1162
|
|
|
if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) { |
1163
|
|
|
throw new ShareNotFound(); |
1164
|
|
|
} |
1165
|
|
|
|
1166
|
|
|
$share = $this->shareManager->getShareById('ocFederatedSharing:' . $id, $recipient); |
1167
|
|
|
} |
1168
|
|
|
|
1169
|
|
|
return $share; |
1170
|
|
|
} |
1171
|
|
|
} |
1172
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.