Browse Source

Public editing

Signed-off-by: Lukas Reschke <lukas@statuscode.ch>
tags/1.1.22
Lukas Reschke 3 years ago
parent
commit
7ef24654cf
No account linked to committer's email address

+ 7
- 0
appinfo/app.php View File

@@ -31,6 +31,13 @@ $eventDispatcher->addListener(
\OCP\Util::addStyle('richdocuments', 'viewer/odfviewer');
}
);
$eventDispatcher->addListener(
'OCA\Files_Sharing::loadAdditionalScripts',
function() {
\OCP\Util::addScript('richdocuments', 'viewer/viewer');
\OCP\Util::addStyle('richdocuments', 'viewer/odfviewer');
}
);

if (class_exists('\OC\Files\Type\TemplateManager')) {
$manager = \OC_Helper::getFileTemplateManager();

+ 0
- 16
appinfo/database.xml View File

@@ -70,26 +70,22 @@
<autoincrement>1</autoincrement>
<unsigned>true</unsigned>
<length>4</length>
<comments>Unique per token</comments>
</field>
<field>
<name>owner_uid</name>
<type>text</type>
<length>64</length>
<comments>Document owner UserId - a textual user identifier (unique?)</comments>
</field>
<field>
<name>editor_uid</name>
<type>text</type>
<length>64</length>
<comments>Document editor's UserId, can be different from uid if shared</comments>
</field>
<field>
<name>fileid</name>
<type>integer</type>
<notnull>true</notnull>
<length>4</length>
<comments>The unique ID of the file authorized</comments>
</field>
<field>
<name>version</name>
@@ -97,28 +93,18 @@
<notnull>true</notnull>
<default>0</default>
<length>4</length>
<comments>Authorized version, if any, of given fileid</comments>
</field>
<field>
<name>path</name>
<type>text</type>
<notnull>true</notnull>
<length>512</length>
<comments>Relative to storage e.g. /welcome.odt</comments>
</field>
<field>
<name>canwrite</name>
<type>boolean</type>
<default>false</default>
<notnull>true</notnull>
<comments>Can make changes to this file</comments>
</field>
<field>
<name>server_host</name>
<type>text</type>
<default>localhost</default>
<notnull>true</notnull>
<comments>Host from which token generation request originated</comments>
</field>
<field>
<name>token</name>
@@ -126,14 +112,12 @@
<default></default>
<notnull>true</notnull>
<length>32</length>
<comments>File access token</comments>
</field>
<field>
<name>expiry</name>
<type>integer</type>
<unsigned>true</unsigned>
<length>4</length>
<comments>Expiration time of the token</comments>
</field>

<index>

+ 1
- 1
appinfo/info.xml View File

@@ -5,7 +5,7 @@
<description>Collabora Online allows you to to work with all kinds of office documents directly in your browser. This application requires Collabora Cloudsuite to be installed on one of your servers, please read the documentation to learn more about that.</description>
<summary>Edit office documents directly in your browser.</summary>
<licence>AGPL</licence>
<version>1.1.19</version>
<version>1.1.21</version>
<author>Collabora Productivity based on work of Frank Karlitschek, Victor Dubiniuk</author>
<bugs>https://github.com/nextcloud/richdocuments/issues</bugs>
<repository type="git">https://github.com/nextcloud/richdocuments.git</repository>

+ 1
- 2
appinfo/routes.php View File

@@ -15,17 +15,16 @@ return [
'routes' => [
//documents
['name' => 'document#index', 'url' => 'index', 'verb' => 'GET'],
['name' => 'document#publicPage', 'url' => '/public', 'verb' => 'GET'],
['name' => 'document#create', 'url' => 'ajax/documents/create', 'verb' => 'POST'],

// WOPI access
['name' => 'wopi#getToken', 'url' => 'wopi/token/{fileId}', 'verb' => 'GET'],
['name' => 'wopi#checkFileInfo', 'url' => 'wopi/files/{fileId}', 'verb' => 'GET'],
['name' => 'wopi#getFile', 'url' => 'wopi/files/{fileId}/contents', 'verb' => 'GET'],
['name' => 'wopi#putFile', 'url' => 'wopi/files/{fileId}/contents', 'verb' => 'POST'],

//settings
['name' => 'settings#setSettings', 'url' => 'ajax/admin.php', 'verb' => 'POST'],
['name' => 'settings#getSupportedMimes', 'url' => 'ajax/mimes.php', 'verb' => 'GET'],
['name' => 'settings#getSettings', 'url' => 'ajax/settings.php', 'verb' => 'GET'],
]
];

+ 1
- 1
css/style.css View File

@@ -124,7 +124,7 @@
width:100%;
z-index: 500;
background-color: #ddd !important;
top: 45px;
top: 0px;
bottom: 0;
}


+ 15
- 60
js/documents.js View File

@@ -18,11 +18,7 @@ $.widget('oc.documentGrid', {
},

_load : function (fileId){
var url = OC.generateUrl('apps/richdocuments/wopi/token/{file_id}', {file_id: fileId});
$.get(
url,
documentsMain.initSession
);
documentsMain.initSession();
},

_render : function (data){
@@ -65,7 +61,6 @@ var documentsMain = {
isEditorMode : false,
isViewerMode: false,
isGuest : false,
memberId : false,
esId : false,
ready :false,
fileName: null,
@@ -306,6 +301,7 @@ var documentsMain = {
console.log('Waiting for page to render ...');
return;
}
parent.postMessage('loading', '*');

$(document.body).addClass("claro");
$(document.body).prepend(documentsMain.UI.container);
@@ -428,7 +424,7 @@ var documentsMain = {
documentsMain.UI.init();

// Does anything indicate that we need to autostart a session?
fileId = parent.location.hash.replace(/^\W*/, '');
fileId = getURLParameter('fileid').replace(/^\W*/, '');

if (fileId.indexOf('_') >= 0) {
documentsMain.returnToDir = unescape(fileId.replace(/^[^_]*_/, ''));
@@ -460,42 +456,28 @@ var documentsMain = {
prepareSession : function(){
documentsMain.isEditorMode = true;
documentsMain.overlay.documentOverlay('show');
$(window).on("unload", documentsMain.onTerminate);
},

initSession: function(response) {
documentsMain.urlsrc = response.documents[0].urlsrc;
documentsMain.fullPath = response.documents[0].path;
documentsMain.token = response.documents[0].token;
initSession: function() {
documentsMain.urlsrc = richdocuments_urlsrc;
documentsMain.fullPath = richdocuments_path;
documentsMain.token = richdocuments_token;

$('footer,nav').hide();
$(documentsMain.toolbar).appendTo('#header');

if (!response) {
documentsMain.onEditorShutdown(t('richdocuments', 'Failed to load this document. Please check if it can be opened with an external editor. This might also mean it has been unshared or deleted recently.'));
return;
}

//Wait for 3 sec if editor is still loading
if (!documentsMain.ready){
setTimeout(function(){ documentsMain.initSession(response); }, 3000);
console.log('Waiting for the editor to start...');
return;
}

documentsMain.canShare = !documentsMain.isGuest
&& typeof OC.Share !== 'undefined' && response.permissions & OC.PERMISSION_SHARE;
&& typeof OC.Share !== 'undefined' && richdocuments_permissions & OC.PERMISSION_SHARE;

// fade out file list and show the cloudsuite
$('#content-wrapper').fadeOut('fast').promise().done(function() {

documentsMain.fileId = response.file_id;
documentsMain.fileName = response.title;
documentsMain.fileId = richdocuments_fileId;
documentsMain.fileName = richdocuments_title;

documentsMain.memberId = response.member_id;
documentsMain.canEdit = Boolean(response.permissions & OC.PERMISSION_UPDATE);
documentsMain.canEdit = Boolean(richdocuments_permissions & OC.PERMISSION_UPDATE);

documentsMain.loadDocument(response);
documentsMain.loadDocument(documentsMain.fileName, documentsMain.fileId);

if (documentsMain.isGuest){
$('#odf-close').text(t('richdocuments', 'Save') );
@@ -514,8 +496,8 @@ var documentsMain = {
});
},

loadDocument: function(response) {
documentsMain.UI.showEditor(response.title, response.file_id, 'write');
loadDocument: function(title, fileId) {
documentsMain.UI.showEditor(title, fileId, 'write');
},

onEditorShutdown : function (message){
@@ -537,9 +519,6 @@ var documentsMain = {


onClose: function() {
if (!documentsMain.isEditorMode){
return;
}
documentsMain.isEditorMode = false;
$(window).off('beforeunload');
$(window).off('unload');
@@ -549,11 +528,7 @@ var documentsMain = {
documentsMain.UI.hideEditor();
$('#ocToolbar').remove();

if (documentsMain.returnToDir) {
window.location = OC.generateUrl('apps/files?dir={dir}', {dir: documentsMain.returnToDir});
} else {
documentsMain.show();
}
parent.postMessage('close', '*');
},

onCloseViewer: function() {
@@ -567,26 +542,6 @@ var documentsMain = {
$('#loleafletframe').focus();
},

onTerminate: function(){
var url = '';
if (documentsMain.isGuest){
url = OC.generateUrl('apps/richdocuments/ajax/user/disconnectGuest/{member_id}', {member_id: documentsMain.memberId});
} else {
url = OC.generateUrl('apps/richdocuments/ajax/user/disconnect/{member_id}', {member_id: documentsMain.memberId});
}
$.ajax({
type: "POST",
url: url,
data: {esId: documentsMain.esId},
dataType: "json",
async: false // Should be sync to complete before the page is closed
});

if (documentsMain.isGuest){
$('footer,nav').show();
}
},

show: function(fileId){
if (documentsMain.isGuest){
return;

+ 87
- 38
js/viewer/viewer.js View File

@@ -1,9 +1,6 @@
/* globals FileList, OCA.Files.fileActions, oc_debug */
var odfViewer = {
isDocuments : false,
supportedMimesReadOnly: [
],

supportedMimesReadWrite: [
'application/vnd.oasis.opendocument.text',
'application/vnd.oasis.opendocument.spreadsheet',
@@ -36,20 +33,14 @@ var odfViewer = {
'application/vnd.ms-powerpoint.slideshow.macroEnabled.12'
],

register : function(response){
register : function() {
var i,
mimeReadOnly,
mimeReadWrite;

if (response && response.mimes){
jQuery.each(response.mimes, function(i, mime){
odfViewer.supportedMimesReadOnly.push(mime);
odfViewer.supportedMimesReadWrite.push(mime);
});
}
for (i = 0; i < odfViewer.supportedMimesReadOnly.length; ++i) {
mimeReadOnly = odfViewer.supportedMimesReadOnly[i];
OCA.Files.fileActions.register(mimeReadOnly, 'View', OC.PERMISSION_READ, '', odfViewer.onView);
for (i = 0; i < odfViewer.supportedMimesReadWrite.length; ++i) {
mimeReadOnly = odfViewer.supportedMimesReadWrite[i];
OCA.Files.fileActions.register(mimeReadOnly, 'View', OC.PERMISSION_READ, '', odfViewer.onEdit);
OCA.Files.fileActions.setDefault(mimeReadOnly, 'View');
}
for (i = 0; i < odfViewer.supportedMimesReadWrite.length; ++i) {
@@ -62,40 +53,82 @@ var odfViewer = {
odfViewer.onEdit,
t('richdocuments', 'Edit')
);
OCA.Files.fileActions.register(
mimeReadWrite,
'View',
OC.PERMISSION_READ,
OC.imagePath('core', 'actions/rename'),
odfViewer.onEdit,
t('richdocuments', 'View')
);
OCA.Files.fileActions.setDefault(mimeReadWrite, 'View');
OCA.Files.fileActions.setDefault(mimeReadWrite, 'Edit');
}
},

dispatch : function(filename){
if (odfViewer.supportedMimesReadWrite.indexOf(OCA.Files.fileActions.getCurrentMimeType()) !== -1
&& OCA.Files.fileActions.getCurrentPermissions() & OC.PERMISSION_UPDATE
){
odfViewer.onEdit(filename);
} else {
odfViewer.onView(filename);
}
odfViewer.onEdit(filename);
},

onEdit : function(fileName, context){
var fileId = context.$file.attr('data-id');
var fileDir = context.dir;
onEdit : function(fileName, context) {
if(context) {
var fileDir = context.dir;
var fileId = context.$file.attr('data-id');
}

if (fileDir) {
window.location = OC.generateUrl('apps/richdocuments/index#{file_id}_{dir}', {file_id: fileId, dir: fileDir});
var viewer;
if($('#isPublic').val() === '1') {
viewer = OC.generateUrl(
'apps/richdocuments/public?shareToken={shareToken}&fileName={fileName}&requesttoken={requesttoken}',
{
shareToken: $('#sharingToken').val(),
fileName: fileName,
dir: fileDir,
requesttoken: OC.requestToken
}
);
} else {
window.location = OC.generateUrl('apps/richdocuments/index#{file_id}', {file_id: fileId});
viewer = OC.generateUrl(
'apps/richdocuments/index?fileId={fileId}_{dir}&requesttoken={requesttoken}',
{
fileId: fileId,
dir: fileDir,
requesttoken: OC.requestToken
}
);
}
},

onView: function(filename, context) {
var attachTo = odfViewer.isDocuments ? '#documents-content' : '#controls';
if(context) {
FileList.setViewerMode(true);
}

var $iframe = $('<iframe id="richdocumentsframe" style="width:100%;height:100%;display:block;position:absolute;top:0;" src="'+viewer+'" />');
if ($('#isPublic').val()) {
// force the preview to adjust its height
$('#preview').append($iframe).css({height: '100%'});
$('body').css({height: '100%'});
$('#content').addClass('full-height');
$('footer').addClass('hidden');
$('#imgframe').addClass('hidden');
$('.directLink').addClass('hidden');
$('.directDownload').addClass('hidden');
$('#controls').addClass('hidden');
$('#content').addClass('loading');
} else {
$('#app-content').append($iframe);
}

FileList.setViewerMode(true);
$('#app-content #controls').addClass('hidden');
$('#app-content').append($iframe);
},


onClose: function() {
FileList.setViewerMode(false);
$('#loleafletframe').remove();
if(typeof FileList !== "undefined") {
FileList.setViewerMode(false);
}
$('#app-content #controls').removeClass('hidden');
$('#richdocumentsframe').remove();
},

registerFilesMenu: function(response) {
@@ -186,11 +219,7 @@ $(document).ready(function() {
&& typeof OCA.Files !== 'undefined'
&& typeof OCA.Files.fileActions !== 'undefined'
) {
$.get(
OC.filePath('richdocuments', 'ajax', 'mimes.php'),
{},
odfViewer.register
);
odfViewer.register();

$.get(
OC.filePath('richdocuments', 'ajax', 'settings.php'),
@@ -198,6 +227,26 @@ $(document).ready(function() {
odfViewer.registerFilesMenu
);
}
});

$('#odf_close').live('click', odfViewer.onClose);
// FIXME: Hack for single public file view since it is not attached to the fileslist
$(document).ready(function(){
// FIXME: FIlter compatible mime types
if ($('#isPublic').val() && odfViewer.supportedMimesReadWrite.indexOf($('#mimetype').val()) !== -1) {
odfViewer.onEdit($('#filename').val());
}
});

$(document).ready(function() {
var eventMethod = window.addEventListener ? 'addEventListener' : 'attachEvent';
var eventer = window[eventMethod];
var messageEvent = eventMethod == 'attachEvent' ? 'onmessage' : 'message';

eventer(messageEvent,function(e) {
if(e.data === 'close') {
odfViewer.onClose();
} else if(e.data === 'loading') {
$('#content').removeClass('loading');
}
}, false);
});

+ 121
- 51
lib/Controller/DocumentController.php View File

@@ -11,8 +11,13 @@

namespace OCA\Richdocuments\Controller;

use OCA\Richdocuments\WOPI\DiscoveryManager;
use OCA\Richdocuments\TokenManager;
use OCA\Richdocuments\WOPI\Parser;
use \OCP\AppFramework\Controller;
use OCP\AppFramework\Http\JSONResponse;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use \OCP\IRequest;
use \OCP\IConfig;
use \OCP\IL10N;
@@ -21,57 +26,148 @@ use \OCP\AppFramework\Http\TemplateResponse;
use \OCA\Richdocuments\AppConfig;
use \OCA\Richdocuments\Helper;
use \OC\Files\View;
use \OCP\ICacheFactory;
use OCP\Share\IManager;

class DocumentController extends Controller {
/** @var string */
private $uid;
/** @var IL10N */
private $l10n;
/** @var IConfig */
private $settings;
/** @var AppConfig */
private $appConfig;
private $cache;
/** @var DiscoveryManager */
private $discoveryManager;
/** @var Parser */
private $wopiParser;
/** @var IManager */
private $shareManager;
/** @var TokenManager */
private $tokenManager;
/** @var IRootFolder */
private $rootFolder;

const ODT_TEMPLATE_PATH = '/assets/odttemplate.odt';

/**
* @param string $appName
* @param IRequest $request
* @param IConfig $settings
* @param AppConfig $appConfig
* @param IL10N $l10n
* @param Parser $wopiParser
* @param IManager $shareManager
* @param TokenManager $tokenManager
* @param IRootFolder $rootFolder
* @param string $UserId
*/
public function __construct($appName,
$UserId,
IRequest $request,
IConfig $settings,
AppConfig $appConfig,
IL10N $l10n,
ICacheFactory $cache,
DiscoveryManager $discoveryManager) {
Parser $wopiParser,
IManager $shareManager,
TokenManager $tokenManager,
IRootFolder $rootFolder,
$UserId) {
parent::__construct($appName, $request);
$this->uid = $UserId;
$this->l10n = $l10n;
$this->settings = $settings;
$this->appConfig = $appConfig;
$this->cache = $cache->create($appName);
$this->discoveryManager = $discoveryManager;
$this->wopiParser = $wopiParser;
$this->shareManager = $shareManager;
$this->tokenManager = $tokenManager;
$this->rootFolder = $rootFolder;
}

/**
* @NoAdminRequired
* @NoCSRFRequired
*
* @param string $fileId
* @return TemplateResponse
*/
public function index(){
$response = new TemplateResponse('richdocuments', 'documents');
$policy = new ContentSecurityPolicy();
$policy->addAllowedFrameDomain($this->appConfig->getAppValue('wopi_url'));
$response->setContentSecurityPolicy($policy);
return $response;
public function index($fileId) {
try {
$folder = $this->rootFolder->getUserFolder($this->uid);
$item = $folder->getById($fileId)[0];
if(!($item instanceof Node)) {
throw new \Exception();
}
list($urlSrc, $token) = $this->tokenManager->getToken($item->getId());
$params = [
'permissions' => $item->getPermissions(),
'title' => $item->getName(),
'fileId' => $item->getId(),
'token' => $token,
'urlsrc' => $urlSrc,
'path' => '/',
];

$response = new TemplateResponse('richdocuments', 'documents', $params, 'empty');
$policy = new ContentSecurityPolicy();
$policy->addAllowedFrameDomain($this->appConfig->getAppValue('wopi_url'));
$policy->allowInlineScript(true);
$response->setContentSecurityPolicy($policy);
return $response;
} catch (\Exception $e) {
}

return new TemplateResponse('core', '403', [], 'guest');
}

/**
* @PublicPage
*
* @param string $shareToken
* @param string $fileName
* @return TemplateResponse
* @throws \Exception
*/
public function publicPage($shareToken, $fileName) {
try {
$share = $this->shareManager->getShareByToken($shareToken);
$node = $share->getNode();
if($node instanceof Folder) {
$item = $node->get($fileName);
} else {
$item = $node;
}
if ($item instanceof Node) {
list($urlSrc, $token) = $this->tokenManager->getToken($item->getId(), $shareToken);
$params = [
'permissions' => $share->getPermissions(),
'title' => $item->getName(),
'fileId' => $item->getId(),
'token' => $token,
'urlsrc' => $urlSrc,
'path' => '/',
];

$response = new TemplateResponse('richdocuments', 'documents', $params, 'empty');
$policy = new ContentSecurityPolicy();
$policy->addAllowedFrameDomain($this->appConfig->getAppValue('wopi_url'));
$policy->allowInlineScript(true);
$response->setContentSecurityPolicy($policy);
return $response;
}
} catch (\Exception $e) {
}

return new TemplateResponse('core', '403', [], 'guest');
}

/**
* @NoAdminRequired
*
* @param string $mimetype
* @param string $filename
* @param string $dir
* @return JSONResponse
*/
public function create(){
$mimetype = $this->request->post['mimetype'];
$filename = $this->request->post['filename'];
$dir = $this->request->post['dir'];
public function create($mimetype,
$filename,
$dir){

$view = new View('/' . $this->uid . '/files');
if (!$dir){
@@ -117,36 +213,9 @@ class DocumentController extends Controller {
$content = file_get_contents(dirname(__DIR__) . self::ODT_TEMPLATE_PATH);
}

$discovery_parsed = null;
try {
$discovery = $this->discoveryManager->get();

$loadEntities = libxml_disable_entity_loader(true);
$discovery_parsed = simplexml_load_string($discovery);
libxml_disable_entity_loader($loadEntities);

if ($discovery_parsed === false) {
$this->cache->remove('discovery.xml');
$wopiRemote = $this->getWopiUrl(false);

return array(
'status' => 'error',
'message' => $this->l10n->t('Collabora Online: discovery.xml from "%s" is not a well-formed XML string.', array($wopiRemote)),
'hint' => $this->l10n->t('Please contact the "%s" administrator.', array($wopiRemote))
);
}
}
catch (ResponseException $e) {
return array(
'status' => 'error',
'message' => $e->getMessage(),
'hint' => $e->getHint()
);
}

if ($content && $view->file_put_contents($path, $content)){
if ($content && $view->file_put_contents($path, $content)) {
$info = $view->getFileInfo($path);
$ret = $this->getWopiSrcUrl($discovery_parsed, $mimetype);
$ret = $this->wopiParser->getUrlSrc($mimetype);
$response = array(
'status' => 'success',
'fileid' => $info['fileid'],
@@ -161,6 +230,7 @@ class DocumentController extends Controller {
'message' => (string) $this->l10n->t('Can\'t create document')
);
}

return $response;
}
}

+ 10
- 62
lib/Controller/WopiController.php View File

@@ -63,60 +63,6 @@ class WopiController extends Controller {
$this->wopiParser = $wopiParser;
}

/**
* Generates and returns an access token for a given fileId
*
* @NoAdminRequired
*
* @param string $fileId
* @return JSONResponse
*/
public function getToken($fileId) {
$arr = explode('_', $fileId, 2);
$version = '0';
if (count($arr) === 2) {
list($fileId, $version) = $arr;
}

try {
/** @var File $file */
$file = $this->rootFolder->getUserFolder($this->userId)->getById($fileId)[0];
$updatable = $file->isUpdateable();
} catch (\Exception $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}

// If token is for some versioned file
if ($version !== '0') {
$updatable = false;
}

$row = new Wopi();
$serverHost = $this->request->getServerProtocol() . '://' . $this->request->getServerHost();
$token = $row->generateFileToken($fileId, $version, $updatable, $serverHost);

try {
$userFolder = $this->rootFolder->getUserFolder($this->userId);
/** @var File $file */
$file = $userFolder->getById($fileId)[0];
$sessionData['title'] = basename($file->getPath());
$sessionData['permissions'] = $file->getPermissions();
$sessionData['file_id'] = $file->getId();

$sessionData['documents'] = [
0 => [
'urlsrc' => $this->wopiParser->getUrlSrc($file->getMimeType())['urlsrc'],
'path' => $file->getPath(),
'token' => $token,
],
];

return new JSONResponse($sessionData);
} catch (\Exception $e){
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
}

/**
* Returns general info about a file.
*
@@ -141,25 +87,29 @@ class WopiController extends Controller {

$res = $row->getPathForToken($fileId, $version, $token);
if ($res === false) {
return new JSONResponse();
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}

// Login the user to see his mount locations
try {
/** @var File $file */
$userFolder = $this->rootFolder->getUserFolder($res['editor']);
$userFolder = $this->rootFolder->getUserFolder($res['owner']);
$file = $userFolder->getById($fileId)[0];
} catch (\Exception $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}

if(!($file instanceof File)) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}

return new JSONResponse(
[
'BaseFileName' => $file->getName(),
'Size' => $file->getSize(),
'Version' => $version,
'UserId' => $res['editor'],
'UserFriendlyName' => $this->userManager->get($res['editor'])->getDisplayName(),
'UserId' => $res['editor'] !== '' ? $res['editor'] : 'Guest user',
'UserFriendlyName' => $res['editor'] !== '' ? $res['editor'] : 'Guest user',
'UserCanWrite' => $res['canwrite'] ? true : false,
'PostMessageOrigin' => $res['server_host'],
]
@@ -192,7 +142,7 @@ class WopiController extends Controller {

try {
/** @var File $file */
$userFolder = $this->rootFolder->getUserFolder($res['editor']);
$userFolder = $this->rootFolder->getUserFolder($res['owner']);
$file = $userFolder->getById($fileId)[0];
$response = new StreamResponse($file->fopen('rb'));
$response->addHeader('Content-Disposition', 'attachment');
@@ -203,8 +153,6 @@ class WopiController extends Controller {
}
}



/**
* Given an access token and a fileId, replaces the files with the request body.
* Expects a valid token in access_token parameter.
@@ -233,7 +181,7 @@ class WopiController extends Controller {

try {
/** @var File $file */
$userFolder = $this->rootFolder->getUserFolder($res['editor']);
$userFolder = $this->rootFolder->getUserFolder($res['owner']);
$file = $userFolder->getById($fileId)[0];
$content = fopen('php://input', 'rb');
$file->putContent($content);

+ 105
- 0
lib/TokenManager.php View File

@@ -0,0 +1,105 @@
<?php
/**
* @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
*
* @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\Richdocuments;

use OC\Share\Constants;
use OCA\Richdocuments\Db\Wopi;
use OCA\Richdocuments\WOPI\Parser;
use OCP\Files\File;
use OCP\Files\IRootFolder;
use OCP\IURLGenerator;
use OCP\Share\IManager;

class TokenManager {
/** @var IRootFolder */
private $rootFolder;
/** @var IManager */
private $shareManager;
/** @var IURLGenerator */
private $urlGenerator;
/** @var Parser */
private $wopiParser;

/**
* @param IRootFolder $rootFolder
* @param IManager $shareManager
* @param IURLGenerator $urlGenerator
* @param string $UserId
*/
public function __construct(IRootFolder $rootFolder,
IManager $shareManager,
IURLGenerator $urlGenerator,
Parser $wopiParser,
$UserId) {
$this->rootFolder = $rootFolder;
$this->shareManager = $shareManager;
$this->urlGenerator = $urlGenerator;
$this->wopiParser = $wopiParser;
$this->userId = $UserId;
}

/**
* @param string $fileId
* @param string $shareToken
* @return array
* @throws \Exception
*/
public function getToken($fileId, $shareToken = null) {
$arr = explode('_', $fileId, 2);
$version = '0';
if (count($arr) === 2) {
list($fileId, $version) = $arr;
}

// if the user is not logged-in do use the sharers storage
if($shareToken !== null) {
/** @var File $file */
$rootFolder = $this->rootFolder;
$share = $this->shareManager->getShareByToken($shareToken);
$updatable = (bool)($share->getPermissions() & \OCP\Constants::PERMISSION_UPDATE);
} else {
try {
/** @var File $file */
$rootFolder = $this->rootFolder->getUserFolder($this->userId);
$updatable = $rootFolder->isUpdateable();
} catch (\Exception $e) {
throw $e;
}
}
/** @var File $file */
$file = $rootFolder->getById($fileId)[0];

$row = new Wopi();
$serverHost = $this->urlGenerator->getAbsoluteURL('/');//$this->request->getServerProtocol() . '://' . $this->request->getServerHost();
$token = $row->generateFileToken($fileId, $file->getOwner()->getUID(), $this->userId, $version, $updatable, $serverHost);

try {

return [
$this->wopiParser->getUrlSrc($file->getMimeType())['urlsrc'],
$token,
];
} catch (\Exception $e){
throw $e;
}
}
}

+ 1
- 19
lib/WOPI/DiscoveryManager.php View File

@@ -84,25 +84,7 @@ class DiscoveryManager {
try {
$response = $client->get($wopiDiscovery);
} catch (\Exception $e) {
$error_message = $e->getMessage();
if (preg_match('/^cURL error ([0-9]*):/', $error_message, $matches)) {
$admin_check = $this->l10n->t('Please ask your administrator to check the Collabora Online server setting. The exact error message was: ') . $error_message;

$curl_error = $matches[1];
switch ($curl_error) {
case '1':
throw new ResponseException($this->l10n->t('Collabora Online: The protocol specified in "%s" is not allowed.', array($wopiRemote)), $admin_check);
case '3':
throw new ResponseException($this->l10n->t('Collabora Online: Malformed URL "%s".', array($wopiRemote)), $admin_check);
case '6':
throw new ResponseException($this->l10n->t('Collabora Online: Cannot resolve the host "%s".', array($wopiRemote)), $admin_check);
case '7':
throw new ResponseException($this->l10n->t('Collabora Online: Cannot connect to the host "%s".', array($wopiRemote)), $admin_check);
case '60':
throw new ResponseException($this->l10n->t('Collabora Online: SSL certificate is not installed.'), $this->l10n->t('Please ask your administrator to add ca-chain.cert.pem to the ca-bundle.crt, for example "cat /etc/loolwsd/ca-chain.cert.pem >> <server-installation>/resources/config/ca-bundle.crt" . The exact error message was: ') . $error_message);
}
}
throw new ResponseException($this->l10n->t('Collabora Online unknown error: ') . $error_message, $contact_admin);
throw $e;
}

$responseBody = $response->getBody();

+ 5
- 40
lib/db/wopi.php View File

@@ -23,63 +23,28 @@ class Wopi extends \OCA\Richdocuments\Db{

protected $tableName = '`*PREFIX*richdocuments_wopi`';

protected $insertStatement = 'INSERT INTO `*PREFIX*richdocuments_wopi` (`owner_uid`, `editor_uid`, `fileid`, `version`, `path`, `canwrite`, `server_host`, `token`, `expiry`)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)';
protected $insertStatement = 'INSERT INTO `*PREFIX*richdocuments_wopi` (`fileid`, `owner_uid`, `editor_uid`, `version`, `canwrite`, `server_host`, `token`, `expiry`)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)';

protected $loadStatement = 'SELECT * FROM `*PREFIX*richdocuments_wopi` WHERE `token`= ?';

/*
* Given a fileId and version, generates a token
* and stores in the database.
* version is 0 if current version of fileId is requested, otherwise
* its the version number as stored by files_version app
* Returns the token.
*/
public function generateFileToken($fileId, $version, $updatable, $serverHost){

// Get the FS view of the current user.
$view = \OC\Files\Filesystem::getView();

// Get the virtual path (if the file is shared).
$path = $view->getPath($fileId);

if (!$view->is_file($path)) {
throw new \Exception('Invalid fileId.');
}

// Figure out the real owner, if not us.
$owner = $view->getOwner($path);

// Create a view into the owner's FS.
$view = new \OC\Files\View('/' . $owner . '/files');
// Find the real path.
$path = $view->getPath($fileId);
if (!$view->is_file($path)) {
throw new \Exception('Invalid fileId.');
}

$editor = \OC::$server->getUserSession()->getUser()->getUID();

public function generateFileToken($fileId, $owner, $editor, $version, $updatable, $serverHost) {
$token = \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate(32,
\OCP\Security\ISecureRandom::CHAR_LOWER . \OCP\Security\ISecureRandom::CHAR_UPPER .
\OCP\Security\ISecureRandom::CHAR_DIGITS);

\OC::$server->getLogger()->debug('Issuing token for {editor} file {fileId}, version {version} owned by {owner}, path {path}: {token}',
[ 'owner' => $owner, 'editor' => $editor, 'fileId' => $fileId, 'version' => $version, 'path' => $path, 'token' => $token ]);

$wopi = new \OCA\Richdocuments\Db\Wopi([
$fileId,
$owner,
$editor,
$fileId,
$version,
$path,
$updatable,
$serverHost,
$token,
time() + self::TOKEN_LIFETIME_SECONDS
]);

if (!$wopi->insert()){
if (!$wopi->insert()) {
throw new \Exception('Failed to add wopi token into database');
}


+ 0
- 74
lib/filter.php View File

@@ -1,74 +0,0 @@
<?php

/**
* ownCloud - Richdocuments App
*
* @author Victor Dubiniuk
* @copyright 2013 Victor Dubiniuk victor.dubiniuk@gmail.com
*
* This file is licensed under the Affero General Public License version 3 or
* later.
*/

namespace OCA\Richdocuments;

class Filter {
protected static $filters = array();
public static function add($mimetype, $class){
self::$filters[$mimetype] = $class;
}

public static function read($content, $mimetype){
$data = array(
'mimetype' => $mimetype,
'content' => $content
);

if (isset(self::$filters[$mimetype])){
$data = call_user_func(
array(
self::$filters[$mimetype],
'read'
),
$data
);
}
return $data;
}
public static function write($content, $mimetype){
$data = array(
'mimetype' => $mimetype,
'content' => $content
);
if (isset(self::$filters[$mimetype])){
$data = call_user_func(
array(
self::$filters[$mimetype],
'write'
),
$data
);
}
return $data;
}
public static function getAll(){
return array_keys(self::$filters);
}
/**
* Checks if mimetype is supported by the app
* @param string $mimetype - checked mimetype
* @return bool
*/
public static function isSupportedMimetype($mimetype){
return in_array($mimetype, Storage::getSupportedMimetypes());
}
}


+ 0
- 12
lib/helper.php View File

@@ -12,7 +12,6 @@
namespace OCA\Richdocuments;

class Helper {

const APP_ID = 'richdocuments';

public static function getNewFileName($view, $path, $prepend = ' '){
@@ -25,15 +24,4 @@ class Helper {

return $path;
}

public static function getArrayValueByKey($array, $key, $default=''){
if (array_key_exists($key, $array)){
return $array[$key];
}
return $default;
}

public static function isVersionsEnabled(){
return \OCP\App::isEnabled('files_versions');
}
}

+ 0
- 175
lib/storage.php View File

@@ -1,175 +0,0 @@
<?php

/**
* ownCloud - Richdocuments App
*
* @author Frank Karlitschek
* @copyright 2013-2014 Frank Karlitschek frank@owncloud.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 library. If not, see <http://www.gnu.org/licenses/>.
*
*/


namespace OCA\Richdocuments;

class Storage {
public static $MIMETYPE_LIBREOFFICE_WORDPROCESSOR = array(
'application/vnd.oasis.opendocument.text',
'application/vnd.oasis.opendocument.presentation',
'application/vnd.oasis.opendocument.spreadsheet',
'application/vnd.oasis.opendocument.graphics',
'application/vnd.lotus-wordpro',
'image/svg+xml',
'application/vnd.visio',
'application/vnd.wordperfect',
'application/msonenote',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'application/vnd.ms-word.document.macroEnabled.12',
'application/vnd.ms-word.template.macroEnabled.12',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
'application/vnd.ms-excel.sheet.macroEnabled.12',
'application/vnd.ms-excel.template.macroEnabled.12',
'application/vnd.ms-excel.addin.macroEnabled.12',
'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'application/vnd.openxmlformats-officedocument.presentationml.template',
'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
'application/vnd.ms-powerpoint.addin.macroEnabled.12',
'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
'application/vnd.ms-powerpoint.template.macroEnabled.12',
'application/vnd.ms-powerpoint.slideshow.macroEnabled.12'
);

public static function getDocuments() {
$list = array_filter(
self::searchDocuments(),
function($item){
//filter Deleted
if (strpos($item['path'], '_trashbin')===0){
return false;
}
return true;
}
);

return $list;
}

public static function getDocumentById($fileId){
$root = \OC::$server->getUserFolder();
$ret = array();

// If type of fileId is a string, then it
// doesn't work for shared documents, lets cast to int everytime
$document = $root->getById((int)$fileId)[0];
if ($document === null){
error_log('File with file id, ' . $fileId . ', not found');
return $ret;
}

$ret['mimetype'] = $document->getMimeType();
$ret['path'] = $root->getRelativePath($document->getPath());
$ret['name'] = $document->getName();
$ret['fileid'] = $fileId;

return $ret;
}

public static function resolvePath($fileId){
$list = array_filter(
self::searchDocuments(),
function($item) use ($fileId){
return intval($item['fileid'])==$fileId;
}
);
if (count($list)>0){
$item = current($list);
return $item['path'];
}
return false;
}

/**
* @brief Cleanup session data on removing the document
* @param array
*
* This function is connected to the delete signal of OC_Filesystem
* to delete the related info from database
*/
public static function onDelete($params) {
$info = \OC\Files\Filesystem::getFileInfo($params['path']);

$fileId = @$info['fileid'];
if (!$fileId){
return;
}

$session = new Db\Session();
$session->loadBy('file_id', $fileId);

if (!$session->getEsId()){
return;
}

$member = new Db\Member();
$sessionMembers = $member->getCollectionBy('es_id', $session->getEsId());
foreach ($sessionMembers as $memberData){
if (intval($memberData['status'])===Db\Member::MEMBER_STATUS_ACTIVE){
return;
}
}

}

private static function processDocuments($rawDocuments){
$documents = array();
$view = \OC\Files\Filesystem::getView();
foreach($rawDocuments as $rawDocument){
$document = array(
'fileid' => $rawDocument->getId(),
'path' => $view->getPath($rawDocument->getId()),
'name' => $rawDocument->getName(),
'mimetype' => $rawDocument->getMimetype(),
'mtime' => $rawDocument->getMTime()
);

array_push($documents, $document);
}

return $documents;
}

protected static function searchDocuments(){
$documents = array();
foreach (self::getSupportedMimetypes() as $mime){
$rawDocuments = \OCP\Files::searchByMime($mime);
$documents = array_merge($documents, self::processDocuments($rawDocuments));
}

return $documents;
}

public static function getSupportedMimetypes(){
return array_merge(
self::$MIMETYPE_LIBREOFFICE_WORDPROCESSOR,
Filter::getAll()
);
}
}

+ 9
- 0
templates/documents.php View File

@@ -1,3 +1,12 @@
<script nonce="<?php p(\OC::$server->getContentSecurityPolicyNonceManager()->getNonce()) ?>">
var richdocuments_permissions = '<?php p($_['permissions']) ?>';
var richdocuments_title = '<?php p($_['title']) ?>';
var richdocuments_fileId = '<?php p($_['fileId']) ?>';
var richdocuments_token = '<?php p($_['token']) ?>';
var richdocuments_urlsrc = '<?php p($_['urlsrc']) ?>';
var richdocuments_path = '<?php p($_['path']) ?>';
</script>

<?php
style( 'richdocuments', 'style' );
script('richdocuments', 'documents');

Loading…
Cancel
Save