diff --git a/userena/forms.py b/userena/forms.py index dff9c0bf..92faf1dd 100644 --- a/userena/forms.py +++ b/userena/forms.py @@ -45,6 +45,10 @@ class SignupForm(forms.Form): render_value=False), label=_("Repeat password")) + def __init__(self, redirect=None, *args, **kwargs): + self.redirect = redirect + super(SignupForm, self).__init__(*args, **kwargs) + def clean_username(self): """ Validate that the username is alphanumeric and is not already in use. @@ -94,7 +98,8 @@ def save(self): email, password, not userena_settings.USERENA_ACTIVATION_REQUIRED, - userena_settings.USERENA_ACTIVATION_REQUIRED) + userena_settings.USERENA_ACTIVATION_REQUIRED, + redirect=self.redirect) return new_user class SignupFormOnlyEmail(SignupForm): diff --git a/userena/managers.py b/userena/managers.py index a4d91c09..dfc25d86 100644 --- a/userena/managers.py +++ b/userena/managers.py @@ -36,7 +36,7 @@ class UserenaManager(UserManager): """ Extra functionality for the Userena model. """ def create_user(self, username, email, password, active=False, - send_email=True): + send_email=True, redirect=None): """ A simple wrapper that creates a new :class:`User`. @@ -78,7 +78,7 @@ def create_user(self, username, email, password, active=False, userena_profile = self.create_userena_profile(new_user) if send_email: - userena_profile.send_activation_email() + userena_profile.send_activation_email(redirect=redirect) return new_user diff --git a/userena/models.py b/userena/models.py index a23d60be..3e7c50ed 100644 --- a/userena/models.py +++ b/userena/models.py @@ -156,11 +156,11 @@ def activation_key_expired(self): return True return False - def send_activation_email(self): + def send_activation_email(self, redirect=None): """ Sends a activation email to the user. - This email is send when the user wants to activate their newly created + This email is sent when the user wants to activate their newly created user. """ @@ -169,7 +169,8 @@ def send_activation_email(self): 'protocol': get_protocol(), 'activation_days': userena_settings.USERENA_ACTIVATION_DAYS, 'activation_key': self.activation_key, - 'site': Site.objects.get_current()} + 'site': Site.objects.get_current(), + 'redirect': redirect} mailer = UserenaConfirmationMail(context=context) mailer.generate_mail("activation") diff --git a/userena/templates/userena/emails/activation_email_message.html b/userena/templates/userena/emails/activation_email_message.html index 9f50e037..c3fcb6ae 100644 --- a/userena/templates/userena/emails/activation_email_message.html +++ b/userena/templates/userena/emails/activation_email_message.html @@ -5,7 +5,7 @@ {% blocktrans with site.name as site %}

Thank you for signing up at {{ site }}.

{% endblocktrans %}

{% trans "To activate your account you should click on the link below:" %}
- {{ protocol }}://{{ site.domain }}{% url 'userena_activate' activation_key %} + {{ protocol }}://{{ site.domain }}{% url 'userena_activate' activation_key %}{% if redirect %}?next={{redirect}}{% endif %}

{% trans "Thanks for using our site!" %}
diff --git a/userena/templates/userena/emails/activation_email_message.txt b/userena/templates/userena/emails/activation_email_message.txt index 7bf5d631..e1a50888 100644 --- a/userena/templates/userena/emails/activation_email_message.txt +++ b/userena/templates/userena/emails/activation_email_message.txt @@ -5,7 +5,7 @@ {% trans "To activate your account you should click on the link below:" %} -{{ protocol }}://{{ site.domain }}{% url 'userena_activate' activation_key %} +{{ protocol }}://{{ site.domain }}{% url 'userena_activate' activation_key %}{% if redirect %}?next={{redirect}}{% endif %} {% trans "Thanks for using our site!" %} diff --git a/userena/templates/userena/signup_form.html b/userena/templates/userena/signup_form.html index f10283b6..e2bc01fd 100644 --- a/userena/templates/userena/signup_form.html +++ b/userena/templates/userena/signup_form.html @@ -25,5 +25,6 @@ {% endfor %} + {% if next %}{% endif %} {% endblock %} diff --git a/userena/tests/tests_views.py b/userena/tests/tests_views.py index 6860332a..53253370 100644 --- a/userena/tests/tests_views.py +++ b/userena/tests/tests_views.py @@ -1,7 +1,7 @@ import re from datetime import datetime, timedelta -from django.contrib.auth import get_user_model +from django.contrib.auth import get_user_model, REDIRECT_FIELD_NAME from django.core.urlresolvers import reverse from django.core import mail from django.contrib.auth.forms import PasswordChangeForm @@ -36,6 +36,30 @@ def test_valid_activation(self): user = User.objects.get(email='alice@example.com') self.assertTrue(user.is_active) + def test_activation_redirect(self): + """ A ``GET`` to the activation view with redirect parameter """ + + redirect = '/some/url/' + + # First, register an account. + self.client.post('%s?%s=%s' % (reverse('userena_signup'), REDIRECT_FIELD_NAME, redirect), + data={'username': 'alice', + 'email': 'alice@example.com', + 'password1': 'swordfish', + 'password2': 'swordfish', + 'tos': 'on'}) + user = User.objects.get(email='alice@example.com') + + # Send a GET request with the redirect parameter to the url + response = self.client.get( + '%s?%s=%s' % (reverse('userena_activate', kwargs={'activation_key': user.userena_signup.activation_key}), + REDIRECT_FIELD_NAME, redirect)) + + self.assertTrue(response.get('Location').endswith(redirect)) + + if hasattr(response, 'url'): + self.assertTrue(response.url.endswith(redirect)) + def test_activation_expired_retry(self): """ A ``GET`` to the activation view when activation link is expired """ # First, register an account. diff --git a/userena/views.py b/userena/views.py index a06d7214..064411f0 100644 --- a/userena/views.py +++ b/userena/views.py @@ -4,6 +4,7 @@ from django.contrib.auth import get_user_model from django.contrib.auth.forms import PasswordChangeForm from django.contrib.auth.views import logout as Signout +from django.views.decorators.debug import sensitive_post_parameters from django.views.generic import TemplateView from django.views.generic.list import ListView from django.contrib import messages @@ -69,16 +70,19 @@ def get_queryset(self): queryset = profile_model.objects.get_visible_profiles(self.request.user).select_related() return queryset + +@sensitive_post_parameters('password1', 'password2') @secure_required def signup(request, signup_form=SignupForm, template_name='userena/signup_form.html', success_url=None, - extra_context=None): + extra_context=None, redirect_field_name=REDIRECT_FIELD_NAME): """ Signup of an account. Signup requiring a username, email and password. After signup a user gets an email with an activation link used to activate their account. After - successful signup redirects to ``success_url``. + successful signup redirects to the ``REDIRECT_FIELD_NAME`` query parameter + if provided, otherwise ``success_url``. :param signup_form: Form that will be used to sign a user. Defaults to userena's @@ -98,10 +102,16 @@ def signup(request, signup_form=SignupForm, context. Defaults to a dictionary with a ``form`` key containing the ``signup_form``. + :param redirect_field_name: + String containing the name of the field that contains a redirect url. + Defaults to ``next``. + **Context** ``form`` Form supplied by ``signup_form``. + ``next`` + Next supplied by a query parameter. """ # If signup is disabled, return 403 @@ -115,8 +125,11 @@ def signup(request, signup_form=SignupForm, form = signup_form() + redirect_url = request.GET.get(redirect_field_name, request.POST.get(redirect_field_name, None)) + if request.method == 'POST': - form = signup_form(request.POST, request.FILES) + form = signup_form(redirect=redirect_url, data=request.POST, files=request.FILES) + if form.is_valid(): user = form.save() @@ -124,7 +137,6 @@ def signup(request, signup_form=SignupForm, userena_signals.signup_complete.send(sender=None, user=user) - if success_url: redirect_to = success_url else: redirect_to = reverse('userena_signup_complete', kwargs={'username': user.username}) @@ -138,10 +150,18 @@ def signup(request, signup_form=SignupForm, user = authenticate(identification=user.email, check_password=False) login(request, user) + # since we signed in the user and have no more information to display, + # we can redirect the user to a requested page + if redirect_url: + redirect_to = redirect_url + return redirect(redirect_to) if not extra_context: extra_context = dict() - extra_context['form'] = form + extra_context.update({ + 'form': form, + 'next': redirect_url, + }) return ExtraContextTemplateView.as_view(template_name=template_name, extra_context=extra_context)(request) @@ -149,17 +169,19 @@ def signup(request, signup_form=SignupForm, def activate(request, activation_key, template_name='userena/activate_fail.html', retry_template_name='userena/activate_retry.html', - success_url=None, extra_context=None): + success_url=None, extra_context=None, + redirect_field_name=REDIRECT_FIELD_NAME): """ Activate a user with an activation key. The key is a SHA1 string. When the SHA1 is found with an :class:`UserenaSignup`, the :class:`User` of that account will be activated. After a successful activation the view will redirect to - ``success_url``. If the SHA1 is not found, the user will be shown the - ``template_name`` template displaying a fail message. - If the SHA1 is found but expired, ``retry_template_name`` is used instead, - so the user can proceed to :func:`activate_retry` to get a new activation key. + the ``REDIRECT_FIELD_NAME`` query parameter if provided, otherwise + ``success_url``. If the SHA1 is not found, the user will be shown the + ``template_name`` template displaying a fail message. If the SHA1 is found + but expired, ``retry_template_name`` is used instead, so the user can + proceed to :func:`activate_retry` to get a new activation key. :param activation_key: String of a SHA1 string of 40 characters long. A SHA1 is always 160bit @@ -201,7 +223,10 @@ def activate(request, activation_key, messages.success(request, _('Your account has been activated and you have been signed in.'), fail_silently=True) - if success_url: redirect_to = success_url % {'username': user.username } + redirect_to = request.GET.get(redirect_field_name, None) + + if redirect_to: pass + elif success_url: redirect_to = success_url % {'username': user.username } else: redirect_to = reverse('userena_profile_detail', kwargs={'username': user.username}) return redirect(redirect_to) @@ -386,6 +411,8 @@ def disabled_account(request, username, template_name, extra_context=None): return ExtraContextTemplateView.as_view(template_name=template_name, extra_context=extra_context)(request) + +@sensitive_post_parameters('password') @secure_required def signin(request, auth_form=AuthenticationForm, template_name='userena/signin_form.html', @@ -563,6 +590,8 @@ def email_change(request, username, email_form=ChangeEmailForm, return ExtraContextTemplateView.as_view(template_name=template_name, extra_context=extra_context)(request) + +@sensitive_post_parameters('old_password', 'new_password1', 'new_password2') @secure_required @permission_required_or_403('change_user', (get_user_model(), 'username', 'username')) def password_change(request, username, template_name='userena/password_form.html',