Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 9 additions & 45 deletions lib/Controller/FilesIntegrationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
use OCP\IUserSession;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager as IShareManager;
use OCP\Share\IShare;

class FilesIntegrationController extends OCSController {

Expand Down Expand Up @@ -134,27 +133,20 @@ public function getRoomByFileId(string $fileId): DataResponse {
}


$share = $this->util->getAnyPublicShareOfFileOwnedByUserOrAnyDirectShareOfFileAccessibleByUser($fileId, $currentUser->getUID());
$groupFolder = null;
if (!$share) {
$groupFolder = $this->util->getGroupFolderNode($fileId, $currentUser->getUID());
if (!$groupFolder) {
throw new OCSNotFoundException($this->l->t('File is not shared, or shared but not with the user'));
}
$node = $this->util->getAnyNodeOfFileAccessibleByUser($fileId, $currentUser->getUID());
if ($node === null) {
throw new OCSNotFoundException($this->l->t('File is not shared, or shared but not with the user'));
}

$users = $this->util->getUsersWithAccessFile($fileId);
if (count($users) <= 1 && !$this->util->canGuestsAccessFile($fileId)) {
throw new OCSNotFoundException($this->l->t('File is not shared, or shared but not with the user'));
}

try {
$room = $this->manager->getRoomByObject('file', $fileId);
} catch (RoomNotFoundException $e) {
if ($share) {
try {
$name = $this->getFileName($share, $fileId);
} catch (NotFoundException $e) {
throw new OCSNotFoundException($this->l->t('File is not shared, or shared but not with the user'));
}
} else {
$name = $groupFolder->getName();
}
$name = $node->getName();
$name = $this->roomService->prepareConversationName($name);
$room = $this->roomService->createConversation(Room::PUBLIC_CALL, $name, null, 'file', $fileId);
}
Expand Down Expand Up @@ -244,32 +236,4 @@ public function getRoomByShareToken(string $shareToken): DataResponse {
'userDisplayName' => $currentUserDisplayName,
]);
}

/**
* Returns the name of the file in the share.
*
* If the given share itself is a file its name is returned; otherwise the
* file is looked for in the given shared folder and its name is returned.
*
* @param IShare $share
* @param string $fileId
* @return string
* @throws NotFoundException
*/
private function getFileName(IShare $share, string $fileId): string {
$node = $share->getNode();

if ($node->getType() === FileInfo::TYPE_FILE) {
return $node->getName();
}

$fileById = $node->getById($fileId);

if (empty($fileById)) {
throw new NotFoundException('File not found in share');
}

$file = array_shift($fileById);
return $file->getName();
}
}
11 changes: 4 additions & 7 deletions lib/Files/Listener.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,9 @@ public function preventUsersWithoutAccessToTheFileFromJoining(Room $room, string
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 access to the file');
}
$node = $this->util->getAnyNodeOfFileAccessibleByUser($room->getObjectId(), $userId);
if ($node === null) {
throw new UnauthorizedException('User does not have access to the file');
}
}

Expand All @@ -152,7 +149,7 @@ public function addUserAsPersistentParticipant(Room $room, string $userId): void
return;
}

if (!$this->util->getAnyPublicShareOfFileOwnedByUserOrAnyDirectShareOfFileAccessibleByUser($room->getObjectId(), $userId)) {
if ($this->util->getAnyNodeOfFileAccessibleByUser($room->getObjectId(), $userId) === null) {
return;
}

Expand Down
204 changes: 41 additions & 163 deletions lib/Files/Util.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@

namespace OCA\Talk\Files;

use OCA\GroupFolders\Mount\GroupFolderStorage;
use OCA\Files_Sharing\SharedStorage;
use OCP\Files\Config\ICachedMountInfo;
use OCP\Files\Config\IUserMountCache;
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;

class Util {

Expand All @@ -42,15 +42,21 @@ class Util {
private $session;
/** @var IShareManager */
private $shareManager;
/** @var IUserMountCache */
private $userMountCache;
/** @var array[] */
private $accessLists = [];
/** @var bool[] */
private $publicAccessLists = [];

public function __construct(IRootFolder $rootFolder,
ISession $session,
IShareManager $shareManager) {
IShareManager $shareManager,
IUserMountCache $userMountCache) {
$this->rootFolder = $rootFolder;
$this->session = $session;
$this->shareManager = $shareManager;
$this->userMountCache = $userMountCache;
}

public function getUsersWithAccessFile(string $fileId): array {
Expand All @@ -63,6 +69,16 @@ public function getUsersWithAccessFile(string $fileId): array {

$node = array_shift($nodes);
$accessList = $this->shareManager->getAccessList($node);
if (!$node->getStorage()->instanceOfStorage(SharedStorage::class)) {
// The file is not a shared file,
// let's check the accesslist for mount points of groupfolders and external storages
$mountsForFile = $this->userMountCache->getMountsForFileId($fileId);
$affectedUserIds = array_map(function (ICachedMountInfo $mount) {
return $mount->getUser()->getUID();
}, $mountsForFile);

$accessList['users'] = array_unique(array_merge($affectedUserIds, $accessList['users']));
}

$this->accessLists[$fileId] = $accessList['users'];
}
Expand All @@ -74,6 +90,21 @@ public function canUserAccessFile(string $fileId, string $userId): bool {
return \in_array($userId, $this->getUsersWithAccessFile($fileId), true);
}

public function canGuestsAccessFile(string $fileId): bool {
if (!isset($this->publicAccessLists[$fileId])) {
$nodes = $this->rootFolder->getById($fileId);

if (empty($nodes)) {
return false;
}

$node = array_shift($nodes);
$accessList = $this->shareManager->getAccessList($node, false);
$this->publicAccessLists[$fileId] = $accessList['public'];
}
return $this->publicAccessLists[$fileId] === true;
}

public function canGuestAccessFile(string $shareToken): bool {
try {
$share = $this->shareManager->getShareByToken($shareToken);
Expand All @@ -90,178 +121,25 @@ public function canGuestAccessFile(string $shareToken): bool {
}

/**
* Returns any share of the file that is public and owned by the user, or
* Returns any node 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
* public link, for example), or if she is the owner of such a share.
* Note that this includes too files received as a descendant of a folder
* that meets the above conditions.
*
* Only files are taken into account; folders are ignored.
*
* @param string $fileId
* @param string $userId
* @return IShare|null
*/
public function getAnyPublicShareOfFileOwnedByUserOrAnyDirectShareOfFileAccessibleByUser(string $fileId, string $userId): ?IShare {
$userFolder = $this->rootFolder->getUserFolder($userId);
$nodes = $userFolder->getById($fileId);
if (empty($nodes)) {
return null;
}

$nodes = array_filter($nodes, function ($node) {
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);

$share = $this->getAnyDirectShareOfNodeAccessibleByUser($node, $userId);
if ($share) {
return $share;
}

try {
$nodes[] = $node->getParent();
} catch (NotFoundException $e) {
}
}

return null;
}

/**
* 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, IShare::TYPE_LINK, $node, $reshares, $limit);
if (!empty($shares)) {
return $shares[0];
}

$shares = $this->shareManager->getSharesBy($userId, IShare::TYPE_EMAIL, $node, $reshares, $limit);
if (!empty($shares)) {
return $shares[0];
}

return null;
}

/**
* Returns any share of the node that the user has direct access to.
*
* @param Node $node
* @param string $userId
* @return IShare|null
*/
private function getAnyDirectShareOfNodeAccessibleByUser(Node $node, string $userId): ?IShare {
$reshares = false;
$limit = 1;

$shares = $this->shareManager->getSharesBy($userId, IShare::TYPE_USER, $node, $reshares, $limit);
if (!empty($shares)) {
return $shares[0];
}

$shares = $this->shareManager->getSharesBy($userId, IShare::TYPE_GROUP, $node, $reshares, $limit);
if (!empty($shares)) {
return $shares[0];
}

$shares = $this->shareManager->getSharesBy($userId, IShare::TYPE_CIRCLE, $node, $reshares, $limit);
if (!empty($shares)) {
return $shares[0];
}

$shares = $this->shareManager->getSharesBy($userId, IShare::TYPE_ROOM, $node, $reshares, $limit);
if (!empty($shares)) {
return $shares[0];
}

// If the node is not shared then there is no need for further checks.
// Note that "isShared()" returns false for owned shares, so the check
// can not be moved above.
if (!$node->isShared()) {
return null;
}

$shares = $this->shareManager->getSharedWith($userId, IShare::TYPE_USER, $node, $limit);
if (!empty($shares)) {
return $shares[0];
}

$shares = $this->shareManager->getSharedWith($userId, IShare::TYPE_GROUP, $node, $limit);
if (!empty($shares)) {
return $shares[0];
}

$shares = $this->shareManager->getSharedWith($userId, IShare::TYPE_CIRCLE, $node, $limit);
if (!empty($shares)) {
return $shares[0];
}

$shares = $this->shareManager->getSharedWith($userId, IShare::TYPE_ROOM, $node, $limit);
if (!empty($shares)) {
return $shares[0];
}

return null;
}

/**
* ...
*
* @param string $fileId
* @param string $userId
* @return Node|null
*/
public function getGroupFolderNode(string $fileId, string $userId): ?Node {
public function getAnyNodeOfFileAccessibleByUser(string $fileId, string $userId): ?Node {
$userFolder = $this->rootFolder->getUserFolder($userId);
$nodes = $userFolder->getById($fileId);
if (empty($nodes)) {
return null;
}
$nodes = $userFolder->getById((int) $fileId);

$nodes = array_filter($nodes, function (Node $node) {
$nodes = array_filter($nodes, static function (Node $node) {
return $node->getType() === FileInfo::TYPE_FILE;
});

if (empty($nodes)) {
return null;
}

/** @var Node $node */
$node = array_shift($nodes);
try {
$storage = $node->getStorage();
if ($storage->instanceOfStorage(GroupFolderStorage::class)) {
return $node;
}
} catch (NotFoundException $e) {
}

return null;
return array_shift($nodes);
}
}
1 change: 1 addition & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<referencedClass name="Doctrine\DBAL\Platforms\PostgreSQL94Platform" />
<referencedClass name="Doctrine\DBAL\Types\Types" />
<referencedClass name="OC" />
<referencedClass name="OCA\Files_Sharing\SharedStorage" />
</errorLevel>
</UndefinedClass>
<UndefinedDocblockClass>
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/features/chat/mentions.feature
Original file line number Diff line number Diff line change
Expand Up @@ -275,11 +275,11 @@ Feature: chat/mentions
And user "participant2" is participant of room "file welcome (2).txt room" (v4)
Then user "participant1" gets the following candidate mentions in room "file welcome (2).txt room" for "" with 200
| id | label | source |
| all | welcome.txt | calls |
| all | welcome (2).txt | calls |
| participant2 | participant2-displayname | users |
And user "participant2" gets the following candidate mentions in room "file welcome (2).txt room" for "" with 200
| id | label | source |
| all | welcome.txt | calls |
| all | welcome (2).txt | calls |
| participant1 | participant1-displayname | users |

Scenario: get matched mentions in a file room
Expand Down
Loading