Merge branch 'master' into feature/password_reset_service

Conflicts:
	modoboa/core/forms.py
This commit is contained in:
Antoine Nguyen
2017-06-22 16:54:02 +02:00
12 changed files with 150 additions and 30 deletions
+6
View File
@@ -43,6 +43,12 @@ Documentation
A detailed `documentation <https://modoboa.readthedocs.io/>`_ will help you
to install, use or extend Modoboa.
*****************
Demo Installation
*****************
If you want to try out Modoboa, check out our `demo installation <https://demo.modoboa.org/>`_.
*************
External code
*************
+1 -1
View File
@@ -23,7 +23,7 @@ The following example illustrates how to realize this (Debian like system):
> sudo useradd modoboa
> sudo -i modoboa
> virtualenv env
(env)> source env/bin/activate
> source env/bin/activate
(env)> pip install -U pip
Modoboa depends on external tools and some of them require compilation
+1 -1
View File
@@ -1 +1 @@
django-auth-ldap
django-auth-ldap>=1.2.13
+7 -6
View File
@@ -10,7 +10,7 @@ from django.core.urlresolvers import reverse
from django.http import QueryDict
from django.utils.translation import ugettext as _, ugettext_lazy
from passwords.fields import PasswordField
from django.contrib.auth import password_validation
from modoboa.core import signals as core_signals
from modoboa.core.models import User
@@ -45,11 +45,11 @@ class AccountFormGeneral(forms.ModelForm):
choices=[("", ugettext_lazy("Choose"))],
help_text=ugettext_lazy("What level of permission this user will have")
)
password1 = PasswordField(
password1 = forms.CharField(
label=ugettext_lazy("Password"), widget=forms.widgets.PasswordInput
)
password2 = PasswordField(
password2 = forms.CharField(
label=ugettext_lazy("Confirmation"),
widget=forms.widgets.PasswordInput,
help_text=ugettext_lazy(
@@ -157,6 +157,8 @@ class AccountFormGeneral(forms.ModelForm):
if password1 != password2:
raise forms.ValidationError(
_("The two password fields didn't match."))
if password2 != "":
password_validation.validate_password(password2, self.instance)
return password2
def clean(self):
@@ -181,9 +183,8 @@ class AccountFormGeneral(forms.ModelForm):
raise lib_exceptions.PermDeniedException(
_("You can't disable your own account"))
if commit:
if "password1" in self.cleaned_data \
and self.cleaned_data["password1"] != "":
account.set_password(self.cleaned_data["password1"])
if self.cleaned_data.get("password2", "") != "":
account.set_password(self.cleaned_data["password2"])
account.save()
account.role = self.cleaned_data["role"]
return account
+4 -5
View File
@@ -7,8 +7,9 @@ from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext as _, ugettext_lazy
from django.utils.encoding import force_text
from django.contrib.auth import password_validation
from rest_framework import serializers
from passwords import validators
from modoboa.admin import models as admin_models
from modoboa.core import constants as core_constants
@@ -159,8 +160,7 @@ class AccountPasswordSerializer(serializers.ModelSerializer):
def validate_new_password(self, value):
"""Check new password."""
try:
validators.validate_length(value)
validators.complexity(value)
password_validation.validate_password(value, self.instance)
except ValidationError as exc:
raise serializers.ValidationError(exc.messages[0])
return value
@@ -198,8 +198,7 @@ class WritableAccountSerializer(AccountSerializer):
def validate_password(self, value):
"""Check password constraints."""
try:
validators.validate_length(value)
validators.complexity(value)
password_validation.validate_password(value, self.instance)
except ValidationError as exc:
raise serializers.ValidationError(exc.messages[0])
return value
+8 -3
View File
@@ -6,7 +6,7 @@ from django import forms
from django.conf import settings
from django.utils.translation import ugettext as _, ugettext_lazy
from passwords import validators
from django.contrib.auth import password_validation
from modoboa.lib import fields as lib_fields
from modoboa.lib.cryptutils import random_key
@@ -83,8 +83,7 @@ class GeneralParametersForm(param_forms.AdminParametersForm):
label=ugettext_lazy("Default password"),
initial="password",
help_text=ugettext_lazy(
"Default password for automatically created accounts."),
validators=[validators.validate_length, validators.complexity]
"Default password for automatically created accounts.")
)
# LDAP specific settings
@@ -351,6 +350,12 @@ class GeneralParametersForm(param_forms.AdminParametersForm):
raise forms.ValidationError(_("Invalid rounds number"))
return value
def clean_default_password(self):
"""Check password complexity."""
value = self.cleaned_data["default_password"]
password_validation.validate_password(value)
return value
def clean(self):
"""Custom validation method
@@ -178,15 +178,31 @@ REST_FRAMEWORK = {
MODOBOA_API_URL = 'https://api.modoboa.org/1/'
# django-passwords
# Password validation rules
PASSWORD_MIN_LENGTH = 8
PASSWORD_COMPLEXITY = {
"UPPER": 1,
"LOWER": 1,
"DIGITS": 1
}
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
{
'NAME': 'modoboa.core.password_validation.ComplexityValidator',
'OPTIONS': {
'upper': 1,
'lower': 1,
'digits': 1,
'specials': 0
}
},
]
# CKeditor
+5 -4
View File
@@ -11,7 +11,7 @@ from django.utils.translation import ugettext as _, ugettext_lazy
from django.contrib.auth import forms as auth_forms
from django.contrib.auth import get_user_model
from passwords.fields import PasswordField
from django.contrib.auth import password_validation
from modoboa.core.models import User
from modoboa.parameters import tools as param_tools
@@ -41,11 +41,11 @@ class ProfileForm(forms.ModelForm):
label=ugettext_lazy("Old password"), required=False,
widget=forms.PasswordInput(attrs={"class": "form-control"})
)
newpassword = PasswordField(
newpassword = forms.CharField(
label=ugettext_lazy("New password"), required=False,
widget=forms.PasswordInput(attrs={"class": "form-control"})
)
confirmation = PasswordField(
confirmation = forms.CharField(
label=ugettext_lazy("Confirmation"), required=False,
widget=forms.PasswordInput(attrs={"class": "form-control"})
)
@@ -82,7 +82,8 @@ class ProfileForm(forms.ModelForm):
confirmation = self.cleaned_data["confirmation"]
if newpassword != confirmation:
raise forms.ValidationError(_("Passwords mismatch"))
return self.cleaned_data["confirmation"]
password_validation.validate_password(confirmation, self.instance)
return confirmation
def save(self, commit=True):
user = super(ProfileForm, self).save(commit=False)
+68
View File
@@ -0,0 +1,68 @@
"""Custom password validators."""
from __future__ import unicode_literals
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _, ungettext
class ComplexityValidator(object):
"""Check password contains at least a few things."""
def __init__(self, upper=1, lower=1, digits=1, specials=1):
self.upper = upper
self.lower = lower
self.digits = digits
self.specials = specials
def validate(self, password, user=None):
special_characters = "~!@#$%^&*()_+{}\":;,'[]"
condition = (
self.digits > 0 and
sum([1 for char in password if char.isdigit()]) < self.digits)
if condition:
raise ValidationError(
ungettext(
"Password must contain at least {} digit.",
"Password must contain at least {} digits.",
self.digits
).format(self.digits))
condition = (
self.lower > 0 and
sum([1 for char in password if char.islower()]) < self.lower)
if condition:
raise ValidationError(
ungettext(
"Password must contain at least {} lowercase letter.",
"Password must contain at least {} lowercase letters.",
self.lower
)
.format(self.lower))
condition = (
self.upper > 0 and
sum([1 for char in password if char.isupper()]) < self.upper)
if condition:
raise ValidationError(
ungettext(
"Password must contain at least {} uppercase letter.",
"Password must contain at least {} uppercase letters.",
self.upper
)
.format(self.upper))
condition = (
self.specials > 0 and
sum([1 for char in password if char in special_characters]) <
self.specials)
if condition:
raise ValidationError(
ungettext(
"Password must contain at least {} special character.",
"Password must contain at least {} special characters.",
self.specials
)
.format(self.specials))
def get_help_text(self):
return _(
"Your password must contain a combination of different "
"character types.")
+1 -1
View File
@@ -108,7 +108,7 @@ try:
def authenticate(self, username, password):
if self.global_params["authentication_type"] == "ldap":
return super(LDAPBackend, self).authenticate(
username, password)
username=username, password=password)
return None
except ImportError:
-1
View File
@@ -1,6 +1,5 @@
django>=1.10,<=1.10.99
django-ckeditor==5.2.1
django-passwords==0.3.12
django-reversion==1.10.2
django-subcommand2==0.1.1
django-versionfield2>=0.5.0
+25
View File
@@ -87,6 +87,31 @@ AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
)
# Password validation rules
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
{
'NAME': 'modoboa.core.password_validation.ComplexityValidator',
'OPTIONS': {
'upper': 1,
'lower': 1,
'digits': 1,
'specials': 0
}
},
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',