diff --git a/docker/kolab/utils/23-patch-system.sh b/docker/kolab/utils/23-patch-system.sh index 19eeb4e7..fa1d6849 100755 --- a/docker/kolab/utils/23-patch-system.sh +++ b/docker/kolab/utils/23-patch-system.sh @@ -1,11 +1,9 @@ #!/bin/bash PATCHPATH=$(pwd)/patches -pushd /usr/lib/python2.7/site-packages/ || exit -patch -p1 < "$PATCHPATH/0001-Resolve-base_dn-in-kolab_user_base_dn-user_base_dn-a.patch" -patch -p1 < "$PATCHPATH/0001-Make-iTip-messages-outlook-compatible.patch" -patch -p1 < "$PATCHPATH/0002-Implement-ACT_STORE_AND_NOTIFY-policy-for-resources-.patch" +pushd /usr/share/roundcubemail/ || exit +# patch -p1 -l < "$PATCHPATH/0002-WOAT-support.patch" +# patch -p1 -l < "$PATCHPATH/0003-PROXY-protocol-support.patch" popd || exit -systemctl restart kolabd -systemctl restart wallace +# systemctl restart httpd diff --git a/docker/kolab/utils/patches/0001-Make-iTip-messages-outlook-compatible.patch b/docker/kolab/utils/patches/0001-Make-iTip-messages-outlook-compatible.patch deleted file mode 100644 index 943eb962..00000000 --- a/docker/kolab/utils/patches/0001-Make-iTip-messages-outlook-compatible.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 294ef5b076c42d029fdb58b8167124f0cfd9e4a8 Mon Sep 17 00:00:00 2001 -From: Christian Mollekopf -Date: Fri, 19 Nov 2021 11:16:33 +0100 -Subject: [PATCH 1/2] Make iTip messages outlook compatible - -Summary: Use multipart/alternative and loose the Content-Disposition header - -Differential Revision: https://git.kolab.org/D3056 ---- - pykolab/xml/event.py | 3 +-- - 1 file changed, 1 insertion(+), 2 deletions(-) - -diff --git a/pykolab/xml/event.py b/pykolab/xml/event.py -index 276bb6f..a668f77 100644 ---- a/pykolab/xml/event.py -+++ b/pykolab/xml/event.py -@@ -1273,7 +1273,7 @@ class Event(object): - from email import charset - charset.add_charset('utf-8', charset.SHORTEST, charset.QP) - -- msg = MIMEMultipart() -+ msg = MIMEMultipart("alternative") - - msg_from = None - attendees = None -@@ -1353,7 +1353,6 @@ class Event(object): - - part.set_payload(self.as_string_itip(method=method)) - -- part.add_header('Content-Disposition', 'attachment; filename="event.ics"') - part.add_header('Content-Transfer-Encoding', '8bit') - - msg.attach(part) --- -2.33.1 - diff --git a/docker/kolab/utils/patches/0001-Resolve-base_dn-in-kolab_user_base_dn-user_base_dn-a.patch b/docker/kolab/utils/patches/0001-Resolve-base_dn-in-kolab_user_base_dn-user_base_dn-a.patch deleted file mode 100644 index eecce30b..00000000 --- a/docker/kolab/utils/patches/0001-Resolve-base_dn-in-kolab_user_base_dn-user_base_dn-a.patch +++ /dev/null @@ -1,92 +0,0 @@ -From f0a02b4484360617baa434bada6c651b8b0b5d30 Mon Sep 17 00:00:00 2001 -From: Aleksander Machniak -Date: Fri, 1 Oct 2021 15:08:12 +0200 -Subject: [PATCH 1/2] Resolve %base_dn in kolab_user_base_dn, user_base_dn and - resource_base_dn - -Reviewers: #pykolab_developers, vanmeeuwen - -Reviewed By: #pykolab_developers, vanmeeuwen - -Subscribers: #pykolab_developers - -Differential Revision: https://git.kolab.org/D2900 ---- - pykolab/auth/ldap/__init__.py | 40 ++++++++++++++++++++--------------- - 1 file changed, 23 insertions(+), 17 deletions(-) - -diff --git a/pykolab/auth/ldap/__init__.py b/pykolab/auth/ldap/__init__.py -index 5c8c668..046c30c 100644 ---- a/pykolab/auth/ldap/__init__.py -+++ b/pykolab/auth/ldap/__init__.py -@@ -647,13 +647,7 @@ class LDAP(Base): - if len(_filter) <= 6: - return None - -- config_base_dn = self.config_get('resource_base_dn') -- ldap_base_dn = self._kolab_domain_root_dn(self.domain) -- -- if ldap_base_dn is not None and not ldap_base_dn == config_base_dn: -- resource_base_dn = ldap_base_dn -- else: -- resource_base_dn = config_base_dn -+ resource_base_dn = self._object_base_dn('resource') - - _results = self.ldap.search_s( - resource_base_dn, -@@ -801,13 +795,7 @@ class LDAP(Base): - if len(_filter) <= 6: - return None - -- config_base_dn = self.config_get('resource_base_dn') -- ldap_base_dn = self._kolab_domain_root_dn(self.domain) -- -- if ldap_base_dn is not None and not ldap_base_dn == config_base_dn: -- resource_base_dn = ldap_base_dn -- else: -- resource_base_dn = config_base_dn -+ resource_base_dn = self._object_base_dn('resource') - - _results = self.ldap.search_s( - resource_base_dn, -@@ -2470,9 +2458,7 @@ class LDAP(Base): - - conf_prefix = 'kolab_' if kolabuser else '' - -- user_base_dn = self.config_get(conf_prefix + 'user_base_dn') -- if user_base_dn is None: -- user_base_dn = self.config_get('base_dn') -+ user_base_dn = self._object_base_dn('user', conf_prefix) - - auth_attrs = self.config_get_list('auth_attributes') - -@@ -2684,6 +2670,26 @@ class LDAP(Base): - - return domains - -+ def _object_base_dn(self, objectType, prefix=''): -+ """ -+ Get configured base DN for specified Kolab object type -+ """ -+ object_base_dn = self.config_get(prefix + objectType + '_base_dn') -+ config_base_dn = self.config_get('base_dn') -+ ldap_base_dn = self._kolab_domain_root_dn(self.domain) -+ -+ if ldap_base_dn is not None and not ldap_base_dn == config_base_dn: -+ base_dn = ldap_base_dn -+ else: -+ base_dn = config_base_dn -+ -+ if object_base_dn is None: -+ object_base_dn = base_dn -+ else: -+ object_base_dn = object_base_dn % ({'base_dn': base_dn}) -+ -+ return object_base_dn -+ - def _synchronize_callback(self, *args, **kw): - """ - Determine the characteristics of the callback being placed, and --- -2.33.1 - diff --git a/docker/kolab/utils/patches/0002-Implement-ACT_STORE_AND_NOTIFY-policy-for-resources-.patch b/docker/kolab/utils/patches/0002-Implement-ACT_STORE_AND_NOTIFY-policy-for-resources-.patch deleted file mode 100644 index fb3b55b1..00000000 --- a/docker/kolab/utils/patches/0002-Implement-ACT_STORE_AND_NOTIFY-policy-for-resources-.patch +++ /dev/null @@ -1,324 +0,0 @@ -From 51d63556196de7866e9582fde55310ac2f3eee47 Mon Sep 17 00:00:00 2001 -From: Aleksander Machniak -Date: Sun, 28 Nov 2021 20:22:40 +0100 -Subject: [PATCH 2/2] Implement ACT_STORE_AND_NOTIFY policy for resources, add - webmail url to the notification body - -Set the status to NEEDS_ACTION and don't send out an immediate reply to -the organizer. - -Test Plan: nosetests - -Reviewers: #pykolab_developers - -Subscribers: #pykolab_developers - -Differential Revision: https://git.kolab.org/D3050 ---- - pykolab/imap/__init__.py | 3 + - tests/unit/test-011-wallace_resources.py | 56 +++++++++++++++- - wallace/module_resources.py | 82 ++++++++++++++++-------- - 3 files changed, 113 insertions(+), 28 deletions(-) - -diff --git a/pykolab/imap/__init__.py b/pykolab/imap/__init__.py -index 8ea23e7..813d722 100644 ---- a/pykolab/imap/__init__.py -+++ b/pykolab/imap/__init__.py -@@ -285,6 +285,9 @@ class IMAP(object): - else: - raise AttributeError(_("%r has no attribute %s") % (self, name)) - -+ def append(self, folder, message): -+ return self.imap.m.append(self.folder_utf7(folder), None, None, message) -+ - def folder_utf7(self, folder): - from pykolab import imap_utf7 - return imap_utf7.encode(folder) -diff --git a/tests/unit/test-011-wallace_resources.py b/tests/unit/test-011-wallace_resources.py -index 35d85a0..8b8cc0f 100644 ---- a/tests/unit/test-011-wallace_resources.py -+++ b/tests/unit/test-011-wallace_resources.py -@@ -3,6 +3,7 @@ import logging - import datetime - - from pykolab import itip -+from pykolab.imap import IMAP - from icalendar import Calendar - from email import message - from email import message_from_string -@@ -41,7 +42,7 @@ CALSCALE:GREGORIAN - METHOD:REQUEST - BEGIN:VEVENT - UID:626421779C777FBE9C9B85A80D04DDFA-A4BF5BBB9FEAA271 --DTSTAMP:20120713T1254140 -+DTSTAMP:20120713T125414 - DTSTART;TZID=3DEurope/London:20120713T100000 - DTEND;TZID=3DEurope/London:20120713T110000 - SUMMARY:test -@@ -75,7 +76,7 @@ CALSCALE:GREGORIAN - METHOD:REQUEST - BEGIN:VEVENT - UID:626421779C777FBE9C9B85A80D04DDFA-A4BF5BBB9FEAA271 --DTSTAMP:20120713T1254140 -+DTSTAMP:20120713T125414 - DTSTART;TZID=3DEurope/London:20120713T100000 - DTEND;TZID=3DEurope/London:20120713T110000 - SUMMARY:test -@@ -105,6 +106,12 @@ class TestWallaceResources(unittest.TestCase): - self.patch(pykolab.auth.Auth, "get_entry_attributes", self._mock_get_entry_attributes) - self.patch(pykolab.auth.Auth, "search_entry_by_attribute", self._mock_search_entry_by_attribute) - -+ # Mock IMAP operations -+ self.patch(pykolab.imap.IMAP, "connect", self._mock_nop) -+ self.patch(pykolab.imap.IMAP, "disconnect", self._mock_nop) -+ self.patch(pykolab.imap.IMAP, "set_acl", self._mock_nop) -+ self.patch(pykolab.imap.IMAP, "append", self._mock_imap_append) -+ - # intercept calls to smtplib.SMTP.sendmail() - import smtplib - self.patch(smtplib.SMTP, "__init__", self._mock_smtp_init) -@@ -113,8 +120,9 @@ class TestWallaceResources(unittest.TestCase): - self.patch(smtplib.SMTP, "sendmail", self._mock_smtp_sendmail) - - self.smtplog = [] -+ self.imap_append_log = [] - -- def _mock_nop(self, domain=None): -+ def _mock_nop(self, domain=None, arg3=None, arg4=None): - pass - - def _mock_find_resource(self, address): -@@ -142,6 +150,10 @@ class TestWallaceResources(unittest.TestCase): - self.smtplog.append((from_addr, to_addr, message)) - return [] - -+ def _mock_imap_append(self, folder, msg): -+ self.imap_append_log.append((folder, msg)) -+ return True -+ - def _get_ics_part(self, message): - ics_part = None - for part in message.walk(): -@@ -234,3 +246,41 @@ class TestWallaceResources(unittest.TestCase): - self.assertIn("ACCEPTED".lower(), response2['subject'].lower(), "Delegation message subject: %r" % (response2['subject'])) - self.assertEqual(ical2['attendee'].__str__(), "MAILTO:resource-car-audi-a4@example.org") - self.assertEqual(ical2['attendee'].params['PARTSTAT'], u"ACCEPTED") -+ -+ def test_007_accept_reservation_request_store_and_notify(self): -+ itip_event = itip.events_from_message(message_from_string(itip_multipart))[0] -+ -+ resource = { -+ 'mail': 'resource-collection-car@example.org', -+ 'kolabtargetfolder': 'shared/Resources/Test@example.org', -+ 'dn': 'cn=cars,ou=Resources,cd=example,dc=org', -+ 'cn': 'Cars', -+ 'owner': 'uid=foo,ou=People,cd=example,dc=org', -+ 'kolabinvitationpolicy': [module_resources.ACT_STORE_AND_NOTIFY] -+ } -+ -+ conf.command_set('wallace', 'webmail_url', 'https://%(domain)s/roundcube') -+ module_resources.imap = IMAP() -+ -+ module_resources.accept_reservation_request(itip_event, resource) -+ -+ # Assert no reply message sent to the organizer -+ self.assertEqual(len(self.smtplog), 1) -+ self.assertEqual(len(self.imap_append_log), 1) -+ -+ # Assert the notification sent to the resource owner -+ mail = message_from_string(self.smtplog[0][2]) -+ -+ self.assertEqual("resource-collection-car@example.org", self.smtplog[1][0]) -+ self.assertEqual("resource-collection-car@example.org", mail['from']) -+ self.assertEqual("foo@example.org", self.smtplog[1][1]) -+ self.assertEqual("foo@example.org", mail['to']) -+ self.assertFalse(mail.is_multipart()) -+ self.assertIn("New booking request for Cars", mail['subject']) -+ body = mail.get_payload(decode=True) -+ self.assertIn("The resource booking request is for Cars by Doe, John ", body) -+ self.assertIn("You can change the status via https://example.org/roundcube?_task=calendar", body) -+ -+ # Assert the message appended to the resource folder -+ self.assertEqual(resource['kolabtargetfolder'], self.imap_append_log[0][0]) -+ self.assertIn("NEEDS-ACTION", self.imap_append_log[0][1]) -diff --git a/wallace/module_resources.py b/wallace/module_resources.py -index 798577d..44b1db5 100644 ---- a/wallace/module_resources.py -+++ b/wallace/module_resources.py -@@ -58,14 +58,17 @@ COND_NOTIFY = 256 - ACT_MANUAL = 1 - ACT_ACCEPT = 2 - ACT_REJECT = 8 -+ACT_STORE = 16 - ACT_ACCEPT_AND_NOTIFY = ACT_ACCEPT + COND_NOTIFY -+ACT_STORE_AND_NOTIFY = ACT_STORE + COND_NOTIFY - - # noqa: E241 - policy_name_map = { - 'ACT_MANUAL': ACT_MANUAL, # noqa: E241 - 'ACT_ACCEPT': ACT_ACCEPT, # noqa: E241 - 'ACT_REJECT': ACT_REJECT, # noqa: E241 -- 'ACT_ACCEPT_AND_NOTIFY': ACT_ACCEPT_AND_NOTIFY -+ 'ACT_ACCEPT_AND_NOTIFY': ACT_ACCEPT_AND_NOTIFY, -+ 'ACT_STORE_AND_NOTIFY': ACT_STORE_AND_NOTIFY - } - - # pylint: disable=invalid-name -@@ -1031,11 +1034,14 @@ def accept_reservation_request( - ): - """ - Accepts the given iTip event by booking it into the resource's -- calendar. Then set the attendee status of the given resource to -- ACCEPTED and sends an iTip reply message to the organizer. -+ calendar. Then, depending on the policy, set the attendee status of the given resource to -+ ACCEPTED/TENTATIVE and send an iTip reply message to the organizer, or set the status to -+ NEEDS-ACTION and don't send a reply to the organizer. - """ - owner = get_resource_owner(resource) - confirmation_required = False -+ do_send_response = True -+ partstat = 'ACCEPTED' - - if not confirmed and owner: - if invitationpolicy is None: -@@ -1046,9 +1052,13 @@ def accept_reservation_request( - for policy in invitationpolicy: - if policy & ACT_MANUAL and owner['mail']: - confirmation_required = True -+ partstat = 'TENTATIVE' -+ break -+ if policy & ACT_STORE: -+ partstat = 'NEEDS-ACTION' -+ # Do not send an immediate response to the organizer -+ do_send_response = False - break -- -- partstat = 'TENTATIVE' if confirmation_required else 'ACCEPTED' - - itip_event['xml'].set_transparency(False) - itip_event['xml'].set_attendee_participant_status( -@@ -1063,7 +1073,7 @@ def accept_reservation_request( - level=8 - ) - -- if saved: -+ if saved and do_send_response: - send_response(delegator['mail'] if delegator else resource['mail'], itip_event, owner) - - if owner and confirmation_required: -@@ -1110,7 +1120,6 @@ def save_resource_event(itip_event, resource): - """ - try: - save_event = itip_event['xml'] -- targetfolder = imap.folder_quote(resource['kolabtargetfolder']) - - # add exception to existing recurring main event - if resource.get('existing_master') is not None: -@@ -1132,18 +1141,17 @@ def save_resource_event(itip_event, resource): - - else: - imap.set_acl( -- targetfolder, -+ resource['kolabtargetfolder'], - conf.get(conf.get('kolab', 'imap_backend'), 'admin_login'), - "lrswipkxtecda" - ) - - # append new version -- result = imap.imap.m.append( -- targetfolder, -- None, -- None, -+ result = imap.append( -+ resource['kolabtargetfolder'], - save_event.to_message(creator="Kolab Server ").as_string() - ) -+ - return result - - # pylint: disable=broad-except -@@ -1642,16 +1650,21 @@ def send_owner_notification(resource, owner, itip_event, success=True): - if 'preferredlanguage' in owner: - pykolab.translate.setUserLanguage(owner['preferredlanguage']) - -- message_text = owner_notification_text(resource, owner, itip_event['xml'], success) -+ message_text = owner_notification_text(resource, owner, itip_event['xml'], success, status) - - msg = MIMEText(utils.stripped_message(message_text), _charset='utf-8') - - msg['To'] = owner['mail'] - msg['From'] = resource['mail'] - msg['Date'] = formatdate(localtime=True) -- msg['Subject'] = utils.str2unicode(_('Booking for %s has been %s') % ( -- resource['cn'], participant_status_label(status) if success else _('failed') -- )) -+ if status == 'NEEDS-ACTION': -+ msg['Subject'] = utils.str2unicode(_('New booking request for %s') % ( -+ resource['cn'] -+ )) -+ else: -+ msg['Subject'] = utils.str2unicode(_('Booking for %s has been %s') % ( -+ resource['cn'], participant_status_label(status) if success else _('failed') -+ )) - - seed = random.randint(0, 6) - alarm_after = (seed * 10) + 60 -@@ -1663,19 +1676,37 @@ def send_owner_notification(resource, owner, itip_event, success=True): - signal.alarm(0) - - --def owner_notification_text(resource, owner, event, success): -+def owner_notification_text(resource, owner, event, success, status): - organizer = event.get_organizer() - status = event.get_attendee_by_email(resource['mail']).get_participant_status(True) -+ domain = resource['mail'].split('@')[1] -+ url = conf.get('wallace', 'webmail_url') - - if success: -- message_text = _( -- """ -- The resource booking for %(resource)s by %(orgname)s <%(orgemail)s> has been -- %(status)s for %(date)s. -+ if status == 'NEEDS-ACTION': -+ message_text = _( -+ """ -+ The resource booking request is for %(resource)s by %(orgname)s <%(orgemail)s> for %(date)s. - -- *** This is an automated message, sent to you as the resource owner. *** -- """ -- ) -+ *** This is an automated message, sent to you as the resource owner. *** -+ """ -+ ) -+ else: -+ message_text = _( -+ """ -+ The resource booking for %(resource)s by %(orgname)s <%(orgemail)s> has been -+ %(status)s for %(date)s. -+ -+ *** This is an automated message, sent to you as the resource owner. *** -+ """ -+ ) -+ -+ -+ if url: -+ message_text += ( -+ "\n " -+ + _("You can change the status via %(url)s") % { 'url': url } + '?_task=calendar' -+ ) - else: - message_text = _( - """ -@@ -1695,7 +1726,8 @@ def owner_notification_text(resource, owner, event, success): - 'date': event.get_date_text(), - 'status': participant_status_label(status), - 'orgname': organizer.name(), -- 'orgemail': organizer.email() -+ 'orgemail': organizer.email(), -+ 'domain': domain - } - - --- -2.33.1 -