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 referer = request.META.get('HTTP_REFERER', '/')
35 # If the referer is equal to the sign up page, e. g. if the previous login attempt was not successful we do not
36 # change the sign in URL. The user should go to the same page.
37 if not referer.replace(settings.APP_URL, '') == reverse('auth_signin'):
38 request.session['on_signin_url'] = referer
40 all_providers = [provider.context for provider in AUTH_PROVIDERS.values()]
42 sort = lambda c1, c2: c1.weight - c2.weight
43 can_show = lambda c: not request.user.is_authenticated() or c.show_to_logged_in_user
45 bigicon_providers = sorted([
46 context for context in all_providers if context.mode == 'BIGICON' and can_show(context)
49 smallicon_providers = sorted([
50 context for context in all_providers if context.mode == 'SMALLICON' and can_show(context)
53 top_stackitem_providers = sorted([
54 context for context in all_providers if context.mode == 'TOP_STACK_ITEM' and can_show(context)
57 stackitem_providers = sorted([
58 context for context in all_providers if context.mode == 'STACK_ITEM' and can_show(context)
62 msg = request.session['auth_error']
63 del request.session['auth_error']
67 return render_to_response(
71 'all_providers': all_providers,
72 'bigicon_providers': bigicon_providers,
73 'top_stackitem_providers': top_stackitem_providers,
74 'stackitem_providers': stackitem_providers,
75 'smallicon_providers': smallicon_providers,
77 RequestContext(request))
79 def prepare_provider_signin(request, provider):
80 force_email_request = request.REQUEST.get('validate_email', 'yes') == 'yes'
81 request.session['force_email_request'] = force_email_request
83 if provider in AUTH_PROVIDERS:
84 provider_class = AUTH_PROVIDERS[provider].consumer
87 request_url = provider_class.prepare_authentication_request(request,
88 reverse('auth_provider_done',
89 kwargs={'provider': provider}))
91 return HttpResponseRedirect(request_url)
92 except NotImplementedError, e:
93 return process_provider_signin(request, provider)
94 except InvalidAuthentication, e:
95 request.session['auth_error'] = e.message
97 return HttpResponseRedirect(reverse('auth_signin'))
102 def process_provider_signin(request, provider):
103 if provider in AUTH_PROVIDERS:
104 provider_class = AUTH_PROVIDERS[provider].consumer
107 assoc_key = provider_class.process_authentication_request(request)
108 except InvalidAuthentication, e:
109 request.session['auth_error'] = e.message
110 return HttpResponseRedirect(reverse('auth_signin'))
112 if request.user.is_authenticated():
113 if isinstance(assoc_key, (type, User)):
114 if request.user != assoc_key:
115 request.session['auth_error'] = _(
116 "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
119 request.session['auth_error'] = _("You are already logged in with that user.")
122 assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
123 if assoc.user == request.user:
124 request.session['auth_error'] = _(
125 "These login credentials are already associated with your account.")
127 request.session['auth_error'] = _(
128 "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
131 uassoc = AuthKeyUserAssociation(user=request.user, key=assoc_key, provider=provider)
133 request.user.message_set.create(
134 message=_('The new credentials are now associated with your account'))
135 return HttpResponseRedirect(reverse('user_authsettings', args=[request.user.id]))
137 return HttpResponseRedirect(reverse('auth_signin'))
139 if isinstance(assoc_key, User):
140 return login_and_forward(request, assoc_key)
143 assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
145 return login_and_forward(request, user_)
147 request.session['assoc_key'] = assoc_key
148 request.session['auth_provider'] = provider
149 return HttpResponseRedirect(reverse('auth_external_register'))
151 return HttpResponseRedirect(reverse('auth_signin'))
153 def external_register(request):
154 if request.method == 'POST' and 'bnewaccount' in request.POST:
155 form1 = SimpleRegistrationForm(request.POST)
156 email_feeds_form = SimpleEmailSubscribeForm(request.POST)
158 if (form1.is_valid() and email_feeds_form.is_valid()):
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 if email_feeds_form.cleaned_data['subscribe'] == 'n':
185 user_.subscription_settings.enable_notifications = False
186 user_.subscription_settings.save()
188 del request.session['assoc_key']
189 del request.session['auth_provider']
191 return login_and_forward(request, user_, message=_("A welcome email has been sent to your email address. "))
193 auth_provider = request.session.get('auth_provider', None)
194 if not auth_provider:
195 request.session['auth_error'] = _(
196 "Oops, something went wrong in the middle of this process. Please try again.")
197 logging.error("Missing session data when trying to complete user registration: %s" % ", ".join(
198 ["%s: %s" % (k, v) for k, v in request.META.items()]))
199 return HttpResponseRedirect(reverse('auth_signin'))
201 provider_class = AUTH_PROVIDERS[auth_provider].consumer
202 user_data = provider_class.get_user_data(request.session['assoc_key'])
205 user_data = request.session.get('auth_consumer_data', {})
207 username = user_data.get('username', '')
208 email = user_data.get('email', '')
211 request.session['auth_validated_email'] = email
213 form1 = SimpleRegistrationForm(initial={
215 'username': username,
218 email_feeds_form = SimpleEmailSubscribeForm()
220 provider_context = AUTH_PROVIDERS[request.session['auth_provider']].context
222 return render_to_response('auth/complete.html', {
224 'email_feeds_form': email_feeds_form,
225 'provider':mark_safe(provider_context.human_name),
226 'login_type':provider_context.id,
227 'gravatar_faq_url':reverse('faq') + '#gravatar',
228 }, context_instance=RequestContext(request))
230 def request_temp_login(request):
231 if request.method == 'POST':
232 form = TemporaryLoginRequestForm(request.POST)
235 users = form.user_cache
239 return forward_suspended_user(request, u, False)
243 hash = get_object_or_404(ValidationHash, user=u, type='templogin')
244 if hash.expiration < datetime.datetime.now():
246 return request_temp_login(request)
248 hash = ValidationHash.objects.create_new(u, 'templogin', [u.id])
250 send_template_email([u], "auth/temp_login_email.html", {'temp_login_code': hash})
252 request.user.message_set.create(message=_("An email has been sent with your temporary login key"))
254 return HttpResponseRedirect(reverse('index'))
256 form = TemporaryLoginRequestForm()
258 return render_to_response(
259 'auth/temp_login_request.html', {'form': form},
260 context_instance=RequestContext(request))
262 def temp_signin(request, user, code):
263 user = get_object_or_404(User, id=user)
265 if (ValidationHash.objects.validate(code, user, 'templogin', [user.id])):
267 # If the user requests temp_signin he must have forgotten his password. So we mark it as unusable.
268 user.set_unusable_password()
271 return login_and_forward(request, user, reverse('user_authsettings', kwargs={'id': user.id}),
273 "You are logged in with a temporary access key, please take the time to fix your issue with authentication."
278 def send_validation_email(request):
279 if not request.user.is_authenticated():
280 return HttpResponseUnauthorized(request)
282 # We check if there are some old validation hashes. If there are -- we delete them.
284 hash = ValidationHash.objects.get(user=request.user, type='email')
289 # We don't care if there are previous cashes in the database... In every case we have to create a new one
290 hash = ValidationHash.objects.create_new(request.user, 'email', [request.user.email])
292 send_template_email([request.user], "auth/mail_validation.html", {'validation_code': hash})
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 user.email_isvalid = True
304 return login_and_forward(request, user, reverse('index'), _("Thank you, your email is now validated."))
306 return render_to_response('auth/mail_already_validated.html', { 'user' : user }, RequestContext(request))
308 def auth_settings(request, id):
309 user_ = get_object_or_404(User, id=id)
311 if not (request.user.is_superuser or request.user == user_):
312 return HttpResponseUnauthorized(request)
314 auth_keys = user_.auth_keys.all()
316 if request.user.is_superuser or (not user_.has_usable_password()):
317 FormClass = SetPasswordForm
319 FormClass = ChangePasswordForm
322 form = FormClass(request.POST, user=user_)
324 is_new_pass = not user_.has_usable_password()
325 user_.set_password(form.cleaned_data['password1'])
329 request.user.message_set.create(message=_("New password set"))
330 if not request.user.is_superuser:
331 form = ChangePasswordForm(user=user_)
333 request.user.message_set.create(message=_("Your password was changed"))
335 return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': user_.id}))
337 form = FormClass(user=user_)
342 provider = AUTH_PROVIDERS.get(k.provider, None)
344 if provider is not None:
345 name = "%s: %s" % (provider.context.human_name, provider.context.readable_key(k))
347 from forum.authentication.base import ConsumerTemplateContext
348 "unknown: %s" % ConsumerTemplateContext.readable_key(k)
350 auth_keys_list.append({
355 return render_to_response('auth/auth_settings.html', {
357 "can_view_private": (user_ == request.user) or request.user.is_superuser,
359 'has_password': user_.has_usable_password(),
360 'auth_keys': auth_keys_list,
361 'allow_local_auth': AUTH_PROVIDERS.get('local', None),
362 }, context_instance=RequestContext(request))
364 def remove_external_provider(request, id):
365 association = get_object_or_404(AuthKeyUserAssociation, id=id)
366 if not (request.user.is_superuser or request.user == association.user):
367 return HttpResponseUnauthorized(request)
369 request.user.message_set.create(message=_("You removed the association with %s") % association.provider)
371 return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': association.user.id}))
373 def login_and_forward(request, user, forward=None, message=None):
374 if user.is_suspended():
375 return forward_suspended_user(request, user)
377 user.backend = "django.contrib.auth.backends.ModelBackend"
381 message = _("Welcome back %s, you are now logged in") % user.username
383 request.user.message_set.create(message=message)
386 forward = request.session.get('on_signin_url', reverse('index'))
388 pending_data = request.session.get('pending_submission_data', None)
390 if pending_data and (user.email_isvalid or pending_data['type'] not in settings.REQUIRE_EMAIL_VALIDATION_TO):
391 submission_time = pending_data['time']
392 if submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.HOLD_PENDING_POSTS_MINUTES)):
393 del request.session['pending_submission_data']
394 elif submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.WARN_PENDING_POSTS_MINUTES)):
395 user.message_set.create(message=(_("You have a %s pending submission.") % pending_data['data_name']) + " %s, %s, %s" % (
396 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('save')}), _("save it")),
397 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('review')}), _("review")),
398 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('cancel')}), _("cancel"))
401 return manage_pending_data(request, _('save'), forward)
403 return HttpResponseRedirect(forward)
405 def forward_suspended_user(request, user, show_private_msg=True):
406 message = _("Sorry, but this account is suspended")
408 msg_type = 'privatemsg'
410 msg_type = 'publicmsg'
412 suspension = user.suspension
414 message += (":<br />" + suspension.extra.get(msg_type, ''))
416 request.user.message_set.create(message)
417 return HttpResponseRedirect(reverse('index'))
419 @decorate.withfn(login_required)
420 def signout(request):
422 return HttpResponseRedirect(reverse('index'))