1.1 --- a/actions/PostMessage.py Thu May 30 21:41:23 2013 +0200
1.2 +++ b/actions/PostMessage.py Sun Jun 02 01:34:19 2013 +0200
1.3 @@ -8,201 +8,18 @@
1.4
1.5 from MoinMoin.Page import Page
1.6 from MoinMoin.PageEditor import PageEditor
1.7 -from MoinMoin.log import getLogger
1.8 -from MoinMoin.user import User
1.9 from MoinMoin import wikiutil
1.10 -from MoinSupport import ItemStore, getHeader, getMetadata, getWikiDict, writeHeaders
1.11 -from MoinMessage import GPG, Message, MoinMessageError, is_collection
1.12 -from email.parser import Parser
1.13 +from MoinSupport import getMetadata, writeHeaders
1.14 +from MoinMessage import Message, is_collection, to_replace, to_store
1.15 +from MoinMessageSupport import MoinMessageAction
1.16 import time
1.17
1.18 -try:
1.19 - from cStringIO import StringIO
1.20 -except ImportError:
1.21 - from StringIO import StringIO
1.22 -
1.23 Dependencies = ['pages']
1.24
1.25 -class PostMessage:
1.26 +class PostMessage(MoinMessageAction):
1.27
1.28 "A posted message handler."
1.29
1.30 - def __init__(self, pagename, request):
1.31 -
1.32 - """
1.33 - On the page with the given 'pagename', use the given 'request' when
1.34 - reading posted messages, modifying the Wiki.
1.35 - """
1.36 -
1.37 - self.pagename = pagename
1.38 - self.request = request
1.39 - self.page = Page(request, pagename)
1.40 - self.store = ItemStore(self.page, "messages", "message-locks")
1.41 -
1.42 - def do_action(self):
1.43 - request = self.request
1.44 - content_length = getHeader(request, "Content-Length", "HTTP")
1.45 - if content_length:
1.46 - content_length = int(content_length)
1.47 -
1.48 - self.handle_message_text(request.read(content_length))
1.49 -
1.50 - def handle_message_text(self, message_text):
1.51 -
1.52 - "Handle the given 'message_text'."
1.53 -
1.54 - message = Parser().parse(StringIO(message_text))
1.55 - self.handle_message(message)
1.56 -
1.57 - def handle_message(self, message):
1.58 -
1.59 - "Handle the given 'message'."
1.60 -
1.61 - request = self.request
1.62 - mimetype = message.get_content_type()
1.63 - encoding = message.get_content_charset()
1.64 -
1.65 - # Detect PGP/GPG-encoded payloads.
1.66 - # See: http://tools.ietf.org/html/rfc3156
1.67 -
1.68 - if mimetype == "multipart/signed" and \
1.69 - message.get_param("protocol") == "application/pgp-signature":
1.70 -
1.71 - self.handle_signed_message(message)
1.72 -
1.73 - elif mimetype == "multipart/encrypted" and \
1.74 - message.get_param("protocol") == "application/pgp-encrypted":
1.75 -
1.76 - self.handle_encrypted_message(message)
1.77 -
1.78 - # Reject unsigned payloads.
1.79 -
1.80 - else:
1.81 - writeHeaders(request, "text/plain", getMetadata(self.page), "415 Unsupported Media Type")
1.82 - request.write("Only PGP/GPG-signed payloads are supported.")
1.83 -
1.84 - def handle_encrypted_message(self, message):
1.85 -
1.86 - "Handle the given encrypted 'message'."
1.87 -
1.88 - request = self.request
1.89 -
1.90 - try:
1.91 - declaration, content = message.get_payload()
1.92 - except ValueError:
1.93 - writeHeaders(request, "text/plain", getMetadata(self.page), "415 Unsupported Media Type")
1.94 - request.write("There must be a declaration and a content part for encrypted uploads.")
1.95 - return
1.96 -
1.97 - # Verify the message format.
1.98 -
1.99 - if content.get_content_type() != "application/octet-stream":
1.100 - writeHeaders(request, "text/plain", getMetadata(self.page), "415 Unsupported Media Type")
1.101 - request.write("Encrypted data must be provided as application/octet-stream.")
1.102 - return
1.103 -
1.104 - homedir = self.get_homedir()
1.105 - if not homedir:
1.106 - return
1.107 -
1.108 - gpg = GPG(homedir)
1.109 -
1.110 - # Get the decrypted message text.
1.111 -
1.112 - try:
1.113 - text = gpg.decryptMessage(content.get_payload())
1.114 -
1.115 - # Log non-fatal errors.
1.116 -
1.117 - if gpg.errors:
1.118 - getLogger(__name__).warning(gpg.errors)
1.119 -
1.120 - # Handle the embedded message.
1.121 -
1.122 - self.handle_message_text(text)
1.123 -
1.124 - # Otherwise, reject the unverified message.
1.125 -
1.126 - except MoinMessageError:
1.127 - writeHeaders(request, "text/plain", getMetadata(self.page), "403 Forbidden")
1.128 - request.write("The message could not be decrypted.")
1.129 -
1.130 - def handle_signed_message(self, message):
1.131 -
1.132 - "Handle the given signed 'message'."
1.133 -
1.134 - request = self.request
1.135 -
1.136 - # NOTE: RFC 3156 states that signed messages should employ a detached
1.137 - # NOTE: signature but then shows "BEGIN PGP MESSAGE" for signatures
1.138 - # NOTE: instead of "BEGIN PGP SIGNATURE".
1.139 - # NOTE: The "micalg" parameter is currently not supported.
1.140 -
1.141 - try:
1.142 - content, signature = message.get_payload()
1.143 - except ValueError:
1.144 - writeHeaders(request, "text/plain", getMetadata(self.page), "415 Unsupported Media Type")
1.145 - request.write("There must be a content part and a signature for signed uploads.")
1.146 - return
1.147 -
1.148 - # Verify the message format.
1.149 -
1.150 - if signature.get_content_type() != "application/pgp-signature":
1.151 - writeHeaders(request, "text/plain", getMetadata(self.page), "415 Unsupported Media Type")
1.152 - request.write("Signature data must be provided in the second part as application/pgp-signature.")
1.153 - return
1.154 -
1.155 - homedir = self.get_homedir()
1.156 - if not homedir:
1.157 - return
1.158 -
1.159 - gpg = GPG(homedir)
1.160 -
1.161 - # Verify the message.
1.162 -
1.163 - try:
1.164 - fingerprint, identity = gpg.verifyMessage(signature.get_payload(), content.as_string())
1.165 -
1.166 - # Map the fingerprint to a Wiki user.
1.167 -
1.168 - old_user = None
1.169 - request = self.request
1.170 -
1.171 - try:
1.172 - if fingerprint:
1.173 - gpg_users = getWikiDict(
1.174 - getattr(request.cfg, "moinmessage_gpg_users_page", "MoinMessageUserDict"),
1.175 - request
1.176 - )
1.177 -
1.178 - # With a user mapping and a fingerprint corresponding to a known
1.179 - # user, temporarily switch user in order to make the edit.
1.180 -
1.181 - if gpg_users and gpg_users.has_key(fingerprint):
1.182 - old_user = request.user
1.183 - request.user = User(request, auth_method="gpg", auth_username=gpg_users[fingerprint])
1.184 -
1.185 - # Log non-fatal errors.
1.186 -
1.187 - if gpg.errors:
1.188 - getLogger(__name__).warning(gpg.errors)
1.189 -
1.190 - # Handle the embedded message.
1.191 -
1.192 - self.handle_message_content(content)
1.193 -
1.194 - # Restore any user identity.
1.195 -
1.196 - finally:
1.197 - if old_user:
1.198 - request.user = old_user
1.199 -
1.200 - # Otherwise, reject the unverified message.
1.201 -
1.202 - except MoinMessageError:
1.203 - writeHeaders(request, "text/plain", getMetadata(self.page), "403 Forbidden")
1.204 - request.write("The message could not be verified.")
1.205 -
1.206 def handle_message_content(self, content):
1.207
1.208 "Handle the given message 'content'."
1.209 @@ -295,22 +112,6 @@
1.210
1.211 self.page = Page(self.request, self.pagename)
1.212
1.213 - def get_homedir(self):
1.214 -
1.215 - "Locate the GPG home directory."
1.216 -
1.217 - homedir = getattr(self.request.cfg, "moinmessage_gpg_homedir")
1.218 - if not homedir:
1.219 - writeHeaders(request, "text/plain", getMetadata(self.page), "415 Unsupported Media Type")
1.220 - request.write("Encoded data cannot currently be understood. Please notify the site administrator.")
1.221 - return homedir
1.222 -
1.223 -def to_replace(message):
1.224 - return message.get("Update-Action") == "replace"
1.225 -
1.226 -def to_store(message):
1.227 - return message.get("Update-Action") == "store"
1.228 -
1.229 # Action function.
1.230
1.231 def execute(pagename, request):