You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					548 lines
				
				20 KiB
			
		
		
			
		
	
	
					548 lines
				
				20 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								"""passlib.registry - registry for password hash handlers"""
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# imports
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# core
							 | 
						||
| 
								 | 
							
								import re
							 | 
						||
| 
								 | 
							
								import logging; log = logging.getLogger(__name__)
							 | 
						||
| 
								 | 
							
								from warnings import warn
							 | 
						||
| 
								 | 
							
								# pkg
							 | 
						||
| 
								 | 
							
								from passlib import exc
							 | 
						||
| 
								 | 
							
								from passlib.exc import ExpectedTypeError, PasslibWarning
							 | 
						||
| 
								 | 
							
								from passlib.ifc import PasswordHash
							 | 
						||
| 
								 | 
							
								from passlib.utils import (
							 | 
						||
| 
								 | 
							
								    is_crypt_handler, has_crypt as os_crypt_present,
							 | 
						||
| 
								 | 
							
								    unix_crypt_schemes as os_crypt_schemes,
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								from passlib.utils.compat import unicode_or_str
							 | 
						||
| 
								 | 
							
								from passlib.utils.decor import memoize_single_value
							 | 
						||
| 
								 | 
							
								# local
							 | 
						||
| 
								 | 
							
								__all__ = [
							 | 
						||
| 
								 | 
							
								    "register_crypt_handler_path",
							 | 
						||
| 
								 | 
							
								    "register_crypt_handler",
							 | 
						||
| 
								 | 
							
								    "get_crypt_handler",
							 | 
						||
| 
								 | 
							
								    "list_crypt_handlers",
							 | 
						||
| 
								 | 
							
								]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# proxy object used in place of 'passlib.hash' module
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								class _PasslibRegistryProxy(object):
							 | 
						||
| 
								 | 
							
								    """proxy module passlib.hash
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this module is in fact an object which lazy-loads
							 | 
						||
| 
								 | 
							
								    the requested password hash algorithm from wherever it has been stored.
							 | 
						||
| 
								 | 
							
								    it acts as a thin wrapper around :func:`passlib.registry.get_crypt_handler`.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    __name__ = "passlib.hash"
							 | 
						||
| 
								 | 
							
								    __package__ = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __getattr__(self, attr):
							 | 
						||
| 
								 | 
							
								        if attr.startswith("_"):
							 | 
						||
| 
								 | 
							
								            raise AttributeError("missing attribute: %r" % (attr,))
							 | 
						||
| 
								 | 
							
								        handler = get_crypt_handler(attr, None)
							 | 
						||
| 
								 | 
							
								        if handler:
							 | 
						||
| 
								 | 
							
								            return handler
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            raise AttributeError("unknown password hash: %r" % (attr,))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __setattr__(self, attr, value):
							 | 
						||
| 
								 | 
							
								        if attr.startswith("_"):
							 | 
						||
| 
								 | 
							
								            # writing to private attributes should behave normally.
							 | 
						||
| 
								 | 
							
								            # (required so GAE can write to the __loader__ attribute).
							 | 
						||
| 
								 | 
							
								            object.__setattr__(self, attr, value)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            # writing to public attributes should be treated
							 | 
						||
| 
								 | 
							
								            # as attempting to register a handler.
							 | 
						||
| 
								 | 
							
								            register_crypt_handler(value, _attr=attr)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __repr__(self):
							 | 
						||
| 
								 | 
							
								        return "<proxy module 'passlib.hash'>"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __dir__(self):
							 | 
						||
| 
								 | 
							
								        # this adds in lazy-loaded handler names,
							 | 
						||
| 
								 | 
							
								        # otherwise this is the standard dir() implementation.
							 | 
						||
| 
								 | 
							
								        attrs = set(dir(self.__class__))
							 | 
						||
| 
								 | 
							
								        attrs.update(self.__dict__)
							 | 
						||
| 
								 | 
							
								        attrs.update(_locations)
							 | 
						||
| 
								 | 
							
								        return sorted(attrs)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# create single instance - available publically as 'passlib.hash'
							 | 
						||
| 
								 | 
							
								_proxy = _PasslibRegistryProxy()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# internal registry state
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# singleton uses to detect omitted keywords
							 | 
						||
| 
								 | 
							
								_UNSET = object()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# dict mapping name -> loaded handlers (just uses proxy object's internal dict)
							 | 
						||
| 
								 | 
							
								_handlers = _proxy.__dict__
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# dict mapping names -> import path for lazy loading.
							 | 
						||
| 
								 | 
							
								#     * import path should be "module.path" or "module.path:attr"
							 | 
						||
| 
								 | 
							
								#     * if attr omitted, "name" used as default.
							 | 
						||
| 
								 | 
							
								_locations = dict(
							 | 
						||
| 
								 | 
							
								    # NOTE: this is a hardcoded list of the handlers built into passlib,
							 | 
						||
| 
								 | 
							
								    #       applications should call register_crypt_handler_path()
							 | 
						||
| 
								 | 
							
								    apr_md5_crypt = "passlib.handlers.md5_crypt",
							 | 
						||
| 
								 | 
							
								    argon2 = "passlib.handlers.argon2",
							 | 
						||
| 
								 | 
							
								    atlassian_pbkdf2_sha1 = "passlib.handlers.pbkdf2",
							 | 
						||
| 
								 | 
							
								    bcrypt = "passlib.handlers.bcrypt",
							 | 
						||
| 
								 | 
							
								    bcrypt_sha256 = "passlib.handlers.bcrypt",
							 | 
						||
| 
								 | 
							
								    bigcrypt = "passlib.handlers.des_crypt",
							 | 
						||
| 
								 | 
							
								    bsd_nthash = "passlib.handlers.windows",
							 | 
						||
| 
								 | 
							
								    bsdi_crypt = "passlib.handlers.des_crypt",
							 | 
						||
| 
								 | 
							
								    cisco_pix = "passlib.handlers.cisco",
							 | 
						||
| 
								 | 
							
								    cisco_asa = "passlib.handlers.cisco",
							 | 
						||
| 
								 | 
							
								    cisco_type7 = "passlib.handlers.cisco",
							 | 
						||
| 
								 | 
							
								    cta_pbkdf2_sha1 = "passlib.handlers.pbkdf2",
							 | 
						||
| 
								 | 
							
								    crypt16 = "passlib.handlers.des_crypt",
							 | 
						||
| 
								 | 
							
								    des_crypt = "passlib.handlers.des_crypt",
							 | 
						||
| 
								 | 
							
								    django_argon2 = "passlib.handlers.django",
							 | 
						||
| 
								 | 
							
								    django_bcrypt = "passlib.handlers.django",
							 | 
						||
| 
								 | 
							
								    django_bcrypt_sha256 = "passlib.handlers.django",
							 | 
						||
| 
								 | 
							
								    django_pbkdf2_sha256 = "passlib.handlers.django",
							 | 
						||
| 
								 | 
							
								    django_pbkdf2_sha1 = "passlib.handlers.django",
							 | 
						||
| 
								 | 
							
								    django_salted_sha1 = "passlib.handlers.django",
							 | 
						||
| 
								 | 
							
								    django_salted_md5 = "passlib.handlers.django",
							 | 
						||
| 
								 | 
							
								    django_des_crypt = "passlib.handlers.django",
							 | 
						||
| 
								 | 
							
								    django_disabled = "passlib.handlers.django",
							 | 
						||
| 
								 | 
							
								    dlitz_pbkdf2_sha1 = "passlib.handlers.pbkdf2",
							 | 
						||
| 
								 | 
							
								    fshp = "passlib.handlers.fshp",
							 | 
						||
| 
								 | 
							
								    grub_pbkdf2_sha512 = "passlib.handlers.pbkdf2",
							 | 
						||
| 
								 | 
							
								    hex_md4 = "passlib.handlers.digests",
							 | 
						||
| 
								 | 
							
								    hex_md5 = "passlib.handlers.digests",
							 | 
						||
| 
								 | 
							
								    hex_sha1 = "passlib.handlers.digests",
							 | 
						||
| 
								 | 
							
								    hex_sha256 = "passlib.handlers.digests",
							 | 
						||
| 
								 | 
							
								    hex_sha512 = "passlib.handlers.digests",
							 | 
						||
| 
								 | 
							
								    htdigest = "passlib.handlers.digests",
							 | 
						||
| 
								 | 
							
								    ldap_plaintext = "passlib.handlers.ldap_digests",
							 | 
						||
| 
								 | 
							
								    ldap_md5 = "passlib.handlers.ldap_digests",
							 | 
						||
| 
								 | 
							
								    ldap_sha1 = "passlib.handlers.ldap_digests",
							 | 
						||
| 
								 | 
							
								    ldap_hex_md5 = "passlib.handlers.roundup",
							 | 
						||
| 
								 | 
							
								    ldap_hex_sha1 = "passlib.handlers.roundup",
							 | 
						||
| 
								 | 
							
								    ldap_salted_md5 = "passlib.handlers.ldap_digests",
							 | 
						||
| 
								 | 
							
								    ldap_salted_sha1 = "passlib.handlers.ldap_digests",
							 | 
						||
| 
								 | 
							
								    ldap_salted_sha256 = "passlib.handlers.ldap_digests",
							 | 
						||
| 
								 | 
							
								    ldap_salted_sha512 = "passlib.handlers.ldap_digests",
							 | 
						||
| 
								 | 
							
								    ldap_des_crypt = "passlib.handlers.ldap_digests",
							 | 
						||
| 
								 | 
							
								    ldap_bsdi_crypt = "passlib.handlers.ldap_digests",
							 | 
						||
| 
								 | 
							
								    ldap_md5_crypt = "passlib.handlers.ldap_digests",
							 | 
						||
| 
								 | 
							
								    ldap_bcrypt = "passlib.handlers.ldap_digests",
							 | 
						||
| 
								 | 
							
								    ldap_sha1_crypt = "passlib.handlers.ldap_digests",
							 | 
						||
| 
								 | 
							
								    ldap_sha256_crypt = "passlib.handlers.ldap_digests",
							 | 
						||
| 
								 | 
							
								    ldap_sha512_crypt = "passlib.handlers.ldap_digests",
							 | 
						||
| 
								 | 
							
								    ldap_pbkdf2_sha1 = "passlib.handlers.pbkdf2",
							 | 
						||
| 
								 | 
							
								    ldap_pbkdf2_sha256 = "passlib.handlers.pbkdf2",
							 | 
						||
| 
								 | 
							
								    ldap_pbkdf2_sha512 = "passlib.handlers.pbkdf2",
							 | 
						||
| 
								 | 
							
								    lmhash = "passlib.handlers.windows",
							 | 
						||
| 
								 | 
							
								    md5_crypt = "passlib.handlers.md5_crypt",
							 | 
						||
| 
								 | 
							
								    msdcc = "passlib.handlers.windows",
							 | 
						||
| 
								 | 
							
								    msdcc2 = "passlib.handlers.windows",
							 | 
						||
| 
								 | 
							
								    mssql2000 = "passlib.handlers.mssql",
							 | 
						||
| 
								 | 
							
								    mssql2005 = "passlib.handlers.mssql",
							 | 
						||
| 
								 | 
							
								    mysql323 = "passlib.handlers.mysql",
							 | 
						||
| 
								 | 
							
								    mysql41 = "passlib.handlers.mysql",
							 | 
						||
| 
								 | 
							
								    nthash = "passlib.handlers.windows",
							 | 
						||
| 
								 | 
							
								    oracle10 = "passlib.handlers.oracle",
							 | 
						||
| 
								 | 
							
								    oracle11 = "passlib.handlers.oracle",
							 | 
						||
| 
								 | 
							
								    pbkdf2_sha1 = "passlib.handlers.pbkdf2",
							 | 
						||
| 
								 | 
							
								    pbkdf2_sha256 = "passlib.handlers.pbkdf2",
							 | 
						||
| 
								 | 
							
								    pbkdf2_sha512 = "passlib.handlers.pbkdf2",
							 | 
						||
| 
								 | 
							
								    phpass = "passlib.handlers.phpass",
							 | 
						||
| 
								 | 
							
								    plaintext = "passlib.handlers.misc",
							 | 
						||
| 
								 | 
							
								    postgres_md5 = "passlib.handlers.postgres",
							 | 
						||
| 
								 | 
							
								    roundup_plaintext = "passlib.handlers.roundup",
							 | 
						||
| 
								 | 
							
								    scram = "passlib.handlers.scram",
							 | 
						||
| 
								 | 
							
								    scrypt = "passlib.handlers.scrypt",
							 | 
						||
| 
								 | 
							
								    sha1_crypt = "passlib.handlers.sha1_crypt",
							 | 
						||
| 
								 | 
							
								    sha256_crypt = "passlib.handlers.sha2_crypt",
							 | 
						||
| 
								 | 
							
								    sha512_crypt = "passlib.handlers.sha2_crypt",
							 | 
						||
| 
								 | 
							
								    sun_md5_crypt = "passlib.handlers.sun_md5_crypt",
							 | 
						||
| 
								 | 
							
								    unix_disabled = "passlib.handlers.misc",
							 | 
						||
| 
								 | 
							
								    unix_fallback = "passlib.handlers.misc",
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# master regexp for detecting valid handler names
							 | 
						||
| 
								 | 
							
								_name_re = re.compile("^[a-z][a-z0-9_]+[a-z0-9]$")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# names which aren't allowed for various reasons
							 | 
						||
| 
								 | 
							
								# (mainly keyword conflicts in CryptContext)
							 | 
						||
| 
								 | 
							
								_forbidden_names = frozenset(["onload", "policy", "context", "all",
							 | 
						||
| 
								 | 
							
								                              "default", "none", "auto"])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# registry frontend functions
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								def _validate_handler_name(name):
							 | 
						||
| 
								 | 
							
								    """helper to validate handler name
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :raises ValueError:
							 | 
						||
| 
								 | 
							
								        * if empty name
							 | 
						||
| 
								 | 
							
								        * if name not lower case
							 | 
						||
| 
								 | 
							
								        * if name contains double underscores
							 | 
						||
| 
								 | 
							
								        * if name is reserved (e.g. ``context``, ``all``).
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    if not name:
							 | 
						||
| 
								 | 
							
								        raise ValueError("handler name cannot be empty: %r" % (name,))
							 | 
						||
| 
								 | 
							
								    if name.lower() != name:
							 | 
						||
| 
								 | 
							
								        raise ValueError("name must be lower-case: %r" % (name,))
							 | 
						||
| 
								 | 
							
								    if not _name_re.match(name):
							 | 
						||
| 
								 | 
							
								        raise ValueError("invalid name (must be 3+ characters, "
							 | 
						||
| 
								 | 
							
								                         " begin with a-z, and contain only underscore, a-z, "
							 | 
						||
| 
								 | 
							
								                         "0-9): %r" % (name,))
							 | 
						||
| 
								 | 
							
								    if '__' in name:
							 | 
						||
| 
								 | 
							
								        raise ValueError("name may not contain double-underscores: %r" %
							 | 
						||
| 
								 | 
							
								                         (name,))
							 | 
						||
| 
								 | 
							
								    if name in _forbidden_names:
							 | 
						||
| 
								 | 
							
								        raise ValueError("that name is not allowed: %r" % (name,))
							 | 
						||
| 
								 | 
							
								    return True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def register_crypt_handler_path(name, path):
							 | 
						||
| 
								 | 
							
								    """register location to lazy-load handler when requested.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    custom hashes may be registered via :func:`register_crypt_handler`,
							 | 
						||
| 
								 | 
							
								    or they may be registered by this function,
							 | 
						||
| 
								 | 
							
								    which will delay actually importing and loading the handler
							 | 
						||
| 
								 | 
							
								    until a call to :func:`get_crypt_handler` is made for the specified name.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :arg name: name of handler
							 | 
						||
| 
								 | 
							
								    :arg path: module import path
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    the specified module path should contain a password hash handler
							 | 
						||
| 
								 | 
							
								    called :samp:`{name}`, or the path may contain a colon,
							 | 
						||
| 
								 | 
							
								    specifying the module and module attribute to use.
							 | 
						||
| 
								 | 
							
								    for example, the following would cause ``get_handler("myhash")`` to look
							 | 
						||
| 
								 | 
							
								    for a class named ``myhash`` within the ``myapp.helpers`` module::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        >>> from passlib.registry import registry_crypt_handler_path
							 | 
						||
| 
								 | 
							
								        >>> registry_crypt_handler_path("myhash", "myapp.helpers")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    ...while this form would cause ``get_handler("myhash")`` to look
							 | 
						||
| 
								 | 
							
								    for a class name ``MyHash`` within the ``myapp.helpers`` module::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        >>> from passlib.registry import registry_crypt_handler_path
							 | 
						||
| 
								 | 
							
								        >>> registry_crypt_handler_path("myhash", "myapp.helpers:MyHash")
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    # validate name
							 | 
						||
| 
								 | 
							
								    _validate_handler_name(name)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # validate path
							 | 
						||
| 
								 | 
							
								    if path.startswith("."):
							 | 
						||
| 
								 | 
							
								        raise ValueError("path cannot start with '.'")
							 | 
						||
| 
								 | 
							
								    if ':' in path:
							 | 
						||
| 
								 | 
							
								        if path.count(':') > 1:
							 | 
						||
| 
								 | 
							
								            raise ValueError("path cannot have more than one ':'")
							 | 
						||
| 
								 | 
							
								        if path.find('.', path.index(':')) > -1:
							 | 
						||
| 
								 | 
							
								            raise ValueError("path cannot have '.' to right of ':'")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # store location
							 | 
						||
| 
								 | 
							
								    _locations[name] = path
							 | 
						||
| 
								 | 
							
								    log.debug("registered path to %r handler: %r", name, path)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def register_crypt_handler(handler, force=False, _attr=None):
							 | 
						||
| 
								 | 
							
								    """register password hash handler.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this method immediately registers a handler with the internal passlib registry,
							 | 
						||
| 
								 | 
							
								    so that it will be returned by :func:`get_crypt_handler` when requested.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :arg handler: the password hash handler to register
							 | 
						||
| 
								 | 
							
								    :param force: force override of existing handler (defaults to False)
							 | 
						||
| 
								 | 
							
								    :param _attr:
							 | 
						||
| 
								 | 
							
								        [internal kwd] if specified, ensures ``handler.name``
							 | 
						||
| 
								 | 
							
								        matches this value, or raises :exc:`ValueError`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :raises TypeError:
							 | 
						||
| 
								 | 
							
								        if the specified object does not appear to be a valid handler.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :raises ValueError:
							 | 
						||
| 
								 | 
							
								        if the specified object's name (or other required attributes)
							 | 
						||
| 
								 | 
							
								        contain invalid values.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :raises KeyError:
							 | 
						||
| 
								 | 
							
								        if a (different) handler was already registered with
							 | 
						||
| 
								 | 
							
								        the same name, and ``force=True`` was not specified.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    # validate handler
							 | 
						||
| 
								 | 
							
								    if not is_crypt_handler(handler):
							 | 
						||
| 
								 | 
							
								        raise ExpectedTypeError(handler, "password hash handler", "handler")
							 | 
						||
| 
								 | 
							
								    if not handler:
							 | 
						||
| 
								 | 
							
								        raise AssertionError("``bool(handler)`` must be True")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # validate name
							 | 
						||
| 
								 | 
							
								    name = handler.name
							 | 
						||
| 
								 | 
							
								    _validate_handler_name(name)
							 | 
						||
| 
								 | 
							
								    if _attr and _attr != name:
							 | 
						||
| 
								 | 
							
								        raise ValueError("handlers must be stored only under their own name (%r != %r)" %
							 | 
						||
| 
								 | 
							
								                         (_attr, name))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # check for existing handler
							 | 
						||
| 
								 | 
							
								    other = _handlers.get(name)
							 | 
						||
| 
								 | 
							
								    if other:
							 | 
						||
| 
								 | 
							
								        if other is handler:
							 | 
						||
| 
								 | 
							
								            log.debug("same %r handler already registered: %r", name, handler)
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								        elif force:
							 | 
						||
| 
								 | 
							
								            log.warning("overriding previously registered %r handler: %r",
							 | 
						||
| 
								 | 
							
								                        name, other)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            raise KeyError("another %r handler has already been registered: %r" %
							 | 
						||
| 
								 | 
							
								                           (name, other))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # register handler
							 | 
						||
| 
								 | 
							
								    _handlers[name] = handler
							 | 
						||
| 
								 | 
							
								    log.debug("registered %r handler: %r", name, handler)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def get_crypt_handler(name, default=_UNSET):
							 | 
						||
| 
								 | 
							
								    """return handler for specified password hash scheme.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this method looks up a handler for the specified scheme.
							 | 
						||
| 
								 | 
							
								    if the handler is not already loaded,
							 | 
						||
| 
								 | 
							
								    it checks if the location is known, and loads it first.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :arg name: name of handler to return
							 | 
						||
| 
								 | 
							
								    :param default: optional default value to return if no handler with specified name is found.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :raises KeyError: if no handler matching that name is found, and no default specified, a KeyError will be raised.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :returns: handler attached to name, or default value (if specified).
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    # catch invalid names before we check _handlers,
							 | 
						||
| 
								 | 
							
								    # since it's a module dict, and exposes things like __package__, etc.
							 | 
						||
| 
								 | 
							
								    if name.startswith("_"):
							 | 
						||
| 
								 | 
							
								        if default is _UNSET:
							 | 
						||
| 
								 | 
							
								            raise KeyError("invalid handler name: %r" % (name,))
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            return default
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # check if handler is already loaded
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        return _handlers[name]
							 | 
						||
| 
								 | 
							
								    except KeyError:
							 | 
						||
| 
								 | 
							
								        pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # normalize name (and if changed, check dict again)
							 | 
						||
| 
								 | 
							
								    assert isinstance(name, unicode_or_str), "name must be string instance"
							 | 
						||
| 
								 | 
							
								    alt = name.replace("-","_").lower()
							 | 
						||
| 
								 | 
							
								    if alt != name:
							 | 
						||
| 
								 | 
							
								        warn("handler names should be lower-case, and use underscores instead "
							 | 
						||
| 
								 | 
							
								             "of hyphens: %r => %r" % (name, alt), PasslibWarning,
							 | 
						||
| 
								 | 
							
								             stacklevel=2)
							 | 
						||
| 
								 | 
							
								        name = alt
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # try to load using new name
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            return _handlers[name]
							 | 
						||
| 
								 | 
							
								        except KeyError:
							 | 
						||
| 
								 | 
							
								            pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # check if lazy load mapping has been specified for this driver
							 | 
						||
| 
								 | 
							
								    path = _locations.get(name)
							 | 
						||
| 
								 | 
							
								    if path:
							 | 
						||
| 
								 | 
							
								        if ':' in path:
							 | 
						||
| 
								 | 
							
								            modname, modattr = path.split(":")
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            modname, modattr = path, name
							 | 
						||
| 
								 | 
							
								        ##log.debug("loading %r handler from path: '%s:%s'", name, modname, modattr)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # try to load the module - any import errors indicate runtime config, usually
							 | 
						||
| 
								 | 
							
								        # either missing package, or bad path provided to register_crypt_handler_path()
							 | 
						||
| 
								 | 
							
								        mod = __import__(modname, fromlist=[modattr], level=0)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # first check if importing module triggered register_crypt_handler(),
							 | 
						||
| 
								 | 
							
								        # (this is discouraged due to its magical implicitness)
							 | 
						||
| 
								 | 
							
								        handler = _handlers.get(name)
							 | 
						||
| 
								 | 
							
								        if handler:
							 | 
						||
| 
								 | 
							
								            # XXX: issue deprecation warning here?
							 | 
						||
| 
								 | 
							
								            assert is_crypt_handler(handler), "unexpected object: name=%r object=%r" % (name, handler)
							 | 
						||
| 
								 | 
							
								            return handler
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # then get real handler & register it
							 | 
						||
| 
								 | 
							
								        handler = getattr(mod, modattr)
							 | 
						||
| 
								 | 
							
								        register_crypt_handler(handler, _attr=name)
							 | 
						||
| 
								 | 
							
								        return handler
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # fail!
							 | 
						||
| 
								 | 
							
								    if default is _UNSET:
							 | 
						||
| 
								 | 
							
								        raise KeyError("no crypt handler found for algorithm: %r" % (name,))
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        return default
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def list_crypt_handlers(loaded_only=False):
							 | 
						||
| 
								 | 
							
								    """return sorted list of all known crypt handler names.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param loaded_only: if ``True``, only returns names of handlers which have actually been loaded.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :returns: list of names of all known handlers
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    names = set(_handlers)
							 | 
						||
| 
								 | 
							
								    if not loaded_only:
							 | 
						||
| 
								 | 
							
								        names.update(_locations)
							 | 
						||
| 
								 | 
							
								    # strip private attrs out of namespace and sort.
							 | 
						||
| 
								 | 
							
								    # TODO: make _handlers a separate list, so we don't have module namespace mixed in.
							 | 
						||
| 
								 | 
							
								    return sorted(name for name in names if not name.startswith("_"))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# NOTE: these two functions mainly exist just for the unittests...
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _has_crypt_handler(name, loaded_only=False):
							 | 
						||
| 
								 | 
							
								    """check if handler name is known.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this is only useful for two cases:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    * quickly checking if handler has already been loaded
							 | 
						||
| 
								 | 
							
								    * checking if handler exists, without actually loading it
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :arg name: name of handler
							 | 
						||
| 
								 | 
							
								    :param loaded_only: if ``True``, returns False if handler exists but hasn't been loaded
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    return (name in _handlers) or (not loaded_only and name in _locations)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _unload_handler_name(name, locations=True):
							 | 
						||
| 
								 | 
							
								    """unloads a handler from the registry.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. warning::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        this is an internal function,
							 | 
						||
| 
								 | 
							
								        used only by the unittests.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if loaded handler is found with specified name, it's removed.
							 | 
						||
| 
								 | 
							
								    if path to lazy load handler is found, it's removed.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    missing names are a noop.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :arg name: name of handler to unload
							 | 
						||
| 
								 | 
							
								    :param locations: if False, won't purge registered handler locations (default True)
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    if name in _handlers:
							 | 
						||
| 
								 | 
							
								        del _handlers[name]
							 | 
						||
| 
								 | 
							
								    if locations and name in _locations:
							 | 
						||
| 
								 | 
							
								        del _locations[name]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# inspection helpers
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# general
							 | 
						||
| 
								 | 
							
								#------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# TODO: needs UTs
							 | 
						||
| 
								 | 
							
								def _resolve(hasher, param="value"):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    internal helper to resolve argument to hasher object
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    if is_crypt_handler(hasher):
							 | 
						||
| 
								 | 
							
								        return hasher
							 | 
						||
| 
								 | 
							
								    elif isinstance(hasher, unicode_or_str):
							 | 
						||
| 
								 | 
							
								        return get_crypt_handler(hasher)
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        raise exc.ExpectedTypeError(hasher, unicode_or_str, param)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#: backend aliases
							 | 
						||
| 
								 | 
							
								ANY = "any"
							 | 
						||
| 
								 | 
							
								BUILTIN = "builtin"
							 | 
						||
| 
								 | 
							
								OS_CRYPT = "os_crypt"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# TODO: needs UTs
							 | 
						||
| 
								 | 
							
								def has_backend(hasher, backend=ANY, safe=False):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    Test if specified backend is available for hasher.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param hasher:
							 | 
						||
| 
								 | 
							
								        Hasher name or object.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param backend:
							 | 
						||
| 
								 | 
							
								        Name of backend, or ``"any"`` if any backend will do.
							 | 
						||
| 
								 | 
							
								        For hashers without multiple backends, will pretend
							 | 
						||
| 
								 | 
							
								        they have a single backend named ``"builtin"``.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param safe:
							 | 
						||
| 
								 | 
							
								        By default, throws error if backend is unknown.
							 | 
						||
| 
								 | 
							
								        If ``safe=True``, will just return false value.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :raises ValueError:
							 | 
						||
| 
								 | 
							
								        * if hasher name is unknown.
							 | 
						||
| 
								 | 
							
								        * if backend is unknown to hasher, and safe=False.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :return:
							 | 
						||
| 
								 | 
							
								        True if backend available, False if not available,
							 | 
						||
| 
								 | 
							
								        and None if unknown + safe=True.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    hasher = _resolve(hasher)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if backend == ANY:
							 | 
						||
| 
								 | 
							
								        if not hasattr(hasher, "get_backend"):
							 | 
						||
| 
								 | 
							
								            # single backend, assume it's loaded
							 | 
						||
| 
								 | 
							
								            return True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # multiple backends, check at least one is loadable
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            hasher.get_backend()
							 | 
						||
| 
								 | 
							
								            return True
							 | 
						||
| 
								 | 
							
								        except exc.MissingBackendError:
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # test for specific backend
							 | 
						||
| 
								 | 
							
								    if hasattr(hasher, "has_backend"):
							 | 
						||
| 
								 | 
							
								        # multiple backends
							 | 
						||
| 
								 | 
							
								        if safe and backend not in hasher.backends:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								        return hasher.has_backend(backend)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # single builtin backend
							 | 
						||
| 
								 | 
							
								    if backend == BUILTIN:
							 | 
						||
| 
								 | 
							
								        return True
							 | 
						||
| 
								 | 
							
								    elif safe:
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        raise exc.UnknownBackendError(hasher, backend)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# os crypt
							 | 
						||
| 
								 | 
							
								#------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# TODO: move unix_crypt_schemes list to here.
							 | 
						||
| 
								 | 
							
								# os_crypt_schemes -- alias for unix_crypt_schemes above
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# TODO: needs UTs
							 | 
						||
| 
								 | 
							
								@memoize_single_value
							 | 
						||
| 
								 | 
							
								def get_supported_os_crypt_schemes():
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    return tuple of schemes which :func:`crypt.crypt` natively supports.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    if not os_crypt_present:
							 | 
						||
| 
								 | 
							
								        return ()
							 | 
						||
| 
								 | 
							
								    cache = tuple(name for name in os_crypt_schemes
							 | 
						||
| 
								 | 
							
								                  if get_crypt_handler(name).has_backend(OS_CRYPT))
							 | 
						||
| 
								 | 
							
								    if not cache:  # pragma: no cover -- sanity check
							 | 
						||
| 
								 | 
							
								        # no idea what OS this could happen on...
							 | 
						||
| 
								 | 
							
								        import platform
							 | 
						||
| 
								 | 
							
								        warn("crypt.crypt() function is present, but doesn't support any "
							 | 
						||
| 
								 | 
							
								             "formats known to passlib! (system=%r release=%r)" %
							 | 
						||
| 
								 | 
							
								             (platform.system(), platform.release()),
							 | 
						||
| 
								 | 
							
								             exc.PasslibRuntimeWarning)
							 | 
						||
| 
								 | 
							
								    return cache
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# TODO: needs UTs
							 | 
						||
| 
								 | 
							
								def has_os_crypt_support(hasher):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    check if hash is supported by native :func:`crypt.crypt` function.
							 | 
						||
| 
								 | 
							
								    if :func:`crypt.crypt` is not present, will always return False.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param hasher:
							 | 
						||
| 
								 | 
							
								        name or hasher object.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :returns bool:
							 | 
						||
| 
								 | 
							
								        True if hash format is supported by OS, else False.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    return os_crypt_present and has_backend(hasher, OS_CRYPT, safe=True)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# eof
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 |