diff options
author | Joas Schilling <213943+nickvergessen@users.noreply.github.com> | 2019-09-27 09:54:34 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-09-27 09:54:34 +0200 |
commit | 918d6f9773870984df2d305039eaf8bd1baee2f7 (patch) | |
tree | 0e2cacb3411f56c8751b2547f8e55eebf418a0bd /lib | |
parent | b444c91e99d63d43948b3eb5e17558be404c8b5e (diff) | |
parent | 891a73f05dacdb7fadebf11e9f3d1d38f6817598 (diff) |
Merge pull request #2107 from nextcloud/add-support-for-talk-sidebar-in-public-share-pages
Add support for Talk Sidebar in public share pages
Diffstat (limited to 'lib')
-rw-r--r-- | lib/AppInfo/Application.php | 2 | ||||
-rw-r--r-- | lib/Chat/AutoComplete/SearchPlugin.php | 9 | ||||
-rw-r--r-- | lib/Chat/SystemMessage/Listener.php | 4 | ||||
-rw-r--r-- | lib/Controller/FilesIntegrationController.php | 23 | ||||
-rw-r--r-- | lib/Controller/PublicShareController.php | 148 | ||||
-rw-r--r-- | lib/Files/Listener.php | 57 | ||||
-rw-r--r-- | lib/Files/Util.php | 63 | ||||
-rw-r--r-- | lib/PublicShare/TemplateLoader.php | 57 | ||||
-rw-r--r-- | lib/TalkSession.php | 12 |
9 files changed, 346 insertions, 29 deletions
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 4b46701bb..e1837f65e 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -43,6 +43,7 @@ use OCA\Talk\Notification\Listener as NotificationListener; use OCA\Talk\Notification\Notifier; use OCA\Talk\PublicShareAuth\Listener as PublicShareAuthListener; use OCA\Talk\PublicShareAuth\TemplateLoader as PublicShareAuthTemplateLoader; +use OCA\Talk\PublicShare\TemplateLoader as PublicShareTemplateLoader; use OCA\Talk\Room; use OCA\Talk\Settings\Personal; use OCA\Talk\Share\RoomShareProvider; @@ -110,6 +111,7 @@ class Application extends App { ParserListener::register($dispatcher); PublicShareAuthListener::register($dispatcher); PublicShareAuthTemplateLoader::register($dispatcher); + PublicShareTemplateLoader::register($dispatcher); FilesListener::register($dispatcher); FilesTemplateLoader::register($dispatcher); RestrictStartingCallsListener::register($dispatcher); diff --git a/lib/Chat/AutoComplete/SearchPlugin.php b/lib/Chat/AutoComplete/SearchPlugin.php index eae62560c..64d74172a 100644 --- a/lib/Chat/AutoComplete/SearchPlugin.php +++ b/lib/Chat/AutoComplete/SearchPlugin.php @@ -84,8 +84,6 @@ class SearchPlugin implements ISearchPlugin { if (!empty($usersWithFileAccess)) { $this->searchUsers($search, $usersWithFileAccess, $searchResult); } - - return false; } $userIds = $guestSessionHashes = []; @@ -113,6 +111,8 @@ class SearchPlugin implements ISearchPlugin { protected function searchUsers(string $search, array $userIds, ISearchResult $searchResult): void { $search = strtolower($search); + $type = new SearchResultType('users'); + $matches = $exactMatches = []; foreach ($userIds as $userId) { if ($this->userId !== '' && $this->userId === $userId) { @@ -120,6 +120,10 @@ class SearchPlugin implements ISearchPlugin { continue; } + if ($searchResult->hasResult($type, $userId)) { + continue; + } + if ($search === '') { $matches[] = $this->createResult('user', $userId, ''); continue; @@ -151,7 +155,6 @@ class SearchPlugin implements ISearchPlugin { } } - $type = new SearchResultType('users'); $searchResult->addResultSet($type, $matches, $exactMatches); } diff --git a/lib/Chat/SystemMessage/Listener.php b/lib/Chat/SystemMessage/Listener.php index 2ef7eb197..3933b6d3a 100644 --- a/lib/Chat/SystemMessage/Listener.php +++ b/lib/Chat/SystemMessage/Listener.php @@ -195,7 +195,9 @@ class Listener { /** @var self $listener */ $listener = \OC::$server->query(self::class); foreach ($participants as $participant) { - if ($room->getObjectType() === 'file' || $userId !== $participant['userId']) { + $userJoinedFileRoom = $room->getObjectType() === 'file' && + (!array_key_exists('participantType', $participant) || $participant['participantType'] !== Participant::USER_SELF_JOINED); + if ($userJoinedFileRoom || $userId !== $participant['userId']) { $listener->sendSystemMessage($room, 'user_added', ['user' => $participant['userId']]); } } diff --git a/lib/Controller/FilesIntegrationController.php b/lib/Controller/FilesIntegrationController.php index 4ab4e44fb..cd5d60b73 100644 --- a/lib/Controller/FilesIntegrationController.php +++ b/lib/Controller/FilesIntegrationController.php @@ -66,20 +66,25 @@ class FilesIntegrationController extends OCSController { * * Returns the token of the room associated to the given file id. * + * This is the counterpart of PublicShareController::getRoom() for file ids + * instead of share tokens, although both return the same room token if the + * given file id and share token refer to the same file. + * * If there is no room associated to the given file id a new room is * created; the new room is a public room associated with a "file" object * with the given file id. Unlike normal rooms in which the owner is the * user that created the room these are special rooms without owner - * (although self joined users become persistent participants automatically - * when they join until they explicitly leave or no longer have access to - * the file). + * (although self joined users with direct access to the file become + * persistent participants automatically when they join until they + * explicitly leave or no longer have access to the file). * * In any case, to create or even get the token of the room, the file must - * be shared and the user must have direct access to that file; an error - * is returned otherwise. A user has direct access to a file if she has - * access to it (or to an ancestor) through a user, group, circle or room - * share (but not through a link share, for example), or if she is the owner - * of such a file. + * be shared and the user must be the owner of a public share of the file + * (like a link share, for example) or have direct access to that file; an + * error is returned otherwise. A user has direct access to a file if she + * has access to it (or to an ancestor) through a user, group, circle or + * room share (but not through a link share, for example), or if she is the + * owner of such a file. * * @param string $fileId * @return DataResponse the status code is "200 OK" if a room is returned, @@ -87,7 +92,7 @@ class FilesIntegrationController extends OCSController { * @throws OCSNotFoundException */ public function getRoom(string $fileId): DataResponse { - $share = $this->util->getAnyDirectShareOfFileAccessibleByUser($fileId, $this->currentUser); + $share = $this->util->getAnyPublicShareOfFileOwnedByUserOrAnyDirectShareOfFileAccessibleByUser($fileId, $this->currentUser); $groupFolder = null; if (!$share) { $groupFolder = $this->util->getGroupFolderNode($fileId, $this->currentUser); diff --git a/lib/Controller/PublicShareController.php b/lib/Controller/PublicShareController.php new file mode 100644 index 000000000..a555ae86d --- /dev/null +++ b/lib/Controller/PublicShareController.php @@ -0,0 +1,148 @@ +<?php +declare(strict_types=1); + +/** + * + * @copyright Copyright (c) 2019, Daniel Calviño Sánchez (danxuliu@gmail.com) + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Talk\Controller; + +use OCA\Talk\Exceptions\RoomNotFoundException; +use OCA\Talk\Manager; +use OCA\Talk\TalkSession; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\OCSController; +use OCP\Files\FileInfo; +use OCP\Files\NotFoundException; +use OCP\IRequest; +use OCP\IUser; +use OCP\IUserManager; +use OCP\ISession; +use OCP\Share\Exceptions\ShareNotFound; +use OCP\Share\IManager as ShareManager; +use OCP\Share\IShare; + +class PublicShareController extends OCSController { + + /** @var string|null */ + private $userId; + /** @var IUserManager */ + private $userManager; + /** @var ShareManager */ + private $shareManager; + /** @var ISession */ + private $session; + /** @var TalkSession */ + private $talkSession; + /** @var Manager */ + private $manager; + + public function __construct( + $appName, + ?string $UserId, + IRequest $request, + IUserManager $userManager, + ShareManager $shareManager, + ISession $session, + TalkSession $talkSession, + Manager $manager + ) { + parent::__construct($appName, $request); + $this->userId = $UserId; + $this->userManager = $userManager; + $this->shareManager = $shareManager; + $this->session = $session; + $this->talkSession = $talkSession; + $this->manager = $manager; + } + + /** + * @PublicPage + * @UseSession + * + * Returns the token of the room associated to the file id of the given + * share token. + * + * This is the counterpart of FilesController::getRoom() for share tokens + * instead of file ids, although both return the same room token if the + * given file id and share token refer to the same file. + * + * If there is no room associated to the file id of the given share token a + * new room is created; the new room is a public room associated with a + * "file" object with the file id of the given share token. Unlike normal + * rooms in which the owner is the user that created the room these are + * special rooms without owner (although self joined users with direct + * access to the file become persistent participants automatically when they + * join until they explicitly leave or no longer have access to the file). + * + * In any case, to create or even get the token of the room, the file must + * be publicly shared (like a link share, for example); an error is returned + * otherwise. + * + * Besides the token of the room this also returns the current user ID and + * display name, if any; this is needed by the Talk sidebar to know the + * actual current user, as the public share page uses the incognito mode and + * thus logged in users as seen as guests. + * + * @param string $shareToken + * @return DataResponse the status code is "200 OK" if a room is returned, + * or "404 Not found" if the given share token was invalid. + */ + public function getRoom(string $shareToken) { + try { + $share = $this->shareManager->getShareByToken($shareToken); + if ($share->getPassword() !== null) { + $shareId = $this->session->get('public_link_authenticated'); + if ($share->getId() !== $shareId) { + throw new ShareNotFound(); + } + } + } catch (ShareNotFound $e) { + return new DataResponse([], Http::STATUS_NOT_FOUND); + } + + if ($share->getNodeType() !== FileInfo::TYPE_FILE) { + return new DataResponse([], Http::STATUS_NOT_FOUND); + } + + $fileId = (string)$share->getNodeId(); + + try { + $room = $this->manager->getRoomByObject('file', $fileId); + } catch (RoomNotFoundException $e) { + $name = $share->getNode()->getName(); + $room = $this->manager->createPublicRoom($name, 'file', $fileId); + } + + $this->talkSession->setFileShareTokenForRoom($room->getToken(), $shareToken); + + $currentUser = $this->userManager->get($this->userId); + $currentUserId = $currentUser instanceof IUser ? $currentUser->getUID() : ''; + $currentUserDisplayName = $currentUser instanceof IUser ? $currentUser->getDisplayName() : ''; + + return new DataResponse([ + 'token' => $room->getToken(), + 'userId' => $currentUserId, + 'userDisplayName' => $currentUserDisplayName, + ]); + } + +} diff --git a/lib/Files/Listener.php b/lib/Files/Listener.php index 332bed37f..0cac3a6fa 100644 --- a/lib/Files/Listener.php +++ b/lib/Files/Listener.php @@ -26,6 +26,7 @@ namespace OCA\Talk\Files; use OCA\Talk\Exceptions\ParticipantNotFoundException; use OCA\Talk\Exceptions\UnauthorizedException; use OCA\Talk\Room; +use OCA\Talk\TalkSession; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; @@ -34,11 +35,12 @@ use Symfony\Component\EventDispatcher\GenericEvent; * * The rooms for files are intended to give the users a way to talk about a * specific shared file, for example, when collaboratively editing it. The room - * is persistent and can be accessed simultaneously by any user with direct + * is persistent and can be accessed simultaneously by any user or guest if the + * file is publicly shared (link share, for example), or by any user with direct * access (user, group, circle and room share, but not link share, for example) * to that file (or to an ancestor). The room has no owner, although self joined - * users become persistent participants automatically when they join until they - * explicitly leave or no longer have access to the file. + * users with direct access become persistent participants automatically when + * they join until they explicitly leave or no longer have access to the file. * * These rooms are associated to a "file" object, and their custom behaviour is * provided by calling the methods of this class as a response to different room @@ -48,9 +50,13 @@ class Listener { /** @var Util */ protected $util; + /** @var TalkSession */ + protected $talkSession; - public function __construct(Util $util) { + public function __construct(Util $util, + TalkSession $talkSession) { $this->util = $util; + $this->talkSession = $talkSession; } public static function register(EventDispatcherInterface $dispatcher): void { @@ -61,7 +67,7 @@ class Listener { $listener = \OC::$server->query(self::class); try { - $listener->preventUsersWithoutDirectAccessToTheFileFromJoining($room, $event->getArgument('userId')); + $listener->preventUsersWithoutAccessToTheFileFromJoining($room, $event->getArgument('userId')); $listener->addUserAsPersistentParticipant($room, $event->getArgument('userId')); } catch (UnauthorizedException $e) { $event->setArgument('cancel', true); @@ -72,8 +78,11 @@ class Listener { $listener = function(GenericEvent $event) { /** @var Room $room */ $room = $event->getSubject(); + /** @var self $listener */ + $listener = \OC::$server->query(self::class); + try { - self::preventGuestsFromJoining($room); + $listener->preventGuestsFromJoiningIfNotPubliclyAccessible($room); } catch (UnauthorizedException $e) { $event->setArgument('cancel', true); } @@ -82,8 +91,10 @@ class Listener { } /** - * Prevents users from joining if they do not have direct access to the - * file. + * Prevents users from joining if they do not have access to the file. + * + * A user has access to the file if the file is publicly accessible (through + * a link share, for example) or if the user has direct access to it. * * A user has direct access to a file if she received the file (or an * ancestor) through a user, group, circle or room share (but not through a @@ -95,16 +106,22 @@ class Listener { * @param string $userId * @throws UnauthorizedException */ - public function preventUsersWithoutDirectAccessToTheFileFromJoining(Room $room, string $userId): void { + public function preventUsersWithoutAccessToTheFileFromJoining(Room $room, string $userId): void { if ($room->getObjectType() !== 'file') { return; } - $share = $this->util->getAnyDirectShareOfFileAccessibleByUser($room->getObjectId(), $userId); + // If a guest can access the file then any user can too. + $shareToken = $this->talkSession->getFileShareTokenForRoom($room->getToken()); + if ($shareToken && $this->util->canGuestAccessFile($shareToken)) { + return; + } + + $share = $this->util->getAnyPublicShareOfFileOwnedByUserOrAnyDirectShareOfFileAccessibleByUser($room->getObjectId(), $userId); if (!$share) { $groupFolder = $this->util->getGroupFolderNode($room->getObjectId(), $userId); if (!$groupFolder) { - throw new UnauthorizedException('User does not have direct access to the file'); + throw new UnauthorizedException('User does not have access to the file'); } } } @@ -112,6 +129,9 @@ class Listener { /** * Add user as a persistent participant of a file room. * + * Only users with direct access to the file are added as persistent + * participants of the room. + * * This method should be called before a user joins a room, but only if the * user should be able to join the room. * @@ -123,6 +143,10 @@ class Listener { return; } + if (!$this->util->getAnyPublicShareOfFileOwnedByUserOrAnyDirectShareOfFileAccessibleByUser($room->getObjectId(), $userId)) { + return; + } + try { $room->getParticipant($userId); } catch (ParticipantNotFoundException $e) { @@ -131,19 +155,24 @@ class Listener { } /** - * Prevents guests from joining the room. + * Prevents guests from joining the room if it is not publicly accessible. * * This method should be called before a guest joins a room. * * @param Room $room * @throws UnauthorizedException */ - protected static function preventGuestsFromJoining(Room $room): void { + protected function preventGuestsFromJoiningIfNotPubliclyAccessible(Room $room): void { if ($room->getObjectType() !== 'file') { return; } - throw new UnauthorizedException('Guests are not allowed in rooms for files'); + $shareToken = $this->talkSession->getFileShareTokenForRoom($room->getToken()); + if ($shareToken && $this->util->canGuestAccessFile($shareToken)) { + return; + } + + throw new UnauthorizedException('Guests are not allowed in this room'); } } diff --git a/lib/Files/Util.php b/lib/Files/Util.php index 5658a1e7b..c4dc0af10 100644 --- a/lib/Files/Util.php +++ b/lib/Files/Util.php @@ -28,6 +28,8 @@ use OCP\Files\FileInfo; use OCP\Files\IRootFolder; use OCP\Files\Node; use OCP\Files\NotFoundException; +use OCP\ISession; +use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager as IShareManager; use OCP\Share\IShare; @@ -35,14 +37,18 @@ class Util { /** @var IRootFolder */ private $rootFolder; + /** @var ISession */ + private $session; /** @var IShareManager */ private $shareManager; /** @var array[] */ private $accessLists = []; public function __construct(IRootFolder $rootFolder, + ISession $session, IShareManager $shareManager) { $this->rootFolder = $rootFolder; + $this->session = $session; $this->shareManager = $shareManager; } @@ -67,8 +73,29 @@ class Util { return \in_array($userId, $this->getUsersWithAccessFile($fileId), true); } + public function canGuestAccessFile(string $shareToken): bool { + try { + $share = $this->shareManager->getShareByToken($shareToken); + if ($share->getPassword() !== null) { + $shareId = $this->session->get('public_link_authenticated'); + if ($share->getId() !== $shareId) { + throw new ShareNotFound(); + } + } + return true; + } catch (ShareNotFound $e) { + return false; + } + } + /** - * Returns any share of the file that the user has direct access to. + * Returns any share of the file that is public and owned by the user, or + * that the user has direct access to. + * + * A public share is one accessible by any user, including guests, like a + * share by link. Note that only a share of the file itself is taken into + * account; if an ancestor folder is shared publicly that share will not be + * returned. * * A user has direct access to a share and, thus, to a file, if she received * the file through a user, group, circle or room share (but not through a @@ -82,7 +109,7 @@ class Util { * @param string $userId * @return IShare|null */ - public function getAnyDirectShareOfFileAccessibleByUser(string $fileId, string $userId): ?IShare { + public function getAnyPublicShareOfFileOwnedByUserOrAnyDirectShareOfFileAccessibleByUser(string $fileId, string $userId): ?IShare { $userFolder = $this->rootFolder->getUserFolder($userId); $nodes = $userFolder->getById($fileId); if (empty($nodes)) { @@ -93,6 +120,13 @@ class Util { return $node->getType() === FileInfo::TYPE_FILE; }); + if (!empty($nodes)) { + $share = $this->getAnyPublicShareOfNodeOwnedByUser($nodes[0], $userId); + if ($share) { + return $share; + } + } + while (!empty($nodes)) { $node = array_pop($nodes); @@ -111,6 +145,31 @@ class Util { } /** + * Returns any public share of the node (like a link share) created by the + * user. + * + * @param Node $node + * @param string $userId + * @return IShare|null + */ + private function getAnyPublicShareOfNodeOwnedByUser(Node $node, string $userId): ?IShare { + $reshares = false; + $limit = 1; + + $shares = $this->shareManager->getSharesBy($userId, \OCP\Share::SHARE_TYPE_LINK, $node, $reshares, $limit); + if (\count($shares) > 0) { + return $shares[0]; + } + + $shares = $this->shareManager->getSharesBy($userId, \OCP\Share::SHARE_TYPE_EMAIL, $node, $reshares, $limit); + if (\count($shares) > 0) { + return $shares[0]; + } + + return null; + } + + /** * Returns any share of the node that the user has direct access to. * * @param Node $node diff --git a/lib/PublicShare/TemplateLoader.php b/lib/PublicShare/TemplateLoader.php new file mode 100644 index 000000000..1d37eb7c7 --- /dev/null +++ b/lib/PublicShare/TemplateLoader.php @@ -0,0 +1,57 @@ +<?php +declare(strict_types=1); + +/** + * + * @copyright Copyright (c) 2018, Daniel Calviño Sánchez (danxuliu@gmail.com) + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Talk\PublicShare; + +use OCP\Util; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * Helper class to extend the "publicshare" template from the server. + * + * The "loadTalkSidebarUi" method loads additional scripts that, when run on the + * browser, adjust the page generated by the server to inject the Talk UI as + * needed. + */ +class TemplateLoader { + |