1 | ''' |
---|
2 | Created on 29 Oct 2012 |
---|
3 | |
---|
4 | @author: mnagni |
---|
5 | ''' |
---|
6 | from django.contrib.auth import authenticate, REDIRECT_FIELD_NAME, \ |
---|
7 | SESSION_KEY, BACKEND_SESSION_KEY |
---|
8 | from django.shortcuts import render_to_response |
---|
9 | from django.core.context_processors import csrf |
---|
10 | from django.conf import settings |
---|
11 | from django_authopenid.views import not_authenticated, ask_openid,\ |
---|
12 | signin_failure, _build_context |
---|
13 | from django_authopenid.forms import OpenidSigninForm |
---|
14 | from django.db.models import Q |
---|
15 | from django.contrib.auth.backends import ModelBackend |
---|
16 | from django.shortcuts import render_to_response as render |
---|
17 | |
---|
18 | from userdb_model.models import User |
---|
19 | |
---|
20 | import md5 |
---|
21 | import logging |
---|
22 | import base64 |
---|
23 | from django_authopenid.utils import get_url_host |
---|
24 | from django.core.urlresolvers import reverse |
---|
25 | import urllib |
---|
26 | from django.http import HttpResponseRedirect |
---|
27 | from django.contrib.auth.forms import AuthenticationForm |
---|
28 | from django import forms |
---|
29 | from django.contrib.auth.signals import user_logged_in |
---|
30 | from django.db.utils import DatabaseError |
---|
31 | from dj_security.exception import DSJOpenIDNotFoundError |
---|
32 | |
---|
33 | # Get an instance of a logger |
---|
34 | LOGGER = logging.getLogger(__name__) |
---|
35 | |
---|
36 | OPENID = 'openid' |
---|
37 | |
---|
38 | def 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 | |
---|
44 | class 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 | ''' |
---|
75 | def 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 | |
---|
124 | def 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 | |
---|
136 | def 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 | |
---|
146 | def _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 | |
---|
153 | class 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 |
---|
175 | def 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 | |
---|
231 | def 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) |
---|