0001"""Security related decorators"""
0002import logging
0003
0004from decorator import decorator
0005from webhelpers.rails import secure_form_tag
0006
0007import pylons
0008from pylons.controllers.util import abort, redirect_to
0009
0010__all__ = ['authenticate_form', 'https']
0011
0012log = logging.getLogger(__name__)
0013
0014csrf_detected_message = (
0015 "Cross-site request forgery detected, request denied. See "
0016 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for more "
0017 "information.")
0018
0019def authenticated_form(params):
0020 submitted_token = params.get(secure_form_tag.token_key)
0021 return submitted_token is not None and submitted_token == secure_form_tag.authentication_token()
0023
0024def authenticate_form(func, *args, **kwargs):
0025 """Decorator for authenticating a form according to an authorization token
0026 stored in the client's session. For prevention of certain Cross-site
0027 request forgery (CSRF) attacks (See
0028 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
0029 information).
0030
0031 For use with the ``webhelpers.rails.secure_form_tag`` helper functions.
0032 """
0033 request = pylons.request._current_obj()
0034 if authenticated_form(request.POST):
0035 del request.POST[secure_form_tag.token_key]
0036 return func(*args, **kwargs)
0037 else:
0038 log.debug('Cross-site request forgery detected, request denied: %r' %
0039 request)
0040 abort(403, detail=csrf_detected_message)
0041authenticate_form = decorator(authenticate_form)
0042
0043def https(*redirect_args, **redirect_kwargs):
0044 """Decorator to redirect to the SSL version of a page if not currently
0045 using HTTPS. Takes as arguments the parameters to pass to redirect_to.
0046 (Specify no arguments necessary to redirect the current page). Apply this
0047 decorator to controller methods (actions).
0048
0049 Non-https POST requests are aborted (405 response code) by this decorator.
0050
0051 Example:
0052
0053 .. code-block: Python
0054
0055 @https('/pylons') # redirect to HTTPS /pylons
0056 def index(self):
0057 #...
0058
0059 # redirect to HTTPS /auth/login
0060 @https(controller='auth', action='login')
0061 def login(self):
0062 #...
0063
0064 @https() # redirect to HTTPS version of myself
0065 def get(self):
0066 #...
0067 """
0068 def wrapper(func, *args, **kwargs):
0069 """Decorator Wrapper function"""
0070 request = pylons.request._current_obj()
0071 if request.scheme.lower() == 'https':
0072 return func(*args, **kwargs)
0073 else:
0074 if request.method.upper() != 'POST':
0075 redirect_kwargs['protocol'] = 'https' # ensure https
0076 log.debug('Redirecting non-https request: %s to redirect '
0077 'args: *%r, **%r', request.path_info, redirect_args,
0078 redirect_kwargs)
0079 redirect_to(*redirect_args, **redirect_kwargs)
0080 else:
0081 abort(405, headers=[('Allow', 'GET')]) # don't allow POSTs.
0082 return decorator(wrapper)