diff options
| author | Eli Collins <elic@assurancetechnologies.com> | 2011-02-02 03:31:49 +0000 |
|---|---|---|
| committer | Eli Collins <elic@assurancetechnologies.com> | 2011-02-02 03:31:49 +0000 |
| commit | 710fd478750b9be544f3cc72dcfeb36c9a564428 (patch) | |
| tree | f9a478abdb8fb3b480c0469f5a6e5bb0c76abac6 | |
| parent | f195f54550ca8c0ec3fe0dd74d60be15ed5e05de (diff) | |
| download | passlib-710fd478750b9be544f3cc72dcfeb36c9a564428.tar.gz | |
work on autogenerating function docstrings for hash modules
| -rw-r--r-- | docs/lib/passlib.utils.rst | 4 | ||||
| -rw-r--r-- | passlib/hash/apr_md5_crypt.py | 21 | ||||
| -rw-r--r-- | passlib/hash/bcrypt.py | 7 | ||||
| -rw-r--r-- | passlib/hash/des_crypt.py | 20 | ||||
| -rw-r--r-- | passlib/hash/ext_des_crypt.py | 24 | ||||
| -rw-r--r-- | passlib/hash/md5_crypt.py | 36 | ||||
| -rw-r--r-- | passlib/hash/nthash.py | 2 | ||||
| -rw-r--r-- | passlib/utils/__init__.py | 153 |
8 files changed, 189 insertions, 78 deletions
diff --git a/docs/lib/passlib.utils.rst b/docs/lib/passlib.utils.rst index 91788f4..0d6d809 100644 --- a/docs/lib/passlib.utils.rst +++ b/docs/lib/passlib.utils.rst @@ -44,9 +44,7 @@ Object Tests ============ .. autofunction:: is_crypt_handler -.. todo:: - - is_crypt_context +.. autofunction:: is_crypt_context Crypt Handler Helpers ===================== diff --git a/passlib/hash/apr_md5_crypt.py b/passlib/hash/apr_md5_crypt.py index 5eb45fa..1fa35b0 100644 --- a/passlib/hash/apr_md5_crypt.py +++ b/passlib/hash/apr_md5_crypt.py @@ -8,7 +8,7 @@ import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs -from passlib.utils import norm_rounds, norm_salt +from passlib.utils import norm_rounds, norm_salt, autodocument #pkg from passlib.hash.md5_crypt import raw_md5_crypt #local @@ -35,6 +35,9 @@ name = "apr_md5_crypt" setting_kwds = ("salt",) context_kwds = () +min_salt_chars = 0 +max_salt_chars = 8 + #========================================================= #internal helpers #========================================================= @@ -65,20 +68,7 @@ def render(salt, checksum=None): #primary interface #========================================================= def genconfig(salt=None, rounds=None): - """generate apr-md5-crypt configuration string - - :param salt: - optional salt string to use. - - if omitted, one will be automatically generated (recommended). - - length must be between 0 and 8 characters inclusive. - characters must be in range ``A-Za-z0-9./``. - - :returns: - md5-crypt configuration string. - """ - salt = norm_salt(salt, 0, 8, name=name) + salt = norm_salt(salt, min_salt_chars, max_salt_chars, name=name) return render(salt, None) def genhash(secret, config): @@ -110,6 +100,7 @@ def verify(secret, hash): def identify(hash): return bool(hash and _pat.match(hash)) +autodocument(globals()) #========================================================= #eof #========================================================= diff --git a/passlib/hash/bcrypt.py b/passlib/hash/bcrypt.py index cacfb40..6984454 100644 --- a/passlib/hash/bcrypt.py +++ b/passlib/hash/bcrypt.py @@ -17,7 +17,7 @@ import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs -from passlib.utils import norm_rounds, norm_salt +from passlib.utils import norm_rounds, norm_salt, autodocument #pkg #local __all__ = [ @@ -69,6 +69,8 @@ name = "bcrypt" setting_kwds = ("salt", "rounds") context_kwds = () +min_salt_chars = max_salt_chars = 22 + default_rounds = 12 #current passlib default min_rounds = 4 # bcrypt spec specified minimum max_rounds = 31 # 32-bit integer limit (real_rounds=1<<rounds) @@ -134,7 +136,7 @@ def genconfig(salt=None, rounds=None, omit_null_suffix=False): :returns: bcrypt configuration string. """ - salt = norm_salt(salt, 22, name=name) + salt = norm_salt(salt, min_salt_chars, max_salt_chars, name=name) rounds = norm_rounds(rounds, default_rounds, min_rounds, max_rounds, name=name) return render(rounds, salt, None, omit_null_suffix) @@ -159,6 +161,7 @@ def verify(secret, hash): def identify(hash): return bool(hash and _pat.match(hash)) +autodocument(globals()) #========================================================= #eof #========================================================= diff --git a/passlib/hash/des_crypt.py b/passlib/hash/des_crypt.py index 18eb403..dc26229 100644 --- a/passlib/hash/des_crypt.py +++ b/passlib/hash/des_crypt.py @@ -9,7 +9,7 @@ import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs -from passlib.utils import norm_salt, h64 +from passlib.utils import norm_salt, h64, autodocument from passlib.utils.des import mdes_encrypt_int_block #pkg #local @@ -86,6 +86,8 @@ name = "des_crypt" setting_kwds = ("salt",) context_kwds = () +min_salt_chars = max_salt_chars = 2 + #========================================================= #internal helpers #========================================================= @@ -117,20 +119,7 @@ def render(salt, checksum=None): #primary interface #========================================================= def genconfig(salt=None): - """generate xxx configuration string - - :param salt: - optional salt string to use. - - if omitted, one will be automatically generated (recommended). - - length must be 2 characters. - characters must be in range ``A-Za-z0-9./``. - - :returns: - xxx configuration string. - """ - salt = norm_salt(salt, 2, name=name) + salt = norm_salt(salt, min_salt_chars, max_salt_chars, name=name) return render(salt, None) def genhash(secret, config): @@ -180,6 +169,7 @@ def verify(secret, hash): def identify(hash): return bool(hash and _pat.match(hash)) +autodocument(globals()) #========================================================= #eof #========================================================= diff --git a/passlib/hash/ext_des_crypt.py b/passlib/hash/ext_des_crypt.py index 9c7a715..d306a80 100644 --- a/passlib/hash/ext_des_crypt.py +++ b/passlib/hash/ext_des_crypt.py @@ -8,7 +8,7 @@ import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs -from passlib.utils import norm_rounds, norm_salt, h64 +from passlib.utils import norm_rounds, norm_salt, h64, autodocument from passlib.utils.des import mdes_encrypt_int_block from passlib.hash.des_crypt import _crypt_secret_to_key #pkg @@ -68,6 +68,8 @@ name = "ext_des_crypt" setting_kwds = ("salt", "rounds") context_kwds = () +min_salt_chars = max_salt_chars = 4 + default_rounds = 10000 min_rounds = 0 max_rounds = 16777215 # (1<<24)-1 @@ -109,24 +111,7 @@ def render(rounds, salt, checksum=None): #primary interface #========================================================= def genconfig(salt=None, rounds=None): - """generate xxx configuration string - - :param salt: - optional salt string to use. - - if omitted, one will be automatically generated (recommended). - - length must be 4 characters. - characters must be in range ``A-Za-z0-9./``. - - :param rounds: - - optional number of rounds, must be between 0 and 16777215 inclusive. - - :returns: - xxx configuration string. - """ - salt = norm_salt(salt, 4, name=name) + salt = norm_salt(salt, min_salt_chars, max_salt_chars, name=name) rounds = norm_rounds(rounds, default_rounds, min_rounds, max_rounds, name=name) return render(rounds, salt, None) @@ -155,6 +140,7 @@ def verify(secret, hash): def identify(hash): return bool(hash and _pat.match(hash)) +autodocument(globals()) #========================================================= #eof #========================================================= diff --git a/passlib/hash/md5_crypt.py b/passlib/hash/md5_crypt.py index 0c9a662..986ff04 100644 --- a/passlib/hash/md5_crypt.py +++ b/passlib/hash/md5_crypt.py @@ -9,7 +9,7 @@ import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs -from passlib.utils import norm_rounds, norm_salt, h64 +from passlib.utils import norm_rounds, norm_salt, h64, autodocument #pkg #local __all__ = [ @@ -150,6 +150,9 @@ name = "md5_crypt" setting_kwds = ("salt",) context_kwds = () +min_salt_chars = 0 +max_salt_chars = 8 + #========================================================= #internal helpers #========================================================= @@ -179,21 +182,21 @@ def render(salt, checksum=None): #========================================================= #primary interface #========================================================= -def genconfig(salt=None, rounds=None): - """generate md5-crypt configuration string - - :param salt: - optional salt string to use. - - if omitted, one will be automatically generated (recommended). - - length must be between 0 and 8 characters inclusive. - characters must be in range ``A-Za-z0-9./``. - - :returns: - md5-crypt configuration string. - """ - salt = norm_salt(salt, 0, 8, name=name) +def genconfig(salt=None): + ##"""generate md5-crypt configuration string + ## + ##:param salt: + ## optional salt string to use. + ## + ## if omitted, one will be automatically generated (recommended). + ## + ## length must be between 0 and 8 characters inclusive. + ## characters must be in range ``A-Za-z0-9./``. + ## + ##:returns: + ## md5-crypt configuration string. + ##""" + salt = norm_salt(salt, min_salt_chars, max_salt_chars, name=name) return render(salt, None) def genhash(secret, config): @@ -230,6 +233,7 @@ def verify(secret, hash): def identify(hash): return bool(hash and _pat.match(hash)) +autodocument(globals()) #========================================================= #eof #========================================================= diff --git a/passlib/hash/nthash.py b/passlib/hash/nthash.py index 543122a..3dcd3dd 100644 --- a/passlib/hash/nthash.py +++ b/passlib/hash/nthash.py @@ -9,6 +9,7 @@ from warnings import warn #site #libs from passlib.utils.md4 import md4 +from passlib.utils import autodocument #pkg #local __all__ = [ @@ -94,6 +95,7 @@ def verify(secret, hash): def identify(hash): return bool(hash and _pat.match(hash)) +autodocument(globals()) #========================================================= #eof #========================================================= diff --git a/passlib/utils/__init__.py b/passlib/utils/__init__.py index 0a88df9..9347f3f 100644 --- a/passlib/utils/__init__.py +++ b/passlib/utils/__init__.py @@ -76,7 +76,7 @@ Undef = object() #singleton used as default kwd value in some functions #protocol helpers #========================================================== def is_crypt_handler(obj): - "check if object follows the :ref:`crypt handler api <crypt-handler-api>`" + "check if object follows the :ref:`password-hash-api`" return all(hasattr(obj, name) for name in ( "name", "setting_kwds", "context_kwds", @@ -84,13 +84,13 @@ def is_crypt_handler(obj): "verify", "encrypt", "identify", )) -##def is_crypt_context(obj): -## "check if obj follows CryptContext api" -## #NOTE: this isn't an exhaustive check of all required attrs, -## #just a quick check of the most uniquely identifying ones -## return all(hasattr(obj, name) for name in ( -## "lookup", "verify", "encrypt", "identify", -## )) +def is_crypt_context(obj): + "check if object follows :class:`CryptContext` interface" + return all(hasattr(obj, name) for name in ( + "lookup", + "genconfig", "genhash", + "verify", "encrypt", "identify", + )) #================================================================================= #string helpers @@ -423,6 +423,143 @@ def norm_salt(salt, min_chars, max_chars=None, charset=h64.CHARS, gen_charset=No else: return salt +def autodocument(scope, salt_charset="[a-zA-Z0-9./]", log_rounds=False): + """helper to auto-generate documentation for password hash handler + + :arg scope: dict containing encrypt/verify/etc functions (module scope or class dict) + """ + + name = scope['name'] + + setting_kwds = scope['setting_kwds'] + has_salt = 'salt' in setting_kwds + has_rounds = 'rounds' in setting_kwds + has_other = any(c for c in setting_kwds if c not in ("salt", "rounds")) + + if has_salt: + max_salt_chars = scope["max_salt_chars"] + min_salt_chars = scope["min_salt_chars"] + + if has_rounds: + default_rounds = scope['default_rounds'] + min_rounds = scope['min_rounds'] + max_rounds = scope['max_rounds'] + + context_kwds = scope['context_kwds'] + + encrypt = scope['encrypt'] + if not encrypt.__doc__: + pass + + genconfig = scope['genconfig'] + if not genconfig.__doc__: + if setting_kwds: + if has_other: + raise NotImplementedError, "can't auto generate genconfig docs w/ unknown setting_kwds" + d = "generate %(name)s configuration string\n\n" % dict(name=name) + + if has_salt: + d += """:param salt: optional salt string to use.\n\n if omitted, one will be automatically generated (this is recommended for most cases).\n\n""" + if max_salt_chars: + if min_salt_chars != max_salt_chars: + d += """ length must be between %d .. %d characters, inclusive.\n""" % (min_salt_chars, max_salt_chars) + else: + d += """ length must be %d characters\n""" % (max_salt_chars,) + if salt_charset: + d += """ characters must be in range ``%s``\n""" % (salt_charset,) + + if has_rounds: + d += """:param rounds:\n optional number of rounds to apply (default is %d).\n value must be between %d and %d, inclusive.\n""" %(default_rounds, min_rounds, max_rounds) + if log_rounds: + raise NotImplementedError, "todo" + + d += """\n:raises ValueError: if invalid settings are passed in\n\n""" + d += """:returns:\n %(name)s configuration string\n""" % dict(name=name) + else: + d = """generate %(name)s configuration string + + :returns: + this hash has no configuration options, so genconfig always returns ``None`` + """ % dict(name=name) + genconfig.__doc__ = d + + genhash = scope['genhash'] + if not genhash.__doc__: + if context_kwds: + raise NotImplementedError, "can't auto generate genhash docs w/ context kwds" + genhash.__doc__ = """generate %(name)s hash from secret, using configuration string or existing hash. + +:arg secret: string containing password to be encrypted +:arg config: configuration string as returned by :func:`genconfig` OR existing hash string + +:raises TypeError: if the configuration string is not provided, or the secret is not a string + +:raises ValueError: if the configuration string is not in a recognized format, or the secret contains a forbidden character. + +:returns: + encoded %(name)s hash of secret, using specified config string + """ % dict(name=name) + + encrypt = scope['encrypt'] + if not encrypt.__doc__: + encrypt.__doc__ = """encrypt secret, returning resulting %(name)s hash + + this is a convience function, + it has the same effect as ``genhash(secret,genconfig(**settings))`` + + :arg secret: a string containing the secret to encode + + :param kwds: all other keywords used to generate config string, see :func:`genconfig`. + + :returns: %(name)s hash of secret, using specified settings + """ % dict(name=name) + + identify = scope['identify'] + if not identify.__doc__: + identify.__doc__ = """identify this is a %(name)s hash. + + :arg hash: + the candidate hash string to check + + :returns: + * ``True`` if input appears to be a %(name)s hash string. + * ``True`` if input appears to be a %(name)s configuration. + * ``False`` if no input is specified + * ``False`` if none of the above conditions was met. + + """ % dict(name=name) + #TODO: variant of this note, and also handle no settings + ##.. note:: + ## Some handlers may or may not return ``True`` for malformed hashes. + ## Those that do will raise a ValueError once the hash is passed to :func:`verify`. + ## Most handlers, however, will just return ``False``. + + verify = scope['verify'] + if not verify.__doc__: + if context_kwds: + raise NotImplementedError, "can't auto generate verify docs w/ context kwds" + verify.__doc__ = """verify a secret against an existing %(name)s hash. + + This checks if a secret matches against the one + encrypted in the specified %(name)s hash. + + :param secret: + A string containing the secret to check. + :param hash: + A string containing the hash to check against. + + :raises TypeError: + * if the secret is not a string. + + :raises ValueError: + * if the hash not specified + * if the hash is not recognized as a %(name)s hash. + * if the provided secret contains forbidden chars (see :func:`encrypt`) + + :returns: + ``True`` if the secret matches, otherwise ``False``. + """ % dict(name=name) + #================================================================================= #eof #================================================================================= |
