Commit 8164415b authored by Björn Schießle's avatar Björn Schießle
Browse files

Merge pull request #12749 from owncloud/server2server-sharing-ng

server to server sharing next generation
parents ad6814f9 24993280
......@@ -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);
}
}
......
......@@ -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
......
......@@ -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
......@@ -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
*/
......
......@@ -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
*
......
<?php
namespace OCA\Files_Sharing\AppInfo;
use OCA\Files_Sharing\Application;
$application = new Application();
$application->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');
......@@ -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
......
......@@ -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'
});
}
}
);
}
});
}
});
......@@ -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();
}
}
}
......
......@@ -69,6 +69,8 @@ class PublicAuth extends \Sabre\DAV\Auth\Backend\AbstractBasic {
} else {
return false;