summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2012-02-08 23:22:09 -0500
committerEli Collins <elic@assurancetechnologies.com>2012-02-08 23:22:09 -0500
commitbf927985d1fe8df1b6a1cb9db4c314bf4c4c13af (patch)
tree4f40525d27469277f8c82c042ad0ebf404c02bbf
parent098f270258d6991ec0c74b63783cc0fcf3eecab9 (diff)
downloadpasslib-bf927985d1fe8df1b6a1cb9db4c314bf4c4c13af.tar.gz
documentation updates for last two commits
-rw-r--r--CHANGES16
-rw-r--r--docs/conf.py3
-rw-r--r--docs/lib/passlib.hash.apr_md5_crypt.rst2
-rw-r--r--docs/lib/passlib.hash.bigcrypt.rst2
-rw-r--r--docs/lib/passlib.hash.bsdi_crypt.rst2
-rw-r--r--docs/lib/passlib.hash.crypt16.rst2
-rw-r--r--docs/lib/passlib.hash.des_crypt.rst2
-rw-r--r--docs/lib/passlib.hash.md5_crypt.rst2
-rw-r--r--docs/lib/passlib.hash.oracle11.rst2
-rw-r--r--docs/lib/passlib.hash.phpass.rst2
-rw-r--r--docs/lib/passlib.hash.scram.rst2
-rw-r--r--docs/lib/passlib.hash.sha1_crypt.rst2
-rw-r--r--docs/lib/passlib.hash.sha256_crypt.rst2
-rw-r--r--docs/lib/passlib.hash.sha512_crypt.rst2
-rw-r--r--docs/lib/passlib.hash.sun_md5_crypt.rst2
-rw-r--r--docs/lib/passlib.utils.handlers.rst9
-rw-r--r--docs/lib/passlib.utils.rst9
-rw-r--r--docs/password_hash_api.rst85
-rw-r--r--passlib/tests/utils.py5
-rw-r--r--passlib/utils/handlers.py193
20 files changed, 185 insertions, 161 deletions
diff --git a/CHANGES b/CHANGES
index 2b94b98..a0171db 100644
--- a/CHANGES
+++ b/CHANGES
@@ -17,7 +17,7 @@ Release History
* Fixed rare ``'NoneType' object has no attribute 'decode'`` error
that sometimes occurred on platforms with a deviant implementation
- of :func:`!os_crypt`.
+ of :func:`!os_crypt`.
CryptContext
@@ -34,7 +34,6 @@ Release History
operations should all have much shorter code-paths.
* Config parsing now done with :class:`SafeConfigParser`.
-
:meth:`CryptPolicy.from_path` and :meth:`CryptPolicy.from_string`
previously used :class:`!ConfigParser` interpolation.
Release 1.5 switched to :class:`SafeConfigParser`,
@@ -47,6 +46,19 @@ Release History
.. currentmodule:: passlib.utils.handlers
+ * :class:`~passlib.utils.handlers.GenericHandler` and related mixins
+ changed in backward-incompatible way: the ``strict`` keyword
+ was removed. :class:`!GenericHandler` now defaults to a behavior
+ which matches ``strict=True``: the constructor strictly requires
+ all values be specified, and that all values be within correct bounds.
+ The new keywords ``use_defaults`` and ``relaxed`` can be used
+ to disable these two requirements, respectively.
+
+ * :class:`~passlib.utils.handlers.GenericHandler` and related mixins
+ changed in backward-incompatible way: the :samp:`norm_{xxx}`
+ classmethods have been renamed to :samp:`_norm_{xxx}`, and turned
+ into instance methods.
+
* Calls to :meth:`HasManyBackends.set_backend`
should now use the string ``"any"`` instead of the value ``None``.
``None`` was deprecated in release 1.5, and is no longer supported.
diff --git a/docs/conf.py b/docs/conf.py
index 41da9ad..6e0d6d1 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -100,7 +100,8 @@ version = csp.get_version(release)
# directories to ignore when looking for source files.
exclude_patterns = [
#disabling documentation of this until module is more mature.
- "lib/passlib.ext.django.rst"
+ "lib/passlib.ext.django.rst",
+ "lib/passlib.utils.compat.rst",
]
# The reST default role (used for this markup: `text`) to use for all documents.
diff --git a/docs/lib/passlib.hash.apr_md5_crypt.rst b/docs/lib/passlib.hash.apr_md5_crypt.rst
index 6ae5361..ee4ac73 100644
--- a/docs/lib/passlib.hash.apr_md5_crypt.rst
+++ b/docs/lib/passlib.hash.apr_md5_crypt.rst
@@ -20,7 +20,7 @@ see that class for details.
Interface
=========
-.. autoclass:: apr_md5_crypt(checksum=None, salt=None, strict=False)
+.. autoclass:: apr_md5_crypt()
Format & Algorithm
==================
diff --git a/docs/lib/passlib.hash.bigcrypt.rst b/docs/lib/passlib.hash.bigcrypt.rst
index e16c261..5f2429e 100644
--- a/docs/lib/passlib.hash.bigcrypt.rst
+++ b/docs/lib/passlib.hash.bigcrypt.rst
@@ -20,7 +20,7 @@ This class can be used in exactly the same manner as :class:`~passlib.hash.des_c
Interface
=========
-.. autoclass:: bigcrypt(checksum=None, salt=None, strict=False)
+.. autoclass:: bigcrypt()
Format
======
diff --git a/docs/lib/passlib.hash.bsdi_crypt.rst b/docs/lib/passlib.hash.bsdi_crypt.rst
index 6a60f79..99e7231 100644
--- a/docs/lib/passlib.hash.bsdi_crypt.rst
+++ b/docs/lib/passlib.hash.bsdi_crypt.rst
@@ -35,7 +35,7 @@ This class can be used directly as follows::
Interface
=========
-.. autoclass:: bsdi_crypt(checksum=None, salt=None, rounds=None, strict=False)
+.. autoclass:: bsdi_crypt()
Format
======
diff --git a/docs/lib/passlib.hash.crypt16.rst b/docs/lib/passlib.hash.crypt16.rst
index 2cdd5eb..43f63c6 100644
--- a/docs/lib/passlib.hash.crypt16.rst
+++ b/docs/lib/passlib.hash.crypt16.rst
@@ -25,7 +25,7 @@ This class can be used in exactly the same manner as :class:`~passlib.hash.des_c
Interface
=========
-.. autoclass:: crypt16(checksum=None, salt=None, strict=False)
+.. autoclass:: crypt16()
Format
======
diff --git a/docs/lib/passlib.hash.des_crypt.rst b/docs/lib/passlib.hash.des_crypt.rst
index ba5202e..1c808c5 100644
--- a/docs/lib/passlib.hash.des_crypt.rst
+++ b/docs/lib/passlib.hash.des_crypt.rst
@@ -34,7 +34,7 @@ This class can be used directly as follows::
Interface
=========
-.. autoclass:: des_crypt(checksum=None, salt=None, strict=False)
+.. autoclass:: des_crypt()
Format
======
diff --git a/docs/lib/passlib.hash.md5_crypt.rst b/docs/lib/passlib.hash.md5_crypt.rst
index a75187f..538ced5 100644
--- a/docs/lib/passlib.hash.md5_crypt.rst
+++ b/docs/lib/passlib.hash.md5_crypt.rst
@@ -33,7 +33,7 @@ PassLib provides an md5_crypt class, which can be can be used directly as follow
Interface
=========
-.. autoclass:: md5_crypt(checksum=None, salt=None, strict=False)
+.. autoclass:: md5_crypt()
Format
======
diff --git a/docs/lib/passlib.hash.oracle11.rst b/docs/lib/passlib.hash.oracle11.rst
index c879bc0..9f5ea02 100644
--- a/docs/lib/passlib.hash.oracle11.rst
+++ b/docs/lib/passlib.hash.oracle11.rst
@@ -38,7 +38,7 @@ PassLib provides an oracle11 class, which can be can be used directly as follows
Interface
=========
-.. autoclass:: oracle11(checksum=None, salt=None, strict=False)
+.. autoclass:: oracle11()
Format & Algorithm
==================
diff --git a/docs/lib/passlib.hash.phpass.rst b/docs/lib/passlib.hash.phpass.rst
index 20a3bb0..ee5c036 100644
--- a/docs/lib/passlib.hash.phpass.rst
+++ b/docs/lib/passlib.hash.phpass.rst
@@ -21,7 +21,7 @@ this scheme is used in exactly the same way as :doc:`bcrypt <passlib.hash.bcrypt
Interface
=========
-.. autoclass:: phpass(checksum=None, salt=None, rounds=None, strict=False)
+.. autoclass:: phpass()
Format
==================
diff --git a/docs/lib/passlib.hash.scram.rst b/docs/lib/passlib.hash.scram.rst
index fff87bf..397f743 100644
--- a/docs/lib/passlib.hash.scram.rst
+++ b/docs/lib/passlib.hash.scram.rst
@@ -94,7 +94,7 @@ for SCRAM-specific actions::
Interface
=========
-.. autoclass:: scram(algs=None, salt=None, rounds=None, strict=False)
+.. autoclass:: scram()
.. rst-class:: html-toggle
diff --git a/docs/lib/passlib.hash.sha1_crypt.rst b/docs/lib/passlib.hash.sha1_crypt.rst
index fa80a6b..639429d 100644
--- a/docs/lib/passlib.hash.sha1_crypt.rst
+++ b/docs/lib/passlib.hash.sha1_crypt.rst
@@ -15,7 +15,7 @@ this scheme is used in exactly the same way as :doc:`sha512_crypt <passlib.hash.
Functions
=========
-.. autoclass:: sha1_crypt(checksum=None, salt=None, rounds=None, strict=False)
+.. autoclass:: sha1_crypt()
Format
======
diff --git a/docs/lib/passlib.hash.sha256_crypt.rst b/docs/lib/passlib.hash.sha256_crypt.rst
index 2ae3243..9b7adce 100644
--- a/docs/lib/passlib.hash.sha256_crypt.rst
+++ b/docs/lib/passlib.hash.sha256_crypt.rst
@@ -17,7 +17,7 @@ This class can be used in exactly the same manner as :class:`~passlib.hash.sha51
Interface
=========
-.. autoclass:: sha256_crypt(checksum=None, salt=None, rounds=None, strict=False)
+.. autoclass:: sha256_crypt()
Format & Algorithm
==================
diff --git a/docs/lib/passlib.hash.sha512_crypt.rst b/docs/lib/passlib.hash.sha512_crypt.rst
index f9e438c..963c61f 100644
--- a/docs/lib/passlib.hash.sha512_crypt.rst
+++ b/docs/lib/passlib.hash.sha512_crypt.rst
@@ -42,7 +42,7 @@ This class can be used directly as follows::
Interface
=========
-.. autoclass:: sha512_crypt(checksum=None, salt=None, rounds=None, strict=False)
+.. autoclass:: sha512_crypt()
Format & Algorithm
==================
diff --git a/docs/lib/passlib.hash.sun_md5_crypt.rst b/docs/lib/passlib.hash.sun_md5_crypt.rst
index b3b891a..2a5cf4b 100644
--- a/docs/lib/passlib.hash.sun_md5_crypt.rst
+++ b/docs/lib/passlib.hash.sun_md5_crypt.rst
@@ -25,7 +25,7 @@ as :doc:`SHA-512 Crypt <passlib.hash.sha512_crypt>`.
Interface
=========
-.. autoclass:: sun_md5_crypt(checksum=None, salt=None, rounds=None, bare_salt=False, strict=False)
+.. autoclass:: sun_md5_crypt()
Format
======
diff --git a/docs/lib/passlib.utils.handlers.rst b/docs/lib/passlib.utils.handlers.rst
index 301448c..385e5a2 100644
--- a/docs/lib/passlib.utils.handlers.rst
+++ b/docs/lib/passlib.utils.handlers.rst
@@ -14,6 +14,11 @@
definitely need to be rewritten for clarity. They are not yet
organized, and may leave out some important details.
+.. note::
+
+ Since this module is primarily a support module used internally
+ by Passlib, it's interface may change slightly between major releases.
+
Implementing Custom Handlers
============================
All that is required in order to write a custom handler that will work with
@@ -45,7 +50,7 @@ workflow for hashes is some combination of the following:
1. parse hash into constituent parts - performed by :meth:`~GenericHandler.from_string`.
2. validate constituent parts - performed by :class:`!GenericHandler`'s constructor,
- and the normalization functions such as :meth:`~GenericHandler.norm_checksum` and :meth:`~HasSalt.norm_salt`
+ and the normalization functions such as :meth:`~GenericHandler._norm_checksum` and :meth:`~HasSalt._norm_salt`
which are provided by it's related mixin classes.
3. calculate the raw checksum for a specific password - performed by :meth:`~GenericHandler.calc_checksum`.
4. assemble hash, including new checksum, into a new string - performed by :meth:`~GenericHandler.to_string`.
@@ -157,7 +162,7 @@ checking if a handler adheres to the :ref:`password-hash-api`.
Usage
-----
As an example of how to use :class:`!HandlerCase`,
-the following is an annoted version
+the following is an annotated version
of the unittest for :class:`passlib.hash.des_crypt`::
from passlib.hash import des_crypt
diff --git a/docs/lib/passlib.utils.rst b/docs/lib/passlib.utils.rst
index 5508191..ca82c57 100644
--- a/docs/lib/passlib.utils.rst
+++ b/docs/lib/passlib.utils.rst
@@ -35,7 +35,6 @@ Constants
are known to support which hashes.
..
-
PYPY
JYTHON
rounds_cost_values
@@ -105,16 +104,15 @@ Predefined Instances
Predefined instance of :class:`Base64Engine` which uses
the :data:`!HASH64_CHARS` character map and little-endian encoding.
- (see :data:`!HASH64_CHARS` for more details).
+ (see :data:`HASH64_CHARS` for more details).
.. data:: h64big
Predefined variant of :data:`h64` which uses big-endian encoding.
This is mainly used by :class:`~passlib.hash.des_crypt`.
-.. note::
-
- *changed in Passlib 1.6:* Previous versions of Passlib contained
+.. versionchanged:: 1.6
+ Previous versions of Passlib contained
a module named :mod:`!passlib.utils.h64`; As of Passlib 1.6 this
was replaced by the the ``h64`` and ``h64big`` instances;
the interface remains mostly unchanged.
@@ -173,5 +171,4 @@ There are also a few sub modules which provide additional utility functions:
passlib.utils.pbkdf2
..
-
passlib.utils.compat
diff --git a/docs/password_hash_api.rst b/docs/password_hash_api.rst
index e824fea..5e59f83 100644
--- a/docs/password_hash_api.rst
+++ b/docs/password_hash_api.rst
@@ -143,6 +143,14 @@ Required Attributes
the same hash. The class's documentation will generally list
the allowed values, allowing alternate output formats to be selected.
+ ``relaxed``
+ If supported, ``relaxed=True`` will cause the handler to
+ be more forgiving about invalid input. Instead of immediately throwing
+ a :exc:`ValueError`, it will first attempt to correct the input,
+ and issue a :exc:`~passlib.exc.PasslibHandlerWarning` if successful.
+ This includes actions like clamping out-of-range rounds values,
+ and truncating salts that are too long.
+
.. attribute:: PasswordHash.context_kwds
This attribute should contain a tuple of keywords
@@ -197,23 +205,34 @@ which scheme a hash belongs to when multiple schemes are in use.
Common settings keywords include ``salt`` and ``rounds``.
:raises ValueError:
- * if settings are invalid and handler cannot correct them.
- (eg: if a ``salt`` string is to short, this will
- cause an error; but a ``rounds`` value that's too large
- should be silently clipped).
- * if a context keyword contains an invalid value, or was required
- but omitted.
+ * If a keyword's value is invalid (e.g. if a ``salt`` string
+ is too small, or a ``rounds`` value is out of range).
- * if secret contains forbidden characters (e.g: des-crypt forbids null characters).
- this should rarely occur, since most modern algorithms have no limitations
- on the types of characters.
+ * If the secret contains characters forbidden by the handler
+ (e.g. :class:`!des_crypt` forbids NULL characters). This should not
+ happen often, since most modern algorithms have no limitations on
+ the character values they accept.
- :raises TypeError: if :samp:`{secret}` is not a bytes or unicode instance.
+ :raises TypeError:
+
+ * if :samp:`{secret}` is not a bytes or unicode instance.
+
+ * if a required option (such as a context keyword) was not set.
:returns:
Hash string, using an algorithm-specific format.
+ .. versionchanged:: 1.6
+
+ Previous versions of Passlib would raise :exc:`ValueError` if a
+ required keyword was missing; this has been changed to :exc:`TypeError`
+ in order to conform with normal Python behavior.
+
+ Previous versions of Passlib would silently correct invalid settings
+ where possible (e.g. silently clamping out-of-range ``rounds``); as
+ of Passlib 1.6 the policy is to raise an explicit error.
+
.. classmethod:: PasswordHash.identify(hash)
identify if a hash string belongs to this algorithm.
@@ -292,41 +311,27 @@ and :meth:`~PasswordHash.genhash`.
referred to as a ``salt string``, though it may contain much more
than just a salt).
- This function takes in optional configuration options (a complete list
- of which should be found in :attr:`~PasswordHash.setting_kwds`), validates
- the inputs, fills in defaults where appropriate, and returns
- a configuration string.
- For algorithms which do not have any configuration options,
- this function should always return ``None``.
-
- While each algorithm may have it's own configuration options,
- the following keywords (if supported) should always have a consistent
- meaning:
+ This function takes in configuration options specific to the handler,
+ validates the inputs, fills in defaults where appropriate, and returns
+ a configuration string. For algorithms which do not have any configuration
+ options, this function should always return ``None``.
- * ``salt`` - algorithm uses a salt. if passed into genconfig,
- should contain an encoded salt string of length and character set
- required by the specific handler.
-
- salt strings which are too small or have invalid characters
- should cause an error, salt strings which are too large
- should be truncated but accepted.
+ :param \*\*settings_kwds:
- * ``rounds`` - algorithm uses a variable number of rounds. if passed
- into genconfig, should contain an integer number of rounds
- (this may represent logarithmic rounds, eg bcrypt, or linear, eg sha-crypt).
- if the number of rounds is too small or too large, it should
- be clipped but accepted.
+ While each algorithm may have it's own specific configuration options
+ (detailed in it's documentation), a list of common options can be
+ found in in :attr:`~PasswordHash.setting_kwds`.
- :param \*\*settings_kwds:
- this function takes in keywords as specified in :attr:`~PasswordHash.setting_kwds`.
commonly supported keywords include ``salt`` and ``rounds``.
+ :raises TypeError:
+ if any required configuration options are omitted
+ (most options do not need to be specified; e.g. an appropriate
+ value for ``salt`` will be autogenerated for each call).
+
:raises ValueError:
- * if any configuration options are required, missing, AND
- a default value cannot be autogenerated.
- (for example: salt strings should be autogenerated if not specified).
- * if any configuration options are invalid, and cannot be
- normalized in a reasonble manner (eg: salt strings clipped to maximum size).
+ if any configuration options are invalid (and cannot
+ be corrected, if in relaxed parsing mode).
:returns:
the configuration string, or ``None`` if the algorithm does not support
@@ -582,7 +587,7 @@ and ease of implementation issues:
use ``utf-8`` to encode unicode passwords,
and reproduce existing passwords as opaque bytes.
-* Internally, it is recommended that handlers use
+* Internally, it is recommended that handlers use
:class:`unicode` for parsing / formatting
purposes, and only use :class:`bytes` for decoded
binary data ready to be passed into their digest routines.
diff --git a/passlib/tests/utils.py b/passlib/tests/utils.py
index 92e601e..5a29203 100644
--- a/passlib/tests/utils.py
+++ b/passlib/tests/utils.py
@@ -1004,8 +1004,9 @@ class HandlerCase(TestCase):
# use crypt.crypt() to check handlers that have an 'os_crypt' backend.
if _has_possible_crypt_support(handler):
possible = True
- # NOTE: disabling when self._orig_crypt set, means has_backend
- # will return a false positive.
+ # NOTE: disabling this when self._orig_crypt is set, since that flag
+ # indicates the current testcase has temporarily hacked os_crypt so
+ # that has_backend() will return a false positive.
if not self._orig_crypt and handler.has_backend("os_crypt"):
def check_crypt(secret, hash):
from crypt import crypt
diff --git a/passlib/utils/handlers.py b/passlib/utils/handlers.py
index 9adc03b..3bba10a 100644
--- a/passlib/utils/handlers.py
+++ b/passlib/utils/handlers.py
@@ -27,7 +27,9 @@ __all__ = [
#framework for implementing handlers
'StaticHandler',
'GenericHandler',
- 'HasRawChecksum',
+ # checksum mixins
+ 'HasRawChecksum',
+ 'HasStubChecksum',
'HasManyIdents',
'HasSalt',
'HasRawSalt',
@@ -180,16 +182,17 @@ class StaticHandler(object):
The :meth:`genhash` method you implement must accept
all valid hashes, *as well as* whatever value :meth:`genconfig` returns.
This defaults to ``None``, but you may set the :attr:`_stub_config` attr
- to a random hash string, and :meth:`genconfig` will return this instead.
+ to a specific hash string, and :meth:`genconfig` will return this instead.
The default :meth:`verify` method uses simple equality to compare hash strings.
- If your hash may have multiple encoding (eg case-insensitive), this
- method (or the private :meth:`_norm_hash` method)
- should be overridden on a per-handler basis.
+ If your hash has multiple encodings (e.g. is case-insensitive), the
+ :meth:`_norm_hash` method should be overridden to normalize to a single
+ representation.
If your hash has options, such as multiple identifiers, salts,
or variable rounds, this is not the right class to start with.
- You should use the :class:`GenericHandler` class, or implement the handler yourself.
+ You should use the :class:`GenericHandler` class, or implement the handler
+ yourself.
"""
#=====================================================
@@ -221,6 +224,7 @@ class StaticHandler(object):
@classmethod
def genconfig(cls):
+ "default genconfig() implementation for unsalted hash algorithms"
return cls._stub_config
@classmethod
@@ -229,13 +233,15 @@ class StaticHandler(object):
@classmethod
def encrypt(cls, secret, *cargs, **context):
- #NOTE: subclasses generally won't need to override this.
+ "default encrypt() implementation for unsalted hash algorithms"
+ # NOTE: subclasses generally won't need to override this
config = cls.genconfig()
return cls.genhash(secret, config, *cargs, **context)
@classmethod
def verify(cls, secret, hash, *cargs, **context):
- #NOTE: subclasses generally won't need to override this.
+ "default verify() implementation for unsalted hash algorithms"
+ # NOTE: subclasses generally won't need to override this.
if hash is None:
raise ValueError("no hash specified")
hash = cls._norm_hash(hash)
@@ -263,6 +269,10 @@ class StaticHandler(object):
class GenericHandler(object):
"""helper class for implementing hash handlers.
+ GenericHandler-derived classes will have (at least) the following
+ constructor options, though others may be added by mixins
+ and by the class itself:
+
:param checksum:
this should contain the digest portion of a
parsed hash (mainly provided when the constructor is called
@@ -327,16 +337,16 @@ class GenericHandler(object):
The checksum string as provided by the constructor (after passing it
through :meth:`_norm_checksum`).
- Required Class Methods
- ======================
+ Required Subclass Methods
+ =========================
The following methods must be provided by handler subclass:
.. automethod:: from_string
.. automethod:: to_string
.. automethod:: calc_checksum
- Default Class Methods
- =====================
+ Default Methods
+ ===============
The following methods provide generally useful default behaviors,
though they may be overridden if the hash subclass needs to:
@@ -412,8 +422,9 @@ class GenericHandler(object):
#=====================================================
@classmethod
def identify(cls, hash):
- #NOTE: subclasses may wish to use faster / simpler identify,
- # and raise value errors only when an invalid (but identifiable) string is parsed
+ # NOTE: subclasses may wish to use faster / simpler identify,
+ # and raise value errors only when an invalid (but identifiable)
+ # string is parsed
if not hash:
return False
ident = cls.ident
@@ -424,8 +435,9 @@ class GenericHandler(object):
ident = ident.encode('ascii')
return hash.startswith(ident)
else:
- #don't have that, so fall back to trying to parse hash
- #(inefficient for these purposes)
+ # don't have known ident prefix; so as fallback, try to parse hash
+ # to trying to parse hash and see if we succeed.
+ # (inefficient, but works for most cases)
try:
cls.from_string(hash)
return True
@@ -460,7 +472,8 @@ class GenericHandler(object):
#
# withchk=True -- if false, omit checksum portion of hash
#
- raise NotImplementedError("%s must implement from_string()" % (type(self),))
+ raise NotImplementedError("%s must implement from_string()" %
+ (self.__class__,))
##def to_config_string(self):
## "helper for generating configuration string (ignoring hash)"
@@ -488,8 +501,11 @@ class GenericHandler(object):
return self.to_string()
def calc_checksum(self, secret): #pragma: no cover
- "given secret; calcuate and return encoded checksum portion of hash string, taking config from object state"
- raise NotImplementedError("%s must implement calc_checksum()" % (self.__class__,))
+ """given secret; calcuate and return encoded checksum portion of hash
+ string, taking config from object state
+ """
+ raise NotImplementedError("%s must implement calc_checksum()" %
+ (self.__class__,))
#=========================================================
#'application' interface (default implementation)
@@ -503,7 +519,7 @@ class GenericHandler(object):
@classmethod
def verify(cls, secret, hash):
#NOTE: classes with multiple checksum encodings (rare)
- # may wish to either override this, or override norm_checksum
+ # may wish to either override this, or override _norm_checksum
# to normalize any checksums provided by from_string()
self = cls.from_string(hash)
chk = self.checksum
@@ -529,6 +545,7 @@ class HasRawChecksum(GenericHandler):
document this class's usage
"""
+ # NOTE: GenericHandler.checksum_chars is ignored by this implementation.
def _norm_checksum(self, checksum):
if checksum is None:
@@ -683,70 +700,71 @@ class HasSalt(GenericHandler):
"""mixin for validating salts.
This :class:`GenericHandler` mixin adds a ``salt`` keyword to the class constuctor;
- any value provided is passed through the :meth:`norm_salt` method,
+ any value provided is passed through the :meth:`_norm_salt` method,
which takes care of validating salt length and content,
as well as generating new salts if one it not provided.
- :param salt: optional salt string
- :param salt_size: optional size of salt (only used if no salt provided); defaults to :attr:`default_salt_size`.
- :param strict: if ``True``, requires a valid salt be provided; otherwise is tolerant of correctable errors (the default).
+ :param salt:
+ optional salt string
+
+ :param salt_size:
+ optional size of salt (only used if no salt provided);
+ defaults to :attr:`default_salt_size`.
Class Attributes
================
- In order for :meth:`!norm_salt` to do it's job, the following
- attributes must be provided by the handler subclass:
+ In order for :meth:`!_norm_salt` to do it's job, the following
+ attributes should be provided by the handler subclass:
.. attribute:: min_salt_size
- [required]
The minimum number of characters allowed in a salt string.
- An :exc:`ValueError` will be throw if the salt is too small.
+ An :exc:`ValueError` will be throw if the provided salt is too small.
+ Defaults to ``None``, for no minimum.
.. attribute:: max_salt_size
- [required]
The maximum number of characters allowed in a salt string.
- When ``strict=True`` (such as when parsing a hash),
- an :exc:`ValueError` will be throw if the salt is too large.
- WHen ``strict=False`` (such as when parsing user-provided values),
- the salt will be silently trimmed to this length if it's too long.
+ By default an :exc:`ValueError` will be throw if the provided salt is
+ too large; but if ``relaxed=True``, it will be clipped and a warning
+ issued instead. Defaults to ``None``, for no maximum.
.. attribute:: default_salt_size
- [optional]
+ [required]
If no salt is provided, this should specify the size of the salt
- that will be generated by :meth:`generate_salt`.
- If this is not specified, it will default to :attr:`max_salt_size`.
+ that will be generated by :meth:`_generate_salt`. By default
+ this will fall back to :attr:`max_salt_size`.
.. attribute:: salt_chars
- [required]
- A string containing all the characters which are allowed in the salt string.
- An :exc:`ValueError` will be throw if any other characters are encountered.
- May be set to ``None`` to skip this check (but see in :attr:`default_salt_chars`).
+ A string containing all the characters which are allowed in the salt
+ string. An :exc:`ValueError` will be throw if any other characters
+ are encountered. May be set to ``None`` to skip this check (but see
+ in :attr:`default_salt_chars`).
.. attribute:: default_salt_chars
- [optional]
+ [required]
This attribute controls the set of characters use to generate
*new* salt strings. By default, it mirrors :attr:`salt_chars`.
If :attr:`!salt_chars` is ``None``, this attribute must be specified
in order to generate new salts. Aside from that purpose,
the main use of this attribute is for hashes which wish to generate
- salts from a restricted subset of :attr:`!salt_chars`; such as accepting all characters,
- but only using a-z.
+ salts from a restricted subset of :attr:`!salt_chars`; such as
+ accepting all characters, but only using a-z.
Instance Attributes
===================
.. attribute:: salt
This instance attribute will be filled in with the salt provided
- to the constructor (as adapted by :meth:`norm_salt`)
+ to the constructor (as adapted by :meth:`_norm_salt`)
- Class Methods
- =============
- .. automethod:: norm_salt
- .. automethod:: generate_salt
+ Subclassable Methods
+ ====================
+ .. automethod:: _norm_salt
+ .. automethod:: _generate_salt
"""
#XXX: allow providing raw salt to this class, and encoding it?
@@ -760,12 +778,12 @@ class HasSalt(GenericHandler):
@classproperty
def default_salt_size(cls):
- "default salt chars (defaults to max_salt_size if not specified by subclass)"
+ "default salt size (defaults to *max_salt_size*)"
return cls.max_salt_size
@classproperty
def default_salt_chars(cls):
- "required - set of characters used to generate *new* salt strings (defaults to salt_chars)"
+ "charset used to generate new salt strings (defaults to *salt_chars*)"
return cls.salt_chars
# private helpers for HasRawSalt, shouldn't be used by subclasses
@@ -865,6 +883,7 @@ class HasSalt(GenericHandler):
def _generate_salt(self, salt_size):
"""helper method for _norm_salt(); generates a new random salt string.
+
:arg salt_size: salt size to generate
"""
return getrandstr(rng, self.default_salt_chars, salt_size)
@@ -898,42 +917,33 @@ class HasRawSalt(HasSalt):
class HasRounds(GenericHandler):
"""mixin for validating rounds parameter
- This :class:`GenericHandler` mixin adds a ``rounds`` keyword to the class constuctor;
- any value provided is passed through the :meth:`norm_rounds` method,
- which takes care of validating the number of rounds.
+ This :class:`GenericHandler` mixin adds a ``rounds`` keyword to the class
+ constuctor; any value provided is passed through the :meth:`_norm_rounds`
+ method, which takes care of validating the number of rounds.
:param rounds: optional number of rounds hash should use
- :param strict: if ``True``, requires a valid rounds vlaue be provided; otherwise is tolerant of correctable errors (the default).
Class Attributes
================
- In order for :meth:`!norm_rounds` to do it's job, the following
+ In order for :meth:`!_norm_rounds` to do it's job, the following
attributes must be provided by the handler subclass:
.. attribute:: min_rounds
- [optional]
- The minimum number of rounds allowed.
- An :exc:`ValueError` will be thrown if the rounds value is too small.
- When ``strict=True`` (such as when parsing a hash),
- an :exc:`ValueError` will be throw if the rounds value is too small.
- WHen ``strict=False`` (such as when parsing user-provided values),
- the rounds value will be silently clipped if it's too small.
- Defaults to ``0``.
+ The minimum number of rounds allowed. A :exc:`ValueError` will be
+ thrown if the rounds value is too small. Defaults to ``0``.
.. attribute:: max_rounds
- [required]
- The maximum number of rounds allowed.
- When ``strict=True`` (such as when parsing a hash),
- an :exc:`ValueError` will be throw if the rounds value is too large.
- WHen ``strict=False`` (such as when parsing user-provided values),
- the rounds value will be silently clipped if it's too large.
+ The maximum number of rounds allowed. A :exc:`ValueError` will be
+ thrown if the rounds value is larger than this. Defaults to ``None``
+ which indicates no limit to the rounds value.
.. attribute:: default_rounds
- [required]
If no rounds value is provided to constructor, this value will be used.
+ If this is not specified, a rounds value *must* be specified by the
+ application.
.. attribute:: rounds_cost
@@ -943,26 +953,16 @@ class HasRounds(GenericHandler):
(the default) or ``"log2"``, depending on how the rounds value relates
to the actual amount of time that will be required.
- .. attribute:: _strict_rounds_bounds
-
- [optional]
- If the handler subclass wishes to *always* throw an error if a rounds
- value is provided that's out of bounds (such as when it's provided by the user),
- set this private attribute to ``True``.
- The default policy in such cases is to silently clip the rounds value
- to within :attr:`min_rounds` and :attr:`max_rounds`;
- while issuing a :exc:`UserWarning`.
-
Instance Attributes
===================
.. attribute:: rounds
This instance attribute will be filled in with the rounds value provided
- to the constructor (as adapted by :meth:`norm_rounds`)
+ to the constructor (as adapted by :meth:`_norm_rounds`)
- Class Methods
- =============
- .. automethod:: norm_rounds
+ Subclassable Methods
+ ====================
+ .. automethod:: _norm_rounds
"""
#=========================================================
#class attrs
@@ -987,18 +987,20 @@ class HasRounds(GenericHandler):
def _norm_rounds(self, rounds):
"""helper routine for normalizing rounds
- :arg rounds: rounds integer or ``None``
- :param strict: enable strict checking (see below); disabled by default
+ :arg rounds: ``None``, or integer cost parameter.
- :raises ValueError:
- * if rounds is ``None`` and ``strict=True``
- * if rounds is ``None`` and no :attr:`default_rounds` are specified by class.
- * if rounds is outside bounds of :attr:`min_rounds` and :attr:`max_rounds`, and ``strict=True``.
+ :raises TypeError:
+ * if ``use_defaults=False`` and no rounds is specified
+ * if rounds is not an integer.
+
+ :raises ValueError:
- if rounds are not specified and ``strict=False``, uses :attr:`default_rounds`.
- if rounds are outside bounds and ``strict=False``, rounds are clipped as appropriate,
- but a warning is issued.
+ * if rounds is ``None`` and class does not specify a value for
+ :attr:`default_rounds`.
+ * if ``relaxed=False`` and rounds is outside bounds of
+ :attr:`min_rounds` and :attr:`max_rounds` (if ``relaxed=True``,
+ the rounds value will be clamped, and a warning issued).
:returns:
normalized rounds value
@@ -1203,11 +1205,12 @@ class HasManyBackends(GenericHandler):
def calc_checksum(self, secret):
"stub for calc_checksum(), default backend will be selected first time stub is called"
- #backend not loaded - run detection and call replacement
+ # if we got here, no backend has been loaded; so load default backend
assert not self._backend, "set_backend() failed to replace lazy loader"
self.set_backend()
assert self._backend, "set_backend() failed to load a default backend"
- #set_backend() should have replaced this method, so call it again.
+
+ # this should now invoke the backend-specific version, so call it again.
return self.calc_checksum(secret)
#=========================================================