1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 from django.conf import settings
17 from django.contrib.auth import REDIRECT_FIELD_NAME
18 from django.contrib.auth.forms import *
19 from django.contrib.auth.models import User
20 from django.contrib.auth import login, logout
21 from django.contrib.auth.decorators import login_required
22 from django.contrib.sites.models import Site
23
24 from django.http import HttpResponseRedirect
25 from django.shortcuts import render_to_response as render
26 from django.template import RequestContext, loader, Context
27
28 from django.core.urlresolvers import reverse
29 from django.utils.encoding import smart_unicode
30 from django.utils.translation import ugettext as _
31
32 from django.utils.http import urlquote_plus
33 from django.core.mail import send_mail
34
35 from openid.consumer.consumer import Consumer, \
36 SUCCESS, CANCEL, FAILURE, SETUP_NEEDED
37 from openid.consumer.discover import DiscoveryFailure
38 from openid.extensions import sreg, ax
39
40 try:
41 from openid.yadis import xri
42 except ImportError:
43 from yadis import xri
44
45 import re
46 import urllib
47
48 from madrona.openid import DjangoOpenIDStore
49 from madrona.openid.forms import *
50 from madrona.openid.models import UserAssociation
51 from madrona.openid.signals import oid_register
52 from madrona.openid.utils import *
53
54 -def _build_context(request, extra_context=None):
55 if extra_context is None:
56 extra_context = {}
57 context = RequestContext(request)
58 for key, value in extra_context.items():
59 context[key] = callable(value) and value() or value
60 return context
61
62 -def ask_openid(request, openid_url, redirect_to, on_failure=None):
63 """ basic function to ask openid and return response """
64 on_failure = on_failure or signin_failure
65 sreg_req = None
66 ax_req = None
67
68 trust_root = getattr(
69 settings, 'OPENID_TRUST_ROOT', get_url_host(request) + '/'
70 )
71 if xri.identifierScheme(openid_url) == 'XRI' and getattr(
72 settings, 'OPENID_DISALLOW_INAMES', False
73 ):
74 msg = _("i-names are not supported")
75 return on_failure(request, msg)
76 consumer = Consumer(request.session, DjangoOpenIDStore())
77 try:
78 auth_request = consumer.begin(openid_url)
79 except DiscoveryFailure:
80 msg = _("The OpenID %s was invalid") % openid_url
81 return on_failure(request, msg)
82
83
84 use_ax, use_sreg = discover_extensions(openid_url)
85 if use_sreg:
86
87
88 sreg_attrs = getattr(settings, 'OPENID_SREG', {})
89 sreg_attrs.update({"optional": ['nickname', 'email']})
90 sreg_req = sreg.SRegRequest(**sreg_attrs)
91 if use_ax:
92
93
94 ax_req = ax.FetchRequest()
95 ax_req.add(ax.AttrInfo('http://schema.openid.net/contact/email',
96 alias='email', required=True))
97 ax_req.add(ax.AttrInfo('http://schema.openid.net/namePerson/friendly',
98 alias='nickname', required=True))
99
100
101 ax_attrs = getattr(settings, 'OPENID_AX', [])
102 for attr in ax_attrs:
103 if len(attr) == 2:
104 ax_req.add(ax.AttrInfo(attr[0], required=alias[1]))
105 else:
106 ax_req.add(ax.AttrInfo(attr[0]))
107
108 if sreg_req is not None:
109 auth_request.addExtension(sreg_req)
110 if ax_req is not None:
111 auth_request.addExtension(ax_req)
112
113 redirect_url = auth_request.redirectURL(trust_root, redirect_to)
114 return HttpResponseRedirect(redirect_url)
115
116 -def complete(request, on_success=None, on_failure=None, return_to=None,
117 **kwargs):
118 """ complete openid signin """
119 on_success = on_success or default_on_success
120 on_failure = on_failure or default_on_failure
121
122 consumer = Consumer(request.session, DjangoOpenIDStore())
123
124 params = dict((k,smart_unicode(v)) for k, v in request.GET.items())
125 openid_response = consumer.complete(params, return_to)
126
127 if openid_response.status == SUCCESS:
128 return on_success(request, openid_response.identity_url,
129 openid_response, **kwargs)
130 elif openid_response.status == CANCEL:
131 return on_failure(request, 'The request was canceled', **kwargs)
132 elif openid_response.status == FAILURE:
133 return on_failure(request, openid_response.message, **kwargs)
134 elif openid_response.status == SETUP_NEEDED:
135 return on_failure(request, 'Setup needed', **kwargs)
136 else:
137 assert False, "Bad openid status: %s" % openid_response.status
138
140 """ default action on openid signin success """
141 request.session['openid'] = from_openid_response(openid_response)
142 return HttpResponseRedirect(clean_next(request.GET.get('next')))
143
145 """ default failure action on signin """
146 return render('openid_failure.html', {
147 'message': message
148 })
149
151 """ decorator that redirect user to next page if
152 he is already logged."""
153 def decorated(request, *args, **kwargs):
154 if request.user.is_authenticated():
155 next = request.GET.get("next", "/")
156 return HttpResponseRedirect(next)
157 return func(request, *args, **kwargs)
158 return decorated
159
160 -def signin_success(request, identity_url, openid_response,
161 redirect_field_name=REDIRECT_FIELD_NAME, **kwargs):
162 """
163 openid signin success.
164
165 If the openid is already registered, the user is redirected to
166 url set par next or in settings with OPENID_REDIRECT_NEXT variable.
167 If none of these urls are set user is redirectd to /.
168
169 if openid isn't registered user is redirected to register page.
170 """
171
172 openid_ = from_openid_response(openid_response)
173
174 openids = request.session.get('openids', [])
175 openids.append(openid_)
176 request.session['openids'] = openids
177 request.session['openid'] = openid_
178 try:
179 rel = UserAssociation.objects.get(openid_url__exact=str(openid_))
180 except:
181
182 redirect_to = request.REQUEST.get(redirect_field_name, '')
183 if not redirect_to or '//' in redirect_to or ' ' in redirect_to:
184 redirect_to = settings.LOGIN_REDIRECT_URL
185 return HttpResponseRedirect(
186 "%s?%s" % (reverse('user_register'),
187 urllib.urlencode({redirect_field_name: redirect_to}))
188 )
189 user_ = rel.user
190 if user_.is_active:
191 user_.backend = "django.contrib.auth.backends.ModelBackend"
192 login(request, user_)
193
194 redirect_to = request.GET.get(redirect_field_name, '')
195 if not redirect_to or '//' in redirect_to or ' ' in redirect_to:
196 redirect_to = settings.LOGIN_REDIRECT_URL
197 return HttpResponseRedirect(redirect_to)
198
199 -def signin_failure(request, message, template_name='authopenid/signin.html',
200 redirect_field_name=REDIRECT_FIELD_NAME, openid_form=OpenidSigninForm,
201 auth_form=AuthenticationForm, extra_context=None, **kwargs):
202 """
203 falure with openid signin. Go back to signin page.
204
205 :attr request: request object
206 :attr template_name: string, name of template to use, default is
207 'authopenid/signin.html'
208 :attr redirect_field_name: string, field name used for redirect. by default
209 'next'
210 :attr openid_form: form use for openid signin, by default `OpenidSigninForm`
211 :attr auth_form: form object used for legacy authentification.
212 by default AuthentificationForm form auser auth contrib.
213 :attr extra_context: A dictionary of variables to add to the template
214 context. Any callable object in this dictionary will be called to produce
215 the end result which appears in the context.
216 """
217 return render(template_name, {
218 'msg': message,
219 'form1': openid_form(),
220 'form2': auth_form(),
221 redirect_field_name: request.REQUEST.get(redirect_field_name, '')
222 }, context_instance=_build_context(request, extra_context))
223
224 @not_authenticated
225 -def signin(request, template_name='authopenid/signin.html',
226 redirect_field_name=REDIRECT_FIELD_NAME, openid_form=OpenidSigninForm,
227 auth_form=AuthenticationForm, on_failure=None, extra_context=None):
228 """Signin page. It manage the legacy authentification (user/password)
229 and authentification with openid.
230
231 :attr request: request object
232 :attr template_name: string, name of template to use
233 :attr redirect_field_name: string, field name used for redirect. by
234 default 'next'
235 :attr openid_form: form use for openid signin, by default
236 `OpenidSigninForm`
237 :attr auth_form: form object used for legacy authentification.
238 By default AuthentificationForm form auser auth contrib.
239 :attr extra_context: A dictionary of variables to add to the
240 template context. Any callable object in this dictionary will
241 be called to produce the end result which appears in the context.
242 """
243 if on_failure is None:
244 on_failure = signin_failure
245
246 from madrona.common.utils import get_logger
247 log = get_logger()
248
249 redirect_to = request.REQUEST.get(redirect_field_name, '')
250 form1 = openid_form()
251 form2 = auth_form()
252 log.debug(request.POST.keys())
253 if request.POST:
254 if not redirect_to or '//' in redirect_to or ' ' in redirect_to:
255 redirect_to = settings.LOGIN_REDIRECT_URL
256 if 'openid_url' in request.POST.keys():
257 form1 = openid_form(data=request.POST)
258 if form1.is_valid():
259 redirect_url = "%s%s?%s" % (
260 get_url_host(request),
261 reverse('user_complete_signin'),
262 urllib.urlencode({redirect_field_name: redirect_to})
263 )
264 return ask_openid(request,
265 form1.cleaned_data['openid_url'],
266 redirect_url,
267 on_failure=on_failure)
268 else:
269
270 form2 = auth_form(data=request.POST)
271 if form2.is_valid():
272 login(request, form2.get_user())
273 if request.session.test_cookie_worked():
274 request.session.delete_test_cookie()
275 return HttpResponseRedirect(redirect_to)
276 return render(template_name, {
277 'form1': form1,
278 'form2': form2,
279 redirect_field_name: redirect_to,
280 'msg': request.GET.get('msg','')
281 }, context_instance=_build_context(request, extra_context=extra_context))
282
287 """
288 in case of complete signin with openid
289
290 :attr request: request object
291 :attr openid_form: form use for openid signin, by default
292 `OpenidSigninForm`
293 :attr auth_form: form object used for legacy authentification.
294 by default AuthentificationForm form auser auth contrib.
295 :attr on_success: callbale, function used when openid auth success
296 :attr on_failure: callable, function used when openid auth failed.
297 :attr extra_context: A dictionary of variables to add to the template
298 context.
299 Any callable object in this dictionary will be called to produce the
300 end result which appears in the context.
301 """
302 return complete(request, on_success, on_failure,
303 get_url_host(request) + reverse('user_complete_signin'),
304 redirect_field_name=redirect_field_name, openid_form=openid_form,
305 auth_form=auth_form, extra_context=extra_context)
306
308 """ test if an openid is already in database """
309 is_exist = True
310 try:
311 uassoc = UserAssociation.objects.get(openid_url__exact=str(openid_url))
312 except:
313 is_exist = False
314 return is_exist
315
317 """ create an account """
318 user = User.objects.create_user(form.cleaned_data['username'],
319 form.cleaned_data['email'])
320 user.backend = "django.contrib.auth.backends.ModelBackend"
321 oid_register.send(sender=user, openid=_openid)
322 return user
323
324 @not_authenticated
325 -def register(request, template_name='authopenid/complete.html',
326 redirect_field_name=REDIRECT_FIELD_NAME,
327 register_form=OpenidRegisterForm, auth_form=AuthenticationForm,
328 register_account=register_account, send_email=True,
329 extra_context=None):
330 """
331 register an openid.
332
333 If user is already a member he can associate its openid with
334 its account.
335
336 A new account could also be created and automaticaly associated
337 to the openid.
338
339 :attr request: request object
340 :attr template_name: string, name of template to use,
341 'authopenid/complete.html' by default
342 :attr redirect_field_name: string, field name used for redirect. by default
343 'next'
344 :attr register_form: form use to create a new account. By default
345 `OpenidRegisterForm`
346 :attr auth_form: form object used for legacy authentification.
347 by default `OpenidVerifyForm` form auser auth contrib.
348 :attr register_account: callback used to create a new account from openid.
349 It take the register_form as param.
350 :attr send_email: boolean, by default True. If True, an email will be sent
351 to the user.
352 :attr extra_context: A dictionary of variables to add to the template
353 context. Any callable object in this dictionary will be called to produce
354 the end result which appears in the context.
355 """
356 is_redirect = False
357 redirect_to = request.REQUEST.get(redirect_field_name, '')
358 openid_ = request.session.get('openid', None)
359 if openid_ is None or not openid_:
360 return HttpResponseRedirect("%s?%s" % (reverse('user_signin'),
361 urllib.urlencode({
362 redirect_field_name: redirect_to})))
363
364 nickname = ''
365 email = ''
366 if openid_.sreg is not None:
367 nickname = openid_.sreg.get('nickname', '')
368 email = openid_.sreg.get('email', '')
369 if openid_.ax is not None and not nickname or not email:
370 if openid_.ax.get('http://schema.openid.net/namePerson/friendly', False):
371 nickname = openid_.ax.get('http://schema.openid.net/namePerson/friendly')[0]
372 if openid_.ax.get('http://schema.openid.net/contact/email', False):
373 email = openid_.ax.get('http://schema.openid.net/contact/email')[0]
374
375 form1 = register_form(initial={
376 'username': nickname,
377 'email': email,
378 })
379 form2 = auth_form(initial={
380 'username': nickname,
381 })
382
383 if request.POST:
384 user_ = None
385 if not redirect_to or '//' in redirect_to or ' ' in redirect_to:
386 redirect_to = settings.LOGIN_REDIRECT_URL
387 if 'email' in request.POST.keys():
388 form1 = register_form(data=request.POST)
389 if form1.is_valid():
390 user_ = register_account(form1, openid_)
391 else:
392 form2 = auth_form(data=request.POST)
393 if form2.is_valid():
394 user_ = form2.get_user()
395 if user_ is not None:
396
397 uassoc = UserAssociation(
398 openid_url=str(openid_),
399 user_id=user_.id
400 )
401 uassoc.save(send_email=send_email)
402 login(request, user_)
403 return HttpResponseRedirect(redirect_to)
404
405 return render(template_name, {
406 'form1': form1,
407 'form2': form2,
408 redirect_field_name: redirect_to,
409 'nickname': nickname,
410 'email': email
411 }, context_instance=_build_context(request, extra_context=extra_context))
412
413 @login_required
414 -def signout(request, next_page=None, template_name='registration/logged_out.html'):
415 """
416 signout from the website. Remove openid from session and kill it.
417 :attr request: request object
418 :attr next_page: default redirect page after logout
419 :attr template_name: string, name of template to use when next_page isn't set,
420 'registration/logged_out.html' by default
421 """
422 try:
423 del request.session['openid']
424 except KeyError:
425 pass
426 next = request.GET.get('next')
427 logout(request)
428 if next is not None:
429 return HttpResponseRedirect(next)
430
431 if next_page is None:
432 return render(template_name, {
433 'title': _('Logged out')}, context_instance=RequestContext(request))
434
435 return HttpResponseRedirect(next_page or request.path)
436
437 -def xrdf(request, template_name='authopenid/yadis.xrdf'):
438 """ view used to process the xrdf file"""
439
440 url_host = get_url_host(request)
441 return_to = [
442 "%s%s" % (url_host, reverse('user_complete_signin'))
443 ]
444 response = render(template_name, {
445 'return_to': return_to
446 }, context_instance=RequestContext(request))
447
448 response['Content-Type'] = "application/xrds+xml"
449 response['X-XRDS-Location'] = request.build_absolute_uri(reverse('oid_xrdf'))
450 return response
451
452 @login_required
453 -def password_change(request,
454 template_name='authopenid/password_change_form.html',
455 set_password_form=SetPasswordForm,
456 change_password_form=PasswordChangeForm, post_change_redirect=None,
457 extra_context=None):
458 """
459 View that allow a user to add a password to its account or change it.
460
461 :attr request: request object
462 :attr template_name: string, name of template to use,
463 'authopenid/password_change_form.html' by default
464 :attr set_password_form: form use to create a new password. By default
465 ``django.contrib.auth.forms.SetPasswordForm``
466 :attr change_password_form: form objectto change passworf.
467 by default `django.contrib.auth.forms.SetPasswordForm.PasswordChangeForm`
468 form auser auth contrib.
469 :attr post_change_redirect: url used to redirect user after password change.
470 It take the register_form as param.
471 :attr extra_context: A dictionary of variables to add to the template context.
472 Any callable object in this dictionary will be called to produce the
473 end result which appears in the context.
474 """
475 if post_change_redirect is None:
476 post_change_redirect = settings.LOGIN_REDIRECT_URL
477
478 set_password = False
479 if request.user.has_usable_password():
480 change_form = change_password_form
481 else:
482 set_password = True
483 change_form = set_password_form
484
485 if request.POST:
486 form = change_form(request.user, request.POST)
487 if form.is_valid():
488 form.save()
489 msg = urllib.quote(_("Password changed"))
490 redirect_to = "%s?%s" % (post_change_redirect,
491 urllib.urlencode({"msg": msg}))
492 return HttpResponseRedirect(redirect_to)
493 else:
494 form = change_form(request.user)
495
496 return render(template_name, {
497 'form': form,
498 'set_password': set_password
499 }, context_instance=_build_context(request, extra_context=extra_context))
500
501 @login_required
502 -def associate_failure(request, message,
503 template_failure="authopenid/associate.html",
504 openid_form=AssociateOpenID, redirect_name=None,
505 extra_context=None, **kwargs):
506
507 """ function used when new openid association fail"""
508
509 return render(template_failure, {
510 'form': openid_form(request.user),
511 'msg': message,
512 }, context_instance=_build_context(request, extra_context=extra_context))
513
514 @login_required
515 -def associate_success(request, identity_url, openid_response,
516 redirect_field_name=REDIRECT_FIELD_NAME, send_email=True, **kwargs):
517 """
518 function used when new openid association success. redirect the user
519 """
520 openid_ = from_openid_response(openid_response)
521 openids = request.session.get('openids', [])
522 openids.append(openid_)
523 request.session['openids'] = openids
524 uassoc = UserAssociation(
525 openid_url=str(openid_),
526 user_id=request.user.id
527 )
528 uassoc.save(send_email=send_email)
529
530 redirect_to = request.GET.get(redirect_field_name, '')
531 if not redirect_to or '//' in redirect_to or ' ' in redirect_to:
532 redirect_to = settings.LOGIN_REDIRECT_URL
533 return HttpResponseRedirect(redirect_to)
534
541
542 """ in case of complete association with openid """
543
544 return complete(request, on_success, on_failure,
545 get_url_host(request) + reverse('user_complete_associate'),
546 redirect_field_name=redirect_field_name, openid_form=openid_form,
547 template_failure=template_failure, redirect_name=redirect_name,
548 send_email=send_email, extra_context=extra_context)
549
550 @login_required
551 -def associate(request, template_name='authopenid/associate.html',
552 openid_form=AssociateOpenID, redirect_field_name=REDIRECT_FIELD_NAME,
553 on_failure=associate_failure, extra_context=None):
554
555 """View that allow a user to associate a new openid to its account.
556
557 :attr request: request object
558 :attr template_name: string, name of template to use,
559 'authopenid/associate.html' by default
560 :attr openid_form: form use enter openid url. By default
561 ``madrona.openid.forms.AssociateOpenID``
562 :attr redirect_field_name: string, field name used for redirect.
563 by default 'next'
564 :attr on_success: callbale, function used when openid auth success
565 :attr on_failure: callable, function used when openid auth failed.
566 by default ``madrona.openid.views.associate_failure`
567 :attr extra_context: A dictionary of variables to add to the template
568 context. A callable object in this dictionary will be called to produce
569 the end result which appears in the context.
570 """
571
572 redirect_to = request.REQUEST.get(redirect_field_name, '')
573 if request.POST:
574 form = openid_form(request.user, data=request.POST)
575 if form.is_valid():
576 if not redirect_to or '//' in redirect_to or ' ' in redirect_to:
577 redirect_to = settings.LOGIN_REDIRECT_URL
578 redirect_url = "%s%s?%s" % (
579 get_url_host(request),
580 reverse('user_complete_associate'),
581 urllib.urlencode({redirect_field_name: redirect_to})
582 )
583 return ask_openid(request,
584 form.cleaned_data['openid_url'],
585 redirect_url,
586 on_failure=on_failure)
587 else:
588 form = openid_form(request.user)
589 return render(template_name, {
590 'form': form,
591 redirect_field_name: redirect_to
592 }, context_instance=_build_context(request, extra_context=extra_context))
593
594 @login_required
595 -def dissociate(request, template_name="authopenid/dissociate.html",
596 dissociate_form=OpenidDissociateForm,
597 redirect_field_name=REDIRECT_FIELD_NAME,
598 default_redirect=settings.LOGIN_REDIRECT_URL, extra_context=None):
599
600 """ view used to dissociate an openid from an account """
601 redirect_to = request.REQUEST.get(redirect_field_name, '')
602 if not redirect_to or '//' in redirect_to or ' ' in redirect_to:
603 redirect_to = default_redirect
604
605
606 rels = UserAssociation.objects.filter(user__id=request.user.id)
607 associated_openids = [rel.openid_url for rel in rels]
608 if len(associated_openids) == 1 and not request.user.has_usable_password():
609 msg = _("You can't remove this openid. "
610 "You should set a password first.")
611 return HttpResponseRedirect("%s?%s" % (redirect_to,
612 urllib.urlencode({"msg": msg})))
613
614 if request.POST:
615 form = dissociate_form(request.POST)
616 if form.is_valid():
617 openid_url = form.cleaned_data['openid_url']
618 msg = ""
619 if openid_url not in associated_openids:
620 msg = _("%s is not associated to your account") % openid_url
621
622 if not msg:
623 UserAssociation.objects.get(openid_url__exact=openid_url).delete()
624 if openid_url == request.session.get('openid_url'):
625 del request.session['openid_url']
626 msg = _("openid removed.")
627 return HttpResponseRedirect("%s?%s" % (redirect_to,
628 urllib.urlencode({"msg": msg})))
629 else:
630 openid_url = request.GET.get('openid_url', '')
631 if not openid_url:
632 msg = _("Invalid OpenID url.")
633 return HttpResponseRedirect("%s?%s" % (redirect_to,
634 urllib.urlencode({"msg": msg})))
635 form = dissociate_form(initial={'openid_url': openid_url})
636 return render(template_name, {
637 "form": form,
638 "openid_url": openid_url
639 }, context_instance=_build_context(request, extra_context=extra_context))
640