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.actions import EmailValidationAction
18 from forum.utils import html
19 from forum.views.decorators import login_required
20 from forum.modules import decorate
21 from forum.forms import SimpleRegistrationForm, TemporaryLoginRequestForm, ChangePasswordForm, SetPasswordForm
22 from forum.http_responses import HttpResponseUnauthorized
23 from forum.utils.mail import send_template_email
24 from forum.authentication.base import InvalidAuthentication
25 from forum.authentication import AUTH_PROVIDERS
26 from forum.models import User, AuthKeyUserAssociation, ValidationHash
27 from forum.actions import UserJoinsAction, UserLoginAction
28 from forum import settings
30 from vars import ON_SIGNIN_SESSION_ATTR, PENDING_SUBMISSION_SESSION_ATTR
32 def signin_page(request):
33 referer = request.META.get('HTTP_REFERER', '/')
35 # If the referer is equal to the sign up page, e. g. if the previous login attempt was not successful we do not
36 # change the sign in URL. The user should go to the same page.
37 if not referer.replace(settings.APP_URL, '') == reverse('auth_signin'):
38 request.session[ON_SIGNIN_SESSION_ATTR] = referer
40 all_providers = [provider.context for provider in AUTH_PROVIDERS.values() if provider.context]
42 sort = lambda c1, c2: c1.weight - c2.weight
43 can_show = lambda c: not request.user.is_authenticated() or c.show_to_logged_in_user
45 bigicon_providers = sorted([
46 context for context in all_providers if context.mode == 'BIGICON' and can_show(context)
49 smallicon_providers = sorted([
50 context for context in all_providers if context.mode == 'SMALLICON' and can_show(context)
53 top_stackitem_providers = sorted([
54 context for context in all_providers if context.mode == 'TOP_STACK_ITEM' and can_show(context)
57 stackitem_providers = sorted([
58 context for context in all_providers if context.mode == 'STACK_ITEM' and can_show(context)
62 msg = request.session['auth_error']
63 del request.session['auth_error']
67 return render_to_response(
71 'all_providers': all_providers,
72 'bigicon_providers': bigicon_providers,
73 'top_stackitem_providers': top_stackitem_providers,
74 'stackitem_providers': stackitem_providers,
75 'smallicon_providers': smallicon_providers,
77 RequestContext(request))
79 def prepare_provider_signin(request, provider):
80 force_email_request = request.REQUEST.get('validate_email', 'yes') == 'yes'
81 request.session['force_email_request'] = force_email_request
83 if provider in AUTH_PROVIDERS:
84 provider_class = AUTH_PROVIDERS[provider].consumer
87 request_url = provider_class.prepare_authentication_request(request,
88 reverse('auth_provider_done',
89 kwargs={'provider': provider}))
91 return HttpResponseRedirect(request_url)
92 except NotImplementedError, e:
93 return process_provider_signin(request, provider)
94 except InvalidAuthentication, e:
95 request.session['auth_error'] = e.message
97 return HttpResponseRedirect(reverse('auth_signin'))
102 def process_provider_signin(request, provider):
103 if provider in AUTH_PROVIDERS:
104 provider_class = AUTH_PROVIDERS[provider].consumer
107 assoc_key = provider_class.process_authentication_request(request)
108 except InvalidAuthentication, e:
109 request.session['auth_error'] = e.message
110 return HttpResponseRedirect(reverse('auth_signin'))
112 if request.user.is_authenticated():
113 if isinstance(assoc_key, (type, User)):
114 if request.user != assoc_key:
115 request.session['auth_error'] = _(
116 "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
119 request.session['auth_error'] = _("You are already logged in with that user.")
122 assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
123 if assoc.user == request.user:
124 request.session['auth_error'] = _(
125 "These login credentials are already associated with your account.")
127 request.session['auth_error'] = _(
128 "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
131 uassoc = AuthKeyUserAssociation(user=request.user, key=assoc_key, provider=provider)
133 request.user.message_set.create(
134 message=_('The new credentials are now associated with your account'))
135 return HttpResponseRedirect(reverse('user_authsettings', args=[request.user.id]))
137 return HttpResponseRedirect(reverse('auth_signin'))
139 if isinstance(assoc_key, User):
140 return login_and_forward(request, assoc_key)
143 assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
145 return login_and_forward(request, user_)
146 except AuthKeyUserAssociation.DoesNotExist:
147 request.session['assoc_key'] = assoc_key
148 request.session['auth_provider'] = provider
149 return HttpResponseRedirect(reverse('auth_external_register'))
151 return HttpResponseRedirect(reverse('auth_signin'))
153 def external_register(request):
154 if request.method == 'POST' and 'bnewaccount' in request.POST:
155 form1 = SimpleRegistrationForm(request.POST)
158 user_ = User(username=form1.cleaned_data['username'], email=form1.cleaned_data['email'])
159 user_.email_isvalid = request.session.get('auth_validated_email', '') == form1.cleaned_data['email']
160 user_.set_unusable_password()
162 if User.objects.all().count() == 0:
163 user_.is_superuser = True
164 user_.is_staff = True
167 UserJoinsAction(user=user_, ip=request.META['REMOTE_ADDR']).save()
170 assoc_key = request.session['assoc_key']
171 auth_provider = request.session['auth_provider']
173 request.session['auth_error'] = _(
174 "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 logging.error("Missing session data when trying to complete user registration: %s" % ", ".join(
177 ["%s: %s" % (k, v) for k, v in request.META.items()]))
178 return HttpResponseRedirect(reverse('auth_signin'))
180 uassoc = AuthKeyUserAssociation(user=user_, key=assoc_key, provider=auth_provider)
183 del request.session['assoc_key']
184 del request.session['auth_provider']
186 return login_and_forward(request, user_, message=_("A welcome email has been sent to your email address. "))
188 auth_provider = request.session.get('auth_provider', None)
189 if not auth_provider:
190 request.session['auth_error'] = _(
191 "Oops, something went wrong in the middle of this process. Please try again.")
192 logging.error("Missing session data when trying to complete user registration: %s" % ", ".join(
193 ["%s: %s" % (k, v) for k, v in request.META.items()]))
194 return HttpResponseRedirect(reverse('auth_signin'))
196 provider_class = AUTH_PROVIDERS[auth_provider].consumer
198 # Pass the cookies to the Facebook authentication class get_user_data method. We need them to take the access token.
199 if provider_class.__class__.__name__ == 'FacebookAuthConsumer':
200 user_data = provider_class.get_user_data(request.COOKIES)
202 user_data = provider_class.get_user_data(request.session['assoc_key'])
205 user_data = request.session.get('auth_consumer_data', {})
207 username = user_data.get('username', '')
208 email = user_data.get('email', '')
211 request.session['auth_validated_email'] = email
213 form1 = SimpleRegistrationForm(initial={
215 'username': username,
219 provider_context = AUTH_PROVIDERS[request.session['auth_provider']].context
221 return render_to_response('auth/complete.html', {
223 'provider':provider_context and mark_safe(provider_context.human_name) or _('unknown'),
224 'login_type':provider_context.id,
225 'gravatar_faq_url':reverse('faq') + '#gravatar',
226 }, context_instance=RequestContext(request))
228 def request_temp_login(request):
229 if request.method == 'POST':
230 form = TemporaryLoginRequestForm(request.POST)
233 users = form.user_cache
237 return forward_suspended_user(request, u, False)
241 hash = get_object_or_404(ValidationHash, user=u, type='templogin')
242 if hash.expiration < datetime.datetime.now():
244 return request_temp_login(request)
246 hash = ValidationHash.objects.create_new(u, 'templogin', [u.id])
248 send_template_email([u], "auth/temp_login_email.html", {'temp_login_code': hash})
250 request.user.message_set.create(message=_("An email has been sent with your temporary login key"))
252 return HttpResponseRedirect(reverse('index'))
254 form = TemporaryLoginRequestForm()
256 return render_to_response(
257 'auth/temp_login_request.html', {'form': form},
258 context_instance=RequestContext(request))
260 def temp_signin(request, user, code):
261 user = get_object_or_404(User, id=user)
263 if (ValidationHash.objects.validate(code, user, 'templogin', [user.id])):
265 # If the user requests temp_signin he must have forgotten his password. So we mark it as unusable.
266 user.set_unusable_password()
269 return login_and_forward(request, user, reverse('user_authsettings', kwargs={'id': user.id}),
271 "You are logged in with a temporary access key, please take the time to fix your issue with authentication."
276 def send_validation_email(request):
277 if not request.user.is_authenticated():
278 return HttpResponseUnauthorized(request)
280 # We check if there are some old validation hashes. If there are -- we delete them.
282 hash = ValidationHash.objects.get(user=request.user, type='email')
287 # We don't care if there are previous cashes in the database... In every case we have to create a new one
288 hash = ValidationHash.objects.create_new(request.user, 'email', [request.user.email])
290 send_template_email([request.user], "auth/mail_validation.html", {'validation_code': hash})
291 request.user.message_set.create(message=_("A message with an email validation link was just sent to your address."))
292 return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
296 def validate_email(request, user, code):
297 user = get_object_or_404(User, id=user)
299 if (ValidationHash.objects.validate(code, user, 'email', [user.email])):
300 EmailValidationAction(user=user, ip=request.META['REMOTE_ADDR']).save()
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"
377 # Store the login action
378 UserLoginAction(user=user, ip=request.META['REMOTE_ADDR']).save()
381 message = _("Welcome back %s, you are now logged in") % smart_unicode(user.username)
383 request.user.message_set.create(message=message)
386 forward = request.session.get(ON_SIGNIN_SESSION_ATTR, reverse('index'))
388 pending_data = request.session.get(PENDING_SUBMISSION_SESSION_ATTR, None)
390 if pending_data and (user.email_isvalid or pending_data['type'] not in settings.REQUIRE_EMAIL_VALIDATION_TO):
391 submission_time = pending_data['time']
392 if submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.HOLD_PENDING_POSTS_MINUTES)):
393 del request.session[PENDING_SUBMISSION_SESSION_ATTR]
394 elif submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.WARN_PENDING_POSTS_MINUTES)):
395 user.message_set.create(message=(_("You have a %s pending submission.") % pending_data['data_name']) + " %s, %s, %s" % (
396 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('save')}), _("save it")),
397 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('review')}), _("review")),
398 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('cancel')}), _("cancel"))
401 return manage_pending_data(request, _('save'), forward)
403 return HttpResponseRedirect(forward)
405 def forward_suspended_user(request, user, show_private_msg=True):
406 message = _("Sorry, but this account is suspended")
408 msg_type = 'privatemsg'
410 msg_type = 'publicmsg'
412 suspension = user.suspension
414 message += (":<br />" + suspension.extra.get(msg_type, ''))
416 request.user.message_set.create(message)
417 return HttpResponseRedirect(reverse('index'))
419 @decorate.withfn(login_required)
420 def signout(request):
422 return HttpResponseRedirect(reverse('index'))