diff options
-rw-r--r-- | tests/functional/test_wallace/test_005_resource_invitation.py | 77 | ||||
-rw-r--r-- | tests/functional/test_wallace/test_007_invitationpolicy.py | 2 | ||||
-rw-r--r-- | wallace/module_resources.py | 42 |
3 files changed, 104 insertions, 17 deletions
diff --git a/tests/functional/test_wallace/test_005_resource_invitation.py b/tests/functional/test_wallace/test_005_resource_invitation.py index c21f12c..61b9402 100644 --- a/tests/functional/test_wallace/test_005_resource_invitation.py +++ b/tests/functional/test_wallace/test_005_resource_invitation.py @@ -239,11 +239,12 @@ class TestResourceInvitation(unittest.TestCase): smtp.sendmail(from_addr, to_addr, mime_message % (to_addr, itip_payload)) smtp.quit() - def send_itip_invitation(self, resource_email, start=None, allday=False, template=None): + def send_itip_invitation(self, resource_email, start=None, allday=False, template=None, uid=None): if start is None: start = datetime.datetime.now() - uid = str(uuid.uuid4()) + if uid is None: + uid = str(uuid.uuid4()) if allday: default_template = itip_allday @@ -393,6 +394,7 @@ class TestResourceInvitation(unittest.TestCase): self.assertEqual(event.get_summary(), "test") + # @depends test_002_invite_resource def test_003_invite_resource_conflict(self): uid = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,7,13, 12,0,0)) @@ -670,6 +672,7 @@ class TestResourceInvitation(unittest.TestCase): event = self.check_resource_calendar_event(self.room3['kolabtargetfolder'], uid) self.assertIsInstance(event, pykolab.xml.Event) + self.assertEqual(event.get_status(True), 'CONFIRMED') self.assertEqual(event.get_attendee_by_email(self.room3['mail']).get_participant_status(True), 'ACCEPTED') @@ -705,6 +708,74 @@ class TestResourceInvitation(unittest.TestCase): response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DECLINED') }, self.room3['mail']) self.assertIsInstance(response, email.message.Message) - # tentative reservation was removed from resource calendar + # tentative reservation was set to cancelled event = self.check_resource_calendar_event(self.room3['kolabtargetfolder'], uid) self.assertEqual(event, None) + #self.assertEqual(event.get_status(True), 'CANCELLED') + #self.assertEqual(event.get_attendee_by_email(self.room3['mail']).get_participant_status(True), 'DECLINED') + + + def test_015_owner_confirmation_update(self): + self.purge_mailbox(self.john['mailbox']) + + uid = self.send_itip_invitation(self.room3['mail'], datetime.datetime(2014,8,19, 9,0,0), uid="http://a-totally.stupid/?uid") + + # requester (john) gets a TENTATIVE confirmation + response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('TENTATIVE') }, self.room3['mail']) + self.assertIsInstance(response, email.message.Message) + + # check first confirmation message sent to resource owner (jane) + notify1 = self.check_message_received(_('Booking request for %s requires confirmation') % (self.room3['cn']), mailbox=self.jane['mailbox']) + self.assertIsInstance(notify1, email.message.Message) + + itip_event1 = events_from_message(notify1)[0] + self.assertEqual(itip_event1['start'].hour, 9) + + self.purge_mailbox(self.jane['mailbox']) + self.purge_mailbox(self.john['mailbox']) + + # send update with new date (and sequence) + self.send_itip_update(self.room3['mail'], uid, datetime.datetime(2014,8,19, 16,0,0)) + + event = self.check_resource_calendar_event(self.room3['kolabtargetfolder'], uid) + self.assertIsInstance(event, pykolab.xml.Event) + self.assertEqual(event.get_attendee_by_email(self.room3['mail']).get_participant_status(True), 'TENTATIVE') + + # check second confirmation message sent to resource owner (jane) + notify2 = self.check_message_received(_('Booking request for %s requires confirmation') % (self.room3['cn']), mailbox=self.jane['mailbox']) + self.assertIsInstance(notify2, email.message.Message) + + itip_event2 = events_from_message(notify2)[0] + self.assertEqual(itip_event2['start'].hour, 16) + + # resource owner declines the first reservation request + itip_reply = itip_event1['xml'].to_message_itip(self.jane['mail'], + method="REPLY", + participant_status='DECLINED', + message_text="Request declined", + subject=_('Booking for %s has been %s') % (self.room3['cn'], participant_status_label('DECLINED')) + ) + smtp = smtplib.SMTP('localhost', 10026) + smtp.sendmail(self.jane['mail'], str(itip_event1['organizer']), str(itip_reply)) + smtp.quit() + + time.sleep(5) + + # resource owner accpets the second reservation request + itip_reply = itip_event2['xml'].to_message_itip(self.jane['mail'], + method="REPLY", + participant_status='ACCEPTED', + message_text="Request accepred", + subject=_('Booking for %s has been %s') % (self.room3['cn'], participant_status_label('ACCEPTED')) + ) + smtp = smtplib.SMTP('localhost', 10026) + smtp.sendmail(self.jane['mail'], str(itip_event2['organizer']), str(itip_reply)) + smtp.quit() + + # requester (john) now gets the ACCEPTED response + response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.room3['mail']) + self.assertIsInstance(response, email.message.Message) + + event = self.check_resource_calendar_event(self.room3['kolabtargetfolder'], uid) + self.assertIsInstance(event, pykolab.xml.Event) + self.assertEqual(event.get_attendee_by_email(self.room3['mail']).get_participant_status(True), 'ACCEPTED') diff --git a/tests/functional/test_wallace/test_007_invitationpolicy.py b/tests/functional/test_wallace/test_007_invitationpolicy.py index 1af22ff..9bc808f 100644 --- a/tests/functional/test_wallace/test_007_invitationpolicy.py +++ b/tests/functional/test_wallace/test_007_invitationpolicy.py @@ -598,7 +598,7 @@ class TestWallaceInvitationpolicy(unittest.TestCase): event = self.check_user_calendar_event(self.jane['kolabtargetfolder'], uid) self.assertIsInstance(event, pykolab.xml.Event) self.assertEqual(event.get_summary(), "cancelled") - self.assertEqual(event.get_status(), 'CANCELLED') + self.assertEqual(event.get_status(True), 'CANCELLED') self.assertTrue(event.get_transparency()) diff --git a/wallace/module_resources.py b/wallace/module_resources.py index f7c9441..47259da 100644 --- a/wallace/module_resources.py +++ b/wallace/module_resources.py @@ -25,7 +25,7 @@ import random import tempfile import time from urlparse import urlparse -import urllib +import base64 import uuid import re @@ -204,9 +204,10 @@ def execute(*args, **kw): for recipient in recipients: # extract reference UID from recipients like resource+UID@domain.org - if re.match('.+\+[A-Za-z0-9%/_-]+@', recipient): + if re.match('.+\+[A-Za-z0-9=/-]+@', recipient): (prefix, host) = recipient.split('@') - (local, reference_uid) = prefix.split('+') + (local, uid) = prefix.split('+') + reference_uid = base64.b64decode(uid, '-/') recipient = local + '@' + host if not len(resource_record_from_email_address(recipient)) == 0: @@ -268,6 +269,13 @@ def execute(*args, **kw): log.error("Could not find envelope sender attendee: %r" % (e)) continue + # compare sequence number to avoid outdated replies + if not itip_event['sequence'] == event.get_sequence(): + log.info(_("The iTip reply sequence (%r) doesn't match the referred event version (%r). Ignoring.") % ( + itip_event['sequence'], event.get_sequence() + )) + continue + # forward owner response comment comment = itip_event['xml'].get_comment() if comment: @@ -276,10 +284,12 @@ def execute(*args, **kw): itip_event_ = dict(xml=event, uid=event.get_uid()) if owner_reply == kolabformat.PartAccepted: + event.set_status(kolabformat.StatusConfirmed) accept_reservation_request(itip_event_, receiving_resource, confirmed=True) elif owner_reply == kolabformat.PartDeclined: decline_reservation_request(itip_event_, receiving_resource) - # TODO: set partstat=DECLINED and status=CANCELLED instead of deleting? + # TODO: set status=CANCELLED instead of deleting? + # event.set_status(kolabformat.StatusCancelled) delete_resource_event(reference_uid, receiving_resource) else: log.info("Invalid response (%r) recieved from resource owner for event %r" % ( @@ -288,6 +298,9 @@ def execute(*args, **kw): else: log.info(_("Event referenced by this REPLY (%r) not found in resource calendar") % (reference_uid)) + else: + log.info(_("No event reference found in this REPLY. Ignoring.")) + # exit for-loop break @@ -615,16 +628,13 @@ def accept_reservation_request(itip_event, resource, delegator=None, confirmed=F partstat = 'TENTATIVE' if confirmation_required else 'ACCEPTED' + itip_event['xml'].set_transparency(False); itip_event['xml'].set_attendee_participant_status( itip_event['xml'].get_attendee_by_email(resource['mail']), partstat ) - # remove old copy of the reservation - if confirmed: - delete_resource_event(itip_event['uid'], resource) - - saved = save_resource_event(itip_event, resource) + saved = save_resource_event(itip_event, resource, replace=confirmed) log.debug( _("Adding event to %r: %r") % (resource['kolabtargetfolder'], saved), @@ -658,14 +668,20 @@ def decline_reservation_request(itip_event, resource): send_owner_notification(resource, owner, itip_event, True) -def save_resource_event(itip_event, resource): +def save_resource_event(itip_event, resource, replace=False): """ Append the given event object to the resource's calendar """ try: # Administrator login name comes from configuration. targetfolder = imap.folder_quote(resource['kolabtargetfolder']) - imap.imap.m.setacl(targetfolder, conf.get(conf.get('kolab', 'imap_backend'), 'admin_login'), "lrswipkxtecda") + + # remove old copy of the reservation (also sets ACLs) + if replace: + delete_resource_event(itip_event['uid'], resource) + else: + imap.imap.m.setacl(targetfolder, conf.get(conf.get('kolab', 'imap_backend'), 'admin_login'), "lrswipkxtecda") + result = imap.imap.m.append( targetfolder, None, @@ -788,7 +804,7 @@ def resource_records_from_itip_events(itip_events, recipient_email=None): log.debug(_("Raw set of resources: %r") % (resources_raw), level=9) # consider organizer (in REPLY messages), too - organizers_raw = [re.sub('\+[A-Za-z0-9%/_-]+@', '@', str(y['organizer'])) for y in itip_events if y.has_key('organizer')] + organizers_raw = [re.sub('\+[A-Za-z0-9=/-]+@', '@', str(y['organizer'])) for y in itip_events if y.has_key('organizer')] log.debug(_("Raw set of organizers: %r") % (organizers_raw), level=8) @@ -1159,7 +1175,7 @@ def send_owner_confirmation(resource, owner, itip_event): # generate new UID and set the resource as organizer (mail, domain) = resource['mail'].split('@') event.set_uid(str(uuid.uuid4())) - event.set_organizer(mail + '+' + urllib.quote(uid) + '@' + domain, resource['cn']) + event.set_organizer(mail + '+' + base64.b64encode(uid, '-/') + '@' + domain, resource['cn']) itip_event['uid'] = event.get_uid() # add resource owner as (the sole) attendee |