# HG changeset patch # User Paul Boddie # Date 1366377340 -7200 # Node ID 94922f130a8ec255a977ace280dbf2588f45662c # Parent e09466179fcba618df5461f21805316d935cd74e Introduced date-based message validation, comparing message dates to page and message store last-modified dates. Changed item store initialisation to use string-based paths (following changes to MoinSupport). diff -r e09466179fcb -r 94922f130a8e MoinMessage.py --- a/MoinMessage.py Sun Mar 10 01:19:48 2013 +0100 +++ b/MoinMessage.py Fri Apr 19 15:15:40 2013 +0200 @@ -11,6 +11,7 @@ from email.mime.multipart import MIMEMultipart from email.mime.application import MIMEApplication from email.mime.base import MIMEBase +from email.utils import formatdate, parsedate from subprocess import Popen, PIPE from tempfile import mkstemp from urlparse import urlsplit @@ -25,10 +26,20 @@ "An update message." def __init__(self, text=None): + self.date = None self.updates = [] if text: self.parse_text(text) + def init_date(self, message): + + "Obtain the date of the given 'message'." + + if message.has_key("Date"): + self.date = parsedate(message["Date"]) + else: + self.date = None + def parse_text(self, text): "Parse the given 'text' as a message." @@ -39,6 +50,8 @@ "Handle the given 'message', recording the separate updates." + self.init_date(message) + # The message either consists of a collection of updates. if message.is_multipart() and is_collection(message): @@ -79,9 +92,15 @@ part.attach(alternative) return part - def get_payload(self): + def get_payload(self, timestamped=True): - "Get the multipart payload for the message." + """ + Get the multipart payload for the message. If the 'timestamped' + parameter is omitted or set to a true value, the payload will be given a + date header set to the current date and time that can be used to assess + the validity of a message and to determine whether it has already been + received by a recipient. + """ if len(self.updates) == 1: message = self.updates[0] @@ -91,6 +110,10 @@ for update in self.updates: message.attach(update) + if timestamped: + timestamp(message) + self.init_date(message) + return message class Mailbox: @@ -289,6 +312,20 @@ # Communications functions. +def timestamp(message): + + """ + Timestamp the given 'message' so that its validity can be assessed by the + recipient. + """ + + datestr = formatdate() + + if not message.has_key("Date"): + message.add_header("Date", datestr) + else: + message["Date"] = datestr + def sendMessage(message, url): "Send 'message' to the given 'url." diff -r e09466179fcb -r 94922f130a8e README.txt --- a/README.txt Sun Mar 10 01:19:48 2013 +0100 +++ b/README.txt Fri Apr 19 15:15:40 2013 +0200 @@ -73,6 +73,10 @@ moinmessage_gpg_recipients_page (optional, default is MoinMessageRecipientsDict) This provides a mapping from recipients to remote URLs and key fingerprints. + moinmessage_reject_messages_without_dates (optional, default is True) + This causes messages sent to a Wiki using the PostMessage action to be + rejected if date information is missing. + Fingerprints and Keys --------------------- diff -r e09466179fcb -r 94922f130a8e actions/PostMessage.py --- a/actions/PostMessage.py Sun Mar 10 01:19:48 2013 +0100 +++ b/actions/PostMessage.py Fri Apr 19 15:15:40 2013 +0200 @@ -10,9 +10,11 @@ from MoinMoin.PageEditor import PageEditor from MoinMoin.log import getLogger from MoinMoin.user import User -from MoinSupport import ItemStore +from MoinMoin import wikiutil +from MoinSupport import ItemStore, getHeader, getMetadata, getWikiDict, writeHeaders from MoinMessage import GPG, Message, MoinMessageError from email.parser import Parser +import time try: from cStringIO import StringIO @@ -35,6 +37,7 @@ self.pagename = pagename self.request = request self.page = Page(request, pagename) + self.store = ItemStore(self.page, "messages", "message-locks") def do_action(self): request = self.request @@ -211,6 +214,29 @@ message = Message() message.handle_message(content) + # Test any date against the page or message store. + + if message.date: + store_date = time.gmtime(self.store.mtime()) + page_date = time.gmtime(wikiutil.version2timestamp(self.page.mtime_usecs())) + last_date = max(store_date, page_date) + + # Reject messages older than the page date. + + if message.date < last_date: + writeHeaders(request, "text/plain", getMetadata(self.page), "403 Forbidden") + request.write("The message is too old: %s versus %s." % (message.date, last_date)) + return + + # Reject messages without dates if so configured. + + elif getattr(request.cfg, "moinmessage_reject_messages_without_dates", True): + writeHeaders(request, "text/plain", getMetadata(self.page), "403 Forbidden") + request.write("The message has no date information.") + return + + # Handle each update. + for update in message.updates: # Handle a single part. @@ -240,8 +266,7 @@ # Update a message store for the page. if to_store(update): - store = ItemStore(self.page, "messages", "message-locks") - store.append(update.as_string()) + self.store.append(update.as_string()) # Update the page. diff -r e09466179fcb -r 94922f130a8e macros/ShowMessages.py --- a/macros/ShowMessages.py Sun Mar 10 01:19:48 2013 +0100 +++ b/macros/ShowMessages.py Fri Apr 19 15:15:40 2013 +0200 @@ -44,7 +44,7 @@ # Show the messages. # NOTE: Support additional access control. - store = ItemStore(page, ("messages",), ("message-locks",)) + store = ItemStore(page, "messages", "message-locks") output = [] output.append(fmt.div(on=1, css_class="showmessages-messages"))