Decouple federation and dav app

This commit is contained in:
Thomas Müller 2015-12-04 13:38:32 +01:00
parent dbddbb634b
commit 90d9834c8c
10 changed files with 271 additions and 26 deletions

View file

@ -19,6 +19,21 @@
* *
*/ */
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 = \OC::$server->getContactsManager();
$cm->register(function() use ($cm) { $cm->register(function() use ($cm) {
$userId = \OC::$server->getUserSession()->getUser()->getUID(); $userId = \OC::$server->getUserSession()->getUser()->getUID();

View file

@ -37,9 +37,6 @@ class CardDavBackend implements BackendInterface, SyncSupport {
/** @var Principal */ /** @var Principal */
private $principalBackend; private $principalBackend;
/** @var ILogger */
private $logger;
/** @var string */ /** @var string */
private $dbCardsTable = 'cards'; private $dbCardsTable = 'cards';
@ -59,12 +56,10 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* *
* @param IDBConnection $db * @param IDBConnection $db
* @param Principal $principalBackend * @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->db = $db;
$this->principalBackend = $principalBackend; $this->principalBackend = $principalBackend;
$this->logger = $logger;
} }
/** /**

View file

@ -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()];
}
}

View file

@ -46,11 +46,11 @@ class RootCollection extends SimpleCollection {
\OC::$server->getSystemTagObjectMapper() \OC::$server->getSystemTagObjectMapper()
); );
$usersCardDavBackend = new CardDavBackend($db, $userPrincipalBackend, \OC::$server->getLogger()); $usersCardDavBackend = new CardDavBackend($db, $userPrincipalBackend);
$usersAddressBookRoot = new AddressBookRoot($userPrincipalBackend, $usersCardDavBackend, 'principals/users'); $usersAddressBookRoot = new AddressBookRoot($userPrincipalBackend, $usersCardDavBackend, 'principals/users');
$usersAddressBookRoot->disableListing = $disableListing; $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 = new AddressBookRoot(new SystemPrincipalBackend(), $systemCardDavBackend, 'principals/system');
$systemAddressBookRoot->disableListing = $disableListing; $systemAddressBookRoot->disableListing = $disableListing;

View file

@ -45,9 +45,6 @@ class CardDavBackendTest extends TestCase {
/** @var Principal | \PHPUnit_Framework_MockObject_MockObject */ /** @var Principal | \PHPUnit_Framework_MockObject_MockObject */
private $principal; private $principal;
/** @var ILogger | \PHPUnit_Framework_MockObject_MockObject */
private $logger;
/** @var IDBConnection */ /** @var IDBConnection */
private $db; private $db;
@ -70,11 +67,10 @@ class CardDavBackendTest extends TestCase {
->willReturn([ ->willReturn([
'uri' => 'principals/best-friend' 'uri' => 'principals/best-friend'
]); ]);
$this->logger = $this->getMock('\OCP\ILogger');
$this->db = \OC::$server->getDatabaseConnection(); $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 // start every test with a empty cards_properties and cards table
$query = $this->db->getQueryBuilder(); $query = $this->db->getQueryBuilder();
@ -129,7 +125,7 @@ class CardDavBackendTest extends TestCase {
/** @var CardDavBackend | \PHPUnit_Framework_MockObject_MockObject $backend */ /** @var CardDavBackend | \PHPUnit_Framework_MockObject_MockObject $backend */
$backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend') $backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')
->setConstructorArgs([$this->db, $this->principal, $this->logger]) ->setConstructorArgs([$this->db, $this->principal])
->setMethods(['updateProperties', 'purgeProperties'])->getMock(); ->setMethods(['updateProperties', 'purgeProperties'])->getMock();
// create a new address book // create a new address book
@ -175,7 +171,7 @@ class CardDavBackendTest extends TestCase {
public function testMultiCard() { public function testMultiCard() {
$this->backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend') $this->backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')
->setConstructorArgs([$this->db, $this->principal, $this->logger]) ->setConstructorArgs([$this->db, $this->principal])
->setMethods(['updateProperties'])->getMock(); ->setMethods(['updateProperties'])->getMock();
// create a new address book // create a new address book
@ -222,7 +218,7 @@ class CardDavBackendTest extends TestCase {
public function testSyncSupport() { public function testSyncSupport() {
$this->backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend') $this->backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')
->setConstructorArgs([$this->db, $this->principal, $this->logger]) ->setConstructorArgs([$this->db, $this->principal])
->setMethods(['updateProperties'])->getMock(); ->setMethods(['updateProperties'])->getMock();
// create a new address book // create a new address book
@ -279,7 +275,7 @@ class CardDavBackendTest extends TestCase {
$cardId = 2; $cardId = 2;
$backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend') $backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')
->setConstructorArgs([$this->db, $this->principal, $this->logger]) ->setConstructorArgs([$this->db, $this->principal])
->setMethods(['getCardId'])->getMock(); ->setMethods(['getCardId'])->getMock();
$backend->expects($this->any())->method('getCardId')->willReturn($cardId); $backend->expects($this->any())->method('getCardId')->willReturn($cardId);

View file

@ -52,9 +52,8 @@
</field> </field>
<field> <field>
<name>sync_token</name> <name>sync_token</name>
<type>integer</type> <type>text</type>
<notnull>true</notnull> <length>512</length>
<default>0</default>
<comments>cardDav sync token</comments> <comments>cardDav sync token</comments>
</field> </field>
<index> <index>

View 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> <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> <licence>AGPL</licence>
<author>Bjoern Schiessle</author> <author>Bjoern Schiessle</author>
<version>0.0.1</version> <version>0.0.2</version>
<namespace>Federation</namespace> <namespace>Federation</namespace>
<category>other</category> <category>other</category>
<dependencies> <dependencies>

View file

@ -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));

View file

@ -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('');
}
}

View file

@ -207,14 +207,15 @@ class DbHandler {
* @param string $url * @param string $url
* @param int $status * @param int $status
*/ */
public function setServerStatus($url, $status) { public function setServerStatus($url, $status, $token = null) {
$hash = $this->hash($url); $hash = $this->hash($url);
$query = $this->connection->getQueryBuilder(); $query = $this->connection->getQueryBuilder();
$query->update($this->dbTable) $query->update($this->dbTable)
->set('status', $query->createParameter('status')) ->set('status', $query->createNamedParameter($status))
->where($query->expr()->eq('url_hash', $query->createParameter('url_hash'))) ->where($query->expr()->eq('url_hash', $query->createNamedParameter($hash)));
->setParameter('url_hash', $hash) if (!is_null($token)) {
->setParameter('status', $status); $query->set('sync_token', $query->createNamedParameter($token));
}
$query->execute(); $query->execute();
} }