Passed
Push — master ( 6f3c4f...d83ea2 )
by Blizzz
16:41 queued 12s
created
apps/files_sharing/lib/Controller/ShareController.php 2 patches
Indentation   +533 added lines, -533 removed lines patch added patch discarded remove patch
@@ -86,537 +86,537 @@
 block discarded – undo
86 86
  * @package OCA\Files_Sharing\Controllers
87 87
  */
88 88
 class ShareController extends AuthPublicShareController {
89
-	protected IConfig $config;
90
-	protected IUserManager $userManager;
91
-	protected ILogger $logger;
92
-	protected \OCP\Activity\IManager $activityManager;
93
-	protected IPreview $previewManager;
94
-	protected IRootFolder $rootFolder;
95
-	protected FederatedShareProvider $federatedShareProvider;
96
-	protected IAccountManager $accountManager;
97
-	protected IEventDispatcher $eventDispatcher;
98
-	protected IL10N $l10n;
99
-	protected Defaults $defaults;
100
-	protected ShareManager $shareManager;
101
-	protected ISecureRandom $secureRandom;
102
-	protected ?Share\IShare $share = null;
103
-	private IPublicShareTemplateFactory $publicShareTemplateFactory;
104
-
105
-	public function __construct(
106
-		string $appName,
107
-		IRequest $request,
108
-		IConfig $config,
109
-		IURLGenerator $urlGenerator,
110
-		IUserManager $userManager,
111
-		ILogger $logger,
112
-		\OCP\Activity\IManager $activityManager,
113
-		ShareManager $shareManager,
114
-		ISession $session,
115
-		IPreview $previewManager,
116
-		IRootFolder $rootFolder,
117
-		FederatedShareProvider $federatedShareProvider,
118
-		IAccountManager $accountManager,
119
-		IEventDispatcher $eventDispatcher,
120
-		IL10N $l10n,
121
-		ISecureRandom $secureRandom,
122
-		Defaults $defaults,
123
-		IPublicShareTemplateFactory $publicShareTemplateFactory
124
-	) {
125
-		parent::__construct($appName, $request, $session, $urlGenerator);
126
-
127
-		$this->config = $config;
128
-		$this->userManager = $userManager;
129
-		$this->logger = $logger;
130
-		$this->activityManager = $activityManager;
131
-		$this->previewManager = $previewManager;
132
-		$this->rootFolder = $rootFolder;
133
-		$this->federatedShareProvider = $federatedShareProvider;
134
-		$this->accountManager = $accountManager;
135
-		$this->eventDispatcher = $eventDispatcher;
136
-		$this->l10n = $l10n;
137
-		$this->secureRandom = $secureRandom;
138
-		$this->defaults = $defaults;
139
-		$this->shareManager = $shareManager;
140
-		$this->publicShareTemplateFactory = $publicShareTemplateFactory;
141
-	}
142
-
143
-	public const SHARE_ACCESS = 'access';
144
-	public const SHARE_AUTH = 'auth';
145
-	public const SHARE_DOWNLOAD = 'download';
146
-
147
-	/**
148
-	 * @PublicPage
149
-	 * @NoCSRFRequired
150
-	 *
151
-	 * Show the authentication page
152
-	 * The form has to submit to the authenticate method route
153
-	 */
154
-	public function showAuthenticate(): TemplateResponse {
155
-		$templateParameters = ['share' => $this->share];
156
-
157
-		$this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH));
158
-
159
-		$response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
160
-		if ($this->share->getSendPasswordByTalk()) {
161
-			$csp = new ContentSecurityPolicy();
162
-			$csp->addAllowedConnectDomain('*');
163
-			$csp->addAllowedMediaDomain('blob:');
164
-			$response->setContentSecurityPolicy($csp);
165
-		}
166
-
167
-		return $response;
168
-	}
169
-
170
-	/**
171
-	 * The template to show when authentication failed
172
-	 */
173
-	protected function showAuthFailed(): TemplateResponse {
174
-		$templateParameters = ['share' => $this->share, 'wrongpw' => true];
175
-
176
-		$this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH));
177
-
178
-		$response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
179
-		if ($this->share->getSendPasswordByTalk()) {
180
-			$csp = new ContentSecurityPolicy();
181
-			$csp->addAllowedConnectDomain('*');
182
-			$csp->addAllowedMediaDomain('blob:');
183
-			$response->setContentSecurityPolicy($csp);
184
-		}
185
-
186
-		return $response;
187
-	}
188
-
189
-	/**
190
-	 * The template to show after user identification
191
-	 */
192
-	protected function showIdentificationResult(bool $success = false): TemplateResponse {
193
-		$templateParameters = ['share' => $this->share, 'identityOk' => $success];
194
-
195
-		$this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH));
196
-
197
-		$response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
198
-		if ($this->share->getSendPasswordByTalk()) {
199
-			$csp = new ContentSecurityPolicy();
200
-			$csp->addAllowedConnectDomain('*');
201
-			$csp->addAllowedMediaDomain('blob:');
202
-			$response->setContentSecurityPolicy($csp);
203
-		}
204
-
205
-		return $response;
206
-	}
207
-
208
-	/**
209
-	 * Validate the identity token of a public share
210
-	 *
211
-	 * @param ?string $identityToken
212
-	 * @return bool
213
-	 */
214
-	protected function validateIdentity(?string $identityToken = null): bool {
215
-
216
-		if ($this->share->getShareType() !== IShare::TYPE_EMAIL) {
217
-			return false;
218
-		}
219
-
220
-		if ($identityToken === null || $this->share->getSharedWith() === null) {
221
-			return false;
222
-		}
223
-
224
-		return $identityToken === $this->share->getSharedWith();
225
-	}
226
-
227
-	/**
228
-	 * Generates a password for the share, respecting any password policy defined
229
-	 */
230
-	protected function generatePassword(): void {
231
-		$event = new \OCP\Security\Events\GenerateSecurePasswordEvent();
232
-		$this->eventDispatcher->dispatchTyped($event);
233
-		$password = $event->getPassword() ?? $this->secureRandom->generate(20);
234
-
235
-		$this->share->setPassword($password);
236
-		$this->shareManager->updateShare($this->share);
237
-	}
238
-
239
-	protected function verifyPassword(string $password): bool {
240
-		return $this->shareManager->checkPassword($this->share, $password);
241
-	}
242
-
243
-	protected function getPasswordHash(): string {
244
-		return $this->share->getPassword();
245
-	}
246
-
247
-	public function isValidToken(): bool {
248
-		try {
249
-			$this->share = $this->shareManager->getShareByToken($this->getToken());
250
-		} catch (ShareNotFound $e) {
251
-			return false;
252
-		}
253
-
254
-		return true;
255
-	}
256
-
257
-	protected function isPasswordProtected(): bool {
258
-		return $this->share->getPassword() !== null;
259
-	}
260
-
261
-	protected function authSucceeded() {
262
-		// For share this was always set so it is still used in other apps
263
-		$this->session->set('public_link_authenticated', (string)$this->share->getId());
264
-	}
265
-
266
-	protected function authFailed() {
267
-		$this->emitAccessShareHook($this->share, 403, 'Wrong password');
268
-		$this->emitShareAccessEvent($this->share, self::SHARE_AUTH, 403, 'Wrong password');
269
-	}
270
-
271
-	/**
272
-	 * throws hooks when a share is attempted to be accessed
273
-	 *
274
-	 * @param \OCP\Share\IShare|string $share the Share instance if available,
275
-	 * otherwise token
276
-	 * @param int $errorCode
277
-	 * @param string $errorMessage
278
-	 *
279
-	 * @throws \OCP\HintException
280
-	 * @throws \OC\ServerNotAvailableException
281
-	 *
282
-	 * @deprecated use OCP\Files_Sharing\Event\ShareLinkAccessedEvent
283
-	 */
284
-	protected function emitAccessShareHook($share, int $errorCode = 200, string $errorMessage = '') {
285
-		$itemType = $itemSource = $uidOwner = '';
286
-		$token = $share;
287
-		$exception = null;
288
-		if ($share instanceof \OCP\Share\IShare) {
289
-			try {
290
-				$token = $share->getToken();
291
-				$uidOwner = $share->getSharedBy();
292
-				$itemType = $share->getNodeType();
293
-				$itemSource = $share->getNodeId();
294
-			} catch (\Exception $e) {
295
-				// we log what we know and pass on the exception afterwards
296
-				$exception = $e;
297
-			}
298
-		}
299
-
300
-		\OC_Hook::emit(Share::class, 'share_link_access', [
301
-			'itemType' => $itemType,
302
-			'itemSource' => $itemSource,
303
-			'uidOwner' => $uidOwner,
304
-			'token' => $token,
305
-			'errorCode' => $errorCode,
306
-			'errorMessage' => $errorMessage
307
-		]);
308
-
309
-		if (!is_null($exception)) {
310
-			throw $exception;
311
-		}
312
-	}
313
-
314
-	/**
315
-	 * Emit a ShareLinkAccessedEvent event when a share is accessed, downloaded, auth...
316
-	 */
317
-	protected function emitShareAccessEvent(IShare $share, string $step = '', int $errorCode = 200, string $errorMessage = ''): void {
318
-		if ($step !== self::SHARE_ACCESS &&
319
-			$step !== self::SHARE_AUTH &&
320
-			$step !== self::SHARE_DOWNLOAD) {
321
-			return;
322
-		}
323
-		$this->eventDispatcher->dispatchTyped(new ShareLinkAccessedEvent($share, $step, $errorCode, $errorMessage));
324
-	}
325
-
326
-	/**
327
-	 * Validate the permissions of the share
328
-	 *
329
-	 * @param Share\IShare $share
330
-	 * @return bool
331
-	 */
332
-	private function validateShare(\OCP\Share\IShare $share) {
333
-		// If the owner is disabled no access to the link is granted
334
-		$owner = $this->userManager->get($share->getShareOwner());
335
-		if ($owner === null || !$owner->isEnabled()) {
336
-			return false;
337
-		}
338
-
339
-		// If the initiator of the share is disabled no access is granted
340
-		$initiator = $this->userManager->get($share->getSharedBy());
341
-		if ($initiator === null || !$initiator->isEnabled()) {
342
-			return false;
343
-		}
344
-
345
-		return $share->getNode()->isReadable() && $share->getNode()->isShareable();
346
-	}
347
-
348
-	/**
349
-	 * @PublicPage
350
-	 * @NoCSRFRequired
351
-	 *
352
-	 *
353
-	 * @param string $path
354
-	 * @return TemplateResponse
355
-	 * @throws NotFoundException
356
-	 * @throws \Exception
357
-	 */
358
-	public function showShare($path = ''): TemplateResponse {
359
-		\OC_User::setIncognitoMode(true);
360
-
361
-		// Check whether share exists
362
-		try {
363
-			$share = $this->shareManager->getShareByToken($this->getToken());
364
-		} catch (ShareNotFound $e) {
365
-			// The share does not exists, we do not emit an ShareLinkAccessedEvent
366
-			$this->emitAccessShareHook($this->getToken(), 404, 'Share not found');
367
-			throw new NotFoundException();
368
-		}
369
-
370
-		if (!$this->validateShare($share)) {
371
-			throw new NotFoundException();
372
-		}
373
-
374
-		$shareNode = $share->getNode();
375
-
376
-		try {
377
-			$templateProvider = $this->publicShareTemplateFactory->getProvider($share);
378
-			$response = $templateProvider->renderPage($share, $this->getToken(), $path);
379
-		} catch (NotFoundException $e) {
380
-			$this->emitAccessShareHook($share, 404, 'Share not found');
381
-			$this->emitShareAccessEvent($share, ShareController::SHARE_ACCESS, 404, 'Share not found');
382
-			throw new NotFoundException();
383
-		}
384
-
385
-		// We can't get the path of a file share
386
-		try {
387
-			if ($shareNode instanceof \OCP\Files\File && $path !== '') {
388
-				$this->emitAccessShareHook($share, 404, 'Share not found');
389
-				$this->emitShareAccessEvent($share, self::SHARE_ACCESS, 404, 'Share not found');
390
-				throw new NotFoundException();
391
-			}
392
-		} catch (\Exception $e) {
393
-			$this->emitAccessShareHook($share, 404, 'Share not found');
394
-			$this->emitShareAccessEvent($share, self::SHARE_ACCESS, 404, 'Share not found');
395
-			throw $e;
396
-		}
397
-
398
-
399
-		$this->emitAccessShareHook($share);
400
-		$this->emitShareAccessEvent($share, self::SHARE_ACCESS);
401
-
402
-		return $response;
403
-	}
404
-
405
-	/**
406
-	 * @PublicPage
407
-	 * @NoCSRFRequired
408
-	 * @NoSameSiteCookieRequired
409
-	 *
410
-	 * @param string $token
411
-	 * @param string $files
412
-	 * @param string $path
413
-	 * @param string $downloadStartSecret
414
-	 * @return void|\OCP\AppFramework\Http\Response
415
-	 * @throws NotFoundException
416
-	 */
417
-	public function downloadShare($token, $files = null, $path = '', $downloadStartSecret = '') {
418
-		\OC_User::setIncognitoMode(true);
419
-
420
-		$share = $this->shareManager->getShareByToken($token);
421
-
422
-		if (!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) {
423
-			return new \OCP\AppFramework\Http\DataResponse('Share has no read permission');
424
-		}
425
-
426
-		$files_list = null;
427
-		if (!is_null($files)) { // download selected files
428
-			$files_list = json_decode($files);
429
-			// in case we get only a single file
430
-			if ($files_list === null) {
431
-				$files_list = [$files];
432
-			}
433
-			// Just in case $files is a single int like '1234'
434
-			if (!is_array($files_list)) {
435
-				$files_list = [$files_list];
436
-			}
437
-		}
438
-
439
-		if (!$this->validateShare($share)) {
440
-			throw new NotFoundException();
441
-		}
442
-
443
-		$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
444
-		$originalSharePath = $userFolder->getRelativePath($share->getNode()->getPath());
445
-
446
-
447
-		// Single file share
448
-		if ($share->getNode() instanceof \OCP\Files\File) {
449
-			// Single file download
450
-			$this->singleFileDownloaded($share, $share->getNode());
451
-		}
452
-		// Directory share
453
-		else {
454
-			/** @var \OCP\Files\Folder $node */
455
-			$node = $share->getNode();
456
-
457
-			// Try to get the path
458
-			if ($path !== '') {
459
-				try {
460
-					$node = $node->get($path);
461
-				} catch (NotFoundException $e) {
462
-					$this->emitAccessShareHook($share, 404, 'Share not found');
463
-					$this->emitShareAccessEvent($share, self::SHARE_DOWNLOAD, 404, 'Share not found');
464
-					return new NotFoundResponse();
465
-				}
466
-			}
467
-
468
-			$originalSharePath = $userFolder->getRelativePath($node->getPath());
469
-
470
-			if ($node instanceof \OCP\Files\File) {
471
-				// Single file download
472
-				$this->singleFileDownloaded($share, $share->getNode());
473
-			} else {
474
-				try {
475
-					if (!empty($files_list)) {
476
-						$this->fileListDownloaded($share, $files_list, $node);
477
-					} else {
478
-						// The folder is downloaded
479
-						$this->singleFileDownloaded($share, $share->getNode());
480
-					}
481
-				} catch (NotFoundException $e) {
482
-					return new NotFoundResponse();
483
-				}
484
-			}
485
-		}
486
-
487
-		/* FIXME: We should do this all nicely in OCP */
488
-		OC_Util::tearDownFS();
489
-		OC_Util::setupFS($share->getShareOwner());
490
-
491
-		/**
492
-		 * this sets a cookie to be able to recognize the start of the download
493
-		 * the content must not be longer than 32 characters and must only contain
494
-		 * alphanumeric characters
495
-		 */
496
-		if (!empty($downloadStartSecret)
497
-			&& !isset($downloadStartSecret[32])
498
-			&& preg_match('!^[a-zA-Z0-9]+$!', $downloadStartSecret) === 1) {
499
-
500
-			// FIXME: set on the response once we use an actual app framework response
501
-			setcookie('ocDownloadStarted', $downloadStartSecret, time() + 20, '/');
502
-		}
503
-
504
-		$this->emitAccessShareHook($share);
505
-		$this->emitShareAccessEvent($share, self::SHARE_DOWNLOAD);
506
-
507
-		$server_params = [ 'head' => $this->request->getMethod() === 'HEAD' ];
508
-
509
-		/**
510
-		 * Http range requests support
511
-		 */
512
-		if (isset($_SERVER['HTTP_RANGE'])) {
513
-			$server_params['range'] = $this->request->getHeader('Range');
514
-		}
515
-
516
-		// download selected files
517
-		if (!is_null($files) && $files !== '') {
518
-			// FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well
519
-			// after dispatching the request which results in a "Cannot modify header information" notice.
520
-			OC_Files::get($originalSharePath, $files_list, $server_params);
521
-			exit();
522
-		} else {
523
-			// FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well
524
-			// after dispatching the request which results in a "Cannot modify header information" notice.
525
-			OC_Files::get(dirname($originalSharePath), basename($originalSharePath), $server_params);
526
-			exit();
527
-		}
528
-	}
529
-
530
-	/**
531
-	 * create activity for every downloaded file
532
-	 *
533
-	 * @param Share\IShare $share
534
-	 * @param array $files_list
535
-	 * @param \OCP\Files\Folder $node
536
-	 * @throws NotFoundException when trying to download a folder or multiple files of a "hide download" share
537
-	 */
538
-	protected function fileListDownloaded(Share\IShare $share, array $files_list, \OCP\Files\Folder $node) {
539
-		if ($share->getHideDownload() && count($files_list) > 1) {
540
-			throw new NotFoundException('Downloading more than 1 file');
541
-		}
542
-
543
-		foreach ($files_list as $file) {
544
-			$subNode = $node->get($file);
545
-			$this->singleFileDownloaded($share, $subNode);
546
-		}
547
-	}
548
-
549
-	/**
550
-	 * create activity if a single file was downloaded from a link share
551
-	 *
552
-	 * @param Share\IShare $share
553
-	 * @throws NotFoundException when trying to download a folder of a "hide download" share
554
-	 */
555
-	protected function singleFileDownloaded(Share\IShare $share, \OCP\Files\Node $node) {
556
-		if ($share->getHideDownload() && $node instanceof Folder) {
557
-			throw new NotFoundException('Downloading a folder');
558
-		}
559
-
560
-		$fileId = $node->getId();
561
-
562
-		$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
563
-		$userNodeList = $userFolder->getById($fileId);
564
-		$userNode = $userNodeList[0];
565
-		$ownerFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
566
-		$userPath = $userFolder->getRelativePath($userNode->getPath());
567
-		$ownerPath = $ownerFolder->getRelativePath($node->getPath());
568
-		$remoteAddress = $this->request->getRemoteAddress();
569
-		$dateTime = new \DateTime();
570
-		$dateTime = $dateTime->format('Y-m-d H');
571
-		$remoteAddressHash = md5($dateTime . '-' . $remoteAddress);
572
-
573
-		$parameters = [$userPath];
574
-
575
-		if ($share->getShareType() === IShare::TYPE_EMAIL) {
576
-			if ($node instanceof \OCP\Files\File) {
577
-				$subject = Downloads::SUBJECT_SHARED_FILE_BY_EMAIL_DOWNLOADED;
578
-			} else {
579
-				$subject = Downloads::SUBJECT_SHARED_FOLDER_BY_EMAIL_DOWNLOADED;
580
-			}
581
-			$parameters[] = $share->getSharedWith();
582
-		} else {
583
-			if ($node instanceof \OCP\Files\File) {
584
-				$subject = Downloads::SUBJECT_PUBLIC_SHARED_FILE_DOWNLOADED;
585
-				$parameters[] = $remoteAddressHash;
586
-			} else {
587
-				$subject = Downloads::SUBJECT_PUBLIC_SHARED_FOLDER_DOWNLOADED;
588
-				$parameters[] = $remoteAddressHash;
589
-			}
590
-		}
591
-
592
-		$this->publishActivity($subject, $parameters, $share->getSharedBy(), $fileId, $userPath);
593
-
594
-		if ($share->getShareOwner() !== $share->getSharedBy()) {
595
-			$parameters[0] = $ownerPath;
596
-			$this->publishActivity($subject, $parameters, $share->getShareOwner(), $fileId, $ownerPath);
597
-		}
598
-	}
599
-
600
-	/**
601
-	 * publish activity
602
-	 *
603
-	 * @param string $subject
604
-	 * @param array $parameters
605
-	 * @param string $affectedUser
606
-	 * @param int $fileId
607
-	 * @param string $filePath
608
-	 */
609
-	protected function publishActivity($subject,
610
-										array $parameters,
611
-										$affectedUser,
612
-										$fileId,
613
-										$filePath) {
614
-		$event = $this->activityManager->generateEvent();
615
-		$event->setApp('files_sharing')
616
-			->setType('public_links')
617
-			->setSubject($subject, $parameters)
618
-			->setAffectedUser($affectedUser)
619
-			->setObject('files', $fileId, $filePath);
620
-		$this->activityManager->publish($event);
621
-	}
89
+    protected IConfig $config;
90
+    protected IUserManager $userManager;
91
+    protected ILogger $logger;
92
+    protected \OCP\Activity\IManager $activityManager;
93
+    protected IPreview $previewManager;
94
+    protected IRootFolder $rootFolder;
95
+    protected FederatedShareProvider $federatedShareProvider;
96
+    protected IAccountManager $accountManager;
97
+    protected IEventDispatcher $eventDispatcher;
98
+    protected IL10N $l10n;
99
+    protected Defaults $defaults;
100
+    protected ShareManager $shareManager;
101
+    protected ISecureRandom $secureRandom;
102
+    protected ?Share\IShare $share = null;
103
+    private IPublicShareTemplateFactory $publicShareTemplateFactory;
104
+
105
+    public function __construct(
106
+        string $appName,
107
+        IRequest $request,
108
+        IConfig $config,
109
+        IURLGenerator $urlGenerator,
110
+        IUserManager $userManager,
111
+        ILogger $logger,
112
+        \OCP\Activity\IManager $activityManager,
113
+        ShareManager $shareManager,
114
+        ISession $session,
115
+        IPreview $previewManager,
116
+        IRootFolder $rootFolder,
117
+        FederatedShareProvider $federatedShareProvider,
118
+        IAccountManager $accountManager,
119
+        IEventDispatcher $eventDispatcher,
120
+        IL10N $l10n,
121
+        ISecureRandom $secureRandom,
122
+        Defaults $defaults,
123
+        IPublicShareTemplateFactory $publicShareTemplateFactory
124
+    ) {
125
+        parent::__construct($appName, $request, $session, $urlGenerator);
126
+
127
+        $this->config = $config;
128
+        $this->userManager = $userManager;
129
+        $this->logger = $logger;
130
+        $this->activityManager = $activityManager;
131
+        $this->previewManager = $previewManager;
132
+        $this->rootFolder = $rootFolder;
133
+        $this->federatedShareProvider = $federatedShareProvider;
134
+        $this->accountManager = $accountManager;
135
+        $this->eventDispatcher = $eventDispatcher;
136
+        $this->l10n = $l10n;
137
+        $this->secureRandom = $secureRandom;
138
+        $this->defaults = $defaults;
139
+        $this->shareManager = $shareManager;
140
+        $this->publicShareTemplateFactory = $publicShareTemplateFactory;
141
+    }
142
+
143
+    public const SHARE_ACCESS = 'access';
144
+    public const SHARE_AUTH = 'auth';
145
+    public const SHARE_DOWNLOAD = 'download';
146
+
147
+    /**
148
+     * @PublicPage
149
+     * @NoCSRFRequired
150
+     *
151
+     * Show the authentication page
152
+     * The form has to submit to the authenticate method route
153
+     */
154
+    public function showAuthenticate(): TemplateResponse {
155
+        $templateParameters = ['share' => $this->share];
156
+
157
+        $this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH));
158
+
159
+        $response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
160
+        if ($this->share->getSendPasswordByTalk()) {
161
+            $csp = new ContentSecurityPolicy();
162
+            $csp->addAllowedConnectDomain('*');
163
+            $csp->addAllowedMediaDomain('blob:');
164
+            $response->setContentSecurityPolicy($csp);
165
+        }
166
+
167
+        return $response;
168
+    }
169
+
170
+    /**
171
+     * The template to show when authentication failed
172
+     */
173
+    protected function showAuthFailed(): TemplateResponse {
174
+        $templateParameters = ['share' => $this->share, 'wrongpw' => true];
175
+
176
+        $this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH));
177
+
178
+        $response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
179
+        if ($this->share->getSendPasswordByTalk()) {
180
+            $csp = new ContentSecurityPolicy();
181
+            $csp->addAllowedConnectDomain('*');
182
+            $csp->addAllowedMediaDomain('blob:');
183
+            $response->setContentSecurityPolicy($csp);
184
+        }
185
+
186
+        return $response;
187
+    }
188
+
189
+    /**
190
+     * The template to show after user identification
191
+     */
192
+    protected function showIdentificationResult(bool $success = false): TemplateResponse {
193
+        $templateParameters = ['share' => $this->share, 'identityOk' => $success];
194
+
195
+        $this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH));
196
+
197
+        $response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
198
+        if ($this->share->getSendPasswordByTalk()) {
199
+            $csp = new ContentSecurityPolicy();
200
+            $csp->addAllowedConnectDomain('*');
201
+            $csp->addAllowedMediaDomain('blob:');
202
+            $response->setContentSecurityPolicy($csp);
203
+        }
204
+
205
+        return $response;
206
+    }
207
+
208
+    /**
209
+     * Validate the identity token of a public share
210
+     *
211
+     * @param ?string $identityToken
212
+     * @return bool
213
+     */
214
+    protected function validateIdentity(?string $identityToken = null): bool {
215
+
216
+        if ($this->share->getShareType() !== IShare::TYPE_EMAIL) {
217
+            return false;
218
+        }
219
+
220
+        if ($identityToken === null || $this->share->getSharedWith() === null) {
221
+            return false;
222
+        }
223
+
224
+        return $identityToken === $this->share->getSharedWith();
225
+    }
226
+
227
+    /**
228
+     * Generates a password for the share, respecting any password policy defined
229
+     */
230
+    protected function generatePassword(): void {
231
+        $event = new \OCP\Security\Events\GenerateSecurePasswordEvent();
232
+        $this->eventDispatcher->dispatchTyped($event);
233
+        $password = $event->getPassword() ?? $this->secureRandom->generate(20);
234
+
235
+        $this->share->setPassword($password);
236
+        $this->shareManager->updateShare($this->share);
237
+    }
238
+
239
+    protected function verifyPassword(string $password): bool {
240
+        return $this->shareManager->checkPassword($this->share, $password);
241
+    }
242
+
243
+    protected function getPasswordHash(): string {
244
+        return $this->share->getPassword();
245
+    }
246
+
247
+    public function isValidToken(): bool {
248
+        try {
249
+            $this->share = $this->shareManager->getShareByToken($this->getToken());
250
+        } catch (ShareNotFound $e) {
251
+            return false;
252
+        }
253
+
254
+        return true;
255
+    }
256
+
257
+    protected function isPasswordProtected(): bool {
258
+        return $this->share->getPassword() !== null;
259
+    }
260
+
261
+    protected function authSucceeded() {
262
+        // For share this was always set so it is still used in other apps
263
+        $this->session->set('public_link_authenticated', (string)$this->share->getId());
264
+    }
265
+
266
+    protected function authFailed() {
267
+        $this->emitAccessShareHook($this->share, 403, 'Wrong password');
268
+        $this->emitShareAccessEvent($this->share, self::SHARE_AUTH, 403, 'Wrong password');
269
+    }
270
+
271
+    /**
272
+     * throws hooks when a share is attempted to be accessed
273
+     *
274
+     * @param \OCP\Share\IShare|string $share the Share instance if available,
275
+     * otherwise token
276
+     * @param int $errorCode
277
+     * @param string $errorMessage
278
+     *
279
+     * @throws \OCP\HintException
280
+     * @throws \OC\ServerNotAvailableException
281
+     *
282
+     * @deprecated use OCP\Files_Sharing\Event\ShareLinkAccessedEvent
283
+     */
284
+    protected function emitAccessShareHook($share, int $errorCode = 200, string $errorMessage = '') {
285
+        $itemType = $itemSource = $uidOwner = '';
286
+        $token = $share;
287
+        $exception = null;
288
+        if ($share instanceof \OCP\Share\IShare) {
289
+            try {
290
+                $token = $share->getToken();
291
+                $uidOwner = $share->getSharedBy();
292
+                $itemType = $share->getNodeType();
293
+                $itemSource = $share->getNodeId();
294
+            } catch (\Exception $e) {
295
+                // we log what we know and pass on the exception afterwards
296
+                $exception = $e;
297
+            }
298
+        }
299
+
300
+        \OC_Hook::emit(Share::class, 'share_link_access', [
301
+            'itemType' => $itemType,
302
+            'itemSource' => $itemSource,
303
+            'uidOwner' => $uidOwner,
304
+            'token' => $token,
305
+            'errorCode' => $errorCode,
306
+            'errorMessage' => $errorMessage
307
+        ]);
308
+
309
+        if (!is_null($exception)) {
310
+            throw $exception;
311
+        }
312
+    }
313
+
314
+    /**
315
+     * Emit a ShareLinkAccessedEvent event when a share is accessed, downloaded, auth...
316
+     */
317
+    protected function emitShareAccessEvent(IShare $share, string $step = '', int $errorCode = 200, string $errorMessage = ''): void {
318
+        if ($step !== self::SHARE_ACCESS &&
319
+            $step !== self::SHARE_AUTH &&
320
+            $step !== self::SHARE_DOWNLOAD) {
321
+            return;
322
+        }
323
+        $this->eventDispatcher->dispatchTyped(new ShareLinkAccessedEvent($share, $step, $errorCode, $errorMessage));
324
+    }
325
+
326
+    /**
327
+     * Validate the permissions of the share
328
+     *
329
+     * @param Share\IShare $share
330
+     * @return bool
331
+     */
332
+    private function validateShare(\OCP\Share\IShare $share) {
333
+        // If the owner is disabled no access to the link is granted
334
+        $owner = $this->userManager->get($share->getShareOwner());
335
+        if ($owner === null || !$owner->isEnabled()) {
336
+            return false;
337
+        }
338
+
339
+        // If the initiator of the share is disabled no access is granted
340
+        $initiator = $this->userManager->get($share->getSharedBy());
341
+        if ($initiator === null || !$initiator->isEnabled()) {
342
+            return false;
343
+        }
344
+
345
+        return $share->getNode()->isReadable() && $share->getNode()->isShareable();
346
+    }
347
+
348
+    /**
349
+     * @PublicPage
350
+     * @NoCSRFRequired
351
+     *
352
+     *
353
+     * @param string $path
354
+     * @return TemplateResponse
355
+     * @throws NotFoundException
356
+     * @throws \Exception
357
+     */
358
+    public function showShare($path = ''): TemplateResponse {
359
+        \OC_User::setIncognitoMode(true);
360
+
361
+        // Check whether share exists
362
+        try {
363
+            $share = $this->shareManager->getShareByToken($this->getToken());
364
+        } catch (ShareNotFound $e) {
365
+            // The share does not exists, we do not emit an ShareLinkAccessedEvent
366
+            $this->emitAccessShareHook($this->getToken(), 404, 'Share not found');
367
+            throw new NotFoundException();
368
+        }
369
+
370
+        if (!$this->validateShare($share)) {
371
+            throw new NotFoundException();
372
+        }
373
+
374
+        $shareNode = $share->getNode();
375
+
376
+        try {
377
+            $templateProvider = $this->publicShareTemplateFactory->getProvider($share);
378
+            $response = $templateProvider->renderPage($share, $this->getToken(), $path);
379
+        } catch (NotFoundException $e) {
380
+            $this->emitAccessShareHook($share, 404, 'Share not found');
381
+            $this->emitShareAccessEvent($share, ShareController::SHARE_ACCESS, 404, 'Share not found');
382
+            throw new NotFoundException();
383
+        }
384
+
385
+        // We can't get the path of a file share
386
+        try {
387
+            if ($shareNode instanceof \OCP\Files\File && $path !== '') {
388
+                $this->emitAccessShareHook($share, 404, 'Share not found');
389
+                $this->emitShareAccessEvent($share, self::SHARE_ACCESS, 404, 'Share not found');
390
+                throw new NotFoundException();
391
+            }
392
+        } catch (\Exception $e) {
393
+            $this->emitAccessShareHook($share, 404, 'Share not found');
394
+            $this->emitShareAccessEvent($share, self::SHARE_ACCESS, 404, 'Share not found');
395
+            throw $e;
396
+        }
397
+
398
+
399
+        $this->emitAccessShareHook($share);
400
+        $this->emitShareAccessEvent($share, self::SHARE_ACCESS);
401
+
402
+        return $response;
403
+    }
404
+
405
+    /**
406
+     * @PublicPage
407
+     * @NoCSRFRequired
408
+     * @NoSameSiteCookieRequired
409
+     *
410
+     * @param string $token
411
+     * @param string $files
412
+     * @param string $path
413
+     * @param string $downloadStartSecret
414
+     * @return void|\OCP\AppFramework\Http\Response
415
+     * @throws NotFoundException
416
+     */
417
+    public function downloadShare($token, $files = null, $path = '', $downloadStartSecret = '') {
418
+        \OC_User::setIncognitoMode(true);
419
+
420
+        $share = $this->shareManager->getShareByToken($token);
421
+
422
+        if (!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) {
423
+            return new \OCP\AppFramework\Http\DataResponse('Share has no read permission');
424
+        }
425
+
426
+        $files_list = null;
427
+        if (!is_null($files)) { // download selected files
428
+            $files_list = json_decode($files);
429
+            // in case we get only a single file
430
+            if ($files_list === null) {
431
+                $files_list = [$files];
432
+            }
433
+            // Just in case $files is a single int like '1234'
434
+            if (!is_array($files_list)) {
435
+                $files_list = [$files_list];
436
+            }
437
+        }
438
+
439
+        if (!$this->validateShare($share)) {
440
+            throw new NotFoundException();
441
+        }
442
+
443
+        $userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
444
+        $originalSharePath = $userFolder->getRelativePath($share->getNode()->getPath());
445
+
446
+
447
+        // Single file share
448
+        if ($share->getNode() instanceof \OCP\Files\File) {
449
+            // Single file download
450
+            $this->singleFileDownloaded($share, $share->getNode());
451
+        }
452
+        // Directory share
453
+        else {
454
+            /** @var \OCP\Files\Folder $node */
455
+            $node = $share->getNode();
456
+
457
+            // Try to get the path
458
+            if ($path !== '') {
459
+                try {
460
+                    $node = $node->get($path);
461
+                } catch (NotFoundException $e) {
462
+                    $this->emitAccessShareHook($share, 404, 'Share not found');
463
+                    $this->emitShareAccessEvent($share, self::SHARE_DOWNLOAD, 404, 'Share not found');
464
+                    return new NotFoundResponse();
465
+                }
466
+            }
467
+
468
+            $originalSharePath = $userFolder->getRelativePath($node->getPath());
469
+
470
+            if ($node instanceof \OCP\Files\File) {
471
+                // Single file download
472
+                $this->singleFileDownloaded($share, $share->getNode());
473
+            } else {
474
+                try {
475
+                    if (!empty($files_list)) {
476
+                        $this->fileListDownloaded($share, $files_list, $node);
477
+                    } else {
478
+                        // The folder is downloaded
479
+                        $this->singleFileDownloaded($share, $share->getNode());
480
+                    }
481
+                } catch (NotFoundException $e) {
482
+                    return new NotFoundResponse();
483
+                }
484
+            }
485
+        }
486
+
487
+        /* FIXME: We should do this all nicely in OCP */
488
+        OC_Util::tearDownFS();
489
+        OC_Util::setupFS($share->getShareOwner());
490
+
491
+        /**
492
+         * this sets a cookie to be able to recognize the start of the download
493
+         * the content must not be longer than 32 characters and must only contain
494
+         * alphanumeric characters
495
+         */
496
+        if (!empty($downloadStartSecret)
497
+            && !isset($downloadStartSecret[32])
498
+            && preg_match('!^[a-zA-Z0-9]+$!', $downloadStartSecret) === 1) {
499
+
500
+            // FIXME: set on the response once we use an actual app framework response
501
+            setcookie('ocDownloadStarted', $downloadStartSecret, time() + 20, '/');
502
+        }
503
+
504
+        $this->emitAccessShareHook($share);
505
+        $this->emitShareAccessEvent($share, self::SHARE_DOWNLOAD);
506
+
507
+        $server_params = [ 'head' => $this->request->getMethod() === 'HEAD' ];
508
+
509
+        /**
510
+         * Http range requests support
511
+         */
512
+        if (isset($_SERVER['HTTP_RANGE'])) {
513
+            $server_params['range'] = $this->request->getHeader('Range');
514
+        }
515
+
516
+        // download selected files
517
+        if (!is_null($files) && $files !== '') {
518
+            // FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well
519
+            // after dispatching the request which results in a "Cannot modify header information" notice.
520
+            OC_Files::get($originalSharePath, $files_list, $server_params);
521
+            exit();
522
+        } else {
523
+            // FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well
524
+            // after dispatching the request which results in a "Cannot modify header information" notice.
525
+            OC_Files::get(dirname($originalSharePath), basename($originalSharePath), $server_params);
526
+            exit();
527
+        }
528
+    }
529
+
530
+    /**
531
+     * create activity for every downloaded file
532
+     *
533
+     * @param Share\IShare $share
534
+     * @param array $files_list
535
+     * @param \OCP\Files\Folder $node
536
+     * @throws NotFoundException when trying to download a folder or multiple files of a "hide download" share
537
+     */
538
+    protected function fileListDownloaded(Share\IShare $share, array $files_list, \OCP\Files\Folder $node) {
539
+        if ($share->getHideDownload() && count($files_list) > 1) {
540
+            throw new NotFoundException('Downloading more than 1 file');
541
+        }
542
+
543
+        foreach ($files_list as $file) {
544
+            $subNode = $node->get($file);
545
+            $this->singleFileDownloaded($share, $subNode);
546
+        }
547
+    }
548
+
549
+    /**
550
+     * create activity if a single file was downloaded from a link share
551
+     *
552
+     * @param Share\IShare $share
553
+     * @throws NotFoundException when trying to download a folder of a "hide download" share
554
+     */
555
+    protected function singleFileDownloaded(Share\IShare $share, \OCP\Files\Node $node) {
556
+        if ($share->getHideDownload() && $node instanceof Folder) {
557
+            throw new NotFoundException('Downloading a folder');
558
+        }
559
+
560
+        $fileId = $node->getId();
561
+
562
+        $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
563
+        $userNodeList = $userFolder->getById($fileId);
564
+        $userNode = $userNodeList[0];
565
+        $ownerFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
566
+        $userPath = $userFolder->getRelativePath($userNode->getPath());
567
+        $ownerPath = $ownerFolder->getRelativePath($node->getPath());
568
+        $remoteAddress = $this->request->getRemoteAddress();
569
+        $dateTime = new \DateTime();
570
+        $dateTime = $dateTime->format('Y-m-d H');
571
+        $remoteAddressHash = md5($dateTime . '-' . $remoteAddress);
572
+
573
+        $parameters = [$userPath];
574
+
575
+        if ($share->getShareType() === IShare::TYPE_EMAIL) {
576
+            if ($node instanceof \OCP\Files\File) {
577
+                $subject = Downloads::SUBJECT_SHARED_FILE_BY_EMAIL_DOWNLOADED;
578
+            } else {
579
+                $subject = Downloads::SUBJECT_SHARED_FOLDER_BY_EMAIL_DOWNLOADED;
580
+            }
581
+            $parameters[] = $share->getSharedWith();
582
+        } else {
583
+            if ($node instanceof \OCP\Files\File) {
584
+                $subject = Downloads::SUBJECT_PUBLIC_SHARED_FILE_DOWNLOADED;
585
+                $parameters[] = $remoteAddressHash;
586
+            } else {
587
+                $subject = Downloads::SUBJECT_PUBLIC_SHARED_FOLDER_DOWNLOADED;
588
+                $parameters[] = $remoteAddressHash;
589
+            }
590
+        }
591
+
592
+        $this->publishActivity($subject, $parameters, $share->getSharedBy(), $fileId, $userPath);
593
+
594
+        if ($share->getShareOwner() !== $share->getSharedBy()) {
595
+            $parameters[0] = $ownerPath;
596
+            $this->publishActivity($subject, $parameters, $share->getShareOwner(), $fileId, $ownerPath);
597
+        }
598
+    }
599
+
600
+    /**
601
+     * publish activity
602
+     *
603
+     * @param string $subject
604
+     * @param array $parameters
605
+     * @param string $affectedUser
606
+     * @param int $fileId
607
+     * @param string $filePath
608
+     */
609
+    protected function publishActivity($subject,
610
+                                        array $parameters,
611
+                                        $affectedUser,
612
+                                        $fileId,
613
+                                        $filePath) {
614
+        $event = $this->activityManager->generateEvent();
615
+        $event->setApp('files_sharing')
616
+            ->setType('public_links')
617
+            ->setSubject($subject, $parameters)
618
+            ->setAffectedUser($affectedUser)
619
+            ->setObject('files', $fileId, $filePath);
620
+        $this->activityManager->publish($event);
621
+    }
622 622
 }
Please login to merge, or discard this patch.
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -260,7 +260,7 @@  discard block
 block discarded – undo
260 260
 
261 261
 	protected function authSucceeded() {
262 262
 		// For share this was always set so it is still used in other apps
263
-		$this->session->set('public_link_authenticated', (string)$this->share->getId());
263
+		$this->session->set('public_link_authenticated', (string) $this->share->getId());
264 264
 	}
265 265
 
266 266
 	protected function authFailed() {
@@ -504,7 +504,7 @@  discard block
 block discarded – undo
504 504
 		$this->emitAccessShareHook($share);
505 505
 		$this->emitShareAccessEvent($share, self::SHARE_DOWNLOAD);
506 506
 
507
-		$server_params = [ 'head' => $this->request->getMethod() === 'HEAD' ];
507
+		$server_params = ['head' => $this->request->getMethod() === 'HEAD'];
508 508
 
509 509
 		/**
510 510
 		 * Http range requests support
@@ -568,7 +568,7 @@  discard block
 block discarded – undo
568 568
 		$remoteAddress = $this->request->getRemoteAddress();
569 569
 		$dateTime = new \DateTime();
570 570
 		$dateTime = $dateTime->format('Y-m-d H');
571
-		$remoteAddressHash = md5($dateTime . '-' . $remoteAddress);
571
+		$remoteAddressHash = md5($dateTime.'-'.$remoteAddress);
572 572
 
573 573
 		$parameters = [$userPath];
574 574
 
Please login to merge, or discard this patch.