diff options
Diffstat (limited to 'apps')
-rw-r--r-- | apps/dav/lib/connector/sabre/principal.php | 18 | ||||
-rw-r--r-- | apps/dav/lib/server.php | 17 | ||||
-rw-r--r-- | apps/dav/tests/unit/connector/sabre/principal.php | 14 | ||||
-rw-r--r-- | apps/encryption/lib/crypto/encryption.php | 74 | ||||
-rw-r--r-- | apps/encryption/tests/lib/crypto/encryptionTest.php | 19 | ||||
-rw-r--r-- | apps/files/command/deleteorphanedfiles.php | 30 | ||||
-rw-r--r-- | apps/files/lib/backgroundjob/deleteorphaneditems.php | 41 | ||||
-rw-r--r-- | apps/files_sharing/js/public.js | 3 | ||||
-rw-r--r-- | apps/files_sharing/tests/js/publicAppSpec.js | 8 | ||||
-rw-r--r-- | apps/files_versions/ajax/getVersions.php | 5 | ||||
-rw-r--r-- | apps/files_versions/js/versioncollection.js | 3 | ||||
-rw-r--r-- | apps/files_versions/lib/storage.php | 7 | ||||
-rw-r--r-- | apps/updatenotification/controller/admincontroller.php | 4 | ||||
-rw-r--r-- | apps/updatenotification/lib/resettokenbackgroundjob.php | 3 | ||||
-rw-r--r-- | apps/updatenotification/tests/controller/AdminControllerTest.php | 4 |
15 files changed, 183 insertions, 67 deletions
diff --git a/apps/dav/lib/connector/sabre/principal.php b/apps/dav/lib/connector/sabre/principal.php index 4f26390e3cc..a573124007d 100644 --- a/apps/dav/lib/connector/sabre/principal.php +++ b/apps/dav/lib/connector/sabre/principal.php @@ -49,6 +49,9 @@ class Principal implements BackendInterface { /** @var string */ private $principalPrefix; + /** @var bool */ + private $hasGroups; + /** * @param IUserManager $userManager * @param IGroupManager $groupManager @@ -60,6 +63,7 @@ class Principal implements BackendInterface { $this->userManager = $userManager; $this->groupManager = $groupManager; $this->principalPrefix = trim($principalPrefix, '/'); + $this->hasGroups = ($principalPrefix === 'principals/users/'); } /** @@ -141,13 +145,15 @@ class Principal implements BackendInterface { throw new Exception('Principal not found'); } - $groups = $this->groupManager->getUserGroups($user); - $groups = array_map(function($group) { - /** @var IGroup $group */ - return $this->principalPrefix . '/' . $group->getGID(); - }, $groups); + if ($this->hasGroups) { + $groups = $this->groupManager->getUserGroups($user); + $groups = array_map(function($group) { + /** @var IGroup $group */ + return 'principals/groups/' . $group->getGID(); + }, $groups); - return $groups; + return $groups; + } } return []; } diff --git a/apps/dav/lib/server.php b/apps/dav/lib/server.php index 3bf8e155082..f5f1875a480 100644 --- a/apps/dav/lib/server.php +++ b/apps/dav/lib/server.php @@ -30,6 +30,10 @@ use OCA\DAV\Files\CustomPropertiesBackend; use OCP\IRequest; use OCP\SabrePluginEvent; use Sabre\DAV\Auth\Plugin; +use Sabre\DAV\IFile; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use Sabre\HTTP\Util; class Server { @@ -104,6 +108,19 @@ class Server { $this->server->addPlugin(new \OCA\DAV\Connector\Sabre\FakeLockerPlugin()); } + // Serve all files with an Content-Disposition of type "attachment" + $this->server->on('beforeMethod', function (RequestInterface $requestInterface, ResponseInterface $responseInterface) { + if ($requestInterface->getMethod() === 'GET') { + $path = $requestInterface->getPath(); + if ($this->server->tree->nodeExists($path)) { + $node = $this->server->tree->getNodeForPath($path); + if (($node instanceof IFile)) { + $responseInterface->addHeader('Content-Disposition', 'attachment'); + } + } + } + }); + // wait with registering these until auth is handled and the filesystem is setup $this->server->on('beforeMethod', function () { // custom properties plugin must be the last one diff --git a/apps/dav/tests/unit/connector/sabre/principal.php b/apps/dav/tests/unit/connector/sabre/principal.php index 07bfd5d263b..1747885240a 100644 --- a/apps/dav/tests/unit/connector/sabre/principal.php +++ b/apps/dav/tests/unit/connector/sabre/principal.php @@ -202,16 +202,26 @@ class Principal extends TestCase { public function testGetGroupMembership() { $fooUser = $this->getMockBuilder('\OC\User\User') ->disableOriginalConstructor()->getMock(); + $group = $this->getMockBuilder('\OCP\IGroup') + ->disableOriginalConstructor()->getMock(); + $group->expects($this->once()) + ->method('getGID') + ->willReturn('group1'); $this->userManager ->expects($this->once()) ->method('get') ->with('foo') ->willReturn($fooUser); $this->groupManager + ->expects($this->once()) ->method('getUserGroups') - ->willReturn([]); + ->willReturn([ + $group + ]); - $expectedResponse = []; + $expectedResponse = [ + 'principals/groups/group1' + ]; $response = $this->connector->getGroupMembership('principals/users/foo'); $this->assertSame($expectedResponse, $response); } diff --git a/apps/encryption/lib/crypto/encryption.php b/apps/encryption/lib/crypto/encryption.php index a637f52a869..620bdd6ca9a 100644 --- a/apps/encryption/lib/crypto/encryption.php +++ b/apps/encryption/lib/crypto/encryption.php @@ -29,6 +29,7 @@ namespace OCA\Encryption\Crypto; use OC\Encryption\Exceptions\DecryptionFailedException; +use OC\Files\Cache\Scanner; use OC\Files\View; use OCA\Encryption\Exceptions\PublicKeyMissingException; use OCA\Encryption\Session; @@ -57,9 +58,6 @@ class Encryption implements IEncryptionModule { private $path; /** @var string */ - private $realPath; - - /** @var string */ private $user; /** @var string */ @@ -107,6 +105,9 @@ class Encryption implements IEncryptionModule { /** @var int Current version of the file */ private $version = 0; + /** @var array remember encryption signature version */ + private static $rememberVersion = []; + /** * @@ -171,7 +172,6 @@ class Encryption implements IEncryptionModule { */ public function begin($path, $user, $mode, array $header, array $accessList) { $this->path = $this->getPathToRealFile($path); - $this->realPath = $path; $this->accessList = $accessList; $this->user = $user; $this->isWriteOperation = false; @@ -187,7 +187,10 @@ class Encryption implements IEncryptionModule { $this->fileKey = $this->keyManager->getFileKey($this->path, $this->user); } - $this->version = (int)$this->keyManager->getVersion($this->realPath, new View()); + // always use the version from the original file, also part files + // need to have a correct version number if they get moved over to the + // final location + $this->version = (int)$this->keyManager->getVersion($this->stripPartFileExtension($path), new View()); if ( $mode === 'w' @@ -199,6 +202,13 @@ class Encryption implements IEncryptionModule { if (empty($this->fileKey)) { $this->fileKey = $this->crypt->generateFileKey(); } + } else { + // if we read a part file we need to increase the version by 1 + // because the version number was also increased by writing + // the part file + if(Scanner::isPartialFile($path)) { + $this->version = $this->version + 1; + } } if ($this->isWriteOperation) { @@ -230,15 +240,16 @@ class Encryption implements IEncryptionModule { public function end($path, $position = 0) { $result = ''; if ($this->isWriteOperation) { - // Partial files do not increase the version - if(\OC\Files\Cache\Scanner::isPartialFile($path)) { - $version = $this->version; - } else { - $version = $this->version + 1; + $this->keyManager->setVersion($path, $this->version + 1, new View()); + // in case of a part file we remember the new signature versions + // the version will be set later on update. + // This way we make sure that other apps listening to the pre-hooks + // still get the old version which should be the correct value for them + if (Scanner::isPartialFile($path)) { + self::$rememberVersion[$this->stripPartFileExtension($path)] = $this->version + 1; } - $this->keyManager->setVersion($this->path, $this->version+1, new View()); if (!empty($this->writeCache)) { - $result = $this->crypt->symmetricEncryptFileContent($this->writeCache, $this->fileKey, $version, $position); + $result = $this->crypt->symmetricEncryptFileContent($this->writeCache, $this->fileKey, $this->version + 1, $position); $this->writeCache = ''; } $publicKeys = array(); @@ -319,13 +330,7 @@ class Encryption implements IEncryptionModule { // Read the chunk from the start of $data $chunk = substr($data, 0, $this->unencryptedBlockSizeSigned); - // Partial files do not increase the version - if(\OC\Files\Cache\Scanner::isPartialFile($this->path)) { - $version = $this->version; - } else { - $version = $this->version + 1; - } - $encrypted .= $this->crypt->symmetricEncryptFileContent($chunk, $this->fileKey, $version, $position); + $encrypted .= $this->crypt->symmetricEncryptFileContent($chunk, $this->fileKey, $this->version + 1, $position); // Remove the chunk we just processed from // $data, leaving only unprocessed data in $data @@ -368,11 +373,16 @@ class Encryption implements IEncryptionModule { * @return boolean */ public function update($path, $uid, array $accessList) { - $fileKey = $this->keyManager->getFileKey($path, $uid); - if(empty($this->realPath)) { - $this->realPath = $path; + + if (empty($accessList)) { + if (isset(self::$rememberVersion[$path])) { + $this->keyManager->setVersion($path, self::$rememberVersion[$path], new View()); + unset(self::$rememberVersion[$path]); + } + return; } - $version = $this->keyManager->getVersion($this->realPath, new View()); + + $fileKey = $this->keyManager->getFileKey($path, $uid); if (!empty($fileKey)) { @@ -393,8 +403,6 @@ class Encryption implements IEncryptionModule { $this->keyManager->setAllFileKeys($path, $encryptedFileKey); - $this->keyManager->setVersion($path, $version, new View()); - } else { $this->logger->debug('no file key found, we assume that the file "{file}" is not encrypted', array('file' => $path, 'app' => 'encryption')); @@ -520,4 +528,20 @@ class Encryption implements IEncryptionModule { return $realPath; } + /** + * remove .part file extension and the ocTransferId from the file to get the + * original file name + * + * @param string $path + * @return string + */ + protected function stripPartFileExtension($path) { + if (pathinfo($path, PATHINFO_EXTENSION) === 'part') { + $pos = strrpos($path, '.', -6); + $path = substr($path, 0, $pos); + } + + return $path; + } + } diff --git a/apps/encryption/tests/lib/crypto/encryptionTest.php b/apps/encryption/tests/lib/crypto/encryptionTest.php index ad943ab6e49..0ce1a2cb76a 100644 --- a/apps/encryption/tests/lib/crypto/encryptionTest.php +++ b/apps/encryption/tests/lib/crypto/encryptionTest.php @@ -298,6 +298,9 @@ class EncryptionTest extends TestCase { return $publicKeys; }); + $this->keyManagerMock->expects($this->never())->method('getVersion'); + $this->keyManagerMock->expects($this->never())->method('setVersion'); + $this->assertSame($expected, $this->instance->update('path', 'user1', ['users' => ['user1']]) ); @@ -311,6 +314,22 @@ class EncryptionTest extends TestCase { ); } + public function testUpdateNoUsers() { + + $this->invokePrivate($this->instance, 'rememberVersion', [['path' => 2]]); + + $this->keyManagerMock->expects($this->never())->method('getFileKey'); + $this->keyManagerMock->expects($this->never())->method('getPublicKey'); + $this->keyManagerMock->expects($this->never())->method('addSystemKeys'); + $this->keyManagerMock->expects($this->once())->method('setVersion') + ->willReturnCallback(function($path, $version, $view) { + $this->assertSame('path', $path); + $this->assertSame(2, $version); + $this->assertTrue($view instanceof \OC\Files\View); + }); + $this->instance->update('path', 'user1', []); + } + /** * by default the encryption module should encrypt regular files, files in * files_versions and files in files_trashbin diff --git a/apps/files/command/deleteorphanedfiles.php b/apps/files/command/deleteorphanedfiles.php index d276e9a0993..f897c68fd8d 100644 --- a/apps/files/command/deleteorphanedfiles.php +++ b/apps/files/command/deleteorphanedfiles.php @@ -33,6 +33,8 @@ use Symfony\Component\Console\Output\OutputInterface; */ class DeleteOrphanedFiles extends Command { + const CHUNK_SIZE = 200; + /** * @var IDBConnection */ @@ -50,13 +52,31 @@ class DeleteOrphanedFiles extends Command { } public function execute(InputInterface $input, OutputInterface $output) { + $deletedEntries = 0; + + $query = $this->connection->getQueryBuilder(); + $query->select('fc.fileid') + ->from('filecache', 'fc') + ->where($query->expr()->isNull('s.numeric_id')) + ->leftJoin('fc', 'storages', 's', $query->expr()->eq('fc.storage', 's.numeric_id')) + ->setMaxResults(self::CHUNK_SIZE); + + $deleteQuery = $this->connection->getQueryBuilder(); + $deleteQuery->delete('filecache') + ->where($deleteQuery->expr()->eq('fileid', $deleteQuery->createParameter('objectid'))); - $sql = - 'DELETE FROM `*PREFIX*filecache` ' . - 'WHERE NOT EXISTS ' . - '(SELECT 1 FROM `*PREFIX*storages` WHERE `storage` = `numeric_id`)'; + $deletedInLastChunk = self::CHUNK_SIZE; + while ($deletedInLastChunk === self::CHUNK_SIZE) { + $deletedInLastChunk = 0; + $result = $query->execute(); + while ($row = $result->fetch()) { + $deletedInLastChunk++; + $deletedEntries += $deleteQuery->setParameter('objectid', (int) $row['fileid']) + ->execute(); + } + $result->closeCursor(); + } - $deletedEntries = $this->connection->executeUpdate($sql); $output->writeln("$deletedEntries orphaned file cache entries deleted"); } diff --git a/apps/files/lib/backgroundjob/deleteorphaneditems.php b/apps/files/lib/backgroundjob/deleteorphaneditems.php index cefa1d655de..773bb997e56 100644 --- a/apps/files/lib/backgroundjob/deleteorphaneditems.php +++ b/apps/files/lib/backgroundjob/deleteorphaneditems.php @@ -29,6 +29,8 @@ use OCP\DB\QueryBuilder\IQueryBuilder; */ class DeleteOrphanedItems extends TimedJob { + const CHUNK_SIZE = 200; + /** @var \OCP\IDBConnection */ protected $connection; @@ -66,19 +68,38 @@ class DeleteOrphanedItems extends TimedJob { /** * Deleting orphaned system tag mappings * + * @param string $table + * @param string $idCol + * @param string $typeCol * @return int Number of deleted entries */ protected function cleanUp($table, $idCol, $typeCol) { - $subQuery = $this->connection->getQueryBuilder(); - $subQuery->select($subQuery->expr()->literal('1')) - ->from('filecache', 'f') - ->where($subQuery->expr()->eq($idCol, 'f.fileid')); + $deletedEntries = 0; $query = $this->connection->getQueryBuilder(); - $deletedEntries = $query->delete($table) + $query->select('t1.' . $idCol) + ->from($table, 't1') ->where($query->expr()->eq($typeCol, $query->expr()->literal('files'))) - ->andWhere($query->expr()->isNull($query->createFunction('(' . $subQuery->getSql() . ')'))) - ->execute(); + ->andWhere($query->expr()->isNull('t2.fileid')) + ->leftJoin('t1', 'filecache', 't2', $query->expr()->eq($query->expr()->castColumn('t1.' . $idCol, IQueryBuilder::PARAM_INT), 't2.fileid')) + ->groupBy('t1.' . $idCol) + ->setMaxResults(self::CHUNK_SIZE); + + $deleteQuery = $this->connection->getQueryBuilder(); + $deleteQuery->delete($table) + ->where($deleteQuery->expr()->eq($idCol, $deleteQuery->createParameter('objectid'))); + + $deletedInLastChunk = self::CHUNK_SIZE; + while ($deletedInLastChunk === self::CHUNK_SIZE) { + $result = $query->execute(); + $deletedInLastChunk = 0; + while ($row = $result->fetch()) { + $deletedInLastChunk++; + $deletedEntries += $deleteQuery->setParameter('objectid', (int) $row[$idCol]) + ->execute(); + } + $result->closeCursor(); + } return $deletedEntries; } @@ -111,8 +132,7 @@ class DeleteOrphanedItems extends TimedJob { * @return int Number of deleted entries */ protected function cleanComments() { - $qb = $this->connection->getQueryBuilder(); - $deletedEntries = $this->cleanUp('comments', $qb->expr()->castColumn('object_id', IQueryBuilder::PARAM_INT), 'object_type'); + $deletedEntries = $this->cleanUp('comments', 'object_id', 'object_type'); $this->logger->debug("$deletedEntries orphaned comments deleted", ['app' => 'DeleteOrphanedItems']); return $deletedEntries; } @@ -123,8 +143,7 @@ class DeleteOrphanedItems extends TimedJob { * @return int Number of deleted entries */ protected function cleanCommentMarkers() { - $qb = $this->connection->getQueryBuilder(); - $deletedEntries = $this->cleanUp('comments_read_markers', $qb->expr()->castColumn('object_id', IQueryBuilder::PARAM_INT), 'object_type'); + $deletedEntries = $this->cleanUp('comments_read_markers', 'object_id', 'object_type'); $this->logger->debug("$deletedEntries orphaned comment read marks deleted", ['app' => 'DeleteOrphanedItems']); return $deletedEntries; } diff --git a/apps/files_sharing/js/public.js b/apps/files_sharing/js/public.js index bc609f04f79..da83044714e 100644 --- a/apps/files_sharing/js/public.js +++ b/apps/files_sharing/js/public.js @@ -154,9 +154,6 @@ OCA.Sharing.PublicApp = { // TODO: move this to a separate PublicFileList class that extends OCA.Files.FileList (+ unit tests) this.fileList.getDownloadUrl = function (filename, dir, isDir) { var path = dir || this.getCurrentDirectory(); - if (filename && !_.isArray(filename) && !isDir) { - return OC.getProtocol() + '://' + token + '@' + OC.getHost() + OC.getRootPath() + '/public.php/webdav' + OC.joinPaths(path, filename); - } if (_.isArray(filename)) { filename = JSON.stringify(filename); } diff --git a/apps/files_sharing/tests/js/publicAppSpec.js b/apps/files_sharing/tests/js/publicAppSpec.js index 8561836d77c..58565744882 100644 --- a/apps/files_sharing/tests/js/publicAppSpec.js +++ b/apps/files_sharing/tests/js/publicAppSpec.js @@ -102,12 +102,12 @@ describe('OCA.Sharing.PublicApp tests', function() { it('returns correct download URL for single files', function() { expect(fileList.getDownloadUrl('some file.txt')) - .toEqual('https://sh4tok@example.com:9876/owncloud/public.php/webdav/subdir/some file.txt'); - expect(fileList.getDownloadUrl('some file.txt', '/another path/abc')) - .toEqual('https://sh4tok@example.com:9876/owncloud/public.php/webdav/another path/abc/some file.txt'); + .toEqual(OC.webroot + '/index.php/s/sh4tok/download?path=%2Fsubdir&files=some%20file.txt'); + expect(fileList.getDownloadUrl('some file.txt', '/anotherpath/abc')) + .toEqual(OC.webroot + '/index.php/s/sh4tok/download?path=%2Fanotherpath%2Fabc&files=some%20file.txt'); fileList.changeDirectory('/'); expect(fileList.getDownloadUrl('some file.txt')) - .toEqual('https://sh4tok@example.com:9876/owncloud/public.php/webdav/some file.txt'); + .toEqual(OC.webroot + '/index.php/s/sh4tok/download?path=%2F&files=some%20file.txt'); }); it('returns correct download URL for multiple files', function() { expect(fileList.getDownloadUrl(['a b c.txt', 'd e f.txt'])) diff --git a/apps/files_versions/ajax/getVersions.php b/apps/files_versions/ajax/getVersions.php index 1953a55d13d..7d704c14618 100644 --- a/apps/files_versions/ajax/getVersions.php +++ b/apps/files_versions/ajax/getVersions.php @@ -41,6 +41,11 @@ if( $versions ) { $versions = array_slice($versions, $start, $count); + // remove owner path from request to not disclose it to the recipient + foreach ($versions as $version) { + unset($version['path']); + } + \OCP\JSON::success(array('data' => array('versions' => $versions, 'endReached' => $endReached))); } else { diff --git a/apps/files_versions/js/versioncollection.js b/apps/files_versions/js/versioncollection.js index 176f8e7529c..fdb12bae0a9 100644 --- a/apps/files_versions/js/versioncollection.js +++ b/apps/files_versions/js/versioncollection.js @@ -73,12 +73,13 @@ }, parse: function(result) { + var fullPath = this._fileInfo.getFullPath(); var results = _.map(result.data.versions, function(version) { var revision = parseInt(version.version, 10); return { id: revision, name: version.name, - fullPath: version.path, + fullPath: fullPath, timestamp: revision, size: version.size }; diff --git a/apps/files_versions/lib/storage.php b/apps/files_versions/lib/storage.php index 0b121c344f9..d5e21959698 100644 --- a/apps/files_versions/lib/storage.php +++ b/apps/files_versions/lib/storage.php @@ -169,11 +169,8 @@ class Storage { // Keep the "encrypted" value of the original file $oldVersion = $files_view->getFileInfo($filename)->getEncryptedVersion(); - $qb = \OC::$server->getDatabaseConnection()->getQueryBuilder(); - $qb->update('filecache') - ->set('encrypted', $qb->createNamedParameter($oldVersion)) - ->where($qb->expr()->eq('fileid', $qb->createNamedParameter($newFileInfo->getId()))) - ->execute(); + $cache = $newFileInfo->getStorage()->getCache(); + $cache->update($newFileInfo->getId(), ['encrypted' => $oldVersion, 'encryptedVersion' => $oldVersion]); } } diff --git a/apps/updatenotification/controller/admincontroller.php b/apps/updatenotification/controller/admincontroller.php index ec1cc45075c..505ea01edd9 100644 --- a/apps/updatenotification/controller/admincontroller.php +++ b/apps/updatenotification/controller/admincontroller.php @@ -77,8 +77,8 @@ class AdminController extends Controller { $this->config->setAppValue('core', 'updater.secret.created', $this->timeFactory->getTime()); // Create a new token - $newToken = $this->secureRandom->generate(32); - $this->config->setSystemValue('updater.secret', $newToken); + $newToken = $this->secureRandom->generate(64); + $this->config->setSystemValue('updater.secret', password_hash($newToken, PASSWORD_DEFAULT)); return new DataResponse($newToken); } diff --git a/apps/updatenotification/lib/resettokenbackgroundjob.php b/apps/updatenotification/lib/resettokenbackgroundjob.php index 0b737f681b6..61bd9fc0490 100644 --- a/apps/updatenotification/lib/resettokenbackgroundjob.php +++ b/apps/updatenotification/lib/resettokenbackgroundjob.php @@ -67,7 +67,8 @@ class ResetTokenBackgroundJob extends TimedJob { * @param $argument */ protected function run($argument) { - if($this->timeFactory->getTime() - $this->config->getAppValue('core', 'updater.secret.created', $this->timeFactory->getTime()) >= 86400) { + // Delete old tokens after 2 days + if($this->timeFactory->getTime() - $this->config->getAppValue('core', 'updater.secret.created', $this->timeFactory->getTime()) >= 172800) { $this->config->deleteSystemValue('updater.secret'); } } diff --git a/apps/updatenotification/tests/controller/AdminControllerTest.php b/apps/updatenotification/tests/controller/AdminControllerTest.php index 01801626000..5a0f9d21469 100644 --- a/apps/updatenotification/tests/controller/AdminControllerTest.php +++ b/apps/updatenotification/tests/controller/AdminControllerTest.php @@ -77,12 +77,12 @@ class AdminControllerTest extends TestCase { $this->secureRandom ->expects($this->once()) ->method('generate') - ->with(32) + ->with(64) ->willReturn('MyGeneratedToken'); $this->config ->expects($this->once()) ->method('setSystemValue') - ->with('updater.secret', 'MyGeneratedToken'); + ->with('updater.secret'); $this->timeFactory ->expects($this->once()) ->method('getTime') |