summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorJoas Schilling <213943+nickvergessen@users.noreply.github.com>2019-09-27 09:54:34 +0200
committerGitHub <noreply@github.com>2019-09-27 09:54:34 +0200
commit918d6f9773870984df2d305039eaf8bd1baee2f7 (patch)
tree0e2cacb3411f56c8751b2547f8e55eebf418a0bd /lib
parentb444c91e99d63d43948b3eb5e17558be404c8b5e (diff)
parent891a73f05dacdb7fadebf11e9f3d1d38f6817598 (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.php2
-rw-r--r--lib/Chat/AutoComplete/SearchPlugin.php9
-rw-r--r--lib/Chat/SystemMessage/Listener.php4
-rw-r--r--lib/Controller/FilesIntegrationController.php23
-rw-r--r--lib/Controller/PublicShareController.php148
-rw-r--r--lib/Files/Listener.php57
-rw-r--r--lib/Files/Util.php63
-rw-r--r--lib/PublicShare/TemplateLoader.php57
-rw-r--r--lib/TalkSession.php12
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 {
+