1 # -*- coding: utf-8 -*-
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
16 from writers import manage_pending_data
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
31 from vars import ON_SIGNIN_SESSION_ATTR, PENDING_SUBMISSION_SESSION_ATTR
33 def signin_page(request):
34 referer = request.META.get('HTTP_REFERER', '/')
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
41 all_providers = [provider.context for provider in AUTH_PROVIDERS.values() if provider.context]
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
46 bigicon_providers = sorted([
47 context for context in all_providers if context.mode == 'BIGICON' and can_show(context)
50 smallicon_providers = sorted([
51 context for context in all_providers if context.mode == 'SMALLICON' and can_show(context)
54 top_stackitem_providers = sorted([
55 context for context in all_providers if context.mode == 'TOP_STACK_ITEM' and can_show(context)
58 stackitem_providers = sorted([
59 context for context in all_providers if context.mode == 'STACK_ITEM' and can_show(context)
63 msg = request.session['auth_error']
64 del request.session['auth_error']
68 return render_to_response(
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,
78 RequestContext(request))
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
84 if provider in AUTH_PROVIDERS:
85 provider_class = AUTH_PROVIDERS[provider].consumer
88 request_url = provider_class.prepare_authentication_request(request,
89 reverse('auth_provider_done',
90 kwargs={'provider': provider}))
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
98 return HttpResponseRedirect(reverse('auth_signin'))
103 def process_provider_signin(request, provider):
104 if provider in AUTH_PROVIDERS:
105 provider_class = AUTH_PROVIDERS[provider].consumer
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'))
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."
120 request.session['auth_error'] = _("You are already logged in with that user.")
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.")
128 request.session['auth_error'] = _(
129 "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
132 uassoc = AuthKeyUserAssociation(user=request.user, key=assoc_key, provider=provider)
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]))
138 return HttpResponseRedirect(reverse('auth_signin'))
140 if isinstance(assoc_key, User):
141 return login_and_forward(request, assoc_key)
144 assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
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'))
152 return HttpResponseRedirect(reverse('auth_signin'))
154 def external_register(request):
155 if request.method == 'POST' and 'bnewaccount' in request.POST:
156 form1 = SimpleRegistrationForm(request.POST)
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()
163 if User.objects.all().count() == 0:
164 user_.is_superuser = True
165 user_.is_staff = True
168 UserJoinsAction(user=user_, ip=request.META['REMOTE_ADDR']).save()
171 assoc_key = request.session['assoc_key']
172 auth_provider = request.session['auth_provider']
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."
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'))
181 uassoc = AuthKeyUserAssociation(user=user_, key=assoc_key, provider=auth_provider)
184 del request.session['assoc_key']
185 del request.session['auth_provider']
187 return login_and_forward(request, user_, message=_("A welcome email has been sent to your email address. "))
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'))
197 provider_class = AUTH_PROVIDERS[auth_provider].consumer
199 if provider_class.__class__.__name__ == 'FacebookAuthConsumer':
200 user_data = provider_class.get_user_data(request.session['access_token'])
202 user_data = provider_class.get_user_data(request.session['assoc_key'])
206 user_data = request.session.get('auth_consumer_data', {})
208 username = user_data.get('username', '')
209 email = user_data.get('email', '')
212 request.session['auth_validated_email'] = email
214 form1 = SimpleRegistrationForm(initial={
216 'username': username,
220 provider_context = AUTH_PROVIDERS[request.session['auth_provider']].context
222 return render_to_response('auth/complete.html', {
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))
229 def request_temp_login(request):
230 if request.method == 'POST':
231 form = TemporaryLoginRequestForm(request.POST)
234 users = form.user_cache
238 return forward_suspended_user(request, u, False)
242 hash = get_object_or_404(ValidationHash, user=u, type='templogin')
243 if hash.expiration < datetime.datetime.now():
245 return request_temp_login(request)
247 hash = ValidationHash.objects.create_new(u, 'templogin', [u.id])
249 send_template_email([u], "auth/temp_login_email.html", {'temp_login_code': hash})
251 request.user.message_set.create(message=_("An email has been sent with your temporary login key"))
253 return HttpResponseRedirect(reverse('index'))
255 form = TemporaryLoginRequestForm()
257 return render_to_response(
258 'auth/temp_login_request.html', {'form': form},
259 context_instance=RequestContext(request))
261 def temp_signin(request, user, code):
262 user = get_object_or_404(User, id=user)
264 if (ValidationHash.objects.validate(code, user, 'templogin', [user.id])):
266 # If the user requests temp_signin he must have forgotten his password. So we mark it as unusable.
267 user.set_unusable_password()
270 return login_and_forward(request, user, reverse('user_authsettings', kwargs={'id': user.id}),
272 "You are logged in with a temporary access key, please take the time to fix your issue with authentication."
277 def send_validation_email(request):
278 if not request.user.is_authenticated():
279 return HttpResponseUnauthorized(request)
281 # We check if there are some old validation hashes. If there are -- we delete them.
283 hash = ValidationHash.objects.get(user=request.user, type='email')
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])
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
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', '/'))
302 def validate_email(request, user, code):
303 user = get_object_or_404(User, id=user)
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."))
309 return render_to_response('auth/mail_already_validated.html', { 'user' : user }, RequestContext(request))
311 def auth_settings(request, id):
312 user_ = get_object_or_404(User, id=id)
314 if not (request.user.is_superuser or request.user == user_):
315 return HttpResponseUnauthorized(request)
317 auth_keys = user_.auth_keys.all()
319 if request.user.is_superuser or (not user_.has_usable_password()):
320 FormClass = SetPasswordForm
322 FormClass = ChangePasswordForm
325 form = FormClass(request.POST, user=user_)
327 is_new_pass = not user_.has_usable_password()
328 user_.set_password(form.cleaned_data['password1'])
332 request.user.message_set.create(message=_("New password set"))
333 if not request.user.is_superuser:
334 form = ChangePasswordForm(user=user_)
336 request.user.message_set.create(message=_("Your password was changed"))
338 return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': user_.id}))
340 form = FormClass(user=user_)
345 provider = AUTH_PROVIDERS.get(k.provider, None)
347 if provider is not None:
348 name = "%s: %s" % (provider.context.human_name, provider.context.readable_key(k))
350 from forum.authentication.base import ConsumerTemplateContext
351 "unknown: %s" % ConsumerTemplateContext.readable_key(k)
353 auth_keys_list.append({
358 return render_to_response('auth/auth_settings.html', {
360 "can_view_private": (user_ == request.user) or request.user.is_superuser,
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))
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)
372 request.user.message_set.create(message=_("You removed the association with %s") % association.provider)
374 return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': association.user.id}))
376 def login_and_forward(request, user, forward=None, message=None):
377 if user.is_suspended():
378 return forward_suspended_user(request, user)
380 user.backend = "django.contrib.auth.backends.ModelBackend"
383 # Store the login action
384 UserLoginAction(user=user, ip=request.META['REMOTE_ADDR']).save()
387 message = _("Welcome back %s, you are now logged in") % smart_unicode(user.username)
389 request.user.message_set.create(message=message)
392 forward = request.session.get(ON_SIGNIN_SESSION_ATTR, reverse('index'))
394 pending_data = request.session.get(PENDING_SUBMISSION_SESSION_ATTR, None)
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"))
407 return manage_pending_data(request, _('save'), forward)
409 additional_get_params = urllib.urlencode(request.GET)
410 return HttpResponseRedirect(forward + "?%s" % additional_get_params)
412 def forward_suspended_user(request, user, show_private_msg=True):
413 message = _("Sorry, but this account is suspended")
415 msg_type = 'privatemsg'
417 msg_type = 'publicmsg'
419 suspension = user.suspension
421 message += (":<br />" + suspension.extra.get(msg_type, ''))
423 request.user.message_set.create(message)
424 return HttpResponseRedirect(reverse('index'))
426 @decorate.withfn(login_required)
427 def signout(request):
429 return HttpResponseRedirect(reverse('index'))