1# (c) 2005 Clark C. Evans 2# This module is part of the Python Paste Project and is released under 3# the MIT License: http://www.opensource.org/licenses/mit-license.php 4# This code was written with funding by http://prometheusresearch.com 5""" 6Authentication via HTML Form 7 8This is a very simple HTML form login screen that asks for the username 9and password. This middleware component requires that an authorization 10function taking the name and passsword and that it be placed in your 11application stack. This class does not include any session management 12code or way to save the user's authorization; however, it is easy enough 13to put ``paste.auth.cookie`` in your application stack. 14 15>>> from paste.wsgilib import dump_environ 16>>> from paste.httpserver import serve 17>>> from paste.auth.cookie import AuthCookieHandler 18>>> from paste.auth.form import AuthFormHandler 19>>> def authfunc(environ, username, password): 20... return username == password 21>>> serve(AuthCookieHandler( 22... AuthFormHandler(dump_environ, authfunc))) 23serving on... 24 25""" 26from paste.request import construct_url, parse_formvars 27 28TEMPLATE = """\ 29<html> 30 <head><title>Please Login!</title></head> 31 <body> 32 <h1>Please Login</h1> 33 <form action="%s" method="post"> 34 <dl> 35 <dt>Username:</dt> 36 <dd><input type="text" name="username"></dd> 37 <dt>Password:</dt> 38 <dd><input type="password" name="password"></dd> 39 </dl> 40 <input type="submit" name="authform" /> 41 <hr /> 42 </form> 43 </body> 44</html> 45""" 46 47class AuthFormHandler(object): 48 """ 49 HTML-based login middleware 50 51 This causes a HTML form to be returned if ``REMOTE_USER`` is 52 not found in the ``environ``. If the form is returned, the 53 ``username`` and ``password`` combination are given to a 54 user-supplied authentication function, ``authfunc``. If this 55 is successful, then application processing continues. 56 57 Parameters: 58 59 ``application`` 60 61 The application object is called only upon successful 62 authentication, and can assume ``environ['REMOTE_USER']`` 63 is set. If the ``REMOTE_USER`` is already set, this 64 middleware is simply pass-through. 65 66 ``authfunc`` 67 68 This is a mandatory user-defined function which takes a 69 ``environ``, ``username`` and ``password`` for its first 70 three arguments. It should return ``True`` if the user is 71 authenticated. 72 73 ``template`` 74 75 This is an optional (a default is provided) HTML 76 fragment that takes exactly one ``%s`` substution 77 argument; which *must* be used for the form's ``action`` 78 to ensure that this middleware component does not alter 79 the current path. The HTML form must use ``POST`` and 80 have two input names: ``username`` and ``password``. 81 82 Since the authentication form is submitted (via ``POST``) 83 neither the ``PATH_INFO`` nor the ``QUERY_STRING`` are accessed, 84 and hence the current path remains _unaltered_ through the 85 entire authentication process. If authentication succeeds, the 86 ``REQUEST_METHOD`` is converted from a ``POST`` to a ``GET``, 87 so that a redirect is unnecessary (unlike most form auth 88 implementations) 89 """ 90 91 def __init__(self, application, authfunc, template=None): 92 self.application = application 93 self.authfunc = authfunc 94 self.template = template or TEMPLATE 95 96 def __call__(self, environ, start_response): 97 username = environ.get('REMOTE_USER','') 98 if username: 99 return self.application(environ, start_response) 100 101 if 'POST' == environ['REQUEST_METHOD']: 102 formvars = parse_formvars(environ, include_get_vars=False) 103 username = formvars.get('username') 104 password = formvars.get('password') 105 if username and password: 106 if self.authfunc(environ, username, password): 107 environ['AUTH_TYPE'] = 'form' 108 environ['REMOTE_USER'] = username 109 environ['REQUEST_METHOD'] = 'GET' 110 environ['CONTENT_LENGTH'] = '' 111 environ['CONTENT_TYPE'] = '' 112 del environ['paste.parsed_formvars'] 113 return self.application(environ, start_response) 114 115 content = self.template % construct_url(environ) 116 start_response("200 OK", [('Content-Type', 'text/html'), 117 ('Content-Length', str(len(content)))]) 118 return [content] 119 120middleware = AuthFormHandler 121 122__all__ = ['AuthFormHandler'] 123 124def make_form(app, global_conf, realm, authfunc, **kw): 125 """ 126 Grant access via form authentication 127 128 Config looks like this:: 129 130 [filter:grant] 131 use = egg:Paste#auth_form 132 realm=myrealm 133 authfunc=somepackage.somemodule:somefunction 134 135 """ 136 from paste.util.import_string import eval_import 137 import types 138 authfunc = eval_import(authfunc) 139 assert isinstance(authfunc, types.FunctionType), "authfunc must resolve to a function" 140 template = kw.get('template') 141 if template is not None: 142 template = eval_import(template) 143 assert isinstance(template, str), "template must resolve to a string" 144 145 return AuthFormHandler(app, authfunc, template) 146 147if "__main__" == __name__: 148 import doctest 149 doctest.testmod(optionflags=doctest.ELLIPSIS) 150