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
34 from forum.settings import REP_GAIN_BY_EMAIL_VALIDATION
35 from vars import ON_SIGNIN_SESSION_ATTR, PENDING_SUBMISSION_SESSION_ATTR
37 def signin_page(request):
38 referer = request.META.get('HTTP_REFERER', '/')
40 # If the referer is equal to the sign up page, e. g. if the previous login attempt was not successful we do not
41 # change the sign in URL. The user should go to the same page.
42 if not referer.replace(settings.APP_URL, '') == reverse('auth_signin'):
43 request.session[ON_SIGNIN_SESSION_ATTR] = referer
45 all_providers = [provider.context for provider in AUTH_PROVIDERS.values() if provider.context]
47 sort = lambda c1, c2: c1.weight - c2.weight
48 can_show = lambda c: not request.user.is_authenticated() or c.show_to_logged_in_user
50 bigicon_providers = sorted([
51 context for context in all_providers if context.mode == 'BIGICON' and can_show(context)
54 smallicon_providers = sorted([
55 context for context in all_providers if context.mode == 'SMALLICON' and can_show(context)
58 top_stackitem_providers = sorted([
59 context for context in all_providers if context.mode == 'TOP_STACK_ITEM' and can_show(context)
62 stackitem_providers = sorted([
63 context for context in all_providers if context.mode == 'STACK_ITEM' and can_show(context)
67 msg = request.session['auth_error']
68 del request.session['auth_error']
72 return render_to_response(
76 'all_providers': all_providers,
77 'bigicon_providers': bigicon_providers,
78 'top_stackitem_providers': top_stackitem_providers,
79 'stackitem_providers': stackitem_providers,
80 'smallicon_providers': smallicon_providers,
82 RequestContext(request))
84 def prepare_provider_signin(request, provider):
85 force_email_request = request.REQUEST.get('validate_email', 'yes') == 'yes'
86 request.session['force_email_request'] = force_email_request
88 if provider in AUTH_PROVIDERS:
89 provider_class = AUTH_PROVIDERS[provider].consumer
92 request_url = provider_class.prepare_authentication_request(request,
93 reverse('auth_provider_done',
94 kwargs={'provider': provider}))
96 return HttpResponseRedirect(request_url)
97 except NotImplementedError, e:
98 return process_provider_signin(request, provider)
99 except InvalidAuthentication, e:
100 request.session['auth_error'] = e.message
102 return HttpResponseRedirect(reverse('auth_signin'))
107 def process_provider_signin(request, provider):
108 if provider in AUTH_PROVIDERS:
109 provider_class = AUTH_PROVIDERS[provider].consumer
112 assoc_key = provider_class.process_authentication_request(request)
113 except InvalidAuthentication, e:
114 request.session['auth_error'] = e.message
115 return HttpResponseRedirect(reverse('auth_signin'))
117 if request.user.is_authenticated():
118 if isinstance(assoc_key, (type, User)):
119 if request.user != assoc_key:
120 request.session['auth_error'] = _(
121 "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
124 request.session['auth_error'] = _("You are already logged in with that user.")
127 assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
128 if assoc.user == request.user:
129 request.session['auth_error'] = _(
130 "These login credentials are already associated with your account.")
132 request.session['auth_error'] = _(
133 "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
136 uassoc = AuthKeyUserAssociation(user=request.user, key=assoc_key, provider=provider)
138 request.user.message_set.create(
139 message=_('The new credentials are now associated with your account'))
140 return HttpResponseRedirect(reverse('user_authsettings', args=[request.user.id]))
142 return HttpResponseRedirect(reverse('auth_signin'))
144 if isinstance(assoc_key, User):
145 return login_and_forward(request, assoc_key)
148 assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
150 return login_and_forward(request, user_)
151 except AuthKeyUserAssociation.DoesNotExist:
152 request.session['assoc_key'] = assoc_key
153 request.session['auth_provider'] = provider
154 return HttpResponseRedirect(reverse('auth_external_register'))
156 return HttpResponseRedirect(reverse('auth_signin'))
158 def external_register(request):
159 if request.method == 'POST' and 'bnewaccount' in request.POST:
160 form1 = SimpleRegistrationForm(request.POST)
161 email_feeds_form = SimpleEmailSubscribeForm(request.POST)
163 if (form1.is_valid() and email_feeds_form.is_valid()):
164 user_ = User(username=form1.cleaned_data['username'], email=form1.cleaned_data['email'])
165 user_.email_isvalid = request.session.get('auth_validated_email', '') == form1.cleaned_data['email']
166 user_.set_unusable_password()
168 if User.objects.all().count() == 0:
169 user_.is_superuser = True
170 user_.is_staff = True
173 UserJoinsAction(user=user_, ip=request.META['REMOTE_ADDR']).save()
176 assoc_key = request.session['assoc_key']
177 auth_provider = request.session['auth_provider']
179 request.session['auth_error'] = _(
180 "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."
182 logging.error("Missing session data when trying to complete user registration: %s" % ", ".join(
183 ["%s: %s" % (k, v) for k, v in request.META.items()]))
184 return HttpResponseRedirect(reverse('auth_signin'))
186 uassoc = AuthKeyUserAssociation(user=user_, key=assoc_key, provider=auth_provider)
189 if email_feeds_form.cleaned_data['subscribe'] == 'n':
190 user_.subscription_settings.enable_notifications = False
191 user_.subscription_settings.save()
193 del request.session['assoc_key']
194 del request.session['auth_provider']
196 return login_and_forward(request, user_, message=_("A welcome email has been sent to your email address. "))
198 auth_provider = request.session.get('auth_provider', None)
199 if not auth_provider:
200 request.session['auth_error'] = _(
201 "Oops, something went wrong in the middle of this process. Please try again.")
202 logging.error("Missing session data when trying to complete user registration: %s" % ", ".join(
203 ["%s: %s" % (k, v) for k, v in request.META.items()]))
204 return HttpResponseRedirect(reverse('auth_signin'))
206 provider_class = AUTH_PROVIDERS[auth_provider].consumer
207 user_data = provider_class.get_user_data(request.session['assoc_key'])
210 user_data = request.session.get('auth_consumer_data', {})
212 username = user_data.get('username', '')
213 email = user_data.get('email', '')
216 request.session['auth_validated_email'] = email
218 form1 = SimpleRegistrationForm(initial={
220 'username': username,
223 email_feeds_form = SimpleEmailSubscribeForm()
225 provider_context = AUTH_PROVIDERS[request.session['auth_provider']].context
227 return render_to_response('auth/complete.html', {
229 'email_feeds_form': email_feeds_form,
230 'provider':provider_context and mark_safe(provider_context.human_name) or _('unknown'),
231 'login_type':provider_context.id,
232 'gravatar_faq_url':reverse('faq') + '#gravatar',
233 }, context_instance=RequestContext(request))
235 def request_temp_login(request):
236 if request.method == 'POST':
237 form = TemporaryLoginRequestForm(request.POST)
240 users = form.user_cache
244 return forward_suspended_user(request, u, False)
248 hash = get_object_or_404(ValidationHash, user=u, type='templogin')
249 if hash.expiration < datetime.datetime.now():
251 return request_temp_login(request)
253 hash = ValidationHash.objects.create_new(u, 'templogin', [u.id])
255 send_template_email([u], "auth/temp_login_email.html", {'temp_login_code': hash})
257 request.user.message_set.create(message=_("An email has been sent with your temporary login key"))
259 return HttpResponseRedirect(reverse('index'))
261 form = TemporaryLoginRequestForm()
263 return render_to_response(
264 'auth/temp_login_request.html', {'form': form},
265 context_instance=RequestContext(request))
267 def temp_signin(request, user, code):
268 user = get_object_or_404(User, id=user)
270 if (ValidationHash.objects.validate(code, user, 'templogin', [user.id])):
272 # If the user requests temp_signin he must have forgotten his password. So we mark it as unusable.
273 user.set_unusable_password()
276 return login_and_forward(request, user, reverse('user_authsettings', kwargs={'id': user.id}),
278 "You are logged in with a temporary access key, please take the time to fix your issue with authentication."
283 def send_validation_email(request):
284 if not request.user.is_authenticated():
285 return HttpResponseUnauthorized(request)
287 # We check if there are some old validation hashes. If there are -- we delete them.
289 hash = ValidationHash.objects.get(user=request.user, type='email')
294 # We don't care if there are previous cashes in the database... In every case we have to create a new one
295 hash = ValidationHash.objects.create_new(request.user, 'email', [request.user.email])
297 send_template_email([request.user], "auth/mail_validation.html", {'validation_code': hash})
298 request.user.message_set.create(message=_("A message with an email validation link was just sent to your address."))
299 return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
303 def validate_email(request, user, code):
304 user = get_object_or_404(User, id=user)
306 if (ValidationHash.objects.validate(code, user, 'email', [user.email])):
307 EmailValidationAction(user=user, ip=request.META['REMOTE_ADDR']).save()
308 if REP_GAIN_BY_EMAIL_VALIDATION > 0:
309 message = _("Thank you, your email is now validated and you've got %d points." % int(REP_GAIN_BY_EMAIL_VALIDATION))
312 message = _("Thank you, your email is now validated.")
314 return login_and_forward(request, user, reverse('index'), message)
316 return render_to_response('auth/mail_already_validated.html', { 'user' : user }, RequestContext(request))
318 def auth_settings(request, id):
319 user_ = get_object_or_404(User, id=id)
321 if not (request.user.is_superuser or request.user == user_):
322 return HttpResponseUnauthorized(request)
324 auth_keys = user_.auth_keys.all()
326 if request.user.is_superuser or (not user_.has_usable_password()):
327 FormClass = SetPasswordForm
329 FormClass = ChangePasswordForm
332 form = FormClass(request.POST, user=user_)
334 is_new_pass = not user_.has_usable_password()
335 user_.set_password(form.cleaned_data['password1'])
339 request.user.message_set.create(message=_("New password set"))
340 if not request.user.is_superuser:
341 form = ChangePasswordForm(user=user_)
343 request.user.message_set.create(message=_("Your password was changed"))
345 return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': user_.id}))
347 form = FormClass(user=user_)
352 provider = AUTH_PROVIDERS.get(k.provider, None)
354 if provider is not None:
355 name = "%s: %s" % (provider.context.human_name, provider.context.readable_key(k))
357 from forum.authentication.base import ConsumerTemplateContext
358 "unknown: %s" % ConsumerTemplateContext.readable_key(k)
360 auth_keys_list.append({
365 return render_to_response('auth/auth_settings.html', {
367 "can_view_private": (user_ == request.user) or request.user.is_superuser,
369 'has_password': user_.has_usable_password(),
370 'auth_keys': auth_keys_list,
371 'allow_local_auth': AUTH_PROVIDERS.get('local', None),
372 }, context_instance=RequestContext(request))
374 def remove_external_provider(request, id):
375 association = get_object_or_404(AuthKeyUserAssociation, id=id)
376 if not (request.user.is_superuser or request.user == association.user):
377 return HttpResponseUnauthorized(request)
379 request.user.message_set.create(message=_("You removed the association with %s") % association.provider)
381 return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': association.user.id}))
383 def login_and_forward(request, user, forward=None, message=None):
384 if user.is_suspended():
385 return forward_suspended_user(request, user)
387 user.backend = "django.contrib.auth.backends.ModelBackend"
391 message = _("Welcome back %s, you are now logged in") % user.username
393 request.user.message_set.create(message=message)
396 forward = request.session.get(ON_SIGNIN_SESSION_ATTR, reverse('index'))
398 pending_data = request.session.get(PENDING_SUBMISSION_SESSION_ATTR, None)
400 if pending_data and (user.email_isvalid or pending_data['type'] not in settings.REQUIRE_EMAIL_VALIDATION_TO):
401 submission_time = pending_data['time']
402 if submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.HOLD_PENDING_POSTS_MINUTES)):
403 del request.session[PENDING_SUBMISSION_SESSION_ATTR]
404 elif submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.WARN_PENDING_POSTS_MINUTES)):
405 user.message_set.create(message=(_("You have a %s pending submission.") % pending_data['data_name']) + " %s, %s, %s" % (
406 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('save')}), _("save it")),
407 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('review')}), _("review")),
408 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('cancel')}), _("cancel"))
411 return manage_pending_data(request, _('save'), forward)
413 return HttpResponseRedirect(forward)
415 def forward_suspended_user(request, user, show_private_msg=True):
416 message = _("Sorry, but this account is suspended")
418 msg_type = 'privatemsg'
420 msg_type = 'publicmsg'
422 suspension = user.suspension
424 message += (":<br />" + suspension.extra.get(msg_type, ''))
426 request.user.message_set.create(message)
427 return HttpResponseRedirect(reverse('index'))
429 @decorate.withfn(login_required)
430 def signout(request):
432 return HttpResponseRedirect(reverse('index'))