├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.rst ├── TEST ├── passwords ├── __init__.py ├── auth_password_validators.py ├── fields.py ├── locale │ ├── bg │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── de │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── en │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── es │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── es_AR │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── fr │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── ja │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── pl │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── pt_BR │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── pt_PT │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── ru │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── tr │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ └── zh_CN │ │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── models.py └── validators.py ├── requirements └── test.txt ├── setup.py ├── tests ├── __init__.py ├── test_fields.py └── test_validators.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | /.tox 2 | /.cache 3 | .venv 4 | 5 | *.egg-info 6 | __pycache__ 7 | *.pyc 8 | .coverage 9 | 10 | build 11 | dist 12 | *.sublime-project 13 | *.sublime-workspace -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - '2.7' 4 | - '3.6' 5 | env: 6 | - DJANGO="django>=1.11,<2.0" 7 | - DJANGO="django>=2.0,<3.0" 8 | install: 9 | - pip install -r requirements/test.txt 10 | - pip install coverage coveralls 11 | - pip install $DJANGO 12 | script: 13 | - py.test tests --cov passwords 14 | matrix: 15 | exclude: 16 | - python: '2.7' 17 | env: DJANGO="django>=2.0,<3.0" 18 | - python: '3.6' 19 | env: DJANGO="django>=1.11,<2.0" 20 | after_success: 21 | - coverage report 22 | - coveralls 23 | notifications: 24 | email: 25 | recipients: 26 | - maccesch@web.de 27 | deploy: 28 | provider: pypi 29 | user: maccesch 30 | password: 31 | secure: NoM2NEezIgizj/Q8JeezHKDDNzYsJCEBGIVJ3gAqSIhsq71hWBiA5coautBq214zIXR9P8WlX5ZzEvdEiZJc/u6jgL9Ggp2YOsi5fjGqbAdiGvlPd6lAfgnlbdNqJgjkTEckktiQEmiZYmZGz8tE57In7+BDIuxEg2QWX9eQ21E= 32 | on: 33 | tags: true 34 | repo: dstufft/django-passwords 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Donald Stufft 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | recursive-include passwords/locale * 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://travis-ci.org/dstufft/django-passwords.svg?branch=master 2 | :target: https://travis-ci.org/dstufft/django-passwords 3 | .. image:: https://img.shields.io/pypi/v/django-passwords.svg 4 | :target: https://pypi.python.org/pypi/django-passwords/ 5 | .. image:: https://img.shields.io/pypi/dm/django-passwords.svg 6 | :target: https://pypi.python.org/pypi/django-passwords/ 7 | .. image:: https://img.shields.io/pypi/l/django-passwords.svg 8 | :target: https://pypi.python.org/pypi/django-passwords/ 9 | 10 | 11 | Django Passwords 12 | ================ 13 | 14 | django-passwords is a reusable app that provides a form field and 15 | validators that check the strength of a password. 16 | 17 | Installation 18 | ------------ 19 | 20 | You can install django-passwords with pip by typing:: 21 | 22 | pip install django-passwords 23 | 24 | Or with easy_install by typing:: 25 | 26 | easy_install django-passwords 27 | 28 | Or manually by downloading a tarball and typing:: 29 | 30 | python setup.py install 31 | 32 | Compatibility 33 | ------------- 34 | 35 | django-passwords is compatible with Django 1.3 through 1.9 RC1. Pythons 2.7 36 | and 3.4 are both supported. 37 | 38 | Settings 39 | -------- 40 | 41 | django-passwords adds 6 optional settings 42 | 43 | Optional: 44 | Specifies minimum length for passwords: 45 | 46 | .. code-block:: python 47 | 48 | PASSWORD_MIN_LENGTH = 6 # Defaults to 6 49 | 50 | Specifies maximum length for passwords: 51 | 52 | .. code-block:: python 53 | 54 | PASSWORD_MAX_LENGTH = 120 # Defaults to None 55 | 56 | Specifies the location of a dictionary (file with one word per line): 57 | 58 | .. code-block:: python 59 | 60 | PASSWORD_DICTIONARY = "/usr/share/dict/words" # Defaults to None 61 | 62 | Specifies how close a fuzzy match has to be to be considered a match: 63 | 64 | .. code-block:: python 65 | 66 | PASSWORD_MATCH_THRESHOLD = 0.9 # Defaults to 0.9, should be 0.0 - 1.0 where 1.0 means exactly the same. 67 | 68 | Specifies a list of common sequences to attempt to match a password against: 69 | 70 | .. code-block:: python 71 | 72 | PASSWORD_COMMON_SEQUENCES = [] # Should be a list of strings, see passwords/validators.py for default 73 | 74 | Specifies number of characters within various sets that a password must contain: 75 | 76 | .. code-block:: python 77 | 78 | PASSWORD_COMPLEXITY = { # You can omit any or all of these for no limit for that particular set 79 | "UPPER": 1, # Uppercase 80 | "LOWER": 1, # Lowercase 81 | "LETTERS": 1, # Either uppercase or lowercase letters 82 | "DIGITS": 1, # Digits 83 | "SPECIAL": 1, # Not alphanumeric, space or punctuation character 84 | "WORDS": 1 # Words (alphanumeric sequences separated by a whitespace or punctuation character) 85 | } 86 | 87 | Usage 88 | ----- 89 | 90 | To use the formfield simply import it and use it: 91 | 92 | .. code-block:: python 93 | 94 | from django import forms 95 | from passwords.fields import PasswordField 96 | 97 | class ExampleForm(forms.Form): 98 | password = PasswordField(label="Password") 99 | 100 | You can make use of the validators on your own fields: 101 | 102 | .. code-block:: python 103 | 104 | from django import forms 105 | from passwords.validators import dictionary_words 106 | 107 | field = forms.CharField(validators=[dictionary_words]) 108 | 109 | You can also create custom validator instances to specify your own 110 | field-specific configurations, rather than using the global 111 | configurations: 112 | 113 | .. code-block:: python 114 | 115 | from django import forms 116 | from passwords.validators import ( 117 | DictionaryValidator, LengthValidator, ComplexityValidator) 118 | 119 | field = forms.CharField(validators=[ 120 | DictionaryValidator(words=['banned_word'], threshold=0.9), 121 | LengthValidator(min_length=8), 122 | ComplexityValidator(complexities=dict( 123 | UPPER=1, 124 | LOWER=1, 125 | DIGITS=1 126 | )), 127 | ]) 128 | 129 | 130 | Django's `password validation API`_ is slightly different than the form 131 | validation API and has wrappers in the `auth_password_validators` module: 132 | 133 | .. code-block:: python 134 | 135 | AUTH_PASSWORD_VALIDATORS = [ 136 | …, 137 | {"NAME": "passwords.auth_password_validators.ComplexityValidator"} 138 | ] 139 | 140 | 141 | .. _`password validation API`: https://docs.djangoproject.com/en/2.1/topics/auth/passwords/#password-validation 142 | -------------------------------------------------------------------------------- /TEST: -------------------------------------------------------------------------------- 1 | To run the test suite using your current environment's Python interpreter and 2 | packages, first make sure you have the testing dependencies installed: 3 | 4 | pip install -r requirements/test.txt 5 | 6 | Then run: 7 | 8 | py.test 9 | 10 | Optionally append `--cov passwords' for coverage output. 11 | 12 | To run py.test against different combinations of Django and Python versions, 13 | run: 14 | 15 | tox 16 | 17 | Instead. Modify tox.ini to alter those combinations. 18 | -------------------------------------------------------------------------------- /passwords/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = (0, 4, 0, "final", 0) 2 | 3 | 4 | def get_version(): 5 | if VERSION[3] == "final": 6 | return "%s.%s.%s" % (VERSION[0], VERSION[1], VERSION[2]) 7 | elif VERSION[3] == "dev": 8 | if VERSION[2] == 0: 9 | return "%s.%s.%s%s" % (VERSION[0], VERSION[1], VERSION[3], VERSION[4]) 10 | return "%s.%s.%s.%s%s" % (VERSION[0], VERSION[1], VERSION[2], VERSION[3], VERSION[4]) 11 | else: 12 | return "%s.%s.%s%s" % (VERSION[0], VERSION[1], VERSION[2], VERSION[3]) 13 | 14 | 15 | __version__ = get_version() 16 | -------------------------------------------------------------------------------- /passwords/auth_password_validators.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.utils.translation import ugettext_lazy as _ 3 | from . import validators 4 | 5 | 6 | class ComplexityValidator(object): 7 | """ 8 | Wrapper for validators.ComplexityValidator which is compatible 9 | with the Django 1.9+ password validation API 10 | """ 11 | 12 | def __init__(self): 13 | self.validator = validators.ComplexityValidator(settings.PASSWORD_COMPLEXITY) 14 | 15 | def get_help_text(self): 16 | return _("Your password fails to meet our complexity requirements.") 17 | 18 | def validate(self, value, user=None): 19 | return self.validator(value) 20 | -------------------------------------------------------------------------------- /passwords/fields.py: -------------------------------------------------------------------------------- 1 | from django.forms import CharField, PasswordInput 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | from passwords.validators import (validate_length, common_sequences, 5 | dictionary_words, complexity, 6 | PASSWORD_MIN_LENGTH, PASSWORD_MAX_LENGTH) 7 | 8 | 9 | class PasswordField(CharField): 10 | 11 | default_validators = [ 12 | validate_length, 13 | common_sequences, 14 | dictionary_words, 15 | complexity] 16 | 17 | def __init__(self, *args, **kwargs): 18 | if 'widget' not in kwargs: 19 | attrs = {} 20 | 21 | # 'minlength' is poorly supported, so use 'pattern' instead. 22 | # See http://stackoverflow.com/a/10294291/25507, 23 | # http://caniuse.com/#feat=input-minlength. 24 | if PASSWORD_MIN_LENGTH and PASSWORD_MAX_LENGTH: 25 | attrs['pattern'] = ('.{%i,%i}' % 26 | (PASSWORD_MIN_LENGTH, PASSWORD_MAX_LENGTH)) 27 | attrs['title'] = (_('%i to %i characters') % 28 | (PASSWORD_MIN_LENGTH, PASSWORD_MAX_LENGTH)) 29 | elif PASSWORD_MIN_LENGTH: 30 | attrs['pattern'] = '.{%i,}' % PASSWORD_MIN_LENGTH 31 | attrs['title'] = _('%i characters minimum') % PASSWORD_MIN_LENGTH 32 | 33 | if PASSWORD_MAX_LENGTH: 34 | attrs['maxlength'] = PASSWORD_MAX_LENGTH 35 | 36 | kwargs["widget"] = PasswordInput(render_value=False, attrs=attrs) 37 | 38 | super(PasswordField, self).__init__(*args, **kwargs) 39 | -------------------------------------------------------------------------------- /passwords/locale/bg/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstufft/django-passwords/d68953b83717a2f67f6f71581a17bb9c22cb65ff/passwords/locale/bg/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /passwords/locale/bg/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-passwords package. 2 | # 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: django-passwords\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2015-11-20 12:17+0200\n" 8 | "PO-Revision-Date: 2015-11-20 12:17+0200\n" 9 | "Last-Translator: Venelin Stoykov \n" 10 | "Language-Team: Bulgarian \n" 11 | "Language: bg\n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=UTF-8\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 16 | 17 | #: fields.py:27 18 | #, python-format 19 | msgid "%i to %i characters" 20 | msgstr "От %i до %i символа" 21 | 22 | #: fields.py:31 23 | #, python-format 24 | msgid "%i characters minimum" 25 | msgstr "Минимум %i символа" 26 | 27 | #: validators.py:47 28 | #, python-format 29 | msgid "Invalid Length (%s)" 30 | msgstr "Невалидна дължина (%s)" 31 | 32 | #: validators.py:57 33 | #, python-format 34 | msgid "Must be %s characters or more" 35 | msgstr "Трябва да бъде поне %s символа" 36 | 37 | #: validators.py:59 38 | #, python-format 39 | msgid "Must be %s characters or less" 40 | msgstr "Трябва да бъде не повече от %s символа" 41 | 42 | #: validators.py:66 43 | #, python-format 44 | msgid "Must be more complex (%s)" 45 | msgstr "Трябва да бъде по-сложна (%s)" 46 | 47 | #: validators.py:98 48 | #, python-format 49 | msgid "%(UPPER)s or more unique uppercase characters" 50 | msgstr "минимум %(UPPER)s главни букви" 51 | 52 | #: validators.py:102 53 | #, python-format 54 | msgid "%(LOWER)s or more unique lowercase characters" 55 | msgstr "минимум %(LOWER)s малки букви" 56 | 57 | #: validators.py:106 58 | #, python-format 59 | msgid "%(LETTERS)s or more unique letters" 60 | msgstr "минимум %(LETTERS)s уникални букви" 61 | 62 | #: validators.py:110 63 | #, python-format 64 | msgid "%(DIGITS)s or more unique digits" 65 | msgstr "минимум %(DIGITS)s уникални цифри" 66 | 67 | #: validators.py:114 68 | #, python-format 69 | msgid "%(PUNCTUATION)s or more unique punctuation characters: %%s" 70 | msgstr "минимум %(PUNCTUATION)s уникални пунктоационни знаци" 71 | 72 | #: validators.py:118 73 | #, python-format 74 | msgid "%(SPECIAL)s or more non unique special characters" 75 | msgstr "минимум %(SPECIAL)s уникални специални символи" 76 | 77 | #: validators.py:122 78 | #, python-format 79 | msgid "%(WORDS)s or more unique words" 80 | msgstr "минимум %(WORDS)s уникални думи" 81 | 82 | #: validators.py:126 83 | msgid "must contain " 84 | msgstr "трябва да съдържа " 85 | 86 | #: validators.py:132 87 | #, python-format 88 | msgid "Too Similar to [%(haystacks)s]" 89 | msgstr "Много прилича на [%(haystacks)s]" 90 | 91 | #: validators.py:176 92 | msgid "Based on a dictionary word" 93 | msgstr "Често срещана парола" 94 | 95 | #: validators.py:195 96 | msgid "Based on a common sequence of characters" 97 | msgstr "Често срещана поредност на символи" 98 | -------------------------------------------------------------------------------- /passwords/locale/de/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstufft/django-passwords/d68953b83717a2f67f6f71581a17bb9c22cb65ff/passwords/locale/de/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /passwords/locale/de/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: django-passwords\n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "POT-Creation-Date: 2015-08-27 12:30+0200\n" 6 | "PO-Revision-Date: 2015-08-27 14:09+0200\n" 7 | "Language: de\n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=UTF-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 12 | "Last-Translator: Christian Schürmann \n" 13 | "Language-Team: \n" 14 | "X-Generator: Poedit 1.8.4\n" 15 | 16 | #: fields.py:27 17 | #, python-format 18 | msgid "%i to %i characters" 19 | msgstr "Zwischen %i und %i Zeichen" 20 | 21 | #: fields.py:31 22 | #, python-format 23 | msgid "%i characters minimum" 24 | msgstr "Mindestens %i Zeichen" 25 | 26 | #: validators.py:47 27 | #, python-format 28 | msgid "Invalid Length (%s)" 29 | msgstr "Ungültige Länge (%s)" 30 | 31 | #: validators.py:57 32 | #, python-format 33 | msgid "Must be %s characters or more" 34 | msgstr "Es müssen %s oder mehr Zeichen enthalten sein" 35 | 36 | #: validators.py:59 37 | #, python-format 38 | msgid "Must be %s characters or less" 39 | msgstr "Es müssen %s oder weniger Zeichen enthalten sein" 40 | 41 | #: validators.py:66 42 | #, python-format 43 | msgid "Must be more complex (%s)" 44 | msgstr "Höhere Komplexität erforderlich (%s)" 45 | 46 | #: validators.py:98 47 | #, python-format 48 | msgid "%(UPPER)s or more unique uppercase characters" 49 | msgstr "Es müssen %(UPPER)s oder mehr verschiedene Großbuchstaben vorhanden sein" 50 | 51 | #: validators.py:102 52 | #, python-format 53 | msgid "%(LOWER)s or more unique lowercase characters" 54 | msgstr "Es müssen %(LOWER)s oder mehr verschiedene Kleinbuchstaben vorhanden sein" 55 | 56 | #: validators.py:106 57 | #, python-format 58 | msgid "%(LETTERS)s or more unique letters" 59 | msgstr "Es müssen %(LETTERS)s oder mehr verschiedene Buchstaben vorhanden sein" 60 | 61 | #: validators.py:110 62 | #, python-format 63 | msgid "%(DIGITS)s or more unique digits" 64 | msgstr "Es müssen %(DIGITS)s oder mehr verschiedene Zahlen vorhanden sein" 65 | 66 | #: validators.py:114 67 | #, python-format 68 | msgid "%(PUNCTUATION)s or more unique punctuation characters: %%s" 69 | msgstr "Es müssen %(PUNCTUATION)s oder mehr verschiedene Satzzeichen enthalten sein: %%s" 70 | 71 | #: validators.py:118 72 | #, python-format 73 | msgid "%(SPECIAL)s or more non unique special characters" 74 | msgstr "Es müssen %(SPECIAL)s oder mehr Sonderzeichen enthalten sein" 75 | 76 | #: validators.py:122 77 | #, python-format 78 | msgid "%(WORDS)s or more unique words" 79 | msgstr "Es müssen %(WORDS)s oder mehr verschiedene Wörter enthalten sein" 80 | 81 | #: validators.py:119 82 | msgid "must contain " 83 | msgstr "erforderlich: " 84 | 85 | #: validators.py:132 86 | #, python-format 87 | msgid "Too Similar to [%(haystacks)s]" 88 | msgstr "Zu ähnlich zu [%(haystacks)s]" 89 | 90 | #: validators.py:176 91 | msgid "Based on a dictionary word" 92 | msgstr "Basiert auf einem Wort im Wörterbuch" 93 | 94 | #: validators.py:195 95 | msgid "Based on a common sequence of characters" 96 | msgstr "Basiert auf einer verbreiteten Sequenz von Buchstaben" 97 | -------------------------------------------------------------------------------- /passwords/locale/en/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstufft/django-passwords/d68953b83717a2f67f6f71581a17bb9c22cb65ff/passwords/locale/en/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /passwords/locale/en/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-passwords package. 2 | # 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: django-passwords\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2012-12-17 21:21+0400\n" 8 | "PO-Revision-Date: 2012-12-17 21:21+0400\n" 9 | "Last-Translator: FULL NAME \n" 10 | "Language-Team: English \n" 11 | "Language: en\n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=UTF-8\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | 16 | #: validators.py:31 17 | #, python-format 18 | msgid "Invalid Length (%s)" 19 | msgstr "" 20 | 21 | #: validators.py:41 22 | #, python-format 23 | msgid "Must be %s characters or more" 24 | msgstr "" 25 | 26 | #: validators.py:45 27 | #, python-format 28 | msgid "Must be %s characters or less" 29 | msgstr "" 30 | 31 | #: validators.py:49 32 | #, python-format 33 | msgid "Must be more complex (%s)" 34 | msgstr "" 35 | 36 | #: validators.py:77 37 | #, python-format 38 | msgid "Must contain %(UPPER)s or more unique uppercase characters" 39 | msgstr "" 40 | 41 | #: validators.py:81 42 | #, python-format 43 | msgid "Must contain %(LOWER)s or more unique lowercase characters" 44 | msgstr "" 45 | 46 | #: validators.py:85 47 | #, python-format 48 | msgid "Must contain %(DIGITS)s or more unique digits" 49 | msgstr "" 50 | 51 | #: validators.py:89 52 | #, python-format 53 | msgid "Must contain %(PUNCTUATION)s or more unique punctuation characters" 54 | msgstr "" 55 | 56 | #: validators.py:93 57 | #, python-format 58 | msgid "Must contain %(SPECIAL)s or more unique special characters" 59 | msgstr "" 60 | 61 | #: validators.py:97 62 | #, python-format 63 | msgid "Must contain %(WORDS)s or more unique words" 64 | msgstr "" 65 | 66 | #: validators.py:102 67 | #, python-format 68 | msgid "Too Similar to [%(haystacks)s]" 69 | msgstr "" 70 | 71 | #: validators.py:138 72 | msgid "Based on a dictionary word" 73 | msgstr "" 74 | 75 | #: validators.py:154 76 | msgid "Based on a common sequence of characters" 77 | msgstr "" 78 | -------------------------------------------------------------------------------- /passwords/locale/es/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstufft/django-passwords/d68953b83717a2f67f6f71581a17bb9c22cb65ff/passwords/locale/es/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /passwords/locale/es/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: django-passwords\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2016-03-21 13:51+0200\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 20 | 21 | #: fields.py:27 22 | #, python-format 23 | msgid "%i to %i characters" 24 | msgstr "%i a %i caracteres" 25 | 26 | #: fields.py:31 27 | #, python-format 28 | msgid "%i characters minimum" 29 | msgstr "%i caracteres como mínimo" 30 | 31 | #: validators.py:47 32 | #, python-format 33 | msgid "Invalid Length (%s)" 34 | msgstr "Longitud inválida (%s)" 35 | 36 | #: validators.py:57 37 | #, python-format 38 | msgid "Must be %s characters or more" 39 | msgstr "Debe tener por lo menos %s caracteres" 40 | 41 | #: validators.py:59 42 | #, python-format 43 | msgid "Must be %s characters or less" 44 | msgstr "Debe tener %s caracteres o menos" 45 | 46 | #: validators.py:66 47 | #, python-format 48 | msgid "Must be more complex (%s)" 49 | msgstr "Debe ser mas complejo (%s)" 50 | 51 | #: validators.py:98 52 | #, fuzzy, python-format 53 | #| msgid "Must contain %(UPPER)s or more uppercase characters" 54 | msgid "%(UPPER)s or more unique uppercase characters" 55 | msgstr "Debe tener %(UPPER)s o más mayúsculas" 56 | 57 | #: validators.py:102 58 | #, fuzzy, python-format 59 | #| msgid "Must contain %(LOWER)s or more lowercase characters" 60 | msgid "%(LOWER)s or more unique lowercase characters" 61 | msgstr "Debe tener %(LOWER)s o más minúsculas" 62 | 63 | #: validators.py:106 64 | #, fuzzy, python-format 65 | #| msgid "Must contain %(WORDS)s or more unique words" 66 | msgid "%(LETTERS)s or more unique letters" 67 | msgstr "Debe tener %(WORDS)s o más palabras únicas" 68 | 69 | #: validators.py:110 70 | #, fuzzy, python-format 71 | #| msgid "Must contain %(DIGITS)s or more digits" 72 | msgid "%(DIGITS)s or more unique digits" 73 | msgstr "Debe tener %(DIGITS)s o más números" 74 | 75 | #: validators.py:114 76 | #, fuzzy, python-format 77 | #| msgid "Must contain %(PUNCTUATION)s or more punctuation character" 78 | msgid "%(PUNCTUATION)s or more unique punctuation characters: %%s" 79 | msgstr "Debe tener %(PUNCTUATION)s o más signos de puntuación" 80 | 81 | #: validators.py:118 82 | #, fuzzy, python-format 83 | #| msgid "Must contain %(NON ASCII)s or more non ascii characters" 84 | msgid "%(SPECIAL)s or more non unique special characters" 85 | msgstr "Debe tener %(NON ASCII)s o más caracteres no ascii" 86 | 87 | #: validators.py:119 88 | msgid "must contain " 89 | msgstr "debe tener " 90 | 91 | #: validators.py:122 92 | #, fuzzy, python-format 93 | #| msgid "Must contain %(WORDS)s or more unique words" 94 | msgid "must contain %(WORDS)s or more unique words" 95 | msgstr "Debe tener %(WORDS)s o más palabras únicas" 96 | 97 | #: validators.py:132 98 | #, python-format 99 | msgid "Too Similar to [%(haystacks)s]" 100 | msgstr "Demasiado similar a [%(haystacks)s]" 101 | 102 | #: validators.py:176 103 | #, fuzzy 104 | #| msgid "Based on a dictionary word." 105 | msgid "Based on a dictionary word" 106 | msgstr "Basada en un diccionario de palabras" 107 | 108 | #: validators.py:195 109 | msgid "Based on a common sequence of characters" 110 | msgstr "Basado en una secuencia común de caracteres" 111 | -------------------------------------------------------------------------------- /passwords/locale/es_AR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstufft/django-passwords/d68953b83717a2f67f6f71581a17bb9c22cb65ff/passwords/locale/es_AR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /passwords/locale/es_AR/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: django-passwords\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2015-09-21 01:09-0300\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 20 | 21 | #: fields.py:27 22 | #, python-format 23 | msgid "%i to %i characters" 24 | msgstr "%i a %i caracteres" 25 | 26 | #: fields.py:31 27 | #, python-format 28 | msgid "%i characters minimum" 29 | msgstr "%i caracteres como mínimo" 30 | 31 | #: validators.py:47 32 | #, python-format 33 | msgid "Invalid Length (%s)" 34 | msgstr "Longitud inválida (%s)" 35 | 36 | #: validators.py:57 37 | #, python-format 38 | msgid "Must be %s characters or more" 39 | msgstr "Deben ser por lo menos %s caracteres" 40 | 41 | #: validators.py:59 42 | #, python-format 43 | msgid "Must be %s characters or less" 44 | msgstr "Deben ser %s caracteres o menos" 45 | 46 | #: validators.py:66 47 | #, python-format 48 | msgid "Must be more complex (%s)" 49 | msgstr "Debe ser mas compleja (%s)" 50 | 51 | #: validators.py:98 52 | #, fuzzy, python-format 53 | #| msgid "Must contain %(UPPER)s or more uppercase characters" 54 | msgid "%(UPPER)s or more unique uppercase characters" 55 | msgstr "Debe contener %(UPPER)s o más caracteres en mayúscula" 56 | 57 | #: validators.py:102 58 | #, fuzzy, python-format 59 | #| msgid "Must contain %(LOWER)s or more lowercase characters" 60 | msgid "%(LOWER)s or more unique lowercase characters" 61 | msgstr "Debe contener %(LOWER)s o más caracteres en minúscula" 62 | 63 | #: validators.py:106 64 | #, fuzzy, python-format 65 | #| msgid "Must contain %(WORDS)s or more unique words" 66 | msgid "%(LETTERS)s or more unique letters" 67 | msgstr "Debe contener %(WORDS)s o más palabras únicas" 68 | 69 | #: validators.py:110 70 | #, fuzzy, python-format 71 | #| msgid "Must contain %(DIGITS)s or more digits" 72 | msgid "%(DIGITS)s or more unique digits" 73 | msgstr "Debe contener %(DIGITS)s o más dígitos" 74 | 75 | #: validators.py:114 76 | #, fuzzy, python-format 77 | #| msgid "Must contain %(PUNCTUATION)s or more punctuation character" 78 | msgid "%(PUNCTUATION)s or more unique punctuation characters: %%s" 79 | msgstr "Debe contener %(PUNCTUATION)s o más signos de puntuación" 80 | 81 | #: validators.py:118 82 | #, fuzzy, python-format 83 | #| msgid "Must contain %(NON ASCII)s or more non ascii characters" 84 | msgid "%(SPECIAL)s or more non unique special characters" 85 | msgstr "Debe contener %(NON ASCII)s o más caracteres no ascii" 86 | 87 | #: validators.py:119 88 | msgid "must contain " 89 | msgstr " " 90 | 91 | #: validators.py:122 92 | #, fuzzy, python-format 93 | #| msgid "Must contain %(WORDS)s or more unique words" 94 | msgid "must contain %(WORDS)s or more unique words" 95 | msgstr "Debe contener %(WORDS)s o más palabras únicas" 96 | 97 | #: validators.py:132 98 | #, python-format 99 | msgid "Too Similar to [%(haystacks)s]" 100 | msgstr "Demasiado similar a [%(haystacks)s]" 101 | 102 | #: validators.py:176 103 | #, fuzzy 104 | #| msgid "Based on a dictionary word." 105 | msgid "Based on a dictionary word" 106 | msgstr "Basada en un diccionario de palabras" 107 | 108 | #: validators.py:195 109 | msgid "Based on a common sequence of characters" 110 | msgstr "Basado en una secuencia común de caracteres" 111 | -------------------------------------------------------------------------------- /passwords/locale/fr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstufft/django-passwords/d68953b83717a2f67f6f71581a17bb9c22cb65ff/passwords/locale/fr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /passwords/locale/fr/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-passwords package. 2 | # 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: PACKAGE VERSION\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2015-09-03 23:44+0200\n" 8 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 9 | "Last-Translator: Antoine Nguyen \n" 10 | "Language-Team: French \n" 11 | "Language: \n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=UTF-8\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 16 | 17 | #: fields.py:27 18 | #, python-format 19 | msgid "%i to %i characters" 20 | msgstr "%i à %i caractères" 21 | 22 | #: fields.py:31 23 | #, python-format 24 | msgid "%i characters minimum" 25 | msgstr "%i caractères minimum" 26 | 27 | #: validators.py:47 28 | #, python-format 29 | msgid "Invalid Length (%s)" 30 | msgstr "Taille invalide (%s)" 31 | 32 | #: validators.py:57 33 | #, python-format 34 | msgid "Must be %s characters or more" 35 | msgstr "Doit faire %s caractères ou plus" 36 | 37 | #: validators.py:59 38 | #, python-format 39 | msgid "Must be %s characters or less" 40 | msgstr "Doit faire %s caractères ou moins" 41 | 42 | #: validators.py:66 43 | #, python-format 44 | msgid "Must be more complex (%s)" 45 | msgstr "Doit être plus complexe (%s)" 46 | 47 | #: validators.py:98 48 | #, python-format 49 | msgid "%(UPPER)s or more unique uppercase characters" 50 | msgstr "doit contenir %(UPPER)s caractères en majuscule uniques ou plus" 51 | 52 | #: validators.py:102 53 | #, python-format 54 | msgid "%(LOWER)s or more unique lowercase characters" 55 | msgstr "doit contenir %(LOWER)s caractères en minuscule uniques ou plus" 56 | 57 | #: validators.py:106 58 | #, python-format 59 | msgid "%(LETTERS)s or more unique letters" 60 | msgstr "doit contenir %(LETTERS)s lettres uniques ou plus" 61 | 62 | #: validators.py:110 63 | #, python-format 64 | msgid "%(DIGITS)s or more unique digits" 65 | msgstr "doit contenir %(DIGITS)s digits uniques ou plus" 66 | 67 | #: validators.py:114 68 | #, python-format 69 | msgid "%(PUNCTUATION)s or more unique punctuation characters: %%s" 70 | msgstr "" 71 | "doit contenir %(PUNCTUATION)s caractères de ponctuation uniques ou plus : %%s" 72 | 73 | #: validators.py:118 74 | #, python-format 75 | msgid "%(SPECIAL)s or more non unique special characters" 76 | msgstr "doit contenir %(SPECIAL)s caractères spéciaux non uniques ou plus" 77 | 78 | #: validators.py:122 79 | #, python-format 80 | msgid "%(WORDS)s or more unique words" 81 | msgstr "doit contenir %(WORDS)s mots uniques ou plus" 82 | 83 | #: validators.py:119 84 | msgid "must contain " 85 | msgstr " " 86 | 87 | #: validators.py:132 88 | #, python-format 89 | msgid "Too Similar to [%(haystacks)s]" 90 | msgstr "Trop similaire à [%(haystacks)s]" 91 | 92 | #: validators.py:176 93 | msgid "Based on a dictionary word" 94 | msgstr "Basé sur un mot du dictionnaire" 95 | 96 | #: validators.py:195 97 | msgid "Based on a common sequence of characters" 98 | msgstr "Basé sur une séquence commune de caractères" 99 | -------------------------------------------------------------------------------- /passwords/locale/ja/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstufft/django-passwords/d68953b83717a2f67f6f71581a17bb9c22cb65ff/passwords/locale/ja/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /passwords/locale/ja/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: 1.0\n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "POT-Creation-Date: 2015-08-12 22:52+0900\n" 6 | "PO-Revision-Date: 2015-08-12 23:36+0900\n" 7 | "Last-Translator: Shugo Nakamura \n" 8 | "Language-Team: hoerin \n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "Plural-Forms: nplurals=1; plural=0;\n" 13 | "X-Generator: Poedit 1.5.7\n" 14 | "Language: Japanese\n" 15 | 16 | #: validators.py:47 17 | #, python-format 18 | msgid "Invalid Length (%s)" 19 | msgstr "無効な長さです (%s)" 20 | 21 | #: validators.py:57 22 | #, python-format 23 | msgid "Must be %s characters or more" 24 | msgstr "%s文字以上でご入力ください" 25 | 26 | #: validators.py:59 27 | #, python-format 28 | msgid "Must be %s characters or less" 29 | msgstr "%s文字以下でご入力ください" 30 | 31 | #: validators.py:66 32 | #, python-format 33 | msgid "Must be more complex (%s)" 34 | msgstr "複雑なパスワードをご入力ください (%s)" 35 | 36 | #: validators.py:98 37 | #, python-format 38 | msgid "%(UPPER)s or more unique uppercase characters" 39 | msgstr "%(UPPER)s文字以上の大文字をご入力ください" 40 | 41 | #: validators.py:102 42 | #, python-format 43 | msgid "%(LOWER)s or more unique lowercase characters" 44 | msgstr "%(LOWER)s文字以上の小文字をご入力ください" 45 | 46 | #: validators.py:106 47 | #, python-format 48 | msgid "%(LETTERS)s or more unique letters" 49 | msgstr "%(LETTERS)s文字以上の大文字または小文字をご入力ください" 50 | 51 | #: validators.py:110 52 | #, python-format 53 | msgid "%(DIGITS)s or more unique digits" 54 | msgstr "%(DIGITS)s文字以上の数字をご入力ください" 55 | 56 | #: validators.py:114 57 | #, python-format 58 | msgid "%(PUNCTUATION)s or more unique punctuation characters: %%s" 59 | msgstr "%(PUNCTUATION)s文字以上の記号をご入力ください" 60 | 61 | #: validators.py:118 62 | #, python-format 63 | msgid "%(SPECIAL)s or more non unique special characters" 64 | msgstr "%(SPECIAL)s文字以上のスペースまたは記号をご入力ください" 65 | 66 | #: validators.py:122 67 | #, python-format 68 | msgid "%(WORDS)s or more unique words" 69 | msgstr "%(WORDS)s語以上の単語をご入力ください" 70 | 71 | #: validators.py:119 72 | msgid "must contain " 73 | msgstr " " 74 | 75 | #: validators.py:132 76 | #, python-format 77 | msgid "Too Similar to [%(haystacks)s]" 78 | msgstr "[%(haystacks)s]と似すぎています" 79 | 80 | #: validators.py:176 81 | msgid "Based on a dictionary word" 82 | msgstr "辞書語が含まれています" 83 | 84 | #: validators.py:195 85 | msgid "Based on a common sequence of characters" 86 | msgstr "推測しやすい文字の並びが含まれています" 87 | -------------------------------------------------------------------------------- /passwords/locale/pl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstufft/django-passwords/d68953b83717a2f67f6f71581a17bb9c22cb65ff/passwords/locale/pl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /passwords/locale/pl/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: django-passwords\n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "POT-Creation-Date: 2015-08-27 12:30+0200\n" 6 | "PO-Revision-Date: 2016-04-20 13:34+0200\n" 7 | "MIME-Version: 1.0\n" 8 | "Content-Type: text/plain; charset=UTF-8\n" 9 | "Content-Transfer-Encoding: 8bit\n" 10 | "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" 11 | "Language-Team: \n" 12 | "X-Generator: Poedit 1.8.7\n" 13 | "Last-Translator: Roman Bryś \n" 14 | "Language: pl\n" 15 | 16 | #: fields.py:27 17 | #, python-format 18 | msgid "%i to %i characters" 19 | msgstr "Liczba znaków od %i do %i" 20 | 21 | #: fields.py:31 22 | #, python-format 23 | msgid "%i characters minimum" 24 | msgstr "Minimalna długość (liczba znaków): %i" 25 | 26 | #: validators.py:47 27 | #, python-format 28 | msgid "Invalid Length (%s)" 29 | msgstr "Nieprawidłowa długość (%s)" 30 | 31 | #: validators.py:57 32 | #, python-format 33 | msgid "Must be %s characters or more" 34 | msgstr "Musi zawierać %s znaków lub więcej" 35 | 36 | #: validators.py:59 37 | #, python-format 38 | msgid "Must be %s characters or less" 39 | msgstr "Musi zawierać %s znaków lub mniej" 40 | 41 | #: validators.py:66 42 | #, python-format 43 | msgid "Must be more complex (%s)" 44 | msgstr "Musi być bardziej złożone (%s)" 45 | 46 | #: validators.py:98 47 | #, python-format 48 | msgid "%(UPPER)s or more unique uppercase characters" 49 | msgstr "%(UPPER)s lub więcej unikalnych dużych liter" 50 | 51 | #: validators.py:102 52 | #, python-format 53 | msgid "%(LOWER)s or more unique lowercase characters" 54 | msgstr "%(LOWER)s lub więcej unikalnych małych liter" 55 | 56 | #: validators.py:106 57 | #, python-format 58 | msgid "%(LETTERS)s or more unique letters" 59 | msgstr "%(LETTERS)s lub więcej unikalnych liter" 60 | 61 | #: validators.py:110 62 | #, python-format 63 | msgid "%(DIGITS)s or more unique digits" 64 | msgstr "%(DIGITS)s lub więcej unikalnych cyfr" 65 | 66 | #: validators.py:114 67 | #, python-format 68 | msgid "%(PUNCTUATION)s or more unique punctuation characters: %%s" 69 | msgstr "%(PUNCTUATION)s lub więcej unikalnych znaków interpunkcyjnych: %%s" 70 | 71 | #: validators.py:118 72 | #, python-format 73 | msgid "%(SPECIAL)s or more non unique special characters" 74 | msgstr "%(SPECIAL)s lub więcej znaków specjalnych" 75 | 76 | #: validators.py:122 77 | #, python-format 78 | msgid "%(WORDS)s or more unique words" 79 | msgstr "%(WORDS)s lub więcej unikalnych słów" 80 | 81 | #: validators.py:119 82 | msgid "must contain " 83 | msgstr "musi zawierać " 84 | 85 | #: validators.py:132 86 | #, python-format 87 | msgid "Too Similar to [%(haystacks)s]" 88 | msgstr "Zbyt podobne do [%(haystacks)s]" 89 | 90 | #: validators.py:176 91 | msgid "Based on a dictionary word" 92 | msgstr "Bazuje na wyrazie słownikowym." 93 | 94 | #: validators.py:195 95 | msgid "Based on a common sequence of characters" 96 | msgstr "Bazuje na powszechnej sekwencji znaków" 97 | -------------------------------------------------------------------------------- /passwords/locale/pt_BR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstufft/django-passwords/d68953b83717a2f67f6f71581a17bb9c22cb65ff/passwords/locale/pt_BR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /passwords/locale/pt_BR/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2016-09-22 16:35-0300\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 20 | 21 | #: passwords/fields.py:27 22 | #, python-format 23 | msgid "%i to %i characters" 24 | msgstr "%i a %i caracteres" 25 | 26 | #: passwords/fields.py:31 27 | #, python-format 28 | msgid "%i characters minimum" 29 | msgstr "mínimo de %i caracteres" 30 | 31 | #: passwords/validators.py:46 32 | #, python-format 33 | msgid "Invalid Length (%s)" 34 | msgstr "Tamanho inválido (%s)" 35 | 36 | #: passwords/validators.py:56 37 | #, python-format 38 | msgid "Must be %s characters or more" 39 | msgstr "Deve possuir %s ou mais caracteres" 40 | 41 | #: passwords/validators.py:58 42 | #, python-format 43 | msgid "Must be %s characters or less" 44 | msgstr "Deve possuir %s ou menos caracteres" 45 | 46 | #: passwords/validators.py:65 47 | #, python-format 48 | msgid "Must be more complex (%s)" 49 | msgstr "Deve ser mais complexa (%s)" 50 | 51 | #: passwords/validators.py:95 52 | #, python-format 53 | msgid "%(UPPER)s or more unique uppercase characters" 54 | msgstr "%(UPPER)s ou mais caracteres maiúsculos não repetidos" 55 | 56 | #: passwords/validators.py:99 57 | #, python-format 58 | msgid "%(LOWER)s or more unique lowercase characters" 59 | msgstr "%(LOWER)s ou mais caracteres minúsculos não repetidos" 60 | 61 | #: passwords/validators.py:103 62 | #, python-format 63 | msgid "%(LETTERS)s or more unique letters" 64 | msgstr "%(LETTERS)s or mais letras não repetidas" 65 | 66 | #: passwords/validators.py:107 67 | #, python-format 68 | msgid "%(DIGITS)s or more unique digits" 69 | msgstr "%(DIGITS)s ou mais dígitos não repetidos" 70 | 71 | #: passwords/validators.py:111 72 | #, python-format 73 | msgid "%(SPECIAL)s or more non unique special characters" 74 | msgstr "%(SPECIAL)s ou mais caracteres especiais" 75 | 76 | #: passwords/validators.py:115 77 | #, python-format 78 | msgid "%(WORDS)s or more unique words" 79 | msgstr "%(WORDS)s ou mais palavras não repetidas" 80 | 81 | #: passwords/validators.py:119 82 | msgid "must contain " 83 | msgstr "deve conter " 84 | 85 | #: passwords/validators.py:124 86 | #, python-format 87 | msgid "Too Similar to [%(haystacks)s]" 88 | msgstr "Muito similar a [%(haystacks)s]" 89 | 90 | #: passwords/validators.py:168 91 | msgid "Based on a dictionary word" 92 | msgstr "Baseada em uma palavra do dicionário" 93 | 94 | #: passwords/validators.py:187 95 | msgid "Based on a common sequence of characters" 96 | msgstr "Baseada em uma sequência comum de caracteres" 97 | -------------------------------------------------------------------------------- /passwords/locale/pt_PT/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstufft/django-passwords/d68953b83717a2f67f6f71581a17bb9c22cb65ff/passwords/locale/pt_PT/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /passwords/locale/pt_PT/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-passwords package. 2 | # 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: django-passwords\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2015-06-24 21:21+0400\n" 8 | "PO-Revision-Date: 2015-09-18 17:27+0400\n" 9 | "Last-Translator: Nuno Khan \n" 10 | "Language-Team: Portuguese \n" 11 | "Language: pt-pt\n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=UTF-8\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" 16 | "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" 17 | 18 | 19 | #: fields.py:27 20 | #, python-format 21 | msgid "%i to %i characters" 22 | msgstr "%i a %i carácteres" 23 | 24 | #: fields.py:31 25 | #, python-format 26 | msgid "%i characters minimum" 27 | msgstr "%i carácteres minímos" 28 | 29 | #: validators.py:47 30 | #, python-format 31 | msgid "Invalid Length (%s)" 32 | msgstr "Tamanho Inválido: %s" 33 | 34 | #: validators.py:57 35 | #, python-format 36 | msgid "Must be %s characters or more" 37 | msgstr "Deve ser %s carácter(es) ou mais" 38 | 39 | #: validators.py:59 40 | #, python-format 41 | msgid "Must be %s characters or less" 42 | msgstr "Deve ser %s carácter(es) ou menos" 43 | 44 | #: validators.py:66 45 | #, python-format 46 | msgid "Must be more complex (%s)" 47 | msgstr "Deve ser mais complexa (%s)" 48 | 49 | #: validators.py:98 50 | #, python-format 51 | msgid "%(UPPER)s or more unique uppercase characters" 52 | msgstr "deve conter %(UPPER)s ou carácteres maiúsculos mais únicos" 53 | 54 | #: validators.py:102 55 | #, python-format 56 | msgid "%(LOWER)s or more unique lowercase characters" 57 | msgstr "deve conter %(LOWER)s ou carácteres minúsculos mais únicos" 58 | 59 | #: validators.py:106 60 | #, python-format 61 | msgid "%(LETTERS)s or more unique letters" 62 | msgstr "deve conter %(LETTERS)s ou letras mais únicas" 63 | 64 | #: validators.py:110 65 | #, python-format 66 | msgid "%(DIGITS)s or more unique digits" 67 | msgstr "deve conter %(DIGITS)s ou dígitos mais únicos" 68 | 69 | #: validators.py:114 70 | #, python-format 71 | msgid "%(PUNCTUATION)s or more unique punctuation characters" 72 | msgstr "deve conter %(PUNCTUATION)s ou carácteres de pontuação mais únicos" 73 | 74 | #: validators.py:118 75 | #, python-format 76 | msgid "%(SPECIAL)s or more unique special characters" 77 | msgstr "deve conter %(SPECIAL)s ou carácteres especiais mais únicos" 78 | 79 | #: validators.py:122 80 | #, python-format 81 | msgid "%(WORDS)s or more unique words" 82 | msgstr "deve conter %(WORDS)s ou palavras mais únicas" 83 | 84 | #: validators.py:119 85 | msgid "must contain " 86 | msgstr " " 87 | 88 | #: validators.py:132 89 | #, python-format 90 | msgid "Too Similar to [%(haystacks)s]" 91 | msgstr "Muito Similar a [%(haystacks)s]" 92 | 93 | #: validators.py:176 94 | msgid "Based on a dictionary word" 95 | msgstr "Baseada numa palavra de dicionário" 96 | 97 | #: validators.py:195 98 | msgid "Based on a common sequence of characters" 99 | msgstr "Baseada numa sequência comum de carácteres" 100 | -------------------------------------------------------------------------------- /passwords/locale/ru/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstufft/django-passwords/d68953b83717a2f67f6f71581a17bb9c22cb65ff/passwords/locale/ru/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /passwords/locale/ru/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-passwords package. 2 | # 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: django-passwords\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2015-09-03 13:44+0300\n" 8 | "PO-Revision-Date: 2012-12-17 21:21+0400\n" 9 | "Last-Translator: Nikolay Malakhov \n" 10 | "Language-Team: Russian \n" 11 | "Language: ru\n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=UTF-8\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" 16 | "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" 17 | 18 | #: fields.py:27 19 | #, python-format 20 | msgid "%i to %i characters" 21 | msgstr "От %i до %i символов" 22 | 23 | #: fields.py:31 24 | #, python-format 25 | msgid "%i characters minimum" 26 | msgstr "Минимум %i символов" 27 | 28 | #: validators.py:47 29 | #, python-format 30 | msgid "Invalid Length (%s)" 31 | msgstr "Неправильная длина: %s" 32 | 33 | #: validators.py:57 34 | #, python-format 35 | msgid "Must be %s characters or more" 36 | msgstr "должна составлять %s символов или более" 37 | 38 | #: validators.py:59 39 | #, python-format 40 | msgid "Must be %s characters or less" 41 | msgstr "должна составлять %s символов или менее" 42 | 43 | #: validators.py:66 44 | #, python-format 45 | msgid "Must be more complex (%s)" 46 | msgstr "Должен быть более сложным: %s" 47 | 48 | #: validators.py:98 49 | #, python-format 50 | msgid "%(UPPER)s or more unique uppercase characters" 51 | msgstr "должен содержать %(UPPER)s или более уникальных прописных букв" 52 | 53 | #: validators.py:102 54 | #, python-format 55 | msgid "%(LOWER)s or more unique lowercase characters" 56 | msgstr "должен содержать %(LOWER)s или более уникальных строчных букв" 57 | 58 | #: validators.py:106 59 | #, python-format 60 | msgid "%(LETTERS)s or more unique letters" 61 | msgstr "должен содержать %(LETTERS)s или более уникальных букв" 62 | 63 | #: validators.py:110 64 | #, python-format 65 | msgid "%(DIGITS)s or more unique digits" 66 | msgstr "должен содержать %(DIGITS)s или более уникальных цифр" 67 | 68 | #: validators.py:114 69 | #, python-format 70 | msgid "%(PUNCTUATION)s or more unique punctuation characters: %%s" 71 | msgstr "" 72 | "должен содержать %(PUNCTUATION)s или более уникальных знаков пунктуации: %%s" 73 | 74 | #: validators.py:118 75 | #, python-format 76 | msgid "%(SPECIAL)s or more non unique special characters" 77 | msgstr "должен содержать %(SPECIAL)s или более уникальных специальных символов" 78 | 79 | #: validators.py:122 80 | #, python-format 81 | msgid "%(WORDS)s or more unique words" 82 | msgstr "должен содержать %(WORDS)s или более уникальных слов" 83 | 84 | #: validators.py:119 85 | msgid "must contain " 86 | msgstr " " 87 | 88 | #: validators.py:132 89 | #, python-format 90 | msgid "Too Similar to [%(haystacks)s]" 91 | msgstr "Слишком похож на [%(haystacks)s]" 92 | 93 | #: validators.py:176 94 | msgid "Based on a dictionary word" 95 | msgstr "Основан на слове из словаря" 96 | 97 | #: validators.py:195 98 | msgid "Based on a common sequence of characters" 99 | msgstr "Основан на распространенной последовательности символов" 100 | -------------------------------------------------------------------------------- /passwords/locale/tr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstufft/django-passwords/d68953b83717a2f67f6f71581a17bb9c22cb65ff/passwords/locale/tr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /passwords/locale/tr/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the django-passwords package. 2 | # 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: django-passwords\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2012-12-17 21:21+0400\n" 8 | "PO-Revision-Date: 2016-06-24 12:01+0200\n" 9 | "Last-Translator: FULL NAME \n" 10 | "Language-Team: English \n" 11 | "Language: en\n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=UTF-8\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | "X-Generator: Poedit 1.5.4\n" 16 | 17 | #: validators.py:31 18 | #, python-format 19 | msgid "Invalid Length (%s)" 20 | msgstr "Hatalı Uzunluk (%s)" 21 | 22 | #: validators.py:41 23 | #, python-format 24 | msgid "Must be %s characters or more" 25 | msgstr "%s karakter ya da daha fazla olmalı" 26 | 27 | #: validators.py:45 28 | #, python-format 29 | msgid "Must be %s characters or less" 30 | msgstr "%s karakter ya da daha az olmalı" 31 | 32 | #: validators.py:49 33 | #, python-format 34 | msgid "Must be more complex (%s)" 35 | msgstr "Daha karmaşık olmalı (%s)" 36 | 37 | #: validators.py:77 38 | #, python-format 39 | msgid "Must contain %(UPPER)s or more unique uppercase characters" 40 | msgstr "%(UPPER)s ya da fazla eşsiz büyük harf içermeli" 41 | 42 | #: validators.py:81 43 | #, python-format 44 | msgid "Must contain %(LOWER)s or more unique lowercase characters" 45 | msgstr "%(LOWER)s ya da fazla eşsiz küçük harf içermeli" 46 | 47 | #: validators.py:85 48 | #, python-format 49 | msgid "Must contain %(DIGITS)s or more unique digits" 50 | msgstr "%(DIGITS)s ya da fazla eşsiz sayı içermeli" 51 | 52 | #: validators.py:89 53 | #, python-format 54 | msgid "Must contain %(PUNCTUATION)s or more unique punctuation characters" 55 | msgstr "%(PUNCTUATION)s ya da fazla noktalama işareti içermeli" 56 | 57 | #: validators.py:93 58 | #, python-format 59 | msgid "Must contain %(SPECIAL)s or more unique special characters" 60 | msgstr "%(SPECIAL)s ya da fazla eşsiz özel karakter içermeli" 61 | 62 | #: validators.py:97 63 | #, python-format 64 | msgid "Must contain %(WORDS)s or more unique words" 65 | msgstr "%(WORDS)s ya da fazla eşsiz kelime içermeli" 66 | 67 | #: validators.py:102 68 | #, python-format 69 | msgid "Too Similar to [%(haystacks)s]" 70 | msgstr "Çok benzer [%(haystacks)s]" 71 | 72 | #: validators.py:138 73 | msgid "Based on a dictionary word" 74 | msgstr "Sözlük bulunan kelime" 75 | 76 | #: validators.py:154 77 | msgid "Based on a common sequence of characters" 78 | msgstr "Çok tipik bir karakter dizisi" 79 | -------------------------------------------------------------------------------- /passwords/locale/zh_CN/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstufft/django-passwords/d68953b83717a2f67f6f71581a17bb9c22cb65ff/passwords/locale/zh_CN/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /passwords/locale/zh_CN/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: PACKAGE VERSION\n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "POT-Creation-Date: 2016-01-06 20:20+0800\n" 6 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 7 | "Last-Translator: FULL NAME \n" 8 | "Language-Team: LANGUAGE \n" 9 | "Language: \n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=1; plural=0;\n" 14 | 15 | #: fields.py:27 16 | #, python-format 17 | msgid "%i to %i characters" 18 | msgstr "%i 个 到 %i 个字符" 19 | 20 | #: fields.py:31 21 | #, python-format 22 | msgid "%i characters minimum" 23 | msgstr "至少 %i 个字符" 24 | 25 | #: validators.py:46 26 | #, python-format 27 | msgid "Invalid Length (%s)" 28 | msgstr "无效的长度 (%s)" 29 | 30 | #: validators.py:56 31 | #, python-format 32 | msgid "Must be %s characters or more" 33 | msgstr "至少需要 %s 个以上字符" 34 | 35 | #: validators.py:58 36 | #, python-format 37 | msgid "Must be %s characters or less" 38 | msgstr "必须少于 %s 个字符" 39 | 40 | #: validators.py:65 41 | #, python-format 42 | msgid "Must be more complex (%s)" 43 | msgstr "必须更加复杂 (%s)" 44 | 45 | #: validators.py:95 46 | #, python-format 47 | msgid "%(UPPER)s or more unique uppercase characters" 48 | msgstr "%(UPPER)s 个及以上不同的大写字母" 49 | 50 | #: validators.py:99 51 | #, python-format 52 | msgid "%(LOWER)s or more unique lowercase characters" 53 | msgstr "%(LOWER)s 个及以上不同的小写字母" 54 | 55 | #: validators.py:103 56 | #, python-format 57 | msgid "%(LETTERS)s or more unique letters" 58 | msgstr "%(LETTERS)s 个及以上不同的大小写字母" 59 | 60 | #: validators.py:107 61 | #, python-format 62 | msgid "%(DIGITS)s or more unique digits" 63 | msgstr "%(DIGITS)s 个及以上不同的数字" 64 | 65 | #: validators.py:111 66 | #, python-format 67 | msgid "%(SPECIAL)s or more non unique special characters" 68 | msgstr "%(SPECIAL)s 个及以上的特殊字符" 69 | 70 | #: validators.py:115 71 | #, python-format 72 | msgid "%(WORDS)s or more unique words" 73 | msgstr "%(WORDS)s 个及以上不同的单词" 74 | 75 | #: validators.py:119 76 | msgid "must contain " 77 | msgstr "必须包含 " 78 | 79 | #: validators.py:124 80 | #, python-format 81 | msgid "Too Similar to [%(haystacks)s]" 82 | msgstr "和 [%(haystacks)s] 过于相似" 83 | 84 | #: validators.py:168 85 | msgid "Based on a dictionary word" 86 | msgstr "包含一些字典里的单词" 87 | 88 | #: validators.py:187 89 | msgid "Based on a common sequence of characters" 90 | msgstr "包含一些过于常见的字符序列" 91 | -------------------------------------------------------------------------------- /passwords/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dstufft/django-passwords/d68953b83717a2f67f6f71581a17bb9c22cb65ff/passwords/models.py -------------------------------------------------------------------------------- /passwords/validators.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from __future__ import division, unicode_literals 3 | 4 | import re 5 | 6 | from django.conf import settings 7 | from django.core.exceptions import ValidationError 8 | from django.utils.translation import gettext_lazy as _ 9 | from django.utils.encoding import smart_str 10 | 11 | 12 | COMMON_SEQUENCES = [ 13 | "0123456789", 14 | "`1234567890-=", 15 | "~!@#$%^&*()_+", 16 | "abcdefghijklmnopqrstuvwxyz", 17 | "qwertyuiop[]\\asdfghjkl;\'zxcvbnm,./", 18 | 'qwertyuiop{}|asdfghjkl;"zxcvbnm<>?', 19 | "qwertyuiopasdfghjklzxcvbnm", 20 | "1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik,9ol.0p;/-['=]\\", 21 | "qazwsxedcrfvtgbyhnujmikolp", 22 | "qwertzuiopü+asdfghjklöä#yxcvbnm;:_", 24 | "qaywsxedcrfvtgbzhnujmikolp", 25 | ] 26 | DICT_CACHE = [] 27 | DICT_FILESIZE = -1 28 | DICT_MAX_CACHE = 1000000 29 | 30 | # Settings 31 | PASSWORD_MIN_LENGTH = getattr( 32 | settings, "PASSWORD_MIN_LENGTH", 6) 33 | PASSWORD_MAX_LENGTH = getattr( 34 | settings, "PASSWORD_MAX_LENGTH", None) 35 | PASSWORD_DICTIONARY = getattr( 36 | settings, "PASSWORD_DICTIONARY", None) 37 | PASSWORD_MATCH_THRESHOLD = getattr( 38 | settings, "PASSWORD_MATCH_THRESHOLD", 0.9) 39 | PASSWORD_COMMON_SEQUENCES = getattr( 40 | settings, "PASSWORD_COMMON_SEQUENCES", COMMON_SEQUENCES) 41 | PASSWORD_COMPLEXITY = getattr( 42 | settings, "PASSWORD_COMPLEXITY", None) 43 | 44 | 45 | class LengthValidator(object): 46 | message = _("Invalid Length (%s)") 47 | code = "length" 48 | 49 | def __init__(self, min_length=None, max_length=None): 50 | self.min_length = min_length 51 | self.max_length = max_length 52 | 53 | def __call__(self, value): 54 | err = None 55 | if self.min_length is not None and len(value) < self.min_length: 56 | err = _("Must be %s characters or more") % self.min_length 57 | elif self.max_length is not None and len(value) > self.max_length: 58 | err = _("Must be %s characters or less") % self.max_length 59 | 60 | if err is not None: 61 | raise ValidationError(self.message % err, code=self.code) 62 | 63 | 64 | class ComplexityValidator(object): 65 | message = _("Must be more complex (%s)") 66 | code = "complexity" 67 | 68 | def __init__(self, complexities): 69 | self.complexities = complexities 70 | 71 | def __call__(self, value): 72 | if self.complexities is None: 73 | return 74 | 75 | uppercase, lowercase, letters = set(), set(), set() 76 | digits, special = set(), set() 77 | 78 | for character in value: 79 | if character.isupper(): 80 | uppercase.add(character) 81 | letters.add(character) 82 | elif character.islower(): 83 | lowercase.add(character) 84 | letters.add(character) 85 | elif character.isdigit(): 86 | digits.add(character) 87 | elif not character.isspace(): 88 | special.add(character) 89 | 90 | words = set(re.findall(r'\b\w+', value, re.UNICODE)) 91 | 92 | errors = [] 93 | if len(uppercase) < self.complexities.get("UPPER", 0): 94 | errors.append( 95 | _("%(UPPER)s or more unique uppercase characters") % 96 | self.complexities) 97 | if len(lowercase) < self.complexities.get("LOWER", 0): 98 | errors.append( 99 | _("%(LOWER)s or more unique lowercase characters") % 100 | self.complexities) 101 | if len(letters) < self.complexities.get("LETTERS", 0): 102 | errors.append( 103 | _("%(LETTERS)s or more unique letters") % 104 | self.complexities) 105 | if len(digits) < self.complexities.get("DIGITS", 0): 106 | errors.append( 107 | _("%(DIGITS)s or more unique digits") % 108 | self.complexities) 109 | if len(special) < self.complexities.get("SPECIAL", 0): 110 | errors.append( 111 | _("%(SPECIAL)s or more non unique special characters") % 112 | self.complexities) 113 | if len(words) < self.complexities.get("WORDS", 0): 114 | errors.append( 115 | _("%(WORDS)s or more unique words") % 116 | self.complexities) 117 | 118 | if errors: 119 | raise ValidationError(self.message % (_(u'must contain ') + u', '.join(errors),), 120 | code=self.code) 121 | 122 | 123 | class BaseSimilarityValidator(object): 124 | message = _("Too Similar to [%(haystacks)s]") 125 | code = "similarity" 126 | 127 | def __init__(self, haystacks=None, threshold=None): 128 | self.haystacks = haystacks if haystacks else [] 129 | if threshold is None: 130 | self.threshold = PASSWORD_MATCH_THRESHOLD 131 | else: 132 | self.threshold = threshold 133 | 134 | def fuzzy_substring(self, needle, haystack): 135 | needle, haystack = needle.lower(), haystack.lower() 136 | m, n = len(needle), len(haystack) 137 | 138 | if m == 1: 139 | if needle not in haystack: 140 | return -1 141 | if n == 0: 142 | return m 143 | 144 | row1 = [0] * (n + 1) 145 | for i in range(0, m): 146 | row2 = [i + 1] 147 | for j in range(0, n): 148 | cost = 1 if needle[i] != haystack[j] else 0 149 | row2.append(min( 150 | row1[j + 1] + 1, 151 | row2[j] + 1, 152 | row1[j] + cost)) 153 | row1 = row2 154 | return min(row1) 155 | 156 | def __call__(self, value): 157 | for haystack in self.haystacks: 158 | distance = self.fuzzy_substring(value, haystack) 159 | longest = max(len(value), len(haystack)) 160 | similarity = (longest - distance) / longest 161 | if similarity >= self.threshold: 162 | raise ValidationError( 163 | self.message % {"haystacks": ", ".join(self.haystacks)}, 164 | code=self.code) 165 | 166 | 167 | class DictionaryValidator(BaseSimilarityValidator): 168 | message = _("Based on a dictionary word") 169 | code = "dictionary_word" 170 | 171 | def __init__(self, words=None, dictionary=None, threshold=None): 172 | haystacks = [] 173 | if dictionary: 174 | words = self.get_dictionary_words(dictionary) 175 | if words: 176 | haystacks.extend(words) 177 | super(DictionaryValidator, self).__init__( 178 | haystacks=haystacks, 179 | threshold=threshold) 180 | 181 | def get_dictionary_words(self, dictionary): 182 | if DICT_CACHE: 183 | return DICT_CACHE 184 | if DICT_FILESIZE is -1: 185 | f = open(dictionary) 186 | f.seek(0,2) 187 | DICT_FILESIZE = f.tell() 188 | f.close() 189 | if DICT_FILESIZE < 1000000: 190 | with open(dictionary) as dictionary: 191 | DICT_CACHE = [smart_str(x.strip()) for x in dictionary.readlines()] 192 | return DICT_CACHE 193 | with open(dictionary) as dictionary: 194 | return [smart_str(x.strip()) for x in dictionary.readlines()] 195 | 196 | 197 | class CommonSequenceValidator(BaseSimilarityValidator): 198 | message = _("Based on a common sequence of characters") 199 | code = "common_sequence" 200 | 201 | 202 | validate_length = LengthValidator(PASSWORD_MIN_LENGTH, PASSWORD_MAX_LENGTH) 203 | complexity = ComplexityValidator(PASSWORD_COMPLEXITY) 204 | dictionary_words = DictionaryValidator(dictionary=PASSWORD_DICTIONARY) 205 | common_sequences = CommonSequenceValidator(PASSWORD_COMMON_SEQUENCES) 206 | -------------------------------------------------------------------------------- /requirements/test.txt: -------------------------------------------------------------------------------- 1 | coverage==5.0.3 2 | flake8==3.7.9 3 | pytest-cov==2.2.0 4 | pytest==2.8.1 5 | tox==3.14.5 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name="django-passwords", 5 | version=__import__("passwords").__version__, 6 | author="Donald Stufft", 7 | author_email="donald@e.vilgeni.us", 8 | description=("A Django reusable app that provides validators and a form " 9 | "field that checks the strength of a password"), 10 | long_description=open("README.rst").read(), 11 | url="http://github.com/dstufft/django-passwords/", 12 | license="BSD", 13 | packages=[ 14 | "passwords", 15 | ], 16 | include_package_data=True, 17 | install_requires = [ 18 | "Django >= 1.3", 19 | ], 20 | classifiers = [ 21 | "Development Status :: 4 - Beta", 22 | "Environment :: Web Environment", 23 | "Intended Audience :: Developers", 24 | "License :: OSI Approved :: BSD License", 25 | "Operating System :: OS Independent", 26 | "Programming Language :: Python :: 2.6", 27 | "Programming Language :: Python :: 2.7", 28 | "Programming Language :: Python :: 3.4", 29 | "Programming Language :: Python :: 3.5", 30 | "Topic :: Utilities", 31 | "Framework :: Django", 32 | ], 33 | ) 34 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | import django 3 | 4 | settings.configure() 5 | 6 | try: 7 | django.setup() 8 | except AttributeError: # django 1.4 9 | pass 10 | -------------------------------------------------------------------------------- /tests/test_fields.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | from __future__ import unicode_literals 4 | from django.core.exceptions import ValidationError 5 | from django.forms import PasswordInput, TimeInput, CharField 6 | from passwords import fields, validators 7 | from unittest import TestCase 8 | 9 | 10 | class TestFields(TestCase): 11 | 12 | def test_default_widget(self): 13 | p = fields.PasswordField() 14 | assert isinstance(p.widget, PasswordInput) 15 | 16 | p = fields.PasswordField(widget=TimeInput()) 17 | assert isinstance(p.widget, TimeInput) 18 | 19 | def test_widget_attributes(self): 20 | p = fields.PasswordField() 21 | # Verify that our default minimum length creates some sort of HTML5 22 | # validation. 23 | self.assertTrue( 24 | 'minlength' in p.widget.attrs or 'pattern' in p.widget.attrs) 25 | 26 | def test_default_validation(self): 27 | # because our tests/__init__ has not provided any configuration to 28 | # Django, we get default behaviour here. 29 | p = fields.PasswordField() 30 | p.clean('password') # robust defaults it seems 31 | 32 | def test_using_validators_on_other_fields(self): 33 | dict_validator = validators.DictionaryValidator( 34 | words=['nostromo'], 35 | threshold=0.8) 36 | length_validator = validators.LengthValidator( 37 | min_length=2) 38 | 39 | p = CharField(validators=[dict_validator, length_validator]) 40 | 41 | p.clean('aa') 42 | with self.assertRaises(ValidationError): 43 | p.clean('a') # too short 44 | with self.assertRaises(ValidationError): 45 | p.clean('nostrilomo') # too much like nostromo 46 | -------------------------------------------------------------------------------- /tests/test_validators.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | from __future__ import unicode_literals 4 | from django.core.exceptions import ValidationError 5 | from passwords import validators 6 | from unittest import TestCase 7 | from six import assertRaisesRegex 8 | 9 | 10 | class TestLengthValidatorTests(TestCase): 11 | 12 | def test_length_validator_explicit_minimum(self): 13 | lv = validators.LengthValidator(min_length=8, max_length=None) 14 | 15 | # these should all fail validation 16 | badstrings = '', '1', '1234567', '$%ǟ^&*(' 17 | for bad in badstrings: 18 | with self.assertRaises(ValidationError): 19 | lv(bad) 20 | 21 | # these should all pass validation 22 | goodstrings = '12345678', '^@$%()ǟ^&$@%ǟ_(^$%@ǟ\'\')' 23 | for good in goodstrings: 24 | lv(good) 25 | 26 | def test_length_validator_zero_minimum(self): 27 | lv = validators.LengthValidator(min_length=0, max_length=None) 28 | lv('') 29 | 30 | def test_length_validator_no_minimum(self): 31 | lv = validators.LengthValidator(min_length=None, max_length=None) 32 | lv('') 33 | 34 | def test_length_validator_explicit_maximum(self): 35 | lv = validators.LengthValidator(min_length=None, max_length=8) 36 | 37 | # these should all pass validation 38 | goodstrings = '', '1', '1234567', '$%ǟ^&*(' 39 | for good in goodstrings: 40 | lv(good) 41 | 42 | # these should all fail validation 43 | badstrings = '123456789', '^@$%()ǟ^&$@%ǟ_(^$%@ǟ\'\')' 44 | for bad in badstrings: 45 | with self.assertRaises(ValidationError): 46 | lv(bad) 47 | 48 | def test_length_validator_zero_maximum(self): 49 | lv = validators.LengthValidator(min_length=None, max_length=0) 50 | # no problems here 51 | lv('') 52 | 53 | with self.assertRaises(ValidationError): 54 | lv('longer than zero chars') 55 | 56 | def test_length_validator_no_maximum(self): 57 | lv = validators.LengthValidator(min_length=None, max_length=None) 58 | lv('any length is fine') 59 | 60 | 61 | class ValidatorTestCase(TestCase): 62 | 63 | def assertValid(self, validator, string): 64 | # we test validationerror isn't raised by just running the validation. 65 | validator(string) 66 | 67 | def assertInvalid(self, validator, string, exc_re=None): 68 | if exc_re is None: 69 | with self.assertRaises(ValidationError): 70 | validator(string) 71 | else: 72 | with assertRaisesRegex(self, ValidationError, exc_re): 73 | validator(string) 74 | 75 | 76 | class ComplexityValidatorTests(ValidatorTestCase): 77 | 78 | def mkvalidator(self, **complexities): 79 | return validators.ComplexityValidator(complexities=complexities) 80 | 81 | def test_minimum_uppercase_count(self): 82 | cv = self.mkvalidator(UPPER=0) 83 | self.assertValid(cv, 'no uppercase') 84 | self.assertValid(cv, 'Some UpperCase') 85 | self.assertValid(cv, 'ALL UPPERCASE') 86 | 87 | cv = self.mkvalidator(UPPER=1) 88 | self.assertInvalid(cv, 'no uppercase', 'uppercase') 89 | self.assertValid(cv, 'Some UpperCase') 90 | self.assertValid(cv, 'ALL UPPERCASE') 91 | 92 | cv = self.mkvalidator(UPPER=100) 93 | self.assertInvalid(cv, 'no uppercase', 'uppercase') 94 | self.assertInvalid(cv, 'Some UpperCase', 'uppercase') 95 | self.assertInvalid(cv, 'ALL UPPERCASE', 'uppercase') 96 | 97 | def test_minimum_lowercase_count(self): 98 | cv = self.mkvalidator(LOWER=0) 99 | self.assertValid(cv, 'NO LOWERCASE') 100 | self.assertValid(cv, 'sOME lOWERCASE') 101 | self.assertValid(cv, 'all lowercase') 102 | 103 | cv = self.mkvalidator(LOWER=1) 104 | self.assertInvalid(cv, 'NO LOWERCASE', 'lowercase') 105 | self.assertValid(cv, 'sOME lOWERCASE') 106 | self.assertValid(cv, 'all lowercase') 107 | 108 | cv = self.mkvalidator(LOWER=100) 109 | self.assertInvalid(cv, 'NO LOWERCASE', 'lowercase') 110 | self.assertInvalid(cv, 'sOME lOWERCASE', 'lowercase') 111 | self.assertInvalid(cv, 'all lowercase', 'lowercase') 112 | 113 | def test_minimum_letter_count(self): 114 | cv = self.mkvalidator(LETTERS=0) 115 | self.assertValid(cv, '1234. ?') 116 | self.assertValid(cv, 'soME 123') 117 | self.assertValid(cv, 'allletters') 118 | 119 | cv = self.mkvalidator(LETTERS=1) 120 | self.assertInvalid(cv, '1234. ?', 'letter') 121 | self.assertValid(cv, 'soME 123') 122 | self.assertValid(cv, 'allletters') 123 | 124 | cv = self.mkvalidator(LETTERS=100) 125 | self.assertInvalid(cv, '1234. ?', 'letter') 126 | self.assertInvalid(cv, 'soME 123', 'letter') 127 | self.assertInvalid(cv, 'allletters', 'letter') 128 | 129 | def test_minimum_digit_count(self): 130 | cv = self.mkvalidator(DIGITS=0) 131 | self.assertValid(cv, '') 132 | self.assertValid(cv, '0') 133 | self.assertValid(cv, '1') 134 | self.assertValid(cv, '11') 135 | self.assertValid(cv, 'one 1') 136 | 137 | cv = self.mkvalidator(DIGITS=1) 138 | self.assertInvalid(cv, '', 'digits') 139 | self.assertValid(cv, '0') 140 | self.assertValid(cv, '1') 141 | self.assertValid(cv, '11') 142 | self.assertValid(cv, 'one 1') 143 | 144 | def test_minimum_punctuation_count(self): 145 | none = 'no punctuation' 146 | one = 'ffs!' 147 | mixed = r'w@oo%lo(om!ol~oo&' 148 | # this is a copy of string.punctuation 149 | allpunc = r'!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' 150 | 151 | cv = self.mkvalidator(SPECIAL=0) 152 | self.assertValid(cv, none) 153 | self.assertValid(cv, one) 154 | self.assertValid(cv, mixed) 155 | self.assertValid(cv, allpunc) 156 | 157 | cv = self.mkvalidator(SPECIAL=1) 158 | self.assertInvalid(cv, none, 'special') 159 | self.assertValid(cv, one) 160 | self.assertValid(cv, mixed) 161 | self.assertValid(cv, allpunc) 162 | 163 | cv = self.mkvalidator(SPECIAL=100) 164 | self.assertInvalid(cv, none, 'special') 165 | self.assertInvalid(cv, one, 'special') 166 | self.assertInvalid(cv, mixed, 'special') 167 | self.assertInvalid(cv, allpunc, 'special') 168 | 169 | def test_minimum_nonascii_count(self): 170 | none = 'regularchars and numbers 100' 171 | one = '\x00' # null 172 | many = "\x00\x01\x02\x03\x04\x05\t\n\r" 173 | 174 | cv = self.mkvalidator(SPECIAL=0) 175 | self.assertValid(cv, none) 176 | self.assertValid(cv, one) 177 | self.assertValid(cv, many) 178 | 179 | cv = self.mkvalidator(SPECIAL=1) 180 | self.assertInvalid(cv, none) 181 | self.assertValid(cv, one) 182 | self.assertValid(cv, many) 183 | 184 | cv = self.mkvalidator(SPECIAL=100) 185 | self.assertInvalid(cv, none) 186 | self.assertInvalid(cv, one) 187 | self.assertInvalid(cv, many) 188 | 189 | def test_minimum_words_count(self): 190 | none = '' 191 | one = 'oneword' 192 | some = 'one or two words' 193 | many = 'a b c d e f g h i 1 2 3 4 5 6 7 8 9 { $ # ! )}' 194 | 195 | cv = self.mkvalidator(WORDS=0) 196 | self.assertValid(cv, none) 197 | self.assertValid(cv, one) 198 | self.assertValid(cv, some) 199 | self.assertValid(cv, many) 200 | 201 | cv = self.mkvalidator(WORDS=1) 202 | self.assertInvalid(cv, none, 'unique words') 203 | self.assertValid(cv, one) 204 | self.assertValid(cv, some) 205 | self.assertValid(cv, many) 206 | 207 | cv = self.mkvalidator(WORDS=10) 208 | self.assertInvalid(cv, none, 'unique words') 209 | self.assertInvalid(cv, one, 'unique words') 210 | self.assertInvalid(cv, some, 'unique words') 211 | self.assertValid(cv, many) 212 | 213 | cv = self.mkvalidator(WORDS=100) 214 | self.assertInvalid(cv, none, 'unique words') 215 | self.assertInvalid(cv, one, 'unique words') 216 | self.assertInvalid(cv, some, 'unique words') 217 | self.assertInvalid(cv, many, 'unique words') 218 | 219 | 220 | class DictionaryValidator(ValidatorTestCase): 221 | 222 | different = 'ljasdfkjhsdfkjhsiudfyisd' 223 | vsimilar = 'uncommon' 224 | same = 'words' 225 | 226 | def mkvalidator(self, words=None, dictionary_words=None, threshold=None): 227 | instance = validators.DictionaryValidator( 228 | words=words, 229 | threshold=threshold) 230 | if dictionary_words: 231 | instance.get_dictionary_words = lambda self, d: dictionary_words 232 | return instance 233 | 234 | def test_provide_words_but_no_dictionary(self): 235 | dv = self.mkvalidator(words=['common', 'words']) 236 | self.assertValid(dv, self.different) 237 | 238 | def test_provide_dictionary_but_no_words(self): 239 | dv = self.mkvalidator(dictionary_words='common\nwords') 240 | self.assertValid(dv, self.different) 241 | 242 | def test_thresholds(self): 243 | dv = self.mkvalidator(words=['common', 'words'], threshold=0.0) 244 | self.assertInvalid(dv, self.different, 'dictionary word') 245 | self.assertInvalid(dv, self.vsimilar, 'dictionary word') 246 | self.assertInvalid(dv, self.same, 'dictionary word') 247 | 248 | dv = self.mkvalidator(words=['common', 'words'], threshold=0.5) 249 | self.assertValid(dv, self.different) 250 | self.assertInvalid(dv, self.vsimilar, 'dictionary word') 251 | self.assertInvalid(dv, self.same, 'dictionary word') 252 | 253 | dv = self.mkvalidator(words=['common', 'words'], threshold=1.0) 254 | self.assertValid(dv, self.different) 255 | self.assertValid(dv, self.vsimilar) 256 | self.assertInvalid(dv, self.same, 'dictionary word') 257 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py27-dj{13,14,15,16,17,18,19}, 4 | py34-dj{15,16,17,18,19} 5 | py35-dj{17,18,19} 6 | py36-dj{17,18,19,22} 7 | py37-dj{17,18,19,22} 8 | 9 | [testenv] 10 | commands = py.test tests 11 | deps = 12 | pytest 13 | dj13: Django>=1.3,<1.4 14 | dj14: Django>=1.4,<1.5 15 | dj15: Django>=1.5,<1.6 16 | dj16: Django>=1.6,<1.7 17 | dj17: Django>=1.7,<1.8 18 | dj18: Django>=1.8,<1.9 19 | dj19: Django==1.9rc1 20 | dj22: Django==2.2 21 | --------------------------------------------------------------------------------