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 # -*- coding: utf-8 -*- 1 # -*- coding: utf-8 -*-
2 2
3 -from collections import OrderedDict  
4 from importlib import import_module 3 from importlib import import_module
5 4
6 from django import forms 5 from django import forms
7 from django.conf import settings 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 from django.core.urlresolvers import reverse 10 from django.core.urlresolvers import reverse
12 from django.utils.functional import lazy 11 from django.utils.functional import lazy
13 from django.utils.translation import ugettext_lazy as _ 12 from django.utils.translation import ugettext_lazy as _
14 from django.utils.safestring import mark_safe 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 from .signals import user_created 15 from .signals import user_created
22 from .utils.validators import validate_social_account 16 from .utils.validators import validate_social_account
@@ -169,7 +163,40 @@ class ListsForm(forms.Form): @@ -169,7 +163,40 @@ class ListsForm(forms.Form):
169 choices=lazy(get_lists_choices, list)()) 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 A form that creates a user, with no privileges, from the given username and 201 A form that creates a user, with no privileges, from the given username and
175 password. 202 password.
@@ -240,6 +267,8 @@ class UserCreationForm(UserForm): @@ -240,6 +267,8 @@ class UserCreationForm(UserForm):
240 self.error_messages['password_mismatch'], 267 self.error_messages['password_mismatch'],
241 code='password_mismatch', 268 code='password_mismatch',
242 ) 269 )
  270 +
  271 + super(UserCreationForm, self).clean_password2()
243 return password2 272 return password2
244 273
245 def save(self, commit=True): 274 def save(self, commit=True):
@@ -293,248 +322,9 @@ class UserChangeForm(forms.ModelForm): @@ -293,248 +322,9 @@ class UserChangeForm(forms.ModelForm):
293 return self.initial["password"] 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,7 +10,7 @@ from django.test import TestCase, override_settings
10 from django.core.urlresolvers import reverse 10 from django.core.urlresolvers import reverse
11 11
12 from colab.accounts.forms import UserCreationForm, UserChangeForm,\ 12 from colab.accounts.forms import UserCreationForm, UserChangeForm,\
13 - UserUpdateForm, UserForm, get_lists_choices, SetPasswordForm 13 + UserUpdateForm, UserForm, get_lists_choices, ColabSetPasswordForm
14 from colab.accounts import forms as accounts_forms 14 from colab.accounts import forms as accounts_forms
15 from colab.accounts.models import User 15 from colab.accounts.models import User
16 from colab.accounts.utils import mailman 16 from colab.accounts.utils import mailman
@@ -37,21 +37,21 @@ class SetPasswordFormTestCase(TestCase): @@ -37,21 +37,21 @@ class SetPasswordFormTestCase(TestCase):
37 'new_password2': '12345'} 37 'new_password2': '12345'}
38 38
39 def test_no_custom_validators(self): 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 self.assertTrue(form.is_valid(), True) 41 self.assertTrue(form.is_valid(), True)
42 42
43 @override_settings(COLAB_APPS=TEST_COLAB_APPS) 43 @override_settings(COLAB_APPS=TEST_COLAB_APPS)
44 @patch('colab.accounts.tests.utils.password_validator') 44 @patch('colab.accounts.tests.utils.password_validator')
45 def test_custom_validator(self, validator): 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 self.assertTrue(form.is_valid()) 47 self.assertTrue(form.is_valid())
48 validator.assert_called_with('12345') 48 validator.assert_called_with('12345')
49 49
50 @override_settings(COLAB_APPS=TEST_COLAB_APPS) 50 @override_settings(COLAB_APPS=TEST_COLAB_APPS)
51 def test_custom_validator_raise_error(self): 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 self.assertFalse(form.is_valid()) 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 class FormTest(TestCase): 57 class FormTest(TestCase):
colab/accounts/urls.py
@@ -5,7 +5,7 @@ from django.contrib.auth import views as auth_views @@ -5,7 +5,7 @@ from django.contrib.auth import views as auth_views
5 5
6 from .views import (UserProfileDetailView, UserProfileUpdateView, 6 from .views import (UserProfileDetailView, UserProfileUpdateView,
7 ManageUserSubscriptionsView) 7 ManageUserSubscriptionsView)
8 -from .forms import PasswordChangeForm 8 +from .forms import ColabPasswordChangeForm, ColabSetPasswordForm
9 9
10 10
11 urlpatterns = patterns('', 11 urlpatterns = patterns('',
@@ -21,7 +21,8 @@ urlpatterns = patterns(&#39;&#39;, @@ -21,7 +21,8 @@ urlpatterns = patterns(&#39;&#39;,
21 21
22 url(r'^password-reset-confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$', 22 url(r'^password-reset-confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$',
23 auth_views.password_reset_confirm, 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 name="password_reset_confirm"), 26 name="password_reset_confirm"),
26 27
27 url(r'^password-reset/?$', auth_views.password_reset, 28 url(r'^password-reset/?$', auth_views.password_reset,
@@ -30,7 +31,7 @@ urlpatterns = patterns(&#39;&#39;, @@ -30,7 +31,7 @@ urlpatterns = patterns(&#39;&#39;,
30 31
31 url(r'^change-password/?$', auth_views.password_change, 32 url(r'^change-password/?$', auth_views.password_change,
32 {'template_name': 'registration/password_change_form_custom.html', 33 {'template_name': 'registration/password_change_form_custom.html',
33 - 'password_change_form': PasswordChangeForm}, 34 + 'password_change_form': ColabPasswordChangeForm},
34 name='password_change'), 35 name='password_change'),
35 36
36 url(r'^change-password-done/?$', 37 url(r'^change-password-done/?$',