diff options
Diffstat (limited to 'apps/dav')
94 files changed, 4684 insertions, 192 deletions
diff --git a/apps/dav/appinfo/app.php b/apps/dav/appinfo/app.php index 4f4fbe6e126..089aaeb6c78 100644 --- a/apps/dav/appinfo/app.php +++ b/apps/dav/appinfo/app.php @@ -48,6 +48,19 @@ $eventDispatcher->addListener('OCP\Federation\TrustedServerEvent::remove', } ); +$eventHandler = function() use ($app) { + try { + $job = $app->getContainer()->query(\OCA\DAV\BackgroundJob\UpdateCalendarResourcesRoomsBackgroundJob::class); + $job->run([]); + $app->getContainer()->getServer()->getJobList()->setLastRun($job); + } catch(\Exception $ex) { + $app->getContainer()->getServer()->getLogger()->logException($ex); + } +}; + +$eventDispatcher->addListener('\OCP\Calendar\Resource\ForceRefreshEvent', $eventHandler); +$eventDispatcher->addListener('\OCP\Calendar\Room\ForceRefreshEvent', $eventHandler); + $cm = \OC::$server->getContactsManager(); $cm->register(function() use ($cm, $app) { $user = \OC::$server->getUserSession()->getUser(); diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index d31851fe17e..9e0779a934d 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -5,7 +5,7 @@ <name>WebDAV</name> <summary>WebDAV endpoint</summary> <description>WebDAV endpoint</description> - <version>1.5.2</version> + <version>1.6.0</version> <licence>agpl</licence> <author>owncloud.org</author> <namespace>DAV</namespace> @@ -21,6 +21,8 @@ <background-jobs> <job>OCA\DAV\BackgroundJob\CleanupDirectLinksJob</job> + <job>OCA\DAV\BackgroundJob\UpdateCalendarResourcesRoomsBackgroundJob</job> + <job>OCA\DAV\BackgroundJob\CleanupInvitationTokenJob</job> </background-jobs> <repair-steps> @@ -36,6 +38,7 @@ <command>OCA\DAV\Command\CreateCalendar</command> <command>OCA\DAV\Command\SyncBirthdayCalendar</command> <command>OCA\DAV\Command\SyncSystemAddressBook</command> + <command>OCA\DAV\Command\RemoveInvalidShares</command> </commands> <settings> diff --git a/apps/dav/appinfo/routes.php b/apps/dav/appinfo/routes.php index 2aaeda98964..a7d9e2ec33c 100644 --- a/apps/dav/appinfo/routes.php +++ b/apps/dav/appinfo/routes.php @@ -25,6 +25,10 @@ return [ 'routes' => [ ['name' => 'birthday_calendar#enable', 'url' => '/enableBirthdayCalendar', 'verb' => 'POST'], ['name' => 'birthday_calendar#disable', 'url' => '/disableBirthdayCalendar', 'verb' => 'POST'], + ['name' => 'invitation_response#accept', 'url' => '/invitation/accept/{token}', 'verb' => 'GET'], + ['name' => 'invitation_response#decline', 'url' => '/invitation/decline/{token}', 'verb' => 'GET'], + ['name' => 'invitation_response#options', 'url' => '/invitation/moreOptions/{token}', 'verb' => 'GET'], + ['name' => 'invitation_response#processMoreOptionsResult', 'url' => '/invitation/moreOptions/{token}', 'verb' => 'POST'] ], 'ocs' => [ ['name' => 'direct#getUrl', 'url' => '/api/v1/direct', 'verb' => 'POST'], diff --git a/apps/dav/appinfo/v1/caldav.php b/apps/dav/appinfo/v1/caldav.php index b4907ce94cb..ecc4fbb8b60 100644 --- a/apps/dav/appinfo/v1/caldav.php +++ b/apps/dav/appinfo/v1/caldav.php @@ -46,6 +46,7 @@ $principalBackend = new Principal( \OC::$server->getGroupManager(), \OC::$server->getShareManager(), \OC::$server->getUserSession(), + \OC::$server->getConfig(), 'principals/' ); $db = \OC::$server->getDatabaseConnection(); diff --git a/apps/dav/appinfo/v1/carddav.php b/apps/dav/appinfo/v1/carddav.php index eddc7a794ac..e55eee610ef 100644 --- a/apps/dav/appinfo/v1/carddav.php +++ b/apps/dav/appinfo/v1/carddav.php @@ -47,6 +47,7 @@ $principalBackend = new Principal( \OC::$server->getGroupManager(), \OC::$server->getShareManager(), \OC::$server->getUserSession(), + \OC::$server->getConfig(), 'principals/' ); $db = \OC::$server->getDatabaseConnection(); diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index 50689568ebb..8b266c156f3 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -12,7 +12,9 @@ return array( 'OCA\\DAV\\Avatars\\AvatarNode' => $baseDir . '/../lib/Avatars/AvatarNode.php', 'OCA\\DAV\\Avatars\\RootCollection' => $baseDir . '/../lib/Avatars/RootCollection.php', 'OCA\\DAV\\BackgroundJob\\CleanupDirectLinksJob' => $baseDir . '/../lib/BackgroundJob/CleanupDirectLinksJob.php', + 'OCA\\DAV\\BackgroundJob\\CleanupInvitationTokenJob' => $baseDir . '/../lib/BackgroundJob/CleanupInvitationTokenJob.php', 'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => $baseDir . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php', + 'OCA\\DAV\\BackgroundJob\\UpdateCalendarResourcesRoomsBackgroundJob' => $baseDir . '/../lib/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJob.php', 'OCA\\DAV\\CalDAV\\Activity\\Backend' => $baseDir . '/../lib/CalDAV/Activity/Backend.php', 'OCA\\DAV\\CalDAV\\Activity\\Filter\\Calendar' => $baseDir . '/../lib/CalDAV/Activity/Filter/Calendar.php', 'OCA\\DAV\\CalDAV\\Activity\\Filter\\Todo' => $baseDir . '/../lib/CalDAV/Activity/Filter/Todo.php', @@ -32,6 +34,7 @@ return array( 'OCA\\DAV\\CalDAV\\CalendarManager' => $baseDir . '/../lib/CalDAV/CalendarManager.php', 'OCA\\DAV\\CalDAV\\CalendarObject' => $baseDir . '/../lib/CalDAV/CalendarObject.php', 'OCA\\DAV\\CalDAV\\CalendarRoot' => $baseDir . '/../lib/CalDAV/CalendarRoot.php', + 'OCA\\DAV\\CalDAV\\InvitationResponse\\InvitationResponseServer' => $baseDir . '/../lib/CalDAV/InvitationResponse/InvitationResponseServer.php', 'OCA\\DAV\\CalDAV\\Plugin' => $baseDir . '/../lib/CalDAV/Plugin.php', 'OCA\\DAV\\CalDAV\\Principal\\Collection' => $baseDir . '/../lib/CalDAV/Principal/Collection.php', 'OCA\\DAV\\CalDAV\\Principal\\User' => $baseDir . '/../lib/CalDAV/Principal/User.php', @@ -40,6 +43,9 @@ return array( 'OCA\\DAV\\CalDAV\\PublicCalendarRoot' => $baseDir . '/../lib/CalDAV/PublicCalendarRoot.php', 'OCA\\DAV\\CalDAV\\Publishing\\PublishPlugin' => $baseDir . '/../lib/CalDAV/Publishing/PublishPlugin.php', 'OCA\\DAV\\CalDAV\\Publishing\\Xml\\Publisher' => $baseDir . '/../lib/CalDAV/Publishing/Xml/Publisher.php', + 'OCA\\DAV\\CalDAV\\ResourceBooking\\AbstractPrincipalBackend' => $baseDir . '/../lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php', + 'OCA\\DAV\\CalDAV\\ResourceBooking\\ResourcePrincipalBackend' => $baseDir . '/../lib/CalDAV/ResourceBooking/ResourcePrincipalBackend.php', + 'OCA\\DAV\\CalDAV\\ResourceBooking\\RoomPrincipalBackend' => $baseDir . '/../lib/CalDAV/ResourceBooking/RoomPrincipalBackend.php', 'OCA\\DAV\\CalDAV\\Schedule\\IMipPlugin' => $baseDir . '/../lib/CalDAV/Schedule/IMipPlugin.php', 'OCA\\DAV\\CalDAV\\Schedule\\Plugin' => $baseDir . '/../lib/CalDAV/Schedule/Plugin.php', 'OCA\\DAV\\CalDAV\\Search\\SearchPlugin' => $baseDir . '/../lib/CalDAV/Search/SearchPlugin.php', @@ -65,6 +71,7 @@ return array( 'OCA\\DAV\\CardDAV\\Xml\\Groups' => $baseDir . '/../lib/CardDAV/Xml/Groups.php', 'OCA\\DAV\\Command\\CreateAddressBook' => $baseDir . '/../lib/Command/CreateAddressBook.php', 'OCA\\DAV\\Command\\CreateCalendar' => $baseDir . '/../lib/Command/CreateCalendar.php', + 'OCA\\DAV\\Command\\RemoveInvalidShares' => $baseDir . '/../lib/Command/RemoveInvalidShares.php', 'OCA\\DAV\\Command\\SyncBirthdayCalendar' => $baseDir . '/../lib/Command/SyncBirthdayCalendar.php', 'OCA\\DAV\\Command\\SyncSystemAddressBook' => $baseDir . '/../lib/Command/SyncSystemAddressBook.php', 'OCA\\DAV\\Comments\\CommentNode' => $baseDir . '/../lib/Comments/CommentNode.php', @@ -74,6 +81,7 @@ return array( 'OCA\\DAV\\Comments\\RootCollection' => $baseDir . '/../lib/Comments/RootCollection.php', 'OCA\\DAV\\Connector\\LegacyDAVACL' => $baseDir . '/../lib/Connector/LegacyDAVACL.php', 'OCA\\DAV\\Connector\\PublicAuth' => $baseDir . '/../lib/Connector/PublicAuth.php', + 'OCA\\DAV\\Connector\\Sabre\\AnonymousOptionsPlugin' => $baseDir . '/../lib/Connector/Sabre/AnonymousOptionsPlugin.php', 'OCA\\DAV\\Connector\\Sabre\\AppEnabledPlugin' => $baseDir . '/../lib/Connector/Sabre/AppEnabledPlugin.php', 'OCA\\DAV\\Connector\\Sabre\\Auth' => $baseDir . '/../lib/Connector/Sabre/Auth.php', 'OCA\\DAV\\Connector\\Sabre\\BearerAuth' => $baseDir . '/../lib/Connector/Sabre/BearerAuth.php', @@ -111,6 +119,7 @@ return array( 'OCA\\DAV\\Connector\\Sabre\\TagsPlugin' => $baseDir . '/../lib/Connector/Sabre/TagsPlugin.php', 'OCA\\DAV\\Controller\\BirthdayCalendarController' => $baseDir . '/../lib/Controller/BirthdayCalendarController.php', 'OCA\\DAV\\Controller\\DirectController' => $baseDir . '/../lib/Controller/DirectController.php', + 'OCA\\DAV\\Controller\\InvitationResponseController' => $baseDir . '/../lib/Controller/InvitationResponseController.php', 'OCA\\DAV\\DAV\\CustomPropertiesBackend' => $baseDir . '/../lib/DAV/CustomPropertiesBackend.php', 'OCA\\DAV\\DAV\\GroupPrincipalBackend' => $baseDir . '/../lib/DAV/GroupPrincipalBackend.php', 'OCA\\DAV\\DAV\\PublicAuth' => $baseDir . '/../lib/DAV/PublicAuth.php', @@ -129,6 +138,7 @@ return array( 'OCA\\DAV\\Files\\BrowserErrorPagePlugin' => $baseDir . '/../lib/Files/BrowserErrorPagePlugin.php', 'OCA\\DAV\\Files\\FileSearchBackend' => $baseDir . '/../lib/Files/FileSearchBackend.php', 'OCA\\DAV\\Files\\FilesHome' => $baseDir . '/../lib/Files/FilesHome.php', + 'OCA\\DAV\\Files\\LazySearchBackend' => $baseDir . '/../lib/Files/LazySearchBackend.php', 'OCA\\DAV\\Files\\RootCollection' => $baseDir . '/../lib/Files/RootCollection.php', 'OCA\\DAV\\Files\\Sharing\\FilesDropPlugin' => $baseDir . '/../lib/Files/Sharing/FilesDropPlugin.php', 'OCA\\DAV\\Files\\Sharing\\PublicLinkCheckPlugin' => $baseDir . '/../lib/Files/Sharing/PublicLinkCheckPlugin.php', @@ -142,6 +152,8 @@ return array( 'OCA\\DAV\\Migration\\Version1004Date20170924124212' => $baseDir . '/../lib/Migration/Version1004Date20170924124212.php', 'OCA\\DAV\\Migration\\Version1004Date20170926103422' => $baseDir . '/../lib/Migration/Version1004Date20170926103422.php', 'OCA\\DAV\\Migration\\Version1005Date20180413093149' => $baseDir . '/../lib/Migration/Version1005Date20180413093149.php', + 'OCA\\DAV\\Migration\\Version1005Date20180530124431' => $baseDir . '/../lib/Migration/Version1005Date20180530124431.php', + 'OCA\\DAV\\Migration\\Version1006Date20180619154313' => $baseDir . '/../lib/Migration/Version1006Date20180619154313.php', 'OCA\\DAV\\RootCollection' => $baseDir . '/../lib/RootCollection.php', 'OCA\\DAV\\Server' => $baseDir . '/../lib/Server.php', 'OCA\\DAV\\Settings\\CalDAVSettings' => $baseDir . '/../lib/Settings/CalDAVSettings.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index 760ca3426f7..09eb4d257cc 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -27,7 +27,9 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Avatars\\AvatarNode' => __DIR__ . '/..' . '/../lib/Avatars/AvatarNode.php', 'OCA\\DAV\\Avatars\\RootCollection' => __DIR__ . '/..' . '/../lib/Avatars/RootCollection.php', 'OCA\\DAV\\BackgroundJob\\CleanupDirectLinksJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupDirectLinksJob.php', + 'OCA\\DAV\\BackgroundJob\\CleanupInvitationTokenJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupInvitationTokenJob.php', 'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php', + 'OCA\\DAV\\BackgroundJob\\UpdateCalendarResourcesRoomsBackgroundJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJob.php', 'OCA\\DAV\\CalDAV\\Activity\\Backend' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Backend.php', 'OCA\\DAV\\CalDAV\\Activity\\Filter\\Calendar' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Filter/Calendar.php', 'OCA\\DAV\\CalDAV\\Activity\\Filter\\Todo' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Filter/Todo.php', @@ -47,6 +49,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\CalDAV\\CalendarManager' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarManager.php', 'OCA\\DAV\\CalDAV\\CalendarObject' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarObject.php', 'OCA\\DAV\\CalDAV\\CalendarRoot' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarRoot.php', + 'OCA\\DAV\\CalDAV\\InvitationResponse\\InvitationResponseServer' => __DIR__ . '/..' . '/../lib/CalDAV/InvitationResponse/InvitationResponseServer.php', 'OCA\\DAV\\CalDAV\\Plugin' => __DIR__ . '/..' . '/../lib/CalDAV/Plugin.php', 'OCA\\DAV\\CalDAV\\Principal\\Collection' => __DIR__ . '/..' . '/../lib/CalDAV/Principal/Collection.php', 'OCA\\DAV\\CalDAV\\Principal\\User' => __DIR__ . '/..' . '/../lib/CalDAV/Principal/User.php', @@ -55,6 +58,9 @@ class ComposerStaticInitDAV 'OCA\\DAV\\CalDAV\\PublicCalendarRoot' => __DIR__ . '/..' . '/../lib/CalDAV/PublicCalendarRoot.php', 'OCA\\DAV\\CalDAV\\Publishing\\PublishPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/Publishing/PublishPlugin.php', 'OCA\\DAV\\CalDAV\\Publishing\\Xml\\Publisher' => __DIR__ . '/..' . '/../lib/CalDAV/Publishing/Xml/Publisher.php', + 'OCA\\DAV\\CalDAV\\ResourceBooking\\AbstractPrincipalBackend' => __DIR__ . '/..' . '/../lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php', + 'OCA\\DAV\\CalDAV\\ResourceBooking\\ResourcePrincipalBackend' => __DIR__ . '/..' . '/../lib/CalDAV/ResourceBooking/ResourcePrincipalBackend.php', + 'OCA\\DAV\\CalDAV\\ResourceBooking\\RoomPrincipalBackend' => __DIR__ . '/..' . '/../lib/CalDAV/ResourceBooking/RoomPrincipalBackend.php', 'OCA\\DAV\\CalDAV\\Schedule\\IMipPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/Schedule/IMipPlugin.php', 'OCA\\DAV\\CalDAV\\Schedule\\Plugin' => __DIR__ . '/..' . '/../lib/CalDAV/Schedule/Plugin.php', 'OCA\\DAV\\CalDAV\\Search\\SearchPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/Search/SearchPlugin.php', @@ -80,6 +86,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\CardDAV\\Xml\\Groups' => __DIR__ . '/..' . '/../lib/CardDAV/Xml/Groups.php', 'OCA\\DAV\\Command\\CreateAddressBook' => __DIR__ . '/..' . '/../lib/Command/CreateAddressBook.php', 'OCA\\DAV\\Command\\CreateCalendar' => __DIR__ . '/..' . '/../lib/Command/CreateCalendar.php', + 'OCA\\DAV\\Command\\RemoveInvalidShares' => __DIR__ . '/..' . '/../lib/Command/RemoveInvalidShares.php', 'OCA\\DAV\\Command\\SyncBirthdayCalendar' => __DIR__ . '/..' . '/../lib/Command/SyncBirthdayCalendar.php', 'OCA\\DAV\\Command\\SyncSystemAddressBook' => __DIR__ . '/..' . '/../lib/Command/SyncSystemAddressBook.php', 'OCA\\DAV\\Comments\\CommentNode' => __DIR__ . '/..' . '/../lib/Comments/CommentNode.php', @@ -89,6 +96,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Comments\\RootCollection' => __DIR__ . '/..' . '/../lib/Comments/RootCollection.php', 'OCA\\DAV\\Connector\\LegacyDAVACL' => __DIR__ . '/..' . '/../lib/Connector/LegacyDAVACL.php', 'OCA\\DAV\\Connector\\PublicAuth' => __DIR__ . '/..' . '/../lib/Connector/PublicAuth.php', + 'OCA\\DAV\\Connector\\Sabre\\AnonymousOptionsPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/AnonymousOptionsPlugin.php', 'OCA\\DAV\\Connector\\Sabre\\AppEnabledPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/AppEnabledPlugin.php', 'OCA\\DAV\\Connector\\Sabre\\Auth' => __DIR__ . '/..' . '/../lib/Connector/Sabre/Auth.php', 'OCA\\DAV\\Connector\\Sabre\\BearerAuth' => __DIR__ . '/..' . '/../lib/Connector/Sabre/BearerAuth.php', @@ -126,6 +134,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Connector\\Sabre\\TagsPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/TagsPlugin.php', 'OCA\\DAV\\Controller\\BirthdayCalendarController' => __DIR__ . '/..' . '/../lib/Controller/BirthdayCalendarController.php', 'OCA\\DAV\\Controller\\DirectController' => __DIR__ . '/..' . '/../lib/Controller/DirectController.php', + 'OCA\\DAV\\Controller\\InvitationResponseController' => __DIR__ . '/..' . '/../lib/Controller/InvitationResponseController.php', 'OCA\\DAV\\DAV\\CustomPropertiesBackend' => __DIR__ . '/..' . '/../lib/DAV/CustomPropertiesBackend.php', 'OCA\\DAV\\DAV\\GroupPrincipalBackend' => __DIR__ . '/..' . '/../lib/DAV/GroupPrincipalBackend.php', 'OCA\\DAV\\DAV\\PublicAuth' => __DIR__ . '/..' . '/../lib/DAV/PublicAuth.php', @@ -144,6 +153,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Files\\BrowserErrorPagePlugin' => __DIR__ . '/..' . '/../lib/Files/BrowserErrorPagePlugin.php', 'OCA\\DAV\\Files\\FileSearchBackend' => __DIR__ . '/..' . '/../lib/Files/FileSearchBackend.php', 'OCA\\DAV\\Files\\FilesHome' => __DIR__ . '/..' . '/../lib/Files/FilesHome.php', + 'OCA\\DAV\\Files\\LazySearchBackend' => __DIR__ . '/..' . '/../lib/Files/LazySearchBackend.php', 'OCA\\DAV\\Files\\RootCollection' => __DIR__ . '/..' . '/../lib/Files/RootCollection.php', 'OCA\\DAV\\Files\\Sharing\\FilesDropPlugin' => __DIR__ . '/..' . '/../lib/Files/Sharing/FilesDropPlugin.php', 'OCA\\DAV\\Files\\Sharing\\PublicLinkCheckPlugin' => __DIR__ . '/..' . '/../lib/Files/Sharing/PublicLinkCheckPlugin.php', @@ -157,6 +167,8 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Migration\\Version1004Date20170924124212' => __DIR__ . '/..' . '/../lib/Migration/Version1004Date20170924124212.php', 'OCA\\DAV\\Migration\\Version1004Date20170926103422' => __DIR__ . '/..' . '/../lib/Migration/Version1004Date20170926103422.php', 'OCA\\DAV\\Migration\\Version1005Date20180413093149' => __DIR__ . '/..' . '/../lib/Migration/Version1005Date20180413093149.php', + 'OCA\\DAV\\Migration\\Version1005Date20180530124431' => __DIR__ . '/..' . '/../lib/Migration/Version1005Date20180530124431.php', + 'OCA\\DAV\\Migration\\Version1006Date20180619154313' => __DIR__ . '/..' . '/../lib/Migration/Version1006Date20180619154313.php', 'OCA\\DAV\\RootCollection' => __DIR__ . '/..' . '/../lib/RootCollection.php', 'OCA\\DAV\\Server' => __DIR__ . '/..' . '/../lib/Server.php', 'OCA\\DAV\\Settings\\CalDAVSettings' => __DIR__ . '/..' . '/../lib/Settings/CalDAVSettings.php', diff --git a/apps/dav/css/schedule-response.css b/apps/dav/css/schedule-response.css new file mode 100644 index 00000000000..789ea16df7a --- /dev/null +++ b/apps/dav/css/schedule-response.css @@ -0,0 +1,78 @@ +/* Database selector on install page */ +form #selectPartStatForm { + text-align:center; + white-space: nowrap; + margin: 0; +} + +form #selectPartStatForm .info { + white-space: normal; +} + +form #selectPartStatForm input[type="radio"] { + display: none; +} + +form #selectPartStatForm input[type="radio"]:checked+label { + background-color: #e8e8e8; +} + +form #selectPartStatForm input[type="radio"]:checked ~ form fieldset#more_options { + display: none; +} + +form #selectPartStatForm label { + color: #000; + background-color: #f8f8f8; + position: static; + margin: 0 -3px 5px; + cursor:pointer; + border: 1px solid #ddd; + display: inline-block; + padding: 0; + line-height: normal; + vertical-align: middle; + text-align: center; + overflow: visible; +} + +form #selectPartStatForm label:first-of-type { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} + +form #selectPartStatForm label:last-of-type { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +form #selectPartStatForm label span { + cursor: pointer; + padding: 10px 20px; + display: block; + line-height: normal; +} +form #selectPartStatForm label.ui-state-hover, +form #selectPartStatForm label.ui-state-active { + color:#000; + background-color:#e8e8e8; +} + +form input[type="number"] { + width: 249px; + background: #fff; + color: #555; + cursor: text; + font-family: inherit; + -webkit-appearance: textfield; + -moz-appearance: textfield; + box-sizing: content-box; + border: none; + font-weight: 300; +} + +form input[type="submit"] { + display: block; + margin: 0 auto; + padding: 11px 20px 9px +}
\ No newline at end of file diff --git a/apps/dav/js/schedule-response.js b/apps/dav/js/schedule-response.js new file mode 100644 index 00000000000..b2514b1ba82 --- /dev/null +++ b/apps/dav/js/schedule-response.js @@ -0,0 +1,3 @@ +// $(document).ready(function() { +// $('#selectPartStatForm').buttonset(); +// });
\ No newline at end of file diff --git a/apps/dav/js/settings-admin-caldav.js b/apps/dav/js/settings-admin-caldav.js index cf1a2006f69..1a40c208dfe 100644 --- a/apps/dav/js/settings-admin-caldav.js +++ b/apps/dav/js/settings-admin-caldav.js @@ -31,8 +31,8 @@ $('#caldavGenerateBirthdayCalendar').change(function() { var val = $(this)[0].checked; if (val) { - $.post(OC.generateUrl(OC.linkTo("dav", "enableBirthdayCalendar"))); + $.post(OC.generateUrl('/apps/dav/enableBirthdayCalendar')); } else { - $.post(OC.generateUrl(OC.linkTo("dav", "disableBirthdayCalendar"))); + $.post(OC.generateUrl('/apps/dav/disableBirthdayCalendar')); } }); diff --git a/apps/dav/l10n/cs.js b/apps/dav/l10n/cs.js index 6171105a7b1..8cb5bbd2a39 100644 --- a/apps/dav/l10n/cs.js +++ b/apps/dav/l10n/cs.js @@ -5,15 +5,15 @@ OC.L10N.register( "Todos" : "Úkoly", "Personal" : "Osobní", "{actor} created calendar {calendar}" : "{actor} vytvořil(a) kalendář {calendar}", - "You created calendar {calendar}" : "Vytvořil(a", + "You created calendar {calendar}" : "Vytvořili jste kalendář {calendar}", "{actor} deleted calendar {calendar}" : "{actor} smazal(a) kalendář {calendar}", - "You deleted calendar {calendar}" : "Smazal(a) jste kalendář {calendar}", + "You deleted calendar {calendar}" : "Smazali jste kalendář {calendar}", "{actor} updated calendar {calendar}" : "{actor} aktualizoval(a) kalendář {calendar}", - "You updated calendar {calendar}" : "Aktualizoval(a) jste kalendář {calendar}", + "You updated calendar {calendar}" : "Aktualizovali jste kalendář {calendar}", "You shared calendar {calendar} as public link" : "Sdílel(a) jste kalendář {calendar} jako veřejný odkaz", "You removed public link for calendar {calendar}" : "Odstranil(a) jste veřejný odkaz pro kalendář {calendar} ", "{actor} shared calendar {calendar} with you" : "{actor} s vámi nasdílel(a) kalendář {calendar}", - "You shared calendar {calendar} with {user}" : "S uživatelem {user} jste začal(a) sdílet kalendář {calendar}", + "You shared calendar {calendar} with {user}" : "S uživatelem {user} jste začali sdílet kalendář {calendar}", "{actor} shared calendar {calendar} with {user}" : "{actor} začal sdílet kalendář {calendar} s uživatelem {user}", "{actor} unshared calendar {calendar} from you" : "{actor} s vámi přestal(a) sdílet kalendář {calendar}", "You unshared calendar {calendar} from {user}" : "S uživatelem {user} jste přestal(a) sdílet kalendář {calendar}", @@ -54,11 +54,16 @@ OC.L10N.register( "Where:" : "Kde:", "Description:" : "Popis:", "Link:" : "Odkaz:", + "Accept" : "Přijmout", + "More options ..." : "Další volby…", "Contacts" : "Kontakty", "WebDAV" : "WebDAV", "Technical details" : "Technické detaily", "Remote Address: %s" : "Vzdálená adresa: %s", "Request ID: %s" : "ID požadavku: %s", + "Please contact the organizer directly." : "Kontaktujte organizátora přímo.", + "Are you accepting the invitation?" : "Přijímáte pozvání?", + "Save" : "Uložit", "CalDAV server" : "CalDAV server", "Send invitations to attendees" : "Poslat pozvánky na adresy účastníků", "Please make sure to properly set up the email settings above." : "Ujistěte se, že jste správně nastavili výše uvedená nastavení e-mailu.", diff --git a/apps/dav/l10n/cs.json b/apps/dav/l10n/cs.json index ce0bc06e43e..3b66407ba2a 100644 --- a/apps/dav/l10n/cs.json +++ b/apps/dav/l10n/cs.json @@ -3,15 +3,15 @@ "Todos" : "Úkoly", "Personal" : "Osobní", "{actor} created calendar {calendar}" : "{actor} vytvořil(a) kalendář {calendar}", - "You created calendar {calendar}" : "Vytvořil(a", + "You created calendar {calendar}" : "Vytvořili jste kalendář {calendar}", "{actor} deleted calendar {calendar}" : "{actor} smazal(a) kalendář {calendar}", - "You deleted calendar {calendar}" : "Smazal(a) jste kalendář {calendar}", + "You deleted calendar {calendar}" : "Smazali jste kalendář {calendar}", "{actor} updated calendar {calendar}" : "{actor} aktualizoval(a) kalendář {calendar}", - "You updated calendar {calendar}" : "Aktualizoval(a) jste kalendář {calendar}", + "You updated calendar {calendar}" : "Aktualizovali jste kalendář {calendar}", "You shared calendar {calendar} as public link" : "Sdílel(a) jste kalendář {calendar} jako veřejný odkaz", "You removed public link for calendar {calendar}" : "Odstranil(a) jste veřejný odkaz pro kalendář {calendar} ", "{actor} shared calendar {calendar} with you" : "{actor} s vámi nasdílel(a) kalendář {calendar}", - "You shared calendar {calendar} with {user}" : "S uživatelem {user} jste začal(a) sdílet kalendář {calendar}", + "You shared calendar {calendar} with {user}" : "S uživatelem {user} jste začali sdílet kalendář {calendar}", "{actor} shared calendar {calendar} with {user}" : "{actor} začal sdílet kalendář {calendar} s uživatelem {user}", "{actor} unshared calendar {calendar} from you" : "{actor} s vámi přestal(a) sdílet kalendář {calendar}", "You unshared calendar {calendar} from {user}" : "S uživatelem {user} jste přestal(a) sdílet kalendář {calendar}", @@ -52,11 +52,16 @@ "Where:" : "Kde:", "Description:" : "Popis:", "Link:" : "Odkaz:", + "Accept" : "Přijmout", + "More options ..." : "Další volby…", "Contacts" : "Kontakty", "WebDAV" : "WebDAV", "Technical details" : "Technické detaily", "Remote Address: %s" : "Vzdálená adresa: %s", "Request ID: %s" : "ID požadavku: %s", + "Please contact the organizer directly." : "Kontaktujte organizátora přímo.", + "Are you accepting the invitation?" : "Přijímáte pozvání?", + "Save" : "Uložit", "CalDAV server" : "CalDAV server", "Send invitations to attendees" : "Poslat pozvánky na adresy účastníků", "Please make sure to properly set up the email settings above." : "Ujistěte se, že jste správně nastavili výše uvedená nastavení e-mailu.", diff --git a/apps/dav/l10n/da.js b/apps/dav/l10n/da.js index 8ddad543dc7..63f9716ff52 100644 --- a/apps/dav/l10n/da.js +++ b/apps/dav/l10n/da.js @@ -10,6 +10,8 @@ OC.L10N.register( "You deleted calendar {calendar}" : "Du slettede kalenderen {calendar}", "{actor} updated calendar {calendar}" : "{actor} opdaterede kalenderen {calendar}", "You updated calendar {calendar}" : "Du opdaterede kalenderen {calendar}", + "You shared calendar {calendar} as public link" : "Du har delt kalenderen {calendar} som offentligt link", + "You removed public link for calendar {calendar}" : "Du har fjernet det offentlige link til kalenderen {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} delte kalenderen {calendar} med dig", "You shared calendar {calendar} with {user}" : "Du delte kalenderen {calendar} med {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} delte kalenderen {calendar} med {user}", @@ -53,11 +55,16 @@ OC.L10N.register( "Description:" : "Beskrivelse:", "Link:" : "Link:", "Contacts" : "Kontakter", + "WebDAV" : "WebDAV", + "WebDAV endpoint" : "WebDAV endpoint", "Technical details" : "Tekniske detaljer", "Remote Address: %s" : "Fjernadresse: %s", "Request ID: %s" : "Forespørgsels-ID: %s", "CalDAV server" : "CalDAV server", "Send invitations to attendees" : "Send invitation til deltagere", - "Please make sure to properly set up the email settings above." : "Vær venligst sikker på at indstille email indstillingerne ovenover ordenligt." + "Please make sure to properly set up the email settings above." : "Vær venligst sikker på at indstille email indstillingerne ovenover ordenligt.", + "Automatically generate a birthday calendar" : "Generer en fødselsdagskalender automatisk", + "Birthday calendars will be generated by a background job." : "Fødselsdagskalendere vil blive oprettet af et job, der kører i baggrunden.", + "Hence they will not be available immediately after enabling but will show up after some time." : "Derfor vil de ikke blive synlige med det samme efter aktivering, men vil vise sig efter noget tid." }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/dav/l10n/da.json b/apps/dav/l10n/da.json index fd40e7d0350..c40ec28963a 100644 --- a/apps/dav/l10n/da.json +++ b/apps/dav/l10n/da.json @@ -8,6 +8,8 @@ "You deleted calendar {calendar}" : "Du slettede kalenderen {calendar}", "{actor} updated calendar {calendar}" : "{actor} opdaterede kalenderen {calendar}", "You updated calendar {calendar}" : "Du opdaterede kalenderen {calendar}", + "You shared calendar {calendar} as public link" : "Du har delt kalenderen {calendar} som offentligt link", + "You removed public link for calendar {calendar}" : "Du har fjernet det offentlige link til kalenderen {calendar}", "{actor} shared calendar {calendar} with you" : "{actor} delte kalenderen {calendar} med dig", "You shared calendar {calendar} with {user}" : "Du delte kalenderen {calendar} med {user}", "{actor} shared calendar {calendar} with {user}" : "{actor} delte kalenderen {calendar} med {user}", @@ -51,11 +53,16 @@ "Description:" : "Beskrivelse:", "Link:" : "Link:", "Contacts" : "Kontakter", + "WebDAV" : "WebDAV", + "WebDAV endpoint" : "WebDAV endpoint", "Technical details" : "Tekniske detaljer", "Remote Address: %s" : "Fjernadresse: %s", "Request ID: %s" : "Forespørgsels-ID: %s", "CalDAV server" : "CalDAV server", "Send invitations to attendees" : "Send invitation til deltagere", - "Please make sure to properly set up the email settings above." : "Vær venligst sikker på at indstille email indstillingerne ovenover ordenligt." + "Please make sure to properly set up the email settings above." : "Vær venligst sikker på at indstille email indstillingerne ovenover ordenligt.", + "Automatically generate a birthday calendar" : "Generer en fødselsdagskalender automatisk", + "Birthday calendars will be generated by a background job." : "Fødselsdagskalendere vil blive oprettet af et job, der kører i baggrunden.", + "Hence they will not be available immediately after enabling but will show up after some time." : "Derfor vil de ikke blive synlige med det samme efter aktivering, men vil vise sig efter noget tid." },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/dav/l10n/de.js b/apps/dav/l10n/de.js index 4402c07a397..d7af0e5f459 100644 --- a/apps/dav/l10n/de.js +++ b/apps/dav/l10n/de.js @@ -54,11 +54,22 @@ OC.L10N.register( "Where:" : "Wo:", "Description:" : "Beschreibung:", "Link:" : "Link:", + "Accept" : "Akzeptieren", + "Decline" : "Ablehnen", + "More options ..." : "Weitere Optionen…", + "More options at %s" : "Weitere Optionen unter %s", "Contacts" : "Kontakte", "WebDAV" : "WebDAV", + "WebDAV endpoint" : "WebDAV-Endpunkt", "Technical details" : "Technische Details", "Remote Address: %s" : "Entfernte Adresse: %s", "Request ID: %s" : "Anfragekennung: %s", + "There was an error updating your attendance status." : "Es ist ein Fehler beim Aktualisieren Deines Teilnehmerstatus aufgetreten.", + "Please contact the organizer directly." : "Bitte den Organisator direkt kontaktieren.", + "Are you accepting the invitation?" : "Die Einladung annehmen?", + "Tentative" : "Vorläufig", + "Save" : "Speichern", + "Your attendance was updated successfully." : "Dein Anwesenheits-Status wurde aktualisiert.", "CalDAV server" : "CalDAV-Server", "Send invitations to attendees" : "Einladungen an die Teilnehmer versenden", "Please make sure to properly set up the email settings above." : "Bitte sicherstellen, dass die E-Mail Einstellungen oben korrekt angegeben sind.", diff --git a/apps/dav/l10n/de.json b/apps/dav/l10n/de.json index 83973d8a7b6..c4161d1d1da 100644 --- a/apps/dav/l10n/de.json +++ b/apps/dav/l10n/de.json @@ -52,11 +52,22 @@ "Where:" : "Wo:", "Description:" : "Beschreibung:", "Link:" : "Link:", + "Accept" : "Akzeptieren", + "Decline" : "Ablehnen", + "More options ..." : "Weitere Optionen…", + "More options at %s" : "Weitere Optionen unter %s", "Contacts" : "Kontakte", "WebDAV" : "WebDAV", + "WebDAV endpoint" : "WebDAV-Endpunkt", "Technical details" : "Technische Details", "Remote Address: %s" : "Entfernte Adresse: %s", "Request ID: %s" : "Anfragekennung: %s", + "There was an error updating your attendance status." : "Es ist ein Fehler beim Aktualisieren Deines Teilnehmerstatus aufgetreten.", + "Please contact the organizer directly." : "Bitte den Organisator direkt kontaktieren.", + "Are you accepting the invitation?" : "Die Einladung annehmen?", + "Tentative" : "Vorläufig", + "Save" : "Speichern", + "Your attendance was updated successfully." : "Dein Anwesenheits-Status wurde aktualisiert.", "CalDAV server" : "CalDAV-Server", "Send invitations to attendees" : "Einladungen an die Teilnehmer versenden", "Please make sure to properly set up the email settings above." : "Bitte sicherstellen, dass die E-Mail Einstellungen oben korrekt angegeben sind.", diff --git a/apps/dav/l10n/de_DE.js b/apps/dav/l10n/de_DE.js index 058a2af65a0..4edd79558a8 100644 --- a/apps/dav/l10n/de_DE.js +++ b/apps/dav/l10n/de_DE.js @@ -54,11 +54,22 @@ OC.L10N.register( "Where:" : "Wo:", "Description:" : "Beschreibung:", "Link:" : "Link:", + "Accept" : "Akzeptieren", + "Decline" : "Ablehnen", + "More options ..." : "Weitere Optionen…", + "More options at %s" : "Weitere Optionen unter %s", "Contacts" : "Kontakte", "WebDAV" : "WebDAV", + "WebDAV endpoint" : "WebDAV-Endpunkt", "Technical details" : "Technische Details", "Remote Address: %s" : "Entfernte Adresse: %s", "Request ID: %s" : "Anfragekennung: %s", + "There was an error updating your attendance status." : "Es ist ein Fehler beim Aktualisieren Ihres Teilnehmerstatus aufgetreten.", + "Please contact the organizer directly." : "Bitte den Organisator direkt kontaktieren.", + "Are you accepting the invitation?" : "Die Einladung annehmen?", + "Tentative" : "Vorläufig", + "Save" : "Speichern", + "Your attendance was updated successfully." : "Ihr Teilnehmerstatus wurde aktualisiert.", "CalDAV server" : "CalDAV-Server", "Send invitations to attendees" : "Einladungen an die Teilnehmer versenden", "Please make sure to properly set up the email settings above." : "Stellen Sie sicher, dass die obigen E-Mail-Einstellungen korrekt sind.", diff --git a/apps/dav/l10n/de_DE.json b/apps/dav/l10n/de_DE.json index 824e670dfa2..72c60c0c6e6 100644 --- a/apps/dav/l10n/de_DE.json +++ b/apps/dav/l10n/de_DE.json @@ -52,11 +52,22 @@ "Where:" : "Wo:", "Description:" : "Beschreibung:", "Link:" : "Link:", + "Accept" : "Akzeptieren", + "Decline" : "Ablehnen", + "More options ..." : "Weitere Optionen…", + "More options at %s" : "Weitere Optionen unter %s", "Contacts" : "Kontakte", "WebDAV" : "WebDAV", + "WebDAV endpoint" : "WebDAV-Endpunkt", "Technical details" : "Technische Details", "Remote Address: %s" : "Entfernte Adresse: %s", "Request ID: %s" : "Anfragekennung: %s", + "There was an error updating your attendance status." : "Es ist ein Fehler beim Aktualisieren Ihres Teilnehmerstatus aufgetreten.", + "Please contact the organizer directly." : "Bitte den Organisator direkt kontaktieren.", + "Are you accepting the invitation?" : "Die Einladung annehmen?", + "Tentative" : "Vorläufig", + "Save" : "Speichern", + "Your attendance was updated successfully." : "Ihr Teilnehmerstatus wurde aktualisiert.", "CalDAV server" : "CalDAV-Server", "Send invitations to attendees" : "Einladungen an die Teilnehmer versenden", "Please make sure to properly set up the email settings above." : "Stellen Sie sicher, dass die obigen E-Mail-Einstellungen korrekt sind.", diff --git a/apps/dav/l10n/en_GB.js b/apps/dav/l10n/en_GB.js index eeda81b6612..9dbace82f50 100644 --- a/apps/dav/l10n/en_GB.js +++ b/apps/dav/l10n/en_GB.js @@ -56,6 +56,7 @@ OC.L10N.register( "Link:" : "Link:", "Contacts" : "Contacts", "WebDAV" : "WebDAV", + "WebDAV endpoint" : "WebDAV endpoint", "Technical details" : "Technical details", "Remote Address: %s" : "Remote Address: %s", "Request ID: %s" : "Request ID: %s", diff --git a/apps/dav/l10n/en_GB.json b/apps/dav/l10n/en_GB.json index e3a7a0b46b6..4a36e95c3d6 100644 --- a/apps/dav/l10n/en_GB.json +++ b/apps/dav/l10n/en_GB.json @@ -54,6 +54,7 @@ "Link:" : "Link:", "Contacts" : "Contacts", "WebDAV" : "WebDAV", + "WebDAV endpoint" : "WebDAV endpoint", "Technical details" : "Technical details", "Remote Address: %s" : "Remote Address: %s", "Request ID: %s" : "Request ID: %s", diff --git a/apps/dav/l10n/es.js b/apps/dav/l10n/es.js index 07b1600e8b0..398741d9626 100644 --- a/apps/dav/l10n/es.js +++ b/apps/dav/l10n/es.js @@ -54,11 +54,22 @@ OC.L10N.register( "Where:" : "Dónde:", "Description:" : "Descripción:", "Link:" : "Enlace:", + "Accept" : "Aceptar", + "Decline" : "Rechazar", + "More options ..." : "Más opciones...", + "More options at %s" : "Más opciones en %s", "Contacts" : "Contactos", "WebDAV" : "WebDAV", + "WebDAV endpoint" : "Extremo del WebDAV", "Technical details" : "Detalles técnicos", "Remote Address: %s" : "Dirección remota: %s", "Request ID: %s" : "ID de solicitud: %s", + "There was an error updating your attendance status." : "Ha habido un error al actualizar tu estado de asistencia.", + "Please contact the organizer directly." : "Por favor, contacta directamente con el organizador.", + "Are you accepting the invitation?" : "¿Aceptas la invitación?", + "Tentative" : "Provisional", + "Save" : "Guardar", + "Your attendance was updated successfully." : "Tu asistencia se ha actualizado con éxito.", "CalDAV server" : "Servidor CalDAV", "Send invitations to attendees" : "Enviar invitaciones a los asistentes", "Please make sure to properly set up the email settings above." : "Por favor, asegúrate de que las configuraciones de correo de arriba son correctas", diff --git a/apps/dav/l10n/es.json b/apps/dav/l10n/es.json index 9bb9118d043..d31347608a3 100644 --- a/apps/dav/l10n/es.json +++ b/apps/dav/l10n/es.json @@ -52,11 +52,22 @@ "Where:" : "Dónde:", "Description:" : "Descripción:", "Link:" : "Enlace:", + "Accept" : "Aceptar", + "Decline" : "Rechazar", + "More options ..." : "Más opciones...", + "More options at %s" : "Más opciones en %s", "Contacts" : "Contactos", "WebDAV" : "WebDAV", + "WebDAV endpoint" : "Extremo del WebDAV", "Technical details" : "Detalles técnicos", "Remote Address: %s" : "Dirección remota: %s", "Request ID: %s" : "ID de solicitud: %s", + "There was an error updating your attendance status." : "Ha habido un error al actualizar tu estado de asistencia.", + "Please contact the organizer directly." : "Por favor, contacta directamente con el organizador.", + "Are you accepting the invitation?" : "¿Aceptas la invitación?", + "Tentative" : "Provisional", + "Save" : "Guardar", + "Your attendance was updated successfully." : "Tu asistencia se ha actualizado con éxito.", "CalDAV server" : "Servidor CalDAV", "Send invitations to attendees" : "Enviar invitaciones a los asistentes", "Please make sure to properly set up the email settings above." : "Por favor, asegúrate de que las configuraciones de correo de arriba son correctas", diff --git a/apps/dav/l10n/es_MX.js b/apps/dav/l10n/es_MX.js index d01873bd444..00f6fd81bbb 100644 --- a/apps/dav/l10n/es_MX.js +++ b/apps/dav/l10n/es_MX.js @@ -56,6 +56,7 @@ OC.L10N.register( "Link:" : "Enlace:", "Contacts" : "Contactos", "WebDAV" : "WebDAV", + "WebDAV endpoint" : "Endpoint WebDAV", "Technical details" : "Detalles técnicos", "Remote Address: %s" : "Dirección remota: %s", "Request ID: %s" : "ID de solicitud: %s", diff --git a/apps/dav/l10n/es_MX.json b/apps/dav/l10n/es_MX.json index 5cbd4755efd..bd83d8e3ad3 100644 --- a/apps/dav/l10n/es_MX.json +++ b/apps/dav/l10n/es_MX.json @@ -54,6 +54,7 @@ "Link:" : "Enlace:", "Contacts" : "Contactos", "WebDAV" : "WebDAV", + "WebDAV endpoint" : "Endpoint WebDAV", "Technical details" : "Detalles técnicos", "Remote Address: %s" : "Dirección remota: %s", "Request ID: %s" : "ID de solicitud: %s", diff --git a/apps/dav/l10n/fr.js b/apps/dav/l10n/fr.js index 291e5615265..eb814d7a6cb 100644 --- a/apps/dav/l10n/fr.js +++ b/apps/dav/l10n/fr.js @@ -54,11 +54,19 @@ OC.L10N.register( "Where:" : "Où :", "Description:" : "Description :", "Link:" : "Lien :", + "Accept" : "Accepter", + "Decline" : "Refuser", + "More options ..." : "Plus d'options ...", + "More options at %s" : "Plus d'options à %s", "Contacts" : "Contacts", "WebDAV" : "WebDAV", "Technical details" : "Détails techniques", "Remote Address: %s" : "Adresse distante : %s", "Request ID: %s" : "ID de la requête : %s", + "Please contact the organizer directly." : "Merci de contacter l'organisateur directement.", + "Are you accepting the invitation?" : "Acceptez-vous l'invitation ?", + "Tentative" : "Provisoire", + "Save" : "Sauvegarder", "CalDAV server" : "Serveur CalDAV", "Send invitations to attendees" : "Envoyer des invitations aux participants", "Please make sure to properly set up the email settings above." : "Merci de vérifier d'avoir correctement configuré les paramètres de courriel ci-dessus", diff --git a/apps/dav/l10n/fr.json b/apps/dav/l10n/fr.json index b09c10b96f3..672e6aec7e3 100644 --- a/apps/dav/l10n/fr.json +++ b/apps/dav/l10n/fr.json @@ -52,11 +52,19 @@ "Where:" : "Où :", "Description:" : "Description :", "Link:" : "Lien :", + "Accept" : "Accepter", + "Decline" : "Refuser", + "More options ..." : "Plus d'options ...", + "More options at %s" : "Plus d'options à %s", "Contacts" : "Contacts", "WebDAV" : "WebDAV", "Technical details" : "Détails techniques", "Remote Address: %s" : "Adresse distante : %s", "Request ID: %s" : "ID de la requête : %s", + "Please contact the organizer directly." : "Merci de contacter l'organisateur directement.", + "Are you accepting the invitation?" : "Acceptez-vous l'invitation ?", + "Tentative" : "Provisoire", + "Save" : "Sauvegarder", "CalDAV server" : "Serveur CalDAV", "Send invitations to attendees" : "Envoyer des invitations aux participants", "Please make sure to properly set up the email settings above." : "Merci de vérifier d'avoir correctement configuré les paramètres de courriel ci-dessus", diff --git a/apps/dav/l10n/he.js b/apps/dav/l10n/he.js index 137476ddc05..2f91c82d3e5 100644 --- a/apps/dav/l10n/he.js +++ b/apps/dav/l10n/he.js @@ -12,9 +12,38 @@ OC.L10N.register( "You updated calendar {calendar}" : "עדכנת את היומן {calendar}", "You shared calendar {calendar} as public link" : "שיתפת את היומן {calendar} כקישור ציבורי", "You removed public link for calendar {calendar}" : "הסרת את הקישור הציבורי ליומן {calendar}", + "{actor} shared calendar {calendar} with you" : "שותף אתך לוח השנה {calendar} על ידי {actor}", + "You shared calendar {calendar} with {user}" : "שיתפת לוח שנה {calendar} עם {user}", + "{actor} shared calendar {calendar} with {user}" : "לוח השנה {calendar} שותף על ידי {actor} עם {user}", + "{actor} unshared calendar {calendar} from you" : "השיתוף של לוח השנה {calendar} אתך הופסק על ידי {actor}", + "You unshared calendar {calendar} from {user}" : "ביטלת את שיתוף לוח השנה {calendar} עם {user}", + "{actor} unshared calendar {calendar} from {user}" : "השיתוף של לוח השנה {calendar} עם {user} הופסק על ידי {actor}", + "{actor} unshared calendar {calendar} from themselves" : "השיתוף של לוח השנה {calendar} עם עצמם הופסק על ידי {actor}", + "You shared calendar {calendar} with group {group}" : "שיתפת את לוח השנה {calendar} עם הקבוצה {group}", + "{actor} shared calendar {calendar} with group {group}" : "לוח השנה {calendar} שותף עם הקבוצה {group} על ידי {actor}", + "You unshared calendar {calendar} from group {group}" : "הפסקת את שיתוף לוח השנה {calendar} עם הקבוצה {group}", + "{actor} unshared calendar {calendar} from group {group}" : "השיתוף של לוח השנה {calendar} עם {group} הופסק על ידי {actor}", + "{actor} created event {event} in calendar {calendar}" : "האירוע {event} נוצר בלוח השנה {calendar} על ידי {actor}", + "You created event {event} in calendar {calendar}" : "יצרת אירוע {event} בלוח השנה {calendar}", + "{actor} deleted event {event} from calendar {calendar}" : "האירוע {event} נמחק מלוח השנה {calendar} על ידי {actor}", + "You deleted event {event} from calendar {calendar}" : "מחקת אירוע {event} מלוח השנה {calendar}", + "{actor} updated event {event} in calendar {calendar}" : "האירוע {event} עודכן בלוח השנה {calendar} על ידי {actor}", + "You updated event {event} in calendar {calendar}" : "עדכנת את האירוע {event} בלוח השנה {calendar}", + "{actor} created todo {todo} in list {calendar}" : "המשימה לביצוע {todo} ברשימה {calendar} נוצרה על ידי {actor}", + "You created todo {todo} in list {calendar}" : "יצרת את המשימה לביצוע {todo} ברשימה {calendar}", + "{actor} deleted todo {todo} from list {calendar}" : "המשימה לביצוע {todo} מהרשימה {calendar} נמחקה על ידי {actor}", + "You deleted todo {todo} from list {calendar}" : "מחקת את המשימה לביצוע {todo} מהרשימה {calendar}", + "{actor} updated todo {todo} in list {calendar}" : "המשימה לביצוע {todo} ברשימה {calendar} עודכנה על ידי {actor}", + "You updated todo {todo} in list {calendar}" : "עדכנת את המשימה לביצוע {todo} ברשימה {calendar}", + "{actor} solved todo {todo} in list {calendar}" : "המשימה לביצוע {todo} ברשימה {calendar} נפתרה על ידי {actor}", + "You solved todo {todo} in list {calendar}" : "פתרת משימה לביצוע {todo} ברשימה {calendar}", + "{actor} reopened todo {todo} in list {calendar}" : "המשימה לביצוע {todo} ברשימה {calendar} נפתחה מחדש על ידי {actor}", + "You reopened todo {todo} in list {calendar}" : "פתחת מחדש את המשימה לביצוע {todo} ברשימה {calendar}", "A <strong>calendar</strong> was modified" : " <strong>יומן</strong> נערך", "A calendar <strong>event</strong> was modified" : "<strong>אירוע</strong> ביומן נערך", + "A calendar <strong>todo</strong> was modified" : "נערכה <strong>מטלה</strong> בלוח שנה", "Contact birthdays" : "ימי הולדת של אנשי קשר", + "%s via %s" : "%s דרך %s", "Invitation canceled" : "ההזמנה בוטלה", "Hello %s," : "שלום %s,", "The meeting »%s« with %s was canceled." : "הפגישה „%s” עם %s בוטלה.", @@ -24,11 +53,22 @@ OC.L10N.register( "Where:" : "איפה:", "Description:" : "תיאור:", "Link:" : "קישור:", + "Accept" : "קבלה", + "Decline" : "דחייה", + "More options ..." : "אפשרויות נוספות…", + "More options at %s" : "אפשרויות נוספים ב־%s", "Contacts" : "אנשי קשר", "WebDAV" : "WebDAV", + "WebDAV endpoint" : "נקודת קצה WebDAV", "Technical details" : "פרטים טכניים", "Remote Address: %s" : "כתובת מרוחקת: %s", "Request ID: %s" : "מזהה בקשה: %s", + "There was an error updating your attendance status." : "אירעה שגיאה בעת עדכון מצב ההשתתפות שלך.", + "Please contact the organizer directly." : "נא ליצור קשר עם הגוף מארגן ישירות.", + "Are you accepting the invitation?" : "האם להיענות להזמנה?", + "Tentative" : "טנטטיבית", + "Save" : "שמירה", + "Your attendance was updated successfully." : "ההשתתפות שלך עודכנה בהצלחה.", "CalDAV server" : "שרת CalDAV", "Send invitations to attendees" : "שליחת הזמנות למשתתפים", "Please make sure to properly set up the email settings above." : "נא לוודא שהגדרת את הדוא״ל שלהלן כראוי.", diff --git a/apps/dav/l10n/he.json b/apps/dav/l10n/he.json index b0d2b58a58d..5b46ea6e0a0 100644 --- a/apps/dav/l10n/he.json +++ b/apps/dav/l10n/he.json @@ -10,9 +10,38 @@ "You updated calendar {calendar}" : "עדכנת את היומן {calendar}", "You shared calendar {calendar} as public link" : "שיתפת את היומן {calendar} כקישור ציבורי", "You removed public link for calendar {calendar}" : "הסרת את הקישור הציבורי ליומן {calendar}", + "{actor} shared calendar {calendar} with you" : "שותף אתך לוח השנה {calendar} על ידי {actor}", + "You shared calendar {calendar} with {user}" : "שיתפת לוח שנה {calendar} עם {user}", + "{actor} shared calendar {calendar} with {user}" : "לוח השנה {calendar} שותף על ידי {actor} עם {user}", + "{actor} unshared calendar {calendar} from you" : "השיתוף של לוח השנה {calendar} אתך הופסק על ידי {actor}", + "You unshared calendar {calendar} from {user}" : "ביטלת את שיתוף לוח השנה {calendar} עם {user}", + "{actor} unshared calendar {calendar} from {user}" : "השיתוף של לוח השנה {calendar} עם {user} הופסק על ידי {actor}", + "{actor} unshared calendar {calendar} from themselves" : "השיתוף של לוח השנה {calendar} עם עצמם הופסק על ידי {actor}", + "You shared calendar {calendar} with group {group}" : "שיתפת את לוח השנה {calendar} עם הקבוצה {group}", + "{actor} shared calendar {calendar} with group {group}" : "לוח השנה {calendar} שותף עם הקבוצה {group} על ידי {actor}", + "You unshared calendar {calendar} from group {group}" : "הפסקת את שיתוף לוח השנה {calendar} עם הקבוצה {group}", + "{actor} unshared calendar {calendar} from group {group}" : "השיתוף של לוח השנה {calendar} עם {group} הופסק על ידי {actor}", + "{actor} created event {event} in calendar {calendar}" : "האירוע {event} נוצר בלוח השנה {calendar} על ידי {actor}", + "You created event {event} in calendar {calendar}" : "יצרת אירוע {event} בלוח השנה {calendar}", + "{actor} deleted event {event} from calendar {calendar}" : "האירוע {event} נמחק מלוח השנה {calendar} על ידי {actor}", + "You deleted event {event} from calendar {calendar}" : "מחקת אירוע {event} מלוח השנה {calendar}", + "{actor} updated event {event} in calendar {calendar}" : "האירוע {event} עודכן בלוח השנה {calendar} על ידי {actor}", + "You updated event {event} in calendar {calendar}" : "עדכנת את האירוע {event} בלוח השנה {calendar}", + "{actor} created todo {todo} in list {calendar}" : "המשימה לביצוע {todo} ברשימה {calendar} נוצרה על ידי {actor}", + "You created todo {todo} in list {calendar}" : "יצרת את המשימה לביצוע {todo} ברשימה {calendar}", + "{actor} deleted todo {todo} from list {calendar}" : "המשימה לביצוע {todo} מהרשימה {calendar} נמחקה על ידי {actor}", + "You deleted todo {todo} from list {calendar}" : "מחקת את המשימה לביצוע {todo} מהרשימה {calendar}", + "{actor} updated todo {todo} in list {calendar}" : "המשימה לביצוע {todo} ברשימה {calendar} עודכנה על ידי {actor}", + "You updated todo {todo} in list {calendar}" : "עדכנת את המשימה לביצוע {todo} ברשימה {calendar}", + "{actor} solved todo {todo} in list {calendar}" : "המשימה לביצוע {todo} ברשימה {calendar} נפתרה על ידי {actor}", + "You solved todo {todo} in list {calendar}" : "פתרת משימה לביצוע {todo} ברשימה {calendar}", + "{actor} reopened todo {todo} in list {calendar}" : "המשימה לביצוע {todo} ברשימה {calendar} נפתחה מחדש על ידי {actor}", + "You reopened todo {todo} in list {calendar}" : "פתחת מחדש את המשימה לביצוע {todo} ברשימה {calendar}", "A <strong>calendar</strong> was modified" : " <strong>יומן</strong> נערך", "A calendar <strong>event</strong> was modified" : "<strong>אירוע</strong> ביומן נערך", + "A calendar <strong>todo</strong> was modified" : "נערכה <strong>מטלה</strong> בלוח שנה", "Contact birthdays" : "ימי הולדת של אנשי קשר", + "%s via %s" : "%s דרך %s", "Invitation canceled" : "ההזמנה בוטלה", "Hello %s," : "שלום %s,", "The meeting »%s« with %s was canceled." : "הפגישה „%s” עם %s בוטלה.", @@ -22,11 +51,22 @@ "Where:" : "איפה:", "Description:" : "תיאור:", "Link:" : "קישור:", + "Accept" : "קבלה", + "Decline" : "דחייה", + "More options ..." : "אפשרויות נוספות…", + "More options at %s" : "אפשרויות נוספים ב־%s", "Contacts" : "אנשי קשר", "WebDAV" : "WebDAV", + "WebDAV endpoint" : "נקודת קצה WebDAV", "Technical details" : "פרטים טכניים", "Remote Address: %s" : "כתובת מרוחקת: %s", "Request ID: %s" : "מזהה בקשה: %s", + "There was an error updating your attendance status." : "אירעה שגיאה בעת עדכון מצב ההשתתפות שלך.", + "Please contact the organizer directly." : "נא ליצור קשר עם הגוף מארגן ישירות.", + "Are you accepting the invitation?" : "האם להיענות להזמנה?", + "Tentative" : "טנטטיבית", + "Save" : "שמירה", + "Your attendance was updated successfully." : "ההשתתפות שלך עודכנה בהצלחה.", "CalDAV server" : "שרת CalDAV", "Send invitations to attendees" : "שליחת הזמנות למשתתפים", "Please make sure to properly set up the email settings above." : "נא לוודא שהגדרת את הדוא״ל שלהלן כראוי.", diff --git a/apps/dav/l10n/it.js b/apps/dav/l10n/it.js index 34893da53b0..10fa534a9d6 100644 --- a/apps/dav/l10n/it.js +++ b/apps/dav/l10n/it.js @@ -54,11 +54,22 @@ OC.L10N.register( "Where:" : "Dove:", "Description:" : "Descrizione:", "Link:" : "Collegamento:", + "Accept" : "Accetta", + "Decline" : "Rifiuta", + "More options ..." : "Altre opzioni...", + "More options at %s" : "Altre opzioni alle %s", "Contacts" : "Contatti", "WebDAV" : "WebDAV", + "WebDAV endpoint" : "Terminatore WebDAV", "Technical details" : "Dettagli tecnici", "Remote Address: %s" : "Indirizzo remoto: %s", "Request ID: %s" : "ID richiesta: %s", + "There was an error updating your attendance status." : "Si è verificato un errore durante l'aggiornamento dello stato della tua partecipazione.", + "Please contact the organizer directly." : "Contatta direttamente l'amministratore.", + "Are you accepting the invitation?" : "Accetti l'invito?", + "Tentative" : "Provvisorio", + "Save" : "Salva", + "Your attendance was updated successfully." : "La tua partecipazione è stata aggiornata correttamente.", "CalDAV server" : "Server CalDAV", "Send invitations to attendees" : "Invia gli inviti ai partecipanti", "Please make sure to properly set up the email settings above." : "Assicurati di configurare correttamente le impostazioni di posta sopra.", diff --git a/apps/dav/l10n/it.json b/apps/dav/l10n/it.json index 538b041e10a..eee0dddcf95 100644 --- a/apps/dav/l10n/it.json +++ b/apps/dav/l10n/it.json @@ -52,11 +52,22 @@ "Where:" : "Dove:", "Description:" : "Descrizione:", "Link:" : "Collegamento:", + "Accept" : "Accetta", + "Decline" : "Rifiuta", + "More options ..." : "Altre opzioni...", + "More options at %s" : "Altre opzioni alle %s", "Contacts" : "Contatti", "WebDAV" : "WebDAV", + "WebDAV endpoint" : "Terminatore WebDAV", "Technical details" : "Dettagli tecnici", "Remote Address: %s" : "Indirizzo remoto: %s", "Request ID: %s" : "ID richiesta: %s", + "There was an error updating your attendance status." : "Si è verificato un errore durante l'aggiornamento dello stato della tua partecipazione.", + "Please contact the organizer directly." : "Contatta direttamente l'amministratore.", + "Are you accepting the invitation?" : "Accetti l'invito?", + "Tentative" : "Provvisorio", + "Save" : "Salva", + "Your attendance was updated successfully." : "La tua partecipazione è stata aggiornata correttamente.", "CalDAV server" : "Server CalDAV", "Send invitations to attendees" : "Invia gli inviti ai partecipanti", "Please make sure to properly set up the email settings above." : "Assicurati di configurare correttamente le impostazioni di posta sopra.", diff --git a/apps/dav/l10n/pl.js b/apps/dav/l10n/pl.js index 11d53da887d..a36bdc8c44b 100644 --- a/apps/dav/l10n/pl.js +++ b/apps/dav/l10n/pl.js @@ -59,6 +59,8 @@ OC.L10N.register( "Technical details" : "Szczegóły techniczne", "Remote Address: %s" : "Adres zdalny: %s", "Request ID: %s" : "ID żądania: %s", + "Are you accepting the invitation?" : "Czy akceptujesz zaproszenie?", + "Save" : "Zapisz", "CalDAV server" : "Serwer CalDAV", "Send invitations to attendees" : "Wyślij uczestnikom zaproszenia", "Please make sure to properly set up the email settings above." : "Upewnij się, że dobrze skonfigurowano powyżej ustawienia poczty e-mail.", diff --git a/apps/dav/l10n/pl.json b/apps/dav/l10n/pl.json index e04a6e25b5c..899a32ce5da 100644 --- a/apps/dav/l10n/pl.json +++ b/apps/dav/l10n/pl.json @@ -57,6 +57,8 @@ "Technical details" : "Szczegóły techniczne", "Remote Address: %s" : "Adres zdalny: %s", "Request ID: %s" : "ID żądania: %s", + "Are you accepting the invitation?" : "Czy akceptujesz zaproszenie?", + "Save" : "Zapisz", "CalDAV server" : "Serwer CalDAV", "Send invitations to attendees" : "Wyślij uczestnikom zaproszenia", "Please make sure to properly set up the email settings above." : "Upewnij się, że dobrze skonfigurowano powyżej ustawienia poczty e-mail.", diff --git a/apps/dav/l10n/pt_BR.js b/apps/dav/l10n/pt_BR.js index 75346f3aacb..9a14b215132 100644 --- a/apps/dav/l10n/pt_BR.js +++ b/apps/dav/l10n/pt_BR.js @@ -54,14 +54,25 @@ OC.L10N.register( "Where:" : "Onde:", "Description:" : "Descrição:", "Link:" : "Link:", + "Accept" : "Aceitar", + "Decline" : "Rejeitar", + "More options ..." : "Mais opções...", + "More options at %s" : "Mais opções em %s", "Contacts" : "Contatos", "WebDAV" : "WebDAV", + "WebDAV endpoint" : "Ponto final WebDAV", "Technical details" : "Detalhes técnicos", "Remote Address: %s" : "Endereço remoto: %s", "Request ID: %s" : "ID do solicitante: %s", + "There was an error updating your attendance status." : "Houve um erro ao atualizar seu status de participação.", + "Please contact the organizer directly." : "Por favor, contate o organizador diretamente.", + "Are you accepting the invitation?" : "Você está aceitando o convite?", + "Tentative" : "Tentativa", + "Save" : "Salvar", + "Your attendance was updated successfully." : "Sua participação foi atualizada com sucesso.", "CalDAV server" : "Servidor CalDAV", "Send invitations to attendees" : "Envie convites aos participantes", - "Please make sure to properly set up the email settings above." : "Certifique-se de configurar corretamente o email acima.", + "Please make sure to properly set up the email settings above." : "Certifique-se de configurar corretamente o e-mail acima.", "Automatically generate a birthday calendar" : "Gerar um calendário de aniversários automaticamente", "Birthday calendars will be generated by a background job." : "Os calendários de aniversários serão gerados na retaguarda.", "Hence they will not be available immediately after enabling but will show up after some time." : "Portanto, eles não estarão disponíveis imediatamente ao habilitar mas após algum tempo." diff --git a/apps/dav/l10n/pt_BR.json b/apps/dav/l10n/pt_BR.json index f773ee0d93f..867977e00d1 100644 --- a/apps/dav/l10n/pt_BR.json +++ b/apps/dav/l10n/pt_BR.json @@ -52,14 +52,25 @@ "Where:" : "Onde:", "Description:" : "Descrição:", "Link:" : "Link:", + "Accept" : "Aceitar", + "Decline" : "Rejeitar", + "More options ..." : "Mais opções...", + "More options at %s" : "Mais opções em %s", "Contacts" : "Contatos", "WebDAV" : "WebDAV", + "WebDAV endpoint" : "Ponto final WebDAV", "Technical details" : "Detalhes técnicos", "Remote Address: %s" : "Endereço remoto: %s", "Request ID: %s" : "ID do solicitante: %s", + "There was an error updating your attendance status." : "Houve um erro ao atualizar seu status de participação.", + "Please contact the organizer directly." : "Por favor, contate o organizador diretamente.", + "Are you accepting the invitation?" : "Você está aceitando o convite?", + "Tentative" : "Tentativa", + "Save" : "Salvar", + "Your attendance was updated successfully." : "Sua participação foi atualizada com sucesso.", "CalDAV server" : "Servidor CalDAV", "Send invitations to attendees" : "Envie convites aos participantes", - "Please make sure to properly set up the email settings above." : "Certifique-se de configurar corretamente o email acima.", + "Please make sure to properly set up the email settings above." : "Certifique-se de configurar corretamente o e-mail acima.", "Automatically generate a birthday calendar" : "Gerar um calendário de aniversários automaticamente", "Birthday calendars will be generated by a background job." : "Os calendários de aniversários serão gerados na retaguarda.", "Hence they will not be available immediately after enabling but will show up after some time." : "Portanto, eles não estarão disponíveis imediatamente ao habilitar mas após algum tempo." diff --git a/apps/dav/l10n/ru.js b/apps/dav/l10n/ru.js index 70cfd8050a6..cbe6509e952 100644 --- a/apps/dav/l10n/ru.js +++ b/apps/dav/l10n/ru.js @@ -56,6 +56,7 @@ OC.L10N.register( "Link:" : "Ссылка:", "Contacts" : "Контакты", "WebDAV" : "WebDAV", + "WebDAV endpoint" : "Конечная точка WebDAV", "Technical details" : "Технические подробности", "Remote Address: %s" : "Удаленный адрес: %s", "Request ID: %s" : "ID запроса: %s", diff --git a/apps/dav/l10n/ru.json b/apps/dav/l10n/ru.json index 934bcaa719e..dc87356477e 100644 --- a/apps/dav/l10n/ru.json +++ b/apps/dav/l10n/ru.json @@ -54,6 +54,7 @@ "Link:" : "Ссылка:", "Contacts" : "Контакты", "WebDAV" : "WebDAV", + "WebDAV endpoint" : "Конечная точка WebDAV", "Technical details" : "Технические подробности", "Remote Address: %s" : "Удаленный адрес: %s", "Request ID: %s" : "ID запроса: %s", diff --git a/apps/dav/l10n/sr.js b/apps/dav/l10n/sr.js index e31b17da482..ce3292345ec 100644 --- a/apps/dav/l10n/sr.js +++ b/apps/dav/l10n/sr.js @@ -54,11 +54,22 @@ OC.L10N.register( "Where:" : "Место:", "Description:" : "Опис:", "Link:" : "Веза:", + "Accept" : "Прихвати", + "Decline" : "Одбиј", + "More options ..." : "Још опција...", + "More options at %s" : "Још опција на %s", "Contacts" : "Контакти", "WebDAV" : "ВебДАВ", + "WebDAV endpoint" : "WebDAV крајња тачка", "Technical details" : "Технички детаљи", "Remote Address: %s" : "Удаљена адреса: %s", "Request ID: %s" : "ИД захтева: %s", + "There was an error updating your attendance status." : "Десила се грешка приликом ажурирања статуса Вашег присуства.", + "Please contact the organizer directly." : "Контактирајте директно организатора.", + "Are you accepting the invitation?" : "Да ли прихватате позивницу?", + "Tentative" : "Условна потврда", + "Save" : "Сачувај", + "Your attendance was updated successfully." : "Ваше присуство је успешно ажурирано.", "CalDAV server" : "CalDAV сервер", "Send invitations to attendees" : "Пошаљи позивницу учесницима", "Please make sure to properly set up the email settings above." : "Пазите да правилно подесите поставке е-поште изнад.", diff --git a/apps/dav/l10n/sr.json b/apps/dav/l10n/sr.json index fea5b91a7d0..2f9951b0e4d 100644 --- a/apps/dav/l10n/sr.json +++ b/apps/dav/l10n/sr.json @@ -52,11 +52,22 @@ "Where:" : "Место:", "Description:" : "Опис:", "Link:" : "Веза:", + "Accept" : "Прихвати", + "Decline" : "Одбиј", + "More options ..." : "Још опција...", + "More options at %s" : "Још опција на %s", "Contacts" : "Контакти", "WebDAV" : "ВебДАВ", + "WebDAV endpoint" : "WebDAV крајња тачка", "Technical details" : "Технички детаљи", "Remote Address: %s" : "Удаљена адреса: %s", "Request ID: %s" : "ИД захтева: %s", + "There was an error updating your attendance status." : "Десила се грешка приликом ажурирања статуса Вашег присуства.", + "Please contact the organizer directly." : "Контактирајте директно организатора.", + "Are you accepting the invitation?" : "Да ли прихватате позивницу?", + "Tentative" : "Условна потврда", + "Save" : "Сачувај", + "Your attendance was updated successfully." : "Ваше присуство је успешно ажурирано.", "CalDAV server" : "CalDAV сервер", "Send invitations to attendees" : "Пошаљи позивницу учесницима", "Please make sure to properly set up the email settings above." : "Пазите да правилно подесите поставке е-поште изнад.", diff --git a/apps/dav/l10n/tr.js b/apps/dav/l10n/tr.js index 4053bf54330..8fa0ceb5baf 100644 --- a/apps/dav/l10n/tr.js +++ b/apps/dav/l10n/tr.js @@ -56,6 +56,7 @@ OC.L10N.register( "Link:" : "Bağlantı:", "Contacts" : "Kişiler", "WebDAV" : "WebDAV", + "WebDAV endpoint" : "WebDAV Bağlantı Noktası", "Technical details" : "Teknik ayrıntılar", "Remote Address: %s" : "Uzak Adres: %s", "Request ID: %s" : "İstek Kodu: %s", diff --git a/apps/dav/l10n/tr.json b/apps/dav/l10n/tr.json index 254ca8d63fb..c9856ad5b03 100644 --- a/apps/dav/l10n/tr.json +++ b/apps/dav/l10n/tr.json @@ -54,6 +54,7 @@ "Link:" : "Bağlantı:", "Contacts" : "Kişiler", "WebDAV" : "WebDAV", + "WebDAV endpoint" : "WebDAV Bağlantı Noktası", "Technical details" : "Teknik ayrıntılar", "Remote Address: %s" : "Uzak Adres: %s", "Request ID: %s" : "İstek Kodu: %s", diff --git a/apps/dav/lib/BackgroundJob/CleanupInvitationTokenJob.php b/apps/dav/lib/BackgroundJob/CleanupInvitationTokenJob.php new file mode 100644 index 00000000000..942f8e23ecb --- /dev/null +++ b/apps/dav/lib/BackgroundJob/CleanupInvitationTokenJob.php @@ -0,0 +1,53 @@ +<?php +declare(strict_types=1); +/** + * @copyright 2018, 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\BackgroundJob; + +use OC\BackgroundJob\TimedJob; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IDBConnection; + +class CleanupInvitationTokenJob extends TimedJob { + + /** @var IDBConnection */ + private $db; + + /** @var ITimeFactory */ + private $timeFactory; + + public function __construct(IDBConnection $db, ITimeFactory $timeFactory) { + $this->db = $db; + $this->timeFactory = $timeFactory; + + $this->setInterval(60 * 60 * 24); + } + + public function run($argument) { + $query = $this->db->getQueryBuilder(); + $query->delete('calendar_invitations') + ->where($query->expr()->lt('expiration', + $query->createNamedParameter($this->timeFactory->getTime()))) + ->execute(); + } +} diff --git a/apps/dav/lib/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJob.php b/apps/dav/lib/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJob.php new file mode 100644 index 00000000000..c5f8f6586e9 --- /dev/null +++ b/apps/dav/lib/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJob.php @@ -0,0 +1,337 @@ +<?php +/** + * @copyright 2018, 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\BackgroundJob; + +use OC\BackgroundJob\TimedJob; +use OCA\DAV\CalDAV\CalDavBackend; +use OCP\Calendar\BackendTemporarilyUnavailableException; +use OCP\Calendar\Resource\IManager as IResourceManager; +use OCP\Calendar\Resource\IResource; +use OCP\Calendar\Room\IManager as IRoomManager; +use OCP\Calendar\Room\IRoom; +use OCP\IDBConnection; + +class UpdateCalendarResourcesRoomsBackgroundJob extends TimedJob { + + /** @var IResourceManager */ + private $resourceManager; + + /** @var IRoomManager */ + private $roomManager; + + /** @var IDBConnection */ + private $db; + + /** @var CalDavBackend */ + private $calDavBackend; + + /** @var string */ + private $resourceDbTable; + + /** @var string */ + private $resourcePrincipalUri; + + /** @var string */ + private $roomDbTable; + + /** @var string */ + private $roomPrincipalUri; + + /** + * UpdateCalendarResourcesRoomsBackgroundJob constructor. + * + * @param IResourceManager $resourceManager + * @param IRoomManager $roomManager + * @param IDBConnection $dbConnection + * @param CalDavBackend $calDavBackend + */ + public function __construct(IResourceManager $resourceManager, IRoomManager $roomManager, + IDBConnection $dbConnection, CalDavBackend $calDavBackend) { + $this->resourceManager = $resourceManager; + $this->roomManager = $roomManager; + $this->db = $dbConnection; + $this->calDavBackend = $calDavBackend; + $this->resourceDbTable = 'calendar_resources'; + $this->resourcePrincipalUri = 'principals/calendar-resources'; + $this->roomDbTable = 'calendar_rooms'; + $this->roomPrincipalUri = 'principals/calendar-rooms'; + + // run once an hour + $this->setInterval(60 * 60); + } + + /** + * @param $argument + */ + public function run($argument) { + $this->runResources(); + $this->runRooms(); + } + + /** + * run timed job for resources + */ + private function runResources() { + $resourceBackends = $this->resourceManager->getBackends(); + $cachedResources = $this->getCached($this->resourceDbTable); + $cachedResourceIds = $this->getCachedResourceIds($cachedResources); + + $remoteResourceIds = []; + foreach($resourceBackends as $resourceBackend) { + try { + $remoteResourceIds[$resourceBackend->getBackendIdentifier()] = + $resourceBackend->listAllResources(); + } catch(BackendTemporarilyUnavailableException $ex) { + // If the backend is temporarily unavailable + // ignore this backend in this execution + unset($cachedResourceIds[$resourceBackend->getBackendIdentifier()]); + } + } + + $sortedResources = $this->sortByNewDeletedExisting($cachedResourceIds, $remoteResourceIds); + + foreach($sortedResources['new'] as $backendId => $newResources) { + foreach ($newResources as $newResource) { + $resource = $this->resourceManager->getBackend($backendId) + ->getResource($newResource); + $this->addToCache($this->resourceDbTable, $resource); + } + } + foreach($sortedResources['deleted'] as $backendId => $deletedResources) { + foreach ($deletedResources as $deletedResource) { + $this->deleteFromCache($this->resourceDbTable, + $this->resourcePrincipalUri, $backendId, $deletedResource); + } + } + foreach($sortedResources['edited'] as $backendId => $editedResources) { + foreach ($editedResources as $editedResource) { + $resource = $this->resourceManager->getBackend($backendId) + ->getResource($editedResource); + $this->updateCache($this->resourceDbTable, $resource); + } + } + } + + /** + * run timed job for rooms + */ + private function runRooms() { + $roomBackends = $this->roomManager->getBackends(); + $cachedRooms = $this->getCached($this->roomDbTable); + $cachedRoomIds = $this->getCachedRoomIds($cachedRooms); + + $remoteRoomIds = []; + foreach($roomBackends as $roomBackend) { + try { + $remoteRoomIds[$roomBackend->getBackendIdentifier()] = + $roomBackend->listAllRooms(); + } catch(BackendTemporarilyUnavailableException $ex) { + // If the backend is temporarily unavailable + // ignore this backend in this execution + unset($cachedRoomIds[$roomBackend->getBackendIdentifier()]); + } + } + + $sortedRooms = $this->sortByNewDeletedExisting($cachedRoomIds, $remoteRoomIds); + + foreach($sortedRooms['new'] as $backendId => $newRooms) { + foreach ($newRooms as $newRoom) { + $resource = $this->roomManager->getBackend($backendId) + ->getRoom($newRoom); + $this->addToCache($this->roomDbTable, $resource); + } + } + foreach($sortedRooms['deleted'] as $backendId => $deletedRooms) { + foreach ($deletedRooms as $deletedRoom) { + $this->deleteFromCache($this->roomDbTable, + $this->roomPrincipalUri, $backendId, $deletedRoom); + } + } + foreach($sortedRooms['edited'] as $backendId => $editedRooms) { + foreach ($editedRooms as $editedRoom) { + $resource = $this->roomManager->getBackend($backendId) + ->getRoom($editedRoom); + $this->updateCache($this->roomDbTable, $resource); + } + } + } + + /** + * get cached db rows for resources / rooms + * @param string $tableName + * @return array + */ + private function getCached($tableName):array { + $query = $this->db->getQueryBuilder(); + $query->select('*')->from($tableName); + + $rows = []; + $stmt = $query->execute(); + while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + $rows[] = $row; + } + + return $rows; + } + + /** + * @param array $cachedResources + * @return array + */ + private function getCachedResourceIds(array $cachedResources):array { + $cachedResourceIds = []; + foreach ($cachedResources as $cachedResource) { + if (!isset($cachedResourceIds[$cachedResource['backend_id']])) { + $cachedResourceIds[$cachedResource['backend_id']] = []; + } + + $cachedResourceIds[$cachedResource['backend_id']][] = + $cachedResource['resource_id']; + } + + return $cachedResourceIds; + } + + /** + * @param array $cachedRooms + * @return array + */ + private function getCachedRoomIds(array $cachedRooms):array { + $cachedRoomIds = []; + foreach ($cachedRooms as $cachedRoom) { + if (!isset($cachedRoomIds[$cachedRoom['backend_id']])) { + $cachedRoomIds[$cachedRoom['backend_id']] = []; + } + + $cachedRoomIds[$cachedRoom['backend_id']][] = + $cachedRoom['resource_id']; + } + + return $cachedRoomIds; + } + + /** + * sort list of ids by whether they appear only in the backend / + * only in the cache / in both + * + * @param array $cached + * @param array $remote + * @return array + */ + private function sortByNewDeletedExisting(array $cached, array $remote):array { + $sorted = [ + 'new' => [], + 'deleted' => [], + 'edited' => [], + ]; + + $backendIds = array_merge(array_keys($cached), array_keys($remote)); + foreach($backendIds as $backendId) { + if (!isset($cached[$backendId])) { + $sorted['new'][$backendId] = $remote[$backendId]; + } elseif (!isset($remote[$backendId])) { + $sorted['deleted'][$backendId] = $cached[$backendId]; + } else { + $sorted['new'][$backendId] = array_diff($remote[$backendId], $cached[$backendId]); + $sorted['deleted'][$backendId] = array_diff($cached[$backendId], $remote[$backendId]); + $sorted['edited'][$backendId] = array_intersect($remote[$backendId], $cached[$backendId]); + } + } + + return $sorted; + } + + /** + * add entry to cache that exists remotely but not yet in cache + * + * @param string $table + * @param IResource|IRoom $remote + */ + private function addToCache($table, $remote) { + $query = $this->db->getQueryBuilder(); + $query->insert($table) + ->values([ + 'backend_id' => $query->createNamedParameter($remote->getBackend()->getBackendIdentifier()), + 'resource_id' => $query->createNamedParameter($remote->getId()), + 'email' => $query->createNamedParameter($remote->getEMail()), + 'displayname' => $query->createNamedParameter($remote->getDisplayName()), + 'group_restrictions' => $query->createNamedParameter( + $this->serializeGroupRestrictions( + $remote->getGroupRestrictions() + )) + ]) + ->execute(); + } + + /** + * delete entry from cache that does not exist anymore remotely + * + * @param string $table + * @param string $principalUri + * @param string $backendId + * @param string $resourceId + */ + private function deleteFromCache($table, $principalUri, $backendId, $resourceId) { + $query = $this->db->getQueryBuilder(); + $query->delete($table) + ->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId))) + ->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId))) + ->execute(); + + $calendar = $this->calDavBackend->getCalendarByUri($principalUri, implode('-', [$backendId, $resourceId])); + if ($calendar !== null) { + $this->calDavBackend->deleteCalendar($calendar['id']); + } + } + + /** + * update an existing entry in cache + * + * @param string $table + * @param IResource|IRoom $remote + */ + private function updateCache($table, $remote) { + $query = $this->db->getQueryBuilder(); + $query->update($table) + ->set('email', $query->createNamedParameter($remote->getEMail())) + ->set('displayname', $query->createNamedParameter($remote->getDisplayName())) + ->set('group_restrictions', $query->createNamedParameter( + $this->serializeGroupRestrictions( + $remote->getGroupRestrictions() + ))) + ->where($query->expr()->eq('backend_id', $query->createNamedParameter($remote->getBackend()->getBackendIdentifier()))) + ->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($remote->getId()))) + ->execute(); + } + + /** + * serialize array of group restrictions to store them in database + * + * @param array $groups + * @return string + */ + private function serializeGroupRestrictions(array $groups):string { + return \json_encode($groups); + } +} diff --git a/apps/dav/lib/CalDAV/Activity/Filter/Calendar.php b/apps/dav/lib/CalDAV/Activity/Filter/Calendar.php index 7456074915b..f85ca1a7769 100644 --- a/apps/dav/lib/CalDAV/Activity/Filter/Calendar.php +++ b/apps/dav/lib/CalDAV/Activity/Filter/Calendar.php @@ -72,7 +72,7 @@ class Calendar implements IFilter { * @since 11.0.0 */ public function getIcon() { - return $this->url->getAbsoluteURL($this->url->imagePath('core', 'places/calendar-dark.svg')); + return $this->url->getAbsoluteURL($this->url->imagePath('core', 'places/calendar.svg')); } /** diff --git a/apps/dav/lib/CalDAV/Activity/Provider/Calendar.php b/apps/dav/lib/CalDAV/Activity/Provider/Calendar.php index db79b0f6656..45bc3d71c4a 100644 --- a/apps/dav/lib/CalDAV/Activity/Provider/Calendar.php +++ b/apps/dav/lib/CalDAV/Activity/Provider/Calendar.php @@ -176,6 +176,8 @@ class Calendar extends Base { case self::SUBJECT_DELETE . '_self': case self::SUBJECT_UPDATE: case self::SUBJECT_UPDATE . '_self': + case self::SUBJECT_PUBLISH . '_self': + case self::SUBJECT_UNPUBLISH . '_self': case self::SUBJECT_SHARE_USER: case self::SUBJECT_UNSHARE_USER: case self::SUBJECT_UNSHARE_USER . '_self': diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index b28c8534aaa..de46dfeb244 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -76,6 +76,9 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription const PERSONAL_CALENDAR_URI = 'personal'; const PERSONAL_CALENDAR_NAME = 'Personal'; + const RESOURCE_BOOKING_CALENDAR_URI = 'calendar'; + const RESOURCE_BOOKING_CALENDAR_NAME = 'Calendar'; + /** * We need to specify a max date, because we need to stop *somewhere* * diff --git a/apps/dav/lib/CalDAV/Calendar.php b/apps/dav/lib/CalDAV/Calendar.php index 02808ab5662..a07bbe93218 100644 --- a/apps/dav/lib/CalDAV/Calendar.php +++ b/apps/dav/lib/CalDAV/Calendar.php @@ -203,7 +203,7 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable { } $this->caldavBackend->updateShares($this, [], [ - 'href' => $principal + $principal ]); return; } diff --git a/apps/dav/lib/CalDAV/CalendarRoot.php b/apps/dav/lib/CalDAV/CalendarRoot.php index 2c1c8bb4ef2..f84e8a96780 100644 --- a/apps/dav/lib/CalDAV/CalendarRoot.php +++ b/apps/dav/lib/CalDAV/CalendarRoot.php @@ -27,4 +27,15 @@ class CalendarRoot extends \Sabre\CalDAV\CalendarRoot { function getChildForPrincipal(array $principal) { return new CalendarHome($this->caldavBackend, $principal); } + + function getName() { + if ($this->principalPrefix === 'principals/calendar-resources' || + $this->principalPrefix === 'principals/calendar-rooms') { + $parts = explode('/', $this->principalPrefix); + + return $parts[1]; + } + + return parent::getName(); + } }
\ No newline at end of file diff --git a/apps/dav/lib/CalDAV/InvitationResponse/InvitationResponseServer.php b/apps/dav/lib/CalDAV/InvitationResponse/InvitationResponseServer.php new file mode 100644 index 00000000000..61ead99ce12 --- /dev/null +++ b/apps/dav/lib/CalDAV/InvitationResponse/InvitationResponseServer.php @@ -0,0 +1,118 @@ +<?php +/** + * @copyright Copyright (c) 2018, Georg Ehrke. + * + * @author Georg Ehrke <oc.list@georgehrke.com> + * + * @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\InvitationResponse; + +use OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin; +use OCA\DAV\Connector\Sabre\CachingTree; +use OCA\DAV\Connector\Sabre\DavAclPlugin; +use OCA\DAV\Connector\Sabre\AnonymousOptionsPlugin; +use OCA\DAV\RootCollection; +use OCP\SabrePluginEvent; +use Sabre\DAV\Auth\Plugin; +use OCA\DAV\AppInfo\PluginManager; +use Sabre\VObject\ITip\Message; + +class InvitationResponseServer { + + /** @var \OCA\DAV\Connector\Sabre\Server */ + public $server; + + /** + * InvitationResponseServer constructor. + */ + public function __construct() { + $baseUri = \OC::$WEBROOT . '/remote.php/dav/'; + $logger = \OC::$server->getLogger(); + $dispatcher = \OC::$server->getEventDispatcher(); + + $root = new RootCollection(); + $this->server = new \OCA\DAV\Connector\Sabre\Server(new CachingTree($root)); + + // Add maintenance plugin + $this->server->addPlugin(new \OCA\DAV\Connector\Sabre\MaintenancePlugin(\OC::$server->getConfig())); + + // Set URL explicitly due to reverse-proxy situations + $this->server->httpRequest->setUrl($baseUri); + $this->server->setBaseUri($baseUri); + + $this->server->addPlugin(new BlockLegacyClientPlugin(\OC::$server->getConfig())); + $this->server->addPlugin(new AnonymousOptionsPlugin()); + $this->server->addPlugin(new class() extends Plugin { + public function getCurrentPrincipal() { + return 'principals/system/public'; + } + }); + + // allow setup of additional auth backends + $event = new SabrePluginEvent($this->server); + $dispatcher->dispatch('OCA\DAV\Connector\Sabre::authInit', $event); + + $this->server->addPlugin(new \OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin('webdav', $logger)); + $this->server->addPlugin(new \OCA\DAV\Connector\Sabre\LockPlugin()); + $this->server->addPlugin(new \Sabre\DAV\Sync\Plugin()); + + // acl + $acl = new DavAclPlugin(); + $acl->principalCollectionSet = [ + 'principals/users', 'principals/groups' + ]; + $acl->defaultUsernamePath = 'principals/users'; + $this->server->addPlugin($acl); + + // calendar plugins + $this->server->addPlugin(new \OCA\DAV\CalDAV\Plugin()); + $this->server->addPlugin(new \Sabre\CalDAV\ICSExportPlugin()); + $this->server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin()); + $this->server->addPlugin(new \Sabre\CalDAV\Subscriptions\Plugin()); + $this->server->addPlugin(new \Sabre\CalDAV\Notifications\Plugin()); + //$this->server->addPlugin(new \OCA\DAV\DAV\Sharing\Plugin($authBackend, \OC::$server->getRequest())); + $this->server->addPlugin(new \OCA\DAV\CalDAV\Publishing\PublishPlugin( + \OC::$server->getConfig(), + \OC::$server->getURLGenerator() + )); + + // wait with registering these until auth is handled and the filesystem is setup + $this->server->on('beforeMethod', function () use ($root) { + // register plugins from apps + $pluginManager = new PluginManager( + \OC::$server, + \OC::$server->getAppManager() + ); + foreach ($pluginManager->getAppPlugins() as $appPlugin) { + $this->server->addPlugin($appPlugin); + } + foreach ($pluginManager->getAppCollections() as $appCollection) { + $root->addChild($appCollection); + } + }); + } + + /** + * @param Message $iTipMessage + * @return void + */ + public function handleITipMessage(Message $iTipMessage) { + /** @var \OCA\DAV\CalDAV\Schedule\Plugin $schedulingPlugin */ + $schedulingPlugin = $this->server->getPlugin('caldav-schedule'); + $schedulingPlugin->scheduleLocalDelivery($iTipMessage); + } +}
\ No newline at end of file diff --git a/apps/dav/lib/CalDAV/Plugin.php b/apps/dav/lib/CalDAV/Plugin.php index 4aa9762899f..f37d9c571a0 100644 --- a/apps/dav/lib/CalDAV/Plugin.php +++ b/apps/dav/lib/CalDAV/Plugin.php @@ -25,15 +25,27 @@ namespace OCA\DAV\CalDAV; class Plugin extends \Sabre\CalDAV\Plugin { + const SYSTEM_CALENDAR_ROOT = 'system-calendars'; + /** * @inheritdoc */ - function getCalendarHomeForPrincipal($principalUrl) { + function getCalendarHomeForPrincipal($principalUrl):string { if (strrpos($principalUrl, 'principals/users', -strlen($principalUrl)) !== false) { list(, $principalId) = \Sabre\Uri\split($principalUrl); - return self::CALENDAR_ROOT .'/' . $principalId; + return self::CALENDAR_ROOT . '/' . $principalId; + } + if (strrpos($principalUrl, 'principals/calendar-resources', -strlen($principalUrl)) !== false) { + list(, $principalId) = \Sabre\Uri\split($principalUrl); + return self::SYSTEM_CALENDAR_ROOT . '/calendar-resources/' . $principalId; } + if (strrpos($principalUrl, 'principals/calendar-rooms', -strlen($principalUrl)) !== false) { + list(, $principalId) = \Sabre\Uri\split($principalUrl); + return self::SYSTEM_CALENDAR_ROOT . '/calendar-rooms/' . $principalId; + } + + throw new \LogicException('This is not supposed to happen'); } } diff --git a/apps/dav/lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php b/apps/dav/lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php new file mode 100644 index 00000000000..dcd393b512e --- /dev/null +++ b/apps/dav/lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php @@ -0,0 +1,361 @@ +<?php +/** + * @copyright 2018, 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\ResourceBooking; + +use OCP\IDBConnection; +use OCP\IGroupManager; +use OCP\ILogger; +use OCP\IUserSession; +use Sabre\DAVACL\PrincipalBackend\BackendInterface; +use Sabre\DAV\Exception; +use \Sabre\DAV\PropPatch; + +abstract class AbstractPrincipalBackend implements BackendInterface { + + /** @var IDBConnection */ + private $db; + + /** @var IUserSession */ + private $userSession; + + /** @var IGroupManager */ + private $groupManager; + + /** @var ILogger */ + private $logger; + + /** @var string */ + private $principalPrefix; + + /** @var string */ + private $dbTableName; + + /** + * @param IDBConnection $dbConnection + * @param IUserSession $userSession + * @param IGroupManager $groupManager + * @param ILogger $logger + * @param string $principalPrefix + * @param string $dbPrefix + */ + public function __construct(IDBConnection $dbConnection, + IUserSession $userSession, + IGroupManager $groupManager, + ILogger $logger, + $principalPrefix, $dbPrefix) { + $this->db = $dbConnection; + $this->userSession = $userSession; + $this->groupManager = $groupManager; + $this->logger = $logger; + $this->principalPrefix = $principalPrefix; + $this->dbTableName = 'calendar_' . $dbPrefix; + } + + /** + * Returns a list of principals based on a prefix. + * + * This prefix will often contain something like 'principals'. You are only + * expected to return principals that are in this base path. + * + * You are expected to return at least a 'uri' for every user, you can + * return any additional properties if you wish so. Common properties are: + * {DAV:}displayname + * + * @param string $prefixPath + * @return string[] + */ + public function getPrincipalsByPrefix($prefixPath) { + $principals = []; + + if ($prefixPath === $this->principalPrefix) { + $query = $this->db->getQueryBuilder(); + $query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname']) + ->from($this->dbTableName); + $stmt = $query->execute(); + + while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + $principals[] = $this->rowToPrincipal($row); + } + + $stmt->closeCursor(); + } + + return $principals; + } + + /** + * Returns a specific principal, specified by it's path. + * The returned structure should be the exact same as from + * getPrincipalsByPrefix. + * + * @param string $path + * @return array + */ + public function getPrincipalByPath($path) { + if (strpos($path, $this->principalPrefix) !== 0) { + return null; + } + list(, $name) = \Sabre\Uri\split($path); + + list($backendId, $resourceId) = explode('-', $name, 2); + + $query = $this->db->getQueryBuilder(); + $query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname']) + ->from($this->dbTableName) + ->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId))) + ->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId))); + $stmt = $query->execute(); + $row = $stmt->fetch(\PDO::FETCH_ASSOC); + + if(!$row) { + return null; + } + + return $this->rowToPrincipal($row); + } + + /** + * Returns the list of members for a group-principal + * + * @param string $principal + * @return string[] + */ + public function getGroupMemberSet($principal) { + return []; + } + + /** + * Returns the list of groups a principal is a member of + * + * @param string $principal + * @return array + */ + public function getGroupMembership($principal) { + return []; + } + + /** + * Updates the list of group members for a group principal. + * + * The principals should be passed as a list of uri's. + * + * @param string $principal + * @param string[] $members + * @throws Exception + */ + public function setGroupMemberSet($principal, array $members) { + throw new Exception('Setting members of the group is not supported yet'); + } + + /** + * @param string $path + * @param PropPatch $propPatch + * @return int + */ + function updatePrincipal($path, PropPatch $propPatch) { + return 0; + } + + /** + * @param string $prefixPath + * @param array $searchProperties + * @param string $test + * @return array + */ + function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') { + $results = []; + if (\count($searchProperties) === 0) { + return []; + } + if ($prefixPath !== $this->principalPrefix) { + return []; + } + + $user = $this->userSession->getUser(); + if (!$user) { + return []; + } + $usersGroups = $this->groupManager->getUserGroupIds($user); + + foreach ($searchProperties as $prop => $value) { + switch ($prop) { + case '{http://sabredav.org/ns}email-address': + $query = $this->db->getQueryBuilder(); + $query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions']) + ->from($this->dbTableName) + ->where($query->expr()->iLike('email', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($value) . '%'))); + + $stmt = $query->execute(); + $principals = []; + while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + if (!$this->isAllowedToAccessResource($row, $usersGroups)) { + continue; + } + $principals[] = $this->rowToPrincipal($row)['uri']; + } + $results[] = $principals; + + $stmt->closeCursor(); + break; + + case '{DAV:}displayname': + $query = $this->db->getQueryBuilder(); + $query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions']) + ->from($this->dbTableName) + ->where($query->expr()->iLike('displayname', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($value) . '%'))); + + $stmt = $query->execute(); + $principals = []; + while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + if (!$this->isAllowedToAccessResource($row, $usersGroups)) { + continue; + } + $principals[] = $this->rowToPrincipal($row)['uri']; + } + $results[] = $principals; + + $stmt->closeCursor(); + break; + + default: + $results[] = []; + break; + } + } + + // results is an array of arrays, so this is not the first search result + // but the results of the first searchProperty + if (count($results) === 1) { + return $results[0]; + } + + switch ($test) { + case 'anyof': + return array_values(array_unique(array_merge(...$results))); + + case 'allof': + default: + return array_values(array_intersect(...$results)); + } + } + + /** + * @param string $uri + * @param string $principalPrefix + * @return null|string + */ + function findByUri($uri, $principalPrefix) { + $user = $this->userSession->getUser(); + if (!$user) { + return null; + } + $usersGroups = $this->groupManager->getUserGroupIds($user); + + if (strpos($uri, 'mailto:') === 0) { + $email = substr($uri, 7); + $query = $this->db->getQueryBuilder(); + $query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions']) + ->from($this->dbTableName) + ->where($query->expr()->eq('email', $query->createNamedParameter($email))); + + $stmt = $query->execute(); + $row = $stmt->fetch(\PDO::FETCH_ASSOC); + + if(!$row) { + return null; + } + if (!$this->isAllowedToAccessResource($row, $usersGroups)) { + return null; + } + + return $this->rowToPrincipal($row)['uri']; + } + + if (strpos($uri, 'principal:') === 0) { + $path = substr($uri, 10); + if (strpos($path, $this->principalPrefix) !== 0) { + return null; + } + + list(, $name) = \Sabre\Uri\split($path); + list($backendId, $resourceId) = explode('-', $name, 2); + + $query = $this->db->getQueryBuilder(); + $query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions']) + ->from($this->dbTableName) + ->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId))) + ->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId))); + $stmt = $query->execute(); + $row = $stmt->fetch(\PDO::FETCH_ASSOC); + + if(!$row) { + return null; + } + if (!$this->isAllowedToAccessResource($row, $usersGroups)) { + return null; + } + + return $this->rowToPrincipal($row)['uri']; + } + + return null; + } + + /** + * convert database row to principal + */ + private function rowToPrincipal($row) { + return [ + 'uri' => $this->principalPrefix . '/' . $row['backend_id'] . '-' . $row['resource_id'], + '{DAV:}displayname' => $row['displayname'], + '{http://sabredav.org/ns}email-address' => $row['email'] + ]; + } + + /** + * @param $row + * @param $userGroups + * @return bool + */ + private function isAllowedToAccessResource($row, $userGroups) { + if (!isset($row['group_restrictions']) || + $row['group_restrictions'] === null || + $row['group_restrictions'] === '') { + return true; + } + + // group restrictions contains something, but not parsable, deny access and log warning + $json = json_decode($row['group_restrictions']); + if (!\is_array($json)) { + $this->logger->info('group_restrictions field could not be parsed for ' . $this->dbTableName . '::' . $row['id'] . ', denying access to resource'); + return false; + } + + // empty array => no group restrictions + if (empty($json)) { + return true; + } + + return !empty(array_intersect($json, $userGroups)); + } +} diff --git a/apps/dav/lib/CalDAV/ResourceBooking/ResourcePrincipalBackend.php b/apps/dav/lib/CalDAV/ResourceBooking/ResourcePrincipalBackend.php new file mode 100644 index 00000000000..a1030376c11 --- /dev/null +++ b/apps/dav/lib/CalDAV/ResourceBooking/ResourcePrincipalBackend.php @@ -0,0 +1,45 @@ +<?php +/** + * @copyright 2018, 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\ResourceBooking; + +use OCP\IDBConnection; +use OCP\IGroupManager; +use OCP\ILogger; +use OCP\IUserSession; + +class ResourcePrincipalBackend extends AbstractPrincipalBackend { + + /** + * @param IDBConnection $dbConnection + * @param IUserSession $userSession + * @param IGroupManager $groupManager + * @param ILogger $logger + */ + public function __construct(IDBConnection $dbConnection, + IUserSession $userSession, + IGroupManager $groupManager, + ILogger $logger) { + parent::__construct($dbConnection, $userSession, $groupManager, $logger, + 'principals/calendar-resources', 'resources'); + } +} diff --git a/apps/dav/lib/CalDAV/ResourceBooking/RoomPrincipalBackend.php b/apps/dav/lib/CalDAV/ResourceBooking/RoomPrincipalBackend.php new file mode 100644 index 00000000000..1d22299515f --- /dev/null +++ b/apps/dav/lib/CalDAV/ResourceBooking/RoomPrincipalBackend.php @@ -0,0 +1,45 @@ +<?php +/** + * @copyright 2018, 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\ResourceBooking; + +use OCP\IDBConnection; +use OCP\IGroupManager; +use OCP\ILogger; +use OCP\IUserSession; + +class RoomPrincipalBackend extends AbstractPrincipalBackend { + + /** + * @param IDBConnection $dbConnection + * @param IUserSession $userSession + * @param IGroupManager $groupManager + * @param ILogger $logger + */ + public function __construct(IDBConnection $dbConnection, + IUserSession $userSession, + IGroupManager $groupManager, + ILogger $logger) { + parent::__construct($dbConnection, $userSession, $groupManager, $logger, + 'principals/calendar-rooms', 'rooms'); + } +} diff --git a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php index 85973a8be12..4065c21b8a1 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php @@ -28,12 +28,14 @@ namespace OCA\DAV\CalDAV\Schedule; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Defaults; use OCP\IConfig; +use OCP\IDBConnection; use OCP\IL10N; use OCP\ILogger; use OCP\IURLGenerator; use OCP\L10N\IFactory as L10NFactory; use OCP\Mail\IEMailTemplate; use OCP\Mail\IMailer; +use OCP\Security\ISecureRandom; use Sabre\CalDAV\Schedule\IMipPlugin as SabreIMipPlugin; use Sabre\VObject\Component\VCalendar; use Sabre\VObject\Component\VEvent; @@ -79,6 +81,12 @@ class IMipPlugin extends SabreIMipPlugin { /** @var IURLGenerator */ private $urlGenerator; + /** @var ISecureRandom */ + private $random; + + /** @var IDBConnection */ + private $db; + /** @var Defaults */ private $defaults; @@ -96,9 +104,14 @@ class IMipPlugin extends SabreIMipPlugin { * @param L10NFactory $l10nFactory * @param IUrlGenerator $urlGenerator * @param Defaults $defaults + * @param ISecureRandom $random + * @param IDBConnection $db * @param string $userId */ - public function __construct(IConfig $config, IMailer $mailer, ILogger $logger, ITimeFactory $timeFactory, L10NFactory $l10nFactory, IURLGenerator $urlGenerator, Defaults $defaults, $userId) { + public function __construct(IConfig $config, IMailer $mailer, ILogger $logger, + ITimeFactory $timeFactory, L10NFactory $l10nFactory, + IURLGenerator $urlGenerator, Defaults $defaults, + ISecureRandom $random, IDBConnection $db, $userId) { parent::__construct(''); $this->userId = $userId; $this->config = $config; @@ -107,6 +120,8 @@ class IMipPlugin extends SabreIMipPlugin { $this->timeFactory = $timeFactory; $this->l10nFactory = $l10nFactory; $this->urlGenerator = $urlGenerator; + $this->random = $random; + $this->db = $db; $this->defaults = $defaults; } @@ -138,7 +153,9 @@ class IMipPlugin extends SabreIMipPlugin { } // don't send out mails for events that already took place - if ($this->isEventInThePast($iTipMessage->message)) { + $lastOccurrence = $this->getLastOccurrence($iTipMessage->message); + $currentTime = $this->timeFactory->getTime(); + if ($lastOccurrence < $currentTime) { return; } @@ -222,6 +239,7 @@ class IMipPlugin extends SabreIMipPlugin { $meetingAttendeeName, $meetingInviteeName); $this->addBulletList($template, $l10n, $meetingWhen, $meetingLocation, $meetingDescription, $meetingUrl); + $this->addResponseButtons($template, $l10n, $iTipMessage, $lastOccurrence); $template->addFooter(); $message->useTemplate($template); @@ -249,9 +267,9 @@ class IMipPlugin extends SabreIMipPlugin { /** * check if event took place in the past already * @param VCalendar $vObject - * @return bool + * @return int */ - private function isEventInThePast(VCalendar $vObject) { + private function getLastOccurrence(VCalendar $vObject) { /** @var VEvent $component */ $component = $vObject->VEVENT; @@ -291,8 +309,7 @@ class IMipPlugin extends SabreIMipPlugin { } } - $currentTime = $this->timeFactory->getTime(); - return $lastOccurrence < $currentTime; + return $lastOccurrence; } @@ -460,6 +477,38 @@ class IMipPlugin extends SabreIMipPlugin { } /** + * @param IEMailTemplate $template + * @param IL10N $l10n + * @param Message $iTipMessage + * @param int $lastOccurrence + */ + private function addResponseButtons(IEMailTemplate $template, IL10N $l10n, + Message $iTipMessage, $lastOccurrence) { + $token = $this->createInvitationToken($iTipMessage, $lastOccurrence); + + $template->addBodyButtonGroup( + $l10n->t('Accept'), + $this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.accept', [ + 'token' => $token, + ]), + $l10n->t('Decline'), + $this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.decline', [ + 'token' => $token, + ]) + ); + + $moreOptionsURL = $this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.options', [ + 'token' => $token, + ]); + $html = vsprintf('<small><a href="%s">%s</a></small>', [ + $moreOptionsURL, $l10n->t('More options ...') + ]); + $text = $l10n->t('More options at %s', [$moreOptionsURL]); + + $template->addBodyText($html, $text); + } + + /** * @param string $path * @return string */ @@ -468,4 +517,37 @@ class IMipPlugin extends SabreIMipPlugin { $this->urlGenerator->imagePath('core', $path) ); } + + /** + * @param Message $iTipMessage + * @param int $lastOccurrence + * @return string + */ + private function createInvitationToken(Message $iTipMessage, $lastOccurrence):string { + $token = $this->random->generate(60, ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS); + + /** @var VEvent $vevent */ + $vevent = $iTipMessage->message->VEVENT; + $attendee = $iTipMessage->recipient; + $organizer = $iTipMessage->sender; + $sequence = $iTipMessage->sequence; + $recurrenceId = isset($vevent->{'RECURRENCE-ID'}) ? + $vevent->{'RECURRENCE-ID'}->serialize() : null; + $uid = $vevent->{'UID'}; + + $query = $this->db->getQueryBuilder(); + $query->insert('calendar_invitations') + ->values([ + 'token' => $query->createNamedParameter($token), + 'attendee' => $query->createNamedParameter($attendee), + 'organizer' => $query->createNamedParameter($organizer), + 'sequence' => $query->createNamedParameter($sequence), + 'recurrenceid' => $query->createNamedParameter($recurrenceId), + 'expiration' => $query->createNamedParameter($lastOccurrence), + 'uid' => $query->createNamedParameter($uid) + ]) + ->execute(); + + return $token; + } } diff --git a/apps/dav/lib/CalDAV/Schedule/Plugin.php b/apps/dav/lib/CalDAV/Schedule/Plugin.php index 34df666637c..b3f7232c2fe 100644 --- a/apps/dav/lib/CalDAV/Schedule/Plugin.php +++ b/apps/dav/lib/CalDAV/Schedule/Plugin.php @@ -31,6 +31,10 @@ use Sabre\DAV\PropFind; use Sabre\DAV\Server; use Sabre\DAV\Xml\Property\LocalHref; use Sabre\DAVACL\IPrincipal; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\Reader; class Plugin extends \Sabre\CalDAV\Schedule\Plugin { @@ -76,20 +80,32 @@ class Plugin extends \Sabre\CalDAV\Schedule\Plugin { $principalUrl = $node->getPrincipalUrl(); $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl); - if (!$calendarHomePath) { return null; } + if (strpos($principalUrl, 'principals/users') === 0) { + $uri = CalDavBackend::PERSONAL_CALENDAR_URI; + $displayname = CalDavBackend::PERSONAL_CALENDAR_NAME; + } elseif (strpos($principalUrl, 'principals/calendar-resources') === 0 || + strpos($principalUrl, 'principals/calendar-rooms') === 0) { + $uri = CalDavBackend::RESOURCE_BOOKING_CALENDAR_URI; + $displayname = CalDavBackend::RESOURCE_BOOKING_CALENDAR_NAME; + } else { + // How did we end up here? + // TODO - throw exception or just ignore? + return null; + } + /** @var CalendarHome $calendarHome */ $calendarHome = $this->server->tree->getNodeForPath($calendarHomePath); - if (!$calendarHome->childExists(CalDavBackend::PERSONAL_CALENDAR_URI)) { - $calendarHome->getCalDAVBackend()->createCalendar($principalUrl, CalDavBackend::PERSONAL_CALENDAR_URI, [ - '{DAV:}displayname' => CalDavBackend::PERSONAL_CALENDAR_NAME, + if (!$calendarHome->childExists($uri)) { + $calendarHome->getCalDAVBackend()->createCalendar($principalUrl, $uri, [ + '{DAV:}displayname' => $displayname, ]); } - $result = $this->server->getPropertiesForPath($calendarHomePath . '/' . CalDavBackend::PERSONAL_CALENDAR_URI, [], 1); + $result = $this->server->getPropertiesForPath($calendarHomePath . '/' . $uri, [], 1); if (empty($result)) { return null; } @@ -98,4 +114,67 @@ class Plugin extends \Sabre\CalDAV\Schedule\Plugin { }); } } + + /** + * This method is triggered whenever there was a calendar object gets + * created or updated. + * + * Basically just a copy of parent::calendarObjectChange, with the change + * from: + * $addresses = $this->getAddressesForPrincipal($calendarNode->getOwner()); + * to: + * $addresses = $this->getAddressesForPrincipal($calendarNode->getPrincipalURI()); + * + * @param RequestInterface $request HTTP request + * @param ResponseInterface $response HTTP Response + * @param VCalendar $vCal Parsed iCalendar object + * @param mixed $calendarPath Path to calendar collection + * @param mixed $modified The iCalendar object has been touched. + * @param mixed $isNew Whether this was a new item or we're updating one + * @return void + */ + function calendarObjectChange(RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew) { + + if (!$this->scheduleReply($this->server->httpRequest)) { + return; + } + + $calendarNode = $this->server->tree->getNodeForPath($calendarPath); + + $addresses = $this->getAddressesForPrincipal( + $calendarNode->getPrincipalURI() + ); + + if (!$isNew) { + $node = $this->server->tree->getNodeForPath($request->getPath()); + $oldObj = Reader::read($node->get()); + } else { + $oldObj = null; + } + + $this->processICalendarChange($oldObj, $vCal, $addresses, [], $modified); + + if ($oldObj) { + // Destroy circular references so PHP will GC the object. + $oldObj->destroy(); + } + + } + + /** + * This method checks the 'Schedule-Reply' header + * and returns false if it's 'F', otherwise true. + * + * Copied from Sabre/DAV's Schedule plugin, because it's + * private for whatever reason + * + * @param RequestInterface $request + * @return bool + */ + private function scheduleReply(RequestInterface $request) { + + $scheduleReply = $request->getHeader('Schedule-Reply'); + return $scheduleReply !== 'F'; + + } } diff --git a/apps/dav/lib/CardDAV/AddressBook.php b/apps/dav/lib/CardDAV/AddressBook.php index a034f8b9426..71202319874 100644 --- a/apps/dav/lib/CardDAV/AddressBook.php +++ b/apps/dav/lib/CardDAV/AddressBook.php @@ -181,7 +181,7 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable { } $this->carddavBackend->updateShares($this, [], [ - 'href' => $principal + $principal ]); return; } diff --git a/apps/dav/lib/Command/CreateCalendar.php b/apps/dav/lib/Command/CreateCalendar.php index 1cbd7b60944..45dd9ba941a 100644 --- a/apps/dav/lib/Command/CreateCalendar.php +++ b/apps/dav/lib/Command/CreateCalendar.php @@ -77,7 +77,8 @@ class CreateCalendar extends Command { $this->userManager, $this->groupManager, \OC::$server->getShareManager(), - \OC::$server->getUserSession() + \OC::$server->getUserSession(), + \OC::$server->getConfig() ); $random = \OC::$server->getSecureRandom(); $logger = \OC::$server->getLogger(); diff --git a/apps/dav/lib/Command/RemoveInvalidShares.php b/apps/dav/lib/Command/RemoveInvalidShares.php new file mode 100644 index 00000000000..12a5ee43d47 --- /dev/null +++ b/apps/dav/lib/Command/RemoveInvalidShares.php @@ -0,0 +1,82 @@ +<?php +declare(strict_types=1); +/** + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @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\Connector\Sabre\Principal; +use OCP\IDBConnection; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Class RemoveInvalidShares - removes shared calendars and addressbook which + * have no matching principal. Happened because of a bug in the calendar app. + */ +class RemoveInvalidShares extends Command { + + /** @var IDBConnection */ + private $connection; + /** @var Principal */ + private $principalBackend; + + public function __construct(IDBConnection $connection, + Principal $principalBackend) { + parent::__construct(); + + $this->connection = $connection; + $this->principalBackend = $principalBackend; + } + + protected function configure() { + $this + ->setName('dav:remove-invalid-shares') + ->setDescription('Remove invalid dav shares'); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $query = $this->connection->getQueryBuilder(); + $result = $query->selectDistinct('principaluri') + ->from('dav_shares') + ->execute(); + + while($row = $result->fetch()) { + $principaluri = $row['principaluri']; + $p = $this->principalBackend->getPrincipalByPath($principaluri); + if ($p === null) { + $this->deleteSharesForPrincipal($principaluri); + } + } + + $result->closeCursor(); + } + + /** + * @param string $principaluri + */ + private function deleteSharesForPrincipal($principaluri) { + $delete = $this->connection->getQueryBuilder(); + $delete->delete('dav_shares') + ->where($delete->expr()->eq('principaluri', $delete->createNamedParameter($principaluri))); + $delete->execute(); + } +} diff --git a/apps/dav/lib/Connector/Sabre/AnonymousOptionsPlugin.php b/apps/dav/lib/Connector/Sabre/AnonymousOptionsPlugin.php new file mode 100644 index 00000000000..7a62f706378 --- /dev/null +++ b/apps/dav/lib/Connector/Sabre/AnonymousOptionsPlugin.php @@ -0,0 +1,66 @@ +<?php +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @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\Connector\Sabre; + +use Sabre\DAV\CorePlugin; +use Sabre\DAV\FS\Directory; +use Sabre\DAV\ServerPlugin; +use Sabre\DAV\Tree; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +class AnonymousOptionsPlugin extends ServerPlugin { + + /** + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @param \Sabre\DAV\Server $server + * @return void + */ + public function initialize(\Sabre\DAV\Server $server) { + $this->server = $server; + // before auth + $this->server->on('beforeMethod', [$this, 'handleAnonymousOptions'], 9); + } + + /** + * @throws \Sabre\DAV\Exception\Forbidden + * @return bool + */ + public function handleAnonymousOptions(RequestInterface $request, ResponseInterface $response) { + if ($request->getMethod() === 'OPTIONS' && $request->getPath() === '') { + /** @var CorePlugin $corePlugin */ + $corePlugin = $this->server->getPlugin('core'); + // setup a fake tree for anonymous access + $this->server->tree = new Tree(new Directory('')); + $corePlugin->httpOptions($request, $response); + $this->server->emit('afterMethod', [$request, $response]); + $this->server->emit('afterMethod:OPTIONS', [$request, $response]); + + $this->server->sapi->sendResponse($response); + return false; + } + } +} diff --git a/apps/dav/lib/Connector/Sabre/File.php b/apps/dav/lib/Connector/Sabre/File.php index bbd6717d94f..e46bdcb2984 100644 --- a/apps/dav/lib/Connector/Sabre/File.php +++ b/apps/dav/lib/Connector/Sabre/File.php @@ -150,12 +150,21 @@ class File extends Node implements IFile { $this->emitPreHooks($exists); } + $view = \OC\Files\Filesystem::getView(); + // the part file and target file might be on a different storage in case of a single file storage (e.g. single file share) /** @var \OC\Files\Storage\Storage $partStorage */ list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath); /** @var \OC\Files\Storage\Storage $storage */ list($storage, $internalPath) = $this->fileView->resolvePath($this->path); try { + if (!$needsPartFile) { + if ($view && !$this->emitPreHooks($exists)) { + throw new Exception('Could not write to final file, canceled by hook'); + } + $this->changeLock(ILockingProvider::LOCK_EXCLUSIVE); + } + $target = $partStorage->fopen($internalPartPath, 'wb'); if ($target === false) { \OC::$server->getLogger()->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']); @@ -184,6 +193,7 @@ class File extends Node implements IFile { } } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e); if ($needsPartFile) { $partStorage->unlink($internalPartPath); } @@ -191,26 +201,25 @@ class File extends Node implements IFile { } try { - $view = \OC\Files\Filesystem::getView(); - $run = ($view && $needsPartFile) ? $this->emitPreHooks($exists) : true; - - try { - $this->changeLock(ILockingProvider::LOCK_EXCLUSIVE); - } catch (LockedException $e) { - if ($needsPartFile) { + if ($needsPartFile) { + if ($view && !$this->emitPreHooks($exists)) { $partStorage->unlink($internalPartPath); + throw new Exception('Could not rename part file to final file, canceled by hook'); + } + try { + $this->changeLock(ILockingProvider::LOCK_EXCLUSIVE); + } catch (LockedException $e) { + if ($needsPartFile) { + $partStorage->unlink($internalPartPath); + } + throw new FileLocked($e->getMessage(), $e->getCode(), $e); } - throw new FileLocked($e->getMessage(), $e->getCode(), $e); - } - if ($needsPartFile) { // rename to correct path try { - if ($run) { - $renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath); - $fileExists = $storage->file_exists($internalPath); - } - if (!$run || $renameOkay === false || $fileExists === false) { + $renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath); + $fileExists = $storage->file_exists($internalPath); + if ($renameOkay === false || $fileExists === false) { \OC::$server->getLogger()->error('renaming part file to final file failed ($run: ' . ( $run ? 'true' : 'false' ) . ', $renameOkay: ' . ( $renameOkay ? 'true' : 'false' ) . ', $fileExists: ' . ( $fileExists ? 'true' : 'false' ) . ')', ['app' => 'webdav']); throw new Exception('Could not rename part file to final file'); } diff --git a/apps/dav/lib/Connector/Sabre/FilesPlugin.php b/apps/dav/lib/Connector/Sabre/FilesPlugin.php index f36ebe5636c..f53f13c5687 100644 --- a/apps/dav/lib/Connector/Sabre/FilesPlugin.php +++ b/apps/dav/lib/Connector/Sabre/FilesPlugin.php @@ -33,6 +33,7 @@ namespace OCA\DAV\Connector\Sabre; use OC\AppFramework\Http\Request; +use OCP\Constants; use OCP\Files\ForbiddenException; use OCP\IPreview; use Sabre\DAV\Exception\Forbidden; @@ -57,6 +58,7 @@ class FilesPlugin extends ServerPlugin { const INTERNAL_FILEID_PROPERTYNAME = '{http://owncloud.org/ns}fileid'; const PERMISSIONS_PROPERTYNAME = '{http://owncloud.org/ns}permissions'; const SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-collaboration-services.org/ns}share-permissions'; + const OCM_SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-cloud-mesh.org/ns}share-permissions'; const DOWNLOADURL_PROPERTYNAME = '{http://owncloud.org/ns}downloadURL'; const SIZE_PROPERTYNAME = '{http://owncloud.org/ns}size'; const GETETAG_PROPERTYNAME = '{DAV:}getetag'; @@ -149,6 +151,7 @@ class FilesPlugin extends ServerPlugin { $server->protectedProperties[] = self::INTERNAL_FILEID_PROPERTYNAME; $server->protectedProperties[] = self::PERMISSIONS_PROPERTYNAME; $server->protectedProperties[] = self::SHARE_PERMISSIONS_PROPERTYNAME; + $server->protectedProperties[] = self::OCM_SHARE_PERMISSIONS_PROPERTYNAME; $server->protectedProperties[] = self::SIZE_PROPERTYNAME; $server->protectedProperties[] = self::DOWNLOADURL_PROPERTYNAME; $server->protectedProperties[] = self::OWNER_ID_PROPERTYNAME; @@ -318,6 +321,14 @@ class FilesPlugin extends ServerPlugin { ); }); + $propFind->handle(self::OCM_SHARE_PERMISSIONS_PROPERTYNAME, function() use ($node, $httpRequest) { + $ncPermissions = $node->getSharePermissions( + $httpRequest->getRawServerValue('PHP_AUTH_USER') + ); + $ocmPermissions = $this->ncPermissions2ocmPermissions($ncPermissions); + return json_encode($ocmPermissions); + }); + $propFind->handle(self::GETETAG_PROPERTYNAME, function() use ($node) { return $node->getETag(); }); @@ -395,6 +406,33 @@ class FilesPlugin extends ServerPlugin { } /** + * translate Nextcloud permissions to OCM Permissions + * + * @param $ncPermissions + * @return array + */ + protected function ncPermissions2ocmPermissions($ncPermissions) { + + $ocmPermissions = []; + + if ($ncPermissions & Constants::PERMISSION_SHARE) { + $ocmPermissions[] = 'share'; + } + + if ($ncPermissions & Constants::PERMISSION_READ) { + $ocmPermissions[] = 'read'; + } + + if (($ncPermissions & Constants::PERMISSION_CREATE) || + ($ncPermissions & Constants::PERMISSION_UPDATE)) { + $ocmPermissions[] = 'write'; + } + + return $ocmPermissions; + + } + + /** * Update ownCloud-specific properties * * @param string $path diff --git a/apps/dav/lib/Connector/Sabre/Node.php b/apps/dav/lib/Connector/Sabre/Node.php index 9e78d21a39d..38d0ff57fb2 100644 --- a/apps/dav/lib/Connector/Sabre/Node.php +++ b/apps/dav/lib/Connector/Sabre/Node.php @@ -38,6 +38,7 @@ use OC\Files\Mount\MoveableMount; use OC\Files\View; use OCA\DAV\Connector\Sabre\Exception\InvalidPath; use OCP\Files\FileInfo; +use OCP\Files\StorageNotAvailableException; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager; @@ -250,15 +251,17 @@ abstract class Node implements \Sabre\DAV\INode { } } - $storage = $this->info->getStorage(); - - $path = $this->info->getInternalPath(); + try { + $storage = $this->info->getStorage(); + } catch (StorageNotAvailableException $e) { + $storage = null; + } - if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) { + if ($storage && $storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) { /** @var \OCA\Files_Sharing\SharedStorage $storage */ $permissions = (int)$storage->getShare()->getPermissions(); } else { - $permissions = $storage->getPermissions($path); + $permissions = $this->info->getPermissions(); } /* diff --git a/apps/dav/lib/Connector/Sabre/ObjectTree.php b/apps/dav/lib/Connector/Sabre/ObjectTree.php index 15988cdadb4..ae185b1a611 100644 --- a/apps/dav/lib/Connector/Sabre/ObjectTree.php +++ b/apps/dav/lib/Connector/Sabre/ObjectTree.php @@ -159,7 +159,7 @@ class ObjectTree extends CachingTree { throw new StorageNotAvailableException(); } } catch (StorageNotAvailableException $e) { - throw new \Sabre\DAV\Exception\ServiceUnavailable('Storage is temporarily not available'); + throw new \Sabre\DAV\Exception\ServiceUnavailable('Storage is temporarily not available', 0, $e); } catch (StorageInvalidException $e) { throw new \Sabre\DAV\Exception\NotFound('Storage ' . $path . ' is invalid'); } catch (LockedException $e) { diff --git a/apps/dav/lib/Connector/Sabre/Principal.php b/apps/dav/lib/Connector/Sabre/Principal.php index b2f57cf715c..feba4d04624 100644 --- a/apps/dav/lib/Connector/Sabre/Principal.php +++ b/apps/dav/lib/Connector/Sabre/Principal.php @@ -30,6 +30,7 @@ namespace OCA\DAV\Connector\Sabre; +use OCP\IConfig; use OCP\IGroup; use OCP\IGroupManager; use OCP\IUser; @@ -54,6 +55,9 @@ class Principal implements BackendInterface { /** @var IUserSession */ private $userSession; + /** @var IConfig */ + private $config; + /** @var string */ private $principalPrefix; @@ -65,17 +69,20 @@ class Principal implements BackendInterface { * @param IGroupManager $groupManager * @param IShareManager $shareManager * @param IUserSession $userSession + * @param IConfig $config * @param string $principalPrefix */ public function __construct(IUserManager $userManager, IGroupManager $groupManager, IShareManager $shareManager, IUserSession $userSession, + IConfig $config, $principalPrefix = 'principals/users/') { $this->userManager = $userManager; $this->groupManager = $groupManager; $this->shareManager = $shareManager; $this->userSession = $userSession; + $this->config = $config; $this->principalPrefix = trim($principalPrefix, '/'); $this->hasGroups = ($principalPrefix === 'principals/users/'); } @@ -205,8 +212,10 @@ class Principal implements BackendInterface { protected function searchUserPrincipals(array $searchProperties, $test = 'allof') { $results = []; - // If sharing is disabled, return the empty array - if (!$this->shareManager->shareApiEnabled()) { + // If sharing is disabled (or FreeBusy was disabled on purpose), return the empty array + $shareAPIEnabled = $this->shareManager->shareApiEnabled(); + $disableFreeBusy = $this->config->getAppValue('dav', 'disableFreeBusy', $shareAPIEnabled ? 'no' : 'yes'); + if ($disableFreeBusy === 'yes') { return []; } @@ -289,8 +298,10 @@ class Principal implements BackendInterface { * @return string */ function findByUri($uri, $principalPrefix) { - // If sharing is disabled, return null as in user not found - if (!$this->shareManager->shareApiEnabled()) { + // If sharing is disabled (or FreeBusy was disabled on purpose), return the empty array + $shareAPIEnabled = $this->shareManager->shareApiEnabled(); + $disableFreeBusy = $this->config->getAppValue('dav', 'disableFreeBusy', $shareAPIEnabled ? 'no' : 'yes'); + if ($disableFreeBusy === 'yes') { return null; } @@ -324,6 +335,13 @@ class Principal implements BackendInterface { return $this->principalPrefix . '/' . $user->getUID(); } } + if (substr($uri, 0, 10) === 'principal:') { + $principal = substr($uri, 10); + $principal = $this->getPrincipalByPath($principal); + if ($principal !== null) { + return $principal['uri']; + } + } return null; } diff --git a/apps/dav/lib/Connector/Sabre/ServerFactory.php b/apps/dav/lib/Connector/Sabre/ServerFactory.php index 302e1f52249..12b00be43f5 100644 --- a/apps/dav/lib/Connector/Sabre/ServerFactory.php +++ b/apps/dav/lib/Connector/Sabre/ServerFactory.php @@ -110,6 +110,7 @@ class ServerFactory { // Load plugins $server->addPlugin(new \OCA\DAV\Connector\Sabre\MaintenancePlugin($this->config)); $server->addPlugin(new \OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin($this->config)); + $server->addPlugin(new \OCA\DAV\Connector\Sabre\AnonymousOptionsPlugin()); $server->addPlugin($authPlugin); // FIXME: The following line is a workaround for legacy components relying on being able to send a GET to / $server->addPlugin(new \OCA\DAV\Connector\Sabre\DummyGetResponsePlugin()); diff --git a/apps/dav/lib/Controller/InvitationResponseController.php b/apps/dav/lib/Controller/InvitationResponseController.php new file mode 100644 index 00000000000..e3bdab90aaf --- /dev/null +++ b/apps/dav/lib/Controller/InvitationResponseController.php @@ -0,0 +1,236 @@ +<?php +declare(strict_types=1); +/** + * @copyright 2018, 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\Controller; + +use OCA\DAV\CalDAV\InvitationResponse\InvitationResponseServer; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IDBConnection; +use OCP\IRequest; +use Sabre\VObject\ITip\Message; +use Sabre\VObject\Reader; + +class InvitationResponseController extends Controller { + + /** @var IDBConnection */ + private $db; + + /** @var ITimeFactory */ + private $timeFactory; + + /** @var InvitationResponseServer */ + private $responseServer; + + /** + * InvitationResponseController constructor. + * + * @param string $appName + * @param IRequest $request + * @param IDBConnection $db + * @param ITimeFactory $timeFactory + * @param InvitationResponseServer $responseServer + */ + public function __construct(string $appName, IRequest $request, + IDBConnection $db, ITimeFactory $timeFactory, + InvitationResponseServer $responseServer) { + parent::__construct($appName, $request); + $this->db = $db; + $this->timeFactory = $timeFactory; + $this->responseServer = $responseServer; + // Don't run `$server->exec()`, because we just need access to the + // fully initialized schedule plugin, but we don't want Sabre/DAV + // to actually handle and reply to the request + } + + /** + * @PublicPage + * @NoCSRFRequired + * + * @param string $token + * @return TemplateResponse + */ + public function accept(string $token):TemplateResponse { + $row = $this->getTokenInformation($token); + if (!$row) { + return new TemplateResponse($this->appName, 'schedule-response-error', [], 'guest'); + } + + $iTipMessage = $this->buildITipResponse($row, 'ACCEPTED'); + $this->responseServer->handleITipMessage($iTipMessage); + if ($iTipMessage->getScheduleStatus() === '1.2') { + return new TemplateResponse($this->appName, 'schedule-response-success', [], 'guest'); + } + + return new TemplateResponse($this->appName, 'schedule-response-error', [ + 'organizer' => $row['organizer'], + ], 'guest'); + } + + /** + * @PublicPage + * @NoCSRFRequired + * + * @param string $token + * @return TemplateResponse + */ + public function decline(string $token):TemplateResponse { + $row = $this->getTokenInformation($token); + if (!$row) { + return new TemplateResponse($this->appName, 'schedule-response-error', [], 'guest'); + } + + $iTipMessage = $this->buildITipResponse($row, 'DECLINED'); + $this->responseServer->handleITipMessage($iTipMessage); + + if ($iTipMessage->getScheduleStatus() === '1.2') { + return new TemplateResponse($this->appName, 'schedule-response-success', [], 'guest'); + } + + return new TemplateResponse($this->appName, 'schedule-response-error', [ + 'organizer' => $row['organizer'], + ], 'guest'); + } + + /** + * @PublicPage + * @NoCSRFRequired + * + * @param string $token + * @return TemplateResponse + */ + public function options(string $token):TemplateResponse { + return new TemplateResponse($this->appName, 'schedule-response-options', [ + 'token' => $token + ], 'guest'); + } + + /** + * @PublicPage + * @NoCSRFRequired + * + * @param string $token + * + * @return TemplateResponse + */ + public function processMoreOptionsResult(string $token):TemplateResponse { + $partstat = $this->request->getParam('partStat'); + $guests = (int) $this->request->getParam('guests'); + $comment = $this->request->getParam('comment'); + + $row = $this->getTokenInformation($token); + if (!$row || !\in_array($partstat, ['ACCEPTED', 'DECLINED', 'TENTATIVE'])) { + return new TemplateResponse($this->appName, 'schedule-response-error', [], 'guest'); + } + + $iTipMessage = $this->buildITipResponse($row, $partstat, $guests, $comment); + $this->responseServer->handleITipMessage($iTipMessage); + if ($iTipMessage->getScheduleStatus() === '1.2') { + return new TemplateResponse($this->appName, 'schedule-response-success', [], 'guest'); + } + + return new TemplateResponse($this->appName, 'schedule-response-error', [ + 'organizer' => $row['organizer'], + ], 'guest'); + } + + /** + * @param string $token + * @return array|null + */ + private function getTokenInformation(string $token) { + $query = $this->db->getQueryBuilder(); + $query->select('*') + ->from('calendar_invitations') + ->where($query->expr()->eq('token', $query->createNamedParameter($token))); + $stmt = $query->execute(); + $row = $stmt->fetch(\PDO::FETCH_ASSOC); + + if(!$row) { + return null; + } + + $currentTime = $this->timeFactory->getTime(); + if (((int) $row['expiration']) < $currentTime) { + return null; + } + + return $row; + } + + /** + * @param array $row + * @param string $partStat participation status of attendee - SEE RFC 5545 + * @param int|null $guests + * @param string|null $comment + * @return Message + */ + private function buildITipResponse(array $row, string $partStat, int $guests=null, + string $comment=null):Message { + $iTipMessage = new Message(); + $iTipMessage->uid = $row['uid']; + $iTipMessage->component = 'VEVENT'; + $iTipMessage->method = 'REPLY'; + $iTipMessage->sequence = $row['sequence']; + $iTipMessage->sender = $row['attendee']; + $iTipMessage->recipient = $row['organizer']; + + $message = <<<EOF +BEGIN:VCALENDAR +PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN +METHOD:REPLY +VERSION:2.0 +BEGIN:VEVENT +ATTENDEE;PARTSTAT=%s:%s +ORGANIZER:%s +UID:%s +SEQUENCE:%s +REQUEST-STATUS:2.0;Success +%sEND:VEVENT +END:VCALENDAR +EOF; + + $vObject = Reader::read(vsprintf($message, [ + $partStat, $row['attendee'], $row['organizer'], + $row['uid'], $row['sequence'] ?? 0, $row['recurrenceid'] ?? '' + ])); + $vEvent = $vObject->{'VEVENT'}; + /** @var \Sabre\VObject\Property\ICalendar\CalAddress $attendee */ + $attendee = $vEvent->{'ATTENDEE'}; + + $vEvent->DTSTAMP = date('Ymd\\THis\\Z', $this->timeFactory->getTime()); + + if ($comment) { + $attendee->add('X-RESPONSE-COMMENT', $comment); + $vEvent->add('COMMENT', $comment); + } + if ($guests) { + $attendee->add('X-NUM-GUESTS', $guests); + } + + $iTipMessage->message = $vObject; + + return $iTipMessage; + } +} diff --git a/apps/dav/lib/DAV/Sharing/Backend.php b/apps/dav/lib/DAV/Sharing/Backend.php index 87c094c6d62..433d9db9c08 100644 --- a/apps/dav/lib/DAV/Sharing/Backend.php +++ b/apps/dav/lib/DAV/Sharing/Backend.php @@ -67,12 +67,18 @@ class Backend { * @param string[] $add * @param string[] $remove */ - public function updateShares($shareable, $add, $remove) { + public function updateShares(IShareable $shareable, array $add, array $remove) { foreach($add as $element) { - $this->shareWith($shareable, $element); + $principal = $this->principalBackend->findByUri($element['href'], ''); + if ($principal !== '') { + $this->shareWith($shareable, $element); + } } foreach($remove as $element) { - $this->unshare($shareable, $element); + $principal = $this->principalBackend->findByUri($element, ''); + if ($principal !== '') { + $this->unshare($shareable, $element); + } } } diff --git a/apps/dav/lib/Files/FilesHome.php b/apps/dav/lib/Files/FilesHome.php index 63e7916edcf..1ff918aabbb 100644 --- a/apps/dav/lib/Files/FilesHome.php +++ b/apps/dav/lib/Files/FilesHome.php @@ -39,15 +39,12 @@ class FilesHome extends Directory { * FilesHome constructor. * * @param array $principalInfo + * @param FileInfo $userFolder */ - public function __construct($principalInfo) { + public function __construct($principalInfo, FileInfo $userFolder) { $this->principalInfo = $principalInfo; $view = \OC\Files\Filesystem::getView(); - $rootInfo = $view->getFileInfo(''); - if (!($rootInfo instanceof FileInfo)) { - throw new \Exception('Home does not exist'); - } - parent::__construct($view, $rootInfo); + parent::__construct($view, $userFolder); } function delete() { diff --git a/apps/dav/lib/Files/LazySearchBackend.php b/apps/dav/lib/Files/LazySearchBackend.php new file mode 100644 index 00000000000..dc7fec607a4 --- /dev/null +++ b/apps/dav/lib/Files/LazySearchBackend.php @@ -0,0 +1,72 @@ +<?php +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @author Robin Appelman <robin@icewind.nl> + * + * @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\Files; + +use SearchDAV\Backend\ISearchBackend; +use SearchDAV\Query\Query; + +class LazySearchBackend implements ISearchBackend { + /** + * @var ISearchBackend $backend + */ + private $backend = null; + + public function setBackend(ISearchBackend $backend) { + $this->backend = $backend; + } + + public function getArbiterPath() { + if ($this->backend) { + return $this->backend->getArbiterPath(); + } else { + return ''; + } + } + + public function isValidScope($href, $depth, $path) { + if ($this->backend) { + return $this->backend->getArbiterPath(); + } else { + return false; + } + } + + public function getPropertyDefinitionsForScope($href, $path) { + if ($this->backend) { + return $this->backend->getPropertyDefinitionsForScope($href, $path); + } else { + return []; + } + } + + public function search(Query $query) { + if ($this->backend) { + return $this->backend->search($query); + } else { + return []; + } + } + + +} diff --git a/apps/dav/lib/Files/RootCollection.php b/apps/dav/lib/Files/RootCollection.php index 59b6690a026..f5544693f2c 100644 --- a/apps/dav/lib/Files/RootCollection.php +++ b/apps/dav/lib/Files/RootCollection.php @@ -23,6 +23,7 @@ */ namespace OCA\DAV\Files; +use OCP\Files\FileInfo; use Sabre\DAV\INode; use Sabre\DAVACL\AbstractPrincipalCollection; use Sabre\DAV\SimpleCollection; @@ -48,7 +49,11 @@ class RootCollection extends AbstractPrincipalCollection { // in the future this could be considered to be used for accessing shared files return new SimpleCollection($name); } - return new FilesHome($principalInfo); + $userFolder = \OC::$server->getUserFolder(); + if (!($userFolder instanceof FileInfo)) { + throw new \Exception('Home does not exist'); + } + return new FilesHome($principalInfo, $userFolder); } function getName() { diff --git a/apps/dav/lib/Migration/Version1005Date20180530124431.php b/apps/dav/lib/Migration/Version1005Date20180530124431.php new file mode 100644 index 00000000000..6f3d6b9cc1f --- /dev/null +++ b/apps/dav/lib/Migration/Version1005Date20180530124431.php @@ -0,0 +1,84 @@ +<?php +/** + * @copyright 2017 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\Migration; + +use Doctrine\DBAL\Types\Type; +use OCP\DB\ISchemaWrapper; +use OCP\Migration\SimpleMigrationStep; +use OCP\Migration\IOutput; + +class Version1005Date20180530124431 extends SimpleMigrationStep { + + /** + * @param IOutput $output + * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return null|ISchemaWrapper + * @since 13.0.0 + */ + public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + $types = ['resources', 'rooms']; + foreach($types as $type) { + if (!$schema->hasTable('calendar_' . $type)) { + $table = $schema->createTable('calendar_' . $type); + + $table->addColumn('id', Type::BIGINT, [ + 'autoincrement' => true, + 'notnull' => true, + 'length' => 11, + 'unsigned' => true, + ]); + $table->addColumn('backend_id', Type::STRING, [ + 'notnull' => false, + 'length' => 64, + ]); + $table->addColumn('resource_id', Type::STRING, [ + 'notnull' => false, + 'length' => 64, + ]); + $table->addColumn('email', Type::STRING, [ + 'notnull' => false, + 'length' => 255, + ]); + $table->addColumn('displayname', Type::STRING, [ + 'notnull' => false, + 'length' => 255, + ]); + $table->addColumn('group_restrictions', Type::STRING, [ + 'notnull' => false, + 'length' => 4000, + ]); + + $table->setPrimaryKey(['id']); + $table->addIndex(['backend_id', 'resource_id'], 'calendar_' . $type . '_bkdrsc'); + $table->addIndex(['email'], 'calendar_' . $type . '_email'); + $table->addIndex(['displayname'], 'calendar_' . $type . '_name'); + } + } + + return $schema; + } +} diff --git a/apps/dav/lib/Migration/Version1006Date20180619154313.php b/apps/dav/lib/Migration/Version1006Date20180619154313.php new file mode 100644 index 00000000000..91d4826c277 --- /dev/null +++ b/apps/dav/lib/Migration/Version1006Date20180619154313.php @@ -0,0 +1,71 @@ +<?php +namespace OCA\DAV\Migration; + +use Doctrine\DBAL\Types\Type; +use OCP\DB\ISchemaWrapper; +use OCP\Migration\SimpleMigrationStep; +use OCP\Migration\IOutput; + +/** + * Auto-generated migration step: Please modify to your needs! + */ +class Version1006Date20180619154313 extends SimpleMigrationStep { + + /** + * @param IOutput $output + * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return null|ISchemaWrapper + * @since 13.0.0 + */ + public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + if (!$schema->hasTable('calendar_invitations')) { + $table = $schema->createTable('calendar_invitations'); + + $table->addColumn('id', Type::BIGINT, [ + 'autoincrement' => true, + 'notnull' => true, + 'length' => 11, + 'unsigned' => true, + ]); + $table->addColumn('uid', Type::STRING, [ + 'notnull' => true, + 'length' => 255, + ]); + $table->addColumn('recurrenceid', Type::STRING, [ + 'notnull' => false, + 'length' => 255, + ]); + $table->addColumn('attendee', Type::STRING, [ + 'notnull' => true, + 'length' => 255, + ]); + $table->addColumn('organizer', Type::STRING, [ + 'notnull' => true, + 'length' => 255, + ]); + $table->addColumn('sequence', Type::BIGINT, [ + 'notnull' => false, + 'length' => 11, + 'unsigned' => true, + ]); + $table->addColumn('token', Type::STRING, [ + 'notnull' => true, + 'length' => 60, + ]); + $table->addColumn('expiration', Type::BIGINT, [ + 'notnull' => true, + 'length' => 11, + 'unsigned' => true, + ]); + + $table->setPrimaryKey(['id']); + $table->addIndex(['token'], 'calendar_invitation_tokens'); + + return $schema; + } + } +} diff --git a/apps/dav/lib/RootCollection.php b/apps/dav/lib/RootCollection.php index a39b8716110..9a3261c388c 100644 --- a/apps/dav/lib/RootCollection.php +++ b/apps/dav/lib/RootCollection.php @@ -27,6 +27,8 @@ namespace OCA\DAV; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\CalendarRoot; use OCA\DAV\CalDAV\PublicCalendarRoot; +use OCA\DAV\CalDAV\ResourceBooking\ResourcePrincipalBackend; +use OCA\DAV\CalDAV\ResourceBooking\RoomPrincipalBackend; use OCA\DAV\CardDAV\AddressBookRoot; use OCA\DAV\CardDAV\CardDavBackend; use OCA\DAV\Connector\Sabre\Principal; @@ -43,6 +45,7 @@ class RootCollection extends SimpleCollection { $random = \OC::$server->getSecureRandom(); $logger = \OC::$server->getLogger(); $userManager = \OC::$server->getUserManager(); + $userSession = \OC::$server->getUserSession(); $groupManager = \OC::$server->getGroupManager(); $shareManager = \OC::$server->getShareManager(); $db = \OC::$server->getDatabaseConnection(); @@ -51,9 +54,12 @@ class RootCollection extends SimpleCollection { $userManager, $groupManager, $shareManager, - \OC::$server->getUserSession() + \OC::$server->getUserSession(), + $config ); $groupPrincipalBackend = new GroupPrincipalBackend($groupManager); + $calendarResourcePrincipalBackend = new ResourcePrincipalBackend($db, $userSession, $groupManager, $logger); + $calendarRoomPrincipalBackend = new RoomPrincipalBackend($db, $userSession, $groupManager, $logger); // as soon as debug mode is enabled we allow listing of principals $disableListing = !$config->getSystemValue('debug', false); @@ -64,11 +70,25 @@ class RootCollection extends SimpleCollection { $groupPrincipals->disableListing = $disableListing; $systemPrincipals = new Collection(new SystemPrincipalBackend(), 'principals/system'); $systemPrincipals->disableListing = $disableListing; + $calendarResourcePrincipals = new Collection($calendarResourcePrincipalBackend, 'principals/calendar-resources'); + $calendarResourcePrincipals->disableListing = $disableListing; + $calendarRoomPrincipals = new Collection($calendarRoomPrincipalBackend, 'principals/calendar-rooms'); + $calendarRoomPrincipals->disableListing = $disableListing; + + $filesCollection = new Files\RootCollection($userPrincipalBackend, 'principals/users'); $filesCollection->disableListing = $disableListing; $caldavBackend = new CalDavBackend($db, $userPrincipalBackend, $userManager, $groupManager, $random, $logger, $dispatcher); - $calendarRoot = new CalendarRoot($userPrincipalBackend, $caldavBackend, 'principals/users'); - $calendarRoot->disableListing = $disableListing; + $userCalendarRoot = new CalendarRoot($userPrincipalBackend, $caldavBackend, 'principals/users'); + $userCalendarRoot->disableListing = $disableListing; + + $resourceCalendarCaldavBackend = new CalDavBackend($db, $userPrincipalBackend, $userManager, $groupManager, $random, $logger, $dispatcher); + $resourceCalendarRoot = new CalendarRoot($calendarResourcePrincipalBackend, $caldavBackend, 'principals/calendar-resources'); + $resourceCalendarRoot->disableListing = $disableListing; + $roomCalendarCaldavBackend = new CalDavBackend($db, $userPrincipalBackend, $userManager, $groupManager, $random, $logger, $dispatcher); + $roomCalendarRoot = new CalendarRoot($calendarRoomPrincipalBackend, $roomCalendarCaldavBackend, 'principals/calendar-rooms'); + $roomCalendarRoot->disableListing = $disableListing; + $publicCalendarRoot = new PublicCalendarRoot($caldavBackend, $l10n, $config); $publicCalendarRoot->disableListing = $disableListing; @@ -110,9 +130,15 @@ class RootCollection extends SimpleCollection { new SimpleCollection('principals', [ $userPrincipals, $groupPrincipals, - $systemPrincipals]), + $systemPrincipals, + $calendarResourcePrincipals, + $calendarRoomPrincipals]), $filesCollection, - $calendarRoot, + $userCalendarRoot, + new SimpleCollection('system-calendars', [ + $resourceCalendarRoot, + $roomCalendarRoot, + ]), $publicCalendarRoot, new SimpleCollection('addressbooks', [ $usersAddressBookRoot, diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index 7fbd7671e8d..fc8bc91c80a 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -52,6 +52,8 @@ use OCA\DAV\DAV\PublicAuth; use OCA\DAV\DAV\CustomPropertiesBackend; use OCA\DAV\Connector\Sabre\QuotaPlugin; use OCA\DAV\Files\BrowserErrorPagePlugin; +use OCA\DAV\Connector\Sabre\AnonymousOptionsPlugin; +use OCA\DAV\Files\LazySearchBackend; use OCA\DAV\SystemTag\SystemTagPlugin; use OCA\DAV\Upload\ChunkingPlugin; use OCP\IRequest; @@ -71,14 +73,13 @@ class Server { private $baseUri; /** @var Connector\Sabre\Server */ - private $server; + public $server; public function __construct(IRequest $request, $baseUri) { $this->request = $request; $this->baseUri = $baseUri; $logger = \OC::$server->getLogger(); $dispatcher = \OC::$server->getEventDispatcher(); - $sendInvitations = \OC::$server->getConfig()->getAppValue('dav', 'sendInvitations', 'yes') === 'yes'; $root = new RootCollection(); $this->server = new \OCA\DAV\Connector\Sabre\Server(new CachingTree($root)); @@ -100,6 +101,7 @@ class Server { $this->server->setBaseUri($this->baseUri); $this->server->addPlugin(new BlockLegacyClientPlugin(\OC::$server->getConfig())); + $this->server->addPlugin(new AnonymousOptionsPlugin()); $authPlugin = new Plugin(); $authPlugin->addBackend(new PublicAuth()); $this->server->addPlugin($authPlugin); @@ -131,30 +133,37 @@ class Server { // acl $acl = new DavAclPlugin(); $acl->principalCollectionSet = [ - 'principals/users', 'principals/groups' + 'principals/users', 'principals/groups', + 'principals/calendar-resources', + 'principals/calendar-rooms', ]; $acl->defaultUsernamePath = 'principals/users'; $this->server->addPlugin($acl); // calendar plugins - $this->server->addPlugin(new \OCA\DAV\CalDAV\Plugin()); - $this->server->addPlugin(new \Sabre\CalDAV\ICSExportPlugin()); - $this->server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin()); - if ($sendInvitations) { - $this->server->addPlugin(\OC::$server->query(\OCA\DAV\CalDAV\Schedule\IMipPlugin::class)); + if ($this->requestIsForSubtree(['calendars', 'public-calendars', 'system-calendars', 'principals'])) { + $this->server->addPlugin(new \OCA\DAV\CalDAV\Plugin()); + $this->server->addPlugin(new \Sabre\CalDAV\ICSExportPlugin()); + $this->server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin()); + if (\OC::$server->getConfig()->getAppValue('dav', 'sendInvitations', 'yes') === 'yes') { + $this->server->addPlugin(\OC::$server->query(\OCA\DAV\CalDAV\Schedule\IMipPlugin::class)); + } + $this->server->addPlugin(new \Sabre\CalDAV\Subscriptions\Plugin()); + $this->server->addPlugin(new \Sabre\CalDAV\Notifications\Plugin()); + $this->server->addPlugin(new DAV\Sharing\Plugin($authBackend, \OC::$server->getRequest())); + $this->server->addPlugin(new \OCA\DAV\CalDAV\Publishing\PublishPlugin( + \OC::$server->getConfig(), + \OC::$server->getURLGenerator() + )); } - $this->server->addPlugin(new \Sabre\CalDAV\Subscriptions\Plugin()); - $this->server->addPlugin(new \Sabre\CalDAV\Notifications\Plugin()); - $this->server->addPlugin(new DAV\Sharing\Plugin($authBackend, \OC::$server->getRequest())); - $this->server->addPlugin(new \OCA\DAV\CalDAV\Publishing\PublishPlugin( - \OC::$server->getConfig(), - \OC::$server->getURLGenerator() - )); // addressbook plugins - $this->server->addPlugin(new \OCA\DAV\CardDAV\Plugin()); - $this->server->addPlugin(new VCFExportPlugin()); - $this->server->addPlugin(new ImageExportPlugin(new PhotoCache(\OC::$server->getAppDataDir('dav-photocache')))); + if ($this->requestIsForSubtree(['addressbooks', 'principals'])) { + $this->server->addPlugin(new DAV\Sharing\Plugin($authBackend, \OC::$server->getRequest())); + $this->server->addPlugin(new \OCA\DAV\CardDAV\Plugin()); + $this->server->addPlugin(new VCFExportPlugin()); + $this->server->addPlugin(new ImageExportPlugin(new PhotoCache(\OC::$server->getAppDataDir('dav-photocache')))); + } // system tags plugins $this->server->addPlugin(new SystemTagPlugin( @@ -189,8 +198,11 @@ class Server { $this->server->addPlugin(new BrowserErrorPagePlugin()); } + $lazySearchBackend = new LazySearchBackend(); + $this->server->addPlugin(new SearchPlugin($lazySearchBackend)); + // wait with registering these until auth is handled and the filesystem is setup - $this->server->on('beforeMethod', function () use ($root) { + $this->server->on('beforeMethod', function () use ($root, $lazySearchBackend) { // custom properties plugin must be the last one $userSession = \OC::$server->getUserSession(); $user = $userSession->getUser(); @@ -249,13 +261,13 @@ class Server { \OC::$server->getGroupManager(), $userFolder )); - $this->server->addPlugin(new SearchPlugin(new \OCA\DAV\Files\FileSearchBackend( + $lazySearchBackend->setBackend(new \OCA\DAV\Files\FileSearchBackend( $this->server->tree, $user, \OC::$server->getRootFolder(), \OC::$server->getShareManager(), $view - ))); + )); } $this->server->addPlugin(new \OCA\DAV\CalDAV\BirthdayCalendar\EnablePlugin( \OC::$server->getConfig(), @@ -280,4 +292,14 @@ class Server { public function exec() { $this->server->exec(); } + + private function requestIsForSubtree(array $subTrees): bool { + foreach ($subTrees as $subTree) { + $subTree = trim($subTree, ' /'); + if (strpos($this->server->getRequestUri(), $subTree.'/') === 0) { + return true; + } + } + return false; + } } diff --git a/apps/dav/lib/Settings/CalDAVSettings.php b/apps/dav/lib/Settings/CalDAVSettings.php index e4b4ca6452b..f38143b5b4e 100644 --- a/apps/dav/lib/Settings/CalDAVSettings.php +++ b/apps/dav/lib/Settings/CalDAVSettings.php @@ -57,13 +57,13 @@ class CalDAVSettings implements ISettings { * @return string */ public function getSection() { - return 'server'; + return 'groupware'; } /** * @return int */ public function getPriority() { - return 20; + return 10; } } diff --git a/apps/dav/templates/schedule-response-error.php b/apps/dav/templates/schedule-response-error.php new file mode 100644 index 00000000000..c65875f3b0b --- /dev/null +++ b/apps/dav/templates/schedule-response-error.php @@ -0,0 +1,7 @@ +<div class="update"> + <p class="message"><?php p($l->t('There was an error updating your attendance status.'));?></p> + <p class="message"><?php p($l->t('Please contact the organizer directly.'));?></p> + <?php if(isset($_['organizer'])): ?> + <p class="message"><a href="<?php p($_['organizer']) ?>"><?php p(substr($_['organizer'], 7)) ?></a></p> + <?php endif; ?> +</div> diff --git a/apps/dav/templates/schedule-response-options.php b/apps/dav/templates/schedule-response-options.php new file mode 100644 index 00000000000..da95454e4f5 --- /dev/null +++ b/apps/dav/templates/schedule-response-options.php @@ -0,0 +1,35 @@ +<?php +style('dav', 'schedule-response'); +//script('dav', 'schedule-response'); +?> + +<div class="update"> + <form action="" method="post"> + <fieldset id="partStat"> + <h2><?php p($l->t('Are you accepting the invitation?')); ?></h2> + <div id="selectPartStatForm"> + <input type="radio" id="partStatAccept" name="partStat" value="ACCEPTED" checked /> + <label for="partStatAccept"> + <span><?php p($l->t('Accept')); ?></span> + </label> + + <input type="radio" id="partStatTentative" name="partStat" value="TENTATIVE" /> + <label for="partStatTentative"> + <span><?php p($l->t('Tentative')); ?></span> + </label> + + <input type="radio" class="declined" id="partStatDeclined" name="partStat" value="DECLINED" /> + <label for="partStatDeclined"> + <span><?php p($l->t('Decline')); ?></span> + </label> + </div> + </fieldset> + <fieldset id="more_options"> + <input type="number" min="0" name="guests" placeholder="Guests" /> + <input type="text" name="comment" placeholder="Comment" /> + </fieldset> + <fieldset> + <input type="submit" value="<?php p($l->t('Save'));?>"> + </fieldset> + </form> +</div> diff --git a/apps/dav/templates/schedule-response-success.php b/apps/dav/templates/schedule-response-success.php new file mode 100644 index 00000000000..f60cb1e0fa9 --- /dev/null +++ b/apps/dav/templates/schedule-response-success.php @@ -0,0 +1,4 @@ +<div class="update" style="justify-content: space-around; display: flex;"> + <span class="icon icon-checkmark-white"></span> + <p class="message"><?php p($l->t('Your attendance was updated successfully.'));?></p> +</div> diff --git a/apps/dav/tests/unit/BackgroundJob/CleanupInvitationTokenJobTest.php b/apps/dav/tests/unit/BackgroundJob/CleanupInvitationTokenJobTest.php new file mode 100644 index 00000000000..2d80b5bf0ea --- /dev/null +++ b/apps/dav/tests/unit/BackgroundJob/CleanupInvitationTokenJobTest.php @@ -0,0 +1,100 @@ +<?php +declare(strict_types=1); +/** + * @copyright 2018, 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\BackgroundJob; + +use OCA\DAV\BackgroundJob\CleanupInvitationTokenJob; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use Test\TestCase; + +class CleanupInvitationTokenJobTest extends TestCase { + + /** @var IDBConnection | \PHPUnit_Framework_MockObject_MockObject */ + private $dbConnection; + + /** @var ITimeFactory | \PHPUnit_Framework_MockObject_MockObject */ + private $timeFactory; + + /** @var \OCA\DAV\BackgroundJob\GenerateBirthdayCalendarBackgroundJob */ + private $backgroundJob; + + protected function setUp() { + parent::setUp(); + + $this->dbConnection = $this->createMock(IDBConnection::class); + $this->timeFactory = $this->createMock(ITimeFactory::class); + + $this->backgroundJob = new CleanupInvitationTokenJob( + $this->dbConnection, $this->timeFactory); + } + + public function testRun() { + $this->timeFactory->expects($this->once()) + ->method('getTime') + ->with() + ->will($this->returnValue(1337)); + + $queryBuilder = $this->createMock(IQueryBuilder::class); + $expr = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class); + $stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class); + + $this->dbConnection->expects($this->once()) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder)); + $queryBuilder->method('expr') + ->will($this->returnValue($expr)); + $queryBuilder->method('createNamedParameter') + ->will($this->returnValueMap([ + [1337, \PDO::PARAM_STR, null, 'namedParameter1337'] + ])); + + $expr->expects($this->once()) + ->method('lt') + ->with('expiration', 'namedParameter1337') + ->will($this->returnValue('LT STATEMENT')); + + $this->dbConnection->expects($this->once()) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder)); + + $queryBuilder->expects($this->at(0)) + ->method('delete') + ->with('calendar_invitations') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(3)) + ->method('where') + ->with('LT STATEMENT') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(4)) + ->method('execute') + ->with() + ->will($this->returnValue($stmt)); + + $this->backgroundJob->run([]); + } +} diff --git a/apps/dav/tests/unit/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJobTest.php b/apps/dav/tests/unit/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJobTest.php new file mode 100644 index 00000000000..e012d5e3f18 --- /dev/null +++ b/apps/dav/tests/unit/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJobTest.php @@ -0,0 +1,285 @@ +<?php +/** + * @copyright Copyright (c) 2018, Georg Ehrke + * + * @author Georg Ehrke <oc.list@georgehrke.com> + * + * @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\BackgroundJob; + +use OCA\DAV\BackgroundJob\UpdateCalendarResourcesRoomsBackgroundJob; + +use OCA\DAV\CalDAV\CalDavBackend; +use OCP\Calendar\BackendTemporarilyUnavailableException; +use OCP\Calendar\Resource\IBackend; +use OCP\Calendar\Resource\IManager as IResourceManager; +use OCP\Calendar\Resource\IResource; +use OCP\Calendar\Room\IManager as IRoomManager; +use Test\TestCase; + +class UpdateCalendarResourcesRoomsBackgroundJobTest extends TestCase { + + /** @var UpdateCalendarResourcesRoomsBackgroundJob */ + private $backgroundJob; + + /** @var IResourceManager | \PHPUnit_Framework_MockObject_MockObject */ + private $resourceManager; + + /** @var IRoomManager | \PHPUnit_Framework_MockObject_MockObject */ + private $roomManager; + + /** @var CalDavBackend | \PHPUnit_Framework_MockObject_MockObject */ + private $calDavBackend; + + protected function setUp() { + parent::setUp(); + + $this->resourceManager = $this->createMock(IResourceManager::class); + $this->roomManager = $this->createMock(IRoomManager::class); + $this->calDavBackend = $this->createMock(CalDavBackend::class); + + $this->backgroundJob = new UpdateCalendarResourcesRoomsBackgroundJob( + $this->resourceManager, $this->roomManager, self::$realDatabase, + $this->calDavBackend); + } + + protected function tearDown() { + $query = self::$realDatabase->getQueryBuilder(); + $query->delete('calendar_resources')->execute(); + $query->delete('calendar_rooms')->execute(); + } + + /** + * Data in Cache: + * resources: + * [backend1, res1, Beamer1, {}] + * [backend1, res2, TV1, {}] + * [backend2, res3, Beamer2, {}] + * [backend2, res4, TV2, {}] + * [backend3, res5, Beamer3, {}] + * [backend3, res6, Pointer, {foo, bar}] + * + * Data in Backend: + * backend1 gone + * backend2 throws BackendTemporarilyUnavailableException + * [backend3, res6, Pointer123, {foo, biz}] + * [backend3, res7, Resource4, {biz}] + * [backend4, res8, Beamer, {}] + * [backend4, res9, Beamer2, {}] + * + * Expected after run: + * [backend2, res3, Beamer2, {}] + * [backend2, res4, TV2, {}] + * [backend3, res6, Pointer123, {foo, biz}] + * [backend3, res7, Resource4, {biz}] + * [backend4, res8, Beamer, {}] + * [backend4, res9, Beamer2, {}] + */ + + public function testRun() { + $this->createTestResourcesInCache(); + + $backend2 = $this->createMock(IBackend::class); + $backend3 = $this->createMock(IBackend::class); + $backend4 = $this->createMock(IBackend::class); + + $res6 = $this->createMock(IResource::class); + $res7 = $this->createMock(IResource::class); + $res8 = $this->createMock(IResource::class); + $res9 = $this->createMock(IResource::class); + + $backend2->method('getBackendIdentifier') + ->will($this->returnValue('backend2')); + $backend2->method('listAllResources') + ->will($this->throwException(new BackendTemporarilyUnavailableException())); + $backend2->method('getResource') + ->will($this->throwException(new BackendTemporarilyUnavailableException())); + $backend2->method('getAllResources') + ->will($this->throwException(new BackendTemporarilyUnavailableException())); + $backend3->method('getBackendIdentifier') + ->will($this->returnValue('backend3')); + $backend3->method('listAllResources') + ->will($this->returnValue(['res6', 'res7'])); + $backend3->method('getResource') + ->will($this->returnValueMap([ + ['res6', $res6], + ['res7', $res7], + ])); + $backend4->method('getBackendIdentifier') + ->will($this->returnValue('backend4')); + $backend4->method('listAllResources') + ->will($this->returnValue(['res8', 'res9'])); + $backend4->method('getResource') + ->will($this->returnValueMap([ + ['res8', $res8], + ['res9', $res9], + ])); + + $res6->method('getId')->will($this->returnValue('res6')); + $res6->method('getDisplayName')->will($this->returnValue('Pointer123')); + $res6->method('getGroupRestrictions')->will($this->returnValue(['foo', 'biz'])); + $res6->method('getEMail')->will($this->returnValue('res6@foo.bar')); + $res6->method('getBackend')->will($this->returnValue($backend3)); + + $res7->method('getId')->will($this->returnValue('res7')); + $res7->method('getDisplayName')->will($this->returnValue('Resource4')); + $res7->method('getGroupRestrictions')->will($this->returnValue(['biz'])); + $res7->method('getEMail')->will($this->returnValue('res7@foo.bar')); + $res7->method('getBackend')->will($this->returnValue($backend3)); + + $res8->method('getId')->will($this->returnValue('res8')); + $res8->method('getDisplayName')->will($this->returnValue('Beamer')); + $res8->method('getGroupRestrictions')->will($this->returnValue([])); + $res8->method('getEMail')->will($this->returnValue('res8@foo.bar')); + $res8->method('getBackend')->will($this->returnValue($backend4)); + + $res9->method('getId')->will($this->returnValue('res9')); + $res9->method('getDisplayName')->will($this->returnValue('Beamer2')); + $res9->method('getGroupRestrictions')->will($this->returnValue([])); + $res9->method('getEMail')->will($this->returnValue('res9@foo.bar')); + $res9->method('getBackend')->will($this->returnValue($backend4)); + + $this->resourceManager + ->method('getBackends') + ->will($this->returnValue([ + $backend2, $backend3, $backend4 + ])); + $this->resourceManager + ->method('getBackend') + ->will($this->returnValueMap([ + ['backend2', $backend2], + ['backend3', $backend3], + ['backend4', $backend4], + ])); + + $this->backgroundJob->run([]); + + $query = self::$realDatabase->getQueryBuilder(); + $query->select('*')->from('calendar_resources'); + + $rows = []; + $stmt = $query->execute(); + while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + unset($row['id']); + $rows[] = $row; + } + + $this->assertEquals([ + [ + 'backend_id' => 'backend2', + 'resource_id' => 'res3', + 'displayname' => 'Beamer2', + 'email' => 'res3@foo.bar', + 'group_restrictions' => '[]', + ], + [ + 'backend_id' => 'backend2', + 'resource_id' => 'res4', + 'displayname' => 'TV2', + 'email' => 'res4@foo.bar', + 'group_restrictions' => '[]', + ], + [ + 'backend_id' => 'backend3', + 'resource_id' => 'res6', + 'displayname' => 'Pointer123', + 'email' => 'res6@foo.bar', + 'group_restrictions' => '["foo","biz"]', + ], + [ + 'backend_id' => 'backend3', + 'resource_id' => 'res7', + 'displayname' => 'Resource4', + 'email' => 'res7@foo.bar', + 'group_restrictions' => '["biz"]', + ], + [ + 'backend_id' => 'backend4', + 'resource_id' => 'res8', + 'displayname' => 'Beamer', + 'email' => 'res8@foo.bar', + 'group_restrictions' => '[]', + ], + [ + 'backend_id' => 'backend4', + 'resource_id' => 'res9', + 'displayname' => 'Beamer2', + 'email' => 'res9@foo.bar', + 'group_restrictions' => '[]', + ], + ], $rows); + } + + protected function createTestResourcesInCache() { + $query = self::$realDatabase->getQueryBuilder(); + $query->insert('calendar_resources') + ->values([ + 'backend_id' => $query->createNamedParameter('backend1'), + 'resource_id' => $query->createNamedParameter('res1'), + 'email' => $query->createNamedParameter('res1@foo.bar'), + 'displayname' => $query->createNamedParameter('Beamer1'), + 'group_restrictions' => $query->createNamedParameter('[]'), + ]) + ->execute(); + $query->insert('calendar_resources') + ->values([ + 'backend_id' => $query->createNamedParameter('backend1'), + 'resource_id' => $query->createNamedParameter('res2'), + 'email' => $query->createNamedParameter('res2@foo.bar'), + 'displayname' => $query->createNamedParameter('TV1'), + 'group_restrictions' => $query->createNamedParameter('[]'), + ]) + ->execute(); + $query->insert('calendar_resources') + ->values([ + 'backend_id' => $query->createNamedParameter('backend2'), + 'resource_id' => $query->createNamedParameter('res3'), + 'email' => $query->createNamedParameter('res3@foo.bar'), + 'displayname' => $query->createNamedParameter('Beamer2'), + 'group_restrictions' => $query->createNamedParameter('[]'), + ]) + ->execute(); + $query->insert('calendar_resources') + ->values([ + 'backend_id' => $query->createNamedParameter('backend2'), + 'resource_id' => $query->createNamedParameter('res4'), + 'email' => $query->createNamedParameter('res4@foo.bar'), + 'displayname' => $query->createNamedParameter('TV2'), + 'group_restrictions' => $query->createNamedParameter('[]'), + ]) + ->execute(); + $query->insert('calendar_resources') + ->values([ + 'backend_id' => $query->createNamedParameter('backend3'), + 'resource_id' => $query->createNamedParameter('res5'), + 'email' => $query->createNamedParameter('res5@foo.bar'), + 'displayname' => $query->createNamedParameter('Beamer3'), + 'group_restrictions' => $query->createNamedParameter('[]'), + ]) + ->execute(); + $query->insert('calendar_resources') + ->values([ + 'backend_id' => $query->createNamedParameter('backend3'), + 'resource_id' => $query->createNamedParameter('res6'), + 'email' => $query->createNamedParameter('res6@foo.bar'), + 'displayname' => $query->createNamedParameter('Pointer'), + 'group_restrictions' => $query->createNamedParameter('["foo", "bar"]'), + ]) + ->execute(); + } +} diff --git a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php index 310433f0913..d49e3bdc778 100644 --- a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php +++ b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php @@ -27,10 +27,13 @@ namespace OCA\DAV\Tests\unit\CalDAV; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\Connector\Sabre\Principal; +use OCP\IConfig; use OCP\IGroupManager; use OCP\ILogger; use OCP\IUserManager; +use OCP\IUserSession; use OCP\Security\ISecureRandom; +use OCP\Share\IManager as ShareManager; use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Test\TestCase; @@ -73,7 +76,13 @@ abstract class AbstractCalDavBackend extends TestCase { $this->groupManager = $this->createMock(IGroupManager::class); $this->dispatcher = $this->createMock(EventDispatcherInterface::class); $this->principal = $this->getMockBuilder(Principal::class) - ->disableOriginalConstructor() + ->setConstructorArgs([ + $this->userManager, + $this->groupManager, + $this->createMock(ShareManager::class), + $this->createMock(IUserSession::class), + $this->createMock(IConfig::class), + ]) ->setMethods(['getPrincipalByPath', 'getGroupMembership']) ->getMock(); $this->principal->expects($this->any())->method('getPrincipalByPath') diff --git a/apps/dav/tests/unit/CalDAV/Activity/Filter/CalendarTest.php b/apps/dav/tests/unit/CalDAV/Activity/Filter/CalendarTest.php index 0fdd50b54c4..1c31508255a 100644 --- a/apps/dav/tests/unit/CalDAV/Activity/Filter/CalendarTest.php +++ b/apps/dav/tests/unit/CalDAV/Activity/Filter/CalendarTest.php @@ -55,7 +55,7 @@ class CalendarTest extends TestCase { public function testGetIcon() { $this->url->expects($this->once()) ->method('imagePath') - ->with('core', 'places/calendar-dark.svg') + ->with('core', 'places/calendar.svg') ->willReturn('path-to-icon'); $this->url->expects($this->once()) diff --git a/apps/dav/tests/unit/CalDAV/PluginTest.php b/apps/dav/tests/unit/CalDAV/PluginTest.php index 7d283b6d1ed..47190d583f0 100644 --- a/apps/dav/tests/unit/CalDAV/PluginTest.php +++ b/apps/dav/tests/unit/CalDAV/PluginTest.php @@ -43,8 +43,12 @@ class PluginTest extends TestCase { 'calendars/MyUserName', ], [ - 'FooFoo', - null, + 'principals/calendar-resources/Resource-ABC', + 'system-calendars/calendar-resources/Resource-ABC', + ], + [ + 'principals/calendar-rooms/Room-ABC', + 'system-calendars/calendar-rooms/Room-ABC', ], ]; } @@ -59,4 +63,11 @@ class PluginTest extends TestCase { $this->assertSame($expected, $this->plugin->getCalendarHomeForPrincipal($input)); } + /** + * @expectedException \LogicException + * @expectedExceptionMessage This is not supposed to happen + */ + public function testGetCalendarHomeForUnknownPrincipal() { + $this->plugin->getCalendarHomeForPrincipal('FOO/BAR/BLUB'); + } } diff --git a/apps/dav/tests/unit/CalDAV/ResourceBooking/AbstractPrincipalBackendTest.php b/apps/dav/tests/unit/CalDAV/ResourceBooking/AbstractPrincipalBackendTest.php new file mode 100644 index 00000000000..4dee0220fc8 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/ResourceBooking/AbstractPrincipalBackendTest.php @@ -0,0 +1,930 @@ +<?php +/** + * @copyright Copyright (c) 2018, Georg Ehrke + * + * @author Georg Ehrke <oc.list@georgehrke.com> + * + * @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\CalDAV\ResourceBooking; + +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use OCP\IGroupManager; +use OCP\ILogger; +use OCP\IUser; +use OCP\IUserSession; +use Sabre\DAV\PropPatch; +use Test\TestCase; + +abstract class AbstractPrincipalBackendTest extends TestCase { + + /** @var \OCA\DAV\CalDAV\ResourceBooking\ResourcePrincipalBackend|\OCA\DAV\CalDAV\ResourceBooking\RoomPrincipalBackend */ + protected $principalBackend; + + /** @var IDBConnection|\PHPUnit_Framework_MockObject_MockObject */ + protected $dbConnection; + + /** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */ + protected $userSession; + + /** @var IGroupManager|\PHPUnit_Framework_MockObject_MockObject */ + protected $groupManager; + + /** @var ILogger|\PHPUnit_Framework_MockObject_MockObject */ + protected $logger; + + /** @var string */ + protected $expectedDbTable; + + /** @var string */ + protected $principalPrefix; + + public function setUp() { + parent::setUp(); + + $this->dbConnection = $this->createMock(IDBConnection::class); + $this->userSession = $this->createMock(IUserSession::class); + $this->groupManager = $this->createMock(IGroupManager::class); + $this->logger = $this->createMock(ILogger::class); + } + + public function testGetPrincipalsByPrefix() { + $queryBuilder = $this->createMock(IQueryBuilder::class); + $stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class); + $this->dbConnection->expects($this->once()) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder)); + + $queryBuilder->expects($this->at(0)) + ->method('select') + ->with(['id', 'backend_id', 'resource_id', 'email', 'displayname']) + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(1)) + ->method('from') + ->with($this->expectedDbTable) + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(2)) + ->method('execute') + ->with() + ->will($this->returnValue($stmt)); + + $stmt->expects($this->at(0)) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue([ + 'id' => 0, + 'backend_id' => 'db', + 'resource_id' => '123', + 'email' => 'foo@bar.com', + 'displayname' => 'Resource 123' + ])); + $stmt->expects($this->at(1)) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue([ + 'id' => 1, + 'backend_id' => 'ldap', + 'resource_id' => '123', + 'email' => 'ldap@bar.com', + 'displayname' => 'Resource 123 ldap' + ])); + $stmt->expects($this->at(2)) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue([ + 'id' => 2, + 'backend_id' => 'db', + 'resource_id' => '456', + 'email' => 'bli@bar.com', + 'displayname' => 'Resource 456' + ])); + $stmt->expects($this->at(3)) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue(null)); + $stmt->expects($this->at(4)) + ->method('closeCursor') + ->with(); + + $actual = $this->principalBackend->getPrincipalsByPrefix($this->principalPrefix); + $this->assertEquals([ + [ + 'uri' => $this->principalPrefix . '/db-123', + '{DAV:}displayname' => 'Resource 123', + '{http://sabredav.org/ns}email-address' => 'foo@bar.com', + ], + [ + 'uri' => $this->principalPrefix . '/ldap-123', + '{DAV:}displayname' => 'Resource 123 ldap', + '{http://sabredav.org/ns}email-address' => 'ldap@bar.com', + ], + [ + 'uri' => $this->principalPrefix . '/db-456', + '{DAV:}displayname' => 'Resource 456', + '{http://sabredav.org/ns}email-address' => 'bli@bar.com', + ], + ], $actual); + + } + + public function testGetNoPrincipalsByPrefixForWrongPrincipalPrefix() { + $this->dbConnection->expects($this->never()) + ->method('getQueryBuilder'); + + $actual = $this->principalBackend->getPrincipalsByPrefix('principals/users'); + $this->assertEquals([], $actual); + } + + public function testGetPrincipalByPath() { + $queryBuilder = $this->createMock(IQueryBuilder::class); + $stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class); + $expr = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class); + + $this->dbConnection->expects($this->at(0)) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder)); + $queryBuilder->method('expr') + ->will($this->returnValue($expr)); + $expr->method('eq') + ->will($this->returnValueMap([ + ['backend_id', 'createNamedParameter-1', null, 'WHERE_CLAUSE_1'], + ['resource_id', 'createNamedParameter-2', null, 'WHERE_CLAUSE_2'], + ])); + $queryBuilder->method('createNamedParameter') + ->will($this->returnValueMap([ + ['db', \PDO::PARAM_STR, null, 'createNamedParameter-1'], + ['123', \PDO::PARAM_STR, null, 'createNamedParameter-2'], + ])); + + $queryBuilder->expects($this->at(0)) + ->method('select') + ->with(['id', 'backend_id', 'resource_id', 'email', 'displayname']) + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(1)) + ->method('from') + ->with($this->expectedDbTable) + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(4)) + ->method('where') + ->with('WHERE_CLAUSE_1') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(7)) + ->method('andWhere') + ->with('WHERE_CLAUSE_2') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(8)) + ->method('execute') + ->with() + ->will($this->returnValue($stmt)); + + $stmt->expects($this->at(0)) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue([ + 'id' => 0, + 'backend_id' => 'db', + 'resource_id' => '123', + 'email' => 'foo@bar.com', + 'displayname' => 'Resource 123' + ])); + + $actual = $this->principalBackend->getPrincipalByPath($this->principalPrefix . '/db-123'); + $this->assertEquals([ + 'uri' => $this->principalPrefix . '/db-123', + '{DAV:}displayname' => 'Resource 123', + '{http://sabredav.org/ns}email-address' => 'foo@bar.com', + ], $actual); + } + + public function testGetPrincipalByPathNotFound() { + $queryBuilder = $this->createMock(IQueryBuilder::class); + $stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class); + $expr = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class); + + $this->dbConnection->expects($this->at(0)) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder)); + $queryBuilder->method('expr') + ->will($this->returnValue($expr)); + $expr->method('eq') + ->will($this->returnValueMap([ + ['backend_id', 'createNamedParameter-1', null, 'WHERE_CLAUSE_1'], + ['resource_id', 'createNamedParameter-2', null, 'WHERE_CLAUSE_2'], + ])); + $queryBuilder->method('createNamedParameter') + ->will($this->returnValueMap([ + ['db', \PDO::PARAM_STR, null, 'createNamedParameter-1'], + ['123', \PDO::PARAM_STR, null, 'createNamedParameter-2'], + ])); + + $queryBuilder->expects($this->at(0)) + ->method('select') + ->with(['id', 'backend_id', 'resource_id', 'email', 'displayname']) + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(1)) + ->method('from') + ->with($this->expectedDbTable) + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(4)) + ->method('where') + ->with('WHERE_CLAUSE_1') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(7)) + ->method('andWhere') + ->with('WHERE_CLAUSE_2') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(8)) + ->method('execute') + ->with() + ->will($this->returnValue($stmt)); + + $stmt->expects($this->at(0)) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue(false)); + + $actual = $this->principalBackend->getPrincipalByPath($this->principalPrefix . '/db-123'); + $this->assertEquals(null, $actual); + } + + public function testGetPrincipalByPathWrongPrefix() { + $this->dbConnection->expects($this->never()) + ->method('getQueryBuilder'); + + $actual = $this->principalBackend->getPrincipalByPath('principals/users/foo-bar'); + $this->assertEquals(null, $actual); + } + + public function testGetGroupMemberSet() { + $actual = $this->principalBackend->getGroupMemberSet($this->principalPrefix . '/foo-bar'); + $this->assertEquals([], $actual); + } + + public function testGetGroupMembership() { + $actual = $this->principalBackend->getGroupMembership($this->principalPrefix . '/foo-bar'); + $this->assertEquals([], $actual); + } + + /** + * @expectedException \Sabre\DAV\Exception + * @expectedExceptionMessage Setting members of the group is not supported yet + */ + public function testSetGroupMemberSet() { + $this->principalBackend->setGroupMemberSet($this->principalPrefix . '/foo-bar', ['foo', 'bar']); + } + + public function testUpdatePrincipal() { + $propPatch = $this->createMock(PropPatch::class); + $actual = $this->principalBackend->updatePrincipal($this->principalPrefix . '/foo-bar', $propPatch); + + $this->assertEquals(0, $actual); + } + + /** + * @dataProvider dataSearchPrincipals + */ + public function testSearchPrincipals($expected, $test) { + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->once()) + ->method('getUser') + ->with() + ->will($this->returnValue($user)); + $this->groupManager->expects($this->once()) + ->method('getUserGroupIds') + ->with($user) + ->will($this->returnValue(['group1', 'group2'])); + + $queryBuilder1 = $this->createMock(IQueryBuilder::class); + $queryBuilder2 = $this->createMock(IQueryBuilder::class); + $stmt1 = $this->createMock(\Doctrine\DBAL\Driver\Statement::class); + $stmt2 = $this->createMock(\Doctrine\DBAL\Driver\Statement::class); + $expr1 = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class); + $expr2 = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class); + + $this->dbConnection->expects($this->at(0)) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder1)); + $this->dbConnection->expects($this->at(1)) + ->method('escapeLikeParameter') + ->with('foo') + ->will($this->returnValue('escapedFoo')); + $this->dbConnection->expects($this->at(2)) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder2)); + $this->dbConnection->expects($this->at(3)) + ->method('escapeLikeParameter') + ->with('bar') + ->will($this->returnValue('escapedBar')); + + $queryBuilder1->method('expr') + ->will($this->returnValue($expr1)); + $queryBuilder2->method('expr') + ->will($this->returnValue($expr2)); + + $expr1->method('iLike') + ->will($this->returnValueMap([ + ['email', 'createNamedParameter-1', null, 'ILIKE_CLAUSE_1'], + ])); + $expr2->method('iLike') + ->will($this->returnValueMap([ + ['displayname', 'createNamedParameter-2', null, 'ILIKE_CLAUSE_2'], + ])); + + $queryBuilder1->method('expr') + ->will($this->returnValue($expr1)); + $queryBuilder2->method('expr') + ->will($this->returnValue($expr2)); + + $queryBuilder1->method('createNamedParameter') + ->will($this->returnValueMap([ + ['%escapedFoo%', \PDO::PARAM_STR, null, 'createNamedParameter-1'], + ])); + $queryBuilder2->method('createNamedParameter') + ->will($this->returnValueMap([ + ['%escapedBar%', \PDO::PARAM_STR, null, 'createNamedParameter-2'], + ])); + + $queryBuilder1->expects($this->at(0)) + ->method('select') + ->with(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions']) + ->will($this->returnValue($queryBuilder1)); + $queryBuilder1->expects($this->at(1)) + ->method('from') + ->with($this->expectedDbTable) + ->will($this->returnValue($queryBuilder1)); + $queryBuilder1->expects($this->at(4)) + ->method('where') + ->with('ILIKE_CLAUSE_1') + ->will($this->returnValue($queryBuilder1)); + $queryBuilder1->expects($this->at(5)) + ->method('execute') + ->with() + ->will($this->returnValue($stmt1)); + + $queryBuilder2->expects($this->at(0)) + ->method('select') + ->with(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions']) + ->will($this->returnValue($queryBuilder2)); + $queryBuilder2->expects($this->at(1)) + ->method('from') + ->with($this->expectedDbTable) + ->will($this->returnValue($queryBuilder2)); + $queryBuilder2->expects($this->at(4)) + ->method('where') + ->with('ILIKE_CLAUSE_2') + ->will($this->returnValue($queryBuilder2)); + $queryBuilder2->expects($this->at(5)) + ->method('execute') + ->with() + ->will($this->returnValue($stmt2)); + + $stmt1->expects($this->at(0)) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue([ + 'id' => 0, + 'backend_id' => 'db', + 'resource_id' => '1', + 'email' => '1', + 'displayname' => 'Resource 1', + 'group_restrictions' => null, + ])); + $stmt1->expects($this->at(1)) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue([ + 'id' => 1, + 'backend_id' => 'db', + 'resource_id' => '2', + 'email' => '2', + 'displayname' => 'Resource 2', + 'group_restrictions' => '', + ])); + $stmt1->expects($this->at(2)) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue([ + 'id' => 2, + 'backend_id' => 'db', + 'resource_id' => '3', + 'email' => '3', + 'displayname' => 'Resource 3', + 'group_restrictions' => '["group3"]', + ])); + $stmt1->expects($this->at(3)) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue([ + 'id' => 99, + 'backend_id' => 'db', + 'resource_id' => '99', + 'email' => '99', + 'displayname' => 'Resource 99', + 'group_restrictions' => '["group1", "group2"]', + ])); + $stmt1->expects($this->at(4)) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue(null)); + + $stmt2->expects($this->at(0)) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue([ + 'id' => 0, + 'backend_id' => 'db', + 'resource_id' => '4', + 'email' => '4', + 'displayname' => 'Resource 4', + 'group_restrictions' => '[]' + ])); + $stmt2->expects($this->at(1)) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue([ + 'id' => 1, + 'backend_id' => 'db', + 'resource_id' => '5', + 'email' => '5', + 'displayname' => 'Resource 5', + 'group_restrictions' => '["group1", "group5"]' + ])); + $stmt2->expects($this->at(2)) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue([ + 'id' => 99, + 'backend_id' => 'db', + 'resource_id' => '99', + 'email' => '99', + 'displayname' => 'Resource 99', + 'group_restrictions' => '["group1", "group2"]', + ])); + $stmt2->expects($this->at(3)) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue(null)); + + $actual = $this->principalBackend->searchPrincipals($this->principalPrefix, [ + '{http://sabredav.org/ns}email-address' => 'foo', + '{DAV:}displayname' => 'bar', + ], $test); + + $this->assertEquals( + str_replace('%prefix%', $this->principalPrefix, $expected), + $actual); + } + + public function dataSearchPrincipals() { + // data providers are called before we subclass + // this class, $this->principalPrefix is null + // at that point, so we need this hack + return [ + [[ + '%prefix%/db-99' + ], 'allof'], + [[ + '%prefix%/db-1', + '%prefix%/db-2', + '%prefix%/db-99', + '%prefix%/db-4', + '%prefix%/db-5', + ], 'anyof'], + ]; + } + + public function testSearchPrincipalsEmptySearchProperties() { + $this->userSession->expects($this->never()) + ->method('getUser'); + $this->groupManager->expects($this->never()) + ->method('getUserGroupIds'); + $this->dbConnection->expects($this->never()) + ->method('getQueryBuilder'); + + $this->principalBackend->searchPrincipals($this->principalPrefix, []); + } + + public function testSearchPrincipalsWrongPrincipalPrefix() { + $this->userSession->expects($this->never()) + ->method('getUser'); + $this->groupManager->expects($this->never()) + ->method('getUserGroupIds'); + $this->dbConnection->expects($this->never()) + ->method('getQueryBuilder'); + + $this->principalBackend->searchPrincipals('principals/users', [ + '{http://sabredav.org/ns}email-address' => 'foo' + ]); + } + + public function testFindByUriByEmail() { + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->once()) + ->method('getUser') + ->with() + ->will($this->returnValue($user)); + $this->groupManager->expects($this->once()) + ->method('getUserGroupIds') + ->with($user) + ->will($this->returnValue(['group1', 'group2'])); + + $queryBuilder = $this->createMock(IQueryBuilder::class); + $stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class); + $expr = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class); + + $this->dbConnection->expects($this->at(0)) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder)); + $queryBuilder->method('expr') + ->will($this->returnValue($expr)); + $expr->method('eq') + ->will($this->returnValueMap([ + ['email', 'createNamedParameter-1', null, 'WHERE_CLAUSE_1'], + ])); + $queryBuilder->method('createNamedParameter') + ->will($this->returnValueMap([ + ['foo@bar.com', \PDO::PARAM_STR, null, 'createNamedParameter-1'], + ])); + + $queryBuilder->expects($this->at(0)) + ->method('select') + ->with(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions']) + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(1)) + ->method('from') + ->with($this->expectedDbTable) + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(4)) + ->method('where') + ->with('WHERE_CLAUSE_1') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(5)) + ->method('execute') + ->with() + ->will($this->returnValue($stmt)); + + $stmt->expects($this->at(0)) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue([ + 'id' => 0, + 'backend_id' => 'db', + 'resource_id' => '123', + 'email' => 'foo@bar.com', + 'displayname' => 'Resource 123', + 'group_restrictions' => '["group1"]', + ])); + + $actual = $this->principalBackend->findByUri('mailto:foo@bar.com', $this->principalPrefix); + $this->assertEquals($this->principalPrefix . '/db-123', $actual); + } + + public function testFindByUriByEmailForbiddenResource() { + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->once()) + ->method('getUser') + ->with() + ->will($this->returnValue($user)); + $this->groupManager->expects($this->once()) + ->method('getUserGroupIds') + ->with($user) + ->will($this->returnValue(['group1', 'group2'])); + + $queryBuilder = $this->createMock(IQueryBuilder::class); + $stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class); + $expr = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class); + + $this->dbConnection->expects($this->at(0)) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder)); + $queryBuilder->method('expr') + ->will($this->returnValue($expr)); + $expr->method('eq') + ->will($this->returnValueMap([ + ['email', 'createNamedParameter-1', null, 'WHERE_CLAUSE_1'], + ])); + $queryBuilder->method('createNamedParameter') + ->will($this->returnValueMap([ + ['foo@bar.com', \PDO::PARAM_STR, null, 'createNamedParameter-1'], + ])); + + $queryBuilder->expects($this->at(0)) + ->method('select') + ->with(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions']) + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(1)) + ->method('from') + ->with($this->expectedDbTable) + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(4)) + ->method('where') + ->with('WHERE_CLAUSE_1') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(5)) + ->method('execute') + ->with() + ->will($this->returnValue($stmt)); + + $stmt->expects($this->at(0)) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue([ + 'id' => 0, + 'backend_id' => 'db', + 'resource_id' => '123', + 'email' => 'foo@bar.com', + 'displayname' => 'Resource 123', + 'group_restrictions' => '["group3"]', + ])); + + $actual = $this->principalBackend->findByUri('mailto:foo@bar.com', $this->principalPrefix); + $this->assertEquals(null, $actual); + } + + public function testFindByUriByEmailNotFound() { + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->once()) + ->method('getUser') + ->with() + ->will($this->returnValue($user)); + $this->groupManager->expects($this->once()) + ->method('getUserGroupIds') + ->with($user) + ->will($this->returnValue(['group1', 'group2'])); + + $queryBuilder = $this->createMock(IQueryBuilder::class); + $stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class); + $expr = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class); + + $this->dbConnection->expects($this->at(0)) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder)); + $queryBuilder->method('expr') + ->will($this->returnValue($expr)); + $expr->method('eq') + ->will($this->returnValueMap([ + ['email', 'createNamedParameter-1', null, 'WHERE_CLAUSE_1'], + ])); + $queryBuilder->method('createNamedParameter') + ->will($this->returnValueMap([ + ['foo@bar.com', \PDO::PARAM_STR, null, 'createNamedParameter-1'], + ])); + + $queryBuilder->expects($this->at(0)) + ->method('select') + ->with(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions']) + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(1)) + ->method('from') + ->with($this->expectedDbTable) + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(4)) + ->method('where') + ->with('WHERE_CLAUSE_1') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(5)) + ->method('execute') + ->with() + ->will($this->returnValue($stmt)); + + $stmt->expects($this->at(0)) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue(null)); + + $actual = $this->principalBackend->findByUri('mailto:foo@bar.com', $this->principalPrefix); + $this->assertEquals(null, $actual); + } + + public function testFindByUriByPrincipal() { + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->once()) + ->method('getUser') + ->with() + ->will($this->returnValue($user)); + $this->groupManager->expects($this->once()) + ->method('getUserGroupIds') + ->with($user) + ->will($this->returnValue(['group1', 'group2'])); + + $queryBuilder = $this->createMock(IQueryBuilder::class); + $stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class); + $expr = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class); + + $this->dbConnection->expects($this->at(0)) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder)); + $queryBuilder->method('expr') + ->will($this->returnValue($expr)); + $expr->method('eq') + ->will($this->returnValueMap([ + ['email', 'createNamedParameter-1', null, 'WHERE_CLAUSE_1'], + ])); + $queryBuilder->method('createNamedParameter') + ->will($this->returnValueMap([ + ['foo@bar.com', \PDO::PARAM_STR, null, 'createNamedParameter-1'], + ])); + + $queryBuilder->expects($this->at(0)) + ->method('select') + ->with(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions']) + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(1)) + ->method('from') + ->with($this->expectedDbTable) + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(4)) + ->method('where') + ->with('WHERE_CLAUSE_1') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(5)) + ->method('execute') + ->with() + ->will($this->returnValue($stmt)); + + $stmt->expects($this->at(0)) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue([ + 'id' => 0, + 'backend_id' => 'db', + 'resource_id' => '123', + 'email' => 'foo@bar.com', + 'displayname' => 'Resource 123', + 'group_restrictions' => '["group1"]', + ])); + + $actual = $this->principalBackend->findByUri('mailto:foo@bar.com', $this->principalPrefix); + $this->assertEquals($this->principalPrefix . '/db-123', $actual); + } + + public function testFindByUriByPrincipalForbiddenResource() { + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->once()) + ->method('getUser') + ->with() + ->will($this->returnValue($user)); + $this->groupManager->expects($this->once()) + ->method('getUserGroupIds') + ->with($user) + ->will($this->returnValue(['group1', 'group2'])); + + $queryBuilder = $this->createMock(IQueryBuilder::class); + $stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class); + $expr = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class); + + $this->dbConnection->expects($this->at(0)) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder)); + $queryBuilder->method('expr') + ->will($this->returnValue($expr)); + $expr->method('eq') + ->will($this->returnValueMap([ + ['backend_id', 'createNamedParameter-1', null, 'WHERE_CLAUSE_1'], + ['resource_id', 'createNamedParameter-2', null, 'WHERE_CLAUSE_2'], + ])); + $queryBuilder->method('createNamedParameter') + ->will($this->returnValueMap([ + ['db', \PDO::PARAM_STR, null, 'createNamedParameter-1'], + ['123', \PDO::PARAM_STR, null, 'createNamedParameter-2'], + ])); + + $queryBuilder->expects($this->at(0)) + ->method('select') + ->with(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions']) + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(1)) + ->method('from') + ->with($this->expectedDbTable) + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(4)) + ->method('where') + ->with('WHERE_CLAUSE_1') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(7)) + ->method('andWhere') + ->with('WHERE_CLAUSE_2') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(8)) + ->method('execute') + ->with() + ->will($this->returnValue($stmt)); + + $stmt->expects($this->at(0)) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue([ + 'id' => 0, + 'backend_id' => 'db', + 'resource_id' => '123', + 'email' => 'foo@bar.com', + 'displayname' => 'Resource 123', + 'group_restrictions' => '["group3"]', + ])); + + $actual = $this->principalBackend->findByUri('principal:' . $this->principalPrefix . '/db-123', $this->principalPrefix); + $this->assertEquals(null, $actual); + } + + public function testFindByUriByPrincipalNotFound() { + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->once()) + ->method('getUser') + ->with() + ->will($this->returnValue($user)); + $this->groupManager->expects($this->once()) + ->method('getUserGroupIds') + ->with($user) + ->will($this->returnValue(['group1', 'group2'])); + + $queryBuilder = $this->createMock(IQueryBuilder::class); + $stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class); + $expr = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class); + + $this->dbConnection->expects($this->at(0)) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder)); + $queryBuilder->method('expr') + ->will($this->returnValue($expr)); + $expr->method('eq') + ->will($this->returnValueMap([ + ['backend_id', 'createNamedParameter-1', null, 'WHERE_CLAUSE_1'], + ['resource_id', 'createNamedParameter-2', null, 'WHERE_CLAUSE_2'], + ])); + $queryBuilder->method('createNamedParameter') + ->will($this->returnValueMap([ + ['db', \PDO::PARAM_STR, null, 'createNamedParameter-1'], + ['123', \PDO::PARAM_STR, null, 'createNamedParameter-2'], + ])); + + $queryBuilder->expects($this->at(0)) + ->method('select') + ->with(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions']) + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(1)) + ->method('from') + ->with($this->expectedDbTable) + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(4)) + ->method('where') + ->with('WHERE_CLAUSE_1') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(7)) + ->method('andWhere') + ->with('WHERE_CLAUSE_2') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(8)) + ->method('execute') + ->with() + ->will($this->returnValue($stmt)); + + $stmt->expects($this->at(0)) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue(null)); + + $actual = $this->principalBackend->findByUri('principal:' . $this->principalPrefix . '/db-123', $this->principalPrefix); + $this->assertEquals(null, $actual); + } + + public function testFindByUriByUnknownUri() { + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->once()) + ->method('getUser') + ->with() + ->will($this->returnValue($user)); + $this->groupManager->expects($this->once()) + ->method('getUserGroupIds') + ->with($user) + ->will($this->returnValue(['group1', 'group2'])); + + $actual = $this->principalBackend->findByUri('foobar:blub', $this->principalPrefix); + $this->assertEquals(null, $actual); + } + +} diff --git a/apps/dav/tests/unit/CalDAV/ResourceBooking/ResourcePrincipalBackendTest.php b/apps/dav/tests/unit/CalDAV/ResourceBooking/ResourcePrincipalBackendTest.php new file mode 100644 index 00000000000..90db4bb4b7b --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/ResourceBooking/ResourcePrincipalBackendTest.php @@ -0,0 +1,35 @@ +<?php +/** + * @copyright Copyright (c) 2018, Georg Ehrke + * + * @author Georg Ehrke <oc.list@georgehrke.com> + * + * @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\CalDAV\ResourceBooking; + +use OCA\DAV\CalDAV\ResourceBooking\ResourcePrincipalBackend; + +Class ResourcePrincipalBackendTest extends AbstractPrincipalBackendTest { + public function setUp() { + parent::setUp(); + + $this->principalBackend = new ResourcePrincipalBackend($this->dbConnection, + $this->userSession, $this->groupManager, $this->logger); + $this->expectedDbTable = 'calendar_resources'; + $this->principalPrefix = 'principals/calendar-resources'; + } +} diff --git a/apps/dav/tests/unit/CalDAV/ResourceBooking/RoomPrincipalBackendTest.php b/apps/dav/tests/unit/CalDAV/ResourceBooking/RoomPrincipalBackendTest.php new file mode 100644 index 00000000000..b55c6ddceb6 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/ResourceBooking/RoomPrincipalBackendTest.php @@ -0,0 +1,35 @@ +<?php +/** + * @copyright Copyright (c) 2018, Georg Ehrke + * + * @author Georg Ehrke <oc.list@georgehrke.com> + * + * @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\CalDAV\ResourceBooking; + +use OCA\DAV\CalDAV\ResourceBooking\RoomPrincipalBackend; + +Class RoomPrincipalBackendTest extends AbstractPrincipalBackendTest { + public function setUp() { + parent::setUp(); + + $this->principalBackend = new RoomPrincipalBackend($this->dbConnection, + $this->userSession, $this->groupManager, $this->logger); + $this->expectedDbTable = 'calendar_rooms'; + $this->principalPrefix = 'principals/calendar-rooms'; + } +} diff --git a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php index 3f89002ab98..c95b32041e0 100644 --- a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php +++ b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php @@ -29,8 +29,10 @@ namespace OCA\DAV\Tests\unit\CalDAV\Schedule; use OC\Mail\Mailer; use OCA\DAV\CalDAV\Schedule\IMipPlugin; use OCP\AppFramework\Utility\ITimeFactory; +use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Defaults; use OCP\IConfig; +use OCP\IDBConnection; use OCP\IL10N; use OCP\ILogger; use OCP\IURLGenerator; @@ -39,6 +41,7 @@ use OCP\Mail\IAttachment; use OCP\Mail\IEMailTemplate; use OCP\Mail\IMailer; use OCP\Mail\IMessage; +use OCP\Security\ISecureRandom; use Sabre\VObject\Component\VCalendar; use Sabre\VObject\ITip\Message; use Test\TestCase; @@ -70,13 +73,38 @@ class IMipPluginTest extends TestCase { $l10nFactory->method('get')->willReturn($l10n); /** @var IURLGenerator | \PHPUnit_Framework_MockObject_MockObject $urlGenerator */ $urlGenerator = $this->createMock(IURLGenerator::class); + /** @var IDBConnection | \PHPUnit_Framework_MockObject_MockObject $db */ + $db = $this->createMock(IDBConnection::class); + /** @var ISecureRandom | \PHPUnit_Framework_MockObject_MockObject $random */ + $random = $this->createMock(ISecureRandom::class); /** @var Defaults | \PHPUnit_Framework_MockObject_MockObject $defaults */ $defaults = $this->createMock(Defaults::class); $defaults->expects($this->once()) ->method('getName') ->will($this->returnValue('Instance Name 123')); - $plugin = new IMipPlugin($config, $mailer, $logger, $timeFactory, $l10nFactory, $urlGenerator, $defaults, 'user123'); + $random->expects($this->once()) + ->method('generate') + ->with(60, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') + ->will($this->returnValue('random_token')); + + $queryBuilder = $this->createMock(IQueryBuilder::class); + + $db->expects($this->once()) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(0)) + ->method('insert') + ->with('calendar_invitations') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(8)) + ->method('values') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(9)) + ->method('execute'); + + $plugin = new IMipPlugin($config, $mailer, $logger, $timeFactory, $l10nFactory, $urlGenerator, $defaults, $random, $db, 'user123'); $message = new Message(); $message->method = 'REQUEST'; $message->message = new VCalendar(); @@ -128,10 +156,35 @@ class IMipPluginTest extends TestCase { $l10nFactory->method('get')->willReturn($l10n); /** @var IURLGenerator | \PHPUnit_Framework_MockObject_MockObject $urlGenerator */ $urlGenerator = $this->createMock(IURLGenerator::class); + /** @var IDBConnection | \PHPUnit_Framework_MockObject_MockObject $db */ + $db = $this->createMock(IDBConnection::class); + /** @var ISecureRandom | \PHPUnit_Framework_MockObject_MockObject $random */ + $random = $this->createMock(ISecureRandom::class); /** @var Defaults | \PHPUnit_Framework_MockObject_MockObject $defaults */ $defaults = $this->createMock(Defaults::class); - $plugin = new IMipPlugin($config, $mailer, $logger, $timeFactory, $l10nFactory, $urlGenerator, $defaults, 'user123'); + $random->expects($this->once()) + ->method('generate') + ->with(60, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') + ->will($this->returnValue('random_token')); + + $queryBuilder = $this->createMock(IQueryBuilder::class); + + $db->expects($this->once()) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(0)) + ->method('insert') + ->with('calendar_invitations') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(8)) + ->method('values') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(9)) + ->method('execute'); + + $plugin = new IMipPlugin($config, $mailer, $logger, $timeFactory, $l10nFactory, $urlGenerator, $defaults, $random, $db, 'user123'); $message = new Message(); $message->method = 'REQUEST'; $message->message = new VCalendar(); @@ -190,10 +243,37 @@ class IMipPluginTest extends TestCase { $l10nFactory->method('get')->willReturn($l10n); /** @var IURLGenerator | \PHPUnit_Framework_MockObject_MockObject $urlGenerator */ $urlGenerator = $this->createMock(IURLGenerator::class); + /** @var IDBConnection | \PHPUnit_Framework_MockObject_MockObject $db */ + $db = $this->createMock(IDBConnection::class); + /** @var ISecureRandom | \PHPUnit_Framework_MockObject_MockObject $random */ + $random = $this->createMock(ISecureRandom::class); /** @var Defaults | \PHPUnit_Framework_MockObject_MockObject $defaults */ $defaults = $this->createMock(Defaults::class); - $plugin = new IMipPlugin($config, $mailer, $logger, $timeFactory, $l10nFactory, $urlGenerator, $defaults, 'user123'); + if ($expectsMail) { + $random->expects($this->once()) + ->method('generate') + ->with(60, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') + ->will($this->returnValue('random_token')); + + $queryBuilder = $this->createMock(IQueryBuilder::class); + + $db->expects($this->once()) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(0)) + ->method('insert') + ->with('calendar_invitations') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(8)) + ->method('values') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(9)) + ->method('execute'); + } + + $plugin = new IMipPlugin($config, $mailer, $logger, $timeFactory, $l10nFactory, $urlGenerator, $defaults, $random, $db, 'user123'); $message = new Message(); $message->method = 'REQUEST'; $message->message = new VCalendar(); diff --git a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php index 63e090873bb..816ba670990 100644 --- a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php +++ b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php @@ -36,10 +36,13 @@ use OCA\DAV\CardDAV\AddressBook; use OCA\DAV\CardDAV\CardDavBackend; use OCA\DAV\Connector\Sabre\Principal; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IConfig; use OCP\IDBConnection; use OCP\IGroupManager; use OCP\IL10N; use OCP\IUserManager; +use OCP\IUserSession; +use OCP\Share\IManager as ShareManager; use Sabre\DAV\PropPatch; use Sabre\VObject\Component\VCard; use Sabre\VObject\Property\Text; @@ -90,7 +93,13 @@ class CardDavBackendTest extends TestCase { $this->userManager = $this->createMock(IUserManager::class); $this->groupManager = $this->createMock(IGroupManager::class); $this->principal = $this->getMockBuilder(Principal::class) - ->disableOriginalConstructor() + ->setConstructorArgs([ + $this->userManager, + $this->groupManager, + $this->createMock(ShareManager::class), + $this->createMock(IUserSession::class), + $this->createMock(IConfig::class), + ]) ->setMethods(['getPrincipalByPath', 'getGroupMembership']) ->getMock(); $this->principal->method('getPrincipalByPath') diff --git a/apps/dav/tests/unit/Command/RemoveInvalidSharesTest.php b/apps/dav/tests/unit/Command/RemoveInvalidSharesTest.php new file mode 100644 index 00000000000..2574e4d0aec --- /dev/null +++ b/apps/dav/tests/unit/Command/RemoveInvalidSharesTest.php @@ -0,0 +1,71 @@ +<?php +/** + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2018, ownCloud GmbH + * @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\Command; + + +use OCA\DAV\Connector\Sabre\Principal; +use OCA\DAV\Command\RemoveInvalidShares; +use OCP\Migration\IOutput; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Test\TestCase; + +/** + * Class RemoveInvalidSharesTest + * + * @package OCA\DAV\Tests\Unit\Repair + * @group DB + */ +class RemoveInvalidSharesTest extends TestCase { + + public function setUp() { + parent::setUp(); + $db = \OC::$server->getDatabaseConnection(); + + $db->insertIfNotExist('*PREFIX*dav_shares', [ + 'principaluri' => 'principal:unknown', + 'type' => 'calendar', + 'access' => 2, + 'resourceid' => 666, + ]); + } + + public function test() { + $db = \OC::$server->getDatabaseConnection(); + /** @var Principal | \PHPUnit_Framework_MockObject_MockObject $principal */ + $principal = $this->createMock(Principal::class); + + /** @var IOutput | \PHPUnit_Framework_MockObject_MockObject $output */ + $output = $this->createMock(IOutput::class); + + $repair = new RemoveInvalidShares($db, $principal); + $this->invokePrivate($repair, 'run', [$this->createMock(InputInterface::class), $this->createMock(OutputInterface::class)]); + + $query = $db->getQueryBuilder(); + $result = $query->select('*')->from('dav_shares') + ->where($query->expr()->eq('principaluri', $query->createNamedParameter('principal:unknown')))->execute(); + $data = $result->fetchAll(); + $result->closeCursor(); + $this->assertEquals(0, count($data)); + } +} diff --git a/apps/dav/tests/unit/Connector/Sabre/NodeTest.php b/apps/dav/tests/unit/Connector/Sabre/NodeTest.php index b46c731d3dc..2b84d8475fc 100644 --- a/apps/dav/tests/unit/Connector/Sabre/NodeTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/NodeTest.php @@ -151,12 +151,13 @@ class NodeTest extends \Test\TestCase { $info = $this->getMockBuilder(FileInfo::class) ->disableOriginalConstructor() - ->setMethods(['getStorage', 'getType', 'getMountPoint']) + ->setMethods(['getStorage', 'getType', 'getMountPoint', 'getPermissions']) ->getMock(); $info->method('getStorage')->willReturn($storage); $info->method('getType')->willReturn($type); $info->method('getMountPoint')->willReturn($mountpoint); + $info->method('getPermissions')->willReturn($permissions); $view = $this->getMockBuilder(View::class) ->disableOriginalConstructor() diff --git a/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php b/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php index 7b9929bc4f3..7e82c446760 100644 --- a/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php @@ -27,6 +27,7 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre; use OC\User\User; +use OCP\IConfig; use OCP\IGroup; use OCP\IGroupManager; use OCP\IUser; @@ -47,18 +48,22 @@ class PrincipalTest extends TestCase { private $shareManager; /** @var IUserSession | \PHPUnit_Framework_MockObject_MockObject */ private $userSession; + /** @var IConfig | \PHPUnit_Framework_MockObject_MockObject */ + private $config; public function setUp() { $this->userManager = $this->createMock(IUserManager::class); $this->groupManager = $this->createMock(IGroupManager::class); $this->shareManager = $this->createMock(IManager::class); $this->userSession = $this->createMock(IUserSession::class); + $this->config = $this->createMock(IConfig::class); $this->connector = new \OCA\DAV\Connector\Sabre\Principal( $this->userManager, $this->groupManager, $this->shareManager, - $this->userSession); + $this->userSession, + $this->config); parent::setUp(); } @@ -278,26 +283,37 @@ class PrincipalTest extends TestCase { /** * @dataProvider searchPrincipalsDataProvider */ - public function testSearchPrincipals($sharingEnabled, $groupsOnly, $result) { + public function testSearchPrincipals($disableFreeBusy, $sharingEnabled, $disableFBSharingCombination, $groupsOnly, $result) { $this->shareManager->expects($this->once()) ->method('shareAPIEnabled') ->will($this->returnValue($sharingEnabled)); - - if ($sharingEnabled) { - $this->shareManager->expects($this->once()) - ->method('shareWithGroupMembersOnly') - ->will($this->returnValue($groupsOnly)); - - if ($groupsOnly) { - $user = $this->createMock(IUser::class); - $this->userSession->expects($this->once()) - ->method('getUser') - ->will($this->returnValue($user)); - - $this->groupManager->expects($this->at(0)) - ->method('getUserGroupIds') - ->with($user) - ->will($this->returnValue(['group1', 'group2'])); + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('dav', 'disableFreeBusy', $sharingEnabled ? 'no' : 'yes') + ->will($this->returnValue($disableFBSharingCombination)); + + if ($disableFreeBusy === 'no') { + if ($sharingEnabled) { + $this->shareManager->expects($this->once()) + ->method('shareWithGroupMembersOnly') + ->will($this->returnValue($groupsOnly)); + + if ($groupsOnly) { + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->once()) + ->method('getUser') + ->will($this->returnValue($user)); + + $this->groupManager->expects($this->at(0)) + ->method('getUserGroupIds') + ->with($user) + ->will($this->returnValue(['group1', 'group2'])); + } + } else { + $this->shareManager->expects($this->never()) + ->method('shareWithGroupMembersOnly'); + $this->groupManager->expects($this->never()) + ->method($this->anything()); } } else { $this->shareManager->expects($this->never()) @@ -306,27 +322,43 @@ class PrincipalTest extends TestCase { ->method($this->anything()); } + $user2 = $this->createMock(IUser::class); $user2->method('getUID')->will($this->returnValue('user2')); $user3 = $this->createMock(IUser::class); $user3->method('getUID')->will($this->returnValue('user3')); - if ($sharingEnabled) { - $this->userManager->expects($this->at(0)) - ->method('getByEmail') - ->with('user') - ->will($this->returnValue([$user2, $user3])); + if ($disableFreeBusy === 'no') { + if ($sharingEnabled) { + $this->userManager->expects($this->at(0)) + ->method('getByEmail') + ->with('user') + ->will($this->returnValue([$user2, $user3])); + } else { + $this->userManager->expects($this->never()) + ->method('getByEmail'); + } + } else { + $this->userManager->expects($this->never()) + ->method('getByEmail'); } - if ($sharingEnabled && $groupsOnly) { - $this->groupManager->expects($this->at(1)) - ->method('getUserGroupIds') - ->with($user2) - ->will($this->returnValue(['group1', 'group3'])); - $this->groupManager->expects($this->at(2)) - ->method('getUserGroupIds') - ->with($user3) - ->will($this->returnValue(['group3', 'group4'])); + if ($disableFreeBusy === 'no') { + if ($sharingEnabled && $groupsOnly) { + $this->groupManager->expects($this->at(1)) + ->method('getUserGroupIds') + ->with($user2) + ->will($this->returnValue(['group1', 'group3'])); + $this->groupManager->expects($this->at(2)) + ->method('getUserGroupIds') + ->with($user3) + ->will($this->returnValue(['group3', 'group4'])); + } + } else { + $this->groupManager->expects($this->never()) + ->method('getUserGroupIds'); + $this->groupManager->expects($this->never()) + ->method('getUserGroupIds'); } $this->assertEquals($result, $this->connector->searchPrincipals('principals/users', @@ -335,9 +367,12 @@ class PrincipalTest extends TestCase { public function searchPrincipalsDataProvider() { return [ - [true, false, ['principals/users/user2', 'principals/users/user3']], - [true, true, ['principals/users/user2']], - [false, false, []], + ['yes', true, 'yes', false, []], + ['no', true, 'no', false, ['principals/users/user2', 'principals/users/user3']], + ['yes', true, 'yes', true, []], + ['no', true, 'no', true, ['principals/users/user2']], + ['yes', false, 'yes', false, []], + ['no', false, 'yes', false, []], ]; } @@ -345,6 +380,10 @@ class PrincipalTest extends TestCase { $this->shareManager->expects($this->once()) ->method('shareApiEnabled') ->will($this->returnValue(false)); + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('dav', 'disableFreeBusy', 'yes') + ->will($this->returnValue('yes')); $this->assertEquals(null, $this->connector->findByUri('mailto:user@foo.com', 'principals/users')); } @@ -352,45 +391,56 @@ class PrincipalTest extends TestCase { /** * @dataProvider findByUriWithGroupRestrictionDataProvider */ - public function testFindByUriWithGroupRestriction($uri, $email, $expects) { + public function testFindByUriWithGroupRestriction($disableFreeBusy, $uri, $email, $expects) { $this->shareManager->expects($this->once()) ->method('shareApiEnabled') ->will($this->returnValue(true)); + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('dav', 'disableFreeBusy', 'no') + ->will($this->returnValue($disableFreeBusy)); - $this->shareManager->expects($this->once()) - ->method('shareWithGroupMembersOnly') - ->will($this->returnValue(true)); + if ($disableFreeBusy === 'yes') { + $this->shareManager->expects($this->never()) + ->method('shareWithGroupMembersOnly'); + $this->userSession->expects($this->never()) + ->method('getUser'); + } else { + $this->shareManager->expects($this->once()) + ->method('shareWithGroupMembersOnly') + ->will($this->returnValue(true)); - $user = $this->createMock(IUser::class); - $this->userSession->expects($this->once()) - ->method('getUser') - ->will($this->returnValue($user)); + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->once()) + ->method('getUser') + ->will($this->returnValue($user)); - $this->groupManager->expects($this->at(0)) - ->method('getUserGroupIds') - ->with($user) - ->will($this->returnValue(['group1', 'group2'])); + $this->groupManager->expects($this->at(0)) + ->method('getUserGroupIds') + ->with($user) + ->will($this->returnValue(['group1', 'group2'])); - $user2 = $this->createMock(IUser::class); - $user2->method('getUID')->will($this->returnValue('user2')); - $user3 = $this->createMock(IUser::class); - $user3->method('getUID')->will($this->returnValue('user3')); + $user2 = $this->createMock(IUser::class); + $user2->method('getUID')->will($this->returnValue('user2')); + $user3 = $this->createMock(IUser::class); + $user3->method('getUID')->will($this->returnValue('user3')); - $this->userManager->expects($this->once()) - ->method('getByEmail') - ->with($email) - ->will($this->returnValue([$email === 'user2@foo.bar' ? $user2 : $user3])); + $this->userManager->expects($this->once()) + ->method('getByEmail') + ->with($email) + ->will($this->returnValue([$email === 'user2@foo.bar' ? $user2 : $user3])); - if ($email === 'user2@foo.bar') { - $this->groupManager->expects($this->at(1)) - ->method('getUserGroupIds') - ->with($user2) - ->will($this->returnValue(['group1', 'group3'])); - } else { - $this->groupManager->expects($this->at(1)) - ->method('getUserGroupIds') - ->with($user3) - ->will($this->returnValue(['group3', 'group3'])); + if ($email === 'user2@foo.bar') { + $this->groupManager->expects($this->at(1)) + ->method('getUserGroupIds') + ->with($user2) + ->will($this->returnValue(['group1', 'group3'])); + } else { + $this->groupManager->expects($this->at(1)) + ->method('getUserGroupIds') + ->with($user3) + ->will($this->returnValue(['group3', 'group3'])); + } } $this->assertEquals($expects, $this->connector->findByUri($uri, 'principals/users')); @@ -398,40 +448,56 @@ class PrincipalTest extends TestCase { public function findByUriWithGroupRestrictionDataProvider() { return [ - ['mailto:user2@foo.bar', 'user2@foo.bar', 'principals/users/user2'], - ['mailto:user3@foo.bar', 'user3@foo.bar', null], + ['yes', 'mailto:user2@foo.bar', 'user2@foo.bar', null], + ['no', 'mailto:user2@foo.bar', 'user2@foo.bar', 'principals/users/user2'], + ['yes', 'mailto:user3@foo.bar', 'user3@foo.bar', null], + ['no', 'mailto:user3@foo.bar', 'user3@foo.bar', null], ]; } /** * @dataProvider findByUriWithoutGroupRestrictionDataProvider */ - public function testFindByUriWithoutGroupRestriction($uri, $email, $expects) { + public function testFindByUriWithoutGroupRestriction($disableFreeBusy, $uri, $email, $expects) { $this->shareManager->expects($this->once()) ->method('shareApiEnabled') ->will($this->returnValue(true)); + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('dav', 'disableFreeBusy', 'no') + ->will($this->returnValue($disableFreeBusy)); - $this->shareManager->expects($this->once()) - ->method('shareWithGroupMembersOnly') - ->will($this->returnValue(false)); + if ($disableFreeBusy === 'yes') { + $this->shareManager->expects($this->never()) + ->method('shareWithGroupMembersOnly'); - $user2 = $this->createMock(IUser::class); - $user2->method('getUID')->will($this->returnValue('user2')); - $user3 = $this->createMock(IUser::class); - $user3->method('getUID')->will($this->returnValue('user3')); + $this->userManager->expects($this->never()) + ->method('getByEmail'); + } else { + $this->shareManager->expects($this->once()) + ->method('shareWithGroupMembersOnly') + ->will($this->returnValue(false)); - $this->userManager->expects($this->once()) - ->method('getByEmail') - ->with($email) - ->will($this->returnValue([$email === 'user2@foo.bar' ? $user2 : $user3])); + $user2 = $this->createMock(IUser::class); + $user2->method('getUID')->will($this->returnValue('user2')); + $user3 = $this->createMock(IUser::class); + $user3->method('getUID')->will($this->returnValue('user3')); + + $this->userManager->expects($this->once()) + ->method('getByEmail') + ->with($email) + ->will($this->returnValue([$email === 'user2@foo.bar' ? $user2 : $user3])); + } $this->assertEquals($expects, $this->connector->findByUri($uri, 'principals/users')); } public function findByUriWithoutGroupRestrictionDataProvider() { return [ - ['mailto:user2@foo.bar', 'user2@foo.bar', 'principals/users/user2'], - ['mailto:user3@foo.bar', 'user3@foo.bar', 'principals/users/user3'], + ['yes', 'mailto:user2@foo.bar', 'user2@foo.bar', null], + ['yes', 'mailto:user3@foo.bar', 'user3@foo.bar', null], + ['no', 'mailto:user2@foo.bar', 'user2@foo.bar', 'principals/users/user2'], + ['no', 'mailto:user3@foo.bar', 'user3@foo.bar', 'principals/users/user3'], ]; } } diff --git a/apps/dav/tests/unit/Controller/InvitationResponseControllerTest.php b/apps/dav/tests/unit/Controller/InvitationResponseControllerTest.php new file mode 100644 index 00000000000..7efb64e3dd4 --- /dev/null +++ b/apps/dav/tests/unit/Controller/InvitationResponseControllerTest.php @@ -0,0 +1,455 @@ +<?php +declare(strict_types=1); +/** + * @copyright 2018, 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\DAV\Controller; + +use OCA\DAV\CalDAV\InvitationResponse\InvitationResponseServer; +use OCA\DAV\CalDAV\Schedule\Plugin; +use OCA\DAV\Controller\InvitationResponseController; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use OCP\IRequest; +use Sabre\VObject\ITip\Message; +use Test\TestCase; + +class InvitationResponseControllerTest extends TestCase { + + /** @var InvitationResponseController */ + private $controller; + + /** @var IDBConnection|\PHPUnit_Framework_MockObject_MockObject */ + private $dbConnection; + + /** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */ + private $request; + + /** @var ITimeFactory|\PHPUnit_Framework_MockObject_MockObject */ + private $timeFactory; + + /** @var InvitationResponseServer|\PHPUnit_Framework_MockObject_MockObject */ + private $responseServer; + + public function setUp() { + parent::setUp(); + + $this->dbConnection = $this->createMock(IDBConnection::class); + $this->request = $this->createMock(IRequest::class); + $this->timeFactory = $this->createMock(ITimeFactory::class); + $this->responseServer = $this->getMockBuilder(InvitationResponseServer::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->controller = new InvitationResponseController( + 'appName', + $this->request, + $this->dbConnection, + $this->timeFactory, + $this->responseServer + ); + } + + public function testAccept() { + $this->buildQueryExpects('TOKEN123', [ + 'id' => 0, + 'uid' => 'this-is-the-events-uid', + 'recurrenceid' => null, + 'attendee' => 'mailto:attendee@foo.bar', + 'organizer' => 'mailto:organizer@foo.bar', + 'sequence' => null, + 'token' => 'TOKEN123', + 'expiration' => 420000, + ], 1337); + + $expected = <<<EOF +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN +METHOD:REPLY +BEGIN:VEVENT +ATTENDEE;PARTSTAT=ACCEPTED:mailto:attendee@foo.bar +ORGANIZER:mailto:organizer@foo.bar +UID:this-is-the-events-uid +SEQUENCE:0 +REQUEST-STATUS:2.0;Success +DTSTAMP:19700101T002217Z +END:VEVENT +END:VCALENDAR + +EOF; + $expected = preg_replace('~\R~u', "\r\n", $expected); + + $called = false; + $this->responseServer->expects($this->once()) + ->method('handleITipMessage') + ->will($this->returnCallback(function(Message $iTipMessage) use (&$called, $expected) { + $called = true; + $this->assertEquals('this-is-the-events-uid', $iTipMessage->uid); + $this->assertEquals('VEVENT', $iTipMessage->component); + $this->assertEquals('REPLY', $iTipMessage->method); + $this->assertEquals(null, $iTipMessage->sequence); + $this->assertEquals('mailto:attendee@foo.bar', $iTipMessage->sender); + $this->assertEquals('mailto:organizer@foo.bar', $iTipMessage->recipient); + + $iTipMessage->scheduleStatus = '1.2;Message delivered locally'; + + $this->assertEquals($expected, $iTipMessage->message->serialize()); + })); + + + + $response = $this->controller->accept('TOKEN123'); + $this->assertInstanceOf(TemplateResponse::class, $response); + $this->assertEquals('schedule-response-success', $response->getTemplateName()); + $this->assertEquals([], $response->getParams()); + $this->assertTrue($called); + } + + public function testAcceptSequence() { + $this->buildQueryExpects('TOKEN123', [ + 'id' => 0, + 'uid' => 'this-is-the-events-uid', + 'recurrenceid' => null, + 'attendee' => 'mailto:attendee@foo.bar', + 'organizer' => 'mailto:organizer@foo.bar', + 'sequence' => 1337, + 'token' => 'TOKEN123', + 'expiration' => 420000, + ], 1337); + + $expected = <<<EOF +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN +METHOD:REPLY +BEGIN:VEVENT +ATTENDEE;PARTSTAT=ACCEPTED:mailto:attendee@foo.bar +ORGANIZER:mailto:organizer@foo.bar +UID:this-is-the-events-uid +SEQUENCE:1337 +REQUEST-STATUS:2.0;Success +DTSTAMP:19700101T002217Z +END:VEVENT +END:VCALENDAR + +EOF; + $expected = preg_replace('~\R~u', "\r\n", $expected); + + $called = false; + $this->responseServer->expects($this->once()) + ->method('handleITipMessage') + ->will($this->returnCallback(function(Message $iTipMessage) use (&$called, $expected) { + $called = true; + $this->assertEquals('this-is-the-events-uid', $iTipMessage->uid); + $this->assertEquals('VEVENT', $iTipMessage->component); + $this->assertEquals('REPLY', $iTipMessage->method); + $this->assertEquals(1337, $iTipMessage->sequence); + $this->assertEquals('mailto:attendee@foo.bar', $iTipMessage->sender); + $this->assertEquals('mailto:organizer@foo.bar', $iTipMessage->recipient); + + $iTipMessage->scheduleStatus = '1.2;Message delivered locally'; + + $this->assertEquals($expected, $iTipMessage->message->serialize()); + })); + + + + $response = $this->controller->accept('TOKEN123'); + $this->assertInstanceOf(TemplateResponse::class, $response); + $this->assertEquals('schedule-response-success', $response->getTemplateName()); + $this->assertEquals([], $response->getParams()); + $this->assertTrue($called); + } + + public function testAcceptRecurrenceId() { + $this->buildQueryExpects('TOKEN123', [ + 'id' => 0, + 'uid' => 'this-is-the-events-uid', + 'recurrenceid' => "RECURRENCE-ID;TZID=Europe/Berlin:20180726T150000\n", + 'attendee' => 'mailto:attendee@foo.bar', + 'organizer' => 'mailto:organizer@foo.bar', + 'sequence' => null, + 'token' => 'TOKEN123', + 'expiration' => 420000, + ], 1337); + + $expected = <<<EOF +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN +METHOD:REPLY +BEGIN:VEVENT +ATTENDEE;PARTSTAT=ACCEPTED:mailto:attendee@foo.bar +ORGANIZER:mailto:organizer@foo.bar +UID:this-is-the-events-uid +SEQUENCE:0 +REQUEST-STATUS:2.0;Success +RECURRENCE-ID;TZID=Europe/Berlin:20180726T150000 +DTSTAMP:19700101T002217Z +END:VEVENT +END:VCALENDAR + +EOF; + $expected = preg_replace('~\R~u', "\r\n", $expected); + + $called = false; + $this->responseServer->expects($this->once()) + ->method('handleITipMessage') + ->will($this->returnCallback(function(Message $iTipMessage) use (&$called, $expected) { + $called = true; + $this->assertEquals('this-is-the-events-uid', $iTipMessage->uid); + $this->assertEquals('VEVENT', $iTipMessage->component); + $this->assertEquals('REPLY', $iTipMessage->method); + $this->assertEquals(0, $iTipMessage->sequence); + $this->assertEquals('mailto:attendee@foo.bar', $iTipMessage->sender); + $this->assertEquals('mailto:organizer@foo.bar', $iTipMessage->recipient); + + $iTipMessage->scheduleStatus = '1.2;Message delivered locally'; + + $this->assertEquals($expected, $iTipMessage->message->serialize()); + })); + + + + $response = $this->controller->accept('TOKEN123'); + $this->assertInstanceOf(TemplateResponse::class, $response); + $this->assertEquals('schedule-response-success', $response->getTemplateName()); + $this->assertEquals([], $response->getParams()); + $this->assertTrue($called); + } + + public function testAcceptTokenNotFound() { + $this->buildQueryExpects('TOKEN123', null, 1337); + + $response = $this->controller->accept('TOKEN123'); + $this->assertInstanceOf(TemplateResponse::class, $response); + $this->assertEquals('schedule-response-error', $response->getTemplateName()); + $this->assertEquals([], $response->getParams()); + } + + public function testAcceptExpiredToken() { + $this->buildQueryExpects('TOKEN123', [ + 'id' => 0, + 'uid' => 'this-is-the-events-uid', + 'recurrenceid' => null, + 'attendee' => 'mailto:attendee@foo.bar', + 'organizer' => 'mailto:organizer@foo.bar', + 'sequence' => null, + 'token' => 'TOKEN123', + 'expiration' => 42, + ], 1337); + + $response = $this->controller->accept('TOKEN123'); + $this->assertInstanceOf(TemplateResponse::class, $response); + $this->assertEquals('schedule-response-error', $response->getTemplateName()); + $this->assertEquals([], $response->getParams()); + } + + public function testDecline() { + $this->buildQueryExpects('TOKEN123', [ + 'id' => 0, + 'uid' => 'this-is-the-events-uid', + 'recurrenceid' => null, + 'attendee' => 'mailto:attendee@foo.bar', + 'organizer' => 'mailto:organizer@foo.bar', + 'sequence' => null, + 'token' => 'TOKEN123', + 'expiration' => 420000, + ], 1337); + + $expected = <<<EOF +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN +METHOD:REPLY +BEGIN:VEVENT +ATTENDEE;PARTSTAT=DECLINED:mailto:attendee@foo.bar +ORGANIZER:mailto:organizer@foo.bar +UID:this-is-the-events-uid +SEQUENCE:0 +REQUEST-STATUS:2.0;Success +DTSTAMP:19700101T002217Z +END:VEVENT +END:VCALENDAR + +EOF; + $expected = preg_replace('~\R~u', "\r\n", $expected); + + $called = false; + $this->responseServer->expects($this->once()) + ->method('handleITipMessage') + ->will($this->returnCallback(function(Message $iTipMessage) use (&$called, $expected) { + $called = true; + $this->assertEquals('this-is-the-events-uid', $iTipMessage->uid); + $this->assertEquals('VEVENT', $iTipMessage->component); + $this->assertEquals('REPLY', $iTipMessage->method); + $this->assertEquals(null, $iTipMessage->sequence); + $this->assertEquals('mailto:attendee@foo.bar', $iTipMessage->sender); + $this->assertEquals('mailto:organizer@foo.bar', $iTipMessage->recipient); + + $iTipMessage->scheduleStatus = '1.2;Message delivered locally'; + + $this->assertEquals($expected, $iTipMessage->message->serialize()); + })); + + + + $response = $this->controller->decline('TOKEN123'); + $this->assertInstanceOf(TemplateResponse::class, $response); + $this->assertEquals('schedule-response-success', $response->getTemplateName()); + $this->assertEquals([], $response->getParams()); + $this->assertTrue($called); + } + + public function testOptions() { + $response = $this->controller->options('TOKEN123'); + $this->assertInstanceOf(TemplateResponse::class, $response); + $this->assertEquals('schedule-response-options', $response->getTemplateName()); + $this->assertEquals(['token' => 'TOKEN123'], $response->getParams()); + } + + public function testProcessMoreOptionsResult() { + $this->request->expects($this->at(0)) + ->method('getParam') + ->with('partStat') + ->will($this->returnValue('TENTATIVE')); + $this->request->expects($this->at(1)) + ->method('getParam') + ->with('guests') + ->will($this->returnValue('7')); + $this->request->expects($this->at(2)) + ->method('getParam') + ->with('comment') + ->will($this->returnValue('Foo bar Bli blub')); + + $this->buildQueryExpects('TOKEN123', [ + 'id' => 0, + 'uid' => 'this-is-the-events-uid', + 'recurrenceid' => null, + 'attendee' => 'mailto:attendee@foo.bar', + 'organizer' => 'mailto:organizer@foo.bar', + 'sequence' => null, + 'token' => 'TOKEN123', + 'expiration' => 420000, + ], 1337); + + $expected = <<<EOF +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN +METHOD:REPLY +BEGIN:VEVENT +ATTENDEE;PARTSTAT=TENTATIVE;X-RESPONSE-COMMENT=Foo bar Bli blub;X-NUM-GUEST + S=7:mailto:attendee@foo.bar +ORGANIZER:mailto:organizer@foo.bar +UID:this-is-the-events-uid +SEQUENCE:0 +REQUEST-STATUS:2.0;Success +DTSTAMP:19700101T002217Z +COMMENT:Foo bar Bli blub +END:VEVENT +END:VCALENDAR + +EOF; + $expected = preg_replace('~\R~u', "\r\n", $expected); + + $called = false; + $this->responseServer->expects($this->once()) + ->method('handleITipMessage') + ->will($this->returnCallback(function(Message $iTipMessage) use (&$called, $expected) { + $called = true; + $this->assertEquals('this-is-the-events-uid', $iTipMessage->uid); + $this->assertEquals('VEVENT', $iTipMessage->component); + $this->assertEquals('REPLY', $iTipMessage->method); + $this->assertEquals(null, $iTipMessage->sequence); + $this->assertEquals('mailto:attendee@foo.bar', $iTipMessage->sender); + $this->assertEquals('mailto:organizer@foo.bar', $iTipMessage->recipient); + + $iTipMessage->scheduleStatus = '1.2;Message delivered locally'; + + $this->assertEquals($expected, $iTipMessage->message->serialize()); + })); + + + + $response = $this->controller->processMoreOptionsResult('TOKEN123'); + $this->assertInstanceOf(TemplateResponse::class, $response); + $this->assertEquals('schedule-response-success', $response->getTemplateName()); + $this->assertEquals([], $response->getParams()); + $this->assertTrue($called); + } + + private function buildQueryExpects($token, $return, $time) { + $queryBuilder = $this->createMock(IQueryBuilder::class); + $stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class); + $expr = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class); + + $this->dbConnection->expects($this->once()) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder)); + $queryBuilder->method('expr') + ->will($this->returnValue($expr)); + $queryBuilder->method('createNamedParameter') + ->will($this->returnValueMap([ + [$token, \PDO::PARAM_STR, null, 'namedParameterToken'] + ])); + + $stmt->expects($this->once()) + ->method('fetch') + ->with(\PDO::FETCH_ASSOC) + ->will($this->returnValue($return)); + + $expr->expects($this->once()) + ->method('eq') + ->with('token', 'namedParameterToken') + ->will($this->returnValue('EQ STATEMENT')); + + $this->dbConnection->expects($this->once()) + ->method('getQueryBuilder') + ->with() + ->will($this->returnValue($queryBuilder)); + + $queryBuilder->expects($this->at(0)) + ->method('select') + ->with('*') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(1)) + ->method('from') + ->with('calendar_invitations') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(4)) + ->method('where') + ->with('EQ STATEMENT') + ->will($this->returnValue($queryBuilder)); + $queryBuilder->expects($this->at(5)) + ->method('execute') + ->with() + ->will($this->returnValue($stmt)); + + $this->timeFactory->method('getTime') + ->will($this->returnValue($time)); + } +} diff --git a/apps/dav/tests/unit/DAV/AnonymousOptionsTest.php b/apps/dav/tests/unit/DAV/AnonymousOptionsTest.php new file mode 100644 index 00000000000..4e440e6644b --- /dev/null +++ b/apps/dav/tests/unit/DAV/AnonymousOptionsTest.php @@ -0,0 +1,71 @@ +<?php +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @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\DAV; + +use OCA\DAV\Connector\Sabre\AnonymousOptionsPlugin; +use Sabre\DAV\Auth\Backend\BasicCallBack; +use Sabre\DAV\Auth\Plugin; +use Sabre\DAV\Server; +use Sabre\HTTP\ResponseInterface; +use Sabre\HTTP\Sapi; +use Test\TestCase; + +class AnonymousOptionsTest extends TestCase { + private function sendRequest($method, $path) { + $server = new Server(); + $server->addPlugin(new AnonymousOptionsPlugin()); + $server->addPlugin(new Plugin(new BasicCallBack(function() { + return false; + }))); + + $server->httpRequest->setMethod($method); + $server->httpRequest->setUrl($path); + + $server->sapi = new SapiMock(); + $server->exec(); + return $server->httpResponse; + } + + public function testAnonymousOptionsRoot() { + $response = $this->sendRequest('OPTIONS', ''); + + $this->assertEquals(200, $response->getStatus()); + } + + public function testAnonymousOptionsNonRoot() { + $response = $this->sendRequest('OPTIONS', 'foo'); + + $this->assertEquals(401, $response->getStatus()); + } +} + +class SapiMock extends Sapi { + /** + * Overriding this so nothing is ever echo'd. + * + * @return void + */ + static function sendResponse(ResponseInterface $response) { + + } + +} diff --git a/apps/dav/tests/unit/ServerTest.php b/apps/dav/tests/unit/ServerTest.php index 58c77c1b0ec..986899a2107 100644 --- a/apps/dav/tests/unit/ServerTest.php +++ b/apps/dav/tests/unit/ServerTest.php @@ -38,12 +38,24 @@ use OCA\DAV\AppInfo\PluginManager; */ class ServerTest extends \Test\TestCase { - public function test() { - /** @var IRequest $r */ + /** + * @dataProvider providesUris + */ + public function test($uri, array $plugins) { + /** @var IRequest | \PHPUnit_Framework_MockObject_MockObject $r */ $r = $this->createMock(IRequest::class); - $r->method('getRequestUri') - ->willReturn('/'); + $r->expects($this->any())->method('getRequestUri')->willReturn($uri); $s = new Server($r, '/'); - $this->assertInstanceOf('OCA\DAV\Server', $s); + $this->assertNotNull($s->server); + foreach ($plugins as $plugin) { + $this->assertNotNull($s->server->getPlugin($plugin)); + } + } + public function providesUris() { + return [ + 'principals' => ['principals/users/admin', ['caldav', 'oc-resource-sharing', 'carddav']], + 'calendars' => ['calendars/admin', ['caldav', 'oc-resource-sharing']], + 'addressbooks' => ['addressbooks/admin', ['carddav', 'oc-resource-sharing']], + ]; } } diff --git a/apps/dav/tests/unit/Settings/CalDAVSettingsTest.php b/apps/dav/tests/unit/Settings/CalDAVSettingsTest.php index 36e2aaa9ebb..a9df63a03ab 100644 --- a/apps/dav/tests/unit/Settings/CalDAVSettingsTest.php +++ b/apps/dav/tests/unit/Settings/CalDAVSettingsTest.php @@ -49,10 +49,10 @@ class CalDAVSettingsTest extends TestCase { } public function testGetSection() { - $this->assertEquals('server', $this->settings->getSection()); + $this->assertEquals('groupware', $this->settings->getSection()); } public function testGetPriority() { - $this->assertEquals(20, $this->settings->getPriority()); + $this->assertEquals(10, $this->settings->getPriority()); } } |