diff options
author | Eli Collins <elic@assurancetechnologies.com> | 2012-02-08 23:22:09 -0500 |
---|---|---|
committer | Eli Collins <elic@assurancetechnologies.com> | 2012-02-08 23:22:09 -0500 |
commit | bf927985d1fe8df1b6a1cb9db4c314bf4c4c13af (patch) | |
tree | 4f40525d27469277f8c82c042ad0ebf404c02bbf | |
parent | 098f270258d6991ec0c74b63783cc0fcf3eecab9 (diff) | |
download | passlib-bf927985d1fe8df1b6a1cb9db4c314bf4c4c13af.tar.gz |
documentation updates for last two commits
-rw-r--r-- | CHANGES | 16 | ||||
-rw-r--r-- | docs/conf.py | 3 | ||||
-rw-r--r-- | docs/lib/passlib.hash.apr_md5_crypt.rst | 2 | ||||
-rw-r--r-- | docs/lib/passlib.hash.bigcrypt.rst | 2 | ||||
-rw-r--r-- | docs/lib/passlib.hash.bsdi_crypt.rst | 2 | ||||
-rw-r--r-- | docs/lib/passlib.hash.crypt16.rst | 2 | ||||
-rw-r--r-- | docs/lib/passlib.hash.des_crypt.rst | 2 | ||||
-rw-r--r-- | docs/lib/passlib.hash.md5_crypt.rst | 2 | ||||
-rw-r--r-- | docs/lib/passlib.hash.oracle11.rst | 2 | ||||
-rw-r--r-- | docs/lib/passlib.hash.phpass.rst | 2 | ||||
-rw-r--r-- | docs/lib/passlib.hash.scram.rst | 2 | ||||
-rw-r--r-- | docs/lib/passlib.hash.sha1_crypt.rst | 2 | ||||
-rw-r--r-- | docs/lib/passlib.hash.sha256_crypt.rst | 2 | ||||
-rw-r--r-- | docs/lib/passlib.hash.sha512_crypt.rst | 2 | ||||
-rw-r--r-- | docs/lib/passlib.hash.sun_md5_crypt.rst | 2 | ||||
-rw-r--r-- | docs/lib/passlib.utils.handlers.rst | 9 | ||||
-rw-r--r-- | docs/lib/passlib.utils.rst | 9 | ||||
-rw-r--r-- | docs/password_hash_api.rst | 85 | ||||
-rw-r--r-- | passlib/tests/utils.py | 5 | ||||
-rw-r--r-- | passlib/utils/handlers.py | 193 |
20 files changed, 185 insertions, 161 deletions
@@ -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) #========================================================= |