From 30d74e685446d735310afef60e4bb07a86aec313 Mon Sep 17 00:00:00 2001 From: Ezra Rice Date: Thu, 3 Nov 2022 16:01:41 -0400 Subject: [PATCH 1/6] Fixed consecutive character bug Added automatic deployment Updated test coverage Updated README to include test status and coverage --- .github/workflows/deploy_to_pypi.yaml | 70 +++++++++++++++++++ .github/workflows/test.yaml | 54 +++++++++++--- README.md | 46 ++++++------ .../advanced_password_validation.py | 6 +- .../tests/test_validators.py | 3 +- pyproject.toml | 30 ++++++++ setup.py | 30 -------- 7 files changed, 174 insertions(+), 65 deletions(-) create mode 100644 .github/workflows/deploy_to_pypi.yaml create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/.github/workflows/deploy_to_pypi.yaml b/.github/workflows/deploy_to_pypi.yaml new file mode 100644 index 0000000..a4dc51a --- /dev/null +++ b/.github/workflows/deploy_to_pypi.yaml @@ -0,0 +1,70 @@ +name: deploy_to_pypi + +on: + push: + tags: + - "v*" + +jobs: + build-n-publish: + name: Build and publish Python distributions to PyPI and TestPyPI + runs-on: ubuntu-latest + + steps: + - name: Checkout source + uses: actions/checkout@release + + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: "3.10" + + - name: Build source and wheel distributions + run: | + python -m pip install --upgrade build twine + python -m build + twine check --strict dist/* + + - name: Publish distribution to Test PyPI + uses: pypa/gh-action-pypi-publish@release + with: + user: __token__ + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository_url: https://test.pypi.org/legacy/ + skip_existing: true + + - name: Publish distribution to PyPI + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@release + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} + skip_existing: true + + - name: Create GitHub Release + id: create_release + uses: actions/create-release@v3 + env: + GITHUB-TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: ${{ github.ref }} + draft: false + prerelease: false + + - name: Get Asset name + run: | + export PKG=$(ls dist/ | grep tar) + set -- $PKG + echo "name=$1" >> $GITHUB_ENV + + - name: Upload Release Asset (sdist) to GitHub + id: upload-release-asset + uses: actions/upload-release-asset@v3 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: dist/${{ env.name }} + asset_name: ${{ env.name }} + asset_content_type: application/zip \ No newline at end of file diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 19a3820..9c2b5aa 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,37 +1,73 @@ name: test -on: [push, pull_request] +on: + push: + branches: + - "main" + - "release" + pull_request: + branches: + - "*" + +env: + GITHUB-TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: + black-lint: + name: Lint with Black + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: psf/black@stable + test: name: Test django-advanced-password-validation runs-on: ubuntu-latest strategy: matrix: versions: + - { "djangoVersion": "2.2.28", "pythonVersion": "3.7" } + - { "djangoVersion": "2.2.28", "pythonVersion": "3.8" } + - { "djangoVersion": "2.2.28", "pythonVersion": "3.9" } + - { "djangoVersion": "2.2.28", "pythonVersion": "3.10" } + - { "djangoVersion": "3.2.16", "pythonVersion": "3.7" } + - { "djangoVersion": "3.2.16", "pythonVersion": "3.8" } + - { "djangoVersion": "3.2.16", "pythonVersion": "3.9" } + - { "djangoVersion": "3.2.16", "pythonVersion": "3.10" } + - { "djangoVersion": "4.0.8", "pythonVersion": "3.8" } + - { "djangoVersion": "4.0.8", "pythonVersion": "3.9" } + - { "djangoVersion": "4.0.8", "pythonVersion": "3.10" } + - { "djangoVersion": "4.1.2", "pythonVersion": "3.8" } + - { "djangoVersion": "4.1.2", "pythonVersion": "3.9" } - { "djangoVersion": "4.1.2", "pythonVersion": "3.10" } steps: - - name: Checkout ๐Ÿ›Ž๏ธ + # Checkout the source + - name: Checkout uses: actions/checkout@v3 - - name: Set up Python ๐Ÿ + # Setup Python + - name: Set up Python uses: actions/setup-python@v3 with: python-version: ${{ matrix.versions.pythonVersion }} - - name: Install dependencies ๐Ÿ“ฆ + # Install Dependencies + - name: Install dependencies run: python -m pip install -r requirements_test.txt && python -m pip install -e . - - name: Install Django ${{ matrix.versions.djangoVersion }} ๐Ÿ“ฆ + # Install Django + - name: Install Django ${{ matrix.versions.djangoVersion }} run: python -m pip install Django==${{ matrix.versions.djangoVersion }} - - name: Check types, syntax and duckstrings ๐Ÿฆ† + # Check syntax + - name: Check types, syntax and duckstrings run: | mypy --exclude=setup.py . flake8 . interrogate --quiet --fail-under=90 . - - name: Test Django ${{ matrix.versions.djangoVersion }} with coverage ๐Ÿงช + # Test package + - name: Test Django ${{ matrix.versions.djangoVersion }} with coverage run: coverage run --source=django_advanced_password_validation -m pytest . && coverage lcov -o coverage.lcov - - name: Submit coverage report to Coveralls ๐Ÿ“ˆ + # Generate Coverage Report + - name: Submit coverage report to Coveralls if: ${{ success() }} uses: coverallsapp/github-action@1.1.3 with: github-token: ${{ secrets.GITHUB_TOKEN }} path-to-lcov: ./coverage.lcov - diff --git a/README.md b/README.md index cd531ac..b2c8939 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,50 @@ -# django-advanced_password_validation +# django_advanced_password_validation -Extends Django password validation options to include minimum uppercase, minimum lowercase, minimum numerical, and minimum special characters. This was created in an attempt to keep up with industry standards for strong user passwords. +![Tests](https://github.com/ezrajrice/django_advanced_password_validation/actions/workflows/test.yml/badge.svg) +[![Coverage Status](https://coveralls.io/repos/github/ezrajrice/django-advanced_password_validation/badge.svg?branch=main)](https://coveralls.io/github/ezrajrice/django-advanced_password_validation?branch=main) -This package works for both python 3.x and 2.x versions. +Extends Django password validation options to include minimum uppercase, minimum lowercase, minimum numerical, and minimum special characters. This was created in an attempt to keep up with industry standards for strong user passwords. -> **_NOTE:_** As of January 01, 2020 python 2.x has been deprecated and will no longer receive continued support. See [Python 2.x EOL](https://www.python.org/doc/sunset-python-2/) for more details. +This package works for python 3.6+. -### Prerequisites +## Prerequisites -Requires Django 1.11 or later. +Requires Django 2.2 or later. You can install the latest version of Django via pip: -``` -$ pip install django +```bash +pip install django ``` Alternatively, you can install a specific version of Django via pip: -``` -$ pip install django=2.2 +```bash +pip install django=3.2 ``` > **_NOTE:_** See the [django-project](https://docs.djangoproject.com) documentation for information on non-deprecated Django versions. -### Installation +## Installation -#### Normal installation +### Normal installation Install django-advanced_password_validation via pip: -``` -$ pip install django-advanced_password_validation +```bash +pip install django-advanced_password_validation ``` -#### Development installation +### Development installation -``` -$ git clone https://github.com/ezrajrice/django-advanced_password_validation.git -$ cd django-advanced_password_validation -$ pip install --editable . +```bash +git clone https://github.com/ezrajrice/django-advanced_password_validation.git +cd django-advanced_password_validation +pip install --editable . ``` ### Usage -The four optional validators must be configured in the settings.py file of your django project. +The optional validators must be configured in the settings.py file of your django project to be actively used in your project. #### /my-cool-project/settings.py @@ -101,7 +102,7 @@ Here is a list of the available options with their default values. ## Authors -* **Ezra Rice** - *Initial work* - [ezrajrice](https://github.com/ezrajrice) +* **Ezra Rice** - _Initial work_ - [ezrajrice](https://github.com/ezrajrice) ## License @@ -109,4 +110,5 @@ This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md ## Acknowledgments -* **Victor Semionov** - *Contributor* - [vsemionov](https://github.com/vsemionov) +* **Victor Semionov** - _Contributor_ - [vsemionov](https://github.com/vsemionov) +* **Mostafa Moradian** - _Contributor_ - [mostafa](https://github.com/mostafa) diff --git a/django_advanced_password_validation/advanced_password_validation.py b/django_advanced_password_validation/advanced_password_validation.py index 8cada5e..6d4a216 100644 --- a/django_advanced_password_validation/advanced_password_validation.py +++ b/django_advanced_password_validation/advanced_password_validation.py @@ -263,7 +263,7 @@ def get_help_text(self): class MaxConsecutiveCharactersValidator: """ - Validates whether the password contains max_consecutive consecutive characters. + Validates whether the password contains more than max_consecutive consecutive characters. """ def __init__(self, max_consecutive=3): @@ -277,7 +277,7 @@ def __init__(self, max_consecutive=3): def validate(self, password, user=None): """ - Validates whether the password contains max_consecutive consecutive characters. + Validates whether the password contains more than max_consecutive consecutive characters. Args: password (str): The password to validate. @@ -289,7 +289,7 @@ def validate(self, password, user=None): """ for c in password: if password.count(c) >= self.max_consecutive: - check = c * self.max_consecutive + check = c * (self.max_consecutive + 1) if check in password: raise ValidationError( gettext( diff --git a/django_advanced_password_validation/tests/test_validators.py b/django_advanced_password_validation/tests/test_validators.py index 845d3e3..8a1eec2 100644 --- a/django_advanced_password_validation/tests/test_validators.py +++ b/django_advanced_password_validation/tests/test_validators.py @@ -91,8 +91,9 @@ def test_max_consecutive_characters_validator(): """ validator = MaxConsecutiveCharactersValidator() assert validator.validate("abcdefghij") is None + assert validator.validate("aaabbbccc") is None with pytest.raises(ValidationError) as exc: - validator.validate("aaabbbccc") + validator.validate("aaaabbbccc") assert ( exc.value.message == "Password contains consecutively repeating characters. e.g 'aaa' or '111'" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6909636 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,30 @@ +[build-system] +requires = ["setuptools>=65", "setuptools_scm[toml]>=6.2"] +build-backend = "setuptools.build_meta" + +[project] +name = "django_advanced_password_validation" +description = "Extends Django password validation options in an attempt to keep up with industry standards for strong user passwords." +readme = "README.md" +changelog = "HISTORY.md" +license = {file = "LICENSE.txt"} +keywords = ["django", "password", "validator"] +authors = [ + {name = "Ezra Rice", email = "ezra.j.rice@gmail.com"}, +] +classifiers = [ + "Environment :: Web Environment", + "Framework :: Django", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Internet :: WWW/HTTP :: Dynamic Content", +] +dynamic = ["version"] + +[project.urls] +repository = "https://github.com/ezrajrice/django_advanced_password_validation" \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 459c5a5..0000000 --- a/setup.py +++ /dev/null @@ -1,30 +0,0 @@ -from setuptools import setup, find_packages - -with open("README.md", encoding="utf-8") as readme_file: - README = readme_file.read() - -with open("HISTORY.md", encoding="utf-8") as history_file: - HISTORY = history_file.read() - -setup_args = dict( - name="django_advanced_password_validation", - version="1.1.0", - description="Extends Django password validation options to include minimum uppercase, " - "lowercase, numerical, special characters, maximum length, maximum consecutive " - "characters, maximum consecutively increasing digits, and maximum consecutively " - "decreasing digits.", - long_description_content_type="text/markdown", - long_description=README + "\n\n" + HISTORY, - license="MIT", - packages=find_packages(), - author="Ezra Rice", - author_email="ezra.j.rice@gmail.com", - keywords=["django", "password", "validator"], - url="https://github.com/ezrajrice/django-advanced_password_validation.git", - download_url="https://pypi.org/project/django-advanced_password_validation", -) - -install_requires = ["Django>=1.11"] - -if __name__ == "__main__": - setup(**setup_args, install_requires=install_requires) From 343c58082bab642a8fa0e22958ce63452f2a4756 Mon Sep 17 00:00:00 2001 From: Ezra Rice Date: Thu, 3 Nov 2022 16:17:16 -0400 Subject: [PATCH 2/6] Removed changelog property from toml file --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6909636..236e5c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,6 @@ build-backend = "setuptools.build_meta" name = "django_advanced_password_validation" description = "Extends Django password validation options in an attempt to keep up with industry standards for strong user passwords." readme = "README.md" -changelog = "HISTORY.md" license = {file = "LICENSE.txt"} keywords = ["django", "password", "validator"] authors = [ From 024c47c79197a4ff454743e8c692ca943a6c4755 Mon Sep 17 00:00:00 2001 From: Ezra Rice Date: Thu, 3 Nov 2022 22:28:04 -0400 Subject: [PATCH 3/6] Added more testing Updated black formatting version due to bug in stable --- .github/workflows/test.yaml | 6 +- README.md | 2 +- .../advanced_password_validation.py | 65 +++++------ .../tests/test_validators.py | 101 ++++++++++++++++++ 4 files changed, 130 insertions(+), 44 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 9c2b5aa..2960010 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,6 +1,6 @@ name: test -on: +on: push: branches: - "main" @@ -17,8 +17,8 @@ jobs: name: Lint with Black runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: psf/black@stable + - uses: actions/checkout@v2 + - uses: psf/black@fcf97961061982656a1384ecc1628e217a52a88c test: name: Test django-advanced-password-validation diff --git a/README.md b/README.md index b2c8939..bdb7d88 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # django_advanced_password_validation ![Tests](https://github.com/ezrajrice/django_advanced_password_validation/actions/workflows/test.yml/badge.svg) -[![Coverage Status](https://coveralls.io/repos/github/ezrajrice/django-advanced_password_validation/badge.svg?branch=main)](https://coveralls.io/github/ezrajrice/django-advanced_password_validation?branch=main) +[![Coverage Status](https://coveralls.io/repos/github/ezrajrice/django_advanced_password_validation/badge.svg?branch=main)](https://coveralls.io/github/ezrajrice/django_advanced_password_validation?branch=main) Extends Django password validation options to include minimum uppercase, minimum lowercase, minimum numerical, and minimum special characters. This was created in an attempt to keep up with industry standards for strong user passwords. diff --git a/django_advanced_password_validation/advanced_password_validation.py b/django_advanced_password_validation/advanced_password_validation.py index 6d4a216..8a4d83e 100644 --- a/django_advanced_password_validation/advanced_password_validation.py +++ b/django_advanced_password_validation/advanced_password_validation.py @@ -47,14 +47,11 @@ def get_help_text(self): """ Get the help text for the validator. """ - return ( - _( - f"Your password must contain at least {self.min_digits} number.", - f"Your password must contain at least {self.min_digits} numbers.", - self.min_digits, - ) - % {"min_digits": self.min_digits} - ) + return _( + f"Your password must contain at least {self.min_digits} number.", + f"Your password must contain at least {self.min_digits} numbers.", + self.min_digits, + ) % {"min_digits": self.min_digits} class ContainsUppercaseValidator: @@ -98,14 +95,11 @@ def get_help_text(self): """ Get the help text for the validator. """ - return ( - _( - f"Your password must contain at least {self.min_uppercase} uppercase character.", - f"Your password must contain at least {self.min_uppercase} uppercase characters.", - self.min_uppercase, - ) - % {"min_uppercase": self.min_uppercase} - ) + return _( + f"Your password must contain at least {self.min_uppercase} uppercase character.", + f"Your password must contain at least {self.min_uppercase} uppercase characters.", + self.min_uppercase, + ) % {"min_uppercase": self.min_uppercase} class ContainsLowercaseValidator: @@ -149,14 +143,11 @@ def get_help_text(self): """ Get the help text for the validator. """ - return ( - _( - f"Your password must contain at least {self.min_lowercase} lowercase character.", - f"Your password must contain at least {self.min_lowercase} lowercase characters.", - self.min_lowercase, - ) - % {"min_lowercase": self.min_lowercase} - ) + return _( + f"Your password must contain at least {self.min_lowercase} lowercase character.", + f"Your password must contain at least {self.min_lowercase} lowercase characters.", + self.min_lowercase, + ) % {"min_lowercase": self.min_lowercase} class ContainsSpecialCharactersValidator: @@ -201,14 +192,11 @@ def get_help_text(self): """ Get the help text for the validator. """ - return ( - _( - f"Your password must contain at least {self.min_characters} special character.", - f"Your password must contain at least {self.min_characters} special characters.", - self.min_characters, - ) - % {"min_characters": self.min_characters} - ) + return _( + f"Your password must contain at least {self.min_characters} special character.", + f"Your password must contain at least {self.min_characters} special characters.", + self.min_characters, + ) % {"min_characters": self.min_characters} class MaximumLengthValidator: @@ -251,14 +239,11 @@ def get_help_text(self): """ Get the help text for the validator. """ - return ( - _( - f"Password must contain at maximum {self.max_length} character.", - f"Password must contain at maximum {self.max_length} characters.", - self.max_length, - ) - % {"max_length": self.max_length} - ) + return _( + f"Password must contain at maximum {self.max_length} character.", + f"Password must contain at maximum {self.max_length} characters.", + self.max_length, + ) % {"max_length": self.max_length} class MaxConsecutiveCharactersValidator: diff --git a/django_advanced_password_validation/tests/test_validators.py b/django_advanced_password_validation/tests/test_validators.py index 8a1eec2..2f3b2e4 100644 --- a/django_advanced_password_validation/tests/test_validators.py +++ b/django_advanced_password_validation/tests/test_validators.py @@ -32,6 +32,16 @@ def test_contains_digits_validator(): assert exc.value.message == "Password must contain at least 1 number." +def test_contains_digits_get_help_text(): + """ + Test that the get_help_text string works as expected. + """ + validator = ContainsDigitsValidator(min_digits=1) + assert validator.get_help_text() == "Your password must contain at least 1 number." + validator = ContainsDigitsValidator(min_digits=2) + assert validator.get_help_text() == "Your password must contain at least 2 numbers." + + def test_contains_uppercase_validator(): """ Test that the ContainsUppercaseValidator works as expected and raises a @@ -45,6 +55,22 @@ def test_contains_uppercase_validator(): assert exc.value.message == "Password must contain at least 1 uppercase character." +def test_contains_uppercase_get_help_text(): + """ + Test that the get_help_text string works as expected. + """ + validator = ContainsUppercaseValidator(min_uppercase=1) + assert ( + validator.get_help_text() + == "Your password must contain at least 1 uppercase character." + ) + validator = ContainsUppercaseValidator(min_uppercase=2) + assert ( + validator.get_help_text() + == "Your password must contain at least 2 uppercase character." + ) + + def test_contains_lowercase_validator(): """ Test that the ContainsLowercaseValidator works as expected and raises a @@ -58,6 +84,22 @@ def test_contains_lowercase_validator(): assert exc.value.message == "Password must contain at least 1 lowercase character." +def test_contains_lowercase_get_help_text(): + """ + Test that the get_help_text string works as expected. + """ + validator = ContainsLowercaseValidator(min_lowercase=1) + assert ( + validator.get_help_text() + == "Your password must contain at least 1 lowercase character." + ) + validator = ContainsLowercaseValidator(min_lowercase=2) + assert ( + validator.get_help_text() + == "Your password must contain at least 2 lowercase character." + ) + + def test_contains_special_characters_validator(): """ Test that the ContainsSpecialCharactersValidator works as expected and raises a @@ -71,6 +113,22 @@ def test_contains_special_characters_validator(): assert exc.value.message == "Password must contain at least 1 special character." +def test_contains_special_characters_get_help_text(): + """ + Test that the get_help_text string works as expected. + """ + validator = ContainsSpecialCharactersValidator(min_characters=1) + assert ( + validator.get_help_text() + == "Your password must contain at least 1 special character." + ) + validator = ContainsSpecialCharactersValidator(min_characters=2) + assert ( + validator.get_help_text() + == "Your password must contain at least 2 special character." + ) + + def test_maximum_length_validator(): """ Test that the MaximumLengthValidator works as expected and raises a @@ -83,6 +141,16 @@ def test_maximum_length_validator(): assert exc.value.message == "Password must contain at maximum 10 characters." +def test_maximum_length_get_help_text(): + """ + Test that the get_help_text string works as expected. + """ + validator = MaximumLengthValidator(max_length=12) + assert ( + validator.get_help_text() == "Password must contain at maximum 12 characters." + ) + + def test_max_consecutive_characters_validator(): """ Test that the MaxConsecutiveCharactersValidator works as expected and raises a @@ -100,6 +168,17 @@ def test_max_consecutive_characters_validator(): ) +def test_max_consecutive_characters_get_help_text(): + """ + Test that the get_help_text string works as expected. + """ + validator = MaxConsecutiveCharactersValidator() + assert ( + validator.get_help_text() + == "Password cannot contain consecutively repeating characters. e.g 'aaa' or '111'" + ) + + def test_consecutively_increasing_digit_validator(): """ Test that the ConsecutivelyIncreasingDigitValidator works as expected and raises a @@ -116,6 +195,17 @@ def test_consecutively_increasing_digit_validator(): ) +def test_consecutively_increasing_digit_get_help_text(): + """ + Test that the get_help_text string works as expected. + """ + validator = ConsecutivelyIncreasingDigitValidator(max_consecutive=3) + assert ( + validator.get_help_text() + == "Password cannot contain consecutively increasing digits. e.g '12345'" + ) + + def test_consecutively_decreasing_digit_validator(): """ Test that the ConsecutivelyDecreasingDigitValidator works as expected and raises a @@ -132,6 +222,17 @@ def test_consecutively_decreasing_digit_validator(): ) +def test_consecutively_decreasing_digit_get_help_text(): + """ + Test that the get_help_text string works as expected. + """ + validator = ConsecutivelyDecreasingDigitValidator(max_consecutive=3) + assert ( + validator.get_help_text() + == "Password cannot contain consecutively decreasing digits. e.g '54321'" + ) + + def test_valid_password(): """ Test that the validate_password function works as expected. From b473e65244fd7ae943a0bca5b5f9efa58dac6259 Mon Sep 17 00:00:00 2001 From: Ezra Rice Date: Thu, 3 Nov 2022 22:33:01 -0400 Subject: [PATCH 4/6] Fixed plurality bug in help text tests --- .../tests/test_validators.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/django_advanced_password_validation/tests/test_validators.py b/django_advanced_password_validation/tests/test_validators.py index 2f3b2e4..3b11191 100644 --- a/django_advanced_password_validation/tests/test_validators.py +++ b/django_advanced_password_validation/tests/test_validators.py @@ -67,7 +67,7 @@ def test_contains_uppercase_get_help_text(): validator = ContainsUppercaseValidator(min_uppercase=2) assert ( validator.get_help_text() - == "Your password must contain at least 2 uppercase character." + == "Your password must contain at least 2 uppercase characters." ) @@ -96,7 +96,7 @@ def test_contains_lowercase_get_help_text(): validator = ContainsLowercaseValidator(min_lowercase=2) assert ( validator.get_help_text() - == "Your password must contain at least 2 lowercase character." + == "Your password must contain at least 2 lowercase characters." ) @@ -125,7 +125,7 @@ def test_contains_special_characters_get_help_text(): validator = ContainsSpecialCharactersValidator(min_characters=2) assert ( validator.get_help_text() - == "Your password must contain at least 2 special character." + == "Your password must contain at least 2 special characters." ) From e6e42681cc5187426b38870ab2c04f932235a78f Mon Sep 17 00:00:00 2001 From: Ezra Rice Date: Thu, 3 Nov 2022 22:48:35 -0400 Subject: [PATCH 5/6] Fixed bug with maximum increasing/decreasing digits to trigger ValidationError when exceeded only --- README.md | 2 +- .../advanced_password_validation.py | 4 ++-- .../tests/test_validators.py | 6 ++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bdb7d88..f464cce 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # django_advanced_password_validation -![Tests](https://github.com/ezrajrice/django_advanced_password_validation/actions/workflows/test.yml/badge.svg) +[![test](https://github.com/ezrajrice/django_advanced_password_validation/actions/workflows/test.yaml/badge.svg?branch=main)](https://github.com/ezrajrice/django_advanced_password_validation/actions/workflows/test.yaml) [![Coverage Status](https://coveralls.io/repos/github/ezrajrice/django_advanced_password_validation/badge.svg?branch=main)](https://coveralls.io/github/ezrajrice/django_advanced_password_validation?branch=main) Extends Django password validation options to include minimum uppercase, minimum lowercase, minimum numerical, and minimum special characters. This was created in an attempt to keep up with industry standards for strong user passwords. diff --git a/django_advanced_password_validation/advanced_password_validation.py b/django_advanced_password_validation/advanced_password_validation.py index 8a4d83e..a9407f3 100644 --- a/django_advanced_password_validation/advanced_password_validation.py +++ b/django_advanced_password_validation/advanced_password_validation.py @@ -330,7 +330,7 @@ def validate(self, password, user=None): count += 1 digit += 1 - while count >= self.max_consecutive: + while count > self.max_consecutive: raise ValidationError( gettext( "Password contains consecutively increasing digits. " @@ -387,7 +387,7 @@ def validate(self, password, user=None): count += 1 digit -= 1 - while count >= self.max_consecutive: + while count > self.max_consecutive: raise ValidationError( gettext( "Password contains consecutively decreasing digits. " diff --git a/django_advanced_password_validation/tests/test_validators.py b/django_advanced_password_validation/tests/test_validators.py index 3b11191..ee04900 100644 --- a/django_advanced_password_validation/tests/test_validators.py +++ b/django_advanced_password_validation/tests/test_validators.py @@ -185,8 +185,9 @@ def test_consecutively_increasing_digit_validator(): ValidationError when the password contains more than the maximum number of consecutive characters. """ - validator = ConsecutivelyIncreasingDigitValidator() + validator = ConsecutivelyIncreasingDigitValidator(max_consecutive=3) assert validator.validate("abcdefghij") is None + assert validator.validate("abcdefg123") is None with pytest.raises(ValidationError) as exc: validator.validate("1234567890") assert ( @@ -212,8 +213,9 @@ def test_consecutively_decreasing_digit_validator(): ValidationError when the password contains more than the maximum number of consecutive characters. """ - validator = ConsecutivelyDecreasingDigitValidator() + validator = ConsecutivelyDecreasingDigitValidator(max_consecutive=3) assert validator.validate("abcdefghij") is None + assert validator.validate("abcdefg321") is None with pytest.raises(ValidationError) as exc: validator.validate("9876543210") assert ( From b1747a723cc574790f56f07b95ab88ae9966973e Mon Sep 17 00:00:00 2001 From: Ezra Rice Date: Thu, 3 Nov 2022 23:19:15 -0400 Subject: [PATCH 6/6] Rollback last commit because I'm an idiot. xD --- .../advanced_password_validation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django_advanced_password_validation/advanced_password_validation.py b/django_advanced_password_validation/advanced_password_validation.py index a9407f3..8a4d83e 100644 --- a/django_advanced_password_validation/advanced_password_validation.py +++ b/django_advanced_password_validation/advanced_password_validation.py @@ -330,7 +330,7 @@ def validate(self, password, user=None): count += 1 digit += 1 - while count > self.max_consecutive: + while count >= self.max_consecutive: raise ValidationError( gettext( "Password contains consecutively increasing digits. " @@ -387,7 +387,7 @@ def validate(self, password, user=None): count += 1 digit -= 1 - while count > self.max_consecutive: + while count >= self.max_consecutive: raise ValidationError( gettext( "Password contains consecutively decreasing digits. "