Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views / decorators.py @ b1cb2583

History | View | Annotate | Download (6.8 kB)

1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from functools import wraps
35

    
36
from django.utils.http import urlquote
37
from django.contrib import messages
38
from django.core.exceptions import PermissionDenied
39
from django.core.urlresolvers import reverse
40
from django.http import HttpResponse, HttpResponseRedirect
41
from django.utils.decorators import available_attrs
42
from django.utils.http import urlencode
43

    
44
from astakos.im import auth_providers as auth
45
from astakos.im.cookie import CookieHandler
46

    
47
REDIRECT_FIELD_NAME = 'next'
48

    
49

    
50
def user_passes_test(test_func, login_url=None,
51
                     redirect_field_name=REDIRECT_FIELD_NAME):
52
    """
53
    Decorator for views that checks that the user passes the given test,
54
    redirecting to the log-in page if necessary. The test should be a callable
55
    that takes the user object and returns True if the user passes.
56
    """
57
    if not login_url:
58
        from django.conf import settings
59
        login_url = settings.LOGIN_URL
60

    
61
    def decorator(view_func):
62
        def _wrapped_view(request, *args, **kwargs):
63
            if test_func(request.user):
64
                return view_func(request, *args, **kwargs)
65
            path = urlquote(request.get_full_path())
66
            tup = reverse('login'), redirect_field_name, path
67
            return HttpResponseRedirect('%s?%s=%s' % tup)
68
        return wraps(view_func,
69
                     assigned=available_attrs(view_func))(_wrapped_view)
70
    return decorator
71

    
72

    
73
def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME,
74
                   login_url=None):
75
    """
76
    Decorator for views that checks that the user is logged in, redirecting
77
    to the log-in page if necessary.
78
    """
79
    actual_decorator = user_passes_test(
80
        lambda u: u.is_authenticated(),
81
        redirect_field_name=redirect_field_name,
82

    
83
    )
84
    if function:
85
        return actual_decorator(function)
86
    return actual_decorator
87

    
88

    
89
def cookie_fix(func):
90
    """
91
    Decorator checks whether the request.user conforms
92
    with the astakos cookie and if not it fixes it.
93
    """
94
    @wraps(func)
95
    def wrapper(request, *args, **kwargs):
96
        cookie = CookieHandler(request)
97
        if not cookie.is_valid:
98
            # redirect to request path to set/delete the cookie
99
            response = HttpResponse(status=302)
100
            response['Location'] = request.get_full_path()
101
            cookie.fix(response)
102
            return response
103

    
104
        response = func(request, *args, **kwargs)
105

    
106
        # if the user authentication status has changed during the processing
107
        # set/delete the cookie appropriately
108
        if not cookie.is_valid:
109
            cookie.fix(response)
110
        return response
111
    return wrapper
112

    
113

    
114
def requires_auth_provider(provider_id, **perms):
115
    """
116
    View requires specified authentication module to be enabled in
117
    ASTAKOS_IM_MODULES setting.
118
    """
119
    def decorator(func, *args, **kwargs):
120
        @wraps(func)
121
        def wrapper(request, *args, **kwargs):
122
            provider = auth.get_provider(provider_id)
123

    
124
            if not provider or not provider.is_active():
125
                raise PermissionDenied
126

    
127
            for pkey, value in perms.iteritems():
128
                attr = 'get_%s_policy' % pkey.lower()
129
                if getattr(provider, attr) != value:
130
                    #TODO: add session message
131
                    return HttpResponseRedirect(reverse('login'))
132
            return func(request, *args)
133
        return wrapper
134
    return decorator
135

    
136

    
137
def requires_anonymous(func):
138
    """
139
    Decorator checkes whether the request.user is not Anonymous and
140
    in that case redirects to `logout`.
141
    """
142
    @wraps(func)
143
    def wrapper(request, *args):
144
        if not request.user.is_anonymous():
145
            next = urlencode({'next': request.build_absolute_uri()})
146
            logout_uri = reverse('astakos.im.views.logout') + '?' + next
147
            return HttpResponseRedirect(logout_uri)
148
        return func(request, *args)
149
    return wrapper
150

    
151

    
152
def signed_terms_required(func):
153
    """
154
    Decorator checks whether the request.user is Anonymous and in that case
155
    redirects to `logout`.
156
    """
157
    @wraps(func)
158
    def wrapper(request, *args, **kwargs):
159
        if request.user.is_authenticated() and not request.user.signed_terms:
160
            params = urlencode({'next': request.build_absolute_uri(),
161
                                'show_form': ''})
162
            terms_uri = reverse('latest_terms') + '?' + params
163
            return HttpResponseRedirect(terms_uri)
164
        return func(request, *args, **kwargs)
165
    return wrapper
166

    
167

    
168
def required_auth_methods_assigned(allow_access=False):
169
    """
170
    Decorator that checks whether the request.user has all
171
    required auth providers assigned.
172
    """
173

    
174
    def decorator(func):
175
        @wraps(func)
176
        def wrapper(request, *args, **kwargs):
177
            if request.user.is_authenticated():
178
                missing = request.user.missing_required_providers()
179
                if missing:
180
                    for provider in missing:
181
                        messages.error(request,
182
                                       provider.get_required_msg)
183
                    if not allow_access:
184
                        return HttpResponseRedirect(reverse('edit_profile'))
185
            return func(request, *args, **kwargs)
186
        return wrapper
187
    return decorator
188

    
189

    
190
def valid_astakos_user_required(func):
191
    return signed_terms_required(
192
        required_auth_methods_assigned()(login_required(func)))