diff options
author | Roeland Jago Douma <rullzer@users.noreply.github.com> | 2020-02-23 19:00:03 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-02-23 19:00:03 +0300 |
commit | 713d3d49ada79f270385826ab62ef07e89de6251 (patch) | |
tree | 43d457535da763d4e02e3e3abe320eee29a5addd /apps | |
parent | f885cef5370f28571649f4d774541b9fd6f2abc5 (diff) | |
parent | b46e5cb270b1730a6e6d4a98e52ca672eafebd39 (diff) |
Merge pull request #19196 from nextcloud/feature/17126/allow_apps_to_register_their_own_calendars
Allow apps to provide Calendars in user's calendarHome
Diffstat (limited to 'apps')
-rw-r--r-- | apps/dav/composer/composer/autoload_classmap.php | 2 | ||||
-rw-r--r-- | apps/dav/composer/composer/autoload_static.php | 2 | ||||
-rw-r--r-- | apps/dav/lib/AppInfo/PluginManager.php | 57 | ||||
-rw-r--r-- | apps/dav/lib/CalDAV/CalendarHome.php | 36 | ||||
-rw-r--r-- | apps/dav/lib/CalDAV/Integration/ExternalCalendar.php | 132 | ||||
-rw-r--r-- | apps/dav/lib/CalDAV/Integration/ICalendarProvider.php | 73 | ||||
-rw-r--r-- | apps/dav/tests/unit/AppInfo/PluginManagerTest.php | 23 | ||||
-rw-r--r-- | apps/dav/tests/unit/CalDAV/CalendarHomeTest.php | 149 | ||||
-rw-r--r-- | apps/dav/tests/unit/CalDAV/Integration/ExternalCalendarTest.php | 119 |
9 files changed, 590 insertions, 3 deletions
diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index 7db6f3c1fef..8a26b80916e 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -42,6 +42,8 @@ return array( 'OCA\\DAV\\CalDAV\\CalendarObject' => $baseDir . '/../lib/CalDAV/CalendarObject.php', 'OCA\\DAV\\CalDAV\\CalendarRoot' => $baseDir . '/../lib/CalDAV/CalendarRoot.php', 'OCA\\DAV\\CalDAV\\ICSExportPlugin\\ICSExportPlugin' => $baseDir . '/../lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php', + 'OCA\\DAV\\CalDAV\\Integration\\ExternalCalendar' => $baseDir . '/../lib/CalDAV/Integration/ExternalCalendar.php', + 'OCA\\DAV\\CalDAV\\Integration\\ICalendarProvider' => $baseDir . '/../lib/CalDAV/Integration/ICalendarProvider.php', 'OCA\\DAV\\CalDAV\\InvitationResponse\\InvitationResponseServer' => $baseDir . '/../lib/CalDAV/InvitationResponse/InvitationResponseServer.php', 'OCA\\DAV\\CalDAV\\Outbox' => $baseDir . '/../lib/CalDAV/Outbox.php', 'OCA\\DAV\\CalDAV\\Plugin' => $baseDir . '/../lib/CalDAV/Plugin.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index 49f0c4ab82e..001297ffd28 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -57,6 +57,8 @@ class ComposerStaticInitDAV 'OCA\\DAV\\CalDAV\\CalendarObject' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarObject.php', 'OCA\\DAV\\CalDAV\\CalendarRoot' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarRoot.php', 'OCA\\DAV\\CalDAV\\ICSExportPlugin\\ICSExportPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php', + 'OCA\\DAV\\CalDAV\\Integration\\ExternalCalendar' => __DIR__ . '/..' . '/../lib/CalDAV/Integration/ExternalCalendar.php', + 'OCA\\DAV\\CalDAV\\Integration\\ICalendarProvider' => __DIR__ . '/..' . '/../lib/CalDAV/Integration/ICalendarProvider.php', 'OCA\\DAV\\CalDAV\\InvitationResponse\\InvitationResponseServer' => __DIR__ . '/..' . '/../lib/CalDAV/InvitationResponse/InvitationResponseServer.php', 'OCA\\DAV\\CalDAV\\Outbox' => __DIR__ . '/..' . '/../lib/CalDAV/Outbox.php', 'OCA\\DAV\\CalDAV\\Plugin' => __DIR__ . '/..' . '/../lib/CalDAV/Plugin.php', diff --git a/apps/dav/lib/AppInfo/PluginManager.php b/apps/dav/lib/AppInfo/PluginManager.php index 7063a150b4d..6a44332ddb2 100644 --- a/apps/dav/lib/AppInfo/PluginManager.php +++ b/apps/dav/lib/AppInfo/PluginManager.php @@ -25,6 +25,7 @@ namespace OCA\DAV\AppInfo; use OC\ServerContainer; +use OCA\DAV\CalDAV\Integration\ICalendarProvider; use OCP\App\IAppManager; use OCP\AppFramework\QueryException; @@ -59,6 +60,13 @@ class PluginManager { private $collections = null; /** + * Calendar plugins + * + * @var array + */ + private $calendarPlugins = null; + + /** * Contstruct a PluginManager * * @param ServerContainer $container server container for resolving plugin classes @@ -94,10 +102,23 @@ class PluginManager { } /** + * Returns an array of app-registered calendar plugins + * + * @return array + */ + public function getCalendarPlugins():array { + if (null === $this->calendarPlugins) { + $this->populate(); + } + return $this->calendarPlugins; + } + + /** * Retrieve plugin and collection list and populate attributes */ private function populate() { $this->plugins = []; + $this->calendarPlugins = []; $this->collections = []; foreach ($this->appManager->getInstalledApps() as $app) { // load plugins and collections from info.xml @@ -107,6 +128,7 @@ class PluginManager { } $this->loadSabrePluginsFromInfoXml($this->extractPluginList($info)); $this->loadSabreCollectionsFromInfoXml($this->extractCollectionList($info)); + $this->loadSabreCalendarPluginsFromInfoXml($this->extractCalendarPluginList($info)); } } @@ -140,6 +162,21 @@ class PluginManager { return []; } + private function extractCalendarPluginList(array $array):array { + if (isset($array['sabre']) && is_array($array['sabre'])) { + if (isset($array['sabre']['calendar-plugins']) && is_array($array['sabre']['calendar-plugins'])) { + if (isset($array['sabre']['calendar-plugins']['plugin'])) { + $items = $array['sabre']['calendar-plugins']['plugin']; + if (!is_array($items)) { + $items = [$items]; + } + return $items; + } + } + } + return []; + } + private function loadSabrePluginsFromInfoXml(array $plugins) { foreach ($plugins as $plugin) { try { @@ -168,4 +205,24 @@ class PluginManager { } } + private function loadSabreCalendarPluginsFromInfoXml(array $calendarPlugins):void { + foreach ($calendarPlugins as $calendarPlugin) { + try { + $instantiatedCalendarPlugin = $this->container->query($calendarPlugin); + } catch (QueryException $e) { + if (class_exists($calendarPlugin)) { + $instantiatedCalendarPlugin = new $calendarPlugin(); + } else { + throw new \Exception("Sabre calendar-plugin class '$calendarPlugin' is unknown and could not be loaded"); + } + } + + if (!($instantiatedCalendarPlugin instanceof ICalendarProvider)) { + throw new \Exception("Sabre calendar-plugin class '$calendarPlugin' does not implement ICalendarProvider interface"); + } + + $this->calendarPlugins[] = $instantiatedCalendarPlugin; + } + } + } diff --git a/apps/dav/lib/CalDAV/CalendarHome.php b/apps/dav/lib/CalDAV/CalendarHome.php index b491c2304d2..ce29ed12db6 100644 --- a/apps/dav/lib/CalDAV/CalendarHome.php +++ b/apps/dav/lib/CalDAV/CalendarHome.php @@ -26,6 +26,9 @@ namespace OCA\DAV\CalDAV; +use OCA\DAV\AppInfo\PluginManager; +use OCA\DAV\CalDAV\Integration\ExternalCalendar; +use OCA\DAV\CalDAV\Integration\ICalendarProvider; use Sabre\CalDAV\Backend\BackendInterface; use Sabre\CalDAV\Backend\NotificationSupport; use Sabre\CalDAV\Backend\SchedulingSupport; @@ -44,6 +47,9 @@ class CalendarHome extends \Sabre\CalDAV\CalendarHome { /** @var \OCP\IConfig */ private $config; + /** @var PluginManager */ + private $pluginManager; + /** @var bool */ private $returnCachedSubscriptions=false; @@ -51,6 +57,10 @@ class CalendarHome extends \Sabre\CalDAV\CalendarHome { parent::__construct($caldavBackend, $principalInfo); $this->l10n = \OC::$server->getL10N('dav'); $this->config = \OC::$server->getConfig(); + $this->pluginManager = new PluginManager( + \OC::$server, + \OC::$server->getAppManager() + ); } /** @@ -66,7 +76,7 @@ class CalendarHome extends \Sabre\CalDAV\CalendarHome { function createExtendedCollection($name, MkCol $mkCol) { $reservedNames = [BirthdayService::BIRTHDAY_CALENDAR_URI]; - if (in_array($name, $reservedNames)) { + if (\in_array($name, $reservedNames, true) || ExternalCalendar::doesViolateReservedName($name)) { throw new MethodNotAllowed('The resource you tried to create has a reserved name'); } @@ -104,6 +114,14 @@ class CalendarHome extends \Sabre\CalDAV\CalendarHome { } } + foreach ($this->pluginManager->getCalendarPlugins() as $calendarPlugin) { + /** @var ICalendarProvider $calendarPlugin */ + $calendars = $calendarPlugin->fetchAllForCalendarHome($this->principalInfo['uri']); + foreach ($calendars as $calendar) { + $objects[] = $calendar; + } + } + return $objects; } @@ -139,7 +157,21 @@ class CalendarHome extends \Sabre\CalDAV\CalendarHome { return new Subscription($this->caldavBackend, $subscription); } } + } + + if (ExternalCalendar::isAppGeneratedCalendar($name)) { + [$appId, $calendarUri] = ExternalCalendar::splitAppGeneratedCalendarUri($name); + foreach ($this->pluginManager->getCalendarPlugins() as $calendarPlugin) { + /** @var ICalendarProvider $calendarPlugin */ + if ($calendarPlugin->getAppId() !== $appId) { + continue; + } + + if ($calendarPlugin->hasCalendarInCalendarHome($this->principalInfo['uri'], $calendarUri)) { + return $calendarPlugin->getCalendarInCalendarHome($this->principalInfo['uri'], $calendarUri); + } + } } throw new NotFound('Node with name \'' . $name . '\' could not be found'); @@ -155,7 +187,7 @@ class CalendarHome extends \Sabre\CalDAV\CalendarHome { return $this->caldavBackend->calendarSearch($principalUri, $filters, $limit, $offset); } - + public function enableCachedSubscriptionsForThisRequest() { $this->returnCachedSubscriptions = true; } diff --git a/apps/dav/lib/CalDAV/Integration/ExternalCalendar.php b/apps/dav/lib/CalDAV/Integration/ExternalCalendar.php new file mode 100644 index 00000000000..88d43f0bb55 --- /dev/null +++ b/apps/dav/lib/CalDAV/Integration/ExternalCalendar.php @@ -0,0 +1,132 @@ +<?php +/** + * @copyright 2020, Georg Ehrke <oc.list@georgehrke.com> + * + * @author Georg Ehrke <oc.list@georgehrke.com> + * + * @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\CalDAV\Integration; + +use Sabre\CalDAV; +use Sabre\DAV; + +/** + * Class ExternalCalendar + * + * @package OCA\DAV\CalDAV\Integration + * @since 19.0.0 + */ +abstract class ExternalCalendar implements CalDAV\ICalendar, DAV\IProperties { + + /** @var string */ + private const PREFIX = 'app-generated'; + + /** + * @var string + * + * Double dash is a valid delimiter, + * because it will always split the calendarURIs correctly: + * - our prefix contains only one dash and won't be split + * - appIds are not allowed to contain dashes as per spec: + * > must contain only lowercase ASCII characters and underscore + * - explode has a limit of three, so even if the app-generated + * calendar uri has double dashes, it won't be split + */ + private const DELIMITER = '--'; + + /** @var string */ + private $appId; + + /** @var string */ + private $calendarUri; + + /** + * ExternalCalendar constructor. + * + * @param string $appId + * @param string $calendarUri + */ + public function __construct(string $appId, string $calendarUri) { + $this->appId = $appId; + $this->calendarUri = $calendarUri; + } + + /** + * @inheritDoc + */ + final public function getName() { + return implode(self::DELIMITER, [ + self::PREFIX, + $this->appId, + $this->calendarUri, + ]); + } + + /** + * @inheritDoc + */ + final public function setName($name) { + throw new DAV\Exception\MethodNotAllowed('Renaming calendars is not yet supported'); + } + + /** + * @inheritDoc + */ + final public function createDirectory($name) { + throw new DAV\Exception\MethodNotAllowed('Creating collections in calendar objects is not allowed'); + + } + + /** + * Checks whether the calendar uri is app-generated + * + * @param string $calendarUri + * @return bool + */ + public static function isAppGeneratedCalendar(string $calendarUri):bool { + return strpos($calendarUri, self::PREFIX) === 0 && substr_count($calendarUri, self::DELIMITER) >= 2; + } + + /** + * Splits an app-generated calendar-uri into appId and calendarUri + * + * @param string $calendarUri + * @return array + */ + public static function splitAppGeneratedCalendarUri(string $calendarUri):array { + $array = array_slice(explode(self::DELIMITER, $calendarUri, 3), 1); + // Check the array has expected amount of elements + // and none of them is an empty string + if (\count($array) !== 2 || \in_array('', $array, true)) { + throw new \InvalidArgumentException('Provided calendar uri was not app-generated'); + } + + return $array; + } + + /** + * Checks whether a calendar-name, the user wants to create, violates + * the reserved name for calendar uris + * + * @param string $calendarUri + * @return bool + */ + public static function doesViolateReservedName(string $calendarUri):bool { + return strpos($calendarUri, self::PREFIX) === 0; + } +} diff --git a/apps/dav/lib/CalDAV/Integration/ICalendarProvider.php b/apps/dav/lib/CalDAV/Integration/ICalendarProvider.php new file mode 100644 index 00000000000..29f5f113ef5 --- /dev/null +++ b/apps/dav/lib/CalDAV/Integration/ICalendarProvider.php @@ -0,0 +1,73 @@ +<?php +/** + * @copyright 2020, Georg Ehrke <oc.list@georgehrke.com> + * + * @author Georg Ehrke <oc.list@georgehrke.com> + * + * @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\CalDAV\Integration; + +use Sabre\CalDAV\ICalendar; + +/** + * Interface ICalendarProvider + * + * @package OCA\DAV\CalDAV\Integration + * @since 19.0.0 + */ +interface ICalendarProvider { + + /** + * Provides the appId of the plugin + * + * @since 19.0.0 + * @return string AppId + */ + public function getAppId(): string; + + /** + * Fetches all calendars for a given principal uri + * + * @since 19.0.0 + * @param string $principalUri E.g. principals/users/user1 + * @return ExternalCalendar[] Array of all calendars + */ + public function fetchAllForCalendarHome(string $principalUri): array; + + /** + * Checks whether plugin has a calendar for a given principalUri and calendarUri + * + * @since 19.0.0 + * @param string $principalUri E.g. principals/users/user1 + * @param string $calendarUri E.g. personal + * @return bool True if calendar for principalUri and calendarUri exists, false otherwise + */ + public function hasCalendarInCalendarHome(string $principalUri, string $calendarUri): bool; + + /** + * Fetches a calendar for a given principalUri and calendarUri + * Returns null if calendar does not exist + * + * @since 19.0.0 + * @param string $principalUri E.g. principals/users/user1 + * @param string $calendarUri E.g. personal + * @return ExternalCalendar|null Calendar if it exists, null otherwise + */ + public function getCalendarInCalendarHome(string $principalUri, string $calendarUri): ?ExternalCalendar; + +} diff --git a/apps/dav/tests/unit/AppInfo/PluginManagerTest.php b/apps/dav/tests/unit/AppInfo/PluginManagerTest.php index e1b852b6a80..0d181f914a1 100644 --- a/apps/dav/tests/unit/AppInfo/PluginManagerTest.php +++ b/apps/dav/tests/unit/AppInfo/PluginManagerTest.php @@ -28,6 +28,7 @@ namespace OCA\DAV\Tests\unit\AppInfo; use OC\App\AppManager; use OC\ServerContainer; use OCA\DAV\AppInfo\PluginManager; +use OCA\DAV\CalDAV\Integration\ICalendarProvider; use Test\TestCase; /** @@ -53,6 +54,12 @@ class PluginManagerTest extends TestCase { '\OCA\DAV\ADavApp\PluginTwo', ], ], + 'calendar-plugins' => [ + 'plugin' => [ + '\OCA\DAV\ADavApp\CalendarPluginOne', + '\OCA\DAV\ADavApp\CalendarPluginTwo', + ], + ], 'collections' => [ 'collection' => [ '\OCA\DAV\ADavApp\CollectionOne', @@ -67,6 +74,9 @@ class PluginManagerTest extends TestCase { 'plugins' => [ 'plugin' => '\OCA\DAV\ADavApp2\PluginOne', ], + 'calendar-plugins' => [ + 'plugin' => '\OCA\DAV\ADavApp2\CalendarPluginOne', + ], 'collections' => [ 'collection' => '\OCA\DAV\ADavApp2\CollectionOne', ], @@ -81,13 +91,20 @@ class PluginManagerTest extends TestCase { $pluginManager = new PluginManager($server, $appManager); + $calendarPlugin1 = $this->createMock(ICalendarProvider::class); + $calendarPlugin2 = $this->createMock(ICalendarProvider::class); + $calendarPlugin3 = $this->createMock(ICalendarProvider::class); + $server->method('query') ->will($this->returnValueMap([ ['\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'], ])); @@ -96,6 +113,11 @@ class PluginManagerTest extends TestCase { 'dummyplugin2', 'dummy2plugin1', ]; + $expectedCalendarPlugins = [ + $calendarPlugin1, + $calendarPlugin2, + $calendarPlugin3, + ]; $expectedCollections = [ 'dummycollection1', 'dummycollection2', @@ -103,6 +125,7 @@ class PluginManagerTest extends TestCase { ]; $this->assertEquals($expectedPlugins, $pluginManager->getAppPlugins()); + $this->assertEquals($expectedCalendarPlugins, $pluginManager->getCalendarPlugins()); $this->assertEquals($expectedCollections, $pluginManager->getAppCollections()); } } diff --git a/apps/dav/tests/unit/CalDAV/CalendarHomeTest.php b/apps/dav/tests/unit/CalDAV/CalendarHomeTest.php index c811cb8b5a1..364286edd4b 100644 --- a/apps/dav/tests/unit/CalDAV/CalendarHomeTest.php +++ b/apps/dav/tests/unit/CalDAV/CalendarHomeTest.php @@ -24,8 +24,14 @@ namespace OCA\DAV\Tests\unit\CalDAV; +use OCA\DAV\AppInfo\PluginManager; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\CalendarHome; +use OCA\DAV\CalDAV\Integration\ExternalCalendar; +use OCA\DAV\CalDAV\Integration\ICalendarProvider; +use OCA\DAV\CalDAV\Outbox; +use Sabre\CalDAV\Schedule\Inbox; +use Sabre\DAV\Exception\NotFound; use Sabre\DAV\MkCol; use Test\TestCase; @@ -37,6 +43,9 @@ class CalendarHomeTest extends TestCase { /** @var array */ private $principalInfo = []; + /** @var PluginManager */ + private $pluginManager; + /** @var CalendarHome */ private $calendarHome; @@ -47,9 +56,16 @@ class CalendarHomeTest extends TestCase { $this->principalInfo = [ 'uri' => 'user-principal-123', ]; + $this->pluginManager = $this->createMock(PluginManager::class); $this->calendarHome = new CalendarHome($this->backend, $this->principalInfo); + + // Replace PluginManager with our mock + $reflection = new \ReflectionClass($this->calendarHome); + $reflectionProperty = $reflection->getProperty('pluginManager'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->calendarHome, $this->pluginManager); } public function testCreateCalendarValidName() { @@ -69,7 +85,6 @@ class CalendarHomeTest extends TestCase { $this->calendarHome->createExtendedCollection('name123', $mkCol); } - public function testCreateCalendarReservedName() { $this->expectException(\Sabre\DAV\Exception\MethodNotAllowed::class); $this->expectExceptionMessage('The resource you tried to create has a reserved name'); @@ -79,4 +94,136 @@ class CalendarHomeTest extends TestCase { $this->calendarHome->createExtendedCollection('contact_birthdays', $mkCol); } + + public function testCreateCalendarReservedNameAppGenerated() { + $this->expectException(\Sabre\DAV\Exception\MethodNotAllowed::class); + $this->expectExceptionMessage('The resource you tried to create has a reserved name'); + + /** @var MkCol | \PHPUnit_Framework_MockObject_MockObject $mkCol */ + $mkCol = $this->createMock(MkCol::class); + + $this->calendarHome->createExtendedCollection('app-generated--example--foo-1', $mkCol); + } + + public function testGetChildren():void { + $this->backend + ->expects($this->at(0)) + ->method('getCalendarsForUser') + ->with('user-principal-123') + ->willReturn([]); + + $this->backend + ->expects($this->at(1)) + ->method('getSubscriptionsForUser') + ->with('user-principal-123') + ->willReturn([]); + + $calendarPlugin1 = $this->createMock(ICalendarProvider::class); + $calendarPlugin1 + ->expects($this->once()) + ->method('fetchAllForCalendarHome') + ->with('user-principal-123') + ->willReturn(['plugin1calendar1', 'plugin1calendar2']); + + $calendarPlugin2 = $this->createMock(ICalendarProvider::class); + $calendarPlugin2 + ->expects($this->once()) + ->method('fetchAllForCalendarHome') + ->with('user-principal-123') + ->willReturn(['plugin2calendar1', 'plugin2calendar2']); + + $this->pluginManager + ->expects($this->once()) + ->method('getCalendarPlugins') + ->with() + ->willReturn([$calendarPlugin1, $calendarPlugin2]); + + $actual = $this->calendarHome->getChildren(); + + $this->assertCount(6, $actual); + $this->assertInstanceOf(Inbox::class, $actual[0]); + $this->assertInstanceOf(Outbox::class, $actual[1]); + $this->assertEquals('plugin1calendar1', $actual[2]); + $this->assertEquals('plugin1calendar2', $actual[3]); + $this->assertEquals('plugin2calendar1', $actual[4]); + $this->assertEquals('plugin2calendar2', $actual[5]); + } + + public function testGetChildNonAppGenerated():void { + $this->backend + ->expects($this->at(0)) + ->method('getCalendarsForUser') + ->with('user-principal-123') + ->willReturn([]); + + $this->backend + ->expects($this->at(1)) + ->method('getSubscriptionsForUser') + ->with('user-principal-123') + ->willReturn([]); + + $this->pluginManager + ->expects($this->never()) + ->method('getCalendarPlugins'); + + $this->expectException(\Sabre\DAV\Exception\NotFound::class); + $this->expectExceptionMessage('Node with name \'personal\' could not be found'); + + $this->calendarHome->getChild('personal'); + } + + public function testGetChildAppGenerated():void { + $this->backend + ->expects($this->at(0)) + ->method('getCalendarsForUser') + ->with('user-principal-123') + ->willReturn([]); + + $this->backend + ->expects($this->at(1)) + ->method('getSubscriptionsForUser') + ->with('user-principal-123') + ->willReturn([]); + + $calendarPlugin1 = $this->createMock(ICalendarProvider::class); + $calendarPlugin1 + ->expects($this->once()) + ->method('getAppId') + ->with() + ->willReturn('calendar_plugin_1'); + $calendarPlugin1 + ->expects($this->never()) + ->method('hasCalendarInCalendarHome'); + $calendarPlugin1 + ->expects($this->never()) + ->method('getCalendarInCalendarHome'); + + $externalCalendarMock = $this->createMock(ExternalCalendar::class); + + $calendarPlugin2 = $this->createMock(ICalendarProvider::class); + $calendarPlugin2 + ->expects($this->once()) + ->method('getAppId') + ->with() + ->willReturn('calendar_plugin_2'); + $calendarPlugin2 + ->expects($this->once()) + ->method('hasCalendarInCalendarHome') + ->with('user-principal-123', 'calendar-uri-from-backend') + ->willReturn(true); + $calendarPlugin2 + ->expects($this->once()) + ->method('getCalendarInCalendarHome') + ->with('user-principal-123', 'calendar-uri-from-backend') + ->willReturn($externalCalendarMock); + + $this->pluginManager + ->expects($this->once()) + ->method('getCalendarPlugins') + ->with() + ->willReturn([$calendarPlugin1, $calendarPlugin2]); + + $actual = $this->calendarHome->getChild('app-generated--calendar_plugin_2--calendar-uri-from-backend'); + $this->assertEquals($externalCalendarMock, $actual); + } } diff --git a/apps/dav/tests/unit/CalDAV/Integration/ExternalCalendarTest.php b/apps/dav/tests/unit/CalDAV/Integration/ExternalCalendarTest.php new file mode 100644 index 00000000000..a6698087155 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Integration/ExternalCalendarTest.php @@ -0,0 +1,119 @@ +<?php +/** + * @copyright 2020, Georg Ehrke <oc.list@georgehrke.com> + * + * @author Georg Ehrke <oc.list@georgehrke.com> + * + * @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\Integration; + +use OCA\DAV\CalDAV\Integration\ExternalCalendar; +use Test\TestCase; + +class ExternalCalendarTest extends TestCase { + + private $abstractExternalCalendar; + + protected function setUp(): void { + parent::setUp(); + + $this->abstractExternalCalendar = + $this->getMockForAbstractClass(ExternalCalendar::class, ['example-app-id', 'calendar-uri-in-backend']); + } + + public function testGetName():void { + // Check that the correct name is returned + $this->assertEquals('app-generated--example-app-id--calendar-uri-in-backend', + $this->abstractExternalCalendar->getName()); + + // Check that the method is final and can't be overriden by other classes + $reflectionMethod = new \ReflectionMethod(ExternalCalendar::class, 'getName'); + $this->assertTrue($reflectionMethod->isFinal()); + } + + public function testSetName():void { + // Check that the method is final and can't be overriden by other classes + $reflectionMethod = new \ReflectionMethod(ExternalCalendar::class, 'setName'); + $this->assertTrue($reflectionMethod->isFinal()); + + $this->expectException(\Sabre\DAV\Exception\MethodNotAllowed::class); + $this->expectExceptionMessage('Renaming calendars is not yet supported'); + + $this->abstractExternalCalendar->setName('other-name'); + } + + public function createDirectory():void { + // Check that the method is final and can't be overriden by other classes + $reflectionMethod = new \ReflectionMethod(ExternalCalendar::class, 'createDirectory'); + $this->assertTrue($reflectionMethod->isFinal()); + + $this->expectException(\Sabre\DAV\Exception\MethodNotAllowed::class); + $this->expectExceptionMessage('Creating collections in calendar objects is not allowed'); + + $this->abstractExternalCalendar->createDirectory('other-name'); + } + + public function testIsAppGeneratedCalendar():void { + $this->assertFalse(ExternalCalendar::isAppGeneratedCalendar('personal')); + $this->assertFalse(ExternalCalendar::isAppGeneratedCalendar('work')); + $this->assertFalse(ExternalCalendar::isAppGeneratedCalendar('contact_birthdays')); + $this->assertFalse(ExternalCalendar::isAppGeneratedCalendar('company')); + $this->assertFalse(ExternalCalendar::isAppGeneratedCalendar('app-generated')); + $this->assertFalse(ExternalCalendar::isAppGeneratedCalendar('app-generated--example')); + + $this->assertTrue(ExternalCalendar::isAppGeneratedCalendar('app-generated--deck--board-1')); + $this->assertTrue(ExternalCalendar::isAppGeneratedCalendar('app-generated--example--foo-2')); + $this->assertTrue(ExternalCalendar::isAppGeneratedCalendar('app-generated--example--foo--2')); + } + + /** + * @dataProvider splitAppGeneratedCalendarUriDataProvider + */ + public function testSplitAppGeneratedCalendarUriInvalid(string $name):void { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Provided calendar uri was not app-generated'); + + ExternalCalendar::splitAppGeneratedCalendarUri($name); + } + + public function splitAppGeneratedCalendarUriDataProvider():array { + return [ + ['personal'], + ['foo_shared_by_admin'], + ['contact_birthdays'], + ]; + } + + public function testSplitAppGeneratedCalendarUri():void { + $this->assertEquals(['deck', 'board-1'], ExternalCalendar::splitAppGeneratedCalendarUri('app-generated--deck--board-1')); + $this->assertEquals(['example', 'foo-2'], ExternalCalendar::splitAppGeneratedCalendarUri('app-generated--example--foo-2')); + $this->assertEquals(['example', 'foo--2'], ExternalCalendar::splitAppGeneratedCalendarUri('app-generated--example--foo--2')); + } + + public function testDoesViolateReservedName():void { + $this->assertFalse(ExternalCalendar::doesViolateReservedName('personal')); + $this->assertFalse(ExternalCalendar::doesViolateReservedName('work')); + $this->assertFalse(ExternalCalendar::doesViolateReservedName('contact_birthdays')); + $this->assertFalse(ExternalCalendar::doesViolateReservedName('company')); + + $this->assertTrue(ExternalCalendar::doesViolateReservedName('app-generated')); + $this->assertTrue(ExternalCalendar::doesViolateReservedName('app-generated-calendar')); + $this->assertTrue(ExternalCalendar::doesViolateReservedName('app-generated--deck-123')); + } +} |