commit
a4b9e783d4
@ -274,4 +274,53 @@
|
||||
</declaration>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<name>*dbprefix*richdocuments_wopi</name>
|
||||
<declaration>
|
||||
<field>
|
||||
<name>id</name>
|
||||
<type>integer</type>
|
||||
<notnull>true</notnull>
|
||||
<autoincrement>1</autoincrement>
|
||||
<unsigned>true</unsigned>
|
||||
<length>4</length>
|
||||
<comments>Unique per token</comments>
|
||||
</field>
|
||||
<field>
|
||||
<name>uid</name>
|
||||
<type>text</type>
|
||||
<length>64</length>
|
||||
<comments>UserId - a textual user identifier (unique?)</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>path</name>
|
||||
<type>text</type>
|
||||
<notnull>true</notnull>
|
||||
<length>512</length>
|
||||
<comments>Relative to storage e.g. /welcome.odt</comments>
|
||||
</field>
|
||||
<field>
|
||||
<name>token</name>
|
||||
<type>text</type>
|
||||
<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>
|
||||
</declaration>
|
||||
</table>
|
||||
</database>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<name>Collabora Online Development Edition</name>
|
||||
<description>An ownCloud app to work with office documents</description>
|
||||
<licence>AGPL</licence>
|
||||
<version>0.12.0</version>
|
||||
<version>0.13.0</version>
|
||||
<author>Collabora Productivity based on work of Frank Karlitschek, Victor Dubiniuk</author>
|
||||
<bugs>https://www.collaboraoffice.com/</bugs>
|
||||
<repository type="git">git://gerrit.libreoffice.org/online.git</repository>
|
||||
|
@ -36,6 +36,10 @@ $application->registerRoutes($this, [
|
||||
['name' => 'document#localLoad', 'url' => 'load/{fileId}', 'verb' => 'POST'],
|
||||
['name' => 'document#localSave', 'url' => 'save/{fileId}', 'verb' => 'POST'],
|
||||
['name' => 'document#localClose', 'url' => 'close/{fileId}', 'verb' => 'POST'],
|
||||
//documents - for WOPI access
|
||||
['name' => 'document#wopiGetToken', 'url' => 'wopi/token/{fileId}', 'verb' => 'GET'],
|
||||
['name' => 'document#wopiGetFile', 'url' => 'wopi/files/{fileId}/contents', 'verb' => 'GET'],
|
||||
['name' => 'document#wopiPutFile', 'url' => 'wopi/files/{fileId}/contents', 'verb' => 'POST'],
|
||||
//settings
|
||||
['name' => 'settings#savePersonal', 'url' => 'ajax/personal.php', 'verb' => 'POST'],
|
||||
['name' => 'settings#setUnstable', 'url' => 'ajax/config/unstable', 'verb' => 'POST'],
|
||||
|
@ -265,6 +265,70 @@ class DocumentController extends Controller{
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates and returns an access token for a given fileId.
|
||||
* Only for authenticated users!
|
||||
*/
|
||||
public function wopiGetToken($fileId){
|
||||
\OC::$server->getLogger()->debug('Generating WOPI Token for file {fileId}.', [ 'app' => $this->appName, 'fileId' => $fileId ]);
|
||||
|
||||
$row = new Db\Wopi();
|
||||
$token = $row->generateFileToken($fileId);
|
||||
|
||||
// Return the token.
|
||||
return array(
|
||||
'status' => 'success',
|
||||
'token' => $token
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
* @PublicPage
|
||||
* Given an access token and a fileId, returns the contents of the file.
|
||||
* Expects a valid token in access_token parameter.
|
||||
*/
|
||||
public function wopiGetFile($fileId){
|
||||
$token = $this->request->getParam('access_token');
|
||||
|
||||
\OC::$server->getLogger()->debug('Getting contents of file {fileId} by token {token}.', [ 'app' => $this->appName, 'fileId' => $fileId, 'token' => $token ]);
|
||||
|
||||
$row = new Db\Wopi();
|
||||
$row->loadBy('token', $token);
|
||||
|
||||
//TODO: Support X-WOPIMaxExpectedSize header.
|
||||
$res = $row->getPathForToken($fileId, $token);
|
||||
return new DownloadResponse($this->request, $res['user'], $res['path']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
* @PublicPage
|
||||
* Given an access token and a fileId, replaces the files with the request body.
|
||||
* Expects a valid token in access_token parameter.
|
||||
*/
|
||||
public function wopiPutFile($fileId){
|
||||
$token = $this->request->getParam('access_token');
|
||||
|
||||
\OC::$server->getLogger()->debug('Putting contents of file {fileId} by token {token}.', [ 'app' => $this->appName, 'fileId' => $fileId, 'token' => $token ]);
|
||||
|
||||
$row = new Db\Wopi();
|
||||
$row->loadBy('token', $token);
|
||||
|
||||
$res = $row->getPathForToken($token);
|
||||
$view = new \OC\Files\View('/' . $res['user'] . '/');
|
||||
|
||||
// Read the contents of the file from the POST body and store.
|
||||
$content = file_get_contents('php://input');
|
||||
$view->file_put_contents($res['path'], $content);
|
||||
|
||||
return array(
|
||||
'status' => 'success'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @PublicPage
|
||||
|
@ -180,27 +180,38 @@ var documentsMain = {
|
||||
|
||||
$('title').text(title + ' - ' + documentsMain.UI.mainTitle);
|
||||
|
||||
// TODO. wopiurl = get from discovery xml
|
||||
var wopiurl = $('#wopi-url').val() + '/loleaflet/dist/loleaflet.html';
|
||||
var wopisrc = documentsMain.url;
|
||||
var action = wopiurl + '?' + wopisrc;
|
||||
var token = oc_requesttoken;
|
||||
$.get(OC.generateUrl('apps/richdocuments/wopi/token/{fileId}', { fileId: documentsMain.fileId }),
|
||||
function (result) {
|
||||
if (!result || result.status === 'error') {
|
||||
if (result && result.message){
|
||||
documentsMain.IU.notify(result.message);
|
||||
}
|
||||
documentsMain.onEditorShutdown(t('richdocuments', 'Failed to aquire access token. Please re-login and try again.'));
|
||||
return;
|
||||
}
|
||||
|
||||
var form = '<form id="loleafletform" name="loleafletform" target="loleafletframe" action="' + action + '" method="post">' +
|
||||
'<input name="access_token" value="' + token + '" type="hidden"/></form>';
|
||||
var frame = '<iframe id="loleafletframe" name= "loleafletframe" allowfullscreen style="width:100%;height:100%;position:absolute;"/>';
|
||||
//TODO: Get WOPISrc from the discovery XML.
|
||||
var url = OC.generateUrl('apps/richdocuments/wopi/files/{file_id}/contents?access_token={token}',
|
||||
{file_id: documentsMain.fileId, token: encodeURIComponent(result.token)});
|
||||
documentsMain.url = window.location.protocol + '//' + window.location.host + url;
|
||||
|
||||
var viewer = window.location.protocol + '//' + window.location.host + '/loleaflet/dist/loleaflet.html?' +
|
||||
'file_path=' + encodeURIComponent(documentsMain.url) +
|
||||
'&host=' + 'ws://' + window.location.hostname + ':9980' +
|
||||
'&permission=' + 'view' +
|
||||
'×tamp=' + '';
|
||||
|
||||
var frame = '<iframe id="loleafletframe" allowfullscreen style="width:100%;height:100%;position:absolute;" src="' + viewer + '" sandbox="allow-scripts allow-same-origin allow-popups allow-modals"/>';
|
||||
$('#mainContainer').append(frame);
|
||||
});
|
||||
|
||||
$('#mainContainer').append(form);
|
||||
$('#mainContainer').append(frame);
|
||||
documentsMain.overlay.documentOverlay('hide');
|
||||
$('#loleafletframe').load(function(){
|
||||
// avoid Blocked a frame with origin different domains
|
||||
|
||||
/*var iframe = $('#loleafletframe').contents();
|
||||
var iframe = $('#loleafletframe').contents();
|
||||
iframe.find('#tb_toolbar-up_item_close').click(function() {
|
||||
documentsMain.onClose();
|
||||
});*/
|
||||
/*var frameWindow = $('#loleafletframe')[0].contentWindow;
|
||||
});
|
||||
var frameWindow = $('#loleafletframe')[0].contentWindow;
|
||||
(function() {
|
||||
cloudSuiteOnClick = frameWindow.onClick;
|
||||
frameWindow.onClick = function() {
|
||||
@ -209,9 +220,8 @@ var documentsMain = {
|
||||
cloudSuiteOnClick.apply(this, arguments);
|
||||
frameWindow.map.options.doc = documentsMain.url;
|
||||
};
|
||||
})();*/
|
||||
})();
|
||||
});
|
||||
$('#loleafletform').submit();
|
||||
},
|
||||
|
||||
hideEditor : function(){
|
||||
@ -506,11 +516,6 @@ var documentsMain = {
|
||||
},
|
||||
|
||||
loadDocument: function() {
|
||||
// Provides access to information about a file and allows
|
||||
// for file-level operations.
|
||||
// HTTP://server/<...>/wopi*/files/<id>
|
||||
var url = window.location.protocol + '//' + window.location.host + OC.generateUrl('apps/documents/wopi/files/{file_id}', {file_id: documentsMain.fileId}, false);
|
||||
documentsMain.url = 'WOPISrc=' + encodeURIComponent(url);
|
||||
documentsMain.UI.showEditor(documentsMain.fileName);
|
||||
},
|
||||
|
||||
|
101
lib/db/wopi.php
Normal file
101
lib/db/wopi.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* ownCloud - Richdocuments App
|
||||
*
|
||||
* @author Ashod Nakashian
|
||||
* @copyright 2016 Ashod Nakashian ashod.nakashian@collabora.co.uk
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
*/
|
||||
|
||||
namespace OCA\Richdocuments\Db;
|
||||
|
||||
use \OCA\Richdocuments\Download;
|
||||
use \OCA\Richdocuments\DownloadResponse;
|
||||
|
||||
/**
|
||||
* @method string generateFileToken()
|
||||
* @method string getPathForToken()
|
||||
*/
|
||||
|
||||
class Wopi extends \OCA\Richdocuments\Db{
|
||||
|
||||
const DB_TABLE = '`*PREFIX*richdocuments_wopi`';
|
||||
|
||||
// Tokens expire after this many seconds (not defined by WOPI specs).
|
||||
const TOKEN_LIFETIME_SECONDS = 30 * 60;
|
||||
|
||||
protected $tableName = '`*PREFIX*richdocuments_wopi`';
|
||||
|
||||
protected $insertStatement = 'INSERT INTO `*PREFIX*richdocuments_wopi` (`uid`, `fileid`, `path`, `token`, `expiry`)
|
||||
VALUES (?, ?, ?, ?, ?)';
|
||||
|
||||
protected $loadStatement = 'SELECT * FROM `*PREFIX*richdocuments_wopi` WHERE `token`= ?';
|
||||
|
||||
/*
|
||||
* Given a fileId, generates a token
|
||||
* and stores in the database.
|
||||
* Returns the token.
|
||||
*/
|
||||
public function generateFileToken($fileId){
|
||||
$user = \OC_User::getUser();
|
||||
$view = new \OC\Files\View('/' . $user . '/');
|
||||
$path = $view->getPath($fileId);
|
||||
|
||||
if (!$view->is_file($path)) {
|
||||
throw new \Exception('Invalid fileId.');
|
||||
}
|
||||
|
||||
$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 {user} file {fileId}: {token}',
|
||||
[ 'user' => $user, 'fileId' => $fileId, 'token' => $token ]);
|
||||
|
||||
$wopi = new \OCA\Richdocuments\Db\Wopi([
|
||||
$user,
|
||||
$fileId,
|
||||
$path,
|
||||
$token,
|
||||
time() + self::TOKEN_LIFETIME_SECONDS
|
||||
]);
|
||||
|
||||
if (!$wopi->insert()){
|
||||
throw new \Exception('Failed to add wopi token into database');
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a token, validates it and
|
||||
* constructs and validates the path.
|
||||
* Returns the path, if valid, else false.
|
||||
*/
|
||||
public function getPathForToken($fileId, $token){
|
||||
|
||||
$wopi = new Wopi();
|
||||
$row = $wopi->loadBy('token', $token)->getData();
|
||||
\OC::$server->getLogger()->debug('Loaded WOPI Token record: {row}.', [ 'row' => $row ]);
|
||||
|
||||
//TODO: validate.
|
||||
if ($row['expiry'] > time() || $row['fileid'] !== $fileId){
|
||||
// Expired token!
|
||||
//$wopi->deleteBy('id', $row['id']);
|
||||
//return false;
|
||||
}
|
||||
|
||||
$user = $row['uid'];
|
||||
$view = new \OC\Files\View('/' . $user . '/');
|
||||
$path = $row['path'];
|
||||
|
||||
if (!$view->is_file($path)) {
|
||||
throw new \Exception('Invalid file path.');
|
||||
}
|
||||
|
||||
return array('user' => $user, 'path' => $path);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user