Browse Source

Merge pull request #35121 from nextcloud/feat/dav-wrap-app-calendars

Feature: Provide access to app generated calendars through CalDAV
pull/37935/head
Christoph Wurst 3 years ago
committed by GitHub
parent
commit
5d29a96b1c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      apps/dav/composer/composer/autoload_classmap.php
  2. 3
      apps/dav/composer/composer/autoload_static.php
  3. 14
      apps/dav/lib/AppInfo/Application.php
  4. 5
      apps/dav/lib/AppInfo/PluginManager.php
  5. 208
      apps/dav/lib/CalDAV/AppCalendar/AppCalendar.php
  6. 74
      apps/dav/lib/CalDAV/AppCalendar/AppCalendarPlugin.php
  7. 153
      apps/dav/lib/CalDAV/AppCalendar/CalendarObject.php
  8. 1
      apps/dav/lib/CalDAV/CalendarProvider.php
  9. 25
      apps/dav/tests/unit/AppInfo/PluginManagerTest.php
  10. 123
      apps/dav/tests/unit/CalDAV/AppCalendar/AppCalendarTest.php
  11. 166
      apps/dav/tests/unit/CalDAV/AppCalendar/CalendarObjectTest.php
  12. 2
      apps/dav/tests/unit/Command/DeleteCalendarTest.php

3
apps/dav/composer/composer/autoload_classmap.php

@ -37,6 +37,9 @@ return array(
'OCA\\DAV\\CalDAV\\Activity\\Setting\\Calendar' => $baseDir . '/../lib/CalDAV/Activity/Setting/Calendar.php',
'OCA\\DAV\\CalDAV\\Activity\\Setting\\Event' => $baseDir . '/../lib/CalDAV/Activity/Setting/Event.php',
'OCA\\DAV\\CalDAV\\Activity\\Setting\\Todo' => $baseDir . '/../lib/CalDAV/Activity/Setting/Todo.php',
'OCA\\DAV\\CalDAV\\AppCalendar\\AppCalendar' => $baseDir . '/../lib/CalDAV/AppCalendar/AppCalendar.php',
'OCA\\DAV\\CalDAV\\AppCalendar\\AppCalendarPlugin' => $baseDir . '/../lib/CalDAV/AppCalendar/AppCalendarPlugin.php',
'OCA\\DAV\\CalDAV\\AppCalendar\\CalendarObject' => $baseDir . '/../lib/CalDAV/AppCalendar/CalendarObject.php',
'OCA\\DAV\\CalDAV\\Auth\\CustomPrincipalPlugin' => $baseDir . '/../lib/CalDAV/Auth/CustomPrincipalPlugin.php',
'OCA\\DAV\\CalDAV\\Auth\\PublicPrincipalPlugin' => $baseDir . '/../lib/CalDAV/Auth/PublicPrincipalPlugin.php',
'OCA\\DAV\\CalDAV\\BirthdayCalendar\\EnablePlugin' => $baseDir . '/../lib/CalDAV/BirthdayCalendar/EnablePlugin.php',

3
apps/dav/composer/composer/autoload_static.php

@ -52,6 +52,9 @@ class ComposerStaticInitDAV
'OCA\\DAV\\CalDAV\\Activity\\Setting\\Calendar' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Setting/Calendar.php',
'OCA\\DAV\\CalDAV\\Activity\\Setting\\Event' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Setting/Event.php',
'OCA\\DAV\\CalDAV\\Activity\\Setting\\Todo' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Setting/Todo.php',
'OCA\\DAV\\CalDAV\\AppCalendar\\AppCalendar' => __DIR__ . '/..' . '/../lib/CalDAV/AppCalendar/AppCalendar.php',
'OCA\\DAV\\CalDAV\\AppCalendar\\AppCalendarPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/AppCalendar/AppCalendarPlugin.php',
'OCA\\DAV\\CalDAV\\AppCalendar\\CalendarObject' => __DIR__ . '/..' . '/../lib/CalDAV/AppCalendar/CalendarObject.php',
'OCA\\DAV\\CalDAV\\Auth\\CustomPrincipalPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/Auth/CustomPrincipalPlugin.php',
'OCA\\DAV\\CalDAV\\Auth\\PublicPrincipalPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/Auth/PublicPrincipalPlugin.php',
'OCA\\DAV\\CalDAV\\BirthdayCalendar\\EnablePlugin' => __DIR__ . '/..' . '/../lib/CalDAV/BirthdayCalendar/EnablePlugin.php',

14
apps/dav/lib/AppInfo/Application.php

@ -35,6 +35,7 @@ namespace OCA\DAV\AppInfo;
use Exception;
use OCA\DAV\BackgroundJob\UpdateCalendarResourcesRoomsBackgroundJob;
use OCA\DAV\CalDAV\Activity\Backend;
use OCA\DAV\CalDAV\AppCalendar\AppCalendarPlugin;
use OCA\DAV\CalDAV\CalendarManager;
use OCA\DAV\CalDAV\CalendarProvider;
use OCA\DAV\CalDAV\Reminder\NotificationProvider\AudioProvider;
@ -44,7 +45,6 @@ use OCA\DAV\CalDAV\Reminder\NotificationProviderManager;
use OCA\DAV\CalDAV\Reminder\Notifier;
use OCA\DAV\Capabilities;
use OCA\DAV\CardDAV\CardDavBackend;
use OCA\DAV\CardDAV\ContactsManager;
use OCA\DAV\CardDAV\PhotoCache;
use OCA\DAV\CardDAV\SyncService;
@ -100,6 +100,7 @@ use OCP\Calendar\IManager as ICalendarManager;
use OCP\Config\BeforePreferenceDeletedEvent;
use OCP\Config\BeforePreferenceSetEvent;
use OCP\Contacts\IManager as IContactsManager;
use OCP\Files\AppData\IAppDataFactory;
use OCP\IServerContainer;
use OCP\IUser;
use Psr\Container\ContainerInterface;
@ -119,14 +120,17 @@ class Application extends App implements IBootstrap {
public function register(IRegistrationContext $context): void {
$context->registerServiceAlias('CardDAVSyncService', SyncService::class);
$context->registerService(PhotoCache::class, function (ContainerInterface $c) {
/** @var IServerContainer $server */
$server = $c->get(IServerContainer::class);
return new PhotoCache(
$server->getAppDataDir('dav-photocache'),
$c->get(IAppDataFactory::class)->get('dav-photocache'),
$c->get(LoggerInterface::class)
);
});
$context->registerService(AppCalendarPlugin::class, function(ContainerInterface $c) {
return new AppCalendarPlugin(
$c->get(ICalendarManager::class),
$c->get(LoggerInterface::class)
);
});
/*
* Register capabilities

5
apps/dav/lib/AppInfo/PluginManager.php

@ -29,6 +29,7 @@ declare(strict_types=1);
namespace OCA\DAV\AppInfo;
use OC\ServerContainer;
use OCA\DAV\CalDAV\AppCalendar\AppCalendarPlugin;
use OCA\DAV\CalDAV\Integration\ICalendarProvider;
use OCA\DAV\CardDAV\Integration\IAddressBookProvider;
use OCP\App\IAppManager;
@ -144,6 +145,8 @@ class PluginManager {
}
$this->populated = true;
$this->calendarPlugins[] = $this->container->get(AppCalendarPlugin::class);
foreach ($this->appManager->getInstalledApps() as $app) {
// load plugins and collections from info.xml
$info = $this->appManager->getAppInfo($app);
@ -253,7 +256,7 @@ class PluginManager {
private function createClass(string $className): object {
try {
return $this->container->query($className);
return $this->container->get($className);
} catch (QueryException $e) {
if (class_exists($className)) {
return new $className();

208
apps/dav/lib/CalDAV/AppCalendar/AppCalendar.php

@ -0,0 +1,208 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Ferdinand Thiessen <opensource@fthiessen.de>
*
* @author Ferdinand Thiessen <opensource@fthiessen.de>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\DAV\CalDAV\AppCalendar;
use OCA\DAV\CalDAV\Plugin;
use OCA\DAV\CalDAV\Integration\ExternalCalendar;
use OCP\Calendar\ICalendar;
use OCP\Calendar\ICreateFromString;
use OCP\Constants;
use Sabre\CalDAV\CalendarQueryValidator;
use Sabre\CalDAV\ICalendarObject;
use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\PropPatch;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Reader;
class AppCalendar extends ExternalCalendar {
protected string $principal;
protected ICalendar $calendar;
public function __construct(string $appId, ICalendar $calendar, string $principal) {
parent::__construct($appId, $calendar->getUri());
$this->principal = $principal;
$this->calendar = $calendar;
}
/**
* Return permissions supported by the backend calendar
* @return int Permissions based on \OCP\Constants
*/
public function getPermissions(): int {
// Make sure to only promote write support if the backend implement the correct interface
if ($this->calendar instanceof ICreateFromString) {
return $this->calendar->getPermissions();
}
return Constants::PERMISSION_READ;
}
public function getOwner(): ?string {
return $this->principal;
}
public function getGroup(): ?string {
return null;
}
public function getACL(): array {
$acl = [
[
'privilege' => '{DAV:}read',
'principal' => $this->getOwner(),
'protected' => true,
],
[
'privilege' => '{DAV:}write-properties',
'principal' => $this->getOwner(),
'protected' => true,
]
];
if ($this->getPermissions() & Constants::PERMISSION_CREATE) {
$acl[] = [
'privilege' => '{DAV:}write',
'principal' => $this->getOwner(),
'protected' => true,
];
}
return $acl;
}
public function setACL(array $acl): void {
throw new Forbidden('Setting ACL is not supported on this node');
}
public function getSupportedPrivilegeSet(): ?array {
// Use the default one
return null;
}
public function getLastModified(): ?int {
// unknown
return null;
}
public function delete(): void {
// No method for deleting a calendar in OCP\Calendar\ICalendar
throw new Forbidden('Deleting an entry is not implemented');
}
public function createFile($name, $data = null) {
if ($this->calendar instanceof ICreateFromString) {
if (is_resource($data)) {
$data = stream_get_contents($data) ?: null;
}
$this->calendar->createFromString($name, is_null($data) ? '' : $data);
return null;
} else {
throw new Forbidden('Creating a new entry is not allowed');
}
}
public function getProperties($properties) {
return [
'{DAV:}displayname' => $this->calendar->getDisplayName() ?: $this->calendar->getKey(),
'{http://apple.com/ns/ical/}calendar-color' => $this->calendar->getDisplayColor() ?: '#0082c9',
'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VEVENT', 'VJOURNAL', 'VTODO']),
];
}
public function calendarQuery(array $filters) {
$result = [];
$objects = $this->getChildren();
foreach ($objects as $object) {
if ($this->validateFilterForObject($object, $filters)) {
$result[] = $object->getName();
}
}
return $result;
}
protected function validateFilterForObject(ICalendarObject $object, array $filters): bool {
/** @var \Sabre\VObject\Component\VCalendar */
$vObject = Reader::read($object->get());
$validator = new CalendarQueryValidator();
$result = $validator->validate($vObject, $filters);
// Destroy circular references so PHP will GC the object.
$vObject->destroy();
return $result;
}
public function childExists($name): bool {
try {
$this->getChild($name);
return true;
} catch (NotFound $error) {
return false;
}
}
public function getChild($name) {
// Try to get calendar by filename
$children = $this->calendar->search($name, ['X-FILENAME']);
if (count($children) === 0) {
// If nothing found try to get by UID from filename
$pos = strrpos($name, '.ics');
$children = $this->calendar->search(substr($name, 0, $pos ?: null), ['UID']);
}
if (count($children) > 0) {
return new CalendarObject($this, $this->calendar, new VCalendar($children));
}
throw new NotFound('Node not found');
}
/**
* @return ICalendarObject[]
*/
public function getChildren(): array {
$objects = $this->calendar->search('');
// We need to group by UID (actually by filename but we do not have that information)
$result = [];
foreach ($objects as $object) {
$uid = (string)$object['UID'] ?: uniqid();
if (!isset($result[$uid])) {
$result[$uid] = [];
}
$result[$uid][] = $object;
}
return array_map(function (array $children) {
return new CalendarObject($this, $this->calendar, new VCalendar($children));
}, $result);
}
public function propPatch(PropPatch $propPatch): void {
// no setDisplayColor or setDisplayName in \OCP\Calendar\ICalendar
}
}

74
apps/dav/lib/CalDAV/AppCalendar/AppCalendarPlugin.php

@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Ferdinand Thiessen <opensource@fthiessen.de>
*
* @author Ferdinand Thiessen <opensource@fthiessen.de>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\DAV\CalDAV\AppCalendar;
use OCA\DAV\CalDAV\Integration\ExternalCalendar;
use OCA\DAV\CalDAV\Integration\ICalendarProvider;
use OCP\Calendar\IManager;
use Psr\Log\LoggerInterface;
/* Plugin for wrapping application generated calendars registered in nextcloud core (OCP\Calendar\ICalendarProvider) */
class AppCalendarPlugin implements ICalendarProvider {
protected IManager $manager;
protected LoggerInterface $logger;
public function __construct(IManager $manager, LoggerInterface $logger) {
$this->manager = $manager;
$this->logger = $logger;
}
public function getAppID(): string {
return 'dav-wrapper';
}
public function fetchAllForCalendarHome(string $principalUri): array {
return array_map(function ($calendar) use (&$principalUri) {
return new AppCalendar($this->getAppID(), $calendar, $principalUri);
}, $this->getWrappedCalendars($principalUri));
}
public function hasCalendarInCalendarHome(string $principalUri, string $calendarUri): bool {
return count($this->getWrappedCalendars($principalUri, [ $calendarUri ])) > 0;
}
public function getCalendarInCalendarHome(string $principalUri, string $calendarUri): ?ExternalCalendar {
$calendars = $this->getWrappedCalendars($principalUri, [ $calendarUri ]);
if (count($calendars) > 0) {
return new AppCalendar($this->getAppID(), $calendars[0], $principalUri);
}
return null;
}
protected function getWrappedCalendars(string $principalUri, array $calendarUris = []): array {
return array_values(
array_filter($this->manager->getCalendarsForPrincipal($principalUri, $calendarUris), function ($c) {
// We must not provide a wrapper for DAV calendars
return ! ($c instanceof \OCA\DAV\CalDAV\CalendarImpl);
})
);
}
}

153
apps/dav/lib/CalDAV/AppCalendar/CalendarObject.php

@ -0,0 +1,153 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Ferdinand Thiessen <opensource@fthiessen.de>
*
* @author Ferdinand Thiessen <opensource@fthiessen.de>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\DAV\CalDAV\AppCalendar;
use OCP\Calendar\ICalendar;
use OCP\Calendar\ICreateFromString;
use OCP\Constants;
use Sabre\CalDAV\ICalendarObject;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAVACL\IACL;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Property\ICalendar\DateTime;
class CalendarObject implements ICalendarObject, IACL {
private VCalendar $vobject;
private AppCalendar $calendar;
private ICalendar|ICreateFromString $backend;
public function __construct(AppCalendar $calendar, ICalendar $backend, VCalendar $vobject) {
$this->backend = $backend;
$this->calendar = $calendar;
$this->vobject = $vobject;
}
public function getOwner() {
return $this->calendar->getOwner();
}
public function getGroup() {
return $this->calendar->getGroup();
}
public function getACL(): array {
$acl = [
[
'privilege' => '{DAV:}read',
'principal' => $this->getOwner(),
'protected' => true,
]
];
if ($this->calendar->getPermissions() & Constants::PERMISSION_UPDATE) {
$acl[] = [
'privilege' => '{DAV:}write-content',
'principal' => $this->getOwner(),
'protected' => true,
];
}
return $acl;
}
public function setACL(array $acl): void {
throw new Forbidden('Setting ACL is not supported on this node');
}
public function getSupportedPrivilegeSet(): ?array {
return null;
}
public function put($data): void {
if ($this->backend instanceof ICreateFromString && $this->calendar->getPermissions() & Constants::PERMISSION_UPDATE) {
if (is_resource($data)) {
$data = stream_get_contents($data) ?: '';
}
$this->backend->createFromString($this->getName(), $data);
} else {
throw new Forbidden('This calendar-object is read-only');
}
}
public function get(): string {
return $this->vobject->serialize();
}
public function getContentType(): string {
return 'text/calendar; charset=utf-8';
}
public function getETag(): ?string {
return null;
}
public function getSize() {
return mb_strlen($this->vobject->serialize());
}
public function delete(): void {
if ($this->backend instanceof ICreateFromString && $this->calendar->getPermissions() & Constants::PERMISSION_DELETE) {
/** @var \Sabre\VObject\Component[] */
$components = $this->vobject->getBaseComponents();
foreach ($components as $key => $component) {
$components[$key]->STATUS = 'CANCELLED';
$components[$key]->SEQUENCE = isset($component->SEQUENCE) ? ((int)$component->SEQUENCE->getValue()) + 1 : 1;
if ($component->name === 'VEVENT') {
$components[$key]->METHOD = 'CANCEL';
}
}
$this->backend->createFromString($this->getName(), (new VCalendar($components))->serialize());
} else {
throw new Forbidden('This calendar-object is read-only');
}
}
public function getName(): string {
// Every object is required to have an UID
$base = $this->vobject->getBaseComponent();
// This should never happen except the app provides invalid calendars (VEvent, VTodo... all require UID to be present)
if ($base === null) {
throw new NotFound('Invalid node');
}
if (isset($base->{'X-FILENAME'})) {
return (string)$base->{'X-FILENAME'};
}
return (string)$base->UID . '.ics';
}
public function setName($name): void {
throw new Forbidden('This calendar-object is read-only');
}
public function getLastModified(): ?int {
$base = $this->vobject->getBaseComponent();
if ($base !== null && $this->vobject->getBaseComponent()->{'LAST-MODIFIED'}) {
/** @var DateTime */
$lastModified = $this->vobject->getBaseComponent()->{'LAST-MODIFIED'};
return $lastModified->getDateTime()->getTimestamp();
}
return null;
}
}

1
apps/dav/lib/CalDAV/CalendarProvider.php

@ -26,7 +26,6 @@ declare(strict_types=1);
namespace OCA\DAV\CalDAV;
use OCP\Calendar\ICalendarProvider;
use OCP\Calendar\ICreateFromString;
use OCP\IConfig;
use OCP\IL10N;
use Psr\Log\LoggerInterface;

25
apps/dav/tests/unit/AppInfo/PluginManagerTest.php

@ -29,6 +29,7 @@ namespace OCA\DAV\Tests\unit\AppInfo;
use OC\App\AppManager;
use OC\ServerContainer;
use OCA\DAV\AppInfo\PluginManager;
use OCA\DAV\CalDAV\AppCalendar\AppCalendarPlugin;
use OCA\DAV\CalDAV\Integration\ICalendarProvider;
use Sabre\DAV\Collection;
use Sabre\DAV\ServerPlugin;
@ -43,7 +44,6 @@ class PluginManagerTest extends TestCase {
public function test(): void {
$server = $this->createMock(ServerContainer::class);
$appManager = $this->createMock(AppManager::class);
$appManager->method('getInstalledApps')
->willReturn(['adavapp', 'adavapp2']);
@ -94,6 +94,7 @@ class PluginManagerTest extends TestCase {
$pluginManager = new PluginManager($server, $appManager);
$appCalendarPlugin = $this->createMock(AppCalendarPlugin::class);
$calendarPlugin1 = $this->createMock(ICalendarProvider::class);
$calendarPlugin2 = $this->createMock(ICalendarProvider::class);
$calendarPlugin3 = $this->createMock(ICalendarProvider::class);
@ -106,17 +107,18 @@ class PluginManagerTest extends TestCase {
$dummyCollection2 = $this->createMock(Collection::class);
$dummy2Collection1 = $this->createMock(Collection::class);
$server->method('query')
$server->method('get')
->willReturnMap([
['\OCA\DAV\ADavApp\PluginOne', true, $dummyPlugin1],
['\OCA\DAV\ADavApp\PluginTwo', true, $dummyPlugin2],
['\OCA\DAV\ADavApp\CalendarPluginOne', true, $calendarPlugin1],
['\OCA\DAV\ADavApp\CalendarPluginTwo', true, $calendarPlugin2],
['\OCA\DAV\ADavApp\CollectionOne', true, $dummyCollection1],
['\OCA\DAV\ADavApp\CollectionTwo', true, $dummyCollection2],
['\OCA\DAV\ADavApp2\PluginOne', true, $dummy2Plugin1],
['\OCA\DAV\ADavApp2\CalendarPluginOne', true, $calendarPlugin3],
['\OCA\DAV\ADavApp2\CollectionOne', true, $dummy2Collection1],
[AppCalendarPlugin::class, $appCalendarPlugin],
['\OCA\DAV\ADavApp\PluginOne', $dummyPlugin1],
['\OCA\DAV\ADavApp\PluginTwo', $dummyPlugin2],
['\OCA\DAV\ADavApp\CalendarPluginOne', $calendarPlugin1],
['\OCA\DAV\ADavApp\CalendarPluginTwo', $calendarPlugin2],
['\OCA\DAV\ADavApp\CollectionOne', $dummyCollection1],
['\OCA\DAV\ADavApp\CollectionTwo', $dummyCollection2],
['\OCA\DAV\ADavApp2\PluginOne', $dummy2Plugin1],
['\OCA\DAV\ADavApp2\CalendarPluginOne', $calendarPlugin3],
['\OCA\DAV\ADavApp2\CollectionOne', $dummy2Collection1],
]);
$expectedPlugins = [
@ -125,6 +127,7 @@ class PluginManagerTest extends TestCase {
$dummy2Plugin1,
];
$expectedCalendarPlugins = [
$appCalendarPlugin,
$calendarPlugin1,
$calendarPlugin2,
$calendarPlugin3,

123
apps/dav/tests/unit/CalDAV/AppCalendar/AppCalendarTest.php

@ -0,0 +1,123 @@
<?php
/**
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\DAV\Tests\unit\CalDAV\AppCalendar;
use OCA\DAV\CalDAV\AppCalendar\AppCalendar;
use OCP\Calendar\ICalendar;
use OCP\Calendar\ICreateFromString;
use OCP\Constants;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
use function Safe\rewind;
class AppCalendarTest extends TestCase {
private $principal = 'principals/users/foo';
private AppCalendar $appCalendar;
private AppCalendar $writeableAppCalendar;
private ICalendar|MockObject $calendar;
private ICalendar|MockObject $writeableCalendar;
protected function setUp(): void {
parent::setUp();
$this->calendar = $this->getMockBuilder(ICalendar::class)->getMock();
$this->calendar->method('getPermissions')
->willReturn(Constants::PERMISSION_READ);
$this->writeableCalendar = $this->getMockBuilder(ICreateFromString::class)->getMock();
$this->writeableCalendar->method('getPermissions')
->willReturn(Constants::PERMISSION_READ | Constants::PERMISSION_CREATE);
$this->appCalendar = new AppCalendar('dav-wrapper', $this->calendar, $this->principal);
$this->writeableAppCalendar = new AppCalendar('dav-wrapper', $this->writeableCalendar, $this->principal);
}
public function testGetPrincipal():void {
// Check that the correct name is returned
$this->assertEquals($this->principal, $this->appCalendar->getOwner());
$this->assertEquals($this->principal, $this->writeableAppCalendar->getOwner());
}
public function testDelete(): void {
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
$this->expectExceptionMessage('Deleting an entry is not implemented');
$this->appCalendar->delete();
}
public function testCreateFile() {
$this->writeableCalendar->expects($this->exactly(3))
->method('createFromString')
->withConsecutive(['some-name', 'data'], ['other-name', ''], ['name', 'some data']);
// pass data
$this->assertNull($this->writeableAppCalendar->createFile('some-name', 'data'));
// null is empty string
$this->assertNull($this->writeableAppCalendar->createFile('other-name', null));
// resource to data
$fp = fopen('php://memory', 'r+');
fwrite($fp, 'some data');
rewind($fp);
$this->assertNull($this->writeableAppCalendar->createFile('name', $fp));
fclose($fp);
}
public function testCreateFile_readOnly() {
// If writing is not supported
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
$this->expectExceptionMessage('Creating a new entry is not allowed');
$this->appCalendar->createFile('some-name', 'data');
}
public function testSetACL(): void {
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
$this->expectExceptionMessage('Setting ACL is not supported on this node');
$this->appCalendar->setACL([]);
}
public function testGetACL():void {
$expectedRO = [
[
'privilege' => '{DAV:}read',
'principal' => $this->principal,
'protected' => true,
],
[
'privilege' => '{DAV:}write-properties',
'principal' => $this->principal,
'protected' => true,
]
];
$expectedRW = $expectedRO;
$expectedRW[] = [
'privilege' => '{DAV:}write',
'principal' => $this->principal,
'protected' => true,
];
// Check that the correct ACL is returned (default be only readable)
$this->assertEquals($expectedRO, $this->appCalendar->getACL());
$this->assertEquals($expectedRW, $this->writeableAppCalendar->getACL());
}
}

166
apps/dav/tests/unit/CalDAV/AppCalendar/CalendarObjectTest.php

@ -0,0 +1,166 @@
<?php
namespace OCA\DAV\Tests\unit\CalDAV\AppCalendar;
use OCA\DAV\CalDAV\AppCalendar\AppCalendar;
use OCA\DAV\CalDAV\AppCalendar\CalendarObject;
use OCP\Calendar\ICalendar;
use OCP\Calendar\ICreateFromString;
use OCP\Constants;
use PHPUnit\Framework\MockObject\MockObject;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Component\VEvent;
use Test\TestCase;
class CalendarObjectTest extends TestCase {
private CalendarObject $calendarObject;
private AppCalendar|MockObject $calendar;
private ICalendar|MockObject $backend;
private VCalendar|MockObject $vobject;
protected function setUp(): void {
parent::setUp();
$this->calendar = $this->createMock(AppCalendar::class);
$this->calendar->method('getOwner')->willReturn('owner');
$this->calendar->method('getGroup')->willReturn('group');
$this->backend = $this->createMock(ICalendar::class);
$this->vobject = $this->createMock(VCalendar::class);
$this->calendarObject = new CalendarObject($this->calendar, $this->backend, $this->vobject);
}
public function testGetOwner() {
$this->assertEquals($this->calendarObject->getOwner(), 'owner');
}
public function testGetGroup() {
$this->assertEquals($this->calendarObject->getGroup(), 'group');
}
public function testGetACL() {
$this->calendar->expects($this->exactly(2))
->method('getPermissions')
->willReturnOnConsecutiveCalls(Constants::PERMISSION_READ, Constants::PERMISSION_ALL);
// read only
$this->assertEquals($this->calendarObject->getACL(), [
[
'privilege' => '{DAV:}read',
'principal' => 'owner',
'protected' => true,
]
]);
// write permissions
$this->assertEquals($this->calendarObject->getACL(), [
[
'privilege' => '{DAV:}read',
'principal' => 'owner',
'protected' => true,
],
[
'privilege' => '{DAV:}write-content',
'principal' => 'owner',
'protected' => true,
]
]);
}
public function testSetACL() {
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
$this->calendarObject->setACL([]);
}
public function testPut_readOnlyBackend() {
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
$this->calendarObject->put('foo');
}
public function testPut_noPermissions() {
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
$backend = $this->createMock(ICreateFromString::class);
$calendarObject = new CalendarObject($this->calendar, $backend, $this->vobject);
$this->calendar->expects($this->once())
->method('getPermissions')
->willReturn(Constants::PERMISSION_READ);
$calendarObject->put('foo');
}
public function testPut() {
$backend = $this->createMock(ICreateFromString::class);
$calendarObject = new CalendarObject($this->calendar, $backend, $this->vobject);
$this->vobject->expects($this->once())
->method('getBaseComponent')
->willReturn((object)['UID' => 'someid']);
$this->calendar->expects($this->once())
->method('getPermissions')
->willReturn(Constants::PERMISSION_ALL);
$backend->expects($this->once())
->method('createFromString')
->with('someid.ics', 'foo');
$calendarObject->put('foo');
}
public function testGet() {
$this->vobject->expects($this->once())
->method('serialize')
->willReturn('foo');
$this->assertEquals($this->calendarObject->get(), 'foo');
}
public function testDelete_notWriteable() {
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
$this->calendarObject->delete();
}
public function testDelete_noPermission() {
$backend = $this->createMock(ICreateFromString::class);
$calendarObject = new CalendarObject($this->calendar, $backend, $this->vobject);
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
$calendarObject->delete();
}
public function testDelete() {
$backend = $this->createMock(ICreateFromString::class);
$calendarObject = new CalendarObject($this->calendar, $backend, $this->vobject);
$components = [(new VCalendar(['VEVENT' => ['UID' => 'someid']]))->getBaseComponent()];
$this->calendar->expects($this->once())
->method('getPermissions')
->willReturn(Constants::PERMISSION_DELETE);
$this->vobject->expects($this->once())
->method('getBaseComponents')
->willReturn($components);
$this->vobject->expects($this->once())
->method('getBaseComponent')
->willReturn($components[0]);
$backend->expects($this->once())
->method('createFromString')
->with('someid.ics', self::callback(fn($data): bool => preg_match('/BEGIN:VEVENT(.|\r\n)+STATUS:CANCELLED/', $data) === 1));
$calendarObject->delete();
}
public function testGetName() {
$this->vobject->expects($this->exactly(2))
->method('getBaseComponent')
->willReturnOnConsecutiveCalls((object)['UID' => 'someid'], (object)['UID' => 'someid', 'X-FILENAME' => 'real-filename.ics']);
$this->assertEquals($this->calendarObject->getName(), 'someid.ics');
$this->assertEquals($this->calendarObject->getName(), 'real-filename.ics');
}
public function testSetName() {
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
$this->calendarObject->setName('Some name');
}
}

2
apps/dav/tests/unit/Command/DeleteCalendarTest.php

@ -25,7 +25,7 @@ declare(strict_types=1);
namespace OCA\DAV\Tests\Command;
use OCA\DAV\CalDAV\BirthdayService;
use OCA\DAV\CalDav\CalDavBackend;
use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\Command\DeleteCalendar;
use OCP\IConfig;
use OCP\IL10N;

Loading…
Cancel
Save