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 user_data = provider_class.get_user_data(request.session['assoc_key'])
202 user_data = request.session.get('auth_consumer_data', {})
204 username = user_data.get('username', '')
205 email = user_data.get('email', '')
208 request.session['auth_validated_email'] = email
210 form1 = SimpleRegistrationForm(initial={
212 'username': username,
216 provider_context = AUTH_PROVIDERS[request.session['auth_provider']].context
218 return render_to_response('auth/complete.html', {
220 'provider':provider_context and mark_safe(provider_context.human_name) or _('unknown'),
221 'login_type':provider_context.id,
222 'gravatar_faq_url':reverse('faq') + '#gravatar',
223 }, context_instance=RequestContext(request))
225 def request_temp_login(request):
226 if request.method == 'POST':
227 form = TemporaryLoginRequestForm(request.POST)
230 users = form.user_cache
234 return forward_suspended_user(request, u, False)
238 hash = get_object_or_404(ValidationHash, user=u, type='templogin')
239 if hash.expiration < datetime.datetime.now():
241 return request_temp_login(request)
243 hash = ValidationHash.objects.create_new(u, 'templogin', [u.id])
245 send_template_email([u], "auth/temp_login_email.html", {'temp_login_code': hash})
247 request.user.message_set.create(message=_("An email has been sent with your temporary login key"))
249 return HttpResponseRedirect(reverse('index'))
251 form = TemporaryLoginRequestForm()
253 return render_to_response(
254 'auth/temp_login_request.html', {'form': form},
255 context_instance=RequestContext(request))
257 def temp_signin(request, user, code):
258 user = get_object_or_404(User, id=user)
260 if (ValidationHash.objects.validate(code, user, 'templogin', [user.id])):
262 # If the user requests temp_signin he must have forgotten his password. So we mark it as unusable.
263 user.set_unusable_password()
266 return login_and_forward(request, user, reverse('user_authsettings', kwargs={'id': user.id}),
268 "You are logged in with a temporary access key, please take the time to fix your issue with authentication."
273 def send_validation_email(request):
274 if not request.user.is_authenticated():
275 return HttpResponseUnauthorized(request)
277 # We check if there are some old validation hashes. If there are -- we delete them.
279 hash = ValidationHash.objects.get(user=request.user, type='email')
284 # We don't care if there are previous cashes in the database... In every case we have to create a new one
285 hash = ValidationHash.objects.create_new(request.user, 'email', [request.user.email])
287 additional_get_params = urllib.urlencode(request.GET)
288 send_template_email([request.user], "auth/mail_validation.html", {
289 'validation_code': hash,
290 'additional_get_params' : additional_get_params
293 request.user.message_set.create(message=_("A message with an email validation link was just sent to your address."))
294 return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
298 def validate_email(request, user, code):
299 user = get_object_or_404(User, id=user)
301 if (ValidationHash.objects.validate(code, user, 'email', [user.email])):
302 EmailValidationAction(user=user, ip=request.META['REMOTE_ADDR']).save()
303 return login_and_forward(request, user, reverse('index'), _("Thank you, your email is now validated."))
305 return render_to_response('auth/mail_already_validated.html', { 'user' : user }, RequestContext(request))
307 def auth_settings(request, id):
308 user_ = get_object_or_404(User, id=id)
310 if not (request.user.is_superuser or request.user == user_):
311 return HttpResponseUnauthorized(request)
313 auth_keys = user_.auth_keys.all()
315 if request.user.is_superuser or (not user_.has_usable_password()):
316 FormClass = SetPasswordForm
318 FormClass = ChangePasswordForm
321 form = FormClass(request.POST, user=user_)
323 is_new_pass = not user_.has_usable_password()
324 user_.set_password(form.cleaned_data['password1'])
328 request.user.message_set.create(message=_("New password set"))
329 if not request.user.is_superuser:
330 form = ChangePasswordForm(user=user_)
332 request.user.message_set.create(message=_("Your password was changed"))
334 return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': user_.id}))
336 form = FormClass(user=user_)
341 provider = AUTH_PROVIDERS.get(k.provider, None)
343 if provider is not None:
344 name = "%s: %s" % (provider.context.human_name, provider.context.readable_key(k))
346 from forum.authentication.base import ConsumerTemplateContext
347 "unknown: %s" % ConsumerTemplateContext.readable_key(k)
349 auth_keys_list.append({
354 return render_to_response('auth/auth_settings.html', {
356 "can_view_private": (user_ == request.user) or request.user.is_superuser,
358 'has_password': user_.has_usable_password(),
359 'auth_keys': auth_keys_list,
360 'allow_local_auth': AUTH_PROVIDERS.get('local', None),
361 }, context_instance=RequestContext(request))
363 def remove_external_provider(request, id):
364 association = get_object_or_404(AuthKeyUserAssociation, id=id)
365 if not (request.user.is_superuser or request.user == association.user):
366 return HttpResponseUnauthorized(request)
368 request.user.message_set.create(message=_("You removed the association with %s") % association.provider)
370 return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': association.user.id}))
372 def login_and_forward(request, user, forward=None, message=None):
373 if user.is_suspended():
374 return forward_suspended_user(request, user)
376 user.backend = "django.contrib.auth.backends.ModelBackend"
379 # Store the login action
380 UserLoginAction(user=user, ip=request.META['REMOTE_ADDR']).save()
383 message = _("Welcome back %s, you are now logged in") % smart_unicode(user.username)
385 request.user.message_set.create(message=message)
388 forward = request.session.get(ON_SIGNIN_SESSION_ATTR, reverse('index'))
390 pending_data = request.session.get(PENDING_SUBMISSION_SESSION_ATTR, None)
392 if pending_data and (user.email_isvalid or pending_data['type'] not in settings.REQUIRE_EMAIL_VALIDATION_TO):
393 submission_time = pending_data['time']
394 if submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.HOLD_PENDING_POSTS_MINUTES)):
395 del request.session[PENDING_SUBMISSION_SESSION_ATTR]
396 elif submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.WARN_PENDING_POSTS_MINUTES)):
397 user.message_set.create(message=(_("You have a %s pending submission.") % pending_data['data_name']) + " %s, %s, %s" % (
398 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('save')}), _("save it")),
399 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('review')}), _("review")),
400 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('cancel')}), _("cancel"))
403 return manage_pending_data(request, _('save'), forward)
405 additional_get_params = urllib.urlencode(request.GET)
406 return HttpResponseRedirect(forward + "?%s" % additional_get_params)
408 def forward_suspended_user(request, user, show_private_msg=True):
409 message = _("Sorry, but this account is suspended")
411 msg_type = 'privatemsg'
413 msg_type = 'publicmsg'
415 suspension = user.suspension
417 message += (":<br />" + suspension.extra.get(msg_type, ''))
419 request.user.message_set.create(message)
420 return HttpResponseRedirect(reverse('index'))
422 @decorate.withfn(login_required)
423 def signout(request):
425 return HttpResponseRedirect(reverse('index'))