diff options
-rw-r--r-- | apps/files_external/js/settings.js | 4 | ||||
-rw-r--r-- | apps/files_external/lib/storageconfig.php | 1 | ||||
-rw-r--r-- | apps/files_external/migration/storagemigrator.php | 6 | ||||
-rw-r--r-- | apps/files_external/tests/js/settingsSpec.js | 2 | ||||
-rw-r--r-- | apps/user_ldap/lib/access.php | 4 | ||||
-rw-r--r-- | console.php | 2 | ||||
-rw-r--r-- | lib/private/files/storage/wrapper/encryption.php | 132 | ||||
-rw-r--r-- | tests/lib/files/storage/wrapper/encryption.php | 158 |
8 files changed, 299 insertions, 10 deletions
diff --git a/apps/files_external/js/settings.js b/apps/files_external/js/settings.js index 26df203091e..96b56d221c5 100644 --- a/apps/files_external/js/settings.js +++ b/apps/files_external/js/settings.js @@ -23,7 +23,7 @@ var MOUNT_OPTIONS_DROPDOWN_TEMPLATE = ' <label for="mountOptionsPreviews">{{t "files_external" "Enable previews"}}</label>' + ' </div>' + ' <div class="optionRow">' + - ' <input id="mountOptionsSharing" name="enable_sharing" type="checkbox" value="true" checked="checked"/>' + + ' <input id="mountOptionsSharing" name="enable_sharing" type="checkbox" value="true"/>' + ' <label for="mountOptionsSharing">{{t "files_external" "Enable sharing"}}</label>' + ' </div>' + ' <div class="optionRow">' + @@ -888,7 +888,7 @@ MountConfigListView.prototype = _.extend({ $tr.find('input.mountOptions').val(JSON.stringify({ 'encrypt': true, 'previews': true, - 'enable_sharing': true, + 'enable_sharing': false, 'filesystem_check_changes': 1 })); } diff --git a/apps/files_external/lib/storageconfig.php b/apps/files_external/lib/storageconfig.php index 6f44b25a2e6..42c515f4671 100644 --- a/apps/files_external/lib/storageconfig.php +++ b/apps/files_external/lib/storageconfig.php @@ -126,6 +126,7 @@ class StorageConfig implements \JsonSerializable { */ public function __construct($id = null) { $this->id = $id; + $this->mountOptions['enable_sharing'] = false; } /** diff --git a/apps/files_external/migration/storagemigrator.php b/apps/files_external/migration/storagemigrator.php index ba81810a4fd..832807feb93 100644 --- a/apps/files_external/migration/storagemigrator.php +++ b/apps/files_external/migration/storagemigrator.php @@ -100,6 +100,12 @@ class StorageMigrator { $this->connection->beginTransaction(); try { foreach ($existingStorage as $storage) { + $mountOptions = $storage->getMountOptions(); + if (!empty($mountOptions) && !isset($mountOptions['enable_sharing'])) { + // existing mounts must have sharing enabled by default to avoid surprises + $mountOptions['enable_sharing'] = true; + $storage->setMountOptions($mountOptions); + } $storageService->addStorage($storage); } $this->connection->commit(); diff --git a/apps/files_external/tests/js/settingsSpec.js b/apps/files_external/tests/js/settingsSpec.js index 6f5bb2a8e3e..2a7afd6c2fa 100644 --- a/apps/files_external/tests/js/settingsSpec.js +++ b/apps/files_external/tests/js/settingsSpec.js @@ -363,7 +363,7 @@ describe('OCA.External.Settings tests', function() { expect(JSON.parse($tr.find('input.mountOptions').val())).toEqual({ encrypt: true, previews: true, - enable_sharing: true, + enable_sharing: false, filesystem_check_changes: 0 }); }); diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php index 16b942084c4..8b83b7d917f 100644 --- a/apps/user_ldap/lib/access.php +++ b/apps/user_ldap/lib/access.php @@ -546,8 +546,8 @@ class Access extends LDAPUtility implements user\IUserTools { if(is_null($nameByLDAP)) { continue; } - $sndName = isset($ldapObject[$sndAttribute]) - ? $ldapObject[$sndAttribute] : ''; + $sndName = isset($ldapObject[$sndAttribute][0]) + ? $ldapObject[$sndAttribute][0] : ''; $this->cacheUserDisplayName($ocName, $nameByLDAP, $sndName); } } diff --git a/console.php b/console.php index eb6c84c3cf8..3c25be34fe4 100644 --- a/console.php +++ b/console.php @@ -77,7 +77,7 @@ try { exit(1); } - if (!function_exists('pcntl_signal')) { + if (!function_exists('pcntl_signal') && !in_array('--no-warnings', $argv)) { echo "The process control (PCNTL) extensions are required in case you want to interrupt long running commands - see http://php.net/manual/en/book.pcntl.php" . PHP_EOL; } diff --git a/lib/private/files/storage/wrapper/encryption.php b/lib/private/files/storage/wrapper/encryption.php index 11c6084d00c..1b0f39428a4 100644 --- a/lib/private/files/storage/wrapper/encryption.php +++ b/lib/private/files/storage/wrapper/encryption.php @@ -61,7 +61,7 @@ class Encryption extends Wrapper { private $uid; /** @var array */ - private $unencryptedSize; + protected $unencryptedSize; /** @var \OCP\Encryption\IFile */ private $fileHelper; @@ -78,6 +78,9 @@ class Encryption extends Wrapper { /** @var Manager */ private $mountManager; + /** @var array remember for which path we execute the repair step to avoid recursions */ + private $fixUnencryptedSizeOf = array(); + /** * @param array $parameters * @param IManager $encryptionManager @@ -147,8 +150,9 @@ class Encryption extends Wrapper { } if (isset($info['fileid']) && $info['encrypted']) { - return $info['size']; + return $this->verifyUnencryptedSize($path, $info['size']); } + return $this->storage->filesize($path); } @@ -169,8 +173,8 @@ class Encryption extends Wrapper { } else { $info = $this->getCache()->get($path); if (isset($info['fileid']) && $info['encrypted']) { + $data['size'] = $this->verifyUnencryptedSize($path, $info['size']); $data['encrypted'] = true; - $data['size'] = $info['size']; } } @@ -441,6 +445,128 @@ class Encryption extends Wrapper { return $this->storage->fopen($path, $mode); } + + /** + * perform some plausibility checks if the the unencrypted size is correct. + * If not, we calculate the correct unencrypted size and return it + * + * @param string $path internal path relative to the storage root + * @param int $unencryptedSize size of the unencrypted file + * + * @return int unencrypted size + */ + protected function verifyUnencryptedSize($path, $unencryptedSize) { + + $size = $this->storage->filesize($path); + $result = $unencryptedSize; + + if ($unencryptedSize < 0 || + ($size > 0 && $unencryptedSize === $size) + ) { + // check if we already calculate the unencrypted size for the + // given path to avoid recursions + if (isset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]) === false) { + $this->fixUnencryptedSizeOf[$this->getFullPath($path)] = true; + try { + $result = $this->fixUnencryptedSize($path, $size, $unencryptedSize); + } catch (\Exception $e) { + $this->logger->error('Couldn\'t re-calculate unencrypted size for '. $path); + $this->logger->logException($e); + } + unset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]); + } + } + + return $result; + } + + /** + * calculate the unencrypted size + * + * @param string $path internal path relative to the storage root + * @param int $size size of the physical file + * @param int $unencryptedSize size of the unencrypted file + * + * @return int calculated unencrypted size + */ + protected function fixUnencryptedSize($path, $size, $unencryptedSize) { + + $headerSize = $this->getHeaderSize($path); + $header = $this->getHeader($path); + $encryptionModule = $this->getEncryptionModule($path); + + $stream = $this->storage->fopen($path, 'r'); + + // if we couldn't open the file we return the old unencrypted size + if (!is_resource($stream)) { + $this->logger->error('Could not open ' . $path . '. Recalculation of unencrypted size aborted.'); + return $unencryptedSize; + } + + $newUnencryptedSize = 0; + $size -= $headerSize; + $blockSize = $this->util->getBlockSize(); + + // if a header exists we skip it + if ($headerSize > 0) { + fread($stream, $headerSize); + } + + // fast path, else the calculation for $lastChunkNr is bogus + if ($size === 0) { + return 0; + } + + $signed = (isset($header['signed']) && $header['signed'] === 'true') ? true : false; + $unencryptedBlockSize = $encryptionModule->getUnencryptedBlockSize($signed); + + // calculate last chunk nr + // next highest is end of chunks, one subtracted is last one + // we have to read the last chunk, we can't just calculate it (because of padding etc) + + $lastChunkNr = ceil($size/ $blockSize)-1; + // calculate last chunk position + $lastChunkPos = ($lastChunkNr * $blockSize); + // try to fseek to the last chunk, if it fails we have to read the whole file + if (@fseek($stream, $lastChunkPos, SEEK_CUR) === 0) { + $newUnencryptedSize += $lastChunkNr * $unencryptedBlockSize; + } + + $lastChunkContentEncrypted=''; + $count = $blockSize; + + while ($count > 0) { + $data=fread($stream, $blockSize); + $count=strlen($data); + $lastChunkContentEncrypted .= $data; + if(strlen($lastChunkContentEncrypted) > $blockSize) { + $newUnencryptedSize += $unencryptedBlockSize; + $lastChunkContentEncrypted=substr($lastChunkContentEncrypted, $blockSize); + } + } + + fclose($stream); + + // we have to decrypt the last chunk to get it actual size + $encryptionModule->begin($this->getFullPath($path), $this->uid, 'r', $header, []); + $decryptedLastChunk = $encryptionModule->decrypt($lastChunkContentEncrypted, $lastChunkNr . 'end'); + $decryptedLastChunk .= $encryptionModule->end($this->getFullPath($path), $lastChunkNr . 'end'); + + // calc the real file size with the size of the last chunk + $newUnencryptedSize += strlen($decryptedLastChunk); + + $this->updateUnencryptedSize($this->getFullPath($path), $newUnencryptedSize); + + // write to cache if applicable + $cache = $this->storage->getCache(); + if ($cache) { + $entry = $cache->get($path); + $cache->update($entry['fileid'], ['size' => $newUnencryptedSize]); + } + + return $newUnencryptedSize; + } + /** * @param Storage $sourceStorage * @param string $sourceInternalPath diff --git a/tests/lib/files/storage/wrapper/encryption.php b/tests/lib/files/storage/wrapper/encryption.php index 2b93aa86db0..c18e518fe6d 100644 --- a/tests/lib/files/storage/wrapper/encryption.php +++ b/tests/lib/files/storage/wrapper/encryption.php @@ -5,8 +5,9 @@ namespace Test\Files\Storage\Wrapper; use OC\Encryption\Util; use OC\Files\Storage\Temporary; use OC\Files\View; +use Test\Files\Storage\Storage; -class Encryption extends \Test\Files\Storage\Storage { +class Encryption extends Storage { /** * block size will always be 8192 for a PHP stream @@ -211,6 +212,161 @@ class Encryption extends \Test\Files\Storage\Storage { } /** + * @dataProvider dataTestGetMetaData + * + * @param string $path + * @param array $metaData + * @param bool $encrypted + * @param bool $unencryptedSizeSet + * @param int $storedUnencryptedSize + * @param array $expected + */ + public function testGetMetaData($path, $metaData, $encrypted, $unencryptedSizeSet, $storedUnencryptedSize, $expected) { + + $sourceStorage = $this->getMockBuilder('\OC\Files\Storage\Storage') + ->disableOriginalConstructor()->getMock(); + + $cache = $this->getMockBuilder('\OC\Files\Cache\Cache') + ->disableOriginalConstructor()->getMock(); + $cache->expects($this->any()) + ->method('get') + ->willReturnCallback( + function($path) use ($encrypted) { + return ['encrypted' => $encrypted, 'path' => $path, 'size' => 0, 'fileid' => 1]; + } + ); + + $this->instance = $this->getMockBuilder('\OC\Files\Storage\Wrapper\Encryption') + ->setConstructorArgs( + [ + [ + 'storage' => $sourceStorage, + 'root' => 'foo', + 'mountPoint' => '/', + 'mount' => $this->mount + ], + $this->encryptionManager, $this->util, $this->logger, $this->file, null, $this->keyStore, $this->update, $this->mountManager + ] + ) + ->setMethods(['getCache', 'verifyUnencryptedSize']) + ->getMock(); + + if($unencryptedSizeSet) { + $this->invokePrivate($this->instance, 'unencryptedSize', [[$path => $storedUnencryptedSize]]); + } + + + $sourceStorage->expects($this->once())->method('getMetaData')->with($path) + ->willReturn($metaData); + + $this->instance->expects($this->any())->method('getCache')->willReturn($cache); + $this->instance->expects($this->any())->method('verifyUnencryptedSize') + ->with($path, 0)->willReturn($expected['size']); + + $result = $this->instance->getMetaData($path); + $this->assertSame($expected['encrypted'], $result['encrypted']); + $this->assertSame($expected['size'], $result['size']); + } + + public function dataTestGetMetaData() { + return [ + ['/test.txt', ['size' => 42, 'encrypted' => false], true, true, 12, ['size' => 12, 'encrypted' => true]], + ['/test.txt', null, true, true, 12, null], + ['/test.txt', ['size' => 42, 'encrypted' => false], false, false, 12, ['size' => 42, 'encrypted' => false]], + ['/test.txt', ['size' => 42, 'encrypted' => false], true, false, 12, ['size' => 12, 'encrypted' => true]] + ]; + } + + public function testFilesize() { + $cache = $this->getMockBuilder('\OC\Files\Cache\Cache') + ->disableOriginalConstructor()->getMock(); + $cache->expects($this->any()) + ->method('get') + ->willReturn(['encrypted' => true, 'path' => '/test.txt', 'size' => 0, 'fileid' => 1]); + + $this->instance = $this->getMockBuilder('\OC\Files\Storage\Wrapper\Encryption') + ->setConstructorArgs( + [ + [ + 'storage' => $this->sourceStorage, + 'root' => 'foo', + 'mountPoint' => '/', + 'mount' => $this->mount + ], + $this->encryptionManager, $this->util, $this->logger, $this->file, null, $this->keyStore, $this->update, $this->mountManager + ] + ) + ->setMethods(['getCache', 'verifyUnencryptedSize']) + ->getMock(); + + $this->instance->expects($this->any())->method('getCache')->willReturn($cache); + $this->instance->expects($this->any())->method('verifyUnencryptedSize') + ->willReturn(42); + + + $this->assertSame(42, + $this->instance->filesize('/test.txt') + ); + + } + + /** + * @dataProvider dataTestVerifyUnencryptedSize + * + * @param int $encryptedSize + * @param int $unencryptedSize + * @param bool $failure + * @param int $expected + */ + public function testVerifyUnencryptedSize($encryptedSize, $unencryptedSize, $failure, $expected) { + $sourceStorage = $this->getMockBuilder('\OC\Files\Storage\Storage') + ->disableOriginalConstructor()->getMock(); + + $this->instance = $this->getMockBuilder('\OC\Files\Storage\Wrapper\Encryption') + ->setConstructorArgs( + [ + [ + 'storage' => $sourceStorage, + 'root' => 'foo', + 'mountPoint' => '/', + 'mount' => $this->mount + ], + $this->encryptionManager, $this->util, $this->logger, $this->file, null, $this->keyStore, $this->update, $this->mountManager + ] + ) + ->setMethods(['fixUnencryptedSize']) + ->getMock(); + + $sourceStorage->expects($this->once())->method('filesize')->willReturn($encryptedSize); + + $this->instance->expects($this->any())->method('fixUnencryptedSize') + ->with('/test.txt', $encryptedSize, $unencryptedSize) + ->willReturnCallback( + function() use ($failure, $expected) { + if ($failure) { + throw new \Exception(); + } else { + return $expected; + } + } + ); + + $this->assertSame( + $expected, + $this->invokePrivate($this->instance, 'verifyUnencryptedSize', ['/test.txt', $unencryptedSize]) + ); + } + + public function dataTestVerifyUnencryptedSize() { + return [ + [120, 80, false, 80], + [120, 120, false, 80], + [120, -1, false, 80], + [120, -1, true, -1] + ]; + } + + /** * @dataProvider dataTestCopyAndRename * * @param string $source |