-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
feature(files): add support for viewing public links in widget #52478
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |||||
|
|
||||||
| use OC\User\NoUserException; | ||||||
| use OCP\Collaboration\Reference\ADiscoverableReferenceProvider; | ||||||
| use OCP\Collaboration\Reference\IPublicReferenceProvider; | ||||||
| use OCP\Collaboration\Reference\IReference; | ||||||
| use OCP\Collaboration\Reference\Reference; | ||||||
| use OCP\Files\IMimeTypeDetector; | ||||||
|
|
@@ -22,8 +23,13 @@ | |||||
| use OCP\IURLGenerator; | ||||||
| use OCP\IUserSession; | ||||||
| use OCP\L10N\IFactory; | ||||||
| use OCP\Share\Exceptions\ShareNotFound; | ||||||
| use OCP\Share\IManager as ShareManager; | ||||||
|
|
||||||
| class FileReferenceProvider extends ADiscoverableReferenceProvider implements IPublicReferenceProvider { | ||||||
| private const PREVIEW_WIDTH = 1600; | ||||||
| private const PREVIEW_HEIGHT = 630; | ||||||
|
|
||||||
| class FileReferenceProvider extends ADiscoverableReferenceProvider { | ||||||
| private ?string $userId; | ||||||
| private IL10N $l10n; | ||||||
|
|
||||||
|
|
@@ -34,13 +40,14 @@ public function __construct( | |||||
| private IMimeTypeDetector $mimeTypeDetector, | ||||||
| private IPreview $previewManager, | ||||||
| IFactory $l10n, | ||||||
| private ShareManager $shareManager, | ||||||
| ) { | ||||||
| $this->userId = $userSession->getUser()?->getUID(); | ||||||
| $this->l10n = $l10n->get('files'); | ||||||
| } | ||||||
|
|
||||||
| public function matchReference(string $referenceText): bool { | ||||||
| return $this->getFilesAppLinkId($referenceText) !== null; | ||||||
| return $this->getFilesAppLinkId($referenceText) !== null || $this->getFilesAppPublicLinkToken($referenceText) !== null; | ||||||
| } | ||||||
|
|
||||||
| private function getFilesAppLinkId(string $referenceText): ?int { | ||||||
|
|
@@ -74,34 +81,105 @@ private function getFilesAppLinkId(string $referenceText): ?int { | |||||
| return $fileId !== null ? (int)$fileId : null; | ||||||
| } | ||||||
|
|
||||||
| private function getFilesAppPublicLinkToken(string $referenceText): ?string { | ||||||
| $prefixes = ['/index.php/s/', '/s/']; | ||||||
|
|
||||||
| $token = null; | ||||||
|
|
||||||
| foreach ($prefixes as $prefix) { | ||||||
| $fullPrefix = $this->urlGenerator->getAbsoluteURL($prefix); | ||||||
| if (mb_strpos($referenceText, $fullPrefix) === 0) { | ||||||
| $token = substr($referenceText, mb_strlen($fullPrefix)); | ||||||
| if (($slashPos = strpos($token, '/')) !== false) { | ||||||
| $token = substr($token, 0, $slashPos); | ||||||
| } | ||||||
| break; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| return $token; | ||||||
| } | ||||||
|
|
||||||
| public function resolveReference(string $referenceText): ?IReference { | ||||||
| if ($this->matchReference($referenceText)) { | ||||||
| $reference = new Reference($referenceText); | ||||||
| try { | ||||||
| $this->fetchReference($reference); | ||||||
| } catch (NotFoundException $e) { | ||||||
| $reference->setRichObject('file', null); | ||||||
| $reference->setAccessible(false); | ||||||
| $reference = new Reference($referenceText); | ||||||
| try { | ||||||
| $fileId = $this->getFilesAppLinkId($referenceText); | ||||||
|
|
||||||
| if ($fileId !== null) { | ||||||
| $this->fetchReference($reference, $fileId); | ||||||
| } else { | ||||||
| $fileToken = $this->getFilesAppPublicLinkToken($referenceText); | ||||||
| if ($fileToken !== null) { | ||||||
| $this->fetchReferenceForPublicFile($reference, $referenceText, $fileToken); | ||||||
| } else { | ||||||
| throw new NotFoundException(); | ||||||
| } | ||||||
| } | ||||||
| return $reference; | ||||||
| } catch (NotFoundException $e) { | ||||||
| $reference->setRichObject('file', null); | ||||||
| $reference->setAccessible(false); | ||||||
| } | ||||||
| return $reference; | ||||||
| } | ||||||
|
|
||||||
| return null; | ||||||
| public function resolveReferencePublic(string $referenceText, string $sharingToken): ?IReference { | ||||||
| $reference = new Reference($referenceText); | ||||||
| try { | ||||||
| $fileToken = $this->getFilesAppPublicLinkToken($referenceText); | ||||||
| if ($fileToken !== null) { | ||||||
| $this->fetchReferenceForPublicFile($reference, $referenceText, $fileToken); | ||||||
| } else { | ||||||
| throw new NotFoundException(); | ||||||
| } | ||||||
| } catch (NotFoundException $e) { | ||||||
| $reference->setRichObject('file', null); | ||||||
| $reference->setAccessible(false); | ||||||
| } | ||||||
| return $reference; | ||||||
| } | ||||||
|
|
||||||
| private function fetchReferenceForPublicFile(Reference $reference, string $referenceText, string $fileToken): void { | ||||||
| try { | ||||||
| $share = $this->shareManager->getShareByToken($fileToken); | ||||||
| $node = $share->getNode(); | ||||||
|
|
||||||
| $reference->setTitle($node->getName()); | ||||||
| $reference->setDescription($node->getMimetype()); | ||||||
| $reference->setUrl($referenceText); | ||||||
| if ($this->previewManager->isMimeSupported($node->getMimeType())) { | ||||||
| $reference->setImageUrl($this->urlGenerator->linkToRouteAbsolute( | ||||||
| 'files_sharing.PublicPreview.getPreview', | ||||||
| ['x' => self::PREVIEW_WIDTH, 'y' => self::PREVIEW_HEIGHT, 'token' => $fileToken])); | ||||||
| } else { | ||||||
| $fileTypeIconUrl = $this->mimeTypeDetector->mimeTypeIcon($node->getMimeType()); | ||||||
| $reference->setImageUrl($fileTypeIconUrl); | ||||||
| } | ||||||
|
|
||||||
| $reference->setRichObject('file', [ | ||||||
| 'id' => $fileToken, // security, public link should not show file id | ||||||
| 'name' => $node->getName(), | ||||||
| 'size' => $node->getSize(), | ||||||
| 'path' => $fileToken, | ||||||
| 'link' => $reference->getUrl(), | ||||||
| 'mimetype' => $node->getMimetype(), | ||||||
| 'mtime' => $node->getMTime(), | ||||||
| 'preview-available' => $this->previewManager->isAvailable($node), | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| 'is-public-link' => true, | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| ]); | ||||||
| } catch (ShareNotFound|NotFoundException|InvalidPathException|NotPermittedException $e) { | ||||||
| $reference->setRichObject('file', null); | ||||||
| $reference->setAccessible(false); | ||||||
|
Comment on lines
+170
to
+171
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this needs to log a brute-force attempt or needs some kind of other protection to not allow users to iterate public share links. I'm not sure this is solvable in a sensible and working way at the moment, so my objection still stands |
||||||
| } | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * @throws NotFoundException | ||||||
| */ | ||||||
| private function fetchReference(Reference $reference): void { | ||||||
| private function fetchReference(Reference $reference, int $fileId): void { | ||||||
| if ($this->userId === null) { | ||||||
| throw new NotFoundException(); | ||||||
| } | ||||||
|
|
||||||
| $fileId = $this->getFilesAppLinkId($reference->getId()); | ||||||
| if ($fileId === null) { | ||||||
| throw new NotFoundException(); | ||||||
| } | ||||||
|
|
||||||
| try { | ||||||
| $userFolder = $this->rootFolder->getUserFolder($this->userId); | ||||||
| $file = $userFolder->getFirstNodeById($fileId); | ||||||
|
|
@@ -114,7 +192,10 @@ private function fetchReference(Reference $reference): void { | |||||
| $reference->setDescription($file->getMimetype()); | ||||||
| $reference->setUrl($this->urlGenerator->getAbsoluteURL('/index.php/f/' . $fileId)); | ||||||
| if ($this->previewManager->isMimeSupported($file->getMimeType())) { | ||||||
| $reference->setImageUrl($this->urlGenerator->linkToRouteAbsolute('core.Preview.getPreviewByFileId', ['x' => 1600, 'y' => 630, 'fileId' => $fileId])); | ||||||
| $reference->setImageUrl($this->urlGenerator->linkToRouteAbsolute( | ||||||
| 'core.Preview.getPreviewByFileId', | ||||||
| ['x' => self::PREVIEW_WIDTH, 'y' => self::PREVIEW_HEIGHT, 'fileId' => $fileId]) | ||||||
| ); | ||||||
| } else { | ||||||
| $fileTypeIconUrl = $this->mimeTypeDetector->mimeTypeIcon($file->getMimeType()); | ||||||
| $reference->setImageUrl($fileTypeIconUrl); | ||||||
|
|
@@ -136,13 +217,26 @@ private function fetchReference(Reference $reference): void { | |||||
| } | ||||||
|
|
||||||
| public function getCachePrefix(string $referenceId): string { | ||||||
| return (string)$this->getFilesAppLinkId($referenceId); | ||||||
| $fileId = $this->getFilesAppLinkId($referenceId); | ||||||
| if ($fileId !== null) { | ||||||
| return (string)$fileId; | ||||||
| } else { | ||||||
| $fileToken = $this->getFilesAppPublicLinkToken($referenceId); | ||||||
| if ($fileToken !== null) { | ||||||
| return $fileToken; | ||||||
| } | ||||||
| } | ||||||
| return ''; | ||||||
| } | ||||||
|
|
||||||
| public function getCacheKey(string $referenceId): ?string { | ||||||
| return $this->userId ?? ''; | ||||||
| } | ||||||
|
|
||||||
| public function getCacheKeyPublic(string $referenceId, string $sharingToken): ?string { | ||||||
| return $sharingToken; | ||||||
| } | ||||||
|
|
||||||
| public function getId(): string { | ||||||
| return 'files'; | ||||||
| } | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.