1.1 --- a/imiptools/handlers/resource.py Sat Jan 30 17:24:39 2016 +0100
1.2 +++ b/imiptools/handlers/resource.py Sun Jan 31 00:45:26 2016 +0100
1.3 @@ -22,8 +22,7 @@
1.4 from imiptools.data import get_address, to_part, uri_dict
1.5 from imiptools.handlers import Handler
1.6 from imiptools.handlers.common import CommonFreebusy, CommonEvent
1.7 -from imiptools.handlers.scheduling import apply_scheduling_functions, \
1.8 - scheduling_functions
1.9 +from imiptools.handlers.scheduling import apply_scheduling_functions
1.10
1.11 class ResourceHandler(CommonEvent, Handler):
1.12
1.13 @@ -168,10 +167,6 @@
1.14 functions = self.get_preferences().get("scheduling_function",
1.15 "schedule_in_freebusy").split("\n")
1.16
1.17 - # Obtain the actual scheduling functions.
1.18 -
1.19 - functions = map(scheduling_functions.get, functions)
1.20 -
1.21 return apply_scheduling_functions(functions, self)
1.22
1.23 class Event(ResourceHandler):
2.1 --- a/imiptools/handlers/scheduling/__init__.py Sat Jan 30 17:24:39 2016 +0100
2.2 +++ b/imiptools/handlers/scheduling/__init__.py Sun Jan 31 00:45:26 2016 +0100
2.3 @@ -19,7 +19,9 @@
2.4 this program. If not, see <http://www.gnu.org/licenses/>.
2.5 """
2.6
2.7 +from imiptools.text import parse_line
2.8 from imiptools.handlers.scheduling.manifest import scheduling_functions
2.9 +import re
2.10
2.11 def apply_scheduling_functions(functions, handler):
2.12
2.13 @@ -28,9 +30,13 @@
2.14 'handler'.
2.15 """
2.16
2.17 + # Obtain the actual scheduling functions with arguments.
2.18 +
2.19 + functions = get_scheduling_function_calls(functions)
2.20 +
2.21 response = "ACCEPTED"
2.22
2.23 - for fn in functions:
2.24 + for fn, args in functions:
2.25
2.26 # NOTE: Should signal an error for incorrectly configured resources.
2.27
2.28 @@ -41,7 +47,7 @@
2.29 # declines or gives a null response.
2.30
2.31 else:
2.32 - result = fn(handler)
2.33 + result = fn(handler, args)
2.34
2.35 # Return a negative result immediately.
2.36
2.37 @@ -56,4 +62,24 @@
2.38
2.39 return response
2.40
2.41 +def get_scheduling_function_calls(lines):
2.42 +
2.43 + """
2.44 + Parse the given 'lines', returning a list of (function, arguments) tuples,
2.45 + with each function being a genuine function object and with the arguments
2.46 + being a list of strings.
2.47 +
2.48 + Each of the 'lines' should employ the function name and argument strings
2.49 + separated by whitespace, with any whitespace inside arguments quoted using
2.50 + single or double quotes.
2.51 + """
2.52 +
2.53 + functions = []
2.54 +
2.55 + for line in lines:
2.56 + parts = parse_line(line)
2.57 + functions.append((scheduling_functions.get(parts[0]), parts[1:]))
2.58 +
2.59 + return functions
2.60 +
2.61 # vim: tabstop=4 expandtab shiftwidth=4
3.1 --- a/imiptools/handlers/scheduling/access.py Sat Jan 30 17:24:39 2016 +0100
3.2 +++ b/imiptools/handlers/scheduling/access.py Sun Jan 31 00:45:26 2016 +0100
3.3 @@ -19,9 +19,97 @@
3.4 this program. If not, see <http://www.gnu.org/licenses/>.
3.5 """
3.6
3.7 -from imiptools.data import get_address
3.8 +from imiptools.data import get_address, get_addresses
3.9 +from imiptools.text import parse_line
3.10 +
3.11 +def access_control_list(handler, args):
3.12 +
3.13 + """
3.14 + Attempt to schedule the current object of the given 'handler' using an
3.15 + access control list provided in the given 'args', applying it to the
3.16 + organiser.
3.17 + """
3.18 +
3.19 + # Obtain either a file from the user's preferences directory...
3.20 +
3.21 + if not args:
3.22 + acl = handler.get_preferences().get("acl")
3.23 + lines = acl.strip().split("\n")
3.24 +
3.25 + # Or obtain the contents of a specific file.
3.26 +
3.27 + else:
3.28 + try:
3.29 + f = open(args[0])
3.30 + except IOError:
3.31 + return None
3.32 + try:
3.33 + lines = f.readlines()
3.34 + finally:
3.35 + f.close()
3.36 +
3.37 + # Use the current object's identities with the ACL rules.
3.38 +
3.39 + organiser = get_address(handler.obj.get_value("ORGANIZER"))
3.40 + attendees = get_addresses(handler.obj.get_values("ATTENDEE"))
3.41 +
3.42 + response = None
3.43 +
3.44 + for line in lines:
3.45 + parts = parse_line(line.strip())
3.46 +
3.47 + # Skip empty lines.
3.48 +
3.49 + if not parts:
3.50 + continue
3.51
3.52 -def same_domain_only(handler):
3.53 + # Accept either a single word with an action or a rule.
3.54 + # NOTE: Should signal an error with the format.
3.55 +
3.56 + if len(parts) == 1:
3.57 + action = parts[0]
3.58 + elif len(parts) >= 3:
3.59 + action, role, identities = parts[0], parts[1], map(get_address, parts[2:])
3.60 + else:
3.61 + return None
3.62 +
3.63 + if action.lower() == "accept":
3.64 + result = "ACCEPTED"
3.65 + elif action.lower() in ["decline", "reject"]:
3.66 + result = "DECLINED"
3.67 + else:
3.68 + return None
3.69 +
3.70 + # With only an action, prepare a default response in case none of
3.71 + # the rules match.
3.72 +
3.73 + if len(parts) == 1:
3.74 + response = result
3.75 + continue
3.76 +
3.77 + # Where no default has been set, use an implicit default based on
3.78 + # the action appearing in a rule.
3.79 +
3.80 + elif not response:
3.81 + response = result == "ACCEPTED" and "DECLINED" or "ACCEPTED"
3.82 +
3.83 + # Interpret a rule, attempting to match identities to properties.
3.84 +
3.85 + if role.lower() in ["organiser", "organizer"]:
3.86 + match = organiser in identities
3.87 + elif role.lower() in ["attendee", "attendees"]:
3.88 + match = set(attendees).intersection(identities)
3.89 + else:
3.90 + return None
3.91 +
3.92 + # Use the result of any match.
3.93 +
3.94 + if match:
3.95 + response = result
3.96 +
3.97 + return response
3.98 +
3.99 +def same_domain_only(handler, args):
3.100
3.101 """
3.102 Attempt to schedule the current object of the given 'handler' if the
3.103 @@ -39,6 +127,7 @@
3.104 # Registry of scheduling functions.
3.105
3.106 scheduling_functions = {
3.107 + "access_control_list" : access_control_list,
3.108 "same_domain_only" : same_domain_only,
3.109 }
3.110
4.1 --- a/imiptools/handlers/scheduling/freebusy.py Sat Jan 30 17:24:39 2016 +0100
4.2 +++ b/imiptools/handlers/scheduling/freebusy.py Sun Jan 31 00:45:26 2016 +0100
4.3 @@ -25,7 +25,7 @@
4.4 periods_from, remove_event_periods, \
4.5 remove_periods
4.6
4.7 -def schedule_in_freebusy(handler, freebusy=None):
4.8 +def schedule_in_freebusy(handler, args, freebusy=None):
4.9
4.10 """
4.11 Attempt to schedule the current object of the given 'handler' in the
4.12 @@ -53,7 +53,7 @@
4.13
4.14 return scheduled and "ACCEPTED" or "DECLINED"
4.15
4.16 -def schedule_corrected_in_freebusy(handler):
4.17 +def schedule_corrected_in_freebusy(handler, args):
4.18
4.19 """
4.20 Attempt to schedule the current object of the given 'handler', correcting
4.21 @@ -88,7 +88,7 @@
4.22
4.23 return scheduled == "ACCEPTED" and (corrected and "COUNTER" or "ACCEPTED") or "DECLINED"
4.24
4.25 -def schedule_next_available_in_freebusy(handler):
4.26 +def schedule_next_available_in_freebusy(handler, args):
4.27
4.28 """
4.29 Attempt to schedule the current object of the given 'handler', correcting
4.30 @@ -202,7 +202,7 @@
4.31
4.32 # Check one last time, reverting the change if not scheduled.
4.33
4.34 - scheduled = schedule_in_freebusy(handler, busy)
4.35 + scheduled = schedule_in_freebusy(handler, args, busy)
4.36
4.37 if scheduled == "DECLINED":
4.38 handler.set_object(obj)
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/imiptools/text.py Sun Jan 31 00:45:26 2016 +0100
5.3 @@ -0,0 +1,56 @@
5.4 +#!/usr/bin/env python
5.5 +
5.6 +"""
5.7 +Parsing of textual content.
5.8 +
5.9 +Copyright (C) 2016 Paul Boddie <paul@boddie.org.uk>
5.10 +
5.11 +This program is free software; you can redistribute it and/or modify it under
5.12 +the terms of the GNU General Public License as published by the Free Software
5.13 +Foundation; either version 3 of the License, or (at your option) any later
5.14 +version.
5.15 +
5.16 +This program is distributed in the hope that it will be useful, but WITHOUT
5.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
5.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
5.19 +details.
5.20 +
5.21 +You should have received a copy of the GNU General Public License along with
5.22 +this program. If not, see <http://www.gnu.org/licenses/>.
5.23 +"""
5.24 +
5.25 +import re
5.26 +
5.27 +# Parsing of lines to obtain functions and arguments.
5.28 +
5.29 +line_pattern_str = r"(?:" \
5.30 + r"(?:'(.*?)')" \
5.31 + r"|" \
5.32 + r'(?:"(.*?)")' \
5.33 + r"|" \
5.34 + r"([^\s]+)" \
5.35 + r")+" \
5.36 + r"(?:\s+|$)"
5.37 +line_pattern = re.compile(line_pattern_str)
5.38 +
5.39 +def parse_line(text):
5.40 +
5.41 + """
5.42 + Parse the given 'text', returning a list of words separated by whitespace in
5.43 + the input, where whitespace may occur inside words if quoted using single or
5.44 + double quotes.
5.45 + """
5.46 +
5.47 + parts = []
5.48 +
5.49 + # Match the components of each part.
5.50 +
5.51 + for match in line_pattern.finditer(text):
5.52 +
5.53 + # Combine the components by traversing the matching groups.
5.54 +
5.55 + parts.append(reduce(lambda a, b: (a or "") + (b or ""), match.groups()))
5.56 +
5.57 + return parts
5.58 +
5.59 +# vim: tabstop=4 expandtab shiftwidth=4
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
6.2 +++ b/tests/templates/event-request-sauna-acl.txt Sun Jan 31 00:45:26 2016 +0100
6.3 @@ -0,0 +1,35 @@
6.4 +Content-Type: multipart/alternative; boundary="===============0047278175=="
6.5 +MIME-Version: 1.0
6.6 +From: paul.boddie@example.com
6.7 +To: resource-room-sauna@example.com
6.8 +Subject: Invitation!
6.9 +
6.10 +--===============0047278175==
6.11 +Content-Type: text/plain; charset="us-ascii"
6.12 +MIME-Version: 1.0
6.13 +Content-Transfer-Encoding: 7bit
6.14 +
6.15 +This message contains an event.
6.16 +--===============0047278175==
6.17 +MIME-Version: 1.0
6.18 +Content-Transfer-Encoding: 7bit
6.19 +Content-Type: text/calendar; charset="us-ascii"; method="REQUEST"
6.20 +
6.21 +BEGIN:VCALENDAR
6.22 +PRODID:-//imip-agent/test//EN
6.23 +METHOD:REQUEST
6.24 +VERSION:2.0
6.25 +BEGIN:VEVENT
6.26 +ORGANIZER:mailto:paul.boddie@example.com
6.27 +ATTENDEE;ROLE=CHAIR:mailto:paul.boddie@example.com
6.28 +ATTENDEE;RSVP=TRUE:mailto:resource-room-sauna@example.com
6.29 +ATTENDEE;RSVP=TRUE:mailto:simon.skunk@example.com
6.30 +DTSTAMP:20141125T004600Z
6.31 +DTSTART;TZID=Europe/Oslo:20141126T170000
6.32 +DTEND;TZID=Europe/Oslo:20141126T174500
6.33 +SUMMARY:Meeting at 5pm
6.34 +UID:event20@example.com
6.35 +END:VEVENT
6.36 +END:VCALENDAR
6.37 +
6.38 +--===============0047278175==--
7.1 --- a/tests/test_resource_invitation_constraints_multiple.sh Sat Jan 30 17:24:39 2016 +0100
7.2 +++ b/tests/test_resource_invitation_constraints_multiple.sh Sun Jan 31 00:45:26 2016 +0100
7.3 @@ -3,12 +3,11 @@
7.4 . "`dirname \"$0\"`/common.sh"
7.5
7.6 USER="mailto:resource-room-sauna@example.com"
7.7 -SENDER="mailto:paul.boddie@example.net"
7.8 +SENDER="mailto:paul.boddie@example.com"
7.9 +OUTSIDESENDER="mailto:paul.boddie@example.net"
7.10 FBFILE="$STORE/$USER/freebusy"
7.11 -FBOFFERFILE="$STORE/$USER/freebusy-offers"
7.12 FBSENDERFILE="$STORE/$SENDER/freebusy"
7.13 -FBSENDEROTHERFILE="$STORE/$SENDER/freebusy-other/$USER"
7.14 -FBSENDERREQUESTS="$STORE/$SENDER/requests"
7.15 +FBOUTSIDESENDERFILE="$STORE/$OUTSIDESENDER/freebusy"
7.16
7.17 mkdir -p "$PREFS/$USER"
7.18 echo 'Europe/Oslo' > "$PREFS/$USER/TZID"
7.19 @@ -33,7 +32,7 @@
7.20
7.21 "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-sauna-outsider.txt" 2>> $ERROR
7.22
7.23 - grep -q "^20141126T150000Z${TAB}20141126T154500Z" "$FBSENDERFILE" \
7.24 + grep -q "^20141126T150000Z${TAB}20141126T154500Z" "$FBOUTSIDESENDERFILE" \
7.25 && echo "Success" \
7.26 || echo "Failed"
7.27
7.28 @@ -87,3 +86,137 @@
7.29 && grep -q 'FREEBUSY;FBTYPE=BUSY:20141126T150000Z/20141126T154500Z' out3.tmp \
7.30 && echo "Success" \
7.31 || echo "Failed"
7.32 +
7.33 +# Try a different scheduling function.
7.34 +
7.35 +ACL="$PWD/acl.tmp"
7.36 +
7.37 +cat > "$PREFS/$USER/scheduling_function" <<EOF
7.38 +schedule_in_freebusy
7.39 +access_control_list "$ACL"
7.40 +EOF
7.41 +
7.42 +# Try without an ACL.
7.43 +
7.44 +cat > "$ACL" <<EOF
7.45 +EOF
7.46 +
7.47 +# Attempt to schedule an event.
7.48 +
7.49 +"$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-sauna-acl.txt" 2>> $ERROR
7.50 +
7.51 + grep -q "^20141126T160000Z${TAB}20141126T164500Z" "$FBSENDERFILE" \
7.52 +&& echo "Success" \
7.53 +|| echo "Failed"
7.54 +
7.55 +# Present the request to the resource.
7.56 +
7.57 + "$RESOURCE_SCRIPT" $ARGS < "$TEMPLATES/event-request-sauna-acl.txt" 2>> $ERROR \
7.58 +| tee out4r.tmp \
7.59 +| "$SHOWMAIL" \
7.60 +> out4.tmp
7.61 +
7.62 + grep -q 'METHOD:REPLY' out4.tmp \
7.63 +&& grep -q 'ATTENDEE.*;PARTSTAT=DECLINED' out4.tmp \
7.64 +&& echo "Success" \
7.65 +|| echo "Failed"
7.66 +
7.67 + ! [ -e "$FBFILE" ] \
7.68 +|| ! grep -q "^20141126T160000Z${TAB}20141126T164500Z" "$FBFILE" \
7.69 +&& echo "Success" \
7.70 +|| echo "Failed"
7.71 +
7.72 +# Try with an unreasonable ACL.
7.73 +
7.74 +cat > "$ACL" <<EOF
7.75 +decline
7.76 +EOF
7.77 +
7.78 +# Attempt to schedule an event.
7.79 +
7.80 +"$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-sauna-acl.txt" 2>> $ERROR
7.81 +
7.82 + grep -q "^20141126T160000Z${TAB}20141126T164500Z" "$FBSENDERFILE" \
7.83 +&& echo "Success" \
7.84 +|| echo "Failed"
7.85 +
7.86 +# Present the request to the resource.
7.87 +
7.88 + "$RESOURCE_SCRIPT" $ARGS < "$TEMPLATES/event-request-sauna-acl.txt" 2>> $ERROR \
7.89 +| tee out5r.tmp \
7.90 +| "$SHOWMAIL" \
7.91 +> out5.tmp
7.92 +
7.93 + grep -q 'METHOD:REPLY' out5.tmp \
7.94 +&& grep -q 'ATTENDEE.*;PARTSTAT=DECLINED' out5.tmp \
7.95 +&& echo "Success" \
7.96 +|| echo "Failed"
7.97 +
7.98 + ! [ -e "$FBFILE" ] \
7.99 +|| ! grep -q "^20141126T160000Z${TAB}20141126T164500Z" "$FBFILE" \
7.100 +&& echo "Success" \
7.101 +|| echo "Failed"
7.102 +
7.103 +# Try with a reasonable ACL.
7.104 +
7.105 +cat > "$ACL" <<EOF
7.106 +accept
7.107 +decline attendee simon.skunk@example.com
7.108 +EOF
7.109 +
7.110 +# Attempt to schedule an event.
7.111 +
7.112 +"$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-sauna-acl.txt" 2>> $ERROR
7.113 +
7.114 + grep -q "^20141126T160000Z${TAB}20141126T164500Z" "$FBSENDERFILE" \
7.115 +&& echo "Success" \
7.116 +|| echo "Failed"
7.117 +
7.118 +# Present the request to the resource.
7.119 +
7.120 + "$RESOURCE_SCRIPT" $ARGS < "$TEMPLATES/event-request-sauna-acl.txt" 2>> $ERROR \
7.121 +| tee out6r.tmp \
7.122 +| "$SHOWMAIL" \
7.123 +> out6.tmp
7.124 +
7.125 + grep -q 'METHOD:REPLY' out6.tmp \
7.126 +&& grep -q 'ATTENDEE.*;PARTSTAT=DECLINED' out6.tmp \
7.127 +&& echo "Success" \
7.128 +|| echo "Failed"
7.129 +
7.130 + ! [ -e "$FBFILE" ] \
7.131 +|| ! grep -q "^20141126T160000Z${TAB}20141126T164500Z" "$FBFILE" \
7.132 +&& echo "Success" \
7.133 +|| echo "Failed"
7.134 +
7.135 +# Modify the ACL, using the implicit preference setting to hold the list.
7.136 +
7.137 +cat > "$PREFS/$USER/scheduling_function" <<EOF
7.138 +schedule_in_freebusy
7.139 +access_control_list
7.140 +EOF
7.141 +
7.142 +cat > "$PREFS/$USER/acl" <<EOF
7.143 +accept
7.144 +decline attendee simon.skunk@example.com
7.145 +accept organiser mailto:paul.boddie@example.com
7.146 +EOF
7.147 +
7.148 +# Present the request to the resource.
7.149 +
7.150 + "$RESOURCE_SCRIPT" $ARGS < "$TEMPLATES/event-request-sauna-acl.txt" 2>> $ERROR \
7.151 +| tee out7r.tmp \
7.152 +| "$SHOWMAIL" \
7.153 +> out7.tmp
7.154 +
7.155 + grep -q 'METHOD:REPLY' out7.tmp \
7.156 +&& grep -q 'ATTENDEE.*;PARTSTAT=ACCEPTED' out7.tmp \
7.157 +&& echo "Success" \
7.158 +|| echo "Failed"
7.159 +
7.160 + ! [ -e "$FBFILE" ] \
7.161 +|| grep -q "^20141126T160000Z${TAB}20141126T164500Z" "$FBFILE" \
7.162 +&& echo "Success" \
7.163 +|| echo "Failed"
7.164 +
7.165 +rm "$ACL"