pylons.decorators.secure
Covered: 80 lines
Missed: 0 lines
Skipped 31 lines
Percent: 100 %

      1

    """Security related decorators"""

      2

    import logging

      3

    import urlparse

      4

      5

    from decorator import decorator

      6

    try:

      7

        import webhelpers.html.secure_form as secure_form

      8

    except ImportError:

      9

        import webhelpers.pylonslib.secure_form as secure_form

     10

     11

    from pylons.controllers.util import abort, redirect

     12

    from pylons.decorators.util import get_pylons

     13

     14

    __all__ = ['authenticate_form', 'https']

     15

     16

    log = logging.getLogger(__name__)

     17

     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 "

     21

        "information.")

     22

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

     27

     28

     29

    @decorator

     30

    def authenticate_form(func, *args, **kwargs):

     31

        """Decorator for authenticating a form

     32

     33

        This decorator uses an authorization token stored in the client's

     34

        session for prevention of certain Cross-site request forgery (CSRF)

     35

        attacks (See

     36

        http://en.wikipedia.org/wiki/Cross-site_request_forgery for more

     37

        information).

     38

     39

        For use with the ``webhelpers.html.secure_form`` helper functions.

     40

     41

        """

     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)

     46

        else:

     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)

     50

     51

     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

     55

        (actions).

     56

     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

     60

        https if necessary.

     61

     62

        Non-HTTPS POST requests are aborted (405 response code) by this

     63

        decorator.

     64

     65

        Example:

     66

     67

        .. code-block:: python

     68

     69

            # redirect to HTTPS /pylons

     70

            @https('/pylons')

     71

            def index(self):

     72

                do_secure()

     73

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

     78

            def login(self):

     79

                do_secure()

     80

     81

            # redirect to HTTPS version of myself

     82

            @https()

     83

            def get(self):

     84

                do_secure()

     85

     86

        """

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

     95

     96

            if url_or_callable is None:

     97

                url = request.url

     98

            elif callable(url_or_callable):

     99

                url = url_or_callable()

    100

            else:

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

    105

                                      parts[2:])

    106

    107

            log.debug('Redirecting non-https request: %s to: %s',

    108

                      request.path_info, url)

    109

            redirect(url)

    110

        return decorator(wrapper)

    111