diff options
99 files changed, 1240 insertions, 4186 deletions
@@ -6,7 +6,7 @@ The Passlib Python Library Welcome ======= -Passlib is a password hashing library for Python 2 & 3, which provides +Passlib is a password hashing library for Python 3, which provides cross-platform implementations of over 30 password hashing algorithms, as well as a framework for managing existing password hashes. It's designed to be useful for a wide range of tasks, from verifying a hash found in /etc/shadow, to diff --git a/admin/bench_pbkdf2.py b/admin/bench_pbkdf2.py index 53a4d47..e04f46d 100644 --- a/admin/bench_pbkdf2.py +++ b/admin/bench_pbkdf2.py @@ -4,8 +4,6 @@ helper script to benchmark pbkdf2 implementations/backends #============================================================================= # init script env #============================================================================= -from __future__ import absolute_import, division, print_function, unicode_literals - # make sure passlib source dir is first in import path import os, sys os.chdir(os.path.abspath(os.path.join(__file__, *[".."]*2))) @@ -25,7 +23,6 @@ except ImportError: assert reload, "expected builtin reload()" # py2x # site # pkg -from passlib.utils.compat import PY3 # local #============================================================================= @@ -126,9 +123,6 @@ def main(): import passlib.crypto.digest as digest_mod for backend in ["from-bytes", "unpack", "hexlify"]: name = "p/%s" % backend - if backend == "from-bytes" and not PY3: - na(name) - continue os.environ['PASSLIB_PBKDF2_BACKEND'] = backend reload(digest_mod) benchmark(name, diff --git a/admin/benchmarks.py b/admin/benchmarks.py index fb02b39..01e4bac 100644 --- a/admin/benchmarks.py +++ b/admin/benchmarks.py @@ -24,7 +24,6 @@ try: except ImportError: PasslibConfigWarning = None import passlib.utils.handlers as uh -from passlib.utils.compat import u, print_, unicode from passlib.tests.utils import time_call # local @@ -78,10 +77,10 @@ class benchmark: usec = int(secs * 1e6) if usec < 1000: return "%.*g usec" % (precision, usec) - msec = usec / 1000 + msec = usec // 1000 if msec < 1000: return "%.*g msec" % (precision, msec) - sec = msec / 1000 + sec = msec // 1000 return "%.*g sec" % (precision, sec) #============================================================================= @@ -90,19 +89,15 @@ class benchmark: sample_config_1p = os.path.join(root, "passlib", "tests", "sample_config_1s.cfg") from passlib.context import CryptContext -if hasattr(CryptContext, "from_path"): - CryptPolicy = None -else: - from passlib.context import CryptPolicy class BlankHandler(uh.HasRounds, uh.HasSalt, uh.GenericHandler): name = "blank" - ident = u("$b$") + ident = u"$b$" setting_kwds = ("rounds", "salt", "salt_size") checksum_size = 1 min_salt_size = max_salt_size = 1 - salt_chars = u("a") + salt_chars = u"a" min_rounds = 1000 max_rounds = 3000 @@ -117,14 +112,14 @@ class BlankHandler(uh.HasRounds, uh.HasSalt, uh.GenericHandler): return uh.render_mc3(self.ident, self.rounds, self.salt, self.checksum) def _calc_checksum(self, secret): - return unicode(secret[0:1]) + return secret[0:1] class AnotherHandler(BlankHandler): name = "another" - ident = u("$a$") + ident = u"$a$" -SECRET = u("toomanysecrets") -OTHER = u("setecastronomy") +SECRET = u"toomanysecrets" +OTHER = u"setecastronomy" #============================================================================= # CryptContext benchmarks @@ -133,12 +128,8 @@ OTHER = u("setecastronomy") def test_context_from_path(): """test speed of CryptContext.from_path()""" path = sample_config_1p - if CryptPolicy: - def helper(): - CryptPolicy.from_path(path) - else: - def helper(): - CryptContext.from_path(path) + def helper(): + CryptContext.from_path(path) return helper @benchmark.constructor() @@ -150,14 +141,9 @@ def test_context_update(): deprecated = [ "des_crypt" ], sha512_crypt__min_rounds=4000, ) - if CryptPolicy: - policy=CryptPolicy.from_path(sample_config_1p) - def helper(): - policy.replace(**kwds) - else: - ctx = CryptContext.from_path(sample_config_1p) - def helper(): - ctx.copy(**kwds) + ctx = CryptContext.from_path(sample_config_1p) + def helper(): + ctx.copy(**kwds) return helper @benchmark.constructor() @@ -306,7 +292,7 @@ def main(*args): if any(re.match(arg, k) for arg in args)) helper = benchmark.run(source, maxtime=2, bestof=3) for name, secs, precision in helper: - print_("%-50s %9s (%d)" % (name, benchmark.pptime(secs), precision)) + print("%-50s %9s (%d)" % (name, benchmark.pptime(secs), precision)) if __name__ == "__main__": import sys diff --git a/admin/plot_verify_timing.py b/admin/plot_verify_timing.py index 3f6255f..b34400b 100644 --- a/admin/plot_verify_timing.py +++ b/admin/plot_verify_timing.py @@ -3,7 +3,6 @@ small helper script used to compare timing of verify() & dummy_verify() """ # core -from __future__ import absolute_import, division, print_function, unicode_literals from argparse import ArgumentParser import sys from timeit import default_timer as tick diff --git a/admin/regen.py b/admin/regen.py index dc4b2fb..a461e3e 100644 --- a/admin/regen.py +++ b/admin/regen.py @@ -5,7 +5,6 @@ helper script which rebuilds all autogenerated bits of code w/in passlib. # imports #============================================================================= # core -from __future__ import absolute_import, division, print_function import datetime import re import os diff --git a/choose_rounds.py b/choose_rounds.py index 80d8609..2949950 100644 --- a/choose_rounds.py +++ b/choose_rounds.py @@ -2,7 +2,6 @@ #============================================================================= # imports #============================================================================= -from __future__ import division, print_function # core import math import logging; log = logging.getLogger(__name__) diff --git a/docs/history/1.6.rst b/docs/history/1.6.rst index 05b3d40..45ae322 100644 --- a/docs/history/1.6.rst +++ b/docs/history/1.6.rst @@ -280,7 +280,7 @@ CryptContext * All new (and hopefully clearer) :ref:`tutorial <context-tutorial>` and :ref:`reference <context-reference>` documentation. - * The :class:`CryptPolicy` class and the :attr:`!CryptContext.policy` attribute have been deprecated. + * The :class:`!CryptPolicy` class and the :attr:`!CryptContext.policy` attribute have been deprecated. This was a semi-internal class, which most applications were not involved with at all, but to be conservative about @@ -303,7 +303,7 @@ CryptContext can now be set to the special string ``"auto"``; which will automatically deprecate all schemes except for the default one. - * The :ref:`min_verify_time <context-min-verify-time-option>` keyword + * The ``min_verify_time`` keyword has been deprecated, will be ignored in release 1.7, and will be removed in release 1.8. It was never very useful, and now complicates the internal code needlessly. diff --git a/docs/history/1.7.rst b/docs/history/1.7.rst index 28546a4..f1b9346 100644 --- a/docs/history/1.7.rst +++ b/docs/history/1.7.rst @@ -1,5 +1,3 @@ -.. _whats-new: - =========== Passlib 1.7 =========== @@ -91,6 +89,11 @@ Bugfixes being run on systems lacking support for the hasher being tested. This test now runs regardless of system support. +Deprecations +------------ + +* Support for Python 2.x, 3.3, and 3.4 is deprecated; and will be dropped in Passlib 1.8. + Other Changes ------------- @@ -184,11 +187,11 @@ Deprecations Due to lack of ``pip`` and ``venv`` support, Passlib is no longer fully tested on Python 2.6 & 3.3. There are no known issues, and bugfixes against these versions will still be accepted for the Passlib 1.7.x series. - However, **Passlib 1.8 will drop support for Python 2.x & 3.3,** and require Python >= 3.4. + However, **Passlib 1.8 will drop support for Python 2.x, 3.3, & 3.4,** and require Python >= 3.5. -* Support for Python 2.x & 3.3 is deprecated; and will be dropped in Passlib 1.8. - *(2020-05-10: Updated to include all of Python 2.x; when 1.7.2 was released, - only Python 2.6 / 3.3 support was deprecated)* +* Support for Python 2.x, 3.3, and 3.4 is deprecated; and will be dropped in Passlib 1.8. + *(2020-10-06: Updated to include all of Python 2.x, 3.3, and 3.4; when 1.7.2 was released, + only Python 2.6 and 3.3 support was deprecated)* * .. py:currentmodule:: passlib.hash @@ -527,7 +530,7 @@ Changes in existing behavior: Scheduled removal of features: - * **[minor]** :mod:`passlib.context`: The :ref:`min_verify_time <context-min-verify-time-option>` keyword + * **[minor]** :mod:`passlib.context`: The ``min_verify_time`` keyword that was deprecated in release 1.6, is now completely ignored. Support will be removed entirely in release 1.8. diff --git a/docs/history/1.8.rst b/docs/history/1.8.rst new file mode 100644 index 0000000..9de5b7b --- /dev/null +++ b/docs/history/1.8.rst @@ -0,0 +1,94 @@ +.. _whats-new: + +=========== +Passlib 1.8 +=========== + +.. rst-class:: emphasize-children toc-always-open + +**1.8.0** (NOT YET RELEASED) +============================ + +Overview +-------- + +.. rst-class:: without-title + +.. warning:: + + **1.8 is under development,** and tenatively scheduled for release in late 2017. + + See https://passlib.readthedocs.io/en/stable/history/1.7.html for the latest release. + +Requirements +------------ + +* **Passlib now requires Python >= 3.5.** As of this release, support for Python 2.x, 3.3, + and 3.4 has been dropped. If you need to use Passlib on an earlier version of Python, + please use the :doc:`1.7` series, which will be maintained in bugfix-only mode + for few more releases. (See :issue:`119` for rationale). + +Backwards Incompatibilities +--------------------------- + +The following previously-deprecated features were removed, +though few of these should be in use, as they've been deprecated +for a number of years / releases: + + **passlib.apache:** + + .. py:currentmodule:: passlib.apache + + * :mod:`passlib.apache`: A number of deprecated options & methods were removed from + :class:`HtpasswdFile` and :class:`Htdigest`: + + - Support for setting ``encoding=None`` removed, use ``return_unicode=True`` instead. + - ``autoload=False`` keyword removed, use ``new=True`` instead. + - :meth:`!load` method no longer supports ``force=False``, use :meth:`~HtpasswdFile.load_if_changed` instead. + - :meth:`!update` alias removed, use :meth:`~HtpasswdFile.set_password` instead. + - :meth:`!find` alias removed, use :meth:`~HtpasswdFile.get_hash` instead. + - :meth:`!verify` alias removed, use :meth:`~HtpasswdFile.check_password` instead. + - ``default`` keyword removed, use ``default_scheme`` instead. + + **passlib.context:** + + .. py:currentmodule:: passlib.context + + * The :class:`!passlib.context.CryptPolicy` class was removed. + Code should be using the equivalent :meth:`~CryptContext` methods instead. + + * Concurrent with that, :class:`CryptContext`'s ``policy`` keyword and attribute + were removed, along with :class:`LazyCryptContext`'s ``create_policy`` keyword. + + * :meth:`!CryptContext.replace` alias removed, use :meth:`CryptContext.using` instead. + + * :class:`CryptContext`'s ``min_verify_time`` and ``harden_verify`` keywords removed. + + **passlib.hash:** + + .. py:currentmodule:: passlib.hash + + * :class:`!passlib.hash.unix_fallback` was removed, use :class:`~unix_disabled` instead. + + **other modules:** + + * In :mod:`passlib.ext.django`, support for ``CONFIG=None`` was dropped. + + * The deprecated :mod:`!passlib.win32` module was removed, use :class:`passlib.hash.lmhash` hash instead. + + **internal details:** + + .. py:currentmodule:: passlib.hash + + * The :meth:`!passlib.hash.nthash.raw_nthash` alias was removed, use :meth:`nthash.raw` instead. + + * In :mod:`passlib.utils.handlers`: :class:`!StaticHandler` subclasses must now always implement + :meth:`!_calc_checksum`, the old genhash-based style is no longer supported or checked for. + + * The deprecated :func:`passlib.utils.des.mdes_encrypt_int_block` method was removed. + + * The :func:`passlib.utils.pbkdf2.norm_hash_name` alias was removed, use :func:`passlib.crypto.digest.norm_hash_name` instead. + + .. py:currentmodule:: passlib.utils + + * Many PY2 compatibility helper inside :mod:`!passlib.utils.compat` have been removed. diff --git a/docs/history/index.rst b/docs/history/index.rst index 90824c2..938f860 100644 --- a/docs/history/index.rst +++ b/docs/history/index.rst @@ -8,15 +8,14 @@ Release History .. seealso:: - **For the latest release:** see :ref:`What's New <whats-new>` in Passlib 1.7 + **For the latest release:** see :ref:`What's New <whats-new>` in Passlib 1.8 .. rst-class:: float-center without-title -.. warning:: +.. toctree:: + :maxdepth: 2 - **Passlib 1.8 will drop support for Python 2.x, 3.3, and 3.4**; - and will require Python >= 3.5. The 1.7 series will be the last - to support Python 2.7. (See :issue:`119` for rationale). + 1.8 Series <1.8> .. toctree:: :maxdepth: 2 diff --git a/docs/index.rst b/docs/index.rst index 45e63ba..20f0604 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -5,7 +5,7 @@ .. rst-class:: float-right -.. seealso:: :ref:`What's new in Passlib 1.7.4 <whats-new>` +.. seealso:: :ref:`What's new in Passlib 1.8 <whats-new>` ========================================== Passlib |release| documentation @@ -33,7 +33,7 @@ Passlib |release| documentation Welcome ======= -Passlib is a password hashing library for Python 2 & 3, which provides +Passlib is a password hashing library for Python 3, which provides cross-platform implementations of over 30 password hashing algorithms, as well as a framework for managing existing password hashes. It's designed to be useful for a wide range of tasks, from verifying a hash found in /etc/shadow, to diff --git a/docs/install.rst b/docs/install.rst index 2ebe119..f48af45 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -6,27 +6,28 @@ Installation Supported Platforms =================== -Passlib requires Python 2 (>= 2.6) or Python 3 (>= 3.3). +Passlib requires Python 3.5 or newer. It is known to work with the following Python implementations: .. rst-class:: float-right without-title .. warning:: - **Passlib 1.8 will drop support for Python 2.x, 3.3, and 3.4**; - and will require Python >= 3.5. The 1.7 series will be the - last to support Python 2. (See :issue:`119` for rationale). + **Passlib 1.8 dropped support for Python 2.x, 3.3, and 3.4**; + If you need support for Python 2.6 - 3.4, use the Passlib 1.7.x series. -* CPython 2 -- v2.6 or newer. -* CPython 3 -- v3.3 or newer. -* PyPy -- v2.0 or newer. +* CPython 3 -- v3.5 or newer. * PyPy3 -- v5.3 or newer. -* Jython -- v2.7 or newer. Passlib should work with all operating systems and environments, as it contains builtin fallbacks for almost all OS-dependant features. Google App Engine is supported as well. +.. versionchanged:: 1.8 + + Support for Python 2.x, 3.3, and 3.4 was dropped. + Jython no longer listed, until it has a Python 3 compatible for testing. + .. versionchanged:: 1.7 Support for Python 2.5, 3.0-3.2 was dropped. @@ -36,25 +37,12 @@ Google App Engine is supported as well. Optional Libraries ================== -* `bcrypt <https://pypi.python.org/pypi/bcrypt>`_, - `py-bcrypt <https://pypi.python.org/pypi/py-bcrypt>`_, or - `bcryptor <https://bitbucket.org/ares/bcryptor/overview>`_ - - .. rst-class:: float-right +* `bcrypt <https://pypi.python.org/pypi/bcrypt>`_ - .. warning:: - - Support for ``py-bcrypt`` and ``bcryptor`` will be dropped in Passlib 1.8, - as these libraries are unmaintained. - - If any of these packages are installed, they will be used to provide - support for the BCrypt hash algorithm. - This is required if you want to handle BCrypt hashes, - and your OS does not provide native BCrypt support - via stdlib's :mod:`!crypt` (which includes pretty much all non-BSD systems). - - `bcrypt <https://pypi.python.org/pypi/bcrypt>`_ is currently the recommended - option -- it's actively maintained, and compatible with both CPython and PyPy. + If installed, this will be used to handle :class:~passlib.hash.bcrypt` and + :class:`~passlib.hash.bcrypt_sha256` hashes. If your system lacks :func:`crypt.crypt()` + support for bcrypt hashes, this library is *required* in order for passlib to provide + bcrypt support. Use ``pip install passlib[bcrypt]`` to get the recommended bcrypt setup. @@ -85,6 +73,10 @@ Optional Libraries If installed, this will be used to provide support for the :class:`~passlib.hash.scrypt` hash algorithm. If not installed, a MUCH slower builtin reference implementation will be used. +.. versionchanged:: 1.8 + + Dropped support for ``py-bcrypt`` and ``bcryptor`` backends. + .. versionchanged:: 1.7 Added fastpbkdf2, cryptography, argon2_cffi, argon2pure, and scrypt support. @@ -129,8 +121,7 @@ algorithms using multiple external sources (if detected at runtime). All unit tests are contained within the :mod:`passlib.tests` subpackage, and are designed to be run using the -`Nose <http://somethingaboutorange.com/mrl/projects/nose>`_ unit testing library -(as well as the ``unittest2`` library under Python 2.6). +`Nose <http://somethingaboutorange.com/mrl/projects/nose>`_ unit testing library. Once Passlib and Nose have been installed, the main suite of tests may be run using:: diff --git a/docs/lib/passlib.apache.rst b/docs/lib/passlib.apache.rst index b87eb63..969ead0 100644 --- a/docs/lib/passlib.apache.rst +++ b/docs/lib/passlib.apache.rst @@ -19,6 +19,10 @@ htpasswd and htdigest files; though the use of two helper classes. These classes will now preserve blank lines and "#" comments when updating htpasswd files; previous releases would throw a parse error. +.. versionchanged:: 1.8 + + A number of methods deprecated since 1.6, have been removed. + .. index:: Apache; htpasswd Htpasswd Files diff --git a/docs/lib/passlib.context.rst b/docs/lib/passlib.context.rst index 5ed7413..bec6f7e 100644 --- a/docs/lib/passlib.context.rst +++ b/docs/lib/passlib.context.rst @@ -166,36 +166,6 @@ Options which directly affect the behavior of the CryptContext instance: .. versionadded:: 1.7 -.. _context-min-verify-time-option: - -``min_verify_time`` - - If specified, unsuccessful :meth:`~CryptContext.verify` - calls will be penalized, and take at least this may - seconds before the method returns. May be an integer - or fractional number of seconds. - - .. deprecated:: 1.6 - This option has not proved very useful, is ignored by 1.7, - and will be removed in version 1.8. - - .. versionchanged:: 1.7 - - Per deprecation roadmap above, this option is now ignored. - -.. _context-harden-verify-option: - -``harden_verify`` - - Companion to ``min_verify_time``, currently ignored. - - .. versionadded:: 1.7 - - .. deprecated:: 1.7.1 - - This option is ignored by 1.7.1, and will be removed in 1.8 - along with ``min_verify_time``. - .. _context-algorithm-options: Algorithm Options @@ -537,9 +507,3 @@ if any invalid-but-correctable values are encountered Other Helpers ============= .. autoclass:: LazyCryptContext([schemes=None,] \*\*kwds [, onload=None]) - -.. rst-class:: html-toggle - -The CryptPolicy Class (deprecated) -================================== -.. autoclass:: CryptPolicy diff --git a/docs/lib/passlib.hash.bcrypt.rst b/docs/lib/passlib.hash.bcrypt.rst index 436148c..3bb5865 100644 --- a/docs/lib/passlib.hash.bcrypt.rst +++ b/docs/lib/passlib.hash.bcrypt.rst @@ -50,17 +50,10 @@ Bcrypt Backends .. rst-class:: float-center -.. warning:: - - Support for ``py-bcrypt`` and ``bcryptor`` will be dropped in Passlib 1.8, - as these libraries are unmaintained. - This class will use the first available of five possible backends: 1. `bcrypt <https://pypi.python.org/pypi/bcrypt>`_, if installed. -2. `py-bcrypt <https://pypi.python.org/pypi/py-bcrypt>`_, if installed (DEPRECATED) -3. `bcryptor <https://bitbucket.org/ares/bcryptor/overview>`_, if installed (DEPRECATED). 4. stdlib's :func:`crypt.crypt()`, if the host OS supports BCrypt (primarily BSD-derived systems). 5. A pure-python implementation of BCrypt, built into Passlib. diff --git a/docs/lib/passlib.hash.unix_disabled.rst b/docs/lib/passlib.hash.unix_disabled.rst index 8bdf9c3..2c68144 100644 --- a/docs/lib/passlib.hash.unix_disabled.rst +++ b/docs/lib/passlib.hash.unix_disabled.rst @@ -36,10 +36,6 @@ Interface ========= .. autoclass:: unix_disabled() -Deprecated Interface -==================== -.. autoclass:: unix_fallback() - Deviations ========== According to the Linux ``shadow`` man page, an empty string is treated diff --git a/docs/lib/passlib.utils.pbkdf2.rst b/docs/lib/passlib.utils.pbkdf2.rst index cfc503a..a72628a 100644 --- a/docs/lib/passlib.utils.pbkdf2.rst +++ b/docs/lib/passlib.utils.pbkdf2.rst @@ -32,6 +32,4 @@ PKCS#5 Key Derivation Functions Helper Functions ================ -.. autofunction:: norm_hash_name - .. autofunction:: get_prf diff --git a/passlib/__init__.py b/passlib/__init__.py index 963bfcc..7b9e2cd 100644 --- a/passlib/__init__.py +++ b/passlib/__init__.py @@ -1,3 +1,3 @@ """passlib - suite of password hashing & generation routines""" -__version__ = '1.7.4' +__version__ = '1.8.0.dev0' diff --git a/passlib/_setup/stamp.py b/passlib/_setup/stamp.py index f14c0d4..ebf6a11 100644 --- a/passlib/_setup/stamp.py +++ b/passlib/_setup/stamp.py @@ -2,7 +2,6 @@ #============================================================================= # imports #============================================================================= -from __future__ import absolute_import, division, print_function # core import datetime from distutils.dist import Distribution diff --git a/passlib/apache.py b/passlib/apache.py index a75f2cf..80a8a91 100644 --- a/passlib/apache.py +++ b/passlib/apache.py @@ -3,8 +3,8 @@ #============================================================================= # imports #============================================================================= -from __future__ import with_statement # core +from io import BytesIO import logging; log = logging.getLogger(__name__) import os from warnings import warn @@ -15,8 +15,7 @@ from passlib.context import CryptContext from passlib.exc import ExpectedStringError from passlib.hash import htdigest from passlib.utils import render_bytes, to_bytes, is_ascii_codec -from passlib.utils.decor import deprecated_method -from passlib.utils.compat import join_bytes, unicode, BytesIO, PY3 +from passlib.utils.compat import join_bytes # local __all__ = [ 'HtpasswdFile', @@ -50,9 +49,9 @@ class _CommonFile(object): # charset encoding used by file (defaults to utf-8) encoding = None - # whether users() and other public methods should return unicode or bytes? - # (defaults to False under PY2, True under PY3) - return_unicode = None + # whether users() and other public methods should return str or bytes? + # (defaults to True) + return_unicode = True # if bound to local file, these will be set. _path = None # local file path @@ -76,7 +75,7 @@ class _CommonFile(object): def from_string(cls, data, **kwds): """create new object from raw string. - :type data: unicode or bytes + :type data: str or bytes :arg data: database to load, as single string. @@ -107,17 +106,14 @@ class _CommonFile(object): #=================================================================== # init #=================================================================== - def __init__(self, path=None, new=False, autoload=True, autosave=False, - encoding="utf-8", return_unicode=PY3, + # XXX: add a new() classmethod, ala TOTP.new()? + + def __init__(self, path=None, new=False, autosave=False, + encoding="utf-8", return_unicode=True, ): # set encoding if not encoding: - warn("``encoding=None`` is deprecated as of Passlib 1.6, " - "and will cause a ValueError in Passlib 1.8, " - "use ``return_unicode=False`` instead.", - DeprecationWarning, stacklevel=2) - encoding = "utf-8" - return_unicode = False + raise TypeError("'encoding' is required") elif not is_ascii_codec(encoding): # htpasswd/htdigest files assumes 1-byte chars, and use ":" separator, # so only ascii-compatible encodings are allowed. @@ -131,11 +127,6 @@ class _CommonFile(object): self._mtime = 0 # init db - if not autoload: - warn("``autoload=False`` is deprecated as of Passlib 1.6, " - "and will be removed in Passlib 1.8, use ``new=True`` instead", - DeprecationWarning, stacklevel=2) - new = True if path and not new: self.load() else: @@ -181,33 +172,17 @@ class _CommonFile(object): self.load() return True - def load(self, path=None, force=True): + def load(self, path=None): """Load state from local file. If no path is specified, attempts to load from ``self.path``. :type path: str :arg path: local file to load from - - :type force: bool - :param force: - if ``force=False``, only load from ``self.path`` if file - has changed since last load. - - .. deprecated:: 1.6 - This keyword will be removed in Passlib 1.8; - Applications should use :meth:`load_if_changed` instead. """ if path is not None: with open(path, "rb") as fh: self._mtime = 0 self._load_lines(fh) - elif not force: - warn("%(name)s.load(force=False) is deprecated as of Passlib 1.6," - "and will be removed in Passlib 1.8; " - "use %(name)s.load_if_changed() instead." % - dict(name=self.__class__.__name__), - DeprecationWarning, stacklevel=2) - return self.load_if_changed() elif self._path: with open(self._path, "rb") as fh: self._mtime = os.path.getmtime(self._path) @@ -376,7 +351,7 @@ class _CommonFile(object): :returns: encoded identifer as bytes """ - if isinstance(value, unicode): + if isinstance(value, str): value = value.encode(self.encoding) elif not isinstance(value, bytes): raise ExpectedStringError(value, param) @@ -397,7 +372,7 @@ class _CommonFile(object): (usually indicates wrong encoding set for file). :returns: - field as unicode or bytes, as appropriate. + field as str or bytes, as appropriate. """ assert isinstance(value, bytes), "expected value to be bytes" if self.return_unicode: @@ -563,7 +538,7 @@ class HtpasswdFile(_CommonFile): .. versionadded:: 1.6 This feature was previously enabled by setting ``autoload=False``. - That alias has been deprecated, and will be removed in Passlib 1.8 + That alias was removed in Passlib 1.8 :type autosave: bool :param autosave: @@ -615,7 +590,7 @@ class HtpasswdFile(_CommonFile): .. versionadded:: 1.6 This keyword was previously named ``default``. That alias - has been deprecated, and will be removed in Passlib 1.8. + was removed in Passlib 1.8. .. versionchanged:: 1.6.3 @@ -641,23 +616,6 @@ class HtpasswdFile(_CommonFile): will probably not be usable by another application, and particularly not by Apache. - :param autoload: - Set to ``False`` to prevent the constructor from automatically - loaded the file from disk. - - .. deprecated:: 1.6 - This has been replaced by the *new* keyword. - Instead of setting ``autoload=False``, you should use - ``new=True``. Support for this keyword will be removed - in Passlib 1.8. - - :param default: - Change the default algorithm used to hash new passwords. - - .. deprecated:: 1.6 - This has been renamed to *default_scheme* for clarity. - Support for this alias will be removed in Passlib 1.8. - Loading & Saving ================ .. automethod:: load @@ -713,12 +671,6 @@ class HtpasswdFile(_CommonFile): #=================================================================== def __init__(self, path=None, default_scheme=None, context=htpasswd_context, **kwds): - if 'default' in kwds: - warn("``default`` is deprecated as of Passlib 1.6, " - "and will be removed in Passlib 1.8, it has been renamed " - "to ``default_scheem``.", - DeprecationWarning, stacklevel=2) - default_scheme = kwds.pop("default") if default_scheme: if default_scheme in _warn_no_bcrypt: warn("HtpasswdFile: no bcrypt backends available, " @@ -727,7 +679,7 @@ class HtpasswdFile(_CommonFile): default_scheme = htpasswd_defaults.get(default_scheme, default_scheme) context = context.copy(default=default_scheme) self.context = context - super(HtpasswdFile, self).__init__(path, **kwds) + super().__init__(path, **kwds) def _parse_record(self, record, lineno): # NOTE: should return (user, hash) tuple @@ -772,24 +724,17 @@ class HtpasswdFile(_CommonFile): .. versionchanged:: 1.6 This method was previously called ``update``, it was renamed to prevent ambiguity with the dictionary method. - The old alias is deprecated, and will be removed in Passlib 1.8. + The old alias was removed in Passlib 1.8. """ hash = self.context.hash(password) return self.set_hash(user, hash) - @deprecated_method(deprecated="1.6", removed="1.8", - replacement="set_password") - def update(self, user, password): - """set password for user""" - return self.set_password(user, password) - def get_hash(self, user): """Return hash stored for user, or ``None`` if user not found. .. versionchanged:: 1.6 This method was previously named ``find``, it was renamed - for clarity. The old name is deprecated, and will be removed - in Passlib 1.8. + for clarity. The old name was removed in Passlib 1.8. """ try: return self._records[self._encode_user(user)] @@ -807,19 +752,13 @@ class HtpasswdFile(_CommonFile): .. versionadded:: 1.7 """ # assert self.context.identify(hash), "unrecognized hash format" - if PY3 and isinstance(hash, str): + if isinstance(hash, str): hash = hash.encode(self.encoding) user = self._encode_user(user) existing = self._set_record(user, hash) self._autosave() return existing - @deprecated_method(deprecated="1.6", removed="1.8", - replacement="get_hash") - def find(self, user): - """return hash for user""" - return self.get_hash(user) - # XXX: rename to something more explicit, like delete_user()? def delete(self, user): """Delete user's entry. @@ -848,13 +787,13 @@ class HtpasswdFile(_CommonFile): .. versionchanged:: 1.6 This method was previously called ``verify``, it was renamed to prevent ambiguity with the :class:`!CryptContext` method. - The old alias is deprecated, and will be removed in Passlib 1.8. + The old alias was removed in Passlib 1.8. """ user = self._encode_user(user) hash = self._records.get(user) if hash is None: return None - if isinstance(password, unicode): + if isinstance(password, str): # NOTE: encoding password to match file, making the assumption # that server will use same encoding to hash the password. password = password.encode(self.encoding) @@ -866,12 +805,6 @@ class HtpasswdFile(_CommonFile): self._autosave() return ok - @deprecated_method(deprecated="1.6", removed="1.8", - replacement="check_password") - def verify(self, user, password): - """verify password for user""" - return self.check_password(user, password) - #=================================================================== # eoc #=================================================================== @@ -928,7 +861,7 @@ class HtdigestFile(_CommonFile): .. versionadded:: 1.6 This feature was previously enabled by setting ``autoload=False``. - That alias has been deprecated, and will be removed in Passlib 1.8 + That alias was removed in Passlib 1.8 :type autosave: bool :param autosave: @@ -949,16 +882,6 @@ class HtdigestFile(_CommonFile): This is also exposed as a readonly instance attribute. - :param autoload: - Set to ``False`` to prevent the constructor from automatically - loaded the file from disk. - - .. deprecated:: 1.6 - This has been replaced by the *new* keyword. - Instead of setting ``autoload=False``, you should use - ``new=True``. Support for this keyword will be removed - in Passlib 1.8. - Loading & Saving ================ .. automethod:: load @@ -1030,7 +953,7 @@ class HtdigestFile(_CommonFile): #=================================================================== def __init__(self, path=None, default_realm=None, **kwds): self.default_realm = default_realm - super(HtdigestFile, self).__init__(path, **kwds) + super().__init__(path, **kwds) def _parse_record(self, record, lineno): result = record.rstrip().split(_BCOLON) @@ -1121,12 +1044,6 @@ class HtdigestFile(_CommonFile): hash = htdigest.hash(password, user, realm, encoding=self.encoding) return self.set_hash(user, realm, hash) - @deprecated_method(deprecated="1.6", removed="1.8", - replacement="set_password") - def update(self, user, realm, password): - """set password for user""" - return self.set_password(user, realm, password) - def get_hash(self, user, realm=None): """Return :class:`~passlib.hash.htdigest` hash stored for user. @@ -1135,15 +1052,13 @@ class HtdigestFile(_CommonFile): .. versionchanged:: 1.6 This method was previously named ``find``, it was renamed - for clarity. The old name is deprecated, and will be removed - in Passlib 1.8. + for clarity. The old name is was removed Passlib 1.8. """ key = self._encode_key(user, realm) hash = self._records.get(key) if hash is None: return None - if PY3: - hash = hash.decode(self.encoding) + hash = hash.decode(self.encoding) return hash def set_hash(self, user, realm=None, hash=_UNSET): @@ -1165,19 +1080,13 @@ class HtdigestFile(_CommonFile): # called w/ two args - (user, hash), use default realm realm, hash = None, realm # assert htdigest.identify(hash), "unrecognized hash format" - if PY3 and isinstance(hash, str): + if isinstance(hash, str): hash = hash.encode(self.encoding) key = self._encode_key(user, realm) existing = self._set_record(key, hash) self._autosave() return existing - @deprecated_method(deprecated="1.6", removed="1.8", - replacement="get_hash") - def find(self, user, realm): - """return hash for user""" - return self.get_hash(user, realm) - # XXX: rename to something more explicit, like delete_user()? def delete(self, user, realm=None): """Delete user's entry for specified realm. @@ -1227,7 +1136,7 @@ class HtdigestFile(_CommonFile): .. versionchanged:: 1.6 This method was previously called ``verify``, it was renamed to prevent ambiguity with the :class:`!CryptContext` method. - The old alias is deprecated, and will be removed in Passlib 1.8. + The old alias was removed in Passlib 1.8. """ if password is _UNSET: # called w/ two args - (user, password), use default realm @@ -1240,12 +1149,6 @@ class HtdigestFile(_CommonFile): return htdigest.verify(password, hash, user, realm, encoding=self.encoding) - @deprecated_method(deprecated="1.6", removed="1.8", - replacement="check_password") - def verify(self, user, realm, password): - """verify password for user""" - return self.check_password(user, realm, password) - #=================================================================== # eoc #=================================================================== diff --git a/passlib/apps.py b/passlib/apps.py index 682bbff..e8a2555 100644 --- a/passlib/apps.py +++ b/passlib/apps.py @@ -56,7 +56,6 @@ def _load_master_config(): # disabled handlers 'django_disabled', 'unix_disabled', - 'unix_fallback', ] for name in excluded: schemes.remove(name) diff --git a/passlib/context.py b/passlib/context.py index bc3cbf5..21865a0 100644 --- a/passlib/context.py +++ b/passlib/context.py @@ -2,8 +2,9 @@ #============================================================================= # imports #============================================================================= -from __future__ import with_statement # core +from configparser import ConfigParser +from io import StringIO import re import logging; log = logging.getLogger(__name__) import threading @@ -19,17 +20,14 @@ from passlib.utils import (handlers as uh, to_bytes, as_bool, timer, rng, getrandstr, ) from passlib.utils.binary import BASE64_CHARS -from passlib.utils.compat import (iteritems, num_types, irange, - PY2, PY3, unicode, SafeConfigParser, - NativeStringIO, BytesIO, - unicode_or_bytes_types, native_string_types, +from passlib.utils.compat import (num_types, + unicode_or_bytes, ) from passlib.utils.decor import deprecated_method, memoized_property # local __all__ = [ 'CryptContext', 'LazyCryptContext', - 'CryptPolicy', ] #============================================================================= @@ -80,508 +78,6 @@ def _always_needs_update(hash, secret=None): _global_settings = set(["truncate_error", "vary_rounds"]) #============================================================================= -# crypt policy -#============================================================================= -_preamble = ("The CryptPolicy class has been deprecated as of " - "Passlib 1.6, and will be removed in Passlib 1.8. ") - -class CryptPolicy(object): - """ - .. deprecated:: 1.6 - This class has been deprecated, and will be removed in Passlib 1.8. - All of its functionality has been rolled into :class:`CryptContext`. - - This class previously stored the configuration options for the - CryptContext class. In the interest of interface simplification, - all of this class' functionality has been rolled into the CryptContext - class itself. - The documentation for this class is now focused on documenting how to - migrate to the new api. Additionally, where possible, the deprecation - warnings issued by the CryptPolicy methods will list the replacement call - that should be used. - - Constructors - ============ - CryptPolicy objects can be constructed directly using any of - the keywords accepted by :class:`CryptContext`. Direct uses of the - :class:`!CryptPolicy` constructor should either pass the keywords - directly into the CryptContext constructor, or to :meth:`CryptContext.update` - if the policy object was being used to update an existing context object. - - In addition to passing in keywords directly, - CryptPolicy objects can be constructed by the following methods: - - .. automethod:: from_path - .. automethod:: from_string - .. automethod:: from_source - .. automethod:: from_sources - .. automethod:: replace - - Introspection - ============= - All of the informational methods provided by this class have been deprecated - by identical or similar methods in the :class:`CryptContext` class: - - .. automethod:: has_schemes - .. automethod:: schemes - .. automethod:: iter_handlers - .. automethod:: get_handler - .. automethod:: get_options - .. automethod:: handler_is_deprecated - .. automethod:: get_min_verify_time - - Exporting - ========= - .. automethod:: iter_config - .. automethod:: to_dict - .. automethod:: to_file - .. automethod:: to_string - - .. note:: - CryptPolicy are immutable. - Use the :meth:`replace` method to mutate existing instances. - - .. deprecated:: 1.6 - """ - #=================================================================== - # class methods - #=================================================================== - @classmethod - def from_path(cls, path, section="passlib", encoding="utf-8"): - """create a CryptPolicy instance from a local file. - - .. deprecated:: 1.6 - - Creating a new CryptContext from a file, which was previously done via - ``CryptContext(policy=CryptPolicy.from_path(path))``, can now be - done via ``CryptContext.from_path(path)``. - See :meth:`CryptContext.from_path` for details. - - Updating an existing CryptContext from a file, which was previously done - ``context.policy = CryptPolicy.from_path(path)``, can now be - done via ``context.load_path(path)``. - See :meth:`CryptContext.load_path` for details. - """ - warn(_preamble + - "Instead of ``CryptPolicy.from_path(path)``, " - "use ``CryptContext.from_path(path)`` " - " or ``context.load_path(path)`` for an existing CryptContext.", - DeprecationWarning, stacklevel=2) - return cls(_internal_context=CryptContext.from_path(path, section, - encoding)) - - @classmethod - def from_string(cls, source, section="passlib", encoding="utf-8"): - """create a CryptPolicy instance from a string. - - .. deprecated:: 1.6 - - Creating a new CryptContext from a string, which was previously done via - ``CryptContext(policy=CryptPolicy.from_string(data))``, can now be - done via ``CryptContext.from_string(data)``. - See :meth:`CryptContext.from_string` for details. - - Updating an existing CryptContext from a string, which was previously done - ``context.policy = CryptPolicy.from_string(data)``, can now be - done via ``context.load(data)``. - See :meth:`CryptContext.load` for details. - """ - warn(_preamble + - "Instead of ``CryptPolicy.from_string(source)``, " - "use ``CryptContext.from_string(source)`` or " - "``context.load(source)`` for an existing CryptContext.", - DeprecationWarning, stacklevel=2) - return cls(_internal_context=CryptContext.from_string(source, section, - encoding)) - - @classmethod - def from_source(cls, source, _warn=True): - """create a CryptPolicy instance from some source. - - this method autodetects the source type, and invokes - the appropriate constructor automatically. it attempts - to detect whether the source is a configuration string, a filepath, - a dictionary, or an existing CryptPolicy instance. - - .. deprecated:: 1.6 - - Create a new CryptContext, which could previously be done via - ``CryptContext(policy=CryptPolicy.from_source(source))``, should - now be done using an explicit method: the :class:`CryptContext` - constructor itself, :meth:`CryptContext.from_path`, - or :meth:`CryptContext.from_string`. - - Updating an existing CryptContext, which could previously be done via - ``context.policy = CryptPolicy.from_source(source)``, should - now be done using an explicit method: :meth:`CryptContext.update`, - or :meth:`CryptContext.load`. - """ - if _warn: - warn(_preamble + - "Instead of ``CryptPolicy.from_source()``, " - "use ``CryptContext.from_string(path)`` " - " or ``CryptContext.from_path(source)``, as appropriate.", - DeprecationWarning, stacklevel=2) - if isinstance(source, CryptPolicy): - return source - elif isinstance(source, dict): - return cls(_internal_context=CryptContext(**source)) - elif not isinstance(source, (bytes,unicode)): - raise TypeError("source must be CryptPolicy, dict, config string, " - "or file path: %r" % (type(source),)) - elif any(c in source for c in "\n\r\t") or not source.strip(" \t./;:"): - return cls(_internal_context=CryptContext.from_string(source)) - else: - return cls(_internal_context=CryptContext.from_path(source)) - - @classmethod - def from_sources(cls, sources, _warn=True): - """create a CryptPolicy instance by merging multiple sources. - - each source is interpreted as by :meth:`from_source`, - and the results are merged together. - - .. deprecated:: 1.6 - Instead of using this method to merge multiple policies together, - a :class:`CryptContext` instance should be created, and then - the multiple sources merged together via :meth:`CryptContext.load`. - """ - if _warn: - warn(_preamble + - "Instead of ``CryptPolicy.from_sources()``, " - "use the various CryptContext constructors " - " followed by ``context.update()``.", - DeprecationWarning, stacklevel=2) - if len(sources) == 0: - raise ValueError("no sources specified") - if len(sources) == 1: - return cls.from_source(sources[0], _warn=False) - kwds = {} - for source in sources: - kwds.update(cls.from_source(source, _warn=False)._context.to_dict(resolve=True)) - return cls(_internal_context=CryptContext(**kwds)) - - def replace(self, *args, **kwds): - """create a new CryptPolicy, optionally updating parts of the - existing configuration. - - .. deprecated:: 1.6 - Callers of this method should :meth:`CryptContext.update` or - :meth:`CryptContext.copy` instead. - """ - if self._stub_policy: - warn(_preamble + # pragma: no cover -- deprecated & unused - "Instead of ``context.policy.replace()``, " - "use ``context.update()`` or ``context.copy()``.", - DeprecationWarning, stacklevel=2) - else: - warn(_preamble + - "Instead of ``CryptPolicy().replace()``, " - "create a CryptContext instance and " - "use ``context.update()`` or ``context.copy()``.", - DeprecationWarning, stacklevel=2) - sources = [ self ] - if args: - sources.extend(args) - if kwds: - sources.append(kwds) - return CryptPolicy.from_sources(sources, _warn=False) - - #=================================================================== - # instance attrs - #=================================================================== - - # internal CryptContext we're wrapping to handle everything - # until this class is removed. - _context = None - - # flag indicating this is wrapper generated by the CryptContext.policy - # attribute, rather than one created independantly by the application. - _stub_policy = False - - #=================================================================== - # init - #=================================================================== - def __init__(self, *args, **kwds): - context = kwds.pop("_internal_context", None) - if context: - assert isinstance(context, CryptContext) - self._context = context - self._stub_policy = kwds.pop("_stub_policy", False) - assert not (args or kwds), "unexpected args: %r %r" % (args,kwds) - else: - if args: - if len(args) != 1: - raise TypeError("only one positional argument accepted") - if kwds: - raise TypeError("cannot specify positional arg and kwds") - kwds = args[0] - warn(_preamble + - "Instead of constructing a CryptPolicy instance, " - "create a CryptContext directly, or use ``context.update()`` " - "and ``context.load()`` to reconfigure existing CryptContext " - "instances.", - DeprecationWarning, stacklevel=2) - self._context = CryptContext(**kwds) - - #=================================================================== - # public interface for examining options - #=================================================================== - def has_schemes(self): - """return True if policy defines *any* schemes for use. - - .. deprecated:: 1.6 - applications should use ``bool(context.schemes())`` instead. - see :meth:`CryptContext.schemes`. - """ - if self._stub_policy: - warn(_preamble + # pragma: no cover -- deprecated & unused - "Instead of ``context.policy.has_schemes()``, " - "use ``bool(context.schemes())``.", - DeprecationWarning, stacklevel=2) - else: - warn(_preamble + - "Instead of ``CryptPolicy().has_schemes()``, " - "create a CryptContext instance and " - "use ``bool(context.schemes())``.", - DeprecationWarning, stacklevel=2) - return bool(self._context.schemes()) - - def iter_handlers(self): - """return iterator over handlers defined in policy. - - .. deprecated:: 1.6 - applications should use ``context.schemes(resolve=True))`` instead. - see :meth:`CryptContext.schemes`. - """ - if self._stub_policy: - warn(_preamble + - "Instead of ``context.policy.iter_handlers()``, " - "use ``context.schemes(resolve=True)``.", - DeprecationWarning, stacklevel=2) - else: - warn(_preamble + - "Instead of ``CryptPolicy().iter_handlers()``, " - "create a CryptContext instance and " - "use ``context.schemes(resolve=True)``.", - DeprecationWarning, stacklevel=2) - return self._context.schemes(resolve=True, unconfigured=True) - - def schemes(self, resolve=False): - """return list of schemes defined in policy. - - .. deprecated:: 1.6 - applications should use :meth:`CryptContext.schemes` instead. - """ - if self._stub_policy: - warn(_preamble + # pragma: no cover -- deprecated & unused - "Instead of ``context.policy.schemes()``, " - "use ``context.schemes()``.", - DeprecationWarning, stacklevel=2) - else: - warn(_preamble + - "Instead of ``CryptPolicy().schemes()``, " - "create a CryptContext instance and " - "use ``context.schemes()``.", - DeprecationWarning, stacklevel=2) - return list(self._context.schemes(resolve=resolve, unconfigured=True)) - - def get_handler(self, name=None, category=None, required=False): - """return handler as specified by name, or default handler. - - .. deprecated:: 1.6 - applications should use :meth:`CryptContext.handler` instead, - though note that the ``required`` keyword has been removed, - and the new method will always act as if ``required=True``. - """ - if self._stub_policy: - warn(_preamble + - "Instead of ``context.policy.get_handler()``, " - "use ``context.handler()``.", - DeprecationWarning, stacklevel=2) - else: - warn(_preamble + - "Instead of ``CryptPolicy().get_handler()``, " - "create a CryptContext instance and " - "use ``context.handler()``.", - DeprecationWarning, stacklevel=2) - # CryptContext.handler() doesn't support required=False, - # so wrapping it in try/except - try: - return self._context.handler(name, category, unconfigured=True) - except KeyError: - if required: - raise - else: - return None - - def get_min_verify_time(self, category=None): - """get min_verify_time setting for policy. - - .. deprecated:: 1.6 - min_verify_time option will be removed entirely in passlib 1.8 - - .. versionchanged:: 1.7 - this method now always returns the value automatically - calculated by :meth:`CryptContext.min_verify_time`, - any value specified by policy is ignored. - """ - warn("get_min_verify_time() and min_verify_time option is deprecated and ignored, " - "and will be removed in Passlib 1.8", DeprecationWarning, - stacklevel=2) - return 0 - - def get_options(self, name, category=None): - """return dictionary of options specific to a given handler. - - .. deprecated:: 1.6 - this method has no direct replacement in the 1.6 api, as there - is not a clearly defined use-case. however, examining the output of - :meth:`CryptContext.to_dict` should serve as the closest alternative. - """ - # XXX: might make a public replacement, but need more study of the use cases. - if self._stub_policy: - warn(_preamble + # pragma: no cover -- deprecated & unused - "``context.policy.get_options()`` will no longer be available.", - DeprecationWarning, stacklevel=2) - else: - warn(_preamble + - "``CryptPolicy().get_options()`` will no longer be available.", - DeprecationWarning, stacklevel=2) - if hasattr(name, "name"): - name = name.name - return self._context._config._get_record_options_with_flag(name, category)[0] - - def handler_is_deprecated(self, name, category=None): - """check if handler has been deprecated by policy. - - .. deprecated:: 1.6 - this method has no direct replacement in the 1.6 api, as there - is not a clearly defined use-case. however, examining the output of - :meth:`CryptContext.to_dict` should serve as the closest alternative. - """ - # XXX: might make a public replacement, but need more study of the use cases. - if self._stub_policy: - warn(_preamble + - "``context.policy.handler_is_deprecated()`` will no longer be available.", - DeprecationWarning, stacklevel=2) - else: - warn(_preamble + - "``CryptPolicy().handler_is_deprecated()`` will no longer be available.", - DeprecationWarning, stacklevel=2) - if hasattr(name, "name"): - name = name.name - return self._context.handler(name, category).deprecated - - #=================================================================== - # serialization - #=================================================================== - - def iter_config(self, ini=False, resolve=False): - """iterate over key/value pairs representing the policy object. - - .. deprecated:: 1.6 - applications should use :meth:`CryptContext.to_dict` instead. - """ - if self._stub_policy: - warn(_preamble + # pragma: no cover -- deprecated & unused - "Instead of ``context.policy.iter_config()``, " - "use ``context.to_dict().items()``.", - DeprecationWarning, stacklevel=2) - else: - warn(_preamble + - "Instead of ``CryptPolicy().iter_config()``, " - "create a CryptContext instance and " - "use ``context.to_dict().items()``.", - DeprecationWarning, stacklevel=2) - # hacked code that renders keys & values in manner that approximates - # old behavior. context.to_dict() is much cleaner. - context = self._context - if ini: - def render_key(key): - return context._render_config_key(key).replace("__", ".") - def render_value(value): - if isinstance(value, (list,tuple)): - value = ", ".join(value) - return value - resolve = False - else: - render_key = context._render_config_key - render_value = lambda value: value - return ( - (render_key(key), render_value(value)) - for key, value in context._config.iter_config(resolve) - ) - - def to_dict(self, resolve=False): - """export policy object as dictionary of options. - - .. deprecated:: 1.6 - applications should use :meth:`CryptContext.to_dict` instead. - """ - if self._stub_policy: - warn(_preamble + - "Instead of ``context.policy.to_dict()``, " - "use ``context.to_dict()``.", - DeprecationWarning, stacklevel=2) - else: - warn(_preamble + - "Instead of ``CryptPolicy().to_dict()``, " - "create a CryptContext instance and " - "use ``context.to_dict()``.", - DeprecationWarning, stacklevel=2) - return self._context.to_dict(resolve) - - def to_file(self, stream, section="passlib"): # pragma: no cover -- deprecated & unused - """export policy to file. - - .. deprecated:: 1.6 - applications should use :meth:`CryptContext.to_string` instead, - and then write the output to a file as desired. - """ - if self._stub_policy: - warn(_preamble + - "Instead of ``context.policy.to_file(stream)``, " - "use ``stream.write(context.to_string())``.", - DeprecationWarning, stacklevel=2) - else: - warn(_preamble + - "Instead of ``CryptPolicy().to_file(stream)``, " - "create a CryptContext instance and " - "use ``stream.write(context.to_string())``.", - DeprecationWarning, stacklevel=2) - out = self._context.to_string(section=section) - if PY2: - out = out.encode("utf-8") - stream.write(out) - - def to_string(self, section="passlib", encoding=None): - """export policy to file. - - .. deprecated:: 1.6 - applications should use :meth:`CryptContext.to_string` instead. - """ - if self._stub_policy: - warn(_preamble + # pragma: no cover -- deprecated & unused - "Instead of ``context.policy.to_string()``, " - "use ``context.to_string()``.", - DeprecationWarning, stacklevel=2) - else: - warn(_preamble + - "Instead of ``CryptPolicy().to_string()``, " - "create a CryptContext instance and " - "use ``context.to_string()``.", - DeprecationWarning, stacklevel=2) - out = self._context.to_string(section=section) - if encoding: - out = out.encode(encoding) - return out - - #=================================================================== - # eoc - #=================================================================== - -#============================================================================= # _CryptConfig helper class #============================================================================= class _CryptConfig(object): @@ -641,7 +137,7 @@ class _CryptConfig(object): """initialize .handlers and .schemes attributes""" handlers = [] schemes = [] - if isinstance(data, native_string_types): + if isinstance(data, str): data = splitcomma(data) for elem in data or (): # resolve elem -> handler & scheme @@ -649,7 +145,7 @@ class _CryptConfig(object): handler = elem scheme = handler.name _validate_handler_name(scheme) - elif isinstance(elem, native_string_types): + elif isinstance(elem, str): handler = get_crypt_handler(elem) scheme = handler.name else: @@ -687,7 +183,7 @@ class _CryptConfig(object): categories = set() # load source config into internal storage - for (cat, scheme, key), value in iteritems(source): + for (cat, scheme, key), value in source.items(): categories.add(cat) explicit_scheme = scheme if not cat and not scheme and key in _global_settings: @@ -732,8 +228,6 @@ class _CryptConfig(object): raise KeyError("'schemes' context option is not allowed " "per category") key, value = norm_context_option(cat, key, value) - if key == "min_verify_time": # ignored in 1.7, to be removed in 1.8 - continue # store in context_options # map structure: context_options[key][category] = value @@ -754,7 +248,7 @@ class _CryptConfig(object): raise KeyError("%r option not allowed in CryptContext " "configuration" % (key,)) # coerce strings for certain fields (e.g. min_rounds uses ints) - if isinstance(value, native_string_types): + if isinstance(value, str): func = _coerce_scheme_options.get(key) if func: value = func(value) @@ -765,12 +259,12 @@ class _CryptConfig(object): if key == "default": if hasattr(value, "name"): value = value.name - elif not isinstance(value, native_string_types): + elif not isinstance(value, str): raise ExpectedTypeError(value, "str", "default") if schemes and value not in schemes: raise KeyError("default scheme not found in policy") elif key == "deprecated": - if isinstance(value, native_string_types): + if isinstance(value, str): value = splitcomma(value) elif not isinstance(value, (list,tuple)): raise ExpectedTypeError(value, "str or seq", "deprecated") @@ -783,19 +277,11 @@ class _CryptConfig(object): elif schemes: # make sure list of deprecated schemes is subset of configured schemes for scheme in value: - if not isinstance(scheme, native_string_types): + if not isinstance(scheme, str): raise ExpectedTypeError(value, "str", "deprecated element") if scheme not in schemes: raise KeyError("deprecated scheme not found " "in policy: %r" % (scheme,)) - elif key == "min_verify_time": - warn("'min_verify_time' was deprecated in Passlib 1.6, is " - "ignored in 1.7, and will be removed in 1.8", - DeprecationWarning) - elif key == "harden_verify": - warn("'harden_verify' is deprecated & ignored as of Passlib 1.7.1, " - " and will be removed in 1.8", - DeprecationWarning) elif key != "schemes": raise KeyError("unknown CryptContext keyword: %r" % (key,)) return key, value @@ -1059,12 +545,9 @@ class _CryptConfig(object): pass # type check - if category is not None and not isinstance(category, native_string_types): - if PY2 and isinstance(category, unicode): - # for compatibility with unicode-centric py2 apps - return self.get_record(scheme, category.encode("utf-8")) + if category is not None and not isinstance(category, str): raise ExpectedTypeError(category, "str or None", "category") - if scheme is not None and not isinstance(scheme, native_string_types): + if scheme is not None and not isinstance(scheme, str): raise ExpectedTypeError(scheme, "str or None", "scheme") # if scheme=None, @@ -1118,7 +601,7 @@ class _CryptConfig(object): # unique identifiers will work properly in a CryptContext. # XXX: if all handlers have a unique prefix (e.g. all are MCF / LDAP), # could use dict-lookup to speed up this search. - if not isinstance(hash, unicode_or_bytes_types): + if not isinstance(hash, unicode_or_bytes): raise ExpectedStringError(hash, "hash") # type check of category - handled by _get_record_list() for record in self._get_record_list(category): @@ -1255,7 +738,7 @@ class CryptContext(object): def from_string(cls, source, section="passlib", encoding="utf-8"): """create new CryptContext instance from an INI-formatted string. - :type source: unicode or bytes + :type source: str or bytes :arg source: string containing INI-formatted content. @@ -1284,8 +767,8 @@ class CryptContext(object): .. seealso:: :meth:`to_string`, the inverse of this constructor. """ - if not isinstance(source, unicode_or_bytes_types): - raise ExpectedTypeError(source, "unicode or bytes", "source") + if not isinstance(source, unicode_or_bytes): + raise ExpectedTypeError(source, "str or bytes", "source") self = cls(_autoload=False) self.load(source, section=section, encoding=encoding) return self @@ -1346,7 +829,7 @@ class CryptContext(object): .. versionadded:: 1.6 This method was previously named :meth:`!replace`. That alias - has been deprecated, and will be removed in Passlib 1.8. + was removed in Passlib 1.8. .. seealso:: :meth:`update` """ @@ -1365,100 +848,39 @@ class CryptContext(object): """ return self.copy(**kwds) - def replace(self, **kwds): - """deprecated alias of :meth:`copy`""" - warn("CryptContext().replace() has been deprecated in Passlib 1.6, " - "and will be removed in Passlib 1.8, " - "it has been renamed to CryptContext().copy()", - DeprecationWarning, stacklevel=2) - return self.copy(**kwds) - #=================================================================== # init #=================================================================== def __init__(self, schemes=None, # keyword only... - policy=_UNSET, # <-- deprecated _autoload=True, **kwds): # XXX: add ability to make flag certain contexts as immutable, # e.g. the builtin passlib ones? # XXX: add a name or import path for the contexts, to help out repr? if schemes is not None: kwds['schemes'] = schemes - if policy is not _UNSET: - warn("The CryptContext ``policy`` keyword has been deprecated as of Passlib 1.6, " - "and will be removed in Passlib 1.8; please use " - "``CryptContext.from_string()` or " - "``CryptContext.from_path()`` instead.", - DeprecationWarning) - if policy is None: - self.load(kwds) - elif isinstance(policy, CryptPolicy): - self.load(policy._context) - self.update(kwds) - else: - raise TypeError("policy must be a CryptPolicy instance") - elif _autoload: + if _autoload: self.load(kwds) else: assert not kwds, "_autoload=False and kwds are mutually exclusive" # XXX: would this be useful? ##def __str__(self): - ## if PY3: - ## return self.to_string() - ## else: - ## return self.to_string().encode("utf-8") + ## return self.to_string() def __repr__(self): return "<CryptContext at 0x%0x>" % id(self) #=================================================================== - # deprecated policy object - #=================================================================== - def _get_policy(self): - # The CryptPolicy class has been deprecated, so to support any - # legacy accesses, we create a stub policy object so .policy attr - # will continue to work. - # - # the code waits until app accesses a specific policy object attribute - # before issuing deprecation warning, so developer gets method-specific - # suggestion for how to upgrade. - - # NOTE: making a copy of the context so the policy acts like a snapshot, - # to retain the pre-1.6 behavior. - return CryptPolicy(_internal_context=self.copy(), _stub_policy=True) - - def _set_policy(self, policy): - warn("The CryptPolicy class and the ``context.policy`` attribute have " - "been deprecated as of Passlib 1.6, and will be removed in " - "Passlib 1.8; please use the ``context.load()`` and " - "``context.update()`` methods instead.", - DeprecationWarning, stacklevel=2) - if isinstance(policy, CryptPolicy): - self.load(policy._context) - else: - raise TypeError("expected CryptPolicy instance") - - policy = property(_get_policy, _set_policy, - doc="[deprecated] returns CryptPolicy instance " - "tied to this CryptContext") - - #=================================================================== # loading / updating configuration #=================================================================== @staticmethod def _parse_ini_stream(stream, section, filename): """helper read INI from stream, extract passlib section as dict""" - # NOTE: this expects a unicode stream under py3, - # and a utf-8 bytes stream under py2, - # allowing the resulting dict to always use native strings. - p = SafeConfigParser() - if PY3: - # python 3.2 deprecated readfp in favor of read_file - p.read_file(stream, filename) - else: - p.readfp(stream, filename) + # NOTE: this expects a unicode stream, + # and resulting dict will always use native strings. + p = ConfigParser() + p.read_file(stream, filename) # XXX: could change load() to accept list of items, # and skip intermediate dict creation return dict(p.items(section)) @@ -1474,22 +896,9 @@ class CryptContext(object): .. versionadded:: 1.6 """ - def helper(stream): + with open(path, "rt", encoding=encoding) as stream: kwds = self._parse_ini_stream(stream, section, path) - return self.load(kwds, update=update) - if PY3: - # decode to unicode, which load() expected under py3 - with open(path, "rt", encoding=encoding) as stream: - return helper(stream) - elif encoding in ["utf-8", "ascii"]: - # keep as utf-8 bytes, which load() expects under py2 - with open(path, "rb") as stream: - return helper(stream) - else: - # transcode to utf-8 bytes - with open(path, "rb") as fh: - tmp = fh.read().decode(encoding).encode("utf-8") - return helper(BytesIO(tmp)) + return self.load(kwds, update=update) def load(self, source, update=False, section="passlib", encoding="utf-8"): """Load new configuration into CryptContext, replacing existing config. @@ -1503,7 +912,7 @@ class CryptContext(object): the key/value pairs will be interpreted the same keywords for the :class:`CryptContext` class constructor. - * a :class:`!unicode` or :class:`!bytes` string + * a :class:`!str` or :class:`!bytes` string this will be interpreted as an INI-formatted file, and appropriate key/value pairs will be loaded from @@ -1556,13 +965,9 @@ class CryptContext(object): # autodetect source type, convert to dict #----------------------------------------------------------- parse_keys = True - if isinstance(source, unicode_or_bytes_types): - if PY3: - source = to_unicode(source, encoding, param="source") - else: - source = to_bytes(source, "utf-8", source_encoding=encoding, - param="source") - source = self._parse_ini_stream(NativeStringIO(source), section, + if isinstance(source, unicode_or_bytes): + source = to_unicode(source, encoding, param="source") + source = self._parse_ini_stream(StringIO(source), section, "<string passed to CryptContext.load()>") elif isinstance(source, CryptContext): # extract dict directly from config, so it can be merged later @@ -1580,8 +985,7 @@ class CryptContext(object): #----------------------------------------------------------- if parse_keys: parse = self._parse_config_key - source = dict((parse(key), value) - for key, value in iteritems(source)) + source = dict((parse(key), value) for key, value in source.items()) if update and self._config is not None: # if updating, do nothing if source is empty, if not source: @@ -1610,7 +1014,7 @@ class CryptContext(object): def _parse_config_key(ckey): """helper used to parse ``cat__scheme__option`` keys into a tuple""" # split string into 1-3 parts - assert isinstance(ckey, native_string_types) + assert isinstance(ckey, str) parts = ckey.replace(".", "__").split("__") count = len(parts) if count == 1: @@ -1669,8 +1073,7 @@ class CryptContext(object): ## return ## ## def strip_items(target, filter): - ## keys = [key for key,value in iteritems(target) - ## if filter(key,value)] + ## keys = [key for key,value in target.items() if filter(key,value)] ## for key in keys: ## del target[key] ## @@ -1695,7 +1098,7 @@ class CryptContext(object): ## strip_items(deprecated, lambda k,v: k and v==cur) ## ## # remove redundant category options. - ## for scheme, config in iteritems(scheme_options): + ## for scheme, config in scheme_options.items(): ## if None in config: ## cur = config[None] ## strip_items(config, lambda k,v: k and v==cur) @@ -1881,7 +1284,7 @@ class CryptContext(object): else: value = str(value) - assert isinstance(value, native_string_types), \ + assert isinstance(value, str), \ "expected string for key: %r %r" % (key, value) # escape any percent signs. @@ -1958,9 +1361,9 @@ class CryptContext(object): .. seealso:: the :ref:`context-serialization-example` example in the tutorial. """ - parser = SafeConfigParser() + parser = ConfigParser() self._write_to_parser(parser, section) - buf = NativeStringIO() + buf = StringIO() parser.write(buf) unregistered = self._get_unregistered_handlers() if unregistered: @@ -1968,10 +1371,7 @@ class CryptContext(object): "# NOTE: the %s handler(s) are not registered with Passlib,\n" "# this string may not correctly reproduce the current configuration.\n\n" ) % ", ".join(repr(handler.name) for handler in unregistered)) - out = buf.getvalue() - if not PY3: - out = out.decode("utf-8") - return out + return buf.getvalue() # XXX: is this useful enough to enable? ##def write_to_path(self, path, section="passlib", update=False): @@ -1987,23 +1387,6 @@ class CryptContext(object): ## fh.close() #=================================================================== - # verify() hardening - # NOTE: this entire feature has been disabled. - # all contents of this section are NOOPs as of 1.7.1, - # and will be removed in 1.8. - #=================================================================== - - mvt_estimate_max_samples = 20 - mvt_estimate_min_samples = 10 - mvt_estimate_max_time = 2 - mvt_estimate_resolution = 0.01 - harden_verify = None - min_verify_time = 0 - - def reset_min_verify_time(self): - self._reset_dummy_verify() - - #=================================================================== # password hash api #=================================================================== @@ -2023,7 +1406,7 @@ class CryptContext(object): def _get_or_identify_record(self, hash, scheme=None, category=None): """return record based on scheme, or failing that, by identifying hash""" if scheme: - if not isinstance(hash, unicode_or_bytes_types): + if not isinstance(hash, unicode_or_bytes): raise ExpectedStringError(hash, "hash") return self._get_record(scheme, category) else: @@ -2060,7 +1443,7 @@ class CryptContext(object): If so, the password should be re-hashed using :meth:`hash` Otherwise, it will return ``False``. - :type hash: unicode or bytes + :type hash: str or bytes :arg hash: The hash string to examine. @@ -2084,7 +1467,7 @@ class CryptContext(object): be used when determining if the hash needs to be updated (e.g. is below the minimum rounds). - :type secret: unicode, bytes, or None + :type secret: str, bytes, or None :param secret: Optional secret associated with the provided ``hash``. This is not required, or even currently used for anything... @@ -2166,7 +1549,7 @@ class CryptContext(object): All registered algorithms will be checked, from first to last, and whichever one positively identifies the hash first will be returned. - :type hash: unicode or bytes + :type hash: str or bytes :arg hash: The hash string to test. @@ -2204,7 +1587,7 @@ class CryptContext(object): def hash(self, secret, scheme=None, category=None, **kwds): """run secret through selected algorithm, returning resulting hash. - :type secret: unicode or bytes + :type secret: str or bytes :arg secret: the password to hash. @@ -2243,7 +1626,7 @@ class CryptContext(object): .. seealso:: the :ref:`context-basic-example` example in the tutorial """ - # XXX: could insert normalization to preferred unicode encoding here + # XXX: could insert normalization to preferred str encoding here if scheme is not None: # TODO: offer replacement alternative. # ``context.handler(scheme).hash()`` would work, @@ -2277,11 +1660,11 @@ class CryptContext(object): (limited to the schemes configured for this context). It will then check whether the password verifies against the hash. - :type secret: unicode or bytes + :type secret: str or bytes :arg secret: the secret to verify - :type hash: unicode or bytes + :type hash: str or bytes :arg hash: hash string to compare to @@ -2357,11 +1740,11 @@ class CryptContext(object): which wish to update deprecated hashes, and this call takes care of all 3 steps efficiently. - :type secret: unicode or bytes + :type secret: str or bytes :arg secret: the secret to verify - :type secret: unicode or bytes + :type secret: str or bytes :arg hash: hash string to compare to. @@ -2458,7 +1841,7 @@ class CryptContext(object): """ type(self)._dummy_hash.clear_cache(self) - def dummy_verify(self, elapsed=0): + def dummy_verify(self): """ Helper that applications can call when user wasn't found, in order to simulate time it would take to hash a password. @@ -2466,12 +1849,6 @@ class CryptContext(object): Runs verify() against a dummy hash, to simulate verification of a real account password. - :param elapsed: - - .. deprecated:: 1.7.1 - - this option is ignored, and will be removed in passlib 1.8. - .. versionadded:: 1.7 """ self.verify(self._dummy_secret, self._dummy_hash) @@ -2567,12 +1944,6 @@ class LazyCryptContext(CryptContext): .. versionadded:: 1.6 - :param create_policy: - - .. deprecated:: 1.6 - This option will be removed in Passlib 1.8, - applications should use ``onload`` instead. - :param kwds: All additional keywords are passed to CryptContext; @@ -2596,11 +1967,9 @@ class LazyCryptContext(CryptContext): """ _lazy_kwds = None - # NOTE: the way this class works changed in 1.6. - # previously it just called _lazy_init() when ``.policy`` was - # first accessed. now that is done whenever any of the public - # attributes are accessed, and the class itself is changed - # to a regular CryptContext, to remove the overhead once it's unneeded. + # NOTE: the way this class works is that whenever any of the public + # attributes are accessed, _lazy_init() is invoked, the class itself is changed + # to a regular CryptContext (to remove the overhead once it's unneeded) def __init__(self, schemes=None, **kwds): if schemes is not None: @@ -2609,21 +1978,11 @@ class LazyCryptContext(CryptContext): def _lazy_init(self): kwds = self._lazy_kwds - if 'create_policy' in kwds: - warn("The CryptPolicy class, and LazyCryptContext's " - "``create_policy`` keyword have been deprecated as of " - "Passlib 1.6, and will be removed in Passlib 1.8; " - "please use the ``onload`` keyword instead.", - DeprecationWarning) - create_policy = kwds.pop("create_policy") - result = create_policy(**kwds) - policy = CryptPolicy.from_source(result, _warn=False) - kwds = policy._context.to_dict() - elif 'onload' in kwds: + if 'onload' in kwds: onload = kwds.pop("onload") kwds = onload(**kwds) del self._lazy_kwds - super(LazyCryptContext, self).__init__(**kwds) + super().__init__(**kwds) self.__class__ = CryptContext def __getattribute__(self, attr): diff --git a/passlib/crypto/_blowfish/__init__.py b/passlib/crypto/_blowfish/__init__.py index 1aa1c85..3ee4590 100644 --- a/passlib/crypto/_blowfish/__init__.py +++ b/passlib/crypto/_blowfish/__init__.py @@ -56,7 +56,6 @@ import struct # pkg from passlib.utils import getrandbytes, rng from passlib.utils.binary import bcrypt64 -from passlib.utils.compat import BytesIO, unicode, u, native_string_types from passlib.crypto._blowfish.unrolled import BlowfishEngine # local __all__ = [ @@ -99,13 +98,13 @@ def raw_bcrypt(password, ident, salt, log_rounds): #=================================================================== # parse ident - assert isinstance(ident, native_string_types) + assert isinstance(ident, str) add_null_padding = True - if ident == u('2a') or ident == u('2y') or ident == u('2b'): + if ident == u'2a' or ident == u'2y' or ident == u'2b': pass - elif ident == u('2'): + elif ident == u'2': add_null_padding = False - elif ident == u('2x'): + elif ident == u'2x': raise ValueError("crypt_blowfish's buggy '2x' hashes are not " "currently supported") else: diff --git a/passlib/crypto/_blowfish/_gen_files.py b/passlib/crypto/_blowfish/_gen_files.py index 2f92cf8..b1486e2 100644 --- a/passlib/crypto/_blowfish/_gen_files.py +++ b/passlib/crypto/_blowfish/_gen_files.py @@ -6,14 +6,13 @@ import os import textwrap # pkg -from passlib.utils.compat import irange # local #============================================================================= # helpers #============================================================================= def varlist(name, count): - return ", ".join(name + str(x) for x in irange(count)) + return ", ".join(name + str(x) for x in range(count)) def indent_block(block, padding): @@ -30,7 +29,7 @@ BFSTR = """\ """.strip() def render_encipher(write, indent=0): - for i in irange(0, 15, 2): + for i in range(0, 15, 2): write(indent, """\ # Feistel substitution on left word (round %(i)d) r ^= %(left)s ^ p%(i1)d @@ -74,7 +73,7 @@ def write_expand_function(write, indent=0): # integrate key #============================================================= """) - for i in irange(18): + for i in range(18): write(indent+1, """\ p%(i)d = P[%(i)d] ^ key_words[%(i)d] """, i=i) @@ -99,7 +98,7 @@ def write_expand_function(write, indent=0): """) - for i in irange(2, 18, 2): + for i in range(2, 18, 2): write(indent+1, """\ #------------------------------------------------ # update P[%(i)d] and P[%(i1)d] diff --git a/passlib/crypto/_md4.py b/passlib/crypto/_md4.py index bdc211f..e6ab63e 100644 --- a/passlib/crypto/_md4.py +++ b/passlib/crypto/_md4.py @@ -20,7 +20,7 @@ Implementated based on rfc at http://www.faqs.org/rfcs/rfc1320.html from binascii import hexlify import struct # site -from passlib.utils.compat import bascii_to_str, irange, PY3 +from passlib.utils.compat import bascii_to_str # local __all__ = ["md4"] @@ -176,16 +176,12 @@ class md4(object): state[a] = ((t<<s) & MASK_32) + (t>>(32-s)) # add back into original state - for i in irange(4): + for i in range(4): orig[i] = (orig[i]+state[i]) & MASK_32 def update(self, content): if not isinstance(content, bytes): - if PY3: - raise TypeError("expected bytes") - else: - # replicate behavior of hashlib under py2 - content = content.encode("ascii") + raise TypeError("expected bytes") buf = self._buf if buf: content = buf + content diff --git a/passlib/crypto/des.py b/passlib/crypto/des.py index 3f87aef..fc63ee0 100644 --- a/passlib/crypto/des.py +++ b/passlib/crypto/des.py @@ -47,8 +47,6 @@ The netbsd des-crypt implementation has some nice notes on how this all works - import struct # pkg from passlib import exc -from passlib.utils.compat import join_byte_values, byte_elem_value, \ - irange, irange, int_types # local __all__ = [ "expand_des_key", @@ -606,14 +604,14 @@ def _unpack56(value): ## assert 0 <= value < 0x80, "value out of range" ## return (value<<1) | (0x9669 >> ((value ^ (value >> 4)) & 0xf)) & 1 -_EXPAND_ITER = irange(49,-7,-7) +_EXPAND_ITER = range(49, -7, -7) def expand_des_key(key): """convert DES from 7 bytes to 8 bytes (by inserting empty parity bits)""" if isinstance(key, bytes): if len(key) != 7: raise ValueError("key must be 7 bytes in size") - elif isinstance(key, int_types): + elif isinstance(key, int): if key < 0 or key > INT_56_MASK: raise ValueError("key must be 56-bit non-negative integer") return _unpack64(expand_des_key(_pack56(key))) @@ -624,9 +622,8 @@ def expand_des_key(key): # but the parity bit would just be ignored in des_encrypt_block(), # so not bothering to use it. # XXX: could make parity-restoring optionally available via flag - ##return join_byte_values(expand_7bit((key >> shift) & 0x7f) - ## for shift in _EXPAND_ITER) - return join_byte_values(((key>>shift) & 0x7f)<<1 for shift in _EXPAND_ITER) + ##return bytes(expand_7bit((key >> shift) & 0x7f) for shift in _EXPAND_ITER) + return bytes(((key >> shift) & 0x7f) << 1 for shift in _EXPAND_ITER) def shrink_des_key(key): """convert DES key from 8 bytes to 7 bytes (by discarding the parity bits)""" @@ -634,7 +631,7 @@ def shrink_des_key(key): if len(key) != 8: raise ValueError("key must be 8 bytes in size") return _pack56(shrink_des_key(_unpack64(key))) - elif isinstance(key, int_types): + elif isinstance(key, int): if key < 0 or key > INT_64_MASK: raise ValueError("key must be 64-bit non-negative integer") else: @@ -748,13 +745,13 @@ def des_encrypt_int_block(key, input, salt=0, rounds=1): raise ValueError("salt must be 24-bit non-negative integer") # validate & unpack key - if not isinstance(key, int_types): + if not isinstance(key, int): raise exc.ExpectedTypeError(key, "int", "key") elif key < 0 or key > INT_64_MASK: raise ValueError("key must be 64-bit non-negative integer") # validate & unpack input - if not isinstance(input, int_types): + if not isinstance(input, int): raise exc.ExpectedTypeError(input, "int", "input") elif input < 0 or input > INT_64_MASK: raise ValueError("input must be 64-bit non-negative integer") diff --git a/passlib/crypto/digest.py b/passlib/crypto/digest.py index 90e0cad..c1b9769 100644 --- a/passlib/crypto/digest.py +++ b/passlib/crypto/digest.py @@ -5,7 +5,6 @@ #============================================================================= # imports #============================================================================= -from __future__ import division # core import hashlib import logging; log = logging.getLogger(__name__) @@ -31,9 +30,8 @@ except ImportError: _fast_pbkdf2_hmac = None # pkg from passlib import exc -from passlib.utils import join_bytes, to_native_str, join_byte_values, to_bytes, \ - SequenceMixin, as_bool -from passlib.utils.compat import irange, int_types, unicode_or_bytes_types, PY3, error_from +from passlib.utils import join_bytes, to_native_str, to_bytes, SequenceMixin, as_bool +from passlib.utils.compat import unicode_or_bytes from passlib.utils.decor import memoized_property # local __all__ = [ @@ -126,7 +124,7 @@ def _gen_fallback_info(): not invoked at runtime. """ out = {} - for alg in sorted(hashlib.algorithms_available | set(["md4"])): + for alg in sorted(hashlib.algorithms_available | {"md4"}): info = lookup_hash(alg) out[info.name] = (info.digest_size, info.block_size) return out @@ -298,7 +296,7 @@ def lookup_hash(digest, # *, # resolve ``digest`` to ``const`` & ``name_record`` cache_by_name = True - if isinstance(digest, unicode_or_bytes_types): + if isinstance(digest, unicode_or_bytes): # normalize name name_list = _get_hash_aliases(digest) name = name_list[0] @@ -584,7 +582,7 @@ mock_fips_mode = False #: algorithms allowed under FIPS mode (subset of hashlib.algorithms_available); #: per https://csrc.nist.gov/Projects/Hash-Functions FIPS 202 list. -_fips_algorithms = set([ +_fips_algorithms = { # FIPS 180-4 and FIPS 202 'sha1', 'sha224', @@ -601,7 +599,7 @@ _fips_algorithms = set([ 'sha3_512', 'shake_128', 'shake_256', -]) +} def _set_mock_fips_mode(enable=True): @@ -622,8 +620,8 @@ if as_bool(os.environ.get("PASSLIB_MOCK_FIPS_MODE")): #============================================================================= #: translation tables used by compile_hmac() -_TRANS_5C = join_byte_values((x ^ 0x5C) for x in irange(256)) -_TRANS_36 = join_byte_values((x ^ 0x36) for x in irange(256)) +_TRANS_5C = bytes((x ^ 0x5C) for x in range(256)) +_TRANS_36 = bytes((x ^ 0x36) for x in range(256)) def compile_hmac(digest, key, multipart=False): """ @@ -634,7 +632,7 @@ def compile_hmac(digest, key, multipart=False): digest name or constructor. :arg key: - secret key as :class:`!bytes` or :class:`!unicode` (unicode will be encoded using utf-8). + secret key as :class:`!bytes` or :class:`!str` (str will be encoded using utf-8). :param multipart: request a multipart constructor instead (see return description). @@ -713,11 +711,11 @@ def pbkdf1(digest, secret, salt, rounds, keylen=None): :arg secret: secret to use when generating the key. - may be :class:`!bytes` or :class:`unicode` (encoded using UTF-8). + may be :class:`!bytes` or :class:`str` (encoded using UTF-8). :arg salt: salt string to use when generating key. - may be :class:`!bytes` or :class:`unicode` (encoded using UTF-8). + may be :class:`!bytes` or :class:`str` (encoded using UTF-8). :param rounds: number of rounds to use to generate key. @@ -742,7 +740,7 @@ def pbkdf1(digest, secret, salt, rounds, keylen=None): salt = to_bytes(salt, param="salt") # validate rounds - if not isinstance(rounds, int_types): + if not isinstance(rounds, int): raise exc.ExpectedTypeError(rounds, "int", "rounds") if rounds < 1: raise ValueError("rounds must be at least 1") @@ -750,7 +748,7 @@ def pbkdf1(digest, secret, salt, rounds, keylen=None): # validate keylen if keylen is None: keylen = digest_size - elif not isinstance(keylen, int_types): + elif not isinstance(keylen, int): raise exc.ExpectedTypeError(keylen, "int or None", "keylen") elif keylen < 0: raise ValueError("keylen must be at least 0") @@ -760,7 +758,7 @@ def pbkdf1(digest, secret, salt, rounds, keylen=None): # main pbkdf1 loop block = secret + salt - for _ in irange(rounds): + for _ in range(rounds): block = const(block).digest() return block[:keylen] @@ -778,11 +776,11 @@ def pbkdf2_hmac(digest, secret, salt, rounds, keylen=None): :arg secret: passphrase to use to generate key. - may be :class:`!bytes` or :class:`unicode` (encoded using UTF-8). + may be :class:`!bytes` or :class:`str` (encoded using UTF-8). :arg salt: salt string to use when generating key. - may be :class:`!bytes` or :class:`unicode` (encoded using UTF-8). + may be :class:`!bytes` or :class:`str` (encoded using UTF-8). :param rounds: number of rounds to use to generate key. @@ -814,7 +812,7 @@ def pbkdf2_hmac(digest, secret, salt, rounds, keylen=None): digest_size = digest_info.digest_size # validate rounds - if not isinstance(rounds, int_types): + if not isinstance(rounds, int): raise exc.ExpectedTypeError(rounds, "int", "rounds") if rounds < 1: raise ValueError("rounds must be at least 1") @@ -822,7 +820,7 @@ def pbkdf2_hmac(digest, secret, salt, rounds, keylen=None): # validate keylen if keylen is None: keylen = digest_size - elif not isinstance(keylen, int_types): + elif not isinstance(keylen, int): raise exc.ExpectedTypeError(keylen, "int or None", "keylen") elif keylen < 1: # XXX: could allow keylen=0, but want to be compat w/ stdlib @@ -866,7 +864,7 @@ def pbkdf2_hmac(digest, secret, salt, rounds, keylen=None): # assemble & return result return join_bytes( calc_block(keyed_hmac, keyed_hmac(salt + _pack_uint32(i)), rounds) - for i in irange(1, block_count + 1) + for i in range(1, block_count + 1) )[:keylen] #------------------------------------------------------------------------------------- @@ -876,7 +874,7 @@ def pbkdf2_hmac(digest, secret, salt, rounds, keylen=None): # NOTE: this env var is only present to support the admin/benchmark_pbkdf2 script _force_backend = os.environ.get("PASSLIB_PBKDF2_BACKEND") or "any" -if PY3 and _force_backend in ["any", "from-bytes"]: +if _force_backend in ["any", "from-bytes"]: from functools import partial def _get_pbkdf2_looper(digest_size): @@ -890,14 +888,19 @@ if PY3 and _force_backend in ["any", "from-bytes"]: from_bytes = int.from_bytes BIG = "big" # endianess doesn't matter, just has to be consistent accum = from_bytes(digest, BIG) - for _ in irange(rounds - 1): + for _ in range(rounds - 1): digest = keyed_hmac(digest) accum ^= from_bytes(digest, BIG) return accum.to_bytes(digest_size, BIG) _builtin_backend = "from-bytes" -elif _force_backend in ["any", "unpack", "from-bytes"]: +elif _force_backend in ["unpack"]: + + # XXX: should run bench_pbkdf2() to verify; + # but think this can be removed now that we're always on python 3 + # (the from_bytes method should always be faster) + from struct import Struct from passlib.utils import sys_bits @@ -912,7 +915,7 @@ elif _force_backend in ["any", "unpack", "from-bytes"]: def helper(keyed_hmac, digest, rounds): accum = digest - for _ in irange(rounds - 1): + for _ in range(rounds - 1): digest = keyed_hmac(digest) accum ^= digest return accum @@ -969,8 +972,8 @@ elif _force_backend in ["any", "unpack", "from-bytes"]: # tdict = dict( digest_size=digest_size, - accum_vars=", ".join("acc_%d" % i for i in irange(count)), - digest_vars=", ".join("dig_%d" % i for i in irange(count)), + accum_vars=", ".join("acc_%d" % i for i in range(count)), + digest_vars=", ".join("dig_%d" % i for i in range(count)), ) # head of function @@ -979,13 +982,13 @@ elif _force_backend in ["any", "unpack", "from-bytes"]: " '''pbkdf2 loop helper for digest_size={digest_size}'''\n" " unpack_digest = struct.unpack\n" " {accum_vars} = unpack_digest(digest)\n" - " for _ in irange(1, rounds):\n" + " for _ in range(1, rounds):\n" " digest = keyed_hmac(digest)\n" " {digest_vars} = unpack_digest(digest)\n" ).format(**tdict) # xor digest - for i in irange(count): + for i in range(count): source += " acc_%d ^= dig_%d\n" % (i, i) # return result @@ -995,7 +998,7 @@ elif _force_backend in ["any", "unpack", "from-bytes"]: # compile helper # code = compile(source, "<generated by passlib.crypto.digest._get_pbkdf2_looper()>", "exec") - gdict = dict(irange=irange, struct=struct) + gdict = dict(struct=struct) ldict = dict() eval(code, gdict, ldict) helper = ldict['helper'] @@ -1011,7 +1014,7 @@ elif _force_backend in ["any", "unpack", "from-bytes"]: _builtin_backend = "unpack" else: - assert _force_backend in ["any", "hexlify"] + assert _force_backend in ["hexlify"] # XXX: older & slower approach that used int(hexlify()), # keeping it around for a little while just for benchmarking. @@ -1025,7 +1028,7 @@ else: def _pbkdf2_looper(keyed_hmac, digest, rounds): hexlify = _hexlify accum = int(hexlify(digest), 16) - for _ in irange(rounds - 1): + for _ in range(rounds - 1): digest = keyed_hmac(digest) accum ^= int(hexlify(digest), 16) return int_to_bytes(accum, len(digest)) diff --git a/passlib/crypto/scrypt/__init__.py b/passlib/crypto/scrypt/__init__.py index c71873a..cd10632 100644 --- a/passlib/crypto/scrypt/__init__.py +++ b/passlib/crypto/scrypt/__init__.py @@ -6,7 +6,6 @@ XXX: add this module to public docs? #========================================================================== # imports #========================================================================== -from __future__ import absolute_import # core import logging; log = logging.getLogger(__name__) from warnings import warn @@ -108,10 +107,10 @@ def scrypt(secret, salt, n, r, p=1, keylen=32): """run SCrypt key derivation function using specified parameters. :arg secret: - passphrase string (unicode is encoded to bytes using utf-8). + passphrase string (str is encoded to bytes using utf-8). :arg salt: - salt string (unicode is encoded to bytes using utf-8). + salt string (str is encoded to bytes using utf-8). :arg n: integer 'N' parameter diff --git a/passlib/crypto/scrypt/_builtin.py b/passlib/crypto/scrypt/_builtin.py index e9bb305..c93e7a3 100644 --- a/passlib/crypto/scrypt/_builtin.py +++ b/passlib/crypto/scrypt/_builtin.py @@ -6,7 +6,6 @@ import operator import struct # pkg -from passlib.utils.compat import izip from passlib.crypto.digest import pbkdf2_hmac from passlib.crypto.scrypt._salsa import salsa20 # local @@ -170,7 +169,7 @@ class ScryptEngine(object): i = 0 while i < n: j = integerify(buffer) & n_mask - result = tuple(a ^ b for a, b in izip(buffer, get_v_elem(j))) + result = tuple(a ^ b for a, b in zip(buffer, get_v_elem(j))) bmix(result, buffer) i += 1 @@ -179,7 +178,7 @@ class ScryptEngine(object): # if not n_is_log_2: # while i < n: # j = integerify(buffer) % n - # tmp = tuple(a^b for a,b in izip(buffer, get_v_elem(j))) + # tmp = tuple(a^b for a,b in zip(buffer, get_v_elem(j))) # bmix(tmp,buffer) # i += 1 @@ -225,15 +224,15 @@ class ScryptEngine(object): j = 0 while j < half: jn = j+16 - target[j:jn] = tmp = salsa20(a ^ b for a, b in izip(tmp, siter)) - target[half+j:half+jn] = tmp = salsa20(a ^ b for a, b in izip(tmp, siter)) + target[j:jn] = tmp = salsa20(a ^ b for a, b in zip(tmp, siter)) + target[half+j:half+jn] = tmp = salsa20(a ^ b for a, b in zip(tmp, siter)) j = jn def _bmix_1(self, source, target): """special bmix() method optimized for ``r=1`` case""" B = source[16:] - target[:16] = tmp = salsa20(a ^ b for a, b in izip(B, iter(source))) - target[16:] = salsa20(a ^ b for a, b in izip(tmp, B)) + target[:16] = tmp = salsa20(a ^ b for a, b in zip(B, iter(source))) + target[16:] = salsa20(a ^ b for a, b in zip(tmp, B)) #================================================================= # eoc diff --git a/passlib/exc.py b/passlib/exc.py index 755c7dc..29ef8af 100644 --- a/passlib/exc.py +++ b/passlib/exc.py @@ -310,8 +310,8 @@ def ExpectedTypeError(value, expected, param): return TypeError("%s must be %s, not %s" % (param, expected, name)) def ExpectedStringError(value, param): - """error message when param was supposed to be unicode or bytes""" - return ExpectedTypeError(value, "unicode or bytes", param) + """error message when param was supposed to be str or bytes""" + return ExpectedTypeError(value, "str or bytes", param) #------------------------------------------------------------------------ # hash/verify parameter errors diff --git a/passlib/ext/django/utils.py b/passlib/ext/django/utils.py index 2f8a2ef..d25a501 100644 --- a/passlib/ext/django/utils.py +++ b/passlib/ext/django/utils.py @@ -3,6 +3,7 @@ # imports #============================================================================= # core +from collections import OrderedDict from functools import update_wrapper, wraps import logging; log = logging.getLogger(__name__) import sys @@ -19,7 +20,7 @@ except ImportError: from passlib import exc, registry from passlib.context import CryptContext from passlib.exc import PasslibRuntimeWarning -from passlib.utils.compat import get_method_function, iteritems, OrderedDict, unicode +from passlib.utils.compat import get_method_function from passlib.utils.decor import memoized_property # local __all__ = [ @@ -180,7 +181,7 @@ class DjangoTranslator(object): #============================================================================= def __init__(self, context=None, **kwds): - super(DjangoTranslator, self).__init__(**kwds) + super().__init__(**kwds) if context is not None: self.context = context @@ -467,7 +468,7 @@ class DjangoContextAdapter(DjangoTranslator): # init parent, filling in default context object if context is None: context = CryptContext() - super(DjangoContextAdapter, self).__init__(context=context, **kwds) + super().__init__(context=context, **kwds) # setup user category if get_user_category: @@ -504,7 +505,7 @@ class DjangoContextAdapter(DjangoTranslator): reset_hashers(setting="PASSWORD_HASHERS") # reset internal caches - super(DjangoContextAdapter, self).reset_hashers() + super().reset_hashers() #============================================================================= # django hashers helpers -- hasher lookup @@ -821,13 +822,7 @@ class DjangoContextAdapter(DjangoTranslator): config = getattr(settings, "PASSLIB_CONTEXT", _UNSET) if config is _UNSET: config = "passlib-default" - if config is None: - warn("setting PASSLIB_CONFIG=None is deprecated, " - "and support will be removed in Passlib 1.8, " - "use PASSLIB_CONFIG='disabled' instead.", - DeprecationWarning) - config = "disabled" - elif not isinstance(config, (unicode, bytes, dict)): + if not isinstance(config, (str, bytes, dict)): raise exc.ExpectedTypeError(config, "str or dict", "PASSLIB_CONFIG") # load custom category func (if any) @@ -1001,7 +996,7 @@ class _PasslibHasherWrapper(object): ] if hasattr(handler, "parsehash"): kwds = handler.parsehash(encoded, sanitize=mask_hash) - for key, value in iteritems(kwds): + for key, value in kwds.items(): key = self._translate_kwds.get(key, key) items.append((_(key), value)) return OrderedDict(items) @@ -1030,7 +1025,6 @@ class _PasslibHasherWrapper(object): ##from passlib.registry import register_crypt_handler ##from passlib.utils import classproperty, to_native_str, to_unicode -##from passlib.utils.compat import unicode ## ## ##class _HasherHandler(object): @@ -1068,7 +1062,7 @@ class _PasslibHasherWrapper(object): ## @property ## def ident(self): ## # this should always be correct, as django relies on ident prefix. -## return unicode(self.django_name + "$") +## return self.django_name + "$" ## ## @property ## def identify(self, hash): @@ -1085,7 +1079,7 @@ class _PasslibHasherWrapper(object): ## opts['iterations'] = kwds.pop("rounds") ## if kwds: ## raise TypeError("unexpected keyword arguments: %r" % list(kwds)) -## if isinstance(secret, unicode): +## if isinstance(secret, str): ## secret = secret.encode("utf-8") ## if salt is None: ## salt = self.django_hasher.salt() @@ -1094,7 +1088,7 @@ class _PasslibHasherWrapper(object): ## @property ## def verify(self, secret, hash): ## hash = to_native_str(hash, "utf-8", "hash") -## if isinstance(secret, unicode): +## if isinstance(secret, str): ## secret = secret.encode("utf-8") ## return self.django_hasher.verify(secret, hash) ## @@ -1168,7 +1162,7 @@ class _PatchManager(object): def check_all(self, strict=False): """run sanity check on all keys, issue warning if out of sync""" same = self._is_same_value - for path, (orig, expected) in iteritems(self._state): + for path, (orig, expected) in self._state.items(): if same(self._get_path(path), expected): continue msg = "another library has patched resource: %r" % path @@ -1222,7 +1216,7 @@ class _PatchManager(object): ##def patch_many(self, **kwds): ## "override specified resources with new values" - ## for path, value in iteritems(kwds): + ## for path, value in kwds.items(): ## self.patch(path, value) def monkeypatch(self, parent, name=None, enable=True, wrap=False): diff --git a/passlib/handlers/argon2.py b/passlib/handlers/argon2.py index 4a5691b..f0c2881 100644 --- a/passlib/handlers/argon2.py +++ b/passlib/handlers/argon2.py @@ -15,7 +15,6 @@ References #============================================================================= # imports #============================================================================= -from __future__ import with_statement, absolute_import # core import logging log = logging.getLogger(__name__) @@ -30,7 +29,7 @@ from passlib import exc from passlib.crypto.digest import MAX_UINT32 from passlib.utils import classproperty, to_bytes, render_bytes from passlib.utils.binary import b64s_encode, b64s_decode -from passlib.utils.compat import u, unicode, bascii_to_str, uascii_to_str, PY2 +from passlib.utils.compat import bascii_to_str import passlib.utils.handlers as uh # local __all__ = [ @@ -49,9 +48,9 @@ __all__ = [ #: argon2 type constants -- subclasses handle mapping these to backend-specific type constants. #: (should be lowercase, to match representation in hash string) -TYPE_I = u("i") -TYPE_D = u("d") -TYPE_ID = u("id") # new 2016-10-29; passlib 1.7.2 requires backends new enough for support +TYPE_I = u"i" +TYPE_D = u"d" +TYPE_ID = u"id" # new 2016-10-29; passlib 1.7.2 requires backends new enough for support #: list of all known types; first (supported) type will be used as default. ALL_TYPES = (TYPE_ID, TYPE_I, TYPE_D) @@ -284,7 +283,7 @@ class _Argon2Common(uh.SubclassBackendMixin, uh.ParallelismMixin, digest_size = checksum_size # create variant - subcls = super(_Argon2Common, cls).using(**kwds) + subcls = super().using(**kwds) # set type if type is not None: @@ -293,7 +292,7 @@ class _Argon2Common(uh.SubclassBackendMixin, uh.ParallelismMixin, # set checksum size relaxed = kwds.get("relaxed") if digest_size is not None: - if isinstance(digest_size, uh.native_string_types): + if isinstance(digest_size, str): digest_size = int(digest_size) # NOTE: this isn't *really* digest size minimum, but want to enforce secure minimum. subcls.checksum_size = uh.norm_integer(subcls, digest_size, min=16, max=MAX_UINT32, @@ -301,7 +300,7 @@ class _Argon2Common(uh.SubclassBackendMixin, uh.ParallelismMixin, # set memory cost if memory_cost is not None: - if isinstance(memory_cost, uh.native_string_types): + if isinstance(memory_cost, str): memory_cost = int(memory_cost) subcls.memory_cost = subcls._norm_memory_cost(memory_cost, relaxed=relaxed) @@ -310,7 +309,7 @@ class _Argon2Common(uh.SubclassBackendMixin, uh.ParallelismMixin, # set max threads if max_threads is not None: - if isinstance(max_threads, uh.native_string_types): + if isinstance(max_threads, str): max_threads = int(max_threads) if max_threads < 1 and max_threads != -1: raise ValueError("max_threads (%d) must be -1 (unlimited), or at least 1." % @@ -394,9 +393,9 @@ class _Argon2Common(uh.SubclassBackendMixin, uh.ParallelismMixin, @classmethod def from_string(cls, hash): - # NOTE: assuming hash will be unicode, or use ascii-compatible encoding. - # TODO: switch to working w/ str or unicode - if isinstance(hash, unicode): + # NOTE: assuming hash will be str, or use ascii-compatible encoding. + # TODO: switch to working w/ str + if isinstance(hash, str): hash = hash.encode("utf-8") if not isinstance(hash, bytes): raise exc.ExpectedStringError(hash, "hash") @@ -434,7 +433,7 @@ class _Argon2Common(uh.SubclassBackendMixin, uh.ParallelismMixin, # NOTE: 'keyid' param currently not supported return "$argon2%s$%sm=%d,t=%d,p=%d%s$%s$%s" % ( - uascii_to_str(self.type), + self.type, vstr, self.memory_cost, self.rounds, @@ -463,7 +462,7 @@ class _Argon2Common(uh.SubclassBackendMixin, uh.ParallelismMixin, self.checksum_size = len(checksum) # call parent - super(_Argon2Common, self).__init__(**kwds) + super().__init__(**kwds) # init type if type is None: @@ -500,11 +499,8 @@ class _Argon2Common(uh.SubclassBackendMixin, uh.ParallelismMixin, @classmethod def _norm_type(cls, value): # type check - if not isinstance(value, unicode): - if PY2 and isinstance(value, bytes): - value = value.decode('ascii') - else: - raise uh.exc.ExpectedTypeError(value, "str", "type") + if not isinstance(value, str): + raise uh.exc.ExpectedTypeError(value, "str", "type") # check if type is valid if value in ALL_TYPES_SET: @@ -520,7 +516,7 @@ class _Argon2Common(uh.SubclassBackendMixin, uh.ParallelismMixin, @classmethod def _norm_version(cls, version): - if not isinstance(version, uh.int_types): + if not isinstance(version, int): raise uh.exc.ExpectedTypeError(version, "integer", "version") # minimum valid version @@ -578,7 +574,7 @@ class _Argon2Common(uh.SubclassBackendMixin, uh.ParallelismMixin, return True if self.checksum_size != cls.checksum_size: return True - return super(_Argon2Common, self)._calc_needs_update(**kwds) + return super()._calc_needs_update(**kwds) #=================================================================== # backend loading @@ -684,7 +680,7 @@ class _NoBackend(_Argon2Common): self._stub_requires_backend() # NOTE: have to use super() here so that we don't recursively # call subclass's wrapped _calc_checksum - return super(argon2, self)._calc_checksum(secret) + return super()._calc_checksum(secret) #=================================================================== # eoc diff --git a/passlib/handlers/bcrypt.py b/passlib/handlers/bcrypt.py index b83b110..9a10f9e 100644 --- a/passlib/handlers/bcrypt.py +++ b/passlib/handlers/bcrypt.py @@ -10,7 +10,6 @@ TODO: #============================================================================= # imports #============================================================================= -from __future__ import with_statement, absolute_import # core from base64 import b64encode from hashlib import sha256 @@ -20,8 +19,6 @@ import logging; log = logging.getLogger(__name__) from warnings import warn # site _bcrypt = None # dynamically imported by _load_backend_bcrypt() -_pybcrypt = None # dynamically imported by _load_backend_pybcrypt() -_bcryptor = None # dynamically imported by _load_backend_bcryptor() # pkg _builtin_bcrypt = None # dynamically imported by _load_backend_builtin() from passlib.crypto.digest import compile_hmac @@ -30,8 +27,6 @@ from passlib.utils import safe_crypt, repeat_string, to_bytes, parse_version, \ rng, getrandstr, test_crypt, to_unicode, \ utf8_truncate, utf8_repeat_string, crypt_accepts_bytes from passlib.utils.binary import bcrypt64 -from passlib.utils.compat import get_unbound_method_function -from passlib.utils.compat import u, uascii_to_str, unicode, str_to_uascii, PY3, error_from import passlib.utils.handlers as uh # local @@ -42,11 +37,11 @@ __all__ = [ #============================================================================= # support funcs & constants #============================================================================= -IDENT_2 = u("$2$") -IDENT_2A = u("$2a$") -IDENT_2X = u("$2x$") -IDENT_2Y = u("$2y$") -IDENT_2B = u("$2b$") +IDENT_2 = u"$2$" +IDENT_2A = u"$2a$" +IDENT_2X = u"$2x$" +IDENT_2Y = u"$2y$" +IDENT_2B = u"$2b$" _BNULL = b'\x00' # reference hash of "test", used in various self-checks @@ -122,8 +117,8 @@ class _BcryptCommon(uh.SubclassBackendMixin, uh.TruncateMixin, uh.HasManyIdents, #-------------------- default_ident = IDENT_2B ident_values = (IDENT_2, IDENT_2A, IDENT_2X, IDENT_2Y, IDENT_2B) - ident_aliases = {u("2"): IDENT_2, u("2a"): IDENT_2A, u("2y"): IDENT_2Y, - u("2b"): IDENT_2B} + ident_aliases = {u"2": IDENT_2, u"2a": IDENT_2A, u"2y": IDENT_2Y, + u"2b": IDENT_2B} #-------------------- # HasSalt @@ -171,9 +166,9 @@ class _BcryptCommon(uh.SubclassBackendMixin, uh.TruncateMixin, uh.HasManyIdents, if ident == IDENT_2X: raise ValueError("crypt_blowfish's buggy '2x' hashes are not " "currently supported") - rounds_str, data = tail.split(u("$")) + rounds_str, data = tail.split(u"$") rounds = int(rounds_str) - if rounds_str != u('%02d') % (rounds,): + if rounds_str != u'%02d' % (rounds,): raise uh.exc.MalformedHashError(cls, "malformed cost field") salt, chk = data[:22], data[22:] return cls( @@ -184,15 +179,15 @@ class _BcryptCommon(uh.SubclassBackendMixin, uh.TruncateMixin, uh.HasManyIdents, ) def to_string(self): - hash = u("%s%02d$%s%s") % (self.ident, self.rounds, self.salt, self.checksum) - return uascii_to_str(hash) + hash = u"%s%02d$%s%s" % (self.ident, self.rounds, self.salt, self.checksum) + return hash # NOTE: this should be kept separate from to_string() # so that bcrypt_sha256() can still use it, while overriding to_string() def _get_config(self, ident): """internal helper to prepare config string for backends""" - config = u("%s%02d$%s") % (ident, self.rounds, self.salt) - return uascii_to_str(config) + config = u"%s%02d$%s" % (ident, self.rounds, self.salt) + return config #=================================================================== # migration @@ -211,7 +206,7 @@ class _BcryptCommon(uh.SubclassBackendMixin, uh.TruncateMixin, uh.HasManyIdents, # TODO: try to detect incorrect 8bit/wraparound hashes using kwds.get("secret") # hand off to base implementation, so HasRounds can check rounds value. - return super(_BcryptCommon, cls).needs_update(hash, **kwds) + return super().needs_update(hash, **kwds) #=================================================================== # specialized salt generation - fixes passlib issue 25 @@ -229,12 +224,12 @@ class _BcryptCommon(uh.SubclassBackendMixin, uh.TruncateMixin, uh.HasManyIdents, def _generate_salt(cls): # generate random salt as normal, # but repair last char so the padding bits always decode to zero. - salt = super(_BcryptCommon, cls)._generate_salt() + salt = super()._generate_salt() return bcrypt64.repair_unused(salt) @classmethod def _norm_salt(cls, salt, **kwds): - salt = super(_BcryptCommon, cls)._norm_salt(salt, **kwds) + salt = super()._norm_salt(salt, **kwds) assert salt is not None, "HasSalt didn't generate new salt!" changed, salt = bcrypt64.check_repair_unused(salt) if changed: @@ -248,7 +243,7 @@ class _BcryptCommon(uh.SubclassBackendMixin, uh.TruncateMixin, uh.HasManyIdents, return salt def _norm_checksum(self, checksum, relaxed=False): - checksum = super(_BcryptCommon, self)._norm_checksum(checksum, relaxed=relaxed) + checksum = super()._norm_checksum(checksum, relaxed=relaxed) changed, checksum = bcrypt64.check_repair_unused(checksum) if changed: warn( @@ -294,8 +289,6 @@ class _BcryptCommon(uh.SubclassBackendMixin, uh.TruncateMixin, uh.HasManyIdents, verify = mixin_cls.verify err_types = (ValueError, uh.exc.MissingBackendError) - if _bcryptor: - err_types += (_bcryptor.engine.SaltError,) def safe_verify(secret, hash): """verify() wrapper which traps 'unknown identifier' errors""" @@ -309,7 +302,6 @@ class _BcryptCommon(uh.SubclassBackendMixin, uh.TruncateMixin, uh.HasManyIdents, # - InternalBackendError if crypt fails for unknown reason # (trapped below so we can debug it) # pybcrypt, bcrypt -- raises ValueError - # bcryptor -- raises bcryptor.engine.SaltError return NotImplemented except uh.exc.InternalBackendError: # _calc_checksum() code may also throw CryptBackendError @@ -490,7 +482,7 @@ class _BcryptCommon(uh.SubclassBackendMixin, uh.TruncateMixin, uh.HasManyIdents, def _norm_digest_args(cls, secret, ident, new=False): # make sure secret is unicode require_valid_utf8_bytes = cls._require_valid_utf8_bytes - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") elif require_valid_utf8_bytes: # if backend requires utf8 bytes (os_crypt); @@ -510,7 +502,7 @@ class _BcryptCommon(uh.SubclassBackendMixin, uh.TruncateMixin, uh.HasManyIdents, cls._check_truncate_policy(secret) # NOTE: especially important to forbid NULLs for bcrypt, since many - # backends (bcryptor, bcrypt) happily accept them, and then + # backends (bcrypt) happily accept them, and then # silently truncate the password at first NULL they encounter! if _BNULL in secret: raise uh.exc.NullPasswordError(cls) @@ -591,7 +583,7 @@ class _NoBackend(_BcryptCommon): self._stub_requires_backend() # NOTE: have to use super() here so that we don't recursively # call subclass's wrapped _calc_checksum, e.g. bcrypt_sha256._calc_checksum - return super(bcrypt, self)._calc_checksum(secret) + return super()._calc_checksum(secret) #=================================================================== # eoc @@ -630,7 +622,7 @@ class _BcryptBackend(_BcryptCommon): # # below method has a few edge cases where it chokes though. # @classmethod # def verify(cls, secret, hash): - # if isinstance(hash, unicode): + # if isinstance(hash, str): # hash = hash.encode("ascii") # ident = hash[:hash.index(b"$", 1)+1].decode("ascii") # if ident not in cls.ident_values: @@ -650,7 +642,7 @@ class _BcryptBackend(_BcryptCommon): # returns ascii bytes secret, ident = self._prepare_digest_args(secret) config = self._get_config(ident) - if isinstance(config, unicode): + if isinstance(config, str): config = config.encode("ascii") hash = _bcrypt.hashpw(secret, config) assert isinstance(hash, bytes) @@ -658,113 +650,6 @@ class _BcryptBackend(_BcryptCommon): raise uh.exc.CryptBackendError(self, config, hash, source="`bcrypt` package") return hash[-31:].decode("ascii") -#----------------------------------------------------------------------- -# bcryptor backend -#----------------------------------------------------------------------- -class _BcryptorBackend(_BcryptCommon): - """ - backend which uses 'bcryptor' package - """ - - @classmethod - def _load_backend_mixin(mixin_cls, name, dryrun): - # try to import bcryptor - global _bcryptor - try: - import bcryptor as _bcryptor - except ImportError: # pragma: no cover - return False - - # deprecated as of 1.7.2 - if not dryrun: - warn("Support for `bcryptor` is deprecated, and will be removed in Passlib 1.8; " - "Please use `pip install bcrypt` instead", DeprecationWarning) - - return mixin_cls._finalize_backend_mixin(name, dryrun) - - def _calc_checksum(self, secret): - # bcryptor behavior: - # py2: unicode secret/hash encoded as ascii bytes before use, - # bytes taken as-is; returns ascii bytes. - # py3: not supported - secret, ident = self._prepare_digest_args(secret) - config = self._get_config(ident) - hash = _bcryptor.engine.Engine(False).hash_key(secret, config) - if not hash.startswith(config) or len(hash) != len(config) + 31: - raise uh.exc.CryptBackendError(self, config, hash, source="bcryptor library") - return str_to_uascii(hash[-31:]) - -#----------------------------------------------------------------------- -# pybcrypt backend -#----------------------------------------------------------------------- -class _PyBcryptBackend(_BcryptCommon): - """ - backend which uses 'pybcrypt' package - """ - - #: classwide thread lock used for pybcrypt < 0.3 - _calc_lock = None - - @classmethod - def _load_backend_mixin(mixin_cls, name, dryrun): - # try to import pybcrypt - global _pybcrypt - if not _detect_pybcrypt(): - # not installed, or bcrypt installed instead - return False - try: - import bcrypt as _pybcrypt - except ImportError: # pragma: no cover - # XXX: should we raise AssertionError here? (if get here, _detect_pybcrypt() is broken) - return False - - # deprecated as of 1.7.2 - if not dryrun: - warn("Support for `py-bcrypt` is deprecated, and will be removed in Passlib 1.8; " - "Please use `pip install bcrypt` instead", DeprecationWarning) - - # determine pybcrypt version - try: - version = _pybcrypt._bcrypt.__version__ - except: - log.warning("(trapped) error reading pybcrypt version", exc_info=True) - version = "<unknown>" - log.debug("detected 'pybcrypt' backend, version %r", version) - - # return calc function based on version - vinfo = parse_version(version) or (0, 0) - if vinfo < (0, 3): - warn("py-bcrypt %s has a major security vulnerability, " - "you should upgrade to py-bcrypt 0.3 immediately." - % version, uh.exc.PasslibSecurityWarning) - if mixin_cls._calc_lock is None: - import threading - mixin_cls._calc_lock = threading.Lock() - mixin_cls._calc_checksum = get_unbound_method_function(mixin_cls._calc_checksum_threadsafe) - - return mixin_cls._finalize_backend_mixin(name, dryrun) - - def _calc_checksum_threadsafe(self, secret): - # as workaround for pybcrypt < 0.3's concurrency issue, - # we wrap everything in a thread lock. as long as bcrypt is only - # used through passlib, this should be safe. - with self._calc_lock: - return self._calc_checksum_raw(secret) - - def _calc_checksum_raw(self, secret): - # py-bcrypt behavior: - # py2: unicode secret/hash encoded as ascii bytes before use, - # bytes taken as-is; returns ascii bytes. - # py3: unicode secret encoded as utf-8 bytes, - # hash encoded as ascii bytes, returns ascii unicode. - secret, ident = self._prepare_digest_args(secret) - config = self._get_config(ident) - hash = _pybcrypt.hashpw(secret, config) - if not hash.startswith(config) or len(hash) != len(config) + 31: - raise uh.exc.CryptBackendError(self, config, hash, source="pybcrypt library") - return str_to_uascii(hash[-31:]) - - _calc_checksum = _calc_checksum_raw #----------------------------------------------------------------------- # os crypt backend @@ -810,14 +695,14 @@ class _OsCryptBackend(_BcryptCommon): # instead of returning None? (would save re-detecting what went wrong) # XXX: isn't secret ALWAYS bytes at this point? # - if PY3 and isinstance(secret, bytes): + if isinstance(secret, bytes): try: secret.decode("utf-8") except UnicodeDecodeError: - raise error_from(uh.exc.PasswordValueError( + raise uh.exc.PasswordValueError( "python3 crypt.crypt() ony supports bytes passwords using UTF8; " "passlib recommends running `pip install bcrypt` for general bcrypt support.", - ), None) + ) from None # # else crypt() call failed for unknown reason. @@ -943,7 +828,7 @@ class bcrypt(_NoBackend, _BcryptCommon): # in order to load the appropriate backend. #: list of potential backends - backends = ("bcrypt", "pybcrypt", "bcryptor", "os_crypt", "builtin") + backends = ("bcrypt", "os_crypt", "builtin") #: flag that this class's bases should be modified by SubclassBackendMixin _backend_mixin_target = True @@ -952,8 +837,6 @@ class bcrypt(_NoBackend, _BcryptCommon): _backend_mixin_map = { None: _NoBackend, "bcrypt": _BcryptBackend, - "pybcrypt": _PyBcryptBackend, - "bcryptor": _BcryptorBackend, "os_crypt": _OsCryptBackend, "builtin": _BuiltinBackend, } @@ -965,7 +848,7 @@ class bcrypt(_NoBackend, _BcryptCommon): #============================================================================= # variants #============================================================================= -_UDOLLAR = u("$") +_UDOLLAR = u"$" # XXX: it might be better to have all the bcrypt variants share a common base class, # and have the (django_)bcrypt_sha256 wrappers just proxy bcrypt instead of subclassing it. @@ -983,17 +866,17 @@ class _wrapped_bcrypt(bcrypt): # def hash(cls, secret, **kwds): # # bypass bcrypt backend overriding this method # # XXX: would wrapping bcrypt make this easier than subclassing it? - # return super(_BcryptCommon, cls).hash(secret, **kwds) + # return super().hash(secret, **kwds) # # @classmethod # def verify(cls, secret, hash): # # bypass bcrypt backend overriding this method - # return super(_BcryptCommon, cls).verify(secret, hash) + # return super().verify(secret, hash) # # @classmethod # def genhash(cls, secret, hash): # # bypass bcrypt backend overriding this method - # return super(_BcryptCommon, cls).genhash(secret, hash) + # return super().genhash(secret, hash) @classmethod def _check_truncate_policy(cls, secret): @@ -1050,7 +933,7 @@ class bcrypt_sha256(_wrapped_bcrypt): # class specific #-------------------- - _supported_versions = set([1, 2]) + _supported_versions = {1, 2} #=================================================================== # instance attrs @@ -1067,7 +950,7 @@ class bcrypt_sha256(_wrapped_bcrypt): @classmethod def using(cls, version=None, **kwds): - subcls = super(bcrypt_sha256, cls).using(**kwds) + subcls = super().using(**kwds) if version is not None: subcls.version = subcls._norm_version(version) ident = subcls.default_ident @@ -1093,7 +976,7 @@ class bcrypt_sha256(_wrapped_bcrypt): # XXX: we can't use .ident attr due to bcrypt code using it. # working around that via prefix. - prefix = u('$bcrypt-sha256$') + prefix = u'$bcrypt-sha256$' #: current version 2 hash format _v2_hash_re = re.compile(r"""(?x) @@ -1152,8 +1035,8 @@ class bcrypt_sha256(_wrapped_bcrypt): checksum=m.group("digest"), ) - _v2_template = u("$bcrypt-sha256$v=2,t=%s,r=%d$%s$%s") - _v1_template = u("$bcrypt-sha256$%s,%d$%s$%s") + _v2_template = u"$bcrypt-sha256$v=2,t=%s,r=%d$%s$%s" + _v1_template = u"$bcrypt-sha256$%s,%d$%s$%s" def to_string(self): if self.version == 1: @@ -1161,7 +1044,7 @@ class bcrypt_sha256(_wrapped_bcrypt): else: template = self._v2_template hash = template % (self.ident.strip(_UDOLLAR), self.rounds, self.salt, self.checksum) - return uascii_to_str(hash) + return hash #=================================================================== # init @@ -1170,7 +1053,7 @@ class bcrypt_sha256(_wrapped_bcrypt): def __init__(self, version=None, **kwds): if version is not None: self.version = self._norm_version(version) - super(bcrypt_sha256, self).__init__(**kwds) + super().__init__(**kwds) #=================================================================== # version @@ -1193,7 +1076,7 @@ class bcrypt_sha256(_wrapped_bcrypt): # thus, have to use base64 (44 bytes) rather than hex (64 bytes). # XXX: it's later come out that 55-72 may be ok, so later revision of bcrypt_sha256 # may switch to hex encoding, since it's simpler to implement elsewhere. - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") if self.version == 1: @@ -1223,7 +1106,7 @@ class bcrypt_sha256(_wrapped_bcrypt): key = b64encode(digest) # hand result off to normal bcrypt algorithm - return super(bcrypt_sha256, self)._calc_checksum(key) + return super()._calc_checksum(key) #=================================================================== # other @@ -1232,7 +1115,7 @@ class bcrypt_sha256(_wrapped_bcrypt): def _calc_needs_update(self, **kwds): if self.version < type(self).version: return True - return super(bcrypt_sha256, self)._calc_needs_update(**kwds) + return super()._calc_needs_update(**kwds) #=================================================================== # eoc diff --git a/passlib/handlers/cisco.py b/passlib/handlers/cisco.py index e715e1a..1b242e9 100644 --- a/passlib/handlers/cisco.py +++ b/passlib/handlers/cisco.py @@ -13,8 +13,6 @@ from warnings import warn # pkg from passlib.utils import right_pad_string, to_unicode, repeat_string, to_bytes from passlib.utils.binary import h64 -from passlib.utils.compat import unicode, u, join_byte_values, \ - join_byte_elems, iter_byte_values, uascii_to_str import passlib.utils.handlers as uh # local __all__ = [ @@ -124,7 +122,7 @@ class cisco_pix(uh.HasUserContext, uh.StaticHandler): # been observed when trying to actually *set* a non-ascii password # via ASDM, and access via SSH seems to strip 8-bit chars. # - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") # @@ -186,7 +184,7 @@ class cisco_pix(uh.HasUserContext, uh.StaticHandler): # user = self.user if user: - if isinstance(user, unicode): + if isinstance(user, str): user = user.encode("utf-8") if not asa or len(secret) < 28: secret += repeat_string(user, 4) @@ -219,7 +217,7 @@ class cisco_pix(uh.HasUserContext, uh.StaticHandler): # 16 bytes, which may have been a general 'char password[]' # size limit under PIX # - digest = join_byte_elems(c for i, c in enumerate(digest) if (i + 1) & 3) + digest = bytes(c for i, c in enumerate(digest) if (i + 1) & 3) # # encode using Hash64 @@ -352,7 +350,7 @@ class cisco_type7(uh.GenericHandler): #=================================================================== @classmethod def using(cls, salt=None, **kwds): - subcls = super(cisco_type7, cls).using(**kwds) + subcls = super().using(**kwds) if salt is not None: salt = subcls._norm_salt(salt, relaxed=kwds.get("relaxed")) subcls._generate_salt = staticmethod(lambda: salt) @@ -367,7 +365,7 @@ class cisco_type7(uh.GenericHandler): return cls(salt=salt, checksum=hash[2:].upper()) def __init__(self, salt=None, **kwds): - super(cisco_type7, self).__init__(**kwds) + super().__init__(**kwds) if salt is not None: salt = self._norm_salt(salt) elif self.use_defaults: @@ -400,12 +398,12 @@ class cisco_type7(uh.GenericHandler): return uh.rng.randint(0, 15) def to_string(self): - return "%02d%s" % (self.salt, uascii_to_str(self.checksum)) + return "%02d%s" % (self.salt, self.checksum) def _calc_checksum(self, secret): # XXX: no idea what unicode policy is, but all examples are # 7-bit ascii compatible, so using UTF-8 - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") return hexlify(self._cipher(secret, self.salt)).decode("ascii").upper() @@ -423,16 +421,16 @@ class cisco_type7(uh.GenericHandler): return raw.decode(encoding) if encoding else raw # type7 uses a xor-based vingere variant, using the following secret key: - _key = u("dsfd;kfoA,.iyewrkldJKDHSUBsgvca69834ncxv9873254k;fg87") + _key = u"dsfd;kfoA,.iyewrkldJKDHSUBsgvca69834ncxv9873254k;fg87" @classmethod - def _cipher(cls, data, salt): + def _cipher(cls, data: bytes, salt: int): """xor static key against data - encrypts & decrypts""" key = cls._key key_size = len(key) - return join_byte_values( + return bytes( value ^ ord(key[(salt + idx) % key_size]) - for idx, value in enumerate(iter_byte_values(data)) + for idx, value in enumerate(data) ) #============================================================================= diff --git a/passlib/handlers/des_crypt.py b/passlib/handlers/des_crypt.py index 68a4ca7..58707ee 100644 --- a/passlib/handlers/des_crypt.py +++ b/passlib/handlers/des_crypt.py @@ -10,7 +10,6 @@ from warnings import warn # pkg from passlib.utils import safe_crypt, test_crypt, to_unicode from passlib.utils.binary import h64, h64big -from passlib.utils.compat import byte_elem_value, u, uascii_to_str, unicode, suppress_cause from passlib.crypto.des import des_encrypt_int_block import passlib.utils.handlers as uh # local @@ -37,8 +36,8 @@ def _crypt_secret_to_key(secret): # but des_encrypt_int_block() would just ignore them... ##return sum(expand_7bit(byte_elem_value(c) & 0x7f) << (56-i*8) ## for i, c in enumerate(secret[:8])) - return sum((byte_elem_value(c) & 0x7f) << (57-i*8) - for i, c in enumerate(secret[:8])) + return sum((c & 0x7f) << (57-i*8) for i, c in enumerate(secret[:8])) + def _raw_des_crypt(secret, salt): """pure-python backed for des_crypt""" @@ -53,7 +52,7 @@ def _raw_des_crypt(secret, salt): salt_value = h64.decode_int12(salt) # gotta do something - no official policy since this predates unicode - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") assert isinstance(secret, bytes) @@ -89,7 +88,7 @@ def _raw_bsdi_crypt(secret, rounds, salt): salt_value = h64.decode_int24(salt) # gotta do something - no official policy since this predates unicode - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") assert isinstance(secret, bytes) @@ -171,11 +170,11 @@ class des_crypt(uh.TruncateMixin, uh.HasManyBackends, uh.HasSalt, uh.GenericHand #=================================================================== # FORMAT: 2 chars of H64-encoded salt + 11 chars of H64-encoded checksum - _hash_regex = re.compile(u(r""" + _hash_regex = re.compile(r""" ^ (?P<salt>[./a-z0-9]{2}) (?P<chk>[./a-z0-9]{11})? - $"""), re.X|re.I) + $""", re.X|re.I) @classmethod def from_string(cls, hash): @@ -184,8 +183,8 @@ class des_crypt(uh.TruncateMixin, uh.HasManyBackends, uh.HasSalt, uh.GenericHand return cls(salt=salt, checksum=chk or None) def to_string(self): - hash = u("%s%s") % (self.salt, self.checksum) - return uascii_to_str(hash) + hash = u"%s%s" % (self.salt, self.checksum) + return hash #=================================================================== # digest calculation @@ -297,13 +296,13 @@ class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler #=================================================================== # parsing #=================================================================== - _hash_regex = re.compile(u(r""" + _hash_regex = re.compile(r""" ^ _ (?P<rounds>[./a-z0-9]{4}) (?P<salt>[./a-z0-9]{4}) (?P<chk>[./a-z0-9]{11})? - $"""), re.X|re.I) + $""", re.X|re.I) @classmethod def from_string(cls, hash): @@ -319,9 +318,9 @@ class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler ) def to_string(self): - hash = u("_%s%s%s") % (h64.encode_int24(self.rounds).decode("ascii"), + hash = u"_%s%s%s" % (h64.encode_int24(self.rounds).decode("ascii"), self.salt, self.checksum) - return uascii_to_str(hash) + return hash #=================================================================== # validation @@ -333,7 +332,7 @@ class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler @classmethod def using(cls, **kwds): - subcls = super(bsdi_crypt, cls).using(**kwds) + subcls = super().using(**kwds) if not subcls.default_rounds & 1: # issue warning if caller set an even 'rounds' value. warn("bsdi_crypt rounds should be odd, as even rounds may reveal weak DES keys", @@ -342,7 +341,7 @@ class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler @classmethod def _generate_rounds(cls): - rounds = super(bsdi_crypt, cls)._generate_rounds() + rounds = super()._generate_rounds() # ensure autogenerated rounds are always odd # NOTE: doing this even for default_rounds so needs_update() doesn't get # caught in a loop. @@ -359,7 +358,7 @@ class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler if not self.rounds & 1: return True # hand off to base implementation - return super(bsdi_crypt, self)._calc_needs_update(**kwds) + return super()._calc_needs_update(**kwds) #=================================================================== # backends @@ -442,11 +441,11 @@ class bigcrypt(uh.HasSalt, uh.GenericHandler): #=================================================================== # internal helpers #=================================================================== - _hash_regex = re.compile(u(r""" + _hash_regex = re.compile(r""" ^ (?P<salt>[./a-z0-9]{2}) (?P<chk>([./a-z0-9]{11})+)? - $"""), re.X|re.I) + $""", re.X|re.I) @classmethod def from_string(cls, hash): @@ -458,11 +457,11 @@ class bigcrypt(uh.HasSalt, uh.GenericHandler): return cls(salt=salt, checksum=chk) def to_string(self): - hash = u("%s%s") % (self.salt, self.checksum) - return uascii_to_str(hash) + hash = u"%s%s" % (self.salt, self.checksum) + return hash def _norm_checksum(self, checksum, relaxed=False): - checksum = super(bigcrypt, self)._norm_checksum(checksum, relaxed=relaxed) + checksum = super()._norm_checksum(checksum, relaxed=relaxed) if len(checksum) % 11: raise uh.exc.InvalidHashError(self) return checksum @@ -471,7 +470,7 @@ class bigcrypt(uh.HasSalt, uh.GenericHandler): # backend #=================================================================== def _calc_checksum(self, secret): - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") chk = _raw_des_crypt(secret, self.salt.encode("ascii")) idx = 8 @@ -546,11 +545,11 @@ class crypt16(uh.TruncateMixin, uh.HasSalt, uh.GenericHandler): #=================================================================== # internal helpers #=================================================================== - _hash_regex = re.compile(u(r""" + _hash_regex = re.compile(r""" ^ (?P<salt>[./a-z0-9]{2}) (?P<chk>[./a-z0-9]{22})? - $"""), re.X|re.I) + $""", re.X|re.I) @classmethod def from_string(cls, hash): @@ -562,14 +561,14 @@ class crypt16(uh.TruncateMixin, uh.HasSalt, uh.GenericHandler): return cls(salt=salt, checksum=chk) def to_string(self): - hash = u("%s%s") % (self.salt, self.checksum) - return uascii_to_str(hash) + hash = u"%s%s" % (self.salt, self.checksum) + return hash #=================================================================== # backend #=================================================================== def _calc_checksum(self, secret): - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") # check for truncation (during .hash() calls only) @@ -579,8 +578,8 @@ class crypt16(uh.TruncateMixin, uh.HasSalt, uh.GenericHandler): # parse salt value try: salt_value = h64.decode_int12(self.salt.encode("ascii")) - except ValueError: # pragma: no cover - caught by class - raise suppress_cause(ValueError("invalid chars in salt")) + except ValueError: # pragma: no cover - caught by class + raise ValueError("invalid chars in salt") from None # convert first 8 byts of secret string into an integer, key1 = _crypt_secret_to_key(secret) diff --git a/passlib/handlers/digests.py b/passlib/handlers/digests.py index 982155c..d9bf283 100644 --- a/passlib/handlers/digests.py +++ b/passlib/handlers/digests.py @@ -9,7 +9,6 @@ import logging; log = logging.getLogger(__name__) # site # pkg from passlib.utils import to_native_str, to_bytes, render_bytes, consteq -from passlib.utils.compat import unicode, str_to_uascii import passlib.utils.handlers as uh from passlib.crypto.digest import lookup_hash # local @@ -45,9 +44,9 @@ class HexDigestHash(uh.StaticHandler): return hash.lower() def _calc_checksum(self, secret): - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") - return str_to_uascii(self._hash_func(secret).hexdigest()) + return self._hash_func(secret).hexdigest() #=================================================================== # eoc @@ -118,7 +117,7 @@ class htdigest(uh.MinimalHandler): if not encoding: encoding = cls.default_encoding uh.validate_secret(secret) - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode(encoding) user = to_bytes(user, encoding, "user") realm = to_bytes(realm, encoding, "realm") diff --git a/passlib/handlers/django.py b/passlib/handlers/django.py index 6dd499a..aabd998 100644 --- a/passlib/handlers/django.py +++ b/passlib/handlers/django.py @@ -13,7 +13,6 @@ from passlib.handlers.bcrypt import _wrapped_bcrypt from passlib.hash import argon2, bcrypt, pbkdf2_sha1, pbkdf2_sha256 from passlib.utils import to_unicode, rng, getrandstr from passlib.utils.binary import BASE64_CHARS -from passlib.utils.compat import str_to_uascii, uascii_to_str, unicode, u from passlib.crypto.digest import pbkdf2_hmac import passlib.utils.handlers as uh # local @@ -115,13 +114,13 @@ class django_salted_sha1(DjangoSaltedHash): """ name = "django_salted_sha1" django_name = "sha1" - ident = u("sha1$") + ident = u"sha1$" checksum_size = 40 def _calc_checksum(self, secret): - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") - return str_to_uascii(sha1(self.salt.encode("ascii") + secret).hexdigest()) + return sha1(self.salt.encode("ascii") + secret).hexdigest() class django_salted_md5(DjangoSaltedHash): """This class implements Django's Salted MD5 hash, and follows the :ref:`password-hash-api`. @@ -153,20 +152,20 @@ class django_salted_md5(DjangoSaltedHash): """ name = "django_salted_md5" django_name = "md5" - ident = u("md5$") + ident = u"md5$" checksum_size = 32 def _calc_checksum(self, secret): - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") - return str_to_uascii(md5(self.salt.encode("ascii") + secret).hexdigest()) + return md5(self.salt.encode("ascii") + secret).hexdigest() #============================================================================= # BCrypt #============================================================================= django_bcrypt = uh.PrefixWrapper("django_bcrypt", bcrypt, - prefix=u('bcrypt$'), ident=u("bcrypt$"), + prefix=u'bcrypt$', ident=u"bcrypt$", # NOTE: this docstring is duplicated in the docs, since sphinx # seems to be having trouble reading it via autodata:: doc="""This class implements Django 1.4's BCrypt wrapper, and follows the :ref:`password-hash-api`. @@ -209,7 +208,7 @@ class django_bcrypt_sha256(_wrapped_bcrypt): # XXX: we can't use .ident attr due to bcrypt code using it. # working around that via django_prefix - django_prefix = u('bcrypt_sha256$') + django_prefix = u'bcrypt_sha256$' @classmethod def identify(cls, hash): @@ -226,17 +225,17 @@ class django_bcrypt_sha256(_wrapped_bcrypt): bhash = hash[len(cls.django_prefix):] if not bhash.startswith("$2"): raise uh.exc.MalformedHashError(cls) - return super(django_bcrypt_sha256, cls).from_string(bhash) + return super().from_string(bhash) def to_string(self): - bhash = super(django_bcrypt_sha256, self).to_string() - return uascii_to_str(self.django_prefix) + bhash + bhash = super().to_string() + return self.django_prefix + bhash def _calc_checksum(self, secret): - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") secret = hexlify(self._digest(secret).digest()) - return super(django_bcrypt_sha256, self)._calc_checksum(secret) + return super()._calc_checksum(secret) #============================================================================= # PBKDF2 variants @@ -280,7 +279,7 @@ class django_pbkdf2_sha256(DjangoVariableHash): """ name = "django_pbkdf2_sha256" django_name = "pbkdf2_sha256" - ident = u('pbkdf2_sha256$') + ident = u'pbkdf2_sha256$' min_salt_size = 1 max_rounds = 0xffffffff # setting at 32-bit limit for now checksum_chars = uh.PADDED_BASE64_CHARS @@ -331,7 +330,7 @@ class django_pbkdf2_sha1(django_pbkdf2_sha256): """ name = "django_pbkdf2_sha1" django_name = "pbkdf2_sha1" - ident = u('pbkdf2_sha1$') + ident = u'pbkdf2_sha1$' checksum_size = 28 # 20 bytes -> base64 default_rounds = pbkdf2_sha1.default_rounds # NOTE: django 1.6 uses 12000 _digest = "sha1" @@ -346,8 +345,8 @@ class django_pbkdf2_sha1(django_pbkdf2_sha256): django_argon2 = uh.PrefixWrapper( name="django_argon2", wrapped=argon2.using(type="I"), - prefix=u('argon2'), - ident=u('argon2$argon2i$'), + prefix=u'argon2', + ident=u'argon2$argon2i$', # NOTE: this docstring is duplicated in the docs, since sphinx # seems to be having trouble reading it via autodata:: doc="""This class implements Django 1.10's Argon2 wrapper, and follows the :ref:`password-hash-api`. @@ -402,7 +401,7 @@ class django_des_crypt(uh.TruncateMixin, uh.HasSalt, uh.GenericHandler): name = "django_des_crypt" django_name = "crypt" setting_kwds = ("salt", "salt_size", "truncate_error") - ident = u("crypt$") + ident = u"crypt$" checksum_chars = salt_chars = uh.HASH64_CHARS checksum_size = 11 min_salt_size = default_salt_size = 2 @@ -487,7 +486,7 @@ class django_disabled(uh.ifc.DisabledHash, uh.StaticHandler): .. versionchanged:: 1.7 started appending an alphanumeric string. """ name = "django_disabled" - _hash_prefix = u("!") + _hash_prefix = u"!" suffix_length = 40 # XXX: move this to StaticHandler, or wherever _hash_prefix is being used? diff --git a/passlib/handlers/fshp.py b/passlib/handlers/fshp.py index db13e74..f8f31a9 100644 --- a/passlib/handlers/fshp.py +++ b/passlib/handlers/fshp.py @@ -12,8 +12,7 @@ import logging; log = logging.getLogger(__name__) # pkg from passlib.utils import to_unicode import passlib.utils.handlers as uh -from passlib.utils.compat import bascii_to_str, iteritems, u,\ - unicode +from passlib.utils.compat import bascii_to_str from passlib.crypto.digest import pbkdf1 # local __all__ = [ @@ -67,7 +66,7 @@ class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): name = "fshp" setting_kwds = ("salt", "salt_size", "rounds", "variant") checksum_chars = uh.PADDED_BASE64_CHARS - ident = u("{FSHP") + ident = u"{FSHP" # checksum_size is property() that depends on variant #--HasRawSalt-- @@ -92,8 +91,8 @@ class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): 3: ("sha512", 64), } _variant_aliases = dict( - [(unicode(k),k) for k in _variant_info] + - [(v[0],k) for k,v in iteritems(_variant_info)] + [(str(k),k) for k in _variant_info] + + [(v[0],k) for k,v in _variant_info.items()] ) #=================================================================== @@ -101,7 +100,7 @@ class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): #=================================================================== @classmethod def using(cls, variant=None, **kwds): - subcls = super(fshp, cls).using(**kwds) + subcls = super().using(**kwds) if variant is not None: subcls.default_variant = cls._norm_variant(variant) return subcls @@ -125,13 +124,13 @@ class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): else: raise TypeError("no variant specified") self.variant = variant - super(fshp, self).__init__(**kwds) + super().__init__(**kwds) @classmethod def _norm_variant(cls, variant): if isinstance(variant, bytes): variant = variant.decode("ascii") - if isinstance(variant, unicode): + if isinstance(variant, str): try: variant = cls._variant_aliases[variant] except KeyError: @@ -154,14 +153,14 @@ class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): # formatting #=================================================================== - _hash_regex = re.compile(u(r""" + _hash_regex = re.compile(r""" ^ \{FSHP (\d+)\| # variant (\d+)\| # salt size (\d+)\} # rounds ([a-zA-Z0-9+/]+={0,3}) # digest - $"""), re.X) + $""", re.X) @classmethod def from_string(cls, hash): @@ -192,7 +191,7 @@ class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): #=================================================================== def _calc_checksum(self, secret): - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") # NOTE: for some reason, FSHP uses pbkdf1 with password & salt reversed. # this has only a minimal impact on security, diff --git a/passlib/handlers/ldap_digests.py b/passlib/handlers/ldap_digests.py index 30254f0..f4d0c7e 100644 --- a/passlib/handlers/ldap_digests.py +++ b/passlib/handlers/ldap_digests.py @@ -12,7 +12,6 @@ import re # pkg from passlib.handlers.misc import plaintext from passlib.utils import unix_crypt_schemes, to_unicode -from passlib.utils.compat import uascii_to_str, unicode, u from passlib.utils.decor import classproperty import passlib.utils.handlers as uh # local @@ -53,7 +52,7 @@ class _Base64DigestHelper(uh.StaticHandler): return cls.ident def _calc_checksum(self, secret): - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") chk = self._hash_func(secret).digest() return b64encode(chk).decode("ascii") @@ -92,10 +91,10 @@ class _SaltedBase64DigestHelper(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHand def to_string(self): data = self.checksum + self.salt hash = self.ident + b64encode(data).decode("ascii") - return uascii_to_str(hash) + return hash def _calc_checksum(self, secret): - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") return self._hash_func(secret + self.salt).digest() @@ -108,9 +107,9 @@ class ldap_md5(_Base64DigestHelper): The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods have no optional keywords. """ name = "ldap_md5" - ident = u("{MD5}") + ident = u"{MD5}" _hash_func = md5 - _hash_regex = re.compile(u(r"^\{MD5\}(?P<chk>[+/a-zA-Z0-9]{22}==)$")) + _hash_regex = re.compile(r"^\{MD5\}(?P<chk>[+/a-zA-Z0-9]{22}==)$") class ldap_sha1(_Base64DigestHelper): """This class stores passwords using LDAP's plain SHA1 format, and follows the :ref:`password-hash-api`. @@ -118,9 +117,9 @@ class ldap_sha1(_Base64DigestHelper): The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods have no optional keywords. """ name = "ldap_sha1" - ident = u("{SHA}") + ident = u"{SHA}" _hash_func = sha1 - _hash_regex = re.compile(u(r"^\{SHA\}(?P<chk>[+/a-zA-Z0-9]{27}=)$")) + _hash_regex = re.compile(r"^\{SHA\}(?P<chk>[+/a-zA-Z0-9]{27}=)$") class ldap_salted_md5(_SaltedBase64DigestHelper): """This class stores passwords using LDAP's salted MD5 format, and follows the :ref:`password-hash-api`. @@ -156,10 +155,10 @@ class ldap_salted_md5(_SaltedBase64DigestHelper): This format now supports variable length salts, instead of a fix 4 bytes. """ name = "ldap_salted_md5" - ident = u("{SMD5}") + ident = u"{SMD5}" checksum_size = 16 _hash_func = md5 - _hash_regex = re.compile(u(r"^\{SMD5\}(?P<tmp>[+/a-zA-Z0-9]{27,}={0,2})$")) + _hash_regex = re.compile(r"^\{SMD5\}(?P<tmp>[+/a-zA-Z0-9]{27,}={0,2})$") class ldap_salted_sha1(_SaltedBase64DigestHelper): """ @@ -197,11 +196,11 @@ class ldap_salted_sha1(_SaltedBase64DigestHelper): This format now supports variable length salts, instead of a fix 4 bytes. """ name = "ldap_salted_sha1" - ident = u("{SSHA}") + ident = u"{SSHA}" checksum_size = 20 _hash_func = sha1 # NOTE: 32 = ceil((20 + 4) * 4/3) - _hash_regex = re.compile(u(r"^\{SSHA\}(?P<tmp>[+/a-zA-Z0-9]{32,}={0,2})$")) + _hash_regex = re.compile(r"^\{SSHA\}(?P<tmp>[+/a-zA-Z0-9]{32,}={0,2})$") @@ -237,12 +236,12 @@ class ldap_salted_sha256(_SaltedBase64DigestHelper): .. versionadded:: 1.7.3 """ name = "ldap_salted_sha256" - ident = u("{SSHA256}") + ident = u"{SSHA256}" checksum_size = 32 default_salt_size = 8 _hash_func = sha256 # NOTE: 48 = ceil((32 + 4) * 4/3) - _hash_regex = re.compile(u(r"^\{SSHA256\}(?P<tmp>[+/a-zA-Z0-9]{48,}={0,2})$")) + _hash_regex = re.compile(r"^\{SSHA256\}(?P<tmp>[+/a-zA-Z0-9]{48,}={0,2})$") class ldap_salted_sha512(_SaltedBase64DigestHelper): @@ -277,12 +276,12 @@ class ldap_salted_sha512(_SaltedBase64DigestHelper): .. versionadded:: 1.7.3 """ name = "ldap_salted_sha512" - ident = u("{SSHA512}") + ident = u"{SSHA512}" checksum_size = 64 default_salt_size = 8 _hash_func = sha512 # NOTE: 91 = ceil((64 + 4) * 4/3) - _hash_regex = re.compile(u(r"^\{SSHA512\}(?P<tmp>[+/a-zA-Z0-9]{91,}={0,2})$")) + _hash_regex = re.compile(r"^\{SSHA512\}(?P<tmp>[+/a-zA-Z0-9]{91,}={0,2})$") class ldap_plaintext(plaintext): @@ -299,7 +298,7 @@ class ldap_plaintext(plaintext): :param encoding: This controls the character encoding to use (defaults to ``utf-8``). - This encoding will be used to encode :class:`!unicode` passwords + This encoding will be used to encode :class:`!str` passwords under Python 2, and decode :class:`!bytes` hashes under Python 3. .. versionchanged:: 1.6 @@ -309,7 +308,7 @@ class ldap_plaintext(plaintext): # is override identify() name = "ldap_plaintext" - _2307_pat = re.compile(u(r"^\{\w+\}.*$")) + _2307_pat = re.compile(r"^\{\w+\}.*$") @uh.deprecated_method(deprecated="1.7", removed="2.0") @classmethod @@ -337,7 +336,7 @@ def _init_ldap_crypt_handlers(): g = globals() for wname in unix_crypt_schemes: name = 'ldap_' + wname - g[name] = uh.PrefixWrapper(name, wname, prefix=u("{CRYPT}"), lazy=True) + g[name] = uh.PrefixWrapper(name, wname, prefix=u"{CRYPT}", lazy=True) del g _init_ldap_crypt_handlers() diff --git a/passlib/handlers/md5_crypt.py b/passlib/handlers/md5_crypt.py index e3a2dfa..419862c 100644 --- a/passlib/handlers/md5_crypt.py +++ b/passlib/handlers/md5_crypt.py @@ -9,7 +9,6 @@ import logging; log = logging.getLogger(__name__) # pkg from passlib.utils import safe_crypt, test_crypt, repeat_string from passlib.utils.binary import h64 -from passlib.utils.compat import unicode, u import passlib.utils.handlers as uh # local __all__ = [ @@ -67,15 +66,15 @@ def _raw_md5_crypt(pwd, salt, use_apr=False): # validate secret # XXX: not sure what official unicode policy is, using this as default - if isinstance(pwd, unicode): + if isinstance(pwd, str): pwd = pwd.encode("utf-8") - assert isinstance(pwd, bytes), "pwd not unicode or bytes" + assert isinstance(pwd, bytes), "pwd not str or bytes" if _BNULL in pwd: raise uh.exc.NullPasswordError(md5_crypt) pwd_len = len(pwd) # validate salt - should have been taken care of by caller - assert isinstance(salt, unicode), "salt not unicode" + assert isinstance(salt, str), "salt not str" salt = salt.encode("ascii") assert len(salt) < 9, "salt too large" # NOTE: spec says salts larger than 8 bytes should be truncated, @@ -255,7 +254,7 @@ class md5_crypt(uh.HasManyBackends, _MD5_Common): # class attrs #=================================================================== name = "md5_crypt" - ident = u("$1$") + ident = u"$1$" #=================================================================== # methods @@ -329,7 +328,7 @@ class apr_md5_crypt(_MD5_Common): # class attrs #=================================================================== name = "apr_md5_crypt" - ident = u("$apr1$") + ident = u"$apr1$" #=================================================================== # methods diff --git a/passlib/handlers/misc.py b/passlib/handlers/misc.py index 44abc34..8c4de54 100644 --- a/passlib/handlers/misc.py +++ b/passlib/handlers/misc.py @@ -10,73 +10,19 @@ from warnings import warn # site # pkg from passlib.utils import to_native_str, str_consteq -from passlib.utils.compat import unicode, u, unicode_or_bytes_types +from passlib.utils.compat import unicode_or_bytes import passlib.utils.handlers as uh # local __all__ = [ "unix_disabled", - "unix_fallback", "plaintext", ] #============================================================================= # handler #============================================================================= -class unix_fallback(uh.ifc.DisabledHash, uh.StaticHandler): - """This class provides the fallback behavior for unix shadow files, and follows the :ref:`password-hash-api`. - This class does not implement a hash, but instead provides fallback - behavior as found in /etc/shadow on most unix variants. - If used, should be the last scheme in the context. - - * this class will positively identify all hash strings. - * for security, passwords will always hash to ``!``. - * it rejects all passwords if the hash is NOT an empty string (``!`` or ``*`` are frequently used). - * by default it rejects all passwords if the hash is an empty string, - but if ``enable_wildcard=True`` is passed to verify(), - all passwords will be allowed through if the hash is an empty string. - - .. deprecated:: 1.6 - This has been deprecated due to its "wildcard" feature, - and will be removed in Passlib 1.8. Use :class:`unix_disabled` instead. - """ - name = "unix_fallback" - context_kwds = ("enable_wildcard",) - - @classmethod - def identify(cls, hash): - if isinstance(hash, unicode_or_bytes_types): - return True - else: - raise uh.exc.ExpectedStringError(hash, "hash") - - def __init__(self, enable_wildcard=False, **kwds): - warn("'unix_fallback' is deprecated, " - "and will be removed in Passlib 1.8; " - "please use 'unix_disabled' instead.", - DeprecationWarning) - super(unix_fallback, self).__init__(**kwds) - self.enable_wildcard = enable_wildcard - - def _calc_checksum(self, secret): - if self.checksum: - # NOTE: hash will generally be "!", but we want to preserve - # it in case it's something else, like "*". - return self.checksum - else: - return u("!") - - @classmethod - def verify(cls, secret, hash, enable_wildcard=False): - uh.validate_secret(secret) - if not isinstance(hash, unicode_or_bytes_types): - raise uh.exc.ExpectedStringError(hash, "hash") - elif hash: - return False - else: - return enable_wildcard - -_MARKER_CHARS = u("*!") +_MARKER_CHARS = u"*!" _MARKER_BYTES = b"*!" class unix_disabled(uh.ifc.DisabledHash, uh.MinimalHandler): @@ -99,8 +45,8 @@ class unix_disabled(uh.ifc.DisabledHash, uh.MinimalHandler): (:attr:`!unix_disabled.default_marker` will contain the default value) .. versionadded:: 1.6 - This class was added as a replacement for the now-deprecated - :class:`unix_fallback` class, which had some undesirable features. + This class was added as a replacement for the now-removed + :class:`!unix_fallback` class. """ name = "unix_disabled" setting_kwds = ("marker",) @@ -110,16 +56,16 @@ class unix_disabled(uh.ifc.DisabledHash, uh.MinimalHandler): # TODO: rename attr to 'marker'... if 'bsd' in sys.platform: # pragma: no cover -- runtime detection - default_marker = u("*") + default_marker = u"*" else: # use the linux default for other systems # (glibc also supports adding old hash after the marker # so it can be restored later). - default_marker = u("!") + default_marker = u"!" @classmethod def using(cls, marker=None, **kwds): - subcls = super(unix_disabled, cls).using(**kwds) + subcls = super().using(**kwds) if marker is not None: if not cls.identify(marker): raise ValueError("invalid marker: %r" % marker) @@ -142,7 +88,7 @@ class unix_disabled(uh.ifc.DisabledHash, uh.MinimalHandler): # * linux may use "!" + hash to disable but preserve original hash # * linux counts empty string as "any password"; # this code recognizes it, but treats it the same as "!" - if isinstance(hash, unicode): + if isinstance(hash, str): start = _MARKER_CHARS elif isinstance(hash, bytes): start = _MARKER_BYTES @@ -215,7 +161,7 @@ class plaintext(uh.MinimalHandler): :param encoding: This controls the character encoding to use (defaults to ``utf-8``). - This encoding will be used to encode :class:`!unicode` passwords + This encoding will be used to encode :class:`!str` passwords under Python 2, and decode :class:`!bytes` hashes under Python 3. .. versionchanged:: 1.6 @@ -230,7 +176,7 @@ class plaintext(uh.MinimalHandler): @classmethod def identify(cls, hash): - if isinstance(hash, unicode_or_bytes_types): + if isinstance(hash, unicode_or_bytes): return True else: raise uh.exc.ExpectedStringError(hash, "hash") diff --git a/passlib/handlers/mssql.py b/passlib/handlers/mssql.py index b060b36..fcd1fe5 100644 --- a/passlib/handlers/mssql.py +++ b/passlib/handlers/mssql.py @@ -43,7 +43,7 @@ from warnings import warn # site # pkg from passlib.utils import consteq -from passlib.utils.compat import bascii_to_str, unicode, u +from passlib.utils.compat import bascii_to_str import passlib.utils.handlers as uh # local __all__ = [ @@ -55,17 +55,17 @@ __all__ = [ # mssql 2000 #============================================================================= def _raw_mssql(secret, salt): - assert isinstance(secret, unicode) + assert isinstance(secret, str) assert isinstance(salt, bytes) return sha1(secret.encode("utf-16-le") + salt).digest() BIDENT = b"0x0100" ##BIDENT2 = b("\x01\x00") -UIDENT = u("0x0100") +UIDENT = u"0x0100" def _ident_mssql(hash, csize, bsize): """common identify for mssql 2000/2005""" - if isinstance(hash, unicode): + if isinstance(hash, str): if len(hash) == csize and hash.startswith(UIDENT): return True elif isinstance(hash, bytes): @@ -79,7 +79,7 @@ def _ident_mssql(hash, csize, bsize): def _parse_mssql(hash, csize, bsize, handler): """common parser for mssql 2000/2005; returns 4 byte salt + checksum""" - if isinstance(hash, unicode): + if isinstance(hash, str): if len(hash) == csize and hash.startswith(UIDENT): try: return unhexlify(hash[6:].encode("utf-8")) diff --git a/passlib/handlers/mysql.py b/passlib/handlers/mysql.py index 4a71253..0958c46 100644 --- a/passlib/handlers/mysql.py +++ b/passlib/handlers/mysql.py @@ -30,8 +30,6 @@ from warnings import warn # site # pkg from passlib.utils import to_native_str -from passlib.utils.compat import bascii_to_str, unicode, u, \ - byte_elem_value, str_to_uascii import passlib.utils.handlers as uh # local __all__ = [ @@ -65,7 +63,7 @@ class mysql323(uh.StaticHandler): def _calc_checksum(self, secret): # FIXME: no idea if mysql has a policy about handling unicode passwords - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") MASK_32 = 0xffffffff @@ -78,11 +76,11 @@ class mysql323(uh.StaticHandler): for c in secret: if c in WHITE: continue - tmp = byte_elem_value(c) + tmp = c nr1 ^= ((((nr1 & 63)+add)*tmp) + (nr1 << 8)) & MASK_32 nr2 = (nr2+((nr2 << 8) ^ nr1)) & MASK_32 add = (add+tmp) & MASK_32 - return u("%08x%08x") % (nr1 & MASK_31, nr2 & MASK_31) + return u"%08x%08x" % (nr1 & MASK_31, nr2 & MASK_31) #=================================================================== # eoc @@ -102,7 +100,7 @@ class mysql41(uh.StaticHandler): # class attrs #=================================================================== name = "mysql41" - _hash_prefix = u("*") + _hash_prefix = u"*" checksum_chars = uh.HEX_CHARS checksum_size = 40 @@ -115,9 +113,9 @@ class mysql41(uh.StaticHandler): def _calc_checksum(self, secret): # FIXME: no idea if mysql has a policy about handling unicode passwords - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") - return str_to_uascii(sha1(sha1(secret).digest()).hexdigest()).upper() + return sha1(sha1(secret).digest()).hexdigest().upper() #=================================================================== # eoc diff --git a/passlib/handlers/oracle.py b/passlib/handlers/oracle.py index a094f37..92f1767 100644 --- a/passlib/handlers/oracle.py +++ b/passlib/handlers/oracle.py @@ -10,8 +10,6 @@ import logging; log = logging.getLogger(__name__) # site # pkg from passlib.utils import to_unicode, xor_bytes -from passlib.utils.compat import irange, u, \ - uascii_to_str, unicode, str_to_uascii from passlib.crypto.des import des_encrypt_block import passlib.utils.handlers as uh # local @@ -41,7 +39,7 @@ def des_cbc_encrypt(key, value, iv=b'\x00' * 8, pad=b'\x00'): """ value += pad * (-len(value) % 8) # null pad to multiple of 8 hash = iv # start things off - for offset in irange(0,len(value),8): + for offset in range(0, len(value), 8): chunk = xor_bytes(hash, value[offset:offset+8]) hash = des_encrypt_block(key, chunk) return hash @@ -141,7 +139,7 @@ class oracle11(uh.HasSalt, uh.GenericHandler): #=================================================================== # methods #=================================================================== - _hash_regex = re.compile(u("^S:(?P<chk>[0-9a-f]{40})(?P<salt>[0-9a-f]{20})$"), re.I) + _hash_regex = re.compile(u"^S:(?P<chk>[0-9a-f]{40})(?P<salt>[0-9a-f]{20})$", re.I) @classmethod def from_string(cls, hash): @@ -154,14 +152,14 @@ class oracle11(uh.HasSalt, uh.GenericHandler): def to_string(self): chk = self.checksum - hash = u("S:%s%s") % (chk.upper(), self.salt.upper()) - return uascii_to_str(hash) + hash = u"S:%s%s" % (chk.upper(), self.salt.upper()) + return hash def _calc_checksum(self, secret): - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") chk = sha1(secret + unhexlify(self.salt.encode("ascii"))).hexdigest() - return str_to_uascii(chk).upper() + return chk.upper() #=================================================================== # eoc diff --git a/passlib/handlers/pbkdf2.py b/passlib/handlers/pbkdf2.py index 274278d..407c2b3 100644 --- a/passlib/handlers/pbkdf2.py +++ b/passlib/handlers/pbkdf2.py @@ -10,7 +10,7 @@ import logging; log = logging.getLogger(__name__) # pkg from passlib.utils import to_unicode from passlib.utils.binary import ab64_decode, ab64_encode -from passlib.utils.compat import str_to_bascii, u, uascii_to_str, unicode +from passlib.utils.compat import str_to_bascii from passlib.crypto.digest import pbkdf2_hmac import passlib.utils.handlers as uh # local @@ -81,7 +81,7 @@ def create_pbkdf2_hash(hash_name, digest_size, rounds=12000, ident=None, module= """create new Pbkdf2DigestHandler subclass for a specific hash""" name = 'pbkdf2_' + hash_name if ident is None: - ident = u("$pbkdf2-%s$") % (hash_name,) + ident = u"$pbkdf2-%s$" % (hash_name,) base = Pbkdf2DigestHandler return type(name, (base,), dict( __module__=module, # so ABCMeta won't clobber it. @@ -128,7 +128,7 @@ def create_pbkdf2_hash(hash_name, digest_size, rounds=12000, ident=None, module= #------------------------------------------------------------------------ # derived handlers #------------------------------------------------------------------------ -pbkdf2_sha1 = create_pbkdf2_hash("sha1", 20, 131000, ident=u("$pbkdf2$")) +pbkdf2_sha1 = create_pbkdf2_hash("sha1", 20, 131000, ident=u"$pbkdf2$") pbkdf2_sha256 = create_pbkdf2_hash("sha256", 32, 29000) pbkdf2_sha512 = create_pbkdf2_hash("sha512", 64, 25000) @@ -183,7 +183,7 @@ class cta_pbkdf2_sha1(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Generic #--GenericHandler-- name = "cta_pbkdf2_sha1" setting_kwds = ("salt", "salt_size", "rounds") - ident = u("$p5k2$") + ident = u"$p5k2$" checksum_size = 20 # NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide a @@ -279,8 +279,8 @@ class dlitz_pbkdf2_sha1(uh.HasRounds, uh.HasSalt, uh.GenericHandler): #--GenericHandler-- name = "dlitz_pbkdf2_sha1" setting_kwds = ("salt", "salt_size", "rounds") - ident = u("$p5k2$") - _stub_checksum = u("0" * 48 + "=") + ident = u"$p5k2$" + _stub_checksum = u"0" * 48 + "=" # NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide a # sanity check. underlying algorithm (and reference implementation) @@ -370,7 +370,7 @@ class atlassian_pbkdf2_sha1(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler) #--GenericHandler-- name = "atlassian_pbkdf2_sha1" setting_kwds =("salt",) - ident = u("{PKCS5S2}") + ident = u"{PKCS5S2}" checksum_size = 32 #--HasRawSalt-- @@ -389,7 +389,7 @@ class atlassian_pbkdf2_sha1(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler) def to_string(self): data = self.salt + self.checksum hash = self.ident + b64encode(data).decode("ascii") - return uascii_to_str(hash) + return hash def _calc_checksum(self, secret): # TODO: find out what crowd's policy is re: unicode @@ -436,7 +436,7 @@ class grub_pbkdf2_sha512(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Gene name = "grub_pbkdf2_sha512" setting_kwds = ("salt", "salt_size", "rounds") - ident = u("grub.pbkdf2.sha512.") + ident = u"grub.pbkdf2.sha512." checksum_size = 64 # NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide a @@ -453,7 +453,7 @@ class grub_pbkdf2_sha512(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Gene @classmethod def from_string(cls, hash): - rounds, salt, chk = uh.parse_mc3(hash, cls.ident, sep=u("."), + rounds, salt, chk = uh.parse_mc3(hash, cls.ident, sep=u".", handler=cls) salt = unhexlify(salt.encode("ascii")) if chk: @@ -463,7 +463,7 @@ class grub_pbkdf2_sha512(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Gene def to_string(self): salt = hexlify(self.salt).decode("ascii").upper() chk = hexlify(self.checksum).decode("ascii").upper() - return uh.render_mc3(self.ident, self.rounds, salt, chk, sep=u(".")) + return uh.render_mc3(self.ident, self.rounds, salt, chk, sep=u".") def _calc_checksum(self, secret): # TODO: find out what grub's policy is re: unicode diff --git a/passlib/handlers/phpass.py b/passlib/handlers/phpass.py index 6736f0f..102f9de 100644 --- a/passlib/handlers/phpass.py +++ b/passlib/handlers/phpass.py @@ -14,7 +14,6 @@ import logging; log = logging.getLogger(__name__) # site # pkg from passlib.utils.binary import h64 -from passlib.utils.compat import u, uascii_to_str, unicode import passlib.utils.handlers as uh # local __all__ = [ @@ -79,9 +78,9 @@ class phpass(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.GenericHandler): rounds_cost = "log2" #--HasManyIdents-- - default_ident = u("$P$") - ident_values = (u("$P$"), u("$H$")) - ident_aliases = {u("P"):u("$P$"), u("H"):u("$H$")} + default_ident = u"$P$" + ident_values = (u"$P$", u"$H$") + ident_aliases = {u"P":u"$P$", u"H":u"$H$"} #=================================================================== # formatting @@ -105,18 +104,18 @@ class phpass(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.GenericHandler): ) def to_string(self): - hash = u("%s%s%s%s") % (self.ident, + hash = u"%s%s%s%s" % (self.ident, h64.encode_int6(self.rounds).decode("ascii"), self.salt, - self.checksum or u('')) - return uascii_to_str(hash) + self.checksum or u'') + return hash #=================================================================== # backend #=================================================================== def _calc_checksum(self, secret): # FIXME: can't find definitive policy on how phpass handles non-ascii. - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") real_rounds = 1<<self.rounds result = md5(self.salt.encode("ascii") + secret).digest() diff --git a/passlib/handlers/postgres.py b/passlib/handlers/postgres.py index 17156fa..abe7663 100644 --- a/passlib/handlers/postgres.py +++ b/passlib/handlers/postgres.py @@ -8,7 +8,6 @@ import logging; log = logging.getLogger(__name__) # site # pkg from passlib.utils import to_bytes -from passlib.utils.compat import str_to_uascii, unicode, u import passlib.utils.handlers as uh # local __all__ = [ @@ -33,7 +32,7 @@ class postgres_md5(uh.HasUserContext, uh.StaticHandler): # algorithm information #=================================================================== name = "postgres_md5" - _hash_prefix = u("md5") + _hash_prefix = u"md5" checksum_chars = uh.HEX_CHARS checksum_size = 32 @@ -41,10 +40,10 @@ class postgres_md5(uh.HasUserContext, uh.StaticHandler): # primary interface #=================================================================== def _calc_checksum(self, secret): - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") user = to_bytes(self.user, "utf-8", param="user") - return str_to_uascii(md5(secret + user).hexdigest()) + return md5(secret + user).hexdigest() #=================================================================== # eoc diff --git a/passlib/handlers/roundup.py b/passlib/handlers/roundup.py index c7f99c1..bb00cf6 100644 --- a/passlib/handlers/roundup.py +++ b/passlib/handlers/roundup.py @@ -7,7 +7,6 @@ import logging; log = logging.getLogger(__name__) # site # pkg import passlib.utils.handlers as uh -from passlib.utils.compat import u # local __all__ = [ "roundup_plaintext", @@ -18,11 +17,11 @@ __all__ = [ # #============================================================================= roundup_plaintext = uh.PrefixWrapper("roundup_plaintext", "plaintext", - prefix=u("{plaintext}"), lazy=True) + prefix=u"{plaintext}", lazy=True) # NOTE: these are here because they're currently only known to be used by roundup -ldap_hex_md5 = uh.PrefixWrapper("ldap_hex_md5", "hex_md5", u("{MD5}"), lazy=True) -ldap_hex_sha1 = uh.PrefixWrapper("ldap_hex_sha1", "hex_sha1", u("{SHA}"), lazy=True) +ldap_hex_md5 = uh.PrefixWrapper("ldap_hex_md5", "hex_md5", u"{MD5}", lazy=True) +ldap_hex_sha1 = uh.PrefixWrapper("ldap_hex_sha1", "hex_sha1", u"{SHA}", lazy=True) #============================================================================= # eof diff --git a/passlib/handlers/scram.py b/passlib/handlers/scram.py index 87bfabd..256de03 100644 --- a/passlib/handlers/scram.py +++ b/passlib/handlers/scram.py @@ -8,7 +8,7 @@ import logging; log = logging.getLogger(__name__) # pkg from passlib.utils import consteq, saslprep, to_native_str, splitcomma from passlib.utils.binary import ab64_decode, ab64_encode -from passlib.utils.compat import bascii_to_str, iteritems, u, native_string_types +from passlib.utils.compat import bascii_to_str from passlib.crypto.digest import pbkdf2_hmac, norm_hash_name import passlib.utils.handlers as uh # local @@ -87,7 +87,7 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): #--GenericHandler-- name = "scram" setting_kwds = ("salt", "salt_size", "rounds", "algs") - ident = u("$scram$") + ident = u"$scram$" #--HasSalt-- default_salt_size = 12 @@ -193,7 +193,7 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): SaltedPassword := Hi(Normalize(password), salt, i) - :type password: unicode or utf-8 bytes + :type password: str or utf-8 bytes :arg password: password to run through digest :type salt: bytes @@ -286,7 +286,7 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): default_algs = algs # create subclass - subcls = super(scram, cls).using(**kwds) + subcls = super().using(**kwds) # fill in algs if default_algs is not None: @@ -297,7 +297,7 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): # init #=================================================================== def __init__(self, algs=None, **kwds): - super(scram, self).__init__(**kwds) + super().__init__(**kwds) # init algs digest_map = self.checksum @@ -318,7 +318,7 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): def _norm_checksum(self, checksum, relaxed=False): if not isinstance(checksum, dict): raise uh.exc.ExpectedTypeError(checksum, "dict", "checksum") - for alg, digest in iteritems(checksum): + for alg, digest in checksum.items(): if alg != norm_hash_name(alg, 'iana'): raise ValueError("malformed algorithm name in scram hash: %r" % (alg,)) @@ -336,7 +336,7 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): @classmethod def _norm_algs(cls, algs): """normalize algs parameter""" - if isinstance(algs, native_string_types): + if isinstance(algs, str): algs = splitcomma(algs) algs = sorted(norm_hash_name(alg, 'iana') for alg in algs) if any(len(alg)>9 for alg in algs): @@ -357,7 +357,7 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): return True # hand off to base implementation - return super(scram, self)._calc_needs_update(**kwds) + return super()._calc_needs_update(**kwds) #=================================================================== # digest methods @@ -390,7 +390,7 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): # check entire hash for consistency. if full: correct = failed = False - for alg, digest in iteritems(chkmap): + for alg, digest in chkmap.items(): other = self._calc_checksum(secret, alg) # NOTE: could do this length check in norm_algs(), # but don't need to be that strict, and want to be able @@ -429,15 +429,14 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): ##def _test_reference_scram(): ## "quick hack testing scram reference vectors" ## # NOTE: "n,," is GS2 header - see https://tools.ietf.org/html/rfc5801 -## from passlib.utils.compat import print_ ## ## engine = _scram_engine( ## alg="sha-1", ## salt='QSXCR+Q6sek8bf92'.decode("base64"), ## rounds=4096, -## password=u("pencil"), +## password=u"pencil", ## ) -## print_(engine.digest.encode("base64").rstrip()) +## print(engine.digest.encode("base64").rstrip()) ## ## msg = engine.format_auth_msg( ## username="user", diff --git a/passlib/handlers/scrypt.py b/passlib/handlers/scrypt.py index 1686fda..2d3d36b 100644 --- a/passlib/handlers/scrypt.py +++ b/passlib/handlers/scrypt.py @@ -2,7 +2,6 @@ #============================================================================= # imports #============================================================================= -from __future__ import with_statement, absolute_import # core import logging; log = logging.getLogger(__name__) # site @@ -10,7 +9,7 @@ import logging; log = logging.getLogger(__name__) from passlib.crypto import scrypt as _scrypt from passlib.utils import h64, to_bytes from passlib.utils.binary import h64, b64s_decode, b64s_encode -from passlib.utils.compat import u, bascii_to_str, suppress_cause +from passlib.utils.compat import bascii_to_str from passlib.utils.decor import classproperty import passlib.utils.handlers as uh # local @@ -22,10 +21,10 @@ __all__ = [ # scrypt format identifiers #============================================================================= -IDENT_SCRYPT = u("$scrypt$") # identifier used by passlib -IDENT_7 = u("$7$") # used by official scrypt spec +IDENT_SCRYPT = u"$scrypt$" # identifier used by passlib +IDENT_7 = u"$7$" # used by official scrypt spec -_UDOLLAR = u("$") +_UDOLLAR = u"$" #============================================================================= # handler @@ -154,9 +153,9 @@ class scrypt(uh.ParallelismMixin, uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum @classmethod def using(cls, block_size=None, **kwds): - subcls = super(scrypt, cls).using(**kwds) + subcls = super().using(**kwds) if block_size is not None: - if isinstance(block_size, uh.native_string_types): + if isinstance(block_size, str): block_size = int(block_size) subcls.block_size = subcls._norm_block_size(block_size, relaxed=kwds.get("relaxed")) @@ -164,7 +163,7 @@ class scrypt(uh.ParallelismMixin, uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum try: _scrypt.validate(1 << cls.default_rounds, cls.block_size, cls.parallelism) except ValueError as err: - raise suppress_cause(ValueError("scrypt: invalid settings combination: " + str(err))) + raise ValueError("scrypt: invalid settings combination: " + str(err)) from None return subcls @@ -288,7 +287,7 @@ class scrypt(uh.ParallelismMixin, uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum try: salt.decode("ascii") except UnicodeDecodeError: - raise suppress_cause(NotImplementedError("scrypt $7$ hashes dont support non-ascii salts")) + raise NotImplementedError("scrypt $7$ hashes dont support non-ascii salts") from None return bascii_to_str(b"".join([ b"$7$", h64.encode_int6(self.rounds), @@ -303,7 +302,7 @@ class scrypt(uh.ParallelismMixin, uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum # init #=================================================================== def __init__(self, block_size=None, **kwds): - super(scrypt, self).__init__(**kwds) + super().__init__(**kwds) # init block size if block_size is None: @@ -320,7 +319,7 @@ class scrypt(uh.ParallelismMixin, uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum return uh.norm_integer(cls, block_size, min=1, param="block_size", relaxed=relaxed) def _generate_salt(self): - salt = super(scrypt, self)._generate_salt() + salt = super()._generate_salt() if self.ident == IDENT_7: # this format doesn't support non-ascii salts. # as workaround, we take raw bytes, encoded to base64 @@ -372,7 +371,7 @@ class scrypt(uh.ParallelismMixin, uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum # XXX: for now, marking all hashes which don't have matching block_size setting if self.block_size != type(self).block_size: return True - return super(scrypt, self)._calc_needs_update(**kwds) + return super()._calc_needs_update(**kwds) #=================================================================== # eoc diff --git a/passlib/handlers/sha1_crypt.py b/passlib/handlers/sha1_crypt.py index 8f9aa71..287f044 100644 --- a/passlib/handlers/sha1_crypt.py +++ b/passlib/handlers/sha1_crypt.py @@ -11,7 +11,6 @@ import logging; log = logging.getLogger(__name__) # pkg from passlib.utils import safe_crypt, test_crypt from passlib.utils.binary import h64 -from passlib.utils.compat import u, unicode, irange from passlib.crypto.digest import compile_hmac import passlib.utils.handlers as uh # local @@ -62,7 +61,7 @@ class sha1_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler #--GenericHandler-- name = "sha1_crypt" setting_kwds = ("salt", "salt_size", "rounds") - ident = u("$sha1$") + ident = u"$sha1$" checksum_size = 28 checksum_chars = uh.HASH64_CHARS @@ -126,16 +125,16 @@ class sha1_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler return True def _calc_checksum_builtin(self, secret): - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") if _BNULL in secret: raise uh.exc.NullPasswordError(self) rounds = self.rounds # NOTE: this seed value is NOT the same as the config string - result = (u("%s$sha1$%s") % (self.salt, rounds)).encode("ascii") + result = (u"%s$sha1$%s" % (self.salt, rounds)).encode("ascii") # NOTE: this algorithm is essentially PBKDF1, modified to use HMAC. keyed_hmac = compile_hmac("sha1", secret) - for _ in irange(rounds): + for _ in range(rounds): result = keyed_hmac(result) return h64.encode_transposed_bytes(result, self._chk_offsets).decode("ascii") diff --git a/passlib/handlers/sha2_crypt.py b/passlib/handlers/sha2_crypt.py index e6060c5..d539472 100644 --- a/passlib/handlers/sha2_crypt.py +++ b/passlib/handlers/sha2_crypt.py @@ -10,8 +10,6 @@ import logging; log = logging.getLogger(__name__) from passlib.utils import safe_crypt, test_crypt, \ repeat_string, to_unicode from passlib.utils.binary import h64 -from passlib.utils.compat import byte_elem_value, u, \ - uascii_to_str, unicode import passlib.utils.handlers as uh # local __all__ = [ @@ -86,7 +84,7 @@ def _raw_sha2_crypt(pwd, salt, rounds, use_512=False): # for details). # validate secret - if isinstance(pwd, unicode): + if isinstance(pwd, str): # XXX: not sure what official unicode policy is, using this as default pwd = pwd.encode("utf-8") assert isinstance(pwd, bytes) @@ -101,7 +99,7 @@ def _raw_sha2_crypt(pwd, salt, rounds, use_512=False): # by the handler class. # validate salt - assert isinstance(salt, unicode), "salt not unicode" + assert isinstance(salt, str), "salt not str" salt = salt.encode("ascii") salt_len = len(salt) assert salt_len < 17, "salt too large" @@ -163,7 +161,7 @@ def _raw_sha2_crypt(pwd, salt, rounds, use_512=False): #=================================================================== # digest S - used instead of salt itself when calculating digest C #=================================================================== - ds = hash_const(salt * (16 + byte_elem_value(da[0]))).digest()[:salt_len] + ds = hash_const(salt * (16 + da[0])).digest()[:salt_len] assert len(ds) == salt_len, "salt_len somehow > hash_len!" #=================================================================== @@ -246,9 +244,9 @@ def _raw_sha2_crypt(pwd, salt, rounds, use_512=False): #============================================================================= # handlers #============================================================================= -_UROUNDS = u("rounds=") -_UDOLLAR = u("$") -_UZERO = u("0") +_UROUNDS = u"rounds=" +_UDOLLAR = u"$" +_UZERO = u"0" class _SHA2_Common(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler): @@ -278,7 +276,7 @@ class _SHA2_Common(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, implicit_rounds = False def __init__(self, implicit_rounds=None, **kwds): - super(_SHA2_Common, self).__init__(**kwds) + super().__init__(**kwds) # if user calls hash() w/ 5000 rounds, default to compact form. if implicit_rounds is None: implicit_rounds = (self.use_defaults and self.rounds == 5000) @@ -339,12 +337,12 @@ class _SHA2_Common(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, def to_string(self): if self.rounds == 5000 and self.implicit_rounds: - hash = u("%s%s$%s") % (self.ident, self.salt, - self.checksum or u('')) + hash = u"%s%s$%s" % (self.ident, self.salt, + self.checksum or u'') else: - hash = u("%srounds=%d$%s$%s") % (self.ident, self.rounds, - self.salt, self.checksum or u('')) - return uascii_to_str(hash) + hash = u"%srounds=%d$%s$%s" % (self.ident, self.rounds, + self.salt, self.checksum or u'') + return hash #=================================================================== # backends @@ -444,7 +442,7 @@ class sha256_crypt(_SHA2_Common): # class attrs #=================================================================== name = "sha256_crypt" - ident = u("$5$") + ident = u"$5$" checksum_size = 43 # NOTE: using 25/75 weighting of builtin & os_crypt backends default_rounds = 535000 @@ -511,7 +509,7 @@ class sha512_crypt(_SHA2_Common): # class attrs #=================================================================== name = "sha512_crypt" - ident = u("$6$") + ident = u"$6$" checksum_size = 86 _cdb_use_512 = True # NOTE: using 25/75 weighting of builtin & os_crypt backends diff --git a/passlib/handlers/sun_md5_crypt.py b/passlib/handlers/sun_md5_crypt.py index 0eeb4e7..d80d7ff 100644 --- a/passlib/handlers/sun_md5_crypt.py +++ b/passlib/handlers/sun_md5_crypt.py @@ -19,8 +19,7 @@ from warnings import warn # pkg from passlib.utils import to_unicode from passlib.utils.binary import h64 -from passlib.utils.compat import byte_elem_value, irange, u, \ - uascii_to_str, unicode, str_to_bascii +from passlib.utils.compat import str_to_bascii import passlib.utils.handlers as uh # local __all__ = [ @@ -73,7 +72,7 @@ MAGIC_HAMLET = ( ) # NOTE: these sequences are pre-calculated iteration ranges used by X & Y loops w/in rounds function below -xr = irange(7) +xr = range(7) _XY_ROUNDS = [ tuple((i,i,i+3) for i in xr), # xrounds 0 tuple((i,i+1,i+4) for i in xr), # xrounds 1 @@ -124,7 +123,7 @@ def raw_sun_md5_crypt(secret, rounds, salt): round = 0 while round < real_rounds: # convert last result byte string to list of byte-ints for easy access - rval = [ byte_elem_value(c) for c in result ].__getitem__ + rval = [c for c in result].__getitem__ # build up X bit by bit x = 0 @@ -151,7 +150,7 @@ def raw_sun_md5_crypt(secret, rounds, salt): h = md5(result) if coin: h.update(MAGIC_HAMLET) - h.update(unicode(round).encode("ascii")) + h.update(str(round).encode("ascii")) result = h.digest() round += 1 @@ -237,7 +236,7 @@ class sun_md5_crypt(uh.HasRounds, uh.HasSalt, uh.GenericHandler): # XXX: ^ not sure what it does if past this bound... does 32 int roll over? rounds_cost = "linear" - ident_values = (u("$md5$"), u("$md5,")) + ident_values = (u"$md5$", u"$md5,") #=================================================================== # instance attrs @@ -249,7 +248,7 @@ class sun_md5_crypt(uh.HasRounds, uh.HasSalt, uh.GenericHandler): #=================================================================== def __init__(self, bare_salt=False, **kwds): self.bare_salt = bare_salt - super(sun_md5_crypt, self).__init__(**kwds) + super().__init__(**kwds) #=================================================================== # internal helpers @@ -268,11 +267,11 @@ class sun_md5_crypt(uh.HasRounds, uh.HasSalt, uh.GenericHandler): # if so, parse and validate it. # by end, set 'rounds' to int value, and 'tail' containing salt+chk # - if hash.startswith(u("$md5$")): + if hash.startswith(u"$md5$"): rounds = 0 salt_idx = 5 - elif hash.startswith(u("$md5,rounds=")): - idx = hash.find(u("$"), 12) + elif hash.startswith(u"$md5,rounds="): + idx = hash.find(u"$", 12) if idx == -1: raise uh.exc.MalformedHashError(cls, "unexpected end of rounds") rstr = hash[12:idx] @@ -280,7 +279,7 @@ class sun_md5_crypt(uh.HasRounds, uh.HasSalt, uh.GenericHandler): rounds = int(rstr) except ValueError: raise uh.exc.MalformedHashError(cls, "bad rounds") - if rstr != unicode(rounds): + if rstr != str(rounds): raise uh.exc.ZeroPaddedRoundsError(cls) if rounds == 0: # NOTE: not sure if this is forbidden by spec or not; @@ -296,20 +295,20 @@ class sun_md5_crypt(uh.HasRounds, uh.HasSalt, uh.GenericHandler): # to deal cleanly with some backward-compatible workarounds # implemented by original implementation. # - chk_idx = hash.rfind(u("$"), salt_idx) + chk_idx = hash.rfind(u"$", salt_idx) if chk_idx == -1: # ''-config for $-hash salt = hash[salt_idx:] chk = None bare_salt = True elif chk_idx == len(hash)-1: - if chk_idx > salt_idx and hash[-2] == u("$"): + if chk_idx > salt_idx and hash[-2] == u"$": raise uh.exc.MalformedHashError(cls, "too many '$' separators") # $-config for $$-hash salt = hash[salt_idx:-1] chk = None bare_salt = False - elif chk_idx > 0 and hash[chk_idx-1] == u("$"): + elif chk_idx > 0 and hash[chk_idx-1] == u"$": # $$-hash salt = hash[salt_idx:chk_idx-1] chk = hash[chk_idx+1:] @@ -328,16 +327,16 @@ class sun_md5_crypt(uh.HasRounds, uh.HasSalt, uh.GenericHandler): ) def to_string(self, _withchk=True): - ss = u('') if self.bare_salt else u('$') + ss = u'' if self.bare_salt else u'$' rounds = self.rounds if rounds > 0: - hash = u("$md5,rounds=%d$%s%s") % (rounds, self.salt, ss) + hash = u"$md5,rounds=%d$%s%s" % (rounds, self.salt, ss) else: - hash = u("$md5$%s%s") % (self.salt, ss) + hash = u"$md5$%s%s" % (self.salt, ss) if _withchk: chk = self.checksum - hash = u("%s$%s") % (hash, chk) - return uascii_to_str(hash) + hash = u"%s$%s" % (hash, chk) + return hash #=================================================================== # primary interface @@ -349,7 +348,7 @@ class sun_md5_crypt(uh.HasRounds, uh.HasSalt, uh.GenericHandler): def _calc_checksum(self, secret): # NOTE: no reference for how sun_md5_crypt handles unicode - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") config = str_to_bascii(self.to_string(_withchk=False)) return raw_sun_md5_crypt(secret, self.rounds, config).decode("ascii") diff --git a/passlib/handlers/windows.py b/passlib/handlers/windows.py index e17beba..5ac890c 100644 --- a/passlib/handlers/windows.py +++ b/passlib/handlers/windows.py @@ -9,7 +9,6 @@ from warnings import warn # site # pkg from passlib.utils import to_unicode, right_pad_string -from passlib.utils.compat import unicode from passlib.crypto.digest import lookup_hash md4 = lookup_hash("md4").const import passlib.utils.handlers as uh @@ -100,7 +99,7 @@ class lmhash(uh.TruncateMixin, uh.HasEncodingContext, uh.StaticHandler): def raw(cls, secret, encoding=None): """encode password using LANMAN hash algorithm. - :type secret: unicode or utf-8 encoded bytes + :type secret: str or utf-8 encoded bytes :arg secret: secret to hash :type encoding: str :arg encoding: @@ -117,7 +116,7 @@ class lmhash(uh.TruncateMixin, uh.HasEncodingContext, uh.StaticHandler): # http://www.freerainbowtables.com/phpBB3/viewtopic.php?t=387&p=12163 from passlib.crypto.des import des_encrypt_block MAGIC = cls._magic - if isinstance(secret, unicode): + if isinstance(secret, str): # perform uppercasing while we're still unicode, # to give a better shot at getting non-ascii chars right. # (though some codepages do NOT upper-case the same as unicode). @@ -129,7 +128,7 @@ class lmhash(uh.TruncateMixin, uh.HasEncodingContext, uh.StaticHandler): # but *that* might not always be right. secret = secret.upper() else: - raise TypeError("secret must be unicode or bytes") + raise TypeError("secret must be str or bytes") secret = right_pad_string(secret, 14) return des_encrypt_block(secret[0:7], MAGIC) + \ des_encrypt_block(secret[7:14], MAGIC) @@ -180,14 +179,6 @@ class nthash(uh.StaticHandler): # XXX: found refs that say only first 128 chars are used. return md4(secret.encode("utf-16-le")).digest() - @classmethod - def raw_nthash(cls, secret, hex=False): - warn("nthash.raw_nthash() is deprecated, and will be removed " - "in Passlib 1.8, please use nthash.raw() instead", - DeprecationWarning) - ret = nthash.raw(secret) - return hexlify(ret).decode("ascii") if hex else ret - #=================================================================== # eoc #=================================================================== @@ -270,7 +261,7 @@ class msdcc(uh.HasUserContext, uh.StaticHandler): def raw(cls, secret, user): """encode password using mscash v1 algorithm - :arg secret: secret as unicode or utf-8 encoded bytes + :arg secret: secret as str or utf-8 encoded bytes :arg user: username to use as salt :returns: returns string of raw bytes @@ -315,7 +306,7 @@ class msdcc2(uh.HasUserContext, uh.StaticHandler): def raw(cls, secret, user): """encode password using msdcc v2 algorithm - :type secret: unicode or utf-8 bytes + :type secret: str or utf-8 bytes :arg secret: secret :type user: str diff --git a/passlib/hash.py b/passlib/hash.py index 2cc0628..898e315 100644 --- a/passlib/hash.py +++ b/passlib/hash.py @@ -45,7 +45,7 @@ if False: from passlib.handlers.fshp import fshp from passlib.handlers.ldap_digests import ldap_bcrypt, ldap_bsdi_crypt, ldap_des_crypt, ldap_md5, ldap_md5_crypt, ldap_plaintext, ldap_salted_md5, ldap_salted_sha1, ldap_salted_sha256, ldap_salted_sha512, ldap_sha1, ldap_sha1_crypt, ldap_sha256_crypt, ldap_sha512_crypt from passlib.handlers.md5_crypt import apr_md5_crypt, md5_crypt - from passlib.handlers.misc import plaintext, unix_disabled, unix_fallback + from passlib.handlers.misc import plaintext, unix_disabled from passlib.handlers.mssql import mssql2000, mssql2005 from passlib.handlers.mysql import mysql323, mysql41 from passlib.handlers.oracle import oracle10, oracle11 diff --git a/passlib/ifc.py b/passlib/ifc.py index 559d256..c183d6b 100644 --- a/passlib/ifc.py +++ b/passlib/ifc.py @@ -14,28 +14,15 @@ __all__ = [ ] #============================================================================= -# 2/3 compatibility helpers -#============================================================================= -def recreate_with_metaclass(meta): - """class decorator that re-creates class using metaclass""" - def builder(cls): - if meta is type(cls): - return cls - return meta(cls.__name__, cls.__bases__, cls.__dict__.copy()) - return builder - -#============================================================================= # PasswordHash interface #============================================================================= -from abc import ABCMeta, abstractmethod, abstractproperty - -# TODO: make this actually use abstractproperty(), -# now that we dropped py25, 'abc' is always available. +from abc import ABC, abstractmethod -# XXX: rename to PasswordHasher? +# XXX: mark some attributes with abstractproperty()? +# or would type hinting be enough? -@recreate_with_metaclass(ABCMeta) -class PasswordHash(object): +# XXX: rename this to PasswordHasher? +class PasswordHash(ABC): """This class describes an abstract interface which all password hashes in Passlib adhere to. Under Python 2.6 and up, this is an actual Abstract Base Class built using the :mod:`!abc` module. diff --git a/passlib/pwd.py b/passlib/pwd.py index 27ed228..6c7f1a4 100644 --- a/passlib/pwd.py +++ b/passlib/pwd.py @@ -2,7 +2,6 @@ #============================================================================= # imports #============================================================================= -from __future__ import absolute_import, division, print_function, unicode_literals # core import codecs from collections import defaultdict @@ -18,7 +17,6 @@ import os # site # pkg from passlib import exc -from passlib.utils.compat import PY2, irange, itervalues, int_types from passlib.utils import rng, getrandstr, to_unicode from passlib.utils.decor import memoized_property # local @@ -87,7 +85,7 @@ def _self_info_rate(source): values = counts.values() size = sum(values) else: - values = itervalues(counts) + values = counts.values() if not size: return 0 # NOTE: the following performs ``- sum(value / size * logf(value / size, 2) for value in values)``, @@ -267,7 +265,7 @@ class SequenceGenerator(object): # hand off to parent if kwds and _superclasses(self, SequenceGenerator) == (object,): raise TypeError("Unexpected keyword(s): %s" % ", ".join(kwds.keys())) - super(SequenceGenerator, self).__init__(**kwds) + super().__init__(**kwds) #============================================================================= # informational helpers @@ -304,8 +302,8 @@ class SequenceGenerator(object): """ if returns is None: return next(self) - elif isinstance(returns, int_types): - return [next(self) for _ in irange(returns)] + elif isinstance(returns, int): + return [next(self) for _ in range(returns)] elif returns is iter: return self else: @@ -314,10 +312,6 @@ class SequenceGenerator(object): def __iter__(self): return self - if PY2: - def next(self): - return self.__next__() - #============================================================================= # eoc #============================================================================= @@ -396,7 +390,7 @@ class WordGenerator(SequenceGenerator): self.chars = chars # hand off to parent - super(WordGenerator, self).__init__(**kwds) + super().__init__(**kwds) # log.debug("WordGenerator(): entropy/char=%r", self.entropy_per_symbol) #============================================================================= @@ -488,7 +482,7 @@ def genword(entropy=None, length=None, returns=None, **kwds): * ``"hex"`` -- Lower case hexadecimal. Providers 4 bits of entropy per character. :returns: - :class:`!unicode` string containing randomly generated password; + :class:`!str` string containing randomly generated password; or list of 1+ passwords if :samp:`returns={int}` is specified. """ gen = WordGenerator(length=length, entropy=entropy, **kwds) @@ -549,7 +543,7 @@ class WordsetDict(MutableMapping): def __init__(self, *args, **kwds): self.paths = {} self._loaded = {} - super(WordsetDict, self).__init__(*args, **kwds) + super().__init__(*args, **kwds) def __getitem__(self, key): try: @@ -661,7 +655,7 @@ class PhraseGenerator(SequenceGenerator): self.sep = sep # hand off to parent - super(PhraseGenerator, self).__init__(**kwds) + super().__init__(**kwds) ##log.debug("PhraseGenerator(): entropy/word=%r entropy/char=%r min_chars=%r", ## self.entropy_per_symbol, self.entropy_per_char, self.min_chars) @@ -678,7 +672,7 @@ class PhraseGenerator(SequenceGenerator): #============================================================================= def __next__(self): - words = (self.rng.choice(self.words) for _ in irange(self.length)) + words = (self.rng.choice(self.words) for _ in range(self.length)) return self.sep.join(words) #============================================================================= @@ -780,7 +774,7 @@ def genphrase(entropy=None, length=None, returns=None, **kwds): Defaults to ``" "`` (a space), but can be an empty string, a hyphen, etc. :returns: - :class:`!unicode` string containing randomly generated passphrase; + :class:`!str` containing randomly generated passphrase; or list of 1+ passphrases if :samp:`returns={int}` is specified. """ gen = PhraseGenerator(entropy=entropy, length=length, **kwds) diff --git a/passlib/registry.py b/passlib/registry.py index 9964b25..3d2976b 100644 --- a/passlib/registry.py +++ b/passlib/registry.py @@ -14,7 +14,6 @@ from passlib.utils import ( is_crypt_handler, has_crypt as os_crypt_present, unix_crypt_schemes as os_crypt_schemes, ) -from passlib.utils.compat import unicode_or_str from passlib.utils.decor import memoize_single_value # local __all__ = [ @@ -162,7 +161,6 @@ _locations = dict( sha512_crypt = "passlib.handlers.sha2_crypt", sun_md5_crypt = "passlib.handlers.sun_md5_crypt", unix_disabled = "passlib.handlers.misc", - unix_fallback = "passlib.handlers.misc", ) # master regexp for detecting valid handler names @@ -324,7 +322,7 @@ def get_crypt_handler(name, default=_UNSET): pass # normalize name (and if changed, check dict again) - assert isinstance(name, unicode_or_str), "name must be string instance" + assert isinstance(name, str), "name must be string instance" alt = name.replace("-","_").lower() if alt != name: warn("handler names should be lower-case, and use underscores instead " @@ -435,10 +433,10 @@ def _resolve(hasher, param="value"): """ if is_crypt_handler(hasher): return hasher - elif isinstance(hasher, unicode_or_str): + elif isinstance(hasher, str): return get_crypt_handler(hasher) else: - raise exc.ExpectedTypeError(hasher, unicode_or_str, param) + raise exc.ExpectedTypeError(hasher, str, param) #: backend aliases diff --git a/passlib/tests/backports.py b/passlib/tests/backports.py deleted file mode 100644 index 5058cec..0000000 --- a/passlib/tests/backports.py +++ /dev/null @@ -1,67 +0,0 @@ -"""backports of needed unittest2 features""" -#============================================================================= -# imports -#============================================================================= -from __future__ import with_statement -# core -import logging; log = logging.getLogger(__name__) -import re -import sys -##from warnings import warn -# site -# pkg -from passlib.utils.compat import PY26 -# local -__all__ = [ - "TestCase", - "unittest", - # TODO: deprecate these exports in favor of "unittest.XXX" - "skip", "skipIf", "skipUnless", -] - -#============================================================================= -# import latest unittest module available -#============================================================================= -try: - import unittest2 as unittest -except ImportError: - if PY26: - raise ImportError("Passlib's tests require 'unittest2' under Python 2.6 (as of Passlib 1.7)") - # python 2.7 and python 3.2 both have unittest2 features (at least, the ones we use) - import unittest - -#============================================================================= -# unittest aliases -#============================================================================= -skip = unittest.skip -skipIf = unittest.skipIf -skipUnless = unittest.skipUnless -SkipTest = unittest.SkipTest - -#============================================================================= -# custom test harness -#============================================================================= -class TestCase(unittest.TestCase): - """backports a number of unittest2 features in TestCase""" - - #=================================================================== - # backport some unittest2 names - #=================================================================== - - #--------------------------------------------------------------- - # backport assertRegex() alias from 3.2 to 2.7 - # was present in 2.7 under an alternate name - #--------------------------------------------------------------- - if not hasattr(unittest.TestCase, "assertRegex"): - assertRegex = unittest.TestCase.assertRegexpMatches - - if not hasattr(unittest.TestCase, "assertRaisesRegex"): - assertRaisesRegex = unittest.TestCase.assertRaisesRegexp - - #=================================================================== - # eoc - #=================================================================== - -#============================================================================= -# eof -#============================================================================= diff --git a/passlib/tests/test_apache.py b/passlib/tests/test_apache.py index 198b425..fcdbbc6 100644 --- a/passlib/tests/test_apache.py +++ b/passlib/tests/test_apache.py @@ -2,19 +2,16 @@ #============================================================================= # imports #============================================================================= -from __future__ import with_statement # core from logging import getLogger +import unittest import os import subprocess # site # pkg from passlib import apache, registry from passlib.exc import MissingBackendError -from passlib.utils.compat import irange -from passlib.tests.backports import unittest from passlib.tests.utils import TestCase, get_file, set_file, ensure_mtime_changed -from passlib.utils.compat import u from passlib.utils import to_bytes from passlib.utils.handlers import to_unicode_for_identify # module @@ -122,8 +119,8 @@ class HtpasswdFileTest(TestCase): b'user6:$5$rounds=110000$cCRp/xUUGVgwR4aP$' b'p0.QKFS5qLNRqw1/47lXYiAcgIjJK.WjCO8nrEKuUK.\n') - def test_00_constructor_autoload(self): - """test constructor autoload""" + def test_00_constructor_new(self): + """constructor -- 'new' keyword""" # check with existing file path = self.mktemp() set_file(path, self.sample_01) @@ -143,13 +140,6 @@ class HtpasswdFileTest(TestCase): self.assertEqual(ht.path, path) self.assertFalse(ht.mtime) - # check autoload=False (deprecated alias for new=True) - with self.assertWarningList("``autoload=False`` is deprecated"): - ht = apache.HtpasswdFile(path, autoload=False) - self.assertEqual(ht.to_string(), b"") - self.assertEqual(ht.path, path) - self.assertFalse(ht.mtime) - # check missing file os.remove(path) self.assertRaises(IOError, apache.HtpasswdFile, path) @@ -196,21 +186,9 @@ class HtpasswdFileTest(TestCase): self.assertFalse(ht.set_password("user5", "pass5")) self.assertEqual(ht.to_string(), self.sample_03) - # test legacy default kwd - with self.assertWarningList("``default`` is deprecated"): - ht = apache.HtpasswdFile.from_string(self.sample_01, default="plaintext") - self.assertTrue(ht.set_password("user2", "pass2x")) - self.assertFalse(ht.set_password("user5", "pass5")) - self.assertEqual(ht.to_string(), self.sample_03) - # invalid user self.assertRaises(ValueError, ht.set_password, "user:", "pass") - # test that legacy update() still works - with self.assertWarningList("update\(\) is deprecated"): - ht.update("user2", "test") - self.assertTrue(ht.check_password("user2", "test")) - def test_02_set_password_autosave(self): path = self.mktemp() sample = b'user1:pass1\n' @@ -263,7 +241,7 @@ class HtpasswdFileTest(TestCase): # users 1..6 of sample_01 run through all the main hash formats, # to make sure they're recognized. - for i in irange(1, 7): + for i in range(1, 7): i = str(i) try: self.assertTrue(ht.check_password("user"+i, "pass"+i)) @@ -276,11 +254,6 @@ class HtpasswdFileTest(TestCase): self.assertRaises(ValueError, ht.check_password, "user:", "pass") - # test that legacy verify() still works - with self.assertWarningList(["verify\(\) is deprecated"]*2): - self.assertTrue(ht.verify("user1", "pass1")) - self.assertFalse(ht.verify("user1", "pass2")) - def test_05_load(self): """test load()""" # setup empty file @@ -348,17 +321,16 @@ class HtpasswdFileTest(TestCase): # check sample utf-8 ht = apache.HtpasswdFile.from_string(self.sample_04_utf8, encoding="utf-8", return_unicode=True) - self.assertEqual(ht.users(), [ u("user\u00e6") ]) + self.assertEqual(ht.users(), [ u"user\u00e6" ]) - # test deprecated encoding=None - with self.assertWarningList("``encoding=None`` is deprecated"): - ht = apache.HtpasswdFile.from_string(self.sample_04_utf8, encoding=None) - self.assertEqual(ht.users(), [ b'user\xc3\xa6' ]) + # encoding=None should throw error + self.assertRaises(TypeError, apache.HtpasswdFile.from_string, + self.sample_04_utf8, encoding=None) # check sample latin-1 ht = apache.HtpasswdFile.from_string(self.sample_04_latin1, encoding="latin-1", return_unicode=True) - self.assertEqual(ht.users(), [ u("user\u00e6") ]) + self.assertEqual(ht.users(), [ u"user\u00e6" ]) def test_08_get_hash(self): """test get_hash()""" @@ -367,9 +339,6 @@ class HtpasswdFileTest(TestCase): self.assertEqual(ht.get_hash("user4"), b"pass4") self.assertEqual(ht.get_hash("user5"), None) - with self.assertWarningList("find\(\) is deprecated"): - self.assertEqual(ht.find("user4"), b"pass4") - def test_09_to_string(self): """test to_string""" @@ -602,11 +571,6 @@ class HtdigestFileTest(TestCase): self.assertRaises(ValueError, ht.set_password, "user", "realm:", "pass") self.assertRaises(ValueError, ht.set_password, "user", "r"*256, "pass") - # test that legacy update() still works - with self.assertWarningList("update\(\) is deprecated"): - ht.update("user2", "realm2", "test") - self.assertTrue(ht.check_password("user2", "test")) - # TODO: test set_password autosave def test_03_users(self): @@ -625,7 +589,7 @@ class HtdigestFileTest(TestCase): self.assertRaises(TypeError, ht.check_password, 1, 'realm', 'pass5') self.assertRaises(TypeError, ht.check_password, 'user', 1, 'pass5') self.assertIs(ht.check_password("user5", "realm","pass5"), None) - for i in irange(1,5): + for i in range(1, 5): i = str(i) self.assertTrue(ht.check_password("user"+i, "realm", "pass"+i)) self.assertIs(ht.check_password("user"+i, "realm", "pass5"), False) @@ -636,11 +600,6 @@ class HtdigestFileTest(TestCase): self.assertTrue(ht.check_password("user1", "pass1")) self.assertIs(ht.check_password("user5", "pass5"), None) - # test that legacy verify() still works - with self.assertWarningList(["verify\(\) is deprecated"]*2): - self.assertTrue(ht.verify("user1", "realm", "pass1")) - self.assertFalse(ht.verify("user1", "realm", "pass2")) - # invalid user self.assertRaises(ValueError, ht.check_password, "user:", "realm", "pass") @@ -678,13 +637,6 @@ class HtdigestFileTest(TestCase): hc.load(path) self.assertEqual(hc.to_string(), self.sample_01) - # change file, test deprecated force=False kwd - ensure_mtime_changed(path) - set_file(path, "") - with self.assertWarningList(r"load\(force=False\) is deprecated"): - ha.load(force=False) - self.assertEqual(ha.to_string(), b"") - def test_06_save(self): """test save()""" # load from file @@ -725,9 +677,6 @@ class HtdigestFileTest(TestCase): self.assertEqual(ht.get_hash("user4", "realm"), "ab7b5d5f28ccc7666315f508c7358519") self.assertEqual(ht.get_hash("user5", "realm"), None) - with self.assertWarningList("find\(\) is deprecated"): - self.assertEqual(ht.find("user4", "realm"), "ab7b5d5f28ccc7666315f508c7358519") - def test_09_encodings(self): """test encoding parameter""" # test bad encodings cause failure in constructor @@ -735,13 +684,13 @@ class HtdigestFileTest(TestCase): # check sample utf-8 ht = apache.HtdigestFile.from_string(self.sample_04_utf8, encoding="utf-8", return_unicode=True) - self.assertEqual(ht.realms(), [ u("realm\u00e6") ]) - self.assertEqual(ht.users(u("realm\u00e6")), [ u("user\u00e6") ]) + self.assertEqual(ht.realms(), [ u"realm\u00e6" ]) + self.assertEqual(ht.users(u"realm\u00e6"), [ u"user\u00e6" ]) # check sample latin-1 ht = apache.HtdigestFile.from_string(self.sample_04_latin1, encoding="latin-1", return_unicode=True) - self.assertEqual(ht.realms(), [ u("realm\u00e6") ]) - self.assertEqual(ht.users(u("realm\u00e6")), [ u("user\u00e6") ]) + self.assertEqual(ht.realms(), [ u"realm\u00e6" ]) + self.assertEqual(ht.users(u"realm\u00e6"), [ u"user\u00e6" ]) def test_10_to_string(self): """test to_string()""" diff --git a/passlib/tests/test_apps.py b/passlib/tests/test_apps.py index 167437f..3a2b0d3 100644 --- a/passlib/tests/test_apps.py +++ b/passlib/tests/test_apps.py @@ -2,7 +2,6 @@ #============================================================================= # imports #============================================================================= -from __future__ import with_statement # core import logging; log = logging.getLogger(__name__) # site diff --git a/passlib/tests/test_context.py b/passlib/tests/test_context.py index 09b52c0..d7f90c6 100644 --- a/passlib/tests/test_context.py +++ b/passlib/tests/test_context.py @@ -3,12 +3,7 @@ # imports #============================================================================= # core -from __future__ import with_statement -from passlib.utils.compat import PY3 -if PY3: - from configparser import NoSectionError -else: - from ConfigParser import NoSectionError +from configparser import NoSectionError import datetime from functools import partial import logging; log = logging.getLogger(__name__) @@ -20,7 +15,6 @@ from passlib import hash from passlib.context import CryptContext, LazyCryptContext from passlib.exc import PasslibConfigWarning, PasslibHashWarning from passlib.utils import tick, to_unicode -from passlib.utils.compat import irange, u, unicode, str_to_uascii, PY2, PY26 import passlib.utils.handlers as uh from passlib.tests.utils import (TestCase, set_file, TICK_RESOLUTION, quicksleep, time_call, handler_derived_from) @@ -75,7 +69,7 @@ class CryptContextTest(TestCase): sample_1_resolved_dict = merge_dicts(sample_1_dict, schemes = sample_1_handlers) - sample_1_unnormalized = u("""\ + sample_1_unnormalized = u"""\ [passlib] schemes = des_crypt, md5_crypt, bsdi_crypt, sha512_crypt default = md5_crypt @@ -85,9 +79,9 @@ bsdi_crypt__default_rounds = 25001 bsdi_crypt__max_rounds = 30001 sha512_crypt__max_rounds = 50000 sha512_crypt__min_rounds = 40000 -""") +""" - sample_1_unicode = u("""\ + sample_1_unicode = u"""\ [passlib] schemes = des_crypt, md5_crypt, bsdi_crypt, sha512_crypt default = md5_crypt @@ -97,7 +91,7 @@ bsdi_crypt__max_rounds = 30001 sha512_crypt__max_rounds = 50000 sha512_crypt__min_rounds = 40000 -""") +""" #--------------------------------------------------------------- # sample 1 external files @@ -107,12 +101,12 @@ sha512_crypt__min_rounds = 40000 sample_1_path = os.path.join(here, "sample1.cfg") # sample 1 with '\r\n' linesep - sample_1b_unicode = sample_1_unicode.replace(u("\n"), u("\r\n")) + sample_1b_unicode = sample_1_unicode.replace(u"\n", u"\r\n") sample_1b_path = os.path.join(here, "sample1b.cfg") # sample 1 using UTF-16 and alt section - sample_1c_bytes = sample_1_unicode.replace(u("[passlib]"), - u("[mypolicy]")).encode("utf-16") + sample_1c_bytes = sample_1_unicode.replace(u"[passlib]", + u"[mypolicy]").encode("utf-16") sample_1c_path = os.path.join(here, "sample1c.cfg") # enable to regenerate sample files @@ -176,7 +170,7 @@ sha512_crypt__min_rounds = 45000 # setup #=================================================================== def setUp(self): - super(CryptContextTest, self).setUp() + super().setUp() warnings.filterwarnings("ignore", "The 'all' scheme is deprecated.*") warnings.filterwarnings("ignore", ".*'scheme' keyword is deprecated as of Passlib 1.7.*") @@ -207,7 +201,7 @@ sha512_crypt__min_rounds = 45000 self.assertEqual(ctx.to_dict(), self.sample_3_dict) # test unicode scheme names (issue 54) - ctx = CryptContext(schemes=[u("sha256_crypt")]) + ctx = CryptContext(schemes=[u"sha256_crypt"]) self.assertEqual(ctx.schemes(), ("sha256_crypt",)) def test_02_from_string(self): @@ -748,11 +742,6 @@ sha512_crypt__min_rounds = 45000 self.assertEqual(ctx.handler(category="admin", unconfigured=True), hash.md5_crypt) self.assertHandlerDerivedFrom(ctx.handler(category="staff"), hash.sha256_crypt) - # test unicode category strings are accepted under py2 - if PY2: - self.assertEqual(ctx.handler(category=u("staff"), unconfigured=True), hash.sha256_crypt) - self.assertEqual(ctx.handler(category=u("admin"), unconfigured=True), hash.md5_crypt) - def test_33_options(self): """test internal _get_record_options() method""" @@ -847,11 +836,7 @@ sha512_crypt__min_rounds = 45000 dump = ctx.to_string() # check ctx->string returns canonical format. - # NOTE: ConfigParser for PY26 doesn't use OrderedDict, - # making to_string()'s ordering unpredictable... - # so we skip this test under PY26. - if not PY26: - self.assertEqual(dump, self.sample_1_unicode) + self.assertEqual(dump, self.sample_1_unicode) # check ctx->string->ctx->dict returns original ctx2 = CryptContext.from_string(dump) @@ -933,14 +918,6 @@ sha512_crypt__min_rounds = 45000 # border cases #-------------------------------------------------------------- - # test unicode category strings are accepted under py2 - # this tests basic _get_record() used by hash/genhash/verify. - # we have to omit scheme=xxx so codepath is tested fully - if PY2: - c2 = cc.copy(default="phpass") - self.assertTrue(c2.genconfig(category=u("admin")).startswith("$P$5")) - self.assertTrue(c2.genconfig(category=u("staff")).startswith("$H$5")) - # throws error without schemes self.assertRaises(KeyError, CryptContext().genconfig) self.assertRaises(KeyError, CryptContext().genconfig, scheme='md5_crypt') @@ -1175,9 +1152,9 @@ sha512_crypt__min_rounds = 45000 def _calc_checksum(self, secret): from hashlib import md5 - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") - return str_to_uascii(md5(secret).hexdigest()) + return md5(secret).hexdigest() # calling needs_update should query callback ctx = CryptContext([dummy]) @@ -1568,7 +1545,7 @@ sha512_crypt__min_rounds = 45000 handler = context.handler(scheme) salt = handler.default_salt_chars[0:1] * handler.max_salt_size seen = set() - for i in irange(300): + for i in range(300): h = context.genconfig(scheme, salt=salt) r = handler.from_string(h).rounds seen.add(r) @@ -1576,20 +1553,8 @@ sha512_crypt__min_rounds = 45000 self.assertEqual(max(seen), upper, "vary_rounds had wrong upper limit:") #=================================================================== - # harden_verify / min_verify_time + # dummy_verify() #=================================================================== - def test_harden_verify_parsing(self): - """harden_verify -- parsing""" - warnings.filterwarnings("ignore", ".*harden_verify.*", - category=DeprecationWarning) - - # valid values - ctx = CryptContext(schemes=["sha256_crypt"]) - self.assertEqual(ctx.harden_verify, None) - self.assertEqual(ctx.using(harden_verify="").harden_verify, None) - self.assertEqual(ctx.using(harden_verify="true").harden_verify, None) - self.assertEqual(ctx.using(harden_verify="false").harden_verify, None) - def test_dummy_verify(self): """ dummy_verify() method @@ -1728,13 +1693,13 @@ class DelayHash(uh.StaticHandler): checksum_chars = uh.LOWER_HEX_CHARS checksum_size = 40 delay = 0 - _hash_prefix = u("$x$") + _hash_prefix = u"$x$" def _calc_checksum(self, secret): time.sleep(self.delay) - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") - return str_to_uascii(hashlib.sha1(b"prefix" + secret).hexdigest()) + return hashlib.sha1(b"prefix" + secret).hexdigest() #============================================================================= # LazyCryptContext diff --git a/passlib/tests/test_context_deprecated.py b/passlib/tests/test_context_deprecated.py deleted file mode 100644 index 0f76624..0000000 --- a/passlib/tests/test_context_deprecated.py +++ /dev/null @@ -1,743 +0,0 @@ -"""tests for passlib.context - -this file is a clone of the 1.5 test_context.py, -containing the tests using the legacy CryptPolicy api. -it's being preserved here to ensure the old api doesn't break -(until Passlib 1.8, when this and the legacy api will be removed). -""" -#============================================================================= -# imports -#============================================================================= -from __future__ import with_statement -# core -from logging import getLogger -import os -import warnings -# site -try: - from pkg_resources import resource_filename -except ImportError: - resource_filename = None -# pkg -from passlib import hash -from passlib.context import CryptContext, CryptPolicy, LazyCryptContext -from passlib.utils import to_bytes, to_unicode -import passlib.utils.handlers as uh -from passlib.tests.utils import TestCase, set_file -from passlib.registry import (register_crypt_handler_path, - _has_crypt_handler as has_crypt_handler, - _unload_handler_name as unload_handler_name, - ) -# module -log = getLogger(__name__) - -#============================================================================= -# -#============================================================================= -class CryptPolicyTest(TestCase): - """test CryptPolicy object""" - - # TODO: need to test user categories w/in all this - - descriptionPrefix = "CryptPolicy" - - #=================================================================== - # sample crypt policies used for testing - #=================================================================== - - #--------------------------------------------------------------- - # sample 1 - average config file - #--------------------------------------------------------------- - # NOTE: copy of this is stored in file passlib/tests/sample_config_1s.cfg - sample_config_1s = """\ -[passlib] -schemes = des_crypt, md5_crypt, bsdi_crypt, sha512_crypt -default = md5_crypt -all.vary_rounds = 10%% -bsdi_crypt.max_rounds = 30000 -bsdi_crypt.default_rounds = 25000 -sha512_crypt.max_rounds = 50000 -sha512_crypt.min_rounds = 40000 -""" - sample_config_1s_path = os.path.abspath(os.path.join( - os.path.dirname(__file__), "sample_config_1s.cfg")) - if not os.path.exists(sample_config_1s_path) and resource_filename: - # in case we're zipped up in an egg. - sample_config_1s_path = resource_filename("passlib.tests", - "sample_config_1s.cfg") - - # make sure sample_config_1s uses \n linesep - tests rely on this - assert sample_config_1s.startswith("[passlib]\nschemes") - - sample_config_1pd = dict( - schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"], - default = "md5_crypt", - # NOTE: not maintaining backwards compat for rendering to "10%" - all__vary_rounds = 0.1, - bsdi_crypt__max_rounds = 30000, - bsdi_crypt__default_rounds = 25000, - sha512_crypt__max_rounds = 50000, - sha512_crypt__min_rounds = 40000, - ) - - sample_config_1pid = { - "schemes": "des_crypt, md5_crypt, bsdi_crypt, sha512_crypt", - "default": "md5_crypt", - # NOTE: not maintaining backwards compat for rendering to "10%" - "all.vary_rounds": 0.1, - "bsdi_crypt.max_rounds": 30000, - "bsdi_crypt.default_rounds": 25000, - "sha512_crypt.max_rounds": 50000, - "sha512_crypt.min_rounds": 40000, - } - - sample_config_1prd = dict( - schemes = [ hash.des_crypt, hash.md5_crypt, hash.bsdi_crypt, hash.sha512_crypt], - default = "md5_crypt", # NOTE: passlib <= 1.5 was handler obj. - # NOTE: not maintaining backwards compat for rendering to "10%" - all__vary_rounds = 0.1, - bsdi_crypt__max_rounds = 30000, - bsdi_crypt__default_rounds = 25000, - sha512_crypt__max_rounds = 50000, - sha512_crypt__min_rounds = 40000, - ) - - #--------------------------------------------------------------- - # sample 2 - partial policy & result of overlay on sample 1 - #--------------------------------------------------------------- - sample_config_2s = """\ -[passlib] -bsdi_crypt.min_rounds = 29000 -bsdi_crypt.max_rounds = 35000 -bsdi_crypt.default_rounds = 31000 -sha512_crypt.min_rounds = 45000 -""" - - sample_config_2pd = dict( - # using this to test full replacement of existing options - bsdi_crypt__min_rounds = 29000, - bsdi_crypt__max_rounds = 35000, - bsdi_crypt__default_rounds = 31000, - # using this to test partial replacement of existing options - sha512_crypt__min_rounds=45000, - ) - - sample_config_12pd = dict( - schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"], - default = "md5_crypt", - # NOTE: not maintaining backwards compat for rendering to "10%" - all__vary_rounds = 0.1, - bsdi_crypt__min_rounds = 29000, - bsdi_crypt__max_rounds = 35000, - bsdi_crypt__default_rounds = 31000, - sha512_crypt__max_rounds = 50000, - sha512_crypt__min_rounds=45000, - ) - - #--------------------------------------------------------------- - # sample 3 - just changing default - #--------------------------------------------------------------- - sample_config_3pd = dict( - default="sha512_crypt", - ) - - sample_config_123pd = dict( - schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"], - default = "sha512_crypt", - # NOTE: not maintaining backwards compat for rendering to "10%" - all__vary_rounds = 0.1, - bsdi_crypt__min_rounds = 29000, - bsdi_crypt__max_rounds = 35000, - bsdi_crypt__default_rounds = 31000, - sha512_crypt__max_rounds = 50000, - sha512_crypt__min_rounds=45000, - ) - - #--------------------------------------------------------------- - # sample 4 - category specific - #--------------------------------------------------------------- - sample_config_4s = """ -[passlib] -schemes = sha512_crypt -all.vary_rounds = 10%% -default.sha512_crypt.max_rounds = 20000 -admin.all.vary_rounds = 5%% -admin.sha512_crypt.max_rounds = 40000 -""" - - sample_config_4pd = dict( - schemes = [ "sha512_crypt" ], - # NOTE: not maintaining backwards compat for rendering to "10%" - all__vary_rounds = 0.1, - sha512_crypt__max_rounds = 20000, - # NOTE: not maintaining backwards compat for rendering to "5%" - admin__all__vary_rounds = 0.05, - admin__sha512_crypt__max_rounds = 40000, - ) - - #--------------------------------------------------------------- - # sample 5 - to_string & deprecation testing - #--------------------------------------------------------------- - sample_config_5s = sample_config_1s + """\ -deprecated = des_crypt -admin__context__deprecated = des_crypt, bsdi_crypt -""" - - sample_config_5pd = sample_config_1pd.copy() - sample_config_5pd.update( - deprecated = [ "des_crypt" ], - admin__context__deprecated = [ "des_crypt", "bsdi_crypt" ], - ) - - sample_config_5pid = sample_config_1pid.copy() - sample_config_5pid.update({ - "deprecated": "des_crypt", - "admin.context.deprecated": "des_crypt, bsdi_crypt", - }) - - sample_config_5prd = sample_config_1prd.copy() - sample_config_5prd.update({ - # XXX: should deprecated return the actual handlers in this case? - # would have to modify how policy stores info, for one. - "deprecated": ["des_crypt"], - "admin__context__deprecated": ["des_crypt", "bsdi_crypt"], - }) - - #=================================================================== - # constructors - #=================================================================== - def setUp(self): - TestCase.setUp(self) - warnings.filterwarnings("ignore", - r"The CryptPolicy class has been deprecated") - warnings.filterwarnings("ignore", - r"the method.*hash_needs_update.*is deprecated") - warnings.filterwarnings("ignore", "The 'all' scheme is deprecated.*") - warnings.filterwarnings("ignore", "bsdi_crypt rounds should be odd") - - def test_00_constructor(self): - """test CryptPolicy() constructor""" - policy = CryptPolicy(**self.sample_config_1pd) - self.assertEqual(policy.to_dict(), self.sample_config_1pd) - - policy = CryptPolicy(self.sample_config_1pd) - self.assertEqual(policy.to_dict(), self.sample_config_1pd) - - self.assertRaises(TypeError, CryptPolicy, {}, {}) - self.assertRaises(TypeError, CryptPolicy, {}, dummy=1) - - # check key with too many separators is rejected - self.assertRaises(TypeError, CryptPolicy, - schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"], - bad__key__bsdi_crypt__max_rounds = 30000, - ) - - # check nameless handler rejected - class nameless(uh.StaticHandler): - name = None - self.assertRaises(ValueError, CryptPolicy, schemes=[nameless]) - - # check scheme must be name or crypt handler - self.assertRaises(TypeError, CryptPolicy, schemes=[uh.StaticHandler]) - - # check name conflicts are rejected - class dummy_1(uh.StaticHandler): - name = 'dummy_1' - self.assertRaises(KeyError, CryptPolicy, schemes=[dummy_1, dummy_1]) - - # with unknown deprecated value - self.assertRaises(KeyError, CryptPolicy, - schemes=['des_crypt'], - deprecated=['md5_crypt']) - - # with unknown default value - self.assertRaises(KeyError, CryptPolicy, - schemes=['des_crypt'], - default='md5_crypt') - - def test_01_from_path_simple(self): - """test CryptPolicy.from_path() constructor""" - # NOTE: this is separate so it can also run under GAE - - # test preset stored in existing file - path = self.sample_config_1s_path - policy = CryptPolicy.from_path(path) - self.assertEqual(policy.to_dict(), self.sample_config_1pd) - - # test if path missing - self.assertRaises(EnvironmentError, CryptPolicy.from_path, path + 'xxx') - - def test_01_from_path(self): - """test CryptPolicy.from_path() constructor with encodings""" - path = self.mktemp() - - # test "\n" linesep - set_file(path, self.sample_config_1s) - policy = CryptPolicy.from_path(path) - self.assertEqual(policy.to_dict(), self.sample_config_1pd) - - # test "\r\n" linesep - set_file(path, self.sample_config_1s.replace("\n","\r\n")) - policy = CryptPolicy.from_path(path) - self.assertEqual(policy.to_dict(), self.sample_config_1pd) - - # test with custom encoding - uc2 = to_bytes(self.sample_config_1s, "utf-16", source_encoding="utf-8") - set_file(path, uc2) - policy = CryptPolicy.from_path(path, encoding="utf-16") - self.assertEqual(policy.to_dict(), self.sample_config_1pd) - - def test_02_from_string(self): - """test CryptPolicy.from_string() constructor""" - # test "\n" linesep - policy = CryptPolicy.from_string(self.sample_config_1s) - self.assertEqual(policy.to_dict(), self.sample_config_1pd) - - # test "\r\n" linesep - policy = CryptPolicy.from_string( - self.sample_config_1s.replace("\n","\r\n")) - self.assertEqual(policy.to_dict(), self.sample_config_1pd) - - # test with unicode - data = to_unicode(self.sample_config_1s) - policy = CryptPolicy.from_string(data) - self.assertEqual(policy.to_dict(), self.sample_config_1pd) - - # test with non-ascii-compatible encoding - uc2 = to_bytes(self.sample_config_1s, "utf-16", source_encoding="utf-8") - policy = CryptPolicy.from_string(uc2, encoding="utf-16") - self.assertEqual(policy.to_dict(), self.sample_config_1pd) - - # test category specific options - policy = CryptPolicy.from_string(self.sample_config_4s) - self.assertEqual(policy.to_dict(), self.sample_config_4pd) - - def test_03_from_source(self): - """test CryptPolicy.from_source() constructor""" - # pass it a path - policy = CryptPolicy.from_source(self.sample_config_1s_path) - self.assertEqual(policy.to_dict(), self.sample_config_1pd) - - # pass it a string - policy = CryptPolicy.from_source(self.sample_config_1s) - self.assertEqual(policy.to_dict(), self.sample_config_1pd) - - # pass it a dict (NOTE: make a copy to detect in-place modifications) - policy = CryptPolicy.from_source(self.sample_config_1pd.copy()) - self.assertEqual(policy.to_dict(), self.sample_config_1pd) - - # pass it existing policy - p2 = CryptPolicy.from_source(policy) - self.assertIs(policy, p2) - - # pass it something wrong - self.assertRaises(TypeError, CryptPolicy.from_source, 1) - self.assertRaises(TypeError, CryptPolicy.from_source, []) - - def test_04_from_sources(self): - """test CryptPolicy.from_sources() constructor""" - - # pass it empty list - self.assertRaises(ValueError, CryptPolicy.from_sources, []) - - # pass it one-element list - policy = CryptPolicy.from_sources([self.sample_config_1s]) - self.assertEqual(policy.to_dict(), self.sample_config_1pd) - - # pass multiple sources - policy = CryptPolicy.from_sources( - [ - self.sample_config_1s_path, - self.sample_config_2s, - self.sample_config_3pd, - ]) - self.assertEqual(policy.to_dict(), self.sample_config_123pd) - - def test_05_replace(self): - """test CryptPolicy.replace() constructor""" - - p1 = CryptPolicy(**self.sample_config_1pd) - - # check overlaying sample 2 - p2 = p1.replace(**self.sample_config_2pd) - self.assertEqual(p2.to_dict(), self.sample_config_12pd) - - # check repeating overlay makes no change - p2b = p2.replace(**self.sample_config_2pd) - self.assertEqual(p2b.to_dict(), self.sample_config_12pd) - - # check overlaying sample 3 - p3 = p2.replace(self.sample_config_3pd) - self.assertEqual(p3.to_dict(), self.sample_config_123pd) - - def test_06_forbidden(self): - """test CryptPolicy() forbidden kwds""" - - # salt not allowed to be set - self.assertRaises(KeyError, CryptPolicy, - schemes=["des_crypt"], - des_crypt__salt="xx", - ) - self.assertRaises(KeyError, CryptPolicy, - schemes=["des_crypt"], - all__salt="xx", - ) - - # schemes not allowed for category - self.assertRaises(KeyError, CryptPolicy, - schemes=["des_crypt"], - user__context__schemes=["md5_crypt"], - ) - - #=================================================================== - # reading - #=================================================================== - def test_10_has_schemes(self): - """test has_schemes() method""" - - p1 = CryptPolicy(**self.sample_config_1pd) - self.assertTrue(p1.has_schemes()) - - p3 = CryptPolicy(**self.sample_config_3pd) - self.assertTrue(not p3.has_schemes()) - - def test_11_iter_handlers(self): - """test iter_handlers() method""" - - p1 = CryptPolicy(**self.sample_config_1pd) - s = self.sample_config_1prd['schemes'] - self.assertEqual(list(p1.iter_handlers()), s) - - p3 = CryptPolicy(**self.sample_config_3pd) - self.assertEqual(list(p3.iter_handlers()), []) - - def test_12_get_handler(self): - """test get_handler() method""" - - p1 = CryptPolicy(**self.sample_config_1pd) - - # check by name - self.assertIs(p1.get_handler("bsdi_crypt"), hash.bsdi_crypt) - - # check by missing name - self.assertIs(p1.get_handler("sha256_crypt"), None) - self.assertRaises(KeyError, p1.get_handler, "sha256_crypt", required=True) - - # check default - self.assertIs(p1.get_handler(), hash.md5_crypt) - - def test_13_get_options(self): - """test get_options() method""" - - p12 = CryptPolicy(**self.sample_config_12pd) - - self.assertEqual(p12.get_options("bsdi_crypt"),dict( - # NOTE: not maintaining backwards compat for rendering to "10%" - vary_rounds = 0.1, - min_rounds = 29000, - max_rounds = 35000, - default_rounds = 31000, - )) - - self.assertEqual(p12.get_options("sha512_crypt"),dict( - # NOTE: not maintaining backwards compat for rendering to "10%" - vary_rounds = 0.1, - min_rounds = 45000, - max_rounds = 50000, - )) - - p4 = CryptPolicy.from_string(self.sample_config_4s) - self.assertEqual(p4.get_options("sha512_crypt"), dict( - # NOTE: not maintaining backwards compat for rendering to "10%" - vary_rounds=0.1, - max_rounds=20000, - )) - - self.assertEqual(p4.get_options("sha512_crypt", "user"), dict( - # NOTE: not maintaining backwards compat for rendering to "10%" - vary_rounds=0.1, - max_rounds=20000, - )) - - self.assertEqual(p4.get_options("sha512_crypt", "admin"), dict( - # NOTE: not maintaining backwards compat for rendering to "5%" - vary_rounds=0.05, - max_rounds=40000, - )) - - def test_14_handler_is_deprecated(self): - """test handler_is_deprecated() method""" - pa = CryptPolicy(**self.sample_config_1pd) - pb = CryptPolicy(**self.sample_config_5pd) - - self.assertFalse(pa.handler_is_deprecated("des_crypt")) - self.assertFalse(pa.handler_is_deprecated(hash.bsdi_crypt)) - self.assertFalse(pa.handler_is_deprecated("sha512_crypt")) - - self.assertTrue(pb.handler_is_deprecated("des_crypt")) - self.assertFalse(pb.handler_is_deprecated(hash.bsdi_crypt)) - self.assertFalse(pb.handler_is_deprecated("sha512_crypt")) - - # check categories as well - self.assertTrue(pb.handler_is_deprecated("des_crypt", "user")) - self.assertFalse(pb.handler_is_deprecated("bsdi_crypt", "user")) - self.assertTrue(pb.handler_is_deprecated("des_crypt", "admin")) - self.assertTrue(pb.handler_is_deprecated("bsdi_crypt", "admin")) - - # check deprecation is overridden per category - pc = CryptPolicy( - schemes=["md5_crypt", "des_crypt"], - deprecated=["md5_crypt"], - user__context__deprecated=["des_crypt"], - ) - self.assertTrue(pc.handler_is_deprecated("md5_crypt")) - self.assertFalse(pc.handler_is_deprecated("des_crypt")) - self.assertFalse(pc.handler_is_deprecated("md5_crypt", "user")) - self.assertTrue(pc.handler_is_deprecated("des_crypt", "user")) - - def test_15_min_verify_time(self): - """test get_min_verify_time() method""" - # silence deprecation warnings for min verify time - warnings.filterwarnings("ignore", category=DeprecationWarning) - - pa = CryptPolicy() - self.assertEqual(pa.get_min_verify_time(), 0) - self.assertEqual(pa.get_min_verify_time('admin'), 0) - - pb = pa.replace(min_verify_time=.1) - self.assertEqual(pb.get_min_verify_time(), 0) - self.assertEqual(pb.get_min_verify_time('admin'), 0) - - #=================================================================== - # serialization - #=================================================================== - def test_20_iter_config(self): - """test iter_config() method""" - p5 = CryptPolicy(**self.sample_config_5pd) - self.assertEqual(dict(p5.iter_config()), self.sample_config_5pd) - self.assertEqual(dict(p5.iter_config(resolve=True)), self.sample_config_5prd) - self.assertEqual(dict(p5.iter_config(ini=True)), self.sample_config_5pid) - - def test_21_to_dict(self): - """test to_dict() method""" - p5 = CryptPolicy(**self.sample_config_5pd) - self.assertEqual(p5.to_dict(), self.sample_config_5pd) - self.assertEqual(p5.to_dict(resolve=True), self.sample_config_5prd) - - def test_22_to_string(self): - """test to_string() method""" - pa = CryptPolicy(**self.sample_config_5pd) - s = pa.to_string() # NOTE: can't compare string directly, ordering etc may not match - pb = CryptPolicy.from_string(s) - self.assertEqual(pb.to_dict(), self.sample_config_5pd) - - s = pa.to_string(encoding="latin-1") - self.assertIsInstance(s, bytes) - - #=================================================================== - # - #=================================================================== - -#============================================================================= -# CryptContext -#============================================================================= -class CryptContextTest(TestCase): - """test CryptContext class""" - descriptionPrefix = "CryptContext" - - def setUp(self): - TestCase.setUp(self) - warnings.filterwarnings("ignore", - r"CryptContext\(\)\.replace\(\) has been deprecated.*") - warnings.filterwarnings("ignore", - r"The CryptContext ``policy`` keyword has been deprecated.*") - warnings.filterwarnings("ignore", ".*(CryptPolicy|context\.policy).*(has|have) been deprecated.*") - warnings.filterwarnings("ignore", - r"the method.*hash_needs_update.*is deprecated") - - #=================================================================== - # constructor - #=================================================================== - def test_00_constructor(self): - """test constructor""" - # create crypt context using handlers - cc = CryptContext([hash.md5_crypt, hash.bsdi_crypt, hash.des_crypt]) - c,b,a = cc.policy.iter_handlers() - self.assertIs(a, hash.des_crypt) - self.assertIs(b, hash.bsdi_crypt) - self.assertIs(c, hash.md5_crypt) - - # create context using names - cc = CryptContext(["md5_crypt", "bsdi_crypt", "des_crypt"]) - c,b,a = cc.policy.iter_handlers() - self.assertIs(a, hash.des_crypt) - self.assertIs(b, hash.bsdi_crypt) - self.assertIs(c, hash.md5_crypt) - - # policy kwd - policy = cc.policy - cc = CryptContext(policy=policy) - self.assertEqual(cc.to_dict(), policy.to_dict()) - - cc = CryptContext(policy=policy, default="bsdi_crypt") - self.assertNotEqual(cc.to_dict(), policy.to_dict()) - self.assertEqual(cc.to_dict(), dict(schemes=["md5_crypt","bsdi_crypt","des_crypt"], - default="bsdi_crypt")) - - self.assertRaises(TypeError, setattr, cc, 'policy', None) - self.assertRaises(TypeError, CryptContext, policy='x') - - def test_01_replace(self): - """test replace()""" - - cc = CryptContext(["md5_crypt", "bsdi_crypt", "des_crypt"]) - self.assertIs(cc.policy.get_handler(), hash.md5_crypt) - - cc2 = cc.replace() - self.assertIsNot(cc2, cc) - # NOTE: was not able to maintain backward compatibility with this... - ##self.assertIs(cc2.policy, cc.policy) - - cc3 = cc.replace(default="bsdi_crypt") - self.assertIsNot(cc3, cc) - # NOTE: was not able to maintain backward compatibility with this... - ##self.assertIs(cc3.policy, cc.policy) - self.assertIs(cc3.policy.get_handler(), hash.bsdi_crypt) - - def test_02_no_handlers(self): - """test no handlers""" - - # check constructor... - cc = CryptContext() - self.assertRaises(KeyError, cc.identify, 'hash', required=True) - self.assertRaises(KeyError, cc.hash, 'secret') - self.assertRaises(KeyError, cc.verify, 'secret', 'hash') - - # check updating policy after the fact... - cc = CryptContext(['md5_crypt']) - p = CryptPolicy(schemes=[]) - cc.policy = p - - self.assertRaises(KeyError, cc.identify, 'hash', required=True) - self.assertRaises(KeyError, cc.hash, 'secret') - self.assertRaises(KeyError, cc.verify, 'secret', 'hash') - - #=================================================================== - # policy adaptation - #=================================================================== - sample_policy_1 = dict( - schemes = [ "des_crypt", "md5_crypt", "phpass", "bsdi_crypt", - "sha256_crypt"], - deprecated = [ "des_crypt", ], - default = "sha256_crypt", - bsdi_crypt__max_rounds = 30, - bsdi_crypt__default_rounds = 25, - bsdi_crypt__vary_rounds = 0, - sha256_crypt__max_rounds = 3000, - sha256_crypt__min_rounds = 2000, - sha256_crypt__default_rounds = 3000, - phpass__ident = "H", - phpass__default_rounds = 7, - ) - - def test_12_hash_needs_update(self): - """test hash_needs_update() method""" - cc = CryptContext(**self.sample_policy_1) - - # check deprecated scheme - self.assertTrue(cc.hash_needs_update('9XXD4trGYeGJA')) - self.assertFalse(cc.hash_needs_update('$1$J8HC2RCr$HcmM.7NxB2weSvlw2FgzU0')) - - # check min rounds - self.assertTrue(cc.hash_needs_update('$5$rounds=1999$jD81UCoo.zI.UETs$Y7qSTQ6mTiU9qZB4fRr43wRgQq4V.5AAf7F97Pzxey/')) - self.assertFalse(cc.hash_needs_update('$5$rounds=2000$228SSRje04cnNCaQ$YGV4RYu.5sNiBvorQDlO0WWQjyJVGKBcJXz3OtyQ2u8')) - - # check max rounds - self.assertFalse(cc.hash_needs_update('$5$rounds=3000$fS9iazEwTKi7QPW4$VasgBC8FqlOvD7x2HhABaMXCTh9jwHclPA9j5YQdns.')) - self.assertTrue(cc.hash_needs_update('$5$rounds=3001$QlFHHifXvpFX4PLs$/0ekt7lSs/lOikSerQ0M/1porEHxYq7W/2hdFpxA3fA')) - - #=================================================================== - # border cases - #=================================================================== - def test_30_nonstring_hash(self): - """test non-string hash values cause error""" - warnings.filterwarnings("ignore", ".*needs_update.*'scheme' keyword is deprecated.*") - - # - # test hash=None or some other non-string causes TypeError - # and that explicit-scheme code path behaves the same. - # - cc = CryptContext(["des_crypt"]) - for hash, kwds in [ - (None, {}), - # NOTE: 'scheme' kwd is deprecated... - (None, {"scheme": "des_crypt"}), - (1, {}), - ((), {}), - ]: - - self.assertRaises(TypeError, cc.hash_needs_update, hash, **kwds) - - cc2 = CryptContext(["mysql323"]) - self.assertRaises(TypeError, cc2.hash_needs_update, None) - - #=================================================================== - # eoc - #=================================================================== - -#============================================================================= -# LazyCryptContext -#============================================================================= -class dummy_2(uh.StaticHandler): - name = "dummy_2" - -class LazyCryptContextTest(TestCase): - descriptionPrefix = "LazyCryptContext" - - def setUp(self): - TestCase.setUp(self) - - # make sure this isn't registered before OR after - unload_handler_name("dummy_2") - self.addCleanup(unload_handler_name, "dummy_2") - - # silence some warnings - warnings.filterwarnings("ignore", - r"CryptContext\(\)\.replace\(\) has been deprecated") - warnings.filterwarnings("ignore", ".*(CryptPolicy|context\.policy).*(has|have) been deprecated.*") - - def test_kwd_constructor(self): - """test plain kwds""" - self.assertFalse(has_crypt_handler("dummy_2")) - register_crypt_handler_path("dummy_2", "passlib.tests.test_context") - - cc = LazyCryptContext(iter(["dummy_2", "des_crypt"]), deprecated=["des_crypt"]) - - self.assertFalse(has_crypt_handler("dummy_2", True)) - - self.assertTrue(cc.policy.handler_is_deprecated("des_crypt")) - self.assertEqual(cc.policy.schemes(), ["dummy_2", "des_crypt"]) - - self.assertTrue(has_crypt_handler("dummy_2", True)) - - def test_callable_constructor(self): - """test create_policy() hook, returning CryptPolicy""" - self.assertFalse(has_crypt_handler("dummy_2")) - register_crypt_handler_path("dummy_2", "passlib.tests.test_context") - - def create_policy(flag=False): - self.assertTrue(flag) - return CryptPolicy(schemes=iter(["dummy_2", "des_crypt"]), deprecated=["des_crypt"]) - - cc = LazyCryptContext(create_policy=create_policy, flag=True) - - self.assertFalse(has_crypt_handler("dummy_2", True)) - - self.assertTrue(cc.policy.handler_is_deprecated("des_crypt")) - self.assertEqual(cc.policy.schemes(), ["dummy_2", "des_crypt"]) - - self.assertTrue(has_crypt_handler("dummy_2", True)) - -#============================================================================= -# eof -#============================================================================= diff --git a/passlib/tests/test_crypto_builtin_md4.py b/passlib/tests/test_crypto_builtin_md4.py index 0aca1eb..e74b924 100644 --- a/passlib/tests/test_crypto_builtin_md4.py +++ b/passlib/tests/test_crypto_builtin_md4.py @@ -2,16 +2,16 @@ #============================================================================= # imports #============================================================================= -from __future__ import with_statement, division # core from binascii import hexlify import hashlib +from unittest import skipUnless # site # pkg # module -from passlib.utils.compat import bascii_to_str, PY3, u +from passlib.utils.compat import bascii_to_str from passlib.crypto.digest import lookup_hash -from passlib.tests.utils import TestCase, skipUnless +from passlib.tests.utils import TestCase # local __all__ = [ "_Common_MD4_Test", @@ -62,16 +62,10 @@ class _Common_MD4_Test(TestCase): h.update(b'bcdefghijklmnopqrstuvwxyz') self.assertEqual(h.hexdigest(), "d79e1c308aa5bbcdeea8ed63df412da9") - if PY3: - # reject unicode, hash should return digest of b'' - h = md4() - self.assertRaises(TypeError, h.update, u('a')) - self.assertEqual(h.hexdigest(), "31d6cfe0d16ae931b73c59d7e0c089c0") - else: - # coerce unicode to ascii, hash should return digest of b'a' - h = md4() - h.update(u('a')) - self.assertEqual(h.hexdigest(), "bde52cb31de33e46245e05fbdbd6fb24") + # reject unicode, hash should return digest of b'' + h = md4() + self.assertRaises(TypeError, h.update, u'a') + self.assertEqual(h.hexdigest(), "31d6cfe0d16ae931b73c59d7e0c089c0") def test_md4_hexdigest(self): """hexdigest() method""" @@ -124,7 +118,7 @@ class MD4_SSL_Test(_Common_MD4_Test): # this is more to test our test is correct :) def setUp(self): - super(MD4_SSL_Test, self).setUp() + super().setUp() # make sure we're using right constructor. self.assertEqual(self.get_md4_const().__module__, "hashlib") @@ -134,7 +128,7 @@ class MD4_Builtin_Test(_Common_MD4_Test): descriptionPrefix = "passlib.crypto._md4.md4()" def setUp(self): - super(MD4_Builtin_Test, self).setUp() + super().setUp() if has_native_md4(): diff --git a/passlib/tests/test_crypto_des.py b/passlib/tests/test_crypto_des.py index ab31845..20f3681 100644 --- a/passlib/tests/test_crypto_des.py +++ b/passlib/tests/test_crypto_des.py @@ -2,7 +2,6 @@ #============================================================================= # imports #============================================================================= -from __future__ import with_statement, division # core from functools import partial # site diff --git a/passlib/tests/test_crypto_digest.py b/passlib/tests/test_crypto_digest.py index 461d209..66f781c 100644 --- a/passlib/tests/test_crypto_digest.py +++ b/passlib/tests/test_crypto_digest.py @@ -2,17 +2,17 @@ #============================================================================= # imports #============================================================================= -from __future__ import with_statement, division # core from binascii import hexlify import hashlib +from unittest import skipUnless import warnings # site # pkg # module from passlib.exc import UnknownHashError -from passlib.utils.compat import PY3, u, JYTHON -from passlib.tests.utils import TestCase, TEST_MODE, skipUnless, hb +from passlib.utils.compat import JYTHON +from passlib.tests.utils import TestCase, TEST_MODE, hb #============================================================================= # test assorted crypto helpers @@ -56,7 +56,7 @@ class HashInfoTest(TestCase): warnings.filterwarnings("ignore", '.*unsupported hash') # test string types - self.assertEqual(norm_hash_name(u("MD4")), "md4") + self.assertEqual(norm_hash_name(u"MD4"), "md4") self.assertEqual(norm_hash_name(b"MD4"), "md4") self.assertRaises(TypeError, norm_hash_name, None) @@ -497,12 +497,7 @@ class Pbkdf2Test(TestCase): self.assertEqual("hashlib-ssl" in PBKDF2_BACKENDS, has_hashlib_ssl) # check for appropriate builtin - from passlib.utils.compat import PY3 - if PY3: - self.assertIn("builtin-from-bytes", PBKDF2_BACKENDS) - else: - # XXX: only true as long as this is preferred over hexlify - self.assertIn("builtin-unpack", PBKDF2_BACKENDS) + self.assertIn("builtin-from-bytes", PBKDF2_BACKENDS) def test_border(self): """test border cases""" diff --git a/passlib/tests/test_crypto_scrypt.py b/passlib/tests/test_crypto_scrypt.py index 73ff1fa..5009db0 100644 --- a/passlib/tests/test_crypto_scrypt.py +++ b/passlib/tests/test_crypto_scrypt.py @@ -7,15 +7,16 @@ from binascii import hexlify import hashlib import logging; log = logging.getLogger(__name__) import struct +from unittest import skipUnless import warnings warnings.filterwarnings("ignore", ".*using builtin scrypt backend.*") # site # pkg from passlib import exc from passlib.utils import getrandbytes -from passlib.utils.compat import PYPY, u, bascii_to_str +from passlib.utils.compat import PYPY, bascii_to_str from passlib.utils.decor import classproperty -from passlib.tests.utils import TestCase, skipUnless, TEST_MODE, hb +from passlib.tests.utils import TestCase, TEST_MODE, hb # subject from passlib.crypto import scrypt as scrypt_mod # local @@ -312,7 +313,7 @@ class _CommonScryptTest(TestCase): def setUp(self): assert self.backend scrypt_mod._set_backend(self.backend) - super(_CommonScryptTest, self).setUp() + super().setUp() #============================================================================= # reference vectors @@ -450,7 +451,7 @@ class _CommonScryptTest(TestCase): return hexstr(scrypt_mod.scrypt(secret, "salt", 2, 2, 2, 16)) # unicode - TEXT = u("abc\u00defg") + TEXT = u"abc\u00defg" self.assertEqual(run_scrypt(TEXT), '05717106997bfe0da42cf4779a2f8bd8') # utf8 bytes @@ -475,7 +476,7 @@ class _CommonScryptTest(TestCase): return hexstr(scrypt_mod.scrypt("secret", salt, 2, 2, 2, 16)) # unicode - TEXT = u("abc\u00defg") + TEXT = u"abc\u00defg" self.assertEqual(run_scrypt(TEXT), 'a748ec0f4613929e9e5f03d1ab741d88') # utf8 bytes @@ -597,7 +598,7 @@ class BuiltinScryptTest(_CommonScryptTest): backend = "builtin" def setUp(self): - super(BuiltinScryptTest, self).setUp() + super().setUp() warnings.filterwarnings("ignore", "(?i)using builtin scrypt backend", category=exc.PasslibSecurityWarning) diff --git a/passlib/tests/test_ext_django.py b/passlib/tests/test_ext_django.py index 2a0b418..4a2d4c9 100644 --- a/passlib/tests/test_ext_django.py +++ b/passlib/tests/test_ext_django.py @@ -3,7 +3,6 @@ # imports #============================================================================= # core -from __future__ import absolute_import, division, print_function import logging; log = logging.getLogger(__name__) import sys import re @@ -15,7 +14,7 @@ from passlib.context import CryptContext from passlib.ext.django.utils import ( DJANGO_VERSION, MIN_DJANGO_VERSION, DjangoTranslator, quirks, ) -from passlib.utils.compat import iteritems, get_method_function, u +from passlib.utils.compat import get_method_function from passlib.utils.decor import memoized_property # tests from passlib.tests.utils import TestCase, TEST_MODE, handler_derived_from @@ -77,7 +76,7 @@ UNSET = object() def update_settings(**kwds): """helper to update django settings from kwds""" - for k,v in iteritems(kwds): + for k,v in kwds.items(): if v is UNSET: if hasattr(settings, k): delattr(settings, k) @@ -347,7 +346,7 @@ class _ExtensionTest(TestCase, _ExtensionSupport): #============================================================================= def setUp(self): - super(_ExtensionTest, self).setUp() + super().setUp() self.require_TEST_MODE("default") @@ -844,7 +843,7 @@ class ExtensionBehaviorTest(DjangoBehaviorTest): ) def setUp(self): - super(ExtensionBehaviorTest, self).setUp() + super().setUp() # always load extension before each test self.load_extension(PASSLIB_CONFIG=self.config) @@ -875,9 +874,9 @@ class DjangoExtensionTest(_ExtensionTest): self.load_extension(PASSLIB_CONFIG="disabled", check=False) self.assert_unpatched() - # check legacy config=None - with self.assertWarningList("PASSLIB_CONFIG=None is deprecated"): - self.load_extension(PASSLIB_CONFIG=None, check=False) + # check onfig=None is rejected + self.assertRaises(TypeError, self.load_extension, PASSLIB_CONFIG=None, + check=False) self.assert_unpatched() # try stock django 1.0 context @@ -960,9 +959,9 @@ class DjangoExtensionTest(_ExtensionTest): "v2RWkZQzctPdejyRqmmTDQpZN6wTh7.RUy9zF2LftT6") self.assertEqual(hasher.safe_summary(encoded), {'algorithm': 'sha256_crypt', - 'salt': u('abcdab**********'), + 'salt': u'abcdab**********', 'rounds': 1234, - 'hash': u('v2RWkZ*************************************'), + 'hash': u'v2RWkZ*************************************', }) # made up name should throw error @@ -974,9 +973,9 @@ class DjangoExtensionTest(_ExtensionTest): #=================================================================== def test_11_config_disabled(self): """test PASSLIB_CONFIG='disabled'""" - # test config=None (deprecated) - with self.assertWarningList("PASSLIB_CONFIG=None is deprecated"): - self.load_extension(PASSLIB_CONFIG=None, check=False) + # test config=None is rejected + self.assertRaises(TypeError, self.load_extension, PASSLIB_CONFIG=None, + check=False) self.assert_unpatched() # test disabled config diff --git a/passlib/tests/test_ext_django_source.py b/passlib/tests/test_ext_django_source.py index 4b42e59..a6da6b6 100644 --- a/passlib/tests/test_ext_django_source.py +++ b/passlib/tests/test_ext_django_source.py @@ -4,12 +4,10 @@ test passlib.ext.django against django source tests #============================================================================= # imports #============================================================================= -from __future__ import absolute_import, division, print_function # core import logging; log = logging.getLogger(__name__) # site # pkg -from passlib.utils.compat import suppress_cause from passlib.ext.django.utils import DJANGO_VERSION, DjangoTranslator, _PasslibHasherWrapper # tests from passlib.tests.utils import TestCase, TEST_MODE @@ -60,10 +58,8 @@ elif has_min_django: try: from auth_tests import test_hashers as test_hashers_mod except ImportError as err: - raise suppress_cause( - EnvironmentError("error trying to import django tests " - "from source path (%r): %r" % - (source_path, err))) + raise EnvironmentError("error trying to import django tests " + "from source path (%r): %r" % (source_path, err)) from None finally: sys.path.remove(tests_path) @@ -89,7 +85,6 @@ if test_hashers_mod: from django.core.signals import setting_changed from django.dispatch import receiver from django.utils.module_loading import import_string - from passlib.utils.compat import get_unbound_method_function class HashersTest(test_hashers_mod.TestUtilsHashPass, _ExtensionSupport): """ @@ -102,7 +97,7 @@ if test_hashers_mod: #================================================================== # port patchAttr() helper method from passlib.tests.utils.TestCase - patchAttr = get_unbound_method_function(TestCase.patchAttr) + patchAttr = TestCase.patchAttr #================================================================== # custom setup @@ -212,7 +207,7 @@ if test_hashers_mod: def tearDown(self): # NOTE: could rely on addCleanup() instead, but need py26 compat self.unload_extension() - super(HashersTest, self).tearDown() + super().tearDown() #================================================================== # skip a few methods that can't be replicated properly diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py index cad5ef9..e3a3e30 100644 --- a/passlib/tests/test_handlers.py +++ b/passlib/tests/test_handlers.py @@ -2,18 +2,15 @@ #============================================================================= # imports #============================================================================= -from __future__ import with_statement # core -import logging; log = logging.getLogger(__name__) -import os +import logging +log = logging.getLogger(__name__) import sys import warnings # site # pkg from passlib import exc, hash -from passlib.utils import repeat_string -from passlib.utils.compat import irange, PY3, u, get_method_function -from passlib.tests.utils import TestCase, HandlerCase, skipUnless, \ +from passlib.tests.utils import TestCase, HandlerCase, \ TEST_MODE, UserHandlerMixin, EncodingHandlerMixin # module @@ -22,9 +19,9 @@ from passlib.tests.utils import TestCase, HandlerCase, skipUnless, \ #============================================================================= # some common unicode passwords which used as test cases -UPASS_WAV = u('\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2') -UPASS_USD = u("\u20AC\u00A5$") -UPASS_TABLE = u("t\u00e1\u0411\u2113\u0259") +UPASS_WAV = u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2' +UPASS_USD = u"\u20AC\u00A5$" +UPASS_TABLE = u"t\u00e1\u0411\u2113\u0259" PASS_TABLE_UTF8 = b't\xc3\xa1\xd0\x91\xe2\x84\x93\xc9\x99' # utf-8 @@ -150,7 +147,7 @@ class bigcrypt_test(HandlerCase): # check that _norm_checksum() also validates checksum size. # (current code uses regex in parser) self.assertRaises(ValueError, hash.bigcrypt, use_defaults=True, - checksum=u('yh4XPJGsOZ')) + checksum=u'yh4XPJGsOZ') #============================================================================= # bsdi crypt @@ -208,7 +205,7 @@ class _bsdi_crypt_test(HandlerCase): def test_77_fuzz_input(self, **kwds): # we want to generate even rounds to verify it's correct, but want to ignore warnings warnings.filterwarnings("ignore", "bsdi_crypt rounds should be odd.*") - super(_bsdi_crypt_test, self).test_77_fuzz_input(**kwds) + super().test_77_fuzz_input(**kwds) def test_needs_update_w_even_rounds(self): """needs_update() should flag even rounds""" @@ -289,7 +286,7 @@ class _des_crypt_test(HandlerCase): ('AlOtBsOl', 'cEpWz5IUCShqM'), # ensures utf-8 used for unicode - (u('hell\u00D6'), 'saykDgk3BPZ9E'), + (u'hell\u00D6', 'saykDgk3BPZ9E'), ] known_unidentified_hashes = [ # bad char in otherwise correctly formatted hash @@ -379,11 +376,11 @@ class fshp_test(HandlerCase): handler(variant=1, **kwds) # accepts bytes or unicode - handler(variant=u('1'), **kwds) + handler(variant=u'1', **kwds) handler(variant=b'1', **kwds) # aliases - handler(variant=u('sha256'), **kwds) + handler(variant=u'sha256', **kwds) handler(variant=b'sha256', **kwds) # rejects None @@ -585,7 +582,7 @@ class ldap_salted_sha256_test(HandlerCase): ("password", '{SSHA256}x1tymSTVjozxQ2PtT46ysrzhZxbcskK0o2f8hEFx7fAQQmhtDSEkJA=='), ("test", '{SSHA256}xfqc9aOR6z15YaEk3/Ufd7UL9+JozB/1EPmCDTizL0GkdA7BuNda6w=='), ("toomanysecrets", '{SSHA256}RrTKrg6HFXcjJ+eDAq4UtbODxOr9RLeG+I69FoJvutcbY0zpfU+p1Q=='), - (u('letm\xe8\xefn'), '{SSHA256}km7UjUTBZN8a+gf1ND2/qn15N7LsO/jmGYJXvyTfJKAbI0RoLWWslQ=='), + (u'letm\xe8\xefn', '{SSHA256}km7UjUTBZN8a+gf1ND2/qn15N7LsO/jmGYJXvyTfJKAbI0RoLWWslQ=='), # alternate salt sizes (4, 15, 16) # generated locally @@ -612,7 +609,7 @@ class ldap_salted_sha512_test(HandlerCase): # generated by testing ldap server web interface (see issue 124 comments) # salt size = 8 ("toomanysecrets", '{SSHA512}wExp4xjiCHS0zidJDC4UJq9EEeIebAQPJ1PWSwfhxWjfutI9XiiKuHm2AE41cEFfK+8HyI8bh+ztbczUGsvVFIgICWWPt7qu'), - (u('letm\xe8\xefn'), '{SSHA512}mpNUSmZc3TNx+RnPwkIAVMf7ocEKLPrIoQNsg4Eu8dHvyCeb2xzHp5A6n4tF7ntknSvfvRZaJII4ImvNJlYsgiwAm0FMqR+3'), + (u'letm\xe8\xefn', '{SSHA512}mpNUSmZc3TNx+RnPwkIAVMf7ocEKLPrIoQNsg4Eu8dHvyCeb2xzHp5A6n4tF7ntknSvfvRZaJII4ImvNJlYsgiwAm0FMqR+3'), # generated locally # salt size = 8 @@ -642,8 +639,8 @@ class ldap_plaintext_test(HandlerCase): handler = hash.ldap_plaintext known_correct_hashes = [ ("password", 'password'), - (UPASS_TABLE, UPASS_TABLE if PY3 else PASS_TABLE_UTF8), - (PASS_TABLE_UTF8, UPASS_TABLE if PY3 else PASS_TABLE_UTF8), + (UPASS_TABLE, UPASS_TABLE), + (PASS_TABLE_UTF8, UPASS_TABLE), ] known_unidentified_hashes = [ "{FOO}bar", @@ -706,7 +703,7 @@ class _ldap_sha1_crypt_test(HandlerCase): def populate_settings(self, kwds): kwds.setdefault("rounds", 10) - super(_ldap_sha1_crypt_test, self).populate_settings(kwds) + super().populate_settings(kwds) def test_77_fuzz_input(self, **ignored): raise self.skipTest("unneeded") @@ -738,14 +735,14 @@ class lmhash_test(EncodingHandlerMixin, HandlerCase): ('Yokohama', '5ecd9236d21095ce7584248b8d2c9f9e'), # ensures cp437 used for unicode - (u('ENCYCLOP\xC6DIA'), 'fed6416bffc9750d48462b9d7aaac065'), - (u('encyclop\xE6dia'), 'fed6416bffc9750d48462b9d7aaac065'), + (u'ENCYCLOP\xC6DIA', 'fed6416bffc9750d48462b9d7aaac065'), + (u'encyclop\xE6dia', 'fed6416bffc9750d48462b9d7aaac065'), # test various encoding values - ((u("\xC6"), None), '25d8ab4a0659c97aaad3b435b51404ee'), - ((u("\xC6"), "cp437"), '25d8ab4a0659c97aaad3b435b51404ee'), - ((u("\xC6"), "latin-1"), '184eecbbe9991b44aad3b435b51404ee'), - ((u("\xC6"), "utf-8"), '00dd240fcfab20b8aad3b435b51404ee'), + ((u"\xC6", None), '25d8ab4a0659c97aaad3b435b51404ee'), + ((u"\xC6", "cp437"), '25d8ab4a0659c97aaad3b435b51404ee'), + ((u"\xC6", "latin-1"), '184eecbbe9991b44aad3b435b51404ee'), + ((u"\xC6", "utf-8"), '00dd240fcfab20b8aad3b435b51404ee'), ] known_unidentified_hashes = [ @@ -795,7 +792,7 @@ class _md5_crypt_test(HandlerCase): ('4lpHa N|_|M3r1K W/ Cur5Es: #$%(*)(*%#', '$1$jQS7o98J$V6iTcr71CGgwW2laf17pi1'), ('test', '$1$SuMrG47N$ymvzYjr7QcEQjaK5m1PGx1'), (b'test', '$1$SuMrG47N$ymvzYjr7QcEQjaK5m1PGx1'), - (u('s'), '$1$ssssssss$YgmLTApYTv12qgTwBoj8i/'), + (u's', '$1$ssssssss$YgmLTApYTv12qgTwBoj8i/'), # ensures utf-8 used for unicode (UPASS_TABLE, '$1$d6/Ky1lU$/xpf8m7ftmWLF.TjHCqel0'), @@ -866,9 +863,9 @@ class msdcc_test(UserHandlerMixin, HandlerCase): (("", "root"), "176a4c2bd45ac73687676c2f09045353"), (("test1", "TEST1"), "64cd29e36a8431a2b111378564a10631"), (("okolada", "nineteen_characters"), "290efa10307e36a79b3eebf2a6b29455"), - ((u("\u00FC"), u("\u00FC")), "48f84e6f73d6d5305f6558a33fa2c9bb"), - ((u("\u00FC\u00FC"), u("\u00FC\u00FC")), "593246a8335cf0261799bda2a2a9c623"), - ((u("\u20AC\u20AC"), "user"), "9121790702dda0fa5d353014c334c2ce"), + ((u"\u00FC", u"\u00FC"), "48f84e6f73d6d5305f6558a33fa2c9bb"), + ((u"\u00FC\u00FC", u"\u00FC\u00FC"), "593246a8335cf0261799bda2a2a9c623"), + ((u"\u20AC\u20AC", "user"), "9121790702dda0fa5d353014c334c2ce"), # # custom @@ -904,9 +901,9 @@ class msdcc2_test(UserHandlerMixin, HandlerCase): (("test2", "TEST2"), "c6758e5be7fc943d00b97972a8a97620"), (("test3", "test3"), "360e51304a2d383ea33467ab0b639cc4"), (("test4", "test4"), "6f79ee93518306f071c47185998566ae"), - ((u("\u00FC"), "joe"), "bdb80f2c4656a8b8591bd27d39064a54"), - ((u("\u20AC\u20AC"), "joe"), "1e1e20f482ff748038e47d801d0d1bda"), - ((u("\u00FC\u00FC"), "admin"), "0839e4a07c00f18a8c65cf5b985b9e73"), + ((u"\u00FC", "joe"), "bdb80f2c4656a8b8591bd27d39064a54"), + ((u"\u20AC\u20AC", "joe"), "1e1e20f482ff748038e47d801d0d1bda"), + ((u"\u00FC\u00FC", "admin"), "0839e4a07c00f18a8c65cf5b985b9e73"), # # custom @@ -999,7 +996,7 @@ class mssql2000_test(HandlerCase): known_malformed_hashes = [ # non-hex char -----\/ b'0x01005B200543327G2E1BC2E7C5DF0F9EBFE486E9BEE063E8D3B332752E1BC2E7C5DF0F9EBFE486E9BEE063E8D3B3', - u('0x01005B200543327G2E1BC2E7C5DF0F9EBFE486E9BEE063E8D3B332752E1BC2E7C5DF0F9EBFE486E9BEE063E8D3B3'), + u'0x01005B200543327G2E1BC2E7C5DF0F9EBFE486E9BEE063E8D3B332752E1BC2E7C5DF0F9EBFE486E9BEE063E8D3B3', ] class mssql2005_test(HandlerCase): @@ -1160,8 +1157,8 @@ class nthash_test(HandlerCase): # # http://msdn.microsoft.com/en-us/library/cc245828(v=prot.10).aspx # - ("OLDPASSWORD", u("6677b2c394311355b54f25eec5bfacf5")), - ("NEWPASSWORD", u("256781a62031289d3c2c98c14f1efc8c")), + ("OLDPASSWORD", u"6677b2c394311355b54f25eec5bfacf5"), + ("NEWPASSWORD", u"256781a62031289d3c2c98c14f1efc8c"), # # from JTR 1.7.9 @@ -1217,7 +1214,7 @@ class oracle10_test(UserHandlerMixin, HandlerCase): # http://www.petefinnigan.com/default/default_password_list.htm # (('tiger', 'scott'), 'F894844C34402B67'), - ((u('ttTiGGeR'), u('ScO')), '7AA1A84E31ED7771'), + ((u'ttTiGGeR', u'ScO'), '7AA1A84E31ED7771'), (("d_syspw", "SYSTEM"), '1B9F1F9A5CB9EB31'), (("strat_passwd", "strat_user"), 'AEBEDBB4EFB5225B'), @@ -1322,8 +1319,8 @@ class plaintext_test(HandlerCase): ('password', 'password'), # ensure unicode uses utf-8 - (UPASS_TABLE, UPASS_TABLE if PY3 else PASS_TABLE_UTF8), - (PASS_TABLE_UTF8, UPASS_TABLE if PY3 else PASS_TABLE_UTF8), + (UPASS_TABLE, UPASS_TABLE), + (PASS_TABLE_UTF8, UPASS_TABLE), ] #============================================================================= @@ -1456,7 +1453,7 @@ class _sha256_crypt_test(HandlerCase): ('test', '$5$rounds=11858$WH1ABM5sKhxbkgCK$aTQsjPkz0rBsH3lQlJxw9HDTDXPKBxC0LlVeV69P.t1'), ('Compl3X AlphaNu3meric', '$5$rounds=10350$o.pwkySLCzwTdmQX$nCMVsnF3TXWcBPOympBUUSQi6LGGloZoOsVJMGJ09UB'), ('4lpHa N|_|M3r1K W/ Cur5Es: #$%(*)(*%#', '$5$rounds=11944$9dhlu07dQMRWvTId$LyUI5VWkGFwASlzntk1RLurxX54LUhgAcJZIt0pYGT7'), - (u('with unic\u00D6de'), '$5$rounds=1000$IbG0EuGQXw5EkMdP$LQ5AfPf13KufFsKtmazqnzSGZ4pxtUNw3woQ.ELRDF4'), + (u'with unic\u00D6de', '$5$rounds=1000$IbG0EuGQXw5EkMdP$LQ5AfPf13KufFsKtmazqnzSGZ4pxtUNw3woQ.ELRDF4'), ] if TEST_MODE("full"): @@ -1754,7 +1751,7 @@ class unix_disabled_test(HandlerCase): def test_76_hash_border(self): # so empty strings pass self.accepts_all_hashes = True - super(unix_disabled_test, self).test_76_hash_border() + super().test_76_hash_border() def test_90_special(self): """test marker option & special behavior""" @@ -1779,41 +1776,6 @@ class unix_disabled_test(HandlerCase): self.assertRaises(ValueError, handler.hash, 'stub', marker='abc') self.assertRaises(ValueError, handler.using, marker='abc') -class unix_fallback_test(HandlerCase): - handler = hash.unix_fallback - accepts_all_hashes = True - - known_correct_hashes = [ - # *everything* should hash to "!", and nothing should verify - ("password", "!"), - (UPASS_TABLE, "!"), - ] - - # silence annoying deprecation warning - def setUp(self): - super(unix_fallback_test, self).setUp() - warnings.filterwarnings("ignore", "'unix_fallback' is deprecated") - - def test_90_wildcard(self): - """test enable_wildcard flag""" - h = self.handler - self.assertTrue(h.verify('password','', enable_wildcard=True)) - self.assertFalse(h.verify('password','')) - for c in "!*x": - self.assertFalse(h.verify('password',c, enable_wildcard=True)) - self.assertFalse(h.verify('password',c)) - - def test_91_preserves_existing(self): - """test preserves existing disabled hash""" - handler = self.handler - - # use marker if no hash - self.assertEqual(handler.genhash("stub", ""), "!") - self.assertEqual(handler.hash("stub"), "!") - - # use hash if provided and valid - self.assertEqual(handler.genhash("stub", "!asd"), "!asd") - #============================================================================= # eof #============================================================================= diff --git a/passlib/tests/test_handlers_argon2.py b/passlib/tests/test_handlers_argon2.py index e771769..3f800ba 100644 --- a/passlib/tests/test_handlers_argon2.py +++ b/passlib/tests/test_handlers_argon2.py @@ -10,7 +10,6 @@ import warnings # site # pkg from passlib import hash -from passlib.utils.compat import unicode from passlib.tests.utils import HandlerCase, TEST_MODE from passlib.tests.test_handlers import UPASS_TABLE, PASS_TABLE_UTF8 # module @@ -200,7 +199,7 @@ class _base_argon2_test(HandlerCase): ] def setUpWarnings(self): - super(_base_argon2_test, self).setUpWarnings() + super().setUpWarnings() warnings.filterwarnings("ignore", ".*Using argon2pure backend.*") def do_stub_encrypt(self, handler=None, **settings): @@ -213,7 +212,7 @@ class _base_argon2_test(HandlerCase): assert self.checksum return self.to_string() else: - return super(_base_argon2_test, self).do_stub_encrypt(handler, **settings) + return super().do_stub_encrypt(handler, **settings) def test_03_legacy_hash_workflow(self): # override base method @@ -303,7 +302,7 @@ class _base_argon2_test(HandlerCase): # check supported type_values for value in cls.type_values: - self.assertIsInstance(value, unicode) + self.assertIsInstance(value, str) self.assertTrue("i" in cls.type_values) self.assertTrue("d" in cls.type_values) diff --git a/passlib/tests/test_handlers_bcrypt.py b/passlib/tests/test_handlers_bcrypt.py index 64fc8bf..abe44ec 100644 --- a/passlib/tests/test_handlers_bcrypt.py +++ b/passlib/tests/test_handlers_bcrypt.py @@ -2,7 +2,6 @@ #============================================================================= # imports #============================================================================= -from __future__ import with_statement # core import logging; log = logging.getLogger(__name__) import os @@ -12,7 +11,6 @@ import warnings from passlib import hash from passlib.handlers.bcrypt import IDENT_2, IDENT_2X from passlib.utils import repeat_string, to_bytes, is_safe_crypt_input -from passlib.utils.compat import irange, PY3 from passlib.tests.utils import HandlerCase, TEST_MODE from passlib.tests.test_handlers import UPASS_TABLE # module @@ -179,7 +177,7 @@ class _bcrypt_test(HandlerCase): self.addCleanup(os.environ.__delitem__, key) os.environ[key] = "true" - super(_bcrypt_test, self).setUp() + super().setUp() # silence this warning, will come up a bunch during testing of old 2a hashes. warnings.filterwarnings("ignore", ".*backend is vulnerable to the bsd wraparound bug.*") @@ -188,7 +186,7 @@ class _bcrypt_test(HandlerCase): # builtin is still just way too slow. if self.backend == "builtin": kwds.setdefault("rounds", 4) - super(_bcrypt_test, self).populate_settings(kwds) + super().populate_settings(kwds) #=================================================================== # fuzz testing @@ -202,8 +200,6 @@ class _bcrypt_test(HandlerCase): fuzz_verifiers = HandlerCase.fuzz_verifiers + ( "fuzz_verifier_bcrypt", - "fuzz_verifier_pybcrypt", - "fuzz_verifier_bcryptor", ) def fuzz_verifier_bcrypt(self): @@ -238,61 +234,6 @@ class _bcrypt_test(HandlerCase): raise ValueError("bcrypt rejected hash: %r (secret=%r)" % (hash, secret)) return check_bcrypt - def fuzz_verifier_pybcrypt(self): - # test against py-bcrypt, if available - from passlib.handlers.bcrypt import ( - IDENT_2, IDENT_2A, IDENT_2B, IDENT_2X, IDENT_2Y, - _PyBcryptBackend, - ) - from passlib.utils import to_native_str - - loaded = _PyBcryptBackend._load_backend_mixin("pybcrypt", False) - if not loaded: - return - - from passlib.handlers.bcrypt import _pybcrypt as bcrypt_mod - - lock = _PyBcryptBackend._calc_lock # reuse threadlock workaround for pybcrypt 0.2 - - def check_pybcrypt(secret, hash): - """pybcrypt""" - secret = to_native_str(secret, self.FuzzHashGenerator.password_encoding) - if len(secret) > 200: # vulnerable to wraparound bug - secret = secret[:200] - if hash.startswith((IDENT_2B, IDENT_2Y)): - hash = IDENT_2A + hash[4:] - try: - if lock: - with lock: - return bcrypt_mod.hashpw(secret, hash) == hash - else: - return bcrypt_mod.hashpw(secret, hash) == hash - except ValueError: - raise ValueError("py-bcrypt rejected hash: %r" % (hash,)) - return check_pybcrypt - - def fuzz_verifier_bcryptor(self): - # test against bcryptor if available - from passlib.handlers.bcrypt import IDENT_2, IDENT_2A, IDENT_2Y, IDENT_2B - from passlib.utils import to_native_str - try: - from bcryptor.engine import Engine - except ImportError: - return - def check_bcryptor(secret, hash): - """bcryptor""" - secret = to_native_str(secret, self.FuzzHashGenerator.password_encoding) - if hash.startswith((IDENT_2B, IDENT_2Y)): - hash = IDENT_2A + hash[4:] - elif hash.startswith(IDENT_2): - # bcryptor doesn't support $2$ hashes; but we can fake it - # using the $2a$ algorithm, by repeating the password until - # it's 72 chars in length. - hash = IDENT_2A + hash[3:] - if secret: - secret = repeat_string(secret, 72) - return Engine(False).hash_key(secret, hash) == hash - return check_bcryptor class FuzzHashGenerator(HandlerCase.FuzzHashGenerator): @@ -366,9 +307,9 @@ class _bcrypt_test(HandlerCase): "unexpectedly malformed hash: %r" % (hash,) self.assertTrue(hash[28] in '.Oeu', "unused bits incorrectly set in hash: %r" % (hash,)) - for i in irange(6): + for i in range(6): check_padding(bcrypt.genconfig()) - for i in irange(3): + for i in range(3): check_padding(bcrypt.using(rounds=bcrypt.min_rounds).hash("bob")) # @@ -425,8 +366,7 @@ class _bcrypt_test(HandlerCase): # create test cases for specific backends bcrypt_bcrypt_test = _bcrypt_test.create_backend_case("bcrypt") -bcrypt_pybcrypt_test = _bcrypt_test.create_backend_case("pybcrypt") -bcrypt_bcryptor_test = _bcrypt_test.create_backend_case("bcryptor") + class bcrypt_os_crypt_test(_bcrypt_test.create_backend_case("os_crypt")): @@ -583,14 +523,14 @@ class _bcrypt_sha256_test(HandlerCase): else: self.addCleanup(os.environ.__delitem__, key) os.environ[key] = "enabled" - super(_bcrypt_sha256_test, self).setUp() + super().setUp() warnings.filterwarnings("ignore", ".*backend is vulnerable to the bsd wraparound bug.*") def populate_settings(self, kwds): # builtin is still just way too slow. if self.backend == "builtin": kwds.setdefault("rounds", 4) - super(_bcrypt_sha256_test, self).populate_settings(kwds) + super().populate_settings(kwds) #=================================================================== # override ident tests for now @@ -670,8 +610,7 @@ class _bcrypt_sha256_test(HandlerCase): # create test cases for specific backends bcrypt_sha256_bcrypt_test = _bcrypt_sha256_test.create_backend_case("bcrypt") -bcrypt_sha256_pybcrypt_test = _bcrypt_sha256_test.create_backend_case("pybcrypt") -bcrypt_sha256_bcryptor_test = _bcrypt_sha256_test.create_backend_case("bcryptor") + class bcrypt_sha256_os_crypt_test(_bcrypt_sha256_test.create_backend_case("os_crypt")): diff --git a/passlib/tests/test_handlers_cisco.py b/passlib/tests/test_handlers_cisco.py index ea6594b..bc23c52 100644 --- a/passlib/tests/test_handlers_cisco.py +++ b/passlib/tests/test_handlers_cisco.py @@ -4,14 +4,12 @@ passlib.tests.test_handlers_cisco - tests for Cisco-specific algorithms #============================================================================= # imports #============================================================================= -from __future__ import absolute_import, division, print_function # core import logging log = logging.getLogger(__name__) # site # pkg from passlib import hash, exc -from passlib.utils.compat import u from .utils import UserHandlerMixin, HandlerCase, repeat_string from .test_handlers import UPASS_TABLE # module @@ -151,8 +149,8 @@ class _PixAsaSharedTest(UserHandlerMixin, HandlerCase): # observed behaviors include: # * ssh cli stripping non-ascii chars entirely # * ASDM web iface double-encoding utf-8 strings - ((u("t\xe1ble").encode("utf-8"), 'user'), 'Og8fB4NyF0m5Ed9c'), - ((u("t\xe1ble").encode("utf-8").decode("latin-1").encode("utf-8"), + ((u"t\xe1ble".encode("utf-8"), 'user'), 'Og8fB4NyF0m5Ed9c'), + ((u"t\xe1ble".encode("utf-8").decode("latin-1").encode("utf-8"), 'user'), 'cMvFC2XVBmK/68yB'), # confirmed ASA 9.6 when typed into ASDM ] diff --git a/passlib/tests/test_handlers_django.py b/passlib/tests/test_handlers_django.py index f7c9a0d..9d03a98 100644 --- a/passlib/tests/test_handlers_django.py +++ b/passlib/tests/test_handlers_django.py @@ -2,17 +2,16 @@ #============================================================================= # imports #============================================================================= -from __future__ import with_statement # core import logging; log = logging.getLogger(__name__) import re +from unittest import skipUnless, SkipTest import warnings # site # pkg from passlib import hash from passlib.utils import repeat_string -from passlib.utils.compat import u -from passlib.tests.utils import TestCase, HandlerCase, skipUnless, SkipTest +from passlib.tests.utils import TestCase, HandlerCase from passlib.tests.test_handlers import UPASS_USD, UPASS_TABLE from passlib.tests.test_ext_django import DJANGO_VERSION, MIN_DJANGO_VERSION, \ check_django_hasher_has_backend @@ -23,7 +22,7 @@ from passlib.tests.test_ext_django import DJANGO_VERSION, MIN_DJANGO_VERSION, \ #============================================================================= # standard string django uses -UPASS_LETMEIN = u('l\xe8tmein') +UPASS_LETMEIN = u'l\xe8tmein' def vstr(version): return ".".join(str(e) for e in version) @@ -146,7 +145,7 @@ class django_des_crypt_test(HandlerCase, _DjangoHelper): # ensures utf-8 used for unicode (UPASS_USD, 'crypt$c2e86$c2hN1Bxd6ZiWs'), (UPASS_TABLE, 'crypt$0.aQs$0.wB.TT0Czvlo'), - (u("hell\u00D6"), "crypt$sa$saykDgk3BPZ9E"), + (u"hell\u00D6", "crypt$sa$saykDgk3BPZ9E"), # prevent regression of issue 22 ("foo", 'crypt$MNVY.9ajgdvDQ$MNVY.9ajgdvDQ'), @@ -298,7 +297,7 @@ class django_bcrypt_test(HandlerCase, _DjangoHelper): def populate_settings(self, kwds): # speed up test w/ lower rounds kwds.setdefault("rounds", 4) - super(django_bcrypt_test, self).populate_settings(kwds) + super().populate_settings(kwds) class FuzzHashGenerator(HandlerCase.FuzzHashGenerator): @@ -348,7 +347,7 @@ class django_bcrypt_sha256_test(HandlerCase, _DjangoHelper): def populate_settings(self, kwds): # speed up test w/ lower rounds kwds.setdefault("rounds", 4) - super(django_bcrypt_sha256_test, self).populate_settings(kwds) + super().populate_settings(kwds) class FuzzHashGenerator(HandlerCase.FuzzHashGenerator): @@ -382,7 +381,7 @@ class django_argon2_test(HandlerCase, _DjangoHelper): ] def setUpWarnings(self): - super(django_argon2_test, self).setUpWarnings() + super().setUpWarnings() warnings.filterwarnings("ignore", ".*Using argon2pure backend.*") def do_stub_encrypt(self, handler=None, **settings): diff --git a/passlib/tests/test_handlers_pbkdf2.py b/passlib/tests/test_handlers_pbkdf2.py index 4d2f048..658f2ce 100644 --- a/passlib/tests/test_handlers_pbkdf2.py +++ b/passlib/tests/test_handlers_pbkdf2.py @@ -9,7 +9,6 @@ import warnings # site # pkg from passlib import hash -from passlib.utils.compat import u from passlib.tests.utils import TestCase, HandlerCase from passlib.tests.test_handlers import UPASS_WAV # module @@ -123,7 +122,7 @@ class cta_pbkdf2_sha1_test(HandlerCase): # # test vectors from original implementation # - (u("hashy the \N{SNOWMAN}"), '$p5k2$1000$ZxK4ZBJCfQg=$jJZVscWtO--p1-xIZl6jhO2LKR0='), + (u"hashy the \N{SNOWMAN}", '$p5k2$1000$ZxK4ZBJCfQg=$jJZVscWtO--p1-xIZl6jhO2LKR0='), # # custom @@ -201,11 +200,11 @@ class scram_test(HandlerCase): # test unicode passwords & saslprep (all the passwords below # should normalize to the same value: 'IX \xE0') - (u('IX \xE0'), '$scram$6400$0BojBCBE6P2/N4bQ$' + (u'IX \xE0', '$scram$6400$0BojBCBE6P2/N4bQ$' 'sha-1=YniLes.b8WFMvBhtSACZyyvxeCc'), - (u('\u2168\u3000a\u0300'), '$scram$6400$0BojBCBE6P2/N4bQ$' + (u'\u2168\u3000a\u0300', '$scram$6400$0BojBCBE6P2/N4bQ$' 'sha-1=YniLes.b8WFMvBhtSACZyyvxeCc'), - (u('\u00ADIX \xE0'), '$scram$6400$0BojBCBE6P2/N4bQ$' + (u'\u00ADIX \xE0', '$scram$6400$0BojBCBE6P2/N4bQ$' 'sha-1=YniLes.b8WFMvBhtSACZyyvxeCc'), ] @@ -245,7 +244,7 @@ class scram_test(HandlerCase): ] def setUp(self): - super(scram_test, self).setUp() + super().setUp() # some platforms lack stringprep (e.g. Jython, IronPython) self.require_stringprep() @@ -287,7 +286,7 @@ class scram_test(HandlerCase): """test internal parsing of 'checksum' keyword""" # check non-bytes checksum values are rejected self.assertRaises(TypeError, self.handler, use_defaults=True, - checksum={'sha-1': u('X')*20}) + checksum={'sha-1': u'X'*20}) # check sha-1 is required self.assertRaises(ValueError, self.handler, use_defaults=True, @@ -340,9 +339,9 @@ class scram_test(HandlerCase): # check various encodings of password work. s1 = b'\x01\x02\x03' d1 = b'\xb2\xfb\xab\x82[tNuPnI\x8aZZ\x19\x87\xcen\xe9\xd3' - self.assertEqual(hash(u("\u2168"), s1, 1000, 'sha-1'), d1) + self.assertEqual(hash(u"\u2168", s1, 1000, 'sha-1'), d1) self.assertEqual(hash(b"\xe2\x85\xa8", s1, 1000, 'SHA-1'), d1) - self.assertEqual(hash(u("IX"), s1, 1000, 'sha1'), d1) + self.assertEqual(hash(u"IX", s1, 1000, 'sha1'), d1) self.assertEqual(hash(b"IX", s1, 1000, 'SHA1'), d1) # check algs @@ -354,7 +353,7 @@ class scram_test(HandlerCase): self.assertRaises(ValueError, hash, "IX", s1, 0, 'sha-1') # unicode salts accepted as of passlib 1.7 (previous caused TypeError) - self.assertEqual(hash(u("IX"), s1.decode("latin-1"), 1000, 'sha1'), d1) + self.assertEqual(hash(u"IX", s1.decode("latin-1"), 1000, 'sha1'), d1) def test_94_saslprep(self): """test hash/verify use saslprep""" @@ -363,18 +362,18 @@ class scram_test(HandlerCase): # to verify full normalization behavior. # hash unnormalized - h = self.do_encrypt(u("I\u00ADX")) - self.assertTrue(self.do_verify(u("IX"), h)) - self.assertTrue(self.do_verify(u("\u2168"), h)) + h = self.do_encrypt(u"I\u00ADX") + self.assertTrue(self.do_verify(u"IX", h)) + self.assertTrue(self.do_verify(u"\u2168", h)) # hash normalized - h = self.do_encrypt(u("\xF3")) - self.assertTrue(self.do_verify(u("o\u0301"), h)) - self.assertTrue(self.do_verify(u("\u200Do\u0301"), h)) + h = self.do_encrypt(u"\xF3") + self.assertTrue(self.do_verify(u"o\u0301", h)) + self.assertTrue(self.do_verify(u"\u200Do\u0301", h)) # throws error if forbidden char provided - self.assertRaises(ValueError, self.do_encrypt, u("\uFDD0")) - self.assertRaises(ValueError, self.do_verify, u("\uFDD0"), h) + self.assertRaises(ValueError, self.do_encrypt, u"\uFDD0") + self.assertRaises(ValueError, self.do_verify, u"\uFDD0", h) def test_94_using_w_default_algs(self, param="default_algs"): """using() -- 'default_algs' parameter""" diff --git a/passlib/tests/test_handlers_scrypt.py b/passlib/tests/test_handlers_scrypt.py index 5ab6d9f..2ce6463 100644 --- a/passlib/tests/test_handlers_scrypt.py +++ b/passlib/tests/test_handlers_scrypt.py @@ -86,14 +86,14 @@ class _scrypt_test(HandlerCase): ] def setUpWarnings(self): - super(_scrypt_test, self).setUpWarnings() + super().setUpWarnings() warnings.filterwarnings("ignore", ".*using builtin scrypt backend.*") def populate_settings(self, kwds): # builtin is still just way too slow. if self.backend == "builtin": kwds.setdefault("rounds", 6) - super(_scrypt_test, self).populate_settings(kwds) + super().populate_settings(kwds) class FuzzHashGenerator(HandlerCase.FuzzHashGenerator): diff --git a/passlib/tests/test_hosts.py b/passlib/tests/test_hosts.py index cbf93ab..b8e6e5e 100644 --- a/passlib/tests/test_hosts.py +++ b/passlib/tests/test_hosts.py @@ -2,7 +2,6 @@ #============================================================================= # imports #============================================================================= -from __future__ import with_statement # core import logging; log = logging.getLogger(__name__) # site diff --git a/passlib/tests/test_pwd.py b/passlib/tests/test_pwd.py index 2c983cd..cc7bad3 100644 --- a/passlib/tests/test_pwd.py +++ b/passlib/tests/test_pwd.py @@ -66,7 +66,7 @@ class WordGeneratorTest(TestCase): descriptionPrefix = "passlib.pwd.genword()" def setUp(self): - super(WordGeneratorTest, self).setUp() + super().setUp() # patch some RNG references so they're reproducible. from passlib.pwd import SequenceGenerator diff --git a/passlib/tests/test_registry.py b/passlib/tests/test_registry.py index 8cec48d..09d8dfd 100644 --- a/passlib/tests/test_registry.py +++ b/passlib/tests/test_registry.py @@ -2,7 +2,6 @@ #============================================================================= # imports #============================================================================= -from __future__ import with_statement # core from logging import getLogger import warnings @@ -40,7 +39,7 @@ class RegistryTest(TestCase): descriptionPrefix = "passlib.registry" def setUp(self): - super(RegistryTest, self).setUp() + super().setUp() # backup registry state & restore it after test. locations = dict(registry._locations) diff --git a/passlib/tests/test_totp.py b/passlib/tests/test_totp.py index 604d2e9..e9c6dd9 100644 --- a/passlib/tests/test_totp.py +++ b/passlib/tests/test_totp.py @@ -3,6 +3,7 @@ # imports #============================================================================= # core +from binascii import Error as DecodeError import datetime from functools import partial import logging; log = logging.getLogger(__name__) @@ -11,7 +12,6 @@ import time as _time # site # pkg from passlib import exc -from passlib.utils.compat import unicode, u from passlib.tests.utils import TestCase, time_call # subject from passlib import totp as totp_module @@ -25,16 +25,6 @@ __all__ = [ # helpers #============================================================================= -# XXX: python 3 changed what error base64.b16decode() throws, from TypeError to base64.Error(). -# it wasn't until 3.3 that base32decode() also got changed. -# really should normalize this in the code to a single BinaryDecodeError, -# predicting this cross-version is getting unmanagable. -Base32DecodeError = Base16DecodeError = TypeError -if sys.version_info >= (3,0): - from binascii import Error as Base16DecodeError -if sys.version_info >= (3,3): - from binascii import Error as Base32DecodeError - PASS1 = "abcdef" PASS2 = b"\x00\xFF" KEY1 = '4AOGGDBBQSYHNTUZ' @@ -174,7 +164,7 @@ class AppWalletTest(TestCase): self.assertEqual(wallet._secrets, ref) # accept unicode - wallet = AppWallet({u("1"): b"aaa", u("02"): b"bbb", u("C"): b"ccc"}) + wallet = AppWallet({u"1": b"aaa", u"02": b"bbb", u"C": b"ccc"}) self.assertEqual(wallet._secrets, ref) # normalize int tags @@ -192,7 +182,7 @@ class AppWalletTest(TestCase): self.assertRaises(ValueError, AppWallet, {"ab*$": "aaa"}) # coerce value to bytes - wallet = AppWallet({"1": u("aaa"), "02": "bbb", "C": b"ccc"}) + wallet = AppWallet({"1": u"aaa", "02": "bbb", "C": b"ccc"}) self.assertEqual(wallet._secrets, ref) # forbid invalid value types @@ -406,7 +396,7 @@ class TotpTest(TestCase): # setup #============================================================================= def setUp(self): - super(TotpTest, self).setUp() + super().setUp() # clear norm_hash_name() cache so 'unknown hash' warnings get emitted each time from passlib.crypto.digest import lookup_hash @@ -629,13 +619,13 @@ class TotpTest(TestCase): self.assertEqual(TOTP(' 4aog gdbb qsyh ntuz ').key, KEY1_RAW) # .. w/ invalid char - self.assertRaises(Base32DecodeError, TOTP, 'ao!ggdbbqsyhntuz') + self.assertRaises(DecodeError, TOTP, 'ao!ggdbbqsyhntuz') # handle hex encoding self.assertEqual(TOTP('e01c630c2184b076ce99', 'hex').key, KEY1_RAW) # .. w/ invalid char - self.assertRaises(Base16DecodeError, TOTP, 'X01c630c2184b076ce99', 'hex') + self.assertRaises(DecodeError, TOTP, 'X01c630c2184b076ce99', 'hex') # handle raw bytes self.assertEqual(TOTP(KEY1_RAW, "raw").key, KEY1_RAW) @@ -749,7 +739,7 @@ class TotpTest(TestCase): otp = self.randotp(digits=7) # unicode & bytes - self.assertEqual(otp.normalize_token(u('1234567')), '1234567') + self.assertEqual(otp.normalize_token(u'1234567'), '1234567') self.assertEqual(otp.normalize_token(b'1234567'), '1234567') # int @@ -880,7 +870,7 @@ class TotpTest(TestCase): time = self.randtime() result = otp.generate(time) token = result.token - self.assertIsInstance(token, unicode) + self.assertIsInstance(token, str) start_time = result.counter * 30 # should generate same token for next 29s @@ -1204,8 +1194,8 @@ class TotpTest(TestCase): from_source = TOTP.from_source # uri (unicode) - otp = from_source(u("otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&" - "issuer=Example")) + otp = from_source(u"otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&" + "issuer=Example") self.assertEqual(otp.key, KEY4_RAW) # uri (bytes) @@ -1218,7 +1208,7 @@ class TotpTest(TestCase): self.assertEqual(otp.key, KEY4_RAW) # json (unicode) - otp = from_source(u('{"v": 1, "type": "totp", "key": "JBSWY3DPEHPK3PXP"}')) + otp = from_source(u'{"v": 1, "type": "totp", "key": "JBSWY3DPEHPK3PXP"}') self.assertEqual(otp.key, KEY4_RAW) # json (bytes) @@ -1239,7 +1229,7 @@ class TotpTest(TestCase): self.assertIs(otp2, otp1) # random string - self.assertRaises(ValueError, from_source, u("foo")) + self.assertRaises(ValueError, from_source, u"foo") self.assertRaises(ValueError, from_source, b"foo") #============================================================================= @@ -1278,8 +1268,8 @@ class TotpTest(TestCase): self.assertRaises(ValueError, from_uri, "otpauth://totp/Example:alice@google.com?digits=6") # undecodable secret - self.assertRaises(Base32DecodeError, from_uri, "otpauth://totp/Example:alice@google.com?" - "secret=JBSWY3DPEHP@3PXP") + self.assertRaises(DecodeError, from_uri, "otpauth://totp/Example:alice@google.com?" + "secret=JBSWY3DPEHP@3PXP") #-------------------------------------------------------------------------------- # label param @@ -1468,8 +1458,7 @@ class TotpTest(TestCase): self.assertRaises(ValueError, from_dict, dict(v=1, type="totp")) # undecodable secret - self.assertRaises(Base32DecodeError, from_dict, - dict(v=1, type="totp", key="JBSWY3DPEHP@3PXP")) + self.assertRaises(DecodeError, from_dict, dict(v=1, type="totp", key="JBSWY3DPEHP@3PXP")) #-------------------------------------------------------------------------------- # label & issuer params diff --git a/passlib/tests/test_utils.py b/passlib/tests/test_utils.py index 59ba160..2bc5c48 100644 --- a/passlib/tests/test_utils.py +++ b/passlib/tests/test_utils.py @@ -2,7 +2,6 @@ #============================================================================= # imports #============================================================================= -from __future__ import with_statement # core from functools import partial import warnings @@ -10,7 +9,7 @@ import warnings # pkg # module from passlib.utils import is_ascii_safe, to_bytes -from passlib.utils.compat import irange, PY2, PY3, u, unicode, join_bytes, PYPY +from passlib.utils.compat import join_bytes, PYPY from passlib.tests.utils import TestCase, hb, run_with_fixed_seeds #============================================================================= @@ -30,21 +29,24 @@ class MiscTest(TestCase): # test synthentic dir() dir(compat) - self.assertTrue('UnicodeIO' in dir(compat)) - self.assertTrue('irange' in dir(compat)) + # FIXME: find another lazy-loaded attr to check, all current ones removed after py2 comapt gone. + # self.assertTrue('UnicodeIO' in dir(compat)) def test_classproperty(self): from passlib.utils.decor import classproperty + def xprop_func(cls): + return cls.xvar + class test(object): xvar = 1 - @classproperty - def xprop(cls): - return cls.xvar + + xprop = classproperty(xprop_func) self.assertEqual(test.xprop, 1) + prop = test.__dict__['xprop'] - self.assertIs(prop.im_func, prop.__func__) + self.assertIs(prop.__func__, xprop_func) def test_deprecated_function(self): from passlib.utils.decor import deprecated_function @@ -81,10 +83,6 @@ class MiscTest(TestCase): self.assertEqual(d.value, 0) self.assertEqual(d.counter, 1) - prop = dummy.value - if not PY3: - self.assertIs(prop.im_func, prop.__func__) - def test_getrandbytes(self): """getrandbytes()""" from passlib.utils import getrandbytes @@ -124,11 +122,11 @@ class MiscTest(TestCase): # and hope that they're sufficient to test the range of behavior. # letters - x = wrapper(u('abc'), 32) - y = wrapper(u('abc'), 32) - self.assertIsInstance(x, unicode) + x = wrapper(u'abc', 32) + y = wrapper(u'abc', 32) + self.assertIsInstance(x, str) self.assertNotEqual(x,y) - self.assertEqual(sorted(set(x)), [u('a'),u('b'),u('c')]) + self.assertEqual(sorted(set(x)), [u'a',u'b',u'c']) # bytes x = wrapper(b'abc', 32) @@ -136,7 +134,7 @@ class MiscTest(TestCase): self.assertIsInstance(x, bytes) self.assertNotEqual(x,y) # NOTE: decoding this due to py3 bytes - self.assertEqual(sorted(set(x.decode("ascii"))), [u('a'),u('b'),u('c')]) + self.assertEqual(sorted(set(x.decode("ascii"))), [u'a',u'b',u'c']) def test_generate_password(self): """generate_password()""" @@ -200,18 +198,18 @@ class MiscTest(TestCase): # helpers to generate hashes & config strings to work with def get_hash(secret): - assert isinstance(secret, unicode) + assert isinstance(secret, str) hash = hasher.hash(secret) if isinstance(hash, bytes): # py2 hash = hash.decode("utf-8") - assert isinstance(hash, unicode) + assert isinstance(hash, str) return hash # test ascii password & return type - s1 = u("test") + s1 = u"test" h1 = get_hash(s1) result = safe_crypt(s1, h1) - self.assertIsInstance(result, unicode) + self.assertIsInstance(result, str) self.assertEqual(result, h1) self.assertEqual(safe_crypt(to_bytes(s1), to_bytes(h1)), h1) @@ -220,7 +218,7 @@ class MiscTest(TestCase): self.assertEqual(safe_crypt(s1, h1x), h1) # test utf-8 / unicode password - s2 = u('test\u1234') + s2 = u'test\u1234' h2 = get_hash(s2) self.assertEqual(safe_crypt(s2, h2), h2) self.assertEqual(safe_crypt(to_bytes(s2), to_bytes(h2)), h2) @@ -259,30 +257,29 @@ class MiscTest(TestCase): from passlib.utils import consteq, str_consteq # ensure error raises for wrong types - self.assertRaises(TypeError, consteq, u(''), b'') - self.assertRaises(TypeError, consteq, u(''), 1) - self.assertRaises(TypeError, consteq, u(''), None) + self.assertRaises(TypeError, consteq, u'', b'') + self.assertRaises(TypeError, consteq, u'', 1) + self.assertRaises(TypeError, consteq, u'', None) - self.assertRaises(TypeError, consteq, b'', u('')) + self.assertRaises(TypeError, consteq, b'', u'') self.assertRaises(TypeError, consteq, b'', 1) self.assertRaises(TypeError, consteq, b'', None) - self.assertRaises(TypeError, consteq, None, u('')) + self.assertRaises(TypeError, consteq, None, u'') self.assertRaises(TypeError, consteq, None, b'') - self.assertRaises(TypeError, consteq, 1, u('')) + self.assertRaises(TypeError, consteq, 1, u'') self.assertRaises(TypeError, consteq, 1, b'') def consteq_supports_string(value): - # under PY2, it supports all unicode strings (when present at all), - # under PY3, compare_digest() only supports ascii unicode strings. - # confirmed for: cpython 2.7.9, cpython 3.4, pypy, pypy3, pyston - return (consteq is str_consteq or PY2 or is_ascii_safe(value)) + # compare_digest() only supports ascii unicode strings. + # confirmed for: cpython 3.4, pypy3, pyston + return (consteq is str_consteq or is_ascii_safe(value)) # check equal inputs compare correctly for value in [ - u("a"), - u("abc"), - u("\xff\xa2\x12\x00")*10, + u"a", + u"abc", + u"\xff\xa2\x12\x00"*10, ]: if consteq_supports_string(value): self.assertTrue(consteq(value, value), "value %r:" % (value,)) @@ -296,18 +293,18 @@ class MiscTest(TestCase): # check non-equal inputs compare correctly for l,r in [ # check same-size comparisons with differing contents fail. - (u("a"), u("c")), - (u("abcabc"), u("zbaabc")), - (u("abcabc"), u("abzabc")), - (u("abcabc"), u("abcabz")), - ((u("\xff\xa2\x12\x00")*10)[:-1] + u("\x01"), - u("\xff\xa2\x12\x00")*10), + (u"a", u"c"), + (u"abcabc", u"zbaabc"), + (u"abcabc", u"abzabc"), + (u"abcabc", u"abcabz"), + ((u"\xff\xa2\x12\x00"*10)[:-1] + u"\x01", + u"\xff\xa2\x12\x00"*10), # check different-size comparisons fail. - (u(""), u("a")), - (u("abc"), u("abcdef")), - (u("abc"), u("defabc")), - (u("qwertyuiopasdfghjklzxcvbnm"), u("abc")), + (u"", u"a"), + (u"abc", u"abcdef"), + (u"abc", u"defabc"), + (u"qwertyuiopasdfghjklzxcvbnm", u"abc"), ]: if consteq_supports_string(l) and consteq_supports_string(r): self.assertFalse(consteq(l, r), "values %r %r:" % (l,r)) @@ -328,12 +325,12 @@ class MiscTest(TestCase): # NOTE: below code was used to generate stats for analysis ##from math import log as logb ##import timeit - ##multipliers = [ 1<<s for s in irange(9)] + ##multipliers = [ 1<<s for s in range(9)] ##correct = u"abcdefgh"*(1<<4) ##incorrect = u"abcdxfgh" ##print ##first = True - ##for run in irange(1): + ##for run in range(1): ## times = [] ## chars = [] ## for m in multipliers: @@ -365,73 +362,73 @@ class MiscTest(TestCase): self.assertRaises(TypeError, sp, b'') # empty strings - self.assertEqual(sp(u('')), u('')) - self.assertEqual(sp(u('\u00AD')), u('')) + self.assertEqual(sp(u''), u'') + self.assertEqual(sp(u'\u00AD'), u'') # verify B.1 chars are stripped, - self.assertEqual(sp(u("$\u00AD$\u200D$")), u("$$$")) + self.assertEqual(sp(u"$\u00AD$\u200D$"), u"$$$") # verify C.1.2 chars are replaced with space - self.assertEqual(sp(u("$ $\u00A0$\u3000$")), u("$ $ $ $")) + self.assertEqual(sp(u"$ $\u00A0$\u3000$"), u"$ $ $ $") # verify normalization to KC - self.assertEqual(sp(u("a\u0300")), u("\u00E0")) - self.assertEqual(sp(u("\u00E0")), u("\u00E0")) + self.assertEqual(sp(u"a\u0300"), u"\u00E0") + self.assertEqual(sp(u"\u00E0"), u"\u00E0") # verify various forbidden characters # control chars - self.assertRaises(ValueError, sp, u("\u0000")) - self.assertRaises(ValueError, sp, u("\u007F")) - self.assertRaises(ValueError, sp, u("\u180E")) - self.assertRaises(ValueError, sp, u("\uFFF9")) + self.assertRaises(ValueError, sp, u"\u0000") + self.assertRaises(ValueError, sp, u"\u007F") + self.assertRaises(ValueError, sp, u"\u180E") + self.assertRaises(ValueError, sp, u"\uFFF9") # private use - self.assertRaises(ValueError, sp, u("\uE000")) + self.assertRaises(ValueError, sp, u"\uE000") # non-characters - self.assertRaises(ValueError, sp, u("\uFDD0")) + self.assertRaises(ValueError, sp, u"\uFDD0") # surrogates - self.assertRaises(ValueError, sp, u("\uD800")) + self.assertRaises(ValueError, sp, u"\uD800") # non-plaintext chars - self.assertRaises(ValueError, sp, u("\uFFFD")) + self.assertRaises(ValueError, sp, u"\uFFFD") # non-canon - self.assertRaises(ValueError, sp, u("\u2FF0")) + self.assertRaises(ValueError, sp, u"\u2FF0") # change display properties - self.assertRaises(ValueError, sp, u("\u200E")) - self.assertRaises(ValueError, sp, u("\u206F")) + self.assertRaises(ValueError, sp, u"\u200E") + self.assertRaises(ValueError, sp, u"\u206F") # unassigned code points (as of unicode 3.2) - self.assertRaises(ValueError, sp, u("\u0900")) - self.assertRaises(ValueError, sp, u("\uFFF8")) + self.assertRaises(ValueError, sp, u"\u0900") + self.assertRaises(ValueError, sp, u"\uFFF8") # tagging characters - self.assertRaises(ValueError, sp, u("\U000e0001")) + self.assertRaises(ValueError, sp, u"\U000e0001") # verify bidi behavior # if starts with R/AL -- must end with R/AL - self.assertRaises(ValueError, sp, u("\u0627\u0031")) - self.assertEqual(sp(u("\u0627")), u("\u0627")) - self.assertEqual(sp(u("\u0627\u0628")), u("\u0627\u0628")) - self.assertEqual(sp(u("\u0627\u0031\u0628")), u("\u0627\u0031\u0628")) + self.assertRaises(ValueError, sp, u"\u0627\u0031") + self.assertEqual(sp(u"\u0627"), u"\u0627") + self.assertEqual(sp(u"\u0627\u0628"), u"\u0627\u0628") + self.assertEqual(sp(u"\u0627\u0031\u0628"), u"\u0627\u0031\u0628") # if starts with R/AL -- cannot contain L - self.assertRaises(ValueError, sp, u("\u0627\u0041\u0628")) + self.assertRaises(ValueError, sp, u"\u0627\u0041\u0628") # if doesn't start with R/AL -- can contain R/AL, but L & EN allowed - self.assertRaises(ValueError, sp, u("x\u0627z")) - self.assertEqual(sp(u("x\u0041z")), u("x\u0041z")) + self.assertRaises(ValueError, sp, u"x\u0627z") + self.assertEqual(sp(u"x\u0041z"), u"x\u0041z") #------------------------------------------------------ # examples pulled from external sources, to be thorough #------------------------------------------------------ # rfc 4031 section 3 examples - self.assertEqual(sp(u("I\u00ADX")), u("IX")) # strip SHY - self.assertEqual(sp(u("user")), u("user")) # unchanged - self.assertEqual(sp(u("USER")), u("USER")) # case preserved - self.assertEqual(sp(u("\u00AA")), u("a")) # normalize to KC form - self.assertEqual(sp(u("\u2168")), u("IX")) # normalize to KC form - self.assertRaises(ValueError, sp, u("\u0007")) # forbid control chars - self.assertRaises(ValueError, sp, u("\u0627\u0031")) # invalid bidi + self.assertEqual(sp(u"I\u00ADX"), u"IX") # strip SHY + self.assertEqual(sp(u"user"), u"user") # unchanged + self.assertEqual(sp(u"USER"), u"USER") # case preserved + self.assertEqual(sp(u"\u00AA"), u"a") # normalize to KC form + self.assertEqual(sp(u"\u2168"), u"IX") # normalize to KC form + self.assertRaises(ValueError, sp, u"\u0007") # forbid control chars + self.assertRaises(ValueError, sp, u"\u0627\u0031") # invalid bidi # rfc 3454 section 6 examples # starts with RAL char, must end with RAL char - self.assertRaises(ValueError, sp, u("\u0627\u0031")) - self.assertEqual(sp(u("\u0627\u0031\u0628")), u("\u0627\u0031\u0628")) + self.assertRaises(ValueError, sp, u"\u0627\u0031") + self.assertEqual(sp(u"\u0627\u0031\u0628"), u"\u0627\u0031\u0628") def test_splitcomma(self): from passlib.utils import splitcomma @@ -569,31 +566,24 @@ class CodecTest(TestCase): def test_bytes(self): """test b() helper, bytes and native str type""" - if PY3: - import builtins - self.assertIs(bytes, builtins.bytes) - else: - import __builtin__ as builtins - self.assertIs(bytes, builtins.str) + import builtins + self.assertIs(bytes, builtins.bytes) self.assertIsInstance(b'', bytes) self.assertIsInstance(b'\x00\xff', bytes) - if PY3: - self.assertEqual(b'\x00\xff'.decode("latin-1"), "\x00\xff") - else: - self.assertEqual(b'\x00\xff', "\x00\xff") + self.assertEqual(b'\x00\xff'.decode("latin-1"), "\x00\xff") def test_to_bytes(self): """test to_bytes()""" from passlib.utils import to_bytes # check unicode inputs - self.assertEqual(to_bytes(u('abc')), b'abc') - self.assertEqual(to_bytes(u('\x00\xff')), b'\x00\xc3\xbf') + self.assertEqual(to_bytes(u'abc'), b'abc') + self.assertEqual(to_bytes(u'\x00\xff'), b'\x00\xc3\xbf') # check unicode w/ encodings - self.assertEqual(to_bytes(u('\x00\xff'), 'latin-1'), b'\x00\xff') - self.assertRaises(ValueError, to_bytes, u('\x00\xff'), 'ascii') + self.assertEqual(to_bytes(u'\x00\xff', 'latin-1'), b'\x00\xff') + self.assertRaises(ValueError, to_bytes, u'\x00\xff', 'ascii') # check bytes inputs self.assertEqual(to_bytes(b'abc'), b'abc') @@ -617,17 +607,17 @@ class CodecTest(TestCase): from passlib.utils import to_unicode # check unicode inputs - self.assertEqual(to_unicode(u('abc')), u('abc')) - self.assertEqual(to_unicode(u('\x00\xff')), u('\x00\xff')) + self.assertEqual(to_unicode(u'abc'), u'abc') + self.assertEqual(to_unicode(u'\x00\xff'), u'\x00\xff') # check unicode input ignores encoding - self.assertEqual(to_unicode(u('\x00\xff'), "ascii"), u('\x00\xff')) + self.assertEqual(to_unicode(u'\x00\xff', "ascii"), u'\x00\xff') # check bytes input - self.assertEqual(to_unicode(b'abc'), u('abc')) - self.assertEqual(to_unicode(b'\x00\xc3\xbf'), u('\x00\xff')) + self.assertEqual(to_unicode(b'abc'), u'abc') + self.assertEqual(to_unicode(b'\x00\xc3\xbf'), u'\x00\xff') self.assertEqual(to_unicode(b'\x00\xff', 'latin-1'), - u('\x00\xff')) + u'\x00\xff') self.assertRaises(ValueError, to_unicode, b'\x00\xff') # check other @@ -639,28 +629,21 @@ class CodecTest(TestCase): from passlib.utils import to_native_str # test plain ascii - self.assertEqual(to_native_str(u('abc'), 'ascii'), 'abc') + self.assertEqual(to_native_str(u'abc', 'ascii'), 'abc') self.assertEqual(to_native_str(b'abc', 'ascii'), 'abc') # test invalid ascii - if PY3: - self.assertEqual(to_native_str(u('\xE0'), 'ascii'), '\xE0') - self.assertRaises(UnicodeDecodeError, to_native_str, b'\xC3\xA0', - 'ascii') - else: - self.assertRaises(UnicodeEncodeError, to_native_str, u('\xE0'), - 'ascii') - self.assertEqual(to_native_str(b'\xC3\xA0', 'ascii'), '\xC3\xA0') + self.assertEqual(to_native_str(u'\xE0', 'ascii'), '\xE0') + self.assertRaises(UnicodeDecodeError, to_native_str, b'\xC3\xA0', + 'ascii') # test latin-1 - self.assertEqual(to_native_str(u('\xE0'), 'latin-1'), '\xE0') + self.assertEqual(to_native_str(u'\xE0', 'latin-1'), '\xE0') self.assertEqual(to_native_str(b'\xE0', 'latin-1'), '\xE0') # test utf-8 - self.assertEqual(to_native_str(u('\xE0'), 'utf-8'), - '\xE0' if PY3 else '\xC3\xA0') - self.assertEqual(to_native_str(b'\xC3\xA0', 'utf-8'), - '\xE0' if PY3 else '\xC3\xA0') + self.assertEqual(to_native_str(u'\xE0', 'utf-8'), '\xE0') + self.assertEqual(to_native_str(b'\xC3\xA0', 'utf-8'), '\xE0') # other types rejected self.assertRaises(TypeError, to_native_str, None, 'ascii') @@ -669,9 +652,9 @@ class CodecTest(TestCase): """test is_ascii_safe()""" from passlib.utils import is_ascii_safe self.assertTrue(is_ascii_safe(b"\x00abc\x7f")) - self.assertTrue(is_ascii_safe(u("\x00abc\x7f"))) + self.assertTrue(is_ascii_safe(u"\x00abc\x7f")) self.assertFalse(is_ascii_safe(b"\x00abc\x80")) - self.assertFalse(is_ascii_safe(u("\x00abc\x80"))) + self.assertFalse(is_ascii_safe(u"\x00abc\x80")) def test_is_same_codec(self): """test is_same_codec()""" @@ -714,15 +697,15 @@ class Base64EngineTest(TestCase): # accept bytes or unicode self.assertEqual(ab64_decode(b"abc"), hb("69b7")) - self.assertEqual(ab64_decode(u("abc")), hb("69b7")) + self.assertEqual(ab64_decode(u"abc"), hb("69b7")) # reject non-ascii unicode - self.assertRaises(ValueError, ab64_decode, u("ab\xff")) + self.assertRaises(ValueError, ab64_decode, u"ab\xff") # underlying a2b_ascii treats non-base64 chars as "Incorrect padding" self.assertRaises(TypeError, ab64_decode, b"ab\xff") self.assertRaises(TypeError, ab64_decode, b"ab!") - self.assertRaises(TypeError, ab64_decode, u("ab!")) + self.assertRaises(TypeError, ab64_decode, u"ab!") # insert correct padding, handle dirty padding bits self.assertEqual(ab64_decode(b"abcd"), hb("69b71d")) # 0 mod 4 @@ -744,8 +727,7 @@ class Base64EngineTest(TestCase): self.assertEqual(ab64_encode(hb("69b7")), b"abc") # reject unicode - self.assertRaises(TypeError if PY3 else UnicodeEncodeError, - ab64_encode, hb("69b7").decode("latin-1")) + self.assertRaises(TypeError, ab64_encode, hb("69b7").decode("latin-1")) # insert correct padding before decoding self.assertEqual(ab64_encode(hb("69b71d")), b"abcd") # 0 mod 4 @@ -761,15 +743,15 @@ class Base64EngineTest(TestCase): # accept bytes or unicode self.assertEqual(b64s_decode(b"abc"), hb("69b7")) - self.assertEqual(b64s_decode(u("abc")), hb("69b7")) + self.assertEqual(b64s_decode(u"abc"), hb("69b7")) # reject non-ascii unicode - self.assertRaises(ValueError, b64s_decode, u("ab\xff")) + self.assertRaises(ValueError, b64s_decode, u"ab\xff") # underlying a2b_ascii treats non-base64 chars as "Incorrect padding" self.assertRaises(TypeError, b64s_decode, b"ab\xff") self.assertRaises(TypeError, b64s_decode, b"ab!") - self.assertRaises(TypeError, b64s_decode, u("ab!")) + self.assertRaises(TypeError, b64s_decode, u"ab!") # insert correct padding, handle dirty padding bits self.assertEqual(b64s_decode(b"abcd"), hb("69b71d")) # 0 mod 4 @@ -786,8 +768,7 @@ class Base64EngineTest(TestCase): self.assertEqual(b64s_encode(hb("69b7")), b"abc") # reject unicode - self.assertRaises(TypeError if PY3 else UnicodeEncodeError, - b64s_encode, hb("69b7").decode("latin-1")) + self.assertRaises(TypeError, b64s_encode, hb("69b7").decode("latin-1")) # insert correct padding before decoding self.assertEqual(b64s_encode(hb("69b71d")), b"abcd") # 0 mod 4 @@ -835,7 +816,7 @@ class _Base64Test(TestCase): """test encode_bytes() with bad input""" engine = self.engine encode = engine.encode_bytes - self.assertRaises(TypeError, encode, u('\x00')) + self.assertRaises(TypeError, encode, u'\x00') self.assertRaises(TypeError, encode, None) #=================================================================== @@ -851,7 +832,7 @@ class _Base64Test(TestCase): def test_decode_bytes_padding(self): """test decode_bytes() ignores padding bits""" - bchr = (lambda v: bytes([v])) if PY3 else chr + bchr = lambda v: bytes([v]) engine = self.engine m = self.m decode = engine.decode_bytes @@ -901,7 +882,7 @@ class _Base64Test(TestCase): from passlib.utils import getrandbytes, getrandstr rng = self.getRandom() saw_zero = False - for i in irange(500): + for i in range(500): # # test raw -> encode() -> decode() -> raw # @@ -999,7 +980,7 @@ class _Base64Test(TestCase): out = engine.decode_bytes(tmp) self.assertEqual(out, result) - self.assertRaises(TypeError, engine.encode_transposed_bytes, u("a"), []) + self.assertRaises(TypeError, engine.encode_transposed_bytes, u"a", []) def test_decode_transposed_bytes(self): """test decode_transposed_bytes()""" @@ -1050,7 +1031,7 @@ class _Base64Test(TestCase): # do random testing. from passlib.utils import getrandstr - for i in irange(100): + for i in range(100): # generate random value, encode, and then decode value = rng.randint(0, upper-1) encoded = encode(value) diff --git a/passlib/tests/test_utils_handlers.py b/passlib/tests/test_utils_handlers.py index 19cd4ca..f665aac 100644 --- a/passlib/tests/test_utils_handlers.py +++ b/passlib/tests/test_utils_handlers.py @@ -2,7 +2,6 @@ #============================================================================= # imports #============================================================================= -from __future__ import with_statement # core import re import hashlib @@ -12,11 +11,8 @@ import warnings # pkg from passlib.hash import ldap_md5, sha256_crypt from passlib.exc import MissingBackendError, PasslibHashWarning -from passlib.utils.compat import str_to_uascii, \ - uascii_to_str, unicode import passlib.utils.handlers as uh from passlib.tests.utils import HandlerCase, TestCase -from passlib.utils.compat import u # module log = getLogger(__name__) @@ -50,27 +46,27 @@ class SkeletonTest(TestCase): class d1(uh.StaticHandler): name = "d1" context_kwds = ("flag",) - _hash_prefix = u("_") - checksum_chars = u("ab") + _hash_prefix = u"_" + checksum_chars = u"ab" checksum_size = 1 def __init__(self, flag=False, **kwds): - super(d1, self).__init__(**kwds) + super().__init__(**kwds) self.flag = flag def _calc_checksum(self, secret): - return u('b') if self.flag else u('a') + return u'b' if self.flag else u'a' # check default identify method - self.assertTrue(d1.identify(u('_a'))) + self.assertTrue(d1.identify(u'_a')) self.assertTrue(d1.identify(b'_a')) - self.assertTrue(d1.identify(u('_b'))) + self.assertTrue(d1.identify(u'_b')) - self.assertFalse(d1.identify(u('_c'))) + self.assertFalse(d1.identify(u'_c')) self.assertFalse(d1.identify(b'_c')) - self.assertFalse(d1.identify(u('a'))) - self.assertFalse(d1.identify(u('b'))) - self.assertFalse(d1.identify(u('c'))) + self.assertFalse(d1.identify(u'a')) + self.assertFalse(d1.identify(u'b')) + self.assertFalse(d1.identify(u'c')) self.assertRaises(TypeError, d1.identify, None) self.assertRaises(TypeError, d1.identify, 1) @@ -79,65 +75,17 @@ class SkeletonTest(TestCase): # check default verify method self.assertTrue(d1.verify('s', b'_a')) - self.assertTrue(d1.verify('s',u('_a'))) + self.assertTrue(d1.verify('s',u'_a')) self.assertFalse(d1.verify('s', b'_b')) - self.assertFalse(d1.verify('s',u('_b'))) + self.assertFalse(d1.verify('s',u'_b')) self.assertTrue(d1.verify('s', b'_b', flag=True)) self.assertRaises(ValueError, d1.verify, 's', b'_c') - self.assertRaises(ValueError, d1.verify, 's', u('_c')) + self.assertRaises(ValueError, d1.verify, 's', u'_c') # check default hash method self.assertEqual(d1.hash('s'), '_a') self.assertEqual(d1.hash('s', flag=True), '_b') - def test_01_calc_checksum_hack(self): - """test StaticHandler legacy attr""" - # release 1.5 StaticHandler required genhash(), - # not _calc_checksum, be implemented. we have backward compat wrapper, - # this tests that it works. - - class d1(uh.StaticHandler): - name = "d1" - - @classmethod - def identify(cls, hash): - if not hash or len(hash) != 40: - return False - try: - int(hash, 16) - except ValueError: - return False - return True - - @classmethod - def genhash(cls, secret, hash): - if secret is None: - raise TypeError("no secret provided") - if isinstance(secret, unicode): - secret = secret.encode("utf-8") - # NOTE: have to support hash=None since this is test of legacy 1.5 api - if hash is not None and not cls.identify(hash): - raise ValueError("invalid hash") - return hashlib.sha1(b"xyz" + secret).hexdigest() - - @classmethod - def verify(cls, secret, hash): - if hash is None: - raise ValueError("no hash specified") - return cls.genhash(secret, hash) == hash.lower() - - # hash should issue api warnings, but everything else should be fine. - with self.assertWarningList("d1.*should be updated.*_calc_checksum"): - hash = d1.hash("test") - self.assertEqual(hash, '7c622762588a0e5cc786ad0a143156f9fd38eea3') - - self.assertTrue(d1.verify("test", hash)) - self.assertFalse(d1.verify("xtest", hash)) - - # not defining genhash either, however, should cause NotImplementedError - del d1.genhash - self.assertRaises(NotImplementedError, d1.hash, 'test') - #=================================================================== # GenericHandler & mixins #=================================================================== @@ -148,7 +96,7 @@ class SkeletonTest(TestCase): def from_string(cls, hash): if isinstance(hash, bytes): hash = hash.decode("ascii") - if hash == u('a'): + if hash == u'a': return cls(checksum=hash) else: raise ValueError @@ -161,7 +109,7 @@ class SkeletonTest(TestCase): self.assertFalse(d1.identify('b')) # check regexp - d1._hash_regex = re.compile(u('@.')) + d1._hash_regex = re.compile(u'@.') self.assertRaises(TypeError, d1.identify, None) self.assertRaises(TypeError, d1.identify, 1) self.assertTrue(d1.identify('@a')) @@ -169,7 +117,7 @@ class SkeletonTest(TestCase): del d1._hash_regex # check ident-based - d1.ident = u('!') + d1.ident = u'!' self.assertRaises(TypeError, d1.identify, None) self.assertRaises(TypeError, d1.identify, 1) self.assertTrue(d1.identify('!a')) @@ -182,35 +130,35 @@ class SkeletonTest(TestCase): class d1(uh.GenericHandler): name = 'd1' checksum_size = 4 - checksum_chars = u('xz') + checksum_chars = u'xz' def norm_checksum(checksum=None, **k): return d1(checksum=checksum, **k).checksum # too small - self.assertRaises(ValueError, norm_checksum, u('xxx')) + self.assertRaises(ValueError, norm_checksum, u'xxx') # right size - self.assertEqual(norm_checksum(u('xxxx')), u('xxxx')) - self.assertEqual(norm_checksum(u('xzxz')), u('xzxz')) + self.assertEqual(norm_checksum(u'xxxx'), u'xxxx') + self.assertEqual(norm_checksum(u'xzxz'), u'xzxz') # too large - self.assertRaises(ValueError, norm_checksum, u('xxxxx')) + self.assertRaises(ValueError, norm_checksum, u'xxxxx') # wrong chars - self.assertRaises(ValueError, norm_checksum, u('xxyx')) + self.assertRaises(ValueError, norm_checksum, u'xxyx') # wrong type self.assertRaises(TypeError, norm_checksum, b'xxyx') # relaxed # NOTE: this could be turned back on if we test _norm_checksum() directly... - #with self.assertWarningList("checksum should be unicode"): - # self.assertEqual(norm_checksum(b'xxzx', relaxed=True), u('xxzx')) + #with self.assertWarningList("checksum should be str"): + # self.assertEqual(norm_checksum(b'xxzx', relaxed=True), u'xxzx') #self.assertRaises(TypeError, norm_checksum, 1, relaxed=True) # test _stub_checksum behavior - self.assertEqual(d1()._stub_checksum, u('xxxx')) + self.assertEqual(d1()._stub_checksum, u'xxxx') def test_12_norm_checksum_raw(self): """test GenericHandler + HasRawChecksum mixin""" @@ -224,11 +172,11 @@ class SkeletonTest(TestCase): # test bytes self.assertEqual(norm_checksum(b'1234'), b'1234') - # test unicode - self.assertRaises(TypeError, norm_checksum, u('xxyx')) + # test str + self.assertRaises(TypeError, norm_checksum, u'xxyx') # NOTE: this could be turned back on if we test _norm_checksum() directly... - # self.assertRaises(TypeError, norm_checksum, u('xxyx'), relaxed=True) + # self.assertRaises(TypeError, norm_checksum, u'xxyx', relaxed=True) # test _stub_checksum behavior self.assertEqual(d1()._stub_checksum, b'\x00'*4) @@ -487,9 +435,9 @@ class SkeletonTest(TestCase): class d1(uh.HasManyIdents, uh.GenericHandler): name = 'd1' setting_kwds = ('ident',) - default_ident = u("!A") - ident_values = (u("!A"), u("!B")) - ident_aliases = { u("A"): u("!A")} + default_ident = u"!A" + ident_values = (u"!A", u"!B") + ident_aliases = { u"A": u"!A"} def norm_ident(**k): return d1(**k).ident @@ -497,25 +445,25 @@ class SkeletonTest(TestCase): # check ident=None self.assertRaises(TypeError, norm_ident) self.assertRaises(TypeError, norm_ident, ident=None) - self.assertEqual(norm_ident(use_defaults=True), u('!A')) + self.assertEqual(norm_ident(use_defaults=True), u'!A') # check valid idents - self.assertEqual(norm_ident(ident=u('!A')), u('!A')) - self.assertEqual(norm_ident(ident=u('!B')), u('!B')) - self.assertRaises(ValueError, norm_ident, ident=u('!C')) + self.assertEqual(norm_ident(ident=u'!A'), u'!A') + self.assertEqual(norm_ident(ident=u'!B'), u'!B') + self.assertRaises(ValueError, norm_ident, ident=u'!C') # check aliases - self.assertEqual(norm_ident(ident=u('A')), u('!A')) + self.assertEqual(norm_ident(ident=u'A'), u'!A') # check invalid idents - self.assertRaises(ValueError, norm_ident, ident=u('B')) + self.assertRaises(ValueError, norm_ident, ident=u'B') # check identify is honoring ident system - self.assertTrue(d1.identify(u("!Axxx"))) - self.assertTrue(d1.identify(u("!Bxxx"))) - self.assertFalse(d1.identify(u("!Cxxx"))) - self.assertFalse(d1.identify(u("A"))) - self.assertFalse(d1.identify(u(""))) + self.assertTrue(d1.identify(u"!Axxx")) + self.assertTrue(d1.identify(u"!Bxxx")) + self.assertFalse(d1.identify(u"!Cxxx")) + self.assertFalse(d1.identify(u"A")) + self.assertFalse(d1.identify(u"")) self.assertRaises(TypeError, d1.identify, None) self.assertRaises(TypeError, d1.identify, 1) @@ -538,12 +486,12 @@ class SkeletonTest(TestCase): # simple hash w/ salt result = hash.des_crypt.parsehash("OgAwTx2l6NADI") - self.assertEqual(result, {'checksum': u('AwTx2l6NADI'), 'salt': u('Og')}) + self.assertEqual(result, {'checksum': u'AwTx2l6NADI', 'salt': u'Og'}) # parse rounds and extra implicit_rounds flag h = '$5$LKO/Ute40T3FNF95$U0prpBQd4PloSGU0pnpM4z9wKn4vZ1.jsrzQfPqxph9' - s = u('LKO/Ute40T3FNF95') - c = u('U0prpBQd4PloSGU0pnpM4z9wKn4vZ1.jsrzQfPqxph9') + s = u'LKO/Ute40T3FNF95' + c = u'U0prpBQd4PloSGU0pnpM4z9wKn4vZ1.jsrzQfPqxph9' result = hash.sha256_crypt.parsehash(h) self.assertEqual(result, dict(salt=s, rounds=5000, implicit_rounds=True, checksum=c)) @@ -555,14 +503,14 @@ class SkeletonTest(TestCase): # sanitize result = hash.sha256_crypt.parsehash(h, sanitize=True) self.assertEqual(result, dict(rounds=5000, implicit_rounds=True, - salt=u('LK**************'), - checksum=u('U0pr***************************************'))) + salt=u'LK**************', + checksum=u'U0pr***************************************')) # parse w/o implicit rounds flag result = hash.sha256_crypt.parsehash('$5$rounds=10428$uy/jIAhCetNCTtb0$YWvUOXbkqlqhyoPMpN8BMe.ZGsGx2aBvxTvDFI613c3') self.assertEqual(result, dict( - checksum=u('YWvUOXbkqlqhyoPMpN8BMe.ZGsGx2aBvxTvDFI613c3'), - salt=u('uy/jIAhCetNCTtb0'), + checksum=u'YWvUOXbkqlqhyoPMpN8BMe.ZGsGx2aBvxTvDFI613c3', + salt=u'uy/jIAhCetNCTtb0', rounds=10428, )) @@ -578,9 +526,9 @@ class SkeletonTest(TestCase): # sanitizing of raw checksums & salts result = hash.pbkdf2_sha1.parsehash(h1, sanitize=True) self.assertEqual(result, dict( - checksum=u('O26************************'), + checksum=u'O26************************', rounds=60000, - salt=u('Do********************'), + salt=u'Do********************', )) def test_92_bitsize(self): @@ -722,7 +670,7 @@ class PrefixWrapperTest(TestCase): def test_12_ident(self): # test ident is proxied h = uh.PrefixWrapper("h2", "ldap_md5", "{XXX}") - self.assertEqual(h.ident, u("{XXX}{MD5}")) + self.assertEqual(h.ident, u"{XXX}{MD5}") self.assertIs(h.ident_values, None) # test lack of ident means no proxy @@ -737,7 +685,7 @@ class PrefixWrapperTest(TestCase): # test custom ident overrides default h = uh.PrefixWrapper("h3", "ldap_md5", "{XXX}", ident="{X") - self.assertEqual(h.ident, u("{X")) + self.assertEqual(h.ident, u"{X") self.assertIs(h.ident_values, None) # test custom ident must match @@ -750,11 +698,11 @@ class PrefixWrapperTest(TestCase): # test ident_values is proxied h = uh.PrefixWrapper("h4", "phpass", "{XXX}") self.assertIs(h.ident, None) - self.assertEqual(h.ident_values, (u("{XXX}$P$"), u("{XXX}$H$"))) + self.assertEqual(h.ident_values, (u"{XXX}$P$", u"{XXX}$H$")) # test ident=True means use prefix even if hash has no ident. h = uh.PrefixWrapper("h5", "des_crypt", "{XXX}", ident=True) - self.assertEqual(h.ident, u("{XXX}")) + self.assertEqual(h.ident, u"{XXX}") self.assertIs(h.ident_values, None) # ... but requires prefix @@ -763,7 +711,7 @@ class PrefixWrapperTest(TestCase): # orig_prefix + HasManyIdent - warning with self.assertWarningList("orig_prefix.*may not work correctly"): h = uh.PrefixWrapper("h7", "phpass", orig_prefix="$", prefix="?") - self.assertEqual(h.ident_values, None) # TODO: should output (u("?P$"), u("?H$"))) + self.assertEqual(h.ident_values, None) # TODO: should output (u"?P$", u"?H$")) self.assertEqual(h.ident, None) def test_13_repr(self): @@ -796,10 +744,11 @@ class UnsaltedHash(uh.StaticHandler): checksum_size = 40 def _calc_checksum(self, secret): - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") data = b"boblious" + secret - return str_to_uascii(hashlib.sha1(data).hexdigest()) + return hashlib.sha1(data).hexdigest() + class SaltedHash(uh.HasSalt, uh.GenericHandler): """test algorithm with a salt""" @@ -811,7 +760,7 @@ class SaltedHash(uh.HasSalt, uh.GenericHandler): checksum_size = 40 salt_chars = checksum_chars = uh.LOWER_HEX_CHARS - _hash_regex = re.compile(u("^@salt[0-9a-f]{42,44}$")) + _hash_regex = re.compile(u"^@salt[0-9a-f]{42,44}$") @classmethod def from_string(cls, hash): @@ -822,14 +771,15 @@ class SaltedHash(uh.HasSalt, uh.GenericHandler): return cls(salt=hash[5:-40], checksum=hash[-40:]) def to_string(self): - hash = u("@salt%s%s") % (self.salt, self.checksum) - return uascii_to_str(hash) + hash = u"@salt%s%s" % (self.salt, self.checksum) + return hash def _calc_checksum(self, secret): - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") data = self.salt.encode("ascii") + secret + self.salt.encode("ascii") - return str_to_uascii(hashlib.sha1(data).hexdigest()) + return hashlib.sha1(data).hexdigest() + #============================================================================= # test sample algorithms - really a self-test of HandlerCase @@ -838,7 +788,7 @@ class SaltedHash(uh.HasSalt, uh.GenericHandler): # TODO: provide data samples for algorithms # (positive knowns, negative knowns, invalid identify) -UPASS_TEMP = u('\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2') +UPASS_TEMP = u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2' class UnsaltedHashTest(HandlerCase): handler = UnsaltedHash diff --git a/passlib/tests/test_utils_md4.py b/passlib/tests/test_utils_md4.py index 5d824a1..e3e04d4 100644 --- a/passlib/tests/test_utils_md4.py +++ b/passlib/tests/test_utils_md4.py @@ -29,7 +29,7 @@ class Legacy_MD4_Test(_Common_MD4_Test): descriptionPrefix = "passlib.utils.md4.md4()" def setUp(self): - super(Legacy_MD4_Test, self).setUp() + super().setUp() warnings.filterwarnings("ignore", ".*passlib.utils.md4.*deprecated", DeprecationWarning) def get_md4_const(self): diff --git a/passlib/tests/test_utils_pbkdf2.py b/passlib/tests/test_utils_pbkdf2.py index 443eb53..0e10248 100644 --- a/passlib/tests/test_utils_pbkdf2.py +++ b/passlib/tests/test_utils_pbkdf2.py @@ -12,72 +12,16 @@ passlib.tests -- tests for passlib.utils.pbkdf2 #============================================================================= # imports #============================================================================= -from __future__ import with_statement # core import hashlib import warnings # site # pkg # module -from passlib.utils.compat import u, JYTHON +from passlib.utils.compat import JYTHON from passlib.tests.utils import TestCase, hb #============================================================================= -# test assorted crypto helpers -#============================================================================= -class UtilsTest(TestCase): - """test various utils functions""" - descriptionPrefix = "passlib.utils.pbkdf2" - - ndn_formats = ["hashlib", "iana"] - ndn_values = [ - # (iana name, hashlib name, ... other unnormalized names) - ("md5", "md5", "SCRAM-MD5-PLUS", "MD-5"), - ("sha1", "sha-1", "SCRAM-SHA-1", "SHA1"), - ("sha256", "sha-256", "SHA_256", "sha2-256"), - ("ripemd160", "ripemd-160", "SCRAM-RIPEMD-160", "RIPEmd160", - # NOTE: there was an older "RIPEMD" & "RIPEMD-128", but python treates "RIPEMD" - # as alias for "RIPEMD-160" - "ripemd", "SCRAM-RIPEMD"), - ("test128", "test-128", "TEST128"), - ("test2", "test2", "TEST-2"), - ("test3_128", "test3-128", "TEST-3-128"), - ] - - def setUp(self): - super(UtilsTest, self).setUp() - warnings.filterwarnings("ignore", ".*passlib.utils.pbkdf2.*deprecated", DeprecationWarning) - - def test_norm_hash_name(self): - """norm_hash_name()""" - from itertools import chain - from passlib.utils.pbkdf2 import norm_hash_name - from passlib.crypto.digest import _known_hash_names - - # test formats - for format in self.ndn_formats: - norm_hash_name("md4", format) - self.assertRaises(ValueError, norm_hash_name, "md4", None) - self.assertRaises(ValueError, norm_hash_name, "md4", "fake") - - # test types - self.assertEqual(norm_hash_name(u("MD4")), "md4") - self.assertEqual(norm_hash_name(b"MD4"), "md4") - self.assertRaises(TypeError, norm_hash_name, None) - - # test selected results - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", '.*unknown hash') - for row in chain(_known_hash_names, self.ndn_values): - for idx, format in enumerate(self.ndn_formats): - correct = row[idx] - for value in row: - result = norm_hash_name(value, format) - self.assertEqual(result, correct, - "name=%r, format=%r:" % (value, - format)) - -#============================================================================= # test PBKDF1 support #============================================================================= class Pbkdf1_Test(TestCase): @@ -108,7 +52,7 @@ class Pbkdf1_Test(TestCase): ) def setUp(self): - super(Pbkdf1_Test, self).setUp() + super().setUp() warnings.filterwarnings("ignore", ".*passlib.utils.pbkdf2.*deprecated", DeprecationWarning) def test_known(self): @@ -263,7 +207,7 @@ class Pbkdf2_Test(TestCase): ] def setUp(self): - super(Pbkdf2_Test, self).setUp() + super().setUp() warnings.filterwarnings("ignore", ".*passlib.utils.pbkdf2.*deprecated", DeprecationWarning) def test_known(self): diff --git a/passlib/tests/test_win32.py b/passlib/tests/test_win32.py deleted file mode 100644 index e818b62..0000000 --- a/passlib/tests/test_win32.py +++ /dev/null @@ -1,50 +0,0 @@ -"""tests for passlib.win32 -- (c) Assurance Technologies 2003-2009""" -#============================================================================= -# imports -#============================================================================= -# core -import warnings -# site -# pkg -from passlib.tests.utils import TestCase -# module -from passlib.utils.compat import u - -#============================================================================= -# -#============================================================================= -class UtilTest(TestCase): - """test util funcs in passlib.win32""" - - ##test hashes from http://msdn.microsoft.com/en-us/library/cc245828(v=prot.10).aspx - ## among other places - - def setUp(self): - super(UtilTest, self).setUp() - warnings.filterwarnings("ignore", - "the 'passlib.win32' module is deprecated") - - def test_lmhash(self): - from passlib.win32 import raw_lmhash - for secret, hash in [ - ("OLDPASSWORD", u("c9b81d939d6fd80cd408e6b105741864")), - ("NEWPASSWORD", u('09eeab5aa415d6e4d408e6b105741864')), - ("welcome", u("c23413a8a1e7665faad3b435b51404ee")), - ]: - result = raw_lmhash(secret, hex=True) - self.assertEqual(result, hash) - - def test_nthash(self): - warnings.filterwarnings("ignore", - r"nthash\.raw_nthash\(\) is deprecated") - from passlib.win32 import raw_nthash - for secret, hash in [ - ("OLDPASSWORD", u("6677b2c394311355b54f25eec5bfacf5")), - ("NEWPASSWORD", u("256781a62031289d3c2c98c14f1efc8c")), - ]: - result = raw_nthash(secret, hex=True) - self.assertEqual(result, hash) - -#============================================================================= -# eof -#============================================================================= diff --git a/passlib/tests/tox_support.py b/passlib/tests/tox_support.py index 43170bc..d0dcd4e 100644 --- a/passlib/tests/tox_support.py +++ b/passlib/tests/tox_support.py @@ -14,7 +14,6 @@ import re import logging; log = logging.getLogger(__name__) # site # pkg -from passlib.utils.compat import print_ # local __all__ = [ ] @@ -40,7 +39,7 @@ def do_hash_tests(*args): from passlib.tests import test_handlers names = [TH_PATH + ":" + name + suffix for name in dir(test_handlers) if not name.startswith("_") and any(re.match(arg,name) for arg in args)] - print_("\n".join(names)) + print("\n".join(names)) return not names def do_preset_tests(name): @@ -48,7 +47,7 @@ def do_preset_tests(name): if name == "django" or name == "django-hashes": do_hash_tests("django_.*_test", "hex_md5_test") if name == "django": - print_("passlib.tests.test_ext_django") + print("passlib.tests.test_ext_django") else: raise ValueError("unknown name: %r" % name) diff --git a/passlib/tests/utils.py b/passlib/tests/utils.py index 79a9f9f..9ade9de 100644 --- a/passlib/tests/utils.py +++ b/passlib/tests/utils.py @@ -2,7 +2,6 @@ #============================================================================= # imports #============================================================================= -from __future__ import with_statement # core from binascii import unhexlify import contextlib @@ -16,8 +15,9 @@ import sys import tempfile import threading import time +import unittest from passlib.exc import PasslibHashWarning, PasslibConfigWarning -from passlib.utils.compat import PY3, JYTHON +from passlib.utils.compat import JYTHON import warnings from warnings import warn # site @@ -25,11 +25,9 @@ from warnings import warn from passlib import exc from passlib.exc import MissingBackendError import passlib.registry as registry -from passlib.tests.backports import TestCase as _TestCase, skip, skipIf, skipUnless, SkipTest from passlib.utils import has_rounds_info, has_salt_info, rounds_cost_values, \ rng as sys_rng, getrandstr, is_ascii_safe, to_native_str, \ repeat_string, tick, batch -from passlib.utils.compat import iteritems, irange, u, unicode, PY2, nullcontext from passlib.utils.decor import classproperty import passlib.utils.handlers as uh # local @@ -219,7 +217,7 @@ def patch_calc_min_rounds(handler): #============================================================================= def set_file(path, content): """set file to specified bytes""" - if isinstance(content, unicode): + if isinstance(content, str): content = content.encode("utf-8") with open(path, "wb") as fh: fh.write(content) @@ -231,15 +229,10 @@ def get_file(path): def tonn(source): """convert native string to non-native string""" - if not isinstance(source, str): - return source - elif PY3: + if isinstance(source, str): return source.encode("utf-8") else: - try: - return source.decode("utf-8") - except UnicodeDecodeError: - return source.decode("latin-1") + return source def hb(source): """ @@ -288,7 +281,7 @@ def run_with_fixed_seeds(count=128, master_seed=0x243F6A8885A308D3): @wraps(func) def wrapper(*args, **kwds): rng = random.Random(master_seed) - for _ in irange(count): + for _ in range(count): kwds['seed'] = rng.getrandbits(32) func(*args, **kwds) return wrapper @@ -298,7 +291,7 @@ def run_with_fixed_seeds(count=128, master_seed=0x243F6A8885A308D3): # custom test harness #============================================================================= -class TestCase(_TestCase): +class TestCase(unittest.TestCase): """passlib-specific test case class this class adds a number of features to the standard TestCase... @@ -321,7 +314,7 @@ class TestCase(_TestCase): def shortDescription(self): """wrap shortDescription() method to prepend descriptionPrefix""" - desc = super(TestCase, self).shortDescription() + desc = super().shortDescription() prefix = self.descriptionPrefix if prefix: desc = "%s: %s" % (prefix, desc or str(self)) @@ -333,7 +326,7 @@ class TestCase(_TestCase): #--------------------------------------------------------------- @classproperty def __unittest_skip__(cls): - # NOTE: this attr is technically a unittest2 internal detail. + # NOTE: this attr is technically a unittest internal detail. name = cls.__name__ return name.startswith("_") or \ getattr(cls, "_%s__unittest_skip" % name, False) @@ -354,7 +347,7 @@ class TestCase(_TestCase): resetWarningState = True def setUp(self): - super(TestCase, self).setUp() + super().setUp() self.setUpWarnings() # have uh.debug_only_repr() return real values for duration of test self.patchAttr(exc, "ENABLE_DEBUG_ONLY_REPR", True) @@ -375,7 +368,6 @@ class TestCase(_TestCase): # should be kept until then, so we test the legacy paths. warnings.filterwarnings("ignore", r"the method .*\.(encrypt|genconfig|genhash)\(\) is deprecated") warnings.filterwarnings("ignore", r"the 'vary_rounds' option is deprecated") - warnings.filterwarnings("ignore", r"Support for `(py-bcrypt|bcryptor)` is deprecated") #--------------------------------------------------------------- # tweak message formatting so longMessage mode is only enabled @@ -397,7 +389,7 @@ class TestCase(_TestCase): msg = kwds.pop("__msg__", None) if _callable is None: # FIXME: this ignores 'msg' - return super(TestCase, self).assertRaises(_exc_type, None, + return super().assertRaises(_exc_type, None, *args, **kwds) try: result = _callable(*args, **kwds) @@ -411,7 +403,7 @@ class TestCase(_TestCase): # forbid a bunch of deprecated aliases so I stop using them #--------------------------------------------------------------- def assertEquals(self, *a, **k): - raise AssertionError("this alias is deprecated by unittest2") + raise AssertionError("this alias is deprecated by stdlib unittest") assertNotEquals = assertRegexMatches = assertEquals #=================================================================== @@ -470,14 +462,13 @@ class TestCase(_TestCase): def __init__(self, case, **kwds): self.case = case self.kwds = kwds - self.__super = super(TestCase._AssertWarningList, self) - self.__super.__init__(record=True) + super().__init__(record=True) def __enter__(self): - self.log = self.__super.__enter__() + self.log = super().__enter__() def __exit__(self, *exc_info): - self.__super.__exit__(*exc_info) + super().__exit__(*exc_info) if exc_info[0] is None: self.case.assertWarningList(self.log, **self.kwds) @@ -631,20 +622,14 @@ class TestCase(_TestCase): # subtests #=================================================================== - has_real_subtest = hasattr(_TestCase, "subTest") - @contextlib.contextmanager def subTest(self, *args, **kwds): """ - wrapper/backport for .subTest() which also traps SkipTest errors. + wrapper for .subTest() which traps SkipTest errors. (see source for details) """ - # this function works around two things: - # * TestCase.subTest() wasn't added until Py34; so for older python versions, - # we either need unittest2 installed, or provide stub of our own. - # this method provides a stub if needed (based on .has_real_subtest check) - # - # * as 2020-10-08, .subTest() doesn't play nicely w/ .skipTest(); + # this function works around issue that as 2020-10-08, + # .subTest() doesn't play nicely w/ .skipTest(); # and also makes it hard to debug which subtest had a failure. # (see https://bugs.python.org/issue25894 and https://bugs.python.org/issue35327) # this method traps skipTest exceptions, and adds some logging to help debug @@ -663,13 +648,8 @@ class TestCase(_TestCase): test_log = self.getLogger() title = _render_title(*args, **kwds) - # use real subtest manager if available - if self.has_real_subtest: - ctx = super(TestCase, self).subTest(*args, **kwds) - else: - ctx = nullcontext() - # run the subtest + ctx = super().subTest(*args, **kwds) with ctx: test_log.info("running subtest: %s", title) try: @@ -677,7 +657,9 @@ class TestCase(_TestCase): except SkipTest: # silence "SkipTest" exceptions, want to keep running next subtest. test_log.info("subtest skipped: %s", title) - pass + # XXX: should revisit whether latest py3 version of UTs handle this ok, + # meaning it's safe to re-raise this. + return except Exception as err: # log unhandled exception occurred # (assuming traceback will be reported up higher, so not bothering here) @@ -734,8 +716,7 @@ class TestCase(_TestCase): return logger named after current test. """ cls = type(self) - # NOTE: conditional on qualname for PY2 compat - path = cls.__module__ + "." + getattr(cls, "__qualname__", cls.__name__) + path = cls.__module__ + "." + cls.__qualname__ name = self._testMethodName if name: path = path + "." + name @@ -778,8 +759,7 @@ class HandlerCase(TestCase): .. note:: - This is subclass of :class:`unittest.TestCase` - (or :class:`unittest2.TestCase` if available). + This is subclass of :class:`unittest.TestCase`. """ #=================================================================== # class attrs - should be filled in by subclass @@ -831,8 +811,8 @@ class HandlerCase(TestCase): # passwords used to test basic hash behavior - generally # don't need to be overidden. stock_passwords = [ - u("test"), - u("\u20AC\u00A5$"), + u"test", + u"\u20AC\u00A5$", b'\xe2\x82\xac\xc2\xa5$' ] @@ -1065,7 +1045,7 @@ class HandlerCase(TestCase): if test_requires_backend and self._skip_backend_reason: raise self.skipTest(self._skip_backend_reason) - super(HandlerCase, self).setUp() + super().setUp() # if needed, select specific backend for duration of test # NOTE: skipping this if create_backend_case() signalled we're skipping backend @@ -1384,7 +1364,7 @@ class HandlerCase(TestCase): def sampler(func): value1 = func() - for _ in irange(samples): + for _ in range(samples): value2 = func() if value1 != value2: return @@ -1500,7 +1480,7 @@ class HandlerCase(TestCase): self.do_stub_encrypt(salt=salt) # check some invalid salt chars, make sure they're rejected - source = u('\x00\xff') + source = u'\x00\xff' if raw: source = source.encode("latin-1") chunk = max(mn, 1) @@ -1516,7 +1496,7 @@ class HandlerCase(TestCase): if getattr(self.handler, "_salt_is_bytes", False): return bytes else: - return unicode + return str def test_15_salt_type(self): """test non-string salt values""" @@ -1530,12 +1510,11 @@ class HandlerCase(TestCase): self.assertRaises(TypeError, self.do_encrypt, 'stub', salt=fake()) # unicode should be accepted only if salt_type is unicode. - if salt_type is not unicode: - self.assertRaises(TypeError, self.do_encrypt, 'stub', salt=u('x') * salt_size) + if salt_type is not str: + self.assertRaises(TypeError, self.do_encrypt, 'stub', salt=u'x' * salt_size) - # bytes should be accepted only if salt_type is bytes, - # OR if salt type is unicode and running PY2 - to allow native strings. - if not (salt_type is bytes or (PY2 and salt_type is unicode)): + # bytes should be accepted only if salt_type is bytes + if salt_type is not bytes: self.assertRaises(TypeError, self.do_encrypt, 'stub', salt=b'x' * salt_size) def test_using_salt_size(self): @@ -1936,7 +1915,7 @@ class HandlerCase(TestCase): handler, subcls, small, medium, large, adj = self._create_using_rounds_helper() def get_effective_range(cls): - seen = set(get_effective_rounds(cls) for _ in irange(1000)) + seen = set(get_effective_rounds(cls) for _ in range(1000)) return min(seen), max(seen) def assert_rounds_range(vary_rounds, lower, upper): @@ -2011,24 +1990,24 @@ class HandlerCase(TestCase): # check ident_values list for value in cls.ident_values: - self.assertIsInstance(value, unicode, - "cls.ident_values must be unicode:") + self.assertIsInstance(value, str, + "cls.ident_values must be str:") self.assertTrue(len(cls.ident_values)>1, "cls.ident_values must have 2+ elements:") # check default_ident value - self.assertIsInstance(cls.default_ident, unicode, - "cls.default_ident must be unicode:") + self.assertIsInstance(cls.default_ident, str, + "cls.default_ident must be str:") self.assertTrue(cls.default_ident in cls.ident_values, "cls.default_ident must specify member of cls.ident_values") # check optional aliases list if cls.ident_aliases: - for alias, ident in iteritems(cls.ident_aliases): - self.assertIsInstance(alias, unicode, - "cls.ident_aliases keys must be unicode:") # XXX: allow ints? - self.assertIsInstance(ident, unicode, - "cls.ident_aliases values must be unicode:") + for alias, ident in cls.ident_aliases.items(): + self.assertIsInstance(alias, str, + "cls.ident_aliases keys must be str:") # XXX: allow ints? + self.assertIsInstance(ident, str, + "cls.ident_aliases values must be str:") self.assertTrue(ident in cls.ident_values, "cls.ident_aliases must map to cls.ident_values members: %r" % (ident,)) @@ -2347,7 +2326,7 @@ class HandlerCase(TestCase): chars = self.forbidden_characters if not chars: raise self.skipTest("none listed") - base = u('stub') + base = u'stub' if isinstance(chars, bytes): from passlib.utils.compat import iter_byte_chars chars = iter_byte_chars(chars) @@ -2366,7 +2345,7 @@ class HandlerCase(TestCase): """ check if we're expecting potential verify failure due to crypt.crypt() encoding limitation """ - if PY3 and self.backend == "os_crypt" and isinstance(secret, bytes): + if self.backend == "os_crypt" and isinstance(secret, bytes): try: secret.decode("utf-8") except UnicodeDecodeError: @@ -2578,7 +2557,7 @@ class HandlerCase(TestCase): # # test hash='' is rejected for all but the plaintext hashes # - for hash in [u(''), b'']: + for hash in [u'', b'']: if self.accepts_all_hashes: # then it accepts empty string as well. self.assertTrue(self.do_identify(hash)) @@ -2609,7 +2588,7 @@ class HandlerCase(TestCase): def require_parsehash(self): if not hasattr(self.handler, "parsehash"): - raise SkipTest("parsehash() not implemented") + raise self.skipTest("parsehash() not implemented") def test_70_parsehash(self): """ @@ -2638,11 +2617,6 @@ class HandlerCase(TestCase): # but all else should be the same result3 = handler.parsehash(hash, sanitize=True) correct3 = result.copy() - if PY2: - # silence warning about bytes & unicode not comparing - # (sanitize may convert bytes into base64 text) - warnings.filterwarnings("ignore", ".*unequal comparison failed to convert.*", - category=UnicodeWarning) for key in ("salt", "checksum"): if key in result3: self.assertNotEqual(result3[key], correct3[key]) @@ -2656,7 +2630,7 @@ class HandlerCase(TestCase): """ if value is None: return - self.assertIsInstance(value, unicode) + self.assertIsInstance(value, str) # assumes mask_value() defaults will never show more than <show> chars (4); # and show nothing if size less than 1/<pct> (8). ref = value if len(value) < 8 else value[4:] @@ -2797,7 +2771,7 @@ class HandlerCase(TestCase): def wrapper(): try: self.test_77_fuzz_input(threaded=True) - except SkipTest: + except unittest.SkipTest: pass except: with failed_lock: @@ -2811,7 +2785,7 @@ class HandlerCase(TestCase): thread.setDaemon(True) thread.start() return thread - threads = [launch(n) for n in irange(thread_count)] + threads = [launch(n) for n in range(thread_count)] # wait until all threads exit timeout = self.max_fuzz_time * thread_count * 4 @@ -2874,7 +2848,7 @@ class HandlerCase(TestCase): used by fuzz testing. verifiers should be callable with signature - ``func(password: unicode, hash: ascii str) -> ok: bool``. + ``func(password: str, hash: ascii str) -> ok: bool``. """ handler = self.handler verifiers = [] @@ -2929,7 +2903,7 @@ class HandlerCase(TestCase): #========================================================== # alphabet for randomly generated passwords - password_alphabet = u('qwertyASDF1234<>.@*#! \u00E1\u0259\u0411\u2113') + password_alphabet = u'qwertyASDF1234<>.@*#! \u00E1\u0259\u0411\u2113' # encoding when testing bytes password_encoding = "utf-8" @@ -3039,7 +3013,7 @@ class HandlerCase(TestCase): # occasionally try an empty password rng = self.rng if rng.random() < .0001: - return u('') + return u'' # check if truncate size needs to be considered handler = self.handler @@ -3058,7 +3032,7 @@ class HandlerCase(TestCase): result = getrandstr(rng, self.password_alphabet, size) # trim ones that encode past truncate point. - if truncate_size and isinstance(result, unicode): + if truncate_size and isinstance(result, str): while len(result.encode("utf-8")) > truncate_size: result = result[:-1] @@ -3210,7 +3184,7 @@ class OsCryptMixin(HandlerCase): if not self.handler.has_backend("os_crypt"): # XXX: currently, any tests that use this are skipped entirely! (see issue 120) self._patch_safe_crypt() - super(OsCryptMixin, self).setUp() + super().setUp() @classmethod def _get_safe_crypt_handler_backend(cls): @@ -3267,7 +3241,7 @@ class OsCryptMixin(HandlerCase): when it's known os_crypt will be faked by _patch_safe_crypt() """ assert backend == "os_crypt" - reason = super(OsCryptMixin, cls)._get_skip_backend_reason(backend) + reason = super()._get_skip_backend_reason(backend) from passlib.utils import has_crypt if reason == cls._BACKEND_NOT_AVAILABLE and has_crypt: @@ -3515,7 +3489,7 @@ class UserHandlerMixin(HandlerCase): context_map = HandlerCase.FuzzHashGenerator.context_map.copy() context_map.update(user="random_user") - user_alphabet = u("asdQWE123") + user_alphabet = u"asdQWE123" def random_user(self): rng = self.rng @@ -3543,14 +3517,14 @@ class EncodingHandlerMixin(HandlerCase): # restrict stock passwords & fuzz alphabet to latin-1, # so different encodings can be tested safely. stock_passwords = [ - u("test"), + u"test", b"test", - u("\u00AC\u00BA"), + u"\u00AC\u00BA", ] class FuzzHashGenerator(HandlerCase.FuzzHashGenerator): - password_alphabet = u('qwerty1234<>.@*#! \u00AC') + password_alphabet = u'qwerty1234<>.@*#! \u00AC' def populate_context(self, secret, kwds): """insert encoding into kwds""" @@ -3569,13 +3543,13 @@ class reset_warnings(warnings.catch_warnings): """catch_warnings() wrapper which clears warning registry & filters""" def __init__(self, reset_filter="always", reset_registry=".*", **kwds): - super(reset_warnings, self).__init__(**kwds) + super().__init__(**kwds) self._reset_filter = reset_filter self._reset_registry = re.compile(reset_registry) if reset_registry else None def __enter__(self): # let parent class archive filter state - ret = super(reset_warnings, self).__enter__() + ret = super().__enter__() # reset the filter to list everything if self._reset_filter: @@ -3614,7 +3588,7 @@ class reset_warnings(warnings.catch_warnings): setattr(mod, "__warningregistry__", orig) else: reg.update(orig) - super(reset_warnings, self).__exit__(*exc_info) + super().__exit__(*exc_info) #============================================================================= # eof diff --git a/passlib/totp.py b/passlib/totp.py index 9ad5000..a133354 100644 --- a/passlib/totp.py +++ b/passlib/totp.py @@ -2,8 +2,6 @@ #============================================================================= # imports #============================================================================= -from __future__ import absolute_import, division, print_function -from passlib.utils.compat import PY3 # core import base64 import calendar @@ -14,11 +12,7 @@ import struct import sys import time as _time import re -if PY3: - from urllib.parse import urlparse, parse_qsl, quote, unquote -else: - from urllib import quote, unquote - from urlparse import urlparse, parse_qsl +from urllib.parse import urlparse, parse_qsl, quote, unquote from warnings import warn # site try: @@ -37,8 +31,7 @@ from passlib.exc import TokenError, MalformedTokenError, InvalidTokenError, Used from passlib.utils import (to_unicode, to_bytes, consteq, getrandbytes, rng, SequenceMixin, xor_bytes, getrandstr) from passlib.utils.binary import BASE64_CHARS, b32encode, b32decode -from passlib.utils.compat import (u, unicode, native_string_types, bascii_to_str, int_types, num_types, - irange, byte_elem_value, UnicodeIO, suppress_cause) +from passlib.utils.compat import bascii_to_str, num_types from passlib.utils.decor import hybrid_method, memoized_property from passlib.crypto.digest import lookup_hash, compile_hmac, pbkdf2_hmac from passlib.hash import pbkdf2_sha256 @@ -60,20 +53,6 @@ __all__ = [ ] #============================================================================= -# HACK: python < 2.7.4's urlparse() won't parse query strings unless the url scheme -# is one of the schemes in the urlparse.uses_query list. 2.7 abandoned -# this, and parses query if present, regardless of the scheme. -# as a workaround for older versions, we add "otpauth" to the known list. -# this was fixed by https://bugs.python.org/issue9374, in 2.7.4 release. -#============================================================================= -if sys.version_info < (2,7,4): - from urlparse import uses_query - if "otpauth" not in uses_query: - uses_query.append("otpauth") - log.debug("registered 'otpauth' scheme with urlparse.uses_query") - del uses_query - -#============================================================================= # internal helpers #============================================================================= @@ -82,7 +61,7 @@ if sys.version_info < (2,7,4): #----------------------------------------------------------------------------- #: regex used to clean whitespace from tokens & keys -_clean_re = re.compile(u(r"\s|[-=]"), re.U) +_clean_re = re.compile(r"\s|[-=]", re.U) _chunk_sizes = [4,6,5] @@ -112,7 +91,7 @@ def group_string(value, sep="-"): """ klen = len(value) size = _get_group_size(klen) - return sep.join(value[o:o+size] for o in irange(0, klen, size)) + return sep.join(value[o:o+size] for o in range(0, klen, size)) #----------------------------------------------------------------------------- # encoding helpers @@ -269,7 +248,7 @@ class AppWallet(object): # init cost # if encrypt_cost is not None: - if isinstance(encrypt_cost, native_string_types): + if isinstance(encrypt_cost, str): encrypt_cost = int(encrypt_cost) assert encrypt_cost >= 0 self.encrypt_cost = encrypt_cost @@ -311,7 +290,7 @@ class AppWallet(object): # to make this easy to pass in configuration from a separate file, # 'secrets' can be string using two formats -- json & "tag:value\n" check_type = True - if isinstance(source, native_string_types): + if isinstance(source, str): if source.lstrip().startswith(("[", "{")): # json list / dict source = json.loads(source) @@ -344,12 +323,12 @@ class AppWallet(object): for tag, value in source) def _parse_secret_pair(self, tag, value): - if isinstance(tag, native_string_types): + if isinstance(tag, str): pass elif isinstance(tag, int): tag = str(tag) else: - raise TypeError("tag must be unicode/string: %r" % (tag,)) + raise TypeError("tag must be string: %r" % (tag,)) if not _tag_re.match(tag): raise ValueError("tag contains invalid characters: %r" % (tag,)) if not isinstance(value, bytes): @@ -378,7 +357,7 @@ class AppWallet(object): try: return secrets[tag] except KeyError: - raise suppress_cause(KeyError("unknown secret tag: %r" % (tag,))) + raise KeyError("unknown secret tag: %r" % (tag,)) from None #======================================================================== # encrypted key helpers -- used internally by TOTP @@ -772,7 +751,7 @@ class TOTP(object): new=False, digits=None, alg=None, size=None, period=None, label=None, issuer=None, changed=False, **kwds): - super(TOTP, self).__init__(**kwds) + super().__init__(**kwds) if changed: self.changed = changed @@ -819,7 +798,7 @@ class TOTP(object): # validate digits if digits is None: digits = self.digits - if not isinstance(digits, int_types): + if not isinstance(digits, int): raise TypeError("digits must be an integer, not a %r" % type(digits)) if digits < 6 or digits > 10: raise ValueError("digits must in range(6,11)") @@ -849,7 +828,7 @@ class TOTP(object): """ check that serial value (e.g. 'counter') is non-negative integer """ - if not isinstance(value, int_types): + if not isinstance(value, int): raise exc.ExpectedTypeError(value, "int", param) if value < minval: raise ValueError("%s must be >= %d" % (param, minval)) @@ -995,7 +974,7 @@ class TOTP(object): :returns: unix epoch timestamp as :class:`int`. """ - if isinstance(time, int_types): + if isinstance(time, int): return time elif isinstance(time, float): return int(time) @@ -1034,20 +1013,20 @@ class TOTP(object): or use the class default. :arg token: - token as ascii bytes, unicode, or an integer. + token as ascii bytes, str, or an integer. :raises ValueError: if token has wrong number of digits, or contains non-numeric characters. :returns: - token as :class:`!unicode` string, containing only digits 0-9. + token as :class:`!str`, containing only digits 0-9. """ digits = self_or_cls.digits - if isinstance(token, int_types): - token = u("%0*d") % (digits, token) + if isinstance(token, int): + token = u"%0*d" % (digits, token) else: token = to_unicode(token, param="token") - token = _clean_re.sub(u(""), token) + token = _clean_re.sub(u"", token) if not token.isdigit(): raise MalformedTokenError("Token must contain only the digits 0-9") if len(token) != digits: @@ -1110,7 +1089,7 @@ class TOTP(object): :returns: token as unicode string """ # generate digest - assert isinstance(counter, int_types), "counter must be integer" + assert isinstance(counter, int), "counter must be integer" assert counter >= 0, "counter must be non-negative" keyed_hmac = self._keyed_hmac if keyed_hmac is None: @@ -1120,8 +1099,9 @@ class TOTP(object): assert len(digest) == digest_size, "digest_size: sanity check failed" # derive 31-bit token value + # assert isinstance(digest, bytes) assert digest_size >= 20, "digest_size: sanity check 2 failed" # otherwise 0xF+4 will run off end of hash. - offset = byte_elem_value(digest[-1]) & 0xF + offset = digest[-1] & 0xF value = _unpack_uint32(digest[offset:offset+4])[0] & 0x7fffffff # render to decimal string, return last <digits> chars @@ -1130,7 +1110,7 @@ class TOTP(object): # if 31-bit mask removed (which breaks spec), would only get values 0-4. digits = self.digits assert 0 < digits < 11, "digits: sanity check failed" - return (u("%0*d") % (digits, value))[-digits:] + return (u"%0*d" % (digits, value))[-digits:] #============================================================================= # token verification @@ -1293,8 +1273,8 @@ class TOTP(object): # XXX: if (end - start) is very large (e.g. for resync purposes), # could start with expected value, and work outward from there, # alternately checking before & after it until match is found. - # XXX: can't use irange(start, end) here since py2x/win32 - # throws error on values >= (1<<31), which 'end' can be. + # TODO: replace counter loop with "for counter in range(start, end)"; + # think this was holding from PY2+win32 issue with values > 32 bit (e.g. 'end'). counter = start while counter < end: if consteq(token, generate(counter)): @@ -1550,11 +1530,11 @@ class TOTP(object): # NOTE: not using urllib.urlencode() because it encodes ' ' as '+'; # but spec says to use '%20', and not sure how fragile # the various totp clients' parsers are. - param_str = u("&").join(u("%s=%s") % (key, quote(value, '')) for key, value in params) + param_str = u"&".join(u"%s=%s" % (key, quote(value, '')) for key, value in params) assert param_str, "param_str should never be empty" # render uri - return u("otpauth://totp/%s?%s") % (label, param_str) + return u"otpauth://totp/%s?%s" % (label, param_str) def _to_uri_params(self): """return list of (key, param) entries for URI""" @@ -1578,7 +1558,7 @@ class TOTP(object): (as generated by :meth:`to_json`). :arg json: - Serialized output from :meth:`to_json`, as unicode or ascii bytes. + Serialized output from :meth:`to_json`, as str or ascii bytes. :raises ValueError: If the key has been encrypted, but the application secret isn't available; diff --git a/passlib/utils/__init__.py b/passlib/utils/__init__.py index 6147886..94833ad 100644 --- a/passlib/utils/__init__.py +++ b/passlib/utils/__init__.py @@ -61,11 +61,10 @@ from passlib.utils.decor import ( hybrid_method, ) from passlib.exc import ExpectedStringError, ExpectedTypeError -from passlib.utils.compat import (add_doc, join_bytes, join_byte_values, - join_byte_elems, irange, imap, PY3, u, - join_unicode, unicode, byte_elem_value, nextgetter, - unicode_or_str, unicode_or_bytes_types, - get_method_function, suppress_cause, PYPY) +from passlib.utils.compat import (add_doc, join_bytes, + join_unicode, + unicode_or_bytes, + get_method_function, PYPY) # local __all__ = [ # constants @@ -113,7 +112,7 @@ __all__ = [ #============================================================================= # bitsize of system architecture (32 or 64) -sys_bits = int(math.log(sys.maxsize if PY3 else sys.maxint, 2) + 1.5) +sys_bits = int(math.log(sys.maxsize, 2) + 1.5) # list of hashes algs supported by crypt() on at least one OS. # XXX: move to .registry for passlib 2.0? @@ -128,13 +127,10 @@ unix_crypt_schemes = [ # list of rounds_cost constants rounds_cost_values = [ "linear", "log2" ] -# legacy import, will be removed in 1.8 -from passlib.exc import MissingBackendError - # internal helpers _BEMPTY = b'' -_UEMPTY = u("") -_USPACE = u(" ") +_UEMPTY = u"" +_USPACE = u" " # maximum password size which passlib will allow; see exc.PasswordSizeError MAX_PASSWORD_SIZE = int(os.environ.get("PASSLIB_MAX_PASSWORD_SIZE") or 4096) @@ -169,30 +165,23 @@ class SequenceMixin(object): def __ne__(self, other): return not self.__eq__(other) -if PY3: - # getargspec() is deprecated, use this under py3. - # even though it's a lot more awkward to get basic info :| - - _VAR_KEYWORD = inspect.Parameter.VAR_KEYWORD - _VAR_ANY_SET = set([_VAR_KEYWORD, inspect.Parameter.VAR_POSITIONAL]) +# getargspec() is deprecated, use this under py3. +# even though it's a lot more awkward to get basic info :| - def accepts_keyword(func, key): - """test if function accepts specified keyword""" - params = inspect.signature(get_method_function(func)).parameters - if not params: - return False - arg = params.get(key) - if arg and arg.kind not in _VAR_ANY_SET: - return True - # XXX: annoying what we have to do to determine if VAR_KWDS in use. - return params[list(params)[-1]].kind == _VAR_KEYWORD +_VAR_KEYWORD = inspect.Parameter.VAR_KEYWORD +_VAR_ANY_SET = {_VAR_KEYWORD, inspect.Parameter.VAR_POSITIONAL} -else: +def accepts_keyword(func, key): + """test if function accepts specified keyword""" + params = inspect.signature(get_method_function(func)).parameters + if not params: + return False + arg = params.get(key) + if arg and arg.kind not in _VAR_ANY_SET: + return True + # XXX: annoying what we have to do to determine if VAR_KWDS in use. + return params[list(params)[-1]].kind == _VAR_KEYWORD - def accepts_keyword(func, key): - """test if function accepts specified keyword""" - spec = inspect.getargspec(get_method_function(func)) - return key in spec.args or spec.keywords is not None def update_mixin_classes(target, add=None, remove=None, append=False, before=None, after=None, dryrun=False): @@ -333,16 +322,16 @@ def consteq(left, right): # http://bugs.python.org/issue14955 # validate types - if isinstance(left, unicode): - if not isinstance(right, unicode): - raise TypeError("inputs must be both unicode or both bytes") - is_py3_bytes = False + if isinstance(left, str): + if not isinstance(right, str): + raise TypeError("inputs must be both str or both bytes") + is_bytes = False elif isinstance(left, bytes): if not isinstance(right, bytes): - raise TypeError("inputs must be both unicode or both bytes") - is_py3_bytes = PY3 + raise TypeError("inputs must be both str or both bytes") + is_bytes = True else: - raise TypeError("inputs must be both unicode or both bytes") + raise TypeError("inputs must be both str or both bytes") # do size comparison. # NOTE: the double-if construction below is done deliberately, to ensure @@ -361,8 +350,7 @@ def consteq(left, right): result = 1 # run constant-time string comparision - # TODO: use izip instead (but first verify it's faster than zip for this case) - if is_py3_bytes: + if is_bytes: for l,r in zip(tmp, right): result |= l ^ r else: @@ -442,8 +430,8 @@ def saslprep(source, param="value"): # validate type # XXX: support bytes (e.g. run through want_unicode)? # might be easier to just integrate this into cryptcontext. - if not isinstance(source, unicode): - raise TypeError("input must be unicode string, not %s" % + if not isinstance(source, str): + raise TypeError("input must be string, not %s" % (type(source),)) # mapping stage @@ -557,19 +545,11 @@ def render_bytes(source, *args): else arg for arg in args) return result.encode("latin-1") -if PY3: - # new in py32 - def bytes_to_int(value): - return int.from_bytes(value, 'big') - def int_to_bytes(value, count): - return value.to_bytes(count, 'big') -else: - # XXX: can any of these be sped up? - from binascii import hexlify, unhexlify - def bytes_to_int(value): - return int(hexlify(value),16) - def int_to_bytes(value, count): - return unhexlify(('%%0%dx' % (count<<1)) % value) +def bytes_to_int(value): + return int.from_bytes(value, 'big') + +def int_to_bytes(value, count): + return value.to_bytes(count, 'big') add_doc(bytes_to_int, "decode byte string as single big-endian integer") add_doc(int_to_bytes, "encode integer as single big-endian byte string") @@ -595,14 +575,14 @@ def utf8_repeat_string(source, size): _BNULL = b"\x00" -_UNULL = u("\x00") +_UNULL = u"\x00" def right_pad_string(source, size, pad=None): """right-pad or truncate <source> string, so it has length <size>""" cur = len(source) if size > cur: if pad is None: - pad = _UNULL if isinstance(source, unicode) else _BNULL + pad = _UNULL if isinstance(source, str) else _BNULL return source+pad*(size-cur) else: return source[:size] @@ -648,7 +628,7 @@ def utf8_truncate(source, index): # loop until we find non-continuation byte while index < end: - if byte_elem_value(source[index]) & 0xC0 != 0x80: + if source[index] & 0xC0 != 0x80: # found single-char byte, or start-char byte. break # else: found continuation byte. @@ -694,7 +674,7 @@ def is_same_codec(left, right): return _lookup_codec(left).name == _lookup_codec(right).name _B80 = b'\x80'[0] -_U80 = u('\x80') +_U80 = u'\x80' def is_ascii_safe(source): """Check if string (bytes or unicode) contains only 7-bit ascii""" r = _B80 if isinstance(source, bytes) else _U80 @@ -717,7 +697,7 @@ def to_bytes(source, encoding="utf-8", param="value", source_encoding=None): the source will be transcoded from *source_encoding* to *encoding* (via unicode). - :raises TypeError: if source is not unicode or bytes. + :raises TypeError: if source is not str or bytes. :returns: * unicode strings will be encoded using *encoding*, and returned. @@ -732,7 +712,7 @@ def to_bytes(source, encoding="utf-8", param="value", source_encoding=None): return source.decode(source_encoding).encode(encoding) else: return source - elif isinstance(source, unicode): + elif isinstance(source, str): return source.encode(encoding) else: raise ExpectedStringError(source, param) @@ -749,42 +729,34 @@ def to_unicode(source, encoding="utf-8", param="value"): :param param: optional name of variable/noun to reference when raising errors. - :raises TypeError: if source is not unicode or bytes. + :raises TypeError: if source is not str or bytes. :returns: * returns unicode strings unchanged. * returns bytes strings decoded using *encoding* """ assert encoding - if isinstance(source, unicode): + if isinstance(source, str): return source elif isinstance(source, bytes): return source.decode(encoding) else: raise ExpectedStringError(source, param) -if PY3: - def to_native_str(source, encoding="utf-8", param="value"): - if isinstance(source, bytes): - return source.decode(encoding) - elif isinstance(source, unicode): - return source - else: - raise ExpectedStringError(source, param) -else: - def to_native_str(source, encoding="utf-8", param="value"): - if isinstance(source, bytes): - return source - elif isinstance(source, unicode): - return source.encode(encoding) - else: - raise ExpectedStringError(source, param) + +def to_native_str(source, encoding="utf-8", param="value"): + if isinstance(source, bytes): + return source.decode(encoding) + elif isinstance(source, str): + return source + else: + raise ExpectedStringError(source, param) + add_doc(to_native_str, - """Take in unicode or bytes, return native string. + """Take in str or bytes, returns str. - Python 2: encodes unicode using specified encoding, leaves bytes alone. - Python 3: leaves unicode alone, decodes bytes using specified encoding. + leaves str alone, decodes bytes using specified encoding. :raises TypeError: if source is not unicode or bytes. @@ -816,7 +788,7 @@ def as_bool(value, none=None, param="boolean"): recognizes strings such as "true", "false" """ assert none in [True, False, None] - if isinstance(value, unicode_or_bytes_types): + if isinstance(value, unicode_or_bytes): clean = value.lower().strip() if clean in _true_set: return True @@ -840,7 +812,7 @@ def is_safe_crypt_input(value): """ UT helper -- test if value is safe to pass to crypt.crypt(); - under PY3, can't pass non-UTF8 bytes to crypt.crypt. + since PY3 won't let us pass non-UTF8 bytes to crypt.crypt. """ if crypt_accepts_bytes or not isinstance(value, bytes): return True @@ -883,9 +855,9 @@ else: # returning NULL / None. examples include ":", ":0", "*0", etc. # safe_crypt() returns None for any string starting with one of the # chars in this string... - _invalid_prefixes = u("*:!") + _invalid_prefixes = u"*:!" - if PY3: + if True: # legacy block from PY3 compat # * pypy3 (as of v7.3.1) has a crypt which accepts bytes, or ASCII-only unicode. # * whereas CPython3 (as of v3.9) has a crypt which doesn't take bytes, @@ -905,11 +877,11 @@ else: if crypt_accepts_bytes: # PyPy3 -- all bytes accepted, but unicode encoded to ASCII, # so handling that ourselves. - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") if _BNULL in secret: raise ValueError("null character in secret") - if isinstance(hash, unicode): + if isinstance(hash, str): hash = hash.encode("ascii") else: # CPython3's crypt() doesn't take bytes, only unicode; unicode which is then @@ -945,27 +917,7 @@ else: if not result or result[0] in _invalid_prefixes: return None return result - else: - #: see feature-detection in PY3 fork above - crypt_accepts_bytes = True - - # Python 2 crypt handler - def safe_crypt(secret, hash): - if isinstance(secret, unicode): - secret = secret.encode("utf-8") - if _NULL in secret: - raise ValueError("null character in secret") - if isinstance(hash, unicode): - hash = hash.encode("ascii") - with _safe_crypt_lock: - result = _crypt(secret, hash) - if not result: - return None - result = result.decode("ascii") - if result[0] in _invalid_prefixes: - return None - return result add_doc(safe_crypt, """Wrapper around stdlib's crypt. @@ -1006,9 +958,9 @@ def test_crypt(secret, hash): # safe_crypt() always returns unicode, which means that for py3, # 'hash' can't be bytes, or "== hash" will never be True. # under py2 unicode & str(bytes) will compare fine; - # so just enforcing "unicode_or_str" limitation - assert isinstance(hash, unicode_or_str), \ - "hash must be unicode_or_str, got %s" % type(hash) + # so just enforcing "str" limitation + assert isinstance(hash, str), \ + "hash must be str, got %s" % type(hash) assert hash, "hash must be non-empty" return safe_crypt(secret, hash) == hash @@ -1055,7 +1007,7 @@ def genseed(value=None): # this method throws error for e.g. SystemRandom instances, # so fall back to extracting 4k of state value = value.getrandbits(1 << 15) - text = u("%s %s %s %.15f %.15f %s") % ( + text = u"%s %s %s %.15f %.15f %s" % ( # if caller specified a seed value, mix it in value, @@ -1106,7 +1058,8 @@ def getrandbytes(rng, count): yield value & 0xff value >>= 3 i += 1 - return join_byte_values(helper()) + return bytes(helper()) + def getrandstr(rng, charset, count): """return string containing *count* number of chars/bytes, whose elements are drawn from specified charset, using specified rng""" @@ -1132,10 +1085,10 @@ def getrandstr(rng, charset, count): value //= letters i += 1 - if isinstance(charset, unicode): + if isinstance(charset, str): return join_unicode(helper()) else: - return join_byte_elems(helper()) + return bytes(helper()) _52charset = '2346789ABCDEFGHJKMNPQRTUVWXYZabcdefghjkmnpqrstuvwxyz' diff --git a/passlib/utils/binary.py b/passlib/utils/binary.py index 521b64a..f60ddf2 100644 --- a/passlib/utils/binary.py +++ b/passlib/utils/binary.py @@ -5,7 +5,6 @@ passlib.utils.binary - binary data encoding/decoding/manipulation # imports #============================================================================= # core -from __future__ import absolute_import, division, print_function from base64 import ( b64encode, b64decode, @@ -19,10 +18,9 @@ log = logging.getLogger(__name__) # pkg from passlib import exc from passlib.utils.compat import ( - PY3, bascii_to_str, - irange, imap, iter_byte_chars, join_byte_values, join_byte_elems, - nextgetter, suppress_cause, - u, unicode, unicode_or_bytes_types, + bascii_to_str, + iter_byte_chars, + unicode_or_bytes, ) from passlib.utils.decor import memoized_property # from passlib.utils import BASE64_CHARS, HASH64_CHARS @@ -64,28 +62,28 @@ __all__ = [ #------------------------------------------------------------- #: standard base64 charmap -BASE64_CHARS = u("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") +BASE64_CHARS = u"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" #: alt base64 charmap -- "." instead of "+" -AB64_CHARS = u("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./") +AB64_CHARS = u"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./" #: charmap used by HASH64 encoding. -HASH64_CHARS = u("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") +HASH64_CHARS = u"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" #: charmap used by BCrypt -BCRYPT_CHARS = u("./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") +BCRYPT_CHARS = u"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" #: std base64 chars + padding char -PADDED_BASE64_CHARS = BASE64_CHARS + u("=") +PADDED_BASE64_CHARS = BASE64_CHARS + u"=" #: all hex chars -HEX_CHARS = u("0123456789abcdefABCDEF") +HEX_CHARS = u"0123456789abcdefABCDEF" #: upper case hex chars -UPPER_HEX_CHARS = u("0123456789ABCDEF") +UPPER_HEX_CHARS = u"0123456789ABCDEF" #: lower case hex chars -LOWER_HEX_CHARS = u("0123456789abcdef") +LOWER_HEX_CHARS = u"0123456789abcdef" #------------------------------------------------------------- # byte strings @@ -93,7 +91,7 @@ LOWER_HEX_CHARS = u("0123456789abcdef") #: special byte string containing all possible byte values #: NOTE: for efficiency, this is treated as singleton by some of the code -ALL_BYTE_VALUES = join_byte_values(irange(256)) +ALL_BYTE_VALUES = bytes(range(256)) #: some string constants we reuse B_EMPTY = b'' @@ -128,10 +126,10 @@ def compile_byte_translation(mapping, source=None): assert isinstance(source, bytes) and len(source) == 255 target = list(iter_byte_chars(source)) for k, v in mapping.items(): - if isinstance(k, unicode_or_bytes_types): + if isinstance(k, unicode_or_bytes): k = ord(k) assert isinstance(k, int) and 0 <= k < 256 - if isinstance(v, unicode): + if isinstance(v, str): v = v.encode("ascii") assert isinstance(v, bytes) and len(v) == 1 target[k] = v @@ -152,12 +150,12 @@ def b64s_decode(data): decode from shortened base64 format which omits padding & whitespace. uses default ``+/`` altchars. """ - if isinstance(data, unicode): + if isinstance(data, str): # needs bytes for replace() call, but want to accept ascii-unicode ala a2b_base64() try: data = data.encode("ascii") except UnicodeEncodeError: - raise suppress_cause(ValueError("string argument should contain only ASCII characters")) + raise ValueError("string argument should contain only ASCII characters") from None off = len(data) & 3 if off == 0: pass @@ -170,7 +168,7 @@ def b64s_decode(data): try: return a2b_base64(data) except _BinAsciiError as err: - raise suppress_cause(TypeError(err)) + raise TypeError(err) from None #============================================================================= # adapted-base64 encoding @@ -198,12 +196,12 @@ def ab64_decode(data): it is primarily used by Passlib's custom pbkdf2 hashes. """ - if isinstance(data, unicode): + if isinstance(data, str): # needs bytes for replace() call, but want to accept ascii-unicode ala a2b_base64() try: data = data.encode("ascii") except UnicodeEncodeError: - raise suppress_cause(ValueError("string argument should contain only ASCII characters")) + raise ValueError("string argument should contain only ASCII characters") from None return b64s_decode(data.replace(b".", b"+")) #============================================================================= @@ -233,7 +231,7 @@ def b32decode(source): padding optional, ignored if present. """ # encode & correct for typos - if isinstance(source, unicode): + if isinstance(source, str): source = source.encode("ascii") source = source.translate(_b32_translate) @@ -336,7 +334,7 @@ class Base64Engine(object): #=================================================================== def __init__(self, charmap, big=False): # validate charmap, generate encode64/decode64 helper functions. - if isinstance(charmap, unicode): + if isinstance(charmap, str): charmap = charmap.encode("latin-1") elif not isinstance(charmap, bytes): raise exc.ExpectedStringError(charmap, "charmap") @@ -385,12 +383,9 @@ class Base64Engine(object): if not isinstance(source, bytes): raise TypeError("source must be bytes, not %s" % (type(source),)) chunks, tail = divmod(len(source), 3) - if PY3: - next_value = nextgetter(iter(source)) - else: - next_value = nextgetter(ord(elem) for elem in source) + next_value = iter(source).__next__ gen = self._encode_bytes(next_value, chunks, tail) - out = join_byte_elems(imap(self._encode64, gen)) + out = bytes(map(self._encode64, gen)) ##if tail: ## padding = self.padding ## if padding: @@ -495,9 +490,9 @@ class Base64Engine(object): if tail == 1: # only 6 bits left, can't encode a whole byte! raise ValueError("input string length cannot be == 1 mod 4") - next_value = nextgetter(imap(self._decode64, source)) + next_value = map(self._decode64, source).__next__ try: - return join_byte_values(self._decode_bytes(next_value, chunks, tail)) + return bytes(self._decode_bytes(next_value, chunks, tail)) except KeyError as err: raise ValueError("invalid character: %r" % (err.args[0],)) @@ -626,7 +621,7 @@ class Base64Engine(object): # we have dirty bits - repair the string by decoding last char, # clearing the padding bits via <mask>, and encoding new char. - if isinstance(source, unicode): + if isinstance(source, str): cm = self.charmap last = cm[cm.index(last) & mask] assert last in padset, "failed to generate valid padding char" @@ -635,8 +630,7 @@ class Base64Engine(object): # all chars used by encoding are 7-bit ascii. last = self._encode64(self._decode64(last) & mask) assert last in padset, "failed to generate valid padding char" - if PY3: - last = bytes([last]) + last = bytes([last]) return True, source[:-1] + last def repair_unused(self, source): @@ -661,19 +655,19 @@ class Base64Engine(object): """encode byte string, first transposing source using offset list""" if not isinstance(source, bytes): raise TypeError("source must be bytes, not %s" % (type(source),)) - tmp = join_byte_elems(source[off] for off in offsets) + tmp = bytes(source[off] for off in offsets) return self.encode_bytes(tmp) def decode_transposed_bytes(self, source, offsets): """decode byte string, then reverse transposition described by offset list""" # NOTE: if transposition does not use all bytes of source, - # the original can't be recovered... and join_byte_elems() will throw + # the original can't be recovered... and bytes() will throw # an error because 1+ values in <buf> will be None. tmp = self.decode_bytes(source) buf = [None] * len(offsets) for off, char in zip(offsets, tmp): buf[off] = char - return join_byte_elems(buf) + return bytes(buf) #=================================================================== # integer decoding helpers - mainly used by des_crypt family @@ -724,9 +718,8 @@ class Base64Engine(object): raise TypeError("source must be bytes, not %s" % (type(source),)) if len(source) != 1: raise ValueError("source must be exactly 1 byte") - if PY3: - # convert to 8bit int before doing lookup - source = source[0] + # convert to 8bit int before doing lookup + source = source[0] try: return self._decode64(source) except KeyError: @@ -792,13 +785,13 @@ class Base64Engine(object): pad = -bits % 6 bits += pad if self.big: - itr = irange(bits-6, -6, -6) + itr = range(bits-6, -6, -6) # shift to add lsb padding. value <<= pad else: - itr = irange(0, bits, 6) + itr = range(0, bits, 6) # padding is msb, so no change needed. - return join_byte_elems(imap(self._encode64, + return bytes(map(self._encode64, ((value>>off) & 0x3f for off in itr))) #--------------------------------------------------------------- @@ -809,10 +802,7 @@ class Base64Engine(object): """encodes 6-bit integer -> single hash64 character""" if value < 0 or value > 63: raise ValueError("value out of range") - if PY3: - return self.bytemap[value:value+1] - else: - return self._encode64(value) + return self.bytemap[value:value+1] def encode_int12(self, value): """encodes 12-bit integer -> 2 char string""" @@ -821,7 +811,7 @@ class Base64Engine(object): raw = [value & 0x3f, (value>>6) & 0x3f] if self.big: raw = reversed(raw) - return join_byte_elems(imap(self._encode64, raw)) + return bytes(map(self._encode64, raw)) def encode_int24(self, value): """encodes 24-bit integer -> 4 char string""" @@ -831,7 +821,7 @@ class Base64Engine(object): (value>>12) & 0x3f, (value>>18) & 0x3f] if self.big: raw = reversed(raw) - return join_byte_elems(imap(self._encode64, raw)) + return bytes(map(self._encode64, raw)) def encode_int30(self, value): """decode 5 char string -> 30 bit integer""" @@ -862,7 +852,7 @@ class LazyBase64Engine(Base64Engine): def _lazy_init(self): args, kwds = self._lazy_opts - super(LazyBase64Engine, self).__init__(*args, **kwds) + super().__init__(*args, **kwds) del self._lazy_opts self.__class__ = Base64Engine diff --git a/passlib/utils/compat/__init__.py b/passlib/utils/compat/__init__.py index f6ead24..06bd266 100644 --- a/passlib/utils/compat/__init__.py +++ b/passlib/utils/compat/__init__.py @@ -7,14 +7,10 @@ # python version #------------------------------------------------------------------------ import sys -PY2 = sys.version_info < (3,0) -PY3 = sys.version_info >= (3,0) # make sure it's not an unsupported version, even if we somehow got this far -if sys.version_info < (2,6) or (3,0) <= sys.version_info < (3,2): - raise RuntimeError("Passlib requires Python 2.6, 2.7, or >= 3.2 (as of passlib 1.7)") - -PY26 = sys.version_info < (2,7) +if sys.version_info < (3, 5): + raise RuntimeError("Passlib requires Python >= 3.5 (as of passlib 1.8)") #------------------------------------------------------------------------ # python implementation @@ -26,18 +22,10 @@ PYPY = hasattr(sys, "pypy_version_info") if PYPY and sys.pypy_version_info < (2,0): raise RuntimeError("passlib requires pypy >= 2.0 (as of passlib 1.7)") -# e.g. '2.7.7\n[Pyston 0.5.1]' -# NOTE: deprecated support 2019-11 -PYSTON = "Pyston" in sys.version - #============================================================================= # common imports #============================================================================= import logging; log = logging.getLogger(__name__) -if PY3: - import builtins -else: - import __builtin__ as builtins def add_doc(obj, doc): """add docstring to an object""" @@ -47,38 +35,15 @@ def add_doc(obj, doc): # the default exported vars #============================================================================= __all__ = [ - # python versions - 'PY2', 'PY3', 'PY26', - - # io - 'BytesIO', 'StringIO', 'NativeStringIO', 'SafeConfigParser', - 'print_', - # type detection ## 'is_mapping', - 'int_types', 'num_types', - 'unicode_or_bytes_types', - 'native_string_types', + 'unicode_or_bytes', # unicode/bytes types & helpers - 'u', - 'unicode', - 'uascii_to_str', 'bascii_to_str', - 'str_to_uascii', 'str_to_bascii', + 'bascii_to_str', + 'str_to_bascii', 'join_unicode', 'join_bytes', - 'join_byte_values', 'join_byte_elems', - 'byte_elem_value', - 'iter_byte_values', - - # iteration helpers - 'irange', #'lrange', - 'imap', 'lmap', - 'iteritems', 'itervalues', - 'next', - - # collections - 'OrderedDict', # context helpers 'nullcontext', @@ -94,179 +59,47 @@ _lazy_attrs = dict() #============================================================================= # unicode & bytes types #============================================================================= -if PY3: - unicode = str - - # TODO: once we drop python 3.2 support, can use u'' again! - def u(s): - assert isinstance(s, str) - return s - - unicode_or_bytes_types = (str, bytes) - native_string_types = (unicode,) -else: - unicode = builtins.unicode - - def u(s): - assert isinstance(s, str) - return s.decode("unicode_escape") - - unicode_or_bytes_types = (basestring,) - native_string_types = (basestring,) - -# shorter preferred aliases -unicode_or_bytes = unicode_or_bytes_types -unicode_or_str = native_string_types - -# unicode -- unicode type, regardless of python version -# bytes -- bytes type, regardless of python version -# unicode_or_bytes_types -- types that text can occur in, whether encoded or not -# native_string_types -- types that native python strings (dict keys etc) can occur in. +#: alias for isinstance() tests to detect any string type +unicode_or_bytes = (str, bytes) #============================================================================= # unicode & bytes helpers #============================================================================= # function to join list of unicode strings -join_unicode = u('').join +join_unicode = u''.join # function to join list of byte strings join_bytes = b''.join -if PY3: - def uascii_to_str(s): - assert isinstance(s, unicode) - return s +if True: # legacy PY3 indent def bascii_to_str(s): assert isinstance(s, bytes) return s.decode("ascii") - def str_to_uascii(s): - assert isinstance(s, str) - return s - def str_to_bascii(s): assert isinstance(s, str) return s.encode("ascii") - join_byte_values = join_byte_elems = bytes - - def byte_elem_value(elem): - assert isinstance(elem, int) - return elem - - def iter_byte_values(s): - assert isinstance(s, bytes) - return s - def iter_byte_chars(s): assert isinstance(s, bytes) # FIXME: there has to be a better way to do this return (bytes([c]) for c in s) -else: - def uascii_to_str(s): - assert isinstance(s, unicode) - return s.encode("ascii") - - def bascii_to_str(s): - assert isinstance(s, bytes) - return s - - def str_to_uascii(s): - assert isinstance(s, str) - return s.decode("ascii") - - def str_to_bascii(s): - assert isinstance(s, str) - return s - - def join_byte_values(values): - return join_bytes(chr(v) for v in values) - - join_byte_elems = join_bytes - - byte_elem_value = ord - - def iter_byte_values(s): - assert isinstance(s, bytes) - return (ord(c) for c in s) - - def iter_byte_chars(s): - assert isinstance(s, bytes) - return s - -add_doc(uascii_to_str, "helper to convert ascii unicode -> native str") +# TODO: move docstrings to funcs... add_doc(bascii_to_str, "helper to convert ascii bytes -> native str") -add_doc(str_to_uascii, "helper to convert ascii native str -> unicode") add_doc(str_to_bascii, "helper to convert ascii native str -> bytes") -# join_byte_values -- function to convert list of ordinal integers to byte string. - -# join_byte_elems -- function to convert list of byte elements to byte string; -# i.e. what's returned by ``b('a')[0]``... -# this is b('a') under PY2, but 97 under PY3. - # byte_elem_value -- function to convert byte element to integer -- a noop under PY3 -add_doc(iter_byte_values, "iterate over byte string as sequence of ints 0-255") add_doc(iter_byte_chars, "iterate over byte string as sequence of 1-byte strings") #============================================================================= # numeric #============================================================================= -if PY3: - int_types = (int,) - num_types = (int, float) -else: - int_types = (int, long) - num_types = (int, long, float) - -#============================================================================= -# iteration helpers -# -# irange - range iterable / view (xrange under py2, range under py3) -# lrange - range list (range under py2, list(range()) under py3) -# -# imap - map to iterator -# lmap - map to list -#============================================================================= -if PY3: - irange = range - ##def lrange(*a,**k): - ## return list(range(*a,**k)) - - def lmap(*a, **k): - return list(map(*a,**k)) - imap = map - - def iteritems(d): - return d.items() - def itervalues(d): - return d.values() - - def nextgetter(obj): - return obj.__next__ - - izip = zip - -else: - irange = xrange - ##lrange = range - lmap = map - from itertools import imap, izip - - def iteritems(d): - return d.iteritems() - def itervalues(d): - return d.itervalues() - - def nextgetter(obj): - return obj.next - -add_doc(nextgetter, "return function that yields successive values from iterable") +num_types = (int, float) #============================================================================= # typing @@ -278,103 +111,10 @@ add_doc(nextgetter, "return function that yields successive values from iterable #============================================================================= # introspection #============================================================================= -if PY3: - method_function_attr = "__func__" -else: - method_function_attr = "im_func" def get_method_function(func): """given (potential) method, return underlying function""" - return getattr(func, method_function_attr, func) - -def get_unbound_method_function(func): - """given unbound method, return underlying function""" - return func if PY3 else func.__func__ - -def error_from(exc, # *, - cause=None): - """ - backward compat hack to suppress exception cause in python3.3+ - - one python < 3.3 support is dropped, can replace all uses with "raise exc from None" - """ - exc.__cause__ = cause - exc.__suppress_context__ = True - return exc - -# legacy alias -suppress_cause = error_from - -#============================================================================= -# input/output -#============================================================================= -if PY3: - _lazy_attrs = dict( - BytesIO="io.BytesIO", - UnicodeIO="io.StringIO", - NativeStringIO="io.StringIO", - SafeConfigParser="configparser.ConfigParser", - ) - - print_ = getattr(builtins, "print") - -else: - _lazy_attrs = dict( - BytesIO="cStringIO.StringIO", - UnicodeIO="StringIO.StringIO", - NativeStringIO="cStringIO.StringIO", - SafeConfigParser="ConfigParser.SafeConfigParser", - ) - - def print_(*args, **kwds): - """The new-style print function.""" - # extract kwd args - fp = kwds.pop("file", sys.stdout) - sep = kwds.pop("sep", None) - end = kwds.pop("end", None) - if kwds: - raise TypeError("invalid keyword arguments") - - # short-circuit if no target - if fp is None: - return - - # use unicode or bytes ? - want_unicode = isinstance(sep, unicode) or isinstance(end, unicode) or \ - any(isinstance(arg, unicode) for arg in args) - - # pick default end sequence - if end is None: - end = u("\n") if want_unicode else "\n" - elif not isinstance(end, unicode_or_bytes_types): - raise TypeError("end must be None or a string") - - # pick default separator - if sep is None: - sep = u(" ") if want_unicode else " " - elif not isinstance(sep, unicode_or_bytes_types): - raise TypeError("sep must be None or a string") - - # write to buffer - first = True - write = fp.write - for arg in args: - if first: - first = False - else: - write(sep) - if not isinstance(arg, basestring): - arg = str(arg) - write(arg) - write(end) - -#============================================================================= -# collections -#============================================================================= -if PY26: - _lazy_attrs['OrderedDict'] = 'passlib.utils.compat._ordered_dict.OrderedDict' -else: - _lazy_attrs['OrderedDict'] = 'collections.OrderedDict' + return getattr(func, "__func__", func) #============================================================================= # context managers diff --git a/passlib/utils/compat/_ordered_dict.py b/passlib/utils/compat/_ordered_dict.py deleted file mode 100644 index cfd766d..0000000 --- a/passlib/utils/compat/_ordered_dict.py +++ /dev/null @@ -1,242 +0,0 @@ -"""passlib.utils.compat._ordered_dict -- backport of collections.OrderedDict for py26 - -taken from stdlib-suggested recipe at http://code.activestate.com/recipes/576693/ - -this should be imported from passlib.utils.compat.OrderedDict, not here. -""" - -try: - from thread import get_ident as _get_ident -except ImportError: - from dummy_thread import get_ident as _get_ident - -class OrderedDict(dict): - """Dictionary that remembers insertion order""" - # An inherited dict maps keys to values. - # The inherited dict provides __getitem__, __len__, __contains__, and get. - # The remaining methods are order-aware. - # Big-O running times for all methods are the same as for regular dictionaries. - - # The internal self.__map dictionary maps keys to links in a doubly linked list. - # The circular doubly linked list starts and ends with a sentinel element. - # The sentinel element never gets deleted (this simplifies the algorithm). - # Each link is stored as a list of length three: [PREV, NEXT, KEY]. - - def __init__(self, *args, **kwds): - '''Initialize an ordered dictionary. Signature is the same as for - regular dictionaries, but keyword arguments are not recommended - because their insertion order is arbitrary. - - ''' - if len(args) > 1: - raise TypeError('expected at most 1 arguments, got %d' % len(args)) - try: - self.__root - except AttributeError: - self.__root = root = [] # sentinel node - root[:] = [root, root, None] - self.__map = {} - self.__update(*args, **kwds) - - def __setitem__(self, key, value, dict_setitem=dict.__setitem__): - 'od.__setitem__(i, y) <==> od[i]=y' - # Setting a new item creates a new link which goes at the end of the linked - # list, and the inherited dictionary is updated with the new key/value pair. - if key not in self: - root = self.__root - last = root[0] - last[1] = root[0] = self.__map[key] = [last, root, key] - dict_setitem(self, key, value) - - def __delitem__(self, key, dict_delitem=dict.__delitem__): - 'od.__delitem__(y) <==> del od[y]' - # Deleting an existing item uses self.__map to find the link which is - # then removed by updating the links in the predecessor and successor nodes. - dict_delitem(self, key) - link_prev, link_next, key = self.__map.pop(key) - link_prev[1] = link_next - link_next[0] = link_prev - - def __iter__(self): - 'od.__iter__() <==> iter(od)' - root = self.__root - curr = root[1] - while curr is not root: - yield curr[2] - curr = curr[1] - - def __reversed__(self): - 'od.__reversed__() <==> reversed(od)' - root = self.__root - curr = root[0] - while curr is not root: - yield curr[2] - curr = curr[0] - - def clear(self): - 'od.clear() -> None. Remove all items from od.' - try: - for node in self.__map.itervalues(): - del node[:] - root = self.__root - root[:] = [root, root, None] - self.__map.clear() - except AttributeError: - pass - dict.clear(self) - - def popitem(self, last=True): - '''od.popitem() -> (k, v), return and remove a (key, value) pair. - Pairs are returned in LIFO order if last is true or FIFO order if false. - - ''' - if not self: - raise KeyError('dictionary is empty') - root = self.__root - if last: - link = root[0] - link_prev = link[0] - link_prev[1] = root - root[0] = link_prev - else: - link = root[1] - link_next = link[1] - root[1] = link_next - link_next[0] = root - key = link[2] - del self.__map[key] - value = dict.pop(self, key) - return key, value - - # -- the following methods do not depend on the internal structure -- - - def keys(self): - 'od.keys() -> list of keys in od' - return list(self) - - def values(self): - 'od.values() -> list of values in od' - return [self[key] for key in self] - - def items(self): - 'od.items() -> list of (key, value) pairs in od' - return [(key, self[key]) for key in self] - - def iterkeys(self): - 'od.iterkeys() -> an iterator over the keys in od' - return iter(self) - - def itervalues(self): - 'od.itervalues -> an iterator over the values in od' - for k in self: - yield self[k] - - def iteritems(self): - 'od.iteritems -> an iterator over the (key, value) items in od' - for k in self: - yield (k, self[k]) - - def update(*args, **kwds): - '''od.update(E, **F) -> None. Update od from dict/iterable E and F. - - If E is a dict instance, does: for k in E: od[k] = E[k] - If E has a .keys() method, does: for k in E.keys(): od[k] = E[k] - Or if E is an iterable of items, does: for k, v in E: od[k] = v - In either case, this is followed by: for k, v in F.items(): od[k] = v - - ''' - if len(args) > 2: - raise TypeError('update() takes at most 2 positional ' - 'arguments (%d given)' % (len(args),)) - elif not args: - raise TypeError('update() takes at least 1 argument (0 given)') - self = args[0] - # Make progressively weaker assumptions about "other" - other = () - if len(args) == 2: - other = args[1] - if isinstance(other, dict): - for key in other: - self[key] = other[key] - elif hasattr(other, 'keys'): - for key in other.keys(): - self[key] = other[key] - else: - for key, value in other: - self[key] = value - for key, value in kwds.items(): - self[key] = value - - __update = update # let subclasses override update without breaking __init__ - - __marker = object() - - def pop(self, key, default=__marker): - '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value. - If key is not found, d is returned if given, otherwise KeyError is raised. - - ''' - if key in self: - result = self[key] - del self[key] - return result - if default is self.__marker: - raise KeyError(key) - return default - - def setdefault(self, key, default=None): - 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od' - if key in self: - return self[key] - self[key] = default - return default - - def __repr__(self, _repr_running={}): - 'od.__repr__() <==> repr(od)' - call_key = id(self), _get_ident() - if call_key in _repr_running: - return '...' - _repr_running[call_key] = 1 - try: - if not self: - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, self.items()) - finally: - del _repr_running[call_key] - - def __reduce__(self): - 'Return state information for pickling' - items = [[k, self[k]] for k in self] - inst_dict = vars(self).copy() - for k in vars(OrderedDict()): - inst_dict.pop(k, None) - if inst_dict: - return (self.__class__, (items,), inst_dict) - return self.__class__, (items,) - - def copy(self): - 'od.copy() -> a shallow copy of od' - return self.__class__(self) - - @classmethod - def fromkeys(cls, iterable, value=None): - '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S - and values equal to v (which defaults to None). - - ''' - d = cls() - for key in iterable: - d[key] = value - return d - - def __eq__(self, other): - '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive - while comparison to a regular mapping is order-insensitive. - - ''' - if isinstance(other, OrderedDict): - return len(self)==len(other) and self.items() == other.items() - return dict.__eq__(self, other) - - def __ne__(self, other): - return not self == other diff --git a/passlib/utils/decor.py b/passlib/utils/decor.py index 9041d5d..e1fd601 100644 --- a/passlib/utils/decor.py +++ b/passlib/utils/decor.py @@ -5,7 +5,6 @@ passlib.utils.decor -- helper decorators & properties # imports #============================================================================= # core -from __future__ import absolute_import, division, print_function import logging log = logging.getLogger(__name__) from functools import wraps, update_wrapper @@ -13,7 +12,6 @@ import types from warnings import warn # site # pkg -from passlib.utils.compat import PY3 # local __all__ = [ "classproperty", @@ -33,15 +31,12 @@ class classproperty(object): """Function decorator which acts like a combination of classmethod+property (limited to read-only properties)""" def __init__(self, func): - self.im_func = func + # XXX: rename to .fget to match property? + self.__func__ = func def __get__(self, obj, cls): - return self.im_func(cls) + return self.__func__(cls) - @property - def __func__(self): - """py3 compatible alias""" - return self.im_func class hybrid_method(object): """ @@ -50,16 +45,14 @@ class hybrid_method(object): """ def __init__(self, func): + # XXX: rename to .fget to match property? self.func = func update_wrapper(self, func) def __get__(self, obj, cls): if obj is None: obj = cls - if PY3: - return types.MethodType(self.func, obj) - else: - return types.MethodType(self.func, obj, cls) + return types.MethodType(self.func, obj) #============================================================================= # memoization @@ -104,13 +97,6 @@ class memoized_property(object): setattr(obj, self.__name__, value) return value - if not PY3: - - @property - def im_func(self): - """py2 alias""" - return self.__func__ - def clear_cache(self, obj): """ class-level helper to clear stored value (if any). @@ -174,8 +160,7 @@ def deprecated_function(msg=None, deprecated=None, removed=None, updoc=True, def build(func): is_classmethod = _is_method and isinstance(func, classmethod) if is_classmethod: - # NOTE: PY26 doesn't support "classmethod().__func__" directly... - func = func.__get__(None, type).__func__ + func = func.__func__ opts = dict( mod=func_module or func.__module__, name=func.__name__, diff --git a/passlib/utils/des.py b/passlib/utils/des.py index 034bfc4..1c407b9 100644 --- a/passlib/utils/des.py +++ b/passlib/utils/des.py @@ -17,30 +17,15 @@ warn("the 'passlib.utils.des' module has been relocated to 'passlib.crypto.des' from passlib.utils.decor import deprecated_function from passlib.crypto.des import expand_des_key, des_encrypt_block, des_encrypt_int_block -expand_des_key = deprecated_function(deprecated="1.7", removed="1.8", +expand_des_key = deprecated_function(deprecated="1.7", removed="2.0", replacement="passlib.crypto.des.expand_des_key")(expand_des_key) -des_encrypt_block = deprecated_function(deprecated="1.7", removed="1.8", +des_encrypt_block = deprecated_function(deprecated="1.7", removed="2.0", replacement="passlib.crypto.des.des_encrypt_block")(des_encrypt_block) -des_encrypt_int_block = deprecated_function(deprecated="1.7", removed="1.8", +des_encrypt_int_block = deprecated_function(deprecated="1.7", removed="2.0", replacement="passlib.crypto.des.des_encrypt_int_block")(des_encrypt_int_block) #============================================================================= -# deprecated functions -- not carried over to passlib.crypto.des -#============================================================================= -import struct -_unpack_uint64 = struct.Struct(">Q").unpack - -@deprecated_function(deprecated="1.6", removed="1.8", - replacement="passlib.crypto.des.des_encrypt_int_block()") -def mdes_encrypt_int_block(key, input, salt=0, rounds=1): # pragma: no cover -- deprecated & unused - if isinstance(key, bytes): - if len(key) == 7: - key = expand_des_key(key) - key = _unpack_uint64(key)[0] - return des_encrypt_int_block(key, input, salt, rounds) - -#============================================================================= # eof #============================================================================= diff --git a/passlib/utils/handlers.py b/passlib/utils/handlers.py index f8681fa..cea096c 100644 --- a/passlib/utils/handlers.py +++ b/passlib/utils/handlers.py @@ -2,7 +2,6 @@ #============================================================================= # imports #============================================================================= -from __future__ import with_statement # core import inspect import logging; log = logging.getLogger(__name__) @@ -27,9 +26,7 @@ from passlib.utils.binary import ( HEX_CHARS, UPPER_HEX_CHARS, LOWER_HEX_CHARS, ALL_BYTE_VALUES, ) -from passlib.utils.compat import join_byte_values, irange, u, native_string_types, \ - uascii_to_str, join_unicode, unicode, str_to_uascii, \ - join_unicode, unicode_or_bytes_types, PY2, int_types +from passlib.utils.compat import join_unicode, unicode_or_bytes from passlib.utils.decor import classproperty, deprecated_method # local __all__ = [ @@ -113,19 +110,19 @@ def extract_settings_kwds(handler, kwds): #============================================================================= # parsing helpers #============================================================================= -_UDOLLAR = u("$") -_UZERO = u("0") +_UDOLLAR = u"$" +_UZERO = u"0" def validate_secret(secret): """ensure secret has correct type & size""" - if not isinstance(secret, unicode_or_bytes_types): + if not isinstance(secret, unicode_or_bytes): raise exc.ExpectedStringError(secret, "secret") if len(secret) > MAX_PASSWORD_SIZE: raise exc.PasswordSizeError(MAX_PASSWORD_SIZE) def to_unicode_for_identify(hash): """convert hash to unicode for identify method""" - if isinstance(hash, unicode): + if isinstance(hash, str): return hash elif isinstance(hash, bytes): # try as utf-8, but if it fails, use foolproof latin-1, @@ -144,8 +141,8 @@ def parse_mc2(hash, prefix, sep=_UDOLLAR, handler=None): this expects a hash of the format :samp:`{prefix}{salt}[${checksum}]`, such as md5_crypt, and parses it into salt / checksum portions. - :arg hash: the hash to parse (bytes or unicode) - :arg prefix: the identifying prefix (unicode) + :arg hash: the hash to parse (bytes or str) + :arg prefix: the identifying prefix (str) :param sep: field separator (unicode, defaults to ``$``). :param handler: handler class to pass to error constructors. @@ -154,12 +151,12 @@ def parse_mc2(hash, prefix, sep=_UDOLLAR, handler=None): """ # detect prefix hash = to_unicode(hash, "ascii", "hash") - assert isinstance(prefix, unicode) + assert isinstance(prefix, str) if not hash.startswith(prefix): raise exc.InvalidHashError(handler) # parse 2-part hash or 1-part config string - assert isinstance(sep, unicode) + assert isinstance(sep, str) parts = hash[len(prefix):].split(sep) if len(parts) == 2: salt, chk = parts @@ -193,12 +190,12 @@ def parse_mc3(hash, prefix, sep=_UDOLLAR, rounds_base=10, """ # detect prefix hash = to_unicode(hash, "ascii", "hash") - assert isinstance(prefix, unicode) + assert isinstance(prefix, str) if not hash.startswith(prefix): raise exc.InvalidHashError(handler) # parse 3-part hash or 2-part config string - assert isinstance(sep, unicode) + assert isinstance(sep, str) parts = hash[len(prefix):].split(sep) if len(parts) == 3: rounds, salt, chk = parts @@ -266,7 +263,7 @@ def parse_int(source, base=10, default=None, param="value", handler=None): #============================================================================= # formatting helpers #============================================================================= -def render_mc2(ident, salt, checksum, sep=u("$")): +def render_mc2(ident, salt, checksum, sep=u"$"): """format hash using 2-part modular crypt format; inverse of parse_mc2() returns native string with format :samp:`{ident}{salt}[${checksum}]`, @@ -284,9 +281,9 @@ def render_mc2(ident, salt, checksum, sep=u("$")): parts = [ident, salt, sep, checksum] else: parts = [ident, salt] - return uascii_to_str(join_unicode(parts)) + return join_unicode(parts) -def render_mc3(ident, rounds, salt, checksum, sep=u("$"), rounds_base=10): +def render_mc3(ident, rounds, salt, checksum, sep=u"$", rounds_base=10): """format hash using 3-part modular crypt format; inverse of parse_mc3() returns native string with format :samp:`{ident}[{rounds}$]{salt}[${checksum}]`, @@ -303,17 +300,17 @@ def render_mc3(ident, rounds, salt, checksum, sep=u("$"), rounds_base=10): config or hash (native str) """ if rounds is None: - rounds = u('') + rounds = u'' elif rounds_base == 16: - rounds = u("%x") % rounds + rounds = u"%x" % rounds else: assert rounds_base == 10 - rounds = unicode(rounds) + rounds = str(rounds) if checksum: parts = [ident, rounds, sep, salt, sep, checksum] else: parts = [ident, rounds, sep, salt] - return uascii_to_str(join_unicode(parts)) + return join_unicode(parts) def mask_value(value, show=4, pct=0.125, char=u"*"): @@ -336,12 +333,12 @@ def mask_value(value, show=4, pct=0.125, char=u"*"): """ if value is None: return None - if not isinstance(value, unicode): + if not isinstance(value, str): if isinstance(value, bytes): from passlib.utils.binary import ab64_encode value = ab64_encode(value).decode("ascii") else: - value = unicode(value) + value = str(value) size = len(value) show = min(show, int(size * pct)) return value[:show] + char * (size - show) @@ -372,7 +369,7 @@ def norm_integer(handler, value, min=1, max=None, # * :returns: validated value """ # check type - if not isinstance(value, int_types): + if not isinstance(value, int): raise exc.ExpectedTypeError(value, "integer", param) # check minimum @@ -452,7 +449,7 @@ class TruncateMixin(MinimalHandler): @classmethod def using(cls, truncate_error=None, **kwds): - subcls = super(TruncateMixin, cls).using(**kwds) + subcls = super().using(**kwds) if truncate_error is not None: truncate_error = as_bool(truncate_error, param="truncate_error") if truncate_error is not None: @@ -618,7 +615,7 @@ class GenericHandler(MinimalHandler): #=================================================================== def __init__(self, checksum=None, use_defaults=False, **kwds): self.use_defaults = use_defaults - super(GenericHandler, self).__init__(**kwds) + super().__init__(**kwds) if checksum is not None: # XXX: do we need to set .relaxed for checksum coercion? self.checksum = self._norm_checksum(checksum) @@ -641,12 +638,12 @@ class GenericHandler(MinimalHandler): if not isinstance(checksum, bytes): raise exc.ExpectedTypeError(checksum, "bytes", "checksum") - elif not isinstance(checksum, unicode): + elif not isinstance(checksum, str): if isinstance(checksum, bytes) and relaxed: warn("checksum should be unicode, not bytes", PasslibHashWarning) checksum = checksum.decode("ascii") else: - raise exc.ExpectedTypeError(checksum, "unicode", "checksum") + raise exc.ExpectedTypeError(checksum, "str", "checksum") # check size cc = self.checksum_size @@ -713,8 +710,7 @@ class GenericHandler(MinimalHandler): :returns: hash string with salt & digest included. - should return native string type (ascii-bytes under python 2, - unicode under python 3) + should return native str. """ raise NotImplementedError("%s must implement from_string()" % (self.__class__,)) @@ -752,7 +748,7 @@ class GenericHandler(MinimalHandler): string, taking config from object state calc checksum implementations may assume secret is always - either unicode or bytes, checks are performed by verify/etc. + either str or bytes, checks are performed by verify/etc. """ raise NotImplementedError("%s must implement _calc_checksum()" % (self.__class__,)) @@ -906,7 +902,7 @@ class GenericHandler(MinimalHandler): def bitsize(cls, **kwds): """[experimental method] return info about bitsizes of hash""" try: - info = super(GenericHandler, cls).bitsize(**kwds) + info = super().bitsize(**kwds) except AttributeError: info = {} cc = ALL_BYTE_VALUES if cls._checksum_is_bytes else cls.checksum_chars @@ -940,7 +936,7 @@ class StaticHandler(GenericHandler): setting_kwds = () # optional constant prefix subclasses can specify - _hash_prefix = u("") + _hash_prefix = u"" @classmethod def from_string(cls, hash, **context): @@ -966,46 +962,7 @@ class StaticHandler(GenericHandler): return hash def to_string(self): - return uascii_to_str(self._hash_prefix + self.checksum) - - # per-subclass: stores dynamically created subclass used by _calc_checksum() stub - __cc_compat_hack = None - - def _calc_checksum(self, secret): - """given secret; calcuate and return encoded checksum portion of hash - string, taking config from object state - """ - # NOTE: prior to 1.6, StaticHandler required classes implement genhash - # instead of this method. so if we reach here, we try calling genhash. - # if that succeeds, we issue deprecation warning. if it fails, - # we'll just recurse back to here, but in a different instance. - # so before we call genhash, we create a subclass which handles - # throwing the NotImplementedError. - cls = self.__class__ - assert cls.__module__ != __name__ - wrapper_cls = cls.__cc_compat_hack - if wrapper_cls is None: - def inner(self, secret): - raise NotImplementedError("%s must implement _calc_checksum()" % - (cls,)) - wrapper_cls = cls.__cc_compat_hack = type(cls.__name__ + "_wrapper", - (cls,), dict(_calc_checksum=inner, __module__=cls.__module__)) - context = dict((k,getattr(self,k)) for k in self.context_kwds) - # NOTE: passing 'config=None' here even though not currently allowed by ifc, - # since it *is* allowed under the old 1.5 ifc we're checking for here. - try: - hash = wrapper_cls.genhash(secret, None, **context) - except TypeError as err: - if str(err) == "config must be string": - raise NotImplementedError("%s must implement _calc_checksum()" % - (cls,)) - else: - raise - warn("%r should be updated to implement StaticHandler._calc_checksum() " - "instead of StaticHandler.genhash(), support for the latter " - "style will be removed in Passlib 1.8" % cls, - DeprecationWarning) - return str_to_uascii(hash) + return self._hash_prefix + self.checksum #============================================================================= # GenericHandler mixin classes @@ -1016,7 +973,7 @@ class HasEncodingContext(GenericHandler): default_encoding = "utf-8" def __init__(self, encoding=None, **kwds): - super(HasEncodingContext, self).__init__(**kwds) + super().__init__(**kwds) self.encoding = encoding or self.default_encoding class HasUserContext(GenericHandler): @@ -1024,7 +981,7 @@ class HasUserContext(GenericHandler): context_kwds = ("user",) def __init__(self, user=None, **kwds): - super(HasUserContext, self).__init__(**kwds) + super().__init__(**kwds) self.user = user # XXX: would like to validate user input here, but calls to from_string() @@ -1033,16 +990,16 @@ class HasUserContext(GenericHandler): # wrap funcs to accept 'user' as positional arg for ease of use. @classmethod def hash(cls, secret, user=None, **context): - return super(HasUserContext, cls).hash(secret, user=user, **context) + return super().hash(secret, user=user, **context) @classmethod def verify(cls, secret, hash, user=None, **context): - return super(HasUserContext, cls).verify(secret, hash, user=user, **context) + return super().verify(secret, hash, user=user, **context) @deprecated_method(deprecated="1.7", removed="2.0") @classmethod def genhash(cls, secret, config, user=None, **context): - return super(HasUserContext, cls).genhash(secret, config, user=user, **context) + return super().genhash(secret, config, user=user, **context) # XXX: how to guess the entropy of a username? # most of these hashes are for a system (e.g. Oracle) @@ -1051,7 +1008,7 @@ class HasUserContext(GenericHandler): # need to find good reference about this. ##@classmethod ##def bitsize(cls, **kwds): - ## info = super(HasUserContext, cls).bitsize(**kwds) + ## info = super().bitsize(**kwds) ## info['user'] = xxx ## return info @@ -1097,7 +1054,7 @@ class HasManyIdents(GenericHandler): #=================================================================== # class attrs #=================================================================== - default_ident = None # should be unicode + default_ident = None # should be str ident_values = None # should be list of unicode strings ident_aliases = None # should be dict of unicode -> unicode # NOTE: any aliases provided to norm_ident() as bytes @@ -1134,7 +1091,7 @@ class HasManyIdents(GenericHandler): default_ident = ident # create subclass - subcls = super(HasManyIdents, cls).using(**kwds) + subcls = super().using(**kwds) # add custom default ident # (NOTE: creates instance to run value through _norm_ident()) @@ -1146,7 +1103,7 @@ class HasManyIdents(GenericHandler): # init #=================================================================== def __init__(self, ident=None, **kwds): - super(HasManyIdents, self).__init__(**kwds) + super().__init__(**kwds) # init ident if ident is not None: @@ -1333,12 +1290,12 @@ class HasSalt(GenericHandler): default_salt_size = salt_size # generate new subclass - subcls = super(HasSalt, cls).using(**kwds) + subcls = super().using(**kwds) # replace default_rounds relaxed = kwds.get("relaxed") if default_salt_size is not None: - if isinstance(default_salt_size, native_string_types): + if isinstance(default_salt_size, str): default_salt_size = int(default_salt_size) subcls.default_salt_size = subcls._clip_to_valid_salt_size(default_salt_size, param="salt_size", @@ -1406,7 +1363,7 @@ class HasSalt(GenericHandler): # init #=================================================================== def __init__(self, salt=None, **kwds): - super(HasSalt, self).__init__(**kwds) + super().__init__(**kwds) if salt is not None: salt = self._parse_salt(salt) elif self.use_defaults: @@ -1446,12 +1403,12 @@ class HasSalt(GenericHandler): if not isinstance(salt, bytes): raise exc.ExpectedTypeError(salt, "bytes", "salt") else: - if not isinstance(salt, unicode): + if not isinstance(salt, str): # NOTE: allowing bytes under py2 so salt can be native str. - if isinstance(salt, bytes) and (PY2 or relaxed): + if relaxed and isinstance(salt, bytes): salt = salt.decode("ascii") else: - raise exc.ExpectedTypeError(salt, "unicode", "salt") + raise exc.ExpectedTypeError(salt, "str", "salt") # check charset sc = cls.salt_chars @@ -1495,7 +1452,7 @@ class HasSalt(GenericHandler): @classmethod def bitsize(cls, salt_size=None, **kwds): """[experimental method] return info about bitsizes of hash""" - info = super(HasSalt, cls).bitsize(**kwds) + info = super().bitsize(**kwds) if salt_size is None: salt_size = cls.default_salt_size # FIXME: this may overestimate size due to padding bits @@ -1650,7 +1607,7 @@ class HasRounds(GenericHandler): default_rounds = rounds # generate new subclass - subcls = super(HasRounds, cls).using(**kwds) + subcls = super().using(**kwds) # replace min_desired_rounds relaxed = kwds.get("relaxed") @@ -1659,7 +1616,7 @@ class HasRounds(GenericHandler): min_desired_rounds = cls.min_desired_rounds else: explicit_min_rounds = True - if isinstance(min_desired_rounds, native_string_types): + if isinstance(min_desired_rounds, str): min_desired_rounds = int(min_desired_rounds) subcls.min_desired_rounds = subcls._norm_rounds(min_desired_rounds, param="min_desired_rounds", @@ -1669,7 +1626,7 @@ class HasRounds(GenericHandler): if max_desired_rounds is None: max_desired_rounds = cls.max_desired_rounds else: - if isinstance(max_desired_rounds, native_string_types): + if isinstance(max_desired_rounds, str): max_desired_rounds = int(max_desired_rounds) if min_desired_rounds and max_desired_rounds < min_desired_rounds: msg = "%s: max_desired_rounds (%r) below min_desired_rounds (%r)" % \ @@ -1685,7 +1642,7 @@ class HasRounds(GenericHandler): # replace default_rounds if default_rounds is not None: - if isinstance(default_rounds, native_string_types): + if isinstance(default_rounds, str): default_rounds = int(default_rounds) if min_desired_rounds and default_rounds < min_desired_rounds: raise ValueError("%s: default_rounds (%r) below min_desired_rounds (%r)" % @@ -1703,7 +1660,7 @@ class HasRounds(GenericHandler): # replace / set vary_rounds if vary_rounds is not None: - if isinstance(vary_rounds, native_string_types): + if isinstance(vary_rounds, str): if vary_rounds.endswith("%"): vary_rounds = float(vary_rounds[:-1]) * 0.01 elif "." in vary_rounds: @@ -1782,7 +1739,7 @@ class HasRounds(GenericHandler): vary_rounds = int(default_rounds * vary_rounds) # calculate bounds based on default_rounds +/- vary_rounds - assert vary_rounds >= 0 and isinstance(vary_rounds, int_types) + assert vary_rounds >= 0 and isinstance(vary_rounds, int) lower = linear_to_native(default_rounds - vary_rounds, False) upper = linear_to_native(default_rounds + vary_rounds, True) return cls._clip_to_desired_rounds(lower), cls._clip_to_desired_rounds(upper) @@ -1791,7 +1748,7 @@ class HasRounds(GenericHandler): # init #=================================================================== def __init__(self, rounds=None, **kwds): - super(HasRounds, self).__init__(**kwds) + super().__init__(**kwds) if rounds is not None: rounds = self._parse_rounds(rounds) elif self.use_defaults: @@ -1873,7 +1830,7 @@ class HasRounds(GenericHandler): max_desired_rounds = self.max_desired_rounds if max_desired_rounds and self.rounds > max_desired_rounds: return True - return super(HasRounds, self)._calc_needs_update(**kwds) + return super()._calc_needs_update(**kwds) #=================================================================== # experimental methods @@ -1881,7 +1838,7 @@ class HasRounds(GenericHandler): @classmethod def bitsize(cls, rounds=None, vary_rounds=.1, **kwds): """[experimental method] return info about bitsizes of hash""" - info = super(HasRounds, cls).bitsize(**kwds) + info = super().bitsize(**kwds) # NOTE: this essentially estimates how many bits of "salt" # can be added by varying the rounds value just a little bit. if cls.rounds_cost != "log2": @@ -1930,9 +1887,9 @@ class ParallelismMixin(GenericHandler): @classmethod def using(cls, parallelism=None, **kwds): - subcls = super(ParallelismMixin, cls).using(**kwds) + subcls = super().using(**kwds) if parallelism is not None: - if isinstance(parallelism, native_string_types): + if isinstance(parallelism, str): parallelism = int(parallelism) subcls.parallelism = subcls._norm_parallelism(parallelism, relaxed=kwds.get("relaxed")) return subcls @@ -1941,7 +1898,7 @@ class ParallelismMixin(GenericHandler): # init #=================================================================== def __init__(self, parallelism=None, **kwds): - super(ParallelismMixin, self).__init__(**kwds) + super().__init__(**kwds) # init parallelism if parallelism is None: @@ -1965,7 +1922,7 @@ class ParallelismMixin(GenericHandler): # XXX: for now, marking all hashes which don't have matching parallelism setting if self.parallelism != type(self).parallelism: return True - return super(ParallelismMixin, self)._calc_needs_update(**kwds) + return super()._calc_needs_update(**kwds) #=================================================================== # eoc @@ -2308,7 +2265,7 @@ class SubclassBackendMixin(BackendMixin): @classmethod def _set_backend(cls, name, dryrun): # invoke backend loader (will throw error if fails) - super(SubclassBackendMixin, cls)._set_backend(name, dryrun) + super()._set_backend(name, dryrun) # sanity check call args (should trust .set_backend, but will really # foul things up if this isn't the owner) @@ -2486,7 +2443,7 @@ class PrefixWrapper(object): #: list of attributes which should be cloned by .using() _using_clone_attrs = () - def __init__(self, name, wrapped, prefix=u(''), orig_prefix=u(''), lazy=False, + def __init__(self, name, wrapped, prefix=u'', orig_prefix=u'', lazy=False, doc=None, ident=None): self.name = name if isinstance(prefix, bytes): @@ -2652,7 +2609,7 @@ class PrefixWrapper(object): if not hash.startswith(orig_prefix): raise exc.InvalidHashError(self.wrapped) wrapped = self.prefix + hash[len(orig_prefix):] - return uascii_to_str(wrapped) + return wrapped #: set by _using(), helper for test harness' handler_derived_from() _derived_from = None diff --git a/passlib/utils/pbkdf2.py b/passlib/utils/pbkdf2.py index 273143b..d9bd083 100644 --- a/passlib/utils/pbkdf2.py +++ b/passlib/utils/pbkdf2.py @@ -6,20 +6,14 @@ maybe rename to "kdf" since it's getting more key derivation functions added. #============================================================================= # imports #============================================================================= -from __future__ import division # core import logging; log = logging.getLogger(__name__) # site # pkg from passlib.exc import ExpectedTypeError -from passlib.utils.decor import deprecated_function -from passlib.utils.compat import native_string_types -from passlib.crypto.digest import norm_hash_name, lookup_hash, pbkdf1 as _pbkdf1, pbkdf2_hmac, compile_hmac +from passlib.crypto.digest import lookup_hash, pbkdf1 as _pbkdf1, pbkdf2_hmac, compile_hmac # local __all__ = [ - # hash utils - "norm_hash_name", - # prf utils "get_prf", @@ -38,13 +32,6 @@ warn("the module 'passlib.utils.pbkdf2' is deprecated as of Passlib 1.7, " DeprecationWarning) #============================================================================= -# hash helpers -#============================================================================= - -norm_hash_name = deprecated_function(deprecated="1.7", removed="1.8", func_module=__name__, - replacement="passlib.crypto.digest.norm_hash_name")(norm_hash_name) - -#============================================================================= # prf lookup #============================================================================= @@ -97,7 +84,7 @@ def get_prf(name): global _prf_cache if name in _prf_cache: return _prf_cache[name] - if isinstance(name, native_string_types): + if isinstance(name, str): if not name.startswith(_HMAC_PREFIXES): raise ValueError("unknown prf algorithm: %r" % (name,)) digest = lookup_hash(name[5:]).name @@ -183,7 +170,7 @@ def pbkdf2(secret, salt, rounds, keylen=None, prf="hmac-sha1"): This has been deprecated in favor of :func:`passlib.crypto.digest.pbkdf2_hmac`, and will be removed in Passlib 2.0. *Note the call signature has changed.* """ - if callable(prf) or (isinstance(prf, native_string_types) and not prf.startswith(_HMAC_PREFIXES)): + if callable(prf) or (isinstance(prf, str) and not prf.startswith(_HMAC_PREFIXES)): raise NotImplementedError("non-HMAC prfs are not supported as of Passlib 1.7") digest = prf[5:] return pbkdf2_hmac(digest, secret, salt, rounds, keylen) diff --git a/passlib/win32.py b/passlib/win32.py deleted file mode 100644 index 223dd6c..0000000 --- a/passlib/win32.py +++ /dev/null @@ -1,68 +0,0 @@ -"""passlib.win32 - MS Windows support - DEPRECATED, WILL BE REMOVED IN 1.8 - -the LMHASH and NTHASH algorithms are used in various windows related contexts, -but generally not in a manner compatible with how passlib is structured. - -in particular, they have no identifying marks, both being -32 bytes of binary data. thus, they can't be easily identified -in a context with other hashes, so a CryptHandler hasn't been defined for them. - -this module provided two functions to aid in any use-cases which exist. - -.. warning:: - - these functions should not be used for new code unless an existing - system requires them, they are both known broken, - and are beyond insecure on their own. - -.. autofunction:: raw_lmhash -.. autofunction:: raw_nthash - -See also :mod:`passlib.hash.nthash`. -""" - -from warnings import warn -warn("the 'passlib.win32' module is deprecated, and will be removed in " - "passlib 1.8; please use the 'passlib.hash.nthash' and " - "'passlib.hash.lmhash' classes instead.", - DeprecationWarning) - -#============================================================================= -# imports -#============================================================================= -# core -from binascii import hexlify -# site -# pkg -from passlib.utils.compat import unicode -from passlib.crypto.des import des_encrypt_block -from passlib.hash import nthash -# local -__all__ = [ - "nthash", - "raw_lmhash", - "raw_nthash", -] -#============================================================================= -# helpers -#============================================================================= -LM_MAGIC = b"KGS!@#$%" - -raw_nthash = nthash.raw_nthash - -def raw_lmhash(secret, encoding="ascii", hex=False): - """encode password using des-based LMHASH algorithm; returns string of raw bytes, or unicode hex""" - # NOTE: various references say LMHASH uses the OEM codepage of the host - # for its encoding. until a clear reference is found, - # as well as a path for getting the encoding, - # letting this default to "ascii" to prevent incorrect hashes - # from being made w/o user explicitly choosing an encoding. - if isinstance(secret, unicode): - secret = secret.encode(encoding) - ns = secret.upper()[:14] + b"\x00" * (14-len(secret)) - out = des_encrypt_block(ns[:7], LM_MAGIC) + des_encrypt_block(ns[7:], LM_MAGIC) - return hexlify(out).decode("ascii") if hex else out - -#============================================================================= -# eoc -#============================================================================= @@ -64,6 +64,9 @@ opts = dict( ], }, + # NOTE: 'python_requires' should be kept in sync w/ passlib.utils.compat's version check. + python_requires='>=3.5', + #================================================================== # details #================================================================== @@ -71,7 +74,7 @@ opts = dict( "comprehensive password hashing framework supporting over 30 schemes", long_description="""\ -Passlib is a password hashing library for Python 2 & 3, which provides +Passlib is a password hashing library for Python 3, which provides cross-platform implementations of over 30 password hashing algorithms, as well as a framework for managing existing password hashes. It's designed to be useful for a wide range of tasks, from verifying a hash found in /etc/shadow, to @@ -103,12 +106,7 @@ Intended Audience :: Developers License :: OSI Approved :: BSD License Natural Language :: English Operating System :: OS Independent -Programming Language :: Python :: 2 -Programming Language :: Python :: 2.6 -Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 -Programming Language :: Python :: 3.3 -Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 @@ -47,7 +47,7 @@ envlist = # TODO: would like to 'default-pyston' but doesnt quite work # TODO: also add default-jython27 # NOTE: removed 2.6 & 3.3 as of 2019-11, tox+pip no longer work for these versions. - default-py{27,34,35,36,37,38,39,py,py3}, + default-py{35,36,37,38,39,py3}, # pbkdf2 backend testing # NOTE: 'hashlib' takes priority under py34+ @@ -55,36 +55,30 @@ envlist = # 'unpack' used for py2 ## pdbkf2-fastpbkdf2-py{2,3}, # tested by default config pbkdf2-hashlib-py{3,py3}, - pbkdf2-unpack-py{2,py}, pbkdf2-frombytes-py{3,py3}, # bcrypt backend testing (bcrypt cffi tested by default test) - # NOTE: 'other' checks bcryptor & py-bcrypt ## bcrypthash-bcrypt-py{2,3,py,py3}, # tested by default config - bcrypthash-other-py{2,3} - bcrypthash-{builtin,disabled}-py{2,3,py,py3} + bcrypthash-{builtin,disabled}-py{3,py3} # scrypt backend testing (builtin backend tested by default test) # XXX: 'scrypt' not compatible w/ pypy, or would include this under default. # could still do that for all but pypy, and do special test for builtin. - scrypthash-scrypt-py{2,3}, + scrypthash-scrypt-py3, ## scrypthash-stdlib-py{3}, # will only work for py36+ && openssl 1.1+ ## scrypthash-builtin-py{2,3,py,py3}, # tested by default config # argon2 backend testing (argon2_cffi tested by default test) ## argon2hash-argon2cffi-py{2,3,py,py3} # tested by default config - argon2hash-argon2pure-py{2,3,py,py3}, + argon2hash-argon2pure-py{3,py3}, # django tests # NOTE: django distributes it'a tests as part of source, not the package, so for full # integration tests to run, caller must provide a copy of the latest django source, # and set the env var PASSLIB_TESTS_DJANGO_SOURCE_PATH to point to it. - # NOTE: django 2.0 dropped python 2 support, so not including that in matrix. # django support roadmap -- https://www.djangoproject.com/download/ # django python versions -- https://docs.djangoproject.com/en/3.1/faq/install/#what-python-version-can-i-use-with-django - django-dj{1x,18}-wdeps-py{2,3}, - django-dj{Latest,31,30,22,21,20}-wdeps-py3, - django-dj{1x}-nodeps-py2, + django-dj{Latest,31,30,22,21,20,1x,18}-wdeps-py3, django-dj{Latest}-nodeps-py3, # other tests @@ -95,24 +89,15 @@ envlist = #=========================================================================== [testenv] basepython = - py2: python2 - py26: python2.6 - py27: python2.7 - py3: python3 - py33: python3.3 - py34: python3.4 py35: python3.5 py36: python3.6 py37: python3.7 py38: python3.8 py39: python3.9 - pypy: pypy pypy3: pypy3 - jython27: jython2.7 - passenv = PASSLIB_TEST_MODE PASSLIB_TESTS_DJANGO_SOURCE_PATH @@ -126,7 +111,7 @@ setenv = bcrypthash-disabled: PASSLIB_TEST_MODE = quick # option that depends on rednose (see below) - !py33-!py34: HIDE_SKIPS = --hide-skips + HIDE_SKIPS = --hide-skips # nose option fragments with_coverage: TEST_COVER_OPTS = --with-xunit --with-coverage --cover-xml --cover-package passlib @@ -151,10 +136,9 @@ commands = deps = # common nose - !py33-!py34: rednose + rednose coverage randomize - unittest2 # totp helper tests # NOTE: cryptography requires python-dev, libffi-dev, libssl-dev @@ -172,8 +156,6 @@ deps = # NOTE: bcrypt10 env disabled, just used to check legacy issues ## bcrypthash-bcrypt10: bcrypt<1.1 default,bcrypthash-bcrypt: bcrypt - bcrypthash-other-py{2,26,27}: bcryptor - bcrypthash-other: py-bcrypt # scrypt backend tests # XXX: would test 'scrypt' under default, but not compatible w/ pypy, @@ -200,6 +182,7 @@ deps = # django-nodeps -- would like to use this as negative dependancy for 'bcrypt' instead # needed by django's internal tests + # XXX: does django still need this as of py35? django: mock #=========================================================================== |