*
*/
+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();
/** @var Principal */
private $principalBackend;
- /** @var ILogger */
- private $logger;
-
/** @var string */
private $dbCardsTable = 'cards';
*
* @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;
}
/**
--- /dev/null
+<?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()];
+ }
+
+
+}
\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;
/** @var Principal | \PHPUnit_Framework_MockObject_MockObject */
private $principal;
- /** @var ILogger | \PHPUnit_Framework_MockObject_MockObject */
- private $logger;
-
/** @var IDBConnection */
private $db;
->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();
/** @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
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
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
$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);
</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>
<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>
--- /dev/null
+<?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));
--- /dev/null
+<?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('');
+ }
+}
* @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();
}