[3] | 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 | """ |
---|
| 6 | Authentication via HTML Form |
---|
| 7 | |
---|
| 8 | This is a very simple HTML form login screen that asks for the username |
---|
| 9 | and password. This middleware component requires that an authorization |
---|
| 10 | function taking the name and passsword and that it be placed in your |
---|
| 11 | application stack. This class does not include any session management |
---|
| 12 | code or way to save the user's authorization; however, it is easy enough |
---|
| 13 | to 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))) |
---|
| 23 | serving on... |
---|
| 24 | |
---|
| 25 | """ |
---|
| 26 | from paste.request import construct_url, parse_formvars |
---|
| 27 | |
---|
| 28 | TEMPLATE = """\ |
---|
| 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 | |
---|
| 47 | class 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 | |
---|
| 120 | middleware = AuthFormHandler |
---|
| 121 | |
---|
| 122 | __all__ = ['AuthFormHandler'] |
---|
| 123 | |
---|
| 124 | def 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 | |
---|
| 147 | if "__main__" == __name__: |
---|
| 148 | import doctest |
---|
| 149 | doctest.testmod(optionflags=doctest.ELLIPSIS) |
---|