diff --git a/ietf/ietfauth/forms.py b/ietf/ietfauth/forms.py index f30f8c103..eb8be4baa 100644 --- a/ietf/ietfauth/forms.py +++ b/ietf/ietfauth/forms.py @@ -173,7 +173,6 @@ from django import forms class ChangePasswordForm(forms.Form): current_password = forms.CharField(widget=forms.PasswordInput) - new_password = forms.CharField(widget=PasswordStrengthInput) new_password_confirmation = forms.CharField(widget=PasswordConfirmationInput) @@ -185,10 +184,29 @@ class ChangePasswordForm(forms.Form): password = self.cleaned_data.get('current_password', None) if not self.user.check_password(password): raise ValidationError('Invalid password') + return password def clean(self): new_password = self.cleaned_data.get('new_password', None) conf_password = self.cleaned_data.get('new_password_confirmation', None) if not new_password == conf_password: raise ValidationError("The password confirmation is different than the new password") - + + +class ChangeUsernameForm(forms.Form): + username = forms.ChoiceField(choices=['-','--------']) + password = forms.CharField(widget=forms.PasswordInput, help_text="Confirm the change with your password") + + def __init__(self, user, *args, **kwargs): + assert isinstance(user, User) + super(ChangeUsernameForm, self).__init__(*args, **kwargs) + self.user = user + emails = user.person.email_set.filter(active=True) + choices = [ (email.address, email.address) for email in emails ] + self.fields['username'] = forms.ChoiceField(choices=choices) + + def clean_password(self): + password = self.cleaned_data['password'] + if not self.user.check_password(password): + raise ValidationError('Invalid password') + return password diff --git a/ietf/ietfauth/tests.py b/ietf/ietfauth/tests.py index b8dfc6440..5687a5e38 100644 --- a/ietf/ietfauth/tests.py +++ b/ietf/ietfauth/tests.py @@ -449,3 +449,50 @@ class IetfAuthTests(TestCase): user = User.objects.get(username="someone@example.com") self.assertTrue(user.check_password(u'foobar')) + def test_change_username(self): + + chun_url = urlreverse(ietf.ietfauth.views.change_username) + prof_url = urlreverse(ietf.ietfauth.views.profile) + login_url = urlreverse(django.contrib.auth.views.login) + redir_url = '%s?next=%s' % (login_url, chun_url) + + # get without logging in + r = self.client.get(chun_url) + self.assertRedirects(r, redir_url) + + user = User.objects.create(username="someone@example.com", email="someone@example.com") + user.set_password("password") + user.save() + p = Person.objects.create(name="Some One", ascii="Some One", user=user) + Email.objects.create(address=user.username, person=p) + Email.objects.create(address="othername@example.org", person=p) + + # log in + r = self.client.post(redir_url, {"username":user.username, "password":"password"}) + self.assertRedirects(r, chun_url) + + # wrong username + r = self.client.post(chun_url, {"username": "fiddlesticks", + "password": "password", + }) + self.assertEqual(r.status_code, 200) + self.assertFormError(r, 'form', 'username', + "Select a valid choice. fiddlesticks is not one of the available choices.") + + # wrong password + r = self.client.post(chun_url, {"username": "othername@example.org", + "password": "foobar", + }) + self.assertEqual(r.status_code, 200) + self.assertFormError(r, 'form', 'password', 'Invalid password') + + # correct username change + r = self.client.post(chun_url, {"username": "othername@example.org", + "password": "password", + }) + self.assertRedirects(r, prof_url) + # refresh user object + prev = user + user = User.objects.get(username="othername@example.org") + self.assertEqual(prev, user) + self.assertTrue(user.check_password(u'password')) diff --git a/ietf/ietfauth/urls.py b/ietf/ietfauth/urls.py index ee278a4d4..3e110e2cd 100644 --- a/ietf/ietfauth/urls.py +++ b/ietf/ietfauth/urls.py @@ -18,5 +18,6 @@ urlpatterns = [ url(r'^reset/confirm/(?P[^/]+)/$', views.confirm_password_reset), url(r'^review/$', views.review_overview), url(r'^testemail/$', views.test_email), - url(r'whitelist/add/?$', views.add_account_whitelist), + url(r'^username/$', views.change_username), + url(r'^whitelist/add/?$', views.add_account_whitelist), ] diff --git a/ietf/ietfauth/views.py b/ietf/ietfauth/views.py index 1380f6e3e..9a9e43164 100644 --- a/ietf/ietfauth/views.py +++ b/ietf/ietfauth/views.py @@ -52,8 +52,9 @@ from django.shortcuts import render, redirect, get_object_or_404 import debug # pyflakes:ignore from ietf.group.models import Role, Group -from ietf.ietfauth.forms import RegistrationForm, PasswordForm, ResetPasswordForm, TestEmailForm, WhitelistForm, ChangePasswordForm -from ietf.ietfauth.forms import get_person_form, RoleEmailForm, NewEmailForm +from ietf.ietfauth.forms import ( RegistrationForm, PasswordForm, ResetPasswordForm, TestEmailForm, + WhitelistForm, ChangePasswordForm, get_person_form, RoleEmailForm, + NewEmailForm, ChangeUsernameForm ) from ietf.ietfauth.htpasswd import update_htpasswd_file from ietf.ietfauth.utils import role_required from ietf.mailinglists.models import Subscribed, Whitelisted @@ -521,3 +522,45 @@ def change_password(request): }) +@login_required +def change_username(request): + person = None + + try: + person = request.user.person + except Person.DoesNotExist: + return render(request, 'registration/missing_person.html') + + emails = [ e.address for e in Email.objects.filter(person=person, active=True) ] + emailz = [ e.address for e in person.email_set.filter(active=True) ] + assert emails == emailz + user = request.user + + if request.method == 'POST': + form = ChangeUsernameForm(user, request.POST) + if form.is_valid(): + new_username = form.cleaned_data["username"] + password = form.cleaned_data["password"] + assert new_username in emails + + user.username = new_username.lower() + user.save() + # password is also stored in htpasswd file + update_htpasswd_file(user.username, password) + # keep the session + update_session_auth_hash(request, user) + + send_mail(request, emails, None, "Datatracker username change notification", "registration/username_change_email.txt", {}) + + messages.success(request, "Your username was successfully changed") + return HttpResponseRedirect(urlreverse('ietf.ietfauth.views.profile')) + + else: + form = ChangeUsernameForm(request.user) + + return render(request, 'registration/change_username.html', { + 'form': form, + 'user': user, + }) + + diff --git a/ietf/templates/base/menu_user.html b/ietf/templates/base/menu_user.html index bf1232dfd..bd1e958c7 100644 --- a/ietf/templates/base/menu_user.html +++ b/ietf/templates/base/menu_user.html @@ -17,17 +17,19 @@ {% if user.is_authenticated %}
  • Sign out
  • Account info
  • +
  • Preferences
  • Change password
  • +
  • Change username
  • {% else %}
  • Sign in
  • Password reset
  • +
  • Preferences
  • {% endif %} {% endif %} {% if not request.user.is_authenticated %}
  • New account
  • {% endif %} -
  • Preferences
  • {% if user|has_role:"Reviewer" %}
  • My reviews
  • diff --git a/ietf/templates/registration/change_username.html b/ietf/templates/registration/change_username.html new file mode 100644 index 000000000..4f32ec8d7 --- /dev/null +++ b/ietf/templates/registration/change_username.html @@ -0,0 +1,40 @@ +{% extends "base.html" %} +{# Copyright The IETF Trust 2015, All Rights Reserved #} +{% load origin %} + +{% load bootstrap3 %} +{% load staticfiles %} + +{% block title %}Change username{% endblock %} + + +{% block content %} + {% origin %} + +
    +
    +
    +

    Change username

    + +
    + This form lets you change your username (login) from {{ user.username }} to + one of your other active email addresses. If you want to change to a new + email address, then please first + edit your profile + to add that email address to the active email addresses for your account. +
    + +
    + {% csrf_token %} + {% bootstrap_form form %} + + {% buttons %} + + {% endbuttons %} +
    + +
    +
    +
    + +{% endblock %} diff --git a/ietf/templates/registration/username_change_email.txt b/ietf/templates/registration/username_change_email.txt new file mode 100644 index 000000000..f962eb201 --- /dev/null +++ b/ietf/templates/registration/username_change_email.txt @@ -0,0 +1,10 @@ +{% autoescape off %} +Hello, + +{% filter wordwrap:73 %}The username (login name) for your datatracker account was just changed using the username change form. If this was not done by you, please contact the secretariat at ietf-action@ietf.org for assistance.{% endfilter %} + +Best regards, + + The datatracker account manager service + (for the IETF Secretariat) +{% endautoescape %}