Merge pull request #1388 from nextcloud/make-rooms-for-files-in-shared-folders-accessible-to-shared-folder-users

Make rooms for files in shared folders accessible to shared folder users
This commit is contained in:
Joas Schilling 2018-12-13 16:49:44 +01:00 коммит произвёл GitHub
Родитель c48380bc21 8905942d92
Коммит 6fa076bec9
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 169 добавлений и 41 удалений

Просмотреть файл

@ -268,12 +268,19 @@
padding-right: 0px;
}
#app-sidebar .tabsContainer .tab .app-not-started-placeholder {
/* Make the placeholder take the full tab height until the app is
* started. */
#app-sidebar .tabsContainer .tab .ui-not-ready-placeholder {
/* Make the placeholder take the full tab height until the UI is ready. */
flex-grow: 1;
}
/* Hide other UI elements when there is a "UI not ready" placeholder. */
#app-sidebar .tabsContainer .tab .ui-not-ready-placeholder ~ div,
/* #chatView needs to be set explicitly to override the display property of
* "#app-sidebar #chatView". */
#app-sidebar .tabsContainer .tab .ui-not-ready-placeholder ~ #chatView {
display: none;
}
#app-sidebar #chatView .comments {
padding-right: 15px;
}

Просмотреть файл

@ -266,7 +266,7 @@
this.listenTo(roomsChannel, 'joinedRoom', this.setActiveRoom);
this.listenTo(roomsChannel, 'leaveCurrentRoom', this.setActiveRoom);
this.$el.append('<div class="app-not-started-placeholder icon-loading"></div>');
this.$el.append('<div class="ui-not-ready-placeholder icon-loading"></div>');
},
/**
@ -325,17 +325,29 @@
return;
}
if (!OCA.Talk.FilesPlugin.isTalkSidebarSupportedForFile(fileInfo)) {
this.model = null;
this.$el.prepend('<div class="ui-not-ready-placeholder icon-loading"></div>');
this._roomForFileModel.leave();
OCA.Talk.FilesPlugin.isTalkSidebarSupportedForFile(fileInfo).then(function(supported) {
if (supported) {
this._setFileInfoWhenTalkSidebarIsSupportedForFile(fileInfo);
} else {
this._setFileInfoWhenTalkSidebarIsNotSupportedForFile();
}
}.bind(this));
},
this._renderFileNotSharedUi();
_setFileInfoWhenTalkSidebarIsNotSupportedForFile: function() {
this.model = null;
return;
}
this._roomForFileModel.leave();
this._renderFileNotSharedUi();
},
_setFileInfoWhenTalkSidebarIsSupportedForFile: function(fileInfo) {
if (this.model === fileInfo) {
this.$el.find('.ui-not-ready-placeholder').remove();
// If the tab was hidden and it is being shown again at this
// point the tab has not been made visible yet, so the
// operations need to be delayed. However, the scroll position
@ -381,6 +393,18 @@
return;
}
// Keep the placeholder visible until the messages for the new room
// have been received to prevent showing the messages of the
// previous room.
// The message collection is updated by the signaling, so there are
// no "sync" events to listen to. Moreover, this relies on the fact
// that the rooms are never empty (as there will be always at least
// a system message for the creation of the room) and thus at least
// one model will be always added, triggering the "update" event.
OCA.SpreedMe.app._messageCollection.once('update', function() {
this.$el.find('.ui-not-ready-placeholder').remove();
}, this);
this._roomForFileModel.join(this.model.get('id'));
this.$el.find('.file-not-shared').remove();
@ -443,13 +467,13 @@
// Force initial rendering; changes in the room state will
// automatically render the button again from now on.
this._callButton.render();
this._callButton.$el.prependTo(this.$el);
this._callButton.$el.insertBefore(OCA.SpreedMe.app._chatView.$el);
},
setAppStarted: function() {
this._appStarted = true;
this.$el.find('.app-not-started-placeholder').remove();
this.$el.find('.ui-not-ready-placeholder').remove();
// Set again the file info now that the app has started.
if (this.model !== null) {
@ -507,30 +531,52 @@
},
/**
* Returns whether the Talk tab can be displayed for the file.
* Returns whether the Talk sidebar is supported for the file or not.
*
* @return True if the file is shared with the current user or by the
* current user to another user (as a user, group...), false
* otherwise.
* In some cases it is not possible to know if the Talk sidebar is
* supported for the file or not just from the data in the FileInfo (for
* example, for files in a folder shared by the current user). Due to
* that a Promise is always returned; the Promise will be resolved as
* soon as possible (in some cases, immediately) with either true or
* false, depending on whether the Talk sidebar is supported for the
* file or not.
*
* The Talk sidebar is supported for a file if the file is shared with
* the current user or by the current user to another user (as a user,
* group...), or if the file is a descendant of a folder that meets
* those conditions.
*
* @param {OCA.Files.FileInfo}
* @return {Promise}
*/
isTalkSidebarSupportedForFile: function(fileInfo) {
var deferred = $.Deferred();
if (!fileInfo) {
return false;
deferred.resolve(false);
return deferred.promise();
}
if (fileInfo.get('type') === 'dir') {
return false;
deferred.resolve(false);
return deferred.promise();
}
if (fileInfo.get('shareOwnerId')) {
// Shared with me
// TODO How to check that it is not a remote share? At least for
// local shares "shareTypes" is not defined when shared with me.
return true;
deferred.resolve(true);
return deferred.promise();
}
if (!fileInfo.get('shareTypes')) {
return false;
OCA.Talk.FilesPlugin._isRoomForFileAccessible(fileInfo.id, deferred);
return deferred.promise();
}
var shareTypes = fileInfo.get('shareTypes').filter(function(shareType) {
@ -544,10 +590,41 @@
});
if (shareTypes.length === 0) {
return false;
OCA.Talk.FilesPlugin._isRoomForFileAccessible(fileInfo.id, deferred);
return deferred.promise();
}
return true;
deferred.resolve(true);
return deferred.promise();
},
/**
* Resolves the Deferred with whether the room for the given file ID is
* accessible or not.
*
* When it is not possible to know whether the Talk sidebar is supported
* for a file or not only from the data in the FileInfo it is necessary
* to query the server.
*
* @param {string} fileId
* @param {Deferred} deferred
*/
_isRoomForFileAccessible: function(fileId, deferred) {
$.ajax({
url: OC.linkToOCS('apps/spreed/api/v1', 2) + 'file/' + fileId,
type: 'GET',
beforeSend: function(request) {
request.setRequestHeader('Accept', 'application/json');
},
success: function() {
deferred.resolve(true);
},
error: function() {
deferred.resolve(false);
}
});
},
setupSignalingEventHandlers: function() {

Просмотреть файл

@ -31,8 +31,10 @@ use OCA\Spreed\Manager;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSNotFoundException;
use OCP\AppFramework\OCSController;
use OCP\Files\NotFoundException;
use OCP\IL10N;
use OCP\IRequest;
use OCP\Share\IShare;
class FilesController extends OCSController {
@ -84,12 +86,14 @@ class FilesController extends OCSController {
* 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 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.
* 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,
* or "404 Not found" if the given file id was invalid.
* @throws OCSNotFoundException
*/
public function getRoom(string $fileId): DataResponse {
$share = $this->util->getAnyDirectShareOfFileAccessibleByUser($fileId, $this->currentUser);
@ -100,8 +104,12 @@ class FilesController extends OCSController {
try {
$room = $this->manager->getRoomByObject('file', $fileId);
} catch (RoomNotFoundException $e) {
$node = $share->getNode();
$room = $this->manager->createPublicRoom($node->getName(), 'file', $fileId);
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'));
}
$room = $this->manager->createPublicRoom($name, 'file', $fileId);
}
try {
@ -115,4 +123,32 @@ class FilesController extends OCSController {
]);
}
/**
* 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() === \OCP\Files\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();
}
}

Просмотреть файл

@ -36,9 +36,9 @@ use Symfony\Component\EventDispatcher\GenericEvent;
* specific shared file, for example, when collaboratively editing it. The room
* is persistent and can be accessed simultaneously by any user with direct
* access (user, group, circle and room share, but not link share, for example)
* to that file. 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.
* 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.
*
* 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
@ -84,9 +84,9 @@ class Listener {
* Prevents users from joining if they do not have direct access to the
* file.
*
* A user has direct access to a file if she received the file 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.
* 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
* link share, for example), or if she is the owner of such a file.
*
* This method should be called before a user joins a room.
*

Просмотреть файл

@ -26,6 +26,7 @@ namespace OCA\Spreed\Files;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
use OCP\Share\IManager as IShareManager;
use OCP\Share\IShare;
@ -77,6 +78,8 @@ class Util {
* 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.
*
@ -86,16 +89,27 @@ class Util {
*/
public function getAnyDirectShareOfFileAccessibleByUser(string $fileId, string $userId) {
$userFolder = $this->rootFolder->getUserFolder($userId);
$fileById = $userFolder->getById($fileId);
if (empty($fileById)) {
$nodes = $userFolder->getById($fileId);
if (empty($nodes)) {
return null;
}
foreach ($fileById as $node) {
$nodes = array_filter($nodes, function($node) {
return $node->getType() === \OCP\Files\FileInfo::TYPE_FILE;
});
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;
@ -104,17 +118,11 @@ class Util {
/**
* Returns any share of the node that the user has direct access to.
*
* Only files are taken into account; folders are ignored.
*
* @param Node $node
* @param string $userId
* @return IShare|null
*/
private function getAnyDirectShareOfNodeAccessibleByUser(Node $node, string $userId) {
if ($node->getType() === \OCP\Files\FileInfo::TYPE_FOLDER) {
return null;
}
$reshares = false;
$limit = 1;