1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/MoinMoin/auth/pgp.py Fri Jun 07 18:41:04 2013 +0200
1.3 @@ -0,0 +1,177 @@
1.4 +# -*- coding: iso-8859-1 -*-
1.5 +"""
1.6 + MoinMoin - PGP authentication using incoming MIME request bodies
1.7 +
1.8 + @copyright: 2001-2003 Juergen Hermann <jh@web.de>,
1.9 + 2003-2006 MoinMoin:ThomasWaldmann
1.10 + 2007 MoinMoin:JohannesBerg
1.11 + 2013 Paul Boddie <paul@boddie.org.uk>
1.12 + @license: GNU GPL, see COPYING for details.
1.13 +"""
1.14 +
1.15 +from MoinMoin import log
1.16 +logging = log.getLogger(__name__)
1.17 +
1.18 +from MoinMoin.user import User
1.19 +from MoinMoin.auth import BaseAuth
1.20 +from MoinSupport import getHeader, getWikiDict
1.21 +from MoinMessage import GPG, is_signed, is_encrypted, \
1.22 + MoinMessageDecodingError, MoinMessageError
1.23 +from MoinMessageSupport import get_homedir
1.24 +from email.parser import Parser
1.25 +
1.26 +try:
1.27 + from cStringIO import StringIO
1.28 +except ImportError:
1.29 + from StringIO import StringIO
1.30 +
1.31 +class PGPAuth(BaseAuth):
1.32 +
1.33 + """
1.34 + Authenticate a user by inspecting the signature on a signed MIME message
1.35 + sent in the body of a request.
1.36 + """
1.37 +
1.38 + name = "pgp"
1.39 +
1.40 + def __init__(self, autocreate=False):
1.41 +
1.42 + "Initialise the authenticator using the given 'autocreate' setting."
1.43 +
1.44 + self.autocreate = autocreate
1.45 + BaseAuth.__init__(self)
1.46 +
1.47 + def request(self, request, user_obj, **kw):
1.48 +
1.49 + """
1.50 + Evaluate the 'request' given an existing 'user_obj', returning a tuple
1.51 + of the form (user, continue) where the user is either the supplied
1.52 + 'user_obj' or a new user object, and where an indication of whether to
1.53 + continue the authentication process is also given.
1.54 + """
1.55 +
1.56 + _ = request.getText
1.57 + user = None
1.58 +
1.59 + # Always revalidate the identity if the provided user claims to be
1.60 + # identified using this method.
1.61 +
1.62 + if user_obj and user_obj.auth_method == self.name:
1.63 + user_obj = None
1.64 +
1.65 + # If something else authenticated before us, accept this.
1.66 +
1.67 + if user_obj:
1.68 + return user_obj, True
1.69 +
1.70 + logging.debug("request: %r" % request)
1.71 +
1.72 + # Read the request body and perform signature validation.
1.73 +
1.74 + # Need to obtain the message text and yet still make it available
1.75 + # normally in the request.
1.76 +
1.77 + content_length = getHeader(request, "Content-Length", "HTTP")
1.78 + if content_length:
1.79 + content_length = int(content_length)
1.80 + message_body = message_text = request.read(content_length)
1.81 +
1.82 + # Obtain a message from the text.
1.83 +
1.84 + message = Parser().parse(StringIO(message_text))
1.85 +
1.86 + try:
1.87 + homedir = get_homedir(request)
1.88 + gpg = GPG(homedir)
1.89 +
1.90 + try:
1.91 + # Encrypted messages must be decrypted first.
1.92 +
1.93 + if is_encrypted(message):
1.94 + message_text = gpg.decryptMessage(message)
1.95 + message = Parser().parse(StringIO(message_text))
1.96 +
1.97 + # Signed messages can be handled directly.
1.98 +
1.99 + if not is_signed(message):
1.100 + return user_obj, True
1.101 +
1.102 + fingerprint, identity, content = gpg.verifyMessage(message)
1.103 +
1.104 + except MoinMessageDecodingError:
1.105 + logging.info("Incoming message was improperly encoded.")
1.106 + return user_obj, True
1.107 +
1.108 + except MoinMessageError:
1.109 + logging.warning("Incoming message was not verifiable.")
1.110 + return user_obj, True
1.111 +
1.112 + # Restore the request body using an extension to the request and
1.113 + # replaced methods.
1.114 +
1.115 + finally:
1.116 + extension = RequestExtension(request, StringIO(message_body))
1.117 + request.read = extension.read
1.118 + request._setup_args_from_cgi_form = extension._setup_args_from_cgi_form
1.119 +
1.120 + # Evaluate the result of the verification process.
1.121 +
1.122 + if fingerprint:
1.123 + gpg_users = getWikiDict(
1.124 + getattr(request.cfg, "moinmessage_gpg_users_page", "MoinMessageUserDict"),
1.125 + request,
1.126 + superuser=True # disable user test because we have no user yet
1.127 + )
1.128 +
1.129 + # With a user mapping and a fingerprint corresponding to a known
1.130 + # user, temporarily switch user in order to make the edit.
1.131 +
1.132 + if gpg_users and gpg_users.has_key(fingerprint):
1.133 + username = gpg_users[fingerprint]
1.134 + user = User(request, auth_method="pgp", auth_username=username)
1.135 + logging.debug("username: %r" % username)
1.136 +
1.137 + logging.debug("user: %r" % user)
1.138 +
1.139 + # Handle user autocreation.
1.140 +
1.141 + if user and self.autocreate:
1.142 + logging.debug("autocreating user")
1.143 + user.create_or_update()
1.144 +
1.145 + # Either return the identified user, if valid.
1.146 +
1.147 + if user and user.valid:
1.148 + logging.debug("returning valid user %r" % user)
1.149 + return user, True # True to get other methods called, too
1.150 +
1.151 + # Or return the supplied user object.
1.152 +
1.153 + else:
1.154 + logging.debug("returning %r" % user_obj)
1.155 + return user_obj, True
1.156 +
1.157 +# Replacement request methods (based on the request_cgi.Request class).
1.158 +
1.159 +class RequestExtension:
1.160 +
1.161 + "An extension to the request providing replacement methods."
1.162 +
1.163 + def __init__(self, request, body):
1.164 + self.request = request
1.165 + self.body = body
1.166 +
1.167 + def _setup_args_from_cgi_form(self):
1.168 + """ Override to create cgi form """
1.169 + form = cgi.FieldStorage(fp=self.body, environ=self.request.env, keep_blank_values=1)
1.170 + return RequestBase._setup_args_from_cgi_form(self.request, form)
1.171 +
1.172 + def read(self, n):
1.173 + """ Read from input stream. """
1.174 + if n is None:
1.175 + logging.warning("calling request.read(None) might block")
1.176 + return self.body.read()
1.177 + else:
1.178 + return self.body.read(n)
1.179 +
1.180 +# vim: tabstop=4 expandtab shiftwidth=4