From f387e68ed6063bc5bd1fd437b0cce88ae9d609e6 Mon Sep 17 00:00:00 2001 From: Sean Meyer Date: Wed, 2 Mar 2022 22:25:43 +0800 Subject: [PATCH 01/12] update travis and tox for latest versions --- .travis.yml | 24 +++++++++++------------- tox.ini | 20 +++++++++----------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8df4f7c..26a8783 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,19 +11,17 @@ before_install: - pyenv global system 3.5.4 env: - - TOX_ENV=py36-django-19 - - TOX_ENV=py35-django-19 - - TOX_ENV=py27-django-19 - - TOX_ENV=py36-django-110 - - TOX_ENV=py35-django-110 - - TOX_ENV=py27-django-110 - - TOX_ENV=py36-django-111 - - TOX_ENV=py35-django-111 - - TOX_ENV=py27-django-111 - - TOX_ENV=py36-django-20 - - TOX_ENV=py35-django-20 - - TOX_ENV=py36-django-21 - - TOX_ENV=py35-django-21 + - TOX_ENV=py35-django-22 + - TOX_ENV=py36-django-22 + - TOX_ENV=py37-django-22 + - TOX_ENV=py36-django-32 + - TOX_ENV=py37-django-32 + - TOX_ENV=py38-django-32 + - TOX_ENV=py39-django-32 + - TOX_ENV=py310-django-32 + - TOX_ENV=py38-django-40 + - TOX_ENV=py39-django-40 + - TOX_ENV=py310-django-40 matrix: fast_finish: true diff --git a/tox.ini b/tox.ini index 1ee6026..855399c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,8 @@ [tox] envlist = - {py27,py35,py36,py37}-django-19 - {py27,py35,py36,py37}-django-110 - {py27,py35,py36,py37}-django-111 - {py35,py36,py37}-django-20 - {py35,py36,py37}-django-21 + {py35,py36,py37}-django-22 + {py36,py37,py38,py39,py310}-django-32 + {py38,py39,py310}-django-40 [testenv] setenv = @@ -13,15 +11,15 @@ setenv = commands = coverage run --source django_pwned_passwords runtests.py deps = - django-19: Django>=1.9,<1.10 - django-110: Django>=1.10,<1.11 - django-111: Django>=1.11 - django-20: Django>=2.0,<2.1 - django-21: Django>=2.1,<3.0 + django-40: Django>=4.0,<4.1 + django-32: Django>=3.2,<4.0 + django-22: Django>=2.2,<3.0 -r{toxinidir}/requirements_test.txt basepython = + py310: python3.10 + py39: python3.9 + py38: python3.8 py37: python3.7 py36: python3.6 py35: python3.5 - py27: python2.7 From 7d22323b299a64eb4c6bc0917b5ad456c9e50d95 Mon Sep 17 00:00:00 2001 From: Sean Meyer Date: Wed, 2 Mar 2022 22:25:57 +0800 Subject: [PATCH 02/12] update requirements to latest versions --- requirements.txt | 3 ++- requirements_dev.txt | 4 ++-- requirements_test.txt | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index c20f36f..de9e83a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -requests==2.20.0 +django>=2.2 +requests==2.27.1 diff --git a/requirements_dev.txt b/requirements_dev.txt index 13702ae..6b2f585 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,2 +1,2 @@ -bumpversion==0.5.3 -wheel==0.29.0 +bumpversion==0.6.0 +wheel==0.37.1 diff --git a/requirements_test.txt b/requirements_test.txt index 7939865..2e7ff4b 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,5 +1,5 @@ -coverage==4.3.4 -requests-mock==1.3.0 +coverage==6.3.2 +requests-mock==1.9.3 mock>=1.0.1 flake8>=2.1.0 tox>=1.7.0 From 12eddad41c0ee00f1749f490ceedfaf46e28dc8e Mon Sep 17 00:00:00 2001 From: Sean Meyer Date: Wed, 2 Mar 2022 22:26:36 +0800 Subject: [PATCH 03/12] remove old imports --- tests/settings.py | 2 -- tests/urls.py | 3 --- 2 files changed, 5 deletions(-) diff --git a/tests/settings.py b/tests/settings.py index 9581e70..bacbfc9 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -from __future__ import unicode_literals, absolute_import - import django DEBUG = True diff --git a/tests/urls.py b/tests/urls.py index 649ad1a..01d56ef 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -1,6 +1,3 @@ # -*- coding: utf-8 -from __future__ import unicode_literals, absolute_import - -from django.conf.urls import url, include urlpatterns = [] From 559c88f190ee00f925b95b19e971e7be6a43d55b Mon Sep 17 00:00:00 2001 From: Sean Meyer Date: Wed, 2 Mar 2022 22:27:13 +0800 Subject: [PATCH 04/12] fix to work with Django 4.0 --- django_pwned_passwords/password_validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_pwned_passwords/password_validation.py b/django_pwned_passwords/password_validation.py index 82c7fb2..a257572 100644 --- a/django_pwned_passwords/password_validation.py +++ b/django_pwned_passwords/password_validation.py @@ -1,6 +1,6 @@ from django.core.exceptions import ValidationError from django.conf import settings -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ import hashlib import requests From 7b543139c9f1e130800237bd5e61ac0a4a5ae189 Mon Sep 17 00:00:00 2001 From: Sean Meyer Date: Wed, 2 Mar 2022 22:27:35 +0800 Subject: [PATCH 05/12] format with black --- django_pwned_passwords/__init__.py | 2 +- django_pwned_passwords/apps.py | 2 +- django_pwned_passwords/password_validation.py | 45 +++--- docs/conf.py | 148 ++++++++++-------- example/example/settings.py | 80 +++++----- example/example/urls.py | 17 +- runtests.py | 6 +- setup.py | 64 ++++---- tests/settings.py | 4 +- tests/test_password_validation.py | 111 +++++++------ 10 files changed, 255 insertions(+), 224 deletions(-) diff --git a/django_pwned_passwords/__init__.py b/django_pwned_passwords/__init__.py index fa721b4..7039708 100644 --- a/django_pwned_passwords/__init__.py +++ b/django_pwned_passwords/__init__.py @@ -1 +1 @@ -__version__ = '4.1.0' +__version__ = "4.1.0" diff --git a/django_pwned_passwords/apps.py b/django_pwned_passwords/apps.py index be9d09f..35f1b40 100644 --- a/django_pwned_passwords/apps.py +++ b/django_pwned_passwords/apps.py @@ -3,4 +3,4 @@ class DjangoPwnedPasswordsConfig(AppConfig): - name = 'django_pwned_passwords' + name = "django_pwned_passwords" diff --git a/django_pwned_passwords/password_validation.py b/django_pwned_passwords/password_validation.py index a257572..b18f05d 100644 --- a/django_pwned_passwords/password_validation.py +++ b/django_pwned_passwords/password_validation.py @@ -19,17 +19,30 @@ class PWNEDPasswordValidator(object): def __init__(self, min_length=8): self.min_length = min_length - self.timeout = getattr(settings, 'PWNED_VALIDATOR_TIMEOUT', 2) - self.fail_safe = getattr(settings, 'PWNED_VALIDATOR_FAIL_SAFE', True) - self.min_breaches = getattr(settings, 'PWNED_VALIDATOR_MINIMUM_BREACHES', 1) - self.url = getattr(settings, 'PWNED_VALIDATOR_URL', - 'https://api.pwnedpasswords.com/range/{short_hash}') - self.error_msg = getattr(settings, 'PWNED_VALIDATOR_ERROR', - "Your password was determined to have been involved in a major security breach.") - self.error_fail_msg = getattr(settings, 'PWNED_VALIDATOR_ERROR_FAIL', - "We could not validate the safety of this password. This does not mean the password is invalid. Please try again later.") - self.help_text = getattr(settings, 'PWNED_VALIDATOR_HELP_TEXT', - "Your password must not have been detected in a major security breach.") + self.timeout = getattr(settings, "PWNED_VALIDATOR_TIMEOUT", 2) + self.fail_safe = getattr(settings, "PWNED_VALIDATOR_FAIL_SAFE", True) + self.min_breaches = getattr(settings, "PWNED_VALIDATOR_MINIMUM_BREACHES", 1) + self.url = getattr( + settings, + "PWNED_VALIDATOR_URL", + "https://api.pwnedpasswords.com/range/{short_hash}", + ) + self.error_msg = getattr( + settings, + "PWNED_VALIDATOR_ERROR", + "Your password was determined to have been involved in a major security breach.", + ) + self.error_fail_msg = getattr( + settings, + "PWNED_VALIDATOR_ERROR_FAIL", + "We could not validate the safety of this password. " + "This does not mean the password is invalid. Please try again later.", + ) + self.help_text = getattr( + settings, + "PWNED_VALIDATOR_HELP_TEXT", + "Your password must not have been detected in a major security breach.", + ) def validate(self, password, user=None): if not self.check_valid(password): @@ -51,7 +64,7 @@ def check_valid(self, password): INVALID = False try: - p_hash = hashlib.sha1(password.encode('utf-8')).hexdigest().upper() + p_hash = hashlib.sha1(password.encode("utf-8")).hexdigest().upper() response = requests.get(self.get_url(p_hash[0:5]), timeout=self.timeout) if self.get_breach_count(p_hash, response.text) >= self.min_breaches: @@ -72,14 +85,10 @@ def check_valid(self, password): raise ValidationError(self.error_fail_msg) def get_url(self, short_hash): - return self.url.format( - short_hash = short_hash - ) + return self.url.format(short_hash=short_hash) def get_help_text(self): - return _( - self.help_text - ) + return _(self.help_text) @staticmethod def get_breach_count(p_hash, response_text): diff --git a/docs/conf.py b/docs/conf.py index 8a5d5a5..c7e6cea 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,7 +16,7 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath('.')) cwd = os.getcwd() parent = os.path.dirname(cwd) @@ -27,27 +27,27 @@ # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] +extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode"] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'django-pwned-passwords' -copyright = u'2017, Jamie Counsell' +project = "django-pwned-passwords" +copyright = "2017, Jamie Counsell" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -60,161 +60,164 @@ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +# keep_warnings = False # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = "default" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'django-pwned-passwordsdoc' +htmlhelp_basename = "django-pwned-passwordsdoc" # -- Options for LaTeX output -------------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'django-pwned-passwords.tex', u'django-pwned-passwords Documentation', - u'Jamie Counsell', 'manual'), + ( + "index", + "django-pwned-passwords.tex", + "django-pwned-passwords Documentation", + "Jamie Counsell", + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output -------------------------------------------- @@ -222,12 +225,17 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'django-pwned-passwords', u'django-pwned-passwords Documentation', - [u'Jamie Counsell'], 1) + ( + "index", + "django-pwned-passwords", + "django-pwned-passwords Documentation", + ["Jamie Counsell"], + 1, + ) ] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ @@ -236,19 +244,25 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'django-pwned-passwords', u'django-pwned-passwords Documentation', - u'Jamie Counsell', 'django-pwned-passwords', 'One line description of project.', - 'Miscellaneous'), + ( + "index", + "django-pwned-passwords", + "django-pwned-passwords Documentation", + "Jamie Counsell", + "django-pwned-passwords", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False diff --git a/example/example/settings.py b/example/example/settings.py index 071a53a..1d2b6db 100644 --- a/example/example/settings.py +++ b/example/example/settings.py @@ -29,57 +29,57 @@ # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - - 'django_pwned_passwords', - + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django_pwned_passwords", # if your app has other dependencies that need to be added to the site # they should be added here ] MIDDLEWARE_CLASSES = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.auth.middleware.SessionAuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'example.urls' +ROOT_URLCONF = "example.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(BASE_DIR, 'templates'), ], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [ + os.path.join(BASE_DIR, "templates"), + ], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -WSGI_APPLICATION = 'example.wsgi.application' +WSGI_APPLICATION = "example.wsgi.application" # Database # https://docs.djangoproject.com/en/1.9/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "db.sqlite3"), } } @@ -88,28 +88,26 @@ AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, - { - 'NAME': 'django_pwned_passwords.password_validation.PWNEDPasswordValidator' - } + {"NAME": "django_pwned_passwords.password_validation.PWNEDPasswordValidator"}, ] # Internationalization # https://docs.djangoproject.com/en/1.9/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -120,4 +118,4 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.9/howto/static-files/ -STATIC_URL = '/static/' +STATIC_URL = "/static/" diff --git a/example/example/urls.py b/example/example/urls.py index e5d029a..1a77621 100644 --- a/example/example/urls.py +++ b/example/example/urls.py @@ -4,13 +4,14 @@ from django.views.generic import RedirectView urlpatterns = [ - url('^$', RedirectView.as_view(pattern_name = 'register')), - url('^register/', CreateView.as_view( - template_name='register.html', - form_class=UserCreationForm, - success_url='/' - ), name = 'register'), - url('^accounts/', include('django.contrib.auth.urls')), - + url("^$", RedirectView.as_view(pattern_name="register")), + url( + "^register/", + CreateView.as_view( + template_name="register.html", form_class=UserCreationForm, success_url="/" + ), + name="register", + ), + url("^accounts/", include("django.contrib.auth.urls")), # rest of your URLs as normal ] diff --git a/runtests.py b/runtests.py index a68cf32..035a375 100644 --- a/runtests.py +++ b/runtests.py @@ -12,9 +12,9 @@ def run_tests(*test_args): if not test_args: - test_args = ['tests'] + test_args = ["tests"] - os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings' + os.environ["DJANGO_SETTINGS_MODULE"] = "tests.settings" django.setup() TestRunner = get_runner(settings) test_runner = TestRunner() @@ -22,5 +22,5 @@ def run_tests(*test_args): sys.exit(bool(failures)) -if __name__ == '__main__': +if __name__ == "__main__": run_tests(*sys.argv[1:]) diff --git a/setup.py b/setup.py index a4c840e..c0608d9 100755 --- a/setup.py +++ b/setup.py @@ -14,68 +14,68 @@ def get_version(*file_paths): """Retrieves the version from django_pwned_passwords/__init__.py""" filename = os.path.join(os.path.dirname(__file__), *file_paths) version_file = open(filename).read() - version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", - version_file, re.M) + version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M) if version_match: return version_match.group(1) - raise RuntimeError('Unable to find version string.') + raise RuntimeError("Unable to find version string.") version = get_version("django_pwned_passwords", "__init__.py") -if sys.argv[-1] == 'publish': +if sys.argv[-1] == "publish": try: import wheel + print("Wheel version: ", wheel.__version__) except ImportError: print('Wheel library missing. Please run "pip install wheel"') sys.exit() - os.system('python setup.py sdist upload') - os.system('python setup.py bdist_wheel upload') + os.system("python setup.py sdist upload") + os.system("python setup.py bdist_wheel upload") sys.exit() -if sys.argv[-1] == 'tag': +if sys.argv[-1] == "tag": print("Tagging the version on git:") os.system("git tag -a v%s -m 'version %s'" % (version, version)) os.system("git push --tags") sys.exit() -readme = open('README.rst').read() -history = open('HISTORY.rst').read().replace('.. :changelog:', '') +readme = open("README.rst").read() +history = open("HISTORY.rst").read().replace(".. :changelog:", "") setup( - name='django-pwned-passwords', + name="django-pwned-passwords", version=version, description="""A Django password validator that checks Troy Hunt's PWNED Passwords API to see if a password has been involved in a major security breach before.""", - long_description=readme + '\n\n' + history, - author='Jamie Counsell', - author_email='jamiecounsell@me.com', - url='https://github.com/jamiecounsell/django-pwned-passwords', + long_description=readme + "\n\n" + history, + author="Jamie Counsell", + author_email="jamiecounsell@me.com", + url="https://github.com/jamiecounsell/django-pwned-passwords", packages=[ - 'django_pwned_passwords', + "django_pwned_passwords", ], include_package_data=True, install_requires=[], license="MIT", zip_safe=False, - keywords='django-pwned-passwords', + keywords="django-pwned-passwords", classifiers=[ - 'Development Status :: 3 - Alpha', - 'Framework :: Django', - 'Framework :: Django :: 1.9', - 'Framework :: Django :: 1.10', - 'Framework :: Django :: 1.11', - 'Framework :: Django :: 2.0', - 'Framework :: Django :: 2.1', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Natural Language :: English', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', + "Development Status :: 3 - Alpha", + "Framework :: Django", + "Framework :: Django :: 1.9", + "Framework :: Django :: 1.10", + "Framework :: Django :: 1.11", + "Framework :: Django :: 2.0", + "Framework :: Django :: 2.1", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Natural Language :: English", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", ], ) diff --git a/tests/settings.py b/tests/settings.py index bacbfc9..aae0829 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -24,9 +24,7 @@ ] AUTH_PASSWORD_VALIDATORS = [ - { - 'NAME': 'django_pwned_passwords.password_validation.PWNEDPasswordValidator' - } + {"NAME": "django_pwned_passwords.password_validation.PWNEDPasswordValidator"} ] SITE_ID = 1 diff --git a/tests/test_password_validation.py b/tests/test_password_validation.py index 93b07fc..c9188b3 100644 --- a/tests/test_password_validation.py +++ b/tests/test_password_validation.py @@ -18,9 +18,8 @@ class TestPasswordValidation(TestCase): - def get_hash(self, password): - return hashlib.sha1(password.encode('utf-8')).hexdigest().upper() + return hashlib.sha1(password.encode("utf-8")).hexdigest().upper() @requests_mock.mock() def test_pwned_password_fails(self, m): @@ -29,14 +28,16 @@ def test_pwned_password_fails(self, m): p_hash = self.get_hash(password) short_hash = p_hash.upper()[:5] - m.get(validator.url.format( - short_hash = short_hash - ), status_code = 200, text = p_hash[5:] + ":1") + m.get( + validator.url.format(short_hash=short_hash), + status_code=200, + text=p_hash[5:] + ":1", + ) with self.assertRaises(ValidationError): validator.validate(password) - @override_settings(PWNED_VALIDATOR_MINIMUM_BREACHES = 2) + @override_settings(PWNED_VALIDATOR_MINIMUM_BREACHES=2) @requests_mock.mock() def test_above_limit_password_fails(self, m): validator = PWNEDPasswordValidator() @@ -44,14 +45,16 @@ def test_above_limit_password_fails(self, m): p_hash = self.get_hash(password) short_hash = p_hash.upper()[:5] - m.get(validator.url.format( - short_hash = short_hash - ), status_code = 200, text = p_hash[5:] + ":3") + m.get( + validator.url.format(short_hash=short_hash), + status_code=200, + text=p_hash[5:] + ":3", + ) with self.assertRaises(ValidationError): validator.validate(password) - @override_settings(PWNED_VALIDATOR_MINIMUM_BREACHES = 10) + @override_settings(PWNED_VALIDATOR_MINIMUM_BREACHES=10) @requests_mock.mock() def test_below_limit_password_passes(self, m): validator = PWNEDPasswordValidator() @@ -59,9 +62,11 @@ def test_below_limit_password_passes(self, m): p_hash = self.get_hash(password) short_hash = p_hash.upper()[:5] - m.get(validator.url.format( - short_hash = short_hash - ), status_code = 200, text = p_hash[5:] + ":9") + m.get( + validator.url.format(short_hash=short_hash), + status_code=200, + text=p_hash[5:] + ":9", + ) try: validator.validate(password) @@ -75,9 +80,7 @@ def test_zero_results_succeeds(self, m): p_hash = self.get_hash(password) short_hash = p_hash.upper()[:5] - m.get(validator.url.format( - short_hash = short_hash - ), status_code = 404) + m.get(validator.url.format(short_hash=short_hash), status_code=404) try: validator.validate(password) @@ -91,9 +94,11 @@ def test_unpwned_password_succeeds(self, m): p_hash = self.get_hash(password) short_hash = p_hash.upper()[:5] - m.get(validator.url.format( - short_hash = short_hash - ), status_code = 200, text = "07A60BA364011AACB2F0470CC983FCA6AF5:1") + m.get( + validator.url.format(short_hash=short_hash), + status_code=200, + text="07A60BA364011AACB2F0470CC983FCA6AF5:1", + ) try: validator.validate(password) @@ -107,9 +112,7 @@ def test_fail_safe_ignores_rate_limit(self, m): p_hash = self.get_hash(password) short_hash = p_hash.upper()[:5] - m.get(validator.url.format( - short_hash = short_hash - ), status_code = 429) + m.get(validator.url.format(short_hash=short_hash), status_code=429) try: validator.validate(password) @@ -123,9 +126,10 @@ def test_custom_fail_safe_ignores_exceptions(self, m): p_hash = self.get_hash(password) short_hash = p_hash.upper()[:5] - m.get(validator.url.format( - short_hash = short_hash - ), exc = requests.exceptions.RequestException) + m.get( + validator.url.format(short_hash=short_hash), + exc=requests.exceptions.RequestException, + ) try: validator.validate(password) @@ -133,16 +137,14 @@ def test_custom_fail_safe_ignores_exceptions(self, m): self.fail("ValidationError was raised for valid password") @requests_mock.mock() - @override_settings(PWNED_VALIDATOR_FAIL_SAFE = False) + @override_settings(PWNED_VALIDATOR_FAIL_SAFE=False) def test_not_fail_safe_fails_on_rate_limit(self, m): validator = PWNEDPasswordValidator() password = "doesn'tmatter" p_hash = self.get_hash(password) short_hash = p_hash.upper()[:5] - m.get(validator.url.format( - short_hash = short_hash - ), status_code = 429) + m.get(validator.url.format(short_hash=short_hash), status_code=429) with self.assertRaises(ValidationError): validator.validate(password) @@ -155,8 +157,11 @@ def test_not_fail_safe_pwned_password_fails(self, m): p_hash = self.get_hash(password) short_hash = p_hash.upper()[:5] - m.get(validator.url.format(short_hash=short_hash), status_code=200, - text=p_hash[5:] + ":1") + m.get( + validator.url.format(short_hash=short_hash), + status_code=200, + text=p_hash[5:] + ":1", + ) with self.assertRaises(ValidationError): validator.validate(password) @@ -195,8 +200,11 @@ def test_not_fail_safe_unpwned_password_succeeds(self, m): p_hash = self.get_hash(password) short_hash = p_hash.upper()[:5] - m.get(validator.url.format(short_hash=short_hash), status_code=200, - text=self.get_hash("notsecret")[5:] + ":1") + m.get( + validator.url.format(short_hash=short_hash), + status_code=200, + text=self.get_hash("notsecret")[5:] + ":1", + ) try: validator.validate(password) @@ -204,16 +212,18 @@ def test_not_fail_safe_unpwned_password_succeeds(self, m): self.fail("ValidationError was raised for valid password") @requests_mock.mock() - @override_settings(PWNED_VALIDATOR_ERROR = "failure") + @override_settings(PWNED_VALIDATOR_ERROR="failure") def test_custom_error_message(self, m): validator = PWNEDPasswordValidator() password = "doesn'tmatter" p_hash = self.get_hash(password) short_hash = p_hash.upper()[:5] - m.get(validator.url.format( - short_hash = short_hash - ), status_code = 200, text = p_hash[5:] + ":1") + m.get( + validator.url.format(short_hash=short_hash), + status_code=200, + text=p_hash[5:] + ":1", + ) try: validator.validate(password) @@ -226,17 +236,15 @@ def test_custom_error_message(self, m): @requests_mock.mock() @override_settings( - PWNED_VALIDATOR_ERROR_FAIL = "failure", - PWNED_VALIDATOR_FAIL_SAFE = False) + PWNED_VALIDATOR_ERROR_FAIL="failure", PWNED_VALIDATOR_FAIL_SAFE=False + ) def test_custom_fail_message(self, m): validator = PWNEDPasswordValidator() password = "doesn'tmatter" p_hash = self.get_hash(password) short_hash = p_hash.upper()[:5] - m.get(validator.url.format( - short_hash = short_hash - ), status_code = 500) + m.get(validator.url.format(short_hash=short_hash), status_code=500) try: validator.validate(password) @@ -247,17 +255,18 @@ def test_custom_fail_message(self, m): @requests_mock.mock() @override_settings( - PWNED_VALIDATOR_ERROR_FAIL = "failure", - PWNED_VALIDATOR_FAIL_SAFE = False) + PWNED_VALIDATOR_ERROR_FAIL="failure", PWNED_VALIDATOR_FAIL_SAFE=False + ) def test_custom_fail_message_timeout(self, m): validator = PWNEDPasswordValidator() password = "doesn'tmatter" p_hash = self.get_hash(password) short_hash = p_hash.upper()[:5] - m.get(validator.url.format( - short_hash = short_hash - ), exc = requests.exceptions.ConnectTimeout) + m.get( + validator.url.format(short_hash=short_hash), + exc=requests.exceptions.ConnectTimeout, + ) try: validator.validate(password) @@ -283,9 +292,11 @@ def test_pwned_password_bad_response(self, m): p_hash = self.get_hash(password) short_hash = p_hash.upper()[:5] - m.get(validator.url.format( - short_hash = short_hash - ), status_code = 200, text ="random test with no colons") + m.get( + validator.url.format(short_hash=short_hash), + status_code=200, + text="random test with no colons", + ) with self.assertRaises(ValidationError): validator.validate(password) From 0adbc152afa88534d78517653aee73f7ae34a067 Mon Sep 17 00:00:00 2001 From: Sean Meyer Date: Wed, 2 Mar 2022 22:29:33 +0800 Subject: [PATCH 06/12] update readme to accurate versions --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 2246adc..1ae5aed 100644 --- a/README.rst +++ b/README.rst @@ -23,8 +23,8 @@ The full documentation is at https://django-pwned-passwords.readthedocs.io. Requirements ------------ -* Django [1.9, 2.1] -* Python 2.7, [3.5, 3.6, 3.7] +* Django [2.2, 3.2, 4.0] +* Python 3.5, 3.6, 3.7, 3.8, 3.9, 3.10 Quickstart ---------- From 05e24f1d1699afc79e45c1274e1032626df8fdbc Mon Sep 17 00:00:00 2001 From: Sean Meyer Date: Wed, 2 Mar 2022 22:32:04 +0800 Subject: [PATCH 07/12] bump version to 5.0.0 --- django_pwned_passwords/__init__.py | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/django_pwned_passwords/__init__.py b/django_pwned_passwords/__init__.py index 7039708..ba7be38 100644 --- a/django_pwned_passwords/__init__.py +++ b/django_pwned_passwords/__init__.py @@ -1 +1 @@ -__version__ = "4.1.0" +__version__ = "5.0.0" diff --git a/setup.cfg b/setup.cfg index bde8bf6..409450c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 4.1.0 +current_version = 5.0.0 commit = True tag = True From 9ad863ec0450ba57f938c58d332e92eaf1f7540e Mon Sep 17 00:00:00 2001 From: Sean Meyer Date: Sat, 28 Jan 2023 22:16:04 +0800 Subject: [PATCH 08/12] update versions --- .travis.yml | 5 ++--- requirements.txt | 4 ++-- requirements_dev.txt | 2 +- requirements_test.txt | 12 ++++++------ setup.py | 17 ++++++++--------- tox.ini | 8 ++++---- 6 files changed, 23 insertions(+), 25 deletions(-) diff --git a/.travis.yml b/.travis.yml index 26a8783..889cdbb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,9 +11,6 @@ before_install: - pyenv global system 3.5.4 env: - - TOX_ENV=py35-django-22 - - TOX_ENV=py36-django-22 - - TOX_ENV=py37-django-22 - TOX_ENV=py36-django-32 - TOX_ENV=py37-django-32 - TOX_ENV=py38-django-32 @@ -22,6 +19,8 @@ env: - TOX_ENV=py38-django-40 - TOX_ENV=py39-django-40 - TOX_ENV=py310-django-40 + - TOX_ENV=py310-django-41 + - TOX_ENV=py311-django-41 matrix: fast_finish: true diff --git a/requirements.txt b/requirements.txt index de9e83a..55a6106 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -django>=2.2 -requests==2.27.1 +django>=3.2 +requests==2.28.2 diff --git a/requirements_dev.txt b/requirements_dev.txt index 6b2f585..2e66fe3 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,2 +1,2 @@ bumpversion==0.6.0 -wheel==0.37.1 +wheel==0.38.4 diff --git a/requirements_test.txt b/requirements_test.txt index 2e7ff4b..ce5c07b 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ -coverage==6.3.2 -requests-mock==1.9.3 -mock>=1.0.1 -flake8>=2.1.0 -tox>=1.7.0 -codecov>=2.0.0 +coverage==7.1.0 +requests-mock==1.10.0 +mock>=5.0.1 +flake8>=6.0.0 +tox>=4.4.2 +codecov>=2.1.12 diff --git a/setup.py b/setup.py index c0608d9..5e1d037 100755 --- a/setup.py +++ b/setup.py @@ -56,26 +56,25 @@ def get_version(*file_paths): "django_pwned_passwords", ], include_package_data=True, - install_requires=[], + install_requires=["requests"], license="MIT", zip_safe=False, keywords="django-pwned-passwords", classifiers=[ "Development Status :: 3 - Alpha", "Framework :: Django", - "Framework :: Django :: 1.9", - "Framework :: Django :: 1.10", - "Framework :: Django :: 1.11", - "Framework :: Django :: 2.0", - "Framework :: Django :: 2.1", + "Framework :: Django :: 3.2", + "Framework :: Django :: 4.0", + "Framework :: Django :: 4.1", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Natural Language :: English", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", ], ) diff --git a/tox.ini b/tox.ini index 855399c..22f45ce 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,8 @@ [tox] envlist = - {py35,py36,py37}-django-22 {py36,py37,py38,py39,py310}-django-32 {py38,py39,py310}-django-40 + {py310,py311}-django-41 [testenv] setenv = @@ -11,15 +11,15 @@ setenv = commands = coverage run --source django_pwned_passwords runtests.py deps = + django-41: Django>=4.1,<4.2 django-40: Django>=4.0,<4.1 django-32: Django>=3.2,<4.0 - django-22: Django>=2.2,<3.0 -r{toxinidir}/requirements_test.txt basepython = + py311: python3.11 py310: python3.10 py39: python3.9 py38: python3.8 py37: python3.7 - py36: python3.6 - py35: python3.5 + py36: python3.6 \ No newline at end of file From 59fe441c5d97e5c39ea793d9137db248a134fcf4 Mon Sep 17 00:00:00 2001 From: Sean Meyer Date: Sat, 28 Jan 2023 22:16:06 +0800 Subject: [PATCH 09/12] =?UTF-8?q?Bump=20version:=205.0.0=20=E2=86=92=205.1?= =?UTF-8?q?.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- django_pwned_passwords/__init__.py | 2 +- setup.cfg | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/django_pwned_passwords/__init__.py b/django_pwned_passwords/__init__.py index ba7be38..0d72820 100644 --- a/django_pwned_passwords/__init__.py +++ b/django_pwned_passwords/__init__.py @@ -1 +1 @@ -__version__ = "5.0.0" +__version__ = "5.1.0" diff --git a/setup.cfg b/setup.cfg index 409450c..c924e8b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 5.0.0 +current_version = 5.1.0 commit = True tag = True @@ -18,4 +18,3 @@ exclude = build, dist max-line-length = 119 - From 58c7b832df7360a21fd8edeaaf9f897c7517baf1 Mon Sep 17 00:00:00 2001 From: Sean Meyer Date: Sat, 28 Jan 2023 22:17:30 +0800 Subject: [PATCH 10/12] update readme --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 1ae5aed..63e40fb 100644 --- a/README.rst +++ b/README.rst @@ -23,8 +23,8 @@ The full documentation is at https://django-pwned-passwords.readthedocs.io. Requirements ------------ -* Django [2.2, 3.2, 4.0] -* Python 3.5, 3.6, 3.7, 3.8, 3.9, 3.10 +* Django [3.2, 4.0, 4.1] +* Python 3.6, 3.7, 3.8, 3.9, 3.10, 3.11 Quickstart ---------- From 34f50b8e43e65a847aa06009d43e830cecb1a6cb Mon Sep 17 00:00:00 2001 From: Sean Meyer Date: Thu, 4 Jul 2024 23:26:52 +0800 Subject: [PATCH 11/12] update to use pytoml and modern tools --- .github/workflows/test.yml | 53 +++++++++ .python-version | 5 + README.rst | 4 +- django_pwned_passwords/__init__.py | 2 +- django_pwned_passwords/password_validation.py | 10 +- docs/conf.py | 3 +- example/example/urls.py | 4 +- manage.py | 2 +- pyproject.toml | 101 ++++++++++++++++++ requirements.txt | 2 +- requirements_dev.txt | 3 +- requirements_test.txt | 6 -- runtests.py | 26 ----- setup.cfg | 20 ---- setup.py | 80 -------------- tests/test_password_validation.py | 11 +- tox.ini | 44 ++++---- 17 files changed, 206 insertions(+), 170 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 .python-version create mode 100644 pyproject.toml delete mode 100644 requirements_test.txt delete mode 100644 runtests.py delete mode 100644 setup.cfg delete mode 100755 setup.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..a4928e9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,53 @@ +name: Test + +on: + push: + branches: + - main + pull_request: + +jobs: + tests: + name: Python ${{ matrix.python-version }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.8" + - "3.9" + - "3.10" + - "3.11" + - "3.12" + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip wheel setuptools + python -m pip install --upgrade tox + - name: Run tox targets for ${{ matrix.python-version }} + run: tox run -f py$(echo ${{ matrix.python-version }} | tr -d .) + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.12" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip tox + - name: Run lint + run: tox -e lint diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..58d1e1f --- /dev/null +++ b/.python-version @@ -0,0 +1,5 @@ +3.12 +3.11 +3.10 +3.9 +3.8 diff --git a/README.rst b/README.rst index 63e40fb..9a7b975 100644 --- a/README.rst +++ b/README.rst @@ -23,8 +23,8 @@ The full documentation is at https://django-pwned-passwords.readthedocs.io. Requirements ------------ -* Django [3.2, 4.0, 4.1] -* Python 3.6, 3.7, 3.8, 3.9, 3.10, 3.11 +* Django [4.2, 5.0, 5.1] +* Python 3.8, 3.9, 3.10, 3.11, 3.12 Quickstart ---------- diff --git a/django_pwned_passwords/__init__.py b/django_pwned_passwords/__init__.py index 0d72820..6c235c5 100644 --- a/django_pwned_passwords/__init__.py +++ b/django_pwned_passwords/__init__.py @@ -1 +1 @@ -__version__ = "5.1.0" +__version__ = "5.2.0" diff --git a/django_pwned_passwords/password_validation.py b/django_pwned_passwords/password_validation.py index b18f05d..5c31425 100644 --- a/django_pwned_passwords/password_validation.py +++ b/django_pwned_passwords/password_validation.py @@ -1,9 +1,9 @@ -from django.core.exceptions import ValidationError -from django.conf import settings -from django.utils.translation import gettext as _ - import hashlib + import requests +from django.conf import settings +from django.core.exceptions import ValidationError +from django.utils.translation import gettext as _ class PWNEDPasswordValidator(object): @@ -30,7 +30,7 @@ def __init__(self, min_length=8): self.error_msg = getattr( settings, "PWNED_VALIDATOR_ERROR", - "Your password was determined to have been involved in a major security breach.", + "Your password was determined to have been involved in a major security breach.", # noqa: E501 ) self.error_fail_msg = getattr( settings, diff --git a/docs/conf.py b/docs/conf.py index c7e6cea..03b0aa9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,7 +11,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import os +import sys # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the diff --git a/example/example/urls.py b/example/example/urls.py index 1a77621..e0dde92 100644 --- a/example/example/urls.py +++ b/example/example/urls.py @@ -1,7 +1,7 @@ -from django.conf.urls import url, include -from django.views.generic.edit import CreateView +from django.conf.urls import include, url from django.contrib.auth.forms import UserCreationForm from django.views.generic import RedirectView +from django.views.generic.edit import CreateView urlpatterns = [ url("^$", RedirectView.as_view(pattern_name="register")), diff --git a/manage.py b/manage.py index 8c5be8a..404ea64 100644 --- a/manage.py +++ b/manage.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from __future__ import unicode_literals, absolute_import +from __future__ import absolute_import, unicode_literals import os import sys diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8e60d8b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,101 @@ +[build-system] +build-backend = "setuptools.build_meta" +requires = [ + "setuptools>=61", +] + +[project] +name = "django-pwned-passwords" +description = "A Django password validator that checks Troy Hunt's PWNED Passwords API to see if a password has been involved in a major security breach before." +readme = "README.md" +license = { text = "MIT" } +authors = [{ name = "Sean Meyer" }] +requires-python = ">=3.8" +classifiers = [ + "Environment :: Web Environment", + "Framework :: Django", + "Framework :: Django :: 4.2", + "Framework :: Django :: 5.0", + "Framework :: Django :: 5.1", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Internet :: WWW/HTTP :: Dynamic Content", + "Topic :: Software Development :: Libraries :: Python Modules", +] +dynamic = [ + "version", +] +dependencies = [ + "django>=4.2" +] + +[project.optional-dependencies] +test = [ + "pytest", + "pytest-django", + +] + +[project.urls] +"CI" = "https://github.com/slinkymanbyday/django-pwned-passwords/actions" +"Changelog" = "https://github.com/slinkymanbyday/django-pwned-passwords/releases" +"Homepage" = "https://github.com/slinkymanbyday/django-pwned-passwords" +"Issues" = "https://github.com/slinkymanbyday/django-pwned-passwords/issues" + +[tool.setuptools.dynamic] +version = { attr = "django_pwned_passwords.__version__" } + +[tool.isort] +profile = "black" + +[tool.ruff] +extend-exclude = [ + "__pycache__", +] + +[tool.ruff.lint] +select = [ + # pycodestyle + "E", "W", + # flake8 + "F", + # isort + "I", + # pytest style + "PT", + # eradicate commented code + "ERA", + # ruff lint + "RUF", +] + +[tool.pytest.ini_options] +DJANGO_SETTINGS_MODULE = "tests.settings" + +[tool.bumpversion] +current_version = "5.2.0" +parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)" +serialize = ["{major}.{minor}.{patch}"] +search = "{current_version}" +replace = "{new_version}" +regex = false +ignore_missing_version = false +ignore_missing_files = false +tag = false +sign_tags = false +tag_name = "v{new_version}" +tag_message = "Bump version: {current_version} → {new_version}" +allow_dirty = false +commit = false +message = "Bump version: {current_version} → {new_version}" +commit_args = "" + +[[tool.bumpversion.files]] +filename = "django_pwned_passwords/__init__.py" diff --git a/requirements.txt b/requirements.txt index 55a6106..3185f40 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ django>=3.2 -requests==2.28.2 +requests diff --git a/requirements_dev.txt b/requirements_dev.txt index 2e66fe3..69b7e52 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,2 +1 @@ -bumpversion==0.6.0 -wheel==0.38.4 +bump-my-version==0.24.2 diff --git a/requirements_test.txt b/requirements_test.txt deleted file mode 100644 index ce5c07b..0000000 --- a/requirements_test.txt +++ /dev/null @@ -1,6 +0,0 @@ -coverage==7.1.0 -requests-mock==1.10.0 -mock>=5.0.1 -flake8>=6.0.0 -tox>=4.4.2 -codecov>=2.1.12 diff --git a/runtests.py b/runtests.py deleted file mode 100644 index 035a375..0000000 --- a/runtests.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -from __future__ import unicode_literals, absolute_import - -import os -import sys - -import django -from django.conf import settings -from django.test.utils import get_runner - - -def run_tests(*test_args): - if not test_args: - test_args = ["tests"] - - os.environ["DJANGO_SETTINGS_MODULE"] = "tests.settings" - django.setup() - TestRunner = get_runner(settings) - test_runner = TestRunner() - failures = test_runner.run_tests(test_args) - sys.exit(bool(failures)) - - -if __name__ == "__main__": - run_tests(*sys.argv[1:]) diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index c924e8b..0000000 --- a/setup.cfg +++ /dev/null @@ -1,20 +0,0 @@ -[bumpversion] -current_version = 5.1.0 -commit = True -tag = True - -[bumpversion:file:django_pwned_passwords/__init__.py] - -[wheel] -universal = 1 - -[flake8] -ignore = D203 -exclude = - django_pwned_passwords/migrations, - .git, - .tox, - docs/conf.py, - build, - dist -max-line-length = 119 diff --git a/setup.py b/setup.py deleted file mode 100755 index 5e1d037..0000000 --- a/setup.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -import os -import re -import sys - -try: - from setuptools import setup -except ImportError: - from distutils.core import setup - - -def get_version(*file_paths): - """Retrieves the version from django_pwned_passwords/__init__.py""" - filename = os.path.join(os.path.dirname(__file__), *file_paths) - version_file = open(filename).read() - version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M) - if version_match: - return version_match.group(1) - raise RuntimeError("Unable to find version string.") - - -version = get_version("django_pwned_passwords", "__init__.py") - - -if sys.argv[-1] == "publish": - try: - import wheel - - print("Wheel version: ", wheel.__version__) - except ImportError: - print('Wheel library missing. Please run "pip install wheel"') - sys.exit() - os.system("python setup.py sdist upload") - os.system("python setup.py bdist_wheel upload") - sys.exit() - -if sys.argv[-1] == "tag": - print("Tagging the version on git:") - os.system("git tag -a v%s -m 'version %s'" % (version, version)) - os.system("git push --tags") - sys.exit() - -readme = open("README.rst").read() -history = open("HISTORY.rst").read().replace(".. :changelog:", "") - -setup( - name="django-pwned-passwords", - version=version, - description="""A Django password validator that checks Troy Hunt's PWNED Passwords API to see if a password has been involved in a major security breach before.""", - long_description=readme + "\n\n" + history, - author="Jamie Counsell", - author_email="jamiecounsell@me.com", - url="https://github.com/jamiecounsell/django-pwned-passwords", - packages=[ - "django_pwned_passwords", - ], - include_package_data=True, - install_requires=["requests"], - license="MIT", - zip_safe=False, - keywords="django-pwned-passwords", - classifiers=[ - "Development Status :: 3 - Alpha", - "Framework :: Django", - "Framework :: Django :: 3.2", - "Framework :: Django :: 4.0", - "Framework :: Django :: 4.1", - "Intended Audience :: Developers", - "License :: OSI Approved :: BSD License", - "Natural Language :: English", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - ], -) diff --git a/tests/test_password_validation.py b/tests/test_password_validation.py index c9188b3..b7fb57f 100644 --- a/tests/test_password_validation.py +++ b/tests/test_password_validation.py @@ -8,13 +8,14 @@ Tests for `django-pwned-passwords` password_validation module. """ -from django.test import TestCase, override_settings -from django.core.exceptions import ValidationError -from django_pwned_passwords.password_validation import PWNEDPasswordValidator +import hashlib -import requests_mock import requests -import hashlib +import requests_mock +from django.core.exceptions import ValidationError +from django.test import TestCase, override_settings + +from django_pwned_passwords.password_validation import PWNEDPasswordValidator class TestPasswordValidation(TestCase): diff --git a/tox.ini b/tox.ini index 22f45ce..7bd634c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,25 +1,33 @@ [tox] envlist = - {py36,py37,py38,py39,py310}-django-32 - {py38,py39,py310}-django-40 - {py310,py311}-django-41 + {py38,py39,py310,py311}-django{42}, + {py310,py311,py312}-django{50,51,-latest}, + lint [testenv] -setenv = - PYTHONPATH = {toxinidir}:{toxinidir}/django_pwned_passwords +package = wheel +wheel_build_env = .pkg +deps = + django42: django>=4.2a1,<5.0 + django50: django>=5.0a1,<5.1 + django51: django>=5.1a1,<5.2 + coverage + mock + pytest + pytest-django + requests-mock -commands = coverage run --source django_pwned_passwords runtests.py -deps = - django-41: Django>=4.1,<4.2 - django-40: Django>=4.0,<4.1 - django-32: Django>=3.2,<4.0 - -r{toxinidir}/requirements_test.txt +extras = test +commands = coverage run --source django_pwned_passwords -m pytest -basepython = - py311: python3.11 - py310: python3.10 - py39: python3.9 - py38: python3.8 - py37: python3.7 - py36: python3.6 \ No newline at end of file +[testenv:lint] +skip_install = true +commands = + black django_pwned_passwords --check + isort django_pwned_passwords --check --dif + ruff check django_pwned_passwords +deps = + black + isort + ruff From d8c8c73d95abf14e05e2f2ca199a488667ed6fad Mon Sep 17 00:00:00 2001 From: Sean Meyer Date: Thu, 4 Jul 2024 23:36:14 +0800 Subject: [PATCH 12/12] update readme and remove travis file --- .github/workflows/test.yml | 8 ++++---- .travis.yml | 40 -------------------------------------- README.rst | 11 ++--------- 3 files changed, 6 insertions(+), 53 deletions(-) delete mode 100644 .travis.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a4928e9..ffc4ad3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,10 +21,10 @@ jobs: - "3.12" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} @@ -39,10 +39,10 @@ jobs: name: Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.12" diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 889cdbb..0000000 --- a/.travis.yml +++ /dev/null @@ -1,40 +0,0 @@ -# Config file for automatic testing at travis-ci.org - -language: python - -python: - - "3.6" - -before_install: - # work around https://github.com/travis-ci/travis-ci/issues/8363 - - pyenv install 3.5.4 - - pyenv global system 3.5.4 - -env: - - TOX_ENV=py36-django-32 - - TOX_ENV=py37-django-32 - - TOX_ENV=py38-django-32 - - TOX_ENV=py39-django-32 - - TOX_ENV=py310-django-32 - - TOX_ENV=py38-django-40 - - TOX_ENV=py39-django-40 - - TOX_ENV=py310-django-40 - - TOX_ENV=py310-django-41 - - TOX_ENV=py311-django-41 - -matrix: - fast_finish: true - -# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors -install: pip install -r requirements_test.txt - -# command to run tests using coverage, e.g. python setup.py test -script: tox -e $TOX_ENV - -after_success: - - codecov -e TOX_ENV - -notifications: - email: - on_success: change - on_failure: change diff --git a/README.rst b/README.rst index 9a7b975..bc144ad 100644 --- a/README.rst +++ b/README.rst @@ -2,14 +2,7 @@ Django PWNED Passwords ====================== -.. image:: https://badge.fury.io/py/django-pwned-passwords.svg - :target: https://badge.fury.io/py/django-pwned-passwords - -.. image:: https://travis-ci.org/jamiecounsell/django-pwned-passwords.svg?branch=master - :target: https://travis-ci.org/jamiecounsell/django-pwned-passwords - -.. image:: https://codecov.io/gh/jamiecounsell/django-pwned-passwords/branch/master/graph/badge.svg - :target: https://codecov.io/gh/jamiecounsell/django-pwned-passwords +This fork currently does not have a pypi package, install it is currently installed directly from github. django-pwned-passwords is a Django password validator that checks Troy Hunt's PWNED Passwords API to see if a password has been involved in a major security breach before. @@ -31,7 +24,7 @@ Quickstart Install django-pwned-passwords:: - pip install django-pwned-passwords + pip install git+https://github.com/slinkymanbyday/django-pwned-passwords.git Add it to your `INSTALLED_APPS`: