diff options
author | Bjoern Schiessle <bjoern@schiessle.org> | 2016-07-12 18:48:00 +0200 |
---|---|---|
committer | Bjoern Schiessle <bjoern@schiessle.org> | 2016-07-14 16:39:48 +0200 |
commit | 5bbba490c4444f7977e7ad0ce4266acbd67eee86 (patch) | |
tree | d9b9ebe78d82ba03c6be37e93be437b55efdfc5a /apps/federatedfilesharing | |
parent | 2f23054b0126ec3497a7b632ff2399f6f3843a0f (diff) | |
download | nextcloud-server-5bbba490c4444f7977e7ad0ce4266acbd67eee86.tar.gz nextcloud-server-5bbba490c4444f7977e7ad0ce4266acbd67eee86.zip |
move some stuff over to the federatedfilesharing app
Diffstat (limited to 'apps/federatedfilesharing')
6 files changed, 525 insertions, 13 deletions
diff --git a/apps/federatedfilesharing/appinfo/app.php b/apps/federatedfilesharing/appinfo/app.php index 8abdf8cac19..e6fbe615e7f 100644 --- a/apps/federatedfilesharing/appinfo/app.php +++ b/apps/federatedfilesharing/appinfo/app.php @@ -20,11 +20,11 @@ * */ -$app = new \OCA\FederatedFileSharing\AppInfo\Application(); - use OCA\FederatedFileSharing\Notifier; +$app = new \OCA\FederatedFileSharing\AppInfo\Application(); $l = \OC::$server->getL10N('files_sharing'); +$eventDispatcher = \OC::$server->getEventDispatcher(); $app->registerSettings(); @@ -39,3 +39,14 @@ $manager->registerNotifier(function() { 'name' => $l->t('Federated sharing'), ]; }); + +$federatedShareProvider = $app->getFederatedShareProvider(); + +$eventDispatcher->addListener( + 'OCA\Files::loadAdditionalScripts', + function() use ($federatedShareProvider) { + if ($federatedShareProvider->isIncomingServer2serverShareEnabled()) { + \OCP\Util::addScript('federatedfilesharing', 'external'); + } + } +); diff --git a/apps/federatedfilesharing/appinfo/routes.php b/apps/federatedfilesharing/appinfo/routes.php index 2a90ef5b59b..c5822c6b653 100644 --- a/apps/federatedfilesharing/appinfo/routes.php +++ b/apps/federatedfilesharing/appinfo/routes.php @@ -22,5 +22,6 @@ return [ 'routes' => [ ['name' => 'SaveToNextcloud#saveToNextcloud', 'url' => '/saveToNextcloud', 'verb' => 'POST'], + ['name' => 'SaveToNextcloud#askForFederatedShare', 'url' => '/askForFederatedShare', 'verb' => 'POST'], ] ]; diff --git a/apps/federatedfilesharing/js/external.js b/apps/federatedfilesharing/js/external.js new file mode 100644 index 00000000000..e8262092ee8 --- /dev/null +++ b/apps/federatedfilesharing/js/external.js @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2014 Robin Appelman <icewind@owncloud.com> + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ +(function () { + /** + * Shows "add external share" dialog. + * + * @param {String} remote remote server URL + * @param {String} owner owner name + * @param {String} name name of the shared folder + * @param {String} token authentication token + * @param {bool} passwordProtected true if the share is password protected + */ + OCA.Sharing.showAddExternalDialog = function (share, passwordProtected, callback) { + var remote = share.remote; + var owner = share.ownerDisplayName || share.owner; + var name = share.name; + var remoteClean = (remote.substr(0, 8) === 'https://') ? remote.substr(8) : remote.substr(7); + + if (!passwordProtected) { + OC.dialogs.confirm( + t( + 'files_sharing', + 'Do you want to add the remote share {name} from {owner}@{remote}?', + {name: name, owner: owner, remote: remoteClean} + ), + t('files_sharing','Remote share'), + function (result) { + callback(result, share); + }, + true + ).then(this._adjustDialog); + } else { + OC.dialogs.prompt( + t( + 'files_sharing', + 'Do you want to add the remote share {name} from {owner}@{remote}?', + {name: name, owner: owner, remote: remoteClean} + ), + t('files_sharing','Remote share'), + function (result, password) { + share.password = password; + callback(result, share); + }, + true, + t('files_sharing','Remote share password'), + true + ).then(this._adjustDialog); + } + }; + + OCA.Sharing._adjustDialog = function() { + var $dialog = $('.oc-dialog:visible'); + var $buttons = $dialog.find('button'); + // hack the buttons + $dialog.find('.ui-icon').remove(); + $buttons.eq(0).text(t('core', 'Cancel')); + $buttons.eq(1).text(t('files_sharing', 'Add remote share')); + }; + + OCA.Sharing.ExternalShareDialogPlugin = { + + filesApp: null, + + attach: function(filesApp) { + var self = this; + this.filesApp = filesApp; + this.processIncomingShareFromUrl(); + + if (!$('#header').find('div.notifications').length) { + // No notification app, display the modal + this.processSharesToConfirm(); + } + + $('body').on('OCA.Notification.Action', function(e) { + if (e.notification.app === 'files_sharing' && e.notification.object_type === 'remote_share' && e.action.type === 'POST') { + // User accepted a remote share reload + self.filesApp.fileList.reload(); + } + }); + }, + + /** + * Process incoming remote share that might have been passed + * through the URL + */ + processIncomingShareFromUrl: function() { + 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/federatedfilesharing/askForFederatedShare'), + { + remote: share.remote, + token: share.token, + owner: share.owner, + ownerDisplayName: share.ownerDisplayName || share.owner, + name: share.name, + password: password + } + ).done( + function(data) { + OC.Notification.showTemporary(data.message); + } + ).fail( + function(data) { + OC.Notification.showTemporary(JSON.parse(data.responseText).message); + } + ); + } + }; + + // clear hash, it is unlikely that it contain any extra parameters + location.hash = ''; + params.passwordProtected = parseInt(params.protected, 10) === 1; + OCA.Sharing.showAddExternalDialog( + params, + params.passwordProtected, + callbackAddShare + ); + } + }, + + /** + * Retrieve a list of remote shares that need to be approved + */ + processSharesToConfirm: function() { + var fileList = this.filesApp.fileList; + // 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}) + .then(function() { + fileList.reload(); + }); + } else { + // Delete + $.ajax({ + url: OC.generateUrl('/apps/files_sharing/api/externalShares/'+share.id), + type: 'DELETE' + }); + } + } + ); + } + + }); + + } + }; +})(); + +OC.Plugins.register('OCA.Files.App', OCA.Sharing.ExternalShareDialogPlugin); diff --git a/apps/federatedfilesharing/lib/Controller/SaveToNextcloudController.php b/apps/federatedfilesharing/lib/Controller/SaveToNextcloudController.php index f2788cf1ccd..8801d7af4e4 100644 --- a/apps/federatedfilesharing/lib/Controller/SaveToNextcloudController.php +++ b/apps/federatedfilesharing/lib/Controller/SaveToNextcloudController.php @@ -28,8 +28,11 @@ use OCA\FederatedFileSharing\FederatedShareProvider; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; +use OCP\Http\Client\IClientService; +use OCP\IL10N; use OCP\IRequest; use OCP\ISession; +use OCP\IUserSession; use OCP\Share\IManager; class SaveToNextcloudController extends Controller { @@ -46,6 +49,15 @@ class SaveToNextcloudController extends Controller { /** @var ISession */ private $session; + /** @var IL10N */ + private $l; + + /** @var IUserSession */ + private $userSession; + + /** @var IClientService */ + private $clientService; + /** * SaveToNextcloudController constructor. * @@ -55,13 +67,19 @@ class SaveToNextcloudController extends Controller { * @param IManager $shareManager * @param AddressHandler $addressHandler * @param ISession $session + * @param IL10N $l + * @param IUserSession $userSession + * @param IClientService $clientService */ public function __construct($appName, - IRequest $request, - FederatedShareProvider $federatedShareProvider, - IManager $shareManager, - AddressHandler $addressHandler, - ISession $session + IRequest $request, + FederatedShareProvider $federatedShareProvider, + IManager $shareManager, + AddressHandler $addressHandler, + ISession $session, + IL10N $l, + IUserSession $userSession, + IClientService $clientService ) { parent::__construct($appName, $request); @@ -69,6 +87,9 @@ class SaveToNextcloudController extends Controller { $this->shareManager = $shareManager; $this->addressHandler = $addressHandler; $this->session = $session; + $this->l = $l; + $this->userSession = $userSession; + $this->clientService = $clientService; } /** @@ -111,4 +132,48 @@ class SaveToNextcloudController extends Controller { return new JSONResponse(['remoteUrl' => $server]); } + /** + * ask other server to get a federated share + * + * @NoAdminRequired + * + * @param string $token + * @param string $remote + * @param string $password + * @return JSONResponse + */ + public function askForFederatedShare($token, $remote, $password = '') { + // check if server admin allows to mount public links from other servers + if ($this->federatedShareProvider->isIncomingServer2serverShareEnabled() === false) { + return new JSONResponse(['message' => $this->l->t('Server to server sharing is not enabled on this server')], Http::STATUS_BAD_REQUEST); + } + + $shareWith = $this->userSession->getUser()->getUID() . '@' . $this->addressHandler->generateRemoteURL(); + + $httpClient = $this->clientService->newClient(); + + try { + $httpClient->post($remote . '/index.php/apps/federatedfilesharing/saveToNextcloud', + [ + 'body' => + [ + 'token' => $token, + 'shareWith' => rtrim($shareWith, '/'), + 'password' => $password + ] + ] + ); + } catch (\Exception $e) { + if (empty($password)) { + $message = $this->l->t("Couldn't establish a federated share."); + } else { + $message = $this->l->t("Couldn't establish a federated share, maybe the password was wrong."); + } + return new JSONResponse(['message' => $message], Http::STATUS_BAD_REQUEST); + } + + return new JSONResponse(['message' => $this->l->t('Federated Share request was successful, you will receive a invitation. Check your notifications.')]); + + } + } diff --git a/apps/federatedfilesharing/tests/Controller/SaveToNextcloudControllerTest.php b/apps/federatedfilesharing/tests/Controller/SaveToNextcloudControllerTest.php index 60136212c31..0e36df9b25a 100644 --- a/apps/federatedfilesharing/tests/Controller/SaveToNextcloudControllerTest.php +++ b/apps/federatedfilesharing/tests/Controller/SaveToNextcloudControllerTest.php @@ -28,8 +28,11 @@ use OCA\FederatedFileSharing\Controller\SaveToNextcloudController; use OCA\FederatedFileSharing\FederatedShareProvider; use OCP\AppFramework\Http; use OCP\Files\IRootFolder; +use OCP\Http\Client\IClientService; +use OCP\IL10N; use OCP\ISession; use OCP\IUserManager; +use OCP\IUserSession; use OCP\Share; use OCP\Share\IManager; use OCP\Share\IShare; @@ -60,29 +63,44 @@ class SaveToNextcloudControllerTest extends \Test\TestCase { /** @var ISession | \PHPUnit_Framework_MockObject_MockObject */ private $session; + /** @var IL10N | \PHPUnit_Framework_MockObject_MockObject */ + private $l10n; + + /** @var IUserSession | \PHPUnit_Framework_MockObject_MockObject */ + private $userSession; + + /** @var IClientService | \PHPUnit_Framework_MockObject_MockObject */ + private $clientService; + /** @var IShare */ private $share; public function setUp() { parent::setUp(); - $this->request = $this->getMock('OCP\IRequest'); + $this->request = $this->getMockBuilder('OCP\IRequest')->disableOriginalConstructor()->getMock(); $this->federatedShareProvider = $this->getMockBuilder('OCA\FederatedFileSharing\FederatedShareProvider') ->disableOriginalConstructor()->getMock(); - $this->shareManager = $this->getMock('OCP\Share\IManager'); + $this->shareManager = $this->getMockBuilder('OCP\Share\IManager')->disableOriginalConstructor()->getMock(); $this->addressHandler = $this->getMockBuilder('OCA\FederatedFileSharing\AddressHandler') ->disableOriginalConstructor()->getMock(); - $this->rootFolder = $this->getMock('OCP\Files\IRootFolder'); - $this->userManager = $this->getMock('OCP\IUserManager'); + $this->rootFolder = $this->getMockBuilder('OCP\Files\IRootFolder')->disableOriginalConstructor()->getMock(); + $this->userManager = $this->getMockBuilder('OCP\IUserManager')->disableOriginalConstructor()->getMock(); $this->share = new \OC\Share20\Share($this->rootFolder, $this->userManager); - $this->session = $this->getMock('OCP\ISession'); + $this->session = $this->getMockBuilder('OCP\ISession')->disableOriginalConstructor()->getMock(); + $this->l10n = $this->getMockBuilder('OCP\IL10N')->disableOriginalConstructor()->getMock(); + $this->userSession = $this->getMockBuilder('OCP\IUserSession')->disableOriginalConstructor()->getMock(); + $this->clientService = $this->getMockBuilder('OCP\Http\Client\IClientService')->disableOriginalConstructor()->getMock(); $this->controller = new SaveToNextcloudController( 'federatedfilesharing', $this->request, $this->federatedShareProvider, $this->shareManager, $this->addressHandler, - $this->session + $this->session, + $this->l10n, + $this->userSession, + $this->clientService ); } diff --git a/apps/federatedfilesharing/tests/js/externalSpec.js b/apps/federatedfilesharing/tests/js/externalSpec.js new file mode 100644 index 00000000000..362df49252b --- /dev/null +++ b/apps/federatedfilesharing/tests/js/externalSpec.js @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com> + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +describe('OCA.Sharing external tests', function() { + var plugin; + var urlQueryStub; + var promptDialogStub; + var confirmDialogStub; + + function dummyShowDialog() { + var deferred = $.Deferred(); + deferred.resolve(); + return deferred.promise(); + } + + beforeEach(function() { + plugin = OCA.Sharing.ExternalShareDialogPlugin; + urlQueryStub = sinon.stub(OC.Util.History, 'parseUrlQuery'); + + confirmDialogStub = sinon.stub(OC.dialogs, 'confirm', dummyShowDialog); + promptDialogStub = sinon.stub(OC.dialogs, 'prompt', dummyShowDialog); + + plugin.filesApp = { + fileList: { + reload: sinon.stub() + } + }; + }); + afterEach(function() { + urlQueryStub.restore(); + confirmDialogStub.restore(); + promptDialogStub.restore(); + plugin = null; + }); + describe('confirmation dialog from URL', function() { + var testShare; + + /** + * Checks that the server call's query matches what is + * expected. + * + * @param {Object} expectedQuery expected query params + */ + function checkRequest(expectedQuery) { + var request = fakeServer.requests[0]; + var query = OC.parseQueryString(request.requestBody); + expect(request.method).toEqual('POST'); + expect(query).toEqual(expectedQuery); + + request.respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify({status: 'success'}) + ); + expect(plugin.filesApp.fileList.reload.calledOnce).toEqual(true); + } + + beforeEach(function() { + testShare = { + remote: 'http://example.com/owncloud', + token: 'abcdefg', + owner: 'theowner', + ownerDisplayName: 'The Generous Owner', + name: 'the share name' + }; + }); + it('does nothing when no share was passed in URL', function() { + urlQueryStub.returns({}); + plugin.processIncomingShareFromUrl(); + expect(promptDialogStub.notCalled).toEqual(true); + expect(confirmDialogStub.notCalled).toEqual(true); + expect(fakeServer.requests.length).toEqual(0); + }); + it('sends share info to server on confirm', function() { + urlQueryStub.returns(testShare); + plugin.processIncomingShareFromUrl(); + expect(promptDialogStub.notCalled).toEqual(true); + expect(confirmDialogStub.calledOnce).toEqual(true); + confirmDialogStub.getCall(0).args[2](true); + expect(fakeServer.requests.length).toEqual(1); + checkRequest({ + remote: 'http://example.com/owncloud', + token: 'abcdefg', + owner: 'theowner', + ownerDisplayName: 'The Generous Owner', + name: 'the share name', + password: '' + }); + }); + it('sends share info with password to server on confirm', function() { + testShare = _.extend(testShare, {protected: 1}); + urlQueryStub.returns(testShare); + plugin.processIncomingShareFromUrl(); + expect(promptDialogStub.calledOnce).toEqual(true); + expect(confirmDialogStub.notCalled).toEqual(true); + promptDialogStub.getCall(0).args[2](true, 'thepassword'); + expect(fakeServer.requests.length).toEqual(1); + checkRequest({ + remote: 'http://example.com/owncloud', + token: 'abcdefg', + owner: 'theowner', + ownerDisplayName: 'The Generous Owner', + name: 'the share name', + password: 'thepassword' + }); + }); + it('does not send share info on cancel', function() { + urlQueryStub.returns(testShare); + plugin.processIncomingShareFromUrl(); + expect(promptDialogStub.notCalled).toEqual(true); + expect(confirmDialogStub.calledOnce).toEqual(true); + confirmDialogStub.getCall(0).args[2](false); + expect(fakeServer.requests.length).toEqual(0); + }); + }); + describe('show dialog for each share to confirm', function() { + var testShare; + + /** + * Call processSharesToConfirm() and make the fake server + * return the passed response. + * + * @param {Array} response list of shares to process + */ + function processShares(response) { + plugin.processSharesToConfirm(); + + expect(fakeServer.requests.length).toEqual(1); + + var req = fakeServer.requests[0]; + expect(req.method).toEqual('GET'); + expect(req.url).toEqual(OC.webroot + '/index.php/apps/files_sharing/api/externalShares'); + + req.respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify(response) + ); + } + + beforeEach(function() { + testShare = { + id: 123, + remote: 'http://example.com/owncloud', + token: 'abcdefg', + owner: 'theowner', + ownerDisplayName: 'The Generous Owner', + name: 'the share name' + }; + }); + + it('does not show any dialog if no shares to confirm', function() { + processShares([]); + expect(confirmDialogStub.notCalled).toEqual(true); + expect(promptDialogStub.notCalled).toEqual(true); + }); + it('sends accept info to server on confirm', function() { + processShares([testShare]); + + expect(promptDialogStub.notCalled).toEqual(true); + expect(confirmDialogStub.calledOnce).toEqual(true); + + confirmDialogStub.getCall(0).args[2](true); + + expect(fakeServer.requests.length).toEqual(2); + + var request = fakeServer.requests[1]; + var query = OC.parseQueryString(request.requestBody); + expect(request.method).toEqual('POST'); + expect(query).toEqual({id: '123'}); + expect(request.url).toEqual( + OC.webroot + '/index.php/apps/files_sharing/api/externalShares' + ); + + expect(plugin.filesApp.fileList.reload.notCalled).toEqual(true); + request.respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify({status: 'success'}) + ); + expect(plugin.filesApp.fileList.reload.calledOnce).toEqual(true); + }); + it('sends delete info to server on cancel', function() { + processShares([testShare]); + + expect(promptDialogStub.notCalled).toEqual(true); + expect(confirmDialogStub.calledOnce).toEqual(true); + + confirmDialogStub.getCall(0).args[2](false); + + expect(fakeServer.requests.length).toEqual(2); + + var request = fakeServer.requests[1]; + expect(request.method).toEqual('DELETE'); + expect(request.url).toEqual( + OC.webroot + '/index.php/apps/files_sharing/api/externalShares/123' + ); + + expect(plugin.filesApp.fileList.reload.notCalled).toEqual(true); + request.respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify({status: 'success'}) + ); + expect(plugin.filesApp.fileList.reload.notCalled).toEqual(true); + }); + xit('shows another dialog when multiple shares need to be accepted', function() { + // TODO: enable this test when fixing multiple dialogs issue / confirm loop + var testShare2 = _.extend({}, testShare); + testShare2.id = 256; + processShares([testShare, testShare2]); + + // confirm first one + expect(confirmDialogStub.calledOnce).toEqual(true); + confirmDialogStub.getCall(0).args[2](true); + + // next dialog not shown yet + expect(confirmDialogStub.calledOnce); + + // respond to the first accept request + fakeServer.requests[1].respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify({status: 'success'}) + ); + + // don't reload yet, there are other shares to confirm + expect(plugin.filesApp.fileList.reload.notCalled).toEqual(true); + + // cancel second share + expect(confirmDialogStub.calledTwice).toEqual(true); + confirmDialogStub.getCall(1).args[2](true); + + // reload only called at the very end + expect(plugin.filesApp.fileList.reload.calledOnce).toEqual(true); + }); + }); +}); |