diff --git a/apps/dav/lib/connector/sabre/filesplugin.php b/apps/dav/lib/connector/sabre/filesplugin.php index 686d0863f9..dd4670da5f 100644 --- a/apps/dav/lib/connector/sabre/filesplugin.php +++ b/apps/dav/lib/connector/sabre/filesplugin.php @@ -39,6 +39,7 @@ use Sabre\DAV\Tree; use \Sabre\HTTP\RequestInterface; use \Sabre\HTTP\ResponseInterface; use OCP\Files\StorageNotAvailableException; +use OCP\IConfig; class FilesPlugin extends ServerPlugin { @@ -55,6 +56,7 @@ class FilesPlugin extends ServerPlugin { const OWNER_ID_PROPERTYNAME = '{http://owncloud.org/ns}owner-id'; const OWNER_DISPLAY_NAME_PROPERTYNAME = '{http://owncloud.org/ns}owner-display-name'; const CHECKSUMS_PROPERTYNAME = '{http://owncloud.org/ns}checksums'; + const DATA_FINGERPRINT_PROPERTYNAME = '{http://owncloud.org/ns}data-fingerprint'; /** * Reference to main server object @@ -86,6 +88,11 @@ class FilesPlugin extends ServerPlugin { */ private $downloadAttachment; + /** + * @var IConfig + */ + private $config; + /** * @param Tree $tree * @param View $view @@ -94,10 +101,12 @@ class FilesPlugin extends ServerPlugin { */ public function __construct(Tree $tree, View $view, + IConfig $config, $isPublic = false, $downloadAttachment = true) { $this->tree = $tree; $this->fileView = $view; + $this->config = $config; $this->isPublic = $isPublic; $this->downloadAttachment = $downloadAttachment; } @@ -125,6 +134,7 @@ class FilesPlugin extends ServerPlugin { $server->protectedProperties[] = self::OWNER_ID_PROPERTYNAME; $server->protectedProperties[] = self::OWNER_DISPLAY_NAME_PROPERTYNAME; $server->protectedProperties[] = self::CHECKSUMS_PROPERTYNAME; + $server->protectedProperties[] = self::DATA_FINGERPRINT_PROPERTYNAME; // normally these cannot be changed (RFC4918), but we want them modifiable through PROPPATCH $allowedProperties = ['{DAV:}getetag']; @@ -272,6 +282,18 @@ class FilesPlugin extends ServerPlugin { $displayName = $owner->getDisplayName(); return $displayName; }); + + $propFind->handle(self::DATA_FINGERPRINT_PROPERTYNAME, function() use ($node) { + if ($node->getPath() === '/') { + return $this->config->getSystemValue('data-fingerprint', ''); + } + }); + } + + if ($node instanceof \OCA\DAV\Files\FilesHome) { + $propFind->handle(self::DATA_FINGERPRINT_PROPERTYNAME, function() use ($node) { + return $this->config->getSystemValue('data-fingerprint', ''); + }); } if ($node instanceof \OCA\DAV\Connector\Sabre\File) { diff --git a/apps/dav/lib/connector/sabre/serverfactory.php b/apps/dav/lib/connector/sabre/serverfactory.php index cab7a85d19..5853370778 100644 --- a/apps/dav/lib/connector/sabre/serverfactory.php +++ b/apps/dav/lib/connector/sabre/serverfactory.php @@ -137,8 +137,15 @@ class ServerFactory { } $objectTree->init($root, $view, $this->mountManager); - $server->addPlugin(new \OCA\DAV\Connector\Sabre\FilesPlugin($objectTree, $view, false, - !$this->config->getSystemValue('debug', false))); + $server->addPlugin( + new \OCA\DAV\Connector\Sabre\FilesPlugin( + $objectTree, + $view, + $this->config, + false, + !$this->config->getSystemValue('debug', false) + ) + ); $server->addPlugin(new \OCA\DAV\Connector\Sabre\QuotaPlugin($view)); if($this->userSession->isLoggedIn()) { diff --git a/apps/dav/lib/server.php b/apps/dav/lib/server.php index e666855644..73e24c9a29 100644 --- a/apps/dav/lib/server.php +++ b/apps/dav/lib/server.php @@ -132,8 +132,15 @@ class Server { $user = \OC::$server->getUserSession()->getUser(); if (!is_null($user)) { $view = \OC\Files\Filesystem::getView(); - $this->server->addPlugin(new FilesPlugin($this->server->tree, $view, false, - !\OC::$server->getConfig()->getSystemValue('debug', false))); + $this->server->addPlugin( + new FilesPlugin( + $this->server->tree, + $view, + \OC::$server->getConfig(), + false, + !\OC::$server->getConfig()->getSystemValue('debug', false) + ) + ); $this->server->addPlugin( new \Sabre\DAV\PropertyStorage\Plugin( diff --git a/apps/dav/tests/unit/connector/sabre/filesplugin.php b/apps/dav/tests/unit/connector/sabre/filesplugin.php index 63ee5a53c1..fb5d658b39 100644 --- a/apps/dav/tests/unit/connector/sabre/filesplugin.php +++ b/apps/dav/tests/unit/connector/sabre/filesplugin.php @@ -43,6 +43,7 @@ class FilesPlugin extends TestCase { const DOWNLOADURL_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::DOWNLOADURL_PROPERTYNAME; const OWNER_ID_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::OWNER_ID_PROPERTYNAME; const OWNER_DISPLAY_NAME_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME; + const DATA_FINGERPRINT_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME; /** * @var \Sabre\DAV\Server | \PHPUnit_Framework_MockObject_MockObject @@ -64,6 +65,11 @@ class FilesPlugin extends TestCase { */ private $view; + /** + * @var \OCP\IConfig | \PHPUnit_Framework_MockObject_MockObject + */ + private $config; + public function setUp() { parent::setUp(); $this->server = $this->getMockBuilder('\Sabre\DAV\Server') @@ -75,8 +81,16 @@ class FilesPlugin extends TestCase { $this->view = $this->getMockBuilder('\OC\Files\View') ->disableOriginalConstructor() ->getMock(); + $this->config = $this->getMock('\OCP\IConfig'); + $this->config->method('getSystemValue') + ->with($this->equalTo('data-fingerprint'), $this->equalTo('')) + ->willReturn('my_fingerprint'); - $this->plugin = new \OCA\DAV\Connector\Sabre\FilesPlugin($this->tree, $this->view); + $this->plugin = new \OCA\DAV\Connector\Sabre\FilesPlugin( + $this->tree, + $this->view, + $this->config + ); $this->plugin->initialize($this->server); } @@ -128,7 +142,8 @@ class FilesPlugin extends TestCase { self::PERMISSIONS_PROPERTYNAME, self::DOWNLOADURL_PROPERTYNAME, self::OWNER_ID_PROPERTYNAME, - self::OWNER_DISPLAY_NAME_PROPERTYNAME + self::OWNER_DISPLAY_NAME_PROPERTYNAME, + self::DATA_FINGERPRINT_PROPERTYNAME, ), 0 ); @@ -166,7 +181,7 @@ class FilesPlugin extends TestCase { $this->assertEquals('http://example.com/', $propFind->get(self::DOWNLOADURL_PROPERTYNAME)); $this->assertEquals('foo', $propFind->get(self::OWNER_ID_PROPERTYNAME)); $this->assertEquals('M. Foo', $propFind->get(self::OWNER_DISPLAY_NAME_PROPERTYNAME)); - $this->assertEquals(array(self::SIZE_PROPERTYNAME), $propFind->get404Properties()); + $this->assertEquals([self::SIZE_PROPERTYNAME, self::DATA_FINGERPRINT_PROPERTYNAME], $propFind->get404Properties()); } public function testGetPropertiesForFileHome() { @@ -185,7 +200,8 @@ class FilesPlugin extends TestCase { self::PERMISSIONS_PROPERTYNAME, self::DOWNLOADURL_PROPERTYNAME, self::OWNER_ID_PROPERTYNAME, - self::OWNER_DISPLAY_NAME_PROPERTYNAME + self::OWNER_DISPLAY_NAME_PROPERTYNAME, + self::DATA_FINGERPRINT_PROPERTYNAME, ), 0 ); @@ -217,6 +233,7 @@ class FilesPlugin extends TestCase { '{http://owncloud.org/ns}owner-id', '{http://owncloud.org/ns}owner-display-name' ], $propFind->get404Properties()); + $this->assertEquals('my_fingerprint', $propFind->get(self::DATA_FINGERPRINT_PROPERTYNAME)); } public function testGetPropertiesStorageNotAvailable() { @@ -244,7 +261,11 @@ class FilesPlugin extends TestCase { } public function testGetPublicPermissions() { - $this->plugin = new \OCA\DAV\Connector\Sabre\FilesPlugin($this->tree, $this->view, true); + $this->plugin = new \OCA\DAV\Connector\Sabre\FilesPlugin( + $this->tree, + $this->view, + $this->config, + true); $this->plugin->initialize($this->server); $propFind = new PropFind( @@ -281,6 +302,7 @@ class FilesPlugin extends TestCase { self::SIZE_PROPERTYNAME, self::PERMISSIONS_PROPERTYNAME, self::DOWNLOADURL_PROPERTYNAME, + self::DATA_FINGERPRINT_PROPERTYNAME, ), 0 ); @@ -299,7 +321,30 @@ class FilesPlugin extends TestCase { $this->assertEquals(1025, $propFind->get(self::SIZE_PROPERTYNAME)); $this->assertEquals('DWCKMSR', $propFind->get(self::PERMISSIONS_PROPERTYNAME)); $this->assertEquals(null, $propFind->get(self::DOWNLOADURL_PROPERTYNAME)); - $this->assertEquals(array(self::DOWNLOADURL_PROPERTYNAME), $propFind->get404Properties()); + $this->assertEquals([self::DOWNLOADURL_PROPERTYNAME, self::DATA_FINGERPRINT_PROPERTYNAME], $propFind->get404Properties()); + } + + public function testGetPropertiesForRootDirectory() { + /** @var \OCA\DAV\Connector\Sabre\Directory | \PHPUnit_Framework_MockObject_MockObject $node */ + $node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory') + ->disableOriginalConstructor() + ->getMock(); + $node->method('getPath')->willReturn('/'); + + $propFind = new PropFind( + '/', + [ + self::DATA_FINGERPRINT_PROPERTYNAME, + ], + 0 + ); + + $this->plugin->handleGetProperties( + $propFind, + $node + ); + + $this->assertEquals('my_fingerprint', $propFind->get(self::DATA_FINGERPRINT_PROPERTYNAME)); } public function testUpdateProps() { diff --git a/apps/dav/tests/unit/connector/sabre/filesreportplugin.php b/apps/dav/tests/unit/connector/sabre/filesreportplugin.php index 87973ef007..ffe1a19ee5 100644 --- a/apps/dav/tests/unit/connector/sabre/filesreportplugin.php +++ b/apps/dav/tests/unit/connector/sabre/filesreportplugin.php @@ -336,7 +336,15 @@ class FilesReportPlugin extends \Test\TestCase { ->method('getSize') ->will($this->returnValue(1024)); - $this->server->addPlugin(new \OCA\DAV\Connector\Sabre\FilesPlugin($this->tree, $this->view)); + $config = $this->getMock('\OCP\IConfig'); + + $this->server->addPlugin( + new \OCA\DAV\Connector\Sabre\FilesPlugin( + $this->tree, + $this->view, + $config + ) + ); $this->plugin->initialize($this->server); $responses = $this->plugin->prepareResponses($requestedProps, [$node1, $node2]); diff --git a/config/config.sample.php b/config/config.sample.php index b54bbf6a7b..db662cfd74 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -1209,6 +1209,19 @@ $CONFIG = array( */ 'debug' => false, +/** + * Sets the data-fingerprint of the current data served + * + * This is a property used by the clients to find out if a backup has been + * restored on the server. Once a backup is restored run + * ./occ maintenance:data-fingerprint + * To set this to a new value. + * + * Updating/Deleting this value can make connected clients stall until + * the user has resolved conflicts. + */ +'data-fingerprint' => '', + /** * This entry is just here to show a warning in case somebody copied the sample * configuration. DO NOT ADD THIS SWITCH TO YOUR CONFIGURATION! diff --git a/core/Command/Maintenance/DataFingerprint.php b/core/Command/Maintenance/DataFingerprint.php new file mode 100644 index 0000000000..38f490fd12 --- /dev/null +++ b/core/Command/Maintenance/DataFingerprint.php @@ -0,0 +1,53 @@ + + * + * @copyright Copyright (c) 2016, 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 + * + */ +namespace OC\Core\Command\Maintenance; + +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IConfig; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + + +class DataFingerprint extends Command { + + /** @var IConfig */ + protected $config; + /** @var ITimeFactory */ + protected $timeFactory; + + public function __construct(IConfig $config, + ITimeFactory $timeFactory) { + $this->config = $config; + $this->timeFactory = $timeFactory; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('maintenance:data-fingerprint') + ->setDescription('update the systems data-fingerprint after a backup is restored'); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $this->config->setSystemValue('data-fingerprint', md5($this->timeFactory->getTime())); + } +} diff --git a/core/register_command.php b/core/register_command.php index 17bd573133..90a54233e6 100644 --- a/core/register_command.php +++ b/core/register_command.php @@ -108,6 +108,7 @@ if (\OC::$server->getConfig()->getSystemValue('installed', false)) { ); $application->add(new OC\Core\Command\Encryption\ShowKeyStorageRoot($util)); + $application->add(new OC\Core\Command\Maintenance\DataFingerprint(\OC::$server->getConfig(), new \OC\AppFramework\Utility\TimeFactory())); $application->add(new OC\Core\Command\Maintenance\Mimetype\UpdateDB(\OC::$server->getMimeTypeDetector(), \OC::$server->getMimeTypeLoader())); $application->add(new OC\Core\Command\Maintenance\Mimetype\UpdateJS(\OC::$server->getMimeTypeDetector())); $application->add(new OC\Core\Command\Maintenance\Mode(\OC::$server->getConfig())); diff --git a/tests/core/command/maintenance/datafingerprinttest.php b/tests/core/command/maintenance/datafingerprinttest.php new file mode 100644 index 0000000000..4d661b5c02 --- /dev/null +++ b/tests/core/command/maintenance/datafingerprinttest.php @@ -0,0 +1,64 @@ + + * + * @copyright Copyright (c) 2016, 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 + * + */ + +namespace Tests\Core\Command\Maintenance; + +use OC\Core\Command\Maintenance\DataFingerprint; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IConfig; +use Test\TestCase; + +class DataFingerprintTest extends TestCase { + /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */ + protected $config; + /** @var \PHPUnit_Framework_MockObject_MockObject */ + protected $consoleInput; + /** @var \PHPUnit_Framework_MockObject_MockObject */ + protected $consoleOutput; + /** @var ITimeFactory|\PHPUnit_Framework_MockObject_MockObject */ + protected $timeFactory; + + /** @var \Symfony\Component\Console\Command\Command */ + protected $command; + + protected function setUp() { + parent::setUp(); + + $this->config = $this->getMock('OCP\IConfig'); + $this->timeFactory = $this->getMock('OCP\AppFramework\Utility\ITimeFactory'); + $this->consoleInput = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $this->consoleOutput = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + + /** @var \OCP\IConfig $config */ + $this->command = new DataFingerprint($this->config, $this->timeFactory); + } + + public function testSetFingerPrint() { + $this->timeFactory->expects($this->once()) + ->method('getTime') + ->willReturn(42); + $this->config->expects($this->once()) + ->method('setSystemValue') + ->with('data-fingerprint', md5(42)); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } +}