pykolab/utils.py pykolab/xml tests/functional tests/unit wallace/module_resources.py

Thomas Brüderli bruederli at kolabsys.com
Mon Mar 10 21:52:58 CET 2014


 pykolab/utils.py                                              |   11 -
 pykolab/xml/event.py                                          |    4 
 tests/functional/resource_func.py                             |    5 
 tests/functional/test_wallace/test_005_resource_invitation.py |   55 ++++++-
 tests/functional/user_add.py                                  |    2 
 tests/unit/test-011-wallace_resources.py                      |   23 ++
 wallace/module_resources.py                                   |   78 ++++++++--
 7 files changed, 151 insertions(+), 27 deletions(-)

New commits:
commit 5e9e21061bfe45f05c4e0f2567f3838ec57e9a19
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Wed Mar 5 12:44:09 2014 -0500

    Test owner assignment for resources and mentions in reservation request responses

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 at example.org',
             'sender': 'John Doe <john.doe at example.org>',
-            'mailbox': 'user/john.doe at example.org'
+            'mailbox': 'user/john.doe at example.org',
+            'dn': 'uid=doe,ou=People,dc=example,dc=org'
+        }
+
+        self.jane = {
+            'displayname': 'Jane Manager',
+            'mail': 'jane.manager at example.org',
+            'sender': 'Jane Manager <jane.manager at example.org>',
+            'mailbox': 'user/jane.manager at 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 at 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 at 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 at 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 at example.org => resource-car-audi-a4 at example.org
         itip_event = module_resources.itip_events_from_message(message_from_string(itip_non_multipart))[0]
         itip_event['xml'].delegate('resource-collection-car at example.org', 'resource-car-audi-a4 at 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




More information about the commits mailing list