~canonical-isd-hackers/canonical-identity-provider/sst-changes

« back to all changes in this revision

Viewing changes to identityprovider/decorators.py

  • Committer: Danny Tamez
  • Date: 2010-04-21 15:29:24 UTC
  • Revision ID: danny.tamez@canonical.com-20100421152924-lq1m92tstk2iz75a
Canonical SSO Provider (Open Source) - Initial Commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2010 Canonical Ltd.  This software is licensed under the
 
2
# GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 
 
4
from django.template import RequestContext
 
5
from django.http import HttpResponseRedirect, HttpResponseForbidden
 
6
from django.core.cache import cache
 
7
from django.conf import settings
 
8
from django.template.loader import render_to_string
 
9
from datetime import datetime, timedelta
 
10
import functools
 
11
from hashlib import sha1
 
12
 
 
13
 
 
14
def guest_required(func):
 
15
    def _guest_required_decorator(request, *args, **kwargs):
 
16
        if request.user.is_authenticated():
 
17
            return HttpResponseRedirect('/')
 
18
        else:
 
19
            return func(request, *args, **kwargs)
 
20
    return _guest_required_decorator
 
21
 
 
22
 
 
23
def dont_cache(func):
 
24
    def _dont_cache_decorator(request, *args, **kwargs):
 
25
        response = func(request, *args, **kwargs)
 
26
        response["Expires"] = "Tue, 03 Jul 2001 06:00:00 GMT"
 
27
        response["Cache-Control"] = ("no-store, no-cache, "
 
28
                                     "must-revalidate, max-age=0")
 
29
        response['Pragma'] = 'no-cache'
 
30
        return response
 
31
    return _dont_cache_decorator
 
32
 
 
33
 
 
34
def check_readonly(func):
 
35
    """ A readonly aware decorator.
 
36
 
 
37
    The decorated view will not be accessible at all during readonly mode.
 
38
    Instead, a static warning page will be displayed.
 
39
    """
 
40
    def wrapper(request, *args, **kwargs):
 
41
        if getattr(settings, 'READ_ONLY_MODE', False):
 
42
            html = render_to_string('readonly.html',
 
43
                {'readonly': False},
 
44
                context_instance=RequestContext(request))
 
45
            return HttpResponseForbidden(html)
 
46
        return func(request, *args, **kwargs)
 
47
    functools.update_wrapper(wrapper, func)
 
48
    return wrapper
 
49
 
 
50
 
 
51
class ratelimit(object):
 
52
    """ A rate-limiting decorator.
 
53
        Strongly based on Simon Willison's code,
 
54
        http://github.com/simonw/ratelimitcache/blob/master/ratelimitcache.py
 
55
    """
 
56
    # This class is designed to be sub-classed
 
57
    minutes = 2  # The time period
 
58
    requests = 20  # Number of allowed requests in that time period
 
59
 
 
60
    prefix = 'rl-'  # Prefix for memcache key
 
61
 
 
62
    def __init__(self, **options):
 
63
        for key, value in options.items():
 
64
            setattr(self, key, value)
 
65
 
 
66
    def __call__(self, fn):
 
67
        def wrapper(request, *args, **kwargs):
 
68
            return self.view_wrapper(request, fn, *args, **kwargs)
 
69
        functools.update_wrapper(wrapper, fn)
 
70
        return wrapper
 
71
 
 
72
    def view_wrapper(self, request, fn, *args, **kwargs):
 
73
        if not self.should_ratelimit(request):
 
74
            return fn(request, *args, **kwargs)
 
75
 
 
76
        counts = self.get_counters(request).values()
 
77
        # Increment rate limiting counter
 
78
        self.cache_incr(self.current_key(request))
 
79
 
 
80
        # Have they failed?
 
81
        if sum(int(x) for x in counts) >= self.requests:
 
82
            return self.disallowed(request)
 
83
 
 
84
        return fn(request, *args, **kwargs)
 
85
 
 
86
    def cache_get_many(self, keys):
 
87
        return cache.get_many(keys)
 
88
 
 
89
    def cache_incr(self, key):
 
90
        # memcache is only backend that can increment atomically
 
91
        try:
 
92
            # add first, to ensure the key exists
 
93
            cache._cache.add(key, '0', time=self.expire_after())
 
94
            cache._cache.incr(key)
 
95
        except AttributeError:
 
96
            cache.set(key, cache.get(key, 0) + 1, self.expire_after())
 
97
 
 
98
    def should_ratelimit(self, request):
 
99
        return True
 
100
 
 
101
    def get_counters(self, request):
 
102
        return self.cache_get_many(self.keys_to_check(request))
 
103
 
 
104
    def keys_to_check(self, request):
 
105
        extra = self.key_extra(request)
 
106
        now = datetime.now()
 
107
        return [
 
108
            '%s%s-%s' % (
 
109
                self.prefix,
 
110
                extra,
 
111
                (now - timedelta(minutes=minute)).strftime('%Y%m%d%H%M')
 
112
            ) for minute in range(self.minutes + 1)
 
113
        ]
 
114
 
 
115
    def current_key(self, request):
 
116
        return '%s%s-%s' % (
 
117
            self.prefix,
 
118
            self.key_extra(request),
 
119
            datetime.now().strftime('%Y%m%d%H%M')
 
120
        )
 
121
 
 
122
    def key_extra(self, request):
 
123
        # By default, their IP address is used
 
124
        return request.META.get('REMOTE_ADDR', '')
 
125
 
 
126
    def disallowed(self, request):
 
127
        "Over-ride this method if you want to log incidents"
 
128
        return HttpResponseForbidden(render_to_string('limitexceeded.html'))
 
129
 
 
130
    def expire_after(self):
 
131
        "Used for setting the memcached cache expiry"
 
132
        return (self.minutes + 1) * 60
 
133
 
 
134
    def reset_count(self, request):
 
135
        "Reset the rate limiting limit count for a request"
 
136
        for key in self.keys_to_check(request):
 
137
            cache.delete(key)
 
138
 
 
139
 
 
140
class ratelimit_post(ratelimit):
 
141
    "Rate limit POSTs - can be used to protect a login form"
 
142
    key_field = None  # If provided, this POST var will affect the rate limit
 
143
 
 
144
    def should_ratelimit(self, request):
 
145
        return request.method == 'POST'
 
146
 
 
147
    def key_extra(self, request):
 
148
        # IP address and key_field (if it is set)
 
149
        extra = super(ratelimit_post, self).key_extra(request)
 
150
        if self.key_field:
 
151
            value = sha1(request.POST.get(self.key_field, '')).hexdigest()
 
152
            extra += '-' + value
 
153
        return extra
 
154
 
 
155
 
 
156
class limitlogin(ratelimit_post):
 
157
    """ Limit login POSTs, per username.
 
158
        Also, take default values from settings.
 
159
    """
 
160
    key_field = 'email'
 
161
 
 
162
    def __init__(self, **options):
 
163
        args = {
 
164
            'minutes': getattr(settings, 'LOGIN_LIMIT_MINUTES', 2),
 
165
            'requests': getattr(settings, 'LOGIN_LIMIT_REQUESTS', 20),
 
166
            }
 
167
        args.update(options)
 
168
        super(limitlogin, self).__init__(**args)