summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2011-02-02 03:31:49 +0000
committerEli Collins <elic@assurancetechnologies.com>2011-02-02 03:31:49 +0000
commit710fd478750b9be544f3cc72dcfeb36c9a564428 (patch)
treef9a478abdb8fb3b480c0469f5a6e5bb0c76abac6
parentf195f54550ca8c0ec3fe0dd74d60be15ed5e05de (diff)
downloadpasslib-710fd478750b9be544f3cc72dcfeb36c9a564428.tar.gz
work on autogenerating function docstrings for hash modules
-rw-r--r--docs/lib/passlib.utils.rst4
-rw-r--r--passlib/hash/apr_md5_crypt.py21
-rw-r--r--passlib/hash/bcrypt.py7
-rw-r--r--passlib/hash/des_crypt.py20
-rw-r--r--passlib/hash/ext_des_crypt.py24
-rw-r--r--passlib/hash/md5_crypt.py36
-rw-r--r--passlib/hash/nthash.py2
-rw-r--r--passlib/utils/__init__.py153
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
#=================================================================================