From 56a9f63b404b1b74f1079ef5e2fe3a44219401ba Mon Sep 17 00:00:00 2001 From: Michael Howitz Date: Mon, 13 Dec 2021 18:01:56 +0100 Subject: [PATCH 1/3] Configuring for pure-python --- .coveragerc | 3 -- .coveralls.yml | 1 - .editorconfig | 39 +++++++++++++++++ .github/workflows/tests.yml | 63 +++++++++++++++++++++++++++ .gitignore | 36 ++++++++++++---- .meta.toml | 34 +++++++++++++++ .travis.yml | 28 ------------ MANIFEST.in | 15 ++++--- setup.cfg | 14 ++++++ tox.ini | 85 +++++++++++++++++++++++++++---------- 10 files changed, 247 insertions(+), 71 deletions(-) delete mode 100644 .coveragerc delete mode 100644 .coveralls.yml create mode 100644 .editorconfig create mode 100644 .github/workflows/tests.yml create mode 100644 .meta.toml delete mode 100644 .travis.yml create mode 100644 setup.cfg diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 0fa786d..0000000 --- a/.coveragerc +++ /dev/null @@ -1,3 +0,0 @@ -[run] -source = z3c.password -omit = */test* diff --git a/.coveralls.yml b/.coveralls.yml deleted file mode 100644 index 25d9264..0000000 --- a/.coveralls.yml +++ /dev/null @@ -1 +0,0 @@ -repo_token: QEp4jiHSbDIIJ8rla0nQFeCCFV2RpO6Rs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c5508b9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,39 @@ +# Generated from: +# https://github.com/zopefoundation/meta/tree/master/config/pure-python +# +# EditorConfig Configuration file, for more details see: +# http://EditorConfig.org +# EditorConfig is a convention description, that could be interpreted +# by multiple editors to enforce common coding conventions for specific +# file types + +# top-most EditorConfig file: +# Will ignore other EditorConfig files in Home directory or upper tree level. +root = true + + +[*] # For All Files +# Unix-style newlines with a newline ending every file +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +# Set default charset +charset = utf-8 +# Indent style default +indent_style = space +# Max Line Length - a hard line wrap, should be disabled +max_line_length = off + +[*.{py,cfg,ini}] +# 4 space indentation +indent_size = 4 + +[*.{yml,zpt,pt,dtml,zcml}] +# 2 space indentation +indent_size = 2 + +[{Makefile,.gitmodules}] +# Tab indentation (no size specified, but view as 4 spaces) +indent_style = tab +indent_size = unset +tab_width = unset diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..266ec14 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,63 @@ +# Generated from: +# https://github.com/zopefoundation/meta/tree/master/config/pure-python +name: tests + +on: + push: + pull_request: + schedule: + - cron: '0 12 * * 0' # run once a week on Sunday + # Allow to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + build: + strategy: + # We want to see all failures: + fail-fast: false + matrix: + os: + - ubuntu + config: + # [Python version, tox env] + - ["3.9", "lint"] + - ["2.7", "py27"] + - ["3.5", "py35"] + - ["3.6", "py36"] + - ["3.7", "py37"] + - ["3.8", "py38"] + - ["3.9", "py39"] + - ["3.10", "py310"] + - ["pypy2", "pypy"] + - ["pypy3", "pypy3"] + - ["3.9", "coverage"] + + runs-on: ${{ matrix.os }}-latest + name: ${{ matrix.config[1] }} + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.config[0] }} + - name: Pip cache + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ matrix.config[0] }}-${{ hashFiles('setup.*', 'tox.ini') }} + restore-keys: | + ${{ runner.os }}-pip-${{ matrix.config[0] }}- + ${{ runner.os }}-pip- + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox + - name: Test + run: tox -e ${{ matrix.config[1] }} + - name: Coverage + if: matrix.config[1] == 'coverage' + run: | + pip install coveralls coverage-python-version + coveralls --service=github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 91afae1..c724a76 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,31 @@ +# Generated from: +# https://github.com/zopefoundation/meta/tree/master/config/pure-python +*.dll +*.egg-info/ +*.profraw *.pyc +*.pyo *.so -*.dll -__pycache__ -src/*.egg-info - .coverage +.coverage.* +.eggs/ .installed.cfg -.tox -bin -build -develop-eggs -parts +.mr.developer.cfg +.tox/ +.vscode/ +__pycache__/ +bin/ +build/ +coverage.xml +develop-eggs/ +develop/ +dist/ +docs/_build +eggs/ +etc/ +lib/ +lib64 +log/ +parts/ +pyvenv.cfg +var/ diff --git a/.meta.toml b/.meta.toml new file mode 100644 index 0000000..f6c16c9 --- /dev/null +++ b/.meta.toml @@ -0,0 +1,34 @@ +# Generated from: +# https://github.com/zopefoundation/meta/tree/master/config/pure-python +[meta] +template = "pure-python" +commit-id = "fba6d957ba447b6fa369d872e803756bd5176391" + +[python] +with-windows = false +with-pypy = true +with-future-python = false +with-legacy-python = true +with-sphinx-doctests = false + +[tox] +use-flake8 = true +testenv-deps = [ + "zope.testrunner", + ] + +[coverage] +fail-under = 86 + +[manifest] +additional-rules = [ + "recursive-include src *.po", + "recursive-include src *.pot", + "recursive-include src *.txt", + "recursive-include src *.zcml", + ] + +[check-manifest] +ignore-bad-ideas = [ + "src/z3c/password/locales/*/LC_MESSAGES/z3c.password.mo", + ] diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6dd15a8..0000000 --- a/.travis.yml +++ /dev/null @@ -1,28 +0,0 @@ -language: python -matrix: - include: - - os: linux - python: 2.7 - - os: linux - python: 3.6 - - os: linux - python: 3.7 - dist: xenial - sudo: true - - os: linux - python: pypy - - os: linux - python: pypy3 - -install: - - travis_retry pip install .[test] - - travis_retry pip install coverage coveralls tox zope.testing - -script: - - coverage run setup.py test -q - -notifications: - email: false - -after_success: - - coveralls diff --git a/MANIFEST.in b/MANIFEST.in index 35da097..7df8c94 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,11 +1,12 @@ +# Generated from: +# https://github.com/zopefoundation/meta/tree/master/config/pure-python include *.rst include *.txt -include *.py +include buildout.cfg include tox.ini -include .coveragerc -include .travis.yml -include .coveralls.yml -recursive-include src * - -global-exclude *.pyc +recursive-include src *.py +recursive-include src *.po +recursive-include src *.pot +recursive-include src *.txt +recursive-include src *.zcml diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..d4aa1b3 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,14 @@ +# Generated from: +# https://github.com/zopefoundation/meta/tree/master/config/pure-python +[bdist_wheel] +universal = 1 + +[flake8] +doctests = 1 + +[check-manifest] +ignore = + .editorconfig + .meta.toml +ignore-bad-ideas = + src/z3c/password/locales/*/LC_MESSAGES/z3c.password.mo diff --git a/tox.ini b/tox.ini index 188e644..e867b0d 100644 --- a/tox.ini +++ b/tox.ini @@ -1,32 +1,71 @@ +# Generated from: +# https://github.com/zopefoundation/meta/tree/master/config/pure-python [tox] -skip_missing_interpreters = True +minversion = 3.18 envlist = - py27,py36,py37,pypy,pypy3,coverage + lint + py27 + py35 + py36 + py37 + py38 + py39 + py310 + pypy + pypy3 + coverage [testenv] +usedevelop = true +deps = + zope.testrunner commands = - coverage run setup.py test -q + zope-testrunner --test-path=src {posargs:-vc} +extras = + test + +[testenv:lint] +basepython = python3 +skip_install = true deps = - ZODB - coverage - zope.component - zope.exceptions - zope.i18n - zope.i18nmessageid - zope.interface - zope.password - zope.pluggableauth - zope.schema - zope.security - zope.testing -setenv = - COVERAGE_FILE=.coverage.{envname} + flake8 + check-manifest + check-python-versions >= 0.19.1 + wheel +commands = + flake8 src setup.py + check-manifest + check-python-versions [testenv:coverage] -setenv = - COVERAGE_FILE=.coverage -skip_install = true +basepython = python3 +allowlist_externals = + mkdir +deps = + coverage + coverage-python-version + zope.testrunner commands = - coverage erase - coverage combine - coverage report + mkdir -p {toxinidir}/parts/htmlcov + coverage run -m zope.testrunner --test-path=src {posargs:-vc} + coverage html + coverage report -m --fail-under=86 + +[coverage:run] +branch = True +plugins = coverage_python_version +source = z3c.password + +[coverage:report] +precision = 2 +exclude_lines = + pragma: no cover + pragma: nocover + except ImportError: + raise NotImplementedError + if __name__ == '__main__': + self.fail + raise AssertionError + +[coverage:html] +directory = parts/htmlcov From 3f582ecb09178b2d36fe3e9d6fe892ba341fec0e Mon Sep 17 00:00:00 2001 From: Michael Howitz Date: Mon, 13 Dec 2021 18:05:59 +0100 Subject: [PATCH 2/3] Lint the code. Add support for Python 3.8, 3.9 and 3.10. Drop support for Python 3.4. --- CHANGES.rst | 15 ++++--- setup.py | 52 ++++++++++++---------- src/z3c/password/_compat.py | 2 +- src/z3c/password/field.py | 7 +-- src/z3c/password/interfaces.py | 80 ++++++++++++++++++++++------------ src/z3c/password/password.py | 33 +++++++------- src/z3c/password/principal.py | 46 +++++++++---------- src/z3c/password/testing.py | 1 + src/z3c/password/tests.py | 13 +++--- 9 files changed, 142 insertions(+), 107 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index b48adb9..0b6e17a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,10 +2,12 @@ CHANGES ======= -1.0.1 (unreleased) +1.1.0 (unreleased) ------------------ -- Nothing changed yet. +- Add support for Python 3.8, 3.9 and 3.10. + +- Drop support for Python 3.4. 1.0.0 (2018-11-14) @@ -17,17 +19,18 @@ CHANGES - Drop support for ``None`` passwords, since they are not supported in the underlying APIs anymore. + 1.0.0a1 (2013-02-28) -------------------- -- Added support for Python 3.3. +- Add support for Python 3.3. -- Dropped dependency on ``zope.app.testing`` and ``zope.app.authentication``. +- Drop dependency on ``zope.app.testing`` and ``zope.app.authentication``. -- Replaced deprecated ``zope.interface.implements`` usage with equivalent +- Replace deprecated ``zope.interface.implements`` usage with equivalent ``zope.interface.implementer`` decorator. -- Dropped support for Python 2.4 and 2.5. +- Drop support for Python 2.4 and 2.5. 0.11.1 (2012-09-19) diff --git a/setup.py b/setup.py index 68dbc30..b74d7a3 100644 --- a/setup.py +++ b/setup.py @@ -16,23 +16,25 @@ import os from setuptools import setup, find_packages + def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() -setup ( + +setup( name='z3c.password', - version='1.0.1.dev0', - author = "Stephan Richter, Roger Ineichen and the Zope Community", - author_email = "zope3-dev@zope.org", - description = "Password generation and verification utility for Zope3", + version='1.1.0.dev0', + author="Stephan Richter, Roger Ineichen and the Zope Community", + author_email="zope3-dev@zope.org", + description="Password generation and verification utility for Zope3", long_description=( read('README.rst') + '\n\n' + read('CHANGES.rst') - ), - license = "ZPL 2.1", - keywords = "zope3 z3c password verification", - classifiers = [ + ), + license="ZPL 2.1", + keywords="zope3 z3c password verification", + classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', @@ -41,28 +43,32 @@ def read(*rnames): '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 :: Implementation :: CPython', "Programming Language :: Python :: Implementation :: PyPy", 'Natural Language :: English', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope :: 3'], - url = 'http://pypi.org/project/z3c.password', - packages = find_packages('src'), - include_package_data = True, - package_dir = {'':'src'}, - namespace_packages = ['z3c'], - extras_require = dict( - test = [ + url='http://pypi.org/project/z3c.password', + packages=find_packages('src'), + include_package_data=True, + package_dir={'': 'src'}, + namespace_packages=['z3c'], + extras_require=dict( + test=[ 'z3c.coverage', 'zope.password', 'zope.pluggableauth', 'zope.testing', - ], - ), - install_requires = [ + ], + ), + install_requires=[ 'setuptools', 'zope.component', 'zope.exceptions', @@ -71,12 +77,12 @@ def read(*rnames): 'zope.interface', 'zope.schema', 'zope.security', - ], - tests_require = [ + ], + tests_require=[ 'zope.password', 'zope.pluggableauth', 'zope.testing', - ], + ], test_suite='z3c.password.tests.test_suite', - zip_safe = False, + zip_safe=False, ) diff --git a/src/z3c/password/_compat.py b/src/z3c/password/_compat.py index 0e3739b..7e207c5 100644 --- a/src/z3c/password/_compat.py +++ b/src/z3c/password/_compat.py @@ -24,4 +24,4 @@ else: unichr = unichr - string_types = (str, unicode) + string_types = (str, unicode) # noqa: F821 undefined name 'unicode' diff --git a/src/z3c/password/field.py b/src/z3c/password/field.py index d8624c1..af5958d 100644 --- a/src/z3c/password/field.py +++ b/src/z3c/password/field.py @@ -18,6 +18,7 @@ from z3c.password import interfaces from z3c.password._compat import string_types + class Password(zope.schema.Password): def __init__(self, checker=None, ignoreEmpty=False, **kw): @@ -52,11 +53,11 @@ def validate(self, value): if checker is not None: self.checker.verify(value, old) - #try to check for disallowPasswordReuse here too, to raise - #problems ASAP + # try to check for disallowPasswordReuse here too, to raise + # problems ASAP if self.context is not None: try: self.context._checkDisallowedPreviousPassword(value) except AttributeError: - #if _checkDisallowedPreviousPassword is missing + # if _checkDisallowedPreviousPassword is missing pass diff --git a/src/z3c/password/interfaces.py b/src/z3c/password/interfaces.py index 7a1b301..91d3863 100644 --- a/src/z3c/password/interfaces.py +++ b/src/z3c/password/interfaces.py @@ -19,6 +19,7 @@ from z3c.password import MessageFactory as _ + class InvalidPassword(zope.schema.ValidationError): """Invalid Password""" @@ -38,6 +39,7 @@ def doc(self): class NoPassword(InvalidPassword): __doc__ = _('''No new password specified.''') + class TooShortPassword(InvalidPassword): __doc__ = _('''Password is too short.''') @@ -49,6 +51,7 @@ def __init__(self, minLength=None): 'Password is too short (minimum length: ${minLength}).', mapping=dict(minLength=minLength)) + class TooLongPassword(InvalidPassword): __doc__ = _('''Password is too long.''') @@ -60,6 +63,7 @@ def __init__(self, maxLength=None): 'Password is too long (maximum length: ${maxLength}).', mapping=dict(maxLength=maxLength)) + class TooSimilarPassword(InvalidPassword): __doc__ = _('''Password is too similar to old one.''') @@ -70,10 +74,12 @@ def __init__(self, similarity=None, maxSimilarity=None): if similarity is not None and maxSimilarity is not None: self.i18n_message = _( 'Password is too similar to old one' - ' (similarity ${similarity}%, should be at most ${maxSimilarity}%).', + ' (similarity ${similarity}%, should be at most' + ' ${maxSimilarity}%).', mapping=dict(similarity=int(round(similarity * 100)), maxSimilarity=int(maxSimilarity * 100))) + class TooManyGroupCharacters(InvalidPassword): __doc__ = _('''Password contains too many characters of one group.''') @@ -86,35 +92,40 @@ def __init__(self, groupMax=None): ' (should have at most ${groupMax}).', mapping=dict(groupMax=groupMax)) + class TooFewGroupCharacters(InvalidPassword): - __doc__ = _('''Password does not contain enough characters of one group.''') + __doc__ = _( + '''Password does not contain enough characters of one group.''') + class TooFewGroupCharactersLowerLetter(TooFewGroupCharacters): __doc__ = _( - '''Password does not contain enough characters of lowercase letters.''') + 'Password does not contain enough characters of lowercase letters.') def __init__(self, minLowerLetter=None): super(TooFewGroupCharactersLowerLetter, self).__init__() self.minLowerLetter = minLowerLetter if minLowerLetter is not None: self.i18n_message = _( - 'Password does not contain enough characters of lowercase letters' - ' (should have at least ${minLowerLetter}).', + 'Password does not contain enough characters of lowercase' + ' letters (should have at least ${minLowerLetter}).', mapping=dict(minLowerLetter=minLowerLetter)) + class TooFewGroupCharactersUpperLetter(TooFewGroupCharacters): __doc__ = _( - '''Password does not contain enough characters of uppercase letters.''') + 'Password does not contain enough characters of uppercase letters.') def __init__(self, minUpperLetter=None): super(TooFewGroupCharactersUpperLetter, self).__init__() self.minUpperLetter = minUpperLetter if minUpperLetter is not None: self.i18n_message = _( - 'Password does not contain enough characters of uppercase letters' - ' (should have at least ${minUpperLetter}).', + 'Password does not contain enough characters of uppercase' + ' letters (should have at least ${minUpperLetter}).', mapping=dict(minUpperLetter=minUpperLetter)) + class TooFewGroupCharactersDigits(TooFewGroupCharacters): __doc__ = _('''Password does not contain enough characters of digits.''') @@ -127,19 +138,21 @@ def __init__(self, minDigits=None): ' (should have at least ${minDigits}).', mapping=dict(minDigits=minDigits)) + class TooFewGroupCharactersSpecials(TooFewGroupCharacters): __doc__ = _( - '''Password does not contain enough characters of special characters.''') + 'Password does not contain enough characters of special characters.') def __init__(self, minSpecials=None): super(TooFewGroupCharactersSpecials, self).__init__() self.minSpecials = minSpecials if minSpecials is not None: self.i18n_message = _( - 'Password does not contain enough characters of special characters' - ' (should have at least ${minSpecials}).', + 'Password does not contain enough characters of special' + ' characters (should have at least ${minSpecials}).', mapping=dict(minSpecials=minSpecials)) + class TooFewGroupCharactersOthers(TooFewGroupCharacters): __doc__ = _( '''Password does not contain enough characters of other characters.''') @@ -149,10 +162,11 @@ def __init__(self, minOthers=None): self.minOthers = minOthers if minOthers is not None: self.i18n_message = _( - 'Password does not contain enough characters of other characters' - ' (should have at least ${minOthers}).', + 'Password does not contain enough characters of other' + ' characters (should have at least ${minOthers}).', mapping=dict(minOthers=minOthers)) + class TooFewUniqueCharacters(InvalidPassword): __doc__ = _('''Password does not contain enough unique characters.''') @@ -165,6 +179,7 @@ def __init__(self, minUniqueCharacters=None): ' (should have at least ${minUniqueCharacters}).', mapping=dict(minUniqueCharacters=minUniqueCharacters)) + class TooFewUniqueLetters(InvalidPassword): __doc__ = _('''Password does not contain enough unique letters.''') @@ -177,6 +192,7 @@ def __init__(self, minUniqueLetters=None): ' (should have at least ${minUniqueLetters}).', mapping=dict(minUniqueLetters=minUniqueLetters)) + class PasswordExpired(Exception): __doc__ = _('''The password has expired.''') @@ -184,6 +200,7 @@ def __init__(self, principal): self.principal = principal Exception.__init__(self, self.__doc__) + class PreviousPasswordNotAllowed(InvalidPassword): __doc__ = _('''The password set was already used before.''') @@ -191,6 +208,7 @@ def __init__(self, principal): self.principal = principal Exception.__init__(self, self.__doc__) + class TooManyLoginFailures(Exception): __doc__ = _('''The password was entered incorrectly too often.''') @@ -198,10 +216,12 @@ def __init__(self, principal): self.principal = principal Exception.__init__(self, self.__doc__) + TML_CHECK_ALL = None TML_CHECK_NONRESOURCE = 'nonres' TML_CHECK_POSTONLY = 'post' + class AccountLocked(Exception): __doc__ = _('The account is locked, because the password was ' 'entered incorrectly too often.') @@ -267,7 +287,8 @@ def minMaxLength(task): if task.minLength is not None and task.maxLength is not None: if task.minLength > task.maxLength: raise zope.interface.Invalid( - u"Minimum length must not be greater than the maximum length.") + u"Minimum length must not be greater than the maximum" + u" length.") groupMax = zope.schema.Int( title=_(u'Maximum Characters of Group'), @@ -279,7 +300,8 @@ def minMaxLength(task): maxSimilarity = zope.schema.Float( title=_(u'Old/New Similarity'), - description=(u'The similarity ratio between the new and old password.'), + description=( + u'The similarity ratio between the new and old password.'), required=False, default=None) @@ -311,7 +333,7 @@ def minMaxLength(task): required=False, default=None) - #WARNING! generating a password with Others is not yet supported + # WARNING! generating a password with Others is not yet supported minOthers = zope.schema.Int( title=_(u'Minimum Number of Other characters'), description=_(u'The minimum amount of other characters that a ' @@ -375,7 +397,7 @@ def saneMinimums(task): @zope.interface.invariant def minUniqueLettersLength(task): if (task.minUniqueLetters is not None - and task.minUniqueLetters is not None): + and task.minUniqueLetters is not None): if task.minUniqueLetters > task.maxLength: raise zope.interface.Invalid( u"Minimum unique letters number must not be greater than " @@ -392,12 +414,11 @@ def minUniqueLettersLength(task): @zope.interface.invariant def minUniqueCharactersLength(task): if (task.minUniqueCharacters is not None - and task.minUniqueCharacters is not None): + and task.minUniqueCharacters is not None): if task.minUniqueCharacters > task.maxLength: raise zope.interface.Invalid( - u"Minimum unique characters length must not be greater than " - u"the maximum length.") - + u"Minimum unique characters length must not be greater" + u" than the maximum length.") class IPasswordOptionsUtility(zope.interface.Interface): @@ -421,16 +442,19 @@ class IPasswordOptionsUtility(zope.interface.Interface): lockOutPeriod = zope.schema.Int( title=_(u'Lockout period (minutes)'), - description=_(u'Lockout the user after too many failed password entries' - 'for this many minutes. The user can try again after.'), + description=_( + u'Lockout the user after too many failed password entries' + u'for this many minutes. The user can try again after.'), required=False, default=None) maxFailedAttempts = zope.schema.Int( - title=_(u'Max. number of failed password entries before account is locked'), - description=_(u'Specifies the amount of failed attempts allowed to check ' - 'the password before the password is locked and no new ' - 'password can be provided.'), + title=_(u'Max. number of failed password entries before account is' + u' locked'), + description=_( + u'Specifies the amount of failed attempts allowed to check ' + u'the password before the password is locked and no new ' + u'password can be provided.'), required=False, default=None) @@ -440,7 +464,7 @@ class IPasswordOptionsUtility(zope.interface.Interface): 'All requests, non-reqource requests, POST requests.'), required=False, values=[TML_CHECK_ALL, TML_CHECK_NONRESOURCE, TML_CHECK_POSTONLY], - default=TML_CHECK_ALL ) + default=TML_CHECK_ALL) disallowPasswordReuse = zope.schema.Bool( title=_(u'Disallow Password Reuse'), diff --git a/src/z3c/password/password.py b/src/z3c/password/password.py index ae879f7..b061429 100644 --- a/src/z3c/password/password.py +++ b/src/z3c/password/password.py @@ -139,44 +139,44 @@ def verify(self, new, ref=None): num_upper_letters > self.groupMax or num_digits > self.groupMax or num_specials > self.groupMax or - num_others > self.groupMax): + num_others > self.groupMax): raise interfaces.TooManyGroupCharacters( groupMax=self.groupMax) if (self.minLowerLetter is not None - and num_lower_letters < self.minLowerLetter): + and num_lower_letters < self.minLowerLetter): raise interfaces.TooFewGroupCharactersLowerLetter( - minLowerLetter=self.minLowerLetter) + minLowerLetter=self.minLowerLetter) if (self.minUpperLetter is not None - and num_upper_letters < self.minUpperLetter): + and num_upper_letters < self.minUpperLetter): raise interfaces.TooFewGroupCharactersUpperLetter( - minUpperLetter=self.minUpperLetter) + minUpperLetter=self.minUpperLetter) if (self.minDigits is not None - and num_digits < self.minDigits): + and num_digits < self.minDigits): raise interfaces.TooFewGroupCharactersDigits( - minDigits=self.minDigits) + minDigits=self.minDigits) if (self.minSpecials is not None - and num_specials < self.minSpecials): + and num_specials < self.minSpecials): raise interfaces.TooFewGroupCharactersSpecials( - minSpecials=self.minSpecials) + minSpecials=self.minSpecials) if (self.minOthers is not None - and num_others < self.minOthers): + and num_others < self.minOthers): raise interfaces.TooFewGroupCharactersOthers( - minOthers=self.minOthers) + minOthers=self.minOthers) if (self.minUniqueCharacters is not None - and len(uniqueChars) < self.minUniqueCharacters): + and len(uniqueChars) < self.minUniqueCharacters): raise interfaces.TooFewUniqueCharacters( - minUniqueCharacters=self.minUniqueCharacters) + minUniqueCharacters=self.minUniqueCharacters) if (self.minUniqueLetters is not None - and len(uniqueLetters) < self.minUniqueLetters): + and len(uniqueLetters) < self.minUniqueLetters): raise interfaces.TooFewUniqueLetters( - minUniqueLetters=self.minUniqueLetters) + minUniqueLetters=self.minUniqueLetters) return @@ -190,7 +190,7 @@ def generate(self, ref=None): # Generate the password chars = self.LOWERLETTERS + self.UPPERLETTERS + \ - self.DIGITS + self.SPECIALS + self.DIGITS + self.SPECIALS for count in range(length): new += self.random.choice(chars) @@ -204,6 +204,7 @@ def generate(self, ref=None): verified = True return new + @zope.interface.implementer(interfaces.IPasswordOptionsUtility) class PasswordOptionsUtility(object): """An implementation of the security options.""" diff --git a/src/z3c/password/principal.py b/src/z3c/password/principal.py index d33a0c7..7d3cc3b 100644 --- a/src/z3c/password/principal.py +++ b/src/z3c/password/principal.py @@ -20,14 +20,15 @@ from z3c.password import interfaces + class PrincipalMixIn(object): """A Principal Mixin class for ``zope.app.principalfolder``'s internal principal.""" passwordExpiresAfter = None passwordSetOn = None - passwordExpired = False #force PasswordExpired, - #e.g. for changePasswordOnNextLogin + # force PasswordExpired, e.g. for changePasswordOnNextLogin + passwordExpired = False failedAttempts = 0 failedAttemptCheck = interfaces.TML_CHECK_ALL @@ -43,8 +44,8 @@ class PrincipalMixIn(object): def _checkDisallowedPreviousPassword(self, password): if self._disallowPasswordReuse(): if self.previousPasswords is not None and password is not None: - #hack, but this should work with zope.app.authentication and - #z3c.authenticator + # hack, but this should work with zope.app.authentication and + # z3c.authenticator passwordManager = self._getPasswordManager() for pwd in self.previousPasswords: @@ -74,11 +75,10 @@ def setPassword(self, password, passwordManagerName=None): self.lastFailedAttempt = None self.passwordExpired = False - password = property(getPassword, setPassword) def now(self): - #hook to facilitate testing and easier override + # hook to facilitate testing and easier override return datetime.datetime.now() def _isRelevantRequest(self): @@ -93,7 +93,7 @@ def _isRelevantRequest(self): try: request = interaction.participations[0] except IndexError: - return True # no request, we regard that as relevant. + return True # no request, we regard that as relevant. if fac == interfaces.TML_CHECK_NONRESOURCE: if '/@@/' in request.getURL(): @@ -121,20 +121,22 @@ def checkPassword(self, pwd, ignoreExpiration=False, ignoreFailures=False): if self.tooManyLoginFailures(): locked = self.accountLocked() if locked is None: - #no lockPeriod + # no lockPeriod pass elif locked: - #account locked by tooManyLoginFailures and within lockPeriod + # account locked by tooManyLoginFailures and within + # lockPeriod if not same: self.lastFailedAttempt = self.now() raise interfaces.AccountLocked(self) else: - #account locked by tooManyLoginFailures and out of lockPeriod + # account locked by tooManyLoginFailures and out of + # lockPeriod self.failedAttempts = 0 self.lastFailedAttempt = None if same: - #successful attempt + # successful attempt if not ignoreExpiration: if self.passwordExpired: raise interfaces.PasswordExpired(self) @@ -146,7 +148,7 @@ def checkPassword(self, pwd, ignoreExpiration=False, ignoreFailures=False): raise interfaces.PasswordExpired(self) add = 0 else: - #failed attempt, record it, increase counter + # failed attempt, record it, increase counter self.failedAttempts += 1 self.lastFailedAttempt = self.now() add = 1 @@ -158,19 +160,19 @@ def checkPassword(self, pwd, ignoreExpiration=False, ignoreFailures=False): raise interfaces.TooManyLoginFailures(self) if same and self.failedAttempts != 0: - #if all nice and good clear failure counter + # if all nice and good clear failure counter self.failedAttempts = 0 self.lastFailedAttempt = None return same - def tooManyLoginFailures(self, add = 0): + def tooManyLoginFailures(self, add=0): attempts = self._maxFailedAttempts() - #this one needs to be >=, because... data just does not - #get saved on an exception when running under of a full Zope env. - #the dance around ``add`` has the same roots - #we need to be able to increase the failedAttempts count and not raise - #at the same time + # this one needs to be >=, because... data just does not + # get saved on an exception when running under of a full Zope env. + # the dance around ``add`` has the same roots + # we need to be able to increase the failedAttempts count and not raise + # at the same time if attempts is not None: attempts += add if self.failedAttempts >= attempts: @@ -180,9 +182,9 @@ def tooManyLoginFailures(self, add = 0): def accountLocked(self): lockPeriod = self._lockOutPeriod() if lockPeriod is not None: - #check if the user locked himself + # check if the user locked himself if (self.lastFailedAttempt is not None - and self.lastFailedAttempt + lockPeriod > self.now()): + and self.lastFailedAttempt + lockPeriod > self.now()): return True else: return False @@ -198,7 +200,7 @@ def passwordExpiresOn(self): def _optionsUtility(self): if self.passwordOptionsUtilityName: - #if we have a utility name, then it must be there + # if we have a utility name, then it must be there return zope.component.getUtility( interfaces.IPasswordOptionsUtility, name=self.passwordOptionsUtilityName) diff --git a/src/z3c/password/testing.py b/src/z3c/password/testing.py index 62dfcb1..7caee29 100644 --- a/src/z3c/password/testing.py +++ b/src/z3c/password/testing.py @@ -17,6 +17,7 @@ import zope.component.testing from zope.password import password + def setUp(test): zope.component.testing.setUp(test) diff --git a/src/z3c/password/tests.py b/src/z3c/password/tests.py index e40f919..4b1c2c8 100644 --- a/src/z3c/password/tests.py +++ b/src/z3c/password/tests.py @@ -32,13 +32,13 @@ r"NoPassword"), (re.compile("zope.security.interfaces.NoInteraction"), r"NoInteraction"), - ]) +]) def test_suite(): - flags = doctest.NORMALIZE_WHITESPACE|\ - doctest.ELLIPSIS|\ - doctest.IGNORE_EXCEPTION_DETAIL + flags = doctest.NORMALIZE_WHITESPACE |\ + doctest.ELLIPSIS |\ + doctest.IGNORE_EXCEPTION_DETAIL return unittest.TestSuite(( DocFileSuite('README.txt', setUp=testing.setUp, tearDown=testing.tearDown, @@ -48,7 +48,4 @@ def test_suite(): setUp=testing.setUp, tearDown=testing.tearDown, optionflags=flags, checker=checker, ), - )) - -if __name__ == '__main__': - unittest.main(defaultTest='test_suite') + )) From e76795ac773d2b8dfc2f7760f388ad70a7743edd Mon Sep 17 00:00:00 2001 From: Michael Howitz Date: Mon, 13 Dec 2021 18:10:06 +0100 Subject: [PATCH 3/3] Fix badge. --- README.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 0c73e12..0b5696c 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,10 @@ +============ +z3c.password +============ -.. image:: https://travis-ci.com/zopefoundation/z3c.password.png?branch=master - :target: https://travis-ci.com/zopefoundation/z3c.password + +.. image:: https://github.com/zopefoundation/z3c.password/actions/workflows/tests.yml/badge.svg + :target: https://github.com/zopefoundation/z3c.password/actions/workflows/tests.yml .. image:: https://coveralls.io/repos/github/zopefoundation/z3c.password/badge.svg?branch=master :target: https://coveralls.io/github/zopefoundation/z3c.password?branch=master