1 # -*- coding: utf-8 -*-
6 from django.shortcuts import render_to_response, get_object_or_404
7 from django.template import RequestContext
8 from django.core.urlresolvers import reverse
9 from django.http import HttpResponseRedirect, Http404
10 from django.utils.safestring import mark_safe
11 from django.utils.translation import ugettext as _
12 from django.utils.encoding import smart_unicode
13 from django.contrib.auth import login, logout
15 from writers import manage_pending_data
17 from forum.utils import html
18 from forum.views.decorators import login_required
19 from forum.modules import decorate
20 from forum.forms import SimpleRegistrationForm, TemporaryLoginRequestForm, ChangePasswordForm, SetPasswordForm
21 from forum.http_responses import HttpResponseUnauthorized
22 from forum.utils.mail import send_template_email
23 from forum.authentication.base import InvalidAuthentication
24 from forum.authentication import AUTH_PROVIDERS
25 from forum.models import User, AuthKeyUserAssociation, ValidationHash
26 from forum.actions import UserJoinsAction
27 from forum import settings
29 from vars import ON_SIGNIN_SESSION_ATTR, PENDING_SUBMISSION_SESSION_ATTR
31 def signin_page(request):
32 referer = request.META.get('HTTP_REFERER', '/')
34 # If the referer is equal to the sign up page, e. g. if the previous login attempt was not successful we do not
35 # change the sign in URL. The user should go to the same page.
36 if not referer.replace(settings.APP_URL, '') == reverse('auth_signin'):
37 request.session[ON_SIGNIN_SESSION_ATTR] = referer
39 all_providers = [provider.context for provider in AUTH_PROVIDERS.values() if provider.context]
41 sort = lambda c1, c2: c1.weight - c2.weight
42 can_show = lambda c: not request.user.is_authenticated() or c.show_to_logged_in_user
44 bigicon_providers = sorted([
45 context for context in all_providers if context.mode == 'BIGICON' and can_show(context)
48 smallicon_providers = sorted([
49 context for context in all_providers if context.mode == 'SMALLICON' and can_show(context)
52 top_stackitem_providers = sorted([
53 context for context in all_providers if context.mode == 'TOP_STACK_ITEM' and can_show(context)
56 stackitem_providers = sorted([
57 context for context in all_providers if context.mode == 'STACK_ITEM' and can_show(context)
61 msg = request.session['auth_error']
62 del request.session['auth_error']
66 return render_to_response(
70 'all_providers': all_providers,
71 'bigicon_providers': bigicon_providers,
72 'top_stackitem_providers': top_stackitem_providers,
73 'stackitem_providers': stackitem_providers,
74 'smallicon_providers': smallicon_providers,
76 RequestContext(request))
78 def prepare_provider_signin(request, provider):
79 force_email_request = request.REQUEST.get('validate_email', 'yes') == 'yes'
80 request.session['force_email_request'] = force_email_request
82 if provider in AUTH_PROVIDERS:
83 provider_class = AUTH_PROVIDERS[provider].consumer
86 request_url = provider_class.prepare_authentication_request(request,
87 reverse('auth_provider_done',
88 kwargs={'provider': provider}))
90 return HttpResponseRedirect(request_url)
91 except NotImplementedError, e:
92 return process_provider_signin(request, provider)
93 except InvalidAuthentication, e:
94 request.session['auth_error'] = e.message
96 return HttpResponseRedirect(reverse('auth_signin'))
101 def process_provider_signin(request, provider):
102 if provider in AUTH_PROVIDERS:
103 provider_class = AUTH_PROVIDERS[provider].consumer
106 assoc_key = provider_class.process_authentication_request(request)
107 except InvalidAuthentication, e:
108 request.session['auth_error'] = e.message
109 return HttpResponseRedirect(reverse('auth_signin'))
111 if request.user.is_authenticated():
112 if isinstance(assoc_key, (type, User)):
113 if request.user != assoc_key:
114 request.session['auth_error'] = _(
115 "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
118 request.session['auth_error'] = _("You are already logged in with that user.")
121 assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
122 if assoc.user == request.user:
123 request.session['auth_error'] = _(
124 "These login credentials are already associated with your account.")
126 request.session['auth_error'] = _(
127 "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
130 uassoc = AuthKeyUserAssociation(user=request.user, key=assoc_key, provider=provider)
132 request.user.message_set.create(
133 message=_('The new credentials are now associated with your account'))
134 return HttpResponseRedirect(reverse('user_authsettings', args=[request.user.id]))
136 return HttpResponseRedirect(reverse('auth_signin'))
138 if isinstance(assoc_key, User):
139 return login_and_forward(request, assoc_key)
142 assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
144 return login_and_forward(request, user_)
145 except AuthKeyUserAssociation.DoesNotExist:
146 request.session['assoc_key'] = assoc_key
147 request.session['auth_provider'] = provider
148 return HttpResponseRedirect(reverse('auth_external_register'))
150 return HttpResponseRedirect(reverse('auth_signin'))
152 def external_register(request):
153 if request.method == 'POST' and 'bnewaccount' in request.POST:
154 form1 = SimpleRegistrationForm(request.POST)
157 user_ = User(username=form1.cleaned_data['username'], email=form1.cleaned_data['email'])
158 user_.email_isvalid = request.session.get('auth_validated_email', '') == form1.cleaned_data['email']
159 user_.set_unusable_password()
161 if User.objects.all().count() == 0:
162 user_.is_superuser = True
163 user_.is_staff = True
166 UserJoinsAction(user=user_, ip=request.META['REMOTE_ADDR']).save()
169 assoc_key = request.session['assoc_key']
170 auth_provider = request.session['auth_provider']
172 request.session['auth_error'] = _(
173 "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."
175 logging.error("Missing session data when trying to complete user registration: %s" % ", ".join(
176 ["%s: %s" % (k, v) for k, v in request.META.items()]))
177 return HttpResponseRedirect(reverse('auth_signin'))
179 uassoc = AuthKeyUserAssociation(user=user_, key=assoc_key, provider=auth_provider)
182 del request.session['assoc_key']
183 del request.session['auth_provider']
185 return login_and_forward(request, user_, message=_("A welcome email has been sent to your email address. "))
187 auth_provider = request.session.get('auth_provider', None)
188 if not auth_provider:
189 request.session['auth_error'] = _(
190 "Oops, something went wrong in the middle of this process. Please try again.")
191 logging.error("Missing session data when trying to complete user registration: %s" % ", ".join(
192 ["%s: %s" % (k, v) for k, v in request.META.items()]))
193 return HttpResponseRedirect(reverse('auth_signin'))
195 provider_class = AUTH_PROVIDERS[auth_provider].consumer
197 # Pass the cookies to the Facebook authentication class get_user_data method. We need them to take the access token.
198 if provider_class.__class__.__name__ == 'FacebookAuthConsumer':
199 user_data = provider_class.get_user_data(request.COOKIES)
201 user_data = provider_class.get_user_data(request.session['assoc_key'])
204 user_data = request.session.get('auth_consumer_data', {})
206 username = user_data.get('username', '')
207 email = user_data.get('email', '')
210 request.session['auth_validated_email'] = email
212 form1 = SimpleRegistrationForm(initial={
214 'username': username,
218 provider_context = AUTH_PROVIDERS[request.session['auth_provider']].context
220 return render_to_response('auth/complete.html', {
222 'provider':provider_context and mark_safe(provider_context.human_name) or _('unknown'),
223 'login_type':provider_context.id,
224 'gravatar_faq_url':reverse('faq') + '#gravatar',
225 }, context_instance=RequestContext(request))
227 def request_temp_login(request):
228 if request.method == 'POST':
229 form = TemporaryLoginRequestForm(request.POST)
232 users = form.user_cache
236 return forward_suspended_user(request, u, False)
240 hash = get_object_or_404(ValidationHash, user=u, type='templogin')
241 if hash.expiration < datetime.datetime.now():
243 return request_temp_login(request)
245 hash = ValidationHash.objects.create_new(u, 'templogin', [u.id])
247 send_template_email([u], "auth/temp_login_email.html", {'temp_login_code': hash})
249 request.user.message_set.create(message=_("An email has been sent with your temporary login key"))
251 return HttpResponseRedirect(reverse('index'))
253 form = TemporaryLoginRequestForm()
255 return render_to_response(
256 'auth/temp_login_request.html', {'form': form},
257 context_instance=RequestContext(request))
259 def temp_signin(request, user, code):
260 user = get_object_or_404(User, id=user)
262 if (ValidationHash.objects.validate(code, user, 'templogin', [user.id])):
264 # If the user requests temp_signin he must have forgotten his password. So we mark it as unusable.
265 user.set_unusable_password()
268 return login_and_forward(request, user, reverse('user_authsettings', kwargs={'id': user.id}),
270 "You are logged in with a temporary access key, please take the time to fix your issue with authentication."
275 def send_validation_email(request):
276 if not request.user.is_authenticated():
277 return HttpResponseUnauthorized(request)
279 # We check if there are some old validation hashes. If there are -- we delete them.
281 hash = ValidationHash.objects.get(user=request.user, type='email')
286 # We don't care if there are previous cashes in the database... In every case we have to create a new one
287 hash = ValidationHash.objects.create_new(request.user, 'email', [request.user.email])
289 send_template_email([request.user], "auth/mail_validation.html", {'validation_code': hash})
290 request.user.message_set.create(message=_("A message with an email validation link was just sent to your address."))
291 return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
295 def validate_email(request, user, code):
296 user = get_object_or_404(User, id=user)
298 if (ValidationHash.objects.validate(code, user, 'email', [user.email])):
299 user.email_isvalid = True
301 return login_and_forward(request, user, reverse('index'), _("Thank you, your email is now validated."))
303 return render_to_response('auth/mail_already_validated.html', { 'user' : user }, RequestContext(request))
305 def auth_settings(request, id):
306 user_ = get_object_or_404(User, id=id)
308 if not (request.user.is_superuser or request.user == user_):
309 return HttpResponseUnauthorized(request)
311 auth_keys = user_.auth_keys.all()
313 if request.user.is_superuser or (not user_.has_usable_password()):
314 FormClass = SetPasswordForm
316 FormClass = ChangePasswordForm
319 form = FormClass(request.POST, user=user_)
321 is_new_pass = not user_.has_usable_password()
322 user_.set_password(form.cleaned_data['password1'])
326 request.user.message_set.create(message=_("New password set"))
327 if not request.user.is_superuser:
328 form = ChangePasswordForm(user=user_)
330 request.user.message_set.create(message=_("Your password was changed"))
332 return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': user_.id}))
334 form = FormClass(user=user_)
339 provider = AUTH_PROVIDERS.get(k.provider, None)
341 if provider is not None:
342 name = "%s: %s" % (provider.context.human_name, provider.context.readable_key(k))
344 from forum.authentication.base import ConsumerTemplateContext
345 "unknown: %s" % ConsumerTemplateContext.readable_key(k)
347 auth_keys_list.append({
352 return render_to_response('auth/auth_settings.html', {
354 "can_view_private": (user_ == request.user) or request.user.is_superuser,
356 'has_password': user_.has_usable_password(),
357 'auth_keys': auth_keys_list,
358 'allow_local_auth': AUTH_PROVIDERS.get('local', None),
359 }, context_instance=RequestContext(request))
361 def remove_external_provider(request, id):
362 association = get_object_or_404(AuthKeyUserAssociation, id=id)
363 if not (request.user.is_superuser or request.user == association.user):
364 return HttpResponseUnauthorized(request)
366 request.user.message_set.create(message=_("You removed the association with %s") % association.provider)
368 return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': association.user.id}))
370 def login_and_forward(request, user, forward=None, message=None):
371 if user.is_suspended():
372 return forward_suspended_user(request, user)
374 user.backend = "django.contrib.auth.backends.ModelBackend"
378 message = _("Welcome back %s, you are now logged in") % smart_unicode(user.username)
380 request.user.message_set.create(message=message)
383 forward = request.session.get(ON_SIGNIN_SESSION_ATTR, reverse('index'))
385 pending_data = request.session.get(PENDING_SUBMISSION_SESSION_ATTR, None)
387 if pending_data and (user.email_isvalid or pending_data['type'] not in settings.REQUIRE_EMAIL_VALIDATION_TO):
388 submission_time = pending_data['time']
389 if submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.HOLD_PENDING_POSTS_MINUTES)):
390 del request.session[PENDING_SUBMISSION_SESSION_ATTR]
391 elif submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.WARN_PENDING_POSTS_MINUTES)):
392 user.message_set.create(message=(_("You have a %s pending submission.") % pending_data['data_name']) + " %s, %s, %s" % (
393 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('save')}), _("save it")),
394 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('review')}), _("review")),
395 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('cancel')}), _("cancel"))
398 return manage_pending_data(request, _('save'), forward)
400 return HttpResponseRedirect(forward)
402 def forward_suspended_user(request, user, show_private_msg=True):
403 message = _("Sorry, but this account is suspended")
405 msg_type = 'privatemsg'
407 msg_type = 'publicmsg'
409 suspension = user.suspension
411 message += (":<br />" + suspension.extra.get(msg_type, ''))
413 request.user.message_set.create(message)
414 return HttpResponseRedirect(reverse('index'))
416 @decorate.withfn(login_required)
417 def signout(request):
419 return HttpResponseRedirect(reverse('index'))