1 # -*- coding: utf-8 -*-
6 from urlparse import urlparse
8 from django.shortcuts import render_to_response, get_object_or_404
9 from django.template import RequestContext
10 from django.core.urlresolvers import reverse
11 from django.http import HttpResponseRedirect, Http404
12 from django.utils.safestring import mark_safe
13 from django.utils.translation import ugettext as _
14 from django.utils.encoding import smart_unicode
15 from django.contrib.auth import login, logout
17 from django.contrib import messages
19 from writers import manage_pending_data
21 from forum.actions import EmailValidationAction
22 from forum.utils import html
23 from forum.views.decorators import login_required
24 from forum.modules import decorate
25 from forum.forms import SimpleRegistrationForm, TemporaryLoginRequestForm, ChangePasswordForm, SetPasswordForm
26 from forum.http_responses import HttpResponseUnauthorized
27 from forum.utils.mail import send_template_email
28 from forum.authentication.base import InvalidAuthentication
29 from forum.authentication import AUTH_PROVIDERS
30 from forum.models import User, AuthKeyUserAssociation, ValidationHash
31 from forum.actions import UserJoinsAction, UserLoginAction
32 from forum import settings
34 from vars import ON_SIGNIN_SESSION_ATTR, PENDING_SUBMISSION_SESSION_ATTR
36 def signin_page(request):
37 referer = request.META.get('HTTP_REFERER', '/')
39 # If the referer is equal to the sign up page, e. g. if the previous login attempt was not successful we do not
40 # change the sign in URL. The user should go to the same page.
41 if not referer.replace(settings.APP_URL, '') == reverse('auth_signin'):
42 request.session[ON_SIGNIN_SESSION_ATTR] = referer
44 all_providers = [provider.context for provider in AUTH_PROVIDERS.values() if provider.context]
46 sort = lambda c1, c2: c1.weight - c2.weight
47 can_show = lambda c: not request.user.is_authenticated() or c.show_to_logged_in_user
49 bigicon_providers = sorted([
50 context for context in all_providers if context.mode == 'BIGICON' and can_show(context)
53 smallicon_providers = sorted([
54 context for context in all_providers if context.mode == 'SMALLICON' and can_show(context)
57 top_stackitem_providers = sorted([
58 context for context in all_providers if context.mode == 'TOP_STACK_ITEM' and can_show(context)
61 stackitem_providers = sorted([
62 context for context in all_providers if context.mode == 'STACK_ITEM' and can_show(context)
66 msg = request.session['auth_error']
67 del request.session['auth_error']
71 return render_to_response(
75 'all_providers': all_providers,
76 'bigicon_providers': bigicon_providers,
77 'top_stackitem_providers': top_stackitem_providers,
78 'stackitem_providers': stackitem_providers,
79 'smallicon_providers': smallicon_providers,
81 RequestContext(request))
83 def prepare_provider_signin(request, provider):
84 force_email_request = request.REQUEST.get('validate_email', 'yes') == 'yes'
85 request.session['force_email_request'] = force_email_request
87 if provider in AUTH_PROVIDERS:
88 provider_class = AUTH_PROVIDERS[provider].consumer
91 request_url = provider_class.prepare_authentication_request(request,
92 reverse('auth_provider_done',
93 kwargs={'provider': provider}))
95 return HttpResponseRedirect(request_url)
96 except NotImplementedError, e:
97 return process_provider_signin(request, provider)
98 except InvalidAuthentication, e:
99 request.session['auth_error'] = e.message
101 return HttpResponseRedirect(reverse('auth_signin'))
106 def process_provider_signin(request, provider):
107 if provider in AUTH_PROVIDERS:
108 provider_class = AUTH_PROVIDERS[provider].consumer
111 assoc_key = provider_class.process_authentication_request(request)
112 except InvalidAuthentication, e:
113 request.session['auth_error'] = e.message
114 return HttpResponseRedirect(reverse('auth_signin'))
116 if request.user.is_authenticated():
117 if isinstance(assoc_key, (type, User)):
118 if request.user != assoc_key:
119 request.session['auth_error'] = _(
120 "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
123 request.session['auth_error'] = _("You are already logged in with that user.")
126 assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
127 if assoc.user == request.user:
128 request.session['auth_error'] = _(
129 "These login credentials are already associated with your account.")
131 request.session['auth_error'] = _(
132 "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
135 uassoc = AuthKeyUserAssociation(user=request.user, key=assoc_key, provider=provider)
137 messages.info(request, _('The new credentials are now associated with your account'))
138 return HttpResponseRedirect(reverse('user_authsettings', args=[request.user.id]))
140 return HttpResponseRedirect(reverse('auth_signin'))
142 if isinstance(assoc_key, User):
143 return login_and_forward(request, assoc_key)
146 assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
148 return login_and_forward(request, user_)
149 except AuthKeyUserAssociation.DoesNotExist:
150 request.session['assoc_key'] = assoc_key
151 request.session['auth_provider'] = provider
152 return HttpResponseRedirect(reverse('auth_external_register'))
154 return HttpResponseRedirect(reverse('auth_signin'))
156 def external_register(request):
157 if request.method == 'POST' and 'bnewaccount' in request.POST:
158 form1 = SimpleRegistrationForm(request.POST)
161 user_ = User(username=form1.cleaned_data['username'], email=form1.cleaned_data['email'], real_name=form1.cleaned_data['real_name'])
162 user_.email_isvalid = request.session.get('auth_validated_email', '') == form1.cleaned_data['email']
163 user_.set_unusable_password()
165 if User.objects.all().count() == 0:
166 user_.is_superuser = True
167 user_.is_staff = True
170 UserJoinsAction(user=user_, ip=request.META['REMOTE_ADDR']).save()
173 assoc_key = request.session['assoc_key']
174 auth_provider = request.session['auth_provider']
176 request.session['auth_error'] = _(
177 "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."
179 logging.error("Missing session data when trying to complete user registration: %s" % ", ".join(
180 ["%s: %s" % (k, v) for k, v in request.META.items()]))
181 return HttpResponseRedirect(reverse('auth_signin'))
183 uassoc = AuthKeyUserAssociation(user=user_, key=assoc_key, provider=auth_provider)
186 del request.session['assoc_key']
187 del request.session['auth_provider']
189 return login_and_forward(request, user_, message=_("A welcome email has been sent to your email address. "))
191 auth_provider = request.session.get('auth_provider', None)
192 if not auth_provider:
193 request.session['auth_error'] = _(
194 "Oops, something went wrong in the middle of this process. Please try again.")
195 logging.error("Missing session data when trying to complete user registration: %s" % ", ".join(
196 ["%s: %s" % (k, v) for k, v in request.META.items()]))
197 return HttpResponseRedirect(reverse('auth_signin'))
199 provider_class = AUTH_PROVIDERS[auth_provider].consumer
201 if provider_class.__class__.__name__ == 'FacebookAuthConsumer':
202 user_data = provider_class.get_user_data(request.session['access_token'])
204 user_data = provider_class.get_user_data(request.session['assoc_key'])
208 user_data = request.session.get('auth_consumer_data', {})
210 username = user_data.get('username', '')
211 email = user_data.get('email', '')
212 real_name = user_data.get('real_name', '')
215 request.session['auth_validated_email'] = email
217 form1 = SimpleRegistrationForm(initial={
219 'username': username,
221 'real_name': real_name,
224 provider_context = AUTH_PROVIDERS[request.session['auth_provider']].context
226 return render_to_response('auth/complete.html', {
228 'provider':provider_context and mark_safe(provider_context.human_name) or _('unknown'),
229 'login_type':provider_context.id,
230 'gravatar_faq_url':reverse('faq') + '#gravatar',
231 }, context_instance=RequestContext(request))
233 def request_temp_login(request):
234 if request.method == 'POST':
235 form = TemporaryLoginRequestForm(request.POST)
238 users = form.user_cache
242 return forward_suspended_user(request, u, False)
246 hash = get_object_or_404(ValidationHash, user=u, type='templogin')
247 if hash.expiration < datetime.datetime.now():
249 return request_temp_login(request)
251 hash = ValidationHash.objects.create_new(u, 'templogin', [u.id])
253 send_template_email([u], "auth/temp_login_email.html", {'temp_login_code': hash})
255 messages.info(request, _("An email has been sent with your temporary login key"))
257 return HttpResponseRedirect(reverse('index'))
259 form = TemporaryLoginRequestForm()
261 return render_to_response(
262 'auth/temp_login_request.html', {'form': form},
263 context_instance=RequestContext(request))
265 def temp_signin(request, user, code):
266 user = get_object_or_404(User, id=user)
268 if (ValidationHash.objects.validate(code, user, 'templogin', [user.id])):
270 # If the user requests temp_signin he must have forgotten his password. So we mark it as unusable.
271 user.set_unusable_password()
274 return login_and_forward(request, user, reverse('user_authsettings', kwargs={'id': user.id}),
276 "You are logged in with a temporary access key, please take the time to fix your issue with authentication."
281 def send_validation_email(request):
282 if not request.user.is_authenticated():
283 return HttpResponseUnauthorized(request)
285 # We check if there are some old validation hashes. If there are -- we delete them.
287 hash = ValidationHash.objects.get(user=request.user, type='email')
292 # We don't care if there are previous cashes in the database... In every case we have to create a new one
293 hash = ValidationHash.objects.create_new(request.user, 'email', [request.user.email])
295 additional_get_params = urllib.urlencode(dict([k, v.encode('utf-8')] for k, v in request.GET.items()))
296 send_template_email([request.user], "auth/mail_validation.html", {
297 'validation_code': hash,
298 'additional_get_params' : additional_get_params
301 messages.info(request, _("A message with an email validation link was just sent to your address."))
302 return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
306 def validate_email(request, user, code):
307 user = get_object_or_404(User, id=user)
309 if (ValidationHash.objects.validate(code, user, 'email', [user.email])):
310 EmailValidationAction(user=user, ip=request.META['REMOTE_ADDR']).save()
311 return login_and_forward(request, user, reverse('index'), _("Thank you, your email is now validated."))
313 return render_to_response('auth/mail_already_validated.html', { 'user' : user }, RequestContext(request))
315 def auth_settings(request, id):
316 user_ = get_object_or_404(User, id=id)
318 if not (request.user.is_superuser or request.user == user_):
319 return HttpResponseUnauthorized(request)
321 auth_keys = user_.auth_keys.all()
323 if request.user.is_superuser or (not user_.has_usable_password()):
324 FormClass = SetPasswordForm
326 FormClass = ChangePasswordForm
329 form = FormClass(request.POST, user=user_)
331 is_new_pass = not user_.has_usable_password()
332 user_.set_password(form.cleaned_data['password1'])
336 messages.info(request, _("New password set"))
337 if not request.user.is_superuser:
338 form = ChangePasswordForm(user=user_)
340 messages.info(request, _("Your password was changed"))
342 return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': user_.id}))
344 form = FormClass(user=user_)
349 provider = AUTH_PROVIDERS.get(k.provider, None)
351 if provider is not None:
352 name = "%s: %s" % (provider.context.human_name, provider.context.readable_key(k))
354 from forum.authentication.base import ConsumerTemplateContext
355 "unknown: %s" % ConsumerTemplateContext.readable_key(k)
357 auth_keys_list.append({
362 return render_to_response('auth/auth_settings.html', {
364 "can_view_private": (user_ == request.user) or request.user.is_superuser,
366 'has_password': user_.has_usable_password(),
367 'auth_keys': auth_keys_list,
368 'allow_local_auth': AUTH_PROVIDERS.get('local', None),
369 }, context_instance=RequestContext(request))
371 def remove_external_provider(request, id):
372 association = get_object_or_404(AuthKeyUserAssociation, id=id)
373 if not (request.user.is_superuser or request.user == association.user):
374 return HttpResponseUnauthorized(request)
376 messages.info(request, _("You removed the association with %s") % association.provider)
378 return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': association.user.id}))
380 def login_and_forward(request, user, forward=None, message=None):
381 if user.is_suspended():
382 return forward_suspended_user(request, user)
384 user.backend = "django.contrib.auth.backends.ModelBackend"
387 # Store the login action
388 UserLoginAction(user=user, ip=request.META['REMOTE_ADDR']).save()
391 message = _("Welcome back %s, you are now logged in") % smart_unicode(user.username)
393 messages.info(request, message)
396 forward = request.session.get(ON_SIGNIN_SESSION_ATTR, reverse('index'))
398 pending_data = request.session.get(PENDING_SUBMISSION_SESSION_ATTR, None)
400 if pending_data and (user.email_isvalid or pending_data['type'] not in settings.REQUIRE_EMAIL_VALIDATION_TO):
401 submission_time = pending_data['time']
402 if submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.HOLD_PENDING_POSTS_MINUTES)):
403 del request.session[PENDING_SUBMISSION_SESSION_ATTR]
404 elif submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.WARN_PENDING_POSTS_MINUTES)):
405 messages.info(request, (_("You have a %s pending submission.") % pending_data['data_name']) + " %s, %s, %s" % (
406 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('save')}), _("save it")),
407 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('review')}), _("review")),
408 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('cancel')}), _("cancel"))
411 return manage_pending_data(request, _('save'), forward)
413 additional_get_params = urllib.urlencode(dict([k, v.encode('utf-8')] for k, v in request.GET.items()))
415 parsed_forward = urlparse(forward)
417 # If there is already some parsed query in the URL then change the forward URL
418 if parsed_forward.query:
419 forward_url = forward + "&%s" % additional_get_params
421 forward_url = forward + "?%s" % additional_get_params
423 return HttpResponseRedirect(forward_url)
425 def forward_suspended_user(request, user, show_private_msg=True):
426 message = _("Sorry, but this account is suspended")
428 msg_type = 'privatemsg'
430 msg_type = 'publicmsg'
432 suspension = user.suspension
434 message += (":<br />" + suspension.extra.get(msg_type, ''))
436 messages.info(request, message)
437 return HttpResponseRedirect(reverse('index'))
439 @decorate.withfn(login_required)
440 def signout(request):
442 return HttpResponseRedirect(reverse('index'))