1.1 --- a/MoinMessageSupport.py Tue Jun 04 14:59:47 2013 +0200
1.2 +++ b/MoinMessageSupport.py Fri Jun 07 01:34:51 2013 +0200
1.3 @@ -11,7 +11,7 @@
1.4 from MoinMoin.user import User
1.5 from MoinMoin import wikiutil
1.6 from MoinSupport import ItemStore, getHeader, getMetadata, getWikiDict, writeHeaders
1.7 -from MoinMessage import GPG, Message, MoinMessageError
1.8 +from MoinMessage import GPG, Message, MoinMessageError, is_signed, is_encrypted
1.9 from email.parser import Parser
1.10 import time
1.11
1.12 @@ -57,26 +57,18 @@
1.13
1.14 "Handle the given 'message'."
1.15
1.16 - request = self.request
1.17 - mimetype = message.get_content_type()
1.18 - encoding = message.get_content_charset()
1.19 -
1.20 # Detect PGP/GPG-encoded payloads.
1.21 # See: http://tools.ietf.org/html/rfc3156
1.22
1.23 - if mimetype == "multipart/signed" and \
1.24 - message.get_param("protocol") == "application/pgp-signature":
1.25 -
1.26 + if is_signed(message):
1.27 self.handle_signed_message(message)
1.28 -
1.29 - elif mimetype == "multipart/encrypted" and \
1.30 - message.get_param("protocol") == "application/pgp-encrypted":
1.31 -
1.32 + elif is_encrypted(message):
1.33 self.handle_encrypted_message(message)
1.34
1.35 - # Reject unsigned payloads.
1.36 + # Reject unsigned and unencrypted payloads.
1.37
1.38 else:
1.39 + request = self.request
1.40 writeHeaders(request, "text/plain", getMetadata(self.page), "415 Unsupported Media Type")
1.41 request.write("Only PGP/GPG-signed payloads are supported.")
1.42
1.43 @@ -86,45 +78,44 @@
1.44
1.45 request = self.request
1.46
1.47 - try:
1.48 - declaration, content = message.get_payload()
1.49 - except ValueError:
1.50 - writeHeaders(request, "text/plain", getMetadata(self.page), "415 Unsupported Media Type")
1.51 - request.write("There must be a declaration and a content part for encrypted uploads.")
1.52 - return
1.53 -
1.54 - # Verify the message format.
1.55 -
1.56 - if content.get_content_type() != "application/octet-stream":
1.57 - writeHeaders(request, "text/plain", getMetadata(self.page), "415 Unsupported Media Type")
1.58 - request.write("Encrypted data must be provided as application/octet-stream.")
1.59 - return
1.60 -
1.61 homedir = self.get_homedir()
1.62 if not homedir:
1.63 return
1.64
1.65 gpg = GPG(homedir)
1.66
1.67 - # Get the decrypted message text.
1.68 + try:
1.69 + text = gpg.decryptMessage(message)
1.70
1.71 - try:
1.72 - text = gpg.decryptMessage(content.get_payload())
1.73 -
1.74 - # Log non-fatal errors.
1.75 + # Reject messages without a declaration.
1.76
1.77 - if gpg.errors:
1.78 - getLogger(__name__).warning(gpg.errors)
1.79 + except MoinMessageMissingPart:
1.80 + writeHeaders(request, "text/plain", getMetadata(self.page), "415 Unsupported Media Type")
1.81 + request.write("There must be a declaration and a content part for encrypted uploads.")
1.82 + return
1.83 +
1.84 + # Reject messages without appropriate content.
1.85
1.86 - # Handle the embedded message.
1.87 + except MoinMessageBadContent:
1.88 + writeHeaders(request, "text/plain", getMetadata(self.page), "415 Unsupported Media Type")
1.89 + request.write("Encrypted data must be provided as application/octet-stream.")
1.90 + return
1.91
1.92 - self.handle_message_text(text)
1.93 -
1.94 - # Otherwise, reject the unverified message.
1.95 + # Reject any unencryptable message.
1.96
1.97 except MoinMessageError:
1.98 writeHeaders(request, "text/plain", getMetadata(self.page), "403 Forbidden")
1.99 request.write("The message could not be decrypted.")
1.100 + return
1.101 +
1.102 + # Log non-fatal errors.
1.103 +
1.104 + if gpg.errors:
1.105 + getLogger(__name__).warning(gpg.errors)
1.106 +
1.107 + # Handle the embedded message which may itself be a signed message.
1.108 +
1.109 + self.handle_message_text(text)
1.110
1.111 def handle_signed_message(self, message):
1.112
1.113 @@ -132,75 +123,74 @@
1.114
1.115 request = self.request
1.116
1.117 + homedir = self.get_homedir()
1.118 + if not homedir:
1.119 + return
1.120 +
1.121 + gpg = GPG(homedir)
1.122 +
1.123 # NOTE: RFC 3156 states that signed messages should employ a detached
1.124 # NOTE: signature but then shows "BEGIN PGP MESSAGE" for signatures
1.125 # NOTE: instead of "BEGIN PGP SIGNATURE".
1.126 # NOTE: The "micalg" parameter is currently not supported.
1.127
1.128 try:
1.129 - content, signature = message.get_payload()
1.130 - except ValueError:
1.131 + fingerprint, identity, content = gpg.verifyMessage(message)
1.132 +
1.133 + # Reject messages without a declaration.
1.134 +
1.135 + except MoinMessageMissingPart:
1.136 writeHeaders(request, "text/plain", getMetadata(self.page), "415 Unsupported Media Type")
1.137 request.write("There must be a content part and a signature for signed uploads.")
1.138 return
1.139
1.140 - # Verify the message format.
1.141 + # Reject messages without appropriate content.
1.142
1.143 - if signature.get_content_type() != "application/pgp-signature":
1.144 + except MoinMessageBadContent:
1.145 writeHeaders(request, "text/plain", getMetadata(self.page), "415 Unsupported Media Type")
1.146 request.write("Signature data must be provided in the second part as application/pgp-signature.")
1.147 return
1.148
1.149 - homedir = self.get_homedir()
1.150 - if not homedir:
1.151 - return
1.152 -
1.153 - gpg = GPG(homedir)
1.154 -
1.155 - # Verify the message.
1.156 -
1.157 - try:
1.158 - fingerprint, identity = gpg.verifyMessage(signature.get_payload(), content.as_string())
1.159 -
1.160 - # Map the fingerprint to a Wiki user.
1.161 -
1.162 - old_user = None
1.163 - request = self.request
1.164 -
1.165 - try:
1.166 - if fingerprint:
1.167 - gpg_users = getWikiDict(
1.168 - getattr(request.cfg, "moinmessage_gpg_users_page", "MoinMessageUserDict"),
1.169 - request
1.170 - )
1.171 -
1.172 - # With a user mapping and a fingerprint corresponding to a known
1.173 - # user, temporarily switch user in order to make the edit.
1.174 -
1.175 - if gpg_users and gpg_users.has_key(fingerprint):
1.176 - old_user = request.user
1.177 - request.user = User(request, auth_method="gpg", auth_username=gpg_users[fingerprint])
1.178 -
1.179 - # Log non-fatal errors.
1.180 -
1.181 - if gpg.errors:
1.182 - getLogger(__name__).warning(gpg.errors)
1.183 -
1.184 - # Handle the embedded message.
1.185 -
1.186 - self.handle_message_content(content)
1.187 -
1.188 - # Restore any user identity.
1.189 -
1.190 - finally:
1.191 - if old_user:
1.192 - request.user = old_user
1.193 -
1.194 - # Otherwise, reject the unverified message.
1.195 + # Reject any unverified message.
1.196
1.197 except MoinMessageError:
1.198 writeHeaders(request, "text/plain", getMetadata(self.page), "403 Forbidden")
1.199 request.write("The message could not be verified.")
1.200 + return
1.201 +
1.202 + # Log non-fatal errors.
1.203 +
1.204 + if gpg.errors:
1.205 + getLogger(__name__).warning(gpg.errors)
1.206 +
1.207 + # Map the fingerprint to a Wiki user.
1.208 +
1.209 + old_user = None
1.210 + request = self.request
1.211 +
1.212 + try:
1.213 + if fingerprint:
1.214 + gpg_users = getWikiDict(
1.215 + getattr(request.cfg, "moinmessage_gpg_users_page", "MoinMessageUserDict"),
1.216 + request
1.217 + )
1.218 +
1.219 + # With a user mapping and a fingerprint corresponding to a known
1.220 + # user, temporarily switch user in order to make the edit.
1.221 +
1.222 + if gpg_users and gpg_users.has_key(fingerprint):
1.223 + old_user = request.user
1.224 + request.user = User(request, auth_method="gpg", auth_username=gpg_users[fingerprint])
1.225 +
1.226 + # Handle the embedded message.
1.227 +
1.228 + self.handle_message_content(content)
1.229 +
1.230 + # Restore any user identity.
1.231 +
1.232 + finally:
1.233 + if old_user:
1.234 + request.user = old_user
1.235
1.236 def handle_message_content(self, content):
1.237