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