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