Merge branch 'master' into issue_20427

remotes/origin/fix_emit_scanFiles
Jesus Macias Portela 9 years ago
commit dcfbbe4737

1
.gitignore vendored

@ -12,6 +12,7 @@
!/apps/dav
!/apps/files
!/apps/files_encryption
!/apps/federation
!/apps/encryption
!/apps/encryption_dummy
!/apps/files_external

@ -38,11 +38,28 @@
RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteRule ^\.well-known/host-meta /public.php?service=host-meta [QSA,L]
RewriteRule ^\.well-known/host-meta\.json /public.php?service=host-meta-json [QSA,L]
RewriteRule ^\.well-known/carddav /remote.php/carddav/ [R=301,L]
RewriteRule ^\.well-known/caldav /remote.php/caldav/ [R=301,L]
RewriteRule ^\.well-known/carddav /remote.php/dav/ [R=301,L]
RewriteRule ^\.well-known/caldav /remote.php/dav/ [R=301,L]
RewriteRule ^remote/(.*) remote.php [QSA,L]
RewriteRule ^(build|tests|config|lib|3rdparty|templates)/.* - [R=404,L]
RewriteRule ^(\.|autotest|occ|issue|indie|db_|console).* - [R=404,L]
# Rewrite rules for `front_controller_active`
Options -MultiViews
<IfModule mod_dir.c>
DirectorySlash off
</IfModule>
RewriteRule ^core/js/oc.js$ index.php/core/js/oc.js [PT,E=PATH_INFO:$1]
RewriteRule ^core/preview.png$ index.php/core/preview.png [PT,E=PATH_INFO:$1]
RewriteCond %{REQUEST_FILENAME} !\.(css|js|svg|gif|png|html|ttf|woff)$
RewriteCond %{REQUEST_FILENAME} !/remote.php
RewriteCond %{REQUEST_FILENAME} !/public.php
RewriteCond %{REQUEST_FILENAME} !/cron.php
RewriteCond %{REQUEST_FILENAME} !/core/ajax/update.php
RewriteCond %{REQUEST_FILENAME} !/status.php
RewriteCond %{REQUEST_FILENAME} !/ocs/v1.php
RewriteCond %{REQUEST_FILENAME} !/ocs/v2.php
RewriteRule .* index.php [PT,E=PATH_INFO:$1]
</IfModule>
<IfModule mod_mime.c>
AddType image/svg+xml svg svgz

@ -37,7 +37,9 @@ matrix:
- php: 5.4
env: DB=pgsql;TC=litmus-v1
- php: 5.4
env: DB=pgsql;TC=carddavtester
env: DB=sqlite;TC=carddavtester
# - php: 5.4
# env: DB=pgsql;TC=carddavtester
# - php: 5.4
# env: DB=mysql;TC=caldavtester

@ -1 +1 @@
Subproject commit be700d4918627e06eb3e8c5f3b025911061badff
Subproject commit a7b34d6f831c8fa363f389d27acd0150128fc0b9

@ -1,35 +1,39 @@
# ownCloud
# ownCloud Core
[ownCloud](http://ownCloud.org) gives you freedom and control over your own data.
A personal cloud which runs on your own server.
### Build Status on [Jenkins CI](https://ci.owncloud.org/)
Git master: [![Build Status](https://ci.owncloud.org/job/server-master-linux/badge/icon)](https://ci.owncloud.org/job/server-master-linux/)
Quality:
- Scrutinizer: [![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/owncloud/core/badges/quality-score.png?s=ce2f5ded03d4ac628e9ee5c767243fa7412e644f)](https://scrutinizer-ci.com/g/owncloud/core/)
- CodeClimate: [![Code Climate](https://codeclimate.com/github/owncloud/core/badges/gpa.svg)](https://codeclimate.com/github/owncloud/core)
- Coverity: [![Coverity](https://scan.coverity.com/projects/6893/badge.svg)](https://scan.coverity.com/projects/owncloud-core)
[![Build Status](https://ci.owncloud.org/job/server-master-linux/badge/icon)](https://ci.owncloud.org/job/server-master-linux/)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/owncloud/core/badges/quality-score.png?s=ce2f5ded03d4ac628e9ee5c767243fa7412e644f)](https://scrutinizer-ci.com/g/owncloud/core/)
[![CodeClimate](https://codeclimate.com/github/owncloud/core/badges/gpa.svg)](https://codeclimate.com/github/owncloud/core)
[![Coverity](https://scan.coverity.com/projects/6893/badge.svg)](https://scan.coverity.com/projects/owncloud-core)
[![Dependency Status](https://www.versioneye.com/user/projects/54f4a2384f3108959a000a16/badge.svg?style=flat)](https://www.versioneye.com/user/projects/54f4a2384f3108959a000a16)
[![Dependency Status](https://www.versioneye.com/user/projects/54d1f76f3ca0840b190000c0/badge.svg?style=flat)](https://www.versioneye.com/user/projects/54d1f76f3ca0840b190000c0)
Dependencies:
**[ownCloud](http://ownCloud.org) gives you freedom and control over your own data.
A personal cloud which runs on your own server.**
[![Dependency Status](https://www.versioneye.com/user/projects/54f4a2384f3108959a000a16/badge.svg?style=flat)](https://www.versioneye.com/user/projects/54f4a2384f3108959a000a16)
![](https://github.com/owncloud/screenshots/blob/master/files/sidebar_1.png)
[![Dependency Status](https://www.versioneye.com/user/projects/54d1f76f3ca0840b190000c0/badge.svg?style=flat)](https://www.versioneye.com/user/projects/54d1f76f3ca0840b190000c0)
## Why is this so awesome?
* :file_folder: **Acess your Data** You can store your files, contacts, calendars and more on a server of your choosing.
* :package: **Sync your Data** You keep your files, contacts, calendars and more synchronized amongst your devices.
* :arrows_counterclockwise: **Share your Data** You share your data with others, and give them access to your latest photo galleries, your calendar or anything else you want them to see.
* :rocket: **Expandable with dozens of Apps** ...like Calendar, Contacts, Mail or News.
* :cloud: **All Benefits of the Cloud** ...on your own Server.
* :lock: **Encryption** You can encrypt data in transit with secure https connections. You can enable the encryption app to encrypt data on storage for improved security and privacy.
* ...
### Installation instructions
## Installation instructions
https://doc.owncloud.org/server/9.0/developer_manual/app/index.html
### Contribution Guidelines
## Contribution Guidelines
https://owncloud.org/contribute/
### Get in touch
* [Forum](https://forum.owncloud.org)
* [Mailing list](https://mailman.owncloud.org/mailman/listinfo)
* [IRC channel](https://webchat.freenode.net/?channels=owncloud)
* [Twitter](https://twitter.com/ownClouders)
## Get in touch
* :clipboard: [Forum](https://forum.owncloud.org)
* :envelope: [Mailing list](https://mailman.owncloud.org/mailman/listinfo)
* :busts_in_silhouette: [IRC channel](https://webchat.freenode.net/?channels=owncloud)
* :hatching_chick: [Twitter](https://twitter.com/ownClouders)
### Important notice on translations
## Important notice on translations
Please submit translations via Transifex:
https://www.transifex.com/projects/p/owncloud/

@ -183,4 +183,442 @@ CREATE TABLE addressbookchanges (
</declaration>
</table>
<!--
CREATE TABLE calendarobjects (
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
calendardata MEDIUMBLOB,
uri VARBINARY(200),
calendarid INTEGER UNSIGNED NOT NULL,
lastmodified INT(11) UNSIGNED,
etag VARBINARY(32),
size INT(11) UNSIGNED NOT NULL,
componenttype VARBINARY(8),
firstoccurence INT(11) UNSIGNED,
lastoccurence INT(11) UNSIGNED,
uid VARBINARY(200),
UNIQUE(calendarid, uri)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-->
<table>
<name>*dbprefix*calendarobjects</name>
<declaration>
<field>
<name>id</name>
<type>integer</type>
<default>0</default>
<notnull>true</notnull>
<autoincrement>1</autoincrement>
<unsigned>true</unsigned>
<length>11</length>
</field>
<field>
<name>calendardata</name>
<type>blob</type>
</field>
<field>
<name>uri</name>
<type>text</type>
</field>
<field>
<name>calendarid</name>
<type>integer</type>
<unsigned>true</unsigned>
<notnull>true</notnull>
</field>
<field>
<name>lastmodified</name>
<type>integer</type>
<unsigned>true</unsigned>
</field>
<field>
<name>etag</name>
<type>text</type>
<length>32</length>
</field>
<field>
<name>size</name>
<type>integer</type>
<notnull>true</notnull>
<unsigned>true</unsigned>
<length>11</length>
</field>
<field>
<name>componenttype</name>
<type>text</type>
</field>
<field>
<name>firstoccurence</name>
<type>integer</type>
<unsigned>true</unsigned>
</field>
<field>
<name>lastoccurence</name>
<type>integer</type>
<unsigned>true</unsigned>
</field>
<field>
<name>uid</name>
<type>text</type>
</field>
<index>
<name>calobjects_index</name>
<unique>true</unique>
<field>
<name>calendarid</name>
</field>
<field>
<name>uri</name>
</field>
</index>
</declaration>
</table>
<!--
CREATE TABLE calendars (
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
principaluri VARBINARY(100),
displayname VARCHAR(100),
uri VARBINARY(200),
synctoken INTEGER UNSIGNED NOT NULL DEFAULT '1',
description TEXT,
calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0',
calendarcolor VARBINARY(10),
timezone TEXT,
components VARBINARY(20),
transparent TINYINT(1) NOT NULL DEFAULT '0',
UNIQUE(principaluri, uri)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-->
<table>
<name>*dbprefix*calendars</name>
<declaration>
<field>
<name>id</name>
<type>integer</type>
<default>0</default>
<notnull>true</notnull>
<autoincrement>1</autoincrement>
<unsigned>true</unsigned>
<length>11</length>
</field>
<field>
<name>principaluri</name>
<type>text</type>
</field>
<field>
<name>displayname</name>
<type>text</type>
</field>
<field>
<name>uri</name>
<type>text</type>
</field>
<field>
<name>synctoken</name>
<type>integer</type>
<default>1</default>
<notnull>true</notnull>
<unsigned>true</unsigned>
</field>
<field>
<name>description</name>
<type>text</type>
</field>
<field>
<name>calendarorder</name>
<type>integer</type>
<default>0</default>
<notnull>true</notnull>
<unsigned>true</unsigned>
</field>
<field>
<name>calendarcolor</name>
<type>text</type>
</field>
<field>
<name>timezone</name>
<type>text</type>
</field>
<field>
<name>components</name>
<type>text</type>
</field>
<field>
<name>transparent</name>
<type>integer</type>
<length>1</length>
<notnull>true</notnull>
<default>0</default>
</field>
<index>
<name>calendars_index</name>
<unique>true</unique>
<field>
<name>principaluri</name>
</field>
<field>
<name>uri</name>
</field>
</index>
</declaration>
</table>
<!--
CREATE TABLE calendarchanges (
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
uri VARBINARY(200) NOT NULL,
synctoken INT(11) UNSIGNED NOT NULL,
calendarid INT(11) UNSIGNED NOT NULL,
operation TINYINT(1) NOT NULL,
INDEX calendarid_synctoken (calendarid, synctoken)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-->
<table>
<name>*dbprefix*calendarchanges</name>
<declaration>
<field>
<name>id</name>
<type>integer</type>
<default>0</default>
<notnull>true</notnull>
<autoincrement>1</autoincrement>
<unsigned>true</unsigned>
<length>11</length>
</field>
<field>
<name>uri</name>
<type>text</type>
</field>
<field>
<name>synctoken</name>
<type>integer</type>
<default>1</default>
<notnull>true</notnull>
<unsigned>true</unsigned>
</field>
<field>
<name>calendarid</name>
<type>integer</type>
<notnull>true</notnull>
</field>
<field>
<name>operation</name>
<type>integer</type>
<notnull>true</notnull>
<length>1</length>
</field>
<index>
<name>calendarid_synctoken</name>
<field>
<name>calendarid</name>
</field>
<field>
<name>synctoken</name>
</field>
</index>
</declaration>
</table>
<!--
CREATE TABLE calendarsubscriptions (
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
uri VARBINARY(200) NOT NULL,
principaluri VARBINARY(100) NOT NULL,
source TEXT,
displayname VARCHAR(100),
refreshrate VARCHAR(10),
calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0',
calendarcolor VARBINARY(10),
striptodos TINYINT(1) NULL,
stripalarms TINYINT(1) NULL,
stripattachments TINYINT(1) NULL,
lastmodified INT(11) UNSIGNED,
UNIQUE(principaluri, uri)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-->
<table>
<name>*dbprefix*calendarsubscriptions</name>
<declaration>
<field>
<name>id</name>
<type>integer</type>
<default>0</default>
<notnull>true</notnull>
<autoincrement>1</autoincrement>
<unsigned>true</unsigned>
<length>11</length>
</field>
<field>
<name>uri</name>
<type>text</type>
</field>
<field>
<name>principaluri</name>
<type>text</type>
</field>
<field>
<name>source</name>
<type>text</type>
</field>
<field>
<name>displayname</name>
<type>text</type>
<length>100</length>
</field>
<field>
<name>refreshrate</name>
<type>text</type>
<length>10</length>
</field>
<field>
<name>calendarorder</name>
<type>integer</type>
<default>0</default>
<notnull>true</notnull>
<unsigned>true</unsigned>
</field>
<field>
<name>calendarcolor</name>
<type>text</type>
</field>
<field>
<name>striptodos</name>
<type>integer</type>
<length>1</length>
</field>
<field>
<name>stripalarms</name>
<type>integer</type>
<length>1</length>
</field>
<field>
<name>stripattachments</name>
<type>integer</type>
<length>1</length>
</field>
<field>
<name>lastmodified</name>
<type>integer</type>
<unsigned>true</unsigned>
</field>
<index>
<name>calsub_index</name>
<unique>true</unique>
<field>
<name>principaluri</name>
</field>
<field>
<name>uri</name>
</field>
</index>
</declaration>
</table>
<!--
CREATE TABLE schedulingobjects (
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
principaluri VARBINARY(255),
calendardata MEDIUMBLOB,
uri VARBINARY(200),
lastmodified INT(11) UNSIGNED,
etag VARBINARY(32),
size INT(11) UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-->
<table>
<name>*dbprefix*schedulingobjects</name>
<declaration>
<field>
<name>id</name>
<type>integer</type>
<default>0</default>
<notnull>true</notnull>
<autoincrement>1</autoincrement>
<unsigned>true</unsigned>
<length>11</length>
</field>
<field>
<name>principaluri</name>
<type>text</type>
</field>
<field>
<name>calendardata</name>
<type>blob</type>
</field>
<field>
<name>uri</name>
<type>text</type>
</field>
<field>
<name>lastmodified</name>
<type>integer</type>
<unsigned>true</unsigned>
</field>
<field>
<name>etag</name>
<type>text</type>
<length>32</length>
</field>
<field>
<name>size</name>
<type>integer</type>
<notnull>true</notnull>
<unsigned>true</unsigned>
<length>11</length>
</field>
</declaration>
</table>
<table>
<name>*dbprefix*dav_shares</name>
<declaration>
<field>
<name>id</name>
<type>integer</type>
<default>0</default>
<notnull>true</notnull>
<autoincrement>1</autoincrement>
<unsigned>true</unsigned>
<length>11</length>
</field>
<field>
<name>uri</name>
<type>text</type>
</field>
<field>
<name>principaluri</name>
<type>text</type>
</field>
<field>
<name>type</name>
<type>text</type>
</field>
<field>
<name>access</name>
<type>integer</type>
<length>1</length>
</field>
<field>
<name>resourceid</name>
<type>integer</type>
<notnull>true</notnull>
<unsigned>true</unsigned>
</field>
<index>
<name>dav_shares_index</name>
<unique>true</unique>
<field>
<name>principaluri</name>
</field>
<field>
<name>uri</name>
</field>
<field>
<name>type</name>
</field>
</index>
</declaration>
</table>
</database>

@ -5,7 +5,7 @@
<description>ownCloud WebDAV endpoint</description>
<licence>AGPL</licence>
<author>owncloud.org</author>
<version>0.1.2</version>
<version>0.1.3</version>
<requiremin>9.0</requiremin>
<shipped>true</shipped>
<standalone/>

@ -1,8 +1,15 @@
<?php
use OCA\DAV\Command\CreateAddressBook;
use OCA\DAV\Command\CreateCalendar;
use OCA\DAV\Command\SyncSystemAddressBook;
$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 CreateAddressBook($userManager, $dbConnection));
$application->add(new CreateAddressBook($userManager, $dbConnection, $config));
$application->add(new CreateCalendar($userManager, $dbConnection));
$application->add(new SyncSystemAddressBook($userManager, $dbConnection, $config));

@ -39,7 +39,8 @@ $serverFactory = new OCA\DAV\Connector\Sabre\ServerFactory(
\OC::$server->getUserSession(),
\OC::$server->getMountManager(),
\OC::$server->getTagManager(),
\OC::$server->getEventDispatcher()
\OC::$server->getEventDispatcher(),
\OC::$server->getRequest()
);
$requestUri = \OC::$server->getRequest()->getRequestUri();

@ -40,7 +40,8 @@ $serverFactory = new \OCA\DAV\Connector\Sabre\ServerFactory(
\OC::$server->getUserSession(),
\OC::$server->getMountManager(),
\OC::$server->getTagManager(),
\OC::$server->getEventDispatcher()
\OC::$server->getEventDispatcher(),
\OC::$server->getRequest()
);
// Backends

@ -3,6 +3,8 @@
namespace OCA\DAV\Command;
use OCA\DAV\CardDAV\CardDavBackend;
use OCA\DAV\Connector\Sabre\Principal;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IUserManager;
use Symfony\Component\Console\Command\Command;
@ -18,26 +20,30 @@ class CreateAddressBook extends Command {
/** @var \OCP\IDBConnection */
protected $dbConnection;
/** @var IConfig */
private $config;
/**
* @param IUserManager $userManager
* @param IDBConnection $dbConnection
*/
function __construct(IUserManager $userManager, IDBConnection $dbConnection) {
function __construct(IUserManager $userManager, IDBConnection $dbConnection, IConfig $config) {
parent::__construct();
$this->userManager = $userManager;
$this->dbConnection = $dbConnection;
$this->config = $config;
}
protected function configure() {
$this
->setName('dav:create-addressbook')
->setDescription('Create a dav addressbook')
->addArgument('user',
InputArgument::REQUIRED,
'User for whom the addressbook will be created')
->addArgument('name',
InputArgument::REQUIRED,
'Name of the addressbook');
->setName('dav:create-addressbook')
->setDescription('Create a dav addressbook')
->addArgument('user',
InputArgument::REQUIRED,
'User for whom the addressbook will be created')
->addArgument('name',
InputArgument::REQUIRED,
'Name of the addressbook');
}
protected function execute(InputInterface $input, OutputInterface $output) {
@ -45,8 +51,13 @@ class CreateAddressBook extends Command {
if (!$this->userManager->userExists($user)) {
throw new \InvalidArgumentException("User <$user> in unknown.");
}
$principalBackend = new Principal(
$this->config,
$this->userManager
);
$name = $input->getArgument('name');
$carddav = new CardDavBackend($this->dbConnection);
$carddav->createAddressBook("principals/$user", $name, []);
$carddav = new CardDavBackend($this->dbConnection, $principalBackend);
$carddav->createAddressBook("principals/users/$user", $name, []);
}
}

@ -0,0 +1,52 @@
<?php
namespace OCA\DAV\Command;
use OCA\DAV\CalDAV\CalDavBackend;
use OCP\IDBConnection;
use OCP\IUserManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class CreateCalendar extends Command {
/** @var IUserManager */
protected $userManager;
/** @var \OCP\IDBConnection */
protected $dbConnection;
/**
* @param IUserManager $userManager
* @param IDBConnection $dbConnection
*/
function __construct(IUserManager $userManager, IDBConnection $dbConnection) {
parent::__construct();
$this->userManager = $userManager;
$this->dbConnection = $dbConnection;
}
protected function configure() {
$this
->setName('dav:create-calendar')
->setDescription('Create a dav calendar')
->addArgument('user',
InputArgument::REQUIRED,
'User for whom the calendar will be created')
->addArgument('name',
InputArgument::REQUIRED,
'Name of the calendar');
}
protected function execute(InputInterface $input, OutputInterface $output) {
$user = $input->getArgument('user');
if (!$this->userManager->userExists($user)) {
throw new \InvalidArgumentException("User <$user> in unknown.");
}
$name = $input->getArgument('name');
$caldav = new CalDavBackend($this->dbConnection);
$caldav->createCalendar("principals/users/$user", $name, []);
}
}

@ -0,0 +1,107 @@
<?php
namespace OCA\DAV\Command;
use OCA\DAV\CardDAV\CardDavBackend;
use OCA\DAV\CardDAV\Converter;
use OCA\DAV\Connector\Sabre\Principal;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IUser;
use OCP\IUserManager;
use Sabre\CardDAV\Plugin;
use Sabre\VObject\Component\VCard;
use Sabre\VObject\Property\Text;
use Sabre\VObject\Reader;
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 SyncSystemAddressBook extends Command {
/** @var IUserManager */
protected $userManager;
/** @var \OCP\IDBConnection */
protected $dbConnection;
/** @var IConfig */
protected $config;
/** @var CardDavBackend */
private $backend;
/**
* @param IUserManager $userManager
* @param IDBConnection $dbConnection
* @param IConfig $config
*/
function __construct(IUserManager $userManager, IDBConnection $dbConnection, IConfig $config) {
parent::__construct();
$this->userManager = $userManager;
$this->dbConnection = $dbConnection;
$this->config = $config;
}
protected function configure() {
$this
->setName('dav:sync-system-addressbook')
->setDescription('Synchronizes users to the system addressbook');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*/
protected function execute(InputInterface $input, OutputInterface $output) {
$principalBackend = new Principal(
$this->config,
$this->userManager
);
$this->backend = new CardDavBackend($this->dbConnection, $principalBackend);
// ensure system addressbook exists
$systemAddressBook = $this->ensureSystemAddressBookExists();
$converter = new Converter();
$output->writeln('Syncing users ...');
$progress = new ProgressBar($output);
$progress->start();
$this->userManager->callForAllUsers(function($user) use ($systemAddressBook, $converter, $progress) {
/** @var IUser $user */
$name = $user->getBackendClassName();
$userId = $user->getUID();
$cardId = "$name:$userId.vcf";
$card = $this->backend->getCard($systemAddressBook['id'], $cardId);
if ($card === false) {
$vCard = $converter->createCardFromUser($user);
$this->backend->createCard($systemAddressBook['id'], $cardId, $vCard->serialize());
} else {
$vCard = Reader::read($card['carddata']);
if ($converter->updateCard($vCard, $user)) {
$this->backend->updateCard($systemAddressBook['id'], $cardId, $vCard->serialize());
}
}
$progress->advance();
});
$progress->finish();
$output->writeln('');
}
protected function ensureSystemAddressBookExists() {
$book = $this->backend->getAddressBooksByUri('system');
if (!is_null($book)) {
return $book;
}
$systemPrincipal = "principals/system/system";
$this->backend->createAddressBook($systemPrincipal, 'system', [
'{' . Plugin::NS_CARDDAV . '}addressbook-description' => 'System addressbook which holds all users of this instance'
]);
return $this->backend->getAddressBooksByUri('system');
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,90 @@
<?php
namespace OCA\DAV\CardDAV;
use OCA\DAV\CardDAV\Sharing\IShareableAddressBook;
use Sabre\DAV\Exception\NotFound;
class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareableAddressBook {
public function __construct(CardDavBackend $carddavBackend, array $addressBookInfo) {
parent::__construct($carddavBackend, $addressBookInfo);
}
/**
* Updates the list of shares.
*
* The first array is a list of people that are to be added to the
* addressbook.
*
* Every element in the add array has the following properties:
* * href - A url. Usually a mailto: address
* * commonName - Usually a first and last name, or false
* * summary - A description of the share, can also be false
* * readOnly - A boolean value
*
* Every element in the remove array is just the address string.
*
* @param array $add
* @param array $remove
* @return void
*/
function updateShares(array $add, array $remove) {
/** @var CardDavBackend $carddavBackend */
$carddavBackend = $this->carddavBackend;
$carddavBackend->updateShares($this->getName(), $add, $remove);
}
/**
* Returns the list of people whom this addressbook is shared with.
*
* Every element in this array should have the following properties:
* * href - Often a mailto: address
* * commonName - Optional, for example a first + last name
* * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
* * readOnly - boolean
* * summary - Optional, a description for the share
*
* @return array
*/
function getShares() {
/** @var CardDavBackend $carddavBackend */
$carddavBackend = $this->carddavBackend;
$carddavBackend->getShares($this->getName());
}
function getACL() {
$acl = parent::getACL();
if ($this->getOwner() === 'principals/system/system') {
$acl[] = [
'privilege' => '{DAV:}read',
'principal' => '{DAV:}authenticated',
'protected' => true,
];
}
return $acl;
}
function getChildACL() {
$acl = parent::getChildACL();
if ($this->getOwner() === 'principals/system/system') {
$acl[] = [
'privilege' => '{DAV:}read',
'principal' => '{DAV:}authenticated',
'protected' => true,
];
}
return $acl;
}
function getChild($name) {
$obj = $this->carddavBackend->getCard($this->addressBookInfo['id'], $name);
if (!$obj) {
throw new NotFound('Card not found');
}
return new Card($this->carddavBackend, $this->addressBookInfo, $obj);
}
}

@ -0,0 +1,33 @@
<?php
namespace OCA\DAV\CardDAV;
class AddressBookRoot extends \Sabre\CardDAV\AddressBookRoot {
/**
* This method returns a node for a principal.
*
* The passed array contains principal information, and is guaranteed to
* at least contain a uri item. Other properties may or may not be
* supplied by the authentication backend.
*
* @param array $principal
* @return \Sabre\DAV\INode
*/
function getChildForPrincipal(array $principal) {
return new UserAddressBooks($this->carddavBackend, $principal['uri']);
}
function getName() {
// Grabbing all the components of the principal path.
$parts = explode('/', $this->principalPrefix);
// We are only interested in the second part.
return $parts[1];
}
}

@ -0,0 +1,39 @@
<?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;
class Card extends \Sabre\CardDAV\Card {
function getACL() {
$acl = parent::getACL();
if ($this->getOwner() === 'principals/system/system') {
$acl[] = [
'privilege' => '{DAV:}read',
'principal' => '{DAV:}authenticated',
'protected' => true,
];
}
return $acl;
}
}

@ -22,6 +22,7 @@
namespace OCA\DAV\CardDAV;
use OCA\DAV\Connector\Sabre\Principal;
use Sabre\CardDAV\Backend\BackendInterface;
use Sabre\CardDAV\Backend\SyncSupport;
use Sabre\CardDAV\Plugin;
@ -29,8 +30,12 @@ use Sabre\DAV\Exception\BadRequest;
class CardDavBackend implements BackendInterface, SyncSupport {
public function __construct(\OCP\IDBConnection $db) {
/** @var Principal */
private $principalBackend;
public function __construct(\OCP\IDBConnection $db, Principal $principalBackend) {
$this->db = $db;
$this->principalBackend = $principalBackend;
}
/**
@ -73,9 +78,61 @@ class CardDavBackend implements BackendInterface, SyncSupport {
}
$result->closeCursor();
// query for shared calendars
$query = $this->db->getQueryBuilder();
$query2 = $this->db->getQueryBuilder();
$query2->select(['resourceid'])
->from('dav_shares')
->where($query2->expr()->eq('principaluri', $query2->createParameter('principaluri')))
->andWhere($query2->expr()->eq('type', $query2->createParameter('type')));
$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
->from('addressbooks')
->where($query->expr()->in('id', $query->createFunction($query2->getSQL())))
->setParameter('type', 'addressbook')
->setParameter('principaluri', $principalUri)
->execute();
while($row = $result->fetch()) {
$addressBooks[] = [
'id' => $row['id'],
'uri' => $row['uri'],
'principaluri' => $row['principaluri'],
'{DAV:}displayname' => $row['displayname'],
'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
];
}
$result->closeCursor();
return $addressBooks;
}
public function getAddressBooksByUri($addressBookUri) {
$query = $this->db->getQueryBuilder();
$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
->from('addressbooks')
->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
->setMaxResults(1)
->execute();
$row = $result->fetch();
$result->closeCursor();
if ($row === false) {
return null;
}
return [
'id' => $row['id'],
'uri' => $row['uri'],
'principaluri' => $row['principaluri'],
'{DAV:}displayname' => $row['displayname'],
'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
];
}
/**
* Updates properties for an address book.
*
@ -86,7 +143,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* Calling the handle method is like telling the PropPatch object "I
* promise I can handle updating this property".
*
* Read the PropPatch documenation for more info and examples.
* Read the PropPatch documentation for more info and examples.
*
* @param string $addressBookId
* @param \Sabre\DAV\PropPatch $propPatch
@ -201,6 +258,11 @@ class CardDavBackend implements BackendInterface, SyncSupport {
->where($query->expr()->eq('id', $query->createParameter('id')))
->setParameter('id', $addressBookId)
->execute();
$query->delete('dav_shares')
->where($query->expr()->eq('resourceid', $query->createNamedParameter($addressBookId)))
->andWhere($query->expr()->eq('type', $query->createNamedParameter('addressbook')))
->execute();
}
/**
@ -281,7 +343,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* If the backend supports this, it may allow for some speed-ups.
*
* @param mixed $addressBookId
* @param array $uris
* @param string[] $uris
* @return array
*/
function getMultipleCards($addressBookId, array $uris) {
@ -328,7 +390,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* @param mixed $addressBookId
* @param string $cardUri
* @param string $cardData
* @return string|null
* @return string
*/
function createCard($addressBookId, $cardUri, $cardData) {
$etag = md5($cardData);
@ -373,7 +435,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* @param mixed $addressBookId
* @param string $cardUri
* @param string $cardData
* @return string|null
* @return string
*/
function updateCard($addressBookId, $cardUri, $cardData) {
@ -561,4 +623,115 @@ class CardDavBackend implements BackendInterface, SyncSupport {
return $cardData;
}
/**
* @param string $path
* @param string[] $add
* @param string[] $remove
*/
public function updateShares($path, $add, $remove) {
foreach($add as $element) {
$this->shareWith($path, $element);
}
foreach($remove as $element) {
$this->unshare($path, $element);
}
}
/**
* @param string $addressBookUri
* @param string $element
*/
private function shareWith($addressBookUri, $element) {
$user = $element['href'];
$parts = explode(':', $user, 2);
if ($parts[0] !== 'principal') {
return;
}
$p = $this->principalBackend->getPrincipalByPath($parts[1]);
if (is_null($p)) {
return;
}
$addressBook = $this->getAddressBooksByUri($addressBookUri);
if (is_null($addressBook)) {
return;
}
// remove the share if it already exists
$this->unshare($addressBookUri, $element);
$query = $this->db->getQueryBuilder();
$query->insert('dav_shares')
->values([
'principaluri' => $query->createNamedParameter($parts[1]),
'uri' => $query->createNamedParameter($addressBookUri),
'type' => $query->createNamedParameter('addressbook'),
'access' => $query->createNamedParameter(0),
'resourceid' => $query->createNamedParameter($addressBook['id'])
]);
$query->execute();
}
/**
* @param string $addressBookUri
* @param string $element
*/
private function unshare($addressBookUri, $element) {
$user = $element['href'];
$parts = explode(':', $user, 2);
if ($parts[0] !== 'principal') {
return;
}
$p = $this->principalBackend->getPrincipalByPath($parts[1]);
if (is_null($p)) {
return;
}
$addressBook = $this->getAddressBooksByUri($addressBookUri);
if (is_null($addressBook)) {
return;
}
$query = $this->db->getQueryBuilder();
$query->delete('dav_shares')
->where($query->expr()->eq('resourceid', $query->createNamedParameter($addressBook['id'])))
->andWhere($query->expr()->eq('type', $query->createNamedParameter('addressbook')))
->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($parts[1])))
;
$query->execute();
}
/**
* Returns the list of people whom this address book is shared with.
*
* Every element in this array should have the following properties:
* * href - Often a mailto: address
* * commonName - Optional, for example a first + last name
* * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
* * readOnly - boolean
* * summary - Optional, a description for the share
*
* @return array
*/
public function getShares($addressBookUri) {
$query = $this->db->getQueryBuilder();
$result = $query->select(['principaluri', 'access'])
->from('dav_shares')
->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
->andWhere($query->expr()->eq('type', $query->createNamedParameter('addressbook')))
->execute();
$shares = [];
while($row = $result->fetch()) {
$p = $this->principalBackend->getPrincipalByPath($row['principaluri']);
$shares[]= [
'href' => "principal:${p['uri']}",
'commonName' => isset($p['{DAV:}displayname']) ? $p['{DAV:}displayname'] : '',
'status' => 1,
'readOnly' => ($row['access'] === 1)
];
}
return $shares;
}
}

@ -0,0 +1,158 @@
<?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 OCP\IImage;
use OCP\IUser;
use Sabre\VObject\Component\VCard;
use Sabre\VObject\Property\Text;
class Converter {
/**
* @param IUser $user
* @return VCard
*/
public function createCardFromUser(IUser $user) {
$uid = $user->getUID();
$displayName = $user->getDisplayName();
$displayName = empty($displayName ) ? $uid : $displayName;
$emailAddress = $user->getEMailAddress();
$cloudId = $user->getCloudId();
$image = $user->getAvatarImage(-1);
$vCard = new VCard();
$vCard->add(new Text($vCard, 'UID', $uid));
if (!empty($displayName)) {
$vCard->add(new Text($vCard, 'FN', $displayName));
$vCard->add(new Text($vCard, 'N', $this->splitFullName($displayName)));
}
if (!empty($emailAddress)) {
$vCard->add(new Text($vCard, 'EMAIL', $emailAddress, ['TYPE' => 'OTHER']));
}
if (!empty($cloudId)) {
$vCard->add(new Text($vCard, 'CLOUD', $cloudId));
}
if ($image) {
$vCard->add('PHOTO', $image->data(), ['ENCODING' => 'b', 'TYPE' => $image->mimeType()]);
}
$vCard->validate();
return $vCard;
}
/**
* @param VCard $vCard
* @param IUser $user
* @return bool
*/
public function updateCard(VCard $vCard, IUser $user) {
$uid = $user->getUID();
$displayName = $user->getDisplayName();
$displayName = empty($displayName ) ? $uid : $displayName;
$emailAddress = $user->getEMailAddress();
$cloudId = $user->getCloudId();
$image = $user->getAvatarImage(-1);
$updated = false;
if($this->propertyNeedsUpdate($vCard, 'FN', $displayName)) {
$vCard->FN = new Text($vCard, 'FN', $displayName);
unset($vCard->N);
$vCard->add(new Text($vCard, 'N', $this->splitFullName($displayName)));
$updated = true;
}
if($this->propertyNeedsUpdate($vCard, 'EMAIL', $emailAddress)) {
$vCard->EMAIL = new Text($vCard, 'EMAIL', $emailAddress);
$updated = true;
}
if($this->propertyNeedsUpdate($vCard, 'CLOUD', $cloudId)) {
$vCard->CLOUD = new Text($vCard, 'CLOUD', $cloudId);
$updated = true;
}
if($this->propertyNeedsUpdate($vCard, 'PHOTO', $image)) {
$vCard->add('PHOTO', $image->data(), ['ENCODING' => 'b', 'TYPE' => $image->mimeType()]);
$updated = true;
}
if (empty($emailAddress) && !is_null($vCard->EMAIL)) {
unset($vCard->EMAIL);
$updated = true;
}
if (empty($cloudId) && !is_null($vCard->CLOUD)) {
unset($vCard->CLOUD);
$updated = true;
}
if (empty($image) && !is_null($vCard->PHOTO)) {
unset($vCard->PHOTO);
$updated = true;
}
return $updated;
}
/**
* @param VCard $vCard
* @param string $name
* @param string|IImage $newValue
* @return bool
*/
private function propertyNeedsUpdate(VCard $vCard, $name, $newValue) {
if (is_null($newValue)) {
return false;
}
$value = $vCard->__get($name);
if (!is_null($value)) {
$value = $value->getValue();
$newValue = $newValue instanceof IImage ? $newValue->data() : $newValue;
return $value !== $newValue;
}
return true;
}
/**
* @param string $fullName
* @return string[]
*/
public function splitFullName($fullName) {
// Very basic western style parsing. I'm not gonna implement
// https://github.com/android/platform_packages_providers_contactsprovider/blob/master/src/com/android/providers/contacts/NameSplitter.java ;)
$elements = explode(' ', $fullName);
$result = ['', '', '', '', ''];
if (count($elements) > 2) {
$result[0] = implode(' ', array_slice($elements, count($elements)-1));
$result[1] = $elements[0];
$result[2] = implode(' ', array_slice($elements, 1, count($elements)-2));
} elseif (count($elements) === 2) {
$result[0] = $elements[1];
$result[1] = $elements[0];
} else {
$result[0] = $elements[0];
}
return $result;
}
}

@ -0,0 +1,47 @@
<?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\HTTP\URLUtil;
class Plugin extends \Sabre\CardDAV\Plugin {
/**
* Returns the addressbook home for a given principal
*
* @param string $principal
* @return string
*/
protected function getAddressbookHomeForPrincipal($principal) {
if (strrpos($principal, 'principals/users', -strlen($principal)) !== FALSE) {
list(, $principalId) = URLUtil::splitPath($principal);
return self::ADDRESSBOOK_ROOT . '/users/' . $principalId;
}
if (strrpos($principal, 'principals/system', -strlen($principal)) !== FALSE) {
list(, $principalId) = URLUtil::splitPath($principal);
return self::ADDRESSBOOK_ROOT . '/system/' . $principalId;
}
throw new \LogicException('This is not supposed to happen');
}
}

@ -0,0 +1,46 @@
<?php
namespace OCA\DAV\CardDAV\Sharing;
use Sabre\CardDAV\IAddressBook;
/**
* This interface represents a Calendar that can be shared with other users.
*
*/
interface IShareableAddressBook extends IAddressBook {
/**
* Updates the list of shares.
*
* The first array is a list of people that are to be added to the
* addressbook.
*
* Every element in the add array has the following properties:
* * href - A url. Usually a mailto: address
* * commonName - Usually a first and last name, or false
* * summary - A description of the share, can also be false
* * readOnly - A boolean value
*
* Every element in the remove array is just the address string.
*
* @param array $add
* @param array $remove
* @return void
*/
function updateShares(array $add, array $remove);
/**
* Returns the list of people whom this addressbook is shared with.
*
* Every element in this array should have the following properties:
* * href - Often a mailto: address
* * commonName - Optional, for example a first + last name
* * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
* * readOnly - boolean
* * summary - Optional, a description for the share
*
* @return array
*/
function getShares();
}

@ -0,0 +1,218 @@
<?php
namespace OCA\DAV\CardDAV\Sharing;
use OCA\DAV\Connector\Sabre\Auth;
use OCP\IRequest;
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\DAV\XMLUtil;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
class Plugin extends ServerPlugin {
public function __construct(Auth $authBackEnd, IRequest $request) {
$this->auth = $authBackEnd;
$this->request = $request;
}
/**
* Reference to SabreDAV server object.
*
* @var \Sabre\DAV\Server
*/
protected $server;
/**
* This method should return a list of server-features.
*
* This is for example 'versioning' and is added to the DAV: header
* in an OPTIONS response.
*
* @return string[]
*/
function getFeatures() {
return ['oc-addressbook-sharing'];
}
/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using Sabre\DAV\Server::getPlugin
*
* @return string
*/
function getPluginName() {
return 'carddav-sharing';
}
/**
* This initializes the plugin.
*
* This function is called by Sabre\DAV\Server, after
* addPlugin is called.
*
* This method should set up the required event subscriptions.
*
* @param Server $server
* @return void
*/
function initialize(Server $server) {
$this->server = $server;
$server->resourceTypeMapping['OCA\\DAV\CardDAV\\ISharedAddressbook'] = '{' . \Sabre\CardDAV\Plugin::NS_CARDDAV . '}shared';
$this->server->on('method:POST', [$this, 'httpPost']);
}
/**
* We intercept this to handle POST requests on calendars.
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @return null|false
*/
function httpPost(RequestInterface $request, ResponseInterface $response) {
$path = $request->getPath();
// Only handling xml
$contentType = $request->getHeader('Content-Type');
if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false)
return;
// Making sure the node exists
try {
$node = $this->server->tree->getNodeForPath($path);
} catch (NotFound $e) {
return;
}
// CSRF protection
$this->protectAgainstCSRF();
$requestBody = $request->getBodyAsString();
// If this request handler could not deal with this POST request, it
// will return 'null' and other plugins get a chance to handle the
// request.
//
// However, we already requested the full body. This is a problem,
// because a body can only be read once. This is why we preemptively
// re-populated the request body with the existing data.
$request->setBody($requestBody);
$dom = XMLUtil::loadDOMDocument($requestBody);
$documentType = XMLUtil::toClarkNotation($dom->firstChild);
switch ($documentType) {
// Dealing with the 'share' document, which modified invitees on a
// calendar.
case '{' . \Sabre\CardDAV\Plugin::NS_CARDDAV . '}share' :
// We can only deal with IShareableCalendar objects
if (!$node instanceof IShareableAddressBook) {
return;
}
$this->server->transactionType = 'post-calendar-share';
// Getting ACL info
$acl = $this->server->getPlugin('acl');
// If there's no ACL support, we allow everything
if ($acl) {
$acl->checkPrivileges($path, '{DAV:}write');
}
$mutations = $this->parseShareRequest($dom);
$node->updateShares($mutations[0], $mutations[1]);
$response->setStatus(200);
// Adding this because sending a response body may cause issues,
// and I wanted some type of indicator the response was handled.
$response->setHeader('X-Sabre-Status', 'everything-went-well');
// Breaking the event chain
return false;
}
}
/**
* Parses the 'share' POST request.
*
* This method returns an array, containing two arrays.
* The first array is a list of new sharees. Every element is a struct
* containing a:
* * href element. (usually a mailto: address)
* * commonName element (often a first and lastname, but can also be
* false)
* * readOnly (true or false)
* * summary (A description of the share, can also be false)
*
* The second array is a list of sharees that are to be removed. This is
* just a simple array with 'hrefs'.
*
* @param \DOMDocument $dom
* @return array
*/
function parseShareRequest(\DOMDocument $dom) {
$xpath = new \DOMXPath($dom);
$xpath->registerNamespace('cs', \Sabre\CardDAV\Plugin::NS_CARDDAV);
$xpath->registerNamespace('d', 'urn:DAV');
$set = [];
$elems = $xpath->query('cs:set');
for ($i = 0; $i < $elems->length; $i++) {
$xset = $elems->item($i);
$set[] = [
'href' => $xpath->evaluate('string(d:href)', $xset),
'commonName' => $xpath->evaluate('string(cs:common-name)', $xset),
'summary' => $xpath->evaluate('string(cs:summary)', $xset),
'readOnly' => $xpath->evaluate('boolean(cs:read)', $xset) !== false
];
}
$remove = [];
$elems = $xpath->query('cs:remove');
for ($i = 0; $i < $elems->length; $i++) {
$xremove = $elems->item($i);
$remove[] = $xpath->evaluate('string(d:href)', $xremove);
}
return [$set, $remove];
}
private function protectAgainstCSRF() {
$user = $this->auth->getCurrentUser();
if ($this->auth->isDavAuthenticated($user)) {
return true;
}
if ($this->request->passesCSRFCheck()) {
return true;
}
throw new BadRequest();
}
}

@ -0,0 +1,49 @@
<?php
namespace OCA\DAV\CardDAV;
class UserAddressBooks extends \Sabre\CardDAV\AddressBookHome {
/**
* Returns a list of addressbooks
*
* @return array
*/
function getChildren() {
$addressBooks = $this->carddavBackend->getAddressBooksForUser($this->principalUri);
$objects = [];
foreach($addressBooks as $addressBook) {
$objects[] = new AddressBook($this->carddavBackend, $addressBook);
}
return $objects;
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
function getACL() {
$acl = parent::getACL();
if ($this->principalUri === 'principals/system/system') {
$acl[] = [
'privilege' => '{DAV:}read',
'principal' => '{DAV:}authenticated',
'protected' => true,
];
}
return $acl;
}
}

@ -35,6 +35,8 @@ use OCP\IUserSession;
use Sabre\DAV\Auth\Backend\AbstractBasic;
use Sabre\DAV\Exception\NotAuthenticated;
use Sabre\DAV\Exception\ServiceUnavailable;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
class Auth extends AbstractBasic {
const DAV_AUTHENTICATED = 'AUTHENTICATED_TO_DAV_BACKEND';
@ -52,6 +54,7 @@ class Auth extends AbstractBasic {
IUserSession $userSession) {
$this->session = $session;
$this->userSession = $userSession;
$this->principalPrefix = 'principals/users/';
}
/**
@ -65,7 +68,7 @@ class Auth extends AbstractBasic {
* @param string $username
* @return bool
*/
protected function isDavAuthenticated($username) {
public function isDavAuthenticated($username) {
return !is_null($this->session->get(self::DAV_AUTHENTICATED)) &&
$this->session->get(self::DAV_AUTHENTICATED) === $username;
}
@ -122,22 +125,15 @@ class Auth extends AbstractBasic {
}
/**
* Override function here. We want to cache authentication cookies
* in the syncing client to avoid HTTP-401 roundtrips.
* If the sync client supplies the cookies, then OC_User::isLoggedIn()
* will return true and we can see this WebDAV request as already authenticated,
* even if there are no HTTP Basic Auth headers.
* In other case, just fallback to the parent implementation.
*
* @param \Sabre\DAV\Server $server
* @param string $realm
* @return bool
* @throws ServiceUnavailable
* @param RequestInterface $request
* @param ResponseInterface $response
* @return array
* @throws NotAuthenticated
* @throws ServiceUnavailable
*/
public function authenticate(\Sabre\DAV\Server $server, $realm) {
function check(RequestInterface $request, ResponseInterface $response) {
try {
$result = $this->auth($server, $realm);
$result = $this->auth($request, $response);
return $result;
} catch (NotAuthenticated $e) {
throw $e;
@ -149,11 +145,11 @@ class Auth extends AbstractBasic {
}
/**
* @param \Sabre\DAV\Server $server
* @param $realm
* @return bool
* @param RequestInterface $request
* @param ResponseInterface $response
* @return array
*/
private function auth(\Sabre\DAV\Server $server, $realm) {
private function auth(RequestInterface $request, ResponseInterface $response) {
if (\OC_User::handleApacheAuth() ||
($this->userSession->isLoggedIn() && is_null($this->session->get(self::DAV_AUTHENTICATED)))
) {
@ -161,9 +157,16 @@ class Auth extends AbstractBasic {
\OC_Util::setupFS($user);
$this->currentUser = $user;
$this->session->close();
return true;
return [true, $this->principalPrefix . $user];
}
if (!$this->userSession->isLoggedIn() && $request->getHeader('X-Requested-With') === 'XMLHttpRequest') {
// do not re-authenticate over ajax, use dummy auth name to prevent browser popup
$response->addHeader('WWW-Authenticate','DummyBasic realm="' . $this->realm . '"');
$response->setStatus(401);
throw new \Sabre\DAV\Exception\NotAuthenticated('Cannot authenticate over ajax calls');
}
return parent::authenticate($server, $realm);
return parent::check($request, $response);
}
}

@ -28,8 +28,10 @@
*/
namespace OCA\DAV\Connector\Sabre;
use OCA\DAV\Connector\Sabre\Exception\Forbidden;
use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
use OCA\DAV\Connector\Sabre\Exception\FileLocked;
use OCP\Files\ForbiddenException;
use OCP\Lock\ILockingProvider;
use OCP\Lock\LockedException;
use Sabre\DAV\Exception\Locked;
@ -117,6 +119,8 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
} catch (\OCP\Files\InvalidPathException $ex) {
throw new InvalidPath($ex->getMessage());
} catch (ForbiddenException $ex) {
throw new Forbidden($ex->getMessage(), $ex->getRetry());
} catch (LockedException $e) {
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
}
@ -146,6 +150,8 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
} catch (\OCP\Files\InvalidPathException $ex) {
throw new InvalidPath($ex->getMessage());
} catch (ForbiddenException $ex) {
throw new Forbidden($ex->getMessage(), $ex->getRetry());
} catch (LockedException $e) {
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
}
@ -247,6 +253,8 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node
// assume it wasn't possible to remove due to permission issue
throw new \Sabre\DAV\Exception\Forbidden();
}
} catch (ForbiddenException $ex) {
throw new Forbidden($ex->getMessage(), $ex->getRetry());
} catch (LockedException $e) {
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
}

@ -0,0 +1,64 @@
<?php
/**
* @author Joas Schilling <nickvergessen@owncloud.com>
*
* @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\Connector\Sabre\Exception;
class Forbidden extends \Sabre\DAV\Exception\Forbidden {
const NS_OWNCLOUD = 'http://owncloud.org/ns';
/**
* @var bool
*/
private $retry;
/**
* @param string $message
* @param bool $retry
* @param \Exception $previous
*/
public function __construct($message, $retry = false, \Exception $previous = null) {
parent::__construct($message, 0, $previous);
$this->retry = $retry;
}
/**
* This method allows the exception to include additional information
* into the WebDAV error response
*
* @param \Sabre\DAV\Server $server
* @param \DOMElement $errorNode
* @return void
*/
public function serialize(\Sabre\DAV\Server $server,\DOMElement $errorNode) {
// set ownCloud namespace
$errorNode->setAttribute('xmlns:o', self::NS_OWNCLOUD);
// adding the retry node
$error = $errorNode->ownerDocument->createElementNS('o:','o:retry', var_export($this->retry, true));
$errorNode->appendChild($error);
// adding the message node
$error = $errorNode->ownerDocument->createElementNS('o:','o:reason', $this->getMessage());
$errorNode->appendChild($error);
}
}

@ -0,0 +1,155 @@
<?php
/**
* @author Lukas Reschke <lukas@owncloud.com>
*
* @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\Connector\Sabre;
use Sabre\DAV\Locks\LockInfo;
use Sabre\DAV\ServerPlugin;
use Sabre\DAV\Xml\Property\LockDiscovery;
use Sabre\DAV\Xml\Property\SupportedLock;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\DAV\PropFind;
use Sabre\DAV\INode;
/**
* Class FakeLockerPlugin is a plugin only used when connections come in from
* OS X via Finder. The fake locking plugin does emulate Class 2 WebDAV support
* (locking of files) which allows Finder to access the storage in write mode as
* well.
*
* No real locking is performed, instead the plugin just returns always positive
* responses.
*
* @see https://github.com/owncloud/core/issues/17732
* @package OCA\DAV\Connector\Sabre
*/
class FakeLockerPlugin extends ServerPlugin {
/** @var \Sabre\DAV\Server */
private $server;
/** {@inheritDoc} */
public function initialize(\Sabre\DAV\Server $server) {
$this->server = $server;
$this->server->on('method:LOCK', [$this, 'fakeLockProvider'], 1);
$this->server->on('method:UNLOCK', [$this, 'fakeUnlockProvider'], 1);
$server->on('propFind', [$this, 'propFind']);
$server->on('validateTokens', [$this, 'validateTokens']);
}
/**
* Indicate that we support LOCK and UNLOCK
*
* @param string $path
* @return string[]
*/
public function getHTTPMethods($path) {
return [
'LOCK',
'UNLOCK',
];
}
/**
* Indicate that we support locking
*
* @return integer[]
*/
function getFeatures() {
return [2];
}
/**
* Return some dummy response for PROPFIND requests with regard to locking
*
* @param PropFind $propFind
* @param INode $node
* @return void
*/
function propFind(PropFind $propFind, INode $node) {
$propFind->handle('{DAV:}supportedlock', function() {
return new SupportedLock(true);
});
$propFind->handle('{DAV:}lockdiscovery', function() use ($propFind) {
return new LockDiscovery([]);
});
}
/**
* Mark a locking token always as valid
*
* @param RequestInterface $request
* @param array $conditions
*/
public function validateTokens(RequestInterface $request, &$conditions) {
foreach($conditions as &$fileCondition) {
if(isset($fileCondition['tokens'])) {
foreach($fileCondition['tokens'] as &$token) {
if(isset($token['token'])) {
if(substr($token['token'], 0, 16) === 'opaquelocktoken:') {
$token['validToken'] = true;
}
}
}
}
}
}
/**
* Fakes a successful LOCK
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @return bool
*/
public function fakeLockProvider(RequestInterface $request,
ResponseInterface $response) {
$lockInfo = new LockInfo();
$lockInfo->token = md5($request->getPath());
$lockInfo->uri = $request->getPath();
$lockInfo->depth = \Sabre\DAV\Server::DEPTH_INFINITY;
$lockInfo->timeout = 1800;
$body = $this->server->xml->write('{DAV:}prop', [
'{DAV:}lockdiscovery' =>
new LockDiscovery([$lockInfo])
]);
$response->setBody($body);
return false;
}
/**
* Fakes a successful LOCK
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @return bool
*/
public function fakeUnlockProvider(RequestInterface $request,
ResponseInterface $response) {
$response->setStatus(204);
$response->setHeader('Content-Length', '0');
return false;
}
}

@ -35,9 +35,11 @@ namespace OCA\DAV\Connector\Sabre;
use OC\Files\Filesystem;
use OCA\DAV\Connector\Sabre\Exception\EntityTooLarge;
use OCA\DAV\Connector\Sabre\Exception\FileLocked;
use OCA\DAV\Connector\Sabre\Exception\Forbidden as DAVForbiddenException;
use OCA\DAV\Connector\Sabre\Exception\UnsupportedMediaType;
use OCP\Encryption\Exceptions\GenericEncryptionException;
use OCP\Files\EntityTooLargeException;
use OCP\Files\ForbiddenException;
use OCP\Files\InvalidContentException;
use OCP\Files\InvalidPathException;
use OCP\Files\LockNotAcquiredException;
@ -175,6 +177,8 @@ class File extends Node implements IFile {
\OCP\Util::writeLog('webdav', 'renaming part file to final file failed', \OCP\Util::ERROR);
throw new Exception('Could not rename part file to final file');
}
} catch (ForbiddenException $ex) {
throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
} catch (\Exception $e) {
$partStorage->unlink($internalPartPath);
$this->convertToSabreException($e);
@ -188,7 +192,7 @@ class File extends Node implements IFile {
}
// since we skipped the view we need to scan and emit the hooks ourselves
$this->fileView->getUpdater()->update($this->path);
$storage->getUpdater()->update($internalPath);
if ($view) {
$this->emitPostHooks($exists);
@ -209,6 +213,9 @@ class File extends Node implements IFile {
return '"' . $this->info->getEtag() . '"';
}
/**
* @param string $path
*/
private function emitPreHooks($exists, $path = null) {
if (is_null($path)) {
$path = $this->path;
@ -234,6 +241,9 @@ class File extends Node implements IFile {
return $run;
}
/**
* @param string $path
*/
private function emitPostHooks($exists, $path = null) {
if (is_null($path)) {
$path = $this->path;
@ -256,7 +266,7 @@ class File extends Node implements IFile {
/**
* Returns the data
*
* @return string|resource
* @return resource
* @throws Forbidden
* @throws ServiceUnavailable
*/
@ -273,6 +283,8 @@ class File extends Node implements IFile {
throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
} catch (StorageNotAvailableException $e) {
throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
} catch (ForbiddenException $ex) {
throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
} catch (LockedException $e) {
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
}
@ -296,6 +308,8 @@ class File extends Node implements IFile {
}
} catch (StorageNotAvailableException $e) {
throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
} catch (ForbiddenException $ex) {
throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
} catch (LockedException $e) {
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
}
@ -306,7 +320,7 @@ class File extends Node implements IFile {
*
* If null is returned, we'll assume application/octet-stream
*
* @return mixed
* @return string
*/
public function getContentType() {
$mimeType = $this->info->getMimetype();
@ -424,7 +438,7 @@ class File extends Node implements IFile {
$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
// since we skipped the view we need to scan and emit the hooks ourselves
$this->fileView->getUpdater()->update($targetPath);
$targetStorage->getUpdater()->update($targetInternalPath);
$this->emitPostHooks($exists, $targetPath);
@ -474,6 +488,10 @@ class File extends Node implements IFile {
// a more general case - due to whatever reason the content could not be written
throw new Forbidden($e->getMessage(), 0, $e);
}
if ($e instanceof ForbiddenException) {
// the path for the file was forbidden
throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e);
}
if ($e instanceof EntityTooLargeException) {
// the file is too big to be stored
throw new EntityTooLarge($e->getMessage(), 0, $e);

@ -37,11 +37,14 @@ class FilesPlugin extends \Sabre\DAV\ServerPlugin {
// namespace
const NS_OWNCLOUD = 'http://owncloud.org/ns';
const FILEID_PROPERTYNAME = '{http://owncloud.org/ns}id';
const INTERNAL_FILEID_PROPERTYNAME = '{http://owncloud.org/ns}fileid';
const PERMISSIONS_PROPERTYNAME = '{http://owncloud.org/ns}permissions';
const DOWNLOADURL_PROPERTYNAME = '{http://owncloud.org/ns}downloadURL';
const SIZE_PROPERTYNAME = '{http://owncloud.org/ns}size';
const GETETAG_PROPERTYNAME = '{DAV:}getetag';
const LASTMODIFIED_PROPERTYNAME = '{DAV:}lastmodified';
const OWNER_ID_PROPERTYNAME = '{http://owncloud.org/ns}owner-id';
const OWNER_DISPLAY_NAME_PROPERTYNAME = '{http://owncloud.org/ns}owner-display-name';
/**
* Reference to main server object
@ -96,9 +99,12 @@ class FilesPlugin extends \Sabre\DAV\ServerPlugin {
$server->xmlNamespaces[self::NS_OWNCLOUD] = 'oc';
$server->protectedProperties[] = self::FILEID_PROPERTYNAME;
$server->protectedProperties[] = self::INTERNAL_FILEID_PROPERTYNAME;
$server->protectedProperties[] = self::PERMISSIONS_PROPERTYNAME;
$server->protectedProperties[] = self::SIZE_PROPERTYNAME;
$server->protectedProperties[] = self::DOWNLOADURL_PROPERTYNAME;
$server->protectedProperties[] = self::OWNER_ID_PROPERTYNAME;
$server->protectedProperties[] = self::OWNER_DISPLAY_NAME_PROPERTYNAME;
// normally these cannot be changed (RFC4918), but we want them modifiable through PROPPATCH
$allowedProperties = ['{DAV:}getetag'];
@ -110,6 +116,7 @@ class FilesPlugin extends \Sabre\DAV\ServerPlugin {
$this->server->on('afterBind', array($this, 'sendFileIdHeader'));
$this->server->on('afterWriteContent', array($this, 'sendFileIdHeader'));
$this->server->on('afterMethod:GET', [$this,'httpGet']);
$this->server->on('afterMethod:GET', array($this, 'handleDownloadToken'));
$this->server->on('afterResponse', function($request, ResponseInterface $response) {
$body = $response->getBody();
if (is_resource($body)) {
@ -142,6 +149,32 @@ class FilesPlugin extends \Sabre\DAV\ServerPlugin {
}
}
/**
* This sets a cookie to be able to recognize the start of the download
* the content must not be longer than 32 characters and must only contain
* alphanumeric characters
*
* @param RequestInterface $request
* @param ResponseInterface $response
*/
function handleDownloadToken(RequestInterface $request, ResponseInterface $response) {
$queryParams = $request->getQueryParameters();
/**
* this sets a cookie to be able to recognize the start of the download
* the content must not be longer than 32 characters and must only contain
* alphanumeric characters
*/
if (isset($queryParams['downloadStartSecret'])) {
$token = $queryParams['downloadStartSecret'];
if (!isset($token[32])
&& preg_match('!^[a-zA-Z0-9]+$!', $token) === 1) {
// FIXME: use $response->setHeader() instead
setcookie('ocDownloadStarted', $token, time() + 20, '/');
}
}
}
/**
* Plugin that adds a 'Content-Disposition: attachment' header to all files
* delivered by SabreDAV.
@ -171,6 +204,10 @@ class FilesPlugin extends \Sabre\DAV\ServerPlugin {
return $node->getFileId();
});
$propFind->handle(self::INTERNAL_FILEID_PROPERTYNAME, function() use ($node) {
return $node->getInternalFileId();
});
$propFind->handle(self::PERMISSIONS_PROPERTYNAME, function() use ($node) {
$perms = $node->getDavPermissions();
if ($this->isPublic) {
@ -201,6 +238,16 @@ class FilesPlugin extends \Sabre\DAV\ServerPlugin {
return $node->getSize();
});
}
$propFind->handle(self::OWNER_ID_PROPERTYNAME, function() use ($node) {
$owner = $node->getOwner();
return $owner->getUID();
});
$propFind->handle(self::OWNER_DISPLAY_NAME_PROPERTYNAME, function() use ($node) {
$owner = $node->getOwner();
$displayName = $owner->getDisplayName();
return $displayName;
});
}
/**

@ -27,7 +27,6 @@ use OCP\Lock\ILockingProvider;
use OCP\Lock\LockedException;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\ServerPlugin;
use Sabre\DAV\Tree;
use Sabre\HTTP\RequestInterface;
class LockPlugin extends ServerPlugin {
@ -38,18 +37,6 @@ class LockPlugin extends ServerPlugin {
*/
private $server;
/**
* @var \Sabre\DAV\Tree
*/
private $tree;
/**
* @param \Sabre\DAV\Tree $tree tree
*/
public function __construct(Tree $tree) {
$this->tree = $tree;
}
/**
* {@inheritdoc}
*/
@ -66,7 +53,7 @@ class LockPlugin extends ServerPlugin {
return;
}
try {
$node = $this->tree->getNodeForPath($request->getPath());
$node = $this->server->tree->getNodeForPath($request->getPath());
} catch (NotFound $e) {
return;
}
@ -84,7 +71,7 @@ class LockPlugin extends ServerPlugin {
return;
}
try {
$node = $this->tree->getNodeForPath($request->getPath());
$node = $this->server->tree->getNodeForPath($request->getPath());
} catch (NotFound $e) {
return;
}

@ -178,7 +178,7 @@ abstract class Node implements \Sabre\DAV\INode {
/**
* Returns the size of the node, in bytes
*
* @return int|float
* @return integer
*/
public function getSize() {
return $this->info->getSize();
@ -207,7 +207,14 @@ abstract class Node implements \Sabre\DAV\INode {
}
/**
* @return string|null
* @return integer
*/
public function getInternalFileId() {
return $this->info->getId();
}
/**
* @return string
*/
public function getDavPermissions() {
$p = '';
@ -238,6 +245,10 @@ abstract class Node implements \Sabre\DAV\INode {
return $p;
}
public function getOwner() {
return $this->info->getOwner();
}
protected function verifyPath() {
try {
$fileName = basename($this->info->getPath());

@ -25,10 +25,12 @@
namespace OCA\DAV\Connector\Sabre;
use OCA\DAV\Connector\Sabre\Exception\Forbidden;
use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
use OCA\DAV\Connector\Sabre\Exception\FileLocked;
use OC\Files\FileInfo;
use OC\Files\Mount\MoveableMount;
use OCP\Files\ForbiddenException;
use OCP\Files\StorageInvalidException;
use OCP\Files\StorageNotAvailableException;
use OCP\Lock\LockedException;
@ -235,6 +237,8 @@ class ObjectTree extends \Sabre\DAV\Tree {
}
} catch (StorageNotAvailableException $e) {
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
} catch (ForbiddenException $ex) {
throw new Forbidden($ex->getMessage(), $ex->getRetry());
} catch (LockedException $e) {
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
}
@ -274,6 +278,8 @@ class ObjectTree extends \Sabre\DAV\Tree {
$this->fileView->copy($source, $destination);
} catch (StorageNotAvailableException $e) {
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
} catch (ForbiddenException $ex) {
throw new Forbidden($ex->getMessage(), $ex->getRetry());
} catch (LockedException $e) {
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
}

@ -30,9 +30,11 @@
namespace OCA\DAV\Connector\Sabre;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IConfig;
use \Sabre\DAV\PropPatch;
use Sabre\HTTP\URLUtil;
class Principal implements \Sabre\DAVACL\PrincipalBackend\BackendInterface {
/** @var IConfig */
@ -66,20 +68,9 @@ class Principal implements \Sabre\DAVACL\PrincipalBackend\BackendInterface {
public function getPrincipalsByPrefix($prefixPath) {
$principals = [];
if ($prefixPath === 'principals') {
if ($prefixPath === 'principals/users') {
foreach($this->userManager->search('') as $user) {
$principal = [
'uri' => 'principals/' . $user->getUID(),
'{DAV:}displayname' => $user->getUID(),
];
$email = $this->config->getUserValue($user->getUID(), 'settings', 'email');
if(!empty($email)) {
$principal['{http://sabredav.org/ns}email-address'] = $email;
}
$principals[] = $principal;
$principals[] = $this->userToPrincipal($user);
}
}
@ -95,21 +86,18 @@ class Principal implements \Sabre\DAVACL\PrincipalBackend\BackendInterface {
* @return array
*/
public function getPrincipalByPath($path) {
list($prefix, $name) = explode('/', $path);
$elements = explode('/', $path);
if ($elements[0] !== 'principals') {
return null;
}
if ($elements[1] !== 'users') {
return null;
}
$name = $elements[2];
$user = $this->userManager->get($name);
if ($prefix === 'principals' && !is_null($user)) {
$principal = [
'uri' => 'principals/' . $user->getUID(),
'{DAV:}displayname' => $user->getUID(),
];
$email = $this->config->getUserValue($user->getUID(), 'settings', 'email');
if($email) {
$principal['{http://sabredav.org/ns}email-address'] = $email;
}
return $principal;
if (!is_null($user)) {
return $this->userToPrincipal($user);
}
return null;
@ -140,10 +128,10 @@ class Principal implements \Sabre\DAVACL\PrincipalBackend\BackendInterface {
* @throws \Sabre\DAV\Exception
*/
public function getGroupMembership($principal) {
list($prefix, $name) = \Sabre\HTTP\URLUtil::splitPath($principal);
list($prefix, $name) = URLUtil::splitPath($principal);
$group_membership = array();
if ($prefix === 'principals') {
if ($prefix === 'principals/users') {
$principal = $this->getPrincipalByPath($principal);
if (!$principal) {
throw new \Sabre\DAV\Exception('Principal not found');
@ -151,8 +139,8 @@ class Principal implements \Sabre\DAVACL\PrincipalBackend\BackendInterface {
// TODO: for now the user principal has only its own groups
return array(
'principals/'.$name.'/calendar-proxy-read',
'principals/'.$name.'/calendar-proxy-write',
'principals/users/'.$name.'/calendar-proxy-read',
'principals/users/'.$name.'/calendar-proxy-write',
// The addressbook groups are not supported in Sabre,
// see http://groups.google.com/group/sabredav-discuss/browse_thread/thread/ef2fa9759d55f8c#msg_5720afc11602e753
//'principals/'.$name.'/addressbook-proxy-read',
@ -168,7 +156,7 @@ class Principal implements \Sabre\DAVACL\PrincipalBackend\BackendInterface {
* The principals should be passed as a list of uri's.
*
* @param string $principal
* @param array $members
* @param string[] $members
* @throws \Sabre\DAV\Exception
*/
public function setGroupMemberSet($principal, array $members) {
@ -202,4 +190,24 @@ class Principal implements \Sabre\DAVACL\PrincipalBackend\BackendInterface {
function findByUri($uri, $principalPrefix) {
return '';
}
/**
* @param IUser $user
* @return array
*/
protected function userToPrincipal($user) {
$userId = $user->getUID();
$displayName = $user->getDisplayName();
$principal = [
'uri' => "principals/users/$userId",
'{DAV:}displayname' => is_null($displayName) ? $userId : $displayName,
];
$email = $user->getEMailAddress();
if (!empty($email)) {
$principal['{http://sabredav.org/ns}email-address'] = $email;
return $principal;
}
return $principal;
}
}

@ -26,12 +26,41 @@ use OCP\Files\Mount\IMountManager;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\ILogger;
use OCP\IRequest;
use OCP\ITagManager;
use OCP\IUserSession;
use Sabre\DAV\Auth\Backend\BackendInterface;
use Sabre\DAV\Locks\Plugin;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class ServerFactory {
/** @var IConfig */
private $config;
/** @var ILogger */
private $logger;
/** @var IDBConnection */
private $databaseConnection;
/** @var IUserSession */
private $userSession;
/** @var IMountManager */
private $mountManager;
/** @var ITagManager */
private $tagManager;
/** @var EventDispatcherInterface */
private $dispatcher;
/** @var IRequest */
private $request;
/**
* @param IConfig $config
* @param ILogger $logger
* @param IDBConnection $databaseConnection
* @param IUserSession $userSession
* @param IMountManager $mountManager
* @param ITagManager $tagManager
* @param EventDispatcherInterface $dispatcher
* @param IRequest $request
*/
public function __construct(
IConfig $config,
ILogger $logger,
@ -39,7 +68,8 @@ class ServerFactory {
IUserSession $userSession,
IMountManager $mountManager,
ITagManager $tagManager,
EventDispatcherInterface $dispatcher
EventDispatcherInterface $dispatcher,
IRequest $request
) {
$this->config = $config;
$this->logger = $logger;
@ -48,6 +78,7 @@ class ServerFactory {
$this->mountManager = $mountManager;
$this->tagManager = $tagManager;
$this->dispatcher = $dispatcher;
$this->request = $request;
}
/**
@ -57,7 +88,10 @@ class ServerFactory {
* @param callable $viewCallBack callback that should return the view for the dav endpoint
* @return Server
*/
public function createServer($baseUri, $requestUri, BackendInterface $authBackend, callable $viewCallBack) {
public function createServer($baseUri,
$requestUri,
BackendInterface $authBackend,
callable $viewCallBack) {
// Fire up server
$objectTree = new \OCA\DAV\Connector\Sabre\ObjectTree();
$server = new \OCA\DAV\Connector\Sabre\Server($objectTree);
@ -73,8 +107,13 @@ class ServerFactory {
// FIXME: The following line is a workaround for legacy components relying on being able to send a GET to /
$server->addPlugin(new \OCA\DAV\Connector\Sabre\DummyGetResponsePlugin());
$server->addPlugin(new \OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin('webdav', $this->logger));
$server->addPlugin(new \OCA\DAV\Connector\Sabre\LockPlugin($objectTree));
$server->addPlugin(new \OCA\DAV\Connector\Sabre\LockPlugin());
$server->addPlugin(new \OCA\DAV\Connector\Sabre\ListenerPlugin($this->dispatcher));
// Finder on OS X requires Class 2 WebDAV support (locking), since we do
// not provide locking we emulate it using a fake locking plugin.
if($this->request->isUserAgent(['/WebDAVFS/'])) {
$server->addPlugin(new \OCA\DAV\Connector\Sabre\FakeLockerPlugin());
}
// wait with registering these until auth is handled and the filesystem is setup
$server->on('beforeMethod', function () use ($server, $objectTree, $viewCallBack) {

@ -22,82 +22,100 @@
namespace OCA\DAV\Connector\Sabre;
use Sabre\DAV;
use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;
/**
* TagList property
*
* This property contains multiple "tag" elements, each containing a tag name.
*/
class TagList extends DAV\Property {
class TagList implements Element {
const NS_OWNCLOUD = 'http://owncloud.org/ns';
/**
* tags
*
* @var array
*/
private $tags;
/**
* @param array $tags
*/
public function __construct(array $tags) {
$this->tags = $tags;
}
/**
* Returns the tags
*
* @return array
*/
public function getTags() {
return $this->tags;
}
/**
* Serializes this property.
*
* @param DAV\Server $server
* @param \DOMElement $dom
* @return void
*/
public function serialize(DAV\Server $server,\DOMElement $dom) {
$prefix = $server->xmlNamespaces[self::NS_OWNCLOUD];
foreach($this->tags as $tag) {
$elem = $dom->ownerDocument->createElement($prefix . ':tag');
$elem->appendChild($dom->ownerDocument->createTextNode($tag));
$dom->appendChild($elem);
}
}
/**
* Unserializes this property from a DOM Element
*
* This method returns an instance of this class.
* It will only decode tag values.
*
* @param \DOMElement $dom
* @param array $propertyMap
* @return \OCA\DAV\Connector\Sabre\TagList
*/
static function unserialize(\DOMElement $dom, array $propertyMap) {
$tags = array();
foreach($dom->childNodes as $child) {
if (DAV\XMLUtil::toClarkNotation($child)==='{' . self::NS_OWNCLOUD . '}tag') {
$tags[] = $child->textContent;
}
}
return new self($tags);
}
/**
* tags
*
* @var array
*/
private $tags;
/**
* @param array $tags
*/
public function __construct(array $tags) {
$this->tags = $tags;
}
/**
* Returns the tags
*
* @return array
*/
public function getTags() {
return $this->tags;
}
/**
* The deserialize method is called during xml parsing.
*
* This method is called statictly, this is because in theory this method
* may be used as a type of constructor, or factory method.
*
* Often you want to return an instance of the current class, but you are
* free to return other data as well.
*
* You are responsible for advancing the reader to the next element. Not
* doing anything will result in a never-ending loop.
*
* If you just want to skip parsing for this element altogether, you can
* just call $reader->next();
*
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
* the next element.
*
* @param Reader $reader
* @return mixed
*/
static function xmlDeserialize(Reader $reader) {
$tags = [];
foreach ($reader->parseInnerTree() as $elem) {
if ($elem['name'] === '{' . self::NS_OWNCLOUD . '}tag') {
$tags[] = $elem['value'];
}
}
return new self($tags);
}
/**
* The xmlSerialize metod is called during xml writing.
*
* Use the $writer argument to write its own xml serialization.
*
* An important note: do _not_ create a parent element. Any element
* implementing XmlSerializble should only ever write what's considered
* its 'inner xml'.
*
* The parent of the current element is responsible for writing a
* containing element.
*
* This allows serializers to be re-used for different element names.
*
* If you are opening new elements, you must also close them again.
*
* @param Writer $writer
* @return void
*/
function xmlSerialize(Writer $writer) {
foreach ($this->tags as $tag) {
$writer->startElement(self::NS_OWNCLOUD . ':tag');
$writer->writeElement($tag);
$writer->endElement();
}
}
}

@ -0,0 +1,183 @@
<?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\DAV;
use Sabre\DAVACL\PrincipalBackend\AbstractBackend;
use Sabre\HTTP\URLUtil;
class SystemPrincipalBackend extends AbstractBackend {
/**
* Returns a list of principals based on a prefix.
*
* This prefix will often contain something like 'principals'. You are only
* expected to return principals that are in this base path.
*
* You are expected to return at least a 'uri' for every user, you can
* return any additional properties if you wish so. Common properties are:
* {DAV:}displayname
* {http://sabredav.org/ns}email-address - This is a custom SabreDAV
* field that's actually injected in a number of other properties. If
* you have an email address, use this property.
*
* @param string $prefixPath
* @return array
*/
function getPrincipalsByPrefix($prefixPath) {
$principals = [];
if ($prefixPath === 'principals/system') {
$principals[] = [
'uri' => 'principals/system/system',
'{DAV:}displayname' => 'system',
];
}
return $principals;
}
/**
* Returns a specific principal, specified by it's path.
* The returned structure should be the exact same as from
* getPrincipalsByPrefix.
*
* @param string $path
* @return array
*/
function getPrincipalByPath($path) {
$elements = explode('/', $path);
if ($elements[0] !== 'principals') {
return null;
}
if ($elements[1] === 'system') {
$principal = [
'uri' => 'principals/system/system',
'{DAV:}displayname' => 'system',
];
return $principal;
}
return null;
}
/**
* Updates one ore more webdav properties on a principal.
*
* The list of mutations is stored in a Sabre\DAV\PropPatch object.
* To do the actual updates, you must tell this object which properties
* you're going to process with the handle() method.
*
* Calling the handle method is like telling the PropPatch object "I
* promise I can handle updating this property".
*
* Read the PropPatch documentation for more info and examples.
*
* @param string $path
* @param \Sabre\DAV\PropPatch $propPatch
* @return void
*/
function updatePrincipal($path, \Sabre\DAV\PropPatch $propPatch) {
}
/**
* This method is used to search for principals matching a set of
* properties.
*
* This search is specifically used by RFC3744's principal-property-search
* REPORT.
*
* The actual search should be a unicode-non-case-sensitive search. The
* keys in searchProperties are the WebDAV property names, while the values
* are the property values to search on.
*
* By default, if multiple properties are submitted to this method, the
* various properties should be combined with 'AND'. If $test is set to
* 'anyof', it should be combined using 'OR'.
*
* This method should simply return an array with full principal uri's.
*
* If somebody attempted to search on a property the backend does not
* support, you should simply return 0 results.
*
* You can also just return 0 results if you choose to not support
* searching at all, but keep in mind that this may stop certain features
* from working.
*
* @param string $prefixPath
* @param array $searchProperties
* @param string $test
* @return array
*/
function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') {
return [];
}
/**
* Returns the list of members for a group-principal
*
* @param string $principal
* @return array
*/
function getGroupMemberSet($principal) {
// TODO: for now the group principal has only one member, the user itself
$principal = $this->getPrincipalByPath($principal);
if (!$principal) {
throw new \Sabre\DAV\Exception('Principal not found');
}
return [$principal['uri']];
}
/**
* Returns the list of groups a principal is a member of
*
* @param string $principal
* @return array
*/
function getGroupMembership($principal) {
list($prefix, $name) = URLUtil::splitPath($principal);
if ($prefix === 'principals/system') {
$principal = $this->getPrincipalByPath($principal);
if (!$principal) {
throw new \Sabre\DAV\Exception('Principal not found');
}
return [];
}
return [];
}
/**
* Updates the list of group members for a group principal.
*
* The principals should be passed as a list of uri's.
*
* @param string $principal
* @param array $members
* @return void
*/
function setGroupMemberSet($principal, array $members) {
throw new \Sabre\DAV\Exception('Setting members of the group is not supported yet');
}
}

@ -2,36 +2,55 @@
namespace OCA\DAV;
use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CardDAV\AddressBookRoot;
use OCA\DAV\CardDAV\CardDavBackend;
use OCA\DAV\Connector\Sabre\Principal;
use OCA\DAV\DAV\SystemPrincipalBackend;
use Sabre\CalDAV\CalendarRoot;
use Sabre\CalDAV\Principal\Collection;
use Sabre\CardDAV\AddressBookRoot;
use Sabre\DAV\SimpleCollection;
class RootCollection extends SimpleCollection {
public function __construct() {
$config = \OC::$server->getConfig();
$db = \OC::$server->getDatabaseConnection();
$principalBackend = new Principal(
$config,
\OC::$server->getUserManager()
$config,
\OC::$server->getUserManager()
);
// as soon as debug mode is enabled we allow listing of principals
$disableListing = !$config->getSystemValue('debug', false);
// setup the first level of the dav tree
$principalCollection = new Collection($principalBackend);
$principalCollection->disableListing = $disableListing;
$filesCollection = new Files\RootCollection($principalBackend);
$userPrincipals = new Collection($principalBackend, 'principals/users');
$userPrincipals->disableListing = $disableListing;
$systemPrincipals = new Collection(new SystemPrincipalBackend(), 'principals/system');
$systemPrincipals->disableListing = $disableListing;
$filesCollection = new Files\RootCollection($principalBackend, 'principals/users');
$filesCollection->disableListing = $disableListing;
$cardDavBackend = new CardDavBackend(\OC::$server->getDatabaseConnection());
$addressBookRoot = new AddressBookRoot($principalBackend, $cardDavBackend);
$addressBookRoot->disableListing = $disableListing;
$caldavBackend = new CalDavBackend($db);
$calendarRoot = new CalendarRoot($principalBackend, $caldavBackend, 'principals/users');
$calendarRoot->disableListing = $disableListing;
$usersCardDavBackend = new CardDavBackend($db, $principalBackend);
$usersAddressBookRoot = new AddressBookRoot($principalBackend, $usersCardDavBackend, 'principals/users');
$usersAddressBookRoot->disableListing = $disableListing;
$systemCardDavBackend = new CardDavBackend($db, $principalBackend);
$systemAddressBookRoot = new AddressBookRoot(new SystemPrincipalBackend(), $systemCardDavBackend, 'principals/system');
$systemAddressBookRoot->disableListing = $disableListing;
$children = [
$principalCollection,
$filesCollection,
$addressBookRoot,
new SimpleCollection('principals', [
$userPrincipals,
$systemPrincipals]),
$filesCollection,
$calendarRoot,
new SimpleCollection('addressbooks', [
$usersAddressBookRoot,
$systemAddressBookRoot]),
];
parent::__construct('root', $children);

@ -17,6 +17,9 @@ class Server {
public function __construct(IRequest $request, $baseUri) {
$this->request = $request;
$this->baseUri = $baseUri;
$logger = \OC::$server->getLogger();
$dispatcher = \OC::$server->getEventDispatcher();
$root = new RootCollection();
$this->server = new \OCA\DAV\Connector\Sabre\Server($root);
@ -32,10 +35,36 @@ class Server {
$this->server->addPlugin(new BlockLegacyClientPlugin(\OC::$server->getConfig()));
$this->server->addPlugin(new Plugin($authBackend, 'ownCloud'));
$this->server->addPlugin(new \OCA\DAV\Connector\Sabre\DummyGetResponsePlugin());
$this->server->addPlugin(new \OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin('webdav', $logger));
$this->server->addPlugin(new \OCA\DAV\Connector\Sabre\LockPlugin());
$this->server->addPlugin(new \OCA\DAV\Connector\Sabre\ListenerPlugin($dispatcher));
$this->server->addPlugin(new \Sabre\DAV\Sync\Plugin());
// acl
$acl = new \Sabre\DAVACL\Plugin();
$acl->defaultUsernamePath = 'principals/users';
$this->server->addPlugin($acl);
// calendar plugins
$this->server->addPlugin(new \Sabre\CalDAV\Plugin());
$this->server->addPlugin(new \Sabre\CalDAV\ICSExportPlugin());
$senderEmail = \OCP\Util::getDefaultEmailAddress('no-reply');
$this->server->addPlugin(new \Sabre\CalDAV\Schedule\Plugin());
$this->server->addPlugin(new \Sabre\CalDAV\Schedule\IMipPlugin($senderEmail));
$this->server->addPlugin(new \Sabre\CalDAV\SharingPlugin());
$this->server->addPlugin(new \Sabre\CalDAV\Subscriptions\Plugin());
$this->server->addPlugin(new \Sabre\CalDAV\Notifications\Plugin());
$this->server->addPlugin(new CardDAV\Sharing\Plugin($authBackend, \OC::$server->getRequest()));
$this->server->addPlugin(new \Sabre\DAVACL\Plugin());
// addressbook plugins
$this->server->addPlugin(new \OCA\DAV\CardDAV\Plugin());
$this->server->addPlugin(new \Sabre\CardDAV\Plugin());
// Finder on OS X requires Class 2 WebDAV support (locking), since we do
// not provide locking we emulate it using a fake locking plugin.
if($request->isUserAgent(['/WebDAVFS/'])) {
$this->server->addPlugin(new \OCA\DAV\Connector\Sabre\FakeLockerPlugin());
}
// wait with registering these until auth is handled and the filesystem is setup
$this->server->on('beforeMethod', function () {

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<CS:share xmlns:D="DAV:" xmlns:CS="urn:ietf:params:xml:ns:carddav">
<CS:set>
<D:href>principal:principals/admin</D:href>
<CS:read-write />
</CS:set>
</CS:share>

@ -180,7 +180,7 @@
<!-- relative path to main principal collection-->
<substitution>
<key>$principalcollection:</key>
<value>$root:principals/</value>
<value>$root:principals/users/</value>
</substitution>
<!-- the core recored type collections-->
@ -569,7 +569,7 @@
<!-- relative path to user addressbook home-->
<substitution>
<key>$addressbookhome%d:</key>
<value>$addressbooks_uids:$userguid%d:</value>
<value>$addressbooks:users/$userid%d:</value>
</substitution>
<!-- relative path to user addressbook-->
<substitution>

@ -18,11 +18,18 @@ fi
# create test user
cd "$SCRIPTPATH/../../../../"
OC_PASS=user01 php occ user:add --password-from-env user01
php occ dav:create-addressbook user01 addressbook
OC_PASS=user02 php occ user:add --password-from-env user02
php occ dav:create-addressbook user02 addressbook
cd "$SCRIPTPATH/../../../../"
# run the tests
cd "$SCRIPTPATH/CalDAVTester"
PYTHONPATH="$SCRIPTPATH/pycalendar/src" python testcaldav.py --print-details-onfail -s "$SCRIPTPATH/caldavtest/config/serverinfo.xml" -o cdt.txt \
"$SCRIPTPATH/caldavtest/tests/CardDAV/current-user-principal.xml"
"$SCRIPTPATH/caldavtest/tests/CardDAV/current-user-principal.xml" \
"$SCRIPTPATH/caldavtest/tests/CardDAV/sync-report.xml"
RESULT=$?
tail "$SCRIPTPATH/../../../../data-autotest/owncloud.log"
exit $RESULT

@ -1,6 +1,8 @@
<?php
define('PHPUNIT_RUN', 1);
if (!defined('PHPUNIT_RUN')) {
define('PHPUNIT_RUN', 1);
}
require_once __DIR__.'/../../../../lib/base.php';

@ -0,0 +1,348 @@
<?php
/**
* @author Lukas Reschke <lukas@owncloud.com>
*
* @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 Tests\Connector\Sabre;
use DateTime;
use DateTimeZone;
use OCA\DAV\CalDAV\CalDavBackend;
use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet;
use Sabre\DAV\PropPatch;
use Sabre\DAV\Xml\Property\Href;
use Test\TestCase;
/**
* Class CalDavBackendTest
*
* @group DB
*
* @package Tests\Connector\Sabre
*/
class CalDavBackendTest extends TestCase {
/** @var CalDavBackend */
private $backend;
const UNIT_TEST_USER = 'caldav-unit-test';
public function setUp() {
parent::setUp();
$db = \OC::$server->getDatabaseConnection();
$this->backend = new CalDavBackend($db);
$this->tearDown();
}
public function tearDown() {
parent::tearDown();
if (is_null($this->backend)) {
return;
}
$books = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
foreach ($books as $book) {
$this->backend->deleteCalendar($book['id']);
}
$subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER);
foreach ($subscriptions as $subscription) {
$this->backend->deleteSubscription($subscription['id']);
}
}
public function testCalendarOperations() {
$calendarId = $this->createTestCalendar();
// update it's display name
$patch = new PropPatch([
'{DAV:}displayname' => 'Unit test',
'{urn:ietf:params:xml:ns:caldav}calendar-description' => 'Calendar used for unit testing'
]);
$this->backend->updateCalendar($calendarId, $patch);
$patch->commit();
$books = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
$this->assertEquals(1, count($books));
$this->assertEquals('Unit test', $books[0]['{DAV:}displayname']);
$this->assertEquals('Calendar used for unit testing', $books[0]['{urn:ietf:params:xml:ns:caldav}calendar-description']);
// delete the address book
$this->backend->deleteCalendar($books[0]['id']);
$books = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
$this->assertEquals(0, count($books));
}
public function testCalendarObjectsOperations() {
$calendarId = $this->createTestCalendar();
// create a card
$uri = $this->getUniqueID('calobj');
$calData = <<<'EOD'
BEGIN:VCALENDAR
VERSION:2.0
PRODID:ownCloud Calendar
BEGIN:VEVENT
CREATED;VALUE=DATE-TIME:20130910T125139Z
UID:47d15e3ec8
LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
SUMMARY:Test Event
DTSTART;VALUE=DATE-TIME:20130912T130000Z
DTEND;VALUE=DATE-TIME:20130912T140000Z
CLASS:PUBLIC
END:VEVENT
END:VCALENDAR
EOD;
$this->backend->createCalendarObject($calendarId, $uri, $calData);
// get all the cards
$calendarObjects = $this->backend->getCalendarObjects($calendarId);
$this->assertEquals(1, count($calendarObjects));
$this->assertEquals($calendarId, $calendarObjects[0]['calendarid']);
// get the cards
$calendarObject = $this->backend->getCalendarObject($calendarId, $uri);
$this->assertNotNull($calendarObject);
$this->assertArrayHasKey('id', $calendarObject);
$this->assertArrayHasKey('uri', $calendarObject);
$this->assertArrayHasKey('lastmodified', $calendarObject);
$this->assertArrayHasKey('etag', $calendarObject);
$this->assertArrayHasKey('size', $calendarObject);
$this->assertEquals($calData, $calendarObject['calendardata']);
// update the card
$calData = <<<'EOD'
BEGIN:VCALENDAR
VERSION:2.0
PRODID:ownCloud Calendar
BEGIN:VEVENT
CREATED;VALUE=DATE-TIME:20130910T125139Z
UID:47d15e3ec8
LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
SUMMARY:Test Event
DTSTART;VALUE=DATE-TIME:20130912T130000Z
DTEND;VALUE=DATE-TIME:20130912T140000Z
END:VEVENT
END:VCALENDAR
EOD;
$this->backend->updateCalendarObject($calendarId, $uri, $calData);
$calendarObject = $this->backend->getCalendarObject($calendarId, $uri);
$this->assertEquals($calData, $calendarObject['calendardata']);
// delete the card
$this->backend->deleteCalendarObject($calendarId, $uri);
$calendarObjects = $this->backend->getCalendarObjects($calendarId);
$this->assertEquals(0, count($calendarObjects));
}
public function testMultiCalendarObjects() {
$calendarId = $this->createTestCalendar();
// create an event
$calData = <<<'EOD'
BEGIN:VCALENDAR
VERSION:2.0
PRODID:ownCloud Calendar
BEGIN:VEVENT
CREATED;VALUE=DATE-TIME:20130910T125139Z
UID:47d15e3ec8
LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
SUMMARY:Test Event
DTSTART;VALUE=DATE-TIME:20130912T130000Z
DTEND;VALUE=DATE-TIME:20130912T140000Z
CLASS:PUBLIC
END:VEVENT
END:VCALENDAR
EOD;
$uri0 = $this->getUniqueID('card');
$this->backend->createCalendarObject($calendarId, $uri0, $calData);
$uri1 = $this->getUniqueID('card');
$this->backend->createCalendarObject($calendarId, $uri1, $calData);
$uri2 = $this->getUniqueID('card');
$this->backend->createCalendarObject($calendarId, $uri2, $calData);
// get all the cards
$calendarObjects = $this->backend->getCalendarObjects($calendarId);
$this->assertEquals(3, count($calendarObjects));
// get the cards
$calendarObjects = $this->backend->getMultipleCalendarObjects($calendarId, [$uri1, $uri2]);
$this->assertEquals(2, count($calendarObjects));
foreach($calendarObjects as $card) {
$this->assertArrayHasKey('id', $card);
$this->assertArrayHasKey('uri', $card);
$this->assertArrayHasKey('lastmodified', $card);
$this->assertArrayHasKey('etag', $card);
$this->assertArrayHasKey('size', $card);
$this->assertEquals($calData, $card['calendardata']);
}
// delete the card
$this->backend->deleteCalendarObject($calendarId, $uri0);
$this->backend->deleteCalendarObject($calendarId, $uri1);
$this->backend->deleteCalendarObject($calendarId, $uri2);
$calendarObjects = $this->backend->getCalendarObjects($calendarId);
$this->assertEquals(0, count($calendarObjects));
}
/**
* @dataProvider providesCalendarQueryParameters
*/
public function testCalendarQuery($expectedEventsInResult, $propFilters, $compFilter) {
$calendarId = $this->createTestCalendar();
$events = [];
$events[0] = $this->createEvent($calendarId, '20130912T130000Z', '20130912T140000Z');
$events[1] = $this->createEvent($calendarId, '20130912T150000Z', '20130912T170000Z');
$events[2] = $this->createEvent($calendarId, '20130912T173000Z', '20130912T220000Z');
$result = $this->backend->calendarQuery($calendarId, [
'name' => '',
'prop-filters' => $propFilters,
'comp-filters' => $compFilter
]);
$expectedEventsInResult = array_map(function($index) use($events) {
return $events[$index];
}, $expectedEventsInResult);
$this->assertEquals($expectedEventsInResult, $result, '', 0.0, 10, true);
}
public function testGetCalendarObjectByUID() {
$calendarId = $this->createTestCalendar();
$this->createEvent($calendarId, '20130912T130000Z', '20130912T140000Z');
$co = $this->backend->getCalendarObjectByUID(self::UNIT_TEST_USER, '47d15e3ec8');
$this->assertNotNull($co);
}
public function providesCalendarQueryParameters() {
return [
'all' => [[0, 1, 2], [], []],
'only-todos' => [[], ['name' => 'VTODO'], []],
'only-events' => [[0, 1, 2], [], [['name' => 'VEVENT', 'is-not-defined' => false, 'comp-filters' => [], 'time-range' => ['start' => null, 'end' => null], 'prop-filters' => []]],],
'start' => [[1, 2], [], [['name' => 'VEVENT', 'is-not-defined' => false, 'comp-filters' => [], 'time-range' => ['start' => new DateTime('2013-09-12 14:00:00', new DateTimeZone('UTC')), 'end' => null], 'prop-filters' => []]],],
'end' => [[0], [], [['name' => 'VEVENT', 'is-not-defined' => false, 'comp-filters' => [], 'time-range' => ['start' => null, 'end' => new DateTime('2013-09-12 14:00:00', new DateTimeZone('UTC'))], 'prop-filters' => []]],],
];
}
private function createTestCalendar() {
$this->backend->createCalendar(self::UNIT_TEST_USER, 'Example', [
'{http://apple.com/ns/ical/}calendar-color' => '#1C4587FF'
]);
$calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
$this->assertEquals(1, count($calendars));
$this->assertEquals(self::UNIT_TEST_USER, $calendars[0]['principaluri']);
/** @var SupportedCalendarComponentSet $components */
$components = $calendars[0]['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'];
$this->assertEquals(['VEVENT','VTODO'], $components->getValue());
$color = $calendars[0]['{http://apple.com/ns/ical/}calendar-color'];
$this->assertEquals('#1C4587FF', $color);
$this->assertEquals('Example', $calendars[0]['uri']);
$this->assertEquals('Example', $calendars[0]['{DAV:}displayname']);
$calendarId = $calendars[0]['id'];
return $calendarId;
}
private function createEvent($calendarId, $start = '20130912T130000Z', $end = '20130912T140000Z') {
$calData = <<<EOD
BEGIN:VCALENDAR
VERSION:2.0
PRODID:ownCloud Calendar
BEGIN:VEVENT
CREATED;VALUE=DATE-TIME:20130910T125139Z
UID:47d15e3ec8
LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z
DTSTAMP;VALUE=DATE-TIME:20130910T125139Z
SUMMARY:Test Event
DTSTART;VALUE=DATE-TIME:$start
DTEND;VALUE=DATE-TIME:$end
CLASS:PUBLIC
END:VEVENT
END:VCALENDAR
EOD;
$uri0 = $this->getUniqueID('event');
$this->backend->createCalendarObject($calendarId, $uri0, $calData);
return $uri0;
}
public function testSyncSupport() {
$calendarId = $this->createTestCalendar();
// fist call without synctoken
$changes = $this->backend->getChangesForCalendar($calendarId, '', 1);
$syncToken = $changes['syncToken'];
// add a change
$event = $this->createEvent($calendarId, '20130912T130000Z', '20130912T140000Z');
// look for changes
$changes = $this->backend->getChangesForCalendar($calendarId, $syncToken, 1);
$this->assertEquals($event, $changes['added'][0]);
}
public function testSubscriptions() {
$id = $this->backend->createSubscription(self::UNIT_TEST_USER, 'Subscription', [
'{http://calendarserver.org/ns/}source' => new Href('test-source')
]);
$subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER);
$this->assertEquals(1, count($subscriptions));
$this->assertEquals($id, $subscriptions[0]['id']);
$patch = new PropPatch([
'{DAV:}displayname' => 'Unit test',
]);
$this->backend->updateSubscription($id, $patch);
$patch->commit();
$subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER);
$this->assertEquals(1, count($subscriptions));
$this->assertEquals($id, $subscriptions[0]['id']);
$this->assertEquals('Unit test', $subscriptions[0]['{DAV:}displayname']);
$this->backend->deleteSubscription($id);
$subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER);
$this->assertEquals(0, count($subscriptions));
}
public function testScheduling() {
$this->backend->createSchedulingObject(self::UNIT_TEST_USER, 'Sample Schedule', '');
$sos = $this->backend->getSchedulingObjects(self::UNIT_TEST_USER);
$this->assertEquals(1, count($sos));
$so = $this->backend->getSchedulingObject(self::UNIT_TEST_USER, 'Sample Schedule');
$this->assertNotNull($so);
$this->backend->deleteSchedulingObject(self::UNIT_TEST_USER, 'Sample Schedule');
$sos = $this->backend->getSchedulingObjects(self::UNIT_TEST_USER);
$this->assertEquals(0, count($sos));
}
}

@ -24,6 +24,13 @@ use OCA\DAV\CardDAV\CardDavBackend;
use Sabre\DAV\PropPatch;
use Test\TestCase;
/**
* Class CardDavBackendTest
*
* @group DB
*
* @package OCA\DAV\Tests\Unit\CardDAV
*/
class CardDavBackendTest extends TestCase {
/** @var CardDavBackend */
@ -31,12 +38,20 @@ class CardDavBackendTest extends TestCase {
const UNIT_TEST_USER = 'carddav-unit-test';
public function setUp() {
parent::setUp();
$principal = $this->getMockBuilder('OCA\DAV\Connector\Sabre\Principal')
->disableOriginalConstructor()
->setMethods(['getPrincipalByPath'])
->getMock();
$principal->method('getPrincipalByPath')
->willReturn([
'uri' => 'principals/best-friend'
]);
$db = \OC::$server->getDatabaseConnection();
$this->backend = new CardDavBackend($db);
$this->backend = new CardDavBackend($db, $principal);
$this->tearDown();
}
@ -178,4 +193,32 @@ class CardDavBackendTest extends TestCase {
$changes = $this->backend->getChangesForAddressBook($bookId, $syncToken, 1);
$this->assertEquals($uri0, $changes['added'][0]);
}
public function testSharing() {
$this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []);
$books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER);
$this->assertEquals(1, count($books));
$this->backend->updateShares('Example', [['href' => 'principal:principals/best-friend']], []);
$shares = $this->backend->getShares('Example');
$this->assertEquals(1, count($shares));
// adding the same sharee again has no effect
$this->backend->updateShares('Example', [['href' => 'principal:principals/best-friend']], []);
$shares = $this->backend->getShares('Example');
$this->assertEquals(1, count($shares));
$books = $this->backend->getAddressBooksForUser('principals/best-friend');
$this->assertEquals(1, count($books));
$this->backend->updateShares('Example', [], [['href' => 'principal:principals/best-friend']]);
$shares = $this->backend->getShares('Example');
$this->assertEquals(0, count($shares));
$books = $this->backend->getAddressBooksForUser('principals/best-friend');
$this->assertEquals(0, count($books));
}
}

@ -0,0 +1,136 @@
<?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\Tests\Unit;
use OCA\DAV\CardDAV\Converter;
use Test\TestCase;
class ConverterTests extends TestCase {
/**
* @dataProvider providesNewUsers
*/
public function testCreation($expectedVCard, $displayName = null, $eMailAddress = null, $cloudId = null) {
$user = $this->getUserMock($displayName, $eMailAddress, $cloudId);
$converter = new Converter();
$vCard = $converter->createCardFromUser($user);
$cardData = $vCard->serialize();
$this->assertEquals($expectedVCard, $cardData);
}
public function providesNewUsers() {
return [
["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.7//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nPHOTO;ENCODING=b;TYPE=JPEG:MTIzNDU2Nzg5\r\nEND:VCARD\r\n"],
["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.7//EN\r\nUID:12345\r\nFN:Dr. Foo Bar\r\nN:Bar;Dr.;Foo;;\r\nPHOTO;ENCODING=b;TYPE=JPEG:MTIzNDU2Nzg5\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.7//EN\r\nUID:12345\r\nFN:Dr. Foo Bar\r\nN:Bar;Dr.;Foo;;\r\nEMAIL;TYPE=OTHER:foo@bar.net\r\nPHOTO;ENCODING=b;TYPE=JPEG:MTIzNDU2Nzg5\r\nEND:VCARD\r\n", "Dr. Foo Bar", "foo@bar.net"],
["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.7//EN\r\nUID:12345\r\nFN:Dr. Foo Bar\r\nN:Bar;Dr.;Foo;;\r\nCLOUD:foo@bar.net\r\nPHOTO;ENCODING=b;TYPE=JPEG:MTIzNDU2Nzg5\r\nEND:VCARD\r\n", "Dr. Foo Bar", null, "foo@bar.net"],
];
}
/**
* @dataProvider providesNewUsers
*/
public function testUpdateOfUnchangedUser($expectedVCard, $displayName = null, $eMailAddress = null, $cloudId = null) {
$user = $this->getUserMock($displayName, $eMailAddress, $cloudId);
$converter = new Converter();
$vCard = $converter->createCardFromUser($user);
$updated = $converter->updateCard($vCard, $user);
$this->assertFalse($updated);
$cardData = $vCard->serialize();
$this->assertEquals($expectedVCard, $cardData);
}
/**
* @dataProvider providesUsersForUpdateOfRemovedElement
*/
public function testUpdateOfRemovedElement($expectedVCard, $displayName = null, $eMailAddress = null, $cloudId = null) {
$user = $this->getUserMock($displayName, $eMailAddress, $cloudId);
$converter = new Converter();
$vCard = $converter->createCardFromUser($user);
$user1 = $this->getMockBuilder('OCP\IUser')->disableOriginalConstructor()->getMock();
$user1->method('getUID')->willReturn('12345');
$user1->method('getDisplayName')->willReturn(null);
$user1->method('getEMailAddress')->willReturn(null);
$user1->method('getCloudId')->willReturn(null);
$user1->method('getAvatarImage')->willReturn(null);
$updated = $converter->updateCard($vCard, $user1);
$this->assertTrue($updated);
$cardData = $vCard->serialize();
$this->assertEquals($expectedVCard, $cardData);
}
public function providesUsersForUpdateOfRemovedElement() {
return [
["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.7//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.7//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nEND:VCARD\r\n", "Dr. Foo Bar", "foo@bar.net"],
["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.7//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nEND:VCARD\r\n", "Dr. Foo Bar", null, "foo@bar.net"],
];
}
/**
* @dataProvider providesNames
* @param $expected
* @param $fullName
*/
public function testNameSplitter($expected, $fullName) {
$converter = new Converter();
$r = $converter->splitFullName($fullName);
$r = implode(';', $r);
$this->assertEquals($expected, $r);
}
public function providesNames() {
return [
['Sauron;;;;', 'Sauron'],
['Baggins;Bilbo;;;', 'Bilbo Baggins'],
['Tolkien;John;Ronald Reuel;;', 'John Ronald Reuel Tolkien'],
];
}
/**
* @param $displayName
* @param $eMailAddress
* @param $cloudId
* @return \PHPUnit_Framework_MockObject_MockObject
*/
protected function getUserMock($displayName, $eMailAddress, $cloudId) {
$image0 = $this->getMockBuilder('OCP\IImage')->disableOriginalConstructor()->getMock();
$image0->method('mimeType')->willReturn('JPEG');
$image0->method('data')->willReturn('123456789');
$user = $this->getMockBuilder('OCP\IUser')->disableOriginalConstructor()->getMock();
$user->method('getUID')->willReturn('12345');
$user->method('getDisplayName')->willReturn($displayName);
$user->method('getEMailAddress')->willReturn($eMailAddress);
$user->method('getCloudId')->willReturn($cloudId);
$user->method('getAvatarImage')->willReturn($image0);
return $user;
}
}

@ -0,0 +1,173 @@
<?php
/**
* @author Lukas Reschke <lukas@owncloud.com>
*
* @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\Tests\Unit\Connector\Sabre;
use OCA\DAV\Connector\Sabre\FakeLockerPlugin;
use Sabre\HTTP\Response;
use Test\TestCase;
/**
* Class FakeLockerPluginTest
*
* @package OCA\DAV\Tests\Unit\Connector\Sabre
*/
class FakeLockerPluginTest extends TestCase {
/** @var FakeLockerPlugin */
private $fakeLockerPlugin;
public function setUp() {
parent::setUp();
$this->fakeLockerPlugin = new FakeLockerPlugin();
}
public function testInitialize() {
/** @var \Sabre\DAV\Server $server */
$server = $this->getMock('\Sabre\DAV\Server');
$server
->expects($this->at(0))
->method('on')
->with('method:LOCK', [$this->fakeLockerPlugin, 'fakeLockProvider'], 1);
$server
->expects($this->at(1))
->method('on')
->with('method:UNLOCK', [$this->fakeLockerPlugin, 'fakeUnlockProvider'], 1);
$server
->expects($this->at(2))
->method('on')
->with('propFind', [$this->fakeLockerPlugin, 'propFind']);
$server
->expects($this->at(3))
->method('on')
->with('validateTokens', [$this->fakeLockerPlugin, 'validateTokens']);
$this->fakeLockerPlugin->initialize($server);
}
public function testGetHTTPMethods() {
$expected = [
'LOCK',
'UNLOCK',
];
$this->assertSame($expected, $this->fakeLockerPlugin->getHTTPMethods('Test'));
}
public function testGetFeatures() {
$expected = [
2,
];
$this->assertSame($expected, $this->fakeLockerPlugin->getFeatures());
}
public function testPropFind() {
$propFind = $this->getMockBuilder('\Sabre\DAV\PropFind')
->disableOriginalConstructor()
->getMock();
$node = $this->getMock('\Sabre\DAV\INode');
$propFind->expects($this->at(0))
->method('handle')
->with('{DAV:}supportedlock');
$propFind->expects($this->at(1))
->method('handle')
->with('{DAV:}lockdiscovery');
$this->fakeLockerPlugin->propFind($propFind, $node);
}
public function tokenDataProvider() {
return [
[
[
[
'tokens' => [
[
'token' => 'aToken',
'validToken' => false,
],
[],
[
'token' => 'opaquelocktoken:asdf',
'validToken' => false,
]
],
]
],
[
[
'tokens' => [
[
'token' => 'aToken',
'validToken' => false,
],
[],
[
'token' => 'opaquelocktoken:asdf',
'validToken' => true,
]
],
]
],
]
];
}
/**
* @dataProvider tokenDataProvider
* @param array $input
* @param array $expected
*/
public function testValidateTokens(array $input, array $expected) {
$request = $this->getMock('\Sabre\HTTP\RequestInterface');
$this->fakeLockerPlugin->validateTokens($request, $input);
$this->assertSame($expected, $input);
}
public function testFakeLockProvider() {
$request = $this->getMock('\Sabre\HTTP\RequestInterface');
$response = new Response();
$server = $this->getMock('\Sabre\DAV\Server');
$this->fakeLockerPlugin->initialize($server);
$request->expects($this->exactly(2))
->method('getPath')
->will($this->returnValue('MyPath'));
$this->assertSame(false, $this->fakeLockerPlugin->fakeLockProvider($request, $response));
$expectedXml = '<?xml version="1.0" encoding="utf-8"?><d:prop xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"><d:lockdiscovery><d:activelock><d:lockscope><d:exclusive/></d:lockscope><d:locktype><d:write/></d:locktype><d:lockroot><d:href>MyPath</d:href></d:lockroot><d:depth>infinity</d:depth><d:timeout>Second-1800</d:timeout><d:locktoken><d:href>opaquelocktoken:fe4f7f2437b151fbcb4e9f5c8118c6b1</d:href></d:locktoken><d:owner/></d:activelock></d:lockdiscovery></d:prop>';
$this->assertXmlStringEqualsXmlString($expectedXml, $response->getBody());
}
public function testFakeUnlockProvider() {
$request = $this->getMock('\Sabre\HTTP\RequestInterface');
$response = $this->getMock('\Sabre\HTTP\ResponseInterface');
$response->expects($this->once())
->method('setStatus')
->with('204');
$response->expects($this->once())
->method('setHeader')
->with('Content-Length', '0');
$this->assertSame(false, $this->fakeLockerPlugin->fakeUnlockProvider($request, $response));
}
}

@ -249,9 +249,12 @@ class Auth extends TestCase {
}
public function testAuthenticateAlreadyLoggedIn() {
$server = $this->getMockBuilder('\Sabre\DAV\Server')
->disableOriginalConstructor()
->getMock();
$request = $this->getMockBuilder('Sabre\HTTP\RequestInterface')
->disableOriginalConstructor()
->getMock();
$response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface')
->disableOriginalConstructor()
->getMock();
$this->userSession
->expects($this->once())
->method('isLoggedIn')
@ -275,13 +278,10 @@ class Auth extends TestCase {
->expects($this->once())
->method('close');
$this->assertTrue($this->auth->authenticate($server, 'TestRealm'));
$response = $this->auth->check($request, $response);
$this->assertEquals([true, 'principals/users/MyWrongDavUser'], $response);
}
/**
* @expectedException \Sabre\DAV\Exception\NotAuthenticated
* @expectedExceptionMessage No basic authentication headers were found
*/
public function testAuthenticateNoBasicAuthenticateHeadersProvided() {
$server = $this->getMockBuilder('\Sabre\DAV\Server')
->disableOriginalConstructor()
@ -292,7 +292,59 @@ class Auth extends TestCase {
$server->httpResponse = $this->getMockBuilder('\Sabre\HTTP\ResponseInterface')
->disableOriginalConstructor()
->getMock();
$this->auth->authenticate($server, 'TestRealm');
$response = $this->auth->check($server->httpRequest, $server->httpResponse);
$this->assertEquals([false, 'No \'Authorization: Basic\' header found. Either the client didn\'t send one, or the server is mis-configured'], $response);
}
/**
* @expectedException \Sabre\DAV\Exception\NotAuthenticated
* @expectedExceptionMessage Cannot authenticate over ajax calls
*/
public function testAuthenticateNoBasicAuthenticateHeadersProvidedWithAjax() {
/** @var \Sabre\HTTP\RequestInterface $httpRequest */
$httpRequest = $this->getMockBuilder('\Sabre\HTTP\RequestInterface')
->disableOriginalConstructor()
->getMock();
/** @var \Sabre\HTTP\ResponseInterface $httpResponse */
$httpResponse = $this->getMockBuilder('\Sabre\HTTP\ResponseInterface')
->disableOriginalConstructor()
->getMock();
$this->userSession
->expects($this->any())
->method('isLoggedIn')
->will($this->returnValue(false));
$httpRequest
->expects($this->once())
->method('getHeader')
->with('X-Requested-With')
->will($this->returnValue('XMLHttpRequest'));
$this->auth->check($httpRequest, $httpResponse);
}
public function testAuthenticateNoBasicAuthenticateHeadersProvidedWithAjaxButUserIsStillLoggedIn() {
/** @var \Sabre\HTTP\RequestInterface $httpRequest */
$httpRequest = $this->getMockBuilder('\Sabre\HTTP\RequestInterface')
->disableOriginalConstructor()
->getMock();
/** @var \Sabre\HTTP\ResponseInterface $httpResponse */
$httpResponse = $this->getMockBuilder('\Sabre\HTTP\ResponseInterface')
->disableOriginalConstructor()
->getMock();
$this->userSession
->expects($this->any())
->method('isLoggedIn')
->will($this->returnValue(true));
$this->session
->expects($this->once())
->method('get')
->with('AUTHENTICATED_TO_DAV_BACKEND')
->will($this->returnValue('MyTestUser'));
$httpRequest
->expects($this->once())
->method('getHeader')
->with('Authorization')
->will($this->returnValue(null));
$this->auth->check($httpRequest, $httpResponse);
}
public function testAuthenticateValidCredentials() {
@ -303,7 +355,12 @@ class Auth extends TestCase {
->disableOriginalConstructor()
->getMock();
$server->httpRequest
->expects($this->once())
->expects($this->at(0))
->method('getHeader')
->with('X-Requested-With')
->will($this->returnValue(null));
$server->httpRequest
->expects($this->at(1))
->method('getHeader')
->with('Authorization')
->will($this->returnValue('basic dXNlcm5hbWU6cGFzc3dvcmQ='));
@ -325,13 +382,10 @@ class Auth extends TestCase {
->expects($this->exactly(2))
->method('getUser')
->will($this->returnValue($user));
$this->assertTrue($this->auth->authenticate($server, 'TestRealm'));
$response = $this->auth->check($server->httpRequest, $server->httpResponse);
$this->assertEquals([true, 'principals/users/username'], $response);
}
/**
* @expectedException \Sabre\DAV\Exception\NotAuthenticated
* @expectedExceptionMessage Username or password does not match
*/
public function testAuthenticateInvalidCredentials() {
$server = $this->getMockBuilder('\Sabre\DAV\Server')
->disableOriginalConstructor()
@ -340,7 +394,12 @@ class Auth extends TestCase {
->disableOriginalConstructor()
->getMock();
$server->httpRequest
->expects($this->once())
->expects($this->at(0))
->method('getHeader')
->with('X-Requested-With')
->will($this->returnValue(null));
$server->httpRequest
->expects($this->at(1))
->method('getHeader')
->with('Authorization')
->will($this->returnValue('basic dXNlcm5hbWU6cGFzc3dvcmQ='));
@ -352,6 +411,7 @@ class Auth extends TestCase {
->method('login')
->with('username', 'password')
->will($this->returnValue(false));
$this->auth->authenticate($server, 'TestRealm');
$response = $this->auth->check($server->httpRequest, $server->httpResponse);
$this->assertEquals([false, 'Username or password was incorrect'], $response);
}
}

@ -8,6 +8,14 @@ namespace OCA\DAV\Tests\Unit\Connector\Sabre;
* later.
* See the COPYING-README file.
*/
/**
* Class CustomPropertiesBackend
*
* @group DB
*
* @package Tests\Connector\Sabre
*/
class CustomPropertiesBackend extends \Test\TestCase {
/**

@ -9,6 +9,8 @@
namespace OCA\DAV\Tests\Unit\Connector\Sabre;
use OCP\Files\ForbiddenException;
class Directory extends \Test\TestCase {
/** @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject */
@ -48,6 +50,25 @@ class Directory extends \Test\TestCase {
$dir->delete();
}
/**
* @expectedException \OCA\DAV\Connector\Sabre\Exception\Forbidden
*/
public function testDeleteForbidden() {
// deletion allowed
$this->info->expects($this->once())
->method('isDeletable')
->will($this->returnValue(true));
// but fails
$this->view->expects($this->once())
->method('rmdir')
->with('sub')
->willThrowException(new ForbiddenException('', true));
$dir = $this->getDir('sub');
$dir->delete();
}
/**
*
*/

@ -0,0 +1,44 @@
<?php
/**
* Copyright (c) 2015 Thomas Müller <deepdiver@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OCA\DAV\Tests\Unit\Connector\Sabre\Exception;
use OCA\DAV\Connector\Sabre\Exception\Forbidden;
class ForbiddenTest extends \Test\TestCase {
public function testSerialization() {
// create xml doc
$DOM = new \DOMDocument('1.0','utf-8');
$DOM->formatOutput = true;
$error = $DOM->createElementNS('DAV:','d:error');
$error->setAttribute('xmlns:s', \Sabre\DAV\Server::NS_SABREDAV);
$DOM->appendChild($error);
// serialize the exception
$message = "1234567890";
$retry = false;
$expectedXml = <<<EOD
<?xml version="1.0" encoding="utf-8"?>
<d:error xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:o="http://owncloud.org/ns">
<o:retry xmlns:o="o:">false</o:retry>
<o:reason xmlns:o="o:">1234567890</o:reason>
</d:error>
EOD;
$ex = new Forbidden($message, $retry);
$server = $this->getMock('Sabre\DAV\Server');
$ex->serialize($server, $error);
// assert
$xml = $DOM->saveXML();
$this->assertEquals($expectedXml, $xml);
}
}

@ -1,15 +1,15 @@
<?php
namespace OCA\DAV\Tests\Unit\Connector\Sabre\Exception;
use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
/**
* Copyright (c) 2015 Thomas Müller <deepdiver@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OCA\DAV\Tests\Unit\Connector\Sabre\Exception;
use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
class InvalidPathTest extends \Test\TestCase {
public function testSerialization() {

@ -9,10 +9,18 @@
namespace OCA\DAV\Tests\Unit\Connector\Sabre;
use OC\Files\Storage\Local;
use OCP\Files\ForbiddenException;
use Test\HookHelper;
use OC\Files\Filesystem;
use OCP\Lock\ILockingProvider;
/**
* Class File
*
* @group DB
*
* @package Test\Connector\Sabre
*/
class File extends \Test\TestCase {
/**
@ -40,6 +48,9 @@ class File extends \Test\TestCase {
parent::tearDown();
}
/**
* @param string $string
*/
private function getStream($string) {
$stream = fopen('php://temp', 'r+');
fwrite($stream, $string);
@ -72,6 +83,10 @@ class File extends \Test\TestCase {
new \OCP\Files\InvalidPathException(),
'Sabre\DAV\Exception\Forbidden'
],
[
new \OCP\Files\ForbiddenException('', true),
'OCA\DAV\Connector\Sabre\Exception\Forbidden'
],
[
new \OCP\Files\LockNotAcquiredException('/test.txt', 1),
'OCA\DAV\Connector\Sabre\Exception\FileLocked'
@ -234,7 +249,7 @@ class File extends \Test\TestCase {
* @param string $path path to put the file into
* @param string $viewRoot root to use for the view
*
* @return result of the PUT operaiton which is usually the etag
* @return null|string of the PUT operaiton which is usually the etag
*/
private function doPut($path, $viewRoot = null) {
$view = \OC\Files\Filesystem::getView();
@ -690,6 +705,29 @@ class File extends \Test\TestCase {
$file->delete();
}
/**
* @expectedException \OCA\DAV\Connector\Sabre\Exception\Forbidden
*/
public function testDeleteThrowsWhenDeletionThrows() {
// setup
$view = $this->getMock('\OC\Files\View',
array());
// but fails
$view->expects($this->once())
->method('unlink')
->willThrowException(new ForbiddenException('', true));
$info = new \OC\Files\FileInfo('/test.txt', null, null, array(
'permissions' => \OCP\Constants::PERMISSION_ALL
), null);
$file = new \OCA\DAV\Connector\Sabre\File($view, $info);
// action
$file->delete();
}
/**
* Asserts hook call
*
@ -835,4 +873,22 @@ class File extends \Test\TestCase {
$file->get();
}
/**
* @expectedException \OCA\DAV\Connector\Sabre\Exception\Forbidden
*/
public function testGetFopenThrows() {
$view = $this->getMock('\OC\Files\View', ['fopen'], array());
$view->expects($this->atLeastOnce())
->method('fopen')
->willThrowException(new ForbiddenException('', true));
$info = new \OC\Files\FileInfo('/test.txt', null, null, array(
'permissions' => \OCP\Constants::PERMISSION_ALL
), null);
$file = new \OCA\DAV\Connector\Sabre\File($view, $info);
$file->get();
}
}

@ -11,10 +11,13 @@ namespace OCA\DAV\Tests\Unit\Connector\Sabre;
class FilesPlugin extends \Test\TestCase {
const GETETAG_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::GETETAG_PROPERTYNAME;
const FILEID_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::FILEID_PROPERTYNAME;
const INTERNAL_FILEID_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::INTERNAL_FILEID_PROPERTYNAME;
const SIZE_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::SIZE_PROPERTYNAME;
const PERMISSIONS_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::PERMISSIONS_PROPERTYNAME;
const LASTMODIFIED_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::LASTMODIFIED_PROPERTYNAME;
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;
/**
* @var \Sabre\DAV\Server
@ -52,6 +55,9 @@ class FilesPlugin extends \Test\TestCase {
$this->plugin->initialize($this->server);
}
/**
* @param string $class
*/
private function createTestNode($class) {
$node = $this->getMockBuilder($class)
->disableOriginalConstructor()
@ -67,7 +73,10 @@ class FilesPlugin extends \Test\TestCase {
$node->expects($this->any())
->method('getFileId')
->will($this->returnValue(123));
->will($this->returnValue('00000123instanceid'));
$node->expects($this->any())
->method('getInternalFileId')
->will($this->returnValue('123'));
$node->expects($this->any())
->method('getEtag')
->will($this->returnValue('"abc"'));
@ -88,16 +97,33 @@ class FilesPlugin extends \Test\TestCase {
array(
self::GETETAG_PROPERTYNAME,
self::FILEID_PROPERTYNAME,
self::INTERNAL_FILEID_PROPERTYNAME,
self::SIZE_PROPERTYNAME,
self::PERMISSIONS_PROPERTYNAME,
self::DOWNLOADURL_PROPERTYNAME,
self::OWNER_ID_PROPERTYNAME,
self::OWNER_DISPLAY_NAME_PROPERTYNAME
),
0
);
$user = $this->getMockBuilder('\OC\User\User')
->disableOriginalConstructor()->getMock();
$user
->expects($this->once())
->method('getUID')
->will($this->returnValue('foo'));
$user
->expects($this->once())
->method('getDisplayName')
->will($this->returnValue('M. Foo'));
$node->expects($this->once())
->method('getDirectDownload')
->will($this->returnValue(array('url' => 'http://example.com/')));
$node->expects($this->exactly(2))
->method('getOwner')
->will($this->returnValue($user));
$node->expects($this->never())
->method('getSize');
@ -107,10 +133,13 @@ class FilesPlugin extends \Test\TestCase {
);
$this->assertEquals('"abc"', $propFind->get(self::GETETAG_PROPERTYNAME));
$this->assertEquals(123, $propFind->get(self::FILEID_PROPERTYNAME));
$this->assertEquals('00000123instanceid', $propFind->get(self::FILEID_PROPERTYNAME));
$this->assertEquals('123', $propFind->get(self::INTERNAL_FILEID_PROPERTYNAME));
$this->assertEquals(null, $propFind->get(self::SIZE_PROPERTYNAME));
$this->assertEquals('DWCKMSR', $propFind->get(self::PERMISSIONS_PROPERTYNAME));
$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());
}
@ -166,7 +195,7 @@ class FilesPlugin extends \Test\TestCase {
);
$this->assertEquals('"abc"', $propFind->get(self::GETETAG_PROPERTYNAME));
$this->assertEquals(123, $propFind->get(self::FILEID_PROPERTYNAME));
$this->assertEquals('00000123instanceid', $propFind->get(self::FILEID_PROPERTYNAME));
$this->assertEquals(1025, $propFind->get(self::SIZE_PROPERTYNAME));
$this->assertEquals('DWCKMSR', $propFind->get(self::PERMISSIONS_PROPERTYNAME));
$this->assertEquals(null, $propFind->get(self::DOWNLOADURL_PROPERTYNAME));
@ -207,6 +236,36 @@ class FilesPlugin extends \Test\TestCase {
$this->assertEquals(200, $result[self::GETETAG_PROPERTYNAME]);
}
public function testUpdatePropsForbidden() {
$node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File');
$propPatch = new \Sabre\DAV\PropPatch(array(
self::OWNER_ID_PROPERTYNAME => 'user2',
self::OWNER_DISPLAY_NAME_PROPERTYNAME => 'User Two',
self::FILEID_PROPERTYNAME => 12345,
self::PERMISSIONS_PROPERTYNAME => 'C',
self::SIZE_PROPERTYNAME => 123,
self::DOWNLOADURL_PROPERTYNAME => 'http://example.com/',
));
$this->plugin->handleUpdateProperties(
'/dummypath',
$propPatch
);
$propPatch->commit();
$this->assertEmpty($propPatch->getRemainingMutations());
$result = $propPatch->getResult();
$this->assertEquals(403, $result[self::OWNER_ID_PROPERTYNAME]);
$this->assertEquals(403, $result[self::OWNER_DISPLAY_NAME_PROPERTYNAME]);
$this->assertEquals(403, $result[self::FILEID_PROPERTYNAME]);
$this->assertEquals(403, $result[self::PERMISSIONS_PROPERTYNAME]);
$this->assertEquals(403, $result[self::SIZE_PROPERTYNAME]);
$this->assertEquals(403, $result[self::DOWNLOADURL_PROPERTYNAME]);
}
/**
* Testcase from https://github.com/owncloud/core/issues/5251
*

@ -41,6 +41,13 @@ class TestDoubleFileView extends \OC\Files\View {
}
}
/**
* Class ObjectTree
*
* @group DB
*
* @package OCA\DAV\Tests\Unit\Connector\Sabre
*/
class ObjectTree extends \Test\TestCase {
/**

@ -41,43 +41,45 @@ class Principal extends \Test\TestCase {
$fooUser = $this->getMockBuilder('\OC\User\User')
->disableOriginalConstructor()->getMock();
$fooUser
->expects($this->exactly(3))
->method('getUID')
->will($this->returnValue('foo'));
->expects($this->exactly(1))
->method('getUID')
->will($this->returnValue('foo'));
$fooUser
->expects($this->exactly(1))
->method('getDisplayName')
->will($this->returnValue('Dr. Foo-Bar'));
$fooUser
->expects($this->exactly(1))
->method('getEMailAddress')
->will($this->returnValue(''));
$barUser = $this->getMockBuilder('\OC\User\User')
->disableOriginalConstructor()->getMock();
$barUser
->expects($this->exactly(3))
->expects($this->exactly(1))
->method('getUID')
->will($this->returnValue('bar'));
$barUser
->expects($this->exactly(1))
->method('getEMailAddress')
->will($this->returnValue('bar@owncloud.org'));
$this->userManager
->expects($this->once())
->method('search')
->with('')
->will($this->returnValue([$fooUser, $barUser]));
$this->config
->expects($this->at(0))
->method('getUserValue')
->with('foo', 'settings', 'email')
->will($this->returnValue(''));
$this->config
->expects($this->at(1))
->method('getUserValue')
->with('bar', 'settings', 'email')
->will($this->returnValue('bar@owncloud.org'));
$expectedResponse = [
0 => [
'uri' => 'principals/foo',
'{DAV:}displayname' => 'foo'
'uri' => 'principals/users/foo',
'{DAV:}displayname' => 'Dr. Foo-Bar'
],
1 => [
'uri' => 'principals/bar',
'uri' => 'principals/users/bar',
'{DAV:}displayname' => 'bar',
'{http://sabredav.org/ns}email-address' => 'bar@owncloud.org'
]
];
$response = $this->connector->getPrincipalsByPrefix('principals');
$response = $this->connector->getPrincipalsByPrefix('principals/users');
$this->assertSame($expectedResponse, $response);
}
@ -88,7 +90,7 @@ class Principal extends \Test\TestCase {
->with('')
->will($this->returnValue([]));
$response = $this->connector->getPrincipalsByPrefix('principals');
$response = $this->connector->getPrincipalsByPrefix('principals/users');
$this->assertSame([], $response);
}
@ -96,7 +98,7 @@ class Principal extends \Test\TestCase {
$fooUser = $this->getMockBuilder('\OC\User\User')
->disableOriginalConstructor()->getMock();
$fooUser
->expects($this->exactly(3))
->expects($this->exactly(1))
->method('getUID')
->will($this->returnValue('foo'));
$this->userManager
@ -104,17 +106,12 @@ class Principal extends \Test\TestCase {
->method('get')
->with('foo')
->will($this->returnValue($fooUser));
$this->config
->expects($this->once())
->method('getUserValue')
->with('foo', 'settings', 'email')
->will($this->returnValue(''));
$expectedResponse = [
'uri' => 'principals/foo',
'uri' => 'principals/users/foo',
'{DAV:}displayname' => 'foo'
];
$response = $this->connector->getPrincipalByPath('principals/foo');
$response = $this->connector->getPrincipalByPath('principals/users/foo');
$this->assertSame($expectedResponse, $response);
}
@ -122,26 +119,25 @@ class Principal extends \Test\TestCase {
$fooUser = $this->getMockBuilder('\OC\User\User')
->disableOriginalConstructor()->getMock();
$fooUser
->expects($this->exactly(3))
->method('getUID')
->will($this->returnValue('foo'));
->expects($this->exactly(1))
->method('getEMailAddress')
->will($this->returnValue('foo@owncloud.org'));
$fooUser
->expects($this->exactly(1))
->method('getUID')
->will($this->returnValue('foo'));
$this->userManager
->expects($this->once())
->method('get')
->with('foo')
->will($this->returnValue($fooUser));
$this->config
->expects($this->once())
->method('getUserValue')
->with('foo', 'settings', 'email')
->will($this->returnValue('foo@owncloud.org'));
$expectedResponse = [
'uri' => 'principals/foo',
'uri' => 'principals/users/foo',
'{DAV:}displayname' => 'foo',
'{http://sabredav.org/ns}email-address' => 'foo@owncloud.org'
];
$response = $this->connector->getPrincipalByPath('principals/foo');
$response = $this->connector->getPrincipalByPath('principals/users/foo');
$this->assertSame($expectedResponse, $response);
}
@ -152,7 +148,7 @@ class Principal extends \Test\TestCase {
->with('foo')
->will($this->returnValue(null));
$response = $this->connector->getPrincipalByPath('principals/foo');
$response = $this->connector->getPrincipalByPath('principals/users/foo');
$this->assertSame(null, $response);
}
@ -160,7 +156,7 @@ class Principal extends \Test\TestCase {
$fooUser = $this->getMockBuilder('\OC\User\User')
->disableOriginalConstructor()->getMock();
$fooUser
->expects($this->exactly(3))
->expects($this->exactly(1))
->method('getUID')
->will($this->returnValue('foo'));
$this->userManager
@ -168,14 +164,9 @@ class Principal extends \Test\TestCase {
->method('get')
->with('foo')
->will($this->returnValue($fooUser));
$this->config
->expects($this->once())
->method('getUserValue')
->with('foo', 'settings', 'email')
->will($this->returnValue('foo@owncloud.org'));
$response = $this->connector->getGroupMemberSet('principals/foo');
$this->assertSame(['principals/foo'], $response);
$response = $this->connector->getGroupMemberSet('principals/users/foo');
$this->assertSame(['principals/users/foo'], $response);
}
/**
@ -189,14 +180,14 @@ class Principal extends \Test\TestCase {
->with('foo')
->will($this->returnValue(null));
$this->connector->getGroupMemberSet('principals/foo');
$this->connector->getGroupMemberSet('principals/users/foo');
}
public function testGetGroupMembership() {
$fooUser = $this->getMockBuilder('\OC\User\User')
->disableOriginalConstructor()->getMock();
$fooUser
->expects($this->exactly(3))
->expects($this->exactly(1))
->method('getUID')
->will($this->returnValue('foo'));
$this->userManager
@ -204,17 +195,12 @@ class Principal extends \Test\TestCase {
->method('get')
->with('foo')
->will($this->returnValue($fooUser));
$this->config
->expects($this->once())
->method('getUserValue')
->with('foo', 'settings', 'email')
->will($this->returnValue('foo@owncloud.org'));
$expectedResponse = [
'principals/foo/calendar-proxy-read',
'principals/foo/calendar-proxy-write'
'principals/users/foo/calendar-proxy-read',
'principals/users/foo/calendar-proxy-write'
];
$response = $this->connector->getGroupMembership('principals/foo');
$response = $this->connector->getGroupMembership('principals/users/foo');
$this->assertSame($expectedResponse, $response);
}
@ -229,7 +215,7 @@ class Principal extends \Test\TestCase {
->with('foo')
->will($this->returnValue(null));
$this->connector->getGroupMembership('principals/foo');
$this->connector->getGroupMembership('principals/users/foo');
}
/**
@ -237,7 +223,7 @@ class Principal extends \Test\TestCase {
* @expectedExceptionMessage Setting members of the group is not supported yet
*/
public function testSetGroupMembership() {
$this->connector->setGroupMemberSet('principals/foo', ['foo']);
$this->connector->setGroupMemberSet('principals/users/foo', ['foo']);
}
public function testUpdatePrincipal() {
@ -245,6 +231,6 @@ class Principal extends \Test\TestCase {
}
public function testSearchPrincipals() {
$this->assertSame([], $this->connector->searchPrincipals('principals', []));
$this->assertSame([], $this->connector->searchPrincipals('principals/users', []));
}
}

@ -9,6 +9,8 @@
namespace OCA\DAV\Tests\Unit\Connector\Sabre\RequestTest;
use Sabre\DAV\Auth\Backend\BackendInterface;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
class Auth implements BackendInterface {
/**
@ -32,18 +34,35 @@ class Auth implements BackendInterface {
$this->password = $password;
}
/**
* Authenticates the user based on the current request.
* When this method is called, the backend must check if authentication was
* successful.
*
* The returned value must be one of the following
*
* [true, "principals/username"]
* [false, "reason for failure"]
*
* If authentication was successful, it's expected that the authentication
* backend returns a so-called principal url.
*
* Examples of a principal url:
*
* If authentication is successful, true must be returned.
* If authentication fails, an exception must be thrown.
* principals/admin
* principals/user1
* principals/users/joe
* principals/uid/123457
*
* @param \Sabre\DAV\Server $server
* @param string $realm
* @return bool
* If you don't use WebDAV ACL (RFC3744) we recommend that you simply
* return a string such as:
*
* principals/users/[username]
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @return array
*/
function authenticate(\Sabre\DAV\Server $server, $realm) {
function check(RequestInterface $request, ResponseInterface $response) {
$userSession = \OC::$server->getUserSession();
$result = $userSession->login($this->user, $this->password);
if ($result) {
@ -52,18 +71,33 @@ class Auth implements BackendInterface {
\OC_Util::setupFS($user);
//trigger creation of user home and /files folder
\OC::$server->getUserFolder($user);
return [true, "principals/$user"];
}
return $result;
return [false, "login failed"];
}
/**
* Returns information about the currently logged in username.
* This method is called when a user could not be authenticated, and
* authentication was required for the current request.
*
* This gives you the opportunity to set authentication headers. The 401
* status code will already be set.
*
* In this case of Basic Auth, this would for example mean that the
* following header needs to be set:
*
* $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
*
* If nobody is currently logged in, this method should return null.
* Keep in mind that in the case of multiple authentication backends, other
* WWW-Authenticate headers may already have been set, and you'll want to
* append your own WWW-Authenticate header instead of overwriting the
* existing one.
*
* @return string|null
* @param RequestInterface $request
* @param ResponseInterface $response
* @return void
*/
function getCurrentUser() {
return $this->user;
function challenge(RequestInterface $request, ResponseInterface $response) {
// TODO: Implement challenge() method.
}
}

@ -11,6 +11,13 @@ namespace OCA\DAV\Tests\Unit\Connector\Sabre\RequestTest;
use OCP\AppFramework\Http;
use OCP\Lock\ILockingProvider;
/**
* Class DownloadTest
*
* @group DB
*
* @package OCA\DAV\Tests\Unit\Connector\Sabre\RequestTest
*/
class DownloadTest extends RequestTest {
public function testDownload() {
$user = $this->getUniqueID();

@ -11,6 +11,13 @@ namespace OCA\DAV\Tests\Unit\Connector\Sabre\RequestTest;
use OC\Files\View;
use Test\Traits\EncryptionTrait;
/**
* Class EncryptionUploadTest
*
* @group DB
*
* @package OCA\DAV\Tests\Unit\Connector\Sabre\RequestTest
*/
class EncryptionUploadTest extends UploadTest {
use EncryptionTrait;

@ -46,7 +46,8 @@ abstract class RequestTest extends TestCase {
\OC::$server->getUserSession(),
\OC::$server->getMountManager(),
\OC::$server->getTagManager(),
\OC::$server->getEventDispatcher()
\OC::$server->getEventDispatcher(),
$this->getMock('\OCP\IRequest')
);
}
@ -67,6 +68,7 @@ abstract class RequestTest extends TestCase {
* @param resource|string|null $body
* @param array|null $headers
* @return \Sabre\HTTP\Response
* @throws \Exception
*/
protected function request($view, $user, $password, $method, $url, $body = null, $headers = null) {
if (is_string($body)) {

@ -12,6 +12,13 @@ use OC\Connector\Sabre\Exception\FileLocked;
use OCP\AppFramework\Http;
use OCP\Lock\ILockingProvider;
/**
* Class UploadTest
*
* @group DB
*
* @package OCA\DAV\Tests\Unit\Connector\Sabre\RequestTest
*/
class UploadTest extends RequestTest {
public function testBasicUpload() {
$user = $this->getUniqueID();

@ -141,7 +141,7 @@ class UserHooks implements IHook {
*
* @note This method should never be called for users using client side encryption
* @param array $params
* @return bool
* @return boolean|null
*/
public function login($params) {
@ -199,7 +199,7 @@ class UserHooks implements IHook {
* If the password can't be changed within ownCloud, than update the key password in advance.
*
* @param array $params : uid, password
* @return bool
* @return boolean|null
*/
public function preSetPassphrase($params) {
if (App::isEnabled('encryption')) {
@ -216,7 +216,7 @@ class UserHooks implements IHook {
* Change a user's encryption passphrase
*
* @param array $params keys: uid, password
* @return bool
* @return boolean|null
*/
public function setPassphrase($params) {

@ -1,6 +1,7 @@
OC.L10N.register(
"encryption",
{
"Missing recovery key password" : "Falta contraseña de recuperación",
"Recovery key successfully enabled" : "Se habilitó la recuperación de archivos",
"Could not enable recovery key. Please check your recovery key password!" : "No se pudo habilitar la clave de recuperación. Por favor, comprobá tu contraseña.",
"Recovery key successfully disabled" : "Clave de recuperación deshabilitada",

@ -1,4 +1,5 @@
{ "translations": {
"Missing recovery key password" : "Falta contraseña de recuperación",
"Recovery key successfully enabled" : "Se habilitó la recuperación de archivos",
"Could not enable recovery key. Please check your recovery key password!" : "No se pudo habilitar la clave de recuperación. Por favor, comprobá tu contraseña.",
"Recovery key successfully disabled" : "Clave de recuperación deshabilitada",

@ -32,6 +32,8 @@ OC.L10N.register(
"The share will expire on %s." : "Bu paylaşım %s tarihinde sona erecek.",
"Cheers!" : "Hoşçakalın!",
"Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"ownCloud basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "Selam,<br><br>Sistem yöneticisi sunucu tarafında şifrelemeyi etkinleştirdi. Dosyalarınız <strong>%s</strong> parolası kullanılarak şifrelendi.<br><br>Lütfen web arayüzünde oturum açın ve kişisel ayarlarınızdan 'ownCloud temel şifreleme modülü'ne giderek 'eski oturum parolası' alanına bu parolayı girdikten sonra şifreleme parolanızı ve mevcut oturum açma parolanızı güncelleyin.<br><br>",
"Encrypt the home storage" : "Yerel depolamayı şifrele",
"Enabling this option encrypts all files stored on the main storage, otherwise only files on external storage will be encrypted" : "Bu seçeneği etkinleştirmek ana depolamadaki bütün dosyaları şifreler, aksi takdirde sadece harici depolamadaki dosyalar şifrelenir",
"Enable recovery key" : "Kurtarma anahtarını etkinleştir",
"Disable recovery key" : "Kurtarma anahtarını devre dışı bırak",
"The recovery key is an extra encryption key that is used to encrypt files. It allows recovery of a user's files if the user forgets his or her password." : "Kurtarma anahtarı, dosyaların şifrelenmesi için daha fazla \nşifreleme sunar. Bu kullanıcının dosyasının şifresini unuttuğunda kurtarmasına imkan verir.",

@ -30,6 +30,8 @@
"The share will expire on %s." : "Bu paylaşım %s tarihinde sona erecek.",
"Cheers!" : "Hoşçakalın!",
"Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"ownCloud basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "Selam,<br><br>Sistem yöneticisi sunucu tarafında şifrelemeyi etkinleştirdi. Dosyalarınız <strong>%s</strong> parolası kullanılarak şifrelendi.<br><br>Lütfen web arayüzünde oturum açın ve kişisel ayarlarınızdan 'ownCloud temel şifreleme modülü'ne giderek 'eski oturum parolası' alanına bu parolayı girdikten sonra şifreleme parolanızı ve mevcut oturum açma parolanızı güncelleyin.<br><br>",
"Encrypt the home storage" : "Yerel depolamayı şifrele",
"Enabling this option encrypts all files stored on the main storage, otherwise only files on external storage will be encrypted" : "Bu seçeneği etkinleştirmek ana depolamadaki bütün dosyaları şifreler, aksi takdirde sadece harici depolamadaki dosyalar şifrelenir",
"Enable recovery key" : "Kurtarma anahtarını etkinleştir",
"Disable recovery key" : "Kurtarma anahtarını devre dışı bırak",
"The recovery key is an extra encryption key that is used to encrypt files. It allows recovery of a user's files if the user forgets his or her password." : "Kurtarma anahtarı, dosyaların şifrelenmesi için daha fazla \nşifreleme sunar. Bu kullanıcının dosyasının şifresini unuttuğunda kurtarmasına imkan verir.",

@ -34,7 +34,6 @@ use OCA\Encryption\Vendor\PBKDF2Fallback;
use OCP\Encryption\Exceptions\GenericEncryptionException;
use OCP\IConfig;
use OCP\ILogger;
use OCP\IUser;
use OCP\IUserSession;
class Crypt {
@ -146,7 +145,7 @@ class Crypt {
/**
* @param string $plainContent
* @param string $passPhrase
* @return bool|string
* @return false|string
* @throws GenericEncryptionException
*/
public function symmetricEncryptFileContent($plainContent, $passPhrase) {
@ -273,7 +272,7 @@ class Crypt {
}
/**
* @param $data
* @param string $data
* @return string
*/
private function addPadding($data) {
@ -326,7 +325,7 @@ class Crypt {
* @param string $privateKey
* @param string $password
* @param string $uid for regular users, empty for system keys
* @return bool|string
* @return false|string
*/
public function encryptPrivateKey($privateKey, $password, $uid = '') {
$cipher = $this->getCipher();
@ -343,7 +342,7 @@ class Crypt {
* @param string $privateKey
* @param string $password
* @param string $uid for regular users, empty for system keys
* @return bool|string
* @return false|string
*/
public function decryptPrivateKey($privateKey, $password = '', $uid = '') {
@ -386,7 +385,7 @@ class Crypt {
/**
* check if it is a valid private key
*
* @param $plainKey
* @param string $plainKey
* @return bool
*/
protected function isValidPrivateKey($plainKey) {
@ -402,7 +401,7 @@ class Crypt {
}
/**
* @param $keyFileContents
* @param string $keyFileContents
* @param string $passPhrase
* @param string $cipher
* @return string
@ -424,7 +423,7 @@ class Crypt {
* remove padding
*
* @param $padded
* @return bool|string
* @return string|false
*/
private function removePadding($padded) {
if (substr($padded, -2) === 'xx') {
@ -436,8 +435,8 @@ class Crypt {
/**
* split iv from encrypted content
*
* @param $catFile
* @return array
* @param string|false $catFile
* @return string
*/
private function splitIv($catFile) {
// Fetch encryption metadata from end of file
@ -457,8 +456,8 @@ class Crypt {
}
/**
* @param $encryptedContent
* @param $iv
* @param string $encryptedContent
* @param string $iv
* @param string $passPhrase
* @param string $cipher
* @return string
@ -479,7 +478,7 @@ class Crypt {
}
/**
* @param $data
* @param string $data
* @return array
*/
protected function parseHeader($data) {
@ -551,7 +550,7 @@ class Crypt {
* @param $encKeyFile
* @param $shareKey
* @param $privateKey
* @return mixed
* @return string
* @throws MultiKeyDecryptException
*/
public function multiKeyDecrypt($encKeyFile, $shareKey, $privateKey) {

@ -31,6 +31,7 @@ use OCP\IL10N;
use OCP\IUserManager;
use OCP\Mail\IMailer;
use OCP\Security\ISecureRandom;
use OCP\Util;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Helper\Table;
@ -358,14 +359,15 @@ class EncryptAll {
$progress = new ProgressBar($this->output, count($this->userPasswords));
$progress->start();
foreach ($this->userPasswords as $recipient => $password) {
foreach ($this->userPasswords as $uid => $password) {
$progress->advance();
if (!empty($password)) {
$recipientDisplayName = $this->userManager->get($recipient)->getDisplayName();
$to = $this->config->getUserValue($recipient, 'settings', 'email', '');
$recipient = $this->userManager->get($uid);
$recipientDisplayName = $recipient->getDisplayName();
$to = $recipient->getEMailAddress();
if ($to === '') {
$noMail[] = $recipient;
$noMail[] = $uid;
continue;
}
@ -380,12 +382,12 @@ class EncryptAll {
$message->setHtmlBody($htmlBody);
$message->setPlainBody($textBody);
$message->setFrom([
\OCP\Util::getDefaultEmailAddress('admin-noreply')
Util::getDefaultEmailAddress('admin-noreply')
]);
$this->mailer->send($message);
} catch (\Exception $e) {
$noMail[] = $recipient;
$noMail[] = $uid;
}
}
}

@ -247,7 +247,7 @@ class Encryption implements IEncryptionModule {
* encrypt data
*
* @param string $data you want to encrypt
* @return mixed encrypted data
* @return string encrypted data
*/
public function encrypt($data) {
@ -312,7 +312,7 @@ class Encryption implements IEncryptionModule {
* decrypt data
*
* @param string $data you want to decrypt
* @return mixed decrypted data
* @return string decrypted data
* @throws DecryptionFailedException
*/
public function decrypt($data) {

@ -280,7 +280,7 @@ class KeyManager {
/**
* @param $userId
* @param $key
* @param string $key
* @return bool
*/
public function setPrivateKey($userId, $key) {
@ -365,7 +365,7 @@ class KeyManager {
/**
* @param $userId
* @return mixed
* @return string
* @throws PrivateKeyMissingException
*/
public function getPrivateKey($userId) {
@ -379,7 +379,7 @@ class KeyManager {
}
/**
* @param $path
* @param string $path
* @param $uid
* @return string
*/
@ -412,7 +412,7 @@ class KeyManager {
/**
* get the encrypted file key
*
* @param $path
* @param string $path
* @return string
*/
public function getEncryptedFileKey($path) {
@ -508,7 +508,7 @@ class KeyManager {
}
/**
* @param $purpose
* @param string $purpose
* @param bool $timestamp
* @param bool $includeUserKeys
*/
@ -534,13 +534,16 @@ class KeyManager {
}
/**
* @param $uid
* @param string $uid
* @return bool
*/
private function deletePrivateKey($uid) {
return $this->keyStorage->deleteUserKey($uid, $this->privateKeyId, Encryption::ID);
}
/**
* @param string $path
*/
public function deleteAllFileKeys($path) {
return $this->keyStorage->deleteAllFileKeys($path);
}

@ -50,7 +50,7 @@ class Migration {
*/
public function __construct(IConfig $config, View $view, IDBConnection $connection, ILogger $logger) {
$this->view = $view;
$this->view->getUpdater()->disable();
$this->view->disableCacheUpdate();
$this->connection = $connection;
$this->moduleId = \OCA\Encryption\Crypto\Encryption::ID;
$this->config = $config;
@ -237,7 +237,7 @@ class Migration {
/**
* rename system wide public key
*
* @param $privateKey private key for which we want to rename the corresponding public key
* @param string $privateKey private key for which we want to rename the corresponding public key
*/
private function renameSystemPublicKey($privateKey) {
$publicKey = substr($privateKey,0 , strrpos($privateKey, '.privateKey')) . '.publicKey';

@ -103,7 +103,7 @@ class Recovery {
/**
* @param $recoveryKeyId
* @param $password
* @param string $password
* @return bool
*/
public function enableAdminRecovery($password) {
@ -144,7 +144,7 @@ class Recovery {
}
/**
* @param $recoveryPassword
* @param string $recoveryPassword
* @return bool
*/
public function disableAdminRecovery($recoveryPassword) {
@ -212,6 +212,7 @@ class Recovery {
/**
* add recovery key to all encrypted files
* @param string $path
*/
private function addRecoveryKeys($path) {
$dirContent = $this->view->getDirectoryContent($path);
@ -239,6 +240,7 @@ class Recovery {
/**
* remove recovery key to all encrypted files
* @param string $path
*/
private function removeRecoveryKeys($path) {
$dirContent = $this->view->getDirectoryContent($path);

@ -62,6 +62,9 @@ class MigrationTest extends \Test\TestCase {
$this->moduleId = \OCA\Encryption\Crypto\Encryption::ID;
}
/**
* @param string $uid
*/
protected function createDummyShareKeys($uid) {
$this->loginAsUser($uid);
@ -89,6 +92,9 @@ class MigrationTest extends \Test\TestCase {
}
}
/**
* @param string $uid
*/
protected function createDummyUserKeys($uid) {
$this->loginAsUser($uid);
@ -98,6 +104,9 @@ class MigrationTest extends \Test\TestCase {
$this->view->file_put_contents('/files_encryption/public_keys/' . $uid . '.publicKey', 'publicKey');
}
/**
* @param string $uid
*/
protected function createDummyFileKeys($uid) {
$this->loginAsUser($uid);
@ -111,6 +120,9 @@ class MigrationTest extends \Test\TestCase {
$this->view->file_put_contents($uid . '/files_encryption/keys/folder2/file.2.1/fileKey' , 'data');
}
/**
* @param string $uid
*/
protected function createDummyFiles($uid) {
$this->loginAsUser($uid);
@ -124,6 +136,9 @@ class MigrationTest extends \Test\TestCase {
$this->view->file_put_contents($uid . '/files/folder2/file.2.1/fileKey' , 'data');
}
/**
* @param string $uid
*/
protected function createDummyFilesInTrash($uid) {
$this->loginAsUser($uid);
@ -239,6 +254,9 @@ class MigrationTest extends \Test\TestCase {
}
/**
* @param string $uid
*/
protected function verifyFilesInTrash($uid) {
$this->loginAsUser($uid);
@ -266,6 +284,9 @@ class MigrationTest extends \Test\TestCase {
);
}
/**
* @param string $uid
*/
protected function verifyNewKeyPath($uid) {
// private key
if ($uid !== '') {
@ -394,6 +415,11 @@ class MigrationTest extends \Test\TestCase {
}
/**
* @param string $table
* @param string $appid
* @param integer $expected
*/
public function verifyDB($table, $appid, $expected) {
/** @var \OCP\IDBConnection $connection */
$connection = \OC::$server->getDatabaseConnection();

@ -0,0 +1,145 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
*
* @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\Federation\API;
use OCA\Federation\DbHandler;
use OCA\Federation\TrustedServers;
use OCP\AppFramework\Http;
use OCP\BackgroundJob\IJobList;
use OCP\IRequest;
use OCP\Security\ISecureRandom;
use OCP\Security\StringUtils;
/**
* Class OCSAuthAPI
*
* OCS API end-points to exchange shared secret between two connected ownClouds
*
* @package OCA\Federation\API
*/
class OCSAuthAPI {
/** @var IRequest */
private $request;
/** @var ISecureRandom */
private $secureRandom;
/** @var IJobList */
private $jobList;
/** @var TrustedServers */
private $trustedServers;
/** @var DbHandler */
private $dbHandler;
/**
* OCSAuthAPI constructor.
*
* @param IRequest $request
* @param ISecureRandom $secureRandom
* @param IJobList $jobList
* @param TrustedServers $trustedServers
* @param DbHandler $dbHandler
*/
public function __construct(
IRequest $request,
ISecureRandom $secureRandom,
IJobList $jobList,
TrustedServers $trustedServers,
DbHandler $dbHandler
) {
$this->request = $request;
$this->secureRandom = $secureRandom;
$this->jobList = $jobList;
$this->trustedServers = $trustedServers;
$this->dbHandler = $dbHandler;
}
/**
* request received to ask remote server for a shared secret
*
* @return \OC_OCS_Result
*/
public function requestSharedSecret() {
$url = $this->request->getParam('url');
$token = $this->request->getParam('token');
if ($this->trustedServers->isTrustedServer($url) === false) {
return new \OC_OCS_Result(null, HTTP::STATUS_FORBIDDEN);
}
// if both server initiated the exchange of the shared secret the greater
// token wins
$localToken = $this->dbHandler->getToken($url);
if (strcmp($localToken, $token) > 0) {
return new \OC_OCS_Result(null, HTTP::STATUS_FORBIDDEN);
}
$this->jobList->add(
'OCA\Federation\BackgroundJob\GetSharedSecret',
[
'url' => $url,
'token' => $token,
]
);
return new \OC_OCS_Result(null, Http::STATUS_OK);
}
/**
* create shared secret and return it
*
* @return \OC_OCS_Result
*/
public function getSharedSecret() {
$url = $this->request->getParam('url');
$token = $this->request->getParam('token');
if (
$this->trustedServers->isTrustedServer($url) === false
|| $this->isValidToken($url, $token) === false
) {
return new \OC_OCS_Result(null, HTTP::STATUS_FORBIDDEN);
}
$sharedSecret = $this->secureRandom->getMediumStrengthGenerator()->generate(32);
$this->trustedServers->addSharedSecret($url, $sharedSecret);
// reset token after the exchange of the shared secret was successful
$this->dbHandler->addToken($url, '');
return new \OC_OCS_Result(['sharedSecret' => $sharedSecret], Http::STATUS_OK);
}
protected function isValidToken($url, $token) {
$storedToken = $this->dbHandler->getToken($url);
return StringUtils::equals($storedToken, $token);
}
}

@ -0,0 +1,26 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
*
* @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\Federation\AppInfo;
$app = new Application();
$app->registerSettings();
$app->registerHooks();

@ -0,0 +1,148 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
*
* @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\Federation\AppInfo;
use OCA\Federation\API\OCSAuthAPI;
use OCA\Federation\Controller\SettingsController;
use OCA\Federation\DbHandler;
use OCA\Federation\Hooks;
use OCA\Federation\Middleware\AddServerMiddleware;
use OCA\Federation\TrustedServers;
use OCP\API;
use OCP\App;
use OCP\AppFramework\IAppContainer;
use OCP\Util;
class Application extends \OCP\AppFramework\App {
/**
* @param array $urlParams
*/
public function __construct($urlParams = array()) {
parent::__construct('federation', $urlParams);
$this->registerService();
$this->registerMiddleware();
}
/**
* register setting scripts
*/
public function registerSettings() {
App::registerAdmin('federation', 'settings/settings-admin');
}
private function registerService() {
$container = $this->getContainer();
$container->registerService('addServerMiddleware', function(IAppContainer $c) {
return new AddServerMiddleware(
$c->getAppName(),
\OC::$server->getL10N($c->getAppName()),
\OC::$server->getLogger()
);
});
$container->registerService('DbHandler', function(IAppContainer $c) {
return new DbHandler(
\OC::$server->getDatabaseConnection(),
\OC::$server->getL10N($c->getAppName())
);
});
$container->registerService('TrustedServers', function(IAppContainer $c) {
return new TrustedServers(
$c->query('DbHandler'),
\OC::$server->getHTTPClientService(),
\OC::$server->getLogger(),
\OC::$server->getJobList(),
\OC::$server->getSecureRandom(),
\OC::$server->getConfig()
);
});
$container->registerService('SettingsController', function (IAppContainer $c) {
$server = $c->getServer();
return new SettingsController(
$c->getAppName(),
$server->getRequest(),
$server->getL10N($c->getAppName()),
$c->query('TrustedServers')
);
});
}
private function registerMiddleware() {
$container = $this->getContainer();
$container->registerMiddleware('addServerMiddleware');
}
/**
* register OCS API Calls
*/
public function registerOCSApi() {
$container = $this->getContainer();
$server = $container->getServer();
$auth = new OCSAuthAPI(
$server->getRequest(),
$server->getSecureRandom(),
$server->getJobList(),
$container->query('TrustedServers'),
$container->query('DbHandler')
);
API::register('get',
'/apps/federation/api/v1/shared-secret',
array($auth, 'getSharedSecret'),
'federation',
API::GUEST_AUTH
);
API::register('post',
'/apps/federation/api/v1/request-shared-secret',
array($auth, 'requestSharedSecret'),
'federation',
API::GUEST_AUTH
);
}
/**
* listen to federated_share_added hooks to auto-add new servers to the
* list of trusted servers.
*/
public function registerHooks() {
$container = $this->getContainer();
$hooksManager = new Hooks($container->query('TrustedServers'));
Util::connectHook(
'OCP\Share',
'federated_share_added',
$hooksManager,
'addServerHook'
);
}
}

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<database>
<name>*dbname*</name>
<create>true</create>
<overwrite>false</overwrite>
<charset>utf8</charset>
<table>
<name>*dbprefix*trusted_servers</name>
<declaration>
<field>
<name>id</name>
<type>integer</type>
<default>0</default>
<notnull>true</notnull>
<autoincrement>1</autoincrement>
<length>4</length>
</field>
<field>
<name>url</name>
<type>text</type>
<notnull>true</notnull>
<length>512</length>
<comments>Url of trusted server</comments>
</field>
<field>
<name>url_hash</name>
<type>text</type>
<default></default>
<notnull>true</notnull>
<length>32</length>
<comments>md5 hash of the url without the protocol</comments>
</field>
<field>
<name>token</name>
<type>text</type>
<length>128</length>
<comments>toke used to exchange the shared secret</comments>
</field>
<field>
<name>shared_secret</name>
<type>text</type>
<length>256</length>
<comments>shared secret used to authenticate</comments>
</field>
<field>
<name>status</name>
<type>integer</type>
<length>4</length>
<notnull>true</notnull>
<default>2</default>
<comments>current status of the connection</comments>
</field>
<index>
<name>url_hash</name>
<unique>true</unique>
<field>
<name>url_hash</name>
<sorting>ascending</sorting>
</field>
</index>
</declaration>
</table>
</database>

@ -0,0 +1,14 @@
<?xml version="1.0"?>
<info>
<id>federation</id>
<name>Federation</name>
<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>
<namespace>Federation</namespace>
<category>other</category>
<dependencies>
<owncloud min-version="9.0" />
</dependencies>
</info>

@ -0,0 +1,47 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
*
* @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/>
*
*/
$application = new \OCA\Federation\AppInfo\Application();
$application->registerRoutes(
$this,
[
'routes' => [
[
'name' => 'Settings#addServer',
'url' => '/trusted-servers',
'verb' => 'POST'
],
[
'name' => 'Settings#removeServer',
'url' => '/trusted-servers/{id}',
'verb' => 'DELETE'
],
[
'name' => 'Settings#autoAddServers',
'url' => '/auto-add-servers',
'verb' => 'POST'
],
]
]
);
$application->registerOCSApi();

@ -0,0 +1,185 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
*
* @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\Federation\BackgroundJob;
use GuzzleHttp\Exception\ClientException;
use OC\BackgroundJob\JobList;
use OC\BackgroundJob\QueuedJob;
use OCA\Federation\DbHandler;
use OCA\Federation\TrustedServers;
use OCP\AppFramework\Http;
use OCP\BackgroundJob\IJobList;
use OCP\Http\Client\IClient;
use OCP\ILogger;
use OCP\IURLGenerator;
/**
* Class GetSharedSecret
*
* request shared secret from remote ownCloud
*
* @package OCA\Federation\Backgroundjob
*/
class GetSharedSecret extends QueuedJob{
/** @var IClient */
private $httpClient;
/** @var IJobList */
private $jobList;
/** @var IURLGenerator */
private $urlGenerator;
/** @var TrustedServers */
private $trustedServers;
/** @var DbHandler */
private $dbHandler;
/** @var ILogger */
private $logger;
private $endPoint = '/ocs/v2.php/apps/federation/api/v1/shared-secret?format=json';
/**
* RequestSharedSecret constructor.
*
* @param IClient $httpClient
* @param IURLGenerator $urlGenerator
* @param IJobList $jobList
* @param TrustedServers $trustedServers
* @param ILogger $logger
* @param DbHandler $dbHandler
*/
public function __construct(
IClient $httpClient = null,
IURLGenerator $urlGenerator = null,
IJobList $jobList = null,
TrustedServers $trustedServers = null,
ILogger $logger = null,
dbHandler $dbHandler = null
) {
$this->logger = $logger ? $logger : \OC::$server->getLogger();
$this->httpClient = $httpClient ? $httpClient : \OC::$server->getHTTPClientService()->newClient();
$this->jobList = $jobList ? $jobList : \OC::$server->getJobList();
$this->urlGenerator = $urlGenerator ? $urlGenerator : \OC::$server->getURLGenerator();
$this->dbHandler = $dbHandler ? $dbHandler : new DbHandler(\OC::$server->getDatabaseConnection(), \OC::$server->getL10N('federation'));
if ($trustedServers) {
$this->trustedServers = $trustedServers;
} else {
$this->trustedServers = new TrustedServers(
$this->dbHandler,
\OC::$server->getHTTPClientService(),
\OC::$server->getLogger(),
$this->jobList,
\OC::$server->getSecureRandom(),
\OC::$server->getConfig()
);
}
}
/**
* run the job, then remove it from the joblist
*
* @param JobList $jobList
* @param ILogger $logger
*/
public function execute($jobList, ILogger $logger = null) {
$jobList->remove($this, $this->argument);
$target = $this->argument['url'];
// only execute if target is still in the list of trusted domains
if ($this->trustedServers->isTrustedServer($target)) {
$this->parentExecute($jobList, $logger);
}
}
/**
* call execute() method of parent
*
* @param JobList $jobList
* @param ILogger $logger
*/
protected function parentExecute($jobList, $logger) {
parent::execute($jobList, $logger);
}
protected function run($argument) {
$target = $argument['url'];
$source = $this->urlGenerator->getAbsoluteURL('/');
$source = rtrim($source, '/');
$token = $argument['token'];
try {
$result = $this->httpClient->get(
$target . $this->endPoint,
[
'query' =>
[
'url' => $source,
'token' => $token
],
'timeout' => 3,
'connect_timeout' => 3,
]
);
$status = $result->getStatusCode();
} catch (ClientException $e) {
$status = $e->getCode();
}
// if we received a unexpected response we try again later
if (
$status !== Http::STATUS_OK
&& $status !== Http::STATUS_FORBIDDEN
) {
$this->jobList->add(
'OCA\Federation\BackgroundJob\GetSharedSecret',
$argument
);
} else {
// reset token if we received a valid response
$this->dbHandler->addToken($target, '');
}
if ($status === Http::STATUS_OK) {
$body = $result->getBody();
$result = json_decode($body, true);
if (isset($result['ocs']['data']['sharedSecret'])) {
$this->trustedServers->addSharedSecret(
$target,
$result['ocs']['data']['sharedSecret']
);
} else {
$this->logger->error(
'remote server "' . $target . '"" does not return a valid shared secret',
['app' => 'federation']
);
$this->trustedServers->setServerStatus($target, TrustedServers::STATUS_FAILURE);
}
}
}
}

@ -0,0 +1,164 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
*
* @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\Federation\BackgroundJob;
use GuzzleHttp\Exception\ClientException;
use OC\BackgroundJob\JobList;
use OC\BackgroundJob\QueuedJob;
use OCA\Federation\DbHandler;
use OCA\Federation\TrustedServers;
use OCP\AppFramework\Http;
use OCP\BackgroundJob\IJobList;
use OCP\Http\Client\IClient;
use OCP\ILogger;
use OCP\IURLGenerator;
/**
* Class RequestSharedSecret
*
* Ask remote ownCloud to request a sharedSecret from this server
*
* @package OCA\Federation\Backgroundjob
*/
class RequestSharedSecret extends QueuedJob {
/** @var IClient */
private $httpClient;
/** @var IJobList */
private $jobList;
/** @var IURLGenerator */
private $urlGenerator;
/** @var DbHandler */
private $dbHandler;
/** @var TrustedServers */
private $trustedServers;
private $endPoint = '/ocs/v2.php/apps/federation/api/v1/request-shared-secret?format=json';
/**
* RequestSharedSecret constructor.
*
* @param IClient $httpClient
* @param IURLGenerator $urlGenerator
* @param IJobList $jobList
* @param TrustedServers $trustedServers
* @param DbHandler $dbHandler
*/
public function __construct(
IClient $httpClient = null,
IURLGenerator $urlGenerator = null,
IJobList $jobList = null,
TrustedServers $trustedServers = null,
dbHandler $dbHandler = null
) {
$this->httpClient = $httpClient ? $httpClient : \OC::$server->getHTTPClientService()->newClient();
$this->jobList = $jobList ? $jobList : \OC::$server->getJobList();
$this->urlGenerator = $urlGenerator ? $urlGenerator : \OC::$server->getURLGenerator();
$this->dbHandler = $dbHandler ? $dbHandler : new DbHandler(\OC::$server->getDatabaseConnection(), \OC::$server->getL10N('federation'));
if ($trustedServers) {
$this->trustedServers = $trustedServers;
} else {
$this->trustedServers = new TrustedServers(
$this->dbHandler,
\OC::$server->getHTTPClientService(),
\OC::$server->getLogger(),
$this->jobList,
\OC::$server->getSecureRandom(),
\OC::$server->getConfig()
);
}
}
/**
* run the job, then remove it from the joblist
*
* @param JobList $jobList
* @param ILogger $logger
*/
public function execute($jobList, ILogger $logger = null) {
$jobList->remove($this, $this->argument);
$target = $this->argument['url'];
// only execute if target is still in the list of trusted domains
if ($this->trustedServers->isTrustedServer($target)) {
$this->parentExecute($jobList, $logger);
}
}
/**
* @param JobList $jobList
* @param ILogger $logger
*/
protected function parentExecute($jobList, $logger) {
parent::execute($jobList, $logger);
}
protected function run($argument) {
$target = $argument['url'];
$source = $this->urlGenerator->getAbsoluteURL('/');
$source = rtrim($source, '/');
$token = $argument['token'];
try {
$result = $this->httpClient->post(
$target . $this->endPoint,
[
'body' => [
'url' => $source,
'token' => $token,
],
'timeout' => 3,
'connect_timeout' => 3,
]
);
$status = $result->getStatusCode();
} catch (ClientException $e) {
$status = $e->getCode();
}
// if we received a unexpected response we try again later
if (
$status !== Http::STATUS_OK
&& $status !== Http::STATUS_FORBIDDEN
) {
$this->jobList->add(
'OCA\Federation\BackgroundJob\RequestSharedSecret',
$argument
);
}
if ($status === Http::STATUS_FORBIDDEN) {
// clear token if remote server refuses to ask for shared secret
$this->dbHandler->addToken($target, '');
}
}
}

@ -0,0 +1,123 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
*
* @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\Federation\Controller;
use OC\HintException;
use OCA\Federation\TrustedServers;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IRequest;
class SettingsController extends Controller {
/** @var IL10N */
private $l;
/** @var TrustedServers */
private $trustedServers;
/**
* @param string $AppName
* @param IRequest $request
* @param IL10N $l10n
* @param TrustedServers $trustedServers
*/
public function __construct($AppName,
IRequest $request,
IL10N $l10n,
TrustedServers $trustedServers
) {
parent::__construct($AppName, $request);
$this->l = $l10n;
$this->trustedServers = $trustedServers;
}
/**
* add server to the list of trusted ownClouds
*
* @param string $url
* @return DataResponse
* @throws HintException
*/
public function addServer($url) {
$this->checkServer($url);
$id = $this->trustedServers->addServer($url);
return new DataResponse(
[
'url' => $url,
'id' => $id,
'message' => (string) $this->l->t('Server added to the list of trusted ownClouds')
]
);
}
/**
* add server to the list of trusted ownClouds
*
* @param int $id
* @return DataResponse
*/
public function removeServer($id) {
$this->trustedServers->removeServer($id);
return new DataResponse();
}
/**
* enable/disable to automatically add servers to the list of trusted servers
* once a federated share was created and accepted successfully
*
* @param bool $autoAddServers
*/
public function autoAddServers($autoAddServers) {
$this->trustedServers->setAutoAddServers($autoAddServers);
}
/**
* check if the server should be added to the list of trusted servers or not
*
* @param string $url
* @return bool
* @throws HintException
*/
protected function checkServer($url) {
if ($this->trustedServers->isTrustedServer($url) === true) {
$message = 'Server is already in the list of trusted servers.';
$hint = $this->l->t('Server is already in the list of trusted servers.');
throw new HintException($message, $hint);
}
if ($this->trustedServers->isOwnCloudServer($url) === false) {
$message = 'No ownCloud server found';
$hint = $this->l->t('No ownCloud server found');
throw new HintException($message, $hint);
}
return true;
}
}

@ -0,0 +1,26 @@
#ocFederationSettings p {
padding-top: 10px;
}
#listOfTrustedServers li {
padding-top: 10px;
padding-left: 20px;
}
.removeTrustedServer {
display: none;
vertical-align:middle;
padding-left: 10px;
}
#ocFederationAddServerButton {
cursor: pointer;
}
#listOfTrustedServers li:hover {
cursor: pointer;
}
#listOfTrustedServers .status {
margin-right: 10px;
}

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="32" width="32" version="1.0" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
<path d="m13.733 0.00064c-0.52991 0-0.93331 0.40337-0.93331 0.93333v2.6666c-1.182 0.3034-2.243 0.7934-3.2668 1.4001l-1.9334-1.9334c-0.3747-0.3747-0.9586-0.3747-1.3333 0l-3.1999 3.2c-0.37473 0.37474-0.37473 0.95859 0 1.3333l1.9334 1.9335c-0.6067 1.0239-1.0967 2.0849-1.4001 3.2669h-2.6666c-0.52994 0-0.9333 0.403-0.9333 0.933v4.5333c2e-8 0.52996 0.40336 0.93333 0.93331 0.93333h2.6666c0.30335 1.1817 0.79332 2.2426 1.4 3.2666l-1.9334 1.9349c-0.37473 0.37474-0.37473 0.95859 0 1.3333l3.1999 3.2c0.37473 0.37474 0.95857 0.37474 1.3333 0l1.9334-1.9349c1.024 0.608 2.0849 1.0965 3.2665 1.3995v2.6667c0 0.53 0.403 0.933 0.933 0.933h4.5332c0.52991 0 0.93331-0.4032 0.93331-0.9344v-2.6667c1.1816-0.30336 2.2425-0.79335 3.2665-1.4l1.9333 1.9333c0.37473 0.37474 0.95857 0.37474 1.3333 0l3.1999-3.2c0.37473-0.37474 0.37473-0.95859 0-1.3333l-1.9327-1.9328c0.60798-1.024 1.0965-2.0845 1.3994-3.2661h2.6666c0.532 0 0.935-0.403 0.935-0.933v-4.534c0-0.53-0.403-0.933-0.934-0.933h-2.667c-0.303-1.182-0.791-2.243-1.399-3.2666l1.932-1.9334c0.37473-0.37474 0.37473-0.95859 0-1.3333l-3.2-3.2c-0.37473-0.37474-0.95857-0.37474-1.3333 0l-1.9327 1.9334c-1.024-0.6067-2.084-1.0967-3.266-1.4001v-2.6667c0-0.52993-0.403-0.9333-0.933-0.9333zm2.2666 8.8689c3.9361 0 7.1309 3.1947 7.1309 7.1311 0 3.9362-3.1946 7.1311-7.1309 7.1311-3.9361 0-7.1309-3.1955-7.1309-7.1317s3.1948-7.1311 7.1309-7.1311z" display="block" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@ -0,0 +1,82 @@
/**
* @author Björn Schießle <schiessle@owncloud.com>
*
* @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/>
*
*/
$(document).ready(function () {
// show input field to add a new trusted server
$("#ocFederationAddServer").on('click', function() {
$('#ocFederationAddServerButton').addClass('hidden');
$("#serverUrl").removeClass('hidden');
$("#serverUrl").focus();
});
// add new trusted server
$("#serverUrl").keyup(function (e) {
if (e.keyCode === 13) { // add server on "enter"
var url = $('#serverUrl').val();
OC.msg.startSaving('#ocFederationAddServer .msg');
$.post(
OC.generateUrl('/apps/federation/trusted-servers'),
{
url: url
}
).done(function (data) {
$('#serverUrl').attr('value', '');
$('ul#listOfTrustedServers').prepend(
$('<li>')
.attr('id', data.id)
.attr('class', 'icon-delete')
.html('<span class="status indeterminate"></span>' + data.url)
);
OC.msg.finishedSuccess('#ocFederationAddServer .msg', data.message);
})
.fail(function (jqXHR) {
OC.msg.finishedError('#ocFederationAddServer .msg', JSON.parse(jqXHR.responseText).message);
});
} else if (e.keyCode === 27) { // hide input filed again in ESC
$('#ocFederationAddServerButton').toggleClass('hidden');
$("#serverUrl").toggleClass('hidden');
}
});
// remove trusted server from list
$( "#listOfTrustedServers" ).on('click', 'li', function() {
var id = $(this).attr('id');
var $this = $(this);
$.ajax({
url: OC.generateUrl('/apps/federation/trusted-servers/' + id),
type: 'DELETE',
success: function(response) {
$this.remove();
}
});
});
$("#ocFederationSettings #autoAddServers").change(function() {
$.post(
OC.generateUrl('/apps/federation/auto-add-servers'),
{
autoAddServers: $(this).is(":checked")
}
);
});
});

@ -0,0 +1,270 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
*
* @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\Federation;
use OC\Files\Filesystem;
use OC\HintException;
use OCP\IDBConnection;
use OCP\IL10N;
/**
* Class DbHandler
*
* handles all database calls for the federation app
*
* @group DB
* @package OCA\Federation
*/
class DbHandler {
/** @var IDBConnection */
private $connection;
/** @var IL10N */
private $l;
/** @var string */
private $dbTable = 'trusted_servers';
/**
* @param IDBConnection $connection
* @param IL10N $il10n
*/
public function __construct(
IDBConnection $connection,
IL10N $il10n
) {
$this->connection = $connection;
$this->IL10N = $il10n;
}
/**
* add server to the list of trusted ownCloud servers
*
* @param string $url
* @return int
* @throws HintException
*/
public function addServer($url) {
$hash = $this->hash($url);
$url = rtrim($url, '/');
$query = $this->connection->getQueryBuilder();
$query->insert($this->dbTable)
->values(
[
'url' => $query->createParameter('url'),
'url_hash' => $query->createParameter('url_hash'),
]
)
->setParameter('url', $url)
->setParameter('url_hash', $hash);
$result = $query->execute();
if ($result) {
return (int)$this->connection->lastInsertId('*PREFIX*'.$this->dbTable);
} else {
$message = 'Internal failure, Could not add ownCloud as trusted server: ' . $url;
$message_t = $this->l->t('Could not add server');
throw new HintException($message, $message_t);
}
}
/**
* remove server from the list of trusted ownCloud servers
*
* @param int $id
*/
public function removeServer($id) {
$query = $this->connection->getQueryBuilder();
$query->delete($this->dbTable)
->where($query->expr()->eq('id', $query->createParameter('id')))
->setParameter('id', $id);
$query->execute();
}
/**
* get all trusted servers
*
* @return array
*/
public function getAllServer() {
$query = $this->connection->getQueryBuilder();
$query->select(['url', 'id', 'status'])->from($this->dbTable);
$result = $query->execute()->fetchAll();
return $result;
}
/**
* check if server already exists in the database table
*
* @param string $url
* @return bool
*/
public function serverExists($url) {
$hash = $this->hash($url);
$query = $this->connection->getQueryBuilder();
$query->select('url')->from($this->dbTable)
->where($query->expr()->eq('url_hash', $query->createParameter('url_hash')))
->setParameter('url_hash', $hash);
$result = $query->execute()->fetchAll();
return !empty($result);
}
/**
* write token to database. Token is used to exchange the secret
*
* @param string $url
* @param string $token
*/
public function addToken($url, $token) {
$hash = $this->hash($url);
$query = $this->connection->getQueryBuilder();
$query->update($this->dbTable)
->set('token', $query->createParameter('token'))
->where($query->expr()->eq('url_hash', $query->createParameter('url_hash')))
->setParameter('url_hash', $hash)
->setParameter('token', $token);
$query->execute();
}
/**
* get token stored in database
*
* @param string $url
* @return string
*/
public function getToken($url) {
$hash = $this->hash($url);
$query = $this->connection->getQueryBuilder();
$query->select('token')->from($this->dbTable)
->where($query->expr()->eq('url_hash', $query->createParameter('url_hash')))
->setParameter('url_hash', $hash);
$result = $query->execute()->fetch();
return $result['token'];
}
/**
* add shared Secret to database
*
* @param string $url
* @param string $sharedSecret
*/
public function addSharedSecret($url, $sharedSecret) {
$hash = $this->hash($url);
$query = $this->connection->getQueryBuilder();
$query->update($this->dbTable)
->set('shared_secret', $query->createParameter('sharedSecret'))
->where($query->expr()->eq('url_hash', $query->createParameter('url_hash')))
->setParameter('url_hash', $hash)
->setParameter('sharedSecret', $sharedSecret);
$query->execute();
}
/**
* get shared secret from database
*
* @param string $url
* @return string
*/
public function getSharedSecret($url) {
$hash = $this->hash($url);
$query = $this->connection->getQueryBuilder();
$query->select('shared_secret')->from($this->dbTable)
->where($query->expr()->eq('url_hash', $query->createParameter('url_hash')))
->setParameter('url_hash', $hash);
$result = $query->execute()->fetch();
return $result['shared_secret'];
}
/**
* set server status
*
* @param string $url
* @param int $status
*/
public function setServerStatus($url, $status) {
$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);
$query->execute();
}
/**
* get server status
*
* @param string $url
* @return int
*/
public function getServerStatus($url) {
$hash = $this->hash($url);
$query = $this->connection->getQueryBuilder();
$query->select('status')->from($this->dbTable)
->where($query->expr()->eq('url_hash', $query->createParameter('url_hash')))
->setParameter('url_hash', $hash);
$result = $query->execute()->fetch();
return (int)$result['status'];
}
/**
* create hash from URL
*
* @param string $url
* @return string
*/
protected function hash($url) {
$normalized = $this->normalizeUrl($url);
return md5($normalized);
}
/**
* normalize URL, used to create the md5 hash
*
* @param string $url
* @return string
*/
protected function normalizeUrl($url) {
$normalized = $url;
if (strpos($url, 'https://') === 0) {
$normalized = substr($url, strlen('https://'));
} else if (strpos($url, 'http://') === 0) {
$normalized = substr($url, strlen('http://'));
}
$normalized = Filesystem::normalizePath($normalized);
$normalized = trim($normalized, '/');
return $normalized;
}
}

@ -0,0 +1,50 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
*
* @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\Federation;
class Hooks {
/** @var TrustedServers */
private $trustedServers;
public function __construct(TrustedServers $trustedServers) {
$this->trustedServers = $trustedServers;
}
/**
* add servers to the list of trusted servers once a federated share was established
*
* @param array $params
*/
public function addServerHook($params) {
if (
$this->trustedServers->getAutoAddServers() === true &&
$this->trustedServers->isTrustedServer($params['server']) === false
) {
$this->trustedServers->addServer($params['server']);
}
}
}

@ -0,0 +1,254 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
*
* @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\Federation;
use OCP\AppFramework\Http;
use OCP\BackgroundJob\IJobList;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\ILogger;
use OCP\Security\ISecureRandom;
class TrustedServers {
/** after a user list was exchanged at least once successfully */
const STATUS_OK = 1;
/** waiting for shared secret or initial user list exchange */
const STATUS_PENDING = 2;
/** something went wrong, misconfigured server, software bug,... user interaction needed */
const STATUS_FAILURE = 3;
/** @var dbHandler */
private $dbHandler;
/** @var IClientService */
private $httpClientService;
/** @var ILogger */
private $logger;
/** @var IJobList */
private $jobList;
/** @var ISecureRandom */
private $secureRandom;
/** @var IConfig */
private $config;
/**
* @param DbHandler $dbHandler
* @param IClientService $httpClientService
* @param ILogger $logger
* @param IJobList $jobList
* @param ISecureRandom $secureRandom
* @param IConfig $config
*/
public function __construct(
DbHandler $dbHandler,
IClientService $httpClientService,
ILogger $logger,
IJobList $jobList,
ISecureRandom $secureRandom,
IConfig $config
) {
$this->dbHandler = $dbHandler;
$this->httpClientService = $httpClientService;
$this->logger = $logger;
$this->jobList = $jobList;
$this->secureRandom = $secureRandom;
$this->config = $config;
}
/**
* add server to the list of trusted ownCloud servers
*
* @param $url
* @return int server id
*/
public function addServer($url) {
$url = $this->updateProtocol($url);
$result = $this->dbHandler->addServer($url);
if ($result) {
$token = $this->secureRandom->getMediumStrengthGenerator()->generate(16);
$this->dbHandler->addToken($url, $token);
$this->jobList->add(
'OCA\Federation\BackgroundJob\RequestSharedSecret',
[
'url' => $url,
'token' => $token
]
);
}
return $result;
}
/**
* enable/disable to automatically add servers to the list of trusted servers
* once a federated share was created and accepted successfully
*
* @param bool $status
*/
public function setAutoAddServers($status) {
$value = $status ? '1' : '0';
$this->config->setAppValue('federation', 'autoAddServers', $value);
}
/**
* return if we automatically add servers to the list of trusted servers
* once a federated share was created and accepted successfully
*
* @return bool
*/
public function getAutoAddServers() {
$value = $this->config->getAppValue('federation', 'autoAddServers', '1');
return $value === '1';
}
/**
* get shared secret for the given server
*
* @param string $url
* @return string
*/
public function getSharedSecret($url) {
return $this->dbHandler->getSharedSecret($url);
}
/**
* add shared secret for the given server
*
* @param string $url
* @param $sharedSecret
*/
public function addSharedSecret($url, $sharedSecret) {
$this->dbHandler->addSharedSecret($url, $sharedSecret);
}
/**
* remove server from the list of trusted ownCloud servers
*
* @param int $id
*/
public function removeServer($id) {
$this->dbHandler->removeServer($id);
}
/**
* get all trusted servers
*
* @return array
*/
public function getServers() {
return $this->dbHandler->getAllServer();
}
/**
* check if given server is a trusted ownCloud server
*
* @param string $url
* @return bool
*/
public function isTrustedServer($url) {
return $this->dbHandler->serverExists($url);
}
/**
* set server status
*
* @param string $url
* @param int $status
*/
public function setServerStatus($url, $status) {
$this->dbHandler->setServerStatus($url, $status);
}
/**
* @param string $url
* @return int
*/
public function getServerStatus($url) {
return $this->dbHandler->getServerStatus($url);
}
/**
* check if URL point to a ownCloud server
*
* @param string $url
* @return bool
*/
public function isOwnCloudServer($url) {
$isValidOwnCloud = false;
$client = $this->httpClientService->newClient();
try {
$result = $client->get(
$url . '/status.php',
[
'timeout' => 3,
'connect_timeout' => 3,
]
);
if ($result->getStatusCode() === Http::STATUS_OK) {
$isValidOwnCloud = $this->checkOwnCloudVersion($result->getBody());
}
} catch (\Exception $e) {
$this->logger->error($e->getMessage(), ['app' => 'federation']);
return false;
}
return $isValidOwnCloud;
}
/**
* check if ownCloud version is >= 9.0
*
* @param $statusphp
* @return bool
*/
protected function checkOwnCloudVersion($statusphp) {
$decoded = json_decode($statusphp, true);
if (!empty($decoded) && isset($decoded['version'])) {
return version_compare($decoded['version'], '9.0.0', '>=');
}
return false;
}
/**
* check if the URL contain a protocol, if not add https
*
* @param string $url
* @return string
*/
protected function updateProtocol($url) {
if (
strpos($url, 'https://') === 0
|| strpos($url, 'http://') === 0
) {
return $url;
}
return 'https://' . $url;
}
}

@ -0,0 +1,71 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
*
* @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\Federation\Middleware ;
use OC\HintException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Middleware;
use OCP\IL10N;
use OCP\ILogger;
class AddServerMiddleware extends Middleware {
/** @var string */
protected $appName;
/** @var IL10N */
protected $l;
/** @var ILogger */
protected $logger;
public function __construct($appName, IL10N $l, ILogger $logger) {
$this->appName = $appName;
$this->l = $l;
$this->logger = $logger;
}
/**
* Log error message and return a response which can be displayed to the user
*
* @param \OCP\AppFramework\Controller $controller
* @param string $methodName
* @param \Exception $exception
* @return JSONResponse
*/
public function afterException($controller, $methodName, \Exception $exception) {
$this->logger->error($exception->getMessage(), ['app' => $this->appName]);
if ($exception instanceof HintException) {
$message = $exception->getHint();
} else {
$message = $this->l->t('Unknown error');
}
return new JSONResponse(
['message' => $message],
Http::STATUS_BAD_REQUEST
);
}
}

@ -0,0 +1,43 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
*
* @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/>
*
*/
\OC_Util::checkAdminUser();
$template = new OCP\Template('federation', 'settings-admin');
$dbHandler = new \OCA\Federation\DbHandler(
\OC::$server->getDatabaseConnection(),
\OC::$server->getL10N('federation')
);
$trustedServers = new \OCA\Federation\TrustedServers(
$dbHandler,
\OC::$server->getHTTPClientService(),
\OC::$server->getLogger(),
\OC::$server->getJobList(),
\OC::$server->getSecureRandom(),
\OC::$server->getConfig()
);
$template->assign('trustedServers', $trustedServers->getServers());
$template->assign('autoAddServers', $trustedServers->getAutoAddServers());
return $template->fetchPage();

@ -0,0 +1,40 @@
<?php
/** @var array $_ */
use OCA\Federation\TrustedServers;
/** @var OC_L10N $l */
script('federation', 'settings-admin');
style('federation', 'settings-admin')
?>
<div id="ocFederationSettings" class="section">
<h2><?php p($l->t('Federation')); ?></h2>
<em><?php p($l->t('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.')); ?></em>
<p>
<input id="autoAddServers" type="checkbox" class="checkbox" <?php if($_['autoAddServers']) p('checked'); ?> />
<label for="autoAddServers">Add server automatically once a federated share was created successfully</label>
</p>
<h3>Trusted ownCloud Servers</h3>
<p id="ocFederationAddServer">
<button id="ocFederationAddServerButton" class="">+ Add ownCloud server</button>
<input id="serverUrl" class="hidden" type="text" value="" placeholder="ownCloud Server" name="server_url"/>
<span class="msg"></span>
</p>
<ul id="listOfTrustedServers">
<?php foreach($_['trustedServers'] as $trustedServer) { ?>
<li id="<?php p($trustedServer['id']); ?>" class="icon-delete">
<?php if((int)$trustedServer['status'] === TrustedServers::STATUS_OK) { ?>
<span class="status success"></span>
<?php } elseif((int)$trustedServer['status'] === TrustedServers::STATUS_PENDING) { ?>
<span class="status indeterminate"></span>
<?php } else {?>
<span class="status error"></span>
<?php } ?>
<?php p($trustedServer['url']); ?>
</li>
<?php } ?>
</ul>
</div>

@ -0,0 +1,184 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
*
* @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\Federation\Tests\API;
use OC\BackgroundJob\JobList;
use OCA\Federation\API\OCSAuthAPI;
use OCA\Federation\DbHandler;
use OCA\Federation\TrustedServers;
use OCP\AppFramework\Http;
use OCP\IRequest;
use OCP\Security\ISecureRandom;
use Test\TestCase;
class OCSAuthAPITest extends TestCase {
/** @var \PHPUnit_Framework_MockObject_MockObject | IRequest */
private $request;
/** @var \PHPUnit_Framework_MockObject_MockObject | ISecureRandom */
private $secureRandom;
/** @var \PHPUnit_Framework_MockObject_MockObject | JobList */
private $jobList;
/** @var \PHPUnit_Framework_MockObject_MockObject | TrustedServers */
private $trustedServers;
/** @var \PHPUnit_Framework_MockObject_MockObject | DbHandler */
private $dbHandler;
/** @var OCSAuthApi */
private $ocsAuthApi;
public function setUp() {
parent::setUp();
$this->request = $this->getMock('OCP\IRequest');
$this->secureRandom = $this->getMock('OCP\Security\ISecureRandom');
$this->trustedServers = $this->getMockBuilder('OCA\Federation\TrustedServers')
->disableOriginalConstructor()->getMock();
$this->dbHandler = $this->getMockBuilder('OCA\Federation\DbHandler')
->disableOriginalConstructor()->getMock();
$this->jobList = $this->getMockBuilder('OC\BackgroundJob\JobList')
->disableOriginalConstructor()->getMock();
$this->ocsAuthApi = new OCSAuthAPI(
$this->request,
$this->secureRandom,
$this->jobList,
$this->trustedServers,
$this->dbHandler
);
}
/**
* @dataProvider dataTestRequestSharedSecret
*
* @param string $token
* @param string $localToken
* @param bool $isTrustedServer
* @param int $expected
*/
public function testRequestSharedSecret($token, $localToken, $isTrustedServer, $expected) {
$url = 'url';
$this->request->expects($this->at(0))->method('getParam')->with('url')->willReturn($url);
$this->request->expects($this->at(1))->method('getParam')->with('token')->willReturn($token);
$this->trustedServers
->expects($this->once())
->method('isTrustedServer')->with($url)->willReturn($isTrustedServer);
$this->dbHandler->expects($this->any())
->method('getToken')->with($url)->willReturn($localToken);
if ($expected === Http::STATUS_OK) {
$this->jobList->expects($this->once())->method('add')
->with('OCA\Federation\BackgroundJob\GetSharedSecret', ['url' => $url, 'token' => $token]);
} else {
$this->jobList->expects($this->never())->method('add');
}
$result = $this->ocsAuthApi->requestSharedSecret();
$this->assertSame($expected, $result->getStatusCode());
}
public function dataTestRequestSharedSecret() {
return [
['token2', 'token1', true, Http::STATUS_OK],
['token1', 'token2', false, Http::STATUS_FORBIDDEN],
['token1', 'token2', true, Http::STATUS_FORBIDDEN],
];
}
/**
* @dataProvider dataTestGetSharedSecret
*
* @param bool $isTrustedServer
* @param bool $isValidToken
* @param int $expected
*/
public function testGetSharedSecret($isTrustedServer, $isValidToken, $expected) {
$url = 'url';
$token = 'token';
$this->request->expects($this->at(0))->method('getParam')->with('url')->willReturn($url);
$this->request->expects($this->at(1))->method('getParam')->with('token')->willReturn($token);
/** @var OCSAuthAPI | \PHPUnit_Framework_MockObject_MockObject $ocsAuthApi */
$ocsAuthApi = $this->getMockBuilder('OCA\Federation\API\OCSAuthAPI')
->setConstructorArgs(
[
$this->request,
$this->secureRandom,
$this->jobList,
$this->trustedServers,
$this->dbHandler
]
)->setMethods(['isValidToken'])->getMock();
$this->trustedServers
->expects($this->any())
->method('isTrustedServer')->with($url)->willReturn($isTrustedServer);
$ocsAuthApi->expects($this->any())
->method('isValidToken')->with($url, $token)->willReturn($isValidToken);
if($expected === Http::STATUS_OK) {
$this->secureRandom->expects($this->once())->method('getMediumStrengthGenerator')
->willReturn($this->secureRandom);
$this->secureRandom->expects($this->once())->method('generate')->with(32)
->willReturn('secret');
$this->trustedServers->expects($this->once())
->method('addSharedSecret')->willReturn($url, 'secret');
$this->dbHandler->expects($this->once())
->method('addToken')->with($url, '');
} else {
$this->secureRandom->expects($this->never())->method('getMediumStrengthGenerator');
$this->secureRandom->expects($this->never())->method('generate');
$this->trustedServers->expects($this->never())->method('addSharedSecret');
$this->dbHandler->expects($this->never())->method('addToken');
}
$result = $ocsAuthApi->getSharedSecret();
$this->assertSame($expected, $result->getStatusCode());
if ($expected === Http::STATUS_OK) {
$data = $result->getData();
$this->assertSame('secret', $data['sharedSecret']);
}
}
public function dataTestGetSharedSecret() {
return [
[true, true, Http::STATUS_OK],
[false, true, Http::STATUS_FORBIDDEN],
[true, false, Http::STATUS_FORBIDDEN],
[false, false, Http::STATUS_FORBIDDEN],
];
}
}

@ -0,0 +1,197 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
*
* @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\Federation\Tests\BackgroundJob;
use OCA\Federation\BackgroundJob\GetSharedSecret;
use OCA\Files_Sharing\Tests\TestCase;
use OCA\Federation\DbHandler;
use OCA\Federation\TrustedServers;
use OCP\AppFramework\Http;
use OCP\BackgroundJob\IJobList;
use OCP\Http\Client\IClient;
use OCP\Http\Client\IResponse;
use OCP\ILogger;
use OCP\IURLGenerator;
/**
* Class GetSharedSecretTest
*
* @group DB
*
* @package OCA\Federation\Tests\BackgroundJob
*/
class GetSharedSecretTest extends TestCase {
/** @var \PHPUnit_Framework_MockObject_MockObject | IClient */
private $httpClient;
/** @var \PHPUnit_Framework_MockObject_MockObject | IJobList */
private $jobList;
/** @var \PHPUnit_Framework_MockObject_MockObject | IURLGenerator */
private $urlGenerator;
/** @var \PHPUnit_Framework_MockObject_MockObject | TrustedServers */
private $trustedServers;
/** @var \PHPUnit_Framework_MockObject_MockObject | DbHandler */
private $dbHandler;
/** @var \PHPUnit_Framework_MockObject_MockObject | ILogger */
private $logger;
/** @var \PHPUnit_Framework_MockObject_MockObject | IResponse */
private $response;
/** @var GetSharedSecret */
private $getSharedSecret;
public function setUp() {
parent::setUp();
$this->httpClient = $this->getMock('OCP\Http\Client\IClient');
$this->jobList = $this->getMock('OCP\BackgroundJob\IJobList');
$this->urlGenerator = $this->getMock('OCP\IURLGenerator');
$this->trustedServers = $this->getMockBuilder('OCA\Federation\TrustedServers')
->disableOriginalConstructor()->getMock();
$this->dbHandler = $this->getMockBuilder('OCA\Federation\DbHandler')
->disableOriginalConstructor()->getMock();
$this->logger = $this->getMock('OCP\ILogger');
$this->response = $this->getMock('OCP\Http\Client\IResponse');
$this->getSharedSecret = new GetSharedSecret(
$this->httpClient,
$this->urlGenerator,
$this->jobList,
$this->trustedServers,
$this->logger,
$this->dbHandler
);
}
/**
* @dataProvider dataTestExecute
*
* @param bool $isTrustedServer
*/
public function testExecute($isTrustedServer) {
/** @var GetSharedSecret |\PHPUnit_Framework_MockObject_MockObject $getSharedSecret */
$getSharedSecret = $this->getMockBuilder('OCA\Federation\BackgroundJob\GetSharedSecret')
->setConstructorArgs(
[
$this->httpClient,
$this->urlGenerator,
$this->jobList,
$this->trustedServers,
$this->logger,
$this->dbHandler
]
)->setMethods(['parentExecute'])->getMock();
$this->invokePrivate($getSharedSecret, 'argument', [['url' => 'url']]);
$this->jobList->expects($this->once())->method('remove');
$this->trustedServers->expects($this->once())->method('isTrustedServer')
->with('url')->willReturn($isTrustedServer);
if ($isTrustedServer) {
$getSharedSecret->expects($this->once())->method('parentExecute');
} else {
$getSharedSecret->expects($this->never())->method('parentExecute');
}
$getSharedSecret->execute($this->jobList);
}
public function dataTestExecute() {
return [
[true],
[false]
];
}
/**
* @dataProvider dataTestRun
*
* @param int $statusCode
*/
public function testRun($statusCode) {
$target = 'targetURL';
$source = 'sourceURL';
$token = 'token';
$argument = ['url' => $target, 'token' => $token];
$this->urlGenerator->expects($this->once())->method('getAbsoluteURL')->with('/')
->willReturn($source);
$this->httpClient->expects($this->once())->method('get')
->with(
$target . '/ocs/v2.php/apps/federation/api/v1/shared-secret?format=json',
[
'query' =>
[
'url' => $source,
'token' => $token
],
'timeout' => 3,
'connect_timeout' => 3,
]
)->willReturn($this->response);
$this->response->expects($this->once())->method('getStatusCode')
->willReturn($statusCode);
if (
$statusCode !== Http::STATUS_OK
&& $statusCode !== Http::STATUS_FORBIDDEN
) {
$this->jobList->expects($this->once())->method('add')
->with('OCA\Federation\BackgroundJob\GetSharedSecret', $argument);
$this->dbHandler->expects($this->never())->method('addToken');
} else {
$this->dbHandler->expects($this->once())->method('addToken')->with($target, '');
$this->jobList->expects($this->never())->method('add');
}
if ($statusCode === Http::STATUS_OK) {
$this->response->expects($this->once())->method('getBody')
->willReturn('{"ocs":{"data":{"sharedSecret":"secret"}}}');
$this->trustedServers->expects($this->once())->method('addSharedSecret')
->with($target, 'secret');
} else {
$this->trustedServers->expects($this->never())->method('addSharedSecret');
}
$this->invokePrivate($this->getSharedSecret, 'run', [$argument]);
}
public function dataTestRun() {
return [
[Http::STATUS_OK],
[Http::STATUS_FORBIDDEN],
[Http::STATUS_CONFLICT],
];
}
}

@ -0,0 +1,169 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
*
* @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\Federation\Tests\BackgroundJob;
use OCA\Federation\BackgroundJob\RequestSharedSecret;
use OCP\AppFramework\Http;
use Test\TestCase;
class RequestSharedSecretTest extends TestCase {
/** @var \PHPUnit_Framework_MockObject_MockObject | IClient */
private $httpClient;
/** @var \PHPUnit_Framework_MockObject_MockObject | IJobList */
private $jobList;
/** @var \PHPUnit_Framework_MockObject_MockObject | IURLGenerator */
private $urlGenerator;
/** @var \PHPUnit_Framework_MockObject_MockObject | DbHandler */
private $dbHandler;
/** @var \PHPUnit_Framework_MockObject_MockObject | TrustedServers */
private $trustedServers;
/** @var \PHPUnit_Framework_MockObject_MockObject | IResponse */
private $response;
/** @var RequestSharedSecret */
private $requestSharedSecret;
public function setUp() {
parent::setUp();
$this->httpClient = $this->getMock('OCP\Http\Client\IClient');
$this->jobList = $this->getMock('OCP\BackgroundJob\IJobList');
$this->urlGenerator = $this->getMock('OCP\IURLGenerator');
$this->trustedServers = $this->getMockBuilder('OCA\Federation\TrustedServers')
->disableOriginalConstructor()->getMock();
$this->dbHandler = $this->getMockBuilder('OCA\Federation\DbHandler')
->disableOriginalConstructor()->getMock();
$this->response = $this->getMock('OCP\Http\Client\IResponse');
$this->requestSharedSecret = new RequestSharedSecret(
$this->httpClient,
$this->urlGenerator,
$this->jobList,
$this->trustedServers,
$this->dbHandler
);
}
/**
* @dataProvider dataTestExecute
*
* @param bool $isTrustedServer
*/
public function testExecute($isTrustedServer) {
/** @var RequestSharedSecret |\PHPUnit_Framework_MockObject_MockObject $requestSharedSecret */
$requestSharedSecret = $this->getMockBuilder('OCA\Federation\BackgroundJob\RequestSharedSecret')
->setConstructorArgs(
[
$this->httpClient,
$this->urlGenerator,
$this->jobList,
$this->trustedServers,
$this->dbHandler
]
)->setMethods(['parentExecute'])->getMock();
$this->invokePrivate($requestSharedSecret, 'argument', [['url' => 'url']]);
$this->jobList->expects($this->once())->method('remove');
$this->trustedServers->expects($this->once())->method('isTrustedServer')
->with('url')->willReturn($isTrustedServer);
if ($isTrustedServer) {
$requestSharedSecret->expects($this->once())->method('parentExecute');
} else {
$requestSharedSecret->expects($this->never())->method('parentExecute');
}
$requestSharedSecret->execute($this->jobList);
}
public function dataTestExecute() {
return [
[true],
[false]
];
}
/**
* @dataProvider dataTestRun
*
* @param int $statusCode
*/
public function testRun($statusCode) {
$target = 'targetURL';
$source = 'sourceURL';
$token = 'token';
$argument = ['url' => $target, 'token' => $token];
$this->urlGenerator->expects($this->once())->method('getAbsoluteURL')->with('/')
->willReturn($source);
$this->httpClient->expects($this->once())->method('post')
->with(
$target . '/ocs/v2.php/apps/federation/api/v1/request-shared-secret?format=json',
[
'body' =>
[
'url' => $source,
'token' => $token
],
'timeout' => 3,
'connect_timeout' => 3,
]
)->willReturn($this->response);
$this->response->expects($this->once())->method('getStatusCode')
->willReturn($statusCode);
if (
$statusCode !== Http::STATUS_OK
&& $statusCode !== Http::STATUS_FORBIDDEN
) {
$this->jobList->expects($this->once())->method('add')
->with('OCA\Federation\BackgroundJob\RequestSharedSecret', $argument);
$this->dbHandler->expects($this->never())->method('addToken');
}
if ($statusCode === Http::STATUS_FORBIDDEN) {
$this->jobList->expects($this->never())->method('add');
$this->dbHandler->expects($this->once())->method('addToken')->with($target, '');
}
$this->invokePrivate($this->requestSharedSecret, 'run', [$argument]);
}
public function dataTestRun() {
return [
[Http::STATUS_OK],
[Http::STATUS_FORBIDDEN],
[Http::STATUS_CONFLICT],
];
}
}

@ -0,0 +1,166 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
*
* @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\Federation\Tests\Controller;
use OCA\Federation\Controller\SettingsController;
use OCP\AppFramework\Http\DataResponse;
use Test\TestCase;
class SettingsControllerTest extends TestCase {
/** @var SettingsController */
private $controller;
/** @var \PHPUnit_Framework_MockObject_MockObject | \OCP\IRequest */
private $request;
/** @var \PHPUnit_Framework_MockObject_MockObject | \OCP\IL10N */
private $l10n;
/** @var \PHPUnit_Framework_MockObject_MockObject | \OCA\Federation\TrustedServers */
private $trustedServers;
public function setUp() {
parent::setUp();
$this->request = $this->getMock('OCP\IRequest');
$this->l10n = $this->getMock('OCP\IL10N');
$this->trustedServers = $this->getMockBuilder('OCA\Federation\TrustedServers')
->disableOriginalConstructor()->getMock();
$this->controller = new SettingsController(
'SettingsControllerTest',
$this->request,
$this->l10n,
$this->trustedServers
);
}
public function testAddServer() {
$this->trustedServers
->expects($this->once())
->method('isTrustedServer')
->with('url')
->willReturn(false);
$this->trustedServers
->expects($this->once())
->method('isOwnCloudServer')
->with('url')
->willReturn(true);
$result = $this->controller->addServer('url');
$this->assertTrue($result instanceof DataResponse);
$data = $result->getData();
$this->assertSame(200, $result->getStatus());
$this->assertSame('url', $data['url']);
$this->assertArrayHasKey('id', $data);
}
/**
* @dataProvider checkServerFails
* @expectedException \OC\HintException
*
* @param bool $isTrustedServer
* @param bool $isOwnCloud
*/
public function testAddServerFail($isTrustedServer, $isOwnCloud) {
$this->trustedServers
->expects($this->any())
->method('isTrustedServer')
->with('url')
->willReturn($isTrustedServer);
$this->trustedServers
->expects($this->any())
->method('isOwnCloudServer')
->with('url')
->willReturn($isOwnCloud);
$this->controller->addServer('url');
}
public function testRemoveServer() {
$this->trustedServers->expects($this->once())->method('removeServer')
->with('url');
$result = $this->controller->removeServer('url');
$this->assertTrue($result instanceof DataResponse);
$this->assertSame(200, $result->getStatus());
}
public function testCheckServer() {
$this->trustedServers
->expects($this->once())
->method('isTrustedServer')
->with('url')
->willReturn(false);
$this->trustedServers
->expects($this->once())
->method('isOwnCloudServer')
->with('url')
->willReturn(true);
$this->assertTrue(
$this->invokePrivate($this->controller, 'checkServer', ['url'])
);
}
/**
* @dataProvider checkServerFails
* @expectedException \OC\HintException
*
* @param bool $isTrustedServer
* @param bool $isOwnCloud
*/
public function testCheckServerFail($isTrustedServer, $isOwnCloud) {
$this->trustedServers
->expects($this->any())
->method('isTrustedServer')
->with('url')
->willReturn($isTrustedServer);
$this->trustedServers
->expects($this->any())
->method('isOwnCloudServer')
->with('url')
->willReturn($isOwnCloud);
$this->assertTrue(
$this->invokePrivate($this->controller, 'checkServer', ['url'])
);
}
/**
* data to simulate checkServer fails
*
* @return array
*/
public function checkServerFails() {
return [
[true, true],
[false, false]
];
}
}

@ -0,0 +1,259 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
*
* @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\Federation\Tests\lib;
use OCA\Federation\DbHandler;
use OCA\Federation\TrustedServers;
use OCP\IDBConnection;
use Test\TestCase;
/**
* @group DB
*/
class DbHandlerTest extends TestCase {
/** @var DbHandler */
private $dbHandler;
/** @var \PHPUnit_Framework_MockObject_MockObject */
private $il10n;
/** @var IDBConnection */
private $connection;
/** @var string */
private $dbTable = 'trusted_servers';
public function setUp() {
parent::setUp();
$this->connection = \OC::$server->getDatabaseConnection();
$this->il10n = $this->getMock('OCP\IL10N');
$this->dbHandler = new DbHandler(
$this->connection,
$this->il10n
);
$query = $this->connection->getQueryBuilder()->select('*')->from($this->dbTable);
$result = $query->execute()->fetchAll();
$this->assertEmpty($result, 'we need to start with a empty trusted_servers table');
}
public function tearDown() {
parent::tearDown();
$query = $this->connection->getQueryBuilder()->delete($this->dbTable);
$query->execute();
}
/**
* @dataProvider dataTestAddServer
*
* @param string $url passed to the method
* @param string $expectedUrl the url we expect to be written to the db
* @param string $expectedHash the hash value we expect to be written to the db
*/
public function testAddServer($url, $expectedUrl, $expectedHash) {
$id = $this->dbHandler->addServer($url);
$query = $this->connection->getQueryBuilder()->select('*')->from($this->dbTable);
$result = $query->execute()->fetchAll();
$this->assertSame(1, count($result));
$this->assertSame($expectedUrl, $result[0]['url']);
$this->assertSame($id, (int)$result[0]['id']);
$this->assertSame($expectedHash, $result[0]['url_hash']);
$this->assertSame(TrustedServers::STATUS_PENDING, (int)$result[0]['status']);
}
public function dataTestAddServer() {
return [
['http://owncloud.org', 'http://owncloud.org', md5('owncloud.org')],
['https://owncloud.org', 'https://owncloud.org', md5('owncloud.org')],
['http://owncloud.org/', 'http://owncloud.org', md5('owncloud.org')],
];
}
public function testRemove() {
$id1 = $this->dbHandler->addServer('server1');
$id2 = $this->dbHandler->addServer('server2');
$query = $this->connection->getQueryBuilder()->select('*')->from($this->dbTable);
$result = $query->execute()->fetchAll();
$this->assertSame(2, count($result));
$this->assertSame('server1', $result[0]['url']);
$this->assertSame('server2', $result[1]['url']);
$this->assertSame($id1, (int)$result[0]['id']);
$this->assertSame($id2, (int)$result[1]['id']);
$this->dbHandler->removeServer($id2);
$query = $this->connection->getQueryBuilder()->select('*')->from($this->dbTable);
$result = $query->execute()->fetchAll();
$this->assertSame(1, count($result));
$this->assertSame('server1', $result[0]['url']);
$this->assertSame($id1, (int)$result[0]['id']);
}
public function testGetAll() {
$id1 = $this->dbHandler->addServer('server1');
$id2 = $this->dbHandler->addServer('server2');
$result = $this->dbHandler->getAllServer();
$this->assertSame(2, count($result));
$this->assertSame('server1', $result[0]['url']);
$this->assertSame('server2', $result[1]['url']);
$this->assertSame($id1, (int)$result[0]['id']);
$this->assertSame($id2, (int)$result[1]['id']);
}
/**
* @dataProvider dataTestServerExists
*
* @param string $serverInTable
* @param string $checkForServer
* @param bool $expected
*/
public function testServerExists($serverInTable, $checkForServer, $expected) {
$this->dbHandler->addServer($serverInTable);
$this->assertSame($expected,
$this->dbHandler->serverExists($checkForServer)
);
}
public function dataTestServerExists() {
return [
['server1', 'server1', true],
['server1', 'http://server1', true],
['server1', 'server2', false]
];
}
public function testAddToken() {
$this->dbHandler->addServer('server1');
$query = $this->connection->getQueryBuilder()->select('*')->from($this->dbTable);
$result = $query->execute()->fetchAll();
$this->assertSame(1, count($result));
$this->assertSame(null, $result[0]['token']);
$this->dbHandler->addToken('http://server1', 'token');
$query = $this->connection->getQueryBuilder()->select('*')->from($this->dbTable);
$result = $query->execute()->fetchAll();
$this->assertSame(1, count($result));
$this->assertSame('token', $result[0]['token']);
}
public function testGetToken() {
$this->dbHandler->addServer('server1');
$this->dbHandler->addToken('http://server1', 'token');
$this->assertSame('token',
$this->dbHandler->getToken('https://server1')
);
}
public function testAddSharedSecret() {
$this->dbHandler->addServer('server1');
$query = $this->connection->getQueryBuilder()->select('*')->from($this->dbTable);
$result = $query->execute()->fetchAll();
$this->assertSame(1, count($result));
$this->assertSame(null, $result[0]['shared_secret']);
$this->dbHandler->addSharedSecret('http://server1', 'secret');
$query = $this->connection->getQueryBuilder()->select('*')->from($this->dbTable);
$result = $query->execute()->fetchAll();
$this->assertSame(1, count($result));
$this->assertSame('secret', $result[0]['shared_secret']);
}
public function testGetSharedSecret() {
$this->dbHandler->addServer('server1');
$this->dbHandler->addSharedSecret('http://server1', 'secret');
$this->assertSame('secret',
$this->dbHandler->getSharedSecret('https://server1')
);
}
public function testSetServerStatus() {
$this->dbHandler->addServer('server1');
$query = $this->connection->getQueryBuilder()->select('*')->from($this->dbTable);
$result = $query->execute()->fetchAll();
$this->assertSame(1, count($result));
$this->assertSame(TrustedServers::STATUS_PENDING, (int)$result[0]['status']);
$this->dbHandler->setServerStatus('http://server1', TrustedServers::STATUS_OK);
$query = $this->connection->getQueryBuilder()->select('*')->from($this->dbTable);
$result = $query->execute()->fetchAll();
$this->assertSame(1, count($result));
$this->assertSame(TrustedServers::STATUS_OK, (int)$result[0]['status']);
}
public function testGetServerStatus() {
$this->dbHandler->addServer('server1');
$this->dbHandler->setServerStatus('http://server1', TrustedServers::STATUS_OK);
$this->assertSame(TrustedServers::STATUS_OK,
$this->dbHandler->getServerStatus('https://server1')
);
}
/**
* hash should always be computed with the normalized URL
*
* @dataProvider dataTestHash
*
* @param string $url
* @param string $expected
*/
public function testHash($url, $expected) {
$this->assertSame($expected,
$this->invokePrivate($this->dbHandler, 'hash', [$url])
);
}
public function dataTestHash() {
return [
['server1', md5('server1')],
['http://server1', md5('server1')],
['https://server1', md5('server1')],
['http://server1/', md5('server1')],
];
}
/**
* @dataProvider dataTestNormalizeUrl
*
* @param string $url
* @param string $expected
*/
public function testNormalizeUrl($url, $expected) {
$this->assertSame($expected,
$this->invokePrivate($this->dbHandler, 'normalizeUrl', [$url])
);
}
public function dataTestNormalizeUrl() {
return [
['owncloud.org', 'owncloud.org'],
['http://owncloud.org', 'owncloud.org'],
['https://owncloud.org', 'owncloud.org'],
['https://owncloud.org//mycloud', 'owncloud.org/mycloud'],
['https://owncloud.org/mycloud/', 'owncloud.org/mycloud'],
];
}
}

@ -0,0 +1,79 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
*
* @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\Federation\Tests\lib;
use OCA\Federation\Hooks;
use OCA\Federation\TrustedServers;
use Test\TestCase;
class HooksTest extends TestCase {
/** @var \PHPUnit_Framework_MockObject_MockObject | TrustedServers */
private $trustedServers;
/** @var Hooks */
private $hooks;
public function setUp() {
parent::setUp();
$this->trustedServers = $this->getMockBuilder('OCA\Federation\TrustedServers')
->disableOriginalConstructor()->getMock();
$this->hooks = new Hooks($this->trustedServers);
}
/**
* @dataProvider dataTestAddServerHook
*
* @param bool $autoAddEnabled is auto-add enabled
* @param bool $isTrustedServer is the server already in the list of trusted servers
* @param bool $addServer should the server be added
*/
public function testAddServerHook($autoAddEnabled, $isTrustedServer, $addServer) {
$this->trustedServers->expects($this->any())->method('getAutoAddServers')
->willReturn($autoAddEnabled);
$this->trustedServers->expects($this->any())->method('isTrustedServer')
->with('url')->willReturn($isTrustedServer);
if ($addServer) {
$this->trustedServers->expects($this->once())->method('addServer')
->with('url');
} else {
$this->trustedServers->expects($this->never())->method('addServer');
}
$this->hooks->addServerHook(['server' => 'url']);
}
public function dataTestAddServerHook() {
return [
[true, true, false],
[false, true, false],
[true, false, true],
[false, false, false],
];
}
}

@ -0,0 +1,344 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
*
* @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\Federation\Tests\lib;
use OCA\Federation\DbHandler;
use OCA\Federation\TrustedServers;
use OCP\BackgroundJob\IJobList;
use OCP\Http\Client\IClient;
use OCP\Http\Client\IClientService;
use OCP\Http\Client\IResponse;
use OCP\IConfig;
use OCP\ILogger;
use OCP\Security\ISecureRandom;
use Test\TestCase;
class TrustedServersTest extends TestCase {
/** @var TrustedServers */
private $trustedServers;
/** @var \PHPUnit_Framework_MockObject_MockObject | DbHandler */
private $dbHandler;
/** @var \PHPUnit_Framework_MockObject_MockObject | IClientService */
private $httpClientService;
/** @var \PHPUnit_Framework_MockObject_MockObject | IClient */
private $httpClient;
/** @var \PHPUnit_Framework_MockObject_MockObject | IResponse */
private $response;
/** @var \PHPUnit_Framework_MockObject_MockObject | ILogger */
private $logger;
/** @var \PHPUnit_Framework_MockObject_MockObject | IJobList */
private $jobList;
/** @var \PHPUnit_Framework_MockObject_MockObject | ISecureRandom */
private $secureRandom;
/** @var \PHPUnit_Framework_MockObject_MockObject | IConfig */
private $config;
public function setUp() {
parent::setUp();
$this->dbHandler = $this->getMockBuilder('\OCA\Federation\DbHandler')
->disableOriginalConstructor()->getMock();
$this->httpClientService = $this->getMock('OCP\Http\Client\IClientService');
$this->httpClient = $this->getMock('OCP\Http\Client\IClient');
$this->response = $this->getMock('OCP\Http\Client\IResponse');
$this->logger = $this->getMock('OCP\ILogger');
$this->jobList = $this->getMock('OCP\BackgroundJob\IJobList');
$this->secureRandom = $this->getMock('OCP\Security\ISecureRandom');
$this->config = $this->getMock('OCP\IConfig');
$this->trustedServers = new TrustedServers(
$this->dbHandler,
$this->httpClientService,
$this->logger,
$this->jobList,
$this->secureRandom,
$this->config
);
}
/**
* @dataProvider dataTrueFalse
*
* @param bool $success
*/
public function testAddServer($success) {
/** @var \PHPUnit_Framework_MockObject_MockObject | TrustedServers $trustedServer */
$trustedServers = $this->getMockBuilder('OCA\Federation\TrustedServers')
->setConstructorArgs(
[
$this->dbHandler,
$this->httpClientService,
$this->logger,
$this->jobList,
$this->secureRandom,
$this->config
]
)
->setMethods(['normalizeUrl', 'updateProtocol'])
->getMock();
$trustedServers->expects($this->once())->method('updateProtocol')
->with('url')->willReturn('https://url');
$this->dbHandler->expects($this->once())->method('addServer')->with('https://url')
->willReturn($success);
if ($success) {
$this->secureRandom->expects($this->once())->method('getMediumStrengthGenerator')
->willReturn($this->secureRandom);
$this->secureRandom->expects($this->once())->method('generate')
->willReturn('token');
$this->dbHandler->expects($this->once())->method('addToken')->with('https://url', 'token');
$this->jobList->expects($this->once())->method('add')
->with('OCA\Federation\BackgroundJob\RequestSharedSecret',
['url' => 'https://url', 'token' => 'token']);
} else {
$this->jobList->expects($this->never())->method('add');
}
$this->assertSame($success,
$trustedServers->addServer('url')
);
}
public function dataTrueFalse() {
return [
[true],
[false]
];
}
/**
* @dataProvider dataTrueFalse
*
* @param bool $status
*/
public function testSetAutoAddServers($status) {
if ($status) {
$this->config->expects($this->once())->method('setAppValue')
->with('federation', 'autoAddServers', '1');
} else {
$this->config->expects($this->once())->method('setAppValue')
->with('federation', 'autoAddServers', '0');
}
$this->trustedServers->setAutoAddServers($status);
}
/**
* @dataProvider dataTestGetAutoAddServers
*
* @param string $status
* @param bool $expected
*/
public function testGetAutoAddServers($status, $expected) {
$this->config->expects($this->once())->method('getAppValue')
->with('federation', 'autoAddServers', '1')->willReturn($status);
$this->assertSame($expected,
$this->trustedServers->getAutoAddServers($status)
);
}
public function dataTestGetAutoAddServers() {
return [
['1', true],
['0', false]
];
}
public function testAddSharedSecret() {
$this->dbHandler->expects($this->once())->method('addSharedSecret')
->with('url', 'secret');
$this->trustedServers->addSharedSecret('url', 'secret');
}
public function testGetSharedSecret() {
$this->dbHandler->expects($this->once())->method('getSharedSecret')
->with('url')->willReturn(true);
$this->assertTrue(
$this->trustedServers->getSharedSecret('url')
);
}
public function testRemoveServer() {
$id = 42;
$this->dbHandler->expects($this->once())->method('removeServer')->with($id);
$this->trustedServers->removeServer($id);
}
public function testGetServers() {
$this->dbHandler->expects($this->once())->method('getAllServer')->willReturn(true);
$this->assertTrue(
$this->trustedServers->getServers()
);
}
public function testIsTrustedServer() {
$this->dbHandler->expects($this->once())->method('serverExists')->with('url')
->willReturn(true);
$this->assertTrue(
$this->trustedServers->isTrustedServer('url')
);
}
public function testSetServerStatus() {
$this->dbHandler->expects($this->once())->method('setServerStatus')
->with('url', 'status');
$this->trustedServers->setServerStatus('url', 'status');
}
public function testGetServerStatus() {
$this->dbHandler->expects($this->once())->method('getServerStatus')
->with('url')->willReturn(true);
$this->assertTrue(
$this->trustedServers->getServerStatus('url')
);
}
/**
* @dataProvider dataTestIsOwnCloudServer
*
* @param int $statusCode
* @param bool $isValidOwnCloudVersion
* @param bool $expected
*/
public function testIsOwnCloudServer($statusCode, $isValidOwnCloudVersion, $expected) {
$server = 'server1';
/** @var \PHPUnit_Framework_MockObject_MockObject | TrustedServers $trustedServer */
$trustedServers = $this->getMockBuilder('OCA\Federation\TrustedServers')
->setConstructorArgs(
[
$this->dbHandler,
$this->httpClientService,
$this->logger,
$this->jobList,
$this->secureRandom,
$this->config
]
)
->setMethods(['checkOwnCloudVersion'])
->getMock();
$this->httpClientService->expects($this->once())->method('newClient')
->willReturn($this->httpClient);
$this->httpClient->expects($this->once())->method('get')->with($server . '/status.php')
->willReturn($this->response);
$this->response->expects($this->once())->method('getStatusCode')
->willReturn($statusCode);
if ($statusCode === 200) {
$trustedServers->expects($this->once())->method('checkOwnCloudVersion')
->willReturn($isValidOwnCloudVersion);
} else {
$trustedServers->expects($this->never())->method('checkOwnCloudVersion');
}
$this->assertSame($expected,
$trustedServers->isOwnCloudServer($server)
);
}
public function dataTestIsOwnCloudServer() {
return [
[200, true, true],
[200, false, false],
[404, true, false],
];
}
public function testIsOwnCloudServerFail() {
$server = 'server1';
$this->httpClientService->expects($this->once())->method('newClient')
->willReturn($this->httpClient);
$this->logger->expects($this->once())->method('error')
->with('simulated exception', ['app' => 'federation']);
$this->httpClient->expects($this->once())->method('get')->with($server . '/status.php')
->willReturnCallback(function () {
throw new \Exception('simulated exception');
});
$this->assertFalse($this->trustedServers->isOwnCloudServer($server));
}
/**
* @dataProvider dataTestCheckOwnCloudVersion
*
* @param $statusphp
* @param $expected
*/
public function testCheckOwnCloudVersion($statusphp, $expected) {
$this->assertSame($expected,
$this->invokePrivate($this->trustedServers, 'checkOwnCloudVersion', [$statusphp])
);
}
public function dataTestCheckOwnCloudVersion() {
return [
['{"version":"8.4.0"}', false],
['{"version":"9.0.0"}', true],
['{"version":"9.1.0"}', true]
];
}
/**
* @dataProvider dataTestUpdateProtocol
* @param string $url
* @param string $expected
*/
public function testUpdateProtocol($url, $expected) {
$this->assertSame($expected,
$this->invokePrivate($this->trustedServers, 'updateProtocol', [$url])
);
}
public function dataTestUpdateProtocol() {
return [
['http://owncloud.org', 'http://owncloud.org'],
['https://owncloud.org', 'https://owncloud.org'],
['owncloud.org', 'https://owncloud.org'],
['httpserver', 'https://httpserver'],
];
}
}

@ -0,0 +1,100 @@
<?php
/**
* @author Björn Schießle <schiessle@owncloud.com>
*
* @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\Federation\Tests\Middleware;
use OC\HintException;
use OCA\Federation\Middleware\AddServerMiddleware;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use Test\TestCase;
class AddServerMiddlewareTest extends TestCase {
/** @var \PHPUnit_Framework_MockObject_MockObject | ILogger */
private $logger;
/** @var \PHPUnit_Framework_MockObject_MockObject | \OCP\IL10N */
private $l10n;
/** @var AddServerMiddleware */
private $middleware;
/** @var \PHPUnit_Framework_MockObject_MockObject | Controller */
private $controller;
public function setUp() {
parent::setUp();
$this->logger = $this->getMock('OCP\ILogger');
$this->l10n = $this->getMock('OCP\IL10N');
$this->controller = $this->getMockBuilder('OCP\AppFramework\Controller')
->disableOriginalConstructor()->getMock();
$this->middleware = new AddServerMiddleware(
'AddServerMiddlewareTest',
$this->l10n,
$this->logger
);
}
/**
* @dataProvider dataTestAfterException
*
* @param \Exception $exception
* @param string $message
* @param string $hint
*/
public function testAfterException($exception, $message, $hint) {
$this->logger->expects($this->once())->method('error')
->with($message, ['app' => 'AddServerMiddlewareTest']);
$this->l10n->expects($this->any())->method('t')
->willReturnCallback(
function($message) {
return $message;
}
);
$result = $this->middleware->afterException($this->controller, 'method', $exception);
$this->assertSame(Http::STATUS_BAD_REQUEST,
$result->getStatus()
);
$data = $result->getData();
$this->assertSame($hint,
$data['message']
);
}
public function dataTestAfterException() {
return [
[new HintException('message', 'hint'), 'message', 'hint'],
[new \Exception('message'), 'message', 'Unknown error'],
];
}
}

@ -1,81 +0,0 @@
<?php
/**
* @author Arthur Schiwon <blizzz@owncloud.com>
* @author Frank Karlitschek <frank@owncloud.org>
* @author Jakob Sack <mail@jakobsack.de>
* @author Joas Schilling <nickvergessen@owncloud.com>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Lukas Reschke <lukas@owncloud.com>
* @author Robin Appelman <icewind@owncloud.com>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Vincent Petry <pvince81@owncloud.com>
*
* @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/>
*
*/
OCP\JSON::checkLoggedIn();
OCP\JSON::callCheck();
\OC::$server->getSession()->close();
// Get data
$dir = isset($_POST['dir']) ? (string)$_POST['dir'] : '';
$allFiles = isset($_POST["allfiles"]) ? (string)$_POST["allfiles"] : false;
// delete all files in dir ?
if ($allFiles === 'true') {
$files = array();
$fileList = \OC\Files\Filesystem::getDirectoryContent($dir);
foreach ($fileList as $fileInfo) {
$files[] = $fileInfo['name'];
}
} else {
$files = isset($_POST["file"]) ? (string)$_POST["file"] : (string)$_POST["files"];
$files = json_decode($files);
}
$filesWithError = '';
$success = true;
//Now delete
foreach ($files as $file) {
try {
if (\OC\Files\Filesystem::file_exists($dir . '/' . $file) &&
!(\OC\Files\Filesystem::isDeletable($dir . '/' . $file) &&
\OC\Files\Filesystem::unlink($dir . '/' . $file))
) {
$filesWithError .= $file . "\n";
$success = false;
}
} catch (\Exception $e) {
$filesWithError .= $file . "\n";
$success = false;
}
}
// get array with updated storage stats (e.g. max file size) after upload
try {
$storageStats = \OCA\Files\Helper::buildFileStorageStatistics($dir);
} catch(\OCP\Files\NotFoundException $e) {
OCP\JSON::error(['data' => ['message' => 'File not found']]);
return;
}
if ($success) {
OCP\JSON::success(array("data" => array_merge(array("dir" => $dir, "files" => $files), $storageStats)));
} else {
OCP\JSON::error(array("data" => array_merge(array("message" => "Could not delete:\n" . $filesWithError), $storageStats)));
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save