]> source.dussan.org Git - nextcloud-server.git/commitdiff
Decouple federation and dav app
authorThomas Müller <thomas.mueller@tmit.eu>
Fri, 4 Dec 2015 12:38:32 +0000 (13:38 +0100)
committerThomas Müller <thomas.mueller@tmit.eu>
Tue, 12 Jan 2016 13:24:47 +0000 (14:24 +0100)
apps/dav/appinfo/app.php
apps/dav/lib/carddav/carddavbackend.php
apps/dav/lib/carddav/syncservice.php [new file with mode: 0644]
apps/dav/lib/rootcollection.php
apps/dav/tests/unit/carddav/carddavbackendtest.php
apps/federation/appinfo/database.xml
apps/federation/appinfo/info.xml
apps/federation/appinfo/register_command.php [new file with mode: 0644]
apps/federation/command/syncfederationaddressbooks.php [new file with mode: 0644]
apps/federation/lib/dbhandler.php

index bc889176f7f574da1d504eb6aabbe49986608016..d09003e19c0ea4f848dd047fd1454ca0b67e6aa1 100644 (file)
  *
  */
 
+use OCA\DAV\CardDAV\CardDavBackend;
+use OCA\DAV\CardDAV\SyncService;
+use OCA\DAV\Connector\Sabre\Principal;
+
+\OC::$server->registerService('CardDAVSyncService', function() {
+       $principalBackend = new Principal(
+               $this->config,
+               $this->userManager
+       );
+
+       $backend = new CardDavBackend($this->dbConnection, $principalBackend);
+
+       return new SyncService($backend);
+});
+
 $cm = \OC::$server->getContactsManager();
 $cm->register(function() use ($cm) {
        $userId = \OC::$server->getUserSession()->getUser()->getUID();
index 95175b20d1b790b7c36c8852ab4726d50da43d15..cdb3481eaf5c2ea79b287c228ecbfd0bc976849d 100644 (file)
@@ -37,9 +37,6 @@ class CardDavBackend implements BackendInterface, SyncSupport {
        /** @var Principal */
        private $principalBackend;
 
-       /** @var ILogger */
-       private $logger;
-
        /** @var string */
        private $dbCardsTable = 'cards';
 
@@ -59,12 +56,10 @@ class CardDavBackend implements BackendInterface, SyncSupport {
         *
         * @param IDBConnection $db
         * @param Principal $principalBackend
-        * @param ILogger $logger
         */
-       public function __construct(IDBConnection $db, Principal $principalBackend, ILogger $logger) {
+       public function __construct(IDBConnection $db, Principal $principalBackend) {
                $this->db = $db;
                $this->principalBackend = $principalBackend;
-               $this->logger = $logger;
        }
 
        /**
diff --git a/apps/dav/lib/carddav/syncservice.php b/apps/dav/lib/carddav/syncservice.php
new file mode 100644 (file)
index 0000000..8442f7c
--- /dev/null
@@ -0,0 +1,155 @@
+<?php
+/**
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2015, 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\CardDAV;
+
+use Sabre\DAV\Client;
+use Sabre\DAV\Xml\Response\MultiStatus;
+use Sabre\DAV\Xml\Service;
+
+class SyncService {
+
+       /** @var CardDavBackend */
+       private $backend;
+
+       public function __construct(CardDavBackend $backend) {
+               $this->backend = $backend;
+       }
+
+       public function syncRemoteAddressBook($url, $userName, $sharedSecret, $syncToken, $targetBookId, $targetPrincipal, $targetProperties) {
+               // 1. create addressbook
+               $book = $this->ensureSystemAddressBookExists($targetPrincipal, $targetBookId, $targetProperties);
+               $addressBookId = $book['id'];
+
+               // 2. query changes
+               $response = $this->requestSyncReport($url, $userName, $sharedSecret, $syncToken);
+
+               // 3. apply changes
+               // TODO: use multi-get for download
+               foreach ($response['response'] as $resource => $status) {
+                       $cardUri = basename($resource);
+                       if (isset($status[200])) {
+                               $vCard = $this->download($url, $sharedSecret, $resource);
+                               $existingCard = $this->backend->getCard($addressBookId, $cardUri);
+                               if ($existingCard === false) {
+                                       $this->backend->createCard($addressBookId, $cardUri, $vCard['body']);
+                               } else {
+                                       $this->backend->updateCard($addressBookId, $cardUri, $vCard['body']);
+                               }
+                       } else {
+                               $this->backend->deleteCard($addressBookId, $cardUri);
+                       }
+               }
+
+               return $response['token'];
+       }
+
+       protected function ensureSystemAddressBookExists($principal, $id, $properties) {
+               $book = $this->backend->getAddressBooksByUri($id);
+               if (!is_null($book)) {
+                       return $book;
+               }
+               $this->backend->createAddressBook($principal, $id, $properties);
+
+               return $this->backend->getAddressBooksByUri($id);
+       }
+
+       /**
+        * @param string $url
+        * @param string $userName
+        * @param string $sharedSecret
+        * @param string $syncToken
+        * @return array
+        */
+       private function requestSyncReport($url, $userName, $sharedSecret, $syncToken) {
+               $settings = [
+                       'baseUri' => $url,
+                       'userName' => $userName,
+                       'password' => $sharedSecret,
+               ];
+               $client = new Client($settings);
+               $client->setThrowExceptions(true);
+
+               $addressBookUrl = "/remote.php/dav/addressbooks/system/system/system";
+               $body = $this->buildSyncCollectionRequestBody($syncToken);
+
+               $response = $client->request('REPORT', $addressBookUrl, $body, [
+                       'Content-Type' => 'application/xml'
+               ]);
+
+//             if ((int)$response->getStatus() >= 400) {
+//                     throw new Exception('HTTP error: ' . $response->getStatus());
+//             }
+
+               $result = $this->parseMultiStatus($response['body']);
+
+               return $result;
+       }
+
+       private function download($url, $sharedSecret, $changeSet) {
+               $settings = [
+                       'baseUri' => $url,
+                       'userName' => 'system',
+                       'password' => $sharedSecret,
+               ];
+               $client = new Client($settings);
+               $client->setThrowExceptions(true);
+
+               $response = $client->request('GET', $changeSet);
+               return $response;
+       }
+
+       function buildSyncCollectionRequestBody($synToken) {
+
+               $dom = new \DOMDocument('1.0', 'UTF-8');
+               $dom->formatOutput = true;
+               $root = $dom->createElementNS('DAV:', 'd:sync-collection');
+               $sync = $dom->createElement('d:sync-token', $synToken);
+               $prop = $dom->createElement('d:prop');
+               $cont = $dom->createElement('d:getcontenttype');
+               $etag = $dom->createElement('d:getetag');
+
+               $prop->appendChild($cont);
+               $prop->appendChild($etag);
+               $root->appendChild($sync);
+               $root->appendChild($prop);
+               $dom->appendChild($root);
+               $body = $dom->saveXML();
+
+               return $body;
+       }
+
+       private function parseMultiStatus($body) {
+               $xml = new Service();
+
+               /** @var MultiStatus $multiStatus */
+               $multiStatus = $xml->expect('{DAV:}multistatus', $body);
+
+               $result = [];
+               foreach ($multiStatus->getResponses() as $response) {
+                       $result[$response->getHref()] = $response->getResponseProperties();
+               }
+
+               return ['response' => $result, 'token' => $multiStatus->getSyncToken()];
+       }
+
+
+}
index 2261712200a95ce6a214bb60ddebdf821b0d0d77..053ff47f97721dccf301a281502da9bd0cbccc97 100644 (file)
@@ -46,11 +46,11 @@ class RootCollection extends SimpleCollection {
                        \OC::$server->getSystemTagObjectMapper()
                );
 
-               $usersCardDavBackend = new CardDavBackend($db, $userPrincipalBackend, \OC::$server->getLogger());
+               $usersCardDavBackend = new CardDavBackend($db, $userPrincipalBackend);
                $usersAddressBookRoot = new AddressBookRoot($userPrincipalBackend, $usersCardDavBackend, 'principals/users');
                $usersAddressBookRoot->disableListing = $disableListing;
 
-               $systemCardDavBackend = new CardDavBackend($db, $userPrincipalBackend, \OC::$server->getLogger());
+               $systemCardDavBackend = new CardDavBackend($db, $userPrincipalBackend);
                $systemAddressBookRoot = new AddressBookRoot(new SystemPrincipalBackend(), $systemCardDavBackend, 'principals/system');
                $systemAddressBookRoot->disableListing = $disableListing;
 
index fe01aa65cca9d5dae0bd8e2326ee3711ce3267ab..2f5e9eeded6b9d53e518c1c2ad740a450ee2ebdf 100644 (file)
@@ -45,9 +45,6 @@ class CardDavBackendTest extends TestCase {
        /** @var  Principal | \PHPUnit_Framework_MockObject_MockObject */
        private $principal;
 
-       /** @var  ILogger | \PHPUnit_Framework_MockObject_MockObject */
-       private $logger;
-
        /** @var  IDBConnection */
        private $db;
 
@@ -70,11 +67,10 @@ class CardDavBackendTest extends TestCase {
                        ->willReturn([
                                'uri' => 'principals/best-friend'
                        ]);
-               $this->logger = $this->getMock('\OCP\ILogger');
 
                $this->db = \OC::$server->getDatabaseConnection();
 
-               $this->backend = new CardDavBackend($this->db, $this->principal, $this->logger);
+               $this->backend = new CardDavBackend($this->db, $this->principal);
 
                // start every test with a empty cards_properties and cards table
                $query = $this->db->getQueryBuilder();
@@ -129,7 +125,7 @@ class CardDavBackendTest extends TestCase {
 
                /** @var CardDavBackend | \PHPUnit_Framework_MockObject_MockObject $backend */
                $backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')
-                               ->setConstructorArgs([$this->db, $this->principal, $this->logger])
+                               ->setConstructorArgs([$this->db, $this->principal])
                                ->setMethods(['updateProperties', 'purgeProperties'])->getMock();
 
                // create a new address book
@@ -175,7 +171,7 @@ class CardDavBackendTest extends TestCase {
        public function testMultiCard() {
 
                $this->backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')
-                       ->setConstructorArgs([$this->db, $this->principal, $this->logger])
+                       ->setConstructorArgs([$this->db, $this->principal])
                        ->setMethods(['updateProperties'])->getMock();
 
                // create a new address book
@@ -222,7 +218,7 @@ class CardDavBackendTest extends TestCase {
        public function testSyncSupport() {
 
                $this->backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')
-                       ->setConstructorArgs([$this->db, $this->principal, $this->logger])
+                       ->setConstructorArgs([$this->db, $this->principal])
                        ->setMethods(['updateProperties'])->getMock();
 
                // create a new address book
@@ -279,7 +275,7 @@ class CardDavBackendTest extends TestCase {
                $cardId = 2;
 
                $backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')
-                       ->setConstructorArgs([$this->db, $this->principal, $this->logger])
+                       ->setConstructorArgs([$this->db, $this->principal])
                        ->setMethods(['getCardId'])->getMock();
 
                $backend->expects($this->any())->method('getCardId')->willReturn($cardId);
index e6728df1408bd5e0303c46864c73ba7b26d8b949..05b7fb12b4946388a6defda9c1629d4c43a8151b 100644 (file)
@@ -52,9 +52,8 @@
                        </field>
                        <field>
                                <name>sync_token</name>
-                               <type>integer</type>
-                               <notnull>true</notnull>
-                               <default>0</default>
+                               <type>text</type>
+                               <length>512</length>
                                <comments>cardDav sync token</comments>
                        </field>
                        <index>
index 92afc99536617d37e14bf2872c520337044765c0..bfb4ca8f8c9734338c0df6c5be8105c1dae8c662 100644 (file)
@@ -5,7 +5,7 @@
     <description>ownCloud Federation allows you to connect with other trusted ownClouds to exchange the user directory. For example this will be used to auto-complete external users for federated sharing.</description>
     <licence>AGPL</licence>
     <author>Bjoern Schiessle</author>
-    <version>0.0.1</version>
+    <version>0.0.2</version>
     <namespace>Federation</namespace>
     <category>other</category>
        <dependencies>
diff --git a/apps/federation/appinfo/register_command.php b/apps/federation/appinfo/register_command.php
new file mode 100644 (file)
index 0000000..8509a97
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+$config = \OC::$server->getConfig();
+$dbConnection = \OC::$server->getDatabaseConnection();
+$userManager = OC::$server->getUserManager();
+$config = \OC::$server->getConfig();
+
+/** @var Symfony\Component\Console\Application $application */
+$application->add(new \OCA\Federation\Command\SyncFederationAddressBooks($userManager, $dbConnection, $config));
diff --git a/apps/federation/command/syncfederationaddressbooks.php b/apps/federation/command/syncfederationaddressbooks.php
new file mode 100644 (file)
index 0000000..497eba6
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+
+namespace OCA\Federation\Command;
+
+use OCA\DAV\CardDAV\SyncService;
+use OCA\Federation\DbHandler;
+use OCA\Federation\TrustedServers;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\IUserManager;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\ProgressBar;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class SyncFederationAddressBooks extends Command {
+
+       /** @var \OCP\IDBConnection */
+       protected $dbConnection;
+
+       /** @var SyncService */
+       private $syncService;
+
+       /**
+        * @param IUserManager $userManager
+        * @param IDBConnection $dbConnection
+        * @param IConfig $config
+        */
+       function __construct(IDBConnection $dbConnection) {
+               parent::__construct();
+
+               $this->syncService = \OC::$server->query('CardDAVSyncService');
+               $this->dbConnection = $dbConnection;
+       }
+
+       protected function configure() {
+               $this
+                       ->setName('federation:sync-addressbooks')
+                       ->setDescription('Synchronizes addressbooks of all federated clouds');
+       }
+
+       /**
+        * @param InputInterface $input
+        * @param OutputInterface $output
+        */
+       protected function execute(InputInterface $input, OutputInterface $output) {
+
+               $progress = new ProgressBar($output);
+               $progress->start();
+               $db = new DbHandler($this->dbConnection, null);
+               $trustedServers = $db->getAllServer();
+               foreach ($trustedServers as $trustedServer) {
+                       $progress->advance();
+                       $url = $trustedServer['url'];
+                       $sharedSecret = $trustedServer['shared_secret'];
+                       $syncToken = $trustedServer['sync_token'];
+
+                       if (is_null($sharedSecret)) {
+                               continue;
+                       }
+                       $targetBookId = md5($url);
+                       $targetPrincipal = "principals/system/system";
+                       $targetBookProperties = [
+                                       '{DAV:}displayname' => $url
+                       ];
+                       $newToken = $this->syncService->syncRemoteAddressBook($url, 'system', $sharedSecret, $syncToken, $targetPrincipal, $targetBookId, $targetBookProperties);
+                       if ($newToken !== $syncToken) {
+                               $db->setServerStatus($url, TrustedServers::STATUS_OK, $newToken);
+                       }
+               }
+               $progress->finish();
+               $output->writeln('');
+       }
+}
index f86a8e15d808d46634d606073163bfeafe020bc8..0677430f799aee6bc0b97554ca724ebcd08f3698 100644 (file)
@@ -207,14 +207,15 @@ class DbHandler {
         * @param string $url
         * @param int $status
         */
-       public function setServerStatus($url, $status) {
+       public function setServerStatus($url, $status, $token = null) {
                $hash = $this->hash($url);
                $query = $this->connection->getQueryBuilder();
                $query->update($this->dbTable)
-                               ->set('status', $query->createParameter('status'))
-                               ->where($query->expr()->eq('url_hash', $query->createParameter('url_hash')))
-                               ->setParameter('url_hash', $hash)
-                               ->setParameter('status', $status);
+                               ->set('status', $query->createNamedParameter($status))
+                               ->where($query->expr()->eq('url_hash', $query->createNamedParameter($hash)));
+               if (!is_null($token)) {
+                       $query->set('sync_token', $query->createNamedParameter($token));
+               }
                $query->execute();
        }