1
"""Security related decorators"""
5
from decorator import decorator
7
import webhelpers.html.secure_form as secure_form
9
import webhelpers.pylonslib.secure_form as secure_form
11
from pylons.controllers.util import abort, redirect
12
from pylons.decorators.util import get_pylons
14
__all__ = ['authenticate_form', 'https']
16
log = logging.getLogger(__name__)
18
csrf_detected_message = (
19
"Cross-site request forgery detected, request denied. See "
20
"http://en.wikipedia.org/wiki/Cross-site_request_forgery for more "
23
def authenticated_form(params):
24
submitted_token = params.get(secure_form.token_key)
25
return submitted_token is not None and \
26
submitted_token == secure_form.authentication_token()
30
def authenticate_form(func, *args, **kwargs):
31
"""Decorator for authenticating a form
33
This decorator uses an authorization token stored in the client's
34
session for prevention of certain Cross-site request forgery (CSRF)
36
http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
39
For use with the ``webhelpers.html.secure_form`` helper functions.
42
request = get_pylons(args).request
43
if authenticated_form(request.POST):
44
del request.POST[secure_form.token_key]
45
return func(*args, **kwargs)
47
log.warn('Cross-site request forgery detected, request denied: %r '
48
'REMOTE_ADDR: %s' % (request, request.remote_addr))
49
abort(403, detail=csrf_detected_message)
52
def https(url_or_callable=None):
53
"""Decorator to redirect to the SSL version of a page if not
54
currently using HTTPS. Apply this decorator to controller methods
57
Takes a url argument: either a string url, or a callable returning a
58
string url. The callable will be called with no arguments when the
59
decorated method is called. The url's scheme will be rewritten to
62
Non-HTTPS POST requests are aborted (405 response code) by this
67
.. code-block:: python
69
# redirect to HTTPS /pylons
74
# redirect to HTTPS /auth/login, delaying the url() call until
75
# later (as the url object may not be functional when the
76
# decorator/method are defined)
77
@https(lambda: url(controller='auth', action='login'))
81
# redirect to HTTPS version of myself
87
def wrapper(func, *args, **kwargs):
88
"""Decorator Wrapper function"""
89
request = get_pylons(args).request
90
if request.scheme.lower() == 'https':
91
return func(*args, **kwargs)
92
if request.method.upper() == 'POST':
93
# don't allow POSTs (raises an exception)
94
abort(405, headers=[('Allow', 'GET')])
96
if url_or_callable is None:
98
elif callable(url_or_callable):
99
url = url_or_callable()
101
url = url_or_callable
102
# Ensure an https scheme, which also needs a host
103
parts = urlparse.urlparse(url)
104
url = urlparse.urlunparse(('https', parts[1] or request.host) +
107
log.debug('Redirecting non-https request: %s to: %s',
108
request.path_info, url)
110
return decorator(wrapper)