1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - PGP authentication using incoming MIME request bodies 4 5 @copyright: 2001-2003 Juergen Hermann <jh@web.de> 6 2003-2006 MoinMoin:ThomasWaldmann 7 2007 MoinMoin:JohannesBerg 8 2013 Paul Boddie <paul@boddie.org.uk> 9 @license: GNU GPL, see COPYING for details. 10 """ 11 12 from MoinMoin import log 13 logging = log.getLogger(__name__) 14 15 from MoinMoin.user import User 16 from MoinMoin.auth import BaseAuth 17 from MoinSupport import getHeader, getWikiDict 18 from MoinMessage import GPG, is_signed, is_encrypted, \ 19 MoinMessageDecodingError, MoinMessageError 20 from MoinMessageSupport import get_homedir 21 from email.parser import Parser 22 23 try: 24 from cStringIO import StringIO 25 except ImportError: 26 from StringIO import StringIO 27 28 class PGPAuth(BaseAuth): 29 30 """ 31 Authenticate a user by inspecting the signature on a signed MIME message 32 sent in the body of a request. 33 """ 34 35 name = "pgp" 36 37 def __init__(self, autocreate=False): 38 39 "Initialise the authenticator using the given 'autocreate' setting." 40 41 self.autocreate = autocreate 42 BaseAuth.__init__(self) 43 44 def request(self, request, user_obj, **kw): 45 46 """ 47 Evaluate the 'request' given an existing 'user_obj', returning a tuple 48 of the form (user, continue) where the user is either the supplied 49 'user_obj' or a new user object, and where an indication of whether to 50 continue the authentication process is also given. 51 """ 52 53 _ = request.getText 54 user = None 55 56 # Always revalidate the identity if the provided user claims to be 57 # identified using this method. 58 59 if user_obj and user_obj.auth_method == self.name: 60 user_obj = None 61 62 # If something else authenticated before us, accept this. 63 64 if user_obj: 65 return user_obj, True 66 67 logging.debug("request: %r" % request) 68 69 # Read the request body and perform signature validation. 70 71 # Need to obtain the message text and yet still make it available 72 # normally in the request. 73 74 content_length = getHeader(request, "Content-Length", "HTTP") 75 if content_length: 76 content_length = int(content_length) 77 message_body = message_text = request.read(content_length) 78 79 # Obtain a message from the text. 80 81 message = Parser().parse(StringIO(message_text)) 82 83 try: 84 homedir = get_homedir(request) 85 gpg = GPG(homedir) 86 87 try: 88 # Encrypted messages must be decrypted first. 89 90 if is_encrypted(message): 91 message_text = gpg.decryptMessage(message) 92 message = Parser().parse(StringIO(message_text)) 93 94 # Signed messages can be handled directly. 95 96 if not is_signed(message): 97 return user_obj, True 98 99 fingerprint, identity, content = gpg.verifyMessage(message) 100 101 except MoinMessageDecodingError: 102 logging.info("Incoming message was improperly encoded.") 103 return user_obj, True 104 105 except MoinMessageError: 106 logging.warning("Incoming message was not verifiable.") 107 return user_obj, True 108 109 # Restore the request body using an extension to the request and 110 # replaced methods. 111 112 finally: 113 extension = RequestExtension(request, StringIO(message_body)) 114 request.read = extension.read 115 request._setup_args_from_cgi_form = extension._setup_args_from_cgi_form 116 117 # Evaluate the result of the verification process. 118 119 if fingerprint: 120 gpg_users = getWikiDict( 121 getattr(request.cfg, "moinmessage_gpg_users_page", "MoinMessageUserDict"), 122 request, 123 superuser=True # disable user test because we have no user yet 124 ) 125 126 # With a user mapping and a fingerprint corresponding to a known 127 # user, temporarily switch user in order to make the edit. 128 129 if gpg_users and gpg_users.has_key(fingerprint): 130 username = gpg_users[fingerprint] 131 user = User(request, auth_method="pgp", auth_username=username) 132 logging.debug("username: %r" % username) 133 134 logging.debug("user: %r" % user) 135 136 # Handle user autocreation. 137 138 if user and self.autocreate: 139 logging.debug("autocreating user") 140 user.create_or_update() 141 142 # Either return the identified user, if valid. 143 144 if user and user.valid: 145 logging.debug("returning valid user %r" % user) 146 return user, True # True to get other methods called, too 147 148 # Or return the supplied user object. 149 150 else: 151 logging.debug("returning %r" % user_obj) 152 return user_obj, True 153 154 # Replacement request methods (based on the request_cgi.Request class). 155 156 class RequestExtension: 157 158 "An extension to the request providing replacement methods." 159 160 def __init__(self, request, body): 161 self.request = request 162 self.body = body 163 164 def _setup_args_from_cgi_form(self): 165 """ Override to create cgi form """ 166 form = cgi.FieldStorage(fp=self.body, environ=self.request.env, keep_blank_values=1) 167 return RequestBase._setup_args_from_cgi_form(self.request, form) 168 169 def read(self, n): 170 """ Read from input stream. """ 171 if n is None: 172 logging.warning("calling request.read(None) might block") 173 return self.body.read() 174 else: 175 return self.body.read(n) 176 177 # vim: tabstop=4 expandtab shiftwidth=4