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

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/mauRepo/dj_security/trunk/dj_security/views/dj_security_login.py@8769
Revision 8769, 9.6 KB checked in by mnagni, 7 years ago (diff)

Complete - # 22698: [CEDA Site Python Port] MyCEDA Login needs to be more user-friendly
 http://team.ceda.ac.uk/trac/ceda/ticket/22698

  • Property svn:mime-type set to text/plain
Line 
1'''
2Created on 29 Oct 2012
3
4@author: mnagni
5'''
6from django.contrib.auth import authenticate, REDIRECT_FIELD_NAME, \
7    SESSION_KEY
8from django.core.context_processors import csrf
9from django.conf import settings
10from django_authopenid.views import not_authenticated, ask_openid,\
11    signin_failure, _build_context
12from django_authopenid.forms import OpenidSigninForm
13from django.db.models import Q
14from django.contrib.auth.backends import ModelBackend
15from django.shortcuts import render_to_response as render
16from django.contrib import messages
17
18from userdb_model.models import User
19
20import md5
21import logging
22import base64
23from django_authopenid.utils import get_url_host
24from django.core.urlresolvers import reverse
25import urllib
26from django.http import HttpResponseRedirect
27from django.contrib.auth.forms import AuthenticationForm
28from django import forms
29from django.contrib.auth.signals import user_logged_in
30from django.db.utils import DatabaseError
31from dj_security.exception import DSJOpenIDNotFoundError
32from dj_security.middleware import _encode_authenticated_response
33from django.core.exceptions import ObjectDoesNotExist
34
35# Get an instance of a logger
36LOGGER = logging.getLogger(__name__)
37
38OPENID = 'openid.identity'
39
40def error_handle(request, context):
41    form = CEDAAuthenticationForm()
42    context['form'] = form
43    context.update(csrf(request))
44    return render('login.html', context)
45
46class CedaUserAuthenticationBackend(ModelBackend):
47    """
48    Extends Django's ``ModelBackend`` to allow login via username,
49    or verification token.
50
51    Args are either ``username`` and ``password``
52    and ``token``. In either case, ``is_active`` can also be given.
53
54    For login, is_active is not given, so that the login form can
55    raise a specific error for inactive users.
56    For password reset, True is given for is_active.
57    For signup verficiation, False is given for is_active.
58    """
59
60    def __init__(self, *args, **kwargs):
61        super(CedaUserAuthenticationBackend, self).__init__(*args, **kwargs)
62
63    def authenticate(self, **kwargs):
64        if kwargs:
65            username = kwargs.pop("username", None)
66            if username:
67                username = Q(accountid=username)
68                password = kwargs.pop("password", None)
69                try:
70                    user = User.objects.get(username, **kwargs)
71                except User.DoesNotExist:
72                    pass
73                else:
74                    if user.md5passwd == md5.new(password).hexdigest():
75                        return user
76   
77def get_user_byopenid(user_id):
78    """
79        Returns a tbusers row specified by `user_id`
80    - String **user_id**
81        a user
82    """
83    try:
84        return User.objects.get(openid=user_id)
85    except (DatabaseError, ObjectDoesNotExist) as ex:
86        logging.error("Openid: %s - Not Found" % user_id)
87        raise DSJOpenIDNotFoundError(ex)   
88                   
89def logged_in(request):
90    '''
91        Retrieves the user after the openid provider authenticated him/her
92    '''
93    if SESSION_KEY not in request.session:
94        if OPENID in request.session:
95            login(request, get_user_byopenid(request.session[OPENID]))
96           
97    return _encode_authenticated_response_(request, context = {})   
98   
99def _encode_authenticated_response_(request, context):
100    redirect_parameter = getattr(settings, 'REDIRECT_URL', 'r')
101    context['redirect_url'] = \
102        base64.b64decode(request.session.get(redirect_parameter, ''))
103    LOGGER.debug("Redirecting to %s" % (context['redirect_url']))   
104    return render('logged_in.html', context)
105
106class CEDAAuthenticationForm(AuthenticationForm):
107
108    def __init__(self, request=None, *args, **kwargs):
109        super(CEDAAuthenticationForm, self).__init__(request, *args, **kwargs)
110
111    def clean(self):
112        username = self.cleaned_data.get('username')
113        password = self.cleaned_data.get('password')
114
115        if username and password:
116            self.user_cache = authenticate(username=username,
117                                           password=password)
118            if self.user_cache is None:
119                raise forms.ValidationError(
120                    self.error_messages['invalid_login'])
121            #elif not self.user_cache.is_active:
122            #    raise forms.ValidationError(self.error_messages['inactive'])
123        self.check_for_test_cookie()
124        return self.cleaned_data
125
126   
127@not_authenticated
128def signin(request, template_name='authopenid/signin.html',
129        redirect_field_name=REDIRECT_FIELD_NAME, openid_form=OpenidSigninForm,
130        auth_form=CedaUserAuthenticationBackend,
131        on_failure=None, extra_context={'is_login_page': True}):
132    """Signin page. It manage the legacy authentification (user/password) 
133    and authentification with openid.
134
135    :attr request: request object
136    :attr template_name: string, name of template to use
137    :attr redirect_field_name: string, field name used for redirect. by
138    default 'next'
139    :attr openid_form: form use for openid signin, by default
140    `OpenidSigninForm`
141    :attr auth_form: form object used for legacy authentification.
142    By default AuthentificationForm form auser auth contrib.
143    :attr extra_context: A dictionary of variables to add to the
144    template context. Any callable object in this dictionary will
145    be called to produce the end result which appears in the context.
146    """
147    if on_failure is None:
148        on_failure = signin_failure
149       
150    redirect_to = request.REQUEST.get(redirect_field_name, '')
151    form1 = openid_form()
152    form2 = auth_form()
153    if request.POST:
154        if not redirect_to or '//' in redirect_to or ' ' in redirect_to:
155            redirect_to = settings.LOGIN_REDIRECT_URL     
156        if 'openid_url' in request.POST.keys():
157            form1 = openid_form(data=request.POST)
158            if form1.is_valid():
159                redirect_url = "%s%s?%s" % (
160                        get_url_host(request),
161                        reverse('user_complete_signin'),
162                        urllib.urlencode({ redirect_field_name: redirect_to })
163                )
164                return ask_openid(request,
165                        form1.cleaned_data['openid_url'],
166                        redirect_url,
167                        on_failure=on_failure)
168        else:
169            # perform normal django authentification
170            form2 = auth_form(data=request.POST)
171            if form2.is_valid():
172                #login(request, form2.get_user())
173                if request.session.test_cookie_worked():
174                    request.session.delete_test_cookie()
175                redirect_to = base64.b64decode(redirect_to)                                   
176                response = HttpResponseRedirect(redirect_to)
177                _encode_authenticated_response(request,
178                                               response,
179                                               redirect_to,
180                                               form2.get_user())
181                return response
182    return render(template_name, {
183        'form1': form1,
184        'form2': form2,
185        redirect_field_name: redirect_to,
186        'msg':  request.GET.get('msg','')
187    }, context_instance=_build_context(request, extra_context=extra_context)) 
188
189def signin_success(request, identity_url, openid_response,
190        redirect_field_name=REDIRECT_FIELD_NAME, **kwargs):
191   
192    #redirect_parameter = getattr(settings, 'REDIRECT_URL', 'r')   
193    redirect_url = base64.b64decode(request.REQUEST.get(redirect_field_name, ''))
194    LOGGER.debug("Redirecting to %s" % (redirect_url))
195    '''
196        Retrieves the user after the openid provider authenticated him/her
197    '''
198    response = HttpResponseRedirect(redirect_url)
199   
200    if OPENID in request.REQUEST:
201        try:
202            return _encode_authenticated_response(request,
203                                response,
204                                redirect_url,
205                                get_user_byopenid(request.REQUEST[OPENID]))
206        except DSJOpenIDNotFoundError as e:
207            msg = "No user associated with OPENID %s" % (request.REQUEST[OPENID])
208            messages.add_message(request, messages.WARNING, msg)
209            LOGGER.warn(msg)
210            return HttpResponseRedirect(request.environ['HTTP_REFERER'])                       
211    elif SESSION_KEY in request.session:
212        response = HttpResponseRedirect(redirect_url)
213        return _encode_authenticated_response(request, response, redirect_url)           
214   
215   
216   
217def login(request, user):
218    """
219    Persist a user id and a backend in the request. This way a user doesn't
220    have to reauthenticate on every request. Note that data set during
221    the anonymous session is retained when the user logs in.
222    Overrides the django.contrib.auth.login method
223    """
224    if user is None:
225        user = request.user
226    # TODO: It would be nice to support different login methods, like signed cookies.
227    if SESSION_KEY in request.session:
228        if request.session[SESSION_KEY] != user.accountid:
229            # To avoid reusing another user's session, create a new, empty
230            # session if the existing session corresponds to a different
231            # authenticated user.
232            r_copy = None
233            if request.session.has_key('r'):
234                r_copy = request.session['r']
235            request.session.flush()
236            request.user = user
237            request.session['r'] = r_copy
238    else:
239        request.session.cycle_key()
240    request.session[SESSION_KEY] = user.userkey
241    #request.session[BACKEND_SESSION_KEY] = user.backend
242    if hasattr(request, 'user'):
243        request.user = user
244    user_logged_in.send(sender=user.__class__, request=request, user=user)
Note: See TracBrowser for help on using the repository browser.