Changeset View
Changeset View
Standalone View
Standalone View
wallace/module_resources.py
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# pylint: disable=too-many-lines | |||||
# Copyright 2010-2015 Kolab Systems AG (http://www.kolabsys.com) | # Copyright 2010-2015 Kolab Systems AG (http://www.kolabsys.com) | ||||
# | # | ||||
# Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen a kolabsys.com> | # Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen a kolabsys.com> | ||||
# | # | ||||
# This program is free software: you can redistribute it and/or modify | # This program is free software: you can redistribute it and/or modify | ||||
# it under the terms of the GNU General Public License as published by | # it under the terms of the GNU General Public License as published by | ||||
# the Free Software Foundation, either version 3 of the License, or | # the Free Software Foundation, either version 3 of the License, or | ||||
# (at your option) any later version. | # (at your option) any later version. | ||||
# This program is distributed in the hope that it will be useful, | # This program is distributed in the hope that it will be useful, | ||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
# GNU General Public License for more details. | # GNU General Public License for more details. | ||||
# You should have received a copy of the GNU General Public License | # You should have received a copy of the GNU General Public License | ||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
# | # | ||||
import base64 | |||||
import datetime | import datetime | ||||
import icalendar | |||||
from email import message_from_string | |||||
from email.parser import Parser | |||||
from email.utils import formataddr | |||||
from email.utils import getaddresses | |||||
import os | import os | ||||
import pytz | |||||
import random | import random | ||||
import re | |||||
import signal | import signal | ||||
import tempfile | |||||
import time | import time | ||||
from urlparse import urlparse | |||||
from dateutil.tz import tzlocal | |||||
import base64 | |||||
import uuid | import uuid | ||||
import re | |||||
from email import message_from_string | from dateutil.tz import tzlocal | ||||
from email.parser import Parser | |||||
from email.utils import formataddr | |||||
from email.utils import getaddresses | |||||
import modules | import modules | ||||
import pykolab | |||||
import kolabformat | import kolabformat | ||||
import pykolab | |||||
from pykolab.auth import Auth | from pykolab.auth import Auth | ||||
from pykolab.conf import Conf | from pykolab.conf import Conf | ||||
from pykolab.imap import IMAP | from pykolab.imap import IMAP | ||||
from pykolab.logger import LoggerAdapter | |||||
from pykolab.itip import events_from_message | |||||
from pykolab.itip import check_event_conflict | |||||
from pykolab.translate import _ | |||||
from pykolab.xml import to_dt | from pykolab.xml import to_dt | ||||
from pykolab.xml import utils as xmlutils | from pykolab.xml import utils as xmlutils | ||||
from pykolab.xml import event_from_message | from pykolab.xml import event_from_message | ||||
from pykolab.xml import participant_status_label | from pykolab.xml import participant_status_label | ||||
from pykolab.itip import events_from_message | |||||
from pykolab.itip import check_event_conflict | |||||
from pykolab.translate import _ | |||||
# define some contstants used in the code below | # define some contstants used in the code below | ||||
COND_NOTIFY = 256 | COND_NOTIFY = 256 | ||||
ACT_MANUAL = 1 | ACT_MANUAL = 1 | ||||
ACT_ACCEPT = 2 | ACT_ACCEPT = 2 | ||||
ACT_REJECT = 8 | ACT_REJECT = 8 | ||||
ACT_ACCEPT_AND_NOTIFY = ACT_ACCEPT + COND_NOTIFY | ACT_ACCEPT_AND_NOTIFY = ACT_ACCEPT + COND_NOTIFY | ||||
# noqa: E241 | |||||
policy_name_map = { | policy_name_map = { | ||||
'ACT_MANUAL': ACT_MANUAL, | 'ACT_MANUAL': ACT_MANUAL, # noqa: E241 | ||||
'ACT_ACCEPT': ACT_ACCEPT, | 'ACT_ACCEPT': ACT_ACCEPT, # noqa: E241 | ||||
'ACT_REJECT': ACT_REJECT, | 'ACT_REJECT': ACT_REJECT, # noqa: E241 | ||||
'ACT_ACCEPT_AND_NOTIFY': ACT_ACCEPT_AND_NOTIFY | 'ACT_ACCEPT_AND_NOTIFY': ACT_ACCEPT_AND_NOTIFY | ||||
} | } | ||||
# pylint: disable=invalid-name | |||||
log = pykolab.getLogger('pykolab.wallace/resources') | log = pykolab.getLogger('pykolab.wallace/resources') | ||||
extra_log_params = {'qid': '-'} | extra_log_params = {'qid': '-'} | ||||
log = pykolab.logger.LoggerAdapter(log, extra_log_params) | log = LoggerAdapter(log, extra_log_params) | ||||
conf = pykolab.getConf() | conf = pykolab.getConf() | ||||
mybasepath = '/var/spool/pykolab/wallace/resources/' | mybasepath = '/var/spool/pykolab/wallace/resources/' | ||||
auth = None | auth = None | ||||
imap = None | imap = None | ||||
def __init__(): | def __init__(): | ||||
modules.register('resources', execute, description=description(), heartbeat=heartbeat) | modules.register('resources', execute, description=description(), heartbeat=heartbeat) | ||||
def accept(filepath): | def accept(filepath): | ||||
new_filepath = os.path.join( | new_filepath = os.path.join( | ||||
mybasepath, | mybasepath, | ||||
'ACCEPT', | 'ACCEPT', | ||||
os.path.basename(filepath) | os.path.basename(filepath) | ||||
) | ) | ||||
cleanup() | cleanup() | ||||
os.rename(filepath, new_filepath) | os.rename(filepath, new_filepath) | ||||
filepath = new_filepath | filepath = new_filepath | ||||
exec('modules.cb_action_ACCEPT(%r, %r)' % ('resources',filepath)) | exec('modules.cb_action_ACCEPT(%r, %r)' % ('resources', filepath)) | ||||
def description(): | def description(): | ||||
return """Resource management module.""" | return """Resource management module.""" | ||||
def cleanup(): | def cleanup(): | ||||
global auth, imap, extra_log_params | global auth, imap, extra_log_params | ||||
log.debug("cleanup(): %r, %r" % (auth, imap), level=8) | log.debug("cleanup(): %r, %r" % (auth, imap), level=8) | ||||
extra_log_params['qid'] = '-' | extra_log_params['qid'] = '-' | ||||
auth.disconnect() | auth.disconnect() | ||||
del auth | del auth | ||||
# Disconnect IMAP or we lock the mailbox almost constantly | # Disconnect IMAP or we lock the mailbox almost constantly | ||||
imap.disconnect() | imap.disconnect() | ||||
del imap | del imap | ||||
def execute(*args, **kw): | |||||
# pylint: disable=inconsistent-return-statements | |||||
# pylint: disable=too-many-branches | |||||
# pylint: disable=too-many-locals | |||||
# pylint: disable=too-many-return-statements | |||||
# pylint: disable=too-many-statements | |||||
def execute(*args, **kw): # noqa: C901 | |||||
global auth, imap, extra_log_params | global auth, imap, extra_log_params | ||||
# TODO: Test for correct call. | # TODO: Test for correct call. | ||||
filepath = args[0] | filepath = args[0] | ||||
extra_log_params['qid'] = os.path.basename(filepath) | extra_log_params['qid'] = os.path.basename(filepath) | ||||
# (re)set language to default | # (re)set language to default | ||||
pykolab.translate.setUserLanguage(conf.get('kolab','default_locale')) | pykolab.translate.setUserLanguage(conf.get('kolab', 'default_locale')) | ||||
if not os.path.isdir(mybasepath): | if not os.path.isdir(mybasepath): | ||||
os.makedirs(mybasepath) | os.makedirs(mybasepath) | ||||
for stage in ['incoming', 'ACCEPT', 'REJECT', 'HOLD', 'DEFER' ]: | for stage in ['incoming', 'ACCEPT', 'REJECT', 'HOLD', 'DEFER']: | ||||
if not os.path.isdir(os.path.join(mybasepath, stage)): | if not os.path.isdir(os.path.join(mybasepath, stage)): | ||||
os.makedirs(os.path.join(mybasepath, stage)) | os.makedirs(os.path.join(mybasepath, stage)) | ||||
log.debug(_("Resource Management called for %r, %r") % (args, kw), level=8) | log.debug(_("Resource Management called for %r, %r") % (args, kw), level=8) | ||||
auth = Auth() | auth = Auth() | ||||
imap = IMAP() | imap = IMAP() | ||||
if kw.has_key('stage'): | if 'stage' in kw: | ||||
log.debug( | log.debug( | ||||
_("Issuing callback after processing to stage %s") % ( | _("Issuing callback after processing to stage %s") % ( | ||||
kw['stage'] | kw['stage'] | ||||
), | ), | ||||
level=8 | level=8 | ||||
) | ) | ||||
log.debug(_("Testing cb_action_%s()") % (kw['stage']), level=8) | log.debug(_("Testing cb_action_%s()") % (kw['stage']), level=8) | ||||
if hasattr(modules, 'cb_action_%s' % (kw['stage'])): | if hasattr(modules, 'cb_action_%s' % (kw['stage'])): | ||||
log.debug( | log.debug( | ||||
_("Attempting to execute cb_action_%s()") % (kw['stage']), | _("Attempting to execute cb_action_%s()") % (kw['stage']), | ||||
level=8 | level=8 | ||||
) | ) | ||||
exec( | exec( | ||||
'modules.cb_action_%s(%r, %r)' % ( | 'modules.cb_action_%s(%r, %r)' % ( | ||||
kw['stage'], | kw['stage'], | ||||
'resources', | 'resources', | ||||
filepath | filepath | ||||
) | ) | ||||
) | ) | ||||
return filepath | return filepath | ||||
else: | else: | ||||
# Move to incoming | # Move to incoming | ||||
new_filepath = os.path.join( | new_filepath = os.path.join( | ||||
mybasepath, | mybasepath, | ||||
'incoming', | 'incoming', | ||||
os.path.basename(filepath) | os.path.basename(filepath) | ||||
) | ) | ||||
if not filepath == new_filepath: | if not filepath == new_filepath: | ||||
log.debug("Renaming %r to %r" % (filepath, new_filepath)) | log.debug("Renaming %r to %r" % (filepath, new_filepath)) | ||||
os.rename(filepath, new_filepath) | os.rename(filepath, new_filepath) | ||||
filepath = new_filepath | filepath = new_filepath | ||||
# parse full message | # parse full message | ||||
message = Parser().parse(open(filepath, 'r')) | message = Parser().parse(open(filepath, 'r')) | ||||
# invalid message, skip | # invalid message, skip | ||||
if not message.get('X-Kolab-To'): | if not message.get('X-Kolab-To'): | ||||
return filepath | return filepath | ||||
recipients = [address for displayname,address in getaddresses(message.get_all('X-Kolab-To'))] | recipients = [address for displayname, address in getaddresses(message.get_all('X-Kolab-To'))] | ||||
sender_email = [address for displayname,address in getaddresses(message.get_all('X-Kolab-From'))][0] | |||||
sender_email = [ | |||||
address for displayname, address in getaddresses(message.get_all('X-Kolab-From')) | |||||
][0] | |||||
any_itips = False | any_itips = False | ||||
any_resources = False | any_resources = False | ||||
possibly_any_resources = False | possibly_any_resources = False | ||||
reference_uid = None | reference_uid = None | ||||
# An iTip message may contain multiple events. Later on, test if the message | # An iTip message may contain multiple events. Later on, test if the message | ||||
# is an iTip message by checking the length of this list. | # is an iTip message by checking the length of this list. | ||||
try: | try: | ||||
itip_events = events_from_message(message, ['REQUEST', 'REPLY', 'CANCEL']) | itip_events = events_from_message(message, ['REQUEST', 'REPLY', 'CANCEL']) | ||||
except Exception, e: | |||||
log.error(_("Failed to parse iTip events from message: %r" % (e))) | # pylint: disable=broad-except | ||||
except Exception as errmsg: | |||||
log.error(_("Failed to parse iTip events from message: %r" % (errmsg))) | |||||
itip_events = [] | itip_events = [] | ||||
if not len(itip_events) > 0: | if not len(itip_events) > 0: | ||||
log.info( | log.info("Message is not an iTip message or does not contain any (valid) iTip.") | ||||
_("Message is not an iTip message or does not contain any " + \ | |||||
"(valid) iTip.") | |||||
) | |||||
else: | else: | ||||
any_itips = True | any_itips = True | ||||
log.debug( | log.debug( | ||||
_("iTip events attached to this message contain the " + \ | "iTip events attached to this message contain the following information: %r" % ( | ||||
"following information: %r") % (itip_events), | itip_events | ||||
), | |||||
level=8 | level=8 | ||||
) | ) | ||||
if any_itips: | if any_itips: | ||||
# See if any iTip actually allocates a resource. | # See if any iTip actually allocates a resource. | ||||
if len([x['resources'] for x in itip_events if x.has_key('resources')]) > 0 \ | if (len([x['resources'] for x in itip_events if 'resources' in x]) > 0 | ||||
or len([x['attendees'] for x in itip_events if x.has_key('attendees')]) > 0: | or len([x['attendees'] for x in itip_events if 'attendees' in x]) > 0): | ||||
possibly_any_resources = True | possibly_any_resources = True | ||||
if possibly_any_resources: | if possibly_any_resources: | ||||
auth.connect() | auth.connect() | ||||
for recipient in recipients: | for recipient in recipients: | ||||
# extract reference UID from recipients like resource+UID@domain.org | # extract reference UID from recipients like resource+UID@domain.org | ||||
if re.match('.+\+[A-Za-z0-9=/-]+@', recipient): | if re.match(r'.+\+[A-Za-z0-9=/-]+@', recipient): | ||||
try: | try: | ||||
(prefix, host) = recipient.split('@') | (prefix, host) = recipient.split('@') | ||||
(local, uid) = prefix.split('+') | (local, uid) = prefix.split('+') | ||||
reference_uid = base64.b64decode(uid, '-/') | reference_uid = base64.b64decode(uid, '-/') | ||||
recipient = local + '@' + host | recipient = local + '@' + host | ||||
except: | |||||
# pylint: disable=broad-except | |||||
except Exception: | |||||
continue | continue | ||||
if not len(resource_record_from_email_address(recipient)) == 0: | if not len(resource_record_from_email_address(recipient)) == 0: | ||||
resource_recipient = recipient | resource_recipient = recipient | ||||
any_resources = True | any_resources = True | ||||
if any_resources: | if any_resources: | ||||
if not any_itips: | if not any_itips: | ||||
log.debug(_("Not an iTip message, but sent to resource nonetheless. Reject message"), level=5) | log.debug( | ||||
_("Not an iTip message, but sent to resource nonetheless. Reject message"), | |||||
level=5 | |||||
) | |||||
reject(filepath) | reject(filepath) | ||||
return False | return False | ||||
else: | else: | ||||
# Continue. Resources and iTips. We like. | # Continue. Resources and iTips. We like. | ||||
pass | pass | ||||
else: | else: | ||||
if not any_itips: | if not any_itips: | ||||
log.debug(_("No itips, no resources, pass along %r") % (filepath), level=5) | log.debug(_("No itips, no resources, pass along %r") % (filepath), level=5) | ||||
return filepath | return filepath | ||||
else: | else: | ||||
log.debug(_("iTips, but no resources, pass along %r") % (filepath), level=5) | log.debug(_("iTips, but no resources, pass along %r") % (filepath), level=5) | ||||
return filepath | return filepath | ||||
# A simple list of merely resource entry IDs that hold any relevance to the | # A simple list of merely resource entry IDs that hold any relevance to the | ||||
# iTip events | # iTip events | ||||
resource_dns = resource_records_from_itip_events(itip_events, resource_recipient) | resource_dns = resource_records_from_itip_events(itip_events, resource_recipient) | ||||
# check if resource attendees match the envelope recipient | # check if resource attendees match the envelope recipient | ||||
if len(resource_dns) == 0: | if len(resource_dns) == 0: | ||||
log.info(_("No resource attendees matching envelope recipient %s, Reject message") % (resource_recipient)) | log.info( | ||||
_("No resource attendees matching envelope recipient %s, Reject message") % ( | |||||
resource_recipient | |||||
) | |||||
) | |||||
log.debug("%r" % (itip_events), level=8) | log.debug("%r" % (itip_events), level=8) | ||||
reject(filepath) | reject(filepath) | ||||
return False | |||||
return False | |||||
# Get the resource details, which includes details on the IMAP folder | # Get the resource details, which includes details on the IMAP folder | ||||
# This may append resource collection members to recource_dns | # This may append resource collection members to recource_dns | ||||
resources = get_resource_records(resource_dns) | resources = get_resource_records(resource_dns) | ||||
log.debug(_("Resources: %r; %r") % (resource_dns, resources), level=8) | log.debug(_("Resources: %r; %r") % (resource_dns, resources), level=8) | ||||
imap.connect() | imap.connect() | ||||
done = False | done = False | ||||
receiving_resource = resources[resource_dns[0]] | receiving_resource = resources[resource_dns[0]] | ||||
for itip_event in itip_events: | for itip_event in itip_events: | ||||
if itip_event['method'] == 'REPLY': | if itip_event['method'] == 'REPLY': | ||||
done = True | done = True | ||||
# find initial reservation referenced by the reply | # find initial reservation referenced by the reply | ||||
if reference_uid: | if reference_uid: | ||||
(event, master) = find_existing_event(reference_uid, itip_event['recurrence-id'], receiving_resource) | (event, master) = find_existing_event( | ||||
log.debug(_("iTip REPLY to %r, %r; matches %r") % (reference_uid, itip_event['recurrence-id'], type(event)), level=8) | reference_uid, | ||||
itip_event['recurrence-id'], | |||||
receiving_resource | |||||
) | |||||
log.debug( | |||||
_("iTip REPLY to %r, %r; matches %r") % ( | |||||
reference_uid, | |||||
itip_event['recurrence-id'], | |||||
type(event) | |||||
), | |||||
level=8 | |||||
) | |||||
if event: | if event: | ||||
try: | try: | ||||
sender_attendee = itip_event['xml'].get_attendee_by_email(sender_email) | sender_attendee = itip_event['xml'].get_attendee_by_email(sender_email) | ||||
owner_reply = sender_attendee.get_participant_status() | owner_reply = sender_attendee.get_participant_status() | ||||
log.debug(_("Sender Attendee: %r => %r") % (sender_attendee, owner_reply), level=8) | log.debug( | ||||
except Exception, e: | _("Sender Attendee: %r => %r") % (sender_attendee, owner_reply), | ||||
level=8 | |||||
) | |||||
# pylint: disable=broad-except | |||||
except Exception as e: | |||||
log.error(_("Could not find envelope sender attendee: %r") % (e)) | log.error(_("Could not find envelope sender attendee: %r") % (e)) | ||||
continue | continue | ||||
# compare sequence number to avoid outdated replies | # compare sequence number to avoid outdated replies | ||||
if not itip_event['sequence'] == event.get_sequence(): | if not itip_event['sequence'] == event.get_sequence(): | ||||
log.info(_("The iTip reply sequence (%r) doesn't match the referred event version (%r). Ignoring.") % ( | log.info( | ||||
itip_event['sequence'], event.get_sequence() | _("The iTip reply sequence (%r) doesn't match the referred event version (%r). Ignoring.") % ( | ||||
)) | itip_event['sequence'], | ||||
event.get_sequence() | |||||
) | |||||
) | |||||
continue | continue | ||||
# forward owner response comment | # forward owner response comment | ||||
comment = itip_event['xml'].get_comment() | comment = itip_event['xml'].get_comment() | ||||
if comment: | if comment: | ||||
event.set_comment(str(comment)) | event.set_comment(str(comment)) | ||||
_itip_event = dict(xml=event, uid=event.get_uid(), _master=master) | _itip_event = dict(xml=event, uid=event.get_uid(), _master=master) | ||||
_itip_event['recurrence-id'] = event.get_recurrence_id() | _itip_event['recurrence-id'] = event.get_recurrence_id() | ||||
if owner_reply == kolabformat.PartAccepted: | if owner_reply == kolabformat.PartAccepted: | ||||
event.set_status(kolabformat.StatusConfirmed) | event.set_status(kolabformat.StatusConfirmed) | ||||
accept_reservation_request(_itip_event, receiving_resource, confirmed=True) | accept_reservation_request(_itip_event, receiving_resource, confirmed=True) | ||||
elif owner_reply == kolabformat.PartDeclined: | elif owner_reply == kolabformat.PartDeclined: | ||||
decline_reservation_request(_itip_event, receiving_resource) | decline_reservation_request(_itip_event, receiving_resource) | ||||
else: | else: | ||||
log.info(_("Invalid response (%r) received from resource owner for event %r") % ( | log.info(_("Invalid response (%r) received from resource owner for event %r") % ( | ||||
sender_attendee.get_participant_status(True), reference_uid | sender_attendee.get_participant_status(True), reference_uid | ||||
)) | )) | ||||
else: | else: | ||||
log.info(_("Event referenced by this REPLY (%r) not found in resource calendar") % (reference_uid)) | log.info( | ||||
_("Event referenced by this REPLY (%r) not found in resource calendar") % ( | |||||
reference_uid | |||||
) | |||||
) | |||||
else: | else: | ||||
log.info(_("No event reference found in this REPLY. Ignoring.")) | log.info(_("No event reference found in this REPLY. Ignoring.")) | ||||
# exit for-loop | # exit for-loop | ||||
break | break | ||||
# else: | # else: | ||||
try: | try: | ||||
receiving_attendee = itip_event['xml'].get_attendee_by_email(receiving_resource['mail']) | receiving_attendee = itip_event['xml'].get_attendee_by_email( | ||||
log.debug(_("Receiving Resource: %r; %r") % (receiving_resource, receiving_attendee), level=8) | receiving_resource['mail'] | ||||
except Exception, e: | ) | ||||
log.debug( | |||||
_("Receiving Resource: %r; %r") % (receiving_resource, receiving_attendee), | |||||
level=8 | |||||
) | |||||
# pylint: disable=broad-except | |||||
except Exception as e: | |||||
log.error(_("Could not find envelope attendee: %r") % (e)) | log.error(_("Could not find envelope attendee: %r") % (e)) | ||||
continue | continue | ||||
# ignore updates and cancellations to resource collections who already delegated the event | # ignore updates and cancellations to resource collections who already delegated the event | ||||
att_delegated = (len(receiving_attendee.get_delegated_to()) > 0) | att_delegated = (len(receiving_attendee.get_delegated_to()) > 0) | ||||
att_nonpart = (receiving_attendee.get_role() == kolabformat.NonParticipant) | att_nonpart = (receiving_attendee.get_role() == kolabformat.NonParticipant) | ||||
att_rsvp = receiving_attendee.get_rsvp() | att_rsvp = receiving_attendee.get_rsvp() | ||||
if (att_delegated or att_nonpart) and not att_rsvp: | if (att_delegated or att_nonpart) and not att_rsvp: | ||||
done = True | done = True | ||||
log.debug(_("Recipient %r is non-participant, ignoring message") % (receiving_resource['mail']), level=8) | |||||
log.debug( | |||||
_("Recipient %r is non-participant, ignoring message") % ( | |||||
receiving_resource['mail'] | |||||
), | |||||
level=8 | |||||
) | |||||
# process CANCEL messages | # process CANCEL messages | ||||
if not done and itip_event['method'] == "CANCEL": | if not done and itip_event['method'] == "CANCEL": | ||||
for resource in resource_dns: | for resource in resource_dns: | ||||
if resources[resource]['mail'] in [a.get_email() for a in itip_event['xml'].get_attendees()] \ | r_emails = [a.get_email() for a in itip_event['xml'].get_attendees()] | ||||
and resources[resource].has_key('kolabtargetfolder'): | _resource = resources[resource] | ||||
(event, master) = find_existing_event(itip_event['uid'], itip_event['recurrence-id'], resources[resource]) | |||||
if event is None: | if _resource['mail'] in r_emails and 'kolabtargetfolder' in _resource: | ||||
log.debug(_("Cancellation for an event %r: not found, skipping") % (itip_event['uid']), level=8) | (event, master) = find_existing_event( | ||||
itip_event['uid'], | |||||
itip_event['recurrence-id'], | |||||
_resource | |||||
) | |||||
if not event: | |||||
continue | |||||
# remove entire event | # remove entire event | ||||
elif master is None: | if master is None: | ||||
log.debug(_("Cancellation for entire event %r: deleting") % (itip_event['uid']), level=8) | log.debug( | ||||
delete_resource_event(itip_event['uid'], resources[resource], event._msguid) | _("Cancellation for entire event %r: deleting") % (itip_event['uid']), | ||||
level=8 | |||||
) | |||||
delete_resource_event( | |||||
itip_event['uid'], | |||||
resources[resource], | |||||
event._msguid | |||||
) | |||||
# just cancel one single occurrence: add exception with status=cancelled | # just cancel one single occurrence: add exception with status=cancelled | ||||
else: | else: | ||||
log.debug(_("Cancellation for a single occurrence %r of %r: updating...") % (itip_event['recurrence-id'], itip_event['uid']), level=8) | log.debug( | ||||
_("Cancellation for a single occurrence %r of %r: updating...") % ( | |||||
itip_event['recurrence-id'], | |||||
itip_event['uid'] | |||||
), | |||||
level=8 | |||||
) | |||||
event.set_status('CANCELLED') | event.set_status('CANCELLED') | ||||
event.set_transparency(True) | event.set_transparency(True) | ||||
_itip_event = dict(xml=event, uid=event.get_uid(), _master=master) | _itip_event = dict(xml=event, uid=event.get_uid(), _master=master) | ||||
_itip_event['recurrence-id'] = event.get_recurrence_id() | _itip_event['recurrence-id'] = event.get_recurrence_id() | ||||
save_resource_event(_itip_event, resources[resource]) | save_resource_event(_itip_event, resources[resource]) | ||||
done = True | done = True | ||||
if done: | if done: | ||||
os.unlink(filepath) | os.unlink(filepath) | ||||
cleanup() | cleanup() | ||||
return | return | ||||
# do the magic for the receiving attendee | # do the magic for the receiving attendee | ||||
(available_resource, itip_event) = check_availability(itip_events, resource_dns, resources, receiving_attendee) | (available_resource, itip_event) = check_availability( | ||||
itip_events, | |||||
resource_dns, | |||||
resources, | |||||
receiving_attendee | |||||
) | |||||
_reject = False | _reject = False | ||||
resource = None | resource = None | ||||
original_resource = None | original_resource = None | ||||
# accept reservation | # accept reservation | ||||
if available_resource is not None: | if available_resource is not None: | ||||
if available_resource['mail'] in [a.get_email() for a in itip_event['xml'].get_attendees()]: | atts = [a.get_email() for a in itip_event['xml'].get_attendees()] | ||||
if available_resource['mail'] in atts: | |||||
# check if reservation was delegated | # check if reservation was delegated | ||||
if available_resource['mail'] != receiving_resource['mail'] and receiving_attendee.get_participant_status() == kolabformat.PartDelegated: | if available_resource['mail'] != receiving_resource['mail']: | ||||
if receiving_attendee.get_participant_status() == kolabformat.PartDelegated: | |||||
original_resource = receiving_resource | original_resource = receiving_resource | ||||
resource = available_resource | resource = available_resource | ||||
else: | else: | ||||
# This must have been a resource collection originally. | # This must have been a resource collection originally. | ||||
# We have inserted the reference to the original resource | # We have inserted the reference to the original resource | ||||
# record in 'memberof'. | # record in 'memberof'. | ||||
if available_resource.has_key('memberof'): | if available_resource.has_key('memberof'): | ||||
original_resource = resources[available_resource['memberof']] | original_resource = resources[available_resource['memberof']] | ||||
if original_resource['mail'] in [a.get_email() for a in itip_event['xml'].get_attendees()]: | atts = [a.get_email() for a in itip_event['xml'].get_attendees()] | ||||
if original_resource['mail'] in atts: | |||||
# | # | ||||
# Delegate: | # Delegate: | ||||
# - delegator: the original resource collection | # - delegator: the original resource collection | ||||
# - delegatee: the target resource | # - delegatee: the target resource | ||||
# | # | ||||
itip_event['xml'].delegate(original_resource['mail'], available_resource['mail'], available_resource['cn']) | itip_event['xml'].delegate( | ||||
original_resource['mail'], | |||||
available_resource['mail'], | |||||
available_resource['cn'] | |||||
) | |||||
# set delegator to NON-PARTICIPANT and RSVP=FALSE | # set delegator to NON-PARTICIPANT and RSVP=FALSE | ||||
delegator = itip_event['xml'].get_attendee_by_email(original_resource['mail']) | delegator = itip_event['xml'].get_attendee_by_email(original_resource['mail']) | ||||
delegator.set_role(kolabformat.NonParticipant) | delegator.set_role(kolabformat.NonParticipant) | ||||
delegator.set_rsvp(False) | delegator.set_rsvp(False) | ||||
log.debug(_("Delegate invitation for resource collection %r to %r") % (original_resource['mail'], available_resource['mail']), level=8) | log.debug( | ||||
_("Delegate invitation for resource collection %r to %r") % ( | |||||
original_resource['mail'], | |||||
available_resource['mail'] | |||||
), | |||||
level=8 | |||||
) | |||||
resource = available_resource | resource = available_resource | ||||
# Look for ACT_REJECT policy | # Look for ACT_REJECT policy | ||||
if resource is not None: | if resource is not None: | ||||
invitationpolicy = get_resource_invitationpolicy(resource) | invitationpolicy = get_resource_invitationpolicy(resource) | ||||
log.debug(_("Apply invitation policies %r") % (invitationpolicy), level=8) | log.debug(_("Apply invitation policies %r") % (invitationpolicy), level=8) | ||||
if invitationpolicy is not None: | if invitationpolicy is not None: | ||||
for policy in invitationpolicy: | for policy in invitationpolicy: | ||||
if policy & ACT_REJECT: | if policy & ACT_REJECT: | ||||
_reject = True | _reject = True | ||||
break | break | ||||
if resource is not None and not _reject: | if resource is not None and not _reject: | ||||
log.debug(_("Accept invitation for individual resource %r / %r") % (resource['dn'], resource['mail']), level=8) | log.debug( | ||||
accept_reservation_request(itip_event, resource, original_resource, False, invitationpolicy) | _("Accept invitation for individual resource %r / %r") % ( | ||||
resource['dn'], | |||||
resource['mail'] | |||||
), | |||||
level=8 | |||||
) | |||||
accept_reservation_request( | |||||
itip_event, | |||||
resource, | |||||
original_resource, | |||||
False, | |||||
invitationpolicy | |||||
) | |||||
else: | else: | ||||
resource = resources[resource_dns[0]] # this is the receiving resource record | resource = resources[resource_dns[0]] # this is the receiving resource record | ||||
log.debug(_("Decline invitation for individual resource %r / %r") % (resource['dn'], resource['mail']), level=8) | log.debug( | ||||
_("Decline invitation for individual resource %r / %r") % ( | |||||
resource['dn'], | |||||
resource['mail'] | |||||
), | |||||
level=8 | |||||
) | |||||
decline_reservation_request(itip_event, resource) | decline_reservation_request(itip_event, resource) | ||||
cleanup() | cleanup() | ||||
os.unlink(filepath) | os.unlink(filepath) | ||||
def heartbeat(lastrun): | def heartbeat(lastrun): | ||||
Show All 24 Lines | if len(resource_dns) > 0: | ||||
imap = IMAP() | imap = IMAP() | ||||
imap.connect() | imap.connect() | ||||
for resource_dn in resource_dns: | for resource_dn in resource_dns: | ||||
resource_attrs = auth.get_entry_attributes(None, resource_dn, ['kolabtargetfolder']) | resource_attrs = auth.get_entry_attributes(None, resource_dn, ['kolabtargetfolder']) | ||||
if resource_attrs.has_key('kolabtargetfolder'): | if resource_attrs.has_key('kolabtargetfolder'): | ||||
try: | try: | ||||
expunge_resource_calendar(resource_attrs['kolabtargetfolder']) | expunge_resource_calendar(resource_attrs['kolabtargetfolder']) | ||||
except Exception, e: | # pylint: disable=broad-except | ||||
except Exception as e: | |||||
log.error(_("Expunge resource calendar for %s (%s) failed: %r") % ( | log.error(_("Expunge resource calendar for %s (%s) failed: %r") % ( | ||||
resource_dn, resource_attrs['kolabtargetfolder'], e | resource_dn, resource_attrs['kolabtargetfolder'], e | ||||
)) | )) | ||||
imap.disconnect() | imap.disconnect() | ||||
auth.disconnect() | auth.disconnect() | ||||
Show All 27 Lines | def expunge_resource_calendar(mailbox): | ||||
for num in data[0].split(): | for num in data[0].split(): | ||||
log.debug( | log.debug( | ||||
_("Fetching message ID %r from folder %r") % (num, mailbox), | _("Fetching message ID %r from folder %r") % (num, mailbox), | ||||
level=8 | level=8 | ||||
) | ) | ||||
typ, data = imap.imap.m.fetch(num, '(RFC822)') | typ, data = imap.imap.m.fetch(num, '(RFC822)') | ||||
event_message = message_from_string(data[0][1]) | |||||
try: | try: | ||||
event = event_from_message(message_from_string(data[0][1])) | event = event_from_message(message_from_string(data[0][1])) | ||||
except Exception, e: | # pylint: disable=broad-except | ||||
except Exception as e: | |||||
log.error(_("Failed to parse event from message %s/%s: %r") % (mailbox, num, e)) | log.error(_("Failed to parse event from message %s/%s: %r") % (mailbox, num, e)) | ||||
continue | continue | ||||
if event: | if event: | ||||
dt_end = to_dt(event.get_end()) | dt_end = to_dt(event.get_end()) | ||||
# consider recurring events and get real end date | # consider recurring events and get real end date | ||||
if event.is_recurring(): | if event.is_recurring(): | ||||
Show All 23 Lines | def check_availability(itip_events, resource_dns, resources, receiving_attendee=None): | ||||
for resource in resources.keys(): | for resource in resources.keys(): | ||||
# skip this for resource collections | # skip this for resource collections | ||||
if not resources[resource].has_key('kolabtargetfolder'): | if not resources[resource].has_key('kolabtargetfolder'): | ||||
continue | continue | ||||
# sets the 'conflicting' flag and adds a list of conflicting events found | # sets the 'conflicting' flag and adds a list of conflicting events found | ||||
try: | try: | ||||
num_messages += read_resource_calendar(resources[resource], itip_events) | num_messages += read_resource_calendar(resources[resource], itip_events) | ||||
except Exception, e: | # pylint: disable=broad-except | ||||
except Exception as e: | |||||
log.error(_("Failed to read resource calendar for %r: %r") % (resource, e)) | log.error(_("Failed to read resource calendar for %r: %r") % (resource, e)) | ||||
end = time.time() | end = time.time() | ||||
log.debug(_("start: %r, end: %r, total: %r, messages: %d") % (start, end, (end-start), num_messages), level=8) | log.debug(_("start: %r, end: %r, total: %r, messages: %d") % (start, end, (end-start), num_messages), level=8) | ||||
# For each resource (collections are first!) | # For each resource (collections are first!) | ||||
▲ Show 20 Lines • Show All 136 Lines • ▼ Show 20 Lines | for num in data[0].split(): | ||||
_("Fetching message UID %r from folder %r") % (num, mailbox), | _("Fetching message UID %r from folder %r") % (num, mailbox), | ||||
level=8 | level=8 | ||||
) | ) | ||||
typ, data = imap.imap.m.fetch(num, '(UID RFC822)') | typ, data = imap.imap.m.fetch(num, '(UID RFC822)') | ||||
try: | try: | ||||
msguid = re.search(r"\WUID (\d+)", data[0][0]).group(1) | msguid = re.search(r"\WUID (\d+)", data[0][0]).group(1) | ||||
except Exception, e: | # pylint: disable=broad-except | ||||
except Exception: | |||||
log.error(_("No UID found in IMAP response: %r") % (data[0][0])) | log.error(_("No UID found in IMAP response: %r") % (data[0][0])) | ||||
continue | continue | ||||
event_message = message_from_string(data[0][1]) | |||||
try: | try: | ||||
event = event_from_message(message_from_string(data[0][1])) | event = event_from_message(message_from_string(data[0][1])) | ||||
except Exception, e: | # pylint: disable=broad-except | ||||
except Exception as e: | |||||
log.error(_("Failed to parse event from message %s/%s: %r") % (mailbox, num, e)) | log.error(_("Failed to parse event from message %s/%s: %r") % (mailbox, num, e)) | ||||
continue | continue | ||||
if event: | if event: | ||||
for itip in itip_events: | for itip in itip_events: | ||||
conflict = check_event_conflict(event, itip) | conflict = check_event_conflict(event, itip) | ||||
if event.get_uid() == itip['uid']: | if event.get_uid() == itip['uid']: | ||||
Show All 27 Lines | def find_existing_event(uid, recurrence_id, resource_rec): | ||||
master = None | master = None | ||||
mailbox = resource_rec['kolabtargetfolder'] | mailbox = resource_rec['kolabtargetfolder'] | ||||
log.debug(_("Searching %r for event %r") % (mailbox, uid), level=8) | log.debug(_("Searching %r for event %r") % (mailbox, uid), level=8) | ||||
try: | try: | ||||
imap.imap.m.select(imap.folder_quote(mailbox)) | imap.imap.m.select(imap.folder_quote(mailbox)) | ||||
typ, data = imap.imap.m.search(None, '(UNDELETED HEADER SUBJECT "%s")' % (uid)) | typ, data = imap.imap.m.search(None, '(UNDELETED HEADER SUBJECT "%s")' % (uid)) | ||||
except Exception, e: | # pylint: disable=broad-except | ||||
except Exception as e: | |||||
log.error(_("Failed to access resource calendar:: %r") % (e)) | log.error(_("Failed to access resource calendar:: %r") % (e)) | ||||
return event | return event | ||||
for num in reversed(data[0].split()): | for num in reversed(data[0].split()): | ||||
typ, data = imap.imap.m.fetch(num, '(UID RFC822)') | typ, data = imap.imap.m.fetch(num, '(UID RFC822)') | ||||
try: | try: | ||||
msguid = re.search(r"\WUID (\d+)", data[0][0]).group(1) | msguid = re.search(r"\WUID (\d+)", data[0][0]).group(1) | ||||
except Exception, e: | # pylint: disable=broad-except | ||||
except Exception as e: | |||||
log.error(_("No UID found in IMAP response: %r") % (data[0][0])) | log.error(_("No UID found in IMAP response: %r") % (data[0][0])) | ||||
continue | continue | ||||
try: | try: | ||||
event = event_from_message(message_from_string(data[0][1])) | event = event_from_message(message_from_string(data[0][1])) | ||||
# find instance in a recurring series | # find instance in a recurring series | ||||
if recurrence_id and (event.is_recurring() or event.has_exceptions()): | if recurrence_id and (event.is_recurring() or event.has_exceptions()): | ||||
Show All 10 Lines | for num in reversed(data[0].split()): | ||||
log.debug(_("Recurrence-ID not matching on message %s, skipping: %r != %r") % ( | log.debug(_("Recurrence-ID not matching on message %s, skipping: %r != %r") % ( | ||||
msguid, recurrence_id, event.get_recurrence_id() | msguid, recurrence_id, event.get_recurrence_id() | ||||
), level=8) | ), level=8) | ||||
continue | continue | ||||
if event is not None: | if event is not None: | ||||
setattr(event, '_msguid', msguid) | setattr(event, '_msguid', msguid) | ||||
except Exception, e: | # pylint: disable=broad-except | ||||
except Exception as e: | |||||
log.error(_("Failed to parse event from message %s/%s: %r") % (mailbox, num, e)) | log.error(_("Failed to parse event from message %s/%s: %r") % (mailbox, num, e)) | ||||
event = None | event = None | ||||
master = None | master = None | ||||
continue | continue | ||||
if event and event.uid == uid: | if event and event.uid == uid: | ||||
return (event, master) | return (event, master) | ||||
▲ Show 20 Lines • Show All 109 Lines • ▼ Show 20 Lines | try: | ||||
result = imap.imap.m.append( | result = imap.imap.m.append( | ||||
targetfolder, | targetfolder, | ||||
None, | None, | ||||
None, | None, | ||||
save_event.to_message(creator="Kolab Server <wallace@localhost>").as_string() | save_event.to_message(creator="Kolab Server <wallace@localhost>").as_string() | ||||
) | ) | ||||
return result | return result | ||||
except Exception, e: | # pylint: disable=broad-except | ||||
except Exception as e: | |||||
log.error(_("Failed to save event to resource calendar at %r: %r") % ( | log.error(_("Failed to save event to resource calendar at %r: %r") % ( | ||||
resource['kolabtargetfolder'], e | resource['kolabtargetfolder'], e | ||||
)) | )) | ||||
return False | return False | ||||
def delete_resource_event(uid, resource, msguid=None): | def delete_resource_event(uid, resource, msguid=None): | ||||
Show All 21 Lines | try: | ||||
), level=8) | ), level=8) | ||||
for num in data[0].split(): | for num in data[0].split(): | ||||
imap.imap.m.store(num, '+FLAGS', '\\Deleted') | imap.imap.m.store(num, '+FLAGS', '\\Deleted') | ||||
imap.imap.m.expunge() | imap.imap.m.expunge() | ||||
return True | return True | ||||
except Exception, e: | # pylint: disable=broad-except | ||||
except Exception as e: | |||||
log.error(_("Failed to delete calendar object %r from folder %r: %r") % ( | log.error(_("Failed to delete calendar object %r from folder %r: %r") % ( | ||||
uid, targetfolder, e | uid, targetfolder, e | ||||
)) | )) | ||||
return False | return False | ||||
def reject(filepath): | def reject(filepath): | ||||
▲ Show 20 Lines • Show All 499 Lines • Show Last 20 Lines |