diff --git a/.drone.yml b/.drone.yml index 82c91bf792cb..33ee1b18f179 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1317,6 +1317,7 @@ matrix: CHOWN_SERVER: true OWNCLOUD_LOG: true INSTALL_TESTING_APP: true + USE_EMAIL: true - PHP_VERSION: 7.1 TEST_SUITE: selenium @@ -1329,6 +1330,7 @@ matrix: CHOWN_SERVER: true OWNCLOUD_LOG: true INSTALL_TESTING_APP: true + USE_EMAIL: true - PHP_VERSION: 7.1 TEST_SUITE: selenium diff --git a/.htaccess b/.htaccess index 4c026696921a..621eb9136a8f 100644 --- a/.htaccess +++ b/.htaccess @@ -15,22 +15,30 @@ # Add security and privacy related headers + Header unset X-Content-Type-Options Header always set X-Content-Type-Options "nosniff" + Header unset X-XSS-Protection Header always set X-XSS-Protection "1; mode=block" + Header unset X-Robots-Tag Header always set X-Robots-Tag "none" + Header unset X-Frame-Options Header always set X-Frame-Options "SAMEORIGIN" + Header unset X-Download-Options Header always set X-Download-Options "noopen" + Header unset X-Permitted-Cross-Domain-Policies Header always set X-Permitted-Cross-Domain-Policies "none" SetEnv modHeadersAvailable true # Let browsers cache CSS, JS files for half a year + Header unset Cache-Control Header always set Cache-Control "max-age=15778463" # Let browsers cache WOFF files for a week + Header unset Cache-Control Header always set Cache-Control "max-age=604800" @@ -84,4 +92,3 @@ Options -Indexes ModPagespeed Off - diff --git a/CHANGELOG.md b/CHANGELOG.md index 72737c0d9b3e..d10f35fac39a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,16 +4,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). -## [Unreleased] +## [10.2.0] - 2019-05-16 ### Added +- Add new capability to advertise the availability of the detail parameter for private links - [#35104](https://github.com/owncloud/core/issues/35104) - Add background:queue:execute occ command for running cron jobs manually - [#34995](https://github.com/owncloud/core/issues/34995) -- Adding background:queue commands: status and delete - [#34783](https://github.com/owncloud/core/issues/34783) -- Added new permissions option for public link - [#34983](https://github.com/owncloud/core/issues/34983) +- Adding background:queue commands: status and delete - [#34783](https://github.com/owncloud/core/issues/34783) [#35228](https://github.com/owncloud/core/pull/35228) +- Added new permissions option for public link - [#34983](https://github.com/owncloud/core/issues/34983) [#35082](https://github.com/owncloud/core/issues/35082)[#35159](https://github.com/owncloud/core/pull/35159)[#35197](https://github.com/owncloud/core/pull/35197)[#35238](https://github.com/owncloud/core/pull/35238) - Support for extra share key-value attributes - [#34951](https://github.com/owncloud/core/issues/34951) -- Internal permission to prevent file download when set in share attribute, for "secure view" feature - [#34951](https://github.com/owncloud/core/issues/34951) -- Support for automatically accepting incoming federated shares from trusted servers - [#34206](https://github.com/owncloud/core/issues/34206) +- Internal permission to prevent file download when set in share attribute, for "secure view" feature - [#34951](https://github.com/owncloud/core/issues/34951) [#35095](https://github.com/owncloud/core/issues/35095) +- Support for automatically accepting incoming federated shares from trusted servers - [#34206](https://github.com/owncloud/core/issues/34206) [#35135](https://github.com/owncloud/core/issues/35135) - User option for automatically accepting incoming shares - [#34647](https://github.com/owncloud/core/pull/34647) [#34842](https://github.com/owncloud/core/pull/34842) [#34934](https://github.com/owncloud/core/issues/34934) - User option for automatically accepting incoming federated shares - [#34706](https://github.com/owncloud/core/issues/34706) - User option to opt-out autocomplete in share dialog - [#34942](https://github.com/owncloud/core/issues/34942) @@ -31,7 +32,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - Allow admins to enable medial search on group and user - [#34779](https://github.com/owncloud/core/issues/34779) - Add composer cleaner - [#34784](https://github.com/owncloud/core/issues/34784) - Add events for user preference changes - [#34820](https://github.com/owncloud/core/issues/34820) -- Add occ command to poll incoming federated shares for updates - [#34933](https://github.com/owncloud/core/issues/34933) [#34959](https://github.com/owncloud/core/issues/34959) [#34993](https://github.com/owncloud/core/issues/34993) +- Add occ command to poll incoming federated shares for updates - [#34933](https://github.com/owncloud/core/issues/34933) [#34959](https://github.com/owncloud/core/issues/34959) [#34993](https://github.com/owncloud/core/issues/34993) [#35073](https://github.com/owncloud/core/issues/35073) ### Changed @@ -59,14 +60,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - Bump phpseclib/phpseclib from 2.0.13 to 2.0.15 - [#34285](https://github.com/owncloud/core/issues/34285) [#34741](https://github.com/owncloud/core/issues/34741) - Bump pimple/pimple from 3.0.2 to 3.2.3 - [#31753](https://github.com/owncloud/core/issues/31753) - Bump sinon from 7.1.1 to 7.3.1 in /build - [#34881](https://github.com/owncloud/core/issues/34881) [#34943](https://github.com/owncloud/core/issues/34943) -- Bump symfony v3.4.20 => v3.4.24 - [#34042](https://github.com/owncloud/core/issues/34042) [#34663](https://github.com/owncloud/core/issues/34663) [#34954](https://github.com/owncloud/core/issues/34954) -- Bump symfony/process from 3.4.21 to 3.4.22 - [#34407](https://github.com/owncloud/core/issues/34407) -- Bump symfony/translation from 3.4.21 to 3.4.22 - [#34406](https://github.com/owncloud/core/issues/34406) -- Bump symfony/console from 3.4.21 to 3.4.22 - [#34404](https://github.com/owncloud/core/issues/34404) +- Bump symfony and modules to 3.4.26 - [#35062](https://github.com/owncloud/core/issues/35062) - Bump symfony/polyfill components from v1.10.0 to v1.11.0 - [#34882](https://github.com/owncloud/core/pull/34882) -- Bump symfony/routing from 3.4.21 to 3.4.22 - [#34408](https://github.com/owncloud/core/issues/34408) -- Bump symfony/event-dispatcher from 3.4.21 to 3.4.22 - [#34405](https://github.com/owncloud/core/issues/34405) -- Bump remaining symfony 3.4.22 components - [#34412](https://github.com/owncloud/core/issues/34412) - Bump deepdiver1975/tarstreamer from 0.1.0 to 0.1.1 - [#34615](https://github.com/owncloud/core/issues/34615) - Bump zendframework/zend-servicemanager from 3.3.2 to 3.4.0 - [#33971](https://github.com/owncloud/core/issues/33971) - Bump zendframework/zend-inputfilter from 2.9.0 to 2.9.1 - [#34145](https://github.com/owncloud/core/issues/34145) @@ -80,7 +75,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - Add email footer with motto in email for changing password - [#34498](https://github.com/owncloud/core/issues/34498) - Change the styling of the active settings navigation menu item - [#34561](https://github.com/owncloud/core/issues/34561) - Added delay in search field - [#34613](https://github.com/owncloud/core/issues/34613) -- Tidy up code for notification by email - [#34786](https://github.com/owncloud/core/issues/34786) +- Tidy up code for notification by email - [#34786](https://github.com/owncloud/core/issues/34786) [#35137](https://github.com/owncloud/core/issues/35137) - Some code now made PHP 7 specific - [#34925](https://github.com/owncloud/core/issues/34925) ### Removed @@ -92,6 +87,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). ### Fixed +- Wrong translation file referenced for accept & decline share - [#35063](https://github.com/owncloud/core/issues/35063) +- Respect 'writable' appdir flag on update - [#35097](https://github.com/owncloud/core/issues/35097) +- Aborted uploads in web UI are now properly cleared - [#35134](https://github.com/owncloud/core/issues/35134) +- Fix regression with missing progress bar in files drop view - [#35059](https://github.com/owncloud/core/issues/35059) - Log exception when background job class not found - [#34723](https://github.com/owncloud/core/issues/34723) - Prevent concurrent updates in group shares to avoid duplicate entries - [#34769](https://github.com/owncloud/core/issues/34769) - Calender invitation now uses actual sender name - [#34901](https://github.com/owncloud/core/issues/34901) @@ -101,7 +100,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - Reduce sharing query size by properly reusing the query builder - [#34915](https://github.com/owncloud/core/issues/34915) - Tar download support for file names longer than 99 chars - [#34615](https://github.com/owncloud/core/issues/34615) - Fix Webdav error page, include CSP and message - [#34817](https://github.com/owncloud/core/issues/34817) -- Handle accept decline with invalid share id - [#34786](https://github.com/owncloud/core/issues/34786) +- Handle accept decline with invalid share id - [#34786](https://github.com/owncloud/core/issues/34786) [#35221](https://github.com/owncloud/core/pull/35221) - Normalize path when moving chunks to final destination - [#34777](https://github.com/owncloud/core/issues/34777) - Better support for international email addresses after swiftmailer update - [#34759](https://github.com/owncloud/core/issues/34759) - Fix first time login handling - [#34758](https://github.com/owncloud/core/issues/34758) @@ -135,7 +134,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - Fix reset confirmation mail from occ - [#34154](https://github.com/owncloud/core/issues/34154) - Correctly write Login failed entry in log when 2FA is enforced - [#34055](https://github.com/owncloud/core/issues/34055) - Center the logo and login fields - [#34057](https://github.com/owncloud/core/issues/34057) -- Fix Apache warnings by setting headers to "always" in htaccess - [#34089](https://github.com/owncloud/core/issues/34089) +- Fix Apache warnings by setting headers to "always" in htaccess - [#34089](https://github.com/owncloud/core/issues/34089) [#35118](https://github.com/owncloud/core/issues/35118) - Fix external storage advanced checkbox state issue - [#34168](https://github.com/owncloud/core/issues/34168) - Set permissions on log file creation instead of every write - [#34061](https://github.com/owncloud/core/issues/34061) - Images are again properly rotated now based on EXIF rotation - [#34356](https://github.com/owncloud/core/issues/34356) @@ -1023,7 +1022,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - provisioning API now also returns the user's home path: [#26850](https://github.com/owncloud/core/issues/26850) - web updater shows link to changelog in admin page: [#26796](https://github.com/owncloud/core/issues/26796) -[Unreleased]: https://github.com/owncloud/core/compare/v10.1.1...stable10 +[10.2.0]: https://github.com/owncloud/core/compare/v10.1.1...v10.2.0 [10.1.1]: https://github.com/owncloud/core/compare/v10.1.0...v10.1.1 [10.1.0]: https://github.com/owncloud/core/compare/v10.0.10...v10.1.0 [10.0.10]: https://github.com/owncloud/core/compare/v10.0.9...v10.0.10 diff --git a/README.md b/README.md index b09776c3b75d..7b5d260bbfe7 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ A personal cloud which runs on your own server.** * ... ## Installation instructions -https://doc.owncloud.org/server/latest/admin_manual/installation/ +https://doc.owncloud.org/server/10.2/admin_manual/installation/ ## Contribution Guidelines https://owncloud.org/contribute/ @@ -38,4 +38,4 @@ Please submit translations via Transifex: https://www.transifex.com/projects/p/owncloud/ For detailed information about translations: -https://doc.owncloud.org/server/10.1/developer_manual/core/translation.html +https://doc.owncloud.org/server/10.2/developer_manual/core/translation.html diff --git a/apps/federatedfilesharing/lib/AppInfo/Application.php b/apps/federatedfilesharing/lib/AppInfo/Application.php index d50dfc7c9ae5..e89305162cd7 100644 --- a/apps/federatedfilesharing/lib/AppInfo/Application.php +++ b/apps/federatedfilesharing/lib/AppInfo/Application.php @@ -164,8 +164,10 @@ function ($c) { function ($c) use ($server) { if ($server->getAppManager()->isEnabledForUser('files_sharing')) { $sharingApp = new \OCA\Files_Sharing\AppInfo\Application(); + $externalManager = $sharingApp->getContainer()->query('ExternalManager'); $externalMountProvider = $sharingApp->getContainer()->query('ExternalMountProvider'); } else { + $externalManager = null; $externalMountProvider = null; } @@ -173,6 +175,7 @@ function ($c) use ($server) { $server->getDatabaseConnection(), $server->getUserManager(), \OC\Files\Filesystem::getLoader(), + $externalManager, $externalMountProvider ); } diff --git a/apps/federatedfilesharing/lib/Command/PollIncomingShares.php b/apps/federatedfilesharing/lib/Command/PollIncomingShares.php index 4ad70b6d5dd3..dfcbd19a2aeb 100644 --- a/apps/federatedfilesharing/lib/Command/PollIncomingShares.php +++ b/apps/federatedfilesharing/lib/Command/PollIncomingShares.php @@ -22,7 +22,11 @@ namespace OCA\FederatedFileSharing\Command; use OC\ServerNotAvailableException; +use OC\User\NoUserException; +use OCA\FederatedFileSharing\FederatedShareProvider; +use OCA\Files_Sharing\External\Manager; use OCA\Files_Sharing\External\MountProvider; +use OCP\Files\Mount\IMountManager; use OCP\Files\Storage\IStorage; use OCP\Files\Storage\IStorageFactory; use OCP\Files\StorageInvalidException; @@ -44,7 +48,11 @@ class PollIncomingShares extends Command { /** @var IStorageFactory */ private $loader; + /** @var Manager */ + private $externalManager; + /** @var MountProvider | null */ + private $externalMountProvider; /** @@ -55,11 +63,12 @@ class PollIncomingShares extends Command { * @param MountProvider $externalMountProvider * @param IStorageFactory $loader */ - public function __construct(IDBConnection $dbConnection, IUserManager $userManager, IStorageFactory $loader, MountProvider $externalMountProvider = null) { + public function __construct(IDBConnection $dbConnection, IUserManager $userManager, IStorageFactory $loader, Manager $externalManager = null, MountProvider $externalMountProvider = null) { parent::__construct(); $this->dbConnection = $dbConnection; $this->userManager = $userManager; $this->loader = $loader; + $this->externalManager = $externalManager; $this->externalMountProvider = $externalMountProvider; } @@ -82,6 +91,13 @@ public function execute(InputInterface $input, OutputInterface $output) { $cursor = $this->getCursor(); while ($data = $cursor->fetch()) { $user = $this->userManager->get($data['user']); + if ($user === null) { + $output->writeln( + "Skipping user \"{$data['user']}\". Reason: user manager was unable to resolve the uid into the user object" + ); + continue; + } + $userMounts = $this->externalMountProvider->getMountsForUser($user, $this->loader); /** @var \OCA\Files_Sharing\External\Mount $mount */ foreach ($userMounts as $mount) { @@ -89,9 +105,22 @@ public function execute(InputInterface $input, OutputInterface $output) { /** @var Storage $storage */ $storage = $mount->getStorage(); $this->refreshStorageRoot($storage); + } catch (NoUserException $e) { + $shareData = $this->getExternalShareData($data['user'], $mount->getMountPoint()); + $entryId = $shareData['id']; + $remote = $shareData['remote']; + // uid was null so we need to set it + $this->externalManager->setUid($data['user']); + $this->externalManager->removeShare($mount->getMountPoint()); + // and now we need to reset uid back to null + $this->externalManager->setUid(null); + $output->writeln( + "Remote \"$remote\" reports that external share with id \"$entryId\" no longer exists. Removing it.." + ); } catch (\Exception $e) { - $entryId = $this->getExternalShareId($data['user'], $mount->getMountPoint()); - $remote = $storage->getRemote(); + $shareData = $this->getExternalShareData($data['user'], $mount->getMountPoint()); + $entryId = $shareData['id']; + $remote = $shareData['remote']; $reason = $e->getMessage(); $output->writeln( "Skipping external share with id \"$entryId\" from remote \"$remote\". Reason: \"$reason\"" @@ -130,13 +159,13 @@ protected function getCursor() { return $qb->execute(); } - protected function getExternalShareId(string $userId, string $mountPoint) { + protected function getExternalShareData(string $userId, string $mountPoint) { $relativeMountPoint =\rtrim( \substr($mountPoint, \strlen("/$userId/files")), '/' ); $qb = $this->dbConnection->getQueryBuilder(); - $qb->selectDistinct('id') + $qb->select('*') ->from('share_external') ->where( $qb->expr()->eq('user', @@ -148,6 +177,6 @@ protected function getExternalShareId(string $userId, string $mountPoint) { )); $result = $qb->execute(); $externalShare = $result->fetch(); - return $externalShare['id'] ?? 0; + return $externalShare; } } diff --git a/apps/federatedfilesharing/lib/FedShareManager.php b/apps/federatedfilesharing/lib/FedShareManager.php index 7f904854598f..d290fc6b2618 100644 --- a/apps/federatedfilesharing/lib/FedShareManager.php +++ b/apps/federatedfilesharing/lib/FedShareManager.php @@ -130,8 +130,9 @@ public function createShare(Address $ownerAddress, $token ) { $owner = $ownerAddress->getUserId(); + $remote = $ownerAddress->getOrigin(); $shareId = $this->federatedShareProvider->addShare( - $ownerAddress->getOrigin(), $token, $name, $owner, $shareWith, $remoteId + $remote, $token, $name, $owner, $shareWith, $remoteId ); $this->eventDispatcher->dispatch( @@ -163,20 +164,22 @@ public function createShare(Address $ownerAddress, $sharedByAddress->getCloudId(), \trim($name, '/') ]; - $notification = $this->createNotification($shareWith); - $notification->setDateTime(new \DateTime()) - ->setObject('remote_share', $shareId) - ->setSubject('remote_share', $params) - ->setMessage('remote_share', $params); - $declineAction = $notification->createAction(); - $declineAction->setLabel('decline') - ->setLink($link, 'DELETE'); - $notification->addAction($declineAction); - $acceptAction = $notification->createAction(); - $acceptAction->setLabel('accept') - ->setLink($link, 'POST'); - $notification->addAction($acceptAction); - $this->notificationManager->notify($notification); + if (!$this->federatedShareProvider->getAccepted($remote, $shareWith)) { + $notification = $this->createNotification($shareWith); + $notification->setDateTime(new \DateTime()) + ->setObject('remote_share', $shareId) + ->setSubject('remote_share', $params) + ->setMessage('remote_share', $params); + $declineAction = $notification->createAction(); + $declineAction->setLabel('decline') + ->setLink($link, 'DELETE'); + $notification->addAction($declineAction); + $acceptAction = $notification->createAction(); + $acceptAction->setLabel('accept') + ->setLink($link, 'POST'); + $notification->addAction($acceptAction); + $this->notificationManager->notify($notification); + } } /** diff --git a/apps/federatedfilesharing/lib/FederatedShareProvider.php b/apps/federatedfilesharing/lib/FederatedShareProvider.php index cfe4db97fa92..6b217cd56c9f 100644 --- a/apps/federatedfilesharing/lib/FederatedShareProvider.php +++ b/apps/federatedfilesharing/lib/FederatedShareProvider.php @@ -1053,7 +1053,7 @@ public function addShare($remote, $token, $name, $owner, $shareWith, $remoteId) * * @return bool */ - protected function getAccepted($remote, $shareWith) { + public function getAccepted($remote, $shareWith) { $event = $this->eventDispatcher->dispatch( 'remoteshare.received', new GenericEvent('', ['remote' => $remote]) diff --git a/apps/federatedfilesharing/tests/Command/PollIncomingSharesTest.php b/apps/federatedfilesharing/tests/Command/PollIncomingSharesTest.php index 3b9dd52bfc54..2c146df53b9a 100644 --- a/apps/federatedfilesharing/tests/Command/PollIncomingSharesTest.php +++ b/apps/federatedfilesharing/tests/Command/PollIncomingSharesTest.php @@ -22,9 +22,12 @@ namespace OCA\FederatedFileSharing\Tests\Command; use Doctrine\DBAL\Driver\Statement; +use OC\User\NoUserException; use OCA\FederatedFileSharing\Tests\TestCase; use OCA\FederatedFileSharing\Command\PollIncomingShares; +use OCA\Files_Sharing\External\Manager; use OCA\Files_Sharing\External\MountProvider; +use OCA\Files_Sharing\External\Storage; use OCP\DB\QueryBuilder\IExpressionBuilder; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\Storage\IStorageFactory; @@ -32,6 +35,7 @@ use OCP\IDBConnection; use OCP\IUser; use OCP\IUserManager; +use PHPUnit\Framework\MockObject\MockObject; use Symfony\Component\Console\Tester\CommandTester; /** @@ -44,25 +48,31 @@ class PollIncomingSharesTest extends TestCase { /** @var CommandTester */ private $commandTester; - /** @var IDBConnection | \PHPUnit_Framework_MockObject_MockObject */ + /** @var IDBConnection | MockObject */ private $dbConnection; - /** @var IUserManager | \PHPUnit_Framework_MockObject_MockObject */ + /** @var IUserManager | MockObject */ private $userManager; - /** @var MountProvider | \PHPUnit_Framework_MockObject_MockObject */ + /** @var MountProvider | MockObject */ private $externalMountProvider; - /** @var IStorageFactory | \PHPUnit_Framework_MockObject_MockObject */ + /** @var IStorageFactory | MockObject */ private $loader; + /** + * @var Manager | MockObject + */ + private $externalManager; + protected function setUp() { parent::setUp(); $this->dbConnection = $this->createMock(IDBConnection::class); $this->userManager = $this->createMock(IUserManager::class); - $this->externalMountProvider = $this->createMock(MountProvider::class); $this->loader = $this->createMock(IStorageFactory::class); - $command = new PollIncomingShares($this->dbConnection, $this->userManager, $this->loader, $this->externalMountProvider); + $this->externalManager = $this->createMock(Manager::class); + $this->externalMountProvider = $this->createMock(MountProvider::class); + $command = new PollIncomingShares($this->dbConnection, $this->userManager, $this->loader, $this->externalManager, $this->externalMountProvider); $this->commandTester = new CommandTester($command); } @@ -92,7 +102,7 @@ public function testNoSharesPoll() { } public function testWithFilesSharingDisabled() { - $command = new PollIncomingShares($this->dbConnection, $this->userManager, $this->loader, null); + $command = new PollIncomingShares($this->dbConnection, $this->userManager, $this->loader, $this->externalManager, null); $this->commandTester = new CommandTester($command); $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); @@ -103,8 +113,13 @@ public function testUnavailableStorage() { $uid = 'foo'; $exprBuilder = $this->createMock(IExpressionBuilder::class); $statementMock = $this->createMock(Statement::class); - $statementMock->method('fetch')->willReturnOnConsecutiveCalls(['user' => $uid], false); + $statementMock->method('fetch')->willReturnOnConsecutiveCalls( + ['user' => $uid], + ['id' => 50, 'remote' => 'example.org'], + false + ); $qbMock = $this->createMock(IQueryBuilder::class); + $qbMock->method('select')->willReturnSelf(); $qbMock->method('selectDistinct')->willReturnSelf(); $qbMock->method('from')->willReturnSelf(); $qbMock->method('where')->willReturnSelf(); @@ -114,8 +129,8 @@ public function testUnavailableStorage() { $userMock = $this->createMock(IUser::class); $this->userManager->expects($this->once())->method('get') ->with($uid)->willReturn($userMock); - - $storage = $this->createMock(\OCA\Files_Sharing\External\Storage::class); + + $storage = $this->createMock(Storage::class); $storage->method('hasUpdated')->willThrowException(new StorageNotAvailableException('Ooops')); $storage->method('getRemote')->willReturn('example.org'); @@ -129,7 +144,71 @@ public function testUnavailableStorage() { $this->commandTester->execute([]); $output = $this->commandTester->getDisplay(); $this->assertContains( - 'Skipping external share with id "0" from remote "example.org". Reason: "Ooops"', + 'Skipping external share with id "50" from remote "example.org". Reason: "Ooops"', + $output + ); + } + + public function testNotExistingUser() { + $uid = 'foo'; + $exprBuilder = $this->createMock(IExpressionBuilder::class); + $statementMock = $this->createMock(Statement::class); + $statementMock->method('fetch')->willReturnOnConsecutiveCalls(['user' => $uid], false); + $qbMock = $this->createMock(IQueryBuilder::class); + $qbMock->method('selectDistinct')->willReturnSelf(); + $qbMock->method('from')->willReturnSelf(); + $qbMock->method('where')->willReturnSelf(); + $qbMock->method('expr')->willReturn($exprBuilder); + $qbMock->method('execute')->willReturn($statementMock); + + $this->externalMountProvider->expects($this->never())->method('getMountsForUser'); + + $this->dbConnection->method('getQueryBuilder')->willReturn($qbMock); + $this->commandTester->execute([]); + $output = $this->commandTester->getDisplay(); + $this->assertContains( + 'Skipping user "foo". Reason: user manager was unable to resolve the uid into the user object', + $output + ); + } + + public function testPollingUnsharedMount() { + $uid = 'foo'; + $exprBuilder = $this->createMock(IExpressionBuilder::class); + $statementMock = $this->createMock(Statement::class); + $statementMock->method('fetch')->willReturnOnConsecutiveCalls( + ['user' => $uid], + ['id' => 50, 'remote' => 'example.org'], + false + ); + $qbMock = $this->createMock(IQueryBuilder::class); + $qbMock->method('select')->willReturnSelf(); + $qbMock->method('selectDistinct')->willReturnSelf(); + $qbMock->method('from')->willReturnSelf(); + $qbMock->method('where')->willReturnSelf(); + $qbMock->method('expr')->willReturn($exprBuilder); + $qbMock->method('execute')->willReturn($statementMock); + + $userMock = $this->createMock(IUser::class); + $this->userManager->expects($this->once())->method('get') + ->with($uid)->willReturn($userMock); + + $this->externalManager->expects($this->once())->method('removeShare'); + + $storage = $this->createMock(Storage::class); + $storage->method('hasUpdated')->willThrowException(new NoUserException('Ooops')); + + $mount = $this->createMock(\OCA\Files_Sharing\External\Mount::class); + $mount->method('getStorage')->willReturn($storage); + $mount->method('getMountPoint')->willReturn("/$uid/files/point"); + $this->externalMountProvider->expects($this->once())->method('getMountsForUser') + ->willReturn([$mount]); + + $this->dbConnection->method('getQueryBuilder')->willReturn($qbMock); + $this->commandTester->execute([]); + $output = $this->commandTester->getDisplay(); + $this->assertContains( + 'Remote "example.org" reports that external share with id "50" no longer exists. Removing it..', $output ); } diff --git a/apps/federatedfilesharing/tests/FederatedShareProviderTest.php b/apps/federatedfilesharing/tests/FederatedShareProviderTest.php index e805b9afaa83..f5a7ddea4483 100644 --- a/apps/federatedfilesharing/tests/FederatedShareProviderTest.php +++ b/apps/federatedfilesharing/tests/FederatedShareProviderTest.php @@ -919,11 +919,7 @@ public function testGetAccepted($autoAddServers, $globalAutoAccept, $userAutoAcc ->with('remoteshare.received', $this->anything()) ->willReturn($event); - $shouldAutoAccept = $this->invokePrivate( - $this->provider, - 'getAccepted', - ['remote', 'user@server.com'] - ); + $shouldAutoAccept = $this->provider->getAccepted('remote', 'user@server.com'); $this->assertEquals($expected, $shouldAutoAccept); } diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js index 1f5137c6c768..dfa4aa0b7dc5 100644 --- a/apps/files/js/file-upload.js +++ b/apps/files/js/file-upload.js @@ -682,6 +682,7 @@ OC.Uploader.prototype = _.extend({ this.log('canceling uploads'); jQuery.each(this._uploads, function(i, upload) { upload.abort(); + upload.aborted = true; }); this.clear(); }, @@ -691,7 +692,7 @@ OC.Uploader.prototype = _.extend({ clear: function() { var remainingUploads = {}; _.each(this._uploads, function(upload, key) { - if (!upload.isDone) { + if (!upload.isDone && !upload.aborted) { remainingUploads[key] = upload; } }); @@ -829,11 +830,12 @@ OC.Uploader.prototype = _.extend({ return true; } var fileInfo = fileList.findFile(file.name); - var sharePermission = $("#sharePermission").val(); - if (sharePermission !== undefined) { - sharePermission &= (OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE | OC.PERMISSION_DELETE); - } - if (fileInfo && (sharePermission !== (OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE))) { + if (fileInfo) { + var sharePermission = parseInt($("#sharePermission").val()); + if (sharePermission === (OC.PERMISSION_READ | OC.PERMISSION_CREATE)) { + OC.Notification.show(t('files', 'The file {file} already exists', {file: fileInfo.name}), {type: 'error'}); + return false; + } conflicts.push([ // original _.extend(fileInfo, { diff --git a/apps/files/lib/Capabilities.php b/apps/files/lib/Capabilities.php index 4d332ecba303..797756bd24ad 100644 --- a/apps/files/lib/Capabilities.php +++ b/apps/files/lib/Capabilities.php @@ -58,6 +58,7 @@ public function getCapabilities() { ], 'files' => [ 'privateLinks' => true, + 'privateLinksDetailsParam' => true, 'bigfilechunking' => true, 'blacklisted_files' => $this->config->getSystemValue('blacklisted_files', ['.htaccess']), ], diff --git a/apps/files/tests/CapabilitiesTest.php b/apps/files/tests/CapabilitiesTest.php new file mode 100644 index 000000000000..54b13e4e3f0a --- /dev/null +++ b/apps/files/tests/CapabilitiesTest.php @@ -0,0 +1,51 @@ + + * + * @copyright Copyright (c) 2019, ownCloud GmbH + * @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 + * + */ + +class CapabilitiesTest extends TestCase { + + /** @var IConfig| MockObject */ + protected $config; + + /** @var Capabilities */ + protected $capabilities; + + protected function setUp() { + parent::setUp(); + + $this->config = $this->createMock(IConfig::class); + $this->capabilities = new Capabilities($this->config); + } + + public function testGetCapabilities() { + $result = $this->capabilities->getCapabilities(); + $this->assertArrayHasKey('checksums', $result); + $this->assertArrayHasKey('files', $result); + $this->assertArrayHasKey('privateLinksDetailsParam', $result['files']); + $this->assertTrue($result['files']['privateLinksDetailsParam']); + } +} diff --git a/apps/files/tests/js/fileUploadSpec.js b/apps/files/tests/js/fileUploadSpec.js index 9854f62e0898..eae6ce796379 100644 --- a/apps/files/tests/js/fileUploadSpec.js +++ b/apps/files/tests/js/fileUploadSpec.js @@ -133,11 +133,13 @@ describe('OC.Upload tests', function() { it('clear leaves pending uploads', function() { uploader._uploads = { 'abc': {name: 'a job well done.txt', isDone: true}, - 'def': {name: 'whatevs.txt'} + 'def': {name: 'whatevs.txt'}, + 'ghi': {name: 'aborted.txt', aborted: true} }; uploader.clear(); + //This does verify that aborted upload(s) will not be available in the _uploads expect(uploader._uploads).toEqual({'def': {name: 'whatevs.txt'}}); }); }); diff --git a/apps/files_sharing/js/PublicUploadView.js b/apps/files_sharing/js/PublicUploadView.js index 23fe0f8274e7..6f5e8f550df6 100644 --- a/apps/files_sharing/js/PublicUploadView.js +++ b/apps/files_sharing/js/PublicUploadView.js @@ -152,9 +152,10 @@ var view = new OCA.Sharing.PublicUploadView({ shareToken: $('#sharingToken').val() }); + $('#preview .uploadForm').append(view.$el); + view.render(); - $('#preview .uploadForm').append(view.$el); $('#uploadprogresswrapper .stop').on('click', function () { view.onUploadCancel(); }); diff --git a/apps/files_sharing/lib/Controller/Share20OcsController.php b/apps/files_sharing/lib/Controller/Share20OcsController.php index f10585fe4bec..a1923cf981c1 100644 --- a/apps/files_sharing/lib/Controller/Share20OcsController.php +++ b/apps/files_sharing/lib/Controller/Share20OcsController.php @@ -461,7 +461,7 @@ public function createShare() { ); } elseif ($permissions === \OCP\Constants::PERMISSION_CREATE || $permissions === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) || - $permissions === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_CREATE)) { + $permissions === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE)) { $share->setPermissions($permissions); } else { // because when "publicUpload" is passed usually no permissions are set, @@ -776,6 +776,7 @@ public function updateShare($id) { if ($newPermissions !== null && $newPermissions !== Constants::PERMISSION_READ && $newPermissions !== Constants::PERMISSION_CREATE && + $newPermissions !== (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE) && // legacy $newPermissions !== (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE) && // correct @@ -800,9 +801,6 @@ public function updateShare($id) { $share->getNode()->unlock(ILockingProvider::LOCK_SHARED); return new Result(null, 400, $this->l->t('Public upload is only possible for publicly shared folders')); } - - // normalize to correct public upload permissions - $newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE; } // create-only (upload-only) @@ -924,12 +922,13 @@ public function declineShare($id) { * @NoAdminRequired * * @param int $itemSource + * @param string $itemType * @param int $shareType * @param string $recipient * * @return Result */ - public function notifyRecipients($itemSource, $shareType, $recipient) { + public function notifyRecipients($itemSource, $itemType, $shareType, $recipient) { $recipientList = []; if ($shareType === Share::SHARE_TYPE_USER) { $recipientList[] = $this->userManager->get($recipient); @@ -964,21 +963,21 @@ public function notifyRecipients($itemSource, $shareType, $recipient) { // if we were able to send to at least one recipient, mark as sent // allowing the user to resend would spam users who already got a notification if (\count($result) < \count($recipientList)) { - $items = $this->shareManager->getSharedWith($recipient, $shareType, $node); - if (\count($items) > 0) { - $share = $items[0]; - $share->setMailSend(true); - $this->shareManager->updateShare($share); - } + // FIXME: migrate to a new share API + Share::setSendMailStatus($itemType, $itemSource, $shareType, $recipient, true); } - $message = empty($result) - ? null - : $this->l->t( + if (empty($result)) { + $message = null; + $data = ['status' => 'success']; + } else { + $message = $this->l->t( "Couldn't send mail to following recipient(s): %s ", \implode(', ', $result) ); - return new Result([], 200, $message); + $data = ['status' => 'error']; + } + return new Result($data, 200, $message); } /** @@ -988,23 +987,16 @@ public function notifyRecipients($itemSource, $shareType, $recipient) { * @NoAdminRequired * * @param int $itemSource + * @param string $itemType * @param int $shareType * @param string $recipient * * @return Result */ - public function notifyRecipientsDisabled($itemSource, $shareType, $recipient) { - $userFolder = $this->rootFolder->getUserFolder($this->currentUser->getUID()); - $nodes = $userFolder->getById($itemSource, true); - $node = $nodes[0] ?? null; - - $items = $this->shareManager->getSharedWith($recipient, $shareType, $node); - if (\count($items) > 0) { - $share = $items[0]; - $share->setMailSend(true); - $this->shareManager->updateShare($share); - } - return new Result(); + public function notifyRecipientsDisabled($itemSource, $itemType, $shareType, $recipient) { + // FIXME: migrate to a new share API + Share::setSendMailStatus($itemType, $itemSource, $shareType, $recipient, true); + return new Result(['status' => 'success']); } /** diff --git a/apps/files_sharing/lib/External/Manager.php b/apps/files_sharing/lib/External/Manager.php index db92d1d17886..6b2f23cddc4f 100644 --- a/apps/files_sharing/lib/External/Manager.php +++ b/apps/files_sharing/lib/External/Manager.php @@ -30,6 +30,7 @@ namespace OCA\Files_Sharing\External; use OC\Files\Filesystem; +use OC\User\NoUserException; use OCP\Files; use OCP\Notification\IManager; use OCP\Share\Events\AcceptShare; @@ -264,7 +265,7 @@ public function processNotification($remoteShare) { * @return string */ protected function stripPath($path) { - $prefix = '/' . $this->uid . '/files'; + $prefix = "/{$this->uid}/files"; return \rtrim(\substr($path, \strlen($prefix)), '/'); } @@ -315,7 +316,27 @@ public function setMountPoint($source, $target) { return $result; } + /** + * Explicitly set uid when the shares are managed in CLI + * + * @param string|null $uid + */ + public function setUid($uid) { + // FIXME: External manager should not depend on uid + $this->uid = $uid; + } + + /** + * @param $mountPoint + * @return bool + * + * @throws NoUserException + */ public function removeShare($mountPoint) { + if ($this->uid === null) { + throw new NoUserException(); + } + $mountPointObj = $this->mountManager->find($mountPoint); $id = $mountPointObj->getStorage()->getCache()->getId(''); diff --git a/apps/files_sharing/tests/Controller/Share20OcsControllerTest.php b/apps/files_sharing/tests/Controller/Share20OcsControllerTest.php index b725701e5baa..533418670803 100644 --- a/apps/files_sharing/tests/Controller/Share20OcsControllerTest.php +++ b/apps/files_sharing/tests/Controller/Share20OcsControllerTest.php @@ -1773,9 +1773,15 @@ public function testUpdateLinkShareEnablePublicUpload($params) { $this->shareManager->expects($this->once())->method('updateShare')->with( $this->callback(function (\OCP\Share\IShare $share) { - return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) && - $share->getPassword() === 'password' && - $share->getExpirationDate() === null; + if ($share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE)) { + return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) && + $share->getPassword() === 'password' && + $share->getExpirationDate() === null; + } else { + return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE) && + $share->getPassword() === 'password' && + $share->getExpirationDate() === null; + } }) )->will($this->returnArgument(0)); @@ -2052,7 +2058,7 @@ public function testUpdateLinkSharePermissions() { $this->shareManager->expects($this->once())->method('updateShare')->with( $this->callback(function (\OCP\Share\IShare $share) use ($date) { - return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) && + return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE) && $share->getPassword() === 'password' && $share->getExpirationDate() === $date; }) diff --git a/core/js/sharedialoglinkshareview.js b/core/js/sharedialoglinkshareview.js index 12c837e72400..f7c9c8597a5f 100644 --- a/core/js/sharedialoglinkshareview.js +++ b/core/js/sharedialoglinkshareview.js @@ -27,12 +27,12 @@ '' + '

{{publicReadDescription}}

' + '' + + '{{#if publicUploadPossible}}' + '' + - '{{#if publicUploadPossible}}' + '