Beside some small improvements and bug fixes this will probably the final state for OC8. To test this you need to set up two ownCloud instances. Let's say: URL: myPC/firstOwnCloud user: user1 URL: myPC/secondOwnCloud user: user2 Now user1 can share a file with user2 by entering the username and the URL to the second ownCloud to the share-drop-down, in this case "user2@myPC/secondOwnCloud". The next time user2 login he will get a notification that he received a server-to-server share with the option to accept/decline it. If he accept it the share will be mounted. In both cases a event will be send back to user1 and add a notification to the activity stream that the share was accepted/declined. If user1 decides to unshare the file again from user2 the share will automatically be removed from the second ownCloud server and user2 will see a notification in his activity stream that user1@myPC/firstOwnCloud has unshared the file/folder from him.tags/v8.0.0alpha1
@@ -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); | |||
} | |||
} | |||
} |
@@ -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 | |||
* |
@@ -1,5 +1,16 @@ | |||
<?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; | |||
} | |||
} elseif ($linkItem['share_type'] == \OCP\Share::SHARE_TYPE_REMOTE) { | |||
return true; | |||
} else { | |||
return false; | |||
} |
@@ -0,0 +1,86 @@ | |||
<?php | |||
/** | |||
* @author Lukas Reschke <lukas@owncloud.com> | |||
* @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(); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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)); |
@@ -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 |
@@ -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 | |||
*/ |
@@ -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(); |
@@ -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()); |
@@ -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,20 +489,23 @@ 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 (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 (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; | |||
} | |||
} | |||
var $input = $(this); | |||
var $loading = $dropDown.find('.shareWithLoading'); | |||
$loading.removeClass('hidden'); | |||
@@ -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 = $( "<a>" ); | |||
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('<strong></strong>'); | |||
} | |||
return $( "<li>" ) | |||
.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 += '<a href="#" class="unshare"><img class="svg" alt="'+t('core', 'Unshare')+'" title="'+t('core', 'Unshare')+'" src="'+OC.imagePath('core', 'actions/delete')+'"/></a>'; | |||
html += '<span class="username">' + escapeHTML(shareWithDisplayName) + '</span>'; | |||
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 += '<input id="canEdit-'+escapeHTML(shareWith)+'" type="checkbox" name="edit" class="permissions" '+editChecked+' /><label for="canEdit-'+escapeHTML(shareWith)+'">'+t('core', 'can edit')+'</label>'; | |||
} | |||
showCrudsButton = '<a href="#" class="showCruds"><img class="svg" alt="'+t('core', 'access control')+'" src="'+OC.imagePath('core', 'actions/triangle-s')+'"/></a>'; | |||
if (shareType !== OC.Share.SHARE_TYPE_REMOTE) { | |||
showCrudsButton = '<a href="#" class="showCruds"><img class="svg" alt="'+t('core', 'access control')+'" src="'+OC.imagePath('core', 'actions/triangle-s')+'"/></a>'; | |||
} | |||
html += '<div class="cruds" style="display:none;">'; | |||
if (possiblePermissions & OC.PERMISSION_CREATE) { | |||
html += '<input id="canCreate-'+escapeHTML(shareWith)+'" type="checkbox" name="create" class="permissions" '+createChecked+' data-permissions="'+OC.PERMISSION_CREATE+'"/><label for="canCreate-'+escapeHTML(shareWith)+'">'+t('core', 'create')+'</label>'; | |||
} | |||
if (possiblePermissions & OC.PERMISSION_UPDATE) { | |||
html += '<input id="canUpdate-'+escapeHTML(shareWith)+'" type="checkbox" name="update" class="permissions" '+updateChecked+' data-permissions="'+OC.PERMISSION_UPDATE+'"/><label for="canUpdate-'+escapeHTML(shareWith)+'">'+t('core', 'change')+'</label>'; | |||
} | |||
if (possiblePermissions & OC.PERMISSION_DELETE) { | |||
html += '<input id="canDelete-'+escapeHTML(shareWith)+'" type="checkbox" name="delete" class="permissions" '+deleteChecked+' data-permissions="'+OC.PERMISSION_DELETE+'"/><label for="canDelete-'+escapeHTML(shareWith)+'">'+t('core', 'delete')+'</label>'; | |||
} | |||
if (possiblePermissions & OC.PERMISSION_CREATE) { | |||
html += '<input id="canCreate-' + escapeHTML(shareWith) + '" type="checkbox" name="create" class="permissions" ' + createChecked + ' data-permissions="' + OC.PERMISSION_CREATE + '"/><label for="canCreate-' + escapeHTML(shareWith) + '">' + t('core', 'create') + '</label>'; | |||
} | |||
if (possiblePermissions & OC.PERMISSION_UPDATE) { | |||
html += '<input id="canUpdate-' + escapeHTML(shareWith) + '" type="checkbox" name="update" class="permissions" ' + updateChecked + ' data-permissions="' + OC.PERMISSION_UPDATE + '"/><label for="canUpdate-' + escapeHTML(shareWith) + '">' + t('core', 'change') + '</label>'; | |||
} | |||
if (possiblePermissions & OC.PERMISSION_DELETE) { | |||
html += '<input id="canDelete-' + escapeHTML(shareWith) + '" type="checkbox" name="delete" class="permissions" ' + deleteChecked + ' data-permissions="' + OC.PERMISSION_DELETE + '"/><label for="canDelete-' + escapeHTML(shareWith) + '">' + t('core', 'delete') + '</label>'; | |||
} | |||
html += '</div>'; | |||
html += '</li>'; | |||
html = $(html).appendTo('#shareWithList'); |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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) { |
@@ -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(); |
@@ -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; | |||
} |
@@ -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 |
@@ -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); | |||
} |
@@ -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') |
@@ -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(), ''), | |||
); | |||
} | |||
} |
@@ -84,4 +84,8 @@ class Test_Share_Backend implements OCP\Share_Backend { | |||
return $testItems; | |||
} | |||
public function isShareTypeAllowed($shareType) { | |||
return true; | |||
} | |||
} |
@@ -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 |
@@ -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( | |||
'<?xml version="1.0"?><owncloud><version></version><versionstring></versionstring><url></url><web></web></owncloud>' | |||
); | |||
$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( | |||
'<?xml version="1.0"?> | |||
@@ -75,7 +75,7 @@ class UpdaterTest extends \Test\TestCase { | |||
</owncloud>' | |||
); | |||
$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); | |||
} | |||