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, TemporaryLoginRequestForm, \
23 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 from forum.settings import REP_GAIN_BY_EMAIL_VALIDATION
33 from vars import ON_SIGNIN_SESSION_ATTR, PENDING_SUBMISSION_SESSION_ATTR
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_SESSION_ATTR] = referer
43 all_providers = [provider.context for provider in AUTH_PROVIDERS.values() if provider.context]
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_)
149 except AuthKeyUserAssociation.DoesNotExist:
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)
161 user_ = User(username=form1.cleaned_data['username'], email=form1.cleaned_data['email'])
162 user_.email_isvalid = request.session.get('auth_validated_email', '') == form1.cleaned_data['email']
163 user_.set_unusable_password()
165 if User.objects.all().count() == 0:
166 user_.is_superuser = True
167 user_.is_staff = True
170 UserJoinsAction(user=user_, ip=request.META['REMOTE_ADDR']).save()
173 assoc_key = request.session['assoc_key']
174 auth_provider = request.session['auth_provider']
176 request.session['auth_error'] = _(
177 "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."
179 logging.error("Missing session data when trying to complete user registration: %s" % ", ".join(
180 ["%s: %s" % (k, v) for k, v in request.META.items()]))
181 return HttpResponseRedirect(reverse('auth_signin'))
183 uassoc = AuthKeyUserAssociation(user=user_, key=assoc_key, provider=auth_provider)
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,
217 provider_context = AUTH_PROVIDERS[request.session['auth_provider']].context
219 return render_to_response('auth/complete.html', {
221 'provider':provider_context and mark_safe(provider_context.human_name) or _('unknown'),
222 'login_type':provider_context.id,
223 'gravatar_faq_url':reverse('faq') + '#gravatar',
224 }, context_instance=RequestContext(request))
226 def request_temp_login(request):
227 if request.method == 'POST':
228 form = TemporaryLoginRequestForm(request.POST)
231 users = form.user_cache
235 return forward_suspended_user(request, u, False)
239 hash = get_object_or_404(ValidationHash, user=u, type='templogin')
240 if hash.expiration < datetime.datetime.now():
242 return request_temp_login(request)
244 hash = ValidationHash.objects.create_new(u, 'templogin', [u.id])
246 send_template_email([u], "auth/temp_login_email.html", {'temp_login_code': hash})
248 request.user.message_set.create(message=_("An email has been sent with your temporary login key"))
250 return HttpResponseRedirect(reverse('index'))
252 form = TemporaryLoginRequestForm()
254 return render_to_response(
255 'auth/temp_login_request.html', {'form': form},
256 context_instance=RequestContext(request))
258 def temp_signin(request, user, code):
259 user = get_object_or_404(User, id=user)
261 if (ValidationHash.objects.validate(code, user, 'templogin', [user.id])):
263 # If the user requests temp_signin he must have forgotten his password. So we mark it as unusable.
264 user.set_unusable_password()
267 return login_and_forward(request, user, reverse('user_authsettings', kwargs={'id': user.id}),
269 "You are logged in with a temporary access key, please take the time to fix your issue with authentication."
274 def send_validation_email(request):
275 if not request.user.is_authenticated():
276 return HttpResponseUnauthorized(request)
278 # We check if there are some old validation hashes. If there are -- we delete them.
280 hash = ValidationHash.objects.get(user=request.user, type='email')
285 # We don't care if there are previous cashes in the database... In every case we have to create a new one
286 hash = ValidationHash.objects.create_new(request.user, 'email', [request.user.email])
288 send_template_email([request.user], "auth/mail_validation.html", {'validation_code': hash})
289 request.user.message_set.create(message=_("A message with an email validation link was just sent to your address."))
290 return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
294 def validate_email(request, user, code):
295 user = get_object_or_404(User, id=user)
297 if (ValidationHash.objects.validate(code, user, 'email', [user.email])):
298 user.email_isvalid = True
300 return login_and_forward(request, user, reverse('index'), _("Thank you, your email is now validated."))
302 return render_to_response('auth/mail_already_validated.html', { 'user' : user }, RequestContext(request))
304 def auth_settings(request, id):
305 user_ = get_object_or_404(User, id=id)
307 if not (request.user.is_superuser or request.user == user_):
308 return HttpResponseUnauthorized(request)
310 auth_keys = user_.auth_keys.all()
312 if request.user.is_superuser or (not user_.has_usable_password()):
313 FormClass = SetPasswordForm
315 FormClass = ChangePasswordForm
318 form = FormClass(request.POST, user=user_)
320 is_new_pass = not user_.has_usable_password()
321 user_.set_password(form.cleaned_data['password1'])
325 request.user.message_set.create(message=_("New password set"))
326 if not request.user.is_superuser:
327 form = ChangePasswordForm(user=user_)
329 request.user.message_set.create(message=_("Your password was changed"))
331 return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': user_.id}))
333 form = FormClass(user=user_)
338 provider = AUTH_PROVIDERS.get(k.provider, None)
340 if provider is not None:
341 name = "%s: %s" % (provider.context.human_name, provider.context.readable_key(k))
343 from forum.authentication.base import ConsumerTemplateContext
344 "unknown: %s" % ConsumerTemplateContext.readable_key(k)
346 auth_keys_list.append({
351 return render_to_response('auth/auth_settings.html', {
353 "can_view_private": (user_ == request.user) or request.user.is_superuser,
355 'has_password': user_.has_usable_password(),
356 'auth_keys': auth_keys_list,
357 'allow_local_auth': AUTH_PROVIDERS.get('local', None),
358 }, context_instance=RequestContext(request))
360 def remove_external_provider(request, id):
361 association = get_object_or_404(AuthKeyUserAssociation, id=id)
362 if not (request.user.is_superuser or request.user == association.user):
363 return HttpResponseUnauthorized(request)
365 request.user.message_set.create(message=_("You removed the association with %s") % association.provider)
367 return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': association.user.id}))
369 def login_and_forward(request, user, forward=None, message=None):
370 if user.is_suspended():
371 return forward_suspended_user(request, user)
373 user.backend = "django.contrib.auth.backends.ModelBackend"
377 message = _("Welcome back %s, you are now logged in") % user.username
379 request.user.message_set.create(message=message)
382 forward = request.session.get(ON_SIGNIN_SESSION_ATTR, reverse('index'))
384 pending_data = request.session.get(PENDING_SUBMISSION_SESSION_ATTR, None)
386 if pending_data and (user.email_isvalid or pending_data['type'] not in settings.REQUIRE_EMAIL_VALIDATION_TO):
387 submission_time = pending_data['time']
388 if submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.HOLD_PENDING_POSTS_MINUTES)):
389 del request.session[PENDING_SUBMISSION_SESSION_ATTR]
390 elif submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.WARN_PENDING_POSTS_MINUTES)):
391 user.message_set.create(message=(_("You have a %s pending submission.") % pending_data['data_name']) + " %s, %s, %s" % (
392 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('save')}), _("save it")),
393 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('review')}), _("review")),
394 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('cancel')}), _("cancel"))
397 return manage_pending_data(request, _('save'), forward)
399 return HttpResponseRedirect(forward)
401 def forward_suspended_user(request, user, show_private_msg=True):
402 message = _("Sorry, but this account is suspended")
404 msg_type = 'privatemsg'
406 msg_type = 'publicmsg'
408 suspension = user.suspension
410 message += (":<br />" + suspension.extra.get(msg_type, ''))
412 request.user.message_set.create(message)
413 return HttpResponseRedirect(reverse('index'))
415 @decorate.withfn(login_required)
416 def signout(request):
418 return HttpResponseRedirect(reverse('index'))