diff --git a/apps/files_encryption/lib/hooks.php b/apps/files_encryption/lib/hooks.php index bddfb7b254..7ddde0a311 100644 --- a/apps/files_encryption/lib/hooks.php +++ b/apps/files_encryption/lib/hooks.php @@ -25,8 +25,6 @@ namespace OCA\Files_Encryption; -use OC\Files\Filesystem; - /** * Class for hook specific logic */ @@ -364,15 +362,16 @@ class Hooks { if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') { $view = new \OC\Files\View('/'); - $userId = \OCP\User::getUser(); + $userId = $params['uidOwner']; + $userView = new \OC\Files\View('/' . $userId . '/files'); $util = new Util($view, $userId); - $path = \OC\Files\Filesystem::getPath($params['fileSource']); + $path = $userView->getPath($params['fileSource']); // for group shares get a list of the group members if ($params['shareType'] === \OCP\Share::SHARE_TYPE_GROUP) { $userIds = \OC_Group::usersInGroup($params['shareWith']); } else { - if ($params['shareType'] === \OCP\Share::SHARE_TYPE_LINK) { + if ($params['shareType'] === \OCP\Share::SHARE_TYPE_LINK || $params['shareType'] === \OCP\Share::SHARE_TYPE_REMOTE) { $userIds = array($util->getPublicShareKeyId()); } else { $userIds = array($params['shareWith']); @@ -619,8 +618,8 @@ class Hooks { // check if the user still has access to the file, otherwise delete share key $sharingUsers = \OCP\Share::getUsersSharingFile($path, $user); - if (!in_array(\OCP\User::getUser(), $sharingUsers['users'])) { - Keymanager::delShareKey($view, array(\OCP\User::getUser()), $keyPath, $owner, $ownerPath); + if (!in_array($user, $sharingUsers['users'])) { + Keymanager::delShareKey($view, array($user), $keyPath, $owner, $ownerPath); } } diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index ad6948b95a..4aaf7aa257 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -1207,13 +1207,7 @@ class Util { // handle public access if ($this->isPublic) { - $filename = $path; - $fileOwnerUid = $this->userId; - - return array( - $fileOwnerUid, - $filename - ); + return array($this->userId, $path); } else { // Check that UID is valid diff --git a/apps/files_encryption/tests/share.php b/apps/files_encryption/tests/share.php index 9c53c73aaf..d29e6a191c 100755 --- a/apps/files_encryption/tests/share.php +++ b/apps/files_encryption/tests/share.php @@ -115,6 +115,91 @@ class Share extends TestCase { parent::tearDownAfterClass(); } + /** + * @medium + */ + function testDeclineServer2ServerShare() { + + $config = $this->getMockBuilder('\OCP\IConfig') + ->disableOriginalConstructor()->getMock(); + $certificateManager = $this->getMock('\OCP\ICertificateManager'); + $httpHelperMock = $this->getMockBuilder('\OC\HTTPHelper') + ->setConstructorArgs(array($config, $certificateManager)) + ->getMock(); + $httpHelperMock->expects($this->once())->method('post')->with($this->anything())->will($this->returnValue(true)); + + self::loginHelper(self::TEST_ENCRYPTION_SHARE_USER1); + + // save file with content + $cryptedFile = file_put_contents('crypt:///' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename, $this->dataShort); + + // test that data was successfully written + $this->assertTrue(is_int($cryptedFile)); + + // get the file info from previous created file + $fileInfo = $this->view->getFileInfo( + '/' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename); + + + // share the file + $token = \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, '', \OCP\Constants::PERMISSION_ALL); + $this->assertTrue(is_string($token)); + + $publicShareKeyId = \OC::$server->getConfig()->getAppValue('files_encryption', 'publicShareKeyId'); + + // check if share key for public exists + $this->assertTrue($this->view->file_exists( + '/' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/keys/' + . $this->filename . '/' . $publicShareKeyId . '.shareKey')); + + // manipulate share + $query = \OC::$server->getDatabaseConnection()->prepare('UPDATE `*PREFIX*share` SET `share_type` = ?, `share_with` = ? WHERE `token`=?'); + $this->assertTrue($query->execute(array(\OCP\Share::SHARE_TYPE_REMOTE, 'foo@bar', $token))); + + // check if share key not exists + $this->assertTrue($this->view->file_exists( + '/' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/keys/' + . $this->filename . '/' . $publicShareKeyId . '.shareKey')); + + + $query = \OC::$server->getDatabaseConnection()->prepare('SELECT * FROM `*PREFIX*share` WHERE `token`=?'); + $query->execute(array($token)); + + $share = $query->fetch(); + + $this->registerHttpHelper($httpHelperMock); + $_POST['token'] = $token; + $s2s = new \OCA\Files_Sharing\API\Server2Server(); + $s2s->declineShare(array('id' => $share['id'])); + $this->restoreHttpHelper(); + + $this->assertFalse($this->view->file_exists( + '/' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/keys/' + . $this->filename . '/' . $publicShareKeyId . '.shareKey')); + + } + + + /** + * Register an http helper mock for testing purposes. + * @param $httpHelper http helper mock + */ + private function registerHttpHelper($httpHelper) { + $this->oldHttpHelper = \OC::$server->query('HTTPHelper'); + \OC::$server->registerService('HTTPHelper', function ($c) use ($httpHelper) { + return $httpHelper; + }); + } + + /** + * Restore the original http helper + */ + private function restoreHttpHelper() { + $oldHttpHelper = $this->oldHttpHelper; + \OC::$server->registerService('HTTPHelper', function ($c) use ($oldHttpHelper) { + return $oldHttpHelper; + }); + } /** * @medium @@ -285,7 +370,7 @@ class Share extends TestCase { // save file with content $cryptedFile = file_put_contents('crypt:///' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' - . $this->filename, $this->dataShort); + . $this->filename, $this->dataShort); // test that data was successfully written $this->assertTrue(is_int($cryptedFile)); @@ -677,7 +762,7 @@ class Share extends TestCase { // save file with content $cryptedFile1 = file_put_contents('crypt:///' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename, $this->dataShort); $cryptedFile2 = file_put_contents('crypt:///' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' - . $this->filename, $this->dataShort); + . $this->filename, $this->dataShort); // test that data was successfully written $this->assertTrue(is_int($cryptedFile1)); @@ -784,7 +869,7 @@ class Share extends TestCase { // save file with content $cryptedFile1 = file_put_contents('crypt:///' . self::TEST_ENCRYPTION_SHARE_USER2. '/files/' . $this->filename, $this->dataShort); $cryptedFile2 = file_put_contents('crypt:///' . self::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->folder1 . $this->subfolder . $this->subsubfolder . '/' - . $this->filename, $this->dataShort); + . $this->filename, $this->dataShort); // test that data was successfully written $this->assertTrue(is_int($cryptedFile1)); @@ -925,8 +1010,8 @@ class Share extends TestCase { // remove share file $this->view->unlink('/' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/keys/' - . $this->filename . '/' . self::TEST_ENCRYPTION_SHARE_USER3 - . '.shareKey'); + . $this->filename . '/' . self::TEST_ENCRYPTION_SHARE_USER3 + . '.shareKey'); // re-enable the file proxy \OC_FileProxy::$enabled = $proxyStatus; @@ -990,7 +1075,7 @@ class Share extends TestCase { // move the file to a subfolder $this->view->rename('/' . self::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->filename, - '/' . self::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->folder1 . $this->filename); + '/' . self::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->folder1 . $this->filename); // check if we can read the moved file $retrievedRenamedFile = $this->view->file_get_contents( @@ -1122,4 +1207,4 @@ class Share extends TestCase { \OC\Files\Filesystem::unlink($folder); } -} +} \ No newline at end of file diff --git a/apps/files_sharing/ajax/external.php b/apps/files_sharing/ajax/external.php index 6d68b5f7f8..1a709eda07 100644 --- a/apps/files_sharing/ajax/external.php +++ b/apps/files_sharing/ajax/external.php @@ -31,10 +31,11 @@ if(!\OCP\Util::isValidFileName($name)) { } $externalManager = new \OCA\Files_Sharing\External\Manager( - \OC::$server->getDatabaseConnection(), - \OC\Files\Filesystem::getMountManager(), - \OC\Files\Filesystem::getLoader(), - \OC::$server->getUserSession() + \OC::$server->getDatabaseConnection(), + \OC\Files\Filesystem::getMountManager(), + \OC\Files\Filesystem::getLoader(), + \OC::$server->getUserSession(), + \OC::$server->getHTTPHelper() ); $name = OCP\Files::buildNotExistingFileName('/', $name); @@ -44,7 +45,7 @@ if (substr($remote, 0, 5) === 'https' and !OC_Util::getUrlContent($remote)) { \OCP\JSON::error(array('data' => array('message' => $l->t("Invalid or untrusted SSL certificate")))); exit; } else { - $mount = $externalManager->addShare($remote, $token, $password, $name, $owner); + $mount = $externalManager->addShare($remote, $token, $password, $name, $owner, true); /** * @var \OCA\Files_Sharing\External\Storage $storage */ diff --git a/apps/files_sharing/api/server2server.php b/apps/files_sharing/api/server2server.php index 2949e2dd09..f78d64caa7 100644 --- a/apps/files_sharing/api/server2server.php +++ b/apps/files_sharing/api/server2server.php @@ -34,7 +34,7 @@ class Server2Server { public function createShare($params) { if (!$this->isS2SEnabled(true)) { - return \OC_OCS_Result(null, 503, 'Server does not support server-to-server sharing'); + return new \OC_OCS_Result(null, 503, 'Server does not support server-to-server sharing'); } $remote = isset($_POST['remote']) ? $_POST['remote'] : null; @@ -42,7 +42,7 @@ class Server2Server { $name = isset($_POST['name']) ? $_POST['name'] : null; $owner = isset($_POST['owner']) ? $_POST['owner'] : null; $shareWith = isset($_POST['shareWith']) ? $_POST['shareWith'] : null; - $remoteId = isset($_POST['remote_id']) ? (int)$_POST['remote_id'] : null; + $remoteId = isset($_POST['remoteId']) ? (int)$_POST['remoteId'] : null; if ($remote && $token && $name && $owner && $remoteId && $shareWith) { @@ -56,19 +56,28 @@ class Server2Server { \OC_Util::setupFS($shareWith); - $mountPoint = \OC\Files\Filesystem::normalizePath('/' . $name); + $externalManager = new \OCA\Files_Sharing\External\Manager( + \OC::$server->getDatabaseConnection(), + \OC\Files\Filesystem::getMountManager(), + \OC\Files\Filesystem::getLoader(), + \OC::$server->getUserSession(), + \OC::$server->getHTTPHelper()); + $name = \OCP\Files::buildNotExistingFileName('/', $name); try { - \OCA\Files_Sharing\Helper::addServer2ServerShare($remote, $token, $name, $mountPoint, $owner, $shareWith, '', $remoteId); + $externalManager->addShare($remote, $token, '', $name, $owner, false, $shareWith, $remoteId); + + $user = $owner . '@' . $this->cleanupRemote($remote); \OC::$server->getActivityManager()->publishActivity( - 'files_sharing', \OCA\Files_Sharing\Activity::SUBJECT_REMOTE_SHARE_RECEIVED, array($owner), '', array(), - '', '', $shareWith, \OCA\Files_Sharing\Activity::TYPE_REMOTE_SHARE, \OCA\Files_Sharing\Activity::PRIORITY_LOW); + 'files_sharing', \OCA\Files_Sharing\Activity::SUBJECT_REMOTE_SHARE_RECEIVED, array($user), '', array(), + '', '', $shareWith, \OCA\Files_Sharing\Activity::TYPE_REMOTE_SHARE, \OCA\Files_Sharing\Activity::PRIORITY_LOW); return new \OC_OCS_Result(); } catch (\Exception $e) { - return new \OC_OCS_Result(null, 500, 'server can not add remote share, ' . $e->getMessage()); + \OCP\Util::writeLog('files_sharing', 'server can not add remote share, ' . $e->getMessage(), \OCP\Util::ERROR); + return new \OC_OCS_Result(null, 500, 'internal server error, was not able to add share from ' . $remote); } } @@ -84,7 +93,7 @@ class Server2Server { public function acceptShare($params) { if (!$this->isS2SEnabled()) { - return \OC_OCS_Result(null, 503, 'Server does not support server-to-server sharing'); + return new \OC_OCS_Result(null, 503, 'Server does not support server-to-server sharing'); } $id = $params['id']; @@ -95,8 +104,8 @@ class Server2Server { list($file, $link) = self::getFile($share['uid_owner'], $share['file_source']); \OC::$server->getActivityManager()->publishActivity( - 'files_sharing', \OCA\Files_Sharing\Activity::SUBJECT_REMOTE_SHARE_ACCEPTED, array($share['share_with'], basename($file)), '', array(), - $file, $link, $share['uid_owner'], \OCA\Files_Sharing\Activity::TYPE_REMOTE_SHARE, \OCA\Files_Sharing\Activity::PRIORITY_LOW); + 'files_sharing', \OCA\Files_Sharing\Activity::SUBJECT_REMOTE_SHARE_ACCEPTED, array($share['share_with'], basename($file)), '', array(), + $file, $link, $share['uid_owner'], \OCA\Files_Sharing\Activity::TYPE_REMOTE_SHARE, \OCA\Files_Sharing\Activity::PRIORITY_LOW); } return new \OC_OCS_Result(); @@ -111,7 +120,7 @@ class Server2Server { public function declineShare($params) { if (!$this->isS2SEnabled()) { - return \OC_OCS_Result(null, 503, 'Server does not support server-to-server sharing'); + return new \OC_OCS_Result(null, 503, 'Server does not support server-to-server sharing'); } $id = $params['id']; @@ -126,8 +135,8 @@ class Server2Server { list($file, $link) = $this->getFile($share['uid_owner'], $share['file_source']); \OC::$server->getActivityManager()->publishActivity( - 'files_sharing', \OCA\Files_Sharing\Activity::SUBJECT_REMOTE_SHARE_DECLINED, array($share['share_with'], basename($file)), '', array(), - $file, $link, $share['uid_owner'], \OCA\Files_Sharing\Activity::TYPE_REMOTE_SHARE, \OCA\Files_Sharing\Activity::PRIORITY_LOW); + 'files_sharing', \OCA\Files_Sharing\Activity::SUBJECT_REMOTE_SHARE_DECLINED, array($share['share_with'], basename($file)), '', array(), + $file, $link, $share['uid_owner'], \OCA\Files_Sharing\Activity::TYPE_REMOTE_SHARE, \OCA\Files_Sharing\Activity::PRIORITY_LOW); } return new \OC_OCS_Result(); @@ -142,7 +151,7 @@ class Server2Server { public function unshare($params) { if (!$this->isS2SEnabled()) { - return \OC_OCS_Result(null, 503, 'Server does not support server-to-server sharing'); + return new \OC_OCS_Result(null, 503, 'Server does not support server-to-server sharing'); } $id = $params['id']; @@ -154,7 +163,9 @@ class Server2Server { if ($token && $id && !empty($share)) { - $owner = $share['owner'] . '@' . $share['remote']; + $remote = $this->cleanupRemote($share['remote']); + + $owner = $share['owner'] . '@' . $remote; $mountpoint = $share['mountpoint']; $user = $share['user']; @@ -162,13 +173,19 @@ class Server2Server { $query->execute(array($id, $token)); \OC::$server->getActivityManager()->publishActivity( - 'files_sharing', \OCA\Files_Sharing\Activity::SUBJECT_REMOTE_SHARE_DECLINED, array($owner, $mountpoint), '', array(), - '', '', $user, \OCA\Files_Sharing\Activity::TYPE_REMOTE_SHARE, \OCA\Files_Sharing\Activity::PRIORITY_MEDIUM); + 'files_sharing', \OCA\Files_Sharing\Activity::SUBJECT_REMOTE_SHARE_UNSHARED, array($owner, $mountpoint), '', array(), + '', '', $user, \OCA\Files_Sharing\Activity::TYPE_REMOTE_SHARE, \OCA\Files_Sharing\Activity::PRIORITY_MEDIUM); } return new \OC_OCS_Result(); } + private function cleanupRemote($remote) { + $remote = substr($remote, strpos($remote, '://') + 3); + + return rtrim($remote, '/'); + } + /** * get share * diff --git a/apps/files_sharing/appinfo/routes.php b/apps/files_sharing/appinfo/routes.php index 41bdf554fc..dd9509575b 100644 --- a/apps/files_sharing/appinfo/routes.php +++ b/apps/files_sharing/appinfo/routes.php @@ -1,5 +1,16 @@ registerRoutes($this, [ + 'resources' => [ + 'ExternalShares' => ['url' => '/api/externalShares'], + ] +]); + /** @var $this \OCP\Route\IRouter */ $this->create('core_ajax_public_preview', '/publicpreview')->action( function() { @@ -16,31 +27,32 @@ $this->create('sharing_external_add', '/external') ->actionInclude('files_sharing/ajax/external.php'); $this->create('sharing_external_test_remote', '/testremote') ->actionInclude('files_sharing/ajax/testremote.php'); + // OCS API //TODO: SET: mail notification, waiting for PR #4689 to be accepted -OC_API::register('get', +\OC_API::register('get', '/apps/files_sharing/api/v1/shares', array('\OCA\Files_Sharing\API\Local', 'getAllShares'), 'files_sharing'); -OC_API::register('post', +\OC_API::register('post', '/apps/files_sharing/api/v1/shares', array('\OCA\Files_Sharing\API\Local', 'createShare'), 'files_sharing'); -OC_API::register('get', +\OC_API::register('get', '/apps/files_sharing/api/v1/shares/{id}', array('\OCA\Files_Sharing\API\Local', 'getShare'), 'files_sharing'); -OC_API::register('put', +\OC_API::register('put', '/apps/files_sharing/api/v1/shares/{id}', array('\OCA\Files_Sharing\API\Local', 'updateShare'), 'files_sharing'); -OC_API::register('delete', +\OC_API::register('delete', '/apps/files_sharing/api/v1/shares/{id}', array('\OCA\Files_Sharing\API\Local', 'deleteShare'), 'files_sharing'); diff --git a/apps/files_sharing/application.php b/apps/files_sharing/application.php index 089ed6afbd..9fe8785df0 100644 --- a/apps/files_sharing/application.php +++ b/apps/files_sharing/application.php @@ -11,6 +11,7 @@ namespace OCA\Files_Sharing; use OC\AppFramework\Utility\SimpleContainer; +use OCA\Files_Sharing\Controllers\ExternalSharesController; use OCA\Files_Sharing\Controllers\ShareController; use OCA\Files_Sharing\Middleware\SharingCheckMiddleware; use \OCP\AppFramework\App; @@ -44,6 +45,14 @@ class Application extends App { $c->query('ServerContainer')->getLogger() ); }); + $container->registerService('ExternalSharesController', function(SimpleContainer $c) { + return new ExternalSharesController( + $c->query('AppName'), + $c->query('Request'), + $c->query('IsIncomingShareEnabled'), + $c->query('ExternalManager') + ); + }); /** * Core class wrappers @@ -54,6 +63,18 @@ class Application extends App { $container->registerService('URLGenerator', function(SimpleContainer $c) { return $c->query('ServerContainer')->getUrlGenerator(); }); + $container->registerService('IsIncomingShareEnabled', function(SimpleContainer $c) { + return Helper::isIncomingServer2serverShareEnabled(); + }); + $container->registerService('ExternalManager', function(SimpleContainer $c) { + return new \OCA\Files_Sharing\External\Manager( + \OC::$server->getDatabaseConnection(), + \OC\Files\Filesystem::getMountManager(), + \OC\Files\Filesystem::getLoader(), + \OC::$server->getUserSession(), + \OC::$server->getHTTPHelper() + ); + }); /** * Middleware diff --git a/apps/files_sharing/js/external.js b/apps/files_sharing/js/external.js index 6ede2584cd..aeb4b2461f 100644 --- a/apps/files_sharing/js/external.js +++ b/apps/files_sharing/js/external.js @@ -8,16 +8,6 @@ * */ (function () { - var addExternalShare = function (remote, token, owner, name, password) { - return $.post(OC.generateUrl('apps/files_sharing/external'), { - remote: remote, - token: token, - owner: owner, - name: name, - password: password - }); - }; - /** * Shows "add external share" dialog. * @@ -27,20 +17,12 @@ * @param {String} token authentication token * @param {bool} passwordProtected true if the share is password protected */ - OCA.Sharing.showAddExternalDialog = function (remote, token, owner, name, passwordProtected) { + OCA.Sharing.showAddExternalDialog = function (share, passwordProtected, callback) { + var remote = share.remote; + var owner = share.owner; + var name = share.name; var remoteClean = (remote.substr(0, 8) === 'https://') ? remote.substr(8) : remote.substr(7); - var callback = function (add, password) { - password = password || ''; - if (add) { - addExternalShare(remote, token, owner, name, password).then(function (result) { - if (result.status === 'error') { - OC.Notification.show(result.data.message); - } else { - FileList.reload(); - } - }); - } - }; + if (!passwordProtected) { OC.dialogs.confirm( t( @@ -49,7 +31,9 @@ {name: name, owner: owner, remote: remoteClean} ), t('files_sharing','Remote share'), - callback, + function (result) { + callback(result, share); + }, true ).then(this._adjustDialog); } else { @@ -60,7 +44,9 @@ {name: name, owner: owner, remote: remoteClean} ), t('files_sharing','Remote share'), - callback, + function (result) { + callback(result, share); + }, true, t('files_sharing','Remote share password'), true @@ -82,17 +68,66 @@ $(document).ready(function () { // FIXME: HACK: do not init when running unit tests, need a better way if (!window.TESTING && OCA.Files) {// only run in the files app var params = OC.Util.History.parseUrlQuery(); + + //manually add server-to-server share if (params.remote && params.token && params.owner && params.name) { + + var callbackAddShare = function(result, share) { + var password = share.password || ''; + if (result) { + //$.post(OC.generateUrl('/apps/files_sharing/api/externalShares'), {id: share.id}); + $.post(OC.generateUrl('apps/files_sharing/external'), { + remote: share.remote, + token: share.token, + owner: share.owner, + name: share.name, + password: password}, function(result) { + if (result.status === 'error') { + OC.Notification.show(result.data.message); + } else { + FileList.reload(); + } + }); + } + }; + // clear hash, it is unlikely that it contain any extra parameters location.hash = ''; params.passwordProtected = parseInt(params.protected, 10) === 1; OCA.Sharing.showAddExternalDialog( - params.remote, - params.token, - params.owner, - params.name, - params.passwordProtected + params, + params.passwordProtected, + callbackAddShare ); } + + // check for new server-to-server shares which need to be approved + $.get(OC.generateUrl('/apps/files_sharing/api/externalShares'), + {}, + function(shares) { + var index; + for (index = 0; index < shares.length; ++index) { + OCA.Sharing.showAddExternalDialog( + shares[index], + false, + function(result, share) { + if (result) { + // Accept + $.post(OC.generateUrl('/apps/files_sharing/api/externalShares'), {id: share.id}); + FileList.reload(); + } else { + // Delete + $.ajax({ + url: OC.generateUrl('/apps/files_sharing/api/externalShares/'+share.id), + type: 'DELETE' + }); + } + } + ); + } + + }); + } + }); diff --git a/apps/files_sharing/lib/activity.php b/apps/files_sharing/lib/activity.php index 979df1c1da..868830d80c 100644 --- a/apps/files_sharing/lib/activity.php +++ b/apps/files_sharing/lib/activity.php @@ -98,7 +98,7 @@ class Activity implements \OCP\Activity\IExtension { case self::SUBJECT_REMOTE_SHARE_DECLINED: return $l->t('%1$s declined remote share %2$s', $params)->__toString(); case self::SUBJECT_REMOTE_SHARE_UNSHARED: - return $l->t('%1$s unshared %2$s', $params)->__toString(); + return $l->t('%1$s unshared %2$s from you', $params)->__toString(); } } } diff --git a/apps/files_sharing/lib/connector/publicauth.php b/apps/files_sharing/lib/connector/publicauth.php index 4144dafa37..a630d091fd 100644 --- a/apps/files_sharing/lib/connector/publicauth.php +++ b/apps/files_sharing/lib/connector/publicauth.php @@ -69,6 +69,8 @@ class PublicAuth extends \Sabre\DAV\Auth\Backend\AbstractBasic { } else { return false; } + } elseif ($linkItem['share_type'] == \OCP\Share::SHARE_TYPE_REMOTE) { + return true; } else { return false; } diff --git a/apps/files_sharing/lib/controllers/externalsharescontroller.php b/apps/files_sharing/lib/controllers/externalsharescontroller.php new file mode 100644 index 0000000000..773ff8ce98 --- /dev/null +++ b/apps/files_sharing/lib/controllers/externalsharescontroller.php @@ -0,0 +1,86 @@ + + * @copyright 2014 Lukas Reschke + * + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OCA\Files_Sharing\Controllers; + +use OC; +use OCP; +use OCP\AppFramework\Controller; +use OCP\IRequest; +use OCP\AppFramework\Http\JSONResponse; + +/** + * Class ExternalSharesController + * + * @package OCA\Files_Sharing\Controllers + */ +class ExternalSharesController extends Controller { + + /** @var bool */ + private $incomingShareEnabled; + /** @var \OCA\Files_Sharing\External\Manager */ + private $externalManager; + + /** + * @param string $appName + * @param IRequest $request + * @param \OCA\Files_Sharing\External\Manager $externalManager + */ + public function __construct($appName, + IRequest $request, + $incomingShareEnabled, + \OCA\Files_Sharing\External\Manager $externalManager) { + parent::__construct($appName, $request); + $this->incomingShareEnabled = $incomingShareEnabled; + $this->externalManager = $externalManager; + } + + /** + * @NoAdminRequired + * + * @return JSONResponse + */ + public function index() { + $shares = []; + if ($this->incomingShareEnabled) { + $shares = $this->externalManager->getOpenShares(); + } + return new JSONResponse($shares); + } + + /** + * @NoAdminRequired + * + * @param int $id + * @return JSONResponse + */ + public function create($id) { + if ($this->incomingShareEnabled) { + $this->externalManager->acceptShare($id); + } + + return new JSONResponse(); + } + + /** + * @NoAdminRequired + * + * @param $id + * @return JSONResponse + */ + public function destroy($id) { + if ($this->incomingShareEnabled) { + $this->externalManager->declineShare($id); + } + + return new JSONResponse(); + } + +} diff --git a/apps/files_sharing/lib/external/manager.php b/apps/files_sharing/lib/external/manager.php index b52e1a5044..665e47c0fe 100644 --- a/apps/files_sharing/lib/external/manager.php +++ b/apps/files_sharing/lib/external/manager.php @@ -33,6 +33,11 @@ class Manager { */ private $userSession; + /** + * @var \OC\HTTPHelper + */ + private $httpHelper; + /** * @param \OCP\IDBConnection $connection * @param \OC\Files\Mount\Manager $mountManager @@ -40,19 +45,30 @@ class Manager { * @param \OC\Files\Storage\StorageFactory $storageLoader */ public function __construct(\OCP\IDBConnection $connection, \OC\Files\Mount\Manager $mountManager, - \OC\Files\Storage\StorageFactory $storageLoader, \OC\User\Session $userSession) { + \OC\Files\Storage\StorageFactory $storageLoader, \OC\User\Session $userSession, \OC\HTTPHelper $httpHelper) { $this->connection = $connection; $this->mountManager = $mountManager; $this->userSession = $userSession; $this->storageLoader = $storageLoader; + $this->httpHelper = $httpHelper; } - public function addShare($remote, $token, $password, $name, $owner) { - $user = $this->userSession->getUser(); - if ($user) { - $mountPoint = Filesystem::normalizePath('/' . $name); - \OCA\Files_Sharing\Helper::addServer2ServerShare($remote, $token, $name, $mountPoint, $owner, $user->getUID(), $password, -1, true); + public function addShare($remote, $token, $password, $name, $owner, $accepted=false, $user = null, $remoteId = -1) { + $user = $user ? $user: $this->userSession->getUser()->getUID(); + $accepted = $accepted ? 1 : 0; + + $mountPoint = Filesystem::normalizePath('/' . $name); + + $query = $this->connection->prepare(' + INSERT INTO `*PREFIX*share_external` + (`remote`, `share_token`, `password`, `name`, `owner`, `user`, `mountpoint`, `mountpoint_hash`, `accepted`, `remote_id`) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + '); + $hash = md5($mountPoint); + $query->execute(array($remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId)); + + if ($accepted) { $options = array( 'remote' => $remote, 'token' => $token, @@ -87,12 +103,85 @@ class Manager { } } + /** + * get share + * + * @param int $id share id + * @return mixed share of false + */ + private function getShare($id) { + $getShare = $this->connection->prepare(' + SELECT `remote`, `share_token` + FROM `*PREFIX*share_external` + WHERE `id` = ? AND `user` = ?'); + $result = $getShare->execute(array($id, $this->userSession->getUser()->getUID())); + + return $result ? $getShare->fetch() : false; + } + + /** + * accept server-to-server share + * + * @param int $id + */ + public function acceptShare($id) { + + $share = $this->getShare($id); + + if ($share) { + $acceptShare = $this->connection->prepare(' + UPDATE `*PREFIX*share_external` + SET `accepted` = ? + WHERE `id` = ? AND `user` = ?'); + $acceptShare->execute(array(1, $id, $this->userSession->getUser()->getUID())); + $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $id, 'accept'); + } + } + + /** + * decline server-to-server share + * + * @param int $id + */ + public function declineShare($id) { + + $share = $this->getShare($id); + + if ($share) { + $removeShare = $this->connection->prepare(' + DELETE FROM `*PREFIX*share_external` WHERE `id` = ? AND `user` = ?'); + $removeShare->execute(array($id, $this->userSession->getUser()->getUID())); + $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $id, 'decline'); + } + } + + /** + * inform remote server whether server-to-server share was accepted/declined + * + * @param string $remote + * @param string $token + * @param int $id + * @param string $feedback + * @return boolean + */ + private function sendFeedbackToRemote($remote, $token, $id, $feedback) { + + $url = $remote . \OCP\Share::BASE_PATH_TO_SHARE_API . '/' . $id . '/' . $feedback . '?format=' . \OCP\Share::RESPONSE_FORMAT; + $fields = array('token' => $token); + + $result = $this->httpHelper->post($url, $fields); + $status = json_decode($result['result'], true); + + return ($result['success'] && $status['ocs']['meta']['statuscode'] === 100); + } + public static function setup() { $externalManager = new \OCA\Files_Sharing\External\Manager( - \OC::$server->getDatabaseConnection(), - \OC\Files\Filesystem::getMountManager(), - \OC\Files\Filesystem::getLoader(), - \OC::$server->getUserSession() + \OC::$server->getDatabaseConnection(), + \OC\Files\Filesystem::getMountManager(), + \OC\Files\Filesystem::getLoader(), + \OC::$server->getUserSession(), + \OC::$server->getHTTPHelper() ); $externalManager->setupMounts(); } @@ -151,6 +240,18 @@ class Manager { $user = $this->userSession->getUser(); $mountPoint = $this->stripPath($mountPoint); $hash = md5($mountPoint); + + $getShare = $this->connection->prepare(' + SELECT `remote`, `share_token`, `remote_id` + FROM `*PREFIX*share_external` + WHERE `mountpoint_hash` = ? AND `user` = ?'); + $result = $getShare->execute(array($hash, $user->getUID())); + + if ($result) { + $share = $getShare->fetch(); + $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline'); + } + $query = $this->connection->prepare(' DELETE FROM `*PREFIX*share_external` WHERE `mountpoint_hash` = ? @@ -158,4 +259,17 @@ class Manager { '); return (bool)$query->execute(array($hash, $user->getUID())); } -} + + /** + * return a list of shares which are not yet accepted by the user + * + * @return array list of open server-to-server shares + */ + public function getOpenShares() { + $openShares = $this->connection->prepare('SELECT * FROM `*PREFIX*share_external` WHERE `accepted` = ? AND `user` = ?'); + $result = $openShares->execute(array(0, $this->userSession->getUser()->getUID())); + + return $result ? $openShares->fetchAll() : array(); + + } +} \ No newline at end of file diff --git a/apps/files_sharing/lib/helper.php b/apps/files_sharing/lib/helper.php index 71519bd1d4..001d0387fa 100644 --- a/apps/files_sharing/lib/helper.php +++ b/apps/files_sharing/lib/helper.php @@ -2,8 +2,6 @@ namespace OCA\Files_Sharing; -use OC_Config; - class Helper { public static function registerHooks() { @@ -20,30 +18,6 @@ class Helper { \OCP\Util::connectHook('OCP\Share', 'post_unshareFromSelf', '\OC\Files\Cache\Shared_Updater', 'postUnshareFromSelfHook'); } - /** - * add server-to-server share to database - * - * @param string $remote - * @param string $token - * @param string $name - * @param string $mountPoint - * @param string $owner - * @param string $user - * @param string $password - * @param int $remoteId - * @param bool $accepted - */ - public static function addServer2ServerShare($remote, $token, $name, $mountPoint, $owner, $user, $password='', $remoteId=-1, $accepted = false) { - $accepted = $accepted ? 1 : 0; - $query = \OCP\DB::prepare(' - INSERT INTO `*PREFIX*share_external` - (`remote`, `share_token`, `password`, `name`, `owner`, `user`, `mountpoint`, `mountpoint_hash`, `accepted`, `remote_id`) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - '); - $hash = md5($mountPoint); - $query->execute(array($remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId)); - } - /** * Sets up the filesystem and user for public sharing * @param string $token string share token @@ -89,7 +63,7 @@ class Helper { exit(); } - if (isset($linkItem['share_with'])) { + if (isset($linkItem['share_with']) && (int)$linkItem['share_type'] === \OCP\Share::SHARE_TYPE_LINK) { if (!self::authenticate($linkItem, $password)) { \OC_Response::setStatus(403); \OCP\JSON::error(array('success' => false)); diff --git a/apps/files_sharing/lib/share/file.php b/apps/files_sharing/lib/share/file.php index a5b4e75bce..93e4af3c39 100644 --- a/apps/files_sharing/lib/share/file.php +++ b/apps/files_sharing/lib/share/file.php @@ -159,6 +159,20 @@ class OC_Share_Backend_File implements OCP\Share_Backend_File_Dependent { return array(); } + /** + * check if server2server share is enabled + * + * @param int $shareType + * @return boolean + */ + public function isShareTypeAllowed($shareType) { + if ($shareType === \OCP\Share::SHARE_TYPE_REMOTE) { + return \OCA\Files_Sharing\Helper::isOutgoingServer2serverShareEnabled(); + } + + return true; + } + /** * resolve reshares to return the correct source item * @param array $source diff --git a/apps/files_sharing/lib/updater.php b/apps/files_sharing/lib/updater.php index a34140f5a3..9d8ae7cbb4 100644 --- a/apps/files_sharing/lib/updater.php +++ b/apps/files_sharing/lib/updater.php @@ -161,7 +161,10 @@ class Shared_Updater { */ static public function postUnshareHook($params) { - if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') { + // only update etags for file/folders shared to local users/groups + if (($params['itemType'] === 'file' || $params['itemType'] === 'folder') && + $params['shareType'] !== \OCP\Share::SHARE_TYPE_LINK && + $params['shareType'] !== \OCP\Share::SHARE_TYPE_REMOTE) { $deletedShares = isset($params['deletedShares']) ? $params['deletedShares'] : array(); @@ -212,7 +215,7 @@ class Shared_Updater { /** * rename mount point from the children if the parent was renamed - * + * * @param string $oldPath old path relative to data/user/files * @param string $newPath new path relative to data/user/files */ diff --git a/apps/files_sharing/tests/server2server.php b/apps/files_sharing/tests/server2server.php index 7aec0c4951..0400d357b8 100644 --- a/apps/files_sharing/tests/server2server.php +++ b/apps/files_sharing/tests/server2server.php @@ -38,6 +38,16 @@ class Test_Files_Sharing_S2S_OCS_API extends TestCase { self::loginHelper(self::TEST_FILES_SHARING_API_USER1); \OCP\Share::registerBackend('test', 'Test_Share_Backend'); + $config = $this->getMockBuilder('\OCP\IConfig') + ->disableOriginalConstructor()->getMock(); + $certificateManager = $this->getMock('\OCP\ICertificateManager'); + $httpHelperMock = $this->getMockBuilder('\OC\HTTPHelper') + ->setConstructorArgs(array($config, $certificateManager)) + ->getMock(); + $httpHelperMock->expects($this->any())->method('post')->with($this->anything())->will($this->returnValue(true)); + + $this->registerHttpHelper($httpHelperMock); + $this->s2s = new \OCA\Files_Sharing\API\Server2Server(); } @@ -45,9 +55,32 @@ class Test_Files_Sharing_S2S_OCS_API extends TestCase { $query = \OCP\DB::prepare('DELETE FROM `*PREFIX*share_external`'); $query->execute(); + $this->restoreHttpHelper(); + parent::tearDown(); } + /** + * Register an http helper mock for testing purposes. + * @param $httpHelper http helper mock + */ + private function registerHttpHelper($httpHelper) { + $this->oldHttpHelper = \OC::$server->query('HTTPHelper'); + \OC::$server->registerService('HTTPHelper', function ($c) use ($httpHelper) { + return $httpHelper; + }); + } + + /** + * Restore the original http helper + */ + private function restoreHttpHelper() { + $oldHttpHelper = $this->oldHttpHelper; + \OC::$server->registerService('HTTPHelper', function ($c) use ($oldHttpHelper) { + return $oldHttpHelper; + }); + } + /** * @medium */ @@ -58,7 +91,7 @@ class Test_Files_Sharing_S2S_OCS_API extends TestCase { $_POST['name'] = 'name'; $_POST['owner'] = 'owner'; $_POST['shareWith'] = self::TEST_FILES_SHARING_API_USER2; - $_POST['remote_id'] = 1; + $_POST['remoteId'] = 1; $result = $this->s2s->createShare(null); @@ -81,10 +114,10 @@ class Test_Files_Sharing_S2S_OCS_API extends TestCase { function testDeclineShare() { $dummy = \OCP\DB::prepare(' INSERT INTO `*PREFIX*share` - (`share_type`, `uid_owner`, `item_type`, `item_source`, `item_target`, `file_source`, `file_target`, `permissions`, `stime`, `token`) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + (`share_type`, `uid_owner`, `item_type`, `item_source`, `item_target`, `file_source`, `file_target`, `permissions`, `stime`, `token`, `share_with`) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) '); - $dummy->execute(array(\OCP\Share::SHARE_TYPE_REMOTE, self::TEST_FILES_SHARING_API_USER1, 'test', '1', '/1', '1', '/test.txt', '1', time(), 'token')); + $dummy->execute(array(\OCP\Share::SHARE_TYPE_REMOTE, self::TEST_FILES_SHARING_API_USER1, 'test', '1', '/1', '1', '/test.txt', '1', time(), 'token', 'foo@bar')); $verify = \OCP\DB::prepare('SELECT * FROM `*PREFIX*share`'); $result = $verify->execute(); diff --git a/core/ajax/share.php b/core/ajax/share.php index 9f758b4e44..2831d42a36 100644 --- a/core/ajax/share.php +++ b/core/ajax/share.php @@ -36,7 +36,7 @@ if (isset($_POST['action']) && isset($_POST['itemType']) && isset($_POST['itemSo $shareWith = null; } $itemSourceName=(isset($_POST['itemSourceName'])) ? $_POST['itemSourceName']:''; - + $token = OCP\Share::shareItem( $_POST['itemType'], $_POST['itemSource'], @@ -309,6 +309,21 @@ if (isset($_POST['action']) && isset($_POST['itemType']) && isset($_POST['itemSo break; } } + + // allow user to add unknown remote addresses for server-to-server share + $backend = \OCP\Share::getBackend($_GET['itemType']); + if ($backend->isShareTypeAllowed(\OCP\Share::SHARE_TYPE_REMOTE)) { + if (substr_count($_GET['search'], '@') === 1) { + $shareWith[] = array( + 'label' => $_GET['search'], + 'value' => array( + 'shareType' => \OCP\Share::SHARE_TYPE_REMOTE, + 'shareWith' => $_GET['search'] + ) + ); + } + } + $sorter = new \OC\Share\SearchResultSorter($_GET['search'], 'label', new \OC\Log()); diff --git a/core/js/share.js b/core/js/share.js index b8717d94ed..00d1dab519 100644 --- a/core/js/share.js +++ b/core/js/share.js @@ -8,6 +8,7 @@ OC.Share={ SHARE_TYPE_GROUP:1, SHARE_TYPE_LINK:3, SHARE_TYPE_EMAIL:4, + SHARE_TYPE_REMOTE:6, /** * Regular expression for splitting parts of remote share owners: @@ -444,7 +445,11 @@ OC.Share={ if (share.collection) { OC.Share.addShareWith(share.share_type, share.share_with, share.share_with_displayname, share.permissions, possiblePermissions, share.mail_send, share.collection); } else { - OC.Share.addShareWith(share.share_type, share.share_with, share.share_with_displayname, share.permissions, possiblePermissions, share.mail_send, false); + if (share.share_type === OC.Share.SHARE_TYPE_REMOTE) { + OC.Share.addShareWith(share.share_type, share.share_with, share.share_with_displayname, share.permissions, OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE, share.mail_send, false); + } else { + OC.Share.addShareWith(share.share_type, share.share_with, share.share_with_displayname, share.permissions, possiblePermissions, share.mail_send, false); + } } } if (share.expiration != null) { @@ -455,7 +460,7 @@ OC.Share={ $('#shareWith').autocomplete({minLength: 2, delay: 750, source: function(search, response) { var $loading = $('#dropdown .shareWithLoading'); $loading.removeClass('hidden'); - $.get(OC.filePath('core', 'ajax', 'share.php'), { fetch: 'getShareWith', search: search.term.trim(), itemShares: OC.Share.itemShares }, function(result) { + $.get(OC.filePath('core', 'ajax', 'share.php'), { fetch: 'getShareWith', search: search.term.trim(), itemShares: OC.Share.itemShares, itemType: itemType }, function(result) { $loading.addClass('hidden'); if (result.status == 'success' && result.data.length > 0) { $( "#shareWith" ).autocomplete( "option", "autoFocus", true ); @@ -484,19 +489,22 @@ OC.Share={ // Default permissions are Edit (CRUD) and Share // Check if these permissions are possible var permissions = OC.PERMISSION_READ; - if (possiblePermissions & OC.PERMISSION_UPDATE) { - permissions = permissions | OC.PERMISSION_UPDATE; + if (shareType === OC.Share.SHARE_TYPE_REMOTE) { + permissions = OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE | OC.PERMISSION_READ; + } else { + if (possiblePermissions & OC.PERMISSION_UPDATE) { + permissions = permissions | OC.PERMISSION_UPDATE; + } + if (possiblePermissions & OC.PERMISSION_CREATE) { + permissions = permissions | OC.PERMISSION_CREATE; + } + if (possiblePermissions & OC.PERMISSION_DELETE) { + permissions = permissions | OC.PERMISSION_DELETE; + } + if (oc_appconfig.core.resharingAllowed && (possiblePermissions & OC.PERMISSION_SHARE)) { + permissions = permissions | OC.PERMISSION_SHARE; + } } - if (possiblePermissions & OC.PERMISSION_CREATE) { - permissions = permissions | OC.PERMISSION_CREATE; - } - if (possiblePermissions & OC.PERMISSION_DELETE) { - permissions = permissions | OC.PERMISSION_DELETE; - } - if (oc_appconfig.core.resharingAllowed && (possiblePermissions & OC.PERMISSION_SHARE)) { - permissions = permissions | OC.PERMISSION_SHARE; - } - var $input = $(this); var $loading = $dropDown.find('.shareWithLoading'); @@ -507,7 +515,11 @@ OC.Share={ OC.Share.share(itemType, itemSource, shareType, shareWith, permissions, itemSourceName, expirationDate, function() { $input.prop('disabled', false); $loading.addClass('hidden'); - OC.Share.addShareWith(shareType, shareWith, selected.item.label, permissions, possiblePermissions); + var posPermissions = possiblePermissions; + if (shareType === OC.Share.SHARE_TYPE_REMOTE) { + posPermissions = permissions; + } + OC.Share.addShareWith(shareType, shareWith, selected.item.label, permissions, posPermissions); $('#shareWith').val(''); $('#dropdown').trigger(new $.Event('sharesChanged', {shares: OC.Share.currentShares})); OC.Share.updateIcon(itemType, itemSource); @@ -518,13 +530,18 @@ OC.Share={ // customize internal _renderItem function to display groups and users differently .data("ui-autocomplete")._renderItem = function( ul, item ) { var insert = $( "" ); - var text = (item.value.shareType == 1)? item.label + ' ('+t('core', 'group')+')' : item.label; + var text = item.label; + if (item.value.shareType === OC.Share.SHARE_TYPE_GROUP) { + text = text + ' ('+t('core', 'group')+')'; + } else if (item.value.shareType === OC.Share.SHARE_TYPE_REMOTE) { + text = text + ' ('+t('core', 'remote')+')'; + } insert.text( text ); - if(item.value.shareType == 1) { + if(item.value.shareType === OC.Share.SHARE_TYPE_GROUP) { insert = insert.wrapInner(''); } return $( "
  • " ) - .addClass((item.value.shareType == 1)?'group':'user') + .addClass((item.value.shareType === OC.Share.SHARE_TYPE_GROUP)?'group':'user') .append( insert ) .appendTo( ul ); }; @@ -585,9 +602,12 @@ OC.Share={ share_with_displayname: shareWithDisplayName, permissions: permissions }; - if (shareType === 1) { + if (shareType === OC.Share.SHARE_TYPE_GROUP) { shareWithDisplayName = shareWithDisplayName + " (" + t('core', 'group') + ')'; } + if (shareType === OC.Share.SHARE_TYPE_REMOTE) { + shareWithDisplayName = shareWithDisplayName + " (" + t('core', 'remote') + ')'; + } if (!OC.Share.itemShares[shareType]) { OC.Share.itemShares[shareType] = []; } @@ -627,7 +647,7 @@ OC.Share={ html += ''+t('core', 'Unshare')+''; html += '' + escapeHTML(shareWithDisplayName) + ''; var mailNotificationEnabled = $('input:hidden[name=mailNotificationEnabled]').val(); - if (mailNotificationEnabled === 'yes') { + if (mailNotificationEnabled === 'yes' && shareType !== OC.Share.SHARE_TYPE_REMOTE) { var checked = ''; if (mailSend === '1') { checked = 'checked'; @@ -640,17 +660,19 @@ OC.Share={ if (possiblePermissions & OC.PERMISSION_CREATE || possiblePermissions & OC.PERMISSION_UPDATE || possiblePermissions & OC.PERMISSION_DELETE) { html += ''; } - showCrudsButton = ''+t('core', 'access control')+''; + if (shareType !== OC.Share.SHARE_TYPE_REMOTE) { + showCrudsButton = ''+t('core', 'access control')+''; + } html += ''; html += '
  • '; html = $(html).appendTo('#shareWithList'); diff --git a/lib/private/httphelper.php b/lib/private/httphelper.php index 846825dee8..1f3482b351 100644 --- a/lib/private/httphelper.php +++ b/lib/private/httphelper.php @@ -8,7 +8,8 @@ namespace OC; -use \OCP\IConfig; +use OCP\IConfig; +use OCP\ICertificateManager; class HTTPHelper { const USER_AGENT = 'ownCloud Server Crawler'; @@ -16,11 +17,15 @@ class HTTPHelper { /** @var \OCP\IConfig */ private $config; + /** @var \OC\Security\CertificateManager */ + private $certificateManager; + /** * @param \OCP\IConfig $config */ - public function __construct(IConfig $config) { + public function __construct(IConfig $config, ICertificateManager $certificateManager) { $this->config = $config; + $this->certificateManager = $certificateManager; } /** @@ -176,4 +181,50 @@ class HTTPHelper { return $location; } + /** + * create string of parameters for post request + * + * @param array $parameters + * @return string + */ + private function assemblePostParameters(array $parameters) { + $parameterString = ''; + foreach ($parameters as $key => $value) { + $parameterString .= $key . '=' . urlencode($value) . '&'; + } + + return rtrim($parameterString, '&'); + } + + /** + * send http post request + * + * @param string $url + * @param array $fields data send by the request + * @return bool + */ + public function post($url, array $fields) { + + $fieldsString = $this->assemblePostParameters($fields); + + $certBundle = $this->certificateManager->getCertificateBundle(); + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, count($fields)); + curl_setopt($ch, CURLOPT_POSTFIELDS, $fieldsString); + if (is_readable($certBundle)) { + curl_setopt($ch, CURLOPT_CAINFO, $certBundle); + } + + $result = curl_exec($ch); + $success = $result ? true : false; + + curl_close($ch); + + return array('success' => $success, 'result' => $result); + } + } diff --git a/lib/private/security/certificatemanager.php b/lib/private/security/certificatemanager.php index a2a4c8b83d..4a8ea17073 100644 --- a/lib/private/security/certificatemanager.php +++ b/lib/private/security/certificatemanager.php @@ -33,7 +33,7 @@ class CertificateManager implements ICertificateManager { * @return \OCP\ICertificate[] */ public function listCertificates() { - $path = $this->user->getHome() . '/files_external/uploads/'; + $path = $this->getPathToCertificates() . 'uploads/'; if (!is_dir($path)) { return array(); } @@ -57,7 +57,7 @@ class CertificateManager implements ICertificateManager { * create the certificate bundle of all trusted certificated */ protected function createCertificateBundle() { - $path = $this->user->getHome() . '/files_external/'; + $path = $this->getPathToCertificates(); $certs = $this->listCertificates(); $fh_certs = fopen($path . '/rootcerts.crt', 'w'); @@ -86,7 +86,7 @@ class CertificateManager implements ICertificateManager { return false; } - $dir = $this->user->getHome() . '/files_external/uploads/'; + $dir = $this->getPathToCertificates() . 'uploads/'; if (!file_exists($dir)) { //path might not exist (e.g. non-standard OC_User::getHome() value) //in this case create full path using 3rd (recursive=true) parameter. @@ -116,7 +116,7 @@ class CertificateManager implements ICertificateManager { if (!Filesystem::isValidPath($name)) { return false; } - $path = $this->user->getHome() . '/files_external/uploads/'; + $path = $this->getPathToCertificates() . 'uploads/'; if (file_exists($path . $name)) { unlink($path . $name); $this->createCertificateBundle(); @@ -130,6 +130,12 @@ class CertificateManager implements ICertificateManager { * @return string */ public function getCertificateBundle() { - return $this->user->getHome() . '/files_external/rootcerts.crt'; + return $this->getPathToCertificates() . 'rootcerts.crt'; + } + + private function getPathToCertificates() { + $path = $this->user ? $this->user->getHome() . '/files_external/' : '/files_external/'; + + return $path; } } diff --git a/lib/private/server.php b/lib/private/server.php index f4c20576eb..c98f77c647 100644 --- a/lib/private/server.php +++ b/lib/private/server.php @@ -249,7 +249,7 @@ class Server extends SimpleContainer implements IServerContainer { }); $this->registerService('HTTPHelper', function (Server $c) { $config = $c->getConfig(); - return new HTTPHelper($config); + return new HTTPHelper($config, new \OC\Security\CertificateManager($c->getUserSession()->getUser())); }); $this->registerService('EventLogger', function (Server $c) { if (defined('DEBUG') and DEBUG) { diff --git a/lib/private/share/constants.php b/lib/private/share/constants.php index 798327cc15..6c9d943160 100644 --- a/lib/private/share/constants.php +++ b/lib/private/share/constants.php @@ -34,8 +34,12 @@ class Constants { const FORMAT_STATUSES = -2; const FORMAT_SOURCES = -3; // ToDo Check if it is still in use otherwise remove it + const RESPONSE_FORMAT = 'json'; // default resonse format for ocs calls + const TOKEN_LENGTH = 15; // old (oc7) length is 32, keep token length in db at least that for compatibility + const BASE_PATH_TO_SHARE_API = '/ocs/v1.php/cloud/shares'; + protected static $shareTypeUserAndGroups = -1; protected static $shareTypeGroupUserUnique = 2; protected static $backends = array(); diff --git a/lib/private/share/helper.php b/lib/private/share/helper.php index 6bbb101db3..3d20ba2d27 100644 --- a/lib/private/share/helper.php +++ b/lib/private/share/helper.php @@ -38,7 +38,7 @@ class Helper extends \OC\Share\Constants { public static function generateTarget($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $suggestedTarget = null, $groupParent = null) { // FIXME: $uidOwner and $groupParent seems to be unused $backend = \OC\Share\Share::getBackend($itemType); - if ($shareType == self::SHARE_TYPE_LINK) { + if ($shareType === self::SHARE_TYPE_LINK || $shareType === self::SHARE_TYPE_REMOTE) { if (isset($suggestedTarget)) { return $suggestedTarget; } diff --git a/lib/private/share/share.php b/lib/private/share/share.php index abcd14f6ec..f61f65f35a 100644 --- a/lib/private/share/share.php +++ b/lib/private/share/share.php @@ -247,7 +247,7 @@ class Share extends \OC\Share\Constants { * @return mixed Return depends on format */ public static function getItemsSharedWith($itemType, $format = self::FORMAT_NONE, - $parameters = null, $limit = -1, $includeCollections = false) { + $parameters = null, $limit = -1, $includeCollections = false) { return self::getItems($itemType, null, self::$shareTypeUserAndGroups, \OC_User::getUser(), null, $format, $parameters, $limit, $includeCollections); } @@ -263,7 +263,7 @@ class Share extends \OC\Share\Constants { * @return mixed Return depends on format */ public static function getItemsSharedWithUser($itemType, $user, $format = self::FORMAT_NONE, - $parameters = null, $limit = -1, $includeCollections = false) { + $parameters = null, $limit = -1, $includeCollections = false) { return self::getItems($itemType, null, self::$shareTypeUserAndGroups, $user, null, $format, $parameters, $limit, $includeCollections); } @@ -278,7 +278,7 @@ class Share extends \OC\Share\Constants { * @return mixed Return depends on format */ public static function getItemSharedWith($itemType, $itemTarget, $format = self::FORMAT_NONE, - $parameters = null, $includeCollections = false) { + $parameters = null, $includeCollections = false) { return self::getItems($itemType, $itemTarget, self::$shareTypeUserAndGroups, \OC_User::getUser(), null, $format, $parameters, 1, $includeCollections); } @@ -338,12 +338,12 @@ class Share extends \OC\Share\Constants { $groups = \OC_Group::getUserGroups($user); $query = \OC_DB::prepare( - 'SELECT * + 'SELECT * FROM `*PREFIX*share` WHERE `' . $column . '` = ? AND `item_type` = ? AND `share_with` in (?)' - ); + ); $result = \OC_DB::executeAudited($query, array($itemSource, $itemType, implode(',', $groups))); @@ -367,7 +367,7 @@ class Share extends \OC\Share\Constants { * @return array */ public static function getItemSharedWithBySource($itemType, $itemSource, $format = self::FORMAT_NONE, - $parameters = null, $includeCollections = false, $shareWith = null) { + $parameters = null, $includeCollections = false, $shareWith = null) { $shareWith = ($shareWith === null) ? \OC_User::getUser() : $shareWith; return self::getItems($itemType, $itemSource, self::$shareTypeUserAndGroups, $shareWith, null, $format, $parameters, 1, $includeCollections, true); @@ -445,7 +445,7 @@ class Share extends \OC\Share\Constants { * @return mixed Return depends on format */ public static function getItemsShared($itemType, $format = self::FORMAT_NONE, $parameters = null, - $limit = -1, $includeCollections = false) { + $limit = -1, $includeCollections = false) { return self::getItems($itemType, null, null, null, \OC_User::getUser(), $format, $parameters, $limit, $includeCollections); } @@ -460,7 +460,7 @@ class Share extends \OC\Share\Constants { * @return mixed Return depends on format */ public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE, - $parameters = null, $includeCollections = false) { + $parameters = null, $includeCollections = false) { return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), $format, $parameters, -1, $includeCollections); } @@ -503,9 +503,19 @@ class Share extends \OC\Share\Constants { * @throws \Exception */ public static function shareItem($itemType, $itemSource, $shareType, $shareWith, $permissions, $itemSourceName = null, \DateTime $expirationDate = null) { + + $backend = self::getBackend($itemType); + $l = \OC::$server->getL10N('lib'); + + if ($backend->isShareTypeAllowed($shareType) === false) { + $message = 'Sharing %s failed, because the backend does not allow shares from type %i'; + $message_t = $l->t('Sharing %s failed, because the backend does not allow shares from type %i', array($itemSourceName, $shareType)); + \OC_Log::write('OCP\Share', sprintf($message, $itemSourceName, $shareType), \OC_Log::ERROR); + throw new \Exception($message_t); + } + $uidOwner = \OC_User::getUser(); $shareWithinGroupOnly = self::shareWithGroupMembersOnly(); - $l = \OC::$server->getL10N('lib'); if (is_null($itemSourceName)) { $itemSourceName = $itemSource; @@ -655,8 +665,8 @@ class Share extends \OC\Share\Constants { } if ($updateExistingShare === false && - self::isDefaultExpireDateEnabled() && - empty($expirationDate)) { + self::isDefaultExpireDateEnabled() && + empty($expirationDate)) { $expirationDate = Helper::calcExpireDate(); } @@ -681,6 +691,25 @@ class Share extends \OC\Share\Constants { $message_t = $l->t('Sharing %s failed, because sharing with links is not allowed', array($itemSourceName)); \OC_Log::write('OCP\Share', sprintf($message, $itemSourceName), \OC_Log::ERROR); throw new \Exception($message_t); + } else if ($shareType === self::SHARE_TYPE_REMOTE) { + $token = \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate(self::TOKEN_LENGTH, \OCP\Security\ISecureRandom::CHAR_LOWER . \OCP\Security\ISecureRandom::CHAR_UPPER . + \OCP\Security\ISecureRandom::CHAR_DIGITS); + + $shareId = self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, null, $token, $itemSourceName); + + $send = false; + if ($shareId) { + $send = self::sendRemoteShare($token, $shareWith, $itemSourceName, $shareId, $uidOwner); + } + + if ($send === false) { + $currentUser = \OC::$server->getUserSession()->getUser()->getUID(); + self::unshare($itemType, $itemSource, $shareType, $shareWith, $currentUser); + $message_t = $l->t('Sharing %s failed, could not find %s, maybe the server is currently unreachable.', array($itemSourceName, $shareWith)); + throw new \Exception($message_t); + } + + return $send; } else { // Future share types need to include their own conditions $message = 'Share type %s is not valid for %s'; @@ -690,7 +719,9 @@ class Share extends \OC\Share\Constants { } // Put the item into the database - return self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, null, null, $itemSourceName, $expirationDate); + $result = self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, null, null, $itemSourceName, $expirationDate); + + return $result ? true : false; } /** @@ -716,9 +747,9 @@ class Share extends \OC\Share\Constants { // delete the item with the expected share_type and owner if ((int)$item['share_type'] === (int)$shareType && $item['uid_owner'] === $currentUser) { $toDelete = $item; - // if there is more then one result we don't have to delete the children - // but update their parent. For group shares the new parent should always be - // the original group share and not the db entry with the unique name + // if there is more then one result we don't have to delete the children + // but update their parent. For group shares the new parent should always be + // the original group share and not the db entry with the unique name } else if ((int)$item['share_type'] === self::$shareTypeGroupUserUnique) { $newParent = $item['parent']; } else { @@ -794,7 +825,7 @@ class Share extends \OC\Share\Constants { $itemUnshared = false; foreach ($shares as $share) { if ((int)$share['share_type'] === \OCP\Share::SHARE_TYPE_USER && - $share['share_with'] === $uid) { + $share['share_with'] === $uid) { $deletedShares = Helper::delete($share['id']); $shareTmp = array( 'id' => $share['id'], @@ -814,16 +845,16 @@ class Share extends \OC\Share\Constants { $groupShare = $share; } } elseif ((int)$share['share_type'] === self::$shareTypeGroupUserUnique && - $share['share_with'] === $uid) { + $share['share_with'] === $uid) { $uniqueGroupShare = $share; } } if (!$itemUnshared && isset($groupShare) && !isset($uniqueGroupShare)) { $query = \OC_DB::prepare('INSERT INTO `*PREFIX*share`' - .' (`item_type`, `item_source`, `item_target`, `parent`, `share_type`,' - .' `share_with`, `uid_owner`, `permissions`, `stime`, `file_source`, `file_target`)' - .' VALUES (?,?,?,?,?,?,?,?,?,?,?)'); + .' (`item_type`, `item_source`, `item_target`, `parent`, `share_type`,' + .' `share_with`, `uid_owner`, `permissions`, `stime`, `file_source`, `file_target`)' + .' VALUES (?,?,?,?,?,?,?,?,?,?,?)'); $query->execute(array($groupShare['item_type'], $groupShare['item_source'], $groupShare['item_target'], $groupShare['id'], self::$shareTypeGroupUserUnique, \OC_User::getUser(), $groupShare['uid_owner'], 0, $groupShare['stime'], $groupShare['file_source'], @@ -834,7 +865,7 @@ class Share extends \OC\Share\Constants { 'itemTarget' => $groupShare['item_target'], 'itemType' => $groupShare['item_type'], 'shareType' => (int)$groupShare['share_type'], - ); + ); if (isset($groupShare['file_target'])) { $shareTmp['fileTarget'] = $groupShare['file_target']; } @@ -849,7 +880,7 @@ class Share extends \OC\Share\Constants { 'itemTarget' => $uniqueGroupShare['item_target'], 'itemType' => $uniqueGroupShare['item_type'], 'shareType' => (int)$uniqueGroupShare['share_type'], - ); + ); if (isset($uniqueGroupShare['file_target'])) { $shareTmp['fileTarget'] = $uniqueGroupShare['file_target']; } @@ -859,7 +890,7 @@ class Share extends \OC\Share\Constants { if ($itemUnshared) { \OC_Hook::emit('OCP\Share', 'post_unshareFromSelf', - array('unsharedItems' => $listOfUnsharedItems, 'itemType' => $itemType)); + array('unsharedItems' => $listOfUnsharedItems, 'itemType' => $itemType)); } return $itemUnshared; @@ -877,7 +908,7 @@ class Share extends \OC\Share\Constants { $status = $status ? 1 : 0; $query = \OC_DB::prepare( - 'UPDATE `*PREFIX*share` + 'UPDATE `*PREFIX*share` SET `mail_send` = ? WHERE `item_type` = ? AND `item_source` = ? AND `share_type` = ? AND `share_with` = ?'); @@ -1124,6 +1155,10 @@ class Share extends \OC\Share\Constants { $deletedShares[] = $hookParams; $hookParams['deletedShares'] = $deletedShares; \OC_Hook::emit('OCP\Share', 'post_unshare', $hookParams); + if ((int)$item['share_type'] === \OCP\Share::SHARE_TYPE_REMOTE && \OC::$server->getUserSession()->getUser()) { + $urlParts = explode('@', $item['share_with'], 2); + self::sendRemoteUnshare($urlParts[1], $item['id'], $item['token']); + } } /** @@ -1202,14 +1237,14 @@ class Share extends \OC\Share\Constants { } /** - * Get the owners of items shared with a user. - * - * @param string $user The user the items are shared with. - * @param string $type The type of the items shared with the user. - * @param boolean $includeCollections Include collection item types (optional) - * @param boolean $includeOwner include owner in the list of users the item is shared with (optional) - * @return array - */ + * Get the owners of items shared with a user. + * + * @param string $user The user the items are shared with. + * @param string $type The type of the items shared with the user. + * @param boolean $includeCollections Include collection item types (optional) + * @param boolean $includeOwner include owner in the list of users the item is shared with (optional) + * @return array + */ public static function getSharedItemsOwners($user, $type, $includeCollections = false, $includeOwner = false) { // First, we find out if $type is part of a collection (and if that collection is part of // another one and so on). @@ -1271,8 +1306,8 @@ class Share extends \OC\Share\Constants { * */ public static function getItems($itemType, $item = null, $shareType = null, $shareWith = null, - $uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1, - $includeCollections = false, $itemShareWithBySource = false, $checkExpireDate = true) { + $uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1, + $includeCollections = false, $itemShareWithBySource = false, $checkExpireDate = true) { if (!self::isEnabled()) { return array(); } @@ -1481,8 +1516,8 @@ class Share extends \OC\Share\Constants { $parentResult = $query->execute(array($row['parent'])); if (\OC_DB::isError($result)) { \OC_Log::write('OCP\Share', 'Can\'t select parent: ' . - \OC_DB::getErrorMessage($result) . ', select=' . $select . ' where=' . $where, - \OC_Log::ERROR); + \OC_DB::getErrorMessage($result) . ', select=' . $select . ' where=' . $where, + \OC_Log::ERROR); } else { $parentRow = $parentResult->fetchRow(); $tmpPath = $parentRow['file_target']; @@ -1521,7 +1556,7 @@ class Share extends \OC\Share\Constants { } // Add display names to result if ( isset($row['share_with']) && $row['share_with'] != '' && - isset($row['share_with']) && $row['share_type'] === self::SHARE_TYPE_USER) { + isset($row['share_with']) && $row['share_type'] === self::SHARE_TYPE_USER) { $row['share_with_displayname'] = \OCP\User::getDisplayName($row['share_with']); } else { $row['share_with_displayname'] = $row['share_with']; @@ -1668,7 +1703,7 @@ class Share extends \OC\Share\Constants { // only group shares if they already point to the same target, otherwise the file where shared // before grouping of shares was added. In this case we don't group them toi avoid confusions if (( $fileSharing && $item['file_source'] === $r['file_source'] && $item['file_target'] === $r['file_target']) || - (!$fileSharing && $item['item_source'] === $r['item_source'] && $item['item_target'] === $r['item_target'])) { + (!$fileSharing && $item['item_source'] === $r['item_source'] && $item['item_target'] === $r['item_target'])) { // add the first item to the list of grouped shares if (!isset($result[$key]['grouped'])) { $result[$key]['grouped'][] = $result[$key]; @@ -1689,7 +1724,7 @@ class Share extends \OC\Share\Constants { return $result; } -/** + /** * Put shared item into the database * @param string $itemType Item type * @param string $itemSource Item source @@ -1702,10 +1737,10 @@ class Share extends \OC\Share\Constants { * @param string $itemSourceName name of the source item (optional) * @param \DateTime $expirationDate (optional) * @throws \Exception - * @return boolean Returns true on success or false on failure + * @return mixed id of the new share or false */ private static function put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, - $permissions, $parentFolder = null, $token = null, $itemSourceName = null, \DateTime $expirationDate = null) { + $permissions, $parentFolder = null, $token = null, $itemSourceName = null, \DateTime $expirationDate = null) { $queriesToExecute = array(); $suggestedItemTarget = null; @@ -1730,7 +1765,7 @@ class Share extends \OC\Share\Constants { unset($users[array_search(\OCP\User::getUser(), $users)]); } $groupItemTarget = Helper::generateTarget($itemType, $itemSource, $shareType, $shareWith['group'], - $uidOwner, $suggestedItemTarget); + $uidOwner, $suggestedItemTarget); $groupFileTarget = $filePath; // add group share to table and remember the id as parent @@ -1753,7 +1788,7 @@ class Share extends \OC\Share\Constants { } else { $users = array($shareWith); $itemTarget = Helper::generateTarget($itemType, $itemSource, $shareType, $shareWith, $uidOwner, - $suggestedItemTarget); + $suggestedItemTarget); } $run = true; @@ -1829,7 +1864,7 @@ class Share extends \OC\Share\Constants { // do we also need a file target if (isset($fileSource)) { $fileTarget = Helper::generateTarget('file', $filePath, self::SHARE_TYPE_USER, $user, - $uidOwner, $suggestedFileTarget, $parent); + $uidOwner, $suggestedFileTarget, $parent); } else { $fileTarget = null; } @@ -1840,25 +1875,26 @@ class Share extends \OC\Share\Constants { } $queriesToExecute[] = array( - 'itemType' => $itemType, - 'itemSource' => $itemSource, - 'itemTarget' => $itemTarget, - 'shareType' => $shareType, - 'shareWith' => $user, - 'uidOwner' => $uidOwner, - 'permissions' => $permissions, - 'shareTime' => time(), - 'fileSource' => $fileSource, - 'fileTarget' => $fileTarget, - 'token' => $token, - 'parent' => $parent, - 'expiration' => $expirationDate, - ); + 'itemType' => $itemType, + 'itemSource' => $itemSource, + 'itemTarget' => $itemTarget, + 'shareType' => $shareType, + 'shareWith' => $user, + 'uidOwner' => $uidOwner, + 'permissions' => $permissions, + 'shareTime' => time(), + 'fileSource' => $fileSource, + 'fileTarget' => $fileTarget, + 'token' => $token, + 'parent' => $parent, + 'expiration' => $expirationDate, + ); } + $id = false; if ($isGroupShare) { - self::insertShare($queriesToExecute['groupShare']); + $id = self::insertShare($queriesToExecute['groupShare']); // Save this id, any extra rows for this group share will need to reference it $parent = \OC_DB::insertid('*PREFIX*share'); unset($queriesToExecute['groupShare']); @@ -1866,7 +1902,7 @@ class Share extends \OC\Share\Constants { foreach ($queriesToExecute as $shareQuery) { $shareQuery['parent'] = $parent; - self::insertShare($shareQuery); + $id = self::insertShare($shareQuery); } $postHookData = array( @@ -1889,7 +1925,7 @@ class Share extends \OC\Share\Constants { \OC_Hook::emit('OCP\Share', 'post_shared', $postHookData); - return true; + return $id ? $id : false; } private static function checkReshare($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, $itemSourceName, $expirationDate) { @@ -1983,6 +2019,11 @@ class Share extends \OC\Share\Constants { return $result; } + /** + * + * @param array $shareData + * @return mixed false in case of a failure or the id of the new share + */ private static function insertShare(array $shareData) { $query = \OC_DB::prepare('INSERT INTO `*PREFIX*share` (' @@ -2002,7 +2043,30 @@ class Share extends \OC\Share\Constants { $query->bindValue(11, $shareData['token']); $query->bindValue(12, $shareData['parent']); $query->bindValue(13, $shareData['expiration'], 'datetime'); - $query->execute(); + $result = $query->execute(); + + $id = false; + if ($result) { + $id = \OC::$server->getDatabaseConnection()->lastInsertId(); + // Fallback, if lastInterId() doesn't work we need to perform a select + // to get the ID (seems to happen sometimes on Oracle) + if (!$id) { + $getId = \OC_DB::prepare(' + SELECT `id` + FROM`*PREFIX*share` + WHERE `uid_owner` = ? AND `item_target` = ? AND `item_source` = ? AND `stime` = ? + '); + $r = $getId->execute(array($shareData['uidOwner'], $shareData['itemTarget'], $shareData['itemSource'], $shareData['shareTime'])); + if ($r) { + $row = $r->fetchRow(); + $id = $row['id']; + } + } + + } + + return $id; + } /** * Delete all shares with type SHARE_TYPE_LINK @@ -2064,19 +2128,19 @@ class Share extends \OC\Share\Constants { if (isset($uidOwner)) { if ($fileDependent) { $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`,' - . ' `share_type`, `share_with`, `file_source`, `file_target`, `path`, `*PREFIX*share`.`permissions`, `stime`,' - . ' `expiration`, `token`, `storage`, `mail_send`, `uid_owner`'; + . ' `share_type`, `share_with`, `file_source`, `file_target`, `path`, `*PREFIX*share`.`permissions`, `stime`,' + . ' `expiration`, `token`, `storage`, `mail_send`, `uid_owner`'; } else { $select = '`id`, `item_type`, `item_source`, `parent`, `share_type`, `share_with`, `*PREFIX*share`.`permissions`,' - . ' `stime`, `file_source`, `expiration`, `token`, `mail_send`, `uid_owner`'; + . ' `stime`, `file_source`, `expiration`, `token`, `mail_send`, `uid_owner`'; } } else { if ($fileDependent) { if ($format == \OC_Share_Backend_File::FORMAT_GET_FOLDER_CONTENTS || $format == \OC_Share_Backend_File::FORMAT_FILE_APP_ROOT) { $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`, `uid_owner`, ' - . '`share_type`, `share_with`, `file_source`, `path`, `file_target`, `stime`, ' - . '`*PREFIX*share`.`permissions`, `expiration`, `storage`, `*PREFIX*filecache`.`parent` as `file_parent`, ' - . '`name`, `mtime`, `mimetype`, `mimepart`, `size`, `unencrypted_size`, `encrypted`, `etag`, `mail_send`'; + . '`share_type`, `share_with`, `file_source`, `path`, `file_target`, `stime`, ' + . '`*PREFIX*share`.`permissions`, `expiration`, `storage`, `*PREFIX*filecache`.`parent` as `file_parent`, ' + . '`name`, `mtime`, `mimetype`, `mimepart`, `size`, `unencrypted_size`, `encrypted`, `etag`, `mail_send`'; } else { $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `item_target`, `*PREFIX*share`.`parent`, `share_type`, `share_with`, `uid_owner`, @@ -2150,6 +2214,99 @@ class Share extends \OC\Share\Constants { } } + /** + * remove protocol from URL + * + * @param string $url + * @return string + */ + private static function removeProtocolFromUrl($url) { + if (strpos($url, 'https://') === 0) { + return substr($url, strlen('https://')); + } else if (strpos($url, 'http://') === 0) { + return substr($url, strlen('http://')); + } + + return $url; + } + + /** + * try http post first with https and then with http as a fallback + * + * @param string $url + * @param array $fields post parameters + * @return bool + */ + private static function tryHttpPost($url, $fields) { + $protocol = 'https://'; + $success = false; + $try = 0; + while ($success === false && $try < 2) { + $result = \OC::$server->getHTTPHelper()->post($protocol . $url, $fields); + $success = $result['success']; + $try++; + $protocol = 'http://'; + } + + return $result; + } + + /** + * send server-to-server share to remote server + * + * @param string $token + * @param string $shareWith + * @param string $name + * @param int $remote_id + * @param string $owner + * @return bool + */ + private static function sendRemoteShare($token, $shareWith, $name, $remote_id, $owner) { + + list($user, $remote) = explode('@', $shareWith, 2); + + if ($user && $remote) { + $url = $remote . self::BASE_PATH_TO_SHARE_API . '?format=' . self::RESPONSE_FORMAT; + + $local = \OC::$server->getURLGenerator()->getAbsoluteURL(''); + + $fields = array( + 'shareWith' => $user, + 'token' => $token, + 'name' => $name, + 'remoteId' => $remote_id, + 'owner' => $owner, + 'remote' => $local, + ); + + $url = self::removeProtocolFromUrl($url); + $result = self::tryHttpPost($url, $fields); + $status = json_decode($result['result'], true); + + return ($result['success'] && $status['ocs']['meta']['statuscode'] === 100); + + } + + return false; + } + + /** + * send server-to-server unshare to remote server + * + * @param string remote url + * @param int $id share id + * @param string $token + * @return bool + */ + private static function sendRemoteUnshare($remote, $id, $token) { + $url = $remote . self::BASE_PATH_TO_SHARE_API . '/' . $id . '/unshare?format=' . self::RESPONSE_FORMAT; + $fields = array('token' => $token, 'format' => 'json'); + $result = self::tryHttpPost($url, $fields); + $status = json_decode($result['result'], true); + + return ($result['success'] && $status['ocs']['meta']['statuscode'] === 100); + } + /** * check if user can only share with group members * @return bool diff --git a/lib/public/share_backend.php b/lib/public/share_backend.php index 6ab234aecf..1ae63d4c1d 100644 --- a/lib/public/share_backend.php +++ b/lib/public/share_backend.php @@ -65,4 +65,16 @@ interface Share_Backend { */ public function formatItems($items, $format, $parameters = null); + /** + * Check if a given share type is allowd by the back-end + * + * @param int $shareType share type + * @return boolean + * + * The back-end can enable/disable specific share types. Just return true if + * the back-end doesn't provide any specific settings for it and want to allow + * all share types defined by the share API + */ + public function isShareTypeAllowed($shareType); + } diff --git a/tests/lib/app/infoparser.php b/tests/lib/app/infoparser.php index e291b616e8..762a3584cd 100644 --- a/tests/lib/app/infoparser.php +++ b/tests/lib/app/infoparser.php @@ -21,8 +21,9 @@ class InfoParser extends \PHPUnit_Framework_TestCase { public function setUp() { $config = $this->getMockBuilder('\OCP\IConfig') ->disableOriginalConstructor()->getMock(); + $certificateManager = $this->getMock('\OCP\ICertificateManager'); $httpHelper = $this->getMockBuilder('\OC\HTTPHelper') - ->setConstructorArgs(array($config)) + ->setConstructorArgs(array($config, $certificateManager)) ->setMethods(array('getHeaders')) ->getMock(); $urlGenerator = $this->getMockBuilder('\OCP\IURLGenerator') diff --git a/tests/lib/httphelper.php b/tests/lib/httphelper.php index 1cc4232ab4..48d6543f1f 100644 --- a/tests/lib/httphelper.php +++ b/tests/lib/httphelper.php @@ -12,14 +12,17 @@ class TestHTTPHelper extends \Test\TestCase { private $config; /** @var \OC\HTTPHelper */ private $httpHelperMock; + /** @var \OC\Security\CertificateManager */ + private $certificateManager; protected function setUp() { parent::setUp(); $this->config = $this->getMockBuilder('\OCP\IConfig') ->disableOriginalConstructor()->getMock(); + $this->certificateManager = $this->getMock('\OCP\ICertificateManager'); $this->httpHelperMock = $this->getMockBuilder('\OC\HTTPHelper') - ->setConstructorArgs(array($this->config)) + ->setConstructorArgs(array($this->config, $this->certificateManager)) ->setMethods(array('getHeaders')) ->getMock(); } @@ -87,4 +90,23 @@ class TestHTTPHelper extends \Test\TestCase { $this->assertSame($expected, $this->httpHelperMock->isHTTPURL($url)); } + + /** + * @dataProvider postParameters + */ + public function testassemblePostParameters($parameterList, $expectedResult) { + $helper = \OC::$server->getHTTPHelper(); + $result = \Test_Helper::invokePrivate($helper, 'assemblePostParameters', array($parameterList)); + $this->assertSame($expectedResult, $result); + } + + public function postParameters() { + return array( + array(array('k1' => 'v1'), 'k1=v1'), + array(array('k1' => 'v1', 'k2' => 'v2'), 'k1=v1&k2=v2'), + array(array(), ''), + ); + } + + } diff --git a/tests/lib/share/backend.php b/tests/lib/share/backend.php index 0795826694..9c0d7fcb02 100644 --- a/tests/lib/share/backend.php +++ b/tests/lib/share/backend.php @@ -84,4 +84,8 @@ class Test_Share_Backend implements OCP\Share_Backend { return $testItems; } + public function isShareTypeAllowed($shareType) { + return true; + } + } diff --git a/tests/lib/share/share.php b/tests/lib/share/share.php index 1f95502919..b8abfa29a8 100644 --- a/tests/lib/share/share.php +++ b/tests/lib/share/share.php @@ -848,6 +848,23 @@ class Test_Share extends \Test\TestCase { ); } + /** + * @dataProvider urls + */ + function testRemoveProtocolFromUrl($url, $expectedResult) { + $share = new \OC\Share\Share(); + $result = \Test_Helper::invokePrivate($share, 'removeProtocolFromUrl', array($url)); + $this->assertSame($expectedResult, $result); + } + + function urls() { + return array( + array('http://owncloud.org', 'owncloud.org'), + array('https://owncloud.org', 'owncloud.org'), + array('owncloud.org', 'owncloud.org'), + ); + } + /** * @dataProvider dataProviderTestGroupItems * @param type $ungrouped diff --git a/tests/lib/updater.php b/tests/lib/updater.php index 2dab2750dc..f847ffc91b 100644 --- a/tests/lib/updater.php +++ b/tests/lib/updater.php @@ -30,30 +30,30 @@ class UpdaterTest extends \Test\TestCase { $updater = new Updater(\OC::$server->getHTTPHelper(), \OC::$server->getConfig()); $this->assertSame($result, $updater->isUpgradePossible($oldVersion, $newVersion)); } - + public function testBrokenXmlResponse(){ $invalidUpdater = $this->getUpdaterMock('OMG!'); $invalidResult = $invalidUpdater->check(); $this->assertEmpty($invalidResult); } - + public function testEmptyResponse(){ $emptyUpdater = $this->getUpdaterMock(''); $emptyResult = $emptyUpdater->check(); $this->assertEmpty($emptyResult); - + // Error while fetching new contents e.g. too many redirects $falseUpdater = $this->getUpdaterMock(false); $falseResult = $falseUpdater->check(); $this->assertEmpty($falseResult); } - + public function testValidEmptyXmlResponse(){ $updater = $this->getUpdaterMock( '' ); $result = array_map('strval', $updater->check()); - + $this->assertArrayHasKey('version', $result); $this->assertArrayHasKey('versionstring', $result); $this->assertArrayHasKey('url', $result); @@ -63,7 +63,7 @@ class UpdaterTest extends \Test\TestCase { $this->assertEmpty($result['url']); $this->assertEmpty($result['web']); } - + public function testValidUpdateResponse(){ $newUpdater = $this->getUpdaterMock( ' @@ -75,7 +75,7 @@ class UpdaterTest extends \Test\TestCase { ' ); $newResult = array_map('strval', $newUpdater->check()); - + $this->assertArrayHasKey('version', $newResult); $this->assertArrayHasKey('versionstring', $newResult); $this->assertArrayHasKey('url', $newResult); @@ -85,22 +85,22 @@ class UpdaterTest extends \Test\TestCase { $this->assertEquals('http://download.owncloud.org/community/owncloud-7.0.3.zip', $newResult['url']); $this->assertEquals('http://owncloud.org/', $newResult['web']); } - + protected function getUpdaterMock($content){ // Invalidate cache $mockedAppConfig = $this->getMockBuilder('\OC\AppConfig') ->disableOriginalConstructor() ->getMock() ; - + + $certificateManager = $this->getMock('\OCP\ICertificateManager'); $mockedHTTPHelper = $this->getMockBuilder('\OC\HTTPHelper') - ->setConstructorArgs(array(\OC::$server->getConfig())) + ->setConstructorArgs(array(\OC::$server->getConfig(), $certificateManager)) ->getMock() ; - - $mockedHTTPHelper->method('getUrlContent') - ->willReturn($content) - ; + + $mockedHTTPHelper->expects($this->once())->method('getUrlContent')->will($this->returnValue($content)); + return new Updater($mockedHTTPHelper, $mockedAppConfig); }