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 writers import manage_pending_data
19 from forum.actions import EmailValidationAction
20 from forum.utils import html
21 from forum.views.decorators import login_required
22 from forum.modules import decorate
23 from forum.forms import SimpleRegistrationForm, TemporaryLoginRequestForm, ChangePasswordForm, SetPasswordForm
24 from forum.http_responses import HttpResponseUnauthorized
25 from forum.utils.mail import send_template_email
26 from forum.authentication.base import InvalidAuthentication
27 from forum.authentication import AUTH_PROVIDERS
28 from forum.models import User, AuthKeyUserAssociation, ValidationHash
29 from forum.actions import UserJoinsAction, UserLoginAction
30 from forum import settings
32 from vars import ON_SIGNIN_SESSION_ATTR, PENDING_SUBMISSION_SESSION_ATTR
34 def signin_page(request):
35 referer = request.META.get('HTTP_REFERER', '/')
37 # If the referer is equal to the sign up page, e. g. if the previous login attempt was not successful we do not
38 # change the sign in URL. The user should go to the same page.
39 if not referer.replace(settings.APP_URL, '') == reverse('auth_signin'):
40 request.session[ON_SIGNIN_SESSION_ATTR] = referer
42 all_providers = [provider.context for provider in AUTH_PROVIDERS.values() if provider.context]
44 sort = lambda c1, c2: c1.weight - c2.weight
45 can_show = lambda c: not request.user.is_authenticated() or c.show_to_logged_in_user
47 bigicon_providers = sorted([
48 context for context in all_providers if context.mode == 'BIGICON' and can_show(context)
51 smallicon_providers = sorted([
52 context for context in all_providers if context.mode == 'SMALLICON' and can_show(context)
55 top_stackitem_providers = sorted([
56 context for context in all_providers if context.mode == 'TOP_STACK_ITEM' and can_show(context)
59 stackitem_providers = sorted([
60 context for context in all_providers if context.mode == 'STACK_ITEM' and can_show(context)
64 msg = request.session['auth_error']
65 del request.session['auth_error']
69 return render_to_response(
73 'all_providers': all_providers,
74 'bigicon_providers': bigicon_providers,
75 'top_stackitem_providers': top_stackitem_providers,
76 'stackitem_providers': stackitem_providers,
77 'smallicon_providers': smallicon_providers,
79 RequestContext(request))
81 def prepare_provider_signin(request, provider):
82 force_email_request = request.REQUEST.get('validate_email', 'yes') == 'yes'
83 request.session['force_email_request'] = force_email_request
85 if provider in AUTH_PROVIDERS:
86 provider_class = AUTH_PROVIDERS[provider].consumer
89 request_url = provider_class.prepare_authentication_request(request,
90 reverse('auth_provider_done',
91 kwargs={'provider': provider}))
93 return HttpResponseRedirect(request_url)
94 except NotImplementedError, e:
95 return process_provider_signin(request, provider)
96 except InvalidAuthentication, e:
97 request.session['auth_error'] = e.message
99 return HttpResponseRedirect(reverse('auth_signin'))
104 def process_provider_signin(request, provider):
105 if provider in AUTH_PROVIDERS:
106 provider_class = AUTH_PROVIDERS[provider].consumer
109 assoc_key = provider_class.process_authentication_request(request)
110 except InvalidAuthentication, e:
111 request.session['auth_error'] = e.message
112 return HttpResponseRedirect(reverse('auth_signin'))
114 if request.user.is_authenticated():
115 if isinstance(assoc_key, (type, User)):
116 if request.user != assoc_key:
117 request.session['auth_error'] = _(
118 "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
121 request.session['auth_error'] = _("You are already logged in with that user.")
124 assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
125 if assoc.user == request.user:
126 request.session['auth_error'] = _(
127 "These login credentials are already associated with your account.")
129 request.session['auth_error'] = _(
130 "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
133 uassoc = AuthKeyUserAssociation(user=request.user, key=assoc_key, provider=provider)
135 request.user.message_set.create(
136 message=_('The new credentials are now associated with your account'))
137 return HttpResponseRedirect(reverse('user_authsettings', args=[request.user.id]))
139 return HttpResponseRedirect(reverse('auth_signin'))
141 if isinstance(assoc_key, User):
142 return login_and_forward(request, assoc_key)
145 assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
147 return login_and_forward(request, user_)
148 except AuthKeyUserAssociation.DoesNotExist:
149 request.session['assoc_key'] = assoc_key
150 request.session['auth_provider'] = provider
151 return HttpResponseRedirect(reverse('auth_external_register'))
153 return HttpResponseRedirect(reverse('auth_signin'))
155 def external_register(request):
156 if request.method == 'POST' and 'bnewaccount' in request.POST:
157 form1 = SimpleRegistrationForm(request.POST)
160 user_ = User(username=form1.cleaned_data['username'], email=form1.cleaned_data['email'], real_name=form1.cleaned_data['real_name'])
161 user_.email_isvalid = request.session.get('auth_validated_email', '') == form1.cleaned_data['email']
162 user_.set_unusable_password()
164 if User.objects.all().count() == 0:
165 user_.is_superuser = True
166 user_.is_staff = True
169 UserJoinsAction(user=user_, ip=request.META['REMOTE_ADDR']).save()
172 assoc_key = request.session['assoc_key']
173 auth_provider = request.session['auth_provider']
175 request.session['auth_error'] = _(
176 "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."
178 logging.error("Missing session data when trying to complete user registration: %s" % ", ".join(
179 ["%s: %s" % (k, v) for k, v in request.META.items()]))
180 return HttpResponseRedirect(reverse('auth_signin'))
182 uassoc = AuthKeyUserAssociation(user=user_, key=assoc_key, provider=auth_provider)
185 del request.session['assoc_key']
186 del request.session['auth_provider']
188 return login_and_forward(request, user_, message=_("A welcome email has been sent to your email address. "))
190 auth_provider = request.session.get('auth_provider', None)
191 if not auth_provider:
192 request.session['auth_error'] = _(
193 "Oops, something went wrong in the middle of this process. Please try again.")
194 logging.error("Missing session data when trying to complete user registration: %s" % ", ".join(
195 ["%s: %s" % (k, v) for k, v in request.META.items()]))
196 return HttpResponseRedirect(reverse('auth_signin'))
198 provider_class = AUTH_PROVIDERS[auth_provider].consumer
200 if provider_class.__class__.__name__ == 'FacebookAuthConsumer':
201 user_data = provider_class.get_user_data(request.session['access_token'])
203 user_data = provider_class.get_user_data(request.session['assoc_key'])
207 user_data = request.session.get('auth_consumer_data', {})
209 username = user_data.get('username', '')
210 email = user_data.get('email', '')
211 real_name = user_data.get('real_name', '')
214 request.session['auth_validated_email'] = email
216 form1 = SimpleRegistrationForm(initial={
218 'username': username,
220 'real_name': real_name,
223 provider_context = AUTH_PROVIDERS[request.session['auth_provider']].context
225 return render_to_response('auth/complete.html', {
227 'provider':provider_context and mark_safe(provider_context.human_name) or _('unknown'),
228 'login_type':provider_context.id,
229 'gravatar_faq_url':reverse('faq') + '#gravatar',
230 }, context_instance=RequestContext(request))
232 def request_temp_login(request):
233 if request.method == 'POST':
234 form = TemporaryLoginRequestForm(request.POST)
237 users = form.user_cache
241 return forward_suspended_user(request, u, False)
245 hash = get_object_or_404(ValidationHash, user=u, type='templogin')
246 if hash.expiration < datetime.datetime.now():
248 return request_temp_login(request)
250 hash = ValidationHash.objects.create_new(u, 'templogin', [u.id])
252 send_template_email([u], "auth/temp_login_email.html", {'temp_login_code': hash})
254 request.user.message_set.create(message=_("An email has been sent with your temporary login key"))
256 return HttpResponseRedirect(reverse('index'))
258 form = TemporaryLoginRequestForm()
260 return render_to_response(
261 'auth/temp_login_request.html', {'form': form},
262 context_instance=RequestContext(request))
264 def temp_signin(request, user, code):
265 user = get_object_or_404(User, id=user)
267 if (ValidationHash.objects.validate(code, user, 'templogin', [user.id])):
269 # If the user requests temp_signin he must have forgotten his password. So we mark it as unusable.
270 user.set_unusable_password()
273 return login_and_forward(request, user, reverse('user_authsettings', kwargs={'id': user.id}),
275 "You are logged in with a temporary access key, please take the time to fix your issue with authentication."
280 def send_validation_email(request):
281 if not request.user.is_authenticated():
282 return HttpResponseUnauthorized(request)
284 # We check if there are some old validation hashes. If there are -- we delete them.
286 hash = ValidationHash.objects.get(user=request.user, type='email')
291 # We don't care if there are previous cashes in the database... In every case we have to create a new one
292 hash = ValidationHash.objects.create_new(request.user, 'email', [request.user.email])
294 additional_get_params = urllib.urlencode(dict([k, v.encode('utf-8')] for k, v in request.GET.items()))
295 send_template_email([request.user], "auth/mail_validation.html", {
296 'validation_code': hash,
297 'additional_get_params' : additional_get_params
300 request.user.message_set.create(message=_("A message with an email validation link was just sent to your address."))
301 return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
305 def validate_email(request, user, code):
306 user = get_object_or_404(User, id=user)
308 if (ValidationHash.objects.validate(code, user, 'email', [user.email])):
309 EmailValidationAction(user=user, ip=request.META['REMOTE_ADDR']).save()
310 return login_and_forward(request, user, reverse('index'), _("Thank you, your email is now validated."))
312 return render_to_response('auth/mail_already_validated.html', { 'user' : user }, RequestContext(request))
314 def auth_settings(request, id):
315 user_ = get_object_or_404(User, id=id)
317 if not (request.user.is_superuser or request.user == user_):
318 return HttpResponseUnauthorized(request)
320 auth_keys = user_.auth_keys.all()
322 if request.user.is_superuser or (not user_.has_usable_password()):
323 FormClass = SetPasswordForm
325 FormClass = ChangePasswordForm
328 form = FormClass(request.POST, user=user_)
330 is_new_pass = not user_.has_usable_password()
331 user_.set_password(form.cleaned_data['password1'])
335 request.user.message_set.create(message=_("New password set"))
336 if not request.user.is_superuser:
337 form = ChangePasswordForm(user=user_)
339 request.user.message_set.create(message=_("Your password was changed"))
341 return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': user_.id}))
343 form = FormClass(user=user_)
348 provider = AUTH_PROVIDERS.get(k.provider, None)
350 if provider is not None:
351 name = "%s: %s" % (provider.context.human_name, provider.context.readable_key(k))
353 from forum.authentication.base import ConsumerTemplateContext
354 "unknown: %s" % ConsumerTemplateContext.readable_key(k)
356 auth_keys_list.append({
361 return render_to_response('auth/auth_settings.html', {
363 "can_view_private": (user_ == request.user) or request.user.is_superuser,
365 'has_password': user_.has_usable_password(),
366 'auth_keys': auth_keys_list,
367 'allow_local_auth': AUTH_PROVIDERS.get('local', None),
368 }, context_instance=RequestContext(request))
370 def remove_external_provider(request, id):
371 association = get_object_or_404(AuthKeyUserAssociation, id=id)
372 if not (request.user.is_superuser or request.user == association.user):
373 return HttpResponseUnauthorized(request)
375 request.user.message_set.create(message=_("You removed the association with %s") % association.provider)
377 return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': association.user.id}))
379 def login_and_forward(request, user, forward=None, message=None):
380 if user.is_suspended():
381 return forward_suspended_user(request, user)
383 user.backend = "django.contrib.auth.backends.ModelBackend"
386 # Store the login action
387 UserLoginAction(user=user, ip=request.META['REMOTE_ADDR']).save()
390 message = _("Welcome back %s, you are now logged in") % smart_unicode(user.username)
392 request.user.message_set.create(message=message)
395 forward = request.session.get(ON_SIGNIN_SESSION_ATTR, reverse('index'))
397 pending_data = request.session.get(PENDING_SUBMISSION_SESSION_ATTR, None)
399 if pending_data and (user.email_isvalid or pending_data['type'] not in settings.REQUIRE_EMAIL_VALIDATION_TO):
400 submission_time = pending_data['time']
401 if submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.HOLD_PENDING_POSTS_MINUTES)):
402 del request.session[PENDING_SUBMISSION_SESSION_ATTR]
403 elif submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.WARN_PENDING_POSTS_MINUTES)):
404 user.message_set.create(message=(_("You have a %s pending submission.") % pending_data['data_name']) + " %s, %s, %s" % (
405 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('save')}), _("save it")),
406 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('review')}), _("review")),
407 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('cancel')}), _("cancel"))
410 return manage_pending_data(request, _('save'), forward)
412 additional_get_params = urllib.urlencode(dict([k, v.encode('utf-8')] for k, v in request.GET.items()))
414 parsed_forward = urlparse(forward)
416 # If there is already some parsed query in the URL then change the forward URL
417 if parsed_forward.query:
418 forward_url = forward + "&%s" % additional_get_params
420 forward_url = forward + "?%s" % additional_get_params
422 return HttpResponseRedirect(forward_url)
424 def forward_suspended_user(request, user, show_private_msg=True):
425 message = _("Sorry, but this account is suspended")
427 msg_type = 'privatemsg'
429 msg_type = 'publicmsg'
431 suspension = user.suspension
433 message += (":<br />" + suspension.extra.get(msg_type, ''))
435 request.user.message_set.create(message)
436 return HttpResponseRedirect(reverse('index'))
438 @decorate.withfn(login_required)
439 def signout(request):
441 return HttpResponseRedirect(reverse('index'))