]> git.openstreetmap.org Git - osqa.git/blob - forum/views/auth.py
9e7d6c6f828e92d43d76a79dfe90a18e712911d8
[osqa.git] / forum / views / auth.py
1 # -*- coding: utf-8 -*-
2
3 import datetime
4 import logging
5 import urllib
6
7 from django.shortcuts import render_to_response, get_object_or_404
8 from django.template import RequestContext
9 from django.core.urlresolvers import reverse
10 from django.http import HttpResponseRedirect, Http404
11 from django.utils.safestring import mark_safe
12 from django.utils.translation import ugettext as _
13 from django.utils.encoding import smart_unicode
14 from django.contrib.auth import login, logout
15
16 from writers import manage_pending_data
17
18 from forum.actions import EmailValidationAction
19 from forum.utils import html
20 from forum.views.decorators import login_required
21 from forum.modules import decorate
22 from forum.forms import SimpleRegistrationForm, TemporaryLoginRequestForm, ChangePasswordForm, SetPasswordForm
23 from forum.http_responses import HttpResponseUnauthorized
24 from forum.utils.mail import send_template_email
25 from forum.authentication.base import InvalidAuthentication
26 from forum.authentication import AUTH_PROVIDERS
27 from forum.models import User, AuthKeyUserAssociation, ValidationHash
28 from forum.actions import UserJoinsAction, UserLoginAction
29 from forum import settings
30
31 from vars import ON_SIGNIN_SESSION_ATTR, PENDING_SUBMISSION_SESSION_ATTR
32
33 def signin_page(request):
34     referer = request.META.get('HTTP_REFERER', '/')
35
36     # If the referer is equal to the sign up page, e. g. if the previous login attempt was not successful we do not
37     # change the sign in URL. The user should go to the same page.
38     if not referer.replace(settings.APP_URL, '') == reverse('auth_signin'):
39         request.session[ON_SIGNIN_SESSION_ATTR] = referer
40
41     all_providers = [provider.context for provider in AUTH_PROVIDERS.values() if provider.context]
42
43     sort = lambda c1, c2: c1.weight - c2.weight
44     can_show = lambda c: not request.user.is_authenticated() or c.show_to_logged_in_user
45
46     bigicon_providers = sorted([
47     context for context in all_providers if context.mode == 'BIGICON' and can_show(context)
48     ], sort)
49
50     smallicon_providers = sorted([
51     context for context in all_providers if context.mode == 'SMALLICON' and can_show(context)
52     ], sort)
53
54     top_stackitem_providers = sorted([
55     context for context in all_providers if context.mode == 'TOP_STACK_ITEM' and can_show(context)
56     ], sort)
57
58     stackitem_providers = sorted([
59     context for context in all_providers if context.mode == 'STACK_ITEM' and can_show(context)
60     ], sort)
61
62     try:
63         msg = request.session['auth_error']
64         del request.session['auth_error']
65     except:
66         msg = None
67
68     return render_to_response(
69             'auth/signin.html',
70             {
71             'msg': msg,
72             'all_providers': all_providers,
73             'bigicon_providers': bigicon_providers,
74             'top_stackitem_providers': top_stackitem_providers,
75             'stackitem_providers': stackitem_providers,
76             'smallicon_providers': smallicon_providers,
77             },
78             RequestContext(request))
79
80 def prepare_provider_signin(request, provider):
81     force_email_request = request.REQUEST.get('validate_email', 'yes') == 'yes'
82     request.session['force_email_request'] = force_email_request
83
84     if provider in AUTH_PROVIDERS:
85         provider_class = AUTH_PROVIDERS[provider].consumer
86
87         try:
88             request_url = provider_class.prepare_authentication_request(request,
89                                                                         reverse('auth_provider_done',
90                                                                                 kwargs={'provider': provider}))
91
92             return HttpResponseRedirect(request_url)
93         except NotImplementedError, e:
94             return process_provider_signin(request, provider)
95         except InvalidAuthentication, e:
96             request.session['auth_error'] = e.message
97
98         return HttpResponseRedirect(reverse('auth_signin'))
99     else:
100         raise Http404()
101
102
103 def process_provider_signin(request, provider):
104     if provider in AUTH_PROVIDERS:
105         provider_class = AUTH_PROVIDERS[provider].consumer
106
107         try:
108             assoc_key = provider_class.process_authentication_request(request)
109         except InvalidAuthentication, e:
110             request.session['auth_error'] = e.message
111             return HttpResponseRedirect(reverse('auth_signin'))
112
113         if request.user.is_authenticated():
114             if isinstance(assoc_key, (type, User)):
115                 if request.user != assoc_key:
116                     request.session['auth_error'] = _(
117                             "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
118                             )
119                 else:
120                     request.session['auth_error'] = _("You are already logged in with that user.")
121             else:
122                 try:
123                     assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
124                     if assoc.user == request.user:
125                         request.session['auth_error'] = _(
126                                 "These login credentials are already associated with your account.")
127                     else:
128                         request.session['auth_error'] = _(
129                                 "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
130                                 )
131                 except:
132                     uassoc = AuthKeyUserAssociation(user=request.user, key=assoc_key, provider=provider)
133                     uassoc.save()
134                     request.user.message_set.create(
135                             message=_('The new credentials are now associated with your account'))
136                     return HttpResponseRedirect(reverse('user_authsettings', args=[request.user.id]))
137
138             return HttpResponseRedirect(reverse('auth_signin'))
139         else:
140             if isinstance(assoc_key, User):
141                 return login_and_forward(request, assoc_key)
142
143         try:
144             assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
145             user_ = assoc.user
146             return login_and_forward(request, user_)
147         except AuthKeyUserAssociation.DoesNotExist:
148             request.session['assoc_key'] = assoc_key
149             request.session['auth_provider'] = provider
150             return HttpResponseRedirect(reverse('auth_external_register'))
151
152     return HttpResponseRedirect(reverse('auth_signin'))
153
154 def external_register(request):
155     if request.method == 'POST' and 'bnewaccount' in request.POST:
156         form1 = SimpleRegistrationForm(request.POST)
157
158         if form1.is_valid():
159             user_ = User(username=form1.cleaned_data['username'], email=form1.cleaned_data['email'])
160             user_.email_isvalid = request.session.get('auth_validated_email', '') == form1.cleaned_data['email']
161             user_.set_unusable_password()
162
163             if User.objects.all().count() == 0:
164                 user_.is_superuser = True
165                 user_.is_staff = True
166
167             user_.save()
168             UserJoinsAction(user=user_, ip=request.META['REMOTE_ADDR']).save()
169
170             try:
171                 assoc_key = request.session['assoc_key']
172                 auth_provider = request.session['auth_provider']
173             except:
174                 request.session['auth_error'] = _(
175                         "Oops, something went wrong in the middle of this process. Please try again. Note that you need to have cookies enabled for the authentication to work."
176                         )
177                 logging.error("Missing session data when trying to complete user registration: %s" % ", ".join(
178                         ["%s: %s" % (k, v) for k, v in request.META.items()]))
179                 return HttpResponseRedirect(reverse('auth_signin'))
180
181             uassoc = AuthKeyUserAssociation(user=user_, key=assoc_key, provider=auth_provider)
182             uassoc.save()
183
184             del request.session['assoc_key']
185             del request.session['auth_provider']
186
187             return login_and_forward(request, user_, message=_("A welcome email has been sent to your email address. "))
188     else:
189         auth_provider = request.session.get('auth_provider', None)
190         if not auth_provider:
191             request.session['auth_error'] = _(
192                     "Oops, something went wrong in the middle of this process. Please try again.")
193             logging.error("Missing session data when trying to complete user registration: %s" % ", ".join(
194                     ["%s: %s" % (k, v) for k, v in request.META.items()]))
195             return HttpResponseRedirect(reverse('auth_signin'))
196
197         provider_class = AUTH_PROVIDERS[auth_provider].consumer
198
199         user_data = provider_class.get_user_data(request.session['assoc_key'])
200
201         if not user_data:
202             user_data = request.session.get('auth_consumer_data', {})
203
204         username = user_data.get('username', '')
205         email = user_data.get('email', '')
206
207         if email:
208             request.session['auth_validated_email'] = email
209
210         form1 = SimpleRegistrationForm(initial={
211         'next': '/',
212         'username': username,
213         'email': email,
214         })
215
216     provider_context = AUTH_PROVIDERS[request.session['auth_provider']].context
217
218     return render_to_response('auth/complete.html', {
219     'form1': form1,
220     'provider':provider_context and mark_safe(provider_context.human_name) or _('unknown'),
221     'login_type':provider_context.id,
222     'gravatar_faq_url':reverse('faq') + '#gravatar',
223     }, context_instance=RequestContext(request))
224
225 def request_temp_login(request):
226     if request.method == 'POST':
227         form = TemporaryLoginRequestForm(request.POST)
228
229         if form.is_valid():
230             users = form.user_cache
231
232             for u in users:
233                 if u.is_suspended():
234                     return forward_suspended_user(request, u, False)
235
236             for u in users:
237                 try:
238                     hash = get_object_or_404(ValidationHash, user=u, type='templogin')
239                     if hash.expiration < datetime.datetime.now():
240                         hash.delete()
241                         return request_temp_login(request)
242                 except:
243                     hash = ValidationHash.objects.create_new(u, 'templogin', [u.id])
244
245                 send_template_email([u], "auth/temp_login_email.html", {'temp_login_code': hash})
246
247                 request.user.message_set.create(message=_("An email has been sent with your temporary login key"))
248
249             return HttpResponseRedirect(reverse('index'))
250     else:
251         form = TemporaryLoginRequestForm()
252
253     return render_to_response(
254             'auth/temp_login_request.html', {'form': form},
255             context_instance=RequestContext(request))
256
257 def temp_signin(request, user, code):
258     user = get_object_or_404(User, id=user)
259
260     if (ValidationHash.objects.validate(code, user, 'templogin', [user.id])):
261         
262         # If the user requests temp_signin he must have forgotten his password. So we mark it as unusable.
263         user.set_unusable_password()
264         user.save()
265         
266         return login_and_forward(request, user, reverse('user_authsettings', kwargs={'id': user.id}),
267                                  _(
268                                          "You are logged in with a temporary access key, please take the time to fix your issue with authentication."
269                                          ))
270     else:
271         raise Http404()
272
273 def send_validation_email(request):
274     if not request.user.is_authenticated():
275         return HttpResponseUnauthorized(request)
276     else:
277         # We check if there are some old validation hashes. If there are -- we delete them.
278         try:
279             hash = ValidationHash.objects.get(user=request.user, type='email')
280             hash.delete()
281         except:
282             pass
283
284         # We don't care if there are previous cashes in the database... In every case we have to create a new one
285         hash = ValidationHash.objects.create_new(request.user, 'email', [request.user.email])
286
287         additional_get_params = urllib.urlencode(request.GET)
288         send_template_email([request.user], "auth/mail_validation.html", {
289             'validation_code': hash,
290             'additional_get_params' : additional_get_params
291         })
292
293         request.user.message_set.create(message=_("A message with an email validation link was just sent to your address."))
294         return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
295
296         
297
298 def validate_email(request, user, code):
299     user = get_object_or_404(User, id=user)
300
301     if (ValidationHash.objects.validate(code, user, 'email', [user.email])):
302         EmailValidationAction(user=user, ip=request.META['REMOTE_ADDR']).save()
303         return login_and_forward(request, user, reverse('index'), _("Thank you, your email is now validated."))
304     else:
305         return render_to_response('auth/mail_already_validated.html', { 'user' : user }, RequestContext(request))
306
307 def auth_settings(request, id):
308     user_ = get_object_or_404(User, id=id)
309
310     if not (request.user.is_superuser or request.user == user_):
311         return HttpResponseUnauthorized(request)
312
313     auth_keys = user_.auth_keys.all()
314
315     if request.user.is_superuser or (not user_.has_usable_password()):
316         FormClass = SetPasswordForm
317     else:
318         FormClass = ChangePasswordForm
319
320     if request.POST:
321         form = FormClass(request.POST, user=user_)
322         if form.is_valid():
323             is_new_pass = not user_.has_usable_password()
324             user_.set_password(form.cleaned_data['password1'])
325             user_.save()
326
327             if is_new_pass:
328                 request.user.message_set.create(message=_("New password set"))
329                 if not request.user.is_superuser:
330                     form = ChangePasswordForm(user=user_)
331             else:
332                 request.user.message_set.create(message=_("Your password was changed"))
333
334             return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': user_.id}))
335     else:
336         form = FormClass(user=user_)
337
338     auth_keys_list = []
339
340     for k in auth_keys:
341         provider = AUTH_PROVIDERS.get(k.provider, None)
342
343         if provider is not None:
344             name =  "%s: %s" % (provider.context.human_name, provider.context.readable_key(k))
345         else:
346             from forum.authentication.base import ConsumerTemplateContext
347             "unknown: %s" % ConsumerTemplateContext.readable_key(k)
348
349         auth_keys_list.append({
350         'name': name,
351         'id': k.id
352         })
353
354     return render_to_response('auth/auth_settings.html', {
355     'view_user': user_,
356     "can_view_private": (user_ == request.user) or request.user.is_superuser,
357     'form': form,
358     'has_password': user_.has_usable_password(),
359     'auth_keys': auth_keys_list,
360     'allow_local_auth': AUTH_PROVIDERS.get('local', None),
361     }, context_instance=RequestContext(request))
362
363 def remove_external_provider(request, id):
364     association = get_object_or_404(AuthKeyUserAssociation, id=id)
365     if not (request.user.is_superuser or request.user == association.user):
366         return HttpResponseUnauthorized(request)
367
368     request.user.message_set.create(message=_("You removed the association with %s") % association.provider)
369     association.delete()
370     return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': association.user.id}))
371
372 def login_and_forward(request, user, forward=None, message=None):
373     if user.is_suspended():
374         return forward_suspended_user(request, user)
375
376     user.backend = "django.contrib.auth.backends.ModelBackend"
377     login(request, user)
378
379     # Store the login action
380     UserLoginAction(user=user, ip=request.META['REMOTE_ADDR']).save()
381
382     if message is None:
383         message = _("Welcome back %s, you are now logged in") % smart_unicode(user.username)
384
385     request.user.message_set.create(message=message)
386
387     if not forward:
388         forward = request.session.get(ON_SIGNIN_SESSION_ATTR, reverse('index'))
389
390     pending_data = request.session.get(PENDING_SUBMISSION_SESSION_ATTR, None)
391
392     if pending_data and (user.email_isvalid or pending_data['type'] not in settings.REQUIRE_EMAIL_VALIDATION_TO):
393         submission_time = pending_data['time']
394         if submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.HOLD_PENDING_POSTS_MINUTES)):
395             del request.session[PENDING_SUBMISSION_SESSION_ATTR]
396         elif submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.WARN_PENDING_POSTS_MINUTES)):
397             user.message_set.create(message=(_("You have a %s pending submission.") % pending_data['data_name']) + " %s, %s, %s" % (
398                 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('save')}), _("save it")),
399                 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('review')}), _("review")),
400                 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('cancel')}), _("cancel"))
401             ))
402         else:
403             return manage_pending_data(request, _('save'), forward)
404
405     additional_get_params = urllib.urlencode(request.GET)
406     return HttpResponseRedirect(forward + "?%s" % additional_get_params)
407
408 def forward_suspended_user(request, user, show_private_msg=True):
409     message = _("Sorry, but this account is suspended")
410     if show_private_msg:
411         msg_type = 'privatemsg'
412     else:
413         msg_type = 'publicmsg'
414
415     suspension = user.suspension
416     if suspension:
417         message += (":<br />" + suspension.extra.get(msg_type, ''))
418
419     request.user.message_set.create(message)
420     return HttpResponseRedirect(reverse('index'))
421
422 @decorate.withfn(login_required)
423 def signout(request):
424     logout(request)
425     return HttpResponseRedirect(reverse('index'))