Commit e9631d3cee2cf23690cab16716f3310ae9c0df25

Authored by Sergio Oliveira
1 parent e24dc879

Added plugin ability to validate colab password

colab/accounts/forms.py
1 1 # -*- coding: utf-8 -*-
2 2  
  3 +from collections import OrderedDict
  4 +from importlib import import_module
  5 +
3 6 from django import forms
4 7 from django.conf import settings
5 8 from django.contrib.auth import get_user_model
  9 +from django.contrib.auth.tokens import default_token_generator
6 10 from django.contrib.auth.forms import ReadOnlyPasswordHashField
7 11 from django.core.urlresolvers import reverse
8 12 from django.utils.functional import lazy
... ... @@ -282,3 +286,250 @@ class UserChangeForm(forms.ModelForm):
282 286 # This is done here, rather than on the field, because the
283 287 # field does not have access to the initial value
284 288 return self.initial["password"]
  289 +
  290 +
  291 +class AuthenticationForm(forms.Form):
  292 + """
  293 + Base class for authenticating users. Extend this to get a form that accepts
  294 + username/password logins.
  295 + """
  296 + username = forms.CharField(max_length=254)
  297 + password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
  298 +
  299 + error_messages = {
  300 + 'invalid_login': _("Please enter a correct %(username)s and password. "
  301 + "Note that both fields may be case-sensitive."),
  302 + 'inactive': _("This account is inactive."),
  303 + }
  304 +
  305 + def __init__(self, request=None, *args, **kwargs):
  306 + """
  307 + The 'request' parameter is set for custom auth use by subclasses.
  308 + The form data comes in via the standard 'data' kwarg.
  309 + """
  310 + self.request = request
  311 + self.user_cache = None
  312 + super(AuthenticationForm, self).__init__(*args, **kwargs)
  313 +
  314 + # Set the label for the "username" field.
  315 + UserModel = get_user_model()
  316 + self.username_field = UserModel._meta.get_field(
  317 + UserModel.USERNAME_FIELD)
  318 + if self.fields['username'].label is None:
  319 + self.fields['username'].label = capfirst(
  320 + self.username_field.verbose_name)
  321 +
  322 + def clean(self):
  323 + username = self.cleaned_data.get('username')
  324 + password = self.cleaned_data.get('password')
  325 +
  326 + if username and password:
  327 + self.user_cache = authenticate(username=username,
  328 + password=password)
  329 + if self.user_cache is None:
  330 + raise forms.ValidationError(
  331 + self.error_messages['invalid_login'],
  332 + code='invalid_login',
  333 + params={'username': self.username_field.verbose_name},
  334 + )
  335 + else:
  336 + self.confirm_login_allowed(self.user_cache)
  337 +
  338 + return self.cleaned_data
  339 +
  340 + def confirm_login_allowed(self, user):
  341 + """
  342 + Controls whether the given User may log in. This is a policy setting,
  343 + independent of end-user authentication. This default behavior is to
  344 + allow login by active users, and reject login by inactive users.
  345 + If the given user cannot log in, this method should raise a
  346 + ``forms.ValidationError``.
  347 + If the given user may log in, this method should return None.
  348 + """
  349 + if not user.is_active:
  350 + raise forms.ValidationError(
  351 + self.error_messages['inactive'],
  352 + code='inactive',
  353 + )
  354 +
  355 + def get_user_id(self):
  356 + if self.user_cache:
  357 + return self.user_cache.id
  358 + return None
  359 +
  360 + def get_user(self):
  361 + return self.user_cache
  362 +
  363 +
  364 +class PasswordResetForm(forms.Form):
  365 + email = forms.EmailField(label=_("Email"), max_length=254)
  366 +
  367 + def save(self, domain_override=None,
  368 + subject_template_name='registration/password_reset_subject.txt',
  369 + email_template_name='registration/password_reset_email.html',
  370 + use_https=False, token_generator=default_token_generator,
  371 + from_email=None, request=None, html_email_template_name=None):
  372 + """
  373 + Generates a one-use only link for resetting password and sends to the
  374 + user.
  375 + """
  376 + from django.core.mail import send_mail
  377 + UserModel = get_user_model()
  378 + email = self.cleaned_data["email"]
  379 + active_users = UserModel._default_manager.filter(
  380 + email__iexact=email, is_active=True)
  381 + for user in active_users:
  382 + # Make sure that no email is sent to a user that actually has
  383 + # a password marked as unusable
  384 + if not user.has_usable_password():
  385 + continue
  386 + if not domain_override:
  387 + current_site = get_current_site(request)
  388 + site_name = current_site.name
  389 + domain = current_site.domain
  390 + else:
  391 + site_name = domain = domain_override
  392 + c = {
  393 + 'email': user.email,
  394 + 'domain': domain,
  395 + 'site_name': site_name,
  396 + 'uid': urlsafe_base64_encode(force_bytes(user.pk)),
  397 + 'user': user,
  398 + 'token': token_generator.make_token(user),
  399 + 'protocol': 'https' if use_https else 'http',
  400 + }
  401 + subject = loader.render_to_string(subject_template_name, c)
  402 + # Email subject *must not* contain newlines
  403 + subject = ''.join(subject.splitlines())
  404 + email = loader.render_to_string(email_template_name, c)
  405 +
  406 + if html_email_template_name:
  407 + html_email = loader.render_to_string(html_email_template_name,
  408 + c)
  409 + else:
  410 + html_email = None
  411 + send_mail(subject, email, from_email, [user.email],
  412 + html_message=html_email)
  413 +
  414 +
  415 +class SetPasswordForm(forms.Form):
  416 + """
  417 + A form that lets a user change set their password without entering the old
  418 + password
  419 + """
  420 + error_messages = {
  421 + 'password_mismatch': _("The two password fields didn't match."),
  422 + }
  423 + new_password1 = forms.CharField(label=_("New password"),
  424 + widget=forms.PasswordInput)
  425 + new_password2 = forms.CharField(label=_("New password confirmation"),
  426 + widget=forms.PasswordInput)
  427 +
  428 + def __init__(self, user, *args, **kwargs):
  429 + self.user = user
  430 + super(SetPasswordForm, self).__init__(*args, **kwargs)
  431 +
  432 + def clean_new_password1(self):
  433 + password = self.cleaned_data.get('new_password1')
  434 +
  435 + for app in settings.COLAB_APPS.values():
  436 + if 'password_validators' in app:
  437 + for validator_path in app.get('password_validators'):
  438 + module_path, func_name = validator_path.rsplit('.', 1)
  439 + module = import_module(module_path)
  440 + validator_func = getattr(module, func_name, None)
  441 + if validator_func:
  442 + validator_func(password)
  443 +
  444 + return password
  445 +
  446 + def clean_new_password2(self):
  447 + password1 = self.cleaned_data.get('new_password1')
  448 + password2 = self.cleaned_data.get('new_password2')
  449 + if password1 and password2:
  450 + if password1 != password2:
  451 + raise forms.ValidationError(
  452 + self.error_messages['password_mismatch'],
  453 + code='password_mismatch',
  454 + )
  455 + return password2
  456 +
  457 + def save(self, commit=True):
  458 + self.user.set_password(self.cleaned_data['new_password1'])
  459 + if commit:
  460 + self.user.save()
  461 + return self.user
  462 +
  463 +
  464 +class PasswordChangeForm(SetPasswordForm):
  465 + """
  466 + A form that lets a user change their password by entering their old
  467 + password.
  468 + """
  469 + error_messages = dict(SetPasswordForm.error_messages, **{
  470 + 'password_incorrect': _("Your old password was entered incorrectly. "
  471 + "Please enter it again."),
  472 + })
  473 + old_password = forms.CharField(label=_("Old password"),
  474 + widget=forms.PasswordInput)
  475 +
  476 + def clean_old_password(self):
  477 + """
  478 + Validates that the old_password field is correct.
  479 + """
  480 + old_password = self.cleaned_data["old_password"]
  481 + if not self.user.check_password(old_password):
  482 + raise forms.ValidationError(
  483 + self.error_messages['password_incorrect'],
  484 + code='password_incorrect',
  485 + )
  486 + return old_password
  487 +
  488 +PasswordChangeForm.base_fields = OrderedDict(
  489 + (k, PasswordChangeForm.base_fields[k])
  490 + for k in ['old_password', 'new_password1', 'new_password2']
  491 +)
  492 +
  493 +
  494 +class AdminPasswordChangeForm(forms.Form):
  495 + """
  496 + A form used to change the password of a user in the admin interface.
  497 + """
  498 + error_messages = {
  499 + 'password_mismatch': _("The two password fields didn't match."),
  500 + }
  501 + password1 = forms.CharField(label=_("Password"),
  502 + widget=forms.PasswordInput)
  503 + password2 = forms.CharField(label=_("Password (again)"),
  504 + widget=forms.PasswordInput)
  505 +
  506 + def __init__(self, user, *args, **kwargs):
  507 + self.user = user
  508 + super(AdminPasswordChangeForm, self).__init__(*args, **kwargs)
  509 +
  510 + def clean_password2(self):
  511 + password1 = self.cleaned_data.get('password1')
  512 + password2 = self.cleaned_data.get('password2')
  513 + if password1 and password2:
  514 + if password1 != password2:
  515 + raise forms.ValidationError(
  516 + self.error_messages['password_mismatch'],
  517 + code='password_mismatch',
  518 + )
  519 + return password2
  520 +
  521 + def save(self, commit=True):
  522 + """
  523 + Saves the new password.
  524 + """
  525 + self.user.set_password(self.cleaned_data["password1"])
  526 + if commit:
  527 + self.user.save()
  528 + return self.user
  529 +
  530 + def _get_changed_data(self):
  531 + data = super(AdminPasswordChangeForm, self).changed_data
  532 + for name in self.fields.keys():
  533 + if name not in data:
  534 + return []
  535 + return ['password']
... ...
colab/accounts/models.py
... ... @@ -2,9 +2,9 @@
2 2  
3 3 import urlparse
4 4  
5   -from django.db import models
6 5 from django.contrib.auth.models import AbstractUser, UserManager
7 6 from django.core.urlresolvers import reverse
  7 +from django.db import models
8 8 from django.utils.crypto import get_random_string
9 9 from django.utils.translation import ugettext_lazy as _
10 10  
... ...
colab/accounts/urls.py
1 1  
2 2 from django.conf import settings
3 3 from django.conf.urls import patterns, url
  4 +from django.contrib.auth import views as auth_views
4 5  
5 6 from .views import (UserProfileDetailView, UserProfileUpdateView,
6 7 ManageUserSubscriptionsView)
7   -
8   -from colab.accounts import views
9   -from django.contrib.auth import views as auth_views
  8 +from .forms import PasswordChangeForm
10 9  
11 10  
12 11 urlpatterns = patterns('',
... ... @@ -30,7 +29,8 @@ urlpatterns = patterns('',
30 29 name="password_reset"),
31 30  
32 31 url(r'^change-password/?$', auth_views.password_change,
33   - {'template_name':'registration/password_change_form_custom.html'},
  32 + {'template_name': 'registration/password_change_form_custom.html',
  33 + 'password_change_form': PasswordChangeForm},
34 34 name='password_change'),
35 35  
36 36 url(r'^change-password-done/?$',
... ...
colab/utils/conf.py
... ... @@ -49,7 +49,7 @@ def _load_py_file(py_path, path):
49 49 sys.path.remove(path)
50 50  
51 51 py_setting = {var: getattr(py_settings, var) for var in dir(py_settings)
52   - if not var.startswith('__')}
  52 + if not var.startswith('_')}
53 53  
54 54 return py_setting
55 55  
... ... @@ -125,16 +125,7 @@ def load_colab_apps():
125 125  
126 126 app_label = app_name.split('.')[-1]
127 127 COLAB_APPS[app_label] = {}
128   - COLAB_APPS[app_label]['menu_title'] = py_settings_d.get('menu_title')
129   -
130   - fields = ['verbose_name', 'upstream', 'urls',
131   - 'menu_urls', 'middlewares', 'dependencies',
132   - 'context_processors', 'private_token', 'name', 'extra']
133   -
134   - for key in fields:
135   - value = py_settings_d.get(key)
136   - if value:
137   - COLAB_APPS[app_label][key] = value
  128 + COLAB_APPS[app_label] = py_settings_d
138 129  
139 130 return {'COLAB_APPS': COLAB_APPS}
140 131  
... ...