source: mauRepo/dj_security/trunk/dj_security/views/dj_security_login.py @ 8893

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/mauRepo/dj_security/trunk/dj_security/views/dj_security_login.py
Revision 8893, 12.6 KB checked in by aharwood, 5 years ago (diff)
  • Property svn:mime-type set to text/plain
Line 
1'''
2BSD Licence
3Copyright (c) 2012, Science & Technology Facilities Council (STFC)
4All rights reserved.
5
6Redistribution and use in source and binary forms, with or without modification,
7are permitted provided that the following conditions are met:
8
9    * Redistributions of source code must retain the above copyright notice,
10        this list of conditions and the following disclaimer.
11    * Redistributions in binary form must reproduce the above copyright notice,
12        this list of conditions and the following disclaimer in the documentation
13        and/or other materials provided with the distribution.
14    * Neither the name of the Science & Technology Facilities Council (STFC)
15        nor the names of its contributors may be used to endorse or promote
16        products derived from this software without specific prior written permission.
17
18THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
20THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
22BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
23OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28Created on 29 Oct 2012
29
30@author: mnagni
31'''
32from django.contrib.auth import authenticate, SESSION_KEY
33from django.core.context_processors import csrf
34from django.conf import settings
35from django_authopenid.views import not_authenticated, ask_openid,\
36    signin_failure, _build_context
37from django_authopenid.forms import OpenidSigninForm
38from django.db.models import Q
39from django.contrib.auth.backends import ModelBackend
40from django.shortcuts import render_to_response as render
41from django.contrib import messages
42
43from userdb_model.models import User
44
45import md5
46import logging
47import base64
48from django_authopenid.utils import get_url_host
49from django.core.urlresolvers import reverse
50import urllib
51from django.http import HttpResponseRedirect
52from django.contrib.auth.forms import AuthenticationForm
53from django import forms
54from django.contrib.auth.signals import user_logged_in
55from django.db.utils import DatabaseError
56from dj_security.exception import DSJOpenIDNotFoundError, PasswordNotMaches,\
57    UserNotFound
58from dj_security.middleware import _encode_authenticated_response
59from django.core.exceptions import ObjectDoesNotExist
60from dj_security import _redirect_field_name
61
62# Get an instance of a logger
63LOGGER = logging.getLogger(__name__)
64
65OPENID = 'openid.identity'
66
67def error_handle(request, context):
68    form = CEDAAuthenticationForm()
69    context['form'] = form
70    context.update(csrf(request))
71    return render('login.html', context)
72
73class CedaUserAuthenticationBackend(ModelBackend):
74    """
75    Extends Django's ``ModelBackend`` to allow login via username,
76    or verification token.
77
78    Args are either ``username`` and ``password``
79    and ``token``. In either case, ``is_active`` can also be given.
80
81    For login, is_active is not given, so that the login form can
82    raise a specific error for inactive users.
83    For password reset, True is given for is_active.
84    For signup verficiation, False is given for is_active.
85    """
86
87    def __init__(self, *args, **kwargs):
88        super(CedaUserAuthenticationBackend, self).__init__(*args, **kwargs)
89
90    def authenticate(self, **kwargs):
91        if kwargs:
92            username = kwargs.pop("username", None)
93            if username:
94                username = Q(accountid=username)
95                password = kwargs.pop("password", None)
96                try:
97                    user = User.objects.get(username, **kwargs)
98                except User.DoesNotExist:
99                    raise UserNotFound()
100                    pass
101                else:
102                    if user.md5passwd == md5.new(password).hexdigest():                       
103                        return user
104                    else:
105                        LOGGER.error("Wrong password for username: %s" % username)
106                        raise PasswordNotMaches()
107   
108def get_user_byopenid(user_id):
109    """
110        Returns a tbusers row specified by `user_id`
111    - String **user_id**
112        a user
113    """
114    try:
115        return User.objects.get(openid=user_id)
116    except (DatabaseError, ObjectDoesNotExist) as ex:
117        logging.error("Openid: %s - Not Found" % user_id)
118        raise DSJOpenIDNotFoundError(ex)   
119                   
120def logged_in(request):
121    '''
122        Retrieves the user after the openid provider authenticated him/her
123    '''
124    if SESSION_KEY not in request.session:
125        if OPENID in request.session:
126            login(request, get_user_byopenid(request.session[OPENID]))
127           
128    return _encode_authenticated_response_(request, context = {})   
129   
130def _encode_authenticated_response_(request, context):
131    context['redirect_url'] = \
132        base64.b64decode(request.session.get(_redirect_field_name(), ''))
133    LOGGER.info("Redirecting to %s" % (context['redirect_url']))   
134    return render('logged_in.html', context) 
135
136class CEDAAuthenticationForm(AuthenticationForm):
137
138    def __init__(self, request=None, *args, **kwargs):
139        super(CEDAAuthenticationForm, self).__init__(request, *args, **kwargs)
140
141    def clean(self):
142        username = self.cleaned_data.get('username')
143        password = self.cleaned_data.get('password')
144
145        if username and password:
146            self.user_cache = authenticate(username=username,
147                                           password=password)
148            if self.user_cache is None:
149                raise forms.ValidationError(
150                    self.error_messages['invalid_login'])
151            #elif not self.user_cache.is_active:
152            #    raise forms.ValidationError(self.error_messages['inactive'])
153        self.check_for_test_cookie()
154        return self.cleaned_data
155
156   
157@not_authenticated
158def signin(request, template_name='authopenid/signin.html',
159        redirect_field_name=_redirect_field_name(),
160        openid_form=OpenidSigninForm,
161        auth_form=CedaUserAuthenticationBackend,
162        on_failure=None, extra_context={'is_login_page': True}):
163    """Signin page. It manage the legacy authentification (user/password) 
164    and authentification with openid.
165
166    :attr request: request object
167    :attr template_name: string, name of template to use
168    :attr redirect_field_name: string, field name used for redirect. by
169    default 'next'
170    :attr openid_form: form use for openid signin, by default
171    `OpenidSigninForm`
172    :attr auth_form: form object used for legacy authentification.
173    By default AuthentificationForm form auser auth contrib.
174    :attr extra_context: A dictionary of variables to add to the
175    template context. Any callable object in this dictionary will
176    be called to produce the end result which appears in the context.
177    """
178    if on_failure is None:
179        on_failure = signin_failure
180       
181    if not request.session.has_key(redirect_field_name):
182   
183        if request.GET.has_key(redirect_field_name):
184            redirect = urllib.unquote_plus(request.GET.get(redirect_field_name))
185            LOGGER.debug("No redirect in session. Setting to: %s" % redirect)
186            request.session[redirect_field_name] = redirect
187        else:
188            LOGGER.debug("No redirect in session. Setting to default: %s" % settings.LOGIN_REDIRECT_URL)
189            request.session[redirect_field_name] = settings.LOGIN_REDIRECT_URL
190                     
191    form1 = openid_form()
192    form2 = auth_form()
193    if request.POST:
194        if not request.session.has_key(redirect_field_name):
195            request.session[redirect_field_name] = settings.LOGIN_REDIRECT_URL     
196        if 'openid_url' in request.POST.keys():
197            form1 = openid_form(data=request.POST)
198            if form1.is_valid():
199                redirect_url = "%s%s?%s" % (
200                        get_url_host(request),
201                        reverse('user_complete_signin'),
202                        urllib.urlencode({ redirect_field_name:
203                                          request.session[redirect_field_name]})
204                )
205                return ask_openid(request,
206                        form1.cleaned_data['openid_url'],
207                        redirect_url,
208                        on_failure=on_failure)
209        else:
210            # perform normal django authentification
211            form2 = auth_form(data=request.POST)
212            if form2.is_valid():
213                #login(request, form2.get_user())
214                if request.session.test_cookie_worked():
215                    request.session.delete_test_cookie()                                                   
216                response = HttpResponseRedirect(request.session.get(redirect_field_name, ''))
217                return _encode_authenticated_response(request,
218                                               response,
219                                               request.session.get(redirect_field_name, ''),
220                                               form2.get_user())
221            else:
222                return signin_failure(request, "Wrong username and/or password")
223               
224    return render(template_name, {
225        'form1': form1,
226        'form2': form2,
227        redirect_field_name: request.session.get(redirect_field_name, ''),
228        'msg':  request.GET.get('msg','')
229    }, context_instance=_build_context(request, extra_context=extra_context)) 
230
231def signin_failure(request, message, template_name='signin.html',
232        redirect_field_name=_redirect_field_name(), openid_form=OpenidSigninForm,
233        auth_form=AuthenticationForm, extra_context=None, **kwargs):
234    messages.add_message(request, messages.WARNING, message)
235    LOGGER.warn(message)
236    return render(template_name, {
237        'msg': message,
238        'form1': openid_form(),
239        'form2': auth_form(),
240        redirect_field_name: request.REQUEST.get(redirect_field_name, '')
241    }, context_instance=_build_context(request, extra_context))     
242
243def signin_success(request, identity_url, openid_response,
244        redirect_field_name=_redirect_field_name(), **kwargs):
245   
246    #redirect_parameter = getattr(settings, 'REDIRECT_URL', 'r')   
247    redirect_url = request.session.get(redirect_field_name, '')
248    LOGGER.debug("Redirecting to %s" % (redirect_url))
249    '''
250        Retrieves the user after the openid provider authenticated him/her
251    '''
252    response = HttpResponseRedirect(redirect_url)
253   
254    if OPENID in request.REQUEST:
255        try:
256            return _encode_authenticated_response(request,
257                                response,
258                                redirect_url,
259                                get_user_byopenid(request.REQUEST[OPENID]))
260        except DSJOpenIDNotFoundError as e:
261            msg = "No user associated with OPENID %s" % (request.REQUEST[OPENID])
262            messages.add_message(request, messages.WARNING, msg)
263            LOGGER.warn(msg)
264            return response                       
265    elif SESSION_KEY in request.session:
266        response = HttpResponseRedirect(redirect_url)
267        return _encode_authenticated_response(request, response, redirect_url)           
268   
269   
270   
271def login(request, user):
272    """
273    Persist a user id and a backend in the request. This way a user doesn't
274    have to reauthenticate on every request. Note that data set during
275    the anonymous session is retained when the user logs in.
276    Overrides the django.contrib.auth.login method
277    """
278    if user is None:
279        user = request.user
280    # TODO: It would be nice to support different login methods, like signed cookies.
281    if SESSION_KEY in request.session:
282        if request.session[SESSION_KEY] != user.accountid:
283            # To avoid reusing another user's session, create a new, empty
284            # session if the existing session corresponds to a different
285            # authenticated user.
286            r_copy = None
287            if request.session.has_key(_redirect_field_name()):
288                r_copy = request.session[_redirect_field_name()]
289            request.session.flush()
290            request.user = user
291            request.session[_redirect_field_name()] = r_copy
292    else:
293        request.session.cycle_key()
294    request.session[SESSION_KEY] = user.userkey
295    #request.session[BACKEND_SESSION_KEY] = user.backend
296    if hasattr(request, 'user'):
297        request.user = user
298    user_logged_in.send(sender=user.__class__, request=request, user=user)
Note: See TracBrowser for help on using the repository browser.