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

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

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