From e0da6fa8d148b465a4c5c8b22d80e35c45a485f2 Mon Sep 17 00:00:00 2001 From: Pranav Kant Date: Sat, 16 Jul 2016 23:54:56 +0530 Subject: [PATCH 1/4] Create a dummy memory session before signing in as user Since this WOPI Put method is executed when loolwsd hits owncloud server, it has no session or probably invalid session data. Even though WOPI Put file operation initiated by loolwsd succeds, i.e file is successfully put into owncloud storage and versioned, it returns an HTTP 500 Internal server error as response to loolwsd which causes problem on loolwsd side messing up its state. Following trace can be observed in webserver's error logs after HTTP 500 is returned: PHP Fatal error: Uncaught exception 'Exception' with message 'Session has been closed - no further changes to the session are allowed' in /var/www/html/owncloud9/lib/private/session/internal.php:135 Stack trace: #0 /var/www/html/owncloud9/lib/private/session/internal.php(60): OC\\Session\\Internal->validateSession() #1 /var/www/html/owncloud9/lib/private/session/cryptosessiondata.php(150): OC\\Session\\Internal->set('encrypted_sessi...', 'e747091469b9905...') #2 /var/www/html/owncloud9/lib/private/session/cryptosessiondata.php(64): OC\\Session\\CryptoSessionData->close() #3 [internal function]: OC\\Session\\CryptoSessionData->__destruct() #4 {main}\n thrown in /var/www/html/owncloud9/lib/private/session/internal.php on line 135 Creating a dummy memory session, setting it as current session, and then setting the desired user session seems to address this problem and does not emit HTTP 500 anymore. --- controller/documentcontroller.php | 50 ++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/controller/documentcontroller.php b/controller/documentcontroller.php index 61d92d2e..87ee52de 100644 --- a/controller/documentcontroller.php +++ b/controller/documentcontroller.php @@ -79,6 +79,37 @@ class DocumentController extends Controller { return null; } + /** + * Log the user with given $userid. + * This function should only be used from public controller methods where no + * existing session exists, for example, when loolwsd is directly calling a + * public method with its own access token. After validating the access + * token, and retrieving the correct user with help of access token, it can + * be set as current user with help of this method. + * + * @param string $userid + */ + private function loginUser($userid) { + $users = \OC::$server->getUserManager()->search($userid, 1, 0); + if (count($users) > 0) { + $user = array_shift($users); + if (strcasecmp($user->getUID(), $userid) === 0) { + // clear the existing sessions, if any + \OC::$server->getSession()->close(); + + // initialize a dummy memory session + $session = new \OC\Session\Memory(''); + // wrap it + $cryptoWrapper = \OC::$server->getSessionCryptoWrapper(); + $session = $cryptoWrapper->wrapSession($session); + // set our session + \OC::$server->setSession($session); + + \OC::$server->getUserSession()->setUser($user); + } + } + } + private function responseError($message, $hint = ''){ $errors = array('errors' => array(array('error' => $message, 'hint' => $hint))); $response = new TemplateResponse('', 'error', $errors, 'error'); @@ -355,8 +386,6 @@ class DocumentController extends Controller { \OC::$server->getLogger()->debug('Generating WOPI Token for file {fileId}, version {version}.', [ 'app' => $this->appName, 'fileId' => $fileId, 'version' => $version ]); - - $row = new Db\Wopi(); $token = $row->generateFileToken($fileId, $version); @@ -503,15 +532,11 @@ class DocumentController extends Controller { // Log-in as the user to regiser the change under her name. $editorid = $res['editor']; - $users = \OC::$server->getUserManager()->search($editorid, 1, 0); - if (count($users) > 0) - { - $user = array_shift($users); - if (strcasecmp($user->getUID(),$editorid) === 0) - { - \OC::$server->getUserSession()->setUser($user); - } - } + // This call is made from loolwsd, so we need to initialize the + // session before we can make the user who opened the document + // login. This is necessary to make activity app register the + // change made to this file under this user's (editorid) name. + $this->loginUser($editorid); // Set up the filesystem view for the owner (where the file actually is). $userid = $res['owner']; @@ -530,6 +555,9 @@ class DocumentController extends Controller { \OC_Util::tearDownFS(); + // clear any session created before + \OC::$server->getSession()->close(); + return array( 'status' => 'success' ); From 82906c7cd735033b462fff04c2411cd93d2534f4 Mon Sep 17 00:00:00 2001 From: Pranav Kant Date: Sun, 17 Jul 2016 00:53:12 +0530 Subject: [PATCH 2/4] No need to make the user login here, and fix incorrect userid Setting up FS is enough to get the correct file version. No need to make the user login here. File version would be in owner's FS, not editor, so s/editorid/ownerid/ --- controller/documentcontroller.php | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/controller/documentcontroller.php b/controller/documentcontroller.php index 87ee52de..56167e8c 100644 --- a/controller/documentcontroller.php +++ b/controller/documentcontroller.php @@ -469,23 +469,11 @@ class DocumentController extends Controller { if ($version !== '0') { \OCP\JSON::checkAppEnabled('files_versions'); - // Login as this user - $editorid = $res['editor']; - $users = \OC::$server->getUserManager()->search($editorid, 1, 0); - if (count($users) > 0) - { - $user = array_shift($users); - if (strcasecmp($user->getUID(),$editorid) === 0) - { - \OC::$server->getUserSession()->setUser($user); - } - } - - \OCP\JSON::checkLoggedIn(); + $ownerid = $res['owner']; // Setup the FS \OC_Util::tearDownFS(); - \OC_Util::setupFS($editorid, '/' . $editorid . '/files'); + \OC_Util::setupFS($ownerid, '/' . $ownerid . '/files'); list($owner_uid, $filename) = \OCA\Files_Versions\Storage::getUidAndFilename($res['path']); $versionName = '/files_versions/' . $filename . '.v' . $version; From ea484bb3575b5775fc4190b633c616ec41e747de Mon Sep 17 00:00:00 2001 From: Pranav Kant Date: Sun, 17 Jul 2016 21:07:59 +0530 Subject: [PATCH 3/4] Fix a typo --- js/documents.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/documents.js b/js/documents.js index 97b77343..218657ef 100644 --- a/js/documents.js +++ b/js/documents.js @@ -393,7 +393,7 @@ var documentsMain = { function (result) { if (!result || result.status === 'error') { if (result && result.message){ - documentsMain.IU.notify(result.message); + documentsMain.UI.notify(result.message); } documentsMain.onEditorShutdown(t('richdocuments', 'Failed to aquire access token. Please re-login and try again.')); return; @@ -755,7 +755,7 @@ var documentsMain = { function(result) { if (result && result.status === 'error') { if (result.message){ - documentsMain.IU.notify(result.message); + documentsMain.UI.notify(result.message); } return; } From 4cda46798e5963af4550f2542292375d2b23b87c Mon Sep 17 00:00:00 2001 From: Pranav Kant Date: Sat, 16 Jul 2016 18:25:14 +0530 Subject: [PATCH 4/4] Add restore button to the revision history sidebar --- css/style.css | 9 +++++++++ js/documents.js | 50 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/css/style.css b/css/style.css index ddac9c16..ee4671c3 100644 --- a/css/style.css +++ b/css/style.css @@ -241,6 +241,10 @@ padding: 10px 0; } +#revisionsContainer li:first-child .restoreVersion{ + display: none; +} + #revisionsContainer a{ padding-left: 15px; } @@ -249,6 +253,11 @@ background-color: rgba(0, 0, 0, 0.1); } +.restoreVersion{ + position: absolute; + right: 15px; +} + #show-more-versions{ width: 100%; padding: 10px; diff --git a/js/documents.js b/js/documents.js index 218657ef..aad8bfa2 100644 --- a/js/documents.js +++ b/js/documents.js @@ -218,6 +218,7 @@ var documentsMain = { revHistoryItemTemplate: '
  • ' + '' + '{{formattedTimestamp}}' + + '' + '' + '
  • ', @@ -281,7 +282,7 @@ var documentsMain = { addRevision: function(fileId, version, relativeTimestamp, documentPath) { var formattedTimestamp = OC.Util.formatDate(parseInt(version) * 1000); var fileName = documentsMain.fileName.substring(0, documentsMain.fileName.indexOf('.')); - var downloadUrl; + var downloadUrl, restoreUrl; if (version === 0) { formattedTimestamp = t('richdocuments', 'Latest revision'); downloadUrl = OC.generateUrl('apps/files/download'+ documentPath); @@ -290,12 +291,16 @@ var documentsMain = { downloadUrl = OC.generateUrl('apps/files_versions/download.php?file={file}&revision={revision}', {file: documentPath, revision: version}); fileId = fileId + '_' + version; + restoreUrl = OC.generateUrl('apps/files_versions/ajax/rollbackVersion.php?file={file}&revision={revision}', + {file: documentPath, revision: version}); } var revHistoryItemTemplate = Handlebars.compile(documentsMain.UI.revHistoryItemTemplate); var html = revHistoryItemTemplate({ downloadUrl: downloadUrl, downloadIconUrl: OC.imagePath('core', 'actions/download'), + restoreUrl: restoreUrl, + restoreIconUrl: OC.imagePath('core', 'actions/history'), relativeTimestamp: relativeTimestamp, formattedTimestamp: formattedTimestamp }); @@ -363,6 +368,42 @@ var documentsMain = { $(e.currentTarget.parentElement).addClass('active'); }); + $('#revisionsContainer').on('click', '.restoreVersion', function(e) { + e.preventDefault(); + + // close the viewer + documentsMain.onCloseViewer(); + + // close the editor + documentsMain.UI.hideEditor(); + + // If there are changes in the opened editor, we need to wait + // for sometime before these changes can be saved and a revision is created for it, + // before restoring to requested version. + documentsMain.overlay.documentOverlay('show'); + setTimeout(function() { + // restore selected version + $.ajax({ + type: 'GET', + url: e.currentTarget.href, + success: function(response) { + if (response.status === 'error') { + documentsMain.UI.notify(t('richdocuments', 'Failed to revert the document to older version')); + } + + // generate file id with returnToDir information in it, if any + var fileid = e.currentTarget.parentElement.dataset.fileid.replace(/_.*/, '') + + (documentsMain.returnToDir ? '_' + documentsMain.returnToDir : ''); + + // load the file again, it should get reverted now + window.location = OC.generateUrl('apps/richdocuments/index#{fileid}', {fileid: fileid}); + window.location.reload(); + documentsMain.overlay.documentOverlay('hide'); + } + }); + }, 1000); + }); + // fake click on first revision (i.e current revision) $('#revisionsContainer li').first().find('.versionPreview').click(); }, @@ -785,7 +826,7 @@ var documentsMain = { }, - onClose: function(force) { + onClose: function() { if (!documentsMain.isEditorMode){ return; } @@ -798,7 +839,7 @@ var documentsMain = { documentsMain.UI.hideEditor(); $('#ocToolbar').remove(); - if (!force && documentsMain.returnToDir) { + if (documentsMain.returnToDir) { window.location = OC.generateUrl('apps/files?dir={dir}', {dir: documentsMain.returnToDir}); } else { documentsMain.show(); @@ -806,9 +847,12 @@ var documentsMain = { }, onCloseViewer: function() { + $('#revisionsContainer *').off(); + $('#revPanelContainer').remove(); $('#revViewerContainer').remove(); documentsMain.isViewerMode = false; + documentsMain.UI.revisionsStart = 0; $('#loleafletframe').focus(); },