diff options
46 files changed, 1006 insertions, 163 deletions
diff --git a/apps/encryption/lib/crypto/encryptall.php b/apps/encryption/lib/crypto/encryptall.php index a0c69c13fdd..8e97fe341b4 100644 --- a/apps/encryption/lib/crypto/encryptall.php +++ b/apps/encryption/lib/crypto/encryptall.php @@ -280,6 +280,12 @@ class EncryptAll { $newPasswords[] = [$uid, $password]; } } + + if (empty($newPasswords)) { + $this->output->writeln("\nAll users already had a key-pair, no further action needed.\n"); + return; + } + $table->setRows($newPasswords); $table->render(); diff --git a/apps/files/css/detailsView.css b/apps/files/css/detailsView.css index 8acf884f219..faa26678562 100644 --- a/apps/files/css/detailsView.css +++ b/apps/files/css/detailsView.css @@ -32,12 +32,18 @@ #app-sidebar .image .thumbnail { width:100%; display:block; - height: 250px; background-repeat: no-repeat; background-position: center; background-size: 100%; float: none; margin: 0; + height: auto; +} + +#app-sidebar .image.landscape .thumbnail::before { + content: ''; + display: block; + padding-bottom: 56.25%; /* sets height of .thumbnail to 9/16 of the width */ } #app-sidebar .image.portrait .thumbnail { diff --git a/apps/files/js/mainfileinfodetailview.js b/apps/files/js/mainfileinfodetailview.js index 830f074f3f1..82cca0d0fb3 100644 --- a/apps/files/js/mainfileinfodetailview.js +++ b/apps/files/js/mainfileinfodetailview.js @@ -176,9 +176,7 @@ $iconDiv.removeClass('icon-loading icon-32'); var targetHeight = getTargetHeight(img); if (this.model.isImage() && targetHeight > smallPreviewSize) { - if (!isLandscape(img)) { - $container.addClass('portrait'); - } + $container.addClass(isLandscape(img)? 'landscape': 'portrait'); $container.addClass('image'); } @@ -186,7 +184,7 @@ // when we dont have a preview we show the mime icon in the error handler $iconDiv.css({ 'background-image': 'url("' + previewUrl + '")', - 'height': targetHeight + height: (isLandscape(img) && targetHeight > smallPreviewSize)? 'auto': targetHeight }); }.bind(this), error: function () { diff --git a/apps/files_external/controller/globalstoragescontroller.php b/apps/files_external/controller/globalstoragescontroller.php index 3686a6189b4..3c1f2022505 100644 --- a/apps/files_external/controller/globalstoragescontroller.php +++ b/apps/files_external/controller/globalstoragescontroller.php @@ -179,14 +179,5 @@ class GlobalStoragesController extends StoragesController { } - /** - * Get the visibility type for this controller, used in validation - * - * @return string BackendService::VISIBILITY_* constants - */ - protected function getVisibilityType() { - return BackendService::VISIBILITY_ADMIN; - } - } diff --git a/apps/files_external/controller/storagescontroller.php b/apps/files_external/controller/storagescontroller.php index 71055fd1b9c..6a01112f8c5 100644 --- a/apps/files_external/controller/storagescontroller.php +++ b/apps/files_external/controller/storagescontroller.php @@ -153,7 +153,7 @@ abstract class StoragesController extends Controller { $backend = $storage->getBackend(); /** @var AuthMechanism */ $authMechanism = $storage->getAuthMechanism(); - if (!$backend || $backend->checkDependencies()) { + if ($backend->checkDependencies()) { // invalid backend return new DataResponse( array( @@ -165,7 +165,7 @@ abstract class StoragesController extends Controller { ); } - if (!$backend->isVisibleFor($this->getVisibilityType())) { + if (!$backend->isVisibleFor($this->service->getVisibilityType())) { // not permitted to use backend return new DataResponse( array( @@ -176,7 +176,7 @@ abstract class StoragesController extends Controller { Http::STATUS_UNPROCESSABLE_ENTITY ); } - if (!$authMechanism->isVisibleFor($this->getVisibilityType())) { + if (!$authMechanism->isVisibleFor($this->service->getVisibilityType())) { // not permitted to use auth mechanism return new DataResponse( array( @@ -211,13 +211,6 @@ abstract class StoragesController extends Controller { } /** - * Get the visibility type for this controller, used in validation - * - * @return string BackendService::VISIBILITY_* constants - */ - abstract protected function getVisibilityType(); - - /** * Check whether the given storage is available / valid. * * Note that this operation can be time consuming depending diff --git a/apps/files_external/controller/userstoragescontroller.php b/apps/files_external/controller/userstoragescontroller.php index fcbe692d79e..f39f8a85d2d 100644 --- a/apps/files_external/controller/userstoragescontroller.php +++ b/apps/files_external/controller/userstoragescontroller.php @@ -187,13 +187,4 @@ class UserStoragesController extends StoragesController { return parent::destroy($id); } - /** - * Get the visibility type for this controller, used in validation - * - * @return string BackendService::VISIBILITY_* constants - */ - protected function getVisibilityType() { - return BackendService::VISIBILITY_PERSONAL; - } - } diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php index 2dc7de30a6c..afea7ead4e6 100644 --- a/apps/files_external/lib/config.php +++ b/apps/files_external/lib/config.php @@ -112,7 +112,7 @@ class OC_Mount_Config { * @param string $uid user * @return array of mount point string as key, mountpoint config as value * - * @deprecated 8.2.0 use UserGlobalStoragesService::getAllStorages() and UserStoragesService::getAllStorages() + * @deprecated 8.2.0 use UserGlobalStoragesService::getStorages() and UserStoragesService::getStorages() */ public static function getAbsoluteMountPoints($uid) { $mountPoints = array(); @@ -124,7 +124,7 @@ class OC_Mount_Config { $userGlobalStoragesService->setUser($user); $userStoragesService->setUser($user); - foreach ($userGlobalStoragesService->getAllStorages() as $storage) { + foreach ($userGlobalStoragesService->getStorages() as $storage) { $mountPoint = '/'.$uid.'/files'.$storage->getMountPoint(); $mountEntry = self::prepareMountPointEntry($storage, false); foreach ($mountEntry['options'] as &$option) { @@ -133,7 +133,7 @@ class OC_Mount_Config { $mountPoints[$mountPoint] = $mountEntry; } - foreach ($userStoragesService->getAllStorages() as $storage) { + foreach ($userStoragesService->getStorages() as $storage) { $mountPoint = '/'.$uid.'/files'.$storage->getMountPoint(); $mountEntry = self::prepareMountPointEntry($storage, true); foreach ($mountEntry['options'] as &$option) { @@ -153,13 +153,13 @@ class OC_Mount_Config { * * @return array * - * @deprecated 8.2.0 use GlobalStoragesService::getAllStorages() + * @deprecated 8.2.0 use GlobalStoragesService::getStorages() */ public static function getSystemMountPoints() { $mountPoints = []; $service = self::$app->getContainer()->query('OCA\Files_External\Service\GlobalStoragesService'); - foreach ($service->getAllStorages() as $storage) { + foreach ($service->getStorages() as $storage) { $mountPoints[] = self::prepareMountPointEntry($storage, false); } @@ -171,13 +171,13 @@ class OC_Mount_Config { * * @return array * - * @deprecated 8.2.0 use UserStoragesService::getAllStorages() + * @deprecated 8.2.0 use UserStoragesService::getStorages() */ public static function getPersonalMountPoints() { $mountPoints = []; $service = self::$app->getContainer()->query('OCA\Files_External\Service\UserStoragesService'); - foreach ($service->getAllStorages() as $storage) { + foreach ($service->getStorages() as $storage) { $mountPoints[] = self::prepareMountPointEntry($storage, true); } diff --git a/apps/files_external/lib/config/configadapter.php b/apps/files_external/lib/config/configadapter.php index cb8c2f24caa..fb36e011655 100644 --- a/apps/files_external/lib/config/configadapter.php +++ b/apps/files_external/lib/config/configadapter.php @@ -133,7 +133,7 @@ class ConfigAdapter implements IMountProvider { $mounts[$storage->getMountPoint()] = $mount; } - foreach ($this->userStoragesService->getAllStorages() as $storage) { + foreach ($this->userStoragesService->getStorages() as $storage) { try { $this->prepareStorageConfig($storage, $user); $impl = $this->constructStorage($storage); diff --git a/apps/files_external/personal.php b/apps/files_external/personal.php index efd23512ffe..f048d65540b 100644 --- a/apps/files_external/personal.php +++ b/apps/files_external/personal.php @@ -54,7 +54,7 @@ foreach ($authMechanisms as $authMechanism) { $tmpl = new OCP\Template('files_external', 'settings'); $tmpl->assign('encryptionEnabled', \OC::$server->getEncryptionManager()->isEnabled()); $tmpl->assign('isAdminPage', false); -$tmpl->assign('storages', $userStoragesService->getAllStorages()); +$tmpl->assign('storages', $userStoragesService->getStorages()); $tmpl->assign('dependencies', OC_Mount_Config::dependencyMessage($backendService->getBackends())); $tmpl->assign('backends', $backends); $tmpl->assign('authMechanisms', $authMechanisms); diff --git a/apps/files_external/service/globalstoragesservice.php b/apps/files_external/service/globalstoragesservice.php index 0e2d3f2b9c1..49ffea43d1b 100644 --- a/apps/files_external/service/globalstoragesservice.php +++ b/apps/files_external/service/globalstoragesservice.php @@ -210,4 +210,13 @@ class GlobalStoragesService extends StoragesService { ); } } + + /** + * Get the visibility type for this controller, used in validation + * + * @return string BackendService::VISIBILITY_* constants + */ + public function getVisibilityType() { + return BackendService::VISIBILITY_ADMIN; + } } diff --git a/apps/files_external/service/storagesservice.php b/apps/files_external/service/storagesservice.php index 703f277d84e..83a82de0bed 100644 --- a/apps/files_external/service/storagesservice.php +++ b/apps/files_external/service/storagesservice.php @@ -29,6 +29,8 @@ use \OC\Files\Filesystem; use \OCA\Files_external\Lib\StorageConfig; use \OCA\Files_external\NotFoundException; use \OCA\Files_External\Service\BackendService; +use \OCA\Files_External\Lib\Backend\Backend; +use \OCA\Files_External\Lib\Auth\AuthMechanism; /** * Service class to manage external storages @@ -331,7 +333,7 @@ abstract class StoragesService { } /** - * Gets all storages + * Gets all storages, valid or not * * @return array array of storage configs */ @@ -340,6 +342,47 @@ abstract class StoragesService { } /** + * Gets all valid storages + * + * @return array + */ + public function getStorages() { + return array_filter($this->getAllStorages(), [$this, 'validateStorage']); + } + + /** + * Validate storage + * FIXME: De-duplicate with StoragesController::validate() + * + * @param StorageConfig $storage + * @return bool + */ + protected function validateStorage(StorageConfig $storage) { + /** @var Backend */ + $backend = $storage->getBackend(); + /** @var AuthMechanism */ + $authMechanism = $storage->getAuthMechanism(); + + if (!$backend->isVisibleFor($this->getVisibilityType())) { + // not permitted to use backend + return false; + } + if (!$authMechanism->isVisibleFor($this->getVisibilityType())) { + // not permitted to use auth mechanism + return false; + } + + return true; + } + + /** + * Get the visibility type for this controller, used in validation + * + * @return string BackendService::VISIBILITY_* constants + */ + abstract public function getVisibilityType(); + + /** * Add new storage to the configuration * * @param array $newStorage storage attributes diff --git a/apps/files_external/service/userglobalstoragesservice.php b/apps/files_external/service/userglobalstoragesservice.php index b60473f131e..7c60bc6d497 100644 --- a/apps/files_external/service/userglobalstoragesservice.php +++ b/apps/files_external/service/userglobalstoragesservice.php @@ -117,7 +117,7 @@ class UserGlobalStoragesService extends GlobalStoragesService { * @return StorageConfig[] */ public function getUniqueStorages() { - $storages = $this->getAllStorages(); + $storages = $this->getStorages(); $storagesByMountpoint = []; foreach ($storages as $storage) { diff --git a/apps/files_external/service/userstoragesservice.php b/apps/files_external/service/userstoragesservice.php index c69b8d4f51e..6e3c327c09c 100644 --- a/apps/files_external/service/userstoragesservice.php +++ b/apps/files_external/service/userstoragesservice.php @@ -171,4 +171,13 @@ class UserStoragesService extends StoragesService { $this->triggerHooks($newStorage, Filesystem::signal_create_mount); } } + + /** + * Get the visibility type for this controller, used in validation + * + * @return string BackendService::VISIBILITY_* constants + */ + public function getVisibilityType() { + return BackendService::VISIBILITY_PERSONAL; + } } diff --git a/apps/files_external/settings.php b/apps/files_external/settings.php index 5c920a1495b..f82ab035c1c 100644 --- a/apps/files_external/settings.php +++ b/apps/files_external/settings.php @@ -65,7 +65,7 @@ $userBackends = array_filter($backendService->getAvailableBackends(), function($ $tmpl = new OCP\Template('files_external', 'settings'); $tmpl->assign('encryptionEnabled', \OC::$server->getEncryptionManager()->isEnabled()); $tmpl->assign('isAdminPage', true); -$tmpl->assign('storages', $globalStoragesService->getAllStorages()); +$tmpl->assign('storages', $globalStoragesService->getStorages()); $tmpl->assign('backends', $backends); $tmpl->assign('authMechanisms', $authMechanisms); $tmpl->assign('userBackends', $userBackends); diff --git a/apps/files_external/tests/controller/globalstoragescontrollertest.php b/apps/files_external/tests/controller/globalstoragescontrollertest.php index e1bfad8caf6..6b020198bd8 100644 --- a/apps/files_external/tests/controller/globalstoragescontrollertest.php +++ b/apps/files_external/tests/controller/globalstoragescontrollertest.php @@ -24,6 +24,7 @@ use \OCA\Files_external\Controller\GlobalStoragesController; use \OCA\Files_external\Service\GlobalStoragesService; use \OCP\AppFramework\Http; use \OCA\Files_external\NotFoundException; +use \OCA\Files_External\Service\BackendService; class GlobalStoragesControllerTest extends StoragesControllerTest { public function setUp() { @@ -32,6 +33,9 @@ class GlobalStoragesControllerTest extends StoragesControllerTest { ->disableOriginalConstructor() ->getMock(); + $this->service->method('getVisibilityType') + ->willReturn(BackendService::VISIBILITY_ADMIN); + $this->controller = new GlobalStoragesController( 'files_external', $this->getMock('\OCP\IRequest'), diff --git a/apps/files_external/tests/controller/userstoragescontrollertest.php b/apps/files_external/tests/controller/userstoragescontrollertest.php index 9f1a8df8d2e..33ef274c87c 100644 --- a/apps/files_external/tests/controller/userstoragescontrollertest.php +++ b/apps/files_external/tests/controller/userstoragescontrollertest.php @@ -40,6 +40,9 @@ class UserStoragesControllerTest extends StoragesControllerTest { ->disableOriginalConstructor() ->getMock(); + $this->service->method('getVisibilityType') + ->willReturn(BackendService::VISIBILITY_PERSONAL); + $this->controller = new UserStoragesController( 'files_external', $this->getMock('\OCP\IRequest'), diff --git a/apps/files_external/tests/service/storagesservicetest.php b/apps/files_external/tests/service/storagesservicetest.php index 28220c9bc2e..ddf52e6272e 100644 --- a/apps/files_external/tests/service/storagesservicetest.php +++ b/apps/files_external/tests/service/storagesservicetest.php @@ -305,6 +305,52 @@ abstract class StoragesServiceTest extends \Test\TestCase { ); } + public function testGetStoragesBackendNotVisible() { + $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\SMB'); + $backend->expects($this->once()) + ->method('isVisibleFor') + ->with($this->service->getVisibilityType()) + ->willReturn(false); + $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism'); + $authMechanism->method('isVisibleFor') + ->with($this->service->getVisibilityType()) + ->willReturn(true); + + $storage = new StorageConfig(255); + $storage->setMountPoint('mountpoint'); + $storage->setBackend($backend); + $storage->setAuthMechanism($authMechanism); + $storage->setBackendOptions(['password' => 'testPassword']); + + $newStorage = $this->service->addStorage($storage); + + $this->assertCount(1, $this->service->getAllStorages()); + $this->assertEmpty($this->service->getStorages()); + } + + public function testGetStoragesAuthMechanismNotVisible() { + $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\SMB'); + $backend->method('isVisibleFor') + ->with($this->service->getVisibilityType()) + ->willReturn(true); + $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism'); + $authMechanism->expects($this->once()) + ->method('isVisibleFor') + ->with($this->service->getVisibilityType()) + ->willReturn(false); + + $storage = new StorageConfig(255); + $storage->setMountPoint('mountpoint'); + $storage->setBackend($backend); + $storage->setAuthMechanism($authMechanism); + $storage->setBackendOptions(['password' => 'testPassword']); + + $newStorage = $this->service->addStorage($storage); + + $this->assertCount(1, $this->service->getAllStorages()); + $this->assertEmpty($this->service->getStorages()); + } + public static function createHookCallback($params) { self::$hookCalls[] = array( 'signal' => Filesystem::signal_create_mount, diff --git a/apps/files_external/tests/service/userglobalstoragesservicetest.php b/apps/files_external/tests/service/userglobalstoragesservicetest.php index 867872f3683..b6dc952605d 100644 --- a/apps/files_external/tests/service/userglobalstoragesservicetest.php +++ b/apps/files_external/tests/service/userglobalstoragesservicetest.php @@ -209,7 +209,11 @@ class UserGlobalStoragesServiceTest extends GlobalStoragesServiceTest { $expectedPrecedence ) { $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\SMB'); + $backend->method('isVisibleFor') + ->willReturn(true); $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism'); + $authMechanism->method('isVisibleFor') + ->willReturn(true); $storage1 = new StorageConfig(); $storage1->setMountPoint('mountpoint'); @@ -243,6 +247,16 @@ class UserGlobalStoragesServiceTest extends GlobalStoragesServiceTest { } } + public function testGetStoragesBackendNotVisible() { + // we don't test this here + $this->assertTrue(true); + } + + public function testGetStoragesAuthMechanismNotVisible() { + // we don't test this here + $this->assertTrue(true); + } + public function testHooksAddStorage($a = null, $b = null, $c = null) { // we don't test this here $this->assertTrue(true); diff --git a/apps/files_sharing/api/sharees.php b/apps/files_sharing/api/sharees.php index 9e324078dad..734c267020f 100644 --- a/apps/files_sharing/api/sharees.php +++ b/apps/files_sharing/api/sharees.php @@ -62,6 +62,9 @@ class Sharees { /** @var bool */ protected $shareWithGroupOnly = false; + /** @var bool */ + protected $shareeEnumeration = true; + /** @var int */ protected $offset = 0; @@ -134,7 +137,7 @@ class Sharees { } } - if (sizeof($users) < $this->limit) { + if (!$this->shareeEnumeration || sizeof($users) < $this->limit) { $this->reachedEndFor[] = 'users'; } @@ -176,6 +179,10 @@ class Sharees { ]); } } + + if (!$this->shareeEnumeration) { + $this->result['users'] = []; + } } /** @@ -187,7 +194,7 @@ class Sharees { $groups = $this->groupManager->search($search, $this->limit, $this->offset); $groups = array_map(function (IGroup $group) { return $group->getGID(); }, $groups); - if (sizeof($groups) < $this->limit) { + if (!$this->shareeEnumeration || sizeof($groups) < $this->limit) { $this->reachedEndFor[] = 'groups'; } @@ -233,6 +240,10 @@ class Sharees { ]); } } + + if (!$this->shareeEnumeration) { + $this->result['groups'] = []; + } } /** @@ -273,6 +284,10 @@ class Sharees { } } + if (!$this->shareeEnumeration) { + $this->result['remotes'] = []; + } + if (!$foundRemoteById && substr_count($search, '@') >= 1 && substr_count($search, ' ') === 0 && $this->offset === 0) { $this->result['exact']['remotes'][] = [ 'label' => $search, @@ -322,6 +337,7 @@ class Sharees { } $this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes'; + $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; $this->limit = (int) $perPage; $this->offset = $perPage * ($page - 1); diff --git a/apps/files_sharing/js/public.js b/apps/files_sharing/js/public.js index 1993efe7d73..4908968a937 100644 --- a/apps/files_sharing/js/public.js +++ b/apps/files_sharing/js/public.js @@ -159,9 +159,18 @@ OCA.Sharing.PublicApp = { }; this.fileList.generatePreviewUrl = function (urlSpec) { + urlSpec = urlSpec || {}; + if (!urlSpec.x) { + urlSpec.x = 36; + } + if (!urlSpec.y) { + urlSpec.y = 36; + } + urlSpec.x *= window.devicePixelRatio; + urlSpec.y *= window.devicePixelRatio; + urlSpec.x = Math.floor(urlSpec.x); + urlSpec.y = Math.floor(urlSpec.y); urlSpec.t = $('#dirToken').val(); - urlSpec.y = Math.floor(36 * window.devicePixelRatio); - urlSpec.x = Math.floor(36 * window.devicePixelRatio); return OC.generateUrl('/apps/files_sharing/ajax/publicpreview.php?') + $.param(urlSpec); }; @@ -293,15 +302,8 @@ $(document).ready(function () { if (window.Files) { // HACK: for oc-dialogs previews that depends on Files: - Files.lazyLoadPreview = function (path, mime, ready, width, height, etag) { - return App.fileList.lazyLoadPreview({ - path: path, - mime: mime, - callback: ready, - width: width, - height: height, - etag: etag - }); + Files.generatePreviewUrl = function (urlSpec) { + return App.fileList.generatePreviewUrl(urlSpec); }; } }); diff --git a/apps/files_sharing/lib/mountprovider.php b/apps/files_sharing/lib/mountprovider.php index 14a79625993..458e7f2619b 100644 --- a/apps/files_sharing/lib/mountprovider.php +++ b/apps/files_sharing/lib/mountprovider.php @@ -69,12 +69,11 @@ class MountProvider implements IMountProvider { // for updating etags for the share owner when we make changes to this share. $ownerPropagator = $this->propagationManager->getChangePropagator($share['uid_owner']); - // for updating our etags when changes are made to the share from the owners side (probably indirectly by us trough another share) - $this->propagationManager->listenToOwnerChanges($share['uid_owner'], $user->getUID()); return new SharedMount( '\OC\Files\Storage\Shared', '/' . $user->getUID() . '/' . $share['file_target'], array( + 'propagationManager' => $this->propagationManager, 'propagator' => $ownerPropagator, 'share' => $share, 'user' => $user->getUID() diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php index 1ac401f3cf8..27dd2f1e485 100644 --- a/apps/files_sharing/lib/sharedstorage.php +++ b/apps/files_sharing/lib/sharedstorage.php @@ -50,13 +50,34 @@ class Shared extends \OC\Files\Storage\Common implements ISharedStorage { */ private $ownerView; + /** + * @var \OCA\Files_Sharing\Propagation\PropagationManager + */ + private $propagationManager; + + /** + * @var string + */ + private $user; + + private $initialized = false; + public function __construct($arguments) { $this->share = $arguments['share']; $this->ownerView = $arguments['ownerView']; + $this->propagationManager = $arguments['propagationManager']; + $this->user = $arguments['user']; } private function init() { + if ($this->initialized) { + return; + } + $this->initialized = true; Filesystem::initMountPoints($this->share['uid_owner']); + + // for updating our etags when changes are made to the share from the owners side (probably indirectly by us trough another share) + $this->propagationManager->listenToOwnerChanges($this->share['uid_owner'], $this->user); } /** diff --git a/apps/files_sharing/tests/api/shareestest.php b/apps/files_sharing/tests/api/shareestest.php index 5c5d5b0d309..91b8b1c7e66 100644 --- a/apps/files_sharing/tests/api/shareestest.php +++ b/apps/files_sharing/tests/api/shareestest.php @@ -110,16 +110,30 @@ class ShareesTest extends TestCase { public function dataGetUsers() { return [ - ['test', false, [], [], [], [], true, false], - ['test', true, [], [], [], [], true, false], + ['test', false, true, [], [], [], [], true, false], + ['test', false, false, [], [], [], [], true, false], + ['test', true, true, [], [], [], [], true, false], + ['test', true, false, [], [], [], [], true, false], [ - 'test', false, [], [], + 'test', false, true, [], [], [ ['label' => 'Test', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test']], ], [], true, $this->getUserMock('test', 'Test') ], [ - 'test', true, [], [], + 'test', false, false, [], [], + [ + ['label' => 'Test', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test']], + ], [], true, $this->getUserMock('test', 'Test') + ], + [ + 'test', true, true, [], [], + [ + ['label' => 'Test', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test']], + ], [], true, $this->getUserMock('test', 'Test') + ], + [ + 'test', true, false, [], [], [ ['label' => 'Test', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test']], ], [], true, $this->getUserMock('test', 'Test') @@ -127,6 +141,7 @@ class ShareesTest extends TestCase { [ 'test', false, + true, [], [ $this->getUserMock('test1', 'Test One'), @@ -141,6 +156,20 @@ class ShareesTest extends TestCase { [ 'test', false, + false, + [], + [ + $this->getUserMock('test1', 'Test One'), + ], + [], + [], + true, + false, + ], + [ + 'test', + false, + true, [], [ $this->getUserMock('test1', 'Test One'), @@ -157,6 +186,21 @@ class ShareesTest extends TestCase { [ 'test', false, + false, + [], + [ + $this->getUserMock('test1', 'Test One'), + $this->getUserMock('test2', 'Test Two'), + ], + [], + [], + true, + false, + ], + [ + 'test', + false, + true, [], [ $this->getUserMock('test0', 'Test'), @@ -175,6 +219,24 @@ class ShareesTest extends TestCase { ], [ 'test', + false, + false, + [], + [ + $this->getUserMock('test0', 'Test'), + $this->getUserMock('test1', 'Test One'), + $this->getUserMock('test2', 'Test Two'), + ], + [ + ['label' => 'Test', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test0']], + ], + [], + true, + false, + ], + [ + 'test', + true, true, ['abc', 'xyz'], [ @@ -191,6 +253,21 @@ class ShareesTest extends TestCase { [ 'test', true, + false, + ['abc', 'xyz'], + [ + ['abc', 'test', 2, 0, ['test1' => 'Test One']], + ['xyz', 'test', 2, 0, []], + ], + [], + [], + true, + false, + ], + [ + 'test', + true, + true, ['abc', 'xyz'], [ ['abc', 'test', 2, 0, [ @@ -213,6 +290,27 @@ class ShareesTest extends TestCase { [ 'test', true, + false, + ['abc', 'xyz'], + [ + ['abc', 'test', 2, 0, [ + 'test1' => 'Test One', + 'test2' => 'Test Two', + ]], + ['xyz', 'test', 2, 0, [ + 'test1' => 'Test One', + 'test2' => 'Test Two', + ]], + ], + [], + [], + true, + false, + ], + [ + 'test', + true, + true, ['abc', 'xyz'], [ ['abc', 'test', 2, 0, [ @@ -231,6 +329,26 @@ class ShareesTest extends TestCase { false, false, ], + [ + 'test', + true, + false, + ['abc', 'xyz'], + [ + ['abc', 'test', 2, 0, [ + 'test' => 'Test One', + ]], + ['xyz', 'test', 2, 0, [ + 'test2' => 'Test Two', + ]], + ], + [ + ['label' => 'Test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test']], + ], + [], + true, + false, + ], ]; } @@ -239,6 +357,7 @@ class ShareesTest extends TestCase { * * @param string $searchTerm * @param bool $shareWithGroupOnly + * @param bool $shareeEnumeration * @param array $groupResponse * @param array $userResponse * @param array $exactExpected @@ -246,10 +365,11 @@ class ShareesTest extends TestCase { * @param bool $reachedEnd * @param mixed $singleUser */ - public function testGetUsers($searchTerm, $shareWithGroupOnly, $groupResponse, $userResponse, $exactExpected, $expected, $reachedEnd, $singleUser) { + public function testGetUsers($searchTerm, $shareWithGroupOnly, $shareeEnumeration, $groupResponse, $userResponse, $exactExpected, $expected, $reachedEnd, $singleUser) { $this->invokePrivate($this->sharees, 'limit', [2]); $this->invokePrivate($this->sharees, 'offset', [0]); $this->invokePrivate($this->sharees, 'shareWithGroupOnly', [$shareWithGroupOnly]); + $this->invokePrivate($this->sharees, 'shareeEnumeration', [$shareeEnumeration]); $user = $this->getUserMock('admin', 'Administrator'); $this->session->expects($this->any()) @@ -290,9 +410,10 @@ class ShareesTest extends TestCase { public function dataGetGroups() { return [ - ['test', false, [], [], [], [], true, false], + ['test', false, true, [], [], [], [], true, false], + ['test', false, false, [], [], [], [], true, false], [ - 'test', false, + 'test', false, true, [$this->getGroupMock('test1')], [], [], @@ -301,7 +422,16 @@ class ShareesTest extends TestCase { false, ], [ - 'test', false, + 'test', false, false, + [$this->getGroupMock('test1')], + [], + [], + [], + true, + false, + ], + [ + 'test', false, true, [ $this->getGroupMock('test'), $this->getGroupMock('test1'), @@ -313,7 +443,19 @@ class ShareesTest extends TestCase { false, ], [ - 'test', false, + 'test', false, false, + [ + $this->getGroupMock('test'), + $this->getGroupMock('test1'), + ], + [], + [['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']]], + [], + true, + false, + ], + [ + 'test', false, true, [ $this->getGroupMock('test0'), $this->getGroupMock('test1'), @@ -328,7 +470,19 @@ class ShareesTest extends TestCase { null, ], [ - 'test', false, + 'test', false, false, + [ + $this->getGroupMock('test0'), + $this->getGroupMock('test1'), + ], + [], + [], + [], + true, + null, + ], + [ + 'test', false, true, [ $this->getGroupMock('test0'), $this->getGroupMock('test1'), @@ -344,9 +498,24 @@ class ShareesTest extends TestCase { false, $this->getGroupMock('test'), ], - ['test', true, [], [], [], [], true, false], [ - 'test', true, + 'test', false, false, + [ + $this->getGroupMock('test0'), + $this->getGroupMock('test1'), + ], + [], + [ + ['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']], + ], + [], + true, + $this->getGroupMock('test'), + ], + ['test', true, true, [], [], [], [], true, false], + ['test', true, false, [], [], [], [], true, false], + [ + 'test', true, true, [ $this->getGroupMock('test1'), $this->getGroupMock('test2'), @@ -358,7 +527,19 @@ class ShareesTest extends TestCase { false, ], [ - 'test', true, + 'test', true, false, + [ + $this->getGroupMock('test1'), + $this->getGroupMock('test2'), + ], + [$this->getGroupMock('test1')], + [], + [], + true, + false, + ], + [ + 'test', true, true, [ $this->getGroupMock('test'), $this->getGroupMock('test1'), @@ -370,7 +551,19 @@ class ShareesTest extends TestCase { false, ], [ - 'test', true, + 'test', true, false, + [ + $this->getGroupMock('test'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test')], + [['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']]], + [], + true, + false, + ], + [ + 'test', true, true, [ $this->getGroupMock('test'), $this->getGroupMock('test1'), @@ -382,7 +575,19 @@ class ShareesTest extends TestCase { false, ], [ - 'test', true, + 'test', true, false, + [ + $this->getGroupMock('test'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test1')], + [], + [], + true, + false, + ], + [ + 'test', true, true, [ $this->getGroupMock('test'), $this->getGroupMock('test1'), @@ -394,7 +599,19 @@ class ShareesTest extends TestCase { false, ], [ - 'test', true, + 'test', true, false, + [ + $this->getGroupMock('test'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')], + [['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']]], + [], + true, + false, + ], + [ + 'test', true, true, [ $this->getGroupMock('test0'), $this->getGroupMock('test1'), @@ -409,7 +626,19 @@ class ShareesTest extends TestCase { null, ], [ - 'test', true, + 'test', true, false, + [ + $this->getGroupMock('test0'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')], + [], + [], + true, + null, + ], + [ + 'test', true, true, [ $this->getGroupMock('test0'), $this->getGroupMock('test1'), @@ -425,6 +654,20 @@ class ShareesTest extends TestCase { false, $this->getGroupMock('test'), ], + [ + 'test', true, false, + [ + $this->getGroupMock('test0'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')], + [ + ['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']], + ], + [], + true, + $this->getGroupMock('test'), + ], ]; } @@ -433,6 +676,7 @@ class ShareesTest extends TestCase { * * @param string $searchTerm * @param bool $shareWithGroupOnly + * @param bool $shareeEnumeration * @param array $groupResponse * @param array $userGroupsResponse * @param array $exactExpected @@ -440,10 +684,11 @@ class ShareesTest extends TestCase { * @param bool $reachedEnd * @param mixed $singleGroup */ - public function testGetGroups($searchTerm, $shareWithGroupOnly, $groupResponse, $userGroupsResponse, $exactExpected, $expected, $reachedEnd, $singleGroup) { + public function testGetGroups($searchTerm, $shareWithGroupOnly, $shareeEnumeration, $groupResponse, $userGroupsResponse, $exactExpected, $expected, $reachedEnd, $singleGroup) { $this->invokePrivate($this->sharees, 'limit', [2]); $this->invokePrivate($this->sharees, 'offset', [0]); $this->invokePrivate($this->sharees, 'shareWithGroupOnly', [$shareWithGroupOnly]); + $this->invokePrivate($this->sharees, 'shareeEnumeration', [$shareeEnumeration]); $this->groupManager->expects($this->once()) ->method('search') @@ -480,10 +725,22 @@ class ShareesTest extends TestCase { public function dataGetRemote() { return [ - ['test', [], [], [], true], + ['test', [], true, [], [], true], + ['test', [], false, [], [], true], + [ + 'test@remote', + [], + true, + [ + ['label' => 'test@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'test@remote']], + ], + [], + true, + ], [ 'test@remote', [], + false, [ ['label' => 'test@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'test@remote']], ], @@ -508,6 +765,7 @@ class ShareesTest extends TestCase { ], ], ], + true, [], [ ['label' => 'User @ Localhost', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'username@localhost']], @@ -515,6 +773,29 @@ class ShareesTest extends TestCase { true, ], [ + 'test', + [ + [ + 'FN' => 'User3 @ Localhost', + ], + [ + 'FN' => 'User2 @ Localhost', + 'CLOUD' => [ + ], + ], + [ + 'FN' => 'User @ Localhost', + 'CLOUD' => [ + 'username@localhost', + ], + ], + ], + false, + [], + [], + true, + ], + [ 'test@remote', [ [ @@ -532,6 +813,7 @@ class ShareesTest extends TestCase { ], ], ], + true, [ ['label' => 'test@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'test@remote']], ], @@ -541,6 +823,31 @@ class ShareesTest extends TestCase { true, ], [ + 'test@remote', + [ + [ + 'FN' => 'User3 @ Localhost', + ], + [ + 'FN' => 'User2 @ Localhost', + 'CLOUD' => [ + ], + ], + [ + 'FN' => 'User @ Localhost', + 'CLOUD' => [ + 'username@localhost', + ], + ], + ], + false, + [ + ['label' => 'test@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'test@remote']], + ], + [], + true, + ], + [ 'username@localhost', [ [ @@ -558,11 +865,36 @@ class ShareesTest extends TestCase { ], ], ], + true, [ ['label' => 'User @ Localhost', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'username@localhost']], ], + [], + true, + ], + [ + 'username@localhost', [ + [ + 'FN' => 'User3 @ Localhost', + ], + [ + 'FN' => 'User2 @ Localhost', + 'CLOUD' => [ + ], + ], + [ + 'FN' => 'User @ Localhost', + 'CLOUD' => [ + 'username@localhost', + ], + ], ], + false, + [ + ['label' => 'User @ Localhost', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'username@localhost']], + ], + [], true, ], ]; @@ -573,11 +905,13 @@ class ShareesTest extends TestCase { * * @param string $searchTerm * @param array $contacts + * @param bool $shareeEnumeration * @param array $exactExpected * @param array $expected * @param bool $reachedEnd */ - public function testGetRemote($searchTerm, $contacts, $exactExpected, $expected, $reachedEnd) { + public function testGetRemote($searchTerm, $contacts, $shareeEnumeration, $exactExpected, $expected, $reachedEnd) { + $this->invokePrivate($this->sharees, 'shareeEnumeration', [$shareeEnumeration]); $this->contactsManager->expects($this->any()) ->method('search') ->with($searchTerm, ['CLOUD', 'FN']) @@ -595,80 +929,84 @@ class ShareesTest extends TestCase { $allTypes = [Share::SHARE_TYPE_USER, Share::SHARE_TYPE_GROUP, Share::SHARE_TYPE_REMOTE]; return [ - [[], '', true, '', null, $allTypes, 1, 200, false], + [[], '', 'yes', true, '', null, $allTypes, 1, 200, false, true], // Test itemType [[ 'search' => '', - ], '', true, '', null, $allTypes, 1, 200, false], + ], '', 'yes', true, '', null, $allTypes, 1, 200, false, true], [[ 'search' => 'foobar', - ], '', true, 'foobar', null, $allTypes, 1, 200, false], + ], '', 'yes', true, 'foobar', null, $allTypes, 1, 200, false, true], [[ 'search' => 0, - ], '', true, '0', null, $allTypes, 1, 200, false], + ], '', 'yes', true, '0', null, $allTypes, 1, 200, false, true], // Test itemType [[ 'itemType' => '', - ], '', true, '', '', $allTypes, 1, 200, false], + ], '', 'yes', true, '', '', $allTypes, 1, 200, false, true], [[ 'itemType' => 'folder', - ], '', true, '', 'folder', $allTypes, 1, 200, false], + ], '', 'yes', true, '', 'folder', $allTypes, 1, 200, false, true], [[ 'itemType' => 0, - ], '', true, '', '0', $allTypes, 1, 200, false], + ], '', 'yes', true, '', '0', $allTypes, 1, 200, false, true], // Test shareType [[ - ], '', true, '', null, $allTypes, 1, 200, false], + ], '', 'yes', true, '', null, $allTypes, 1, 200, false, true], [[ 'shareType' => 0, - ], '', true, '', null, [0], 1, 200, false], + ], '', 'yes', true, '', null, [0], 1, 200, false, true], [[ 'shareType' => '0', - ], '', true, '', null, [0], 1, 200, false], + ], '', 'yes', true, '', null, [0], 1, 200, false, true], [[ 'shareType' => 1, - ], '', true, '', null, [1], 1, 200, false], + ], '', 'yes', true, '', null, [1], 1, 200, false, true], [[ 'shareType' => 12, - ], '', true, '', null, [], 1, 200, false], + ], '', 'yes', true, '', null, [], 1, 200, false, true], [[ 'shareType' => 'foobar', - ], '', true, '', null, $allTypes, 1, 200, false], + ], '', 'yes', true, '', null, $allTypes, 1, 200, false, true], [[ 'shareType' => [0, 1, 2], - ], '', true, '', null, [0, 1], 1, 200, false], + ], '', 'yes', true, '', null, [0, 1], 1, 200, false, true], [[ 'shareType' => [0, 1], - ], '', true, '', null, [0, 1], 1, 200, false], + ], '', 'yes', true, '', null, [0, 1], 1, 200, false, true], [[ 'shareType' => $allTypes, - ], '', true, '', null, $allTypes, 1, 200, false], + ], '', 'yes', true, '', null, $allTypes, 1, 200, false, true], [[ 'shareType' => $allTypes, - ], '', false, '', null, [0, 1], 1, 200, false], + ], '', 'yes', false, '', null, [0, 1], 1, 200, false, true], // Test pagination [[ 'page' => 1, - ], '', true, '', null, $allTypes, 1, 200, false], + ], '', 'yes', true, '', null, $allTypes, 1, 200, false, true], [[ 'page' => 10, - ], '', true, '', null, $allTypes, 10, 200, false], + ], '', 'yes', true, '', null, $allTypes, 10, 200, false, true], // Test perPage [[ 'perPage' => 1, - ], '', true, '', null, $allTypes, 1, 1, false], + ], '', 'yes', true, '', null, $allTypes, 1, 1, false, true], [[ 'perPage' => 10, - ], '', true, '', null, $allTypes, 1, 10, false], + ], '', 'yes', true, '', null, $allTypes, 1, 10, false, true], // Test $shareWithGroupOnly setting - [[], 'no', true, '', null, $allTypes, 1, 200, false], - [[], 'yes', true, '', null, $allTypes, 1, 200, true], + [[], 'no', 'yes', true, '', null, $allTypes, 1, 200, false, true], + [[], 'yes', 'yes', true, '', null, $allTypes, 1, 200, true, true], + + // Test $shareeEnumeration setting + [[], 'no', 'yes', true, '', null, $allTypes, 1, 200, false, true], + [[], 'no', 'no', true, '', null, $allTypes, 1, 200, false, false], ]; } @@ -678,6 +1016,7 @@ class ShareesTest extends TestCase { * * @param array $getData * @param string $apiSetting + * @param string $enumSetting * @param bool $remoteSharingEnabled * @param string $search * @param string $itemType @@ -685,18 +1024,22 @@ class ShareesTest extends TestCase { * @param int $page * @param int $perPage * @param bool $shareWithGroupOnly + * @param bool $shareeEnumeration */ - public function testSearch($getData, $apiSetting, $remoteSharingEnabled, $search, $itemType, $shareTypes, $page, $perPage, $shareWithGroupOnly) { + public function testSearch($getData, $apiSetting, $enumSetting, $remoteSharingEnabled, $search, $itemType, $shareTypes, $page, $perPage, $shareWithGroupOnly, $shareeEnumeration) { $oldGet = $_GET; $_GET = $getData; $config = $this->getMockBuilder('OCP\IConfig') ->disableOriginalConstructor() ->getMock(); - $config->expects($this->once()) + $config->expects($this->exactly(2)) ->method('getAppValue') - ->with('core', 'shareapi_only_share_with_group_members', 'no') - ->willReturn($apiSetting); + ->with('core', $this->anything(), $this->anything()) + ->willReturnMap([ + ['core', 'shareapi_only_share_with_group_members', 'no', $apiSetting], + ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', $enumSetting], + ]); $sharees = $this->getMockBuilder('\OCA\Files_Sharing\API\Sharees') ->setConstructorArgs([ @@ -735,6 +1078,7 @@ class ShareesTest extends TestCase { $this->assertInstanceOf('\OC_OCS_Result', $sharees->search()); $this->assertSame($shareWithGroupOnly, $this->invokePrivate($sharees, 'shareWithGroupOnly')); + $this->assertSame($shareeEnumeration, $this->invokePrivate($sharees, 'shareeEnumeration')); $_GET = $oldGet; } diff --git a/apps/user_ldap/js/wizard/wizardTabLoginFilter.js b/apps/user_ldap/js/wizard/wizardTabLoginFilter.js index b73d267d168..0316db5b61c 100644 --- a/apps/user_ldap/js/wizard/wizardTabLoginFilter.js +++ b/apps/user_ldap/js/wizard/wizardTabLoginFilter.js @@ -71,7 +71,8 @@ OCA = OCA || {}; ], 'ldap_login_filter_mode' ); - _.bindAll(this, 'onVerifyClick'); + _.bindAll(this, 'onVerifyClick', 'onTestLoginnameChange'); + this.managedItems.ldap_test_loginname.$element.keyup(this.onTestLoginnameChange); this.managedItems.ldap_test_loginname.$relatedElements.click(this.onVerifyClick); }, @@ -231,6 +232,16 @@ OCA = OCA || {}; } else { this.configModel.requestWizard('ldap_test_loginname', {ldap_test_loginname: testLogin}); } + }, + + /** + * enables/disables the "Verify Settings" button, depending whether + * the corresponding text input has a value or not + */ + onTestLoginnameChange: function() { + var loginName = this.managedItems.ldap_test_loginname.$element.val(); + var beDisabled = !_.isString(loginName) || !loginName.trim(); + this.managedItems.ldap_test_loginname.$relatedElements.prop('disabled', beDisabled); } }); diff --git a/apps/user_ldap/templates/part.wizard-loginfilter.php b/apps/user_ldap/templates/part.wizard-loginfilter.php index 8d9fccf24b8..a13931d9327 100644 --- a/apps/user_ldap/templates/part.wizard-loginfilter.php +++ b/apps/user_ldap/templates/part.wizard-loginfilter.php @@ -52,7 +52,7 @@ placeholder="<?php p($l->t('Test Loginname'));?>" class="ldapVerifyInput" title="Attempts to receive a DN for the given loginname and the current login filter"/> - <button class="ldapVerifyLoginName" name="ldapTestLoginSettings" type="button"> + <button class="ldapVerifyLoginName" name="ldapTestLoginSettings" type="button" disabled="disabled"> <?php p($l->t('Verify settings'));?> </button> </p> diff --git a/core/ajax/share.php b/core/ajax/share.php index 69b84564ab1..4546217def3 100644 --- a/core/ajax/share.php +++ b/core/ajax/share.php @@ -379,6 +379,16 @@ if (isset($_POST['action']) && isset($_POST['itemType']) && isset($_POST['itemSo } } + $sharingAutocompletion = \OC::$server->getConfig() + ->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes'); + + if ($sharingAutocompletion !== 'yes') { + $searchTerm = strtolower($_GET['search']); + $shareWith = array_filter($shareWith, function($user) use ($searchTerm) { + return strtolower($user['label']) === $searchTerm + || strtolower($user['value']['shareWith']) === $searchTerm; + }); + } $sorter = new \OC\Share\SearchResultSorter((string)$_GET['search'], 'label', diff --git a/core/img/actions/close.png b/core/img/actions/close.png Binary files differindex ece33258e56..66e3c26cc65 100644 --- a/core/img/actions/close.png +++ b/core/img/actions/close.png diff --git a/core/img/actions/close.svg b/core/img/actions/close.svg index 4471dbc6301..e060da3f8bb 100644 --- a/core/img/actions/close.svg +++ b/core/img/actions/close.svg @@ -1,3 +1,10 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"> - <path d="M12.95,11.536,11.536,12.95,8,9.4142,4.4645,12.95,3.0503,11.536,6.5858,8,3.0503,4.4644,4.4645,3.0502,8,6.5858,11.516,3.0311,12.95,4.4644,9.4143,8z" fill="#000"/> + <defs> + <filter id="a" style="color-interpolation-filters:sRGB" height="1.5994" width="1.6006" y="-.29971" x="-.30029"> + <feGaussianBlur stdDeviation="1.2386625"/> + </filter> + </defs> + <path d="m12.95 11.536-1.414 1.414-3.536-3.5358-3.5355 3.5358-1.4142-1.414 3.5355-3.536-3.5355-3.5356 1.4142-1.4142 3.5355 3.5356 3.516-3.5547 1.434 1.4333-3.5357 3.5356z" filter="url(#a)" stroke="#fff" stroke-width="2" fill="#fff"/> + <path d="m12.95 11.536-1.414 1.414-3.536-3.5358-3.5355 3.5358-1.4142-1.414 3.5355-3.536-3.5355-3.5356 1.4142-1.4142 3.5355 3.5356 3.516-3.5547 1.434 1.4333-3.5357 3.5356z"/> </svg> diff --git a/core/js/oc-dialogs.js b/core/js/oc-dialogs.js index 719ac8587a4..38b91be9d2e 100644 --- a/core/js/oc-dialogs.js +++ b/core/js/oc-dialogs.js @@ -388,9 +388,9 @@ var OCdialogs = { c: original.etag, forceIcon: 0 }; - var previewpath = OC.generateUrl('/core/preview.png?') + $.param(urlSpec); + var previewpath = Files.generatePreviewUrl(urlSpec); // Escaping single quotes - previewpath = previewpath.replace(/'/g, "%27") + previewpath = previewpath.replace(/'/g, "%27"); $originalDiv.find('.icon').css({"background-image": "url('" + previewpath + "')"}); getCroppedPreview(replacement).then( function(path){ diff --git a/core/js/shareitemmodel.js b/core/js/shareitemmodel.js index d883497433f..13396670aae 100644 --- a/core/js/shareitemmodel.js +++ b/core/js/shareitemmodel.js @@ -69,7 +69,7 @@ */ var SHARE_RESPONSE_INT_PROPS = [ 'id', 'file_parent', 'mail_send', 'file_source', 'item_source', 'permissions', - 'storage', 'share_type', 'parent', 'stime', 'expiration' + 'storage', 'share_type', 'parent', 'stime' ]; /** @@ -297,8 +297,7 @@ * @returns {boolean} */ hasUserShares: function() { - var shares = this.get('shares'); - return _.isArray(shares) && shares.length > 0; + return this.getSharesWithCurrentItem().length > 0; }, /** @@ -408,6 +407,20 @@ }, /** + * Returns all share entries that only apply to the current item + * (file/folder) + * + * @return {Array.<OC.Share.Types.ShareInfo>} + */ + getSharesWithCurrentItem: function() { + var shares = this.get('shares') || []; + var fileId = this.fileInfoModel.get('id'); + return _.filter(shares, function(share) { + return share.item_source === fileId; + }); + }, + + /** * @param shareIndex * @returns {string} */ diff --git a/core/js/tests/specs/shareitemmodelSpec.js b/core/js/tests/specs/shareitemmodelSpec.js index 07a6fbdc23f..b4403e28475 100644 --- a/core/js/tests/specs/shareitemmodelSpec.js +++ b/core/js/tests/specs/shareitemmodelSpec.js @@ -169,7 +169,7 @@ describe('OC.Share.ShareItemModel', function() { /* jshint camelcase: false */ shares: [{ displayname_owner: 'root', - expiration: 1111, + expiration: '2015-10-12 00:00:00', file_source: 123, file_target: '/folder', id: 20, @@ -187,7 +187,7 @@ describe('OC.Share.ShareItemModel', function() { uid_owner: 'root' }, { displayname_owner: 'root', - expiration: 2222, + expiration: '2015-10-15 00:00:00', file_source: 456, file_target: '/file_in_folder.txt', id: 21, @@ -263,7 +263,7 @@ describe('OC.Share.ShareItemModel', function() { reshare: {}, shares: [{ displayname_owner: 'root', - expiration: '1403900000', + expiration: '2015-10-12 00:00:00', file_source: '123', file_target: '/folder', id: '20', @@ -301,7 +301,64 @@ describe('OC.Share.ShareItemModel', function() { expect(share.share_type).toEqual(OC.Share.SHARE_TYPE_USER); expect(share.share_with).toEqual('user1'); expect(share.stime).toEqual(1403884258); - expect(share.expiration).toEqual(1403900000); + expect(share.expiration).toEqual('2015-10-12 00:00:00'); + }); + }); + describe('hasUserShares', function() { + it('returns false when no user shares exist', function() { + loadItemStub.yields({ + reshare: {}, + shares: [] + }); + + model.fetch(); + + expect(model.hasUserShares()).toEqual(false); + }); + it('returns true when user shares exist on the current item', function() { + loadItemStub.yields({ + reshare: {}, + shares: [{ + id: 1, + share_type: OC.Share.SHARE_TYPE_USER, + share_with: 'user1', + item_source: '123' + }] + }); + + model.fetch(); + + expect(model.hasUserShares()).toEqual(true); + }); + it('returns true when group shares exist on the current item', function() { + loadItemStub.yields({ + reshare: {}, + shares: [{ + id: 1, + share_type: OC.Share.SHARE_TYPE_GROUP, + share_with: 'group1', + item_source: '123' + }] + }); + + model.fetch(); + + expect(model.hasUserShares()).toEqual(true); + }); + it('returns false when share exist on parent item', function() { + loadItemStub.yields({ + reshare: {}, + shares: [{ + id: 1, + share_type: OC.Share.SHARE_TYPE_GROUP, + share_with: 'group1', + item_source: '111' + }] + }); + + model.fetch(); + + expect(model.hasUserShares()).toEqual(false); }); }); diff --git a/core/shipped.json b/core/shipped.json index 7993b61569c..ffa4ee9c738 100644 --- a/core/shipped.json +++ b/core/shipped.json @@ -3,11 +3,11 @@ "shippedApps": [ "activity", "admin_audit", + "encryption", "enterprise_key", "external", "files", "files_antivirus", - "encryption", "files_external", "files_ldap_home", "files_locking", @@ -21,6 +21,7 @@ "firewall", "firstrunwizard", "gallery", + "notifications", "objectstore", "provisioning_api", "sharepoint", @@ -29,7 +30,6 @@ "user_external", "user_ldap", "user_shibboleth", - "user_webdavauth", "windows_network_drive" ] } diff --git a/lib/private/app.php b/lib/private/app.php index 26d51947642..5122a4964d4 100644 --- a/lib/private/app.php +++ b/lib/private/app.php @@ -837,7 +837,7 @@ class OC_App { $info['active'] = $active; - if (isset($info['shipped']) and ($info['shipped'] == 'true')) { + if (self::isShipped($app)) { $info['internal'] = true; $info['level'] = self::officialApp; $info['removable'] = false; diff --git a/lib/private/connector/sabre/listenerplugin.php b/lib/private/connector/sabre/listenerplugin.php index d0d40f4dc86..ec628add28b 100644 --- a/lib/private/connector/sabre/listenerplugin.php +++ b/lib/private/connector/sabre/listenerplugin.php @@ -21,6 +21,9 @@ namespace OC\Connector\Sabre; +use OCP\AppFramework\Http; +use OCP\SabrePluginEvent; +use OCP\SabrePluginException; use Sabre\DAV\ServerPlugin; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -49,9 +52,16 @@ class ListenerPlugin extends ServerPlugin { * in case the system is in maintenance mode. * * @return bool + * @throws \Exception */ public function emitListener() { - $this->dispatcher->dispatch('OC\Connector\Sabre::beforeMethod'); + $event = new SabrePluginEvent(); + + $this->dispatcher->dispatch('OC\Connector\Sabre::beforeMethod', $event); + + if ($event->getStatusCode() !== Http::STATUS_OK) { + throw new SabrePluginException($event->getMessage(), $event->getStatusCode()); + } return true; } diff --git a/lib/private/db/querybuilder/quotehelper.php b/lib/private/db/querybuilder/quotehelper.php index 0735f313abc..4b62fee6a6c 100644 --- a/lib/private/db/querybuilder/quotehelper.php +++ b/lib/private/db/querybuilder/quotehelper.php @@ -52,7 +52,7 @@ class QuoteHelper { return (string) $string; } - if ($string === null || $string === '*') { + if ($string === null || $string === 'null' || $string === '*') { return $string; } diff --git a/lib/private/encryption/decryptall.php b/lib/private/encryption/decryptall.php index 1ff9c74ef84..c1875f16abd 100644 --- a/lib/private/encryption/decryptall.php +++ b/lib/private/encryption/decryptall.php @@ -80,7 +80,7 @@ class DecryptAll { $this->input = $input; $this->output = $output; - if ($user !== '' && $this->userManager->userExists($user) === false) { + if (!empty($user) && $this->userManager->userExists($user) === false) { $this->output->writeln('User "' . $user . '" does not exist. Please check the username and try again'); return false; } diff --git a/lib/private/files/storage/wrapper/permissionsmask.php b/lib/private/files/storage/wrapper/permissionsmask.php index 50c3f2a6268..8d40d023630 100644 --- a/lib/private/files/storage/wrapper/permissionsmask.php +++ b/lib/private/files/storage/wrapper/permissionsmask.php @@ -66,7 +66,7 @@ class PermissionsMask extends Wrapper { } public function isSharable($path) { - return $this->checkMask(Constants::PERMISSION_SHARE) and parent::isSharable($parm); + return $this->checkMask(Constants::PERMISSION_SHARE) and parent::isSharable($path); } public function getPermissions($path) { diff --git a/lib/private/files/view.php b/lib/private/files/view.php index f92441492f7..95b688fef5c 100644 --- a/lib/private/files/view.php +++ b/lib/private/files/view.php @@ -153,7 +153,10 @@ class View { return '/'; } - if (strpos($path, $this->fakeRoot) !== 0) { + // missing slashes can cause wrong matches! + $root = rtrim($this->fakeRoot, '/') . '/'; + + if (strpos($path, $root) !== 0) { return null; } else { $path = substr($path, strlen($this->fakeRoot)); diff --git a/lib/public/sabrepluginevent.php b/lib/public/sabrepluginevent.php new file mode 100644 index 00000000000..fed3237166d --- /dev/null +++ b/lib/public/sabrepluginevent.php @@ -0,0 +1,82 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCP; + + +use OCP\AppFramework\Http; +use Symfony\Component\EventDispatcher\Event; + +/** + * @since 8.2.0 + */ +class SabrePluginEvent extends Event { + + /** @var int */ + protected $statusCode; + + /** @var string */ + protected $message; + + /** + * @since 8.2.0 + */ + public function __construct() { + $this->message = ''; + $this->statusCode = Http::STATUS_OK; + } + + /** + * @param int $statusCode + * @return self + * @since 8.2.0 + */ + public function setStatusCode($statusCode) { + $this->statusCode = (int) $statusCode; + return $this; + } + + /** + * @param string $message + * @return self + * @since 8.2.0 + */ + public function setMessage($message) { + $this->message = (string) $message; + return $this; + } + + /** + * @return int + * @since 8.2.0 + */ + public function getStatusCode() { + return $this->statusCode; + } + + /** + * @return string + * @since 8.2.0 + */ + public function getMessage() { + return $this->message; + } +} diff --git a/lib/public/sabrepluginexception.php b/lib/public/sabrepluginexception.php new file mode 100644 index 00000000000..5dba3b90a02 --- /dev/null +++ b/lib/public/sabrepluginexception.php @@ -0,0 +1,41 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCP; + + +use Sabre\DAV\Exception; + +/** + * @since 8.2.0 + */ +class SabrePluginException extends Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + * @since 8.2.0 + */ + public function getHTTPCode() { + return $this->code; + } +} diff --git a/settings/admin.php b/settings/admin.php index 38683438f3a..c8bf1d32749 100644 --- a/settings/admin.php +++ b/settings/admin.php @@ -122,6 +122,7 @@ $template->assign('allowPublicUpload', $appConfig->getValue('core', 'shareapi_al $template->assign('allowResharing', $appConfig->getValue('core', 'shareapi_allow_resharing', 'yes')); $template->assign('allowPublicMailNotification', $appConfig->getValue('core', 'shareapi_allow_public_notification', 'no')); $template->assign('allowMailNotification', $appConfig->getValue('core', 'shareapi_allow_mail_notification', 'no')); +$template->assign('allowShareDialogUserEnumeration', $appConfig->getValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes')); $template->assign('onlyShareWithGroupMembers', \OC\Share\Share::shareWithGroupMembersOnly()); $databaseOverload = (strpos(\OCP\Config::getSystemValue('dbtype'), 'sqlite') !== false); $template->assign('databaseOverload', $databaseOverload); diff --git a/settings/templates/admin.php b/settings/templates/admin.php index 36088d9f8c2..bfb0d5d364d 100644 --- a/settings/templates/admin.php +++ b/settings/templates/admin.php @@ -265,6 +265,11 @@ if ($_['cronErrors']) { <br /> <em><?php p($l->t('These groups will still be able to receive shares, but not to initiate them.')); ?></em> </p> + <p class="<?php if ($_['shareAPIEnabled'] === 'no') p('hidden');?>"> + <input type="checkbox" name="shareapi_allow_share_dialog_user_enumeration" value="1" id="shareapi_allow_share_dialog_user_enumeration" + <?php if ($_['allowShareDialogUserEnumeration'] === 'yes') print_unescaped('checked="checked"'); ?> /> + <label for="shareapi_allow_share_dialog_user_enumeration"><?php p($l->t('Allow username autocompletion in share dialog. If this is disabled the full username needs to be entered.'));?></label><br /> + </p> <?php print_unescaped($_['fileSharingSettings']); ?> </div> diff --git a/tests/lib/db/querybuilder/quotehelpertest.php b/tests/lib/db/querybuilder/quotehelpertest.php index 904b4c500db..b83d9eed2df 100644 --- a/tests/lib/db/querybuilder/quotehelpertest.php +++ b/tests/lib/db/querybuilder/quotehelpertest.php @@ -43,6 +43,10 @@ class QuoteHelperTest extends \Test\TestCase { [new Literal('literal'), 'literal'], [new Literal(1), '1'], [new Parameter(':param'), ':param'], + + // (string) 'null' is Doctrines way to set columns to null + // See https://github.com/owncloud/core/issues/19314 + ['null', 'null'], ]; } diff --git a/tests/lib/encryption/decryptalltest.php b/tests/lib/encryption/decryptalltest.php index c2a0711c0a0..ce5bcf1e5ae 100644 --- a/tests/lib/encryption/decryptalltest.php +++ b/tests/lib/encryption/decryptalltest.php @@ -82,11 +82,13 @@ class DecryptAllTest extends TestCase { * @dataProvider dataTrueFalse * @param bool $prepareResult */ - public function testDecryptAll($prepareResult) { + public function testDecryptAll($prepareResult, $user) { - $user = 'user1'; - - $this->userManager->expects($this->once())->method('userExists')->willReturn(true); + if (!empty($user)) { + $this->userManager->expects($this->once())->method('userExists')->willReturn(true); + } else { + $this->userManager->expects($this->never())->method('userExists'); + } /** @var DecryptAll | \PHPUnit_Framework_MockObject_MockObject | $instance */ $instance = $this->getMockBuilder('OC\Encryption\DecryptAll') ->setConstructorArgs( @@ -117,8 +119,10 @@ class DecryptAllTest extends TestCase { public function dataTrueFalse() { return [ - [true], - [false] + [true, 'user1'], + [false, 'user1'], + [true, ''], + [true, null] ]; } diff --git a/tests/lib/files/storage/storage.php b/tests/lib/files/storage/storage.php index fcd7f73dcde..d381b4cdf40 100644 --- a/tests/lib/files/storage/storage.php +++ b/tests/lib/files/storage/storage.php @@ -573,4 +573,29 @@ abstract class Storage extends \Test\TestCase { $this->assertSameAsLorem($target); $this->assertTrue($this->instance->file_exists($source), $source . ' was deleted'); } + + public function testIsCreatable() { + $this->instance->mkdir('source'); + $this->assertTrue($this->instance->isCreatable('source')); + } + + public function testIsReadable() { + $this->instance->mkdir('source'); + $this->assertTrue($this->instance->isReadable('source')); + } + + public function testIsUpdatable() { + $this->instance->mkdir('source'); + $this->assertTrue($this->instance->isUpdatable('source')); + } + + public function testIsDeletable() { + $this->instance->mkdir('source'); + $this->assertTrue($this->instance->isDeletable('source')); + } + + public function testIsShareable() { + $this->instance->mkdir('source'); + $this->assertTrue($this->instance->isSharable('source')); + } } diff --git a/tests/lib/files/view.php b/tests/lib/files/view.php index 83f53833855..ceeb9ba7a94 100644 --- a/tests/lib/files/view.php +++ b/tests/lib/files/view.php @@ -193,7 +193,7 @@ class View extends \Test\TestCase { /** * @medium */ - function testGetPath() { + public function testGetPath() { $storage1 = $this->getTestStorage(); $storage2 = $this->getTestStorage(); $storage3 = $this->getTestStorage(); @@ -219,7 +219,7 @@ class View extends \Test\TestCase { /** * @medium */ - function testMountPointOverwrite() { + public function testMountPointOverwrite() { $storage1 = $this->getTestStorage(false); $storage2 = $this->getTestStorage(); $storage1->mkdir('substorage'); @@ -265,7 +265,7 @@ class View extends \Test\TestCase { $appConfig->setValue('core', 'shareapi_exclude_groups_list', $oldExcludeGroupsList); } - function testCacheIncompleteFolder() { + public function testCacheIncompleteFolder() { $storage1 = $this->getTestStorage(false); \OC\Files\Filesystem::clearMounts(); \OC\Files\Filesystem::mount($storage1, array(), '/incomplete'); @@ -300,7 +300,7 @@ class View extends \Test\TestCase { /** * @medium */ - function testSearch() { + public function testSearch() { $storage1 = $this->getTestStorage(); $storage2 = $this->getTestStorage(); $storage3 = $this->getTestStorage(); @@ -350,7 +350,7 @@ class View extends \Test\TestCase { /** * @medium */ - function testWatcher() { + public function testWatcher() { $storage1 = $this->getTestStorage(); \OC\Files\Filesystem::mount($storage1, array(), '/'); $storage1->getWatcher()->setPolicy(Watcher::CHECK_ALWAYS); @@ -371,7 +371,7 @@ class View extends \Test\TestCase { /** * @medium */ - function testCopyBetweenStorageNoCross() { + public function testCopyBetweenStorageNoCross() { $storage1 = $this->getTestStorage(true, '\Test\Files\TemporaryNoCross'); $storage2 = $this->getTestStorage(true, '\Test\Files\TemporaryNoCross'); $this->copyBetweenStorages($storage1, $storage2); @@ -380,7 +380,7 @@ class View extends \Test\TestCase { /** * @medium */ - function testCopyBetweenStorageCross() { + public function testCopyBetweenStorageCross() { $storage1 = $this->getTestStorage(); $storage2 = $this->getTestStorage(); $this->copyBetweenStorages($storage1, $storage2); @@ -389,7 +389,7 @@ class View extends \Test\TestCase { /** * @medium */ - function testCopyBetweenStorageCrossNonLocal() { + public function testCopyBetweenStorageCrossNonLocal() { $storage1 = $this->getTestStorage(true, '\Test\Files\TemporaryNoLocal'); $storage2 = $this->getTestStorage(true, '\Test\Files\TemporaryNoLocal'); $this->copyBetweenStorages($storage1, $storage2); @@ -417,7 +417,7 @@ class View extends \Test\TestCase { /** * @medium */ - function testMoveBetweenStorageNoCross() { + public function testMoveBetweenStorageNoCross() { $storage1 = $this->getTestStorage(true, '\Test\Files\TemporaryNoCross'); $storage2 = $this->getTestStorage(true, '\Test\Files\TemporaryNoCross'); $this->moveBetweenStorages($storage1, $storage2); @@ -426,7 +426,7 @@ class View extends \Test\TestCase { /** * @medium */ - function testMoveBetweenStorageCross() { + public function testMoveBetweenStorageCross() { $storage1 = $this->getTestStorage(); $storage2 = $this->getTestStorage(); $this->moveBetweenStorages($storage1, $storage2); @@ -435,7 +435,7 @@ class View extends \Test\TestCase { /** * @medium */ - function testMoveBetweenStorageCrossNonLocal() { + public function testMoveBetweenStorageCrossNonLocal() { $storage1 = $this->getTestStorage(true, '\Test\Files\TemporaryNoLocal'); $storage2 = $this->getTestStorage(true, '\Test\Files\TemporaryNoLocal'); $this->moveBetweenStorages($storage1, $storage2); @@ -458,7 +458,7 @@ class View extends \Test\TestCase { /** * @medium */ - function testUnlink() { + public function testUnlink() { $storage1 = $this->getTestStorage(); $storage2 = $this->getTestStorage(); \OC\Files\Filesystem::mount($storage1, array(), '/'); @@ -481,7 +481,7 @@ class View extends \Test\TestCase { /** * @medium */ - function testUnlinkRootMustFail() { + public function testUnlinkRootMustFail() { $storage1 = $this->getTestStorage(); $storage2 = $this->getTestStorage(); \OC\Files\Filesystem::mount($storage1, array(), '/'); @@ -500,7 +500,7 @@ class View extends \Test\TestCase { /** * @medium */ - function testTouch() { + public function testTouch() { $storage = $this->getTestStorage(true, '\Test\Files\TemporaryNoTouch'); \OC\Files\Filesystem::mount($storage, array(), '/'); @@ -524,7 +524,7 @@ class View extends \Test\TestCase { /** * @medium */ - function testViewHooks() { + public function testViewHooks() { $storage1 = $this->getTestStorage(); $storage2 = $this->getTestStorage(); $defaultRoot = \OC\Files\Filesystem::getRoot(); @@ -590,7 +590,7 @@ class View extends \Test\TestCase { /** * @medium */ - function testViewHooksIfRootStartsTheSame() { + public function testViewHooksIfRootStartsTheSame() { $storage1 = $this->getTestStorage(); $storage2 = $this->getTestStorage(); $defaultRoot = \OC\Files\Filesystem::getRoot(); @@ -851,24 +851,98 @@ class View extends \Test\TestCase { } /** - * @dataProvider relativePathProvider + * @dataProvider chrootRelativePathProvider */ - function testGetRelativePath($absolutePath, $expectedPath) { + function testChrootGetRelativePath($root, $absolutePath, $expectedPath) { $view = new \OC\Files\View('/files'); - // simulate a external storage mount point which has a trailing slash - $view->chroot('/files/'); + $view->chroot($root); $this->assertEquals($expectedPath, $view->getRelativePath($absolutePath)); } - function relativePathProvider() { + public function chrootRelativePathProvider() { + return $this->relativePathProvider('/'); + } + + /** + * @dataProvider initRelativePathProvider + */ + public function testInitGetRelativePath($root, $absolutePath, $expectedPath) { + $view = new \OC\Files\View($root); + $this->assertEquals($expectedPath, $view->getRelativePath($absolutePath)); + } + + public function initRelativePathProvider() { + return $this->relativePathProvider(null); + } + + public function relativePathProvider($missingRootExpectedPath) { return array( - array('/files/', '/'), - array('/files', '/'), - array('/files/0', '0'), - array('/files/false', 'false'), - array('/files/true', 'true'), - array('/files/test', 'test'), - array('/files/test/foo', 'test/foo'), + // No root - returns the path + array('', '/files', '/files'), + array('', '/files/', '/files/'), + + // Root equals path - / + array('/files/', '/files/', '/'), + array('/files/', '/files', '/'), + array('/files', '/files/', '/'), + array('/files', '/files', '/'), + + // False negatives: chroot fixes those by adding the leading slash. + // But setting them up with this root (instead of chroot($root)) + // will fail them, although they should be the same. + // TODO init should be fixed, so it also adds the leading slash + array('files/', '/files/', $missingRootExpectedPath), + array('files', '/files/', $missingRootExpectedPath), + array('files/', '/files', $missingRootExpectedPath), + array('files', '/files', $missingRootExpectedPath), + + // False negatives: Paths provided to the method should have a leading slash + // TODO input should be checked to have a leading slash + array('/files/', 'files/', null), + array('/files', 'files/', null), + array('/files/', 'files', null), + array('/files', 'files', null), + + // with trailing slashes + array('/files/', '/files/0', '0'), + array('/files/', '/files/false', 'false'), + array('/files/', '/files/true', 'true'), + array('/files/', '/files/test', 'test'), + array('/files/', '/files/test/foo', 'test/foo'), + + // without trailing slashes + // TODO false expectation: Should match "with trailing slashes" + array('/files', '/files/0', '/0'), + array('/files', '/files/false', '/false'), + array('/files', '/files/true', '/true'), + array('/files', '/files/test', '/test'), + array('/files', '/files/test/foo', '/test/foo'), + + // leading slashes + array('/files/', '/files_trashbin/', null), + array('/files', '/files_trashbin/', null), + array('/files/', '/files_trashbin', null), + array('/files', '/files_trashbin', null), + + // no leading slashes + array('files/', 'files_trashbin/', null), + array('files', 'files_trashbin/', null), + array('files/', 'files_trashbin', null), + array('files', 'files_trashbin', null), + + // mixed leading slashes + array('files/', '/files_trashbin/', null), + array('/files/', 'files_trashbin/', null), + array('files', '/files_trashbin/', null), + array('/files', 'files_trashbin/', null), + array('files/', '/files_trashbin', null), + array('/files/', 'files_trashbin', null), + array('files', '/files_trashbin', null), + array('/files', 'files_trashbin', null), + + array('files', 'files_trashbin/test', null), + array('/files', '/files_trashbin/test', null), + array('/files', 'files_trashbin/test', null), ); } @@ -1012,6 +1086,7 @@ class View extends \Test\TestCase { $storage2->expects($this->any()) ->method('fopen') ->will($this->returnCallback(function ($path, $mode) use ($storage2) { + /** @var \PHPUnit_Framework_MockObject_MockObject | \OC\Files\Storage\Temporary $storage2 */ $source = fopen($storage2->getSourcePath($path), $mode); return \OC\Files\Stream\Quota::wrap($source, 9); })); @@ -1020,7 +1095,7 @@ class View extends \Test\TestCase { $storage1->file_put_contents('foo.txt', '0123456789ABCDEFGH'); $storage1->mkdir('dirtomove'); $storage1->file_put_contents('dirtomove/indir1.txt', '0123456'); // fits - $storage1->file_put_contents('dirtomove/indir2.txt', '0123456789ABCDEFGH'); // doesn't fit + $storage1->file_put_contents('dirtomove/indir2.txt', '0123456789ABCDEFGH'); // doesn't fit $storage2->file_put_contents('existing.txt', '0123'); $storage1->getScanner()->scan(''); $storage2->getScanner()->scan(''); @@ -1296,7 +1371,7 @@ class View extends \Test\TestCase { $thrown = false; try { - // this actually acquires two locks, one on the mount point and one no the storage root, + // this actually acquires two locks, one on the mount point and one on the storage root, // but the one on the storage root will fail $view->lockFile('/mountpoint.txt', ILockingProvider::LOCK_SHARED); } catch (\OCP\Lock\LockedException $e) { |