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.
		
		
		
		
		
			
		
			
				
					
					
						
							270 lines
						
					
					
						
							9.9 KiB
						
					
					
				
			
		
		
	
	
							270 lines
						
					
					
						
							9.9 KiB
						
					
					
				"""passlib.handlers.misc - misc generic handlers
 | 
						|
"""
 | 
						|
#=============================================================================
 | 
						|
# imports
 | 
						|
#=============================================================================
 | 
						|
# core
 | 
						|
import sys
 | 
						|
import logging; log = logging.getLogger(__name__)
 | 
						|
from warnings import warn
 | 
						|
# site
 | 
						|
# pkg
 | 
						|
from passlib.utils import to_native_str, str_consteq
 | 
						|
from passlib.utils.compat import unicode, u, unicode_or_bytes_types
 | 
						|
import passlib.utils.handlers as uh
 | 
						|
# local
 | 
						|
__all__ = [
 | 
						|
    "unix_disabled",
 | 
						|
    "unix_fallback",
 | 
						|
    "plaintext",
 | 
						|
]
 | 
						|
 | 
						|
#=============================================================================
 | 
						|
# handler
 | 
						|
#=============================================================================
 | 
						|
class unix_fallback(uh.ifc.DisabledHash, uh.StaticHandler):
 | 
						|
    """This class provides the fallback behavior for unix shadow files, and follows the :ref:`password-hash-api`.
 | 
						|
 | 
						|
    This class does not implement a hash, but instead provides fallback
 | 
						|
    behavior as found in /etc/shadow on most unix variants.
 | 
						|
    If used, should be the last scheme in the context.
 | 
						|
 | 
						|
    * this class will positively identify all hash strings.
 | 
						|
    * for security, passwords will always hash to ``!``.
 | 
						|
    * it rejects all passwords if the hash is NOT an empty string (``!`` or ``*`` are frequently used).
 | 
						|
    * by default it rejects all passwords if the hash is an empty string,
 | 
						|
      but if ``enable_wildcard=True`` is passed to verify(),
 | 
						|
      all passwords will be allowed through if the hash is an empty string.
 | 
						|
 | 
						|
    .. deprecated:: 1.6
 | 
						|
        This has been deprecated due to its "wildcard" feature,
 | 
						|
        and will be removed in Passlib 1.8. Use :class:`unix_disabled` instead.
 | 
						|
    """
 | 
						|
    name = "unix_fallback"
 | 
						|
    context_kwds = ("enable_wildcard",)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def identify(cls, hash):
 | 
						|
        if isinstance(hash, unicode_or_bytes_types):
 | 
						|
            return True
 | 
						|
        else:
 | 
						|
            raise uh.exc.ExpectedStringError(hash, "hash")
 | 
						|
 | 
						|
    def __init__(self, enable_wildcard=False, **kwds):
 | 
						|
        warn("'unix_fallback' is deprecated, "
 | 
						|
             "and will be removed in Passlib 1.8; "
 | 
						|
             "please use 'unix_disabled' instead.",
 | 
						|
             DeprecationWarning)
 | 
						|
        super(unix_fallback, self).__init__(**kwds)
 | 
						|
        self.enable_wildcard = enable_wildcard
 | 
						|
 | 
						|
    def _calc_checksum(self, secret):
 | 
						|
        if self.checksum:
 | 
						|
            # NOTE: hash will generally be "!", but we want to preserve
 | 
						|
            # it in case it's something else, like "*".
 | 
						|
            return self.checksum
 | 
						|
        else:
 | 
						|
            return u("!")
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def verify(cls, secret, hash, enable_wildcard=False):
 | 
						|
        uh.validate_secret(secret)
 | 
						|
        if not isinstance(hash, unicode_or_bytes_types):
 | 
						|
            raise uh.exc.ExpectedStringError(hash, "hash")
 | 
						|
        elif hash:
 | 
						|
            return False
 | 
						|
        else:
 | 
						|
            return enable_wildcard
 | 
						|
 | 
						|
_MARKER_CHARS = u("*!")
 | 
						|
_MARKER_BYTES = b"*!"
 | 
						|
 | 
						|
class unix_disabled(uh.ifc.DisabledHash, uh.MinimalHandler):
 | 
						|
    """This class provides disabled password behavior for unix shadow files,
 | 
						|
    and follows the :ref:`password-hash-api`.
 | 
						|
 | 
						|
    This class does not implement a hash, but instead matches the "disabled account"
 | 
						|
    strings found in ``/etc/shadow`` on most Unix variants. "encrypting" a password
 | 
						|
    will simply return the disabled account marker. It will reject all passwords,
 | 
						|
    no matter the hash string. The :meth:`~passlib.ifc.PasswordHash.hash`
 | 
						|
    method supports one optional keyword:
 | 
						|
 | 
						|
    :type marker: str
 | 
						|
    :param marker:
 | 
						|
        Optional marker string which overrides the platform default
 | 
						|
        used to indicate a disabled account.
 | 
						|
 | 
						|
        If not specified, this will default to ``"*"`` on BSD systems,
 | 
						|
        and use the Linux default ``"!"`` for all other platforms.
 | 
						|
        (:attr:`!unix_disabled.default_marker` will contain the default value)
 | 
						|
 | 
						|
    .. versionadded:: 1.6
 | 
						|
        This class was added as a replacement for the now-deprecated
 | 
						|
        :class:`unix_fallback` class, which had some undesirable features.
 | 
						|
    """
 | 
						|
    name = "unix_disabled"
 | 
						|
    setting_kwds = ("marker",)
 | 
						|
    context_kwds = ()
 | 
						|
 | 
						|
    _disable_prefixes = tuple(str(_MARKER_CHARS))
 | 
						|
 | 
						|
    # TODO: rename attr to 'marker'...
 | 
						|
    if 'bsd' in sys.platform: # pragma: no cover -- runtime detection
 | 
						|
        default_marker = u("*")
 | 
						|
    else:
 | 
						|
        # use the linux default for other systems
 | 
						|
        # (glibc also supports adding old hash after the marker
 | 
						|
        # so it can be restored later).
 | 
						|
        default_marker = u("!")
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def using(cls, marker=None, **kwds):
 | 
						|
        subcls = super(unix_disabled, cls).using(**kwds)
 | 
						|
        if marker is not None:
 | 
						|
            if not cls.identify(marker):
 | 
						|
                raise ValueError("invalid marker: %r" % marker)
 | 
						|
            subcls.default_marker = marker
 | 
						|
        return subcls
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def identify(cls, hash):
 | 
						|
        # NOTE: technically, anything in the /etc/shadow password field
 | 
						|
        #       which isn't valid crypt() output counts as "disabled".
 | 
						|
        #       but that's rather ambiguous, and it's hard to predict what
 | 
						|
        #       valid output is for unknown crypt() implementations.
 | 
						|
        #       so to be on the safe side, we only match things *known*
 | 
						|
        #       to be disabled field indicators, and will add others
 | 
						|
        #       as they are found. things beginning w/ "$" should *never* match.
 | 
						|
        #
 | 
						|
        # things currently matched:
 | 
						|
        #       * linux uses "!"
 | 
						|
        #       * bsd uses "*"
 | 
						|
        #       * linux may use "!" + hash to disable but preserve original hash
 | 
						|
        #       * linux counts empty string as "any password";
 | 
						|
        #         this code recognizes it, but treats it the same as "!"
 | 
						|
        if isinstance(hash, unicode):
 | 
						|
            start = _MARKER_CHARS
 | 
						|
        elif isinstance(hash, bytes):
 | 
						|
            start = _MARKER_BYTES
 | 
						|
        else:
 | 
						|
            raise uh.exc.ExpectedStringError(hash, "hash")
 | 
						|
        return not hash or hash[0] in start
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def verify(cls, secret, hash):
 | 
						|
        uh.validate_secret(secret)
 | 
						|
        if not cls.identify(hash): # handles typecheck
 | 
						|
            raise uh.exc.InvalidHashError(cls)
 | 
						|
        return False
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def hash(cls, secret, **kwds):
 | 
						|
        if kwds:
 | 
						|
            uh.warn_hash_settings_deprecation(cls, kwds)
 | 
						|
            return cls.using(**kwds).hash(secret)
 | 
						|
        uh.validate_secret(secret)
 | 
						|
        marker = cls.default_marker
 | 
						|
        assert marker and cls.identify(marker)
 | 
						|
        return to_native_str(marker, param="marker")
 | 
						|
 | 
						|
    @uh.deprecated_method(deprecated="1.7", removed="2.0")
 | 
						|
    @classmethod
 | 
						|
    def genhash(cls, secret, config, marker=None):
 | 
						|
        if not cls.identify(config):
 | 
						|
            raise uh.exc.InvalidHashError(cls)
 | 
						|
        elif config:
 | 
						|
            # preserve the existing str,since it might contain a disabled password hash ("!" + hash)
 | 
						|
            uh.validate_secret(secret)
 | 
						|
            return to_native_str(config, param="config")
 | 
						|
        else:
 | 
						|
            if marker is not None:
 | 
						|
                cls = cls.using(marker=marker)
 | 
						|
            return cls.hash(secret)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def disable(cls, hash=None):
 | 
						|
        out = cls.hash("")
 | 
						|
        if hash is not None:
 | 
						|
            hash = to_native_str(hash, param="hash")
 | 
						|
            if cls.identify(hash):
 | 
						|
                # extract original hash, so that we normalize marker
 | 
						|
                hash = cls.enable(hash)
 | 
						|
            if hash:
 | 
						|
                out += hash
 | 
						|
        return out
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def enable(cls, hash):
 | 
						|
        hash = to_native_str(hash, param="hash")
 | 
						|
        for prefix in cls._disable_prefixes:
 | 
						|
            if hash.startswith(prefix):
 | 
						|
                orig = hash[len(prefix):]
 | 
						|
                if orig:
 | 
						|
                    return orig
 | 
						|
                else:
 | 
						|
                    raise ValueError("cannot restore original hash")
 | 
						|
        raise uh.exc.InvalidHashError(cls)
 | 
						|
 | 
						|
class plaintext(uh.MinimalHandler):
 | 
						|
    """This class stores passwords in plaintext, and follows the :ref:`password-hash-api`.
 | 
						|
 | 
						|
    The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
 | 
						|
    following additional contextual keyword:
 | 
						|
 | 
						|
    :type encoding: str
 | 
						|
    :param encoding:
 | 
						|
        This controls the character encoding to use (defaults to ``utf-8``).
 | 
						|
 | 
						|
        This encoding will be used to encode :class:`!unicode` passwords
 | 
						|
        under Python 2, and decode :class:`!bytes` hashes under Python 3.
 | 
						|
 | 
						|
    .. versionchanged:: 1.6
 | 
						|
        The ``encoding`` keyword was added.
 | 
						|
    """
 | 
						|
    # NOTE: this is subclassed by ldap_plaintext
 | 
						|
 | 
						|
    name = "plaintext"
 | 
						|
    setting_kwds = ()
 | 
						|
    context_kwds = ("encoding",)
 | 
						|
    default_encoding = "utf-8"
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def identify(cls, hash):
 | 
						|
        if isinstance(hash, unicode_or_bytes_types):
 | 
						|
            return True
 | 
						|
        else:
 | 
						|
            raise uh.exc.ExpectedStringError(hash, "hash")
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def hash(cls, secret, encoding=None):
 | 
						|
        uh.validate_secret(secret)
 | 
						|
        if not encoding:
 | 
						|
            encoding = cls.default_encoding
 | 
						|
        return to_native_str(secret, encoding, "secret")
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def verify(cls, secret, hash, encoding=None):
 | 
						|
        if not encoding:
 | 
						|
            encoding = cls.default_encoding
 | 
						|
        hash = to_native_str(hash, encoding, "hash")
 | 
						|
        if not cls.identify(hash):
 | 
						|
            raise uh.exc.InvalidHashError(cls)
 | 
						|
        return str_consteq(cls.hash(secret, encoding), hash)
 | 
						|
 | 
						|
    @uh.deprecated_method(deprecated="1.7", removed="2.0")
 | 
						|
    @classmethod
 | 
						|
    def genconfig(cls):
 | 
						|
        return cls.hash("")
 | 
						|
 | 
						|
    @uh.deprecated_method(deprecated="1.7", removed="2.0")
 | 
						|
    @classmethod
 | 
						|
    def genhash(cls, secret, config, encoding=None):
 | 
						|
        # NOTE: 'config' is ignored, as this hash has no salting / etc
 | 
						|
        if not cls.identify(config):
 | 
						|
            raise uh.exc.InvalidHashError(cls)
 | 
						|
        return cls.hash(secret, encoding=encoding)
 | 
						|
 | 
						|
#=============================================================================
 | 
						|
# eof
 | 
						|
#=============================================================================
 |