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 django.utils.encoding import smart_unicode
11 from forum.views.decorators import login_required
12 from forum.modules import decorate
13 from django.contrib.auth import login, logout
14 from django.http import get_host
15 from forum.actions import SuspendAction
16 from forum.utils import html
17 from forum import settings
18 from writers import manage_pending_data
23 from forum.forms import SimpleRegistrationForm, TemporaryLoginRequestForm, \
24 ChangePasswordForm, SetPasswordForm
25 from forum.utils.mail import send_template_email
27 from forum.authentication.base import InvalidAuthentication
28 from forum.authentication import AUTH_PROVIDERS
30 from forum.models import AuthKeyUserAssociation, ValidationHash, Question, Answer
31 from forum.actions import UserJoinsAction
33 from forum.settings import REP_GAIN_BY_EMAIL_VALIDATION
34 from vars import ON_SIGNIN_SESSION_ATTR, PENDING_SUBMISSION_SESSION_ATTR
36 def signin_page(request):
37 referer = request.META.get('HTTP_REFERER', '/')
39 # If the referer is equal to the sign up page, e. g. if the previous login attempt was not successful we do not
40 # change the sign in URL. The user should go to the same page.
41 if not referer.replace(settings.APP_URL, '') == reverse('auth_signin'):
42 request.session[ON_SIGNIN_SESSION_ATTR] = referer
44 all_providers = [provider.context for provider in AUTH_PROVIDERS.values() if provider.context]
46 sort = lambda c1, c2: c1.weight - c2.weight
47 can_show = lambda c: not request.user.is_authenticated() or c.show_to_logged_in_user
49 bigicon_providers = sorted([
50 context for context in all_providers if context.mode == 'BIGICON' and can_show(context)
53 smallicon_providers = sorted([
54 context for context in all_providers if context.mode == 'SMALLICON' and can_show(context)
57 top_stackitem_providers = sorted([
58 context for context in all_providers if context.mode == 'TOP_STACK_ITEM' and can_show(context)
61 stackitem_providers = sorted([
62 context for context in all_providers if context.mode == 'STACK_ITEM' and can_show(context)
66 msg = request.session['auth_error']
67 del request.session['auth_error']
71 return render_to_response(
75 'all_providers': all_providers,
76 'bigicon_providers': bigicon_providers,
77 'top_stackitem_providers': top_stackitem_providers,
78 'stackitem_providers': stackitem_providers,
79 'smallicon_providers': smallicon_providers,
81 RequestContext(request))
83 def prepare_provider_signin(request, provider):
84 force_email_request = request.REQUEST.get('validate_email', 'yes') == 'yes'
85 request.session['force_email_request'] = force_email_request
87 if provider in AUTH_PROVIDERS:
88 provider_class = AUTH_PROVIDERS[provider].consumer
91 request_url = provider_class.prepare_authentication_request(request,
92 reverse('auth_provider_done',
93 kwargs={'provider': provider}))
95 return HttpResponseRedirect(request_url)
96 except NotImplementedError, e:
97 return process_provider_signin(request, provider)
98 except InvalidAuthentication, e:
99 request.session['auth_error'] = e.message
101 return HttpResponseRedirect(reverse('auth_signin'))
106 def process_provider_signin(request, provider):
107 if provider in AUTH_PROVIDERS:
108 provider_class = AUTH_PROVIDERS[provider].consumer
111 assoc_key = provider_class.process_authentication_request(request)
112 except InvalidAuthentication, e:
113 request.session['auth_error'] = e.message
114 return HttpResponseRedirect(reverse('auth_signin'))
116 if request.user.is_authenticated():
117 if isinstance(assoc_key, (type, User)):
118 if request.user != assoc_key:
119 request.session['auth_error'] = _(
120 "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
123 request.session['auth_error'] = _("You are already logged in with that user.")
126 assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
127 if assoc.user == request.user:
128 request.session['auth_error'] = _(
129 "These login credentials are already associated with your account.")
131 request.session['auth_error'] = _(
132 "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
135 uassoc = AuthKeyUserAssociation(user=request.user, key=assoc_key, provider=provider)
137 request.user.message_set.create(
138 message=_('The new credentials are now associated with your account'))
139 return HttpResponseRedirect(reverse('user_authsettings', args=[request.user.id]))
141 return HttpResponseRedirect(reverse('auth_signin'))
143 if isinstance(assoc_key, User):
144 return login_and_forward(request, assoc_key)
147 assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
149 return login_and_forward(request, user_)
150 except AuthKeyUserAssociation.DoesNotExist:
151 request.session['assoc_key'] = assoc_key
152 request.session['auth_provider'] = provider
153 return HttpResponseRedirect(reverse('auth_external_register'))
155 return HttpResponseRedirect(reverse('auth_signin'))
157 def external_register(request):
158 if request.method == 'POST' and 'bnewaccount' in request.POST:
159 form1 = SimpleRegistrationForm(request.POST)
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 del request.session['assoc_key']
188 del request.session['auth_provider']
190 return login_and_forward(request, user_, message=_("A welcome email has been sent to your email address. "))
192 auth_provider = request.session.get('auth_provider', None)
193 if not auth_provider:
194 request.session['auth_error'] = _(
195 "Oops, something went wrong in the middle of this process. Please try again.")
196 logging.error("Missing session data when trying to complete user registration: %s" % ", ".join(
197 ["%s: %s" % (k, v) for k, v in request.META.items()]))
198 return HttpResponseRedirect(reverse('auth_signin'))
200 provider_class = AUTH_PROVIDERS[auth_provider].consumer
202 # Pass the cookies to the Facebook authentication class get_user_data method. We need them to take the access token.
203 if provider_class.__class__.__name__ == 'FacebookAuthConsumer':
204 user_data = provider_class.get_user_data(request.COOKIES)
206 user_data = provider_class.get_user_data(request.session['assoc_key'])
209 user_data = request.session.get('auth_consumer_data', {})
211 username = user_data.get('username', '')
212 email = user_data.get('email', '')
215 request.session['auth_validated_email'] = email
217 form1 = SimpleRegistrationForm(initial={
219 'username': username,
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 send_template_email([request.user], "auth/mail_validation.html", {'validation_code': hash})
295 request.user.message_set.create(message=_("A message with an email validation link was just sent to your address."))
296 return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
300 def validate_email(request, user, code):
301 user = get_object_or_404(User, id=user)
303 if (ValidationHash.objects.validate(code, user, 'email', [user.email])):
304 user.email_isvalid = True
306 return login_and_forward(request, user, reverse('index'), _("Thank you, your email is now validated."))
308 return render_to_response('auth/mail_already_validated.html', { 'user' : user }, RequestContext(request))
310 def auth_settings(request, id):
311 user_ = get_object_or_404(User, id=id)
313 if not (request.user.is_superuser or request.user == user_):
314 return HttpResponseUnauthorized(request)
316 auth_keys = user_.auth_keys.all()
318 if request.user.is_superuser or (not user_.has_usable_password()):
319 FormClass = SetPasswordForm
321 FormClass = ChangePasswordForm
324 form = FormClass(request.POST, user=user_)
326 is_new_pass = not user_.has_usable_password()
327 user_.set_password(form.cleaned_data['password1'])
331 request.user.message_set.create(message=_("New password set"))
332 if not request.user.is_superuser:
333 form = ChangePasswordForm(user=user_)
335 request.user.message_set.create(message=_("Your password was changed"))
337 return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': user_.id}))
339 form = FormClass(user=user_)
344 provider = AUTH_PROVIDERS.get(k.provider, None)
346 if provider is not None:
347 name = "%s: %s" % (provider.context.human_name, provider.context.readable_key(k))
349 from forum.authentication.base import ConsumerTemplateContext
350 "unknown: %s" % ConsumerTemplateContext.readable_key(k)
352 auth_keys_list.append({
357 return render_to_response('auth/auth_settings.html', {
359 "can_view_private": (user_ == request.user) or request.user.is_superuser,
361 'has_password': user_.has_usable_password(),
362 'auth_keys': auth_keys_list,
363 'allow_local_auth': AUTH_PROVIDERS.get('local', None),
364 }, context_instance=RequestContext(request))
366 def remove_external_provider(request, id):
367 association = get_object_or_404(AuthKeyUserAssociation, id=id)
368 if not (request.user.is_superuser or request.user == association.user):
369 return HttpResponseUnauthorized(request)
371 request.user.message_set.create(message=_("You removed the association with %s") % association.provider)
373 return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': association.user.id}))
375 def login_and_forward(request, user, forward=None, message=None):
376 if user.is_suspended():
377 return forward_suspended_user(request, user)
379 user.backend = "django.contrib.auth.backends.ModelBackend"
383 message = _("Welcome back %s, you are now logged in") % smart_unicode(user.username)
385 request.user.message_set.create(message=message)
388 forward = request.session.get(ON_SIGNIN_SESSION_ATTR, reverse('index'))
390 pending_data = request.session.get(PENDING_SUBMISSION_SESSION_ATTR, None)
392 if pending_data and (user.email_isvalid or pending_data['type'] not in settings.REQUIRE_EMAIL_VALIDATION_TO):
393 submission_time = pending_data['time']
394 if submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.HOLD_PENDING_POSTS_MINUTES)):
395 del request.session[PENDING_SUBMISSION_SESSION_ATTR]
396 elif submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.WARN_PENDING_POSTS_MINUTES)):
397 user.message_set.create(message=(_("You have a %s pending submission.") % pending_data['data_name']) + " %s, %s, %s" % (
398 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('save')}), _("save it")),
399 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('review')}), _("review")),
400 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('cancel')}), _("cancel"))
403 return manage_pending_data(request, _('save'), forward)
405 return HttpResponseRedirect(forward)
407 def forward_suspended_user(request, user, show_private_msg=True):
408 message = _("Sorry, but this account is suspended")
410 msg_type = 'privatemsg'
412 msg_type = 'publicmsg'
414 suspension = user.suspension
416 message += (":<br />" + suspension.extra.get(msg_type, ''))
418 request.user.message_set.create(message)
419 return HttpResponseRedirect(reverse('index'))
421 @decorate.withfn(login_required)
422 def signout(request):
424 return HttpResponseRedirect(reverse('index'))