diff options
author | Thomas Bruederli <bruederli@kolabsys.com> | 2014-03-05 12:44:09 -0500 |
---|---|---|
committer | Thomas Bruederli <bruederli@kolabsys.com> | 2014-03-05 12:44:09 -0500 |
commit | 5e9e21061bfe45f05c4e0f2567f3838ec57e9a19 (patch) | |
tree | ab516b5f4b6e0ce9bedde5acb7cf500369f20369 | |
parent | b3afa468579586b62aee849779c89d0104c3d21e (diff) | |
download | pykolab-5e9e21061bfe45f05c4e0f2567f3838ec57e9a19.tar.gz |
Test owner assignment for resources and mentions in reservation request responses
-rw-r--r-- | pykolab/utils.py | 11 | ||||
-rw-r--r-- | pykolab/xml/event.py | 4 | ||||
-rw-r--r-- | tests/functional/resource_func.py | 5 | ||||
-rw-r--r-- | tests/functional/test_wallace/test_005_resource_invitation.py | 55 | ||||
-rw-r--r-- | tests/functional/user_add.py | 2 | ||||
-rw-r--r-- | tests/unit/test-011-wallace_resources.py | 23 | ||||
-rw-r--r-- | wallace/module_resources.py | 78 |
7 files changed, 151 insertions, 27 deletions
diff --git a/pykolab/utils.py b/pykolab/utils.py index b7ff468..d552bff 100644 --- a/pykolab/utils.py +++ b/pykolab/utils.py @@ -280,8 +280,6 @@ def generate_password(): return output def multiline_message(message): - _msg = "" - column_width = 80 # First, replace all occurences of "\n" @@ -289,8 +287,6 @@ def multiline_message(message): message = message.replace("\n", " ") lines = [] - line_length = 0 - line = "" for word in message.split(): if (len(line) + len(word)) > column_width: @@ -306,6 +302,13 @@ def multiline_message(message): return "\n%s\n" % ("\n".join(lines)) +def stripped_message(message): + lines = [] + for line in message.strip().split("\n"): + lines.append(multiline_message(line).strip()) + + return "\n%s\n" % ("\n".join(lines)) + def normalize(_object): if type(_object) == list: result = [] diff --git a/pykolab/xml/event.py b/pykolab/xml/event.py index a165bcf..2218400 100644 --- a/pykolab/xml/event.py +++ b/pykolab/xml/event.py @@ -779,14 +779,14 @@ class Event(object): msg['Date'] = formatdate(localtime=True) if subject is None: - subject = _("Reservation Request for %s was %s") % (self.get_summary(), participant_status) + subject = _("Reservation Request for %s was %s") % (self.get_summary(), _(participant_status)) msg["Subject"] = subject if message_text is None: message_text = _("""This is an automated response to one of your event requests.""") - msg.attach(MIMEText(utils.multiline_message(message_text))) + msg.attach(MIMEText(utils.stripped_message(message_text))) part = MIMEBase('text', 'calendar', charset='UTF-8', method=method) del part['MIME-Version'] # mime parts don't need this diff --git a/tests/functional/resource_func.py b/tests/functional/resource_func.py index e3519d1..43aca96 100644 --- a/tests/functional/resource_func.py +++ b/tests/functional/resource_func.py @@ -4,7 +4,7 @@ from pykolab import wap_client conf = pykolab.getConf() -def resource_add(type, cn, members=None): +def resource_add(type, cn, members=None, owner=None): if type == None or type == '': raise Exception @@ -14,7 +14,8 @@ def resource_add(type, cn, members=None): resource_details = { 'cn': cn, 'kolabtargetfolder': "shared/Resources/" + cn + "@example.org", - 'uniquemember': members + 'uniquemember': members, + 'owner': owner } result = wap_client.authenticate(conf.get('ldap', 'bind_dn'), conf.get('ldap', 'bind_pw'), conf.get('kolab', 'primary_domain')) diff --git a/tests/functional/test_wallace/test_005_resource_invitation.py b/tests/functional/test_wallace/test_005_resource_invitation.py index 53ed2ec..8a1d844 100644 --- a/tests/functional/test_wallace/test_005_resource_invitation.py +++ b/tests/functional/test_wallace/test_005_resource_invitation.py @@ -194,11 +194,21 @@ class TestResourceInvitation(unittest.TestCase): 'displayname': 'John Doe', 'mail': 'john.doe@example.org', 'sender': 'John Doe <john.doe@example.org>', - 'mailbox': 'user/john.doe@example.org' + 'mailbox': 'user/john.doe@example.org', + 'dn': 'uid=doe,ou=People,dc=example,dc=org' + } + + self.jane = { + 'displayname': 'Jane Manager', + 'mail': 'jane.manager@example.org', + 'sender': 'Jane Manager <jane.manager@example.org>', + 'mailbox': 'user/jane.manager@example.org', + 'dn': 'uid=manager,ou=People,dc=example,dc=org' } from tests.functional.user_add import user_add user_add("John", "Doe") + user_add("Jane", "Manager") funcs.purge_resources() self.audi = funcs.resource_add("car", "Audi A4") @@ -206,6 +216,10 @@ class TestResourceInvitation(unittest.TestCase): self.boxter = funcs.resource_add("car", "Porsche Boxter S") self.cars = funcs.resource_add("collection", "Company Cars", [ self.audi['dn'], self.passat['dn'], self.boxter['dn'] ]) + self.room1 = funcs.resource_add("confroom", "Room 101", owner=self.jane['dn']) + self.room2 = funcs.resource_add("confroom", "Conference Room B-222") + self.rooms = funcs.resource_add("collection", "Rooms", [ self.room1['dn'], self.room2['dn'] ], self.jane['dn']) + time.sleep(1) from tests.functional.synchronize import synchronize_once synchronize_once() @@ -267,11 +281,14 @@ class TestResourceInvitation(unittest.TestCase): return uid - def check_message_received(self, subject, from_addr=None): + def check_message_received(self, subject, from_addr=None, mailbox=None): + if mailbox is None: + mailbox = self.john['mailbox'] + imap = IMAP() imap.connect() - imap.set_acl(self.john['mailbox'], "cyrus-admin", "lrs") - imap.imap.m.select(self.john['mailbox']) + imap.set_acl(mailbox, "cyrus-admin", "lrs") + imap.imap.m.select(mailbox) found = None retries = 10 @@ -520,3 +537,33 @@ class TestResourceInvitation(unittest.TestCase): self.assertEqual(self.check_message_received("Reservation Request for test was ACCEPTED", self.audi['mail']), None) + + def test_011_owner_info(self): + self.purge_mailbox(self.john['mailbox']) + + self.send_itip_invitation(self.room1['mail'], datetime.datetime(2014,6,19, 16,0,0)) + + accept = self.check_message_received("Reservation Request for test was ACCEPTED", self.room1['mail']) + self.assertIsInstance(accept, email.message.Message) + respose_text = str(accept.get_payload(0)) + self.assertIn(self.jane['mail'], respose_text) + self.assertIn(self.jane['displayname'], respose_text) + + + def TODO_test_012_owner_notification(self): + self.purge_mailbox(self.john['mailbox']) + self.purge_mailbox(self.jane['mailbox']) + + self.send_itip_invitation(self.room1['mail'], datetime.datetime(2014,5,4, 13,0,0)) + + # check notification message sent to resource owner (jane) + notify = self.check_message_received("Reservation Request for test was ACCEPTED", self.room1['mail'], self.jane['mailbox']) + self.assertIsInstance(notify, email.message.Message) + self.assertEqual(notify['From'], self.room1['mail']) + self.assertEqual(notify['Cc'], self.jane['mail']) + + # check notification sent to collection owner (jane) + self.send_itip_invitation(self.rooms['mail'], datetime.datetime(2014,5,4, 12,30,0)) + + notify = self.check_message_received("Reservation Request for test was ACCEPTED", self.room2['mail'], self.jane['mailbox']) + self.assertIsInstance(notify, email.message.Message) diff --git a/tests/functional/user_add.py b/tests/functional/user_add.py index 6af0419..4939f93 100644 --- a/tests/functional/user_add.py +++ b/tests/functional/user_add.py @@ -49,7 +49,7 @@ def user_add(givenname, sn, preferredlanguage='en_US'): attr_details = user_type_info['form_fields'][attribute] if isinstance(attr_details, dict): - if not attr_details.has_key('optional') or attr_details['optional'] == False: + if not attr_details.has_key('optional') or attr_details['optional'] == False or user_details.has_key(attribute): params[attribute] = user_details[attribute] elif isinstance(attr_details, list): params[attribute] = user_details[attribute] diff --git a/tests/unit/test-011-wallace_resources.py b/tests/unit/test-011-wallace_resources.py index 50fdc6b..6198a6f 100644 --- a/tests/unit/test-011-wallace_resources.py +++ b/tests/unit/test-011-wallace_resources.py @@ -245,6 +245,7 @@ class TestWallaceResources(unittest.TestCase): self.patch(pykolab.auth.Auth, "connect", self._mock_nop) self.patch(pykolab.auth.Auth, "disconnect", self._mock_nop) self.patch(pykolab.auth.Auth, "find_resource", self._mock_find_resource) + self.patch(pykolab.auth.Auth, "get_entry_attributes", self._mock_get_entry_attributes) # intercept calls to smtplib.SMTP.sendmail() import smtplib @@ -262,6 +263,10 @@ class TestWallaceResources(unittest.TestCase): entry_dn = "uid=" + prefix + ",dc=" + ",dc=".join(domain.split('.')) return [ entry_dn ]; + def _mock_get_entry_attributes(self, domain, entry, attributes): + (_, uid) = entry.split(',')[0].split('=') + return { 'cn': uid, 'mail': uid + "@example.org", '_attrib': attributes } + def _mock_smtp_init(self, host=None, port=None, local_hostname=None, timeout=0): pass @@ -334,7 +339,21 @@ class TestWallaceResources(unittest.TestCase): self.assertEqual("uid=resource-collection-car,dc=example,dc=org", res[0]); - def test_004_send_response_accept(self): + def test_004_get_resource_owner(self): + owner1 = module_resources.get_resource_owner({ 'owner': "uid=foo,ou=People,cd=example,dc=org" }) + self.assertIsInstance(owner1, dict) + self.assertEqual("foo@example.org", owner1['mail']) + self.assertIn("telephoneNumber", owner1['_attrib']) + + owner2 = module_resources.get_resource_owner({ 'owner': ["uid=john,ou=People,cd=example,dc=org", "uid=jane,ou=People,cd=example,dc=org"] }) + self.assertIsInstance(owner2, dict) + self.assertEqual("john@example.org", owner2['mail']) + + owner3 = module_resources.get_resource_owner({ 'dn': "uid=cars,ou=Resources,cd=example,dc=org" }) + self.assertEqual(owner3, None) + + + def test_005_send_response_accept(self): itip_event = module_resources.itip_events_from_message(message_from_string(itip_non_multipart)) module_resources.send_response("resource-collection-car@example.org", itip_event) @@ -352,7 +371,7 @@ class TestWallaceResources(unittest.TestCase): self.assertEqual(ics_part.get_param('method'), "REPLY") - def test_005_send_response_delegate(self): + def test_006_send_response_delegate(self): # delegate resource-collection-car@example.org => resource-car-audi-a4@example.org itip_event = module_resources.itip_events_from_message(message_from_string(itip_non_multipart))[0] itip_event['xml'].delegate('resource-collection-car@example.org', 'resource-car-audi-a4@example.org') diff --git a/wallace/module_resources.py b/wallace/module_resources.py index ee63e85..d861614 100644 --- a/wallace/module_resources.py +++ b/wallace/module_resources.py @@ -234,6 +234,8 @@ def execute(*args, **kw): if 'kolabsharedfolder' in [x.lower() for x in resource_attrs['objectclass']]: resources[uniquemember] = resource_attrs resources[uniquemember]['memberof'] = resource_dn + if not resource_attrs.has_key('owner') and resources[resource_dn].has_key('owner'): + resources[uniquemember]['owner'] = resources[resource_dn]['owner'] resource_dns.append(uniquemember) else: resources[resource_dn] = resource_attrs @@ -528,7 +530,7 @@ def accept_reservation_request(itip_event, resource, delegator=None): level=9 ) - send_response(delegator['mail'] if delegator else resource['mail'], itip_event) + send_response(delegator['mail'] if delegator else resource['mail'], itip_event, get_resource_owner(resource)) def decline_reservation_request(itip_event, resource): @@ -542,7 +544,7 @@ def decline_reservation_request(itip_event, resource): "DECLINED" ) - send_response(resource['mail'], itip_event) + send_response(resource['mail'], itip_event, get_resource_owner(resource)) def save_resource_event(itip_event, resource): @@ -550,9 +552,8 @@ def save_resource_event(itip_event, resource): Append the given event object to the resource's calendar """ try: - # TODO: The Cyrus IMAP (or Dovecot) Administrator login - # name comes from configuration. - imap.imap.m.setacl(resource['kolabtargetfolder'], "cyrus-admin", "lrswipkxtecda") + # Administrator login name comes from configuration. + imap.imap.m.setacl(resource['kolabtargetfolder'], conf.get(conf.get('kolab', 'imap_backend'), 'admin_login'), "lrswipkxtecda") result = imap.imap.m.append( resource['kolabtargetfolder'], None, @@ -573,7 +574,7 @@ def delete_resource_event(uid, resource): """ Removes the IMAP object with the given UID from a resource's calendar folder """ - imap.imap.m.setacl(resource['kolabtargetfolder'], "cyrus-admin", "lrswipkxtecda") + imap.imap.m.setacl(resource['kolabtargetfolder'], conf.get(conf.get('kolab', 'imap_backend'), 'admin_login'), "lrswipkxtecda") imap.imap.m.select(resource['kolabtargetfolder']) typ, data = imap.imap.m.search(None, '(HEADER SUBJECT "%s")' % uid) @@ -861,7 +862,33 @@ def resource_records_from_itip_events(itip_events, recipient_email=None): return resource_records -def send_response(from_address, itip_events): +def get_resource_owner(resource): + """ + Get this resource's owner record + """ + global auth + + if not auth: + auth = Auth() + auth.connect() + + if resource.has_key('owner'): + if not isinstance(resource['owner'], list): + resource['owner'] = [ resource['owner'] ] + + for dn in resource['owner']: + owner = auth.get_entry_attributes(None, dn, ['cn','mail','telephoneNumber']) + if owner is not None: + return owner + + else: + # TODO: get owner attribute from collection + pass + + return None + + +def send_response(from_address, itip_events, owner=None): """ Send the given iCal events as a valid iTip response to the organizer. In case the invited resource coolection was delegated to a concrete @@ -880,14 +907,20 @@ def send_response(from_address, itip_events): for itip_event in itip_events: attendee = itip_event['xml'].get_attendee_by_email(from_address) participant_status = itip_event['xml'].get_ical_attendee_participant_status(attendee) - message_text = None + + message_text = reservation_response_text(participant_status, owner) if participant_status == "DELEGATED": # Extra actions to take delegator = itip_event['xml'].get_attendee_by_email(from_address) delegatee = [a for a in itip_event['xml'].get_attendees() if from_address in [b.email() for b in a.get_delegated_from()]][0] + delegatee_status = itip_event['xml'].get_ical_attendee_participant_status(delegatee) - message = itip_event['xml'].to_message_itip(delegatee.get_email(), method="REPLY", participant_status=itip_event['xml'].get_ical_attendee_participant_status(delegatee)) + message = itip_event['xml'].to_message_itip(delegatee.get_email(), + method="REPLY", + participant_status=delegatee_status, + message_text=reservation_response_text(delegatee_status, owner) + ) smtp.sendmail(message['From'], message['To'], message.as_string()) # restore list of attendees after to_message_itip() @@ -896,11 +929,32 @@ def send_response(from_address, itip_events): participant_status = "DELEGATED" message_text = _(""" - Your reservation was delegated to "%s" - which is available for the requested time. + *** This is an automated response, please do not reply! *** + + Your reservation was delegated to "%s" which is available for the requested time. """) % (delegatee.get_name()) - message = itip_event['xml'].to_message_itip(from_address, method="REPLY", participant_status=participant_status, message_text=message_text) + message = itip_event['xml'].to_message_itip(from_address, + method="REPLY", + participant_status=participant_status, + message_text=message_text + ) smtp.sendmail(message['From'], message['To'], message.as_string()) smtp.quit() + + +def reservation_response_text(status, owner): + message_text = _(""" + *** This is an automated response, please do not reply! *** + + We hereby inform you that your reservation was %s. + """) % (_(status)) + + if owner: + message_text += _(""" + If you have questions about this reservation, please contact + %s <%s> %s + """) % (owner['cn'], owner['mail'], owner['telephoneNumber'] if owner.has_key('telephoneNumber') else '') + + return message_text |