Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117880157
module_resources.py
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
53 KB
Referenced Files
None
Subscribers
None
module_resources.py
View Options
# -*- coding: utf-8 -*-
# Copyright 2010-2015 Kolab Systems AG (http://www.kolabsys.com)
#
# Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen a kolabsys.com>
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import
datetime
import
icalendar
import
os
import
pytz
import
random
import
tempfile
import
time
from
urlparse
import
urlparse
from
dateutil.tz
import
tzlocal
import
base64
import
uuid
import
re
from
email
import
message_from_string
from
email.parser
import
Parser
from
email.utils
import
formataddr
from
email.utils
import
getaddresses
import
modules
import
pykolab
import
kolabformat
from
pykolab.auth
import
Auth
from
pykolab.conf
import
Conf
from
pykolab.imap
import
IMAP
from
pykolab.xml
import
to_dt
from
pykolab.xml
import
utils
as
xmlutils
from
pykolab.xml
import
event_from_message
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
COND_NOTIFY
=
256
ACT_MANUAL
=
1
ACT_ACCEPT
=
2
ACT_ACCEPT_AND_NOTIFY
=
ACT_ACCEPT
+
COND_NOTIFY
policy_name_map
=
{
'ACT_MANUAL'
:
ACT_MANUAL
,
'ACT_ACCEPT'
:
ACT_ACCEPT
,
'ACT_ACCEPT_AND_NOTIFY'
:
ACT_ACCEPT_AND_NOTIFY
}
log
=
pykolab
.
getLogger
(
'pykolab.wallace'
)
conf
=
pykolab
.
getConf
()
mybasepath
=
'/var/spool/pykolab/wallace/resources/'
auth
=
None
imap
=
None
def
__init__
():
modules
.
register
(
'resources'
,
execute
,
description
=
description
(),
heartbeat
=
heartbeat
)
def
accept
(
filepath
):
new_filepath
=
os
.
path
.
join
(
mybasepath
,
'ACCEPT'
,
os
.
path
.
basename
(
filepath
)
)
cleanup
()
os
.
rename
(
filepath
,
new_filepath
)
filepath
=
new_filepath
exec
(
'modules.cb_action_ACCEPT(
%r
,
%r
)'
%
(
'resources'
,
filepath
))
def
description
():
return
"""Resource management module."""
def
cleanup
():
global
auth
,
imap
log
.
debug
(
"cleanup():
%r
,
%r
"
%
(
auth
,
imap
),
level
=
9
)
auth
.
disconnect
()
del
auth
# Disconnect IMAP or we lock the mailbox almost constantly
imap
.
disconnect
()
del
imap
def
execute
(
*
args
,
**
kw
):
global
auth
,
imap
# (re)set language to default
pykolab
.
translate
.
setUserLanguage
(
conf
.
get
(
'kolab'
,
'default_locale'
))
if
not
os
.
path
.
isdir
(
mybasepath
):
os
.
makedirs
(
mybasepath
)
for
stage
in
[
'incoming'
,
'ACCEPT'
,
'REJECT'
,
'HOLD'
,
'DEFER'
]:
if
not
os
.
path
.
isdir
(
os
.
path
.
join
(
mybasepath
,
stage
)):
os
.
makedirs
(
os
.
path
.
join
(
mybasepath
,
stage
))
log
.
debug
(
_
(
"Resource Management called for
%r
,
%r
"
)
%
(
args
,
kw
),
level
=
8
)
auth
=
Auth
()
imap
=
IMAP
()
# TODO: Test for correct call.
filepath
=
args
[
0
]
if
kw
.
has_key
(
'stage'
):
log
.
debug
(
_
(
"Issuing callback after processing to stage
%s
"
)
%
(
kw
[
'stage'
]
),
level
=
8
)
log
.
debug
(
_
(
"Testing cb_action_
%s
()"
)
%
(
kw
[
'stage'
]),
level
=
8
)
if
hasattr
(
modules
,
'cb_action_
%s
'
%
(
kw
[
'stage'
])):
log
.
debug
(
_
(
"Attempting to execute cb_action_
%s
()"
)
%
(
kw
[
'stage'
]),
level
=
8
)
exec
(
'modules.cb_action_
%s
(
%r
,
%r
)'
%
(
kw
[
'stage'
],
'resources'
,
filepath
)
)
return
filepath
else
:
# Move to incoming
new_filepath
=
os
.
path
.
join
(
mybasepath
,
'incoming'
,
os
.
path
.
basename
(
filepath
)
)
if
not
filepath
==
new_filepath
:
log
.
debug
(
"Renaming
%r
to
%r
"
%
(
filepath
,
new_filepath
))
os
.
rename
(
filepath
,
new_filepath
)
filepath
=
new_filepath
# parse full message
message
=
Parser
()
.
parse
(
open
(
filepath
,
'r'
))
# invalid message, skip
if
not
message
.
get
(
'X-Kolab-To'
):
return
filepath
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
]
any_itips
=
False
any_resources
=
False
possibly_any_resources
=
False
reference_uid
=
None
# An iTip message may contain multiple events. Later on, test if the message
# is an iTip message by checking the length of this list.
try
:
itip_events
=
events_from_message
(
message
,
[
'REQUEST'
,
'REPLY'
,
'CANCEL'
])
except
Exception
,
e
:
log
.
error
(
_
(
"Failed to parse iTip events from message:
%r
"
%
(
e
)))
itip_events
=
[]
if
not
len
(
itip_events
)
>
0
:
log
.
info
(
_
(
"Message is not an iTip message or does not contain any "
+
\
"(valid) iTip."
)
)
else
:
any_itips
=
True
log
.
debug
(
_
(
"iTip events attached to this message contain the "
+
\
"following information:
%r
"
)
%
(
itip_events
),
level
=
9
)
if
any_itips
:
# See if any iTip actually allocates a resource.
if
len
([
x
[
'resources'
]
for
x
in
itip_events
if
x
.
has_key
(
'resources'
)])
>
0
\
or
len
([
x
[
'attendees'
]
for
x
in
itip_events
if
x
.
has_key
(
'attendees'
)])
>
0
:
possibly_any_resources
=
True
if
possibly_any_resources
:
auth
.
connect
()
for
recipient
in
recipients
:
# extract reference UID from recipients like resource+UID@domain.org
if
re
.
match
(
'.+\+[A-Za-z0-9=/-]+@'
,
recipient
):
try
:
(
prefix
,
host
)
=
recipient
.
split
(
'@'
)
(
local
,
uid
)
=
prefix
.
split
(
'+'
)
reference_uid
=
base64
.
b64decode
(
uid
,
'-/'
)
recipient
=
local
+
'@'
+
host
except
:
continue
if
not
len
(
resource_record_from_email_address
(
recipient
))
==
0
:
resource_recipient
=
recipient
any_resources
=
True
if
any_resources
:
if
not
any_itips
:
log
.
debug
(
_
(
"Not an iTip message, but sent to resource nonetheless. Reject message"
),
level
=
5
)
reject
(
filepath
)
return
False
else
:
# Continue. Resources and iTips. We like.
pass
else
:
if
not
any_itips
:
log
.
debug
(
_
(
"No itips, no resources, pass along
%r
"
)
%
(
filepath
),
level
=
5
)
return
filepath
else
:
log
.
debug
(
_
(
"iTips, but no resources, pass along
%r
"
)
%
(
filepath
),
level
=
5
)
return
filepath
# A simple list of merely resource entry IDs that hold any relevance to the
# iTip events
resource_dns
=
resource_records_from_itip_events
(
itip_events
,
resource_recipient
)
# check if resource attendees match the envelope recipient
if
len
(
resource_dns
)
==
0
:
log
.
info
(
_
(
"No resource attendees matching envelope recipient
%s
, Reject message"
)
%
(
resource_recipient
))
log
.
debug
(
"
%r
"
%
(
itip_events
),
level
=
8
)
reject
(
filepath
)
return
False
# Get the resource details, which includes details on the IMAP folder
# This may append resource collection members to recource_dns
resources
=
get_resource_records
(
resource_dns
)
log
.
debug
(
_
(
"Resources:
%r
;
%r
"
)
%
(
resource_dns
,
resources
),
level
=
8
)
imap
.
connect
()
done
=
False
receiving_resource
=
resources
[
resource_dns
[
0
]]
for
itip_event
in
itip_events
:
if
itip_event
[
'method'
]
==
'REPLY'
:
done
=
True
# find initial reservation referenced by the reply
if
reference_uid
:
(
event
,
master
)
=
find_existing_event
(
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
:
try
:
sender_attendee
=
itip_event
[
'xml'
]
.
get_attendee_by_email
(
sender_email
)
owner_reply
=
sender_attendee
.
get_participant_status
()
log
.
debug
(
_
(
"Sender Attendee:
%r
=>
%r
"
)
%
(
sender_attendee
,
owner_reply
),
level
=
9
)
except
Exception
,
e
:
log
.
error
(
_
(
"Could not find envelope sender attendee:
%r
"
)
%
(
e
))
continue
# compare sequence number to avoid outdated replies
if
not
itip_event
[
'sequence'
]
==
event
.
get_sequence
():
log
.
info
(
_
(
"The iTip reply sequence (
%r
) doesn't match the referred event version (
%r
). Ignoring."
)
%
(
itip_event
[
'sequence'
],
event
.
get_sequence
()
))
continue
# forward owner response comment
comment
=
itip_event
[
'xml'
]
.
get_comment
()
if
comment
:
event
.
set_comment
(
str
(
comment
))
_itip_event
=
dict
(
xml
=
event
,
uid
=
event
.
get_uid
(),
_master
=
master
)
_itip_event
[
'recurrence-id'
]
=
event
.
get_recurrence_id
()
if
owner_reply
==
kolabformat
.
PartAccepted
:
event
.
set_status
(
kolabformat
.
StatusConfirmed
)
accept_reservation_request
(
_itip_event
,
receiving_resource
,
confirmed
=
True
)
elif
owner_reply
==
kolabformat
.
PartDeclined
:
decline_reservation_request
(
_itip_event
,
receiving_resource
)
else
:
log
.
info
(
_
(
"Invalid response (
%r
) received from resource owner for event
%r
"
)
%
(
sender_attendee
.
get_participant_status
(
True
),
reference_uid
))
else
:
log
.
info
(
_
(
"Event referenced by this REPLY (
%r
) not found in resource calendar"
)
%
(
reference_uid
))
else
:
log
.
info
(
_
(
"No event reference found in this REPLY. Ignoring."
))
# exit for-loop
break
# else:
try
:
receiving_attendee
=
itip_event
[
'xml'
]
.
get_attendee_by_email
(
receiving_resource
[
'mail'
])
log
.
debug
(
_
(
"Receiving Resource:
%r
;
%r
"
)
%
(
receiving_resource
,
receiving_attendee
),
level
=
9
)
except
Exception
,
e
:
log
.
error
(
_
(
"Could not find envelope attendee:
%r
"
)
%
(
e
))
continue
# ignore updates and cancellations to resource collections who already delegated the event
if
len
(
receiving_attendee
.
get_delegated_to
())
>
0
or
receiving_attendee
.
get_role
()
==
kolabformat
.
NonParticipant
:
done
=
True
log
.
debug
(
_
(
"Recipient
%r
is non-participant, ignoring message"
)
%
(
receiving_resource
[
'mail'
]),
level
=
8
)
# process CANCEL messages
if
not
done
and
itip_event
[
'method'
]
==
"CANCEL"
:
for
resource
in
resource_dns
:
if
resources
[
resource
][
'mail'
]
in
[
a
.
get_email
()
for
a
in
itip_event
[
'xml'
]
.
get_attendees
()]
\
and
resources
[
resource
]
.
has_key
(
'kolabtargetfolder'
):
(
event
,
master
)
=
find_existing_event
(
itip_event
[
'uid'
],
itip_event
[
'recurrence-id'
],
resources
[
resource
])
# remove entire event
if
event
and
master
is
None
:
log
.
debug
(
_
(
"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
elif
master
is
not
None
:
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_transparency
(
True
)
_itip_event
=
dict
(
xml
=
event
,
uid
=
event
.
get_uid
(),
_master
=
master
)
_itip_event
[
'recurrence-id'
]
=
event
.
get_recurrence_id
()
save_resource_event
(
_itip_event
,
resources
[
resource
])
done
=
True
if
done
:
os
.
unlink
(
filepath
)
cleanup
()
return
# do the magic for the receiving attendee
(
available_resource
,
itip_event
)
=
check_availability
(
itip_events
,
resource_dns
,
resources
,
receiving_attendee
)
# accept reservation
if
available_resource
is
not
None
:
if
available_resource
[
'mail'
]
in
[
a
.
get_email
()
for
a
in
itip_event
[
'xml'
]
.
get_attendees
()]:
log
.
debug
(
_
(
"Accept invitation for individual resource
%r
/
%r
"
)
%
(
available_resource
[
'dn'
],
available_resource
[
'mail'
]),
level
=
8
)
# check if reservation was delegated
original_resource
=
None
if
available_resource
[
'mail'
]
!=
receiving_resource
[
'mail'
]
and
receiving_attendee
.
get_participant_status
()
==
kolabformat
.
PartDelegated
:
original_resource
=
receiving_resource
accept_reservation_request
(
itip_event
,
available_resource
,
original_resource
)
else
:
# This must have been a resource collection originally.
# We have inserted the reference to the original resource
# record in 'memberof'.
if
available_resource
.
has_key
(
'memberof'
):
original_resource
=
resources
[
available_resource
[
'memberof'
]]
if
original_resource
[
'mail'
]
in
[
a
.
get_email
()
for
a
in
itip_event
[
'xml'
]
.
get_attendees
()]:
#
# Delegate:
# - delegator: the original resource collection
# - delegatee: the target resource
#
itip_event
[
'xml'
]
.
delegate
(
original_resource
[
'mail'
],
available_resource
[
'mail'
],
available_resource
[
'cn'
])
# set delegator to NON-PARTICIPANT and RSVP=FALSE
delegator
=
itip_event
[
'xml'
]
.
get_attendee_by_email
(
original_resource
[
'mail'
])
delegator
.
set_role
(
kolabformat
.
NonParticipant
)
delegator
.
set_rsvp
(
False
)
log
.
debug
(
_
(
"Delegate invitation for resource collection
%r
to
%r
"
)
%
(
original_resource
[
'mail'
],
available_resource
[
'mail'
]),
level
=
8
)
accept_reservation_request
(
itip_event
,
available_resource
,
original_resource
)
# decline reservation
else
:
resource
=
resources
[
resource_dns
[
0
]]
# this is the receiving resource record
decline_reservation_request
(
itip_event
,
resource
)
cleanup
()
os
.
unlink
(
filepath
)
def
heartbeat
(
lastrun
):
global
imap
# run archival job every hour only
now
=
int
(
time
.
time
())
if
lastrun
==
0
or
now
-
heartbeat
.
_lastrun
<
3600
:
return
log
.
debug
(
_
(
"module_resources.heartbeat(
%d
)"
)
%
(
heartbeat
.
_lastrun
),
level
=
8
)
# get a list of resource records from LDAP
auth
=
Auth
()
auth
.
connect
()
resource_dns
=
auth
.
find_resource
(
'*'
)
# filter by resource_base_dn
resource_base_dn
=
conf
.
get
(
'ldap'
,
'resource_base_dn'
,
None
)
if
resource_base_dn
is
not
None
:
resource_dns
=
[
dn
for
dn
in
resource_dns
if
resource_base_dn
in
dn
]
if
len
(
resource_dns
)
>
0
:
imap
=
IMAP
()
imap
.
connect
()
for
resource_dn
in
resource_dns
:
resource_attrs
=
auth
.
get_entry_attributes
(
None
,
resource_dn
,
[
'kolabtargetfolder'
])
if
resource_attrs
.
has_key
(
'kolabtargetfolder'
):
try
:
expunge_resource_calendar
(
resource_attrs
[
'kolabtargetfolder'
])
except
Exception
,
e
:
log
.
error
(
_
(
"Expunge resource calendar for
%s
(
%s
) failed:
%r
"
)
%
(
resource_dn
,
resource_attrs
[
'kolabtargetfolder'
],
e
))
imap
.
disconnect
()
auth
.
disconnect
()
heartbeat
.
_lastrun
=
now
heartbeat
.
_lastrun
=
0
def
expunge_resource_calendar
(
mailbox
):
"""
Cleanup routine to remove events older than 100 days from the given resource calendar
"""
global
imap
days
=
int
(
conf
.
get
(
'wallace'
,
'resource_calendar_expire_days'
))
now
=
datetime
.
datetime
.
now
(
tzlocal
())
expire_date
=
now
-
datetime
.
timedelta
(
days
=
days
)
log
.
debug
(
_
(
"Expunge events in resource folder
%r
older than
%d
days"
)
%
(
mailbox
,
days
),
level
=
8
)
# might raise an exception, let that bubble
targetfolder
=
imap
.
folder_quote
(
mailbox
)
imap
.
set_acl
(
targetfolder
,
conf
.
get
(
conf
.
get
(
'kolab'
,
'imap_backend'
),
'admin_login'
),
"lrswipkxtecda"
)
imap
.
imap
.
m
.
select
(
targetfolder
)
typ
,
data
=
imap
.
imap
.
m
.
search
(
None
,
'UNDELETED'
)
for
num
in
data
[
0
]
.
split
():
log
.
debug
(
_
(
"Fetching message ID
%r
from folder
%r
"
)
%
(
num
,
mailbox
),
level
=
9
)
typ
,
data
=
imap
.
imap
.
m
.
fetch
(
num
,
'(RFC822)'
)
event_message
=
message_from_string
(
data
[
0
][
1
])
try
:
event
=
event_from_message
(
message_from_string
(
data
[
0
][
1
]))
except
Exception
,
e
:
log
.
error
(
_
(
"Failed to parse event from message
%s
/
%s
:
%r
"
)
%
(
mailbox
,
num
,
e
))
continue
if
event
:
dt_end
=
to_dt
(
event
.
get_end
())
# consider recurring events and get real end date
if
event
.
is_recurring
():
dt_end
=
event
.
get_last_occurrence
()
if
dt_end
is
None
:
# skip if recurring forever
continue
if
dt_end
and
dt_end
<
expire_date
:
age
=
now
-
dt_end
log
.
debug
(
_
(
"Flag event
%s
from message
%s
/
%s
as deleted (age =
%d
days)"
)
%
(
event
.
uid
,
mailbox
,
num
,
age
.
days
),
level
=
8
)
imap
.
imap
.
m
.
store
(
num
,
'+FLAGS'
,
'
\\
Deleted'
)
imap
.
imap
.
m
.
expunge
()
def
check_availability
(
itip_events
,
resource_dns
,
resources
,
receiving_attendee
=
None
):
"""
For each resource, determine if any of the events in question are in conflict.
"""
# Store the (first) conflicting event(s) alongside the resource information.
start
=
time
.
time
()
num_messages
=
0
available_resource
=
None
for
resource
in
resources
.
keys
():
# skip this for resource collections
if
not
resources
[
resource
]
.
has_key
(
'kolabtargetfolder'
):
continue
# sets the 'conflicting' flag and adds a list of conflicting events found
try
:
num_messages
+=
read_resource_calendar
(
resources
[
resource
],
itip_events
)
except
Exception
,
e
:
log
.
error
(
_
(
"Failed to read resource calendar for
%r
:
%r
"
)
%
(
resource
,
e
))
end
=
time
.
time
()
log
.
debug
(
_
(
"start:
%r
, end:
%r
, total:
%r
, messages:
%d
"
)
%
(
start
,
end
,
(
end
-
start
),
num_messages
),
level
=
9
)
# For each resource (collections are first!)
# check conflicts and either accept or decline the reservation request
for
resource
in
resource_dns
:
log
.
debug
(
_
(
"Polling for resource
%r
"
)
%
(
resource
),
level
=
9
)
if
not
resources
.
has_key
(
resource
):
log
.
debug
(
_
(
"Resource
%r
has been popped from the list"
)
%
(
resource
),
level
=
9
)
continue
if
not
resources
[
resource
]
.
has_key
(
'conflicting_events'
):
log
.
debug
(
_
(
"Resource is a collection"
),
level
=
9
)
# check if there are non-conflicting collection members
conflicting_members
=
[
x
for
x
in
resources
[
resource
][
'uniquemember'
]
if
resources
[
x
][
'conflict'
]]
# found at least one non-conflicting member, remove the conflicting ones and continue
if
len
(
conflicting_members
)
<
len
(
resources
[
resource
][
'uniquemember'
]):
for
member
in
conflicting_members
:
resources
[
resource
][
'uniquemember'
]
=
[
x
for
x
in
resources
[
resource
][
'uniquemember'
]
if
x
!=
member
]
del
resources
[
member
]
log
.
debug
(
_
(
"Removed conflicting resources from
%r
: (
%r
) =>
%r
"
)
%
(
resource
,
conflicting_members
,
resources
[
resource
][
'uniquemember'
]
),
level
=
9
)
else
:
# TODO: shuffle existing bookings of collection members in order
# to make one available for the requested time
pass
continue
if
len
(
resources
[
resource
][
'conflicting_events'
])
>
0
:
log
.
debug
(
_
(
"Conflicting events:
%r
for resource
%r
"
)
%
(
resources
[
resource
][
'conflicting_events'
],
resource
),
level
=
9
)
done
=
False
# This is the event being conflicted with!
for
itip_event
in
itip_events
:
# do not re-assign single occurrences to another resource
if
itip_event
[
'recurrence-id'
]
is
not
None
:
continue
# Now we have the event that was conflicting
if
resources
[
resource
][
'mail'
]
in
[
a
.
get_email
()
for
a
in
itip_event
[
'xml'
]
.
get_attendees
()]:
# this resource initially was delegated from a collection ?
if
receiving_attendee
and
receiving_attendee
.
get_email
()
==
resources
[
resource
][
'mail'
]
\
and
len
(
receiving_attendee
.
get_delegated_from
())
>
0
:
for
delegator
in
receiving_attendee
.
get_delegated_from
():
collection_data
=
get_resource_collection
(
delegator
.
email
())
if
collection_data
is
not
None
:
# check if another collection member is available
(
available_resource
,
dummy
)
=
check_availability
(
itip_events
,
collection_data
[
0
],
collection_data
[
1
])
break
if
available_resource
is
not
None
:
log
.
debug
(
_
(
"Delegate to another resource collection member:
%r
to
%r
"
)
%
\
(
resources
[
resource
][
'mail'
],
available_resource
[
'mail'
]),
level
=
8
)
# set this new resource as delegate for the receiving_attendee
itip_event
[
'xml'
]
.
delegate
(
resources
[
resource
][
'mail'
],
available_resource
[
'mail'
],
available_resource
[
'cn'
])
# set delegator to NON-PARTICIPANT and RSVP=FALSE
receiving_attendee
.
set_role
(
kolabformat
.
NonParticipant
)
receiving_attendee
.
set_rsvp
(
False
)
receiving_attendee
.
setDelegatedFrom
([])
# remove existing_events as we now delegated back to the collection
if
len
(
resources
[
resource
][
'existing_events'
])
>
0
:
for
existing
in
resources
[
resource
][
'existing_events'
]:
delete_resource_event
(
existing
.
uid
,
resources
[
resource
],
existing
.
_msguid
)
done
=
True
if
done
:
break
else
:
# No conflicts, go accept
for
itip_event
in
itip_events
:
# directly invited resource
if
resources
[
resource
][
'mail'
]
in
[
a
.
get_email
()
for
a
in
itip_event
[
'xml'
]
.
get_attendees
()]:
available_resource
=
resources
[
resource
]
done
=
True
else
:
# This must have been a resource collection originally.
# We have inserted the reference to the original resource
# record in 'memberof'.
if
resources
[
resource
]
.
has_key
(
'memberof'
):
original_resource
=
resources
[
resources
[
resource
][
'memberof'
]]
# Randomly select a target resource from the resource collection.
available_resource
=
resources
[
original_resource
[
'uniquemember'
][
random
.
randint
(
0
,(
len
(
original_resource
[
'uniquemember'
])
-
1
))]]
done
=
True
if
done
:
break
# end for resource in resource_dns:
return
(
available_resource
,
itip_event
)
def
read_resource_calendar
(
resource_rec
,
itip_events
):
"""
Read all booked events from the given resource's calendar
and check for conflicts with the given list if itip events
"""
global
imap
resource_rec
[
'conflict'
]
=
False
resource_rec
[
'conflicting_events'
]
=
[]
resource_rec
[
'existing_events'
]
=
[]
mailbox
=
resource_rec
[
'kolabtargetfolder'
]
log
.
debug
(
_
(
"Checking events in resource folder
%r
"
)
%
(
mailbox
),
level
=
9
)
# set read ACLs for admin user
imap
.
set_acl
(
mailbox
,
conf
.
get
(
conf
.
get
(
'kolab'
,
'imap_backend'
),
'admin_login'
),
"lrs"
)
# might raise an exception, let that bubble
imap
.
imap
.
m
.
select
(
imap
.
folder_quote
(
mailbox
))
typ
,
data
=
imap
.
imap
.
m
.
search
(
None
,
'UNDELETED'
)
num_messages
=
len
(
data
[
0
]
.
split
())
for
num
in
data
[
0
]
.
split
():
# For efficiency, makes the routine non-deterministic
if
resource_rec
[
'conflict'
]:
continue
log
.
debug
(
_
(
"Fetching message UID
%r
from folder
%r
"
)
%
(
num
,
mailbox
),
level
=
9
)
typ
,
data
=
imap
.
imap
.
m
.
fetch
(
num
,
'(UID RFC822)'
)
try
:
msguid
=
re
.
search
(
r"\WUID (\d+)"
,
data
[
0
][
0
])
.
group
(
1
)
except
Exception
,
e
:
log
.
error
(
_
(
"No UID found in IMAP response:
%r
"
)
%
(
data
[
0
][
0
]))
continue
event_message
=
message_from_string
(
data
[
0
][
1
])
try
:
event
=
event_from_message
(
message_from_string
(
data
[
0
][
1
]))
except
Exception
,
e
:
log
.
error
(
_
(
"Failed to parse event from message
%s
/
%s
:
%r
"
)
%
(
mailbox
,
num
,
e
))
continue
if
event
:
for
itip
in
itip_events
:
conflict
=
check_event_conflict
(
event
,
itip
)
if
event
.
get_uid
()
==
itip
[
'uid'
]:
setattr
(
event
,
'_msguid'
,
msguid
)
if
event
.
is_recurring
()
or
itip
[
'recurrence-id'
]:
resource_rec
[
'existing_master'
]
=
event
else
:
resource_rec
[
'existing_events'
]
.
append
(
event
)
if
conflict
:
log
.
info
(
_
(
"Event
%r
conflicts with event
%r
"
)
%
(
itip
[
'xml'
]
.
get_uid
(),
event
.
get_uid
()
)
)
resource_rec
[
'conflicting_events'
]
.
append
(
event
.
get_uid
())
resource_rec
[
'conflict'
]
=
True
return
num_messages
def
find_existing_event
(
uid
,
recurrence_id
,
resource_rec
):
"""
Search the resources's calendar folder for the given event (by UID)
"""
global
imap
event
=
None
master
=
None
mailbox
=
resource_rec
[
'kolabtargetfolder'
]
log
.
debug
(
_
(
"Searching
%r
for event
%r
"
)
%
(
mailbox
,
uid
),
level
=
9
)
try
:
imap
.
imap
.
m
.
select
(
imap
.
folder_quote
(
mailbox
))
typ
,
data
=
imap
.
imap
.
m
.
search
(
None
,
'(UNDELETED HEADER SUBJECT "
%s
")'
%
(
uid
))
except
Exception
,
e
:
log
.
error
(
_
(
"Failed to access resource calendar::
%r
"
)
%
(
e
))
return
event
for
num
in
reversed
(
data
[
0
]
.
split
()):
typ
,
data
=
imap
.
imap
.
m
.
fetch
(
num
,
'(UID RFC822)'
)
try
:
msguid
=
re
.
search
(
r"\WUID (\d+)"
,
data
[
0
][
0
])
.
group
(
1
)
except
Exception
,
e
:
log
.
error
(
_
(
"No UID found in IMAP response:
%r
"
)
%
(
data
[
0
][
0
]))
continue
try
:
event
=
event_from_message
(
message_from_string
(
data
[
0
][
1
]))
# find instance in a recurring series
if
recurrence_id
and
(
event
.
is_recurring
()
or
event
.
has_exceptions
()):
master
=
event
event
=
master
.
get_instance
(
recurrence_id
)
setattr
(
master
,
'_msguid'
,
msguid
)
# return master, even if instance is not found
if
not
event
and
master
.
uid
==
uid
:
return
(
event
,
master
)
# compare recurrence-id and skip to next message if not matching
elif
recurrence_id
and
not
xmlutils
.
dates_equal
(
recurrence_id
,
event
.
get_recurrence_id
()):
log
.
debug
(
_
(
"Recurrence-ID not matching on message
%s
, skipping:
%r
!=
%r
"
)
%
(
msguid
,
recurrence_id
,
event
.
get_recurrence_id
()
),
level
=
8
)
continue
if
event
is
not
None
:
setattr
(
event
,
'_msguid'
,
msguid
)
except
Exception
,
e
:
log
.
error
(
_
(
"Failed to parse event from message
%s
/
%s
:
%r
"
)
%
(
mailbox
,
num
,
e
))
event
=
None
master
=
None
continue
if
event
and
event
.
uid
==
uid
:
return
(
event
,
master
)
return
(
event
,
master
)
def
accept_reservation_request
(
itip_event
,
resource
,
delegator
=
None
,
confirmed
=
False
):
"""
Accepts the given iTip event by booking it into the resource's
calendar. Then set the attendee status of the given resource to
ACCEPTED and sends an iTip reply message to the organizer.
"""
owner
=
get_resource_owner
(
resource
)
confirmation_required
=
False
if
not
confirmed
and
owner
:
invitationpolicy
=
get_resource_invitationpolicy
(
resource
)
log
.
debug
(
_
(
"Apply invitation policies
%r
"
)
%
(
invitationpolicy
),
level
=
9
)
if
invitationpolicy
is
not
None
:
for
policy
in
invitationpolicy
:
if
policy
&
ACT_MANUAL
and
owner
[
'mail'
]:
confirmation_required
=
True
break
partstat
=
'TENTATIVE'
if
confirmation_required
else
'ACCEPTED'
itip_event
[
'xml'
]
.
set_transparency
(
False
);
itip_event
[
'xml'
]
.
set_attendee_participant_status
(
itip_event
[
'xml'
]
.
get_attendee_by_email
(
resource
[
'mail'
]),
partstat
)
saved
=
save_resource_event
(
itip_event
,
resource
)
log
.
debug
(
_
(
"Adding event to
%r
:
%r
"
)
%
(
resource
[
'kolabtargetfolder'
],
saved
),
level
=
8
)
if
saved
:
send_response
(
delegator
[
'mail'
]
if
delegator
else
resource
[
'mail'
],
itip_event
,
owner
)
if
owner
and
confirmation_required
:
send_owner_confirmation
(
resource
,
owner
,
itip_event
)
elif
owner
:
send_owner_notification
(
resource
,
owner
,
itip_event
,
saved
)
def
decline_reservation_request
(
itip_event
,
resource
):
"""
Set the attendee status of the given resource to
DECLINED and send an according iTip reply to the organizer.
"""
itip_event
[
'xml'
]
.
set_attendee_participant_status
(
itip_event
[
'xml'
]
.
get_attendee_by_email
(
resource
[
'mail'
]),
"DECLINED"
)
# update master event
if
resource
.
get
(
'existing_master'
)
is
not
None
or
itip_event
.
get
(
'_master'
)
is
not
None
:
save_resource_event
(
itip_event
,
resource
)
# remove old copy of the reservation
elif
resource
.
get
(
'existing_events'
,
[])
and
len
(
resource
[
'existing_events'
])
>
0
:
for
existing
in
resource
[
'existing_events'
]:
delete_resource_event
(
existing
.
uid
,
resource
,
existing
.
_msguid
)
# delete old event referenced by itip_event (from owner confirmation)
elif
hasattr
(
itip_event
[
'xml'
],
'_msguid'
):
delete_resource_event
(
itip_event
[
'xml'
]
.
uid
,
resource
,
itip_event
[
'xml'
]
.
_msguid
)
# send response and notification
owner
=
get_resource_owner
(
resource
)
send_response
(
resource
[
'mail'
],
itip_event
,
get_resource_owner
(
resource
))
if
owner
:
send_owner_notification
(
resource
,
owner
,
itip_event
,
True
)
def
save_resource_event
(
itip_event
,
resource
):
"""
Append the given event object to the resource's calendar
"""
try
:
save_event
=
itip_event
[
'xml'
]
targetfolder
=
imap
.
folder_quote
(
resource
[
'kolabtargetfolder'
])
# add exception to existing recurring main event
if
resource
.
get
(
'existing_master'
)
is
not
None
:
save_event
=
resource
[
'existing_master'
]
save_event
.
add_exception
(
itip_event
[
'xml'
])
elif
itip_event
.
get
(
'_master'
)
is
not
None
:
save_event
=
itip_event
[
'_master'
]
save_event
.
add_exception
(
itip_event
[
'xml'
])
# remove old copy of the reservation (also sets ACLs)
if
resource
.
has_key
(
'existing_events'
)
and
len
(
resource
[
'existing_events'
])
>
0
:
for
existing
in
resource
[
'existing_events'
]:
delete_resource_event
(
existing
.
uid
,
resource
,
existing
.
_msguid
)
# delete old version referenced save_event
elif
hasattr
(
save_event
,
'_msguid'
):
delete_resource_event
(
save_event
.
uid
,
resource
,
save_event
.
_msguid
)
else
:
imap
.
set_acl
(
targetfolder
,
conf
.
get
(
conf
.
get
(
'kolab'
,
'imap_backend'
),
'admin_login'
),
"lrswipkxtecda"
)
# append new version
result
=
imap
.
imap
.
m
.
append
(
targetfolder
,
None
,
None
,
save_event
.
to_message
(
creator
=
"Kolab Server <wallace@localhost>"
)
.
as_string
()
)
return
result
except
Exception
,
e
:
log
.
error
(
_
(
"Failed to save event to resource calendar at
%r
:
%r
"
)
%
(
resource
[
'kolabtargetfolder'
],
e
))
return
False
def
delete_resource_event
(
uid
,
resource
,
msguid
=
None
):
"""
Removes the IMAP object with the given UID from a resource's calendar folder
"""
targetfolder
=
imap
.
folder_quote
(
resource
[
'kolabtargetfolder'
])
try
:
imap
.
set_acl
(
targetfolder
,
conf
.
get
(
conf
.
get
(
'kolab'
,
'imap_backend'
),
'admin_login'
),
"lrswipkxtecda"
)
imap
.
imap
.
m
.
select
(
targetfolder
)
# delete by IMAP UID
if
msguid
is
not
None
:
log
.
debug
(
_
(
"Delete resource calendar object from
%r
by UID
%r
"
)
%
(
targetfolder
,
msguid
),
level
=
8
)
imap
.
imap
.
m
.
uid
(
'store'
,
msguid
,
'+FLAGS'
,
'(
\\
Deleted)'
)
else
:
typ
,
data
=
imap
.
imap
.
m
.
search
(
None
,
'(HEADER SUBJECT "
%s
")'
%
uid
)
log
.
debug
(
_
(
"Delete resource calendar object
%r
in
%r
:
%r
"
)
%
(
uid
,
resource
[
'kolabtargetfolder'
],
data
),
level
=
9
)
for
num
in
data
[
0
]
.
split
():
imap
.
imap
.
m
.
store
(
num
,
'+FLAGS'
,
'
\\
Deleted'
)
imap
.
imap
.
m
.
expunge
()
return
True
except
Exception
,
e
:
log
.
error
(
_
(
"Failed to delete calendar object
%r
from folder
%r
:
%r
"
)
%
(
uid
,
targetfolder
,
e
))
return
False
def
reject
(
filepath
):
new_filepath
=
os
.
path
.
join
(
mybasepath
,
'REJECT'
,
os
.
path
.
basename
(
filepath
)
)
os
.
rename
(
filepath
,
new_filepath
)
filepath
=
new_filepath
exec
(
'modules.cb_action_REJECT(
%r
,
%r
)'
%
(
'resources'
,
filepath
))
def
resource_record_from_email_address
(
email_address
):
"""
Resolves the given email address to a resource entity
"""
global
auth
if
not
auth
:
auth
=
Auth
()
auth
.
connect
()
resource_records
=
[]
local_domains
=
auth
.
list_domains
()
if
not
local_domains
==
None
:
local_domains
=
list
(
set
(
local_domains
.
keys
()))
if
not
email_address
.
split
(
'@'
)[
1
]
in
local_domains
:
return
[]
log
.
debug
(
_
(
"Checking if email address
%r
belongs to a resource (collection)"
)
%
(
email_address
),
level
=
8
)
resource_records
=
auth
.
find_resource
(
email_address
)
if
isinstance
(
resource_records
,
list
):
if
len
(
resource_records
)
>
0
:
log
.
debug
(
_
(
"Resource record(s):
%r
"
)
%
(
resource_records
),
level
=
8
)
else
:
log
.
debug
(
_
(
"No resource (collection) records found for
%r
"
)
%
(
email_address
),
level
=
9
)
elif
isinstance
(
resource_records
,
basestring
):
resource_records
=
[
resource_records
]
log
.
debug
(
_
(
"Resource record:
%r
"
)
%
(
resource_records
),
level
=
8
)
return
resource_records
def
resource_records_from_itip_events
(
itip_events
,
recipient_email
=
None
):
"""
Given a list of itip_events, determine which resources have been
invited as attendees and/or resources.
"""
global
auth
if
not
auth
:
auth
=
Auth
()
auth
.
connect
()
resource_records
=
[]
log
.
debug
(
_
(
"Raw itip_events:
%r
"
)
%
(
itip_events
),
level
=
9
)
attendees_raw
=
[]
for
list_attendees_raw
in
[
x
for
x
in
[
y
[
'attendees'
]
for
y
in
itip_events
if
y
.
has_key
(
'attendees'
)
and
isinstance
(
y
[
'attendees'
],
list
)]]:
attendees_raw
.
extend
(
list_attendees_raw
)
for
list_attendees_raw
in
[
y
[
'attendees'
]
for
y
in
itip_events
if
y
.
has_key
(
'attendees'
)
and
isinstance
(
y
[
'attendees'
],
basestring
)]:
attendees_raw
.
append
(
list_attendees_raw
)
log
.
debug
(
_
(
"Raw set of attendees:
%r
"
)
%
(
attendees_raw
),
level
=
9
)
# TODO: Resources are actually not implemented in the format. We reset this
# list later.
resources_raw
=
[]
for
list_resources_raw
in
[
x
for
x
in
[
y
[
'resources'
]
for
y
in
itip_events
if
y
.
has_key
(
'resources'
)]]:
resources_raw
.
extend
(
list_resources_raw
)
log
.
debug
(
_
(
"Raw set of resources:
%r
"
)
%
(
resources_raw
),
level
=
9
)
# consider organizer (in REPLY messages), too
organizers_raw
=
[
re
.
sub
(
'\+[A-Za-z0-9=/-]+@'
,
'@'
,
str
(
y
[
'organizer'
]))
for
y
in
itip_events
if
y
.
has_key
(
'organizer'
)]
log
.
debug
(
_
(
"Raw set of organizers:
%r
"
)
%
(
organizers_raw
),
level
=
8
)
# TODO: We expect the format of an attendee line to literally be:
#
# ATTENDEE:RSVP=TRUE;ROLE=REQ-PARTICIPANT;MAILTO:lydia.bossers@kolabsys.com
#
# which makes the attendees_raw contain:
#
# RSVP=TRUE;ROLE=REQ-PARTICIPANT;MAILTO:lydia.bossers@kolabsys.com
#
attendees
=
[
x
.
split
(
':'
)[
-
1
]
for
x
in
attendees_raw
+
organizers_raw
]
# Limit the attendee resources to the one that is actually invited
# with the current message. Considering all invited resources would result in
# duplicate responses from every iTip message sent to a resource.
if
recipient_email
is
not
None
:
attendees
=
[
a
for
a
in
attendees
if
a
==
recipient_email
]
for
attendee
in
attendees
:
log
.
debug
(
_
(
"Checking if attendee
%r
is a resource (collection)"
)
%
(
attendee
),
level
=
8
)
_resource_records
=
auth
.
find_resource
(
attendee
)
if
isinstance
(
_resource_records
,
list
):
if
len
(
_resource_records
)
>
0
:
resource_records
.
extend
(
_resource_records
)
log
.
debug
(
_
(
"Resource record(s):
%r
"
)
%
(
_resource_records
),
level
=
8
)
else
:
log
.
debug
(
_
(
"No resource (collection) records found for
%r
"
)
%
(
attendee
),
level
=
9
)
elif
isinstance
(
_resource_records
,
basestring
):
resource_records
.
append
(
_resource_records
)
log
.
debug
(
_
(
"Resource record:
%r
"
)
%
(
_resource_records
),
level
=
8
)
else
:
log
.
warning
(
_
(
"Resource reservation made but no resource records found"
))
# Escape the non-implementation of the free-form, undefined RESOURCES
# list(s) in iTip.
if
len
(
resource_records
)
==
0
:
# TODO: We don't know how to handle this yet!
# We expect the format of an resource line to literally be:
# RESOURCES:MAILTO:resource-car@kolabsys.com
resources_raw
=
[]
resources
=
[
x
.
split
(
':'
)[
-
1
]
for
x
in
resources_raw
]
# Limit the attendee resources to the one that is actually invited
# with the current message.
if
recipient_email
is
not
None
:
resources
=
[
a
for
a
in
resources
if
a
==
recipient_email
]
for
resource
in
resources
:
log
.
debug
(
_
(
"Checking if resource
%r
is a resource (collection)"
)
%
(
resource
),
level
=
8
)
_resource_records
=
auth
.
find_resource
(
resource
)
if
isinstance
(
_resource_records
,
list
):
if
len
(
_resource_records
)
>
0
:
resource_records
.
extend
(
_resource_records
)
log
.
debug
(
_
(
"Resource record(s):
%r
"
)
%
(
_resource_records
),
level
=
8
)
else
:
log
.
debug
(
_
(
"No resource (collection) records found for
%r
"
)
%
(
resource
),
level
=
8
)
elif
isinstance
(
_resource_records
,
basestring
):
resource_records
.
append
(
_resource_records
)
log
.
debug
(
_
(
"Resource record:
%r
"
)
%
(
_resource_records
),
level
=
8
)
else
:
log
.
warning
(
_
(
"Resource reservation made but no resource records found"
))
log
.
debug
(
_
(
"The following resources are being referred to in the "
+
\
"iTip:
%r
"
)
%
(
resource_records
),
level
=
8
)
return
resource_records
def
get_resource_records
(
resource_dns
):
"""
Get the resource details, which includes details on the IMAP folder
"""
global
auth
resources
=
{}
for
resource_dn
in
list
(
set
(
resource_dns
)):
# Get the attributes for the record
# See if it is a resource collection
# If it is, expand to individual resources
# If it is not, ...
resource_attrs
=
auth
.
get_entry_attributes
(
None
,
resource_dn
,
[
'*'
])
resource_attrs
[
'dn'
]
=
resource_dn
parse_kolabinvitationpolicy
(
resource_attrs
)
if
not
'kolabsharedfolder'
in
[
x
.
lower
()
for
x
in
resource_attrs
[
'objectclass'
]]:
if
resource_attrs
.
has_key
(
'uniquemember'
):
if
not
isinstance
(
resource_attrs
[
'uniquemember'
],
list
):
resource_attrs
[
'uniquemember'
]
=
[
resource_attrs
[
'uniquemember'
]
]
resources
[
resource_dn
]
=
resource_attrs
for
uniquemember
in
resource_attrs
[
'uniquemember'
]:
member_attrs
=
auth
.
get_entry_attributes
(
None
,
uniquemember
,
[
'*'
]
)
if
'kolabsharedfolder'
in
[
x
.
lower
()
for
x
in
member_attrs
[
'objectclass'
]]:
member_attrs
[
'dn'
]
=
uniquemember
parse_kolabinvitationpolicy
(
member_attrs
,
resource_attrs
)
resources
[
uniquemember
]
=
member_attrs
resources
[
uniquemember
][
'memberof'
]
=
resource_dn
if
not
member_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
return
resources
def
parse_kolabinvitationpolicy
(
attrs
,
parent
=
None
):
if
attrs
.
has_key
(
'kolabinvitationpolicy'
):
if
not
isinstance
(
attrs
[
'kolabinvitationpolicy'
],
list
):
attrs
[
'kolabinvitationpolicy'
]
=
[
attrs
[
'kolabinvitationpolicy'
]]
attrs
[
'kolabinvitationpolicy'
]
=
[
policy_name_map
[
p
]
for
p
in
attrs
[
'kolabinvitationpolicy'
]
if
policy_name_map
.
has_key
(
p
)]
elif
isinstance
(
parent
,
dict
)
and
parent
.
has_key
(
'kolabinvitationpolicy'
):
attrs
[
'kolabinvitationpolicy'
]
=
parent
[
'kolabinvitationpolicy'
]
def
get_resource_collection
(
email_address
):
"""
"""
resource_dns
=
resource_record_from_email_address
(
email_address
)
if
len
(
resource_dns
)
==
1
:
resource_attrs
=
auth
.
get_entry_attributes
(
None
,
resource_dns
[
0
],
[
'objectclass'
])
if
not
'kolabsharedfolder'
in
[
x
.
lower
()
for
x
in
resource_attrs
[
'objectclass'
]]:
resources
=
get_resource_records
(
resource_dns
)
return
(
resource_dns
,
resources
)
return
None
def
get_resource_owner
(
resource
):
"""
Get this resource's owner record
"""
global
auth
if
not
auth
:
auth
=
Auth
()
auth
.
connect
()
owners
=
[]
if
resource
.
has_key
(
'owner'
):
if
not
isinstance
(
resource
[
'owner'
],
list
):
owners
=
[
resource
[
'owner'
]
]
else
:
owners
=
resource
[
'owner'
]
else
:
# get owner attribute from collection
collections
=
auth
.
search_entry_by_attribute
(
'uniquemember'
,
resource
[
'dn'
])
if
not
isinstance
(
collections
,
list
):
collections
=
[
collections
]
for
dn
,
collection
in
collections
:
if
collection
.
has_key
(
'owner'
)
and
isinstance
(
collection
[
'owner'
],
list
):
owners
+=
collection
[
'owner'
]
elif
collection
.
has_key
(
'owner'
):
owners
.
append
(
collection
[
'owner'
])
for
dn
in
owners
:
owner
=
auth
.
get_entry_attributes
(
None
,
dn
,
[
'cn'
,
'mail'
,
'telephoneNumber'
])
if
owner
is
not
None
:
return
owner
return
None
def
get_resource_invitationpolicy
(
resource
):
"""
Get this resource's kolabinvitationpolicy configuration
"""
global
auth
if
not
resource
.
has_key
(
'kolabinvitationpolicy'
)
or
resource
[
'kolabinvitationpolicy'
]
is
None
:
if
not
auth
:
auth
=
Auth
()
auth
.
connect
()
# get kolabinvitationpolicy attribute from collection
collections
=
auth
.
search_entry_by_attribute
(
'uniquemember'
,
resource
[
'dn'
])
if
not
isinstance
(
collections
,
list
):
collections
=
[
(
collections
[
'dn'
],
collections
)
]
log
.
debug
(
_
(
"Check collections
%r
for kolabinvitationpolicy attributes"
)
%
(
collections
),
level
=
9
)
for
dn
,
collection
in
collections
:
# ldap.search_entry_by_attribute() doesn't return the attributes lower-cased
if
collection
.
has_key
(
'kolabInvitationPolicy'
):
collection
[
'kolabinvitationpolicy'
]
=
collection
[
'kolabInvitationPolicy'
]
if
collection
.
has_key
(
'kolabinvitationpolicy'
):
parse_kolabinvitationpolicy
(
collection
)
resource
[
'kolabinvitationpolicy'
]
=
collection
[
'kolabinvitationpolicy'
]
break
return
resource
[
'kolabinvitationpolicy'
]
if
resource
.
has_key
(
'kolabinvitationpolicy'
)
else
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
resource, this will send an additional DELEGATED response message.
"""
if
isinstance
(
itip_events
,
dict
):
itip_events
=
[
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
)
# TODO: look-up event organizer in LDAP and change localization to its preferredlanguage
message_text
=
reservation_response_text
(
participant_status
,
owner
)
subject_template
=
_
(
"Reservation Request for
%(summary)s
was
%(status)s
"
)
# Extra actions to take: send delegated reply
if
participant_status
==
"DELEGATED"
:
delegatee
=
[
a
for
a
in
itip_event
[
'xml'
]
.
get_attendees
()
if
from_address
in
a
.
get_delegated_from
(
True
)][
0
]
delegated_message_text
=
_
(
"""
*** 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
())
pykolab
.
itip
.
send_reply
(
from_address
,
itip_event
,
delegated_message_text
,
subject
=
subject_template
)
# adjust some vars for the regular reply from the delegatee
message_text
=
reservation_response_text
(
delegatee
.
get_participant_status
(
True
),
owner
)
from_address
=
delegatee
.
get_email
()
time
.
sleep
(
2
)
pykolab
.
itip
.
send_reply
(
from_address
,
itip_event
,
message_text
,
subject
=
subject_template
)
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.
"""
)
%
(
participant_status_label
(
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
def
send_owner_notification
(
resource
,
owner
,
itip_event
,
success
=
True
):
"""
Send a reservation notification to the resource owner
"""
import
smtplib
from
pykolab
import
utils
from
email.MIMEText
import
MIMEText
from
email.Utils
import
formatdate
# encode unicode strings with quoted-printable
from
email
import
charset
charset
.
add_charset
(
'utf-8'
,
charset
.
SHORTEST
,
charset
.
QP
)
notify
=
False
status
=
itip_event
[
'xml'
]
.
get_attendee_by_email
(
resource
[
'mail'
])
.
get_participant_status
(
True
)
invitationpolicy
=
get_resource_invitationpolicy
(
resource
)
if
invitationpolicy
is
not
None
:
for
policy
in
invitationpolicy
:
# TODO: distingish ACCEPTED / DECLINED status notifications?
if
policy
&
COND_NOTIFY
and
owner
[
'mail'
]:
notify
=
True
break
if
notify
or
not
success
:
log
.
debug
(
_
(
"Sending booking notification for event
%r
to
%r
from
%r
"
)
%
(
itip_event
[
'uid'
],
owner
[
'mail'
],
resource
[
'cn'
]
),
level
=
8
)
# change gettext language to the preferredlanguage setting of the resource owner
if
owner
.
has_key
(
'preferredlanguage'
):
pykolab
.
translate
.
setUserLanguage
(
owner
[
'preferredlanguage'
])
message_text
=
owner_notification_text
(
resource
,
owner
,
itip_event
[
'xml'
],
success
)
msg
=
MIMEText
(
utils
.
stripped_message
(
message_text
),
_charset
=
'utf-8'
)
msg
[
'To'
]
=
owner
[
'mail'
]
msg
[
'From'
]
=
resource
[
'mail'
]
msg
[
'Date'
]
=
formatdate
(
localtime
=
True
)
msg
[
'Subject'
]
=
utils
.
str2unicode
(
_
(
'Booking for
%s
has been
%s
'
)
%
(
resource
[
'cn'
],
participant_status_label
(
status
)
if
success
else
_
(
'failed'
)
))
smtp
=
smtplib
.
SMTP
(
"localhost"
,
10027
)
if
conf
.
debuglevel
>
8
:
smtp
.
set_debuglevel
(
True
)
try
:
smtp
.
sendmail
(
resource
[
'mail'
],
owner
[
'mail'
],
msg
.
as_string
())
except
Exception
,
e
:
log
.
error
(
_
(
"SMTP sendmail error:
%r
"
)
%
(
e
))
smtp
.
quit
()
def
owner_notification_text
(
resource
,
owner
,
event
,
success
):
organizer
=
event
.
get_organizer
()
status
=
event
.
get_attendee_by_email
(
resource
[
'mail'
])
.
get_participant_status
(
True
)
if
success
:
message_text
=
_
(
"""
The resource booking for %(resource)s by %(orgname)s <%(orgemail)s> has been %(status)s for %(date)s.
*** This is an automated message, sent to you as the resource owner. ***
"""
)
else
:
message_text
=
_
(
"""
A reservation request for %(resource)s could not be processed automatically.
Please contact %(orgname)s <%(orgemail)s> who requested this resource for %(date)s. Subject: %(summary)s.
*** This is an automated message, sent to you as the resource owner. ***
"""
)
return
message_text
%
{
'resource'
:
resource
[
'cn'
],
'summary'
:
event
.
get_summary
(),
'date'
:
event
.
get_date_text
(),
'status'
:
participant_status_label
(
status
),
'orgname'
:
organizer
.
name
(),
'orgemail'
:
organizer
.
email
()
}
def
send_owner_confirmation
(
resource
,
owner
,
itip_event
):
"""
Send a reservation request to the resource owner for manual confirmation (ACCEPT or DECLINE)
This clones the given invtation with a new UID and setting the resource as organizer in order to
receive the reply from the owner.
"""
uid
=
itip_event
[
'uid'
]
event
=
itip_event
[
'xml'
]
organizer
=
event
.
get_organizer
()
event_attendees
=
[
a
.
get_displayname
()
for
a
in
event
.
get_attendees
()
if
not
a
.
get_cutype
()
==
kolabformat
.
CutypeResource
]
log
.
debug
(
_
(
"Clone invitation for owner confirmation:
%r
from
%r
"
)
%
(
itip_event
[
'uid'
],
event
.
get_organizer
()
.
email
()
),
level
=
8
)
# generate new UID and set the resource as organizer
(
mail
,
domain
)
=
resource
[
'mail'
]
.
split
(
'@'
)
event
.
set_uid
(
str
(
uuid
.
uuid4
()))
event
.
set_organizer
(
mail
+
'+'
+
base64
.
b64encode
(
uid
,
'-/'
)
+
'@'
+
domain
,
resource
[
'cn'
])
itip_event
[
'uid'
]
=
event
.
get_uid
()
# add resource owner as (the sole) attendee
event
.
_attendees
=
[]
event
.
add_attendee
(
owner
[
'mail'
],
owner
[
'cn'
],
rsvp
=
True
,
role
=
kolabformat
.
Required
,
participant_status
=
kolabformat
.
PartNeedsAction
)
# flag this iTip message as confirmation type
event
.
add_custom_property
(
'X-Kolab-InvitationType'
,
'CONFIRMATION'
)
message_text
=
_
(
"""
A reservation request for %(resource)s requires your approval!
Please either accept or decline this invitation without saving it to your calendar.
The reservation request was sent from %(orgname)s <%(orgemail)s>.
Subject: %(summary)s.
Date: %(date)s
Participants: %(attendees)s
*** This is an automated message, please don't reply by email. ***
"""
)
%
{
'resource'
:
resource
[
'cn'
],
'orgname'
:
organizer
.
name
(),
'orgemail'
:
organizer
.
email
(),
'summary'
:
event
.
get_summary
(),
'date'
:
event
.
get_date_text
(),
'attendees'
:
",
\n
+ "
.
join
(
event_attendees
)
}
pykolab
.
itip
.
send_request
(
owner
[
'mail'
],
itip_event
,
message_text
,
subject
=
_
(
'Booking request for
%s
requires confirmation'
)
%
(
resource
[
'cn'
]),
direct
=
True
)
File Metadata
Details
Attached
Mime Type
text/x-script.python
Expires
Sun, Apr 5, 11:17 PM (1 w, 6 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
32/10/5ec82ff3036db562b6a6754e14f8
Default Alt Text
module_resources.py (53 KB)
Attached To
Mode
rP pykolab
Attached
Detach File
Event Timeline