Call navigator.id.logout() when removing browserid_user email
[libravatar:libravatar.git] / libravatar / account / views.py
1 # Copyright (C) 2011  Francois Marier <francois@libravatar.org>
2 # Copyright (C) 2010  Francois Marier <francois@libravatar.org>
3 #                     Jonathan Harker <jon@jon.geek.nz>
4 #                     Brett Wilkins <bushido.katana@gmail.com>
5 #
6 # This file is part of Libravatar
7 #
8 # Libravatar is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Affero General Public License as published
10 # by the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # Libravatar is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU Affero General Public License for more details.
17 #
18 # You should have received a copy of the GNU Affero General Public License
19 # along with Libravatar.  If not, see <http://www.gnu.org/licenses/>.
20
21 from gearman import libgearman
22 from hashlib import sha256
23 import json
24 from openid import oidutil
25 from openid.consumer import consumer
26 import os
27 from StringIO import StringIO
28
29 from django_openid_auth.models import UserOpenID
30 from django.core.files import File
31 from django.core.urlresolvers import reverse
32 from django.contrib.auth import authenticate, login, logout
33 from django.contrib.auth.decorators import login_required
34 from django.contrib.auth.models import User
35 from django.contrib.auth.forms import SetPasswordForm, UserCreationForm
36 from django.db import transaction
37 from django.http import HttpResponse, HttpResponseRedirect
38 from django.shortcuts import render_to_response
39 from django.template import RequestContext
40 from django.views.decorators.csrf import csrf_protect, csrf_exempt
41
42 from libravatar.account.browserid_auth import verify_assertion
43 from libravatar.account.forms import AddEmailForm, AddOpenIdForm, DeleteAccountForm, PasswordResetForm, UploadPhotoForm
44 from libravatar.account.models import ConfirmedEmail, UnconfirmedEmail, ConfirmedOpenId, UnconfirmedOpenId, DjangoOpenIDStore, Photo, password_reset_key
45 from libravatar import settings
46
47
48 @transaction.commit_on_success
49 @csrf_protect
50 def new(request):
51     if settings.DISABLE_SIGNUP:
52         return HttpResponseRedirect(settings.LOGIN_URL)
53     if request.method == 'POST':
54         form = UserCreationForm(request.POST)
55         if form.is_valid():
56             form.save()
57
58             user = authenticate(username=form.cleaned_data['username'], password=form.cleaned_data['password1'])
59             if user is None:
60                 return HttpResponseRedirect(settings.LOGIN_URL)
61
62             login(request, user)
63             return HttpResponseRedirect(settings.LOGIN_REDIRECT_URL)
64     else:
65         form = UserCreationForm()
66
67     return render_to_response('account/new.html', {'form': form},
68                               context_instance=RequestContext(request))
69
70
71 # No transactions: confirmation should always work no matter what
72 @csrf_protect
73 def confirm_email(request):
74     if not 'verification_key' in request.GET:
75         return render_to_response('account/email_notconfirmed.html',
76                                   context_instance=RequestContext(request))
77
78     # be tolerant of extra crap added by mail clients
79     key = request.GET['verification_key'].replace(' ', '')
80     if len(key) != 64:
81         return render_to_response('account/email_notconfirmed.html',
82                                   context_instance=RequestContext(request))
83
84     try:
85         unconfirmed = UnconfirmedEmail.objects.get(verification_key=key)
86     except UnconfirmedEmail.DoesNotExist:
87         return render_to_response('account/email_notconfirmed.html',
88                                   context_instance=RequestContext(request))
89
90     # TODO: check for a reasonable expiration time in unconfirmed email
91
92     (confirmed_id, external_photos) = ConfirmedEmail.objects.create_confirmed_email(
93         unconfirmed.user, request.META['REMOTE_ADDR'], unconfirmed.email,
94         not request.user.is_anonymous())
95
96     unconfirmed.delete()
97
98     return render_to_response('account/email_confirmed.html',
99                               {'email_id': confirmed_id, 'photos': external_photos},
100                               context_instance=RequestContext(request))
101
102
103 @transaction.commit_on_success
104 @csrf_protect
105 def import_photo(request, user_id):
106     if request.method == 'POST':
107         if not 'email_id' in request.POST:
108             return render_to_response('account/photos_notimported.html',
109                                       context_instance=RequestContext(request))
110
111         try:
112             user = User.objects.get(id=user_id)
113         except User.DoesNotExist:
114             return render_to_response('account/photos_notimported.html',
115                                       context_instance=RequestContext(request))
116         try:
117             email = ConfirmedEmail.objects.get(id=request.POST['email_id'], user=user)
118         except ConfirmedEmail.DoesNotExist:
119             return render_to_response('account/photos_notimported.html',
120                                       context_instance=RequestContext(request))
121
122         photos_to_import = False  # are there photos to import at all?
123         photos_imported = False
124
125         if 'photo_Identica' in request.POST:
126             photos_to_import = True
127             p = Photo()
128             p.user = user
129             p.ip_address = request.META['REMOTE_ADDR']
130             if p.import_image('Identica', email.email):
131                 photos_imported = True
132
133         if 'photo_Gravatar' in request.POST:
134             photos_to_import = True
135             p = Photo()
136             p.user = user
137             p.ip_address = request.META['REMOTE_ADDR']
138             if p.import_image('Gravatar', email.email):
139                 photos_imported = True
140
141         if photos_imported:
142             return render_to_response('account/photos_imported.html',
143                                       context_instance=RequestContext(request))
144         elif photos_to_import:
145             return render_to_response('account/photos_notimported.html',
146                                       context_instance=RequestContext(request))
147
148     return HttpResponseRedirect(reverse('libravatar.account.views.profile'))
149
150
151 @transaction.commit_on_success
152 @login_required
153 def successfully_authenticated(request):
154     if request.user.ldap_user:
155         try:
156             confirmed = ConfirmedEmail.objects.get(email=request.user.email)
157         except ConfirmedEmail.DoesNotExist:
158             confirmed = ConfirmedEmail()
159             confirmed.user = request.user
160             confirmed.email = request.user.email
161             confirmed.save()
162
163             # remove unconfirmed email address if necessary
164             try:
165                 unconfirmed = UnconfirmedEmail.objects.get(email=request.user.email)
166                 unconfirmed.delete()
167             except UnconfirmedEmail.DoesNotExist:
168                 pass
169
170             # add photo to database, bung LDAP photo into the expected file
171             photo_contents = request.user.ldap_user.attrs[settings.AUTH_LDAP_USER_PHOTO][0]
172             fp = StringIO(photo_contents)  # file pointer to in-memory string buffer
173             image = File(fp)
174             p = Photo()
175             p.user = request.user
176             p.save(image)
177             return HttpResponseRedirect(reverse('libravatar.account.views.crop_photo', args=[p.id]))
178
179     return HttpResponseRedirect(reverse('libravatar.account.views.profile'))
180
181
182 def _confirm_claimed_openid(user, remote_address):
183     if user.password != u'!':
184         return  # not using OpenID auth
185
186     openids = UserOpenID.objects.filter(user=user)
187     if openids.count() != 1:
188         return  # only the first OpenID needs to be confirmed this way
189
190     claimed_id = openids[0].claimed_id
191     if ConfirmedOpenId.objects.filter(openid=claimed_id).exists():
192         return  # already confirmed (by this user or someone else)
193
194     # confirm the claimed ID for the logged in user
195     confirmed = ConfirmedOpenId()
196     confirmed.user = user
197     confirmed.ip_address = remote_address
198     confirmed.openid = claimed_id
199     confirmed.save()
200
201
202 @csrf_protect
203 @login_required
204 def profile(request):
205     u = request.user
206     _confirm_claimed_openid(u, request.META['REMOTE_ADDR'])
207
208     confirmed_emails = u.confirmed_emails.order_by('email')
209     unconfirmed_emails = u.unconfirmed_emails.order_by('email')
210     confirmed_openids = u.confirmed_openids.order_by('openid')
211     unconfirmed_openids = u.unconfirmed_openids.order_by('openid')
212     photos = u.photos.order_by('add_date')
213     max_photos = len(photos) >= settings.MAX_NUM_PHOTOS
214     max_emails = len(unconfirmed_emails) >= settings.MAX_NUM_UNCONFIRMED_EMAILS
215
216     # force evaluation of the QuerySet objects
217     list(confirmed_emails)
218     list(unconfirmed_emails)
219     list(confirmed_openids)
220     list(unconfirmed_openids)
221     list(photos)
222
223     has_password = request.user.password != u'!'
224     return render_to_response('account/profile.html',
225                               {'confirmed_emails': confirmed_emails, 'unconfirmed_emails': unconfirmed_emails,
226                                'confirmed_openids': confirmed_openids, 'unconfirmed_openids': unconfirmed_openids,
227                                'photos': photos, 'max_photos': max_photos, 'max_emails': max_emails,
228                                'has_password': has_password, 'sender_email': settings.SERVER_EMAIL},
229                               context_instance=RequestContext(request))
230
231
232 def openid_logging(message, level=0):
233     # Normal messages are not that important
234     if (level > 0):
235         print message
236
237
238 @transaction.commit_on_success
239 @csrf_protect
240 @login_required
241 def add_openid(request):
242     if request.method == 'POST':
243         form = AddOpenIdForm(request.POST)
244         if form.is_valid():
245             openid_id = form.save(request.user)
246             if not openid_id:
247                 return render_to_response('account/openid_notadded.html',
248                                           context_instance=RequestContext(request))
249
250             return render_to_response('account/add_openid_redirection.html', {'unconfirmed_id': openid_id},
251                                       context_instance=RequestContext(request))
252     else:
253         form = AddOpenIdForm()
254
255     return render_to_response('account/add_openid.html', {'form': form},
256                               RequestContext(request))
257
258
259 # CSRF check not possible (using a meta redirect)
260 @login_required
261 def redirect_openid(request, openid_id):
262     try:
263         unconfirmed = UnconfirmedOpenId.objects.get(id=openid_id, user=request.user)
264     except UnconfirmedOpenId.DoesNotExist:
265         return render_to_response('account/openid_confirmationfailed.html',
266                                   {'message': 'ID %s not found in the database.' % openid_id},
267                                   context_instance=RequestContext(request))
268
269     user_url = unconfirmed.openid
270     session = {'id': request.session.session_key}
271
272     oidutil.log = openid_logging
273     openid_consumer = consumer.Consumer(session, DjangoOpenIDStore())
274
275     try:
276         auth_request = openid_consumer.begin(user_url)
277     except consumer.DiscoveryFailure as exception:
278         print "OpenID discovery failed (userid=%s) for %s" % (request.user.id, user_url)
279         return render_to_response('account/openid_discoveryfailure.html', {'message': exception},
280                                   context_instance=RequestContext(request))
281     except UnicodeDecodeError as exception:
282         print "OpenID discovery failed (userid=%s) for %s" % (request.user.id, user_url)
283         return render_to_response('account/openid_discoveryfailure.html', {'message': exception},
284                                   context_instance=RequestContext(request))
285
286     if auth_request is None:
287         return render_to_response('account/openid_discoveryfailure', {'message': '(unknown error)'},
288                                   context_instance=RequestContext(request))
289
290     realm = settings.SITE_URL
291     return_url = realm + reverse('libravatar.account.views.confirm_openid', args=[openid_id])
292
293     return HttpResponseRedirect(auth_request.redirectURL(realm, return_url))
294
295
296 # No transactions: confirmation should always work no matter what
297 # CSRF check not needed (OpenID return URL)
298 @login_required
299 def confirm_openid(request, openid_id):
300
301     session = {'id': request.session.session_key}
302     current_url = settings.SITE_URL + request.path
303
304     oidutil.log = openid_logging
305     openid_consumer = consumer.Consumer(session, DjangoOpenIDStore())
306
307     if request.method == 'POST':
308         info = openid_consumer.complete(request.POST, current_url)
309     else:
310         info = openid_consumer.complete(request.GET, current_url)
311
312     if info.status == consumer.FAILURE:
313         return render_to_response('account/openid_confirmationfailed.html', {'message': info.message},
314                                   context_instance=RequestContext(request))
315     elif info.status == consumer.CANCEL:
316         return render_to_response('account/openid_confirmationfailed.html', {'message': '(cancelled by user)'},
317                                   context_instance=RequestContext(request))
318     elif info.status != consumer.SUCCESS:
319         return render_to_response('account/openid_confirmationfailed.html', {'message': '(unknown verification error)'},
320                                   context_instance=RequestContext(request))
321
322     try:
323         unconfirmed = UnconfirmedOpenId.objects.get(id=openid_id, user=request.user)
324     except UnconfirmedOpenId.DoesNotExist:
325         return render_to_response('account/openid_confirmationfailed.html',
326                                   {'message': 'ID %s not found in the database.' % openid_id},
327                                   context_instance=RequestContext(request))
328
329     # TODO: check for a reasonable expiration time
330     confirmed = ConfirmedOpenId()
331     confirmed.user = unconfirmed.user
332     confirmed.ip_address = request.META['REMOTE_ADDR']
333     confirmed.openid = unconfirmed.openid
334     confirmed.save()
335
336     unconfirmed.delete()
337
338     # Also allow user to login using this OpenID (if not taken already)
339     if not UserOpenID.objects.filter(claimed_id=confirmed.openid).exists():
340         user_openid = UserOpenID()
341         user_openid.user = request.user
342         user_openid.claimed_id = confirmed.openid
343         user_openid.display_id = confirmed.openid
344         user_openid.save()
345
346     return HttpResponseRedirect(reverse('libravatar.account.views.profile'))
347
348
349 @transaction.commit_on_success
350 @csrf_protect
351 @login_required
352 def remove_confirmed_openid(request, openid_id):
353     if request.method == 'POST':
354         try:
355             openid = ConfirmedOpenId.objects.get(id=openid_id, user=request.user)
356         except ConfirmedOpenId.DoesNotExist:
357             return render_to_response('account/openid_invalid.html',
358                                       context_instance=RequestContext(request))
359
360         has_password = request.user.password != u'!'
361         if has_password or UserOpenID.objects.filter(user=request.user).count() > 1:
362             # remove it from the auth table as well
363             UserOpenID.objects.filter(claimed_id=openid.openid).delete()
364             openid.delete()
365         else:
366             return render_to_response('account/openid_cannotdelete.html',
367                                       context_instance=RequestContext(request))
368
369     return HttpResponseRedirect(reverse('libravatar.account.views.profile'))
370
371
372 @transaction.commit_on_success
373 @csrf_protect
374 @login_required
375 def remove_unconfirmed_openid(request, openid_id):
376     if request.method == 'POST':
377         try:
378             openid = UnconfirmedOpenId.objects.get(id=openid_id, user=request.user)
379         except UnconfirmedOpenId.DoesNotExist:
380             return render_to_response('account/openid_invalid.html',
381                                       context_instance=RequestContext(request))
382
383         openid.delete()
384
385     return HttpResponseRedirect(reverse('libravatar.account.views.profile'))
386
387
388 @transaction.commit_on_success
389 @csrf_protect
390 @login_required
391 def add_email(request):
392     if request.method == 'POST':
393         form = AddEmailForm(request.POST)
394         if form.is_valid():
395             if not form.save(request.user):
396                 return render_to_response('account/email_notadded.html',
397                                           {'max_emails': settings.MAX_NUM_UNCONFIRMED_EMAILS},
398                                           context_instance=RequestContext(request))
399             return HttpResponseRedirect(reverse('libravatar.account.views.profile'))
400     else:
401         form = AddEmailForm()
402
403     return render_to_response('account/add_email.html', {'form': form},
404                               RequestContext(request))
405
406
407 @transaction.commit_on_success
408 @csrf_protect
409 @login_required
410 def remove_confirmed_email(request, email_id):
411     if request.method == 'POST':
412         try:
413             email = ConfirmedEmail.objects.get(id=email_id, user=request.user)
414         except ConfirmedEmail.DoesNotExist:
415             return render_to_response('account/email_invalid.html',
416                                       context_instance=RequestContext(request))
417
418         email.delete()
419         if 'browserid_user' in request.session and request.session['browserid_user'] == email.email:
420             # Since we are removing the email to which the BrowserID session is tied,
421             # we need to convert the session to a non-BrowserID session
422             del(request.session['browserid_user'])
423
424     return HttpResponseRedirect(reverse('libravatar.account.views.profile'))
425
426
427 @transaction.commit_on_success
428 @csrf_protect
429 @login_required
430 def remove_unconfirmed_email(request, email_id):
431     if request.method == 'POST':
432         try:
433             email = UnconfirmedEmail.objects.get(id=email_id, user=request.user)
434         except UnconfirmedEmail.DoesNotExist:
435             return render_to_response('account/email_invalid.html', context_instance=RequestContext(request))
436
437         email.delete()
438
439     return HttpResponseRedirect(reverse('libravatar.account.views.profile'))
440
441
442 @transaction.commit_on_success
443 @csrf_protect
444 @login_required
445 def upload_photo(request):
446     num_photos = request.user.photos.count()
447     if num_photos >= settings.MAX_NUM_PHOTOS:
448         return render_to_response('account/max_photos.html', context_instance=RequestContext(request))
449
450     if request.method == 'POST':
451         form = UploadPhotoForm(request.POST, request.FILES)
452         if form.is_valid():
453             photo_data = request.FILES['photo']
454             if photo_data.size > settings.MAX_PHOTO_SIZE:
455                 return render_to_response('account/photo_toobig.html', {'max_size': settings.MAX_PHOTO_SIZE},
456                                           context_instance=RequestContext(request))
457
458             photo = form.save(request.user, request.META['REMOTE_ADDR'], photo_data)
459             return HttpResponseRedirect(reverse('libravatar.account.views.crop_photo', args=[photo.id]))
460     else:
461         form = UploadPhotoForm()
462
463     return render_to_response('account/upload_photo.html', {'form': form, 'max_file_size': settings.MAX_PHOTO_SIZE},
464                               context_instance=RequestContext(request))
465
466
467 @csrf_protect
468 @login_required
469 def crop_photo(request, photo_id):
470     try:
471         photo = Photo.objects.get(id=photo_id, user=request.user)
472     except Photo.DoesNotExist:
473         return render_to_response('account/photo_invalid.html',
474                                   context_instance=RequestContext(request))
475
476     photo_file = settings.UPLOADED_FILES_ROOT + photo.full_filename()
477     if not os.path.exists(photo_file):
478         return render_to_response('account/uploaded_photo_missing.html',
479                                   context_instance=RequestContext(request))
480
481     if request.method == 'POST':
482         x = int(request.POST['x'])
483         y = int(request.POST['y'])
484         w = int(request.POST['w'])
485         h = int(request.POST['h'])
486         photo.crop(x, y, w, h)
487         return HttpResponseRedirect(reverse('libravatar.account.views.profile'))
488
489     return render_to_response('account/crop_photo.html', {'photo': photo},
490                               context_instance=RequestContext(request))
491
492
493 @login_required
494 def auto_crop(request, photo_id):
495     try:
496         photo = Photo.objects.get(id=photo_id, user=request.user)
497     except Photo.DoesNotExist:
498         return render_to_response('account/photo_invalid.html',
499                                   context_instance=RequestContext(request))
500
501     photo.crop()
502     return HttpResponseRedirect(reverse('libravatar.account.views.profile'))
503
504
505 @transaction.commit_on_success
506 @csrf_protect
507 @login_required
508 def delete_photo(request, photo_id):
509     try:
510         photo = Photo.objects.get(id=photo_id, user=request.user)
511     except Photo.DoesNotExist:
512         return render_to_response('account/photo_invalid.html', context_instance=RequestContext(request))
513
514     if request.method == 'POST':
515         photo.delete()
516         return HttpResponseRedirect(reverse('libravatar.account.views.profile'))
517
518     return render_to_response('account/delete_photo.html', {'photo': photo},
519                               context_instance=RequestContext(request))
520
521
522 def _assign_photo(request, identifier_type, identifier):
523     if request.method == 'POST':
524         photo = None
525         if 'photo_id' in request.POST and request.POST['photo_id']:
526             try:
527                 photo = Photo.objects.get(id=request.POST['photo_id'], user=request.user)
528             except Photo.DoesNotExist:
529                 return render_to_response('account/photo_invalid.html',
530                                           context_instance=RequestContext(request))
531
532         identifier.set_photo(photo)
533         return HttpResponseRedirect(reverse('libravatar.account.views.profile'))
534
535     photos = request.user.photos.order_by('add_date')
536     list(photos)  # force evaluation of the QuerySet
537     return render_to_response('account/assign_photo_%s.html' % identifier_type, {'photos': photos, identifier_type: identifier},
538                               context_instance=RequestContext(request))
539
540
541 @transaction.commit_on_success
542 @csrf_protect
543 @login_required
544 def assign_photo_email(request, email_id):
545     try:
546         email = ConfirmedEmail.objects.get(id=email_id, user=request.user)
547     except ConfirmedEmail.DoesNotExist:
548         return render_to_response('account/email_invalid.html',
549                                   context_instance=RequestContext(request))
550
551     return _assign_photo(request, 'email', email)
552
553
554 @transaction.commit_on_success
555 @csrf_protect
556 @login_required
557 def assign_photo_openid(request, openid_id):
558     try:
559         openid = ConfirmedOpenId.objects.get(id=openid_id, user=request.user)
560     except ConfirmedOpenId.DoesNotExist:
561         return render_to_response('account/openid_invalid.html',
562                                   context_instance=RequestContext(request))
563
564     return _assign_photo(request, 'openid', openid)
565
566
567 @transaction.commit_on_success
568 @csrf_protect
569 def password_reset(request):
570     if settings.DISABLE_SIGNUP:
571         return HttpResponseRedirect(settings.LOGIN_URL)
572     if request.method == 'POST':
573         form = PasswordResetForm(request.POST)
574         if form.is_valid():
575             form.save()
576             return render_to_response('account/password_reset_submitted.html',
577                                       {'form': form, 'support_email': settings.SUPPORT_EMAIL},
578                                       context_instance=RequestContext(request))
579     else:
580         form = PasswordResetForm()
581
582     return render_to_response('account/password_reset.html', {'form': form},
583                               context_instance=RequestContext(request))
584
585
586 @transaction.commit_on_success
587 @csrf_protect
588 def password_reset_confirm(request):
589     if settings.DISABLE_SIGNUP:
590         return HttpResponseRedirect(settings.LOGIN_URL)
591
592     verification_key = None
593     email_address = None
594
595     if 'verification_key' in request.GET and request.GET['verification_key']:
596         verification_key = request.GET['verification_key'].replace(' ', '')
597
598     if 'email_address' in request.GET and request.GET['email_address']:
599         email_address = request.GET['email_address']
600
601     # Note: all validation errors return the same error message to
602     # avoid leaking information as to the existence or not of
603     # particular email addresses on the system
604
605     if not verification_key or not email_address:
606         return render_to_response('account/reset_invalidparams.html',
607                                   context_instance=RequestContext(request))
608
609     if len(verification_key) < 64 or len(verification_key) > 64 or len(email_address) < 3:
610         return render_to_response('account/reset_invalidparams.html',
611                                   context_instance=RequestContext(request))
612
613     try:
614         email = ConfirmedEmail.objects.get(email=email_address)
615     except ConfirmedEmail.DoesNotExist:
616         return render_to_response('account/reset_invalidparams.html',
617                                   context_instance=RequestContext(request))
618
619     user = email.user
620     if u'!' == user.password:
621         # no password is set, cannot reset it
622         return render_to_response('account/reset_invalidparams.html',
623                                   context_instance=RequestContext(request))
624
625     expected_key = password_reset_key(user)
626     if verification_key != expected_key:
627         return render_to_response('account/reset_invalidparams.html',
628                                   context_instance=RequestContext(request))
629
630     if request.method == 'POST':
631         form = SetPasswordForm(user, request.POST)
632         if form.is_valid():
633             form.save()
634             return render_to_response('account/password_change_done.html',
635                                       context_instance=RequestContext(request))
636     else:
637         form = SetPasswordForm(user)
638
639     return render_to_response('account/password_change.html', {'form': form,
640                               'verification_key': verification_key, 'email_address': email_address},
641                               context_instance=RequestContext(request))
642
643
644 @transaction.commit_on_success
645 @csrf_protect
646 @login_required
647 def delete(request):
648     if request.method == 'POST':
649         form = DeleteAccountForm(request.user, request.POST)
650         if form.is_valid():
651             username = request.user.username
652             download_url = _perform_export(request.user, True)
653             Photo.objects.delete_user_photos(request.user)
654             request.user.delete()  # cascading through unconfirmed and confirmed emails/openids
655             logout(request)
656             return render_to_response('account/delete_done.html',
657                                       {'download_url': download_url, 'username': username},
658                                       context_instance=RequestContext(request))
659     else:
660         form = DeleteAccountForm(request.user)
661
662     has_password = request.user.password != u'!'
663     return render_to_response('account/delete.html', {'form': form, 'has_password': has_password},
664                               context_instance=RequestContext(request))
665
666
667 def _perform_export(user, do_delete):
668     file_hash = sha256(user.username + user.password).hexdigest()
669
670     emails = list(user.confirmed_emails.values_list('email', flat=True))
671     openids = list(user.confirmed_openids.values_list('openid', flat=True))
672
673     photos = []
674     for photo in user.photos.all():
675         photo_details = (photo.filename, photo.format)
676         photos.append(photo_details)
677
678     gm_client = libgearman.Client()
679     for server in settings.GEARMAN_SERVERS:
680         gm_client.add_server(server)
681
682     workload = {'do_delete': do_delete, 'file_hash': file_hash, 'username': user.username,
683                 'emails': emails, 'openids': openids, 'photos': photos}
684     gm_client.do_background('exportaccount', json.dumps(workload))
685
686     download_url = settings.EXPORT_FILES_URL + file_hash + '.xml.gz'
687     return download_url
688
689
690 @csrf_protect
691 @login_required
692 def export(request):
693     if request.method == 'POST':
694         download_url = _perform_export(request.user, False)
695         return render_to_response('account/export_done.html', {'delete': delete, 'download_url': download_url},
696                                   context_instance=RequestContext(request))
697
698     return render_to_response('account/export.html', context_instance=RequestContext(request))
699
700
701 @transaction.commit_on_success
702 @csrf_protect
703 @login_required
704 def password_set(request):
705     has_password = request.user.password != u'!'
706     if has_password or settings.DISABLE_SIGNUP:
707         return HttpResponseRedirect(reverse('libravatar.account.views.profile'))
708
709     if request.method == 'POST':
710         form = SetPasswordForm(request.user, request.POST)
711         if form.is_valid():
712             form.save()
713             return render_to_response('account/password_change_done.html',
714                                       context_instance=RequestContext(request))
715     else:
716         form = SetPasswordForm(request.user)
717
718     return render_to_response('account/password_change.html', {'form': form},
719                               context_instance=RequestContext(request))
720
721
722 @transaction.commit_on_success
723 @csrf_exempt
724 @login_required
725 def add_browserid(request):
726     if not request.method == 'POST' or not 'assertion' in request.POST:
727         return render_to_response('account/browserid_noassertion.json', mimetype='application/json',
728                                   context_instance=RequestContext(request))
729
730     (email_address, assertion_error) = verify_assertion(request.POST['assertion'], settings.SITE_URL,
731                                                         request.is_secure())
732
733     if not email_address:
734         sanitised_error = assertion_error
735         if sanitised_error:
736             sanitised_error = sanitised_error.replace('"', '')
737         return render_to_response('account/browserid_invalidassertion.json', {'error': sanitised_error},
738                                   mimetype='application/json', context_instance=RequestContext(request))
739
740     # Check whether or not the email is already confirmed by someone
741     if ConfirmedEmail.objects.filter(email=email_address).exists():
742         del(request.session['browserid_user'])
743         return render_to_response('account/browserid_emailalreadyconfirmed.json', mimetype='application/json',
744                                   context_instance=RequestContext(request))
745
746     (unused, unused) = ConfirmedEmail.objects.create_confirmed_email(
747         request.user, request.META['REMOTE_ADDR'], email_address, True)
748     request.session['browserid_user'] = email_address
749
750     return HttpResponse(json.dumps({"success": True, "user": email_address}), mimetype="application/json")
751
752
753 @transaction.commit_on_success
754 @csrf_exempt
755 def login_browserid(request):
756     if not request.method == 'POST' or not 'assertion' in request.POST:
757         return render_to_response('account/browserid_noassertion.json', mimetype='application/json',
758                                   context_instance=RequestContext(request))
759
760     user = authenticate(assertion=request.POST['assertion'], host=settings.SITE_URL,
761                         https=request.is_secure(), ip_address=request.META['REMOTE_ADDR'],
762                         session=request.session)
763     if not user:
764         return render_to_response('account/browserid_userauthfailed.json', mimetype='application/json',
765                                   context_instance=RequestContext(request))
766
767     login(request, user)
768     return HttpResponse(json.dumps({"success": True, "user": request.session['browserid_user']}), mimetype="application/json")