1 # -*- coding: utf-8 -*-
 
   6 from urlparse import urlparse
 
   8 from django.shortcuts import render_to_response, get_object_or_404
 
   9 from django.template import RequestContext
 
  10 from django.core.urlresolvers import reverse
 
  11 from django.http import HttpResponseRedirect, Http404
 
  12 from django.utils.safestring import mark_safe
 
  13 from django.utils.translation import ugettext as _
 
  14 from django.utils.encoding import smart_unicode
 
  15 from django.contrib.auth import login, logout
 
  17 from writers import manage_pending_data
 
  19 from forum.actions import EmailValidationAction
 
  20 from forum.utils import html
 
  21 from forum.views.decorators import login_required
 
  22 from forum.modules import decorate
 
  23 from forum.forms import SimpleRegistrationForm, TemporaryLoginRequestForm, ChangePasswordForm, SetPasswordForm
 
  24 from forum.http_responses import HttpResponseUnauthorized
 
  25 from forum.utils.mail import send_template_email
 
  26 from forum.authentication.base import InvalidAuthentication
 
  27 from forum.authentication import AUTH_PROVIDERS
 
  28 from forum.models import User, AuthKeyUserAssociation, ValidationHash
 
  29 from forum.actions import UserJoinsAction, UserLoginAction
 
  30 from forum import settings
 
  32 from vars import ON_SIGNIN_SESSION_ATTR, PENDING_SUBMISSION_SESSION_ATTR
 
  34 def signin_page(request):
 
  35     referer = request.META.get('HTTP_REFERER', '/')
 
  37     # If the referer is equal to the sign up page, e. g. if the previous login attempt was not successful we do not
 
  38     # change the sign in URL. The user should go to the same page.
 
  39     if not referer.replace(settings.APP_URL, '') == reverse('auth_signin'):
 
  40         request.session[ON_SIGNIN_SESSION_ATTR] = referer
 
  42     all_providers = [provider.context for provider in AUTH_PROVIDERS.values() if provider.context]
 
  44     sort = lambda c1, c2: c1.weight - c2.weight
 
  45     can_show = lambda c: not request.user.is_authenticated() or c.show_to_logged_in_user
 
  47     bigicon_providers = sorted([
 
  48     context for context in all_providers if context.mode == 'BIGICON' and can_show(context)
 
  51     smallicon_providers = sorted([
 
  52     context for context in all_providers if context.mode == 'SMALLICON' and can_show(context)
 
  55     top_stackitem_providers = sorted([
 
  56     context for context in all_providers if context.mode == 'TOP_STACK_ITEM' and can_show(context)
 
  59     stackitem_providers = sorted([
 
  60     context for context in all_providers if context.mode == 'STACK_ITEM' and can_show(context)
 
  64         msg = request.session['auth_error']
 
  65         del request.session['auth_error']
 
  69     return render_to_response(
 
  73             'all_providers': all_providers,
 
  74             'bigicon_providers': bigicon_providers,
 
  75             'top_stackitem_providers': top_stackitem_providers,
 
  76             'stackitem_providers': stackitem_providers,
 
  77             'smallicon_providers': smallicon_providers,
 
  79             RequestContext(request))
 
  81 def prepare_provider_signin(request, provider):
 
  82     force_email_request = request.REQUEST.get('validate_email', 'yes') == 'yes'
 
  83     request.session['force_email_request'] = force_email_request
 
  85     if provider in AUTH_PROVIDERS:
 
  86         provider_class = AUTH_PROVIDERS[provider].consumer
 
  89             request_url = provider_class.prepare_authentication_request(request,
 
  90                                                                         reverse('auth_provider_done',
 
  91                                                                                 kwargs={'provider': provider}))
 
  93             return HttpResponseRedirect(request_url)
 
  94         except NotImplementedError, e:
 
  95             return process_provider_signin(request, provider)
 
  96         except InvalidAuthentication, e:
 
  97             request.session['auth_error'] = e.message
 
  99         return HttpResponseRedirect(reverse('auth_signin'))
 
 104 def process_provider_signin(request, provider):
 
 105     if provider in AUTH_PROVIDERS:
 
 106         provider_class = AUTH_PROVIDERS[provider].consumer
 
 109             assoc_key = provider_class.process_authentication_request(request)
 
 110         except InvalidAuthentication, e:
 
 111             request.session['auth_error'] = e.message
 
 112             return HttpResponseRedirect(reverse('auth_signin'))
 
 114         if request.user.is_authenticated():
 
 115             if isinstance(assoc_key, (type, User)):
 
 116                 if request.user != assoc_key:
 
 117                     request.session['auth_error'] = _(
 
 118                             "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
 
 121                     request.session['auth_error'] = _("You are already logged in with that user.")
 
 124                     assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
 
 125                     if assoc.user == request.user:
 
 126                         request.session['auth_error'] = _(
 
 127                                 "These login credentials are already associated with your account.")
 
 129                         request.session['auth_error'] = _(
 
 130                                 "Sorry, these login credentials belong to anoother user. Plese terminate your current session and try again."
 
 133                     uassoc = AuthKeyUserAssociation(user=request.user, key=assoc_key, provider=provider)
 
 135                     request.user.message_set.create(
 
 136                             message=_('The new credentials are now associated with your account'))
 
 137                     return HttpResponseRedirect(reverse('user_authsettings', args=[request.user.id]))
 
 139             return HttpResponseRedirect(reverse('auth_signin'))
 
 141             if isinstance(assoc_key, User):
 
 142                 return login_and_forward(request, assoc_key)
 
 145             assoc = AuthKeyUserAssociation.objects.get(key=assoc_key)
 
 147             return login_and_forward(request, user_)
 
 148         except AuthKeyUserAssociation.DoesNotExist:
 
 149             request.session['assoc_key'] = assoc_key
 
 150             request.session['auth_provider'] = provider
 
 151             return HttpResponseRedirect(reverse('auth_external_register'))
 
 153     return HttpResponseRedirect(reverse('auth_signin'))
 
 155 def external_register(request):
 
 156     if request.method == 'POST' and 'bnewaccount' in request.POST:
 
 157         form1 = SimpleRegistrationForm(request.POST)
 
 160             user_ = User(username=form1.cleaned_data['username'], email=form1.cleaned_data['email'])
 
 161             user_.email_isvalid = request.session.get('auth_validated_email', '') == form1.cleaned_data['email']
 
 162             user_.set_unusable_password()
 
 164             if User.objects.all().count() == 0:
 
 165                 user_.is_superuser = True
 
 166                 user_.is_staff = True
 
 169             UserJoinsAction(user=user_, ip=request.META['REMOTE_ADDR']).save()
 
 172                 assoc_key = request.session['assoc_key']
 
 173                 auth_provider = request.session['auth_provider']
 
 175                 request.session['auth_error'] = _(
 
 176                         "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."
 
 178                 logging.error("Missing session data when trying to complete user registration: %s" % ", ".join(
 
 179                         ["%s: %s" % (k, v) for k, v in request.META.items()]))
 
 180                 return HttpResponseRedirect(reverse('auth_signin'))
 
 182             uassoc = AuthKeyUserAssociation(user=user_, key=assoc_key, provider=auth_provider)
 
 185             del request.session['assoc_key']
 
 186             del request.session['auth_provider']
 
 188             return login_and_forward(request, user_, message=_("A welcome email has been sent to your email address. "))
 
 190         auth_provider = request.session.get('auth_provider', None)
 
 191         if not auth_provider:
 
 192             request.session['auth_error'] = _(
 
 193                     "Oops, something went wrong in the middle of this process. Please try again.")
 
 194             logging.error("Missing session data when trying to complete user registration: %s" % ", ".join(
 
 195                     ["%s: %s" % (k, v) for k, v in request.META.items()]))
 
 196             return HttpResponseRedirect(reverse('auth_signin'))
 
 198         provider_class = AUTH_PROVIDERS[auth_provider].consumer
 
 200         if provider_class.__class__.__name__ == 'FacebookAuthConsumer':
 
 201             user_data = provider_class.get_user_data(request.session['access_token'])
 
 203             user_data = provider_class.get_user_data(request.session['assoc_key'])
 
 207             user_data = request.session.get('auth_consumer_data', {})
 
 209         username = user_data.get('username', '')
 
 210         email = user_data.get('email', '')
 
 213             request.session['auth_validated_email'] = email
 
 215         form1 = SimpleRegistrationForm(initial={
 
 217         'username': username,
 
 221     provider_context = AUTH_PROVIDERS[request.session['auth_provider']].context
 
 223     return render_to_response('auth/complete.html', {
 
 225     'provider':provider_context and mark_safe(provider_context.human_name) or _('unknown'),
 
 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         additional_get_params = urllib.urlencode(request.GET)
 
 293         send_template_email([request.user], "auth/mail_validation.html", {
 
 294             'validation_code': hash,
 
 295             'additional_get_params' : additional_get_params
 
 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         return login_and_forward(request, user, reverse('index'), _("Thank you, your email is now validated."))
 
 310         return render_to_response('auth/mail_already_validated.html', { 'user' : user }, RequestContext(request))
 
 312 def auth_settings(request, id):
 
 313     user_ = get_object_or_404(User, id=id)
 
 315     if not (request.user.is_superuser or request.user == user_):
 
 316         return HttpResponseUnauthorized(request)
 
 318     auth_keys = user_.auth_keys.all()
 
 320     if request.user.is_superuser or (not user_.has_usable_password()):
 
 321         FormClass = SetPasswordForm
 
 323         FormClass = ChangePasswordForm
 
 326         form = FormClass(request.POST, user=user_)
 
 328             is_new_pass = not user_.has_usable_password()
 
 329             user_.set_password(form.cleaned_data['password1'])
 
 333                 request.user.message_set.create(message=_("New password set"))
 
 334                 if not request.user.is_superuser:
 
 335                     form = ChangePasswordForm(user=user_)
 
 337                 request.user.message_set.create(message=_("Your password was changed"))
 
 339             return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': user_.id}))
 
 341         form = FormClass(user=user_)
 
 346         provider = AUTH_PROVIDERS.get(k.provider, None)
 
 348         if provider is not None:
 
 349             name =  "%s: %s" % (provider.context.human_name, provider.context.readable_key(k))
 
 351             from forum.authentication.base import ConsumerTemplateContext
 
 352             "unknown: %s" % ConsumerTemplateContext.readable_key(k)
 
 354         auth_keys_list.append({
 
 359     return render_to_response('auth/auth_settings.html', {
 
 361     "can_view_private": (user_ == request.user) or request.user.is_superuser,
 
 363     'has_password': user_.has_usable_password(),
 
 364     'auth_keys': auth_keys_list,
 
 365     'allow_local_auth': AUTH_PROVIDERS.get('local', None),
 
 366     }, context_instance=RequestContext(request))
 
 368 def remove_external_provider(request, id):
 
 369     association = get_object_or_404(AuthKeyUserAssociation, id=id)
 
 370     if not (request.user.is_superuser or request.user == association.user):
 
 371         return HttpResponseUnauthorized(request)
 
 373     request.user.message_set.create(message=_("You removed the association with %s") % association.provider)
 
 375     return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': association.user.id}))
 
 377 def login_and_forward(request, user, forward=None, message=None):
 
 378     if user.is_suspended():
 
 379         return forward_suspended_user(request, user)
 
 381     user.backend = "django.contrib.auth.backends.ModelBackend"
 
 384     # Store the login action
 
 385     UserLoginAction(user=user, ip=request.META['REMOTE_ADDR']).save()
 
 388         message = _("Welcome back %s, you are now logged in") % smart_unicode(user.username)
 
 390     request.user.message_set.create(message=message)
 
 393         forward = request.session.get(ON_SIGNIN_SESSION_ATTR, reverse('index'))
 
 395     pending_data = request.session.get(PENDING_SUBMISSION_SESSION_ATTR, None)
 
 397     if pending_data and (user.email_isvalid or pending_data['type'] not in settings.REQUIRE_EMAIL_VALIDATION_TO):
 
 398         submission_time = pending_data['time']
 
 399         if submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.HOLD_PENDING_POSTS_MINUTES)):
 
 400             del request.session[PENDING_SUBMISSION_SESSION_ATTR]
 
 401         elif submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.WARN_PENDING_POSTS_MINUTES)):
 
 402             user.message_set.create(message=(_("You have a %s pending submission.") % pending_data['data_name']) + " %s, %s, %s" % (
 
 403                 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('save')}), _("save it")),
 
 404                 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('review')}), _("review")),
 
 405                 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('cancel')}), _("cancel"))
 
 408             return manage_pending_data(request, _('save'), forward)
 
 410     additional_get_params = urllib.urlencode(request.GET)
 
 412     parsed_forward = urlparse(forward)
 
 414     # If there is already some parsed query in the URL then change the forward URL
 
 415     if parsed_forward.query:
 
 416         forward_url = forward + "&%s" % additional_get_params
 
 418         forward_url = forward + "?%s" % additional_get_params
 
 420     return HttpResponseRedirect(forward_url)
 
 422 def forward_suspended_user(request, user, show_private_msg=True):
 
 423     message = _("Sorry, but this account is suspended")
 
 425         msg_type = 'privatemsg'
 
 427         msg_type = 'publicmsg'
 
 429     suspension = user.suspension
 
 431         message += (":<br />" + suspension.extra.get(msg_type, ''))
 
 433     request.user.message_set.create(message)
 
 434     return HttpResponseRedirect(reverse('index'))
 
 436 @decorate.withfn(login_required)
 
 437 def signout(request):
 
 439     return HttpResponseRedirect(reverse('index'))