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 django.contrib import messages
 
  19 from writers import manage_pending_data
 
  21 from forum.actions import EmailValidationAction
 
  22 from forum.utils import html
 
  23 from forum.views.decorators import login_required
 
  24 from forum.modules import decorate
 
  25 from forum.forms import SimpleRegistrationForm, TemporaryLoginRequestForm, ChangePasswordForm, SetPasswordForm
 
  26 from forum.http_responses import HttpResponseUnauthorized
 
  27 from forum.utils.mail import send_template_email
 
  28 from forum.authentication.base import InvalidAuthentication
 
  29 from forum.authentication import AUTH_PROVIDERS
 
  30 from forum.models import User, AuthKeyUserAssociation, ValidationHash
 
  31 from forum.actions import UserJoinsAction, UserLoginAction
 
  32 from forum import settings
 
  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', prefix='/',
 
  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                     messages.info(request, _('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'], real_name=form1.cleaned_data['real_name'])
 
 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
 
 201         if provider_class.__class__.__name__ == 'FacebookAuthConsumer':
 
 202             user_data = provider_class.get_user_data(request.session['access_token'])
 
 204             user_data = provider_class.get_user_data(request.session['assoc_key'])
 
 208             user_data = request.session.get('auth_consumer_data', {})
 
 210         username = user_data.get('username', '')
 
 211         email = user_data.get('email', '')
 
 212         real_name = user_data.get('real_name', '')
 
 215             request.session['auth_validated_email'] = email
 
 217         form1 = SimpleRegistrationForm(initial={
 
 219         'username': username,
 
 221         'real_name': real_name,
 
 224     provider_context = AUTH_PROVIDERS[request.session['auth_provider']].context
 
 226     return render_to_response('auth/complete.html', {
 
 228     'provider':provider_context and mark_safe(provider_context.human_name) or _('unknown'),
 
 229     'login_type':provider_context.id,
 
 230     'gravatar_faq_url':reverse('faq') + '#gravatar',
 
 231     }, context_instance=RequestContext(request))
 
 233 def request_temp_login(request):
 
 234     if request.method == 'POST':
 
 235         form = TemporaryLoginRequestForm(request.POST)
 
 238             users = form.user_cache
 
 242                     return forward_suspended_user(request, u, False)
 
 246                     hash = get_object_or_404(ValidationHash, user=u, type='templogin')
 
 247                     if hash.expiration < datetime.datetime.now():
 
 249                         return request_temp_login(request)
 
 251                     hash = ValidationHash.objects.create_new(u, 'templogin', [u.id])
 
 253                 send_template_email([u], "auth/temp_login_email.html", {'temp_login_code': hash})
 
 255                 messages.info(request, _("An email has been sent with your temporary login key"))
 
 257             return HttpResponseRedirect(reverse('index'))
 
 259         form = TemporaryLoginRequestForm()
 
 261     return render_to_response(
 
 262             'auth/temp_login_request.html', {'form': form},
 
 263             context_instance=RequestContext(request))
 
 265 def temp_signin(request, user, code):
 
 266     user = get_object_or_404(User, id=user)
 
 268     if (ValidationHash.objects.validate(code, user, 'templogin', [user.id])):
 
 270         # If the user requests temp_signin he must have forgotten his password. So we mark it as unusable.
 
 271         user.set_unusable_password()
 
 274         return login_and_forward(request, user, reverse('user_authsettings', kwargs={'id': user.id}),
 
 276                                          "You are logged in with a temporary access key, please take the time to fix your issue with authentication."
 
 281 def send_validation_email(request):
 
 282     if not request.user.is_authenticated():
 
 283         return HttpResponseUnauthorized(request)
 
 285         # We check if there are some old validation hashes. If there are -- we delete them.
 
 287             hash = ValidationHash.objects.get(user=request.user, type='email')
 
 292         # We don't care if there are previous cashes in the database... In every case we have to create a new one
 
 293         hash = ValidationHash.objects.create_new(request.user, 'email', [request.user.email])
 
 295         additional_get_params = urllib.urlencode(dict([k, v.encode('utf-8')] for k, v in request.GET.items()))
 
 296         send_template_email([request.user], "auth/mail_validation.html", {
 
 297             'validation_code': hash,
 
 298             'additional_get_params' : additional_get_params
 
 301         messages.info(request, _("A message with an email validation link was just sent to your address."))
 
 302         return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
 
 306 def validate_email(request, user, code):
 
 307     user = get_object_or_404(User, id=user)
 
 309     if (ValidationHash.objects.validate(code, user, 'email', [user.email])):
 
 310         EmailValidationAction(user=user, ip=request.META['REMOTE_ADDR']).save()
 
 311         return login_and_forward(request, user, reverse('index'), _("Thank you, your email is now validated."))
 
 313         return render_to_response('auth/mail_already_validated.html', { 'user' : user }, RequestContext(request))
 
 315 def auth_settings(request, id):
 
 316     user_ = get_object_or_404(User, id=id)
 
 318     if not (request.user.is_superuser or request.user == user_):
 
 319         return HttpResponseUnauthorized(request)
 
 321     auth_keys = user_.auth_keys.all()
 
 323     if request.user.is_superuser or (not user_.has_usable_password()):
 
 324         FormClass = SetPasswordForm
 
 326         FormClass = ChangePasswordForm
 
 329         form = FormClass(request.POST, user=user_)
 
 331             is_new_pass = not user_.has_usable_password()
 
 332             user_.set_password(form.cleaned_data['password1'])
 
 336                 messages.info(request, _("New password set"))
 
 337                 if not request.user.is_superuser:
 
 338                     form = ChangePasswordForm(user=user_)
 
 340                 messages.info(request, _("Your password was changed"))
 
 342             return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': user_.id}))
 
 344         form = FormClass(user=user_)
 
 349         provider = AUTH_PROVIDERS.get(k.provider, None)
 
 351         if provider is not None:
 
 352             name =  "%s: %s" % (provider.context.human_name, provider.context.readable_key(k))
 
 354             from forum.authentication.base import ConsumerTemplateContext
 
 355             "unknown: %s" % ConsumerTemplateContext.readable_key(k)
 
 357         auth_keys_list.append({
 
 362     return render_to_response('auth/auth_settings.html', {
 
 364     "can_view_private": (user_ == request.user) or request.user.is_superuser,
 
 366     'has_password': user_.has_usable_password(),
 
 367     'auth_keys': auth_keys_list,
 
 368     'allow_local_auth': AUTH_PROVIDERS.get('local', None),
 
 369     }, context_instance=RequestContext(request))
 
 371 def remove_external_provider(request, id):
 
 372     association = get_object_or_404(AuthKeyUserAssociation, id=id)
 
 373     if not (request.user.is_superuser or request.user == association.user):
 
 374         return HttpResponseUnauthorized(request)
 
 376     messages.info(request, _("You removed the association with %s") % association.provider)
 
 378     return HttpResponseRedirect(reverse('user_authsettings', kwargs={'id': association.user.id}))
 
 380 def login_and_forward(request, user, forward=None, message=None):
 
 381     if user.is_suspended():
 
 382         return forward_suspended_user(request, user)
 
 384     user.backend = "django.contrib.auth.backends.ModelBackend"
 
 387     # Store the login action
 
 388     UserLoginAction(user=user, ip=request.META['REMOTE_ADDR']).save()
 
 391         message = _("Welcome back %s, you are now logged in") % smart_unicode(user.username)
 
 393     messages.info(request, message)
 
 396         forward = request.session.get(ON_SIGNIN_SESSION_ATTR, reverse('index'))
 
 398     pending_data = request.session.get(PENDING_SUBMISSION_SESSION_ATTR, None)
 
 400     if pending_data and (user.email_isvalid or pending_data['type'] not in settings.REQUIRE_EMAIL_VALIDATION_TO):
 
 401         submission_time = pending_data['time']
 
 402         if submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.HOLD_PENDING_POSTS_MINUTES)):
 
 403             del request.session[PENDING_SUBMISSION_SESSION_ATTR]
 
 404         elif submission_time < datetime.datetime.now() - datetime.timedelta(minutes=int(settings.WARN_PENDING_POSTS_MINUTES)):
 
 405             messages.info(request, (_("You have a %s pending submission.") % pending_data['data_name']) + " %s, %s, %s" % (
 
 406                 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('save')}), _("save it")),
 
 407                 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('review')}), _("review")),
 
 408                 html.hyperlink(reverse('manage_pending_data', kwargs={'action': _('cancel')}), _("cancel"))
 
 411             return manage_pending_data(request, _('save'), forward)
 
 413     additional_get_params = urllib.urlencode(dict([k, v.encode('utf-8')] for k, v in request.GET.items()))
 
 415     parsed_forward = urlparse(forward)
 
 417     # If there is already some parsed query in the URL then change the forward URL
 
 418     if parsed_forward.query:
 
 419         forward_url = forward + "&%s" % additional_get_params
 
 421         forward_url = forward + "?%s" % additional_get_params
 
 423     return HttpResponseRedirect(forward_url)
 
 425 def forward_suspended_user(request, user, show_private_msg=True):
 
 426     message = _("Sorry, but this account is suspended")
 
 428         msg_type = 'privatemsg'
 
 430         msg_type = 'publicmsg'
 
 432     suspension = user.suspension
 
 434         message += (":<br />" + suspension.extra.get(msg_type, ''))
 
 436     messages.info(request, message)
 
 437     return HttpResponseRedirect(reverse('index'))
 
 439 @decorate.withfn(login_required)
 
 440 def signout(request):
 
 442     return HttpResponseRedirect(reverse('index'))