From 68c38ae8c297ea2cca73d75d9a6cc047cde8d4d7 Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Sat, 11 Jun 2016 11:25:45 +0200 Subject: [PATCH 1/2] [stable9] Backport files drop feature --- apps/dav/appinfo/v1/publicwebdav.php | 4 + apps/files/ajax/upload.php | 24 ++++ apps/files_sharing/ajax/publicpreview.php | 6 + apps/files_sharing/ajax/shareinfo.php | 9 ++ apps/files_sharing/api/share20ocs.php | 1 + apps/files_sharing/css/public.css | 59 +++++++++ apps/files_sharing/js/files_drop.js | 80 +++++++++++ .../lib/controllers/sharecontroller.php | 10 +- apps/files_sharing/templates/public.php | 85 ++++++++---- .../tests/controller/sharecontroller.php | 64 +++++++-- core/controller/avatarcontroller.php | 3 +- core/js/sharedialoglinkshareview.js | 124 +++++++----------- core/js/shareitemmodel.js | 64 +++++---- lib/private/share20/manager.php | 5 +- tests/lib/share20/managertest.php | 1 - 15 files changed, 392 insertions(+), 147 deletions(-) create mode 100644 apps/files_sharing/js/files_drop.js diff --git a/apps/dav/appinfo/v1/publicwebdav.php b/apps/dav/appinfo/v1/publicwebdav.php index b0ee264aac..8b85a3cbdb 100644 --- a/apps/dav/appinfo/v1/publicwebdav.php +++ b/apps/dav/appinfo/v1/publicwebdav.php @@ -59,6 +59,7 @@ $server = $serverFactory->createServer($baseuri, $requestUri, $authBackend, func $rootShare = \OCP\Share::resolveReShare($share); $owner = $rootShare['uid_owner']; $isWritable = $share['permissions'] & (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_CREATE); + $isReadable = $share['permissions'] & \OCP\Constants::PERMISSION_READ; $fileId = $share['file_source']; if (!$isWritable) { @@ -66,6 +67,9 @@ $server = $serverFactory->createServer($baseuri, $requestUri, $authBackend, func return new \OC\Files\Storage\Wrapper\PermissionsMask(array('storage' => $storage, 'mask' => \OCP\Constants::PERMISSION_READ + \OCP\Constants::PERMISSION_SHARE)); }); } + if (!$isReadable) { + return false; + } OC_Util::setupFS($owner); $ownerView = \OC\Files\Filesystem::getView(); diff --git a/apps/files/ajax/upload.php b/apps/files/ajax/upload.php index d14414125f..985284bad5 100644 --- a/apps/files/ajax/upload.php +++ b/apps/files/ajax/upload.php @@ -161,6 +161,15 @@ if (\OC\Files\Filesystem::isValidPath($dir) === true) { $resolution = null; } + if(isset($_POST['dirToken'])) { + // If it is a read only share the resolution will always be autorename + $shareManager = \OC::$server->getShareManager(); + $share = $shareManager->getShareByToken((string)$_POST['dirToken']); + if (!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) { + $resolution = 'autorename'; + } + } + // target directory for when uploading folders $relativePath = ''; if(!empty($_POST['file_directory'])) { @@ -247,6 +256,21 @@ if (\OC\Files\Filesystem::isValidPath($dir) === true) { } if ($error === false) { + // Do not leak file information if it is a read-only share + if(isset($_POST['dirToken'])) { + $shareManager = \OC::$server->getShareManager(); + $share = $shareManager->getShareByToken((string)$_POST['dirToken']); + if (!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) { + $newResults = []; + foreach($result as $singleResult) { + $fileName = $singleResult['originalname']; + $newResults['filename'] = $fileName; + $newResults['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($fileName); + } + $result = $newResults; + } + } + OCP\JSON::encodedPrint($result); } else { OCP\JSON::error(array(array('data' => array_merge(array('message' => $error, 'code' => $errorCode), $storageStats)))); diff --git a/apps/files_sharing/ajax/publicpreview.php b/apps/files_sharing/ajax/publicpreview.php index ac48ee0191..898aea1bd5 100644 --- a/apps/files_sharing/ajax/publicpreview.php +++ b/apps/files_sharing/ajax/publicpreview.php @@ -42,6 +42,12 @@ if($token === ''){ } $linkedItem = \OCP\Share::getShareByToken($token); +$shareManager = \OC::$server->getShareManager(); +$share = $shareManager->getShareByToken($token); +if(!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) { + OCP\JSON::error(array('data' => 'Share is not readable.')); + exit(); +} if($linkedItem === false || ($linkedItem['item_type'] !== 'file' && $linkedItem['item_type'] !== 'folder')) { \OC_Response::setStatus(\OC_Response::STATUS_NOT_FOUND); \OCP\Util::writeLog('core-preview', 'Passed token parameter is not valid', \OCP\Util::DEBUG); diff --git a/apps/files_sharing/ajax/shareinfo.php b/apps/files_sharing/ajax/shareinfo.php index e531e84ebb..a900d28ce6 100644 --- a/apps/files_sharing/ajax/shareinfo.php +++ b/apps/files_sharing/ajax/shareinfo.php @@ -62,6 +62,15 @@ if (!$isWritable) { $rootInfo = \OC\Files\Filesystem::getFileInfo($path); $rootView = new \OC\Files\View(''); + +$shareManager = \OC::$server->getShareManager(); +$share = $shareManager->getShareByToken($token); +$sharePermissions= (int)$share->getPermissions(); +if(!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) { + OCP\JSON::error(array('data' => 'Share is not readable.')); + exit(); +} + /** * @param \OCP\Files\FileInfo $dir * @param \OC\Files\View $view diff --git a/apps/files_sharing/api/share20ocs.php b/apps/files_sharing/api/share20ocs.php index 1aeec1e1cc..f6961079e9 100644 --- a/apps/files_sharing/api/share20ocs.php +++ b/apps/files_sharing/api/share20ocs.php @@ -584,6 +584,7 @@ class Share20OCS { if ($newPermissions !== null && $newPermissions !== \OCP\Constants::PERMISSION_READ && + $newPermissions !== (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE) && $newPermissions !== (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE)) { return new \OC_OCS_Result(null, 400, 'can\'t change permission for public link share'); } diff --git a/apps/files_sharing/css/public.css b/apps/files_sharing/css/public.css index d09947dab2..f72c413609 100644 --- a/apps/files_sharing/css/public.css +++ b/apps/files_sharing/css/public.css @@ -158,3 +158,62 @@ thead { opacity: 1; cursor: pointer; } + +#public-upload .avatardiv { + margin: 0 auto; +} + +#public-upload #emptycontent h2 { + margin: 10px 0 5px 0; +} + +#public-upload #emptycontent h2+p { + margin-bottom: 30px; +} + +#public-upload #emptycontent .icon-folder { + height: 16px; + width: 16px; + background-size: 16px; + display: inline-block; + vertical-align: text-top; + margin-bottom: 0; + margin-right: 5px; + opacity: 1; +} + +#public-upload #emptycontent .button { + background-size: 16px; + height: 16px; + width: 16px; + background-position: 16px; + opacity: .7; + font-size: 20px; + margin: 20px; + padding: 10px 20px; + padding-left: 42px; + font-weight: normal; +} + +#public-upload #emptycontent ul { + width: 160px; + margin: 5px auto; + text-align: left; +} + +#public-upload #emptycontent li { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + padding: 7px 0; +} + +#public-upload #emptycontent li img { + vertical-align: text-bottom; + margin-right: 5px; +} + +#public-upload li span.icon-loading-small { + padding-left: 18px; + margin-right: 7px; +} \ No newline at end of file diff --git a/apps/files_sharing/js/files_drop.js b/apps/files_sharing/js/files_drop.js new file mode 100644 index 0000000000..567d0195d2 --- /dev/null +++ b/apps/files_sharing/js/files_drop.js @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2016 Lukas Reschke + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +(function ($) { + var Drop = { + initialize: function () { + $(document).bind('drop dragover', function (e) { + // Prevent the default browser drop action: + e.preventDefault(); + }); + $('#public-upload').fileupload({ + url: OC.linkTo('files', 'ajax/upload.php'), + dataType: 'json', + dropZone: $('#public-upload'), + formData: { + dirToken: $('#sharingToken').val() + }, + add: function(e, data) { + var errors = []; + if(data.files[0]['size'] && data.files[0]['size'] > $('#maxFilesizeUpload').val()) { + errors.push('File is too big'); + } + + $('#drop-upload-done-indicator').addClass('hidden'); + $('#drop-upload-progress-indicator').removeClass('hidden'); + _.each(data['files'], function(file) { + if(errors.length === 0) { + $('#public-upload ul').append('
  • '+escapeHTML(file.name)+'
  • '); + $('[data-toggle="tooltip"]').tooltip(); + data.submit(); + } else { + OC.Notification.showTemporary(OC.L10N.translate('files_sharing', 'Could not upload "{filename}"', {filename: file.name})); + $('#public-upload ul').append('
  • '+escapeHTML(file.name)+'
  • '); + $('[data-toggle="tooltip"]').tooltip(); + } + }); + }, + success: function (response) { + if(response.status !== 'error') { + var mimeTypeUrl = OC.MimeType.getIconUrl(response['mimetype']); + $('#public-upload ul li[data-name="' + escapeHTML(response['filename']) + '"]').html(' ' + escapeHTML(response['filename'])); + $('[data-toggle="tooltip"]').tooltip(); + } + }, + progressall: function (e, data) { + var progress = parseInt(data.loaded / data.total * 100, 10); + if(progress === 100) { + $('#drop-upload-done-indicator').removeClass('hidden'); + $('#drop-upload-progress-indicator').addClass('hidden'); + } else { + $('#drop-upload-done-indicator').addClass('hidden'); + $('#drop-upload-progress-indicator').removeClass('hidden'); + } + } + }); + $('#public-upload .button.icon-upload').click(function(e) { + e.preventDefault(); + $('#public-upload #emptycontent input').focus().trigger('click'); + }); + } + }; + + $(document).ready(function() { + if($('#upload-only-interface').val() === "1") { + $('.avatardiv').avatar($('#sharingUserId').val(), 128, true); + } + + OCA.Files_Sharing_Drop = Drop; + OCA.Files_Sharing_Drop.initialize(); + }); + + +})(jQuery); diff --git a/apps/files_sharing/lib/controllers/sharecontroller.php b/apps/files_sharing/lib/controllers/sharecontroller.php index 982ce1154a..5435bebbe3 100644 --- a/apps/files_sharing/lib/controllers/sharecontroller.php +++ b/apps/files_sharing/lib/controllers/sharecontroller.php @@ -307,6 +307,7 @@ class ShareController extends Controller { $shareTmpl['fileSize'] = \OCP\Util::humanFileSize($share->getNode()->getSize()); // Show file list + $hideFileList = false; if ($share->getNode() instanceof \OCP\Files\Folder) { $shareTmpl['dir'] = $rootFolder->getRelativePath($path->getPath()); @@ -322,12 +323,14 @@ class ShareController extends Controller { $uploadLimit = Util::uploadLimit(); $maxUploadFilesize = min($freeSpace, $uploadLimit); + $hideFileList = $share->getPermissions() & \OCP\Constants::PERMISSION_READ ? false : true; $folder = new Template('files', 'list', ''); $folder->assign('dir', $rootFolder->getRelativePath($path->getPath())); $folder->assign('dirToken', $token); $folder->assign('permissions', \OCP\Constants::PERMISSION_READ); $folder->assign('isPublic', true); + $folder->assign('hideFileList', $hideFileList); $folder->assign('publicUploadEnabled', 'no'); $folder->assign('uploadMaxFilesize', $maxUploadFilesize); $folder->assign('uploadMaxHumanFilesize', OCP\Util::humanFileSize($maxUploadFilesize)); @@ -338,6 +341,8 @@ class ShareController extends Controller { $shareTmpl['folder'] = $folder->fetchPage(); } + $shareTmpl['hideFileList'] = $hideFileList; + $shareTmpl['shareOwner'] = $this->userManager->get($share->getShareOwner())->getDisplayName(); $shareTmpl['downloadURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.downloadShare', array('token' => $token)); $shareTmpl['maxSizeAnimateGif'] = $this->config->getSystemValue('max_filesize_animated_gifs_public_sharing', 10); $shareTmpl['previewEnabled'] = $this->config->getSystemValue('enable_previews', true); @@ -360,12 +365,15 @@ class ShareController extends Controller { * @param string $files * @param string $path * @param string $downloadStartSecret - * @return void|RedirectResponse + * @return void|OCP\AppFramework\Http\Response */ public function downloadShare($token, $files = null, $path = '', $downloadStartSecret = '') { \OC_User::setIncognitoMode(true); $share = $this->shareManager->getShareByToken($token); + if(!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) { + return new OCP\AppFramework\Http\DataResponse('Share is read-only'); + } // Share is password protected - check whether the user is permitted to access the share if ($share->getPassword() !== null && !$this->linkShareAuth($share)) { diff --git a/apps/files_sharing/templates/public.php b/apps/files_sharing/templates/public.php index b9235b7ebc..ea200916ed 100644 --- a/apps/files_sharing/templates/public.php +++ b/apps/files_sharing/templates/public.php @@ -8,8 +8,8 @@ OCP\Util::addStyle('files_sharing', 'mobile'); OCP\Util::addScript('files_sharing', 'public'); OCP\Util::addScript('files', 'fileactions'); OCP\Util::addScript('files', 'fileactionsmenu'); -OCP\Util::addScript('files', 'jquery.iframe-transport'); OCP\Util::addScript('files', 'jquery.fileupload'); +OCP\Util::addScript('files_sharing', 'files_drop'); // JS required for folders OCP\Util::addStyle('files', 'files'); @@ -33,6 +33,7 @@ $thumbSize = 1024; + @@ -42,7 +43,16 @@ $thumbSize = 1024; - +getIniWrapper()->getBytes('upload_max_filesize'); +$post_max_size = OC::$server->getIniWrapper()->getBytes('post_max_size'); +$maxUploadFilesize = min($upload_max_filesize, $post_max_size); +?> + + + + + @@ -56,11 +66,11 @@ $thumbSize = 1024;

    getName()); - } else { - print_unescaped($theme->getHTMLName()); - } + if(OC_Util::getEditionString() === '') { + p($theme->getName()); + } else { + print_unescaped($theme->getHTMLName()); + } ?>

    @@ -69,25 +79,28 @@ $thumbSize = 1024;
    - - - - + if (!isset($_['hideFileList']) || (isset($_['hideFileList']) && $_['hideFileList'] === false)) { + if ($_['server2serversharing']) { + ?> + + + + + + + "/> + t('Download'))?> + - - "/> - t('Download'))?> -
    - +
    +
    @@ -95,7 +108,7 @@ $thumbSize = 1024;
    -
    @@ -115,10 +128,34 @@ $thumbSize = 1024;
    + + +
    +
    +
    +

    t('Upload files to %s', [$_['shareOwner']])) ?>

    +

    + + + t('Select or drop files')) ?> + + +
      +
    +
    +
    +
    + + +
    + +
    +

    getLongFooter()); ?>

    - + \ No newline at end of file diff --git a/apps/files_sharing/tests/controller/sharecontroller.php b/apps/files_sharing/tests/controller/sharecontroller.php index db8c7fe553..24a6bc7984 100644 --- a/apps/files_sharing/tests/controller/sharecontroller.php +++ b/apps/files_sharing/tests/controller/sharecontroller.php @@ -1,8 +1,9 @@ * @author Georg Ehrke * @author Joas Schilling - * @author Lukas Reschke + * @author Lukas Reschke * @author Morris Jobke * @author Robin Appelman * @author Roeland Jago Douma @@ -26,9 +27,12 @@ * */ -namespace OCA\Files_Sharing\Controllers; +namespace OCA\Files_Sharing\Tests\Controllers; use OC\Files\Filesystem; +use OCA\FederatedFileSharing\FederatedShareProvider; +use OCA\Files_Sharing\Controllers\ShareController; +use OCP\AppFramework\Http\DataResponse; use OCP\Share\Exceptions\ShareNotFound; use OCP\AppFramework\Http\NotFoundResponse; use OCP\AppFramework\Http\RedirectResponse; @@ -66,8 +70,11 @@ class ShareControllerTest extends \Test\TestCase { private $shareManager; /** @var IUserManager | \PHPUnit_Framework_MockObject_MockObject */ private $userManager; + /** @var FederatedShareProvider | \PHPUnit_Framework_MockObject_MockObject */ + private $federatedShareProvider; protected function setUp() { + parent::setUp(); $this->appName = 'files_sharing'; $this->shareManager = $this->getMockBuilder('\OC\Share20\Manager')->disableOriginalConstructor()->getMock(); @@ -76,6 +83,12 @@ class ShareControllerTest extends \Test\TestCase { $this->previewManager = $this->getMock('\OCP\IPreview'); $this->config = $this->getMock('\OCP\IConfig'); $this->userManager = $this->getMock('\OCP\IUserManager'); + $this->federatedShareProvider = $this->getMockBuilder('OCA\FederatedFileSharing\FederatedShareProvider') + ->disableOriginalConstructor()->getMock(); + $this->federatedShareProvider->expects($this->any()) + ->method('isOutgoingServer2serverShareEnabled')->willReturn(true); + $this->federatedShareProvider->expects($this->any()) + ->method('isIncomingServer2serverShareEnabled')->willReturn(true); $this->shareController = new \OCA\Files_Sharing\Controllers\ShareController( $this->appName, @@ -88,7 +101,8 @@ class ShareControllerTest extends \Test\TestCase { $this->shareManager, $this->session, $this->previewManager, - $this->getMock('\OCP\Files\IRootFolder') + $this->getMock('\OCP\Files\IRootFolder'), + $this->federatedShareProvider ); @@ -116,6 +130,7 @@ class ShareControllerTest extends \Test\TestCase { // Set old user \OC_User::setUserId($this->oldUser); \OC_Util::setupFS($this->oldUser); + parent::tearDown(); } public function testShowAuthenticateNotAuthenticated() { @@ -173,7 +188,7 @@ class ShareControllerTest extends \Test\TestCase { $this->assertEquals($expectedResponse, $response); } - public function testAutehnticateInvalidToken() { + public function testAuthenticateInvalidToken() { $this->shareManager ->expects($this->once()) ->method('getShareByToken') @@ -247,11 +262,11 @@ class ShareControllerTest extends \Test\TestCase { ->method('access') ->with($this->callback(function(array $data) { return $data['itemType'] === 'file' && - $data['itemSource'] === 100 && - $data['uidOwner'] === 'initiator' && - $data['token'] === 'token' && - $data['errorCode'] === 403 && - $data['errorMessage'] === 'Wrong password'; + $data['itemSource'] === 100 && + $data['uidOwner'] === 'initiator' && + $data['token'] === 'token' && + $data['errorCode'] === 403 && + $data['errorMessage'] === 'Wrong password'; })); $response = $this->shareController->authenticate('token', 'invalidpassword'); @@ -323,6 +338,8 @@ class ShareControllerTest extends \Test\TestCase { [ ['max_filesize_animated_gifs_public_sharing', 10, 10], ['enable_previews', true, true], + ['preview_max_x', 1024, 1024], + ['preview_max_y', 1024, 1024], ] ); $shareTmpl['maxSizeAnimateGif'] = $this->config->getSystemValue('max_filesize_animated_gifs_public_sharing', 10); @@ -354,6 +371,8 @@ class ShareControllerTest extends \Test\TestCase { 'maxSizeAnimateGif' => 10, 'previewSupported' => true, 'previewEnabled' => true, + 'hideFileList' => false, + 'shareOwner' => 'ownerDisplay' ); $csp = new \OCP\AppFramework\Http\ContentSecurityPolicy(); @@ -412,10 +431,13 @@ class ShareControllerTest extends \Test\TestCase { $this->shareController->showShare('token'); } - public function testDownloadShare() { $share = $this->getMock('\OCP\Share\IShare'); $share->method('getPassword')->willReturn('password'); + $share + ->expects($this->once()) + ->method('getPermissions') + ->willReturn(\OCP\Constants::PERMISSION_READ); $this->shareManager ->expects($this->once()) @@ -434,4 +456,24 @@ class ShareControllerTest extends \Test\TestCase { $this->assertEquals($expectedResponse, $response); } -} + public function testDownloadShareWithCreateOnlyShare() { + $share = $this->getMock('\OCP\Share\IShare'); + $share->method('getPassword')->willReturn('password'); + $share + ->expects($this->once()) + ->method('getPermissions') + ->willReturn(\OCP\Constants::PERMISSION_CREATE); + + $this->shareManager + ->expects($this->once()) + ->method('getShareByToken') + ->with('validtoken') + ->willReturn($share); + + // Test with a password protected share and no authentication + $response = $this->shareController->downloadShare('validtoken'); + $expectedResponse = new DataResponse('Share is read-only'); + $this->assertEquals($expectedResponse, $response); + } + +} \ No newline at end of file diff --git a/core/controller/avatarcontroller.php b/core/controller/avatarcontroller.php index adfe38ab2d..6f84a86c3e 100644 --- a/core/controller/avatarcontroller.php +++ b/core/controller/avatarcontroller.php @@ -85,7 +85,7 @@ class AvatarController extends Controller { IL10N $l10n, IUserManager $userManager, IUserSession $userSession, - Folder $userFolder, + Folder $userFolder = null, ILogger $logger) { parent::__construct($appName, $request); @@ -101,6 +101,7 @@ class AvatarController extends Controller { /** * @NoAdminRequired * @NoCSRFRequired + * @PublicPage * * @param string $userId * @param int $size diff --git a/core/js/sharedialoglinkshareview.js b/core/js/sharedialoglinkshareview.js index 26b0606d81..e1cecfde58 100644 --- a/core/js/sharedialoglinkshareview.js +++ b/core/js/sharedialoglinkshareview.js @@ -24,30 +24,31 @@ '
    ' + '' + '' + - ' {{#if showPasswordCheckBox}}' + - '' + - '' + - ' {{/if}}' + - '
    ' + - ' ' + - ' ' + - ' ' + - '
    ' + ' {{#if publicUpload}}' + '
    ' + ' ' + ' ' + '' + '
    ' + + '{{#if hideFileList}}' + + '
    ' + + ' ' + + ' ' + + '' + + '
    ' + + '{{/if}}' + ' {{/if}}' + - ' {{#if mailPublicNotificationEnabled}}' + - '' + + ' {{#if showPasswordCheckBox}}' + + '' + + '' + ' {{/if}}' + + '
    ' + + ' ' + + ' ' + + ' ' + + '
    ' + '{{else}}' + - // FIXME: this doesn't belong in this view + // FIXME: this doesn't belong in this view '{{#if noSharingPlaceholder}}{{/if}}' + '{{/if}}' ; @@ -76,12 +77,12 @@ showLink: true, events: { - 'submit .emailPrivateLinkForm': '_onEmailPrivateLink', 'focusout input.linkPassText': 'onPasswordEntered', 'keyup input.linkPassText': 'onPasswordKeyUp', 'click .linkCheckbox': 'onLinkCheckBoxChange', 'click .linkText': 'onLinkTextClick', 'change .publicUploadCheckbox': 'onAllowPublicUploadChange', + 'change .hideFileListCheckbox': 'onHideFileListChange', 'click .showPasswordCheckbox': 'onShowPasswordClick' }, @@ -100,6 +101,10 @@ view.render(); }); + this.model.on('change:hideFileListStatus', function() { + view.render(); + }); + this.model.on('change:linkShare', function() { view.render(); }); @@ -112,12 +117,12 @@ _.bindAll( this, - '_onEmailPrivateLink', 'onLinkCheckBoxChange', 'onPasswordEntered', 'onPasswordKeyUp', 'onLinkTextClick', 'onShowPasswordClick', + 'onHideFileListChange', 'onAllowPublicUploadChange' ); }, @@ -218,38 +223,23 @@ }); }, - _onEmailPrivateLink: function(event) { - event.preventDefault(); + onHideFileListChange: function () { + var $checkbox = this.$('.hideFileListCheckbox'); + $checkbox.siblings('.icon-loading-small').removeClass('hidden').addClass('inlineblock'); - var $emailField = this.$el.find('.emailField'); - var $emailButton = this.$el.find('.emailButton'); - var email = $emailField.val(); - if (email !== '') { - $emailField.prop('disabled', true); - $emailButton.prop('disabled', true); - $emailField.val(t('core', 'Sending ...')); - this.model.sendEmailPrivateLink(email).done(function() { - $emailField.css('font-weight', 'bold').val(t('core','Email sent')); - setTimeout(function() { - $emailField.val(''); - $emailField.css('font-weight', 'normal'); - $emailField.prop('disabled', false); - $emailButton.prop('disabled', false); - }, 2000); - }).fail(function() { - $emailField.val(email); - $emailField.css('font-weight', 'normal'); - $emailField.prop('disabled', false); - $emailButton.prop('disabled', false); - }); + var permissions = OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE | OC.PERMISSION_READ; + if ($checkbox.is(':checked')) { + permissions = OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE; } - return false; + + this.model.saveLinkShare({ + permissions: permissions + }); }, render: function() { var linkShareTemplate = this.template(); var resharingAllowed = this.model.sharePermissionPossible(); - var email = this.$el.find('.emailField').val(); if(!resharingAllowed || !this.showLink @@ -274,15 +264,23 @@ publicUploadChecked = 'checked="checked"'; } + var hideFileList = publicUploadChecked; + + var hideFileListChecked = ''; + if(this.model.isHideFileListSet()) { + hideFileListChecked = 'checked="checked"'; + } + var isLinkShare = this.model.get('linkShare').isLinkShare; var isPasswordSet = !!this.model.get('linkShare').password; var showPasswordCheckBox = isLinkShare && ( !this.configModel.get('enforcePasswordForPublicLink') - || !this.model.get('linkShare').password); + || !this.model.get('linkShare').password); this.$el.html(linkShareTemplate({ cid: this.cid, shareAllowed: true, + hideFileList: hideFileList, isLinkShare: isLinkShare, shareLinkURL: this.model.get('linkShare').link, linkShareLabel: t('core', 'Share link'), @@ -294,46 +292,14 @@ showPasswordCheckBox: showPasswordCheckBox, publicUpload: publicUpload && isLinkShare, publicUploadChecked: publicUploadChecked, + hideFileListChecked: hideFileListChecked, publicUploadLabel: t('core', 'Allow editing'), + hideFileListLabel: t('core', 'Hide file listing'), mailPublicNotificationEnabled: isLinkShare && this.configModel.isMailPublicNotificationEnabled(), mailPrivatePlaceholder: t('core', 'Email link to person'), - mailButtonText: t('core', 'Send'), - email: email + mailButtonText: t('core', 'Send') })); - var $emailField = this.$el.find('.emailField'); - if (isLinkShare && $emailField.length !== 0) { - $emailField.autocomplete({ - minLength: 1, - source: function (search, response) { - $.get( - OC.generateUrl('core/ajax/share.php'), { - fetch: 'getShareWithEmail', - search: search.term - }, function(result) { - if (result.status == 'success' && result.data.length > 0) { - response(result.data); - } - }); - }, - select: function( event, item ) { - $emailField.val(item.item.email); - return false; - } - }) - .data("ui-autocomplete")._renderItem = function( ul, item ) { - return $('
  • ') - .append('' + escapeHTML(item.displayname) + "
    " + escapeHTML(item.email) + '
    ' ) - .appendTo( ul ); - }; - } - - // TODO drop with IE8 drop - if($('html').hasClass('ie8')) { - this.$el.find('#linkPassText').removeAttr('placeholder'); - this.$el.find('#linkPassText').val(''); - } - this.delegateEvents(); return this; @@ -354,4 +320,4 @@ OC.Share.ShareDialogLinkShareView = ShareDialogLinkShareView; -})(); +})(); \ No newline at end of file diff --git a/core/js/shareitemmodel.js b/core/js/shareitemmodel.js index 292230d26d..a3accb4f5d 100644 --- a/core/js/shareitemmodel.js +++ b/core/js/shareitemmodel.js @@ -154,21 +154,17 @@ // Default permissions are Edit (CRUD) and Share // Check if these permissions are possible var permissions = OC.PERMISSION_READ; - if (shareType === OC.Share.SHARE_TYPE_REMOTE) { - permissions = OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE | OC.PERMISSION_READ; - } else { - if (this.updatePermissionPossible()) { - permissions = permissions | OC.PERMISSION_UPDATE; - } - if (this.createPermissionPossible()) { - permissions = permissions | OC.PERMISSION_CREATE; - } - if (this.deletePermissionPossible()) { - permissions = permissions | OC.PERMISSION_DELETE; - } - if (this.configModel.get('isResharingAllowed') && (this.sharePermissionPossible())) { - permissions = permissions | OC.PERMISSION_SHARE; - } + if (this.updatePermissionPossible()) { + permissions = permissions | OC.PERMISSION_UPDATE; + } + if (this.createPermissionPossible()) { + permissions = permissions | OC.PERMISSION_CREATE; + } + if (this.deletePermissionPossible()) { + permissions = permissions | OC.PERMISSION_DELETE; + } + if (this.configModel.get('isResharingAllowed') && (this.sharePermissionPossible())) { + permissions = permissions | OC.PERMISSION_SHARE; } attributes.permissions = permissions; @@ -276,6 +272,13 @@ return this.get('allowPublicUploadStatus'); }, + /** + * @returns {boolean} + */ + isHideFileListSet: function() { + return this.get('hideFileListStatus'); + }, + /** * @returns {boolean} */ @@ -411,12 +414,6 @@ if(!_.isObject(share)) { throw "Unknown Share"; } - if( share.share_type === OC.Share.SHARE_TYPE_REMOTE - && ( permission === OC.PERMISSION_SHARE - || permission === OC.PERMISSION_DELETE)) - { - return false; - } return (share.permissions & permission) === permission; }, @@ -487,7 +484,7 @@ } else { deferred.resolve(); } - }); + }); return deferred.promise(); }, @@ -557,8 +554,8 @@ */ editPermissionPossible: function() { return this.createPermissionPossible() - || this.updatePermissionPossible() - || this.deletePermissionPossible(); + || this.updatePermissionPossible() + || this.deletePermissionPossible(); }, /** @@ -566,8 +563,8 @@ */ hasEditPermission: function(shareIndex) { return this.hasCreatePermission(shareIndex) - || this.hasUpdatePermission(shareIndex) - || this.hasDeletePermission(shareIndex); + || this.hasUpdatePermission(shareIndex) + || this.hasDeletePermission(shareIndex); }, _getUrl: function(base, params) { @@ -695,6 +692,16 @@ }); } + var hideFileListStatus = false; + if(!_.isUndefined(data.shares)) { + $.each(data.shares, function (key, value) { + if (value.share_type === OC.Share.SHARE_TYPE_LINK) { + hideFileListStatus = (value.permissions & OC.PERMISSION_READ) ? false : true; + return true; + } + }); + } + /** @type {OC.Share.Types.ShareInfo[]} **/ var shares = _.map(data.shares, function(share) { // properly parse some values because sometimes the server @@ -767,7 +774,8 @@ shares: shares, linkShare: linkShare, permissions: permissions, - allowPublicUploadStatus: allowPublicUploadStatus + allowPublicUploadStatus: allowPublicUploadStatus, + hideFileListStatus: hideFileListStatus }; }, @@ -793,4 +801,4 @@ }); OC.Share.ShareItemModel = ShareItemModel; -})(); +})(); \ No newline at end of file diff --git a/lib/private/share20/manager.php b/lib/private/share20/manager.php index 9e04d57b28..06d4029bec 100644 --- a/lib/private/share20/manager.php +++ b/lib/private/share20/manager.php @@ -233,8 +233,9 @@ class Manager implements IManager { throw new GenericShareException($message_t, $message_t, 404); } - // Check that read permissions are always set - if (($share->getPermissions() & \OCP\Constants::PERMISSION_READ) === 0) { + // Link shares are allowed to have no read permissions to allow upload to hidden folders + if ($share->getShareType() !== \OCP\Share::SHARE_TYPE_LINK && + ($share->getPermissions() & \OCP\Constants::PERMISSION_READ) === 0) { throw new \InvalidArgumentException('Shares need at least read permissions'); } } diff --git a/tests/lib/share20/managertest.php b/tests/lib/share20/managertest.php index 029c8cd854..8ebe3d4b55 100644 --- a/tests/lib/share20/managertest.php +++ b/tests/lib/share20/managertest.php @@ -670,7 +670,6 @@ class ManagerTest extends \Test\TestCase { $data[] = [$this->createShare(null, \OCP\Share::SHARE_TYPE_USER, $allPermssions, $user2, $user0, $user0, 30, null, null), 'Shares need at least read permissions', true]; $data[] = [$this->createShare(null, \OCP\Share::SHARE_TYPE_GROUP, $allPermssions, $group0, $user0, $user0, 2, null, null), 'Shares need at least read permissions', true]; - $data[] = [$this->createShare(null, \OCP\Share::SHARE_TYPE_LINK, $allPermssions, null, $user0, $user0, 16, null, null), 'Shares need at least read permissions', true]; $data[] = [$this->createShare(null, \OCP\Share::SHARE_TYPE_USER, $allPermssions, $user2, $user0, $user0, 31, null, null), null, false]; $data[] = [$this->createShare(null, \OCP\Share::SHARE_TYPE_GROUP, $allPermssions, $group0, $user0, $user0, 3, null, null), null, false]; From 7f5048e2e54ae1e8c8197b285a78f3098f8e4707 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Sat, 11 Jun 2016 15:15:37 +0200 Subject: [PATCH 2/2] smaller files drop fixes * fix infinite spinner on blacklisted files * move HTML to template * indentation --- apps/files/ajax/upload.php | 8 ++++++- apps/files_sharing/js/files_drop.js | 33 +++++++++++++++++++++++++++-- core/js/sharedialoglinkshareview.js | 4 ++-- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/apps/files/ajax/upload.php b/apps/files/ajax/upload.php index 985284bad5..5c541991b8 100644 --- a/apps/files/ajax/upload.php +++ b/apps/files/ajax/upload.php @@ -43,6 +43,7 @@ OCP\JSON::setContentTypeHeader('text/plain'); // If no token is sent along, rely on login only $errorCode = null; +$errorFileName = null; $l = \OC::$server->getL10N('files'); if (empty($_POST['dirToken'])) { @@ -225,6 +226,7 @@ if (\OC\Files\Filesystem::isValidPath($dir) === true) { } else { $error = $l->t('Upload failed. Could not find uploaded file'); + $errorFileName = $files['name'][$i]; } } catch(Exception $ex) { $error = $ex->getMessage(); @@ -273,5 +275,9 @@ if ($error === false) { OCP\JSON::encodedPrint($result); } else { - OCP\JSON::error(array(array('data' => array_merge(array('message' => $error, 'code' => $errorCode), $storageStats)))); + OCP\JSON::error(array(array('data' => array_merge(array( + 'message' => $error, + 'code' => $errorCode, + 'filename' => $errorFileName + ), $storageStats)))); } diff --git a/apps/files_sharing/js/files_drop.js b/apps/files_sharing/js/files_drop.js index 567d0195d2..f38f33abb0 100644 --- a/apps/files_sharing/js/files_drop.js +++ b/apps/files_sharing/js/files_drop.js @@ -9,12 +9,24 @@ */ (function ($) { + var TEMPLATE = + '
  • ' + + '{{#if isUploading}}' + + ' {{name}}' + + '{{else}}' + + ' {{name}}' + + '{{/if}}' + + '
  • '; var Drop = { + /** @type {Function} **/ + _template: undefined, + initialize: function () { $(document).bind('drop dragover', function (e) { // Prevent the default browser drop action: e.preventDefault(); }); + var output = this.template(); $('#public-upload').fileupload({ url: OC.linkTo('files', 'ajax/upload.php'), dataType: 'json', @@ -32,12 +44,12 @@ $('#drop-upload-progress-indicator').removeClass('hidden'); _.each(data['files'], function(file) { if(errors.length === 0) { - $('#public-upload ul').append('
  • '+escapeHTML(file.name)+'
  • '); + $('#public-upload ul').append(output({isUploading: true, name: escapeHTML(file.name)})); $('[data-toggle="tooltip"]').tooltip(); data.submit(); } else { OC.Notification.showTemporary(OC.L10N.translate('files_sharing', 'Could not upload "{filename}"', {filename: file.name})); - $('#public-upload ul').append('
  • '+escapeHTML(file.name)+'
  • '); + $('#public-upload ul').append(output({isUploading: false, name: escapeHTML(file.name)})); $('[data-toggle="tooltip"]').tooltip(); } }); @@ -47,7 +59,13 @@ var mimeTypeUrl = OC.MimeType.getIconUrl(response['mimetype']); $('#public-upload ul li[data-name="' + escapeHTML(response['filename']) + '"]').html(' ' + escapeHTML(response['filename'])); $('[data-toggle="tooltip"]').tooltip(); + } else { + var name = response[0]['data']['filename']; + OC.Notification.showTemporary(OC.L10N.translate('files_sharing', 'Could not upload "{filename}"', {filename: name})); + $('#public-upload ul li[data-name="' + escapeHTML(name) + '"]').html(output({isUploading: false, name: escapeHTML(name)})); + $('[data-toggle="tooltip"]').tooltip(); } + }, progressall: function (e, data) { var progress = parseInt(data.loaded / data.total * 100, 10); @@ -64,6 +82,17 @@ e.preventDefault(); $('#public-upload #emptycontent input').focus().trigger('click'); }); + }, + + /** + * @returns {Function} from Handlebars + * @private + */ + template: function () { + if (!this._template) { + this._template = Handlebars.compile(TEMPLATE); + } + return this._template; } }; diff --git a/core/js/sharedialoglinkshareview.js b/core/js/sharedialoglinkshareview.js index e1cecfde58..dfc0b02081 100644 --- a/core/js/sharedialoglinkshareview.js +++ b/core/js/sharedialoglinkshareview.js @@ -30,13 +30,13 @@ ' ' + '' + '' + - '{{#if hideFileList}}' + + ' {{#if hideFileList}}' + '
    ' + ' ' + ' ' + '' + '
    ' + - '{{/if}}' + + ' {{/if}}' + ' {{/if}}' + ' {{#if showPasswordCheckBox}}' + '' +