imip-agent

imiptools/handlers/person.py

100:3d5387851b23
2014-10-30 Paul Boddie Fixed the validation of objects originating from attendees. Added the storage of participation updates from attendees, in addition to the existing storage of objects sent by organisers.
     1 #!/usr/bin/env python     2      3 """     4 Handlers for a person for whom scheduling is performed.     5 """     6      7 from email.mime.text import MIMEText     8 from imiptools.config import MANAGER_PATH, MANAGER_URL     9 from imiptools.content import Handler, to_part    10 from socket import gethostname    11 from vCalendar import to_node    12     13 def get_manager_url():    14     url_base = MANAGER_URL or "http://%s/" % gethostname()    15     return "%s/%s" % (url_base.rstrip("/"), MANAGER_PATH.lstrip("/"))    16     17 class PersonHandler(Handler):    18     19     "Handling mechanisms specific to people."    20     21     def _record_and_deliver(self, objtype, from_organiser=True, queue=False):    22     23         oa = self.require_organiser_and_attendees(from_organiser)    24         if not oa:    25             return False    26     27         (organiser, organiser_attr), attendees = organiser_item, attendees = oa    28     29         # Validate the organiser or attendee, ignoring spoofed requests.    30     31         if not self.validate_identities(from_organiser and [organiser_item] or attendees.items()):    32             return False    33     34         # Process each attendee separately.    35     36         if from_organiser:    37             for attendee, attendee_attr in attendees.items():    38     39                 if not self.have_new_object(attendee, objtype):    40                     continue    41     42                 # Store the object and queue any request.    43     44                 self.store.set_event(attendee, self.uid, to_node(    45                     {objtype : [(self.details, {})]}    46                     ))    47     48                 if queue:    49                     self.store.queue_request(attendee, self.uid)    50     51         # As organiser, update attendance.    52     53         else:    54             obj = self.get_object(organiser, objtype)    55     56             if obj and self.have_new_object(organiser, objtype, obj):    57                 attendee_map = self.get_value_map("ATTENDEE")    58     59                 for attendee, attendee_attr in attendees.items():    60     61                     # Update attendance in the loaded object.    62     63                     attendee_map[attendee] = attendee_attr    64     65                 # Set the new details and store the object.    66     67                 obj["ATTENDEE"] = attendee_map.items()    68     69                 self.store.set_event(organiser, self.uid, to_node(    70                     {objtype : [(obj, {})]}    71                     ))    72     73         return True    74     75     def reply(self):    76     77         "Wrap any valid message and pass it on to the recipient."    78     79         attendee = self.get_value("ATTENDEE")    80         if attendee:    81             return "REPLY", MIMEText("A reply has been received from %s." % attendee)    82     83 class Event(PersonHandler):    84     85     "An event handler."    86     87     def add(self):    88     89         # NOTE: Queue a suggested modification to any active event.    90     91         # The message is now wrapped and passed on to the recipient.    92     93         return "ADD", MIMEText("An addition to an event has been received.")    94     95     def cancel(self):    96     97         # NOTE: Queue a suggested modification to any active event.    98     99         # The message is now wrapped and passed on to the recipient.   100    101         return "CANCEL", MIMEText("A cancellation has been received.")   102    103     def counter(self):   104    105         # NOTE: Queue a suggested modification to any active event.   106    107         # The message is now wrapped and passed on to the recipient.   108    109         return "COUNTER", MIMEText("A counter proposal has been received.")   110    111     def declinecounter(self):   112    113         # NOTE: Queue a suggested modification to any active event.   114    115         # The message is now wrapped and passed on to the recipient.   116    117         return "DECLINECOUNTER", MIMEText("A declining counter proposal has been received.")   118    119     def publish(self):   120    121         # NOTE: Register details of any relevant event.   122    123         # The message is now wrapped and passed on to the recipient.   124    125         return "PUBLISH", MIMEText("Details of an event have been received.")   126    127     def refresh(self):   128    129         # NOTE: Update details of any active event.   130    131         # The message is now wrapped and passed on to the recipient.   132    133         return "REFRESH", MIMEText("An event update has been received.")   134    135     def reply(self):   136    137         "Record replies and notify the recipient."   138    139         self._record_and_deliver("VEVENT", from_organiser=False, queue=False)   140         return PersonHandler.reply(self)   141    142     def request(self):   143    144         "Hold requests and notify the recipient."   145    146         self._record_and_deliver("VEVENT", from_organiser=True, queue=True)   147    148         # The message is now wrapped and passed on to the recipient.   149    150         url = "%s/%s" % (get_manager_url().rstrip("/"), self.uid)   151         return "REQUEST", MIMEText("A request has been queued and can be viewed here: %s" % url)   152    153 class Freebusy(PersonHandler):   154    155     "A free/busy handler."   156    157     def publish(self):   158    159         # NOTE: Register free/busy information.   160    161         # The message is now wrapped and passed on to the recipient.   162    163         return "PUBLISH", MIMEText("Details of a contact's availability have been received.")   164    165     def reply(self):   166    167         "Record replies and notify the recipient."   168    169         self._record_and_deliver("VFREEBUSY", from_organiser=False, queue=False)   170         return PersonHandler.reply(self)   171    172     def request(self):   173    174         """   175         Respond to a request by preparing a reply containing free/busy   176         information for each indicated attendee.   177         """   178    179         # NOTE: This is currently the same as the resource handler but should be   180         # NOTE: subject to policy/preferences.   181    182         oa = self.require_organiser_and_attendees()   183         if not oa:   184             return None   185    186         (organiser, organiser_attr), attendees = organiser_item, attendees = oa   187    188         # Validate the organiser, ignoring spoofed requests.   189    190         if not self.validate_identities([organiser_item]):   191             return None   192    193         # Construct an appropriate fragment.   194    195         calendar = []   196         cwrite = calendar.append   197    198         # Get the details for each attendee.   199    200         for attendee, attendee_attr in attendees.items():   201             freebusy = self.store.get_freebusy(attendee)   202    203             record = []   204             rwrite = record.append   205    206             rwrite(("ORGANIZER", organiser_attr, organiser))   207             rwrite(("ATTENDEE", attendee_attr, attendee))   208             rwrite(("UID", {}, self.uid))   209    210             if freebusy:   211                 for start, end, uid in freebusy:   212                     rwrite(("FREEBUSY", {"FBTYPE" : "BUSY"}, [start, end]))   213    214             cwrite(("VFREEBUSY", {}, record))   215    216         # Return the reply.   217    218         return "REPLY", to_part("REPLY", calendar)   219    220 class Journal(PersonHandler):   221    222     "A journal entry handler."   223    224     def add(self):   225    226         # NOTE: Queue a suggested modification to any active entry.   227    228         # The message is now wrapped and passed on to the recipient.   229    230         return "ADD", MIMEText("An addition to a journal entry has been received.")   231    232     def cancel(self):   233    234         # NOTE: Queue a suggested modification to any active entry.   235    236         # The message is now wrapped and passed on to the recipient.   237    238         return "CANCEL", MIMEText("A cancellation has been received.")   239    240     def publish(self):   241    242         # NOTE: Register details of any relevant entry.   243    244         # The message is now wrapped and passed on to the recipient.   245    246         return "PUBLISH", MIMEText("Details of a journal entry have been received.")   247    248 class Todo(PersonHandler):   249    250     "A to-do item handler."   251    252     def add(self):   253    254         # NOTE: Queue a suggested modification to any active item.   255    256         # The message is now wrapped and passed on to the recipient.   257    258         return "ADD", MIMEText("An addition to an item has been received.")   259    260     def cancel(self):   261    262         # NOTE: Queue a suggested modification to any active item.   263    264         # The message is now wrapped and passed on to the recipient.   265    266         return "CANCEL", MIMEText("A cancellation has been received.")   267    268     def counter(self):   269    270         # NOTE: Queue a suggested modification to any active item.   271    272         # The message is now wrapped and passed on to the recipient.   273    274         return "COUNTER", MIMEText("A counter proposal has been received.")   275    276     def declinecounter(self):   277    278         # NOTE: Queue a suggested modification to any active item.   279    280         # The message is now wrapped and passed on to the recipient.   281    282         return "DECLINECOUNTER", MIMEText("A declining counter proposal has been received.")   283    284     def publish(self):   285    286         # NOTE: Register details of any relevant item.   287    288         # The message is now wrapped and passed on to the recipient.   289    290         return "PUBLISH", MIMEText("Details of an item have been received.")   291    292     def refresh(self):   293    294         # NOTE: Update details of any active item.   295    296         # The message is now wrapped and passed on to the recipient.   297    298         return "REFRESH", MIMEText("An item update has been received.")   299    300     def reply(self):   301    302         "Record replies and notify the recipient."   303    304         self._record_and_deliver("VTODO", from_organiser=False, queue=False)   305         return PersonHandler.reply(self)   306    307     def request(self):   308    309         "Hold requests and notify the recipient."   310    311         self._record_and_deliver("VTODO", from_organiser=True, queue=True)   312    313         # The message is now wrapped and passed on to the recipient.   314    315         return "REQUEST", MIMEText("A request has been queued.")   316    317 # Handler registry.   318    319 handlers = [   320     ("VFREEBUSY",   Freebusy),   321     ("VEVENT",      Event),   322     ("VTODO",       Todo),   323     ("VJOURNAL",    Journal),   324     ]   325    326 # vim: tabstop=4 expandtab shiftwidth=4