diff --git a/HISTORY.rst b/HISTORY.rst index de08bd2d91..f0fe05e434 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -85,6 +85,9 @@ to a document's OCR content for indexing and other purposes. - Add the 'content' attribute to documents to allow access to a document's parsed content for indexing and other purposes. +- Two new custom password validators added. One ensures + passwords have a minimum number of uppercase letters and the + other ensures passwords have a minimum amount of numbers. 3.1.9 (2018-11-01) ================== diff --git a/docs/chapters/password_validation.rst b/docs/chapters/password_validation.rst new file mode 100644 index 0000000000..c78815e250 --- /dev/null +++ b/docs/chapters/password_validation.rst @@ -0,0 +1,48 @@ +******************* +Password validation +******************* + +To help reduce the use of weak passwords, Mayan EDMS includes support for +password validators. Password validator enforce policies by rejecting +password that don't conform with the validator's logic. + +By default, Mayan EDMS sets this password validation setup: + +- That the password is not similar no any user attributes. +- A minimum password size of 8 characters. +- The password is not one of the 20,000 commonly used weak password. +- That the password is not entirely numeric. + +This default is coded in the following manner by the default Python setup file:: + + 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', + }, + ] + +If using the YAML configuration file the same setup would be coded in the +following manner:: + + 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 + +In addition to the password validators provided by Django +:django-docs:`validators provided by Django `, +Mayan EDMS adds the following validators: + +.. autoclass:: mayan.apps.authentication.validators.MinimumCapitalLettersContentValidator + +.. autoclass:: mayan.apps.authentication.validators.MinimumNumberContentValidator diff --git a/docs/topics/advanced.rst b/docs/topics/advanced.rst index 8c26bda021..23d463b986 100644 --- a/docs/topics/advanced.rst +++ b/docs/topics/advanced.rst @@ -8,3 +8,4 @@ Advanced topics .. include:: ../chapters/metadata.rst .. include:: ../chapters/transformations.rst .. include:: ../chapters/versioning.rst +.. include:: ../chapters/password_validation.rst diff --git a/mayan/apps/authentication/validators.py b/mayan/apps/authentication/validators.py new file mode 100644 index 0000000000..6960115600 --- /dev/null +++ b/mayan/apps/authentication/validators.py @@ -0,0 +1,56 @@ +from __future__ import unicode_literals + +from django.core.exceptions import ValidationError +from django.utils.translation import gettext as _ + + +class MinimumCapitalLettersContentValidator(object): + """ + Validate whether the password contains a minimum number of uppercase + letters. + """ + def __init__(self, minimum_capital_letters=1): + self.minimum_capital_letters = minimum_capital_letters + + def validate(self, password, user=None): + if sum([1 for letter in password if letter.isupper()]) < self.minimum_capital_letters: # NOQA + raise ValidationError( + _( + 'This password must contain at least ' + '%(minimum_capital_letters)d capital letters.' + ), code='password_not_enough_capital_letters', + params={ + 'minimum_capital_letters': self.minimum_capital_letters + }, + ) + + def get_help_text(self): + return _( + 'Your password must contain at least %(minimum_capital_letters)d ' + 'capital letters.' + % {'minimum_capital_letters': self.minimum_capital_letters} + ) + + +class MinimumNumberContentValidator(object): + """ + Validate whether the password contains a minimum number digits. + """ + def __init__(self, minimum_numbers=1): + self.minimum_numbers = minimum_numbers + + def validate(self, password, user=None): + if sum([1 for letter in password if letter.isdigit()]) < self.minimum_numbers: # NOQA + raise ValidationError( + _( + 'This password must contain at least %(minimum_numbers)d ' + 'numbers.' + ), code='password_not_enough_numbers', + params={'minimum_numbers': self.minimum_numbers}, + ) + + def get_help_text(self): + return _( + 'Your password must contain at least %(minimum_numbers)d numbers.' + % {'minimum_numbers': self.minimum_numbers} + )