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 18 from MoinMessage import GPG, is_signed, is_encrypted, \ 19 MoinMessageDecodingError, MoinMessageError 20 from MoinMessageSupport import get_homedir, get_username_for_fingerprint 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 username = get_username_for_fingerprint(request, fingerprint) 121 122 # With a known username, temporarily switch user in order to make 123 # the edit. 124 125 if username: 126 user = User(request, auth_method="pgp", auth_username=username) 127 logging.debug("username: %r" % username) 128 129 logging.debug("user: %r" % user) 130 131 # Handle user autocreation. 132 133 if user and self.autocreate: 134 logging.debug("autocreating user") 135 user.create_or_update() 136 137 # Either return the identified user, if valid. 138 139 if user and user.valid: 140 logging.debug("returning valid user %r" % user) 141 return user, True # True to get other methods called, too 142 143 # Or return the supplied user object. 144 145 else: 146 logging.debug("returning %r" % user_obj) 147 return user_obj, True 148 149 # Replacement request methods (based on the request_cgi.Request class). 150 151 class RequestExtension: 152 153 "An extension to the request providing replacement methods." 154 155 def __init__(self, request, body): 156 self.request = request 157 self.body = body 158 159 def _setup_args_from_cgi_form(self): 160 """ Override to create cgi form """ 161 form = cgi.FieldStorage(fp=self.body, environ=self.request.env, keep_blank_values=1) 162 return RequestBase._setup_args_from_cgi_form(self.request, form) 163 164 def read(self, n): 165 """ Read from input stream. """ 166 if n is None: 167 logging.warning("calling request.read(None) might block") 168 return self.body.read() 169 else: 170 return self.body.read(n) 171 172 # vim: tabstop=4 expandtab shiftwidth=4