summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/dav/appinfo/application.php39
-rw-r--r--apps/dav/appinfo/register_command.php6
-rw-r--r--apps/dav/appinfo/v1/caldav.php5
-rw-r--r--apps/dav/appinfo/v1/carddav.php6
-rw-r--r--apps/dav/command/createaddressbook.php31
-rw-r--r--apps/dav/command/syncbirthdaycalendar.php85
-rw-r--r--apps/dav/command/syncsystemaddressbook.php1
-rw-r--r--apps/dav/lib/caldav/birthdayservice.php187
-rw-r--r--apps/dav/lib/caldav/caldavbackend.php21
-rw-r--r--apps/dav/lib/caldav/calendar.php4
-rw-r--r--apps/dav/lib/carddav/carddavbackend.php34
-rw-r--r--apps/dav/lib/connector/legacydavacl.php69
-rw-r--r--apps/dav/lib/connector/sabre/exceptionloggerplugin.php3
-rw-r--r--apps/dav/lib/connector/sabre/filesplugin.php12
-rw-r--r--apps/dav/lib/rootcollection.php5
-rw-r--r--apps/dav/lib/server.php10
-rw-r--r--apps/dav/lib/systemtag/systemtagnode.php1
-rw-r--r--apps/dav/lib/systemtag/systemtagplugin.php29
-rw-r--r--apps/dav/tests/travis/caldav/script.sh2
-rw-r--r--apps/dav/tests/unit/caldav/calendartest.php6
-rw-r--r--apps/dav/tests/unit/carddav/birthdayservicetest.php171
-rw-r--r--apps/dav/tests/unit/carddav/carddavbackendtest.php12
-rw-r--r--apps/dav/tests/unit/systemtag/systemtagplugin.php255
-rw-r--r--apps/files_sharing/css/public.css6
-rw-r--r--apps/files_versions/lib/storage.php3
-rw-r--r--build/integration/config/behat.yml4
-rw-r--r--build/integration/features/bootstrap/CommentsContext.php263
-rw-r--r--build/integration/features/bootstrap/Sharing.php44
-rw-r--r--build/integration/features/bootstrap/TagsContext.php482
-rw-r--r--build/integration/features/bootstrap/WebDav.php15
-rw-r--r--build/integration/features/comments.feature209
-rw-r--r--build/integration/features/tags.feature370
-rw-r--r--console.php3
-rw-r--r--core/css/systemtags.css36
-rw-r--r--core/js/setupchecks.js2
-rw-r--r--core/js/tests/specs/setupchecksSpec.js6
-rw-r--r--issue_template.md7
-rw-r--r--lib/private/api.php3
-rw-r--r--lib/private/avatarmanager.php24
-rw-r--r--lib/private/console/application.php22
-rw-r--r--lib/private/db/adaptersqlsrv.php37
-rw-r--r--lib/private/files/storage/common.php4
-rw-r--r--lib/private/files/storage/wrapper/encryption.php4
-rw-r--r--lib/private/files/stream/encryption.php2
-rw-r--r--lib/private/group/dummy.php1
-rw-r--r--lib/private/group/manager.php4
-rw-r--r--lib/private/memcache/factory.php2
-rw-r--r--lib/private/preview.php20
-rw-r--r--lib/private/preview/movie.php4
-rw-r--r--lib/private/share20/manager.php16
-rw-r--r--lib/public/appframework/utility/icontrollermethodreflector.php1
-rw-r--r--lib/public/iavatarmanager.php2
-rw-r--r--lib/public/search/pagedprovider.php2
-rw-r--r--settings/controller/userscontroller.php6
-rw-r--r--settings/templates/admin.php2
-rw-r--r--tests/lib/avatarmanagertest.php51
-rw-r--r--tests/lib/share20/managertest.php97
-rw-r--r--tests/settings/controller/userscontrollertest.php26
58 files changed, 2605 insertions, 169 deletions
diff --git a/apps/dav/appinfo/application.php b/apps/dav/appinfo/application.php
index 28b9a833456..7a201e1dd78 100644
--- a/apps/dav/appinfo/application.php
+++ b/apps/dav/appinfo/application.php
@@ -20,6 +20,7 @@
*/
namespace OCA\Dav\AppInfo;
+use OCA\DAV\CalDAV\BirthdayService;
use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CardDAV\CardDavBackend;
use OCA\DAV\CardDAV\ContactsManager;
@@ -34,6 +35,7 @@ use \OCP\AppFramework\App;
use OCP\AppFramework\IAppContainer;
use OCP\Contacts\IManager;
use OCP\IUser;
+use Symfony\Component\EventDispatcher\GenericEvent;
class Application extends App {
@@ -74,12 +76,12 @@ class Application extends App {
$container->registerService('CardDavBackend', function($c) {
/** @var IAppContainer $c */
$db = $c->getServer()->getDatabaseConnection();
- $logger = $c->getServer()->getLogger();
+ $dispatcher = $c->getServer()->getEventDispatcher();
$principal = new \OCA\DAV\Connector\Sabre\Principal(
$c->getServer()->getUserManager(),
$c->getServer()->getGroupManager()
);
- return new CardDavBackend($db, $principal, $logger);
+ return new CardDavBackend($db, $principal, $dispatcher);
});
$container->registerService('CalDavBackend', function($c) {
@@ -109,6 +111,15 @@ class Application extends App {
$c->query('CalDavBackend')
);
});
+
+ $container->registerService('BirthdayService', function($c) {
+ /** @var IAppContainer $c */
+ return new BirthdayService(
+ $c->query('CalDavBackend'),
+ $c->query('CardDavBackend')
+ );
+
+ });
}
/**
@@ -125,6 +136,30 @@ class Application extends App {
/** @var HookManager $hm */
$hm = $this->getContainer()->query('HookManager');
$hm->setup();
+
+ $listener = function($event) {
+ if ($event instanceof GenericEvent) {
+ $b = $this->getContainer()->query('BirthdayService');
+ $b->onCardChanged(
+ $event->getArgument('addressBookId'),
+ $event->getArgument('cardUri'),
+ $event->getArgument('cardData')
+ );
+ }
+ };
+
+ $dispatcher = $this->getContainer()->getServer()->getEventDispatcher();
+ $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::createCard', $listener);
+ $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::updateCard', $listener);
+ $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::deleteCard', function($event) {
+ if ($event instanceof GenericEvent) {
+ $b = $this->getContainer()->query('BirthdayService');
+ $b->onCardDeleted(
+ $event->getArgument('addressBookId'),
+ $event->getArgument('cardUri')
+ );
+ }
+ });
}
public function getSyncService() {
diff --git a/apps/dav/appinfo/register_command.php b/apps/dav/appinfo/register_command.php
index 4981cab9264..e07f6b4a25b 100644
--- a/apps/dav/appinfo/register_command.php
+++ b/apps/dav/appinfo/register_command.php
@@ -24,21 +24,21 @@ use OCA\DAV\Command\CreateAddressBook;
use OCA\DAV\Command\CreateCalendar;
use OCA\Dav\Command\MigrateAddressbooks;
use OCA\Dav\Command\MigrateCalendars;
+use OCA\DAV\Command\SyncBirthdayCalendar;
use OCA\DAV\Command\SyncSystemAddressBook;
-$config = \OC::$server->getConfig();
$dbConnection = \OC::$server->getDatabaseConnection();
$userManager = OC::$server->getUserManager();
$groupManager = OC::$server->getGroupManager();
$config = \OC::$server->getConfig();
-$logger = \OC::$server->getLogger();
$app = new Application();
/** @var Symfony\Component\Console\Application $application */
-$application->add(new CreateAddressBook($userManager, $groupManager, $dbConnection, $logger));
$application->add(new CreateCalendar($userManager, $groupManager, $dbConnection));
+$application->add(new CreateAddressBook($userManager, $app->getContainer()->query('CardDavBackend')));
$application->add(new SyncSystemAddressBook($app->getSyncService()));
+$application->add(new SyncBirthdayCalendar($userManager, $app->getContainer()->query('BirthdayService')));
// the occ tool is *for now* only available in debug mode for developers to test
if ($config->getSystemValue('debug', false)){
diff --git a/apps/dav/appinfo/v1/caldav.php b/apps/dav/appinfo/v1/caldav.php
index 333e8bbb3c4..3e56e3d0e81 100644
--- a/apps/dav/appinfo/v1/caldav.php
+++ b/apps/dav/appinfo/v1/caldav.php
@@ -62,7 +62,10 @@ $server->setBaseUri($baseuri);
$server->addPlugin(new MaintenancePlugin());
$server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend, 'ownCloud'));
$server->addPlugin(new \Sabre\CalDAV\Plugin());
-$server->addPlugin(new \Sabre\DAVACL\Plugin());
+
+$acl = new \OCA\DAV\Connector\LegacyDAVACL();
+$server->addPlugin($acl);
+
$server->addPlugin(new \Sabre\CalDAV\ICSExportPlugin());
$server->addPlugin(new ExceptionLoggerPlugin('caldav', \OC::$server->getLogger()));
diff --git a/apps/dav/appinfo/v1/carddav.php b/apps/dav/appinfo/v1/carddav.php
index 54f0d259bb9..4a3f98475cd 100644
--- a/apps/dav/appinfo/v1/carddav.php
+++ b/apps/dav/appinfo/v1/carddav.php
@@ -22,7 +22,6 @@
// Backends
use OCA\DAV\CardDAV\AddressBookRoot;
use OCA\DAV\CardDAV\CardDavBackend;
-use OCA\DAV\Connector\Sabre\AppEnabledPlugin;
use OCA\DAV\Connector\Sabre\Auth;
use OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin;
use OCA\DAV\Connector\Sabre\MaintenancePlugin;
@@ -63,7 +62,10 @@ $server->setBaseUri($baseuri);
$server->addPlugin(new MaintenancePlugin());
$server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend, 'ownCloud'));
$server->addPlugin(new Plugin());
-$server->addPlugin(new \Sabre\DAVACL\Plugin());
+
+$acl = new \OCA\DAV\Connector\LegacyDAVACL();
+$server->addPlugin($acl);
+
$server->addPlugin(new \Sabre\CardDAV\VCFExportPlugin());
$server->addPlugin(new ExceptionLoggerPlugin('carddav', \OC::$server->getLogger()));
diff --git a/apps/dav/command/createaddressbook.php b/apps/dav/command/createaddressbook.php
index 3d99afd4ba3..48302a2b439 100644
--- a/apps/dav/command/createaddressbook.php
+++ b/apps/dav/command/createaddressbook.php
@@ -36,33 +36,21 @@ use Symfony\Component\Console\Output\OutputInterface;
class CreateAddressBook extends Command {
/** @var IUserManager */
- protected $userManager;
+ private $userManager;
- /** @var \OCP\IDBConnection */
- protected $dbConnection;
-
- /** @var ILogger */
- private $logger;
-
- /** @var IGroupManager $groupManager */
- private $groupManager;
+ /** @var CardDavBackend */
+ private $cardDavBackend;
/**
* @param IUserManager $userManager
- * @param IDBConnection $dbConnection
- * @param IConfig $config
- * @param ILogger $logger
+ * @param CardDavBackend $cardDavBackend
*/
function __construct(IUserManager $userManager,
- IGroupManager $groupManager,
- IDBConnection $dbConnection,
- ILogger $logger
+ CardDavBackend $cardDavBackend
) {
parent::__construct();
$this->userManager = $userManager;
- $this->groupManager = $groupManager;
- $this->dbConnection = $dbConnection;
- $this->logger = $logger;
+ $this->cardDavBackend = $cardDavBackend;
}
protected function configure() {
@@ -82,13 +70,8 @@ class CreateAddressBook extends Command {
if (!$this->userManager->userExists($user)) {
throw new \InvalidArgumentException("User <$user> in unknown.");
}
- $principalBackend = new Principal(
- $this->userManager,
- $this->groupManager
- );
$name = $input->getArgument('name');
- $carddav = new CardDavBackend($this->dbConnection, $principalBackend, $this->logger);
- $carddav->createAddressBook("principals/users/$user", $name, []);
+ $this->cardDavBackend->createAddressBook("principals/users/$user", $name, []);
}
}
diff --git a/apps/dav/command/syncbirthdaycalendar.php b/apps/dav/command/syncbirthdaycalendar.php
new file mode 100644
index 00000000000..66ab540b9ad
--- /dev/null
+++ b/apps/dav/command/syncbirthdaycalendar.php
@@ -0,0 +1,85 @@
+<?php
+/**
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+namespace OCA\DAV\Command;
+
+use OCA\DAV\CalDAV\BirthdayService;
+use OCP\IUser;
+use OCP\IUserManager;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\ProgressBar;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class SyncBirthdayCalendar extends Command {
+
+ /** @var BirthdayService */
+ private $birthdayService;
+
+ /** @var IUserManager */
+ private $userManager;
+
+ /**
+ * @param IUserManager $userManager
+ * @param BirthdayService $birthdayService
+ */
+ function __construct(IUserManager $userManager, BirthdayService $birthdayService) {
+ parent::__construct();
+ $this->birthdayService = $birthdayService;
+ $this->userManager = $userManager;
+ }
+
+ protected function configure() {
+ $this
+ ->setName('dav:sync-birthday-calendar')
+ ->setDescription('Synchronizes the birthday calendar')
+ ->addArgument('user',
+ InputArgument::OPTIONAL,
+ 'User for whom the birthday calendar will be synchronized');
+ }
+
+ /**
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ */
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ if ($input->hasArgument('user')) {
+ $user = $input->getArgument('user');
+ if (!$this->userManager->userExists($user)) {
+ throw new \InvalidArgumentException("User <$user> in unknown.");
+ }
+ $output->writeln("Start birthday calendar sync for $user");
+ $this->birthdayService->syncUser($user);
+ return;
+ }
+ $output->writeln("Start birthday calendar sync for all users ...");
+ $p = new ProgressBar($output);
+ $p->start();
+ $this->userManager->callForAllUsers(function($user) use ($p) {
+ $p->advance();
+ /** @var IUser $user */
+ $this->birthdayService->syncUser($user->getUID());
+ });
+
+ $p->finish();
+ $output->writeln('');
+ }
+}
diff --git a/apps/dav/command/syncsystemaddressbook.php b/apps/dav/command/syncsystemaddressbook.php
index b83a37131c3..b62a42d7b90 100644
--- a/apps/dav/command/syncsystemaddressbook.php
+++ b/apps/dav/command/syncsystemaddressbook.php
@@ -34,7 +34,6 @@ class SyncSystemAddressBook extends Command {
private $syncService;
/**
- * @param IUserManager $userManager
* @param SyncService $syncService
*/
function __construct(SyncService $syncService) {
diff --git a/apps/dav/lib/caldav/birthdayservice.php b/apps/dav/lib/caldav/birthdayservice.php
new file mode 100644
index 00000000000..3b0a2a10e1c
--- /dev/null
+++ b/apps/dav/lib/caldav/birthdayservice.php
@@ -0,0 +1,187 @@
+<?php
+/**
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\CalDAV;
+
+use Exception;
+use OCA\DAV\CardDAV\CardDavBackend;
+use Sabre\VObject\Component\VCalendar;
+use Sabre\VObject\Reader;
+
+class BirthdayService {
+
+ const BIRTHDAY_CALENDAR_URI = 'contact_birthdays';
+
+ /**
+ * BirthdayService constructor.
+ *
+ * @param CalDavBackend $calDavBackEnd
+ * @param CardDavBackend $cardDavBackEnd
+ */
+ public function __construct($calDavBackEnd, $cardDavBackEnd) {
+ $this->calDavBackEnd = $calDavBackEnd;
+ $this->cardDavBackEnd = $cardDavBackEnd;
+ }
+
+ /**
+ * @param int $addressBookId
+ * @param string $cardUri
+ * @param string $cardData
+ */
+ public function onCardChanged($addressBookId, $cardUri, $cardData) {
+
+ $book = $this->cardDavBackEnd->getAddressBookById($addressBookId);
+ $principalUri = $book['principaluri'];
+ $calendarUri = self::BIRTHDAY_CALENDAR_URI;
+ $calendar = $this->ensureCalendarExists($principalUri, $calendarUri, []);
+ $objectUri = $book['uri'] . '-' . $cardUri. '.ics';
+ $calendarData = $this->buildBirthdayFromContact($cardData);
+ $existing = $this->calDavBackEnd->getCalendarObject($calendar['id'], $objectUri);
+ if (is_null($calendarData)) {
+ if (!is_null($existing)) {
+ $this->calDavBackEnd->deleteCalendarObject($calendar['id'], $objectUri);
+ }
+ } else {
+ if (is_null($existing)) {
+ $this->calDavBackEnd->createCalendarObject($calendar['id'], $objectUri, $calendarData->serialize());
+ } else {
+ if ($this->birthdayEvenChanged($existing['calendardata'], $calendarData)) {
+ $this->calDavBackEnd->updateCalendarObject($calendar['id'], $objectUri, $calendarData->serialize());
+ }
+ }
+ }
+ }
+
+ /**
+ * @param int $addressBookId
+ * @param string $cardUri
+ */
+ public function onCardDeleted($addressBookId, $cardUri) {
+ $book = $this->cardDavBackEnd->getAddressBookById($addressBookId);
+ $principalUri = $book['principaluri'];
+ $calendarUri = self::BIRTHDAY_CALENDAR_URI;
+ $calendar = $this->ensureCalendarExists($principalUri, $calendarUri, []);
+ $objectUri = $book['uri'] . '-' . $cardUri. '.ics';
+ $this->calDavBackEnd->deleteCalendarObject($calendar['id'], $objectUri);
+ }
+
+ /**
+ * @param string $principal
+ * @param string $id
+ * @param array $properties
+ * @return array|null
+ * @throws \Sabre\DAV\Exception\BadRequest
+ */
+ public function ensureCalendarExists($principal, $id, $properties) {
+ $book = $this->calDavBackEnd->getCalendarByUri($principal, $id);
+ if (!is_null($book)) {
+ return $book;
+ }
+ $this->calDavBackEnd->createCalendar($principal, $id, $properties);
+
+ return $this->calDavBackEnd->getCalendarByUri($principal, $id);
+ }
+
+ /**
+ * @param string $cardData
+ * @return null|VCalendar
+ */
+ public function buildBirthdayFromContact($cardData) {
+ if (empty($cardData)) {
+ return null;
+ }
+ try {
+ $doc = Reader::read($cardData);
+ } catch (Exception $e) {
+ return null;
+ }
+
+ if (!isset($doc->BDAY)) {
+ return null;
+ }
+ $birthday = $doc->BDAY;
+ if (!(string)$birthday) {
+ return null;
+ }
+ $title = str_replace('{name}',
+ strtr((string)$doc->FN, array('\,' => ',', '\;' => ';')),
+ '{name}\'s Birthday'
+ );
+ try {
+ $date = new \DateTime($birthday);
+ } catch (Exception $e) {
+ return null;
+ }
+ $vCal = new VCalendar();
+ $vCal->VERSION = '2.0';
+ $vEvent = $vCal->createComponent('VEVENT');
+ $vEvent->add('DTSTART');
+ $vEvent->DTSTART->setDateTime(
+ $date
+ );
+ $vEvent->DTSTART['VALUE'] = 'DATE';
+ $vEvent->add('DTEND');
+ $date->add(new \DateInterval('P1D'));
+ $vEvent->DTEND->setDateTime(
+ $date
+ );
+ $vEvent->DTEND['VALUE'] = 'DATE';
+ $vEvent->{'UID'} = $doc->UID;
+ $vEvent->{'RRULE'} = 'FREQ=YEARLY';
+ $vEvent->{'SUMMARY'} = $title . ' (' . $date->format('Y') . ')';
+ $vEvent->{'TRANSP'} = 'TRANSPARENT';
+ $vCal->add($vEvent);
+ return $vCal;
+ }
+
+ /**
+ * @param string $user
+ */
+ public function syncUser($user) {
+ $books = $this->cardDavBackEnd->getAddressBooksForUser('principals/users/'.$user);
+ foreach($books as $book) {
+ $cards = $this->cardDavBackEnd->getCards($book['id']);
+ foreach($cards as $card) {
+ $this->onCardChanged($book['id'], $card['uri'], $card['carddata']);
+ }
+ }
+ }
+
+ /**
+ * @param string $existingCalendarData
+ * @param VCalendar $newCalendarData
+ * @return bool
+ */
+ public function birthdayEvenChanged($existingCalendarData, $newCalendarData) {
+ try {
+ $existingBirthday = Reader::read($existingCalendarData);
+ } catch (Exception $ex) {
+ return true;
+ }
+ if ($newCalendarData->VEVENT->DTSTART->getValue() !== $existingBirthday->VEVENT->DTSTART->getValue() ||
+ $newCalendarData->VEVENT->SUMMARY->getValue() !== $existingBirthday->VEVENT->SUMMARY->getValue()
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/apps/dav/lib/caldav/caldavbackend.php b/apps/dav/lib/caldav/caldavbackend.php
index 775612487f9..7f6810fb1e2 100644
--- a/apps/dav/lib/caldav/caldavbackend.php
+++ b/apps/dav/lib/caldav/caldavbackend.php
@@ -138,6 +138,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @return array
*/
function getCalendarsForUser($principalUri) {
+ $principalUri = $this->convertPrincipal($principalUri, true);
$fields = array_values($this->propertyMap);
$fields[] = 'id';
$fields[] = 'uri';
@@ -164,7 +165,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$calendar = [
'id' => $row['id'],
'uri' => $row['uri'],
- 'principaluri' => $row['principaluri'],
+ 'principaluri' => $this->convertPrincipal($row['principaluri'], false),
'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
@@ -235,6 +236,11 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
return array_values($calendars);
}
+ /**
+ * @param string $principal
+ * @param string $uri
+ * @return array|null
+ */
public function getCalendarByUri($principal, $uri) {
$fields = array_values($this->propertyMap);
$fields[] = 'id';
@@ -1088,7 +1094,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$newValues = [];
foreach($mutations as $propertyName=>$propertyValue) {
- if ($propertyName === '{http://calendarserver.org/ns/}source') {
+ if ($propertyName === '{http://calendarserver.org/ns/}source') {
$newValues['source'] = $propertyValue->getHref();
} else {
$fieldName = $this->subscriptionPropertyMap[$propertyName];
@@ -1367,4 +1373,15 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
public function applyShareAcl($resourceId, $acl) {
return $this->sharingBackend->applyShareAcl($resourceId, $acl);
}
+
+ private function convertPrincipal($principalUri, $toV2) {
+ if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
+ list(, $name) = URLUtil::splitPath($principalUri);
+ if ($toV2 === true) {
+ return "principals/users/$name";
+ }
+ return "principals/$name";
+ }
+ return $principalUri;
+ }
}
diff --git a/apps/dav/lib/caldav/calendar.php b/apps/dav/lib/caldav/calendar.php
index 8ed5b6563d0..6b34d570eb3 100644
--- a/apps/dav/lib/caldav/calendar.php
+++ b/apps/dav/lib/caldav/calendar.php
@@ -80,6 +80,10 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable {
}
function delete() {
+ if ($this->getName() === BirthdayService::BIRTHDAY_CALENDAR_URI) {
+ throw new Forbidden();
+ }
+
if (isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal'])) {
$principal = 'principal:' . parent::getOwner();
$shares = $this->getShares();
diff --git a/apps/dav/lib/carddav/carddavbackend.php b/apps/dav/lib/carddav/carddavbackend.php
index 78706ae6bff..56fa652d798 100644
--- a/apps/dav/lib/carddav/carddavbackend.php
+++ b/apps/dav/lib/carddav/carddavbackend.php
@@ -37,6 +37,8 @@ use Sabre\DAV\Exception\BadRequest;
use Sabre\HTTP\URLUtil;
use Sabre\VObject\Component\VCard;
use Sabre\VObject\Reader;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\GenericEvent;
class CardDavBackend implements BackendInterface, SyncSupport {
@@ -64,15 +66,22 @@ class CardDavBackend implements BackendInterface, SyncSupport {
const ACCESS_READ_WRITE = 2;
const ACCESS_READ = 3;
+ /** @var EventDispatcherInterface */
+ private $dispatcher;
+
/**
* CardDavBackend constructor.
*
* @param IDBConnection $db
* @param Principal $principalBackend
+ * @param EventDispatcherInterface $dispatcher
*/
- public function __construct(IDBConnection $db, Principal $principalBackend) {
+ public function __construct(IDBConnection $db,
+ Principal $principalBackend,
+ $dispatcher ) {
$this->db = $db;
$this->principalBackend = $principalBackend;
+ $this->dispatcher = $dispatcher;
$this->sharingBackend = new Backend($this->db, $principalBackend, 'addressbook');
}
@@ -492,6 +501,14 @@ class CardDavBackend implements BackendInterface, SyncSupport {
$this->addChange($addressBookId, $cardUri, 1);
$this->updateProperties($addressBookId, $cardUri, $cardData);
+ if (!is_null($this->dispatcher)) {
+ $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
+ new GenericEvent(null, [
+ 'addressBookId' => $addressBookId,
+ 'cardUri' => $cardUri,
+ 'cardData' => $cardData]));
+ }
+
return '"' . $etag . '"';
}
@@ -536,6 +553,14 @@ class CardDavBackend implements BackendInterface, SyncSupport {
$this->addChange($addressBookId, $cardUri, 2);
$this->updateProperties($addressBookId, $cardUri, $cardData);
+ if (!is_null($this->dispatcher)) {
+ $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
+ new GenericEvent(null, [
+ 'addressBookId' => $addressBookId,
+ 'cardUri' => $cardUri,
+ 'cardData' => $cardData]));
+ }
+
return '"' . $etag . '"';
}
@@ -560,6 +585,13 @@ class CardDavBackend implements BackendInterface, SyncSupport {
$this->addChange($addressBookId, $cardUri, 3);
+ if (!is_null($this->dispatcher)) {
+ $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
+ new GenericEvent(null, [
+ 'addressBookId' => $addressBookId,
+ 'cardUri' => $cardUri]));
+ }
+
if ($ret === 1) {
if ($cardId !== null) {
$this->purgeProperties($addressBookId, $cardId);
diff --git a/apps/dav/lib/connector/legacydavacl.php b/apps/dav/lib/connector/legacydavacl.php
new file mode 100644
index 00000000000..149bd85e4be
--- /dev/null
+++ b/apps/dav/lib/connector/legacydavacl.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * @author Joas Schilling <nickvergessen@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\Connector;
+
+
+use Sabre\HTTP\URLUtil;
+
+class LegacyDAVACL extends \Sabre\DAVACL\Plugin {
+
+ /**
+ * Converts the v1 principal `principal/<username>` to the new v2
+ * `principal/users/<username>` which is required for permission checks
+ *
+ * @inheritdoc
+ */
+ function getCurrentUserPrincipal() {
+ $principalV1 = parent::getCurrentUserPrincipal();
+ if (is_null($principalV1)) {
+ return $principalV1;
+ }
+ return $this->convertPrincipal($principalV1, true);
+ }
+
+
+ /**
+ * @inheritdoc
+ */
+ function getCurrentUserPrincipals() {
+ $principalV2 = $this->getCurrentUserPrincipal();
+
+ if (is_null($principalV2)) return [];
+
+ $principalV1 = $this->convertPrincipal($principalV2, false);
+ return array_merge(
+ [
+ $principalV2,
+ $principalV1
+ ],
+ $this->getPrincipalMembership($principalV1)
+ );
+ }
+
+ private function convertPrincipal($principal, $toV2) {
+ list(, $name) = URLUtil::splitPath($principal);
+ if ($toV2) {
+ return "principals/users/$name";
+ }
+ return "principals/$name";
+ }
+}
diff --git a/apps/dav/lib/connector/sabre/exceptionloggerplugin.php b/apps/dav/lib/connector/sabre/exceptionloggerplugin.php
index b514a917556..1052c56e968 100644
--- a/apps/dav/lib/connector/sabre/exceptionloggerplugin.php
+++ b/apps/dav/lib/connector/sabre/exceptionloggerplugin.php
@@ -94,6 +94,8 @@ class ExceptionLoggerPlugin extends \Sabre\DAV\ServerPlugin {
$message = "HTTP/1.1 {$ex->getHTTPCode()} $message";
}
+ $user = \OC_User::getUser();
+
$exception = [
'Message' => $message,
'Exception' => $exceptionClass,
@@ -101,6 +103,7 @@ class ExceptionLoggerPlugin extends \Sabre\DAV\ServerPlugin {
'Trace' => $ex->getTraceAsString(),
'File' => $ex->getFile(),
'Line' => $ex->getLine(),
+ 'User' => $user,
];
$this->logger->log($level, 'Exception: ' . json_encode($exception), ['app' => $this->appName]);
}
diff --git a/apps/dav/lib/connector/sabre/filesplugin.php b/apps/dav/lib/connector/sabre/filesplugin.php
index 2e913ee1077..eb9116d219b 100644
--- a/apps/dav/lib/connector/sabre/filesplugin.php
+++ b/apps/dav/lib/connector/sabre/filesplugin.php
@@ -193,11 +193,13 @@ class FilesPlugin extends \Sabre\DAV\ServerPlugin {
// adds a 'Content-Disposition: attachment' header
$response->addHeader('Content-Disposition', 'attachment');
- //Add OC-Checksum header
- /** @var $node File */
- $checksum = $node->getChecksum();
- if ($checksum !== null) {
- $response->addHeader('OC-Checksum', $checksum);
+ if ($node instanceof \OCA\DAV\Connector\Sabre\File) {
+ //Add OC-Checksum header
+ /** @var $node File */
+ $checksum = $node->getChecksum();
+ if ($checksum !== null) {
+ $response->addHeader('OC-Checksum', $checksum);
+ }
}
}
diff --git a/apps/dav/lib/rootcollection.php b/apps/dav/lib/rootcollection.php
index 2a8f63a2270..b3648e7ba38 100644
--- a/apps/dav/lib/rootcollection.php
+++ b/apps/dav/lib/rootcollection.php
@@ -36,6 +36,7 @@ class RootCollection extends SimpleCollection {
public function __construct() {
$config = \OC::$server->getConfig();
$db = \OC::$server->getDatabaseConnection();
+ $dispatcher = \OC::$server->getEventDispatcher();
$userPrincipalBackend = new Principal(
\OC::$server->getUserManager(),
\OC::$server->getGroupManager()
@@ -79,11 +80,11 @@ class RootCollection extends SimpleCollection {
\OC::$server->getLogger()
);
- $usersCardDavBackend = new CardDavBackend($db, $userPrincipalBackend);
+ $usersCardDavBackend = new CardDavBackend($db, $userPrincipalBackend, $dispatcher);
$usersAddressBookRoot = new AddressBookRoot($userPrincipalBackend, $usersCardDavBackend, 'principals/users');
$usersAddressBookRoot->disableListing = $disableListing;
- $systemCardDavBackend = new CardDavBackend($db, $userPrincipalBackend);
+ $systemCardDavBackend = new CardDavBackend($db, $userPrincipalBackend, $dispatcher);
$systemAddressBookRoot = new AddressBookRoot(new SystemPrincipalBackend(), $systemCardDavBackend, 'principals/system');
$systemAddressBookRoot->disableListing = $disableListing;
diff --git a/apps/dav/lib/server.php b/apps/dav/lib/server.php
index ed1ee684049..74be318fe5e 100644
--- a/apps/dav/lib/server.php
+++ b/apps/dav/lib/server.php
@@ -26,6 +26,7 @@ use OCA\DAV\CalDAV\Schedule\IMipPlugin;
use OCA\DAV\Connector\FedAuth;
use OCA\DAV\Connector\Sabre\Auth;
use OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin;
+use OCA\DAV\Connector\Sabre\FilesPlugin;
use OCA\DAV\Files\CustomPropertiesBackend;
use OCP\IRequest;
use OCP\SabrePluginEvent;
@@ -92,7 +93,11 @@ class Server {
$this->server->addPlugin(new \OCA\DAV\CardDAV\Plugin());
// system tags plugins
- $this->server->addPlugin(new \OCA\DAV\SystemTag\SystemTagPlugin(\OC::$server->getSystemTagManager()));
+ $this->server->addPlugin(new \OCA\DAV\SystemTag\SystemTagPlugin(
+ \OC::$server->getSystemTagManager(),
+ \OC::$server->getGroupManager(),
+ \OC::$server->getUserSession()
+ ));
// comments plugin
$this->server->addPlugin(new \OCA\DAV\Comments\CommentsPlugin(
@@ -127,6 +132,9 @@ class Server {
// custom properties plugin must be the last one
$user = \OC::$server->getUserSession()->getUser();
if (!is_null($user)) {
+ $view = \OC\Files\Filesystem::getView();
+ $this->server->addPlugin(new FilesPlugin($this->server->tree, $view));
+
$this->server->addPlugin(
new \Sabre\DAV\PropertyStorage\Plugin(
new CustomPropertiesBackend(
diff --git a/apps/dav/lib/systemtag/systemtagnode.php b/apps/dav/lib/systemtag/systemtagnode.php
index ecdb39a762c..7a47a752ad0 100644
--- a/apps/dav/lib/systemtag/systemtagnode.php
+++ b/apps/dav/lib/systemtag/systemtagnode.php
@@ -103,6 +103,7 @@ class SystemTagNode implements \Sabre\DAV\INode {
* @param bool $userVisible user visible
* @param bool $userAssignable user assignable
* @throws NotFound whenever the given tag id does not exist
+ * @throws Forbidden whenever there is no permission to update said tag
* @throws Conflict whenever a tag already exists with the given attributes
*/
public function update($name, $userVisible, $userAssignable) {
diff --git a/apps/dav/lib/systemtag/systemtagplugin.php b/apps/dav/lib/systemtag/systemtagplugin.php
index 3348b431c47..7da24ba7cf8 100644
--- a/apps/dav/lib/systemtag/systemtagplugin.php
+++ b/apps/dav/lib/systemtag/systemtagplugin.php
@@ -21,6 +21,8 @@
*/
namespace OCA\DAV\SystemTag;
+use OCP\IGroupManager;
+use OCP\IUserSession;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\PropFind;
use Sabre\DAV\PropPatch;
@@ -61,12 +63,26 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
protected $tagManager;
/**
- * System tags plugin
- *
+ * @var IUserSession
+ */
+ protected $userSession;
+
+ /**
+ * @var IGroupManager
+ */
+ protected $groupManager;
+
+ /**
* @param ISystemTagManager $tagManager tag manager
+ * @param IGroupManager $groupManager
+ * @param IUserSession $userSession
*/
- public function __construct(ISystemTagManager $tagManager) {
+ public function __construct(ISystemTagManager $tagManager,
+ IGroupManager $groupManager,
+ IUserSession $userSession) {
$this->tagManager = $tagManager;
+ $this->userSession = $userSession;
+ $this->groupManager = $groupManager;
}
/**
@@ -163,6 +179,13 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
if (isset($data['userAssignable'])) {
$userAssignable = (bool)$data['userAssignable'];
}
+
+ if($userVisible === false || $userAssignable === false) {
+ if(!$this->userSession->isLoggedIn() || !$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) {
+ throw new BadRequest('Not sufficient permissions');
+ }
+ }
+
try {
return $this->tagManager->createTag($tagName, $userVisible, $userAssignable);
} catch (TagAlreadyExistsException $e) {
diff --git a/apps/dav/tests/travis/caldav/script.sh b/apps/dav/tests/travis/caldav/script.sh
index aa5fc732922..7259372567c 100644
--- a/apps/dav/tests/travis/caldav/script.sh
+++ b/apps/dav/tests/travis/caldav/script.sh
@@ -10,6 +10,8 @@ sleep 30
# run the tests
cd "$SCRIPTPATH/CalDAVTester"
PYTHONPATH="$SCRIPTPATH/pycalendar/src" python testcaldav.py --print-details-onfail --basedir "$SCRIPTPATH/../caldavtest/" -o cdt.txt \
+ "CalDAV/current-user-principal.xml" \
+ "CalDAV/sync-report.xml" \
"CalDAV/sharing-calendars.xml"
RESULT=$?
diff --git a/apps/dav/tests/unit/caldav/calendartest.php b/apps/dav/tests/unit/caldav/calendartest.php
index 93b3f4bff8c..4a3c94e8aba 100644
--- a/apps/dav/tests/unit/caldav/calendartest.php
+++ b/apps/dav/tests/unit/caldav/calendartest.php
@@ -37,7 +37,8 @@ class CalendarTest extends TestCase {
$calendarInfo = [
'{http://owncloud.org/ns}owner-principal' => 'user1',
'principaluri' => 'user2',
- 'id' => 666
+ 'id' => 666,
+ 'uri' => 'cal',
];
$c = new Calendar($backend, $calendarInfo);
$c->delete();
@@ -56,7 +57,8 @@ class CalendarTest extends TestCase {
$calendarInfo = [
'{http://owncloud.org/ns}owner-principal' => 'user1',
'principaluri' => 'user2',
- 'id' => 666
+ 'id' => 666,
+ 'uri' => 'cal',
];
$c = new Calendar($backend, $calendarInfo);
$c->delete();
diff --git a/apps/dav/tests/unit/carddav/birthdayservicetest.php b/apps/dav/tests/unit/carddav/birthdayservicetest.php
new file mode 100644
index 00000000000..faf42d9e1b8
--- /dev/null
+++ b/apps/dav/tests/unit/carddav/birthdayservicetest.php
@@ -0,0 +1,171 @@
+<?php
+/**
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\Tests\Unit\CardDAV;
+
+use OCA\DAV\CalDAV\BirthdayService;
+use OCA\DAV\CalDAV\CalDavBackend;
+use OCA\DAV\CardDAV\CardDavBackend;
+use Sabre\VObject\Component\VCalendar;
+use Sabre\VObject\Reader;
+use Test\TestCase;
+
+class BirthdayServiceTest extends TestCase {
+
+ /** @var BirthdayService */
+ private $service;
+ /** @var CalDavBackend | \PHPUnit_Framework_MockObject_MockObject */
+ private $calDav;
+ /** @var CardDavBackend | \PHPUnit_Framework_MockObject_MockObject */
+ private $cardDav;
+
+ public function setUp() {
+ parent::setUp();
+
+ $this->calDav = $this->getMockBuilder('OCA\DAV\CalDAV\CalDavBackend')->disableOriginalConstructor()->getMock();
+ $this->cardDav = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')->disableOriginalConstructor()->getMock();
+
+ $this->service = new BirthdayService($this->calDav, $this->cardDav);
+ }
+
+ /**
+ * @dataProvider providesVCards
+ * @param boolean $nullExpected
+ * @param string | null $data
+ */
+ public function testBuildBirthdayFromContact($nullExpected, $data) {
+ $cal = $this->service->buildBirthdayFromContact($data);
+ if ($nullExpected) {
+ $this->assertNull($cal);
+ } else {
+ $this->assertInstanceOf('Sabre\VObject\Component\VCalendar', $cal);
+ $this->assertTrue(isset($cal->VEVENT));
+ $this->assertEquals('FREQ=YEARLY', $cal->VEVENT->RRULE->getValue());
+ $this->assertEquals('12345\'s Birthday (1900)', $cal->VEVENT->SUMMARY->getValue());
+ $this->assertEquals('TRANSPARENT', $cal->VEVENT->TRANSP->getValue());
+ }
+ }
+
+ public function testOnCardDeleted() {
+ $this->cardDav->expects($this->once())->method('getAddressBookById')
+ ->with(666)
+ ->willReturn([
+ 'principaluri' => 'principals/users/user01',
+ 'uri' => 'default'
+ ]);
+ $this->calDav->expects($this->once())->method('getCalendarByUri')
+ ->with('principals/users/user01', 'contact_birthdays')
+ ->willReturn([
+ 'id' => 1234
+ ]);
+ $this->calDav->expects($this->once())->method('deleteCalendarObject')->with(1234, 'default-gump.vcf.ics');
+
+ $this->service->onCardDeleted(666, 'gump.vcf');
+ }
+
+ /**
+ * @dataProvider providesCardChanges
+ */
+ public function testOnCardChanged($expectedOp) {
+ $this->cardDav->expects($this->once())->method('getAddressBookById')
+ ->with(666)
+ ->willReturn([
+ 'principaluri' => 'principals/users/user01',
+ 'uri' => 'default'
+ ]);
+ $this->calDav->expects($this->once())->method('getCalendarByUri')
+ ->with('principals/users/user01', 'contact_birthdays')
+ ->willReturn([
+ 'id' => 1234
+ ]);
+
+ /** @var BirthdayService | \PHPUnit_Framework_MockObject_MockObject $service */
+ $service = $this->getMock('\OCA\DAV\CalDAV\BirthdayService',
+ ['buildBirthdayFromContact', 'birthdayEvenChanged'], [$this->calDav, $this->cardDav]);
+
+ if ($expectedOp === 'delete') {
+ $this->calDav->expects($this->once())->method('getCalendarObject')->willReturn('');
+ $service->expects($this->once())->method('buildBirthdayFromContact')->willReturn(null);
+ $this->calDav->expects($this->once())->method('deleteCalendarObject')->with(1234, 'default-gump.vcf.ics');
+ }
+ if ($expectedOp === 'create') {
+ $service->expects($this->once())->method('buildBirthdayFromContact')->willReturn(new VCalendar());
+ $this->calDav->expects($this->once())->method('createCalendarObject')->with(1234, 'default-gump.vcf.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n");
+ }
+ if ($expectedOp === 'update') {
+ $service->expects($this->once())->method('buildBirthdayFromContact')->willReturn(new VCalendar());
+ $service->expects($this->once())->method('birthdayEvenChanged')->willReturn(true);
+ $this->calDav->expects($this->once())->method('getCalendarObject')->willReturn([
+ 'calendardata' => '']);
+ $this->calDav->expects($this->once())->method('updateCalendarObject')->with(1234, 'default-gump.vcf.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n");
+ }
+
+ $service->onCardChanged(666, 'gump.vcf', '');
+ }
+
+ /**
+ * @dataProvider providesBirthday
+ * @param $expected
+ * @param $old
+ * @param $new
+ */
+ public function testBirthdayEvenChanged($expected, $old, $new) {
+ $new = Reader::read($new);
+ $this->assertEquals($expected, $this->service->birthdayEvenChanged($old, $new));
+ }
+
+ public function providesBirthday() {
+ return [
+ [true,
+ '',
+ "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"],
+ [false,
+ "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n",
+ "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"],
+ [true,
+ "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:4567's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n",
+ "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"],
+ [true,
+ "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n",
+ "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000102\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"]
+ ];
+ }
+
+ public function providesCardChanges(){
+ return[
+ ['delete'],
+ ['create'],
+ ['update']
+ ];
+ }
+
+ public function providesVCards() {
+ return [
+ [true, null],
+ [true, ''],
+ [true, 'yasfewf'],
+ [true, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
+ [true, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
+ [true, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:someday\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
+ [false, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:1900-01-01\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
+ ];
+ }
+}
diff --git a/apps/dav/tests/unit/carddav/carddavbackendtest.php b/apps/dav/tests/unit/carddav/carddavbackendtest.php
index 3b5395fb09e..f920eb47b6e 100644
--- a/apps/dav/tests/unit/carddav/carddavbackendtest.php
+++ b/apps/dav/tests/unit/carddav/carddavbackendtest.php
@@ -77,7 +77,7 @@ class CardDavBackendTest extends TestCase {
$this->db = \OC::$server->getDatabaseConnection();
- $this->backend = new CardDavBackend($this->db, $this->principal);
+ $this->backend = new CardDavBackend($this->db, $this->principal, null);
// start every test with a empty cards_properties and cards table
$query = $this->db->getQueryBuilder();
@@ -155,7 +155,7 @@ class CardDavBackendTest extends TestCase {
/** @var CardDavBackend | \PHPUnit_Framework_MockObject_MockObject $backend */
$backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')
- ->setConstructorArgs([$this->db, $this->principal])
+ ->setConstructorArgs([$this->db, $this->principal, null])
->setMethods(['updateProperties', 'purgeProperties'])->getMock();
// create a new address book
@@ -201,7 +201,7 @@ class CardDavBackendTest extends TestCase {
public function testMultiCard() {
$this->backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')
- ->setConstructorArgs([$this->db, $this->principal])
+ ->setConstructorArgs([$this->db, $this->principal, null])
->setMethods(['updateProperties'])->getMock();
// create a new address book
@@ -248,7 +248,7 @@ class CardDavBackendTest extends TestCase {
public function testDeleteWithoutCard() {
$this->backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')
- ->setConstructorArgs([$this->db, $this->principal])
+ ->setConstructorArgs([$this->db, $this->principal, null])
->setMethods([
'getCardId',
'addChange',
@@ -289,7 +289,7 @@ class CardDavBackendTest extends TestCase {
public function testSyncSupport() {
$this->backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')
- ->setConstructorArgs([$this->db, $this->principal])
+ ->setConstructorArgs([$this->db, $this->principal, null])
->setMethods(['updateProperties'])->getMock();
// create a new address book
@@ -347,7 +347,7 @@ class CardDavBackendTest extends TestCase {
$cardId = 2;
$backend = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')
- ->setConstructorArgs([$this->db, $this->principal])
+ ->setConstructorArgs([$this->db, $this->principal, null])
->setMethods(['getCardId'])->getMock();
$backend->expects($this->any())->method('getCardId')->willReturn($cardId);
diff --git a/apps/dav/tests/unit/systemtag/systemtagplugin.php b/apps/dav/tests/unit/systemtag/systemtagplugin.php
index b026451701f..b945223e668 100644
--- a/apps/dav/tests/unit/systemtag/systemtagplugin.php
+++ b/apps/dav/tests/unit/systemtag/systemtagplugin.php
@@ -22,6 +22,8 @@
namespace OCA\DAV\Tests\Unit\SystemTag;
use OC\SystemTag\SystemTag;
+use OCP\IGroupManager;
+use OCP\IUserSession;
use OCP\SystemTag\TagAlreadyExistsException;
class SystemTagPlugin extends \Test\TestCase {
@@ -47,6 +49,16 @@ class SystemTagPlugin extends \Test\TestCase {
private $tagManager;
/**
+ * @var IGroupManager
+ */
+ private $groupManager;
+
+ /**
+ * @var IUserSession
+ */
+ private $userSession;
+
+ /**
* @var \OCA\DAV\SystemTag\SystemTagPlugin
*/
private $plugin;
@@ -60,8 +72,14 @@ class SystemTagPlugin extends \Test\TestCase {
$this->server = new \Sabre\DAV\Server($this->tree);
$this->tagManager = $this->getMock('\OCP\SystemTag\ISystemTagManager');
+ $this->groupManager = $this->getMock('\OCP\IGroupManager');
+ $this->userSession = $this->getMock('\OCP\IUserSession');
- $this->plugin = new \OCA\DAV\SystemTag\SystemTagPlugin($this->tagManager);
+ $this->plugin = new \OCA\DAV\SystemTag\SystemTagPlugin(
+ $this->tagManager,
+ $this->groupManager,
+ $this->userSession
+ );
$this->plugin->initialize($this->server);
}
@@ -153,7 +171,204 @@ class SystemTagPlugin extends \Test\TestCase {
$this->assertEquals(200, $result[self::USERVISIBLE_PROPERTYNAME]);
}
+ /**
+ * @expectedException \Sabre\DAV\Exception\BadRequest
+ * @expectedExceptionMessage Not sufficient permissions
+ */
+ public function testCreateNotAssignableTagAsRegularUser() {
+ $user = $this->getMock('\OCP\IUser');
+ $user->expects($this->once())
+ ->method('getUID')
+ ->willReturn('admin');
+ $this->userSession
+ ->expects($this->once())
+ ->method('isLoggedIn')
+ ->willReturn(true);
+ $this->userSession
+ ->expects($this->once())
+ ->method('getUser')
+ ->willReturn($user);
+ $this->groupManager
+ ->expects($this->once())
+ ->method('isAdmin')
+ ->with('admin')
+ ->willReturn(false);
+
+ $requestData = json_encode([
+ 'name' => 'Test',
+ 'userVisible' => true,
+ 'userAssignable' => false,
+ ]);
+
+ $node = $this->getMockBuilder('\OCA\DAV\SystemTag\SystemTagsByIdCollection')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->tagManager->expects($this->never())
+ ->method('createTag');
+
+ $this->tree->expects($this->any())
+ ->method('getNodeForPath')
+ ->with('/systemtags')
+ ->will($this->returnValue($node));
+
+ $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $request->expects($this->once())
+ ->method('getPath')
+ ->will($this->returnValue('/systemtags'));
+
+ $request->expects($this->once())
+ ->method('getBodyAsString')
+ ->will($this->returnValue($requestData));
+
+ $request->expects($this->once())
+ ->method('getHeader')
+ ->with('Content-Type')
+ ->will($this->returnValue('application/json'));
+
+ $this->plugin->httpPost($request, $response);
+ }
+
+ /**
+ * @expectedException \Sabre\DAV\Exception\BadRequest
+ * @expectedExceptionMessage Not sufficient permissions
+ */
+ public function testCreateInvisibleTagAsRegularUser() {
+ $user = $this->getMock('\OCP\IUser');
+ $user->expects($this->once())
+ ->method('getUID')
+ ->willReturn('admin');
+ $this->userSession
+ ->expects($this->once())
+ ->method('isLoggedIn')
+ ->willReturn(true);
+ $this->userSession
+ ->expects($this->once())
+ ->method('getUser')
+ ->willReturn($user);
+ $this->groupManager
+ ->expects($this->once())
+ ->method('isAdmin')
+ ->with('admin')
+ ->willReturn(false);
+
+ $requestData = json_encode([
+ 'name' => 'Test',
+ 'userVisible' => false,
+ 'userAssignable' => true,
+ ]);
+
+ $node = $this->getMockBuilder('\OCA\DAV\SystemTag\SystemTagsByIdCollection')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->tagManager->expects($this->never())
+ ->method('createTag');
+
+ $this->tree->expects($this->any())
+ ->method('getNodeForPath')
+ ->with('/systemtags')
+ ->will($this->returnValue($node));
+
+ $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $request->expects($this->once())
+ ->method('getPath')
+ ->will($this->returnValue('/systemtags'));
+
+ $request->expects($this->once())
+ ->method('getBodyAsString')
+ ->will($this->returnValue($requestData));
+
+ $request->expects($this->once())
+ ->method('getHeader')
+ ->with('Content-Type')
+ ->will($this->returnValue('application/json'));
+
+ $this->plugin->httpPost($request, $response);
+ }
+
+ public function testCreateTagInByIdCollectionAsRegularUser() {
+ $systemTag = new SystemTag(1, 'Test', true, false);
+
+ $requestData = json_encode([
+ 'name' => 'Test',
+ 'userVisible' => true,
+ 'userAssignable' => true,
+ ]);
+
+ $node = $this->getMockBuilder('\OCA\DAV\SystemTag\SystemTagsByIdCollection')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->tagManager->expects($this->once())
+ ->method('createTag')
+ ->with('Test', true, true)
+ ->will($this->returnValue($systemTag));
+
+ $this->tree->expects($this->any())
+ ->method('getNodeForPath')
+ ->with('/systemtags')
+ ->will($this->returnValue($node));
+
+ $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $request->expects($this->once())
+ ->method('getPath')
+ ->will($this->returnValue('/systemtags'));
+
+ $request->expects($this->once())
+ ->method('getBodyAsString')
+ ->will($this->returnValue($requestData));
+
+ $request->expects($this->once())
+ ->method('getHeader')
+ ->with('Content-Type')
+ ->will($this->returnValue('application/json'));
+
+ $request->expects($this->once())
+ ->method('getUrl')
+ ->will($this->returnValue('http://example.com/dav/systemtags'));
+
+ $response->expects($this->once())
+ ->method('setHeader')
+ ->with('Content-Location', 'http://example.com/dav/systemtags/1');
+
+ $this->plugin->httpPost($request, $response);
+ }
+
public function testCreateTagInByIdCollection() {
+ $user = $this->getMock('\OCP\IUser');
+ $user->expects($this->once())
+ ->method('getUID')
+ ->willReturn('admin');
+ $this->userSession
+ ->expects($this->once())
+ ->method('isLoggedIn')
+ ->willReturn(true);
+ $this->userSession
+ ->expects($this->once())
+ ->method('getUser')
+ ->willReturn($user);
+ $this->groupManager
+ ->expects($this->once())
+ ->method('isAdmin')
+ ->with('admin')
+ ->willReturn(true);
+
$systemTag = new SystemTag(1, 'Test', true, false);
$requestData = json_encode([
@@ -214,6 +429,24 @@ class SystemTagPlugin extends \Test\TestCase {
}
public function testCreateTagInMappingCollection() {
+ $user = $this->getMock('\OCP\IUser');
+ $user->expects($this->once())
+ ->method('getUID')
+ ->willReturn('admin');
+ $this->userSession
+ ->expects($this->once())
+ ->method('isLoggedIn')
+ ->willReturn(true);
+ $this->userSession
+ ->expects($this->once())
+ ->method('getUser')
+ ->willReturn($user);
+ $this->groupManager
+ ->expects($this->once())
+ ->method('isAdmin')
+ ->with('admin')
+ ->willReturn(true);
+
$systemTag = new SystemTag(1, 'Test', true, false);
$requestData = json_encode([
@@ -307,9 +540,27 @@ class SystemTagPlugin extends \Test\TestCase {
/**
* @dataProvider nodeClassProvider
- * @expectedException Sabre\DAV\Exception\Conflict
+ * @expectedException \Sabre\DAV\Exception\Conflict
*/
public function testCreateTagConflict($nodeClass) {
+ $user = $this->getMock('\OCP\IUser');
+ $user->expects($this->once())
+ ->method('getUID')
+ ->willReturn('admin');
+ $this->userSession
+ ->expects($this->once())
+ ->method('isLoggedIn')
+ ->willReturn(true);
+ $this->userSession
+ ->expects($this->once())
+ ->method('getUser')
+ ->willReturn($user);
+ $this->groupManager
+ ->expects($this->once())
+ ->method('isAdmin')
+ ->with('admin')
+ ->willReturn(true);
+
$requestData = json_encode([
'name' => 'Test',
'userVisible' => true,
diff --git a/apps/files_sharing/css/public.css b/apps/files_sharing/css/public.css
index bd8e98e966d..d09947dab26 100644
--- a/apps/files_sharing/css/public.css
+++ b/apps/files_sharing/css/public.css
@@ -1,7 +1,11 @@
#content {
height: initial;
min-height: calc(100vh - 120px);
- overflow: hidden;
+}
+
+/* force layout to make sure the content element's height matches its contents' height */
+.ie #content {
+ display: inline-block;
}
#preview {
diff --git a/apps/files_versions/lib/storage.php b/apps/files_versions/lib/storage.php
index da736c868fc..4eac476ed66 100644
--- a/apps/files_versions/lib/storage.php
+++ b/apps/files_versions/lib/storage.php
@@ -104,6 +104,9 @@ class Storage {
$ownerView = new View('/'.$uid.'/files');
try {
$filename = $ownerView->getPath($info['fileid']);
+ // make sure that the file name doesn't end with a trailing slash
+ // can for example happen single files shared across servers
+ $filename = rtrim($filename, '/');
} catch (NotFoundException $e) {
$filename = null;
}
diff --git a/build/integration/config/behat.yml b/build/integration/config/behat.yml
index 42f5f88ac8a..a1f9d610c68 100644
--- a/build/integration/config/behat.yml
+++ b/build/integration/config/behat.yml
@@ -12,6 +12,10 @@ default:
- admin
- admin
regular_user_password: 123456
+ - CommentsContext:
+ baseUrl: http://localhost:8080
+ - TagsContext:
+ baseUrl: http://localhost:8080
federation:
paths:
- %paths.base%/../federation_features
diff --git a/build/integration/features/bootstrap/CommentsContext.php b/build/integration/features/bootstrap/CommentsContext.php
new file mode 100644
index 00000000000..d720bb8dcd6
--- /dev/null
+++ b/build/integration/features/bootstrap/CommentsContext.php
@@ -0,0 +1,263 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+require __DIR__ . '/../../vendor/autoload.php';
+
+class CommentsContext implements \Behat\Behat\Context\Context {
+ /** @var string */
+ private $baseUrl;
+ /** @var array */
+ private $response;
+ /** @var int */
+ private $commentId;
+ /** @var int */
+ private $fileId;
+
+ /**
+ * @param string $baseUrl
+ */
+ public function __construct($baseUrl) {
+ $this->baseUrl = $baseUrl;
+
+ // in case of ci deployment we take the server url from the environment
+ $testServerUrl = getenv('TEST_SERVER_URL');
+ if ($testServerUrl !== false) {
+ $this->baseUrl = substr($testServerUrl, 0, -5);
+ }
+ }
+
+ /** @AfterScenario */
+ public function teardownScenario(\Behat\Behat\Hook\Scope\AfterScenarioScope $scope) {
+ $client = new \GuzzleHttp\Client();
+ try {
+ $client->delete(
+ $this->baseUrl.'/remote.php/webdav/myFileToComment.txt',
+ [
+ 'auth' => [
+ 'user0',
+ '123456',
+ ],
+ 'headers' => [
+ 'Content-Type' => 'application/json',
+ ],
+ ]
+ );
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ $e->getResponse();
+ }
+ }
+
+ /**
+ * @return int
+ */
+ private function getFileIdForPath($path) {
+ $url = $this->baseUrl.'/remote.php/webdav/'.$path;
+ $context = stream_context_create(array(
+ 'http' => array(
+ 'method' => 'PROPFIND',
+ 'header' => "Authorization: Basic dXNlcjA6MTIzNDU2\r\nContent-Type: application/x-www-form-urlencoded",
+ 'content' => '<?xml version="1.0"?>
+<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
+ <d:prop>
+ <oc:fileid />
+ </d:prop>
+</d:propfind>'
+ )
+ ));
+
+ $response = file_get_contents($url, false, $context);
+ preg_match_all('/\<oc:fileid\>(.*)\<\/oc:fileid\>/', $response, $matches);
+ return (int)$matches[1][0];
+ }
+
+ /**
+ * @When :user posts a comment with content :content on the file named :fileName it should return :statusCode
+ */
+ public function postsACommentWithContentOnTheFileNamedItShouldReturn($user, $content, $fileName, $statusCode) {
+ $fileId = $this->getFileIdForPath($fileName);
+ $this->fileId = (int)$fileId;
+ $url = $this->baseUrl.'/remote.php/dav/comments/files/'.$fileId.'/';
+
+ $client = new \GuzzleHttp\Client();
+ try {
+ $res = $client->post(
+ $url,
+ [
+ 'body' => '{"actorId":"user0","actorDisplayName":"user0","actorType":"users","verb":"comment","message":"' . $content . '","creationDateTime":"Thu, 18 Feb 2016 17:04:18 GMT","objectType":"files"}',
+ 'auth' => [
+ $user,
+ '123456',
+ ],
+ 'headers' => [
+ 'Content-Type' => 'application/json',
+ ],
+ ]
+ );
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ $res = $e->getResponse();
+ }
+
+ if($res->getStatusCode() !== (int)$statusCode) {
+ throw new \Exception("Response status code was not $statusCode (".$res->getStatusCode().")");
+ }
+ }
+
+
+ /**
+ * @Then As :user load all the comments of the file named :fileName it should return :statusCode
+ */
+ public function asLoadloadAllTheCommentsOfTheFileNamedItShouldReturn($user, $fileName, $statusCode) {
+ $fileId = $this->getFileIdForPath($fileName);
+ $url = $this->baseUrl.'/remote.php/dav/comments/files/'.$fileId.'/';
+
+ try {
+ $client = new \GuzzleHttp\Client();
+ $res = $client->createRequest(
+ 'REPORT',
+ $url,
+ [
+ 'body' => '<?xml version="1.0" encoding="utf-8" ?>
+<oc:filter-comments xmlns:D="DAV:" xmlns:oc="http://owncloud.org/ns">
+ <oc:limit>200</oc:limit>
+ <oc:offset>0</oc:offset>
+</oc:filter-comments>
+',
+ 'auth' => [
+ $user,
+ '123456',
+ ],
+ 'headers' => [
+ 'Content-Type' => 'application/json',
+ ],
+ ]
+ );
+ $res = $client->send($res);
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ $res = $e->getResponse();
+ }
+
+ if($res->getStatusCode() !== (int)$statusCode) {
+ throw new \Exception("Response status code was not $statusCode (".$res->getStatusCode().")");
+ }
+
+ if($res->getStatusCode() === 207) {
+ $service = new Sabre\Xml\Service();
+ $this->response = $service->parse($res->getBody()->getContents());
+ $this->commentId = (int)$this->response[0]['value'][2]['value'][0]['value'][0]['value'];
+ }
+ }
+
+ /**
+ * @Given As :user sending :verb to :url with
+ */
+ public function asUserSendingToWith($user, $verb, $url, \Behat\Gherkin\Node\TableNode $body) {
+ $client = new \GuzzleHttp\Client();
+ $options = [];
+ $options['auth'] = [$user, '123456'];
+ $fd = $body->getRowsHash();
+ $options['body'] = $fd;
+ $client->send($client->createRequest($verb, $this->baseUrl.'/ocs/v1.php/'.$url, $options));
+ }
+
+ /**
+ * @Then As :user delete the created comment it should return :statusCode
+ */
+ public function asDeleteTheCreatedCommentItShouldReturn($user, $statusCode) {
+ $url = $this->baseUrl.'/remote.php/dav/comments/files/'.$this->fileId.'/'.$this->commentId;
+
+ $client = new \GuzzleHttp\Client();
+ try {
+ $res = $client->delete(
+ $url,
+ [
+ 'auth' => [
+ $user,
+ '123456',
+ ],
+ 'headers' => [
+ 'Content-Type' => 'application/json',
+ ],
+ ]
+ );
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ $res = $e->getResponse();
+ }
+
+ if($res->getStatusCode() !== (int)$statusCode) {
+ throw new \Exception("Response status code was not $statusCode (".$res->getStatusCode().")");
+ }
+ }
+
+ /**
+ * @Then the response should contain a property :key with value :value
+ */
+ public function theResponseShouldContainAPropertyWithValue($key, $value) {
+ $keys = $this->response[0]['value'][2]['value'][0]['value'];
+ $found = false;
+ foreach($keys as $singleKey) {
+ if($singleKey['name'] === '{http://owncloud.org/ns}'.substr($key, 3)) {
+ if($singleKey['value'] === $value) {
+ $found = true;
+ }
+ }
+ }
+ if($found === false) {
+ throw new \Exception("Cannot find property $key with $value");
+ }
+ }
+
+ /**
+ * @Then the response should contain only :number comments
+ */
+ public function theResponseShouldContainOnlyComments($number) {
+ if(count($this->response) !== (int)$number) {
+ throw new \Exception("Found more comments than $number (".count($this->response).")");
+ }
+ }
+
+ /**
+ * @Then As :user edit the last created comment and set text to :text it should return :statusCode
+ */
+ public function asEditTheLastCreatedCommentAndSetTextToItShouldReturn($user, $text, $statusCode) {
+ $client = new \GuzzleHttp\Client();
+ $options = [];
+ $options['auth'] = [$user, '123456'];
+ $options['body'] = '<?xml version="1.0"?>
+<d:propertyupdate xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
+ <d:set>
+ <d:prop>
+ <oc:message>'.$text.'</oc:message>
+ </d:prop>
+ </d:set>
+</d:propertyupdate>';
+ try {
+ $res = $client->send($client->createRequest('PROPPATCH', $this->baseUrl.'/remote.php/dav/comments/files/' . $this->fileId . '/' . $this->commentId, $options));
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ $res = $e->getResponse();
+ }
+
+ if($res->getStatusCode() !== (int)$statusCode) {
+ throw new \Exception("Response status code was not $statusCode (".$res->getStatusCode().")");
+ }
+ }
+
+
+}
diff --git a/build/integration/features/bootstrap/Sharing.php b/build/integration/features/bootstrap/Sharing.php
index 5103b4af508..faf8e0bf507 100644
--- a/build/integration/features/bootstrap/Sharing.php
+++ b/build/integration/features/bootstrap/Sharing.php
@@ -370,5 +370,49 @@ trait Sharing{
}
}
+ /**
+ * @Then As :user remove all shares from the file named :fileName
+ */
+ public function asRemoveAllSharesFromTheFileNamed($user, $fileName) {
+ $url = $this->baseUrl.'v2.php/apps/files_sharing/api/v1/shares?format=json';
+ $client = new \GuzzleHttp\Client();
+ $res = $client->get(
+ $url,
+ [
+ 'auth' => [
+ $user,
+ '123456',
+ ],
+ 'headers' => [
+ 'Content-Type' => 'application/json',
+ ],
+ ]
+ );
+ $json = json_decode($res->getBody()->getContents(), true);
+ $deleted = false;
+ foreach($json['ocs']['data'] as $data) {
+ if (stripslashes($data['path']) === $fileName) {
+ $id = $data['id'];
+ $client->delete(
+ $this->baseUrl.'v2.php/apps/files_sharing/api/v1/shares/'.$id,
+ [
+ 'auth' => [
+ $user,
+ '123456',
+ ],
+ 'headers' => [
+ 'Content-Type' => 'application/json',
+ ],
+ ]
+ );
+ $deleted = true;
+ }
+ }
+
+ if($deleted === false) {
+ throw new \Exception("Could not delete file $fileName");
+ }
+ }
+
}
diff --git a/build/integration/features/bootstrap/TagsContext.php b/build/integration/features/bootstrap/TagsContext.php
new file mode 100644
index 00000000000..5e1f62ba838
--- /dev/null
+++ b/build/integration/features/bootstrap/TagsContext.php
@@ -0,0 +1,482 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+require __DIR__ . '/../../vendor/autoload.php';
+
+use Behat\Gherkin\Node\TableNode;
+use GuzzleHttp\Client;
+use GuzzleHttp\Message\ResponseInterface;
+
+class TagsContext implements \Behat\Behat\Context\Context {
+ /** @var string */
+ private $baseUrl;
+ /** @var Client */
+ private $client;
+ /** @var ResponseInterface */
+ private $response;
+
+ /**
+ * @param string $baseUrl
+ */
+ public function __construct($baseUrl) {
+ $this->baseUrl = $baseUrl;
+
+ // in case of ci deployment we take the server url from the environment
+ $testServerUrl = getenv('TEST_SERVER_URL');
+ if ($testServerUrl !== false) {
+ $this->baseUrl = substr($testServerUrl, 0, -5);
+ }
+ }
+
+ /** @BeforeScenario */
+ public function tearUpScenario() {
+ $this->client = new Client();
+ }
+
+ /** @AfterScenario */
+ public function tearDownScenario() {
+ $user = 'admin';
+ $tags = $this->requestTagsForUser($user);
+ foreach($tags as $tagId => $tag) {
+ $this->response = $this->client->delete(
+ $this->baseUrl . '/remote.php/dav/systemtags/'.$tagId,
+ [
+ 'auth' => [
+ $user,
+ $this->getPasswordForUser($user),
+ ],
+ 'headers' => [
+ 'Content-Type' => 'application/json',
+ ],
+ ]
+ );
+ }
+ try {
+ $this->client->delete(
+ $this->baseUrl . '/remote.php/webdav/myFileToTag.txt',
+ [
+ 'auth' => [
+ 'user0',
+ '123456',
+ ],
+ 'headers' => [
+ 'Content-Type' => 'application/json',
+ ],
+ ]
+ );
+ } catch (\GuzzleHttp\Exception\ClientException $e) {}
+ }
+
+ /**
+ * @param string $userName
+ * @return string
+ */
+ private function getPasswordForUser($userName) {
+ if($userName === 'admin') {
+ return 'admin';
+ }
+ return '123456';
+ }
+
+ /**
+ * @When :user creates a :type tag with name :name
+ */
+ public function createsATagWithName($user, $type, $name) {
+ $userVisible = 'true';
+ $userAssignable = 'true';
+ switch ($type) {
+ case 'normal':
+ break;
+ case 'not user-assignable':
+ $userAssignable = 'false';
+ break;
+ case 'not user-visible':
+ $userVisible = 'false';
+ break;
+ default:
+ throw new \Exception('Unsupported type');
+ }
+
+ try {
+ $this->response = $this->client->post(
+ $this->baseUrl . '/remote.php/dav/systemtags/',
+ [
+ 'auth' => [
+ $user,
+ $this->getPasswordForUser($user),
+ ],
+ 'headers' => [
+ 'Content-Type' => 'application/json',
+ ],
+ 'body' => '{"name":"'.$name.'","userVisible":'.$userVisible.',"userAssignable":'.$userAssignable.'}',
+ ]
+ );
+ } catch (\GuzzleHttp\Exception\ClientException $e){
+ $this->response = $e->getResponse();
+ }
+ }
+
+ /**
+ * @Then The response should have a status code :statusCode
+ */
+ public function theResponseShouldHaveAStatusCode($statusCode) {
+ if((int)$statusCode !== $this->response->getStatusCode()) {
+ throw new \Exception("Expected $statusCode, got ".$this->response->getStatusCode());
+ }
+ }
+
+ /**
+ * Returns all tags for a given user
+ *
+ * @param string $user
+ * @return array
+ */
+ private function requestTagsForUser($user) {
+ try {
+ $request = $this->client->createRequest(
+ 'PROPFIND',
+ $this->baseUrl . '/remote.php/dav/systemtags/',
+ [
+ 'body' => '<?xml version="1.0"?>
+<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
+ <d:prop>
+ <oc:id />
+ <oc:display-name />
+ <oc:user-visible />
+ <oc:user-assignable />
+ </d:prop>
+</d:propfind>',
+ 'auth' => [
+ $user,
+ $this->getPasswordForUser($user),
+ ],
+ 'headers' => [
+ 'Content-Type' => 'application/json',
+ ],
+ ]
+ );
+ $this->response = $this->client->send($request);
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ $this->response = $e->getResponse();
+ }
+
+ $tags = [];
+ $service = new Sabre\Xml\Service();
+ $parsed = $service->parse($this->response->getBody()->getContents());
+ foreach($parsed as $entry) {
+ $singleEntry = $entry['value'][1]['value'][0]['value'];
+ if(empty($singleEntry[0]['value'])) {
+ continue;
+ }
+
+ $tags[$singleEntry[0]['value']] = [
+ 'display-name' => $singleEntry[1]['value'],
+ 'user-visible' => $singleEntry[2]['value'],
+ 'user-assignable' => $singleEntry[3]['value'],
+ ];
+ }
+
+ return $tags;
+ }
+
+ /**
+ * @Then The following tags should exist for :user
+ */
+ public function theFollowingTagsShouldExistFor($user, TableNode $table) {
+ $tags = $this->requestTagsForUser($user);
+
+ if(count($table->getRows()) !== count($tags)) {
+ throw new \Exception(
+ sprintf(
+ "Expected %s tags, got %s.",
+ count($table->getRows()),
+ count($tags)
+ )
+ );
+ }
+
+ foreach($table->getRowsHash() as $rowDisplayName => $row) {
+ foreach($tags as $key => $tag) {
+ if(
+ $tag['display-name'] === $rowDisplayName &&
+ $tag['user-visible'] === $row[0] &&
+ $tag['user-assignable'] === $row[1]
+ ) {
+ unset($tags[$key]);
+ }
+ }
+ }
+ if(count($tags) !== 0) {
+ throw new \Exception('Not expected response');
+ }
+ }
+
+ /**
+ * @Then :count tags should exist for :user
+ */
+ public function tagsShouldExistFor($count, $user) {
+ if((int)$count !== count($this->requestTagsForUser($user))) {
+ throw new \Exception("Expected $count tags, got ".count($this->requestTagsForUser($user)));
+ }
+ }
+
+ /**
+ * @param string $name
+ * @return int
+ */
+ private function findTagIdByName($name) {
+ $tags = $this->requestTagsForUser('admin');
+ $tagId = 0;
+ foreach($tags as $id => $tag) {
+ if($tag['display-name'] === $name) {
+ $tagId = $id;
+ break;
+ }
+ }
+ return (int)$tagId;
+ }
+
+ /**
+ * @When :user edits the tag with name :oldNmae and sets its name to :newName
+ */
+ public function editsTheTagWithNameAndSetsItsNameTo($user, $oldName, $newName) {
+ $tagId = $this->findTagIdByName($oldName);
+ if($tagId === 0) {
+ throw new \Exception('Could not find tag to rename');
+ }
+
+ try {
+ $request = $this->client->createRequest(
+ 'PROPPATCH',
+ $this->baseUrl . '/remote.php/dav/systemtags/' . $tagId,
+ [
+ 'body' => '<?xml version="1.0"?>
+<d:propertyupdate xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
+ <d:set>
+ <d:prop>
+ <oc:display-name>' . $newName . '</oc:display-name>
+ </d:prop>
+ </d:set>
+</d:propertyupdate>',
+ 'auth' => [
+ $user,
+ $this->getPasswordForUser($user),
+ ],
+ ]
+ );
+ $this->response = $this->client->send($request);
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ $this->response = $e->getResponse();
+ }
+ }
+
+ /**
+ * @When :user deletes the tag with name :name
+ */
+ public function deletesTheTagWithName($user, $name) {
+ $tagId = $this->findTagIdByName($name);
+ try {
+ $this->response = $this->client->delete(
+ $this->baseUrl . '/remote.php/dav/systemtags/' . $tagId,
+ [
+ 'auth' => [
+ $user,
+ $this->getPasswordForUser($user),
+ ],
+ 'headers' => [
+ 'Content-Type' => 'application/json',
+ ],
+ ]
+ );
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ $this->response = $e->getResponse();
+ }
+ }
+
+ /**
+ * @param string $path
+ * @param string $user
+ * @return int
+ */
+ private function getFileIdForPath($path, $user) {
+ $url = $this->baseUrl.'/remote.php/webdav/'.$path;
+ $credentials = base64_encode($user .':'.$this->getPasswordForUser($user));
+ $context = stream_context_create(array(
+ 'http' => array(
+ 'method' => 'PROPFIND',
+ 'header' => "Authorization: Basic $credentials\r\nContent-Type: application/x-www-form-urlencoded",
+ 'content' => '<?xml version="1.0"?>
+<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
+ <d:prop>
+ <oc:fileid />
+ </d:prop>
+</d:propfind>'
+ )
+ ));
+ $response = file_get_contents($url, false, $context);
+ preg_match_all('/\<oc:fileid\>(.*)\<\/oc:fileid\>/', $response, $matches);
+ return (int)$matches[1][0];
+ }
+
+ /**
+ * @When :taggingUser adds the tag :tagName to :fileName shared by :sharingUser
+ */
+ public function addsTheTagToSharedBy($taggingUser, $tagName, $fileName, $sharingUser) {
+ $fileId = $this->getFileIdForPath($fileName, $sharingUser);
+ $tagId = $this->findTagIdByName($tagName);
+
+ try {
+ $this->response = $this->client->put(
+ $this->baseUrl.'/remote.php/dav/systemtags-relations/files/'.$fileId.'/'.$tagId,
+ [
+ 'auth' => [
+ $taggingUser,
+ $this->getPasswordForUser($taggingUser),
+ ]
+ ]
+ );
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ $this->response = $e->getResponse();
+ }
+ }
+
+ /**
+ * @Then :fileName shared by :sharingUser has the following tags
+ */
+ public function sharedByHasTheFollowingTags($fileName, $sharingUser, TableNode $table) {
+ $loadedExpectedTags = $table->getTable();
+ $expectedTags = [];
+ foreach($loadedExpectedTags as $expected) {
+ $expectedTags[] = $expected[0];
+ }
+
+ // Get the real tags
+ $request = $this->client->createRequest(
+ 'PROPFIND',
+ $this->baseUrl.'/remote.php/dav/systemtags-relations/files/'.$this->getFileIdForPath($fileName, $sharingUser),
+ [
+ 'auth' => [
+ $sharingUser,
+ $this->getPasswordForUser($sharingUser),
+ ],
+ 'body' => '<?xml version="1.0"?>
+<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
+ <d:prop>
+ <oc:id />
+ <oc:display-name />
+ <oc:user-visible />
+ <oc:user-assignable />
+ </d:prop>
+</d:propfind>',
+ ]
+ );
+ $response = $this->client->send($request)->getBody()->getContents();
+ preg_match_all('/\<oc:display-name\>(.*)\<\/oc:display-name\>/', $response, $realTags);
+
+ foreach($expectedTags as $key => $row) {
+ foreach($realTags as $tag) {
+ if($tag[0] === $row) {
+ unset($expectedTags[$key]);
+ }
+ }
+ }
+
+ if(count($expectedTags) !== 0) {
+ throw new \Exception('Not all tags found.');
+ }
+ }
+
+ /**
+ * @Then :fileName shared by :sharingUser has the following tags for :user
+ */
+ public function sharedByHasTheFollowingTagsFor($fileName, $sharingUser, $user, TableNode $table) {
+ $loadedExpectedTags = $table->getTable();
+ $expectedTags = [];
+ foreach($loadedExpectedTags as $expected) {
+ $expectedTags[] = $expected[0];
+ }
+
+ // Get the real tags
+ try {
+ $request = $this->client->createRequest(
+ 'PROPFIND',
+ $this->baseUrl . '/remote.php/dav/systemtags-relations/files/' . $this->getFileIdForPath($fileName, $sharingUser),
+ [
+ 'auth' => [
+ $user,
+ $this->getPasswordForUser($user),
+ ],
+ 'body' => '<?xml version="1.0"?>
+<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
+ <d:prop>
+ <oc:id />
+ <oc:display-name />
+ <oc:user-visible />
+ <oc:user-assignable />
+ </d:prop>
+</d:propfind>',
+ ]
+ );
+ $this->response = $this->client->send($request)->getBody()->getContents();
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ $this->response = $e->getResponse();
+ }
+ preg_match_all('/\<oc:display-name\>(.*)\<\/oc:display-name\>/', $this->response, $realTags);
+ $realTags = array_filter($realTags);
+ $expectedTags = array_filter($expectedTags);
+
+ foreach($expectedTags as $key => $row) {
+ foreach($realTags as $tag) {
+ foreach($tag as $index => $foo) {
+ if($tag[$index] === $row) {
+ unset($expectedTags[$key]);
+ }
+ }
+ }
+ }
+
+ if(count($expectedTags) !== 0) {
+ throw new \Exception('Not all tags found.');
+ }
+ }
+
+ /**
+ * @When :user removes the tag :tagName from :fileName shared by :shareUser
+ */
+ public function removesTheTagFromSharedBy($user, $tagName, $fileName, $shareUser) {
+ $tagId = $this->findTagIdByName($tagName);
+ $fileId = $this->getFileIdForPath($fileName, $shareUser);
+
+ try {
+ $this->response = $this->client->delete(
+ $this->baseUrl.'/remote.php/dav/systemtags-relations/files/'.$fileId.'/'.$tagId,
+ [
+ 'auth' => [
+ $user,
+ $this->getPasswordForUser($user),
+ ],
+ ]
+ );
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ $this->response = $e->getResponse();
+ }
+ }
+}
diff --git a/build/integration/features/bootstrap/WebDav.php b/build/integration/features/bootstrap/WebDav.php
index 49cd565cf26..58fdfed1711 100644
--- a/build/integration/features/bootstrap/WebDav.php
+++ b/build/integration/features/bootstrap/WebDav.php
@@ -9,8 +9,7 @@ use Sabre\DAV\Client as SClient;
require __DIR__ . '/../../vendor/autoload.php';
-trait WebDav{
-
+trait WebDav {
/** @var string*/
private $davPath = "remote.php/webdav";
@@ -163,6 +162,18 @@ trait WebDav{
}
/**
+ * @When User :user deletes file :file
+ */
+ public function userDeletesFile($user, $file) {
+ try {
+ $this->response = $this->makeDavRequest($user, 'DELETE', $file, []);
+ } catch (\GuzzleHttp\Exception\ServerException $e) {
+ // 4xx and 5xx responses cause an exception
+ $this->response = $e->getResponse();
+ }
+ }
+
+ /**
* @Given User :user created a folder :destination
*/
public function userCreatedAFolder($user, $destination){
diff --git a/build/integration/features/comments.feature b/build/integration/features/comments.feature
new file mode 100644
index 00000000000..135bb016527
--- /dev/null
+++ b/build/integration/features/comments.feature
@@ -0,0 +1,209 @@
+Feature: comments
+ Scenario: Creating a comment on a file belonging to myself
+ Given user "user0" exists
+ Given As an "user0"
+ Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
+ When "user0" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201"
+ Then As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207"
+ And the response should contain a property "oc:parentId" with value "0"
+ And the response should contain a property "oc:childrenCount" with value "0"
+ And the response should contain a property "oc:verb" with value "comment"
+ And the response should contain a property "oc:actorType" with value "users"
+ And the response should contain a property "oc:objectType" with value "files"
+ And the response should contain a property "oc:message" with value "My first comment"
+ And the response should contain a property "oc:actorDisplayName" with value "user0"
+ And the response should contain only "1" comments
+
+ Scenario: Creating a comment on a shared file belonging to another user
+ Given user "user0" exists
+ Given user "user1" exists
+ Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToComment.txt |
+ | shareWith | user1 |
+ | shareType | 0 |
+ When "user1" posts a comment with content "A comment from another user" on the file named "/myFileToComment.txt" it should return "201"
+ Then As "user1" load all the comments of the file named "/myFileToComment.txt" it should return "207"
+ And the response should contain a property "oc:parentId" with value "0"
+ And the response should contain a property "oc:childrenCount" with value "0"
+ And the response should contain a property "oc:verb" with value "comment"
+ And the response should contain a property "oc:actorType" with value "users"
+ And the response should contain a property "oc:objectType" with value "files"
+ And the response should contain a property "oc:message" with value "A comment from another user"
+ And the response should contain a property "oc:actorDisplayName" with value "user1"
+ And the response should contain only "1" comments
+
+ Scenario: Creating a comment on a non-shared file belonging to another user
+ Given user "user0" exists
+ Given user "user1" exists
+ Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
+ Then "user1" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "404"
+
+ Scenario: Reading comments on a non-shared file belonging to another user
+ Given user "user0" exists
+ Given user "user1" exists
+ Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
+ Then As "user1" load all the comments of the file named "/myFileToComment.txt" it should return "404"
+
+ Scenario: Deleting my own comments on a file belonging to myself
+ Given user "user0" exists
+ Given As an "user0"
+ Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
+ Given "user0" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201"
+ When As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207"
+ Then the response should contain a property "oc:parentId" with value "0"
+ Then the response should contain a property "oc:childrenCount" with value "0"
+ And the response should contain a property "oc:verb" with value "comment"
+ And the response should contain a property "oc:actorType" with value "users"
+ And the response should contain a property "oc:objectType" with value "files"
+ And the response should contain a property "oc:message" with value "My first comment"
+ And the response should contain a property "oc:actorDisplayName" with value "user0"
+ And the response should contain only "1" comments
+ And As "user0" delete the created comment it should return "204"
+ And As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207"
+ And the response should contain only "0" comments
+
+ Scenario: Deleting my own comments on a file shared by somebody else
+ Given user "user0" exists
+ Given user "user1" exists
+ Given As an "user0"
+ Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToComment.txt |
+ | shareWith | user1 |
+ | shareType | 0 |
+ Given "user1" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201"
+ When As "user1" load all the comments of the file named "/myFileToComment.txt" it should return "207"
+ Then the response should contain a property "oc:parentId" with value "0"
+ And the response should contain a property "oc:childrenCount" with value "0"
+ And the response should contain a property "oc:verb" with value "comment"
+ And the response should contain a property "oc:actorType" with value "users"
+ And the response should contain a property "oc:objectType" with value "files"
+ And the response should contain a property "oc:message" with value "My first comment"
+ And the response should contain a property "oc:actorDisplayName" with value "user1"
+ And the response should contain only "1" comments
+ And As "user1" delete the created comment it should return "204"
+ And As "user1" load all the comments of the file named "/myFileToComment.txt" it should return "207"
+ And the response should contain only "0" comments
+
+ Scenario: Deleting my own comments on a file unshared by someone else
+ Given user "user0" exists
+ Given user "user1" exists
+ Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToComment.txt |
+ | shareWith | user1 |
+ | shareType | 0 |
+ Given "user1" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201"
+ When As "user1" load all the comments of the file named "/myFileToComment.txt" it should return "207"
+ Then the response should contain a property "oc:parentId" with value "0"
+ And the response should contain a property "oc:childrenCount" with value "0"
+ And the response should contain a property "oc:verb" with value "comment"
+ And the response should contain a property "oc:actorType" with value "users"
+ And the response should contain a property "oc:objectType" with value "files"
+ And the response should contain a property "oc:message" with value "My first comment"
+ And the response should contain a property "oc:actorDisplayName" with value "user1"
+ And the response should contain only "1" comments
+ And As "user0" remove all shares from the file named "/myFileToComment.txt"
+ And As "user1" delete the created comment it should return "404"
+ And As "user1" load all the comments of the file named "/myFileToComment.txt" it should return "404"
+
+ Scenario: Edit my own comments on a file belonging to myself
+ Given user "user0" exists
+ Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
+ Given "user0" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201"
+ When As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207"
+ Then the response should contain a property "oc:parentId" with value "0"
+ And the response should contain a property "oc:childrenCount" with value "0"
+ And the response should contain a property "oc:verb" with value "comment"
+ And the response should contain a property "oc:actorType" with value "users"
+ And the response should contain a property "oc:objectType" with value "files"
+ And the response should contain a property "oc:message" with value "My first comment"
+ And the response should contain a property "oc:actorDisplayName" with value "user0"
+ And the response should contain only "1" comments
+ When As "user0" edit the last created comment and set text to "My edited comment" it should return "207"
+ Then As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207"
+ And the response should contain a property "oc:parentId" with value "0"
+ And the response should contain a property "oc:childrenCount" with value "0"
+ And the response should contain a property "oc:verb" with value "comment"
+ And the response should contain a property "oc:actorType" with value "users"
+ And the response should contain a property "oc:objectType" with value "files"
+ And the response should contain a property "oc:message" with value "My edited comment"
+ And the response should contain a property "oc:actorDisplayName" with value "user0"
+
+ Scenario: Edit my own comments on a file shared by someone with me
+ Given user "user0" exists
+ Given user "user1" exists
+ Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToComment.txt |
+ | shareWith | user1 |
+ | shareType | 0 |
+ Given "user1" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201"
+ When As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207"
+ Then the response should contain a property "oc:parentId" with value "0"
+ And the response should contain a property "oc:childrenCount" with value "0"
+ And the response should contain a property "oc:verb" with value "comment"
+ And the response should contain a property "oc:actorType" with value "users"
+ And the response should contain a property "oc:objectType" with value "files"
+ And the response should contain a property "oc:message" with value "My first comment"
+ And the response should contain a property "oc:actorDisplayName" with value "user1"
+ And the response should contain only "1" comments
+ Given As "user1" edit the last created comment and set text to "My edited comment" it should return "207"
+ Then As "user1" load all the comments of the file named "/myFileToComment.txt" it should return "207"
+ And the response should contain a property "oc:parentId" with value "0"
+ And the response should contain a property "oc:childrenCount" with value "0"
+ And the response should contain a property "oc:verb" with value "comment"
+ And the response should contain a property "oc:actorType" with value "users"
+ And the response should contain a property "oc:objectType" with value "files"
+ And the response should contain a property "oc:message" with value "My edited comment"
+ And the response should contain a property "oc:actorDisplayName" with value "user1"
+
+ Scenario: Edit my own comments on a file unshared by someone with me
+ Given user "user0" exists
+ Given user "user1" exists
+ Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToComment.txt |
+ | shareWith | user1 |
+ | shareType | 0 |
+ When "user1" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201"
+ Then As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207"
+ And the response should contain a property "oc:parentId" with value "0"
+ And the response should contain a property "oc:childrenCount" with value "0"
+ And the response should contain a property "oc:verb" with value "comment"
+ And the response should contain a property "oc:actorType" with value "users"
+ And the response should contain a property "oc:objectType" with value "files"
+ And the response should contain a property "oc:message" with value "My first comment"
+ And the response should contain a property "oc:actorDisplayName" with value "user1"
+ And the response should contain only "1" comments
+ And As "user0" remove all shares from the file named "/myFileToComment.txt"
+ When As "user1" edit the last created comment and set text to "My edited comment" it should return "404"
+ Then As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207"
+ And the response should contain a property "oc:parentId" with value "0"
+ And the response should contain a property "oc:childrenCount" with value "0"
+ And the response should contain a property "oc:verb" with value "comment"
+ And the response should contain a property "oc:actorType" with value "users"
+ And the response should contain a property "oc:objectType" with value "files"
+ And the response should contain a property "oc:message" with value "My first comment"
+ And the response should contain a property "oc:actorDisplayName" with value "user1"
+
+ Scenario: Edit comments of other users should not be possible
+ Given user "user0" exists
+ Given user "user1" exists
+ Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt"
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToComment.txt |
+ | shareWith | user1 |
+ | shareType | 0 |
+ Given "user1" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201"
+ When As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207"
+ Then the response should contain a property "oc:parentId" with value "0"
+ And the response should contain a property "oc:childrenCount" with value "0"
+ And the response should contain a property "oc:verb" with value "comment"
+ And the response should contain a property "oc:actorType" with value "users"
+ And the response should contain a property "oc:objectType" with value "files"
+ And the response should contain a property "oc:message" with value "My first comment"
+ And the response should contain a property "oc:actorDisplayName" with value "user1"
+ And the response should contain only "1" comments
+ Then As "user0" edit the last created comment and set text to "My edited comment" it should return "403" \ No newline at end of file
diff --git a/build/integration/features/tags.feature b/build/integration/features/tags.feature
new file mode 100644
index 00000000000..286fb62bf42
--- /dev/null
+++ b/build/integration/features/tags.feature
@@ -0,0 +1,370 @@
+Feature: tags
+
+ Scenario: Creating a normal tag as regular user should work
+ Given user "user0" exists
+ When "user0" creates a "normal" tag with name "MySuperAwesomeTagName"
+ Then The response should have a status code "201"
+ And The following tags should exist for "admin"
+ |MySuperAwesomeTagName|true|true|
+ And The following tags should exist for "user0"
+ |MySuperAwesomeTagName|true|true|
+
+ Scenario: Creating a not user-assignable tag as regular user should fail
+ Given user "user0" exists
+ When "user0" creates a "not user-assignable" tag with name "MySuperAwesomeTagName"
+ Then The response should have a status code "400"
+ And "0" tags should exist for "admin"
+
+ Scenario: Creating a not user-visible tag as regular user should fail
+ Given user "user0" exists
+ When "user0" creates a "not user-visible" tag with name "MySuperAwesomeTagName"
+ Then The response should have a status code "400"
+ And "0" tags should exist for "admin"
+
+ Scenario: Renaming a normal tag as regular user should work
+ Given user "user0" exists
+ Given "admin" creates a "normal" tag with name "MySuperAwesomeTagName"
+ When "user0" edits the tag with name "MySuperAwesomeTagName" and sets its name to "AnotherTagName"
+ Then The response should have a status code "207"
+ And The following tags should exist for "admin"
+ |AnotherTagName|true|true|
+
+ Scenario: Renaming a not user-assignable tag as regular user should fail
+ Given user "user0" exists
+ Given "admin" creates a "not user-assignable" tag with name "MySuperAwesomeTagName"
+ When "user0" edits the tag with name "MySuperAwesomeTagName" and sets its name to "AnotherTagName"
+ Then The response should have a status code "403"
+ And The following tags should exist for "admin"
+ |MySuperAwesomeTagName|true|false|
+
+ Scenario: Renaming a not user-visible tag as regular user should fail
+ Given user "user0" exists
+ Given "admin" creates a "not user-visible" tag with name "MySuperAwesomeTagName"
+ When "user0" edits the tag with name "MySuperAwesomeTagName" and sets its name to "AnotherTagName"
+ Then The response should have a status code "404"
+ And The following tags should exist for "admin"
+ |MySuperAwesomeTagName|false|true|
+
+ Scenario: Deleting a normal tag as regular user should work
+ Given user "user0" exists
+ Given "admin" creates a "normal" tag with name "MySuperAwesomeTagName"
+ When "user0" deletes the tag with name "MySuperAwesomeTagName"
+ Then The response should have a status code "204"
+ And "0" tags should exist for "admin"
+
+ Scenario: Deleting a not user-assignable tag as regular user should fail
+ Given user "user0" exists
+ Given "admin" creates a "not user-assignable" tag with name "MySuperAwesomeTagName"
+ When "user0" deletes the tag with name "MySuperAwesomeTagName"
+ Then The response should have a status code "403"
+ And The following tags should exist for "admin"
+ |MySuperAwesomeTagName|true|false|
+
+ Scenario: Deleting a not user-visible tag as regular user should fail
+ Given user "user0" exists
+ Given "admin" creates a "not user-visible" tag with name "MySuperAwesomeTagName"
+ When "user0" deletes the tag with name "MySuperAwesomeTagName"
+ Then The response should have a status code "404"
+ And The following tags should exist for "admin"
+ |MySuperAwesomeTagName|false|true|
+
+ Scenario: Deleting a not user-assignable tag as admin should work
+ Given "admin" creates a "not user-assignable" tag with name "MySuperAwesomeTagName"
+ When "admin" deletes the tag with name "MySuperAwesomeTagName"
+ Then The response should have a status code "204"
+ And "0" tags should exist for "admin"
+
+ Scenario: Deleting a not user-visible tag as admin should work
+ Given "admin" creates a "not user-visible" tag with name "MySuperAwesomeTagName"
+ When "admin" deletes the tag with name "MySuperAwesomeTagName"
+ Then The response should have a status code "204"
+ And "0" tags should exist for "admin"
+
+ Scenario: Assigning a normal tag to a file shared by someone else as regular user should work
+ Given user "user0" exists
+ Given user "user1" exists
+ Given "admin" creates a "normal" tag with name "MySuperAwesomeTagName"
+ Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToTag.txt |
+ | shareWith | user1 |
+ | shareType | 0 |
+ When "user1" adds the tag "MySuperAwesomeTagName" to "/myFileToTag.txt" shared by "user0"
+ Then The response should have a status code "201"
+ And "/myFileToTag.txt" shared by "user0" has the following tags
+ |MySuperAwesomeTagName|
+
+ Scenario: Assigning a normal tag to a file belonging to someone else as regular user should fail
+ Given user "user0" exists
+ Given user "user1" exists
+ Given "admin" creates a "normal" tag with name "MyFirstTag"
+ Given "admin" creates a "normal" tag with name "MySecondTag"
+ Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
+ When "user0" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
+ When "user1" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
+ Then The response should have a status code "404"
+ And "/myFileToTag.txt" shared by "user0" has the following tags
+ |MyFirstTag|
+
+ Scenario: Assigning a not user-assignable tag to a file shared by someone else as regular user should fail
+ Given user "user0" exists
+ Given user "user1" exists
+ Given "admin" creates a "normal" tag with name "MyFirstTag"
+ Given "admin" creates a "not user-assignable" tag with name "MySecondTag"
+ Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToTag.txt |
+ | shareWith | user1 |
+ | shareType | 0 |
+ When "user0" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
+ When "user1" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
+ Then The response should have a status code "403"
+ And "/myFileToTag.txt" shared by "user0" has the following tags
+ |MyFirstTag|
+
+ Scenario: Assigning a not user-visible tag to a file shared by someone else as regular user should fail
+ Given user "user0" exists
+ Given user "user1" exists
+ Given "admin" creates a "normal" tag with name "MyFirstTag"
+ Given "admin" creates a "not user-visible" tag with name "MySecondTag"
+ Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToTag.txt |
+ | shareWith | user1 |
+ | shareType | 0 |
+ When "user0" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
+ When "user1" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
+ Then The response should have a status code "412"
+ And "/myFileToTag.txt" shared by "user0" has the following tags
+ |MyFirstTag|
+
+ Scenario: Assigning a not user-visible tag to a file shared by someone else as admin user should work
+ Given user "user0" exists
+ Given "admin" creates a "normal" tag with name "MyFirstTag"
+ Given "admin" creates a "not user-visible" tag with name "MySecondTag"
+ Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToTag.txt |
+ | shareWith | admin |
+ | shareType | 0 |
+ When "user0" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
+ When "admin" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
+ Then The response should have a status code "201"
+ And "/myFileToTag.txt" shared by "user0" has the following tags for "admin"
+ |MyFirstTag|
+ |MySecondTag|
+ And "/myFileToTag.txt" shared by "user0" has the following tags for "user0"
+ |MyFirstTag|
+
+ Scenario: Assigning a not user-assignable tag to a file shared by someone else as admin user should worj
+ Given user "user0" exists
+ Given "admin" creates a "normal" tag with name "MyFirstTag"
+ Given "admin" creates a "not user-assignable" tag with name "MySecondTag"
+ Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToTag.txt |
+ | shareWith | admin |
+ | shareType | 0 |
+ When "user0" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
+ When "admin" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
+ Then The response should have a status code "201"
+ And "/myFileToTag.txt" shared by "user0" has the following tags for "admin"
+ |MyFirstTag|
+ |MySecondTag|
+ And "/myFileToTag.txt" shared by "user0" has the following tags for "user0"
+ |MyFirstTag|
+ |MySecondTag|
+
+ Scenario: Unassigning a normal tag from a file shared by someone else as regular user should work
+ Given user "user0" exists
+ Given user "user1" exists
+ Given "admin" creates a "normal" tag with name "MyFirstTag"
+ Given "admin" creates a "normal" tag with name "MySecondTag"
+ Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToTag.txt |
+ | shareWith | user1 |
+ | shareType | 0 |
+ Given "user0" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
+ Given "user0" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
+ When "user1" removes the tag "MyFirstTag" from "/myFileToTag.txt" shared by "user0"
+ Then The response should have a status code "204"
+ And "/myFileToTag.txt" shared by "user0" has the following tags for "user0"
+ |MySecondTag|
+
+ Scenario: Unassigning a normal tag from a file unshared by someone else as regular user should fail
+ Given user "user0" exists
+ Given user "user1" exists
+ Given "admin" creates a "normal" tag with name "MyFirstTag"
+ Given "admin" creates a "normal" tag with name "MySecondTag"
+ Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
+ Given "user0" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
+ Given "user0" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
+ When "user1" removes the tag "MyFirstTag" from "/myFileToTag.txt" shared by "user0"
+ Then The response should have a status code "404"
+ And "/myFileToTag.txt" shared by "user0" has the following tags for "user0"
+ |MyFirstTag|
+ |MySecondTag|
+
+ Scenario: Unassigning a not user-visible tag from a file shared by someone else as regular user should fail
+ Given user "user0" exists
+ Given user "user1" exists
+ Given "admin" creates a "not user-visible" tag with name "MyFirstTag"
+ Given "admin" creates a "normal" tag with name "MySecondTag"
+ Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToTag.txt |
+ | shareWith | user1 |
+ | shareType | 0 |
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToTag.txt |
+ | shareWith | admin |
+ | shareType | 0 |
+ Given "admin" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
+ Given "user0" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
+ When "user1" removes the tag "MyFirstTag" from "/myFileToTag.txt" shared by "user0"
+ Then The response should have a status code "404"
+ And "/myFileToTag.txt" shared by "user0" has the following tags for "user0"
+ |MySecondTag|
+ And "/myFileToTag.txt" shared by "user0" has the following tags for "admin"
+ |MyFirstTag|
+ |MySecondTag|
+
+ Scenario: Unassigning a not user-visible tag from a file shared by someone else as admin should work
+ Given user "user0" exists
+ Given user "user1" exists
+ Given "admin" creates a "not user-visible" tag with name "MyFirstTag"
+ Given "admin" creates a "normal" tag with name "MySecondTag"
+ Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToTag.txt |
+ | shareWith | user1 |
+ | shareType | 0 |
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToTag.txt |
+ | shareWith | admin |
+ | shareType | 0 |
+ Given "admin" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
+ Given "user0" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
+ When "admin" removes the tag "MyFirstTag" from "/myFileToTag.txt" shared by "user0"
+ Then The response should have a status code "204"
+ And "/myFileToTag.txt" shared by "user0" has the following tags for "user0"
+ |MySecondTag|
+ And "/myFileToTag.txt" shared by "user0" has the following tags for "admin"
+ |MySecondTag|
+
+ Scenario: Unassigning a not user-visible tag from a file unshared by someone else should fail
+ Given user "user0" exists
+ Given user "user1" exists
+ Given "admin" creates a "not user-visible" tag with name "MyFirstTag"
+ Given "admin" creates a "normal" tag with name "MySecondTag"
+ Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToTag.txt |
+ | shareWith | user1 |
+ | shareType | 0 |
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToTag.txt |
+ | shareWith | admin |
+ | shareType | 0 |
+ Given "admin" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
+ Given "user0" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
+ Given As "user0" remove all shares from the file named "/myFileToTag.txt"
+ When "admin" removes the tag "MyFirstTag" from "/myFileToTag.txt" shared by "user0"
+ Then The response should have a status code "404"
+
+ Scenario: Unassigning a not user-assignable tag from a file shared by someone else as regular user should fail
+ Given user "user0" exists
+ Given user "user1" exists
+ Given "admin" creates a "not user-assignable" tag with name "MyFirstTag"
+ Given "admin" creates a "normal" tag with name "MySecondTag"
+ Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToTag.txt |
+ | shareWith | user1 |
+ | shareType | 0 |
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToTag.txt |
+ | shareWith | admin |
+ | shareType | 0 |
+ Given "admin" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
+ Given "user0" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
+ When "user1" removes the tag "MyFirstTag" from "/myFileToTag.txt" shared by "user0"
+ Then The response should have a status code "403"
+ And "/myFileToTag.txt" shared by "user0" has the following tags for "user0"
+ |MyFirstTag|
+ |MySecondTag|
+ And "/myFileToTag.txt" shared by "user0" has the following tags for "admin"
+ |MyFirstTag|
+ |MySecondTag|
+
+ Scenario: Unassigning a not user-assignable tag from a file shared by someone else as admin should work
+ Given user "user0" exists
+ Given user "user1" exists
+ Given "admin" creates a "not user-assignable" tag with name "MyFirstTag"
+ Given "admin" creates a "normal" tag with name "MySecondTag"
+ Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToTag.txt |
+ | shareWith | user1 |
+ | shareType | 0 |
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToTag.txt |
+ | shareWith | admin |
+ | shareType | 0 |
+ Given "admin" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
+ Given "user0" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
+ When "admin" removes the tag "MyFirstTag" from "/myFileToTag.txt" shared by "user0"
+ Then The response should have a status code "204"
+ And "/myFileToTag.txt" shared by "user0" has the following tags for "user0"
+ |MySecondTag|
+ And "/myFileToTag.txt" shared by "user0" has the following tags for "admin"
+ |MySecondTag|
+
+ Scenario: Unassigning a not user-assignable tag from a file unshared by someone else should fail
+ Given user "user0" exists
+ Given user "user1" exists
+ Given "admin" creates a "not user-assignable" tag with name "MyFirstTag"
+ Given "admin" creates a "normal" tag with name "MySecondTag"
+ Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToTag.txt |
+ | shareWith | user1 |
+ | shareType | 0 |
+ Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with
+ | path | myFileToTag.txt |
+ | shareWith | admin |
+ | shareType | 0 |
+ Given "admin" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
+ Given "user0" adds the tag "MySecondTag" to "/myFileToTag.txt" shared by "user0"
+ Given As "user0" remove all shares from the file named "/myFileToTag.txt"
+ When "admin" removes the tag "MyFirstTag" from "/myFileToTag.txt" shared by "user0"
+ Then The response should have a status code "404"
+
+ Scenario: Overwriting existing normal tags should fail
+ Given user "user0" exists
+ Given "user0" creates a "normal" tag with name "MyFirstTag"
+ When "user0" creates a "normal" tag with name "MyFirstTag"
+ Then The response should have a status code "409"
+
+ Scenario: Overwriting existing not user-assignable tags should fail
+ Given "admin" creates a "not user-assignable" tag with name "MyFirstTag"
+ When "admin" creates a "not user-assignable" tag with name "MyFirstTag"
+ Then The response should have a status code "409"
+
+ Scenario: Overwriting existing not user-visible tags should fail
+ Given "admin" creates a "not user-visible" tag with name "MyFirstTag"
+ When "admin" creates a "not user-visible" tag with name "MyFirstTag"
+ Then The response should have a status code "409"
+
+ Scenario: Getting tags only works with access to the file
+ Given user "user0" exists
+ Given user "user1" exists
+ Given "admin" creates a "normal" tag with name "MyFirstTag"
+ Given user "user0" uploads file "data/textfile.txt" to "/myFileToTag.txt"
+ When "user0" adds the tag "MyFirstTag" to "/myFileToTag.txt" shared by "user0"
+ And "/myFileToTag.txt" shared by "user0" has the following tags for "user0"
+ |MyFirstTag|
+ And "/myFileToTag.txt" shared by "user0" has the following tags for "user1"
+ ||
+ And The response should have a status code "404"
diff --git a/console.php b/console.php
index d8c23d4ce00..eb6c84c3cf8 100644
--- a/console.php
+++ b/console.php
@@ -27,6 +27,7 @@
*/
use OC\Console\Application;
+use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
define('OC_CONSOLE', 1);
@@ -81,7 +82,7 @@ try {
}
$application = new Application(\OC::$server->getConfig(), \OC::$server->getEventDispatcher(), \OC::$server->getRequest());
- $application->loadCommands(new ConsoleOutput());
+ $application->loadCommands(new ArgvInput(), new ConsoleOutput());
$application->run();
} catch (Exception $ex) {
echo "An unhandled exception has been thrown:" . PHP_EOL;
diff --git a/core/css/systemtags.css b/core/css/systemtags.css
index 40f93011b6a..22e41ea53ca 100644
--- a/core/css/systemtags.css
+++ b/core/css/systemtags.css
@@ -13,9 +13,12 @@
}
.systemtags-select2-dropdown .select2-highlighted,
.systemtags-select2-dropdown .select2-selected.select2-highlighted {
- background: #3875d7;
+ background: #f8f8f8;
}
+.select2-result {
+ position: relative;
+}
.systemtags-select2-dropdown .select2-highlighted {
color: #000000;
}
@@ -33,10 +36,16 @@
.systemtags-select2-dropdown .select2-result-label .icon {
display: inline-block;
+ opacity: .5;
+}
+.systemtags-select2-dropdown .select2-result-label .icon.rename {
+ padding: 4px;
}
.systemtags-select2-dropdown .systemtags-actions {
- float: right;
+ position: absolute;
+ right: 0;
+ top: 3px;
}
.systemtags-select2-dropdown .systemtags-rename-form {
@@ -44,7 +53,7 @@
margin-left: 10px;
}
-.systemtags-select2-container {
+.systemtags-select2-container {
width: 100%;
}
@@ -57,7 +66,7 @@
border-radius: 3px;
border: 1px solid #ddd;
margin: 3px 3px 3px 0;
- padding: 7px 6px 5px;
+ padding: 2px 0;
min-height: auto;
}
@@ -88,3 +97,22 @@
display: none;
}
+.select2-container-multi .select2-choices .select2-search-choice {
+ background-color: rgba(240,240,240,.9);
+ border-color: rgba(240,240,240,.9);
+ box-shadow: none;
+ background-image: none;
+}
+.select2-results .select2-highlighted {
+ background-color: rgba(240,240,240,.9);
+ color: #000;
+}
+
+.select2-container-multi.select2-container-active .select2-choices,
+.select2-drop-active {
+ border-color: #ddd;
+}
+.select2-container-multi.select2-container-active .select2-choices {
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
diff --git a/core/js/setupchecks.js b/core/js/setupchecks.js
index 1819b5a9c1e..c29c4137c58 100644
--- a/core/js/setupchecks.js
+++ b/core/js/setupchecks.js
@@ -276,7 +276,7 @@
var minimumSeconds = 15768000;
if(isNaN(transportSecurityValidity) || transportSecurityValidity <= (minimumSeconds - 1)) {
messages.push({
- msg: t('core', 'The "Strict-Transport-Security" HTTP header is not configured to least "{seconds}" seconds. For enhanced security we recommend enabling HSTS as described in our <a href="{docUrl}">security tips</a>.', {'seconds': minimumSeconds, docUrl: '#admin-tips'}),
+ msg: t('core', 'The "Strict-Transport-Security" HTTP header is not configured to at least "{seconds}" seconds. For enhanced security we recommend enabling HSTS as described in our <a href="{docUrl}">security tips</a>.', {'seconds': minimumSeconds, docUrl: '#admin-tips'}),
type: OC.SetupChecks.MESSAGE_TYPE_WARNING
});
}
diff --git a/core/js/tests/specs/setupchecksSpec.js b/core/js/tests/specs/setupchecksSpec.js
index 59df3a58746..fa0974c90f9 100644
--- a/core/js/tests/specs/setupchecksSpec.js
+++ b/core/js/tests/specs/setupchecksSpec.js
@@ -542,7 +542,7 @@ describe('OC.SetupChecks tests', function() {
async.done(function( data, s, x ){
expect(data).toEqual([{
- msg: 'The "Strict-Transport-Security" HTTP header is not configured to least "15768000" seconds. For enhanced security we recommend enabling HSTS as described in our <a href="#admin-tips">security tips</a>.',
+ msg: 'The "Strict-Transport-Security" HTTP header is not configured to at least "15768000" seconds. For enhanced security we recommend enabling HSTS as described in our <a href="#admin-tips">security tips</a>.',
type: OC.SetupChecks.MESSAGE_TYPE_WARNING
}]);
done();
@@ -567,7 +567,7 @@ describe('OC.SetupChecks tests', function() {
async.done(function( data, s, x ){
expect(data).toEqual([{
- msg: 'The "Strict-Transport-Security" HTTP header is not configured to least "15768000" seconds. For enhanced security we recommend enabling HSTS as described in our <a href="#admin-tips">security tips</a>.',
+ msg: 'The "Strict-Transport-Security" HTTP header is not configured to at least "15768000" seconds. For enhanced security we recommend enabling HSTS as described in our <a href="#admin-tips">security tips</a>.',
type: OC.SetupChecks.MESSAGE_TYPE_WARNING
}]);
done();
@@ -592,7 +592,7 @@ describe('OC.SetupChecks tests', function() {
async.done(function( data, s, x ){
expect(data).toEqual([{
- msg: 'The "Strict-Transport-Security" HTTP header is not configured to least "15768000" seconds. For enhanced security we recommend enabling HSTS as described in our <a href="#admin-tips">security tips</a>.',
+ msg: 'The "Strict-Transport-Security" HTTP header is not configured to at least "15768000" seconds. For enhanced security we recommend enabling HSTS as described in our <a href="#admin-tips">security tips</a>.',
type: OC.SetupChecks.MESSAGE_TYPE_WARNING
}]);
done();
diff --git a/issue_template.md b/issue_template.md
index 424f6c062a9..e19583977ef 100644
--- a/issue_template.md
+++ b/issue_template.md
@@ -1,3 +1,10 @@
+<!--
+Thanks for reporting issues back to ownCloud! This is the issue tracker of ownCloud, if you have any support question please check out https://owncloud.org/support
+
+This is the bug tracker for the Server component. Find other components at https://github.com/owncloud/core/blob/master/CONTRIBUTING.md#guidelines
+
+To make it possible for us to help you please fill out below information carefully.
+-->
### Steps to reproduce
1.
2.
diff --git a/lib/private/api.php b/lib/private/api.php
index 6c6be233c9d..87f2aa9b118 100644
--- a/lib/private/api.php
+++ b/lib/private/api.php
@@ -188,7 +188,7 @@ class OC_API {
/**
* merge the returned result objects into one response
* @param array $responses
- * @return array|\OC_OCS_Result
+ * @return OC_OCS_Result
*/
public static function mergeResponses($responses) {
// Sort into shipped and third-party
@@ -442,6 +442,7 @@ class OC_API {
/**
* Based on the requested format the response content type is set
+ * @param string $format
*/
public static function setContentType($format = null) {
$format = is_null($format) ? self::requestedFormat() : $format;
diff --git a/lib/private/avatarmanager.php b/lib/private/avatarmanager.php
index b39f5495122..b2d3e6eb3dd 100644
--- a/lib/private/avatarmanager.php
+++ b/lib/private/avatarmanager.php
@@ -26,6 +26,8 @@
namespace OC;
+use OCP\Files\Folder;
+use OCP\Files\NotFoundException;
use OCP\IAvatarManager;
use OCP\IUserManager;
use OCP\Files\IRootFolder;
@@ -45,6 +47,13 @@ class AvatarManager implements IAvatarManager {
/** @var IL10N */
private $l;
+ /**
+ * AvatarManager constructor.
+ *
+ * @param IUserManager $userManager
+ * @param IRootFolder $rootFolder
+ * @param IL10N $l
+ */
public function __construct(
IUserManager $userManager,
IRootFolder $rootFolder,
@@ -57,15 +66,26 @@ class AvatarManager implements IAvatarManager {
/**
* return a user specific instance of \OCP\IAvatar
* @see \OCP\IAvatar
- * @param string $user the ownCloud user id
+ * @param string $userId the ownCloud user id
* @return \OCP\IAvatar
* @throws \Exception In case the username is potentially dangerous
+ * @throws NotFoundException In case there is no user folder yet
*/
public function getAvatar($userId) {
$user = $this->userManager->get($userId);
if (is_null($user)) {
throw new \Exception('user does not exist');
}
- return new Avatar($this->rootFolder->getUserFolder($userId)->getParent(), $this->l, $user);
+
+ /*
+ * Fix for #22119
+ * Basically we do not want to copy the skeleton folder
+ */
+ \OC\Files\Filesystem::initMountPoints($userId);
+ $dir = '/' . $userId;
+ /** @var Folder $folder */
+ $folder = $this->rootFolder->get($dir);
+
+ return new Avatar($folder, $this->l, $user);
}
}
diff --git a/lib/private/console/application.php b/lib/private/console/application.php
index 10ff69b1c80..0895f1788af 100644
--- a/lib/private/console/application.php
+++ b/lib/private/console/application.php
@@ -31,6 +31,7 @@ use OCP\IRequest;
use Symfony\Component\Console\Application as SymfonyApplication;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -56,12 +57,31 @@ class Application {
}
/**
+ * @param InputInterface $input
* @param OutputInterface $output
* @throws \Exception
*/
- public function loadCommands(OutputInterface $output) {
+ public function loadCommands(InputInterface $input, OutputInterface $output) {
// $application is required to be defined in the register_command scripts
$application = $this->application;
+ $inputDefinition = $application->getDefinition();
+ $inputDefinition->addOption(
+ new InputOption(
+ 'no-warnings',
+ null,
+ InputOption::VALUE_NONE,
+ 'Skip global warnings, show command output only',
+ null
+ )
+ );
+ try {
+ $input->bind($inputDefinition);
+ } catch (\RuntimeException $e) {
+ //expected if there are extra options
+ }
+ if ($input->getOption('no-warnings')) {
+ $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
+ }
require_once __DIR__ . '/../../../core/register_command.php';
if ($this->config->getSystemValue('installed', false)) {
if (\OCP\Util::needUpgrade()) {
diff --git a/lib/private/db/adaptersqlsrv.php b/lib/private/db/adaptersqlsrv.php
deleted file mode 100644
index f208b2ba787..00000000000
--- a/lib/private/db/adaptersqlsrv.php
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-/**
- * @author Bart Visscher <bartv@thisnet.nl>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
- */
-
-
-namespace OC\DB;
-
-class AdapterSQLSrv extends Adapter {
- public function fixupStatement($statement) {
- $statement = str_replace(' ILIKE ', ' COLLATE Latin1_General_CI_AS LIKE ', $statement);
- $statement = preg_replace( "/\`(.*?)`/", "[$1]", $statement );
- $statement = str_ireplace( 'NOW()', 'CURRENT_TIMESTAMP', $statement );
- $statement = str_replace( 'LENGTH(', 'LEN(', $statement );
- $statement = str_replace( 'SUBSTR(', 'SUBSTRING(', $statement );
- $statement = str_ireplace( 'UNIX_TIMESTAMP()', 'DATEDIFF(second,{d \'1970-01-01\'},GETDATE())', $statement );
- return $statement;
- }
-}
diff --git a/lib/private/files/storage/common.php b/lib/private/files/storage/common.php
index edc570c967d..1d4801e5b97 100644
--- a/lib/private/files/storage/common.php
+++ b/lib/private/files/storage/common.php
@@ -613,6 +613,10 @@ abstract class Common implements Storage, ILockingStorage {
return $this->rename($sourceInternalPath, $targetInternalPath);
}
+ if (!$sourceStorage->isDeletable($sourceInternalPath)) {
+ return false;
+ }
+
$result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
if ($result) {
if ($sourceStorage->is_dir($sourceInternalPath)) {
diff --git a/lib/private/files/storage/wrapper/encryption.php b/lib/private/files/storage/wrapper/encryption.php
index 26905dfb388..11c6084d00c 100644
--- a/lib/private/files/storage/wrapper/encryption.php
+++ b/lib/private/files/storage/wrapper/encryption.php
@@ -459,6 +459,10 @@ class Encryption extends Wrapper {
// - copy the copyKeys() call from $this->copyBetweenStorage to this method
// - remove $this->copyBetweenStorage
+ if (!$sourceStorage->isDeletable($sourceInternalPath)) {
+ return false;
+ }
+
$result = $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, true);
if ($result) {
if ($sourceStorage->is_dir($sourceInternalPath)) {
diff --git a/lib/private/files/stream/encryption.php b/lib/private/files/stream/encryption.php
index 63949035b5a..37a1d75519d 100644
--- a/lib/private/files/stream/encryption.php
+++ b/lib/private/files/stream/encryption.php
@@ -183,7 +183,7 @@ class Encryption extends Wrapper {
*
* @param resource $source
* @param string $mode
- * @param array $context
+ * @param resource $context
* @param string $protocol
* @param string $class
* @return resource
diff --git a/lib/private/group/dummy.php b/lib/private/group/dummy.php
index c0d206a34e1..97f00385954 100644
--- a/lib/private/group/dummy.php
+++ b/lib/private/group/dummy.php
@@ -114,6 +114,7 @@ class OC_Group_Dummy extends OC_Group_Backend {
if(isset($this->groups[$gid])) {
if(($index=array_search($uid, $this->groups[$gid]))!==false) {
unset($this->groups[$gid][$index]);
+ return true;
}else{
return false;
}
diff --git a/lib/private/group/manager.php b/lib/private/group/manager.php
index 98e5551bcc5..7eca249c701 100644
--- a/lib/private/group/manager.php
+++ b/lib/private/group/manager.php
@@ -150,6 +150,10 @@ class Manager extends PublicEmitter implements IGroupManager {
return $this->getGroupObject($gid);
}
+ /**
+ * @param string $gid
+ * @return \OCP\IGroup
+ */
protected function getGroupObject($gid) {
$backends = array();
foreach ($this->backends as $backend) {
diff --git a/lib/private/memcache/factory.php b/lib/private/memcache/factory.php
index 21149d8b6bf..204ded7d5ab 100644
--- a/lib/private/memcache/factory.php
+++ b/lib/private/memcache/factory.php
@@ -172,7 +172,7 @@ class Factory implements ICacheFactory {
/**
* @see \OC\Memcache\Factory::createLocal()
* @param string $prefix
- * @return \OC\Memcache\Cache|null
+ * @return Cache
*/
public function createLowLatency($prefix = '') {
return $this->createLocal($prefix);
diff --git a/lib/private/preview.php b/lib/private/preview.php
index df6eeceddcb..4fca56dd984 100644
--- a/lib/private/preview.php
+++ b/lib/private/preview.php
@@ -202,7 +202,7 @@ class Preview {
/**
* returns the max width set in ownCloud's config
*
- * @return string
+ * @return integer
*/
public function getConfigMaxX() {
return $this->configMaxWidth;
@@ -211,7 +211,7 @@ class Preview {
/**
* returns the max height set in ownCloud's config
*
- * @return string
+ * @return integer
*/
public function getConfigMaxY() {
return $this->configMaxHeight;
@@ -546,7 +546,7 @@ class Preview {
/**
* Determines the size of the preview we should be looking for in the cache
*
- * @return int[]
+ * @return integer[]
*/
private function simulatePreviewDimensions() {
$askedWidth = $this->getMaxX();
@@ -570,7 +570,7 @@ class Preview {
*
* @param int $originalWidth
* @param int $originalHeight
- * @return \int[]
+ * @return integer[]
*/
private function applyAspectRatio($askedWidth, $askedHeight, $originalWidth = 0, $originalHeight = 0) {
if(!$originalWidth){
@@ -602,7 +602,7 @@ class Preview {
* @param int $askedHeight
* @param int $previewWidth
* @param int $previewHeight
- * @return \int[]
+ * @return integer[]
*/
private function applyCover($askedWidth, $askedHeight, $previewWidth, $previewHeight) {
$originalRatio = $previewWidth / $previewHeight;
@@ -628,7 +628,7 @@ class Preview {
* @param int $askedWidth
* @param int $askedHeight
*
- * @return \int[]
+ * @return integer[]
*/
private function fixSize($askedWidth, $askedHeight) {
if ($this->scalingUp) {
@@ -921,7 +921,7 @@ class Preview {
* @param int $askedWidth
* @param int $askedHeight
* @param int $previewWidth
- * @param null $previewHeight
+ * @param int $previewHeight
*
* @return int[]
*/
@@ -971,7 +971,7 @@ class Preview {
* @param int $askedWidth
* @param int $askedHeight
* @param int $previewWidth
- * @param null $previewHeight
+ * @param int $previewHeight
*/
private function crop($image, $askedWidth, $askedHeight, $previewWidth, $previewHeight = null) {
$cropX = floor(abs($askedWidth - $previewWidth) * 0.5);
@@ -990,7 +990,7 @@ class Preview {
* @param int $askedWidth
* @param int $askedHeight
* @param int $previewWidth
- * @param null $previewHeight
+ * @param int $previewHeight
*/
private function cropAndFill($image, $askedWidth, $askedHeight, $previewWidth, $previewHeight) {
if ($previewWidth > $askedWidth) {
@@ -1218,7 +1218,7 @@ class Preview {
* @param int $maxDim
* @param string $dimName
*
- * @return mixed
+ * @return integer
*/
private function limitMaxDim($dim, $maxDim, $dimName) {
if (!is_null($maxDim)) {
diff --git a/lib/private/preview/movie.php b/lib/private/preview/movie.php
index ee56f017229..43a8d674fc9 100644
--- a/lib/private/preview/movie.php
+++ b/lib/private/preview/movie.php
@@ -83,9 +83,9 @@ class Movie extends Provider {
$tmpPath = \OC::$server->getTempManager()->getTemporaryFile();
if (self::$avconvBinary) {
- $cmd = self::$avconvBinary . ' -an -y -ss ' . escapeshellarg($second) .
+ $cmd = self::$avconvBinary . ' -y -ss ' . escapeshellarg($second) .
' -i ' . escapeshellarg($absPath) .
- ' -f mjpeg -vframes 1 -vsync 1 ' . escapeshellarg($tmpPath) .
+ ' -an -f mjpeg -vframes 1 -vsync 1 ' . escapeshellarg($tmpPath) .
' > /dev/null 2>&1';
} else {
$cmd = self::$ffmpegBinary . ' -y -ss ' . escapeshellarg($second) .
diff --git a/lib/private/share20/manager.php b/lib/private/share20/manager.php
index 4cff3dc818b..9b33e947557 100644
--- a/lib/private/share20/manager.php
+++ b/lib/private/share20/manager.php
@@ -517,8 +517,20 @@ class Manager implements IManager {
// Verify if there are any issues with the path
$this->pathCreateChecks($share->getNode());
- // On creation of a share the owner is always the owner of the path
- $share->setShareOwner($share->getNode()->getOwner()->getUID());
+ /*
+ * On creation of a share the owner is always the owner of the path
+ * Except for mounted federated shares.
+ */
+ $storage = $share->getNode()->getStorage();
+ if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
+ $parent = $share->getNode()->getParent();
+ while($parent->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
+ $parent = $parent->getParent();
+ }
+ $share->setShareOwner($parent->getOwner()->getUID());
+ } else {
+ $share->setShareOwner($share->getNode()->getOwner()->getUID());
+ }
// Cannot share with the owner
if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER &&
diff --git a/lib/public/appframework/utility/icontrollermethodreflector.php b/lib/public/appframework/utility/icontrollermethodreflector.php
index b2f91fdb170..7bf422aa567 100644
--- a/lib/public/appframework/utility/icontrollermethodreflector.php
+++ b/lib/public/appframework/utility/icontrollermethodreflector.php
@@ -35,6 +35,7 @@ interface IControllerMethodReflector {
/**
* @param object $object an object or classname
* @param string $method the method which we want to inspect
+ * @return void
* @since 8.0.0
*/
public function reflect($object, $method);
diff --git a/lib/public/iavatarmanager.php b/lib/public/iavatarmanager.php
index 264c4fcf051..cb63ccaf6fd 100644
--- a/lib/public/iavatarmanager.php
+++ b/lib/public/iavatarmanager.php
@@ -36,6 +36,8 @@ interface IAvatarManager {
* @see \OCP\IAvatar
* @param string $user the ownCloud user id
* @return \OCP\IAvatar
+ * @throws \Exception In case the username is potentially dangerous
+ * @throws \OCP\Files\NotFoundException In case there is no user folder yet
* @since 6.0.0
*/
public function getAvatar($user);
diff --git a/lib/public/search/pagedprovider.php b/lib/public/search/pagedprovider.php
index 93289a1bde4..c8530626e59 100644
--- a/lib/public/search/pagedprovider.php
+++ b/lib/public/search/pagedprovider.php
@@ -58,7 +58,7 @@ abstract class PagedProvider extends Provider {
* Search for $query
* @param string $query
* @param int $page pages start at page 1
- * @param int $size, 0 = SIZE_ALL
+ * @param int $size 0 = SIZE_ALL
* @return array An array of OCP\Search\Result's
* @since 8.0.0
*/
diff --git a/settings/controller/userscontroller.php b/settings/controller/userscontroller.php
index 3e5455751ab..0abcabed11c 100644
--- a/settings/controller/userscontroller.php
+++ b/settings/controller/userscontroller.php
@@ -176,7 +176,11 @@ class UsersController extends Controller {
$avatarAvailable = false;
if ($this->config->getSystemValue('enable_avatars', true) === true) {
- $avatarAvailable = $this->avatarManager->getAvatar($user->getUID())->exists();
+ try {
+ $avatarAvailable = $this->avatarManager->getAvatar($user->getUID())->exists();
+ } catch (\Exception $e) {
+ //No avatar yet
+ }
}
return [
diff --git a/settings/templates/admin.php b/settings/templates/admin.php
index 539e4b94b8b..a51b9aa16e2 100644
--- a/settings/templates/admin.php
+++ b/settings/templates/admin.php
@@ -110,7 +110,7 @@ if ($_['WindowsWarning']) {
foreach ($_['OutdatedCacheWarning'] as $php_module => $data) {
?>
<li>
- <?php p($l->t('%1$s below version %2$s is installed, for stability and performance reasons we recommend to update to a newer %1$s version.', $data)); ?>
+ <?php p($l->t('%1$s below version %2$s is installed, for stability and performance reasons we recommend updating to a newer %1$s version.', $data)); ?>
</li>
<?php
}
diff --git a/tests/lib/avatarmanagertest.php b/tests/lib/avatarmanagertest.php
index cb9068c46a6..f5cdd99176d 100644
--- a/tests/lib/avatarmanagertest.php
+++ b/tests/lib/avatarmanagertest.php
@@ -19,30 +19,32 @@
*
*/
use OC\AvatarManager;
-use OCP\Files\IRootFolder;
-use OCP\IUserManager;
+use Test\Traits\UserTrait;
+use Test\Traits\MountProviderTrait;
+/**
+ * Class AvatarManagerTest
+ * @group DB
+ */
class AvatarManagerTest extends \Test\TestCase {
- /** @var IRootFolder */
- private $rootFolder;
+ use UserTrait;
+ use MountProviderTrait;
- /** @var AvatarManager */
+ /** @var AvatarManager */
private $avatarManager;
- /** @var IUserManager */
- private $userManager;
+ /** @var \OC\Files\Storage\Temporary */
+ private $storage;
public function setUp() {
parent::setUp();
- $this->rootFolder = $this->getMock('\OCP\Files\IRootFolder');
- $this->userManager = $this->getMock('\OCP\IUserManager');
- $l = $this->getMock('\OCP\IL10N');
- $l->method('t')->will($this->returnArgument(0));
- $this->avatarManager = new \OC\AvatarManager(
- $this->userManager,
- $this->rootFolder,
- $l);;
+ $this->createUser('valid-user', 'valid-user');
+
+ $this->storage = new \OC\Files\Storage\Temporary();
+ $this->registerMount('valid-user', $this->storage, '/valid-user/');
+
+ $this->avatarManager = \OC::$server->getAvatarManager();
}
/**
@@ -54,23 +56,10 @@ class AvatarManagerTest extends \Test\TestCase {
}
public function testGetAvatarValidUser() {
- $this->userManager->expects($this->once())
- ->method('get')
- ->with('validUser')
- ->willReturn(true);
-
- $folder = $this->getMock('\OCP\Files\Folder');
- $this->rootFolder->expects($this->once())
- ->method('getUserFolder')
- ->with('validUser')
- ->willReturn($folder);
-
- $folder->expects($this->once())
- ->method('getParent')
- ->will($this->returnSelf());
-
- $this->avatarManager->getAvatar('validUser');
+ $avatar = $this->avatarManager->getAvatar('valid-user');
+ $this->assertInstanceOf('\OCP\IAvatar', $avatar);
+ $this->assertFalse($this->storage->file_exists('files'));
}
}
diff --git a/tests/lib/share20/managertest.php b/tests/lib/share20/managertest.php
index fe94b72c4e6..c41f0754396 100644
--- a/tests/lib/share20/managertest.php
+++ b/tests/lib/share20/managertest.php
@@ -1429,9 +1429,11 @@ class ManagerTest extends \Test\TestCase {
$shareOwner = $this->getMock('\OCP\IUser');
$shareOwner->method('getUID')->willReturn('shareOwner');
+ $storage = $this->getMock('\OCP\Files\Storage');
$path = $this->getMock('\OCP\Files\File');
$path->method('getOwner')->willReturn($shareOwner);
$path->method('getName')->willReturn('target');
+ $path->method('getStorage')->willReturn($storage);
$share = $this->createShare(
null,
@@ -1480,9 +1482,11 @@ class ManagerTest extends \Test\TestCase {
$shareOwner = $this->getMock('\OCP\IUser');
$shareOwner->method('getUID')->willReturn('shareOwner');
+ $storage = $this->getMock('\OCP\Files\Storage');
$path = $this->getMock('\OCP\Files\File');
$path->method('getOwner')->willReturn($shareOwner);
$path->method('getName')->willReturn('target');
+ $path->method('getStorage')->willReturn($storage);
$share = $this->createShare(
null,
@@ -1539,10 +1543,12 @@ class ManagerTest extends \Test\TestCase {
$shareOwner = $this->getMock('\OCP\IUser');
$shareOwner->method('getUID')->willReturn('shareOwner');
+ $storage = $this->getMock('\OCP\Files\Storage');
$path = $this->getMock('\OCP\Files\File');
$path->method('getOwner')->willReturn($shareOwner);
$path->method('getName')->willReturn('target');
$path->method('getId')->willReturn(1);
+ $path->method('getStorage')->willReturn($storage);
$date = new \DateTime();
@@ -1663,9 +1669,11 @@ class ManagerTest extends \Test\TestCase {
$shareOwner = $this->getMock('\OCP\IUser');
$shareOwner->method('getUID')->willReturn('shareOwner');
+ $storage = $this->getMock('\OCP\Files\Storage');
$path = $this->getMock('\OCP\Files\File');
$path->method('getOwner')->willReturn($shareOwner);
$path->method('getName')->willReturn('target');
+ $path->method('getStorage')->willReturn($storage);
$share = $this->createShare(
null,
@@ -1697,8 +1705,86 @@ class ManagerTest extends \Test\TestCase {
->method('setTarget')
->with('/target');
- $dummy = new DummyCreate();
- \OCP\Util::connectHook('OCP\Share', 'pre_shared', $dummy, 'listner');
+ $hookListner = $this->getMockBuilder('Dummy')->setMethods(['pre'])->getMock();
+ \OCP\Util::connectHook('OCP\Share', 'pre_shared', $hookListner, 'pre');
+ $hookListner->expects($this->once())
+ ->method('pre')
+ ->will($this->returnCallback(function (array $data) {
+ $data['run'] = false;
+ $data['error'] = 'I won\'t let you share!';
+ }));
+
+ $manager->createShare($share);
+ }
+
+ public function testCreateShareOfIncommingFederatedShare() {
+ $manager = $this->createManagerMock()
+ ->setMethods(['canShare', 'generalCreateChecks', 'userCreateChecks', 'pathCreateChecks'])
+ ->getMock();
+
+ $shareOwner = $this->getMock('\OCP\IUser');
+ $shareOwner->method('getUID')->willReturn('shareOwner');
+
+ $storage = $this->getMock('\OCP\Files\Storage');
+ $storage->method('instanceOfStorage')
+ ->with('OCA\Files_Sharing\External\Storage')
+ ->willReturn(true);
+
+ $storage2 = $this->getMock('\OCP\Files\Storage');
+ $storage2->method('instanceOfStorage')
+ ->with('OCA\Files_Sharing\External\Storage')
+ ->willReturn(false);
+
+ $path = $this->getMock('\OCP\Files\File');
+ $path->expects($this->never())->method('getOwner');
+ $path->method('getName')->willReturn('target');
+ $path->method('getStorage')->willReturn($storage);
+
+ $parent = $this->getMock('\OCP\Files\Folder');
+ $parent->method('getStorage')->willReturn($storage);
+
+ $parentParent = $this->getMock('\OCP\Files\Folder');
+ $parentParent->method('getStorage')->willReturn($storage2);
+ $parentParent->method('getOwner')->willReturn($shareOwner);
+
+ $path->method('getParent')->willReturn($parent);
+ $parent->method('getParent')->willReturn($parentParent);
+
+ $share = $this->createShare(
+ null,
+ \OCP\Share::SHARE_TYPE_USER,
+ $path,
+ 'sharedWith',
+ 'sharedBy',
+ null,
+ \OCP\Constants::PERMISSION_ALL);
+
+ $manager->expects($this->once())
+ ->method('canShare')
+ ->with($share)
+ ->willReturn(true);
+ $manager->expects($this->once())
+ ->method('generalCreateChecks')
+ ->with($share);;
+ $manager->expects($this->once())
+ ->method('userCreateChecks')
+ ->with($share);;
+ $manager->expects($this->once())
+ ->method('pathCreateChecks')
+ ->with($path);
+
+ $this->defaultProvider
+ ->expects($this->once())
+ ->method('create')
+ ->with($share)
+ ->will($this->returnArgument(0));
+
+ $share->expects($this->once())
+ ->method('setShareOwner')
+ ->with('shareOwner');
+ $share->expects($this->once())
+ ->method('setTarget')
+ ->with('/target');
$manager->createShare($share);
}
@@ -2269,13 +2355,6 @@ class DummyPassword {
}
}
-class DummyCreate {
- public function listner($array) {
- $array['run'] = false;
- $array['error'] = 'I won\'t let you share!';
- }
-}
-
class DummyFactory implements IProviderFactory {
/** @var IShareProvider */
diff --git a/tests/settings/controller/userscontrollertest.php b/tests/settings/controller/userscontrollertest.php
index 947540fa67b..6f07f34ba8d 100644
--- a/tests/settings/controller/userscontrollertest.php
+++ b/tests/settings/controller/userscontrollertest.php
@@ -1696,6 +1696,32 @@ class UsersControllerTest extends \Test\TestCase {
$this->assertEquals($expectedResult, $result);
}
+ public function testNoAvatar() {
+ $this->container['IsAdmin'] = true;
+
+ list($user, $expectedResult) = $this->mockUser();
+
+ $subadmin = $this->getMockBuilder('\OC\SubAdmin')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $subadmin->expects($this->once())
+ ->method('getSubAdminsGroups')
+ ->with($user)
+ ->will($this->returnValue([]));
+ $this->container['GroupManager']
+ ->expects($this->any())
+ ->method('getSubAdmin')
+ ->will($this->returnValue($subadmin));
+
+ $this->container['OCP\\IAvatarManager']
+ ->method('getAvatar')
+ ->will($this->throwException(new \OCP\Files\NotFoundException()));
+ $expectedResult['isAvatarAvailable'] = false;
+
+ $result = self::invokePrivate($this->container['UsersController'], 'formatUserForIndex', [$user]);
+ $this->assertEquals($expectedResult, $result);
+ }
+
/**
* @return array
*/