Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
handlers.py2712 linesDownload Raw Back to utils
1"""passlib.handler - code for implementing handlers, and global registry for handlers"""2#=============================================================================3# imports4#=============================================================================5from __future__ import with_statement6# core7import inspect8import logging; log = logging.getLogger(__name__)9import math10import threading11from warnings import warn12# site13# pkg14import passlib.exc as exc, passlib.ifc as ifc15from passlib.exc import MissingBackendError, PasslibConfigWarning, \16                        PasslibHashWarning17from passlib.ifc import PasswordHash18from passlib.registry import get_crypt_handler19from passlib.utils import (20    consteq, getrandstr, getrandbytes,21    rng, to_native_str,22    is_crypt_handler, to_unicode,23    MAX_PASSWORD_SIZE, accepts_keyword, as_bool,24    update_mixin_classes)25from passlib.utils.binary import (26    BASE64_CHARS, HASH64_CHARS, PADDED_BASE64_CHARS,27    HEX_CHARS, UPPER_HEX_CHARS, LOWER_HEX_CHARS,28    ALL_BYTE_VALUES,29)30from passlib.utils.compat import join_byte_values, irange, u, native_string_types, \31                                 uascii_to_str, join_unicode, unicode, str_to_uascii, \32                                 join_unicode, unicode_or_bytes_types, PY2, int_types33from passlib.utils.decor import classproperty, deprecated_method34# local35__all__ = [36    # helpers for implementing MCF handlers37    'parse_mc2',38    'parse_mc3',39    'render_mc2',40    'render_mc3',41 42    # framework for implementing handlers43    'GenericHandler',44        'StaticHandler',45        'HasUserContext',46        'HasRawChecksum',47        'HasManyIdents',48        'HasSalt',49        'HasRawSalt',50        'HasRounds',51        'HasManyBackends',52 53    # other helpers54    'PrefixWrapper',55 56    # TODO: a bunch of other things are commonly assumed in this namespace57    #       (e.g. HEX_CHARS etc); need to audit uses and update this list.58]59 60#=============================================================================61# constants62#=============================================================================63 64# deprecated aliases - will be removed after passlib 1.865H64_CHARS = HASH64_CHARS66B64_CHARS = BASE64_CHARS67PADDED_B64_CHARS = PADDED_BASE64_CHARS68UC_HEX_CHARS = UPPER_HEX_CHARS69LC_HEX_CHARS = LOWER_HEX_CHARS70 71#=============================================================================72# support functions73#=============================================================================74def _bitsize(count, chars):75    """helper for bitsize() methods"""76    if chars and count:77        import math78        return int(count * math.log(len(chars), 2))79    else:80        return 081 82def guess_app_stacklevel(start=1):83    """84    try to guess stacklevel for application warning.85    looks for first frame not part of passlib.86    """87    frame = inspect.currentframe()88    count = -start89    try:90        while frame:91            name = frame.f_globals.get('__name__', "")92            if name.startswith("passlib.tests.") or not name.startswith("passlib."):93                return max(1, count)94            count += 195            frame = frame.f_back96        return start97    finally:98        del frame99 100def warn_hash_settings_deprecation(handler, kwds):101    warn("passing settings to %(handler)s.hash() is deprecated, and won't be supported in Passlib 2.0; "102         "use '%(handler)s.using(**settings).hash(secret)' instead" % dict(handler=handler.name),103         DeprecationWarning, stacklevel=guess_app_stacklevel(2))104 105def extract_settings_kwds(handler, kwds):106    """107    helper to extract settings kwds from mix of context & settings kwds.108    pops settings keys from kwds, returns them as a dict.109    """110    context_keys = set(handler.context_kwds)111    return dict((key, kwds.pop(key)) for key in list(kwds) if key not in context_keys)112 113#=============================================================================114# parsing helpers115#=============================================================================116_UDOLLAR = u("$")117_UZERO = u("0")118 119def validate_secret(secret):120    """ensure secret has correct type & size"""121    if not isinstance(secret, unicode_or_bytes_types):122        raise exc.ExpectedStringError(secret, "secret")123    if len(secret) > MAX_PASSWORD_SIZE:124        raise exc.PasswordSizeError(MAX_PASSWORD_SIZE)125 126def to_unicode_for_identify(hash):127    """convert hash to unicode for identify method"""128    if isinstance(hash, unicode):129        return hash130    elif isinstance(hash, bytes):131        # try as utf-8, but if it fails, use foolproof latin-1,132        # since we don't really care about non-ascii chars133        # when running identify.134        try:135            return hash.decode("utf-8")136        except UnicodeDecodeError:137            return hash.decode("latin-1")138    else:139        raise exc.ExpectedStringError(hash, "hash")140 141def parse_mc2(hash, prefix, sep=_UDOLLAR, handler=None):142    """parse hash using 2-part modular crypt format.143 144    this expects a hash of the format :samp:`{prefix}{salt}[${checksum}]`,145    such as md5_crypt, and parses it into salt / checksum portions.146 147    :arg hash: the hash to parse (bytes or unicode)148    :arg prefix: the identifying prefix (unicode)149    :param sep: field separator (unicode, defaults to ``$``).150    :param handler: handler class to pass to error constructors.151 152    :returns:153        a ``(salt, chk | None)`` tuple.154    """155    # detect prefix156    hash = to_unicode(hash, "ascii", "hash")157    assert isinstance(prefix, unicode)158    if not hash.startswith(prefix):159        raise exc.InvalidHashError(handler)160 161    # parse 2-part hash or 1-part config string162    assert isinstance(sep, unicode)163    parts = hash[len(prefix):].split(sep)164    if len(parts) == 2:165        salt, chk = parts166        return salt, chk or None167    elif len(parts) == 1:168        return parts[0], None169    else:170        raise exc.MalformedHashError(handler)171 172def parse_mc3(hash, prefix, sep=_UDOLLAR, rounds_base=10,173              default_rounds=None, handler=None):174    """parse hash using 3-part modular crypt format.175 176    this expects a hash of the format :samp:`{prefix}[{rounds}]${salt}[${checksum}]`,177    such as sha1_crypt, and parses it into rounds / salt / checksum portions.178    tries to convert the rounds to an integer,179    and throws error if it has zero-padding.180 181    :arg hash: the hash to parse (bytes or unicode)182    :arg prefix: the identifying prefix (unicode)183    :param sep: field separator (unicode, defaults to ``$``).184    :param rounds_base:185        the numeric base the rounds are encoded in (defaults to base 10).186    :param default_rounds:187        the default rounds value to return if the rounds field was omitted.188        if this is ``None`` (the default), the rounds field is *required*.189    :param handler: handler class to pass to error constructors.190 191    :returns:192        a ``(rounds : int, salt, chk | None)`` tuple.193    """194    # detect prefix195    hash = to_unicode(hash, "ascii", "hash")196    assert isinstance(prefix, unicode)197    if not hash.startswith(prefix):198        raise exc.InvalidHashError(handler)199 200    # parse 3-part hash or 2-part config string201    assert isinstance(sep, unicode)202    parts = hash[len(prefix):].split(sep)203    if len(parts) == 3:204        rounds, salt, chk = parts205    elif len(parts) == 2:206        rounds, salt = parts207        chk = None208    else:209        raise exc.MalformedHashError(handler)210 211    # validate & parse rounds portion212    if rounds.startswith(_UZERO) and rounds != _UZERO:213        raise exc.ZeroPaddedRoundsError(handler)214    elif rounds:215        rounds = int(rounds, rounds_base)216    elif default_rounds is None:217        raise exc.MalformedHashError(handler, "empty rounds field")218    else:219        rounds = default_rounds220 221    # return result222    return rounds, salt, chk or None223 224# def parse_mc3_long(hash, prefix, sep=_UDOLLAR, handler=None):225#     """226#     parse hash using 3-part modular crypt format,227#     with complex settings string instead of simple rounds.228#     otherwise works same as :func:`parse_mc3`229#     """230#     # detect prefix231#     hash = to_unicode(hash, "ascii", "hash")232#     assert isinstance(prefix, unicode)233#     if not hash.startswith(prefix):234#         raise exc.InvalidHashError(handler)235#236#     # parse 3-part hash or 2-part config string237#     assert isinstance(sep, unicode)238#     parts = hash[len(prefix):].split(sep)239#     if len(parts) == 3:240#         return parts241#     elif len(parts) == 2:242#         settings, salt = parts243#         return settings, salt, None244#     else:245#         raise exc.MalformedHashError(handler)246 247def parse_int(source, base=10, default=None, param="value", handler=None):248    """249    helper to parse an integer config field250 251    :arg source: unicode source string252    :param base: numeric base253    :param default: optional default if source is empty254    :param param: name of variable, for error msgs255    :param handler: handler class, for error msgs256    """257    if source.startswith(_UZERO) and source != _UZERO:258        raise exc.MalformedHashError(handler, "zero-padded %s field" % param)259    elif source:260        return int(source, base)261    elif default is None:262        raise exc.MalformedHashError(handler, "empty %s field" % param)263    else:264        return default265 266#=============================================================================267# formatting helpers268#=============================================================================269def render_mc2(ident, salt, checksum, sep=u("$")):270    """format hash using 2-part modular crypt format; inverse of parse_mc2()271 272    returns native string with format :samp:`{ident}{salt}[${checksum}]`,273    such as used by md5_crypt.274 275    :arg ident: identifier prefix (unicode)276    :arg salt: encoded salt (unicode)277    :arg checksum: encoded checksum (unicode or None)278    :param sep: separator char (unicode, defaults to ``$``)279 280    :returns:281        config or hash (native str)282    """283    if checksum:284        parts = [ident, salt, sep, checksum]285    else:286        parts = [ident, salt]287    return uascii_to_str(join_unicode(parts))288 289def render_mc3(ident, rounds, salt, checksum, sep=u("$"), rounds_base=10):290    """format hash using 3-part modular crypt format; inverse of parse_mc3()291 292    returns native string with format :samp:`{ident}[{rounds}$]{salt}[${checksum}]`,293    such as used by sha1_crypt.294 295    :arg ident: identifier prefix (unicode)296    :arg rounds: rounds field (int or None)297    :arg salt: encoded salt (unicode)298    :arg checksum: encoded checksum (unicode or None)299    :param sep: separator char (unicode, defaults to ``$``)300    :param rounds_base: base to encode rounds value (defaults to base 10)301 302    :returns:303        config or hash (native str)304    """305    if rounds is None:306        rounds = u('')307    elif rounds_base == 16:308        rounds = u("%x") % rounds309    else:310        assert rounds_base == 10311        rounds = unicode(rounds)312    if checksum:313        parts = [ident, rounds, sep, salt, sep, checksum]314    else:315        parts = [ident, rounds, sep, salt]316    return uascii_to_str(join_unicode(parts))317 318 319def mask_value(value, show=4, pct=0.125, char=u"*"):320    """321    helper to mask contents of sensitive field.322 323    :param value:324        raw value (str, bytes, etc)325 326    :param show:327        max # of characters to remain visible328 329    :param pct:330        don't show more than this % of input.331 332    :param char:333        character to use for masking334 335    :rtype: str | None336    """337    if value is None:338        return None339    if not isinstance(value, unicode):340        if isinstance(value, bytes):341            from passlib.utils.binary import ab64_encode342            value = ab64_encode(value).decode("ascii")343        else:344            value = unicode(value)345    size = len(value)346    show = min(show, int(size * pct))347    return value[:show] + char * (size - show)348 349#=============================================================================350# parameter helpers351#=============================================================================352 353def validate_default_value(handler, default, norm, param="value"):354    """355    assert helper that quickly validates default value.356    designed to get out of the way and reduce overhead when asserts are stripped.357    """358    assert default is not None, "%s lacks default %s" % (handler.name, param)359    assert norm(default) == default, "%s: invalid default %s: %r" % (handler.name, param, default)360    return True361 362def norm_integer(handler, value, min=1, max=None, # *363                 param="value", relaxed=False):364    """365    helper to normalize and validate an integer value (e.g. rounds, salt_size)366 367    :arg value: value provided to constructor368    :arg default: default value if none provided. if set to ``None``, value is required.369    :arg param: name of parameter (xxx: move to first arg?)370    :param min: minimum value (defaults to 1)371    :param max: maximum value (default ``None`` means no maximum)372    :returns: validated value373    """374    # check type375    if not isinstance(value, int_types):376        raise exc.ExpectedTypeError(value, "integer", param)377 378    # check minimum379    if value < min:380        msg = "%s: %s (%d) is too low, must be at least %d" % (handler.name, param, value, min)381        if relaxed:382            warn(msg, exc.PasslibHashWarning)383            value = min384        else:385            raise ValueError(msg)386 387    # check maximum388    if max and value > max:389        msg = "%s: %s (%d) is too large, cannot be more than %d" % (handler.name, param, value, max)390        if relaxed:391            warn(msg, exc.PasslibHashWarning)392            value = max393        else:394            raise ValueError(msg)395 396    return value397 398#=============================================================================399# MinimalHandler400#=============================================================================401class MinimalHandler(PasswordHash):402    """403    helper class for implementing hash handlers.404    provides nothing besides a base implementation of the .using() subclass constructor.405    """406    #===================================================================407    # class attr408    #===================================================================409 410    #: private flag used by using() constructor to detect if this is already a subclass.411    _configured = False412 413    #===================================================================414    # configuration interface415    #===================================================================416 417    @classmethod418    def using(cls, relaxed=False):419        # NOTE: this provides the base implementation, which takes care of420        #       creating the newly configured class. Mixins and subclasses421        #       should wrap this, and modify the returned class to suit their options.422        # NOTE: 'relaxed' keyword is ignored here, but parsed so that subclasses423        #       can check for it as argument, and modify their parsing behavior accordingly.424        name = cls.__name__425        if not cls._configured:426            # TODO: straighten out class naming, repr, and .name attr427            name = "<customized %s hasher>" % name428        return type(name, (cls,), dict(__module__=cls.__module__, _configured=True))429 430    #===================================================================431    # eoc432    #===================================================================433 434class TruncateMixin(MinimalHandler):435    """436    PasswordHash mixin which provides a method437    that will check if secret would be truncated,438    and can be configured to throw an error.439 440    .. warning::441 442        Hashers using this mixin will generally need to override443        the default PasswordHash.truncate_error policy of "True",444        and will similarly want to override .truncate_verify_reject as well.445 446        TODO: This should be done explicitly, but for now this mixin sets447        these flags implicitly.448    """449 450    truncate_error = False451    truncate_verify_reject = False452 453    @classmethod454    def using(cls, truncate_error=None, **kwds):455        subcls = super(TruncateMixin, cls).using(**kwds)456        if truncate_error is not None:457            truncate_error = as_bool(truncate_error, param="truncate_error")458            if truncate_error is not None:459                subcls.truncate_error = truncate_error460        return subcls461 462    @classmethod463    def _check_truncate_policy(cls, secret):464        """465        make sure secret won't be truncated.466        NOTE: this should only be called for .hash(), not for .verify(),467        which should honor the .truncate_verify_reject policy.468        """469        assert cls.truncate_size is not None, "truncate_size must be set by subclass"470        if cls.truncate_error and len(secret) > cls.truncate_size:471            raise exc.PasswordTruncateError(cls)472 473#=============================================================================474# GenericHandler475#=============================================================================476class GenericHandler(MinimalHandler):477    """helper class for implementing hash handlers.478 479    GenericHandler-derived classes will have (at least) the following480    constructor options, though others may be added by mixins481    and by the class itself:482 483    :param checksum:484        this should contain the digest portion of a485        parsed hash (mainly provided when the constructor is called486        by :meth:`from_string()`).487        defaults to ``None``.488 489    :param use_defaults:490        If ``False`` (the default), a :exc:`TypeError` should be thrown491        if any settings required by the handler were not explicitly provided.492 493        If ``True``, the handler should attempt to provide a default for any494        missing values. This means generate missing salts, fill in default495        cost parameters, etc.496 497        This is typically only set to ``True`` when the constructor498        is called by :meth:`hash`, allowing user-provided values499        to be handled in a more permissive manner.500 501    :param relaxed:502        If ``False`` (the default), a :exc:`ValueError` should be thrown503        if any settings are out of bounds or otherwise invalid.504 505        If ``True``, they should be corrected if possible, and a warning506        issue. If not possible, only then should an error be raised.507        (e.g. under ``relaxed=True``, rounds values will be clamped508        to min/max rounds).509 510        This is mainly used when parsing the config strings of certain511        hashes, whose specifications implementations to be tolerant512        of incorrect values in salt strings.513 514    Class Attributes515    ================516 517    .. attribute:: ident518 519        [optional]520        If this attribute is filled in, the default :meth:`identify` method will use521        it as a identifying prefix that can be used to recognize instances of this handler's522        hash. Filling this out is recommended for speed.523 524        This should be a unicode str.525 526    .. attribute:: _hash_regex527 528        [optional]529        If this attribute is filled in, the default :meth:`identify` method530        will use it to recognize instances of the hash. If :attr:`ident`531        is specified, this will be ignored.532 533        This should be a unique regex object.534 535    .. attribute:: checksum_size536 537        [optional]538        Specifies the number of characters that should be expected in the checksum string.539        If omitted, no check will be performed.540 541    .. attribute:: checksum_chars542 543        [optional]544        A string listing all the characters allowed in the checksum string.545        If omitted, no check will be performed.546 547        This should be a unicode str.548 549    .. attribute:: _stub_checksum550 551        Placeholder checksum that will be used by genconfig()552        in lieu of actually generating a hash for the empty string.553        This should be a string of the same datatype as :attr:`checksum`.554 555    Instance Attributes556    ===================557    .. attribute:: checksum558 559        The checksum string provided to the constructor (after passing it560        through :meth:`_norm_checksum`).561 562    Required Subclass Methods563    =========================564    The following methods must be provided by handler subclass:565 566    .. automethod:: from_string567    .. automethod:: to_string568    .. automethod:: _calc_checksum569 570    Default Methods571    ===============572    The following methods have default implementations that should work for573    most cases, though they may be overridden if the hash subclass needs to:574 575    .. automethod:: _norm_checksum576 577    .. automethod:: genconfig578    .. automethod:: genhash579    .. automethod:: identify580    .. automethod:: hash581    .. automethod:: verify582    """583 584    #===================================================================585    # class attr586    #===================================================================587    # this must be provided by the actual class.588    setting_kwds = None589 590    # providing default since most classes don't use this at all.591    context_kwds = ()592 593    # optional prefix that uniquely identifies hash594    ident = None595 596    # optional regexp for recognizing hashes,597    # used by default identify() if .ident isn't specified.598    _hash_regex = None599 600    # if specified, _norm_checksum will require this length601    checksum_size = None602 603    # if specified, _norm_checksum() will validate this604    checksum_chars = None605 606    # private flag used by HasRawChecksum607    _checksum_is_bytes = False608 609    #===================================================================610    # instance attrs611    #===================================================================612    checksum = None # stores checksum613#    use_defaults = False # whether _norm_xxx() funcs should fill in defaults.614#    relaxed = False # when _norm_xxx() funcs should be strict about inputs615 616    #===================================================================617    # init618    #===================================================================619    def __init__(self, checksum=None, use_defaults=False, **kwds):620        self.use_defaults = use_defaults621        super(GenericHandler, self).__init__(**kwds)622        if checksum is not None:623            # XXX: do we need to set .relaxed for checksum coercion?624            self.checksum = self._norm_checksum(checksum)625 626    # NOTE: would like to make this classmethod, but fshp checksum size627    #       is dependant on .variant, so leaving this as instance method.628    def _norm_checksum(self, checksum, relaxed=False):629        """validates checksum keyword against class requirements,630        returns normalized version of checksum.631        """632        # NOTE: by default this code assumes checksum should be unicode.633        # For classes where the checksum is raw bytes, the HasRawChecksum sets634        # the _checksum_is_bytes flag which alters various code paths below.635 636        # normalize to bytes / unicode637        raw = self._checksum_is_bytes638        if raw:639            # NOTE: no clear route to reasonably convert unicode -> raw bytes,640            #       so 'relaxed' does nothing here641            if not isinstance(checksum, bytes):642                raise exc.ExpectedTypeError(checksum, "bytes", "checksum")643 644        elif not isinstance(checksum, unicode):645            if isinstance(checksum, bytes) and relaxed:646                warn("checksum should be unicode, not bytes", PasslibHashWarning)647                checksum = checksum.decode("ascii")648            else:649                raise exc.ExpectedTypeError(checksum, "unicode", "checksum")650 651        # check size652        cc = self.checksum_size653        if cc and len(checksum) != cc:654            raise exc.ChecksumSizeError(self, raw=raw)655 656        # check charset657        if not raw:658            cs = self.checksum_chars659            if cs and any(c not in cs for c in checksum):660                raise ValueError("invalid characters in %s checksum" % (self.name,))661 662        return checksum663 664    #===================================================================665    # password hash api - formatting interface666    #===================================================================667    @classmethod668    def identify(cls, hash):669        # NOTE: subclasses may wish to use faster / simpler identify,670        # and raise value errors only when an invalid (but identifiable)671        # string is parsed672        hash = to_unicode_for_identify(hash)673        if not hash:674            return False675 676        # does class specify a known unique prefix to look for?677        ident = cls.ident678        if ident is not None:679            return hash.startswith(ident)680 681        # does class provide a regexp to use?682        pat = cls._hash_regex683        if pat is not None:684            return pat.match(hash) is not None685 686        # as fallback, try to parse hash, and see if we succeed.687        # inefficient, but works for most cases.688        try:689            cls.from_string(hash)690            return True691        except ValueError:692            return False693 694    @classmethod695    def from_string(cls, hash, **context): # pragma: no cover696        r"""697        return parsed instance from hash/configuration string698 699        :param \\*\\*context:700            context keywords to pass to constructor (if applicable).701 702        :raises ValueError: if hash is incorrectly formatted703 704        :returns:705            hash parsed into components,706            for formatting / calculating checksum.707        """708        raise NotImplementedError("%s must implement from_string()" % (cls,))709 710    def to_string(self): # pragma: no cover711        """render instance to hash or configuration string712 713        :returns:714            hash string with salt & digest included.715 716            should return native string type (ascii-bytes under python 2,717            unicode under python 3)718        """719        raise NotImplementedError("%s must implement from_string()" % (self.__class__,))720 721    #===================================================================722    # checksum generation723    #===================================================================724 725    # NOTE: this is only used by genconfig(), and will be removed in passlib 2.0726    @property727    def _stub_checksum(self):728        """729        placeholder used by default .genconfig() so it can avoid expense of calculating digest.730        """731        # used fixed string if available732        if self.checksum_size:733            if self._checksum_is_bytes:734                return b'\x00' * self.checksum_size735            if self.checksum_chars:736                return self.checksum_chars[0] * self.checksum_size737 738        # hack to minimize cost of calculating real checksum739        if isinstance(self, HasRounds):740            orig = self.rounds741            self.rounds = self.min_rounds or 1742            try:743                return self._calc_checksum("")744            finally:745                self.rounds = orig746 747        # final fallback, generate a real checksum748        return self._calc_checksum("")749 750    def _calc_checksum(self, secret): # pragma: no cover751        """given secret; calcuate and return encoded checksum portion of hash752        string, taking config from object state753 754        calc checksum implementations may assume secret is always755        either unicode or bytes, checks are performed by verify/etc.756        """757        raise NotImplementedError("%s must implement _calc_checksum()" %758                                  (self.__class__,))759 760    #===================================================================761    #'application' interface (default implementation)762    #===================================================================763 764    @classmethod765    def hash(cls, secret, **kwds):766        if kwds:767            # Deprecating passing any settings keywords via .hash() as of passlib 1.7; everything768            # should use .using().hash() instead.  If any keywords are specified, presume they're769            # context keywords by default (the common case), and extract out any settings kwds.770            # Support for passing settings via .hash() will be removed in Passlib 2.0, along with771            # this block of code.772            settings = extract_settings_kwds(cls, kwds)773            if settings:774                warn_hash_settings_deprecation(cls, settings)775                return cls.using(**settings).hash(secret, **kwds)776        # NOTE: at this point, 'kwds' should just contain context_kwds subset777        validate_secret(secret)778        self = cls(use_defaults=True, **kwds)779        self.checksum = self._calc_checksum(secret)780        return self.to_string()781 782    @classmethod783    def verify(cls, secret, hash, **context):784        # NOTE: classes with multiple checksum encodings should either785        # override this method, or ensure that from_string() / _norm_checksum()786        # ensures .checksum always uses a single canonical representation.787        validate_secret(secret)788        self = cls.from_string(hash, **context)789        chk = self.checksum790        if chk is None:791            raise exc.MissingDigestError(cls)792        return consteq(self._calc_checksum(secret), chk)793 794    #===================================================================795    # legacy crypt interface796    #===================================================================797 798    @deprecated_method(deprecated="1.7", removed="2.0")799    @classmethod800    def genconfig(cls, **kwds):801        # NOTE: 'kwds' should generally always be settings, so after this completes, *should* be empty.802        settings = extract_settings_kwds(cls, kwds)803        if settings:804            return cls.using(**settings).genconfig(**kwds)805        # NOTE: this uses optional stub checksum to bypass potentially expensive digest generation,806        #       when caller just wants the config string.807        self = cls(use_defaults=True, **kwds)808        self.checksum = self._stub_checksum809        return self.to_string()810 811    @deprecated_method(deprecated="1.7", removed="2.0")812    @classmethod813    def genhash(cls, secret, config, **context):814        if config is None:815            raise TypeError("config must be string")816        validate_secret(secret)817        self = cls.from_string(config, **context)818        self.checksum = self._calc_checksum(secret)819        return self.to_string()820 821    #===================================================================822    # migration interface (basde implementation)823    #===================================================================824 825    @classmethod826    def needs_update(cls, hash, secret=None, **kwds):827        # NOTE: subclasses should generally just wrap _calc_needs_update()828        #       to check their particular keywords.829        self = cls.from_string(hash)830        assert isinstance(self, cls)831        return self._calc_needs_update(secret=secret, **kwds)832 833    def _calc_needs_update(self, secret=None):834        """835        internal helper for :meth:`needs_update`.836        """837        # NOTE: this just provides a stub, subclasses & mixins838        #       should override this with their own tests.839        return False840 841    #===================================================================842    # experimental - the following methods are not finished or tested,843    # but way work correctly for some hashes844    #===================================================================845 846    #: internal helper for forcing settings to be included, even if default matches847    _always_parse_settings = ()848 849    #: internal helper for excluding certain setting_kwds from parsehash() result850    _unparsed_settings = ("salt_size", "relaxed")851 852    #: parsehash() keys that need to be sanitized853    _unsafe_settings = ("salt", "checksum")854 855    @classproperty856    def _parsed_settings(cls):857        """858        helper for :meth:`parsehash` --859        returns list of attributes which should be extracted by parse_hash() from hasher object.860 861        default implementation just takes setting_kwds, and excludes _unparsed_settings862        """863        return tuple(key for key in cls.setting_kwds if key not in cls._unparsed_settings)864 865    @classmethod866    def parsehash(cls, hash, checksum=True, sanitize=False):867        """[experimental method] parse hash into dictionary of settings.868 869        this essentially acts as the inverse of :meth:`hash`: for most870        cases, if ``hash = cls.hash(secret, **opts)``, then871        ``cls.parsehash(hash)`` will return a dict matching the original options872        (with the extra keyword *checksum*).873 874        this method may not work correctly for all hashes,875        and may not be available on some few. its interface may876        change in future releases, if it's kept around at all.877 878        :arg hash: hash to parse879        :param checksum: include checksum keyword? (defaults to True)880        :param sanitize: mask data for sensitive fields? (defaults to False)881        """882        # FIXME: this may not work for hashes with non-standard settings.883        # XXX: how should this handle checksum/salt encoding?884        # need to work that out for hash() anyways.885        self = cls.from_string(hash)886        # XXX: could split next few lines out as self._parsehash() for subclassing887        # XXX: could try to resolve ident/variant to publically suitable alias.888        # XXX: for v1.8, consider making "always" the default policy, and compare to class default889        #      only for whitelisted attrs? or make this whole method obsolete by reworking890        #      so "hasher" object & it's attrs are public?891        UNSET = object()892        always = self._always_parse_settings893        kwds = dict((key, getattr(self, key)) for key in self._parsed_settings894                    if key in always or getattr(self, key) != getattr(cls, key, UNSET))895        if checksum and self.checksum is not None:896            kwds['checksum'] = self.checksum897        if sanitize:898            if sanitize is True:899                sanitize = mask_value900            for key in cls._unsafe_settings:901                if key in kwds:902                    kwds[key] = sanitize(kwds[key])903        return kwds904 905    @classmethod906    def bitsize(cls, **kwds):907        """[experimental method] return info about bitsizes of hash"""908        try:909            info = super(GenericHandler, cls).bitsize(**kwds)910        except AttributeError:911            info = {}912        cc = ALL_BYTE_VALUES if cls._checksum_is_bytes else cls.checksum_chars913        if cls.checksum_size and cc:914            # FIXME: this may overestimate size due to padding bits (e.g. bcrypt)915            # FIXME: this will be off by 1 for case-insensitive hashes.916            info['checksum'] = _bitsize(cls.checksum_size, cc)917        return info918 919    #===================================================================920    # eoc921    #===================================================================922 923class StaticHandler(GenericHandler):924    """GenericHandler mixin for classes which have no settings.925 926    This mixin assumes the entirety of the hash ise stored in the927    :attr:`checksum` attribute; that the hash has no rounds, salt,928    etc. This class provides the following:929 930    * a default :meth:`genconfig` that always returns None.931    * a default :meth:`from_string` and :meth:`to_string`932      that store the entire hash within :attr:`checksum`,933      after optionally stripping a constant prefix.934 935    All that is required by subclasses is an implementation of936    the :meth:`_calc_checksum` method.937    """938    # TODO: document _norm_hash()939 940    setting_kwds = ()941 942    # optional constant prefix subclasses can specify943    _hash_prefix = u("")944 945    @classmethod946    def from_string(cls, hash, **context):947        # default from_string() which strips optional prefix,948        # and passes rest unchanged as checksum value.949        hash = to_unicode(hash, "ascii", "hash")950        hash = cls._norm_hash(hash)951        # could enable this for extra strictness952        ##pat = cls._hash_regex953        ##if pat and pat.match(hash) is None:954        ##    raise ValueError("not a valid %s hash" % (cls.name,))955        prefix = cls._hash_prefix956        if prefix:957            if hash.startswith(prefix):958                hash = hash[len(prefix):]959            else:960                raise exc.InvalidHashError(cls)961        return cls(checksum=hash, **context)962 963    @classmethod964    def _norm_hash(cls, hash):965        """helper for subclasses to normalize case if needed"""966        return hash967 968    def to_string(self):969        return uascii_to_str(self._hash_prefix + self.checksum)970 971    # per-subclass: stores dynamically created subclass used by _calc_checksum() stub972    __cc_compat_hack = None973 974    def _calc_checksum(self, secret):975        """given secret; calcuate and return encoded checksum portion of hash976        string, taking config from object state977        """978        # NOTE: prior to 1.6, StaticHandler required classes implement genhash979        # instead of this method. so if we reach here, we try calling genhash.980        # if that succeeds, we issue deprecation warning. if it fails,981        # we'll just recurse back to here, but in a different instance.982        # so before we call genhash, we create a subclass which handles983        # throwing the NotImplementedError.984        cls = self.__class__985        assert cls.__module__ != __name__986        wrapper_cls = cls.__cc_compat_hack987        if wrapper_cls is None:988            def inner(self, secret):989                raise NotImplementedError("%s must implement _calc_checksum()" %990                                          (cls,))991            wrapper_cls = cls.__cc_compat_hack = type(cls.__name__ + "_wrapper",992                  (cls,), dict(_calc_checksum=inner, __module__=cls.__module__))993        context = dict((k,getattr(self,k)) for k in self.context_kwds)994        # NOTE: passing 'config=None' here even though not currently allowed by ifc,995        #       since it *is* allowed under the old 1.5 ifc we're checking for here.996        try:997            hash = wrapper_cls.genhash(secret, None, **context)998        except TypeError as err:999            if str(err) == "config must be string":1000                raise NotImplementedError("%s must implement _calc_checksum()" %1001                                          (cls,))1002            else:1003                raise1004        warn("%r should be updated to implement StaticHandler._calc_checksum() "1005             "instead of StaticHandler.genhash(), support for the latter "1006             "style will be removed in Passlib 1.8" % cls,1007             DeprecationWarning)1008        return str_to_uascii(hash)1009 1010#=============================================================================1011# GenericHandler mixin classes1012#=============================================================================1013class HasEncodingContext(GenericHandler):1014    """helper for classes which require knowledge of the encoding used"""1015    context_kwds = ("encoding",)1016    default_encoding = "utf-8"1017 1018    def __init__(self, encoding=None, **kwds):1019        super(HasEncodingContext, self).__init__(**kwds)1020        self.encoding = encoding or self.default_encoding1021 1022class HasUserContext(GenericHandler):1023    """helper for classes which require a user context keyword"""1024    context_kwds = ("user",)1025 1026    def __init__(self, user=None, **kwds):1027        super(HasUserContext, self).__init__(**kwds)1028        self.user = user1029 1030    # XXX: would like to validate user input here, but calls to from_string()1031    # which lack context keywords would then fail; so leaving code per-handler.1032 1033    # wrap funcs to accept 'user' as positional arg for ease of use.1034    @classmethod1035    def hash(cls, secret, user=None, **context):1036        return super(HasUserContext, cls).hash(secret, user=user, **context)1037 1038    @classmethod1039    def verify(cls, secret, hash, user=None, **context):1040        return super(HasUserContext, cls).verify(secret, hash, user=user, **context)1041 1042    @deprecated_method(deprecated="1.7", removed="2.0")1043    @classmethod1044    def genhash(cls, secret, config, user=None, **context):1045        return super(HasUserContext, cls).genhash(secret, config, user=user, **context)1046 1047    # XXX: how to guess the entropy of a username?1048    #      most of these hashes are for a system (e.g. Oracle)1049    #      which has a few *very common* names and thus really low entropy;1050    #      while the rest are slightly less predictable.1051    #      need to find good reference about this.1052    ##@classmethod1053    ##def bitsize(cls, **kwds):1054    ##    info = super(HasUserContext, cls).bitsize(**kwds)1055    ##    info['user'] = xxx1056    ##    return info1057 1058#------------------------------------------------------------------------1059# checksum mixins1060#------------------------------------------------------------------------1061class HasRawChecksum(GenericHandler):1062    """mixin for classes which work with decoded checksum bytes1063 1064    .. todo::1065 1066        document this class's usage1067    """1068    # NOTE: GenericHandler.checksum_chars is ignored by this implementation.1069 1070    # NOTE: all HasRawChecksum code is currently part of GenericHandler,1071    # using private '_checksum_is_bytes' flag.1072    # this arrangement may be changed in the future.1073    _checksum_is_bytes = True1074 1075#------------------------------------------------------------------------1076# ident mixins1077#------------------------------------------------------------------------1078class HasManyIdents(GenericHandler):1079    """mixin for hashes which use multiple prefix identifiers1080 1081    For the hashes which may use multiple identifier prefixes,1082    this mixin adds an ``ident`` keyword to constructor.1083    Any value provided is passed through the :meth:`norm_idents` method,1084    which takes care of validating the identifier,1085    as well as allowing aliases for easier specification1086    of the identifiers by the user.1087 1088    .. todo::1089 1090        document this class's usage1091 1092    Class Methods1093    =============1094    .. todo:: document using() and needs_update() options1095    """1096 1097    #===================================================================1098    # class attrs1099    #===================================================================1100    default_ident = None # should be unicode1101    ident_values = None # should be list of unicode strings1102    ident_aliases = None # should be dict of unicode -> unicode1103        # NOTE: any aliases provided to norm_ident() as bytes1104        #       will have been converted to unicode before1105        #       comparing against this dictionary.1106 1107        # NOTE: relying on test_06_HasManyIdents() to verify1108        #       these are configured correctly.1109 1110    #===================================================================1111    # instance attrs1112    #===================================================================1113    ident = None1114 1115    #===================================================================1116    # variant constructor1117    #===================================================================1118    @classmethod1119    def using(cls,  # keyword only...1120              default_ident=None, ident=None, **kwds):1121        """1122        This mixin adds support for the following :meth:`~passlib.ifc.PasswordHash.using` keywords:1123 1124        :param default_ident:1125            default identifier that will be used by resulting customized hasher.1126 1127        :param ident:1128            supported as alternate alias for **default_ident**.1129        """1130        # resolve aliases1131        if ident is not None:1132            if default_ident is not None:1133                raise TypeError("'default_ident' and 'ident' are mutually exclusive")1134            default_ident = ident1135 1136        # create subclass1137        subcls = super(HasManyIdents, cls).using(**kwds)1138 1139        # add custom default ident1140        # (NOTE: creates instance to run value through _norm_ident())1141        if default_ident is not None:1142            subcls.default_ident = cls(ident=default_ident, use_defaults=True).ident1143        return subcls1144 1145    #===================================================================1146    # init1147    #===================================================================1148    def __init__(self, ident=None, **kwds):1149        super(HasManyIdents, self).__init__(**kwds)1150 1151        # init ident1152        if ident is not None:1153            ident = self._norm_ident(ident)1154        elif self.use_defaults:1155            ident = self.default_ident1156            assert validate_default_value(self, ident, self._norm_ident, param="default_ident")1157        else:1158            raise TypeError("no ident specified")1159        self.ident = ident1160 1161    @classmethod1162    def _norm_ident(cls, ident):1163        """1164        helper which normalizes & validates 'ident' value.1165        """1166        # handle bytes1167        assert ident is not None1168        if isinstance(ident, bytes):1169            ident = ident.decode('ascii')1170 1171        # check if identifier is valid1172        iv = cls.ident_values1173        if ident in iv:1174            return ident1175 1176        # resolve aliases, and recheck against ident_values1177        ia = cls.ident_aliases1178        if ia:1179            try:1180                value = ia[ident]1181            except KeyError:1182                pass1183            else:1184                if value in iv:1185                    return value1186 1187        # failure!1188        # XXX: give this it's own error type?1189        raise ValueError("invalid ident: %r" % (ident,))1190 1191    #===================================================================1192    # password hash api1193    #===================================================================1194    @classmethod1195    def identify(cls, hash):1196        hash = to_unicode_for_identify(hash)1197        return hash.startswith(cls.ident_values)1198 1199    @classmethod1200    def _parse_ident(cls, hash):

Showing the first 1,200 of 2712 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai