From afcde27ee6bb3b3d4562c4040ce49323523fad24 Mon Sep 17 00:00:00 2001 From: Sujith H Date: Fri, 15 Mar 2019 12:50:06 +0530 Subject: [PATCH] Disable signature check if the file copy fails in encrypted file Disable signature check if the file copy fails in the encrypted file, when files are transferred using transfer-ownership command. When the file is retried with disabled signature check and if the file is again hit with an exception the transfer ownership is stopped from execution. Signed-off-by: Sujith H --- apps/files/lib/Command/TransferOwnership.php | 26 ++++++++- .../Files/Storage/Wrapper/Encryption.php | 55 ++++++++++++++++++- .../Files/Storage/Wrapper/EncryptionTest.php | 8 ++- 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/apps/files/lib/Command/TransferOwnership.php b/apps/files/lib/Command/TransferOwnership.php index e91d2ef98175..697a884c59ae 100644 --- a/apps/files/lib/Command/TransferOwnership.php +++ b/apps/files/lib/Command/TransferOwnership.php @@ -27,6 +27,7 @@ use OC\Encryption\Manager; use OC\Files\Filesystem; use OC\Files\View; +use OC\HintException; use OC\Share20\ProviderFactory; use OCP\Files\FileInfo; use OCP\Files\Mount\IMountManager; @@ -40,6 +41,8 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; class TransferOwnership extends Command { @@ -61,6 +64,9 @@ class TransferOwnership extends Command { /** @var ProviderFactory */ private $shareProviderFactory; + /** @var EventDispatcherInterface */ + private $eventDispatcher; + /** @var bool */ private $filesExist = false; @@ -85,13 +91,19 @@ class TransferOwnership extends Command { /** @var string */ private $finalTarget; - public function __construct(IUserManager $userManager, IManager $shareManager, IMountManager $mountManager, Manager $encryptionManager, ILogger $logger, ProviderFactory $shareProviderFactory) { + private $disableSignatureCheck = false; + + public function __construct(IUserManager $userManager, IManager $shareManager, + IMountManager $mountManager, Manager $encryptionManager, + ILogger $logger, ProviderFactory $shareProviderFactory, + EventDispatcherInterface $eventDispatcher) { $this->userManager = $userManager; $this->shareManager = $shareManager; $this->mountManager = $mountManager; $this->encryptionManager = $encryptionManager; $this->logger = $logger; $this->shareProviderFactory = $shareProviderFactory; + $this->eventDispatcher = $eventDispatcher; parent::__construct(); } @@ -287,6 +299,18 @@ protected function transfer(OutputInterface $output) { } } + $this->eventDispatcher->addListener('files.aftersignaturemismatch', function (GenericEvent $event) use ($output) { + if ($event->hasArgument('fileName')) { + $output->writeln("The file affected by signature mismatch: " . $event->getArgument('fileName') . ""); + } + if (!$event->hasArgument('retryWithIgnoreSignature')) { + $this->disableSignatureCheck = true; + } else { + $this->disableSignatureCheck = false; + } + $event->setArgument('retryWithIgnoreSignature', $this->disableSignatureCheck); + }, 20); + $view->rename($sourcePath, $this->finalTarget); if (!\is_dir("$this->sourceUser/files")) { diff --git a/lib/private/Files/Storage/Wrapper/Encryption.php b/lib/private/Files/Storage/Wrapper/Encryption.php index 2a9bff7e2fbd..9005e5d22e50 100644 --- a/lib/private/Files/Storage/Wrapper/Encryption.php +++ b/lib/private/Files/Storage/Wrapper/Encryption.php @@ -33,6 +33,7 @@ use OC\Files\Filesystem; use OC\Files\Mount\Manager; use OC\Files\Storage\LocalTempFileTrait; +use OC\HintException; use OC\Memcache\ArrayCache; use OCP\Encryption\Exceptions\GenericEncryptionException; use OCP\Encryption\IFile; @@ -42,6 +43,8 @@ use OCP\Files\Storage; use OCP\ILogger; use OCP\Files\Cache\ICacheEntry; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; class Encryption extends Wrapper { use LocalTempFileTrait; @@ -85,6 +88,9 @@ class Encryption extends Wrapper { /** @var ArrayCache */ private $arrayCache; + /** @var EventDispatcherInterface */ + private $eventDispatcher; + /** @var array which has information of sourcePath during rename operation */ private $sourcePath; @@ -112,7 +118,8 @@ public function __construct( IStorage $keyStorage = null, Update $update = null, Manager $mountManager = null, - ArrayCache $arrayCache = null + ArrayCache $arrayCache = null, + EventDispatcherInterface $eventDispatcher = null ) { $this->mountPoint = $parameters['mountPoint']; $this->mount = $parameters['mount']; @@ -126,6 +133,7 @@ public function __construct( $this->update = $update; $this->mountManager = $mountManager; $this->arrayCache = $arrayCache; + $this->eventDispatcher = ($eventDispatcher === null) ? \OC::$server->getEventDispatcher() : $eventDispatcher; parent::__construct($parameters); } @@ -782,7 +790,7 @@ private function copyBetweenStorage(Storage\IStorage $sourceStorage, $sourceInte unset($this->sourcePath[$targetInternalPath]); } $target = $this->fopen($targetInternalPath, 'w'); - list(, $result) = \OC_Helper::streamCopy($source, $target); + $result = $this->copyFile($source, $target, $sourceInternalPath); \fclose($source); \fclose($target); } catch (\Exception $e) { @@ -806,6 +814,49 @@ private function copyBetweenStorage(Storage\IStorage $sourceStorage, $sourceInte return (bool)$result; } + /** + * This method copies the file from the source to the target + * + * When the file is copied, the source file is read. And while decryption + * any signature mismatch happens, then the file is tried to copy again + * by disabling signature check. A custom symfony event is triggered here. + * + * @param resource $source + * @param resource $target + * @param string|null $sourceFilePath + * @throws HintException, thrown when the second attempt to copy fails + */ + private function copyFile($source, $target, $sourceFilePath = null) { + $retry = false; + do { + try { + $signatureMismatchEvent = new GenericEvent("signaturemismatch", []); + if ($retry === true) { + \fseek($source, 0); + } + list(, $result) = \OC_Helper::streamCopy($source, $target); + if ($retry === true) { + unset($signatureMismatchEvent['fileName']); + $signatureMismatchEvent->setArguments(['retryWithIgnoreSignature' => false]); + $this->eventDispatcher->dispatch('files.aftersignaturemismatch', $signatureMismatchEvent); + } + break; + } catch (HintException $e) { + $this->logger->warning("The file with signature mismatch is " . $sourceFilePath); + $this->logger->logException($e); + if ($retry === true) { + throw $e; + } else { + $retry = true; + } + $signatureMismatchEvent->setArguments(['fileName' => $sourceFilePath]); + $this->eventDispatcher->dispatch('files.aftersignaturemismatch', $signatureMismatchEvent); + } + } while ($retry === true); + + return $result; + } + /** * get the path to a local version of the file. * The local version of the file can be temporary and doesn't have to be persistent across requests diff --git a/tests/lib/Files/Storage/Wrapper/EncryptionTest.php b/tests/lib/Files/Storage/Wrapper/EncryptionTest.php index f2fdca552911..04c39ce97b22 100644 --- a/tests/lib/Files/Storage/Wrapper/EncryptionTest.php +++ b/tests/lib/Files/Storage/Wrapper/EncryptionTest.php @@ -7,6 +7,7 @@ use OC\Files\View; use OC\User\Manager; use OCP\Files\Storage\IStorage; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Test\Files\Storage\Storage; use OC\Files\Storage\Wrapper\Encryption; use OC\Files\Cache\Cache; @@ -101,6 +102,9 @@ class EncryptionTest extends Storage { /** @var \OC\Memcache\ArrayCache | \PHPUnit_Framework_MockObject_MockObject */ private $arrayCache; + /** @var EventDispatcherInterface | \PHPUnit_Framework_MockObject_MockObject */ + private $eventDispatcherInterface; + /** @var integer dummy unencrypted size */ private $dummySize = -1; @@ -187,7 +191,9 @@ protected function setUp() { 'mountPoint' => '/', 'mount' => $this->mount ], - $this->encryptionManager, $this->util, $this->logger, $this->file, null, $this->keyStore, $this->update, $this->mountManager, $this->arrayCache + $this->encryptionManager, $this->util, $this->logger, + $this->file, null, $this->keyStore, $this->update, + $this->mountManager, $this->arrayCache, $this->eventDispatcherInterface ] ) ->setMethods(['getMetaData', 'getCache', 'getEncryptionModule'])