$this->share]; $this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH)); $response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest'); if ($this->share->getSendPasswordByTalk()) { $csp = new ContentSecurityPolicy(); $csp->addAllowedConnectDomain('*'); $csp->addAllowedMediaDomain('blob:'); $response->setContentSecurityPolicy($csp); } return $response; } /** * The template to show when authentication failed */ protected function showAuthFailed(): TemplateResponse { $templateParameters = ['share' => $this->share, 'wrongpw' => true]; $this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH)); $response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest'); if ($this->share->getSendPasswordByTalk()) { $csp = new ContentSecurityPolicy(); $csp->addAllowedConnectDomain('*'); $csp->addAllowedMediaDomain('blob:'); $response->setContentSecurityPolicy($csp); } return $response; } /** * The template to show after user identification */ protected function showIdentificationResult(bool $success = false): TemplateResponse { $templateParameters = ['share' => $this->share, 'identityOk' => $success]; $this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH)); $response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest'); if ($this->share->getSendPasswordByTalk()) { $csp = new ContentSecurityPolicy(); $csp->addAllowedConnectDomain('*'); $csp->addAllowedMediaDomain('blob:'); $response->setContentSecurityPolicy($csp); } return $response; } /** * Validate the identity token of a public share * * @param ?string $identityToken * @return bool */ protected function validateIdentity(?string $identityToken = null): bool { if ($this->share->getShareType() !== IShare::TYPE_EMAIL) { return false; } if ($identityToken === null || $this->share->getSharedWith() === null) { return false; } return $identityToken === $this->share->getSharedWith(); } /** * Generates a password for the share, respecting any password policy defined */ protected function generatePassword(): void { $event = new GenerateSecurePasswordEvent(PasswordContext::SHARING); $this->eventDispatcher->dispatchTyped($event); $password = $event->getPassword() ?? $this->secureRandom->generate(20); $this->share->setPassword($password); $this->shareManager->updateShare($this->share); } protected function verifyPassword(string $password): bool { return $this->shareManager->checkPassword($this->share, $password); } protected function getPasswordHash(): ?string { return $this->share->getPassword(); } public function isValidToken(): bool { try { $this->share = $this->shareManager->getShareByToken($this->getToken()); } catch (ShareNotFound $e) { return false; } return true; } protected function isPasswordProtected(): bool { return $this->share->getPassword() !== null; } protected function authSucceeded() { if ($this->share === null) { throw new NotFoundException(); } // For share this was always set so it is still used in other apps $this->session->set(PublicAuth::DAV_AUTHENTICATED, $this->share->getId()); } protected function authFailed() { $this->emitAccessShareHook($this->share, 403, 'Wrong password'); $this->emitShareAccessEvent($this->share, self::SHARE_AUTH, 403, 'Wrong password'); } /** * throws hooks when a share is attempted to be accessed * * @param IShare|string $share the Share instance if available, * otherwise token * @param int $errorCode * @param string $errorMessage * * @throws HintException * @throws \OC\ServerNotAvailableException * * @deprecated use OCP\Files_Sharing\Event\ShareLinkAccessedEvent */ protected function emitAccessShareHook($share, int $errorCode = 200, string $errorMessage = '') { $itemType = $itemSource = $uidOwner = ''; $token = $share; $exception = null; if ($share instanceof IShare) { try { $token = $share->getToken(); $uidOwner = $share->getSharedBy(); $itemType = $share->getNodeType(); $itemSource = $share->getNodeId(); } catch (\Exception $e) { // we log what we know and pass on the exception afterwards $exception = $e; } } \OC_Hook::emit(Share::class, 'share_link_access', [ 'itemType' => $itemType, 'itemSource' => $itemSource, 'uidOwner' => $uidOwner, 'token' => $token, 'errorCode' => $errorCode, 'errorMessage' => $errorMessage ]); if (!is_null($exception)) { throw $exception; } } /** * Emit a ShareLinkAccessedEvent event when a share is accessed, downloaded, auth... */ protected function emitShareAccessEvent(IShare $share, string $step = '', int $errorCode = 200, string $errorMessage = ''): void { if ($step !== self::SHARE_ACCESS && $step !== self::SHARE_AUTH && $step !== self::SHARE_DOWNLOAD) { return; } $this->eventDispatcher->dispatchTyped(new ShareLinkAccessedEvent($share, $step, $errorCode, $errorMessage)); } /** * Validate the permissions of the share * * @param Share\IShare $share * @return bool */ private function validateShare(IShare $share) { // If the owner is disabled no access to the link is granted $owner = $this->userManager->get($share->getShareOwner()); if ($owner === null || !$owner->isEnabled()) { return false; } // If the initiator of the share is disabled no access is granted $initiator = $this->userManager->get($share->getSharedBy()); if ($initiator === null || !$initiator->isEnabled()) { return false; } return $share->getNode()->isReadable() && $share->getNode()->isShareable(); } /** * @param string $path * @return TemplateResponse * @throws NotFoundException * @throws \Exception */ #[PublicPage] #[NoCSRFRequired] public function showShare($path = ''): TemplateResponse { \OC_User::setIncognitoMode(true); // Check whether share exists try { $share = $this->shareManager->getShareByToken($this->getToken()); } catch (ShareNotFound $e) { // The share does not exists, we do not emit an ShareLinkAccessedEvent $this->emitAccessShareHook($this->getToken(), 404, 'Share not found'); throw new NotFoundException($this->l10n->t('This share does not exist or is no longer available')); } if (!$this->validateShare($share)) { throw new NotFoundException($this->l10n->t('This share does not exist or is no longer available')); } $shareNode = $share->getNode(); try { $templateProvider = $this->publicShareTemplateFactory->getProvider($share); $response = $templateProvider->renderPage($share, $this->getToken(), $path); } catch (NotFoundException $e) { $this->emitAccessShareHook($share, 404, 'Share not found'); $this->emitShareAccessEvent($share, ShareController::SHARE_ACCESS, 404, 'Share not found'); throw new NotFoundException($this->l10n->t('This share does not exist or is no longer available')); } // We can't get the path of a file share try { if ($shareNode instanceof File && $path !== '') { $this->emitAccessShareHook($share, 404, 'Share not found'); $this->emitShareAccessEvent($share, self::SHARE_ACCESS, 404, 'Share not found'); throw new NotFoundException($this->l10n->t('This share does not exist or is no longer available')); } } catch (\Exception $e) { $this->emitAccessShareHook($share, 404, 'Share not found'); $this->emitShareAccessEvent($share, self::SHARE_ACCESS, 404, 'Share not found'); throw $e; } $this->emitAccessShareHook($share); $this->emitShareAccessEvent($share, self::SHARE_ACCESS); return $response; } /** * @NoSameSiteCookieRequired * * @param string $token * @param string|null $files * @param string $path * @return void|Response * @throws NotFoundException * @deprecated 31.0.0 Users are encouraged to use the DAV endpoint */ #[PublicPage] #[NoCSRFRequired] public function downloadShare($token, $files = null, $path = '') { \OC_User::setIncognitoMode(true); $share = $this->shareManager->getShareByToken($token); if (!($share->getPermissions() & Constants::PERMISSION_READ)) { return new DataResponse('Share has no read permission'); } $attributes = $share->getAttributes(); if ($attributes?->getAttribute('permissions', 'download') === false) { return new DataResponse('Share has no download permission'); } if (!$this->validateShare($share)) { throw new NotFoundException(); } $node = $share->getNode(); if ($node instanceof Folder) { // Directory share // Try to get the path if ($path !== '') { try { $node = $node->get($path); } catch (NotFoundException $e) { $this->emitAccessShareHook($share, 404, 'Share not found'); $this->emitShareAccessEvent($share, self::SHARE_DOWNLOAD, 404, 'Share not found'); return new NotFoundResponse(); } } if ($node instanceof Folder) { if ($files === null || $files === '') { if ($share->getHideDownload()) { throw new NotFoundException('Downloading a folder'); } } } } $this->emitAccessShareHook($share); $this->emitShareAccessEvent($share, self::SHARE_DOWNLOAD); $davUrl = '/public.php/dav/files/' . $token . '/?accept=zip'; if ($files !== null) { $davUrl .= '&files=' . $files; } return new RedirectResponse($this->urlGenerator->getAbsoluteURL($davUrl)); } }