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
32 def signin_page(request):
33 request.session['on_signin_url'] = request.META.get('HTTP_REFERER', '/')
35 if reverse('auth_signin') == request.session['on_signin_url'].replace(settings.APP_URL, ''):
36 request.session['on_signin_url'] = reverse('index')
38 all_providers = [provider.context for provider in AUTH_PROVIDERS.values()]
40 sort = lambda c1, c2: c1.weight - c2.weight
41 can_show = lambda c: not request.user.is_authenticated() or c.show_to_logged_in_user
43 bigicon_providers = sorted([
44 context for context in all_providers if context.mode == 'BIGICON' and can_show(context)
47 smallicon_providers = sorted([
48 context for context in all_providers if context.mode == 'SMALLICON' and can_show(context)
51 top_stackitem_providers = sorted([
52 context for context in all_providers if context.mode == 'TOP_STACK_ITEM' and can_show(context)
55 stackitem_providers = sorted([
56 context for context in all_providers if context.mode == 'STACK_ITEM' and can_show(context)
60 msg = request.session['auth_error']
61 del request.session['auth_error']
65 return render_to_response(
69 'all_providers': all_providers,
70 'bigicon_providers': bigicon_providers,
71 'top_stackitem_providers': top_stackitem_providers,
72 'stackitem_providers': stackitem_providers,
73 'smallicon_providers': smallicon_providers,
75 RequestContext(request))
77 def prepare_provider_signin(request, provider):
78 force_email_request = request.REQUEST.get('validate_email', 'yes') == 'yes'
79 request.session['force_email_request'] = force_email_request
81 if provider in AUTH_PROVIDERS:
82 provider_class = AUTH_PROVIDERS[provider].consumer
85 request_url = provider_class.prepare_authentication_request(request,
86 reverse('auth_provider_done',
87 kwargs={'provider': provider}))
89 return HttpResponseRedirect(request_url)
90 except NotImplementedError, e:
91 return process_provider_signin(request, provider)
92 except InvalidAuthentication, e:
93 request.session['auth_error'] = e.message
95 return HttpResponseRedirect(reverse('auth_signin'))
100 def process_provider_signin(request, provider):
101 if provider in AUTH_PROVIDERS:
102 provider_class = AUTH_PROVIDERS[provider].consumer
105 assoc_key = provider_class.process_authentication_request(request)
106 except InvalidAuthentication, e:
107 request.session['auth_error'] = e.message
108 return HttpResponseRedirect(reverse('auth_signin'))
110 if request.user.is_authenticated():
111 if isinstance(assoc_key, (type, User)):
112 if request.user != assoc_key:
113 request.session['auth_error'] = _(
114 "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
117 request.session['auth_error'] = _("You are already logged in with that user.")
120 assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
121 if assoc.user == request.user:
122 request.session['auth_error'] = _(
123 "These login credentials are already associated with your account.")
125 request.session['auth_error'] = _(
126 "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
129 uassoc = AuthKeyUserAssociation(user=request.user, key=assoc_key, provider=provider)
131 request.user.message_set.create(
132 message=_('The new credentials are now associated with your account'))
133 return HttpResponseRedirect(reverse('user_authsettings', args=[request.user.id]))
135 return HttpResponseRedirect(reverse('auth_signin'))
137 if isinstance(assoc_key, User):
138 return login_and_forward(request, assoc_key)
141 assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
143 return login_and_forward(request, user_)
145 request.session['assoc_key'] = assoc_key
146 request.session['auth_provider'] = provider
147 return HttpResponseRedirect(reverse('auth_external_register'))
149 return HttpResponseRedirect(reverse('auth_signin'))
151 def external_register(request):
152 if request.method == 'POST' and 'bnewaccount' in request.POST:
153 form1 = SimpleRegistrationForm(request.POST)
154 email_feeds_form = SimpleEmailSubscribeForm(request.POST)
156 if (form1.is_valid() and email_feeds_form.is_valid()):
157 user_ = User(username=form1.cleaned_data['username'], email=form1.cleaned_data['email'])
158 user_.email_isvalid = request.session.get('auth_validated_email', '') == form1.cleaned_data['email']
159 user_.set_unusable_password()
161 if User.objects.all().count() == 0:
162 user_.is_superuser = True
163 user_.is_staff = True
166 UserJoinsAction(user=user_, ip=request.META['REMOTE_ADDR']).save()
169 assoc_key = request.session['assoc_key']
170 auth_provider = request.session['auth_provider']
172 request.session['auth_error'] = _(
173 "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."
175 logging.error("Missing session data when trying to complete user registration: %s" % ", ".join(
176 ["%s: %s" % (k, v) for k, v in request.META.items()]))
177 return HttpResponseRedirect(reverse('auth_signin'))
179 uassoc = AuthKeyUserAssociation(user=user_, key=assoc_key, provider=auth_provider)
182 if email_feeds_form.cleaned_data['subscribe'] == 'n':
183 user_.subscription_settings.enable_notifications = False
184 user_.subscription_settings.save()
186 del request.session['assoc_key']
187 del request.session['auth_provider']
189 return login_and_forward(request, user_, message=_("A welcome email has been sent to your email address. "))
191 auth_provider = request.session.get('auth_provider', None)
192 if not auth_provider:
193 request.session['auth_error'] = _(
194 "Oops, something went wrong in the middle of this process. Please try again.")
195 logging.error("Missing session data when trying to complete user registration: %s" % ", ".join(
196 ["%s: %s" % (k, v) for k, v in request.META.items()]))
197 return HttpResponseRedirect(reverse('auth_signin'))
199 provider_class = AUTH_PROVIDERS[auth_provider].consumer
200 user_data = provider_class.get_user_data(request.session['assoc_key'])
203 user_data = request.session.get('auth_consumer_data', {})
205 username = user_data.get('username', '')
206 email = user_data.get('email', '')
209 request.session['auth_validated_email'] = email
211 form1 = SimpleRegistrationForm(initial={
213 'username': username,
216 email_feeds_form = SimpleEmailSubscribeForm()
218 provider_context = AUTH_PROVIDERS[request.session['auth_provider']].context
220 return render_to_response('auth/complete.html', {
222 'email_feeds_form': email_feeds_form,
223 'provider':mark_safe(provider_context.human_name),
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)
281 hash = ValidationHash.objects.get(user=request.user, type='email')
284 # If we were able to get a previous validation hash we should raise an
285 # Exception immediately. Otherwise new validation hash will not be created
286 # and users will not receive the desired e-mail vaidation link.
287 raise Exception("Validation has already been sent")
289 hash = ValidationHash.objects.create_new(request.user, 'email', [request.user.email])
291 send_template_email([request.user], "auth/mail_validation.html", {'validation_code': hash})
292 request.user.message_set.create(message=_("A message with an email validation link was just sent to your address."))
293 return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
297 def validate_email(request, user, code):
298 user = get_object_or_404(User, id=user)
300 if (ValidationHash.objects.validate(code, user, 'email', [user.email])):
301 user.email_isvalid = True
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"
380 message = _("Welcome back %s, you are now logged in") % user.username
382 request.user.message_set.create(message=message)
385 forward = request.session.get('on_signin_url', reverse('index'))
387 pending_data = request.session.get('pending_submission_data', None)
389 if pending_data and (user.email_isvalid or pending_data['type'] not in settings.REQUIRE_EMAIL_VALIDATION_TO):
390 submission_time = pending_data['time']
391 if submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.HOLD_PENDING_POSTS_MINUTES)):
392 del request.session['pending_submission_data']
393 elif submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.WARN_PENDING_POSTS_MINUTES)):
394 user.message_set.create(message=(_("You have a %s pending submission.") % pending_data['data_name']) + " %s, %s, %s" % (
395 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('save')}), _("save it")),
396 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('review')}), _("review")),
397 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('cancel')}), _("cancel"))
400 return manage_pending_data(request, _('save'), forward)
402 return HttpResponseRedirect(forward)
404 def forward_suspended_user(request, user, show_private_msg=True):
405 message = _("Sorry, but this account is suspended")
407 msg_type = 'privatemsg'
409 msg_type = 'publicmsg'
411 suspension = user.suspension
413 message += (":<br />" + suspension.extra.get(msg_type, ''))
415 request.user.message_set.create(message)
416 return HttpResponseRedirect(reverse('index'))
418 @decorate.withfn(login_required)
419 def signout(request):
421 return HttpResponseRedirect(reverse('index'))