1 from django.shortcuts import render_to_response, get_object_or_404
2 from django.template import RequestContext
3 from django.core.urlresolvers import reverse
4 from forum.models import User
5 from django.http import HttpResponseRedirect, Http404
6 from forum.http_responses import HttpResponseUnauthorized
7 from django.utils.safestring import mark_safe
8 from django.utils.translation import ugettext as _
9 from django.utils.http import urlquote_plus
10 from forum.views.decorators import login_required
11 from forum.modules import decorate
12 from django.contrib.auth import login, logout
13 from django.http import get_host
14 from forum.actions import SuspendAction
15 from forum.utils import html
16 from forum import settings
17 from writers import manage_pending_data
22 from forum.forms import SimpleRegistrationForm, SimpleEmailSubscribeForm, \
23 TemporaryLoginRequestForm, ChangePasswordForm, SetPasswordForm
24 from forum.utils.mail import send_template_email
26 from forum.authentication.base import InvalidAuthentication
27 from forum.authentication import AUTH_PROVIDERS
29 from forum.models import AuthKeyUserAssociation, ValidationHash, Question, Answer
30 from forum.actions import UserJoinsAction, EmailValidationAction
31 from forum.models.action import ActionRepute
33 from forum.settings import REP_GAIN_BY_EMAIL_VALIDATION
35 def signin_page(request):
36 referer = request.META.get('HTTP_REFERER', '/')
38 # If the referer is equal to the sign up page, e. g. if the previous login attempt was not successful we do not
39 # change the sign in URL. The user should go to the same page.
40 if not referer.replace(settings.APP_URL, '') == reverse('auth_signin'):
41 request.session['on_signin_url'] = referer
43 all_providers = [provider.context for provider in AUTH_PROVIDERS.values()]
45 sort = lambda c1, c2: c1.weight - c2.weight
46 can_show = lambda c: not request.user.is_authenticated() or c.show_to_logged_in_user
48 bigicon_providers = sorted([
49 context for context in all_providers if context.mode == 'BIGICON' and can_show(context)
52 smallicon_providers = sorted([
53 context for context in all_providers if context.mode == 'SMALLICON' and can_show(context)
56 top_stackitem_providers = sorted([
57 context for context in all_providers if context.mode == 'TOP_STACK_ITEM' and can_show(context)
60 stackitem_providers = sorted([
61 context for context in all_providers if context.mode == 'STACK_ITEM' and can_show(context)
65 msg = request.session['auth_error']
66 del request.session['auth_error']
70 return render_to_response(
74 'all_providers': all_providers,
75 'bigicon_providers': bigicon_providers,
76 'top_stackitem_providers': top_stackitem_providers,
77 'stackitem_providers': stackitem_providers,
78 'smallicon_providers': smallicon_providers,
80 RequestContext(request))
82 def prepare_provider_signin(request, provider):
83 force_email_request = request.REQUEST.get('validate_email', 'yes') == 'yes'
84 request.session['force_email_request'] = force_email_request
86 if provider in AUTH_PROVIDERS:
87 provider_class = AUTH_PROVIDERS[provider].consumer
90 request_url = provider_class.prepare_authentication_request(request,
91 reverse('auth_provider_done',
92 kwargs={'provider': provider}))
94 return HttpResponseRedirect(request_url)
95 except NotImplementedError, e:
96 return process_provider_signin(request, provider)
97 except InvalidAuthentication, e:
98 request.session['auth_error'] = e.message
100 return HttpResponseRedirect(reverse('auth_signin'))
105 def process_provider_signin(request, provider):
106 if provider in AUTH_PROVIDERS:
107 provider_class = AUTH_PROVIDERS[provider].consumer
110 assoc_key = provider_class.process_authentication_request(request)
111 except InvalidAuthentication, e:
112 request.session['auth_error'] = e.message
113 return HttpResponseRedirect(reverse('auth_signin'))
115 if request.user.is_authenticated():
116 if isinstance(assoc_key, (type, User)):
117 if request.user != assoc_key:
118 request.session['auth_error'] = _(
119 "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
122 request.session['auth_error'] = _("You are already logged in with that user.")
125 assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
126 if assoc.user == request.user:
127 request.session['auth_error'] = _(
128 "These login credentials are already associated with your account.")
130 request.session['auth_error'] = _(
131 "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
134 uassoc = AuthKeyUserAssociation(user=request.user, key=assoc_key, provider=provider)
136 request.user.message_set.create(
137 message=_('The new credentials are now associated with your account'))
138 return HttpResponseRedirect(reverse('user_authsettings', args=[request.user.id]))
140 return HttpResponseRedirect(reverse('auth_signin'))
142 if isinstance(assoc_key, User):
143 return login_and_forward(request, assoc_key)
146 assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
148 return login_and_forward(request, user_)
150 request.session['assoc_key'] = assoc_key
151 request.session['auth_provider'] = provider
152 return HttpResponseRedirect(reverse('auth_external_register'))
154 return HttpResponseRedirect(reverse('auth_signin'))
156 def external_register(request):
157 if request.method == 'POST' and 'bnewaccount' in request.POST:
158 form1 = SimpleRegistrationForm(request.POST)
159 email_feeds_form = SimpleEmailSubscribeForm(request.POST)
161 if (form1.is_valid() and email_feeds_form.is_valid()):
162 user_ = User(username=form1.cleaned_data['username'], email=form1.cleaned_data['email'])
163 user_.email_isvalid = request.session.get('auth_validated_email', '') == form1.cleaned_data['email']
164 user_.set_unusable_password()
166 if User.objects.all().count() == 0:
167 user_.is_superuser = True
168 user_.is_staff = True
171 UserJoinsAction(user=user_, ip=request.META['REMOTE_ADDR']).save()
174 assoc_key = request.session['assoc_key']
175 auth_provider = request.session['auth_provider']
177 request.session['auth_error'] = _(
178 "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."
180 logging.error("Missing session data when trying to complete user registration: %s" % ", ".join(
181 ["%s: %s" % (k, v) for k, v in request.META.items()]))
182 return HttpResponseRedirect(reverse('auth_signin'))
184 uassoc = AuthKeyUserAssociation(user=user_, key=assoc_key, provider=auth_provider)
187 if email_feeds_form.cleaned_data['subscribe'] == 'n':
188 user_.subscription_settings.enable_notifications = False
189 user_.subscription_settings.save()
191 del request.session['assoc_key']
192 del request.session['auth_provider']
194 return login_and_forward(request, user_, message=_("A welcome email has been sent to your email address. "))
196 auth_provider = request.session.get('auth_provider', None)
197 if not auth_provider:
198 request.session['auth_error'] = _(
199 "Oops, something went wrong in the middle of this process. Please try again.")
200 logging.error("Missing session data when trying to complete user registration: %s" % ", ".join(
201 ["%s: %s" % (k, v) for k, v in request.META.items()]))
202 return HttpResponseRedirect(reverse('auth_signin'))
204 provider_class = AUTH_PROVIDERS[auth_provider].consumer
205 user_data = provider_class.get_user_data(request.session['assoc_key'])
208 user_data = request.session.get('auth_consumer_data', {})
210 username = user_data.get('username', '')
211 email = user_data.get('email', '')
214 request.session['auth_validated_email'] = email
216 form1 = SimpleRegistrationForm(initial={
218 'username': username,
221 email_feeds_form = SimpleEmailSubscribeForm()
223 provider_context = AUTH_PROVIDERS[request.session['auth_provider']].context
225 return render_to_response('auth/complete.html', {
227 'email_feeds_form': email_feeds_form,
228 'provider':mark_safe(provider_context.human_name),
229 'login_type':provider_context.id,
230 'gravatar_faq_url':reverse('faq') + '#gravatar',
231 }, context_instance=RequestContext(request))
233 def request_temp_login(request):
234 if request.method == 'POST':
235 form = TemporaryLoginRequestForm(request.POST)
238 users = form.user_cache
242 return forward_suspended_user(request, u, False)
246 hash = get_object_or_404(ValidationHash, user=u, type='templogin')
247 if hash.expiration < datetime.datetime.now():
249 return request_temp_login(request)
251 hash = ValidationHash.objects.create_new(u, 'templogin', [u.id])
253 send_template_email([u], "auth/temp_login_email.html", {'temp_login_code': hash})
255 request.user.message_set.create(message=_("An email has been sent with your temporary login key"))
257 return HttpResponseRedirect(reverse('index'))
259 form = TemporaryLoginRequestForm()
261 return render_to_response(
262 'auth/temp_login_request.html', {'form': form},
263 context_instance=RequestContext(request))
265 def temp_signin(request, user, code):
266 user = get_object_or_404(User, id=user)
268 if (ValidationHash.objects.validate(code, user, 'templogin', [user.id])):
270 # If the user requests temp_signin he must have forgotten his password. So we mark it as unusable.
271 user.set_unusable_password()
274 return login_and_forward(request, user, reverse('user_authsettings', kwargs={'id': user.id}),
276 "You are logged in with a temporary access key, please take the time to fix your issue with authentication."
281 def send_validation_email(request):
282 if not request.user.is_authenticated():
283 return HttpResponseUnauthorized(request)
285 # We check if there are some old validation hashes. If there are -- we delete them.
287 hash = ValidationHash.objects.get(user=request.user, type='email')
292 # We don't care if there are previous cashes in the database... In every case we have to create a new one
293 hash = ValidationHash.objects.create_new(request.user, 'email', [request.user.email])
295 send_template_email([request.user], "auth/mail_validation.html", {'validation_code': hash})
296 request.user.message_set.create(message=_("A message with an email validation link was just sent to your address."))
297 return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
301 def validate_email(request, user, code):
302 user = get_object_or_404(User, id=user)
304 if (ValidationHash.objects.validate(code, user, 'email', [user.email])):
305 EmailValidationAction(user=user, ip=request.META['REMOTE_ADDR']).save()
306 if REP_GAIN_BY_EMAIL_VALIDATION > 0:
307 message = _("Thank you, your email is now validated and you've got %d points." % int(REP_GAIN_BY_EMAIL_VALIDATION))
310 message = _("Thank you, your email is now validated.")
312 return login_and_forward(request, user, reverse('index'), message)
314 return render_to_response('auth/mail_already_validated.html', { 'user' : user }, RequestContext(request))
316 def auth_settings(request, id):
317 user_ = get_object_or_404(User, id=id)
319 if not (request.user.is_superuser or request.user == user_):
320 return HttpResponseUnauthorized(request)
322 auth_keys = user_.auth_keys.all()
324 if request.user.is_superuser or (not user_.has_usable_password()):
325 FormClass = SetPasswordForm
327 FormClass = ChangePasswordForm
330 form = FormClass(request.POST, user=user_)
332 is_new_pass = not user_.has_usable_password()
333 user_.set_password(form.cleaned_data['password1'])
337 request.user.message_set.create(message=_("New password set"))
338 if not request.user.is_superuser:
339 form = ChangePasswordForm(user=user_)
341 request.user.message_set.create(message=_("Your password was changed"))
343 return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': user_.id}))
345 form = FormClass(user=user_)
350 provider = AUTH_PROVIDERS.get(k.provider, None)
352 if provider is not None:
353 name = "%s: %s" % (provider.context.human_name, provider.context.readable_key(k))
355 from forum.authentication.base import ConsumerTemplateContext
356 "unknown: %s" % ConsumerTemplateContext.readable_key(k)
358 auth_keys_list.append({
363 return render_to_response('auth/auth_settings.html', {
365 "can_view_private": (user_ == request.user) or request.user.is_superuser,
367 'has_password': user_.has_usable_password(),
368 'auth_keys': auth_keys_list,
369 'allow_local_auth': AUTH_PROVIDERS.get('local', None),
370 }, context_instance=RequestContext(request))
372 def remove_external_provider(request, id):
373 association = get_object_or_404(AuthKeyUserAssociation, id=id)
374 if not (request.user.is_superuser or request.user == association.user):
375 return HttpResponseUnauthorized(request)
377 request.user.message_set.create(message=_("You removed the association with %s") % association.provider)
379 return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': association.user.id}))
381 def login_and_forward(request, user, forward=None, message=None):
382 if user.is_suspended():
383 return forward_suspended_user(request, user)
385 user.backend = "django.contrib.auth.backends.ModelBackend"
389 message = _("Welcome back %s, you are now logged in") % user.username
391 request.user.message_set.create(message=message)
394 forward = request.session.get('on_signin_url', reverse('index'))
396 pending_data = request.session.get('pending_submission_data', None)
398 if pending_data and (user.email_isvalid or pending_data['type'] not in settings.REQUIRE_EMAIL_VALIDATION_TO):
399 submission_time = pending_data['time']
400 if submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.HOLD_PENDING_POSTS_MINUTES)):
401 del request.session['pending_submission_data']
402 elif submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.WARN_PENDING_POSTS_MINUTES)):
403 user.message_set.create(message=(_("You have a %s pending submission.") % pending_data['data_name']) + " %s, %s, %s" % (
404 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('save')}), _("save it")),
405 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('review')}), _("review")),
406 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('cancel')}), _("cancel"))
409 return manage_pending_data(request, _('save'), forward)
411 return HttpResponseRedirect(forward)
413 def forward_suspended_user(request, user, show_private_msg=True):
414 message = _("Sorry, but this account is suspended")
416 msg_type = 'privatemsg'
418 msg_type = 'publicmsg'
420 suspension = user.suspension
422 message += (":<br />" + suspension.extra.get(msg_type, ''))
424 request.user.message_set.create(message)
425 return HttpResponseRedirect(reverse('index'))
427 @decorate.withfn(login_required)
428 def signout(request):
430 return HttpResponseRedirect(reverse('index'))