From b8917435f37b2c076aaeb27c25d5ca2e4e0eee40 Mon Sep 17 00:00:00 2001 From: Victor Dubiniuk Date: Fri, 18 Sep 2015 00:15:18 +0300 Subject: [PATCH] Use different entry points for users and guests --- appinfo/routes.php | 10 +- controller/sessioncontroller.php | 152 ++++++++++++++++++------------- js/ServerFactory.js | 2 - js/documents.js | 17 +++- lib/db/session.php | 18 ++-- lib/file.php | 60 ++++++------ lib/genesis.php | 12 +-- 7 files changed, 151 insertions(+), 120 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index c765fea6..511105d0 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -19,10 +19,12 @@ $application->registerRoutes($this, [ ['name' => 'user#disconnectUser', 'url' => 'ajax/user/disconnect', 'verb' => 'POST'], ['name' => 'user#disconnectGuest', 'url' => 'ajax/user/disconnectGuest', 'verb' => 'POST'], //session - ['name' => 'session#joinAsUser', 'url' => 'ajax/session/joinasuser/{fileId}', 'verb' => 'POST'], - ['name' => 'session#joinAsGuest', 'url' => 'ajax/session/joinasguest/{token}', 'verb' => 'POST'], - ['name' => 'session#save', 'url' => 'ajax/session/save', 'verb' => 'POST'], - ['name' => 'session#poll', 'url' => 'ajax/otpoll.php', 'verb' => 'POST'], + ['name' => 'session#join', 'url' => 'session/user/join/{fileId}', 'verb' => 'POST'], + ['name' => 'session#poll', 'url' => 'session/user/poll', 'verb' => 'POST'], + ['name' => 'session#save', 'url' => 'session/user/save', 'verb' => 'POST'], + ['name' => 'session#joinAsGuest', 'url' => 'session/guest/join/{token}', 'verb' => 'POST'], + ['name' => 'session#pollAsGuest', 'url' => 'session/guest/poll/{token}', 'verb' => 'POST'], + ['name' => 'session#saveAsGuest', 'url' => 'session/guest/save/{token}', 'verb' => 'POST'], //documents ['name' => 'document#index', 'url' => 'index', 'verb' => 'GET'], ['name' => 'document#create', 'url' => 'ajax/documents/create', 'verb' => 'POST'], diff --git a/controller/sessioncontroller.php b/controller/sessioncontroller.php index 2c806e17..f8c6b6e7 100644 --- a/controller/sessioncontroller.php +++ b/controller/sessioncontroller.php @@ -16,7 +16,6 @@ use \OCP\IRequest; use \OCP\AppFramework\Http; use \OCP\AppFramework\Http\JSONResponse; - use \OCA\Documents\Db; use \OCA\Documents\File; use \OCA\Documents\Helper; @@ -40,6 +39,7 @@ class SessionController extends Controller{ protected $uid; protected $logger; + protected $shareToken; public function __construct($appName, IRequest $request, $logger, $uid){ parent::__construct($appName, $request); @@ -62,13 +62,11 @@ class SessionController extends Controller{ $response = array_merge( Db\Session::start($uid, $file), - array('status'=>'success') + [ 'status'=>'success' ] ); } catch (\Exception $e){ - $this->logger->warning('Starting a session failed. Reason: ' . $e->getMessage(), array('app' => $this->appName)); - $response = array ( - 'status'=>'error' - ); + $this->logger->warning('Starting a session failed. Reason: ' . $e->getMessage(), ['app' => $this->appName]); + $response = [ 'status'=>'error' ]; } return $response; @@ -76,8 +74,27 @@ class SessionController extends Controller{ /** * @NoAdminRequired + * @PublicPage + */ + public function pollAsGuest($command, $args){ + $this->shareToken = $this->request->getParam('token'); + return $this->poll($command, $args); + } + + /** + * Store the document content to its origin + * @NoAdminRequired + * @PublicPage */ - public function joinAsUser($fileId){ + public function saveAsGuest(){ + $this->shareToken = $this->request->getParam('token'); + return $this->save(); + } + + /** + * @NoAdminRequired + */ + public function join($fileId){ try { $view = \OC\Files\Filesystem::getView(); $path = $view->getPath($fileId); @@ -87,20 +104,18 @@ class SessionController extends Controller{ $response = Db\Session::start($this->uid, $file); } else { $info = $view->getFileInfo($path); - $response = array( + $response = [ 'permissions' => $info['permissions'], 'id' => $fileId - ); + ]; } $response = array_merge( $response, - array('status'=>'success') + [ 'status'=>'success' ] ); } catch (\Exception $e){ - $this->logger->warning('Starting a session failed. Reason: ' . $e->getMessage(), array('app' => $this->appName)); - $response = array ( - 'status'=>'error' - ); + $this->logger->warning('Starting a session failed. Reason: ' . $e->getMessage(), [ 'app' => $this->appName ]); + $response = [ 'status'=>'error' ]; } return $response; @@ -108,41 +123,24 @@ class SessionController extends Controller{ /** * @NoAdminRequired - * @PublicPage */ public function poll($command, $args){ $response = new JSONResponse(); try{ $esId = isset($args['es_id']) ? $args['es_id'] : null; - - $session = new Db\Session(); - $session->load($esId); + $session = $this->loadSession($esId); $memberId = isset($args['member_id']) ? $args['member_id'] : null; - $member = new Db\Member(); - $member->load($memberId); - - if (!$member->getIsGuest()){ - \OCP\JSON::checkLoggedIn(); - } + $member = $this->loadMember($memberId); - try { - new File($session->getFileId()); - } catch (\Exception $e){ - $this->logger->warning('Error. Session no longer exists. ' . $e->getMessage(), array('app' => $this->appName)); - $ex = new BadRequestException(); - $ex->setBody( - implode(',', $this->request->getParams()) - ); - throw $ex; - } + $this->validateSession($session); switch ($command){ case 'sync_ops': $seqHead = (string) isset($args['seq_head']) ? $args['seq_head'] : null; if (!is_null($seqHead)){ - $ops = isset($args['client_ops']) ? $args['client_ops'] : array(); + $ops = isset($args['client_ops']) ? $args['client_ops'] : []; $op = new Db\Op(); $currentHead = $op->getHeadSeq($esId); @@ -177,33 +175,24 @@ class SessionController extends Controller{ } catch (BadRequestException $e){ $response->setStatus(Http::STATUS_BAD_REQUEST); $response->setData( - array('err' => 'bad request:[' . $e->getBody() . ']') + [ 'err' => 'bad request:[' . $e->getBody() . ']' ] ); } return $response; } - /** - * @NoAdminRequired - * @PublicPage * Store the document content to its origin + * @NoAdminRequired */ public function save(){ + $response = new JSONResponse(); try { $esId = $this->request->server['HTTP_WEBODF_SESSION_ID']; - if (!$esId){ - throw new \Exception('Session id can not be empty'); - } + $session = $this->loadSession($esId); $memberId = $this->request->server['HTTP_WEBODF_MEMBER_ID']; - $currentMember = new Db\Member(); - $currentMember->load($memberId); - - //check if member belongs to the session - if ($esId != $currentMember->getEsId()){ - throw new \Exception($memberId . ' does not belong to session ' . $esId); - } + $currentMember = $this->loadMember($memberId, $esId); // Extra info for future usage // $sessionRevision = $this->request->server['HTTP_WEBODF_SESSION_REVISION']; @@ -215,13 +204,6 @@ class SessionController extends Controller{ } $content = stream_get_contents($stream); - $session = new Db\Session(); - $session->load($esId); - - if (!$session->getEsId()){ - throw new \Exception('Session does not exist'); - } - try { if ($currentMember->getIsGuest()){ $file = File::getByShareToken($currentMember->getToken()); @@ -229,7 +211,8 @@ class SessionController extends Controller{ $file = new File($session->getFileId()); } - list($view, $path) = $file->getOwnerViewAndPath(true); + $view = $file->getOwnerView(true); + $path = $file->getPath(true); } catch (\Exception $e){ //File was deleted or unshared. We need to save content as new file anyway //Sorry, but for guests it would be lost :( @@ -256,7 +239,7 @@ class SessionController extends Controller{ $memberCount = count($memberIds) - 1; if ($view->file_exists($path)){ - $currentHash = sha1($view->file_get_contents($path)); + $currentHash = $view->hash('sha1', $path, false); if (!Helper::isVersionsEnabled() && $currentHash !== $session->getGenesisHash()){ // Original file was modified externally. Save to a new one @@ -274,7 +257,7 @@ class SessionController extends Controller{ // Not a last user if ($memberCount>0){ // Update genesis hash to prevent conflicts - $this->logger->debug('Update hash', array('app' => $this->appName)); + $this->logger->debug('Update hash', [ 'app' => $this->appName ]); $session->updateGenesisHash($esId, sha1($data['content'])); } else { // Last user. Kill session data @@ -283,13 +266,56 @@ class SessionController extends Controller{ $view->touch($path); } - $response = array('status'=>'success'); + $response->setData(['status'=>'success']); } catch (\Exception $e){ - $this->logger->warning('Saving failed. Reason:' . $e->getMessage(), array('app' => $this->appName)); - \OC_Response::setStatus(500); - $response = array(); + $response->setStatus(Http::STATUS_INTERNAL_SERVER_ERROR); + $response->setData([]); + $this->logger->warning('Saving failed. Reason:' . $e->getMessage(), [ 'app' => $this->appName ]); } return $response; } + + protected function validateSession($session){ + try { + if (is_null($this->shareToken)) { + new File($session->getFileId()); + } else { + File::getByShareToken($this->shareToken); + } + } catch (\Exception $e){ + $this->logger->warning('Error. Session no longer exists. ' . $e->getMessage(), [ 'app' => $this->appName ]); + $ex = new BadRequestException(); + $ex->setBody( + implode(',', $this->request->getParams()) + ); + throw $ex; + } + } + + protected function loadSession($esId){ + if (!$esId){ + throw new \Exception('Session id can not be empty'); + } + + $session = new Db\Session(); + $session->load($esId); + if (!$session->getEsId()){ + throw new \Exception('Session does not exist'); + } + return $session; + } + + protected function loadMember($memberId, $expectedEsId = null){ + if (!$memberId){ + throw new \Exception('Member id can not be empty'); + } + $member = new Db\Member(); + $member->load($memberId); + //check if member belongs to the session + if (!is_null($expectedEsId) && $expectedEsId !== $member->getEsId()){ + throw new \Exception($memberId . ' does not belong to session ' . $expectedEsId); + } + return $member; + } } diff --git a/js/ServerFactory.js b/js/ServerFactory.js index 8f5c98d2..b3f5066b 100644 --- a/js/ServerFactory.js +++ b/js/ServerFactory.js @@ -40,8 +40,6 @@ define("owncloud/ServerFactory", [ this.createServer = function (args) { var server; args = args || {}; - args.url = OC.filePath('documents', 'ajax', 'otpoll.php'); - args.sessionStateToFileUrl = OC.generateUrl('apps/documents/ajax/session/save'); server = new PullBoxServer(args); server.getGenesisUrl = function(sid) { diff --git a/js/documents.js b/js/documents.js index 0bcf1943..9de2211b 100644 --- a/js/documents.js +++ b/js/documents.js @@ -328,6 +328,13 @@ var documentsMain = { return; } + var pollUrl = documentsMain.isGuest + ? OC.generateUrl('apps/documents/session/guest/poll/{token}', {'token' : $("[name='document']").val()}) + : OC.generateUrl('apps/documents/session/user/poll'), + saveUrl = documentsMain.isGuest + ? OC.generateUrl('apps/documents/session/guest/save/{token}', {'token' : $("[name='document']").val()}) + : OC.generateUrl('apps/documents/session/user/save') + ; documentsMain.canShare = !documentsMain.isGuest && typeof OC.Share !== 'undefined' && response.permissions & OC.PERMISSION_SHARE; require({ }, ["owncloud/ServerFactory", "webodf/editor/Editor"], function (ServerFactory, Editor) { @@ -347,8 +354,10 @@ var documentsMain = { documentsMain.memberId = response.member_id; // TODO: set webodf translation system, by passing a proper function translate(!string):!string in "runtime.setTranslator(translate);" - - documentsMain.webodfServerInstance = serverFactory.createServer(); + documentsMain.webodfServerInstance = serverFactory.createServer({ + url : pollUrl, + sessionStateToFileUrl : saveUrl + }); documentsMain.webodfServerInstance.setToken(oc_requesttoken); documentsMain.webodfEditorInstance = new Editor( { @@ -383,9 +392,9 @@ var documentsMain = { console.log('joining session '+fileId); var url; if (documentsMain.isGuest){ - url = OC.generateUrl('apps/documents/ajax/session/joinasguest/{token}', {token: fileId}); + url = OC.generateUrl('apps/documents/session/guest/join/{token}', {token: fileId}); } else { - url = OC.generateUrl('apps/documents/ajax/session/joinasuser/{file_id}', {file_id: fileId}); + url = OC.generateUrl('apps/documents/session/user/join/{file_id}', {file_id: fileId}); } $.post( url, diff --git a/lib/db/session.php b/lib/db/session.php index cbfe0cb8..31b2ab30 100644 --- a/lib/db/session.php +++ b/lib/db/session.php @@ -49,12 +49,7 @@ class Session extends \OCA\Documents\Db { public static function start($uid, $file){ // Create a directory to store genesis $genesis = new \OCA\Documents\Genesis($file); - - list($ownerView, $path) = $file->getOwnerViewAndPath(); - $mimetype = $ownerView->getMimeType($path); - if (!Filter::isSupportedMimetype($mimetype)){ - throw new \Exception( $path . ' is ' . $mimetype . ' and is not supported by Documents app'); - } + $oldSession = new Session(); $oldSession->loadBy('file_id', $file->getFileId()); @@ -78,14 +73,14 @@ class Session extends \OCA\Documents\Db { ; $memberColor = \OCA\Documents\Helper::getMemberColor($uid); - $member = new \OCA\Documents\Db\Member(array( + $member = new \OCA\Documents\Db\Member([ $sessionData['es_id'], $uid, $memberColor, time(), intval($file->isPublicShare()), $file->getToken() - )); + ]); if (!$member->insert()){ throw new \Exception('Failed to add member into database'); @@ -113,10 +108,9 @@ class Session extends \OCA\Documents\Db { $memberColor, $imageUrl ); - - $sessionData['title'] = basename($path); - $fileInfo = $ownerView->getFileInfo($path); - $sessionData['permissions'] = $fileInfo->getPermissions(); + + $sessionData['title'] = basename($file->getPath()); + $sessionData['permissions'] = $file->getPermissions(); return $sessionData; } diff --git a/lib/file.php b/lib/file.php index 58542673..fb6ae9fb 100644 --- a/lib/file.php +++ b/lib/file.php @@ -28,14 +28,14 @@ class File { protected $fileId; protected $owner; protected $sharing; - protected $token =''; + protected $token; protected $passwordProtected = false; protected $ownerView; protected $ownerViewFiles; protected $path; protected $pathFiles; - public function __construct($fileId, $shareOps = null, $token = null){ + public function __construct($fileId, $shareOps = null, $token = ''){ if (!$fileId){ throw new \Exception('No valid file has been passed'); } @@ -43,6 +43,22 @@ class File { $this->fileId = $fileId; $this->sharing = $shareOps; $this->token = $token; + + if ($this->isPublicShare()) { + if (isset($this->sharing['uid_owner'])){ + $this->owner = $this->sharing['uid_owner']; + if (!\OC::$server->getUserManager()->userExists($this->sharing['uid_owner'])) { + throw new \Exception('Share owner' . $this->sharing['uid_owner'] . ' does not exist '); + } + + \OC_Util::tearDownFS(); + \OC_Util::setupFS($this->sharing['uid_owner']); + } else { + throw new \Exception($this->fileId . ' is a broken share'); + } + } else { + $this->owner = \OC::$server->getUserSession()->getUser()->getUID(); + } $this->initViews(); } @@ -131,15 +147,6 @@ class File { public function setPasswordProtected($value){ $this->passwordProtected = $value; } - - /** - * - * @return string owner of the current file item - * @throws \Exception - */ - public function getOwnerViewAndPath($useDefaultRoot = false){ - return $useDefaultRoot ? [$this->ownerViewFiles, $this->pathFiles] : [$this->ownerView, $this->path]; - } public function getOwner(){ return $this->owner; @@ -153,23 +160,12 @@ class File { return $relativeToFiles ? $this->pathFiles : $this->path; } + public function getPermissions(){ + $fileInfo = $this->ownerView->getFileInfo($this->path); + return $fileInfo->getPermissions(); + } + protected function initViews(){ - if ($this->isPublicShare()) { - if (isset($this->sharing['uid_owner'])){ - $this->owner = $this->sharing['uid_owner']; - if (!\OC::$server->getUserManager()->userExists($this->sharing['uid_owner'])) { - throw new \Exception('Share owner' . $this->sharing['uid_owner'] . ' does not exist '); - } - - \OC_Util::tearDownFS(); - \OC_Util::setupFS($this->sharing['uid_owner']); - } else { - throw new \Exception($this->fileId . ' is a broken share'); - } - } else { - $this->owner = \OC::$server->getUserSession()->getUser()->getUID(); - } - $this->ownerView = new View('/' . $this->owner); $this->ownerViewFiles = new View('/' . $this->owner . '/files'); $this->path = $this->ownerView->getPath($this->fileId); @@ -186,6 +182,16 @@ class File { if (!$this->ownerViewFiles->file_exists($this->pathFiles)) { throw new \Exception($this->pathFiles . ' doesn\'t exist'); } + + if (!$this->ownerView->is_file($this->path)){ + throw new \Exception('Object ' . $this->path . ' is not a file.'); + } + //TODO check if it is a valid odt + + $mimetype = $this->ownerView->getMimeType($this->path); + if (!Filter::isSupportedMimetype($mimetype)){ + throw new \Exception( $this->path . ' is ' . $mimetype . ' and is not supported by Documents app'); + } } protected function getPassword(){ diff --git a/lib/genesis.php b/lib/genesis.php index a74d3274..345e7a78 100644 --- a/lib/genesis.php +++ b/lib/genesis.php @@ -40,7 +40,8 @@ class Genesis { * @param File $file * */ public function __construct(File $file){ - list($view, $path) = $file->getOwnerViewAndPath(); + $view = $file->getOwnerView(); + $path = $file->getPath(); $owner = $file->getOwner(); $this->view = new View('/' . $owner); @@ -48,8 +49,9 @@ class Genesis { if (!$this->view->file_exists(self::DOCUMENTS_DIRNAME)){ $this->view->mkdir(self::DOCUMENTS_DIRNAME ); } + $this->validate($view, $path); - $this->hash = $this->getDocumentHash($view, $path); + $this->hash = $view->hash('sha1', $path, false); $this->path = self::DOCUMENTS_DIRNAME . '/' . $this->hash . '.odt'; if (!$this->view->file_exists($this->path)){ //copy new genesis to /user/documents/{hash}.odt @@ -76,12 +78,6 @@ class Genesis { return $this->hash; } - protected function getDocumentHash($view, $path){ - $this->validate($view, $path); - $hash = sha1($view->file_get_contents($path)); - return $hash; - } - /** * Check if genesis is valid * @param \OC\Files\View $view