"""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 #=============================================================================