]> git.openstreetmap.org Git - osqa.git/blob - forum/views/auth.py
some facebook oauth 2.0 fixes, use user id for following association, pass the access...
[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         if provider_class.__class__.__name__ == 'FacebookAuthConsumer':
200             user_data = provider_class.get_user_data(request.session['access_token'])
201         else:
202             user_data = provider_class.get_user_data(request.session['assoc_key'])
203
204
205         if not user_data:
206             user_data = request.session.get('auth_consumer_data', {})
207
208         username = user_data.get('username', '')
209         email = user_data.get('email', '')
210
211         if email:
212             request.session['auth_validated_email'] = email
213
214         form1 = SimpleRegistrationForm(initial={
215         'next': '/',
216         'username': username,
217         'email': email,
218         })
219
220     provider_context = AUTH_PROVIDERS[request.session['auth_provider']].context
221
222     return render_to_response('auth/complete.html', {
223     'form1': form1,
224     'provider':provider_context and mark_safe(provider_context.human_name) or _('unknown'),
225     'login_type':provider_context.id,
226     'gravatar_faq_url':reverse('faq') + '#gravatar',
227     }, context_instance=RequestContext(request))
228
229 def request_temp_login(request):
230     if request.method == 'POST':
231         form = TemporaryLoginRequestForm(request.POST)
232
233         if form.is_valid():
234             users = form.user_cache
235
236             for u in users:
237                 if u.is_suspended():
238                     return forward_suspended_user(request, u, False)
239
240             for u in users:
241                 try:
242                     hash = get_object_or_404(ValidationHash, user=u, type='templogin')
243                     if hash.expiration < datetime.datetime.now():
244                         hash.delete()
245                         return request_temp_login(request)
246                 except:
247                     hash = ValidationHash.objects.create_new(u, 'templogin', [u.id])
248
249                 send_template_email([u], "auth/temp_login_email.html", {'temp_login_code': hash})
250
251                 request.user.message_set.create(message=_("An email has been sent with your temporary login key"))
252
253             return HttpResponseRedirect(reverse('index'))
254     else:
255         form = TemporaryLoginRequestForm()
256
257     return render_to_response(
258             'auth/temp_login_request.html', {'form': form},
259             context_instance=RequestContext(request))
260
261 def temp_signin(request, user, code):
262     user = get_object_or_404(User, id=user)
263
264     if (ValidationHash.objects.validate(code, user, 'templogin', [user.id])):
265         
266         # If the user requests temp_signin he must have forgotten his password. So we mark it as unusable.
267         user.set_unusable_password()
268         user.save()
269         
270         return login_and_forward(request, user, reverse('user_authsettings', kwargs={'id': user.id}),
271                                  _(
272                                          "You are logged in with a temporary access key, please take the time to fix your issue with authentication."
273                                          ))
274     else:
275         raise Http404()
276
277 def send_validation_email(request):
278     if not request.user.is_authenticated():
279         return HttpResponseUnauthorized(request)
280     else:
281         # We check if there are some old validation hashes. If there are -- we delete them.
282         try:
283             hash = ValidationHash.objects.get(user=request.user, type='email')
284             hash.delete()
285         except:
286             pass
287
288         # We don't care if there are previous cashes in the database... In every case we have to create a new one
289         hash = ValidationHash.objects.create_new(request.user, 'email', [request.user.email])
290
291         additional_get_params = urllib.urlencode(request.GET)
292         send_template_email([request.user], "auth/mail_validation.html", {
293             'validation_code': hash,
294             'additional_get_params' : additional_get_params
295         })
296
297         request.user.message_set.create(message=_("A message with an email validation link was just sent to your address."))
298         return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
299
300         
301
302 def validate_email(request, user, code):
303     user = get_object_or_404(User, id=user)
304
305     if (ValidationHash.objects.validate(code, user, 'email', [user.email])):
306         EmailValidationAction(user=user, ip=request.META['REMOTE_ADDR']).save()
307         return login_and_forward(request, user, reverse('index'), _("Thank you, your email is now validated."))
308     else:
309         return render_to_response('auth/mail_already_validated.html', { 'user' : user }, RequestContext(request))
310
311 def auth_settings(request, id):
312     user_ = get_object_or_404(User, id=id)
313
314     if not (request.user.is_superuser or request.user == user_):
315         return HttpResponseUnauthorized(request)
316
317     auth_keys = user_.auth_keys.all()
318
319     if request.user.is_superuser or (not user_.has_usable_password()):
320         FormClass = SetPasswordForm
321     else:
322         FormClass = ChangePasswordForm
323
324     if request.POST:
325         form = FormClass(request.POST, user=user_)
326         if form.is_valid():
327             is_new_pass = not user_.has_usable_password()
328             user_.set_password(form.cleaned_data['password1'])
329             user_.save()
330
331             if is_new_pass:
332                 request.user.message_set.create(message=_("New password set"))
333                 if not request.user.is_superuser:
334                     form = ChangePasswordForm(user=user_)
335             else:
336                 request.user.message_set.create(message=_("Your password was changed"))
337
338             return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': user_.id}))
339     else:
340         form = FormClass(user=user_)
341
342     auth_keys_list = []
343
344     for k in auth_keys:
345         provider = AUTH_PROVIDERS.get(k.provider, None)
346
347         if provider is not None:
348             name =  "%s: %s" % (provider.context.human_name, provider.context.readable_key(k))
349         else:
350             from forum.authentication.base import ConsumerTemplateContext
351             "unknown: %s" % ConsumerTemplateContext.readable_key(k)
352
353         auth_keys_list.append({
354         'name': name,
355         'id': k.id
356         })
357
358     return render_to_response('auth/auth_settings.html', {
359     'view_user': user_,
360     "can_view_private": (user_ == request.user) or request.user.is_superuser,
361     'form': form,
362     'has_password': user_.has_usable_password(),
363     'auth_keys': auth_keys_list,
364     'allow_local_auth': AUTH_PROVIDERS.get('local', None),
365     }, context_instance=RequestContext(request))
366
367 def remove_external_provider(request, id):
368     association = get_object_or_404(AuthKeyUserAssociation, id=id)
369     if not (request.user.is_superuser or request.user == association.user):
370         return HttpResponseUnauthorized(request)
371
372     request.user.message_set.create(message=_("You removed the association with %s") % association.provider)
373     association.delete()
374     return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': association.user.id}))
375
376 def login_and_forward(request, user, forward=None, message=None):
377     if user.is_suspended():
378         return forward_suspended_user(request, user)
379
380     user.backend = "django.contrib.auth.backends.ModelBackend"
381     login(request, user)
382
383     # Store the login action
384     UserLoginAction(user=user, ip=request.META['REMOTE_ADDR']).save()
385
386     if message is None:
387         message = _("Welcome back %s, you are now logged in") % smart_unicode(user.username)
388
389     request.user.message_set.create(message=message)
390
391     if not forward:
392         forward = request.session.get(ON_SIGNIN_SESSION_ATTR, reverse('index'))
393
394     pending_data = request.session.get(PENDING_SUBMISSION_SESSION_ATTR, None)
395
396     if pending_data and (user.email_isvalid or pending_data['type'] not in settings.REQUIRE_EMAIL_VALIDATION_TO):
397         submission_time = pending_data['time']
398         if submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.HOLD_PENDING_POSTS_MINUTES)):
399             del request.session[PENDING_SUBMISSION_SESSION_ATTR]
400         elif submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.WARN_PENDING_POSTS_MINUTES)):
401             user.message_set.create(message=(_("You have a %s pending submission.") % pending_data['data_name']) + " %s, %s, %s" % (
402                 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('save')}), _("save it")),
403                 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('review')}), _("review")),
404                 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('cancel')}), _("cancel"))
405             ))
406         else:
407             return manage_pending_data(request, _('save'), forward)
408
409     additional_get_params = urllib.urlencode(request.GET)
410     return HttpResponseRedirect(forward + "?%s" % additional_get_params)
411
412 def forward_suspended_user(request, user, show_private_msg=True):
413     message = _("Sorry, but this account is suspended")
414     if show_private_msg:
415         msg_type = 'privatemsg'
416     else:
417         msg_type = 'publicmsg'
418
419     suspension = user.suspension
420     if suspension:
421         message += (":<br />" + suspension.extra.get(msg_type, ''))
422
423     request.user.message_set.create(message)
424     return HttpResponseRedirect(reverse('index'))
425
426 @decorate.withfn(login_required)
427 def signout(request):
428     logout(request)
429     return HttpResponseRedirect(reverse('index'))