Commit 291441f94908c53e40ded8484f52b400995c6f94

Authored by Sergio Oliveira
1 parent dd3df95b

Fixed bug for other ways of password set/reset

Signed-off-by: Gustavo Jaruga <darksshades@gmail.com>
Signed-off-by: Sergio Oliveira <seocam@seocam.com>
colab/accounts/forms.py
1 1 # -*- coding: utf-8 -*-
2 2  
3   -from collections import OrderedDict
4 3 from importlib import import_module
5 4  
6 5 from django import forms
7 6 from django.conf import settings
8   -from django.contrib.auth import authenticate, get_user_model
9   -from django.contrib.auth.tokens import default_token_generator
10   -from django.contrib.auth.forms import ReadOnlyPasswordHashField
  7 +from django.contrib.auth import get_user_model
  8 +from django.contrib.auth.forms import (ReadOnlyPasswordHashField,
  9 + SetPasswordForm, PasswordChangeForm)
11 10 from django.core.urlresolvers import reverse
12 11 from django.utils.functional import lazy
13 12 from django.utils.translation import ugettext_lazy as _
14 13 from django.utils.safestring import mark_safe
15   -from django.utils.text import capfirst
16   -from django.template import loader
17   -from django.utils.encoding import force_bytes
18   -from django.utils.http import urlsafe_base64_encode
19   -from django.contrib.sites.shortcuts import get_current_site
20 14  
21 15 from .signals import user_created
22 16 from .utils.validators import validate_social_account
... ... @@ -169,7 +163,40 @@ class ListsForm(forms.Form):
169 163 choices=lazy(get_lists_choices, list)())
170 164  
171 165  
172   -class UserCreationForm(UserForm):
  166 +class ColabSetPasswordFormMixin(object):
  167 +
  168 + def apply_custom_validators(self, password):
  169 + for app in settings.COLAB_APPS.values():
  170 + if 'password_validators' in app:
  171 + for validator_path in app.get('password_validators'):
  172 + module_path, func_name = validator_path.rsplit('.', 1)
  173 + module = import_module(module_path)
  174 + validator_func = getattr(module, func_name, None)
  175 + if validator_func:
  176 + validator_func(password)
  177 +
  178 + return password
  179 +
  180 + def clean_new_password2(self):
  181 + try:
  182 + password = super(ColabSetPasswordFormMixin, self).clean_new_password2()
  183 + except AttributeError:
  184 + password = self.cleaned_data['new_password2']
  185 +
  186 + self.apply_custom_validators(password)
  187 + return password
  188 +
  189 + def clean_password2(self):
  190 + try:
  191 + password = super(ColabSetPasswordFormMixin, self).clean_password2()
  192 + except AttributeError:
  193 + password = self.cleaned_data['password2']
  194 +
  195 + self.apply_custom_validators(password)
  196 + return password
  197 +
  198 +
  199 +class UserCreationForm(UserForm, ColabSetPasswordFormMixin):
173 200 """
174 201 A form that creates a user, with no privileges, from the given username and
175 202 password.
... ... @@ -240,6 +267,8 @@ class UserCreationForm(UserForm):
240 267 self.error_messages['password_mismatch'],
241 268 code='password_mismatch',
242 269 )
  270 +
  271 + super(UserCreationForm, self).clean_password2()
243 272 return password2
244 273  
245 274 def save(self, commit=True):
... ... @@ -293,248 +322,9 @@ class UserChangeForm(forms.ModelForm):
293 322 return self.initial["password"]
294 323  
295 324  
296   -class AuthenticationForm(forms.Form):
297   - """
298   - Base class for authenticating users. Extend this to get a form that accepts
299   - username/password logins.
300   - """
301   - username = forms.CharField(max_length=254)
302   - password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
303   -
304   - error_messages = {
305   - 'invalid_login': _("Please enter a correct %(username)s and password. "
306   - "Note that both fields may be case-sensitive."),
307   - 'inactive': _("This account is inactive."),
308   - }
309   -
310   - def __init__(self, request=None, *args, **kwargs):
311   - """
312   - The 'request' parameter is set for custom auth use by subclasses.
313   - The form data comes in via the standard 'data' kwarg.
314   - """
315   - self.request = request
316   - self.user_cache = None
317   - super(AuthenticationForm, self).__init__(*args, **kwargs)
318   -
319   - # Set the label for the "username" field.
320   - UserModel = get_user_model()
321   - self.username_field = UserModel._meta.get_field(
322   - UserModel.USERNAME_FIELD)
323   - if self.fields['username'].label is None:
324   - self.fields['username'].label = capfirst(
325   - self.username_field.verbose_name)
326   -
327   - def clean(self):
328   - username = self.cleaned_data.get('username')
329   - password = self.cleaned_data.get('password')
330   -
331   - if username and password:
332   - self.user_cache = authenticate(username=username,
333   - password=password)
334   - if self.user_cache is None:
335   - raise forms.ValidationError(
336   - self.error_messages['invalid_login'],
337   - code='invalid_login',
338   - params={'username': self.username_field.verbose_name},
339   - )
340   - else:
341   - self.confirm_login_allowed(self.user_cache)
342   -
343   - return self.cleaned_data
344   -
345   - def confirm_login_allowed(self, user):
346   - """
347   - Controls whether the given User may log in. This is a policy setting,
348   - independent of end-user authentication. This default behavior is to
349   - allow login by active users, and reject login by inactive users.
350   - If the given user cannot log in, this method should raise a
351   - ``forms.ValidationError``.
352   - If the given user may log in, this method should return None.
353   - """
354   - if not user.is_active:
355   - raise forms.ValidationError(
356   - self.error_messages['inactive'],
357   - code='inactive',
358   - )
359   -
360   - def get_user_id(self):
361   - if self.user_cache:
362   - return self.user_cache.id
363   - return None
364   -
365   - def get_user(self):
366   - return self.user_cache
367   -
368   -
369   -class PasswordResetForm(forms.Form):
370   - email = forms.EmailField(label=_("Email"), max_length=254)
371   -
372   - def save(self, domain_override=None,
373   - subject_template_name='registration/password_reset_subject.txt',
374   - email_template_name='registration/password_reset_email.html',
375   - use_https=False, token_generator=default_token_generator,
376   - from_email=None, request=None, html_email_template_name=None):
377   - """
378   - Generates a one-use only link for resetting password and sends to the
379   - user.
380   - """
381   - from django.core.mail import send_mail
382   - UserModel = get_user_model()
383   - email = self.cleaned_data["email"]
384   - active_users = UserModel._default_manager.filter(
385   - email__iexact=email, is_active=True)
386   - for user in active_users:
387   - # Make sure that no email is sent to a user that actually has
388   - # a password marked as unusable
389   - if not user.has_usable_password():
390   - continue
391   - if not domain_override:
392   - current_site = get_current_site(request)
393   - site_name = current_site.name
394   - domain = current_site.domain
395   - else:
396   - site_name = domain = domain_override
397   - c = {
398   - 'email': user.email,
399   - 'domain': domain,
400   - 'site_name': site_name,
401   - 'uid': urlsafe_base64_encode(force_bytes(user.pk)),
402   - 'user': user,
403   - 'token': token_generator.make_token(user),
404   - 'protocol': 'https' if use_https else 'http',
405   - }
406   - subject = loader.render_to_string(subject_template_name, c)
407   - # Email subject *must not* contain newlines
408   - subject = ''.join(subject.splitlines())
409   - email = loader.render_to_string(email_template_name, c)
410   -
411   - if html_email_template_name:
412   - html_email = loader.render_to_string(html_email_template_name,
413   - c)
414   - else:
415   - html_email = None
416   - send_mail(subject, email, from_email, [user.email],
417   - html_message=html_email)
418   -
419   -
420   -class SetPasswordForm(forms.Form):
421   - """
422   - A form that lets a user change set their password without entering the old
423   - password
424   - """
425   - error_messages = {
426   - 'password_mismatch': _("The two password fields didn't match."),
427   - }
428   - new_password1 = forms.CharField(label=_("New password"),
429   - widget=forms.PasswordInput)
430   - new_password2 = forms.CharField(label=_("New password confirmation"),
431   - widget=forms.PasswordInput)
  325 +class ColabSetPasswordForm(ColabSetPasswordFormMixin, SetPasswordForm):
  326 + pass
432 327  
433   - def __init__(self, user, *args, **kwargs):
434   - self.user = user
435   - super(SetPasswordForm, self).__init__(*args, **kwargs)
436 328  
437   - def clean_new_password1(self):
438   - password = self.cleaned_data.get('new_password1')
439   -
440   - for app in settings.COLAB_APPS.values():
441   - if 'password_validators' in app:
442   - for validator_path in app.get('password_validators'):
443   - module_path, func_name = validator_path.rsplit('.', 1)
444   - module = import_module(module_path)
445   - validator_func = getattr(module, func_name, None)
446   - if validator_func:
447   - validator_func(password)
448   -
449   - return password
450   -
451   - def clean_new_password2(self):
452   - password1 = self.cleaned_data.get('new_password1')
453   - password2 = self.cleaned_data.get('new_password2')
454   - if password1 and password2:
455   - if password1 != password2:
456   - raise forms.ValidationError(
457   - self.error_messages['password_mismatch'],
458   - code='password_mismatch',
459   - )
460   - return password2
461   -
462   - def save(self, commit=True):
463   - self.user.set_password(self.cleaned_data['new_password1'])
464   - if commit:
465   - self.user.save()
466   - return self.user
467   -
468   -
469   -class PasswordChangeForm(SetPasswordForm):
470   - """
471   - A form that lets a user change their password by entering their old
472   - password.
473   - """
474   - error_messages = dict(SetPasswordForm.error_messages, **{
475   - 'password_incorrect': _("Your old password was entered incorrectly. "
476   - "Please enter it again."),
477   - })
478   - old_password = forms.CharField(label=_("Old password"),
479   - widget=forms.PasswordInput)
480   -
481   - def clean_old_password(self):
482   - """
483   - Validates that the old_password field is correct.
484   - """
485   - old_password = self.cleaned_data["old_password"]
486   - if not self.user.check_password(old_password):
487   - raise forms.ValidationError(
488   - self.error_messages['password_incorrect'],
489   - code='password_incorrect',
490   - )
491   - return old_password
492   -
493   -PasswordChangeForm.base_fields = OrderedDict(
494   - (k, PasswordChangeForm.base_fields[k])
495   - for k in ['old_password', 'new_password1', 'new_password2']
496   -)
497   -
498   -
499   -class AdminPasswordChangeForm(forms.Form):
500   - """
501   - A form used to change the password of a user in the admin interface.
502   - """
503   - error_messages = {
504   - 'password_mismatch': _("The two password fields didn't match."),
505   - }
506   - password1 = forms.CharField(label=_("Password"),
507   - widget=forms.PasswordInput)
508   - password2 = forms.CharField(label=_("Password (again)"),
509   - widget=forms.PasswordInput)
510   -
511   - def __init__(self, user, *args, **kwargs):
512   - self.user = user
513   - super(AdminPasswordChangeForm, self).__init__(*args, **kwargs)
514   -
515   - def clean_password2(self):
516   - password1 = self.cleaned_data.get('password1')
517   - password2 = self.cleaned_data.get('password2')
518   - if password1 and password2:
519   - if password1 != password2:
520   - raise forms.ValidationError(
521   - self.error_messages['password_mismatch'],
522   - code='password_mismatch',
523   - )
524   - return password2
525   -
526   - def save(self, commit=True):
527   - """
528   - Saves the new password.
529   - """
530   - self.user.set_password(self.cleaned_data["password1"])
531   - if commit:
532   - self.user.save()
533   - return self.user
534   -
535   - def _get_changed_data(self):
536   - data = super(AdminPasswordChangeForm, self).changed_data
537   - for name in self.fields.keys():
538   - if name not in data:
539   - return []
540   - return ['password']
  329 +class ColabPasswordChangeForm(ColabSetPasswordFormMixin, PasswordChangeForm):
  330 + pass
... ...
colab/accounts/tests/test_forms.py
... ... @@ -10,7 +10,7 @@ from django.test import TestCase, override_settings
10 10 from django.core.urlresolvers import reverse
11 11  
12 12 from colab.accounts.forms import UserCreationForm, UserChangeForm,\
13   - UserUpdateForm, UserForm, get_lists_choices, SetPasswordForm
  13 + UserUpdateForm, UserForm, get_lists_choices, ColabSetPasswordForm
14 14 from colab.accounts import forms as accounts_forms
15 15 from colab.accounts.models import User
16 16 from colab.accounts.utils import mailman
... ... @@ -37,21 +37,21 @@ class SetPasswordFormTestCase(TestCase):
37 37 'new_password2': '12345'}
38 38  
39 39 def test_no_custom_validators(self):
40   - form = SetPasswordForm(self.user, data=self.valid_form_data)
  40 + form = ColabSetPasswordForm(self.user, data=self.valid_form_data)
41 41 self.assertTrue(form.is_valid(), True)
42 42  
43 43 @override_settings(COLAB_APPS=TEST_COLAB_APPS)
44 44 @patch('colab.accounts.tests.utils.password_validator')
45 45 def test_custom_validator(self, validator):
46   - form = SetPasswordForm(self.user, data=self.valid_form_data)
  46 + form = ColabSetPasswordForm(self.user, data=self.valid_form_data)
47 47 self.assertTrue(form.is_valid())
48 48 validator.assert_called_with('12345')
49 49  
50 50 @override_settings(COLAB_APPS=TEST_COLAB_APPS)
51 51 def test_custom_validator_raise_error(self):
52   - form = SetPasswordForm(self.user, data=self.valid_form_data)
  52 + form = ColabSetPasswordForm(self.user, data=self.valid_form_data)
53 53 self.assertFalse(form.is_valid())
54   - self.assertEqual(form.errors['new_password1'][0], 'Test error')
  54 + self.assertEqual(form.errors['new_password2'][0], 'Test error')
55 55  
56 56  
57 57 class FormTest(TestCase):
... ...
colab/accounts/urls.py
... ... @@ -5,7 +5,7 @@ from django.contrib.auth import views as auth_views
5 5  
6 6 from .views import (UserProfileDetailView, UserProfileUpdateView,
7 7 ManageUserSubscriptionsView)
8   -from .forms import PasswordChangeForm
  8 +from .forms import ColabPasswordChangeForm, ColabSetPasswordForm
9 9  
10 10  
11 11 urlpatterns = patterns('',
... ... @@ -21,7 +21,8 @@ urlpatterns = patterns(&#39;&#39;,
21 21  
22 22 url(r'^password-reset-confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$',
23 23 auth_views.password_reset_confirm,
24   - {'template_name':'registration/password_reset_confirm_custom.html'},
  24 + {'template_name':'registration/password_reset_confirm_custom.html',
  25 + 'set_password_form': ColabSetPasswordForm},
25 26 name="password_reset_confirm"),
26 27  
27 28 url(r'^password-reset/?$', auth_views.password_reset,
... ... @@ -30,7 +31,7 @@ urlpatterns = patterns(&#39;&#39;,
30 31  
31 32 url(r'^change-password/?$', auth_views.password_change,
32 33 {'template_name': 'registration/password_change_form_custom.html',
33   - 'password_change_form': PasswordChangeForm},
  34 + 'password_change_form': ColabPasswordChangeForm},
34 35 name='password_change'),
35 36  
36 37 url(r'^change-password-done/?$',
... ...