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) |
---|