From 31a25dc6b0545eb9bcb215990c8a71f1792f61f8 Mon Sep 17 00:00:00 2001 From: Brad Rubenstein Date: Sat, 10 Nov 2018 02:35:42 -0800 Subject: Customize presentation of accept/decline buttons in iMip mail Fix Issue #11230 Only present accept/decline button links in iMip mail for REQUEST, not CANCEL or others. Fix Issue #12156 Implement config setting "dav.invitation_link_recipients", to control which invitation recipients see accept/decline button links. The default, for public internet facing servers, is to always include them. For a server on a private intranet, this setting can be set to the email addresses or email domains of users whose browsers can access the nextcloud server referenced by those accept/decline button links. It can also be set to "false" to exclude the links from all requests. Signed-off-by: Brad Rubenstein --- apps/dav/lib/CalDAV/Schedule/IMipPlugin.php | 42 ++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 4 deletions(-) (limited to 'apps/dav/lib') diff --git a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php index 3ff3ed0c569..4375c081d55 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php @@ -144,11 +144,11 @@ class IMipPlugin extends SabreIMipPlugin { $summary = $iTipMessage->message->VEVENT->SUMMARY; - if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto') { + if (strcasecmp(parse_url($iTipMessage->sender, PHP_URL_SCHEME), 'mailto') !== 0) { return; } - if (parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto') { + if (strcasecmp(parse_url($iTipMessage->recipient, PHP_URL_SCHEME), 'mailto') !== 0) { return; } @@ -239,9 +239,44 @@ class IMipPlugin extends SabreIMipPlugin { $meetingAttendeeName, $meetingInviteeName); $this->addBulletList($template, $l10n, $meetingWhen, $meetingLocation, $meetingDescription, $meetingUrl); - $this->addResponseButtons($template, $l10n, $iTipMessage, $lastOccurrence); + + + // Only add response buttons to invitation requests: Fix Issue #11230 + if ($method == self::METHOD_REQUEST) { + + /* + ** Only offer invitation accept/reject buttons, which link back to the + ** nextcloud server, to recipients who can access the nextcloud server via + ** their internet/intranet. Issue #12156 + ** + ** For nextcloud servers accessible to the public internet, the default + ** "dav.invitation_link_recipients" value "true" (all recipients) is appropriate. + ** + ** When the nextcloud server is restricted behind a firewall, accessible + ** only via an internal network or via vpn, you can set "dav.invitation_link_recipients" + ** to the email address or email domain, or array of addresses or domains, + ** of recipients who can access the server. + ** + ** To deliver URL's always, set invitation_link_recipients to boolean "true". + ** To suppress URL's entirely, set invitation_link_recipients to boolean "false". + */ + + $recipientDomain = substr(strrchr($recipient, "@"), 1); + $invitationLinkRecipients = $this->config->getSystemValue('dav.invitation_link_recipients', true); + if (is_array($invitationLinkRecipients)) { + $invitationLinkRecipients = array_map('strtolower', $invitationLinkRecipients); // for case insensitive in_array + } + if ($invitationLinkRecipients === true + || (is_string($invitationLinkRecipients) && strcasecmp($recipient, $invitationLinkRecipients) === 0) + || (is_string($invitationLinkRecipients) && strcasecmp($recipientDomain, $invitationLinkRecipients) === 0) + || (is_array($invitationLinkRecipients) && in_array(strtolower($recipient), $invitationLinkRecipients)) + || (is_array($invitationLinkRecipients) && in_array(strtolower($recipientDomain), $invitationLinkRecipients))) { + $this->addResponseButtons($template, $l10n, $iTipMessage, $lastOccurrence); + } + } $template->addFooter(); + $message->useTemplate($template); $attachment = $this->mailer->createAttachment( @@ -447,7 +482,6 @@ class IMipPlugin extends SabreIMipPlugin { $template->setSubject('Invitation: ' . $summary); $template->addHeading($l10n->t('%1$s invited you to »%2$s«', [$inviteeName, $summary]), $l10n->t('Hello %s,', [$attendeeName])); } - } /** -- cgit v1.2.3 From 6421e30b2ccad91bb708d51609d7dd72cbfc0ebe Mon Sep 17 00:00:00 2001 From: Brad Rubenstein Date: Mon, 12 Nov 2018 12:11:47 -0800 Subject: Respect RSVP parameter for attendees when adding accept/decline buttons. If RSVP=TRUE parameter is FALSE or absent for an ATTENDEE, then do no present accept/decline buttons. The organizer isn't asking for an RSVP. Signed-off-by: Brad Rubenstein --- apps/dav/lib/CalDAV/Schedule/IMipPlugin.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) (limited to 'apps/dav/lib') diff --git a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php index 4375c081d55..987e6c86f5d 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php @@ -242,7 +242,7 @@ class IMipPlugin extends SabreIMipPlugin { // Only add response buttons to invitation requests: Fix Issue #11230 - if ($method == self::METHOD_REQUEST) { + if (($method == self::METHOD_REQUEST) && $this->getAttendeeRSVP($attendee)) { /* ** Only offer invitation accept/reject buttons, which link back to the @@ -380,6 +380,21 @@ class IMipPlugin extends SabreIMipPlugin { return $default; } + /** + * @param Property|null $attendee + * @return bool + */ + private function getAttendeeRSVP(Property $attendee = null) { + if ($attendee !== null) { + $rsvp = $attendee->offsetGet('RSVP'); + if (($rsvp instanceof Parameter) && (strcasecmp($rsvp->getValue(), 'TRUE') === 0)) { + return true; + } + } + // RFC 5545 3.2.17: default RSVP is false + return false; + } + /** * @param IL10N $l10n * @param Property $dtstart @@ -538,7 +553,7 @@ class IMipPlugin extends SabreIMipPlugin { $moreOptionsURL, $l10n->t('More options …') ]); $text = $l10n->t('More options at %s', [$moreOptionsURL]); - + $template->addBodyText($html, $text); } -- cgit v1.2.3 From 79d20e47581b0cae9809b025c7d2b922c0b29dea Mon Sep 17 00:00:00 2001 From: Brad Rubenstein Date: Mon, 12 Nov 2018 12:59:12 -0800 Subject: Revert 3ff3ed0c56 case-insensitive compares. My oops. The comparisons, which are copied from the IMipPlugin shipped with sabre-io/dav, do not need to be case insensitive because the sender and recipient names are normalized by sabre, (see calls to getNormalizedValue in voboject/lib/ITip/Broker.php). Signed-off-by: Brad Rubenstein --- apps/dav/lib/CalDAV/Schedule/IMipPlugin.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'apps/dav/lib') diff --git a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php index 987e6c86f5d..7035d3c780f 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php @@ -144,11 +144,11 @@ class IMipPlugin extends SabreIMipPlugin { $summary = $iTipMessage->message->VEVENT->SUMMARY; - if (strcasecmp(parse_url($iTipMessage->sender, PHP_URL_SCHEME), 'mailto') !== 0) { + if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto') { return; } - if (strcasecmp(parse_url($iTipMessage->recipient, PHP_URL_SCHEME), 'mailto') !== 0) { + if (parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto') { return; } -- cgit v1.2.3 From 8d8bcea1d8cb7328e898654874348a64afc773e4 Mon Sep 17 00:00:00 2001 From: brad2014 Date: Wed, 17 Jul 2019 16:47:15 -0700 Subject: Move dav.invitation_link_recipients from getSystemValue to getAppValue Per @georgehrke change request for PR #12392, instead of setting dav.invitation_link_recipients in the system config.php file, we set it in the database table oc_appconfig. Furthermore, the value of the config variable is always a string: 'yes' to include links in imip mail, 'no' to exclude them, or a comma-separated list of email addresses and/or domains for which they should be included. If not specified in oc_appconfig, the default is 'yes'. Signed-off-by: brad2014 --- apps/dav/lib/CalDAV/Schedule/IMipPlugin.php | 24 ++++++++--------- .../tests/unit/CalDAV/Schedule/IMipPluginTest.php | 30 +++++++++++----------- config/config.sample.php | 27 ------------------- 3 files changed, 26 insertions(+), 55 deletions(-) (limited to 'apps/dav/lib') diff --git a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php index 7035d3c780f..56b3ab04ddc 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php @@ -249,28 +249,26 @@ class IMipPlugin extends SabreIMipPlugin { ** nextcloud server, to recipients who can access the nextcloud server via ** their internet/intranet. Issue #12156 ** + ** The app setting is stored in the appconfig database table. + ** ** For nextcloud servers accessible to the public internet, the default - ** "dav.invitation_link_recipients" value "true" (all recipients) is appropriate. + ** "invitation_link_recipients" value "yes" (all recipients) is appropriate. ** ** When the nextcloud server is restricted behind a firewall, accessible ** only via an internal network or via vpn, you can set "dav.invitation_link_recipients" - ** to the email address or email domain, or array of addresses or domains, + ** to the email address or email domain, or comma separated list of addresses or domains, ** of recipients who can access the server. ** - ** To deliver URL's always, set invitation_link_recipients to boolean "true". - ** To suppress URL's entirely, set invitation_link_recipients to boolean "false". + ** To always deliver URLs, set invitation_link_recipients to "yes". + ** To suppress URLs entirely, set invitation_link_recipients to boolean "no". */ $recipientDomain = substr(strrchr($recipient, "@"), 1); - $invitationLinkRecipients = $this->config->getSystemValue('dav.invitation_link_recipients', true); - if (is_array($invitationLinkRecipients)) { - $invitationLinkRecipients = array_map('strtolower', $invitationLinkRecipients); // for case insensitive in_array - } - if ($invitationLinkRecipients === true - || (is_string($invitationLinkRecipients) && strcasecmp($recipient, $invitationLinkRecipients) === 0) - || (is_string($invitationLinkRecipients) && strcasecmp($recipientDomain, $invitationLinkRecipients) === 0) - || (is_array($invitationLinkRecipients) && in_array(strtolower($recipient), $invitationLinkRecipients)) - || (is_array($invitationLinkRecipients) && in_array(strtolower($recipientDomain), $invitationLinkRecipients))) { + $invitationLinkRecipients = explode(',', preg_replace('/\s+/', '', strtolower($this->config->getAppValue('dav', 'invitation_link_recipients', 'yes')))); + + if (strcmp('yes', $invitationLinkRecipients[0]) === 0 + || in_array(strtolower($recipient), $invitationLinkRecipients) + || in_array(strtolower($recipientDomain), $invitationLinkRecipients)) { $this->addResponseButtons($template, $l10n, $iTipMessage, $lastOccurrence); } } diff --git a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php index 84da1d2727c..967f3a51481 100644 --- a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php +++ b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php @@ -96,9 +96,9 @@ class IMipPluginTest extends TestCase { public function testDelivery() { $this->config - ->method('getSystemValue') - ->with('dav.invitation_link_recipients', true) - ->willReturn(true); + ->method('getAppValue') + ->with('dav', 'invitation_link_recipients', 'yes') + ->willReturn('yes'); $message = $this->_testMessage(); $this->_expectSend(); @@ -108,9 +108,9 @@ class IMipPluginTest extends TestCase { public function testFailedDelivery() { $this->config - ->method('getSystemValue') - ->with('dav.invitation_link_recipients', true) - ->willReturn(true); + ->method('getAppValue') + ->with('dav', 'invitation_link_recipients', 'yes') + ->willReturn('yes'); $message = $this->_testMessage(); $this->mailer @@ -127,9 +127,9 @@ class IMipPluginTest extends TestCase { public function testNoMessageSendForPastEvents($veventParams, $expectsMail) { $this->config - ->method('getSystemValue') - ->with('dav.invitation_link_recipients', true) - ->willReturn(true); + ->method('getAppValue') + ->with('dav', 'invitation_link_recipients', 'yes') + ->willReturn('yes'); $message = $this->_testMessage( $veventParams ); @@ -167,8 +167,8 @@ class IMipPluginTest extends TestCase { $this->_expectSend($recipient, true, $has_buttons); $this->config - ->method('getSystemValue') - ->with('dav.invitation_link_recipients', true) + ->method('getAppValue') + ->with('dav', 'invitation_link_recipients', 'yes') ->willReturn($config_setting); $this->plugin->schedule($message); @@ -178,13 +178,13 @@ class IMipPluginTest extends TestCase { public function dataIncludeResponseButtons() { return [ // dav.invitation_link_recipients, recipient, $has_buttons - [ true, 'joe@internal.com', true], + [ 'yes', 'joe@internal.com', true], [ 'joe@internal.com', 'joe@internal.com', true], [ 'internal.com', 'joe@internal.com', true], - [ ['pete@otherinternal.com', 'internal.com'], 'joe@internal.com', true], - [ false, 'joe@internal.com', false], + [ 'pete@otherinternal.com,internal.com', 'joe@internal.com', true], + [ 'no', 'joe@internal.com', false], [ 'internal.com', 'joe@external.com', false], - [ ['jane@otherinternal.com', 'internal.com'], 'joe@otherinternal.com', false], + [ 'jane@otherinternal.com,internal.com', 'joe@otherinternal.com', false], ]; } diff --git a/config/config.sample.php b/config/config.sample.php index d5f4bb5f437..9c3cc470995 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -1696,33 +1696,6 @@ $CONFIG = array( '/^Microsoft-WebDAV-MiniRedir/', // Windows webdav drive ), -/** -* The caldav server sends invitation emails to invitees, attaching the ICS -* file for the invitation. It also may include, in the body of the e-mail, -* invitation accept/reject web links referencing URL's that point to the nextcloud server. -* -* Although any recipient can read and reply to the ICS file via the iMip protocol, -* we must only present the web links to recipients who can access the nextcloud -* web server via their internet/intranet. -* -* When your nextcloud server is restricted behind a firewall, accessible -* only via an internal network or via vpn, you can set "dav.invitation_link_recipients" -* to the email address or email domain, or array of addresses or domains, -* of recipients who can access the server. Only those recipients will get web links. External -* users can accept/reject invitations by emailing back ICS files containing appropriate -* messages, using the iMip protocol. Many mail clients support this functionality. -* -* To suppress iMip web links entirely, set dav.invitation_link_recipients to false. -* To deliver iMip web links always, set dav.invitation_link_recipients to true. -* -* Examples: -* 'dav.invitation_link_recipients' => 'internal.example.com', -* 'dav.invitation_link_recipients' => array( 'internal.example.com', 'pat@roadwarrior.example.com' ), -* 'dav.invitation_link_recipients' => false, -* -*/ -'dav.invitation_link_recipients' => true, // always include accept/reject server links in iMip emails - /** * By default there is on public pages a link shown that allows users to * learn about the "simple sign up" - see https://nextcloud.com/signup/ -- cgit v1.2.3