Skip to content

Commit

Permalink
Include accept / decline links in CalDAV invitation emails
Browse files Browse the repository at this point in the history
Signed-off-by: Georg Ehrke <developer@georgehrke.com>
  • Loading branch information
georgehrke committed Jun 20, 2018
1 parent c3aea9c commit 5f53787
Show file tree
Hide file tree
Showing 7 changed files with 492 additions and 6 deletions.
4 changes: 4 additions & 0 deletions apps/dav/appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
'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#tentative', 'url' => '/invitation/tentative/{token}', 'verb' => 'GET'],
['name' => 'invitation_response#options', 'url' => '/invitation/moreOptions/{token}', 'verb' => 'GET'],
],
'ocs' => [
['name' => 'direct#getUrl', 'url' => '/api/v1/direct', 'verb' => 'POST'],
Expand Down
3 changes: 3 additions & 0 deletions apps/dav/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
'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',
Expand Down Expand Up @@ -113,6 +114,7 @@
'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',
Expand Down Expand Up @@ -144,6 +146,7 @@
'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\\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',
Expand Down
3 changes: 3 additions & 0 deletions apps/dav/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,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',
Expand Down Expand Up @@ -128,6 +129,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',
Expand Down Expand Up @@ -159,6 +161,7 @@ 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\\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',
Expand Down
108 changes: 108 additions & 0 deletions apps/dav/lib/CalDAV/InvitationResponse/InvitationResponseServer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?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;

class InvitationResponseServer {

/** @var \OCA\DAV\Connector\Sabre\Server */
public $server;

/**
* InvitationResponseServer constructor.
*
* @param string $baseUri
*/
public function __construct($baseUri) {
$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);
}
});
}
}
94 changes: 88 additions & 6 deletions apps/dav/lib/CalDAV/Schedule/IMipPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@
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;
Expand Down Expand Up @@ -79,6 +81,12 @@ class IMipPlugin extends SabreIMipPlugin {
/** @var IURLGenerator */
private $urlGenerator;

/** @var ISecureRandom */
private $random;

/** @var IDBConnection */
private $db;

/** @var Defaults */
private $defaults;

Expand All @@ -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;
Expand All @@ -107,6 +120,8 @@ public function __construct(IConfig $config, IMailer $mailer, ILogger $logger, I
$this->timeFactory = $timeFactory;
$this->l10nFactory = $l10nFactory;
$this->urlGenerator = $urlGenerator;
$this->random = $random;
$this->db = $db;
$this->defaults = $defaults;
}

Expand Down Expand Up @@ -138,7 +153,9 @@ public function schedule(Message $iTipMessage) {
}

// 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;
}

Expand Down Expand Up @@ -222,6 +239,7 @@ public function schedule(Message $iTipMessage) {
$meetingAttendeeName, $meetingInviteeName);
$this->addBulletList($template, $l10n, $meetingWhen, $meetingLocation,
$meetingDescription, $meetingUrl);
$this->addResponseButtons($template, $l10n, $iTipMessage, $lastOccurrence);

$template->addFooter();
$message->useTemplate($template);
Expand Down Expand Up @@ -249,9 +267,9 @@ public function schedule(Message $iTipMessage) {
/**
* 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;

Expand Down Expand Up @@ -291,8 +309,7 @@ private function isEventInThePast(VCalendar $vObject) {
}
}

$currentTime = $this->timeFactory->getTime();
return $lastOccurrence < $currentTime;
return $lastOccurrence;
}


Expand Down Expand Up @@ -459,6 +476,38 @@ private function addBulletList(IEMailTemplate $template, IL10N $l10n, $time, $lo
}
}

/**
* @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
Expand All @@ -468,4 +517,37 @@ private function getAbsoluteImagePath($path) {
$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'}->getValue() : null;
$uid = $vevent->{'UID'};

$query = $this->db->getQueryBuilder();
$query->insert('calendar_invitation_tokens')
->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;
}
}
Loading

0 comments on commit 5f53787

Please sign in to comment.