Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
context.py2638 linesDownload Raw Back to passlib
1"""passlib.context - CryptContext implementation"""2#=============================================================================3# imports4#=============================================================================5from __future__ import with_statement6# core7import re8import logging; log = logging.getLogger(__name__)9import threading10import time11from warnings import warn12# site13# pkg14from passlib import exc15from passlib.exc import ExpectedStringError, ExpectedTypeError, PasslibConfigWarning16from passlib.registry import get_crypt_handler, _validate_handler_name17from passlib.utils import (handlers as uh, to_bytes,18                           to_unicode, splitcomma,19                           as_bool, timer, rng, getrandstr,20                           )21from passlib.utils.binary import BASE64_CHARS22from passlib.utils.compat import (iteritems, num_types, irange,23                                  PY2, PY3, unicode, SafeConfigParser,24                                  NativeStringIO, BytesIO,25                                  unicode_or_bytes_types, native_string_types,26                                  )27from passlib.utils.decor import deprecated_method, memoized_property28# local29__all__ = [30    'CryptContext',31    'LazyCryptContext',32    'CryptPolicy',33]34 35#=============================================================================36# support37#=============================================================================38 39# private object to detect unset params40_UNSET = object()41 42def _coerce_vary_rounds(value):43    """parse vary_rounds string to percent as [0,1) float, or integer"""44    if value.endswith("%"):45        # XXX: deprecate this in favor of raw float?46        return float(value.rstrip("%"))*.0147    try:48        return int(value)49    except ValueError:50        return float(value)51 52# set of options which aren't allowed to be set via policy53_forbidden_scheme_options = set(["salt"])54    # 'salt' - not allowed since a fixed salt would defeat the purpose.55 56# dict containing funcs used to coerce strings to correct type for scheme option keys.57# NOTE: this isn't really needed any longer, since Handler.using() handles the actual parsing.58#       keeping this around for now, though, since it makes context.to_dict() output cleaner.59_coerce_scheme_options = dict(60    min_rounds=int,61    max_rounds=int,62    default_rounds=int,63    vary_rounds=_coerce_vary_rounds,64    salt_size=int,65)66 67def _is_handler_registered(handler):68    """detect if handler is registered or a custom handler"""69    return get_crypt_handler(handler.name, None) is handler70 71@staticmethod72def _always_needs_update(hash, secret=None):73    """74    dummy function patched into handler.needs_update() by _CryptConfig75    when hash alg has been deprecated for context.76    """77    return True78 79#: list of keys allowed under wildcard "all" scheme w/o a security warning.80_global_settings = set(["truncate_error", "vary_rounds"])81 82#=============================================================================83# crypt policy84#=============================================================================85_preamble = ("The CryptPolicy class has been deprecated as of "86             "Passlib 1.6, and will be removed in Passlib 1.8. ")87 88class CryptPolicy(object):89    """90    .. deprecated:: 1.691        This class has been deprecated, and will be removed in Passlib 1.8.92        All of its functionality has been rolled into :class:`CryptContext`.93 94    This class previously stored the configuration options for the95    CryptContext class. In the interest of interface simplification,96    all of this class' functionality has been rolled into the CryptContext97    class itself.98    The documentation for this class is now focused on  documenting how to99    migrate to the new api. Additionally, where possible, the deprecation100    warnings issued by the CryptPolicy methods will list the replacement call101    that should be used.102 103    Constructors104    ============105    CryptPolicy objects can be constructed directly using any of106    the keywords accepted by :class:`CryptContext`. Direct uses of the107    :class:`!CryptPolicy` constructor should either pass the keywords108    directly into the CryptContext constructor, or to :meth:`CryptContext.update`109    if the policy object was being used to update an existing context object.110 111    In addition to passing in keywords directly,112    CryptPolicy objects can be constructed by the following methods:113 114    .. automethod:: from_path115    .. automethod:: from_string116    .. automethod:: from_source117    .. automethod:: from_sources118    .. automethod:: replace119 120    Introspection121    =============122    All of the informational methods provided by this class have been deprecated123    by identical or similar methods in the :class:`CryptContext` class:124 125    .. automethod:: has_schemes126    .. automethod:: schemes127    .. automethod:: iter_handlers128    .. automethod:: get_handler129    .. automethod:: get_options130    .. automethod:: handler_is_deprecated131    .. automethod:: get_min_verify_time132 133    Exporting134    =========135    .. automethod:: iter_config136    .. automethod:: to_dict137    .. automethod:: to_file138    .. automethod:: to_string139 140    .. note::141        CryptPolicy are immutable.142        Use the :meth:`replace` method to mutate existing instances.143 144    .. deprecated:: 1.6145    """146    #===================================================================147    # class methods148    #===================================================================149    @classmethod150    def from_path(cls, path, section="passlib", encoding="utf-8"):151        """create a CryptPolicy instance from a local file.152 153        .. deprecated:: 1.6154 155        Creating a new CryptContext from a file, which was previously done via156        ``CryptContext(policy=CryptPolicy.from_path(path))``, can now be157        done via ``CryptContext.from_path(path)``.158        See :meth:`CryptContext.from_path` for details.159 160        Updating an existing CryptContext from a file, which was previously done161        ``context.policy = CryptPolicy.from_path(path)``, can now be162        done via ``context.load_path(path)``.163        See :meth:`CryptContext.load_path` for details.164        """165        warn(_preamble +166             "Instead of ``CryptPolicy.from_path(path)``, "167             "use ``CryptContext.from_path(path)`` "168             " or ``context.load_path(path)`` for an existing CryptContext.",169             DeprecationWarning, stacklevel=2)170        return cls(_internal_context=CryptContext.from_path(path, section,171                                                            encoding))172 173    @classmethod174    def from_string(cls, source, section="passlib", encoding="utf-8"):175        """create a CryptPolicy instance from a string.176 177        .. deprecated:: 1.6178 179        Creating a new CryptContext from a string, which was previously done via180        ``CryptContext(policy=CryptPolicy.from_string(data))``, can now be181        done via ``CryptContext.from_string(data)``.182        See :meth:`CryptContext.from_string` for details.183 184        Updating an existing CryptContext from a string, which was previously done185        ``context.policy = CryptPolicy.from_string(data)``, can now be186        done via ``context.load(data)``.187        See :meth:`CryptContext.load` for details.188        """189        warn(_preamble +190             "Instead of ``CryptPolicy.from_string(source)``, "191             "use ``CryptContext.from_string(source)`` or "192             "``context.load(source)`` for an existing CryptContext.",193             DeprecationWarning, stacklevel=2)194        return cls(_internal_context=CryptContext.from_string(source, section,195                                                              encoding))196 197    @classmethod198    def from_source(cls, source, _warn=True):199        """create a CryptPolicy instance from some source.200 201        this method autodetects the source type, and invokes202        the appropriate constructor automatically. it attempts203        to detect whether the source is a configuration string, a filepath,204        a dictionary, or an existing CryptPolicy instance.205 206        .. deprecated:: 1.6207 208        Create a new CryptContext, which could previously be done via209        ``CryptContext(policy=CryptPolicy.from_source(source))``, should210        now be done using an explicit method: the :class:`CryptContext`211        constructor itself, :meth:`CryptContext.from_path`,212        or :meth:`CryptContext.from_string`.213 214        Updating an existing CryptContext, which could previously be done via215        ``context.policy = CryptPolicy.from_source(source)``, should216        now be done using an explicit method: :meth:`CryptContext.update`,217        or :meth:`CryptContext.load`.218        """219        if _warn:220            warn(_preamble +221                 "Instead of ``CryptPolicy.from_source()``, "222                 "use ``CryptContext.from_string(path)`` "223                 " or ``CryptContext.from_path(source)``, as appropriate.",224                 DeprecationWarning, stacklevel=2)225        if isinstance(source, CryptPolicy):226            return source227        elif isinstance(source, dict):228            return cls(_internal_context=CryptContext(**source))229        elif not isinstance(source, (bytes,unicode)):230            raise TypeError("source must be CryptPolicy, dict, config string, "231                            "or file path: %r" % (type(source),))232        elif any(c in source for c in "\n\r\t") or not source.strip(" \t./;:"):233            return cls(_internal_context=CryptContext.from_string(source))234        else:235            return cls(_internal_context=CryptContext.from_path(source))236 237    @classmethod238    def from_sources(cls, sources, _warn=True):239        """create a CryptPolicy instance by merging multiple sources.240 241        each source is interpreted as by :meth:`from_source`,242        and the results are merged together.243 244        .. deprecated:: 1.6245            Instead of using this method to merge multiple policies together,246            a :class:`CryptContext` instance should be created, and then247            the multiple sources merged together via :meth:`CryptContext.load`.248        """249        if _warn:250            warn(_preamble +251                 "Instead of ``CryptPolicy.from_sources()``, "252                 "use the various CryptContext constructors "253                 " followed by ``context.update()``.",254                 DeprecationWarning, stacklevel=2)255        if len(sources) == 0:256            raise ValueError("no sources specified")257        if len(sources) == 1:258            return cls.from_source(sources[0], _warn=False)259        kwds = {}260        for source in sources:261            kwds.update(cls.from_source(source, _warn=False)._context.to_dict(resolve=True))262        return cls(_internal_context=CryptContext(**kwds))263 264    def replace(self, *args, **kwds):265        """create a new CryptPolicy, optionally updating parts of the266        existing configuration.267 268        .. deprecated:: 1.6269            Callers of this method should :meth:`CryptContext.update` or270            :meth:`CryptContext.copy` instead.271        """272        if self._stub_policy:273            warn(_preamble + # pragma: no cover -- deprecated & unused274                 "Instead of ``context.policy.replace()``, "275                 "use ``context.update()`` or ``context.copy()``.",276                 DeprecationWarning, stacklevel=2)277        else:278            warn(_preamble +279                 "Instead of ``CryptPolicy().replace()``, "280                 "create a CryptContext instance and "281                 "use ``context.update()`` or ``context.copy()``.",282                 DeprecationWarning, stacklevel=2)283        sources = [ self ]284        if args:285            sources.extend(args)286        if kwds:287            sources.append(kwds)288        return CryptPolicy.from_sources(sources, _warn=False)289 290    #===================================================================291    # instance attrs292    #===================================================================293 294    # internal CryptContext we're wrapping to handle everything295    # until this class is removed.296    _context = None297 298    # flag indicating this is wrapper generated by the CryptContext.policy299    # attribute, rather than one created independantly by the application.300    _stub_policy = False301 302    #===================================================================303    # init304    #===================================================================305    def __init__(self, *args, **kwds):306        context = kwds.pop("_internal_context", None)307        if context:308            assert isinstance(context, CryptContext)309            self._context = context310            self._stub_policy = kwds.pop("_stub_policy", False)311            assert not (args or kwds), "unexpected args: %r %r" % (args,kwds)312        else:313            if args:314                if len(args) != 1:315                    raise TypeError("only one positional argument accepted")316                if kwds:317                    raise TypeError("cannot specify positional arg and kwds")318                kwds = args[0]319            warn(_preamble +320                 "Instead of constructing a CryptPolicy instance, "321                 "create a CryptContext directly, or use ``context.update()`` "322                 "and ``context.load()`` to reconfigure existing CryptContext "323                 "instances.",324                 DeprecationWarning, stacklevel=2)325            self._context = CryptContext(**kwds)326 327    #===================================================================328    # public interface for examining options329    #===================================================================330    def has_schemes(self):331        """return True if policy defines *any* schemes for use.332 333        .. deprecated:: 1.6334            applications should use ``bool(context.schemes())`` instead.335            see :meth:`CryptContext.schemes`.336        """337        if self._stub_policy:338            warn(_preamble + # pragma: no cover -- deprecated & unused339                 "Instead of ``context.policy.has_schemes()``, "340                 "use ``bool(context.schemes())``.",341                 DeprecationWarning, stacklevel=2)342        else:343            warn(_preamble +344                 "Instead of ``CryptPolicy().has_schemes()``, "345                 "create a CryptContext instance and "346                 "use ``bool(context.schemes())``.",347                 DeprecationWarning, stacklevel=2)348        return bool(self._context.schemes())349 350    def iter_handlers(self):351        """return iterator over handlers defined in policy.352 353        .. deprecated:: 1.6354            applications should use ``context.schemes(resolve=True))`` instead.355            see :meth:`CryptContext.schemes`.356        """357        if self._stub_policy:358            warn(_preamble +359                 "Instead of ``context.policy.iter_handlers()``, "360                 "use ``context.schemes(resolve=True)``.",361                 DeprecationWarning, stacklevel=2)362        else:363            warn(_preamble +364                 "Instead of ``CryptPolicy().iter_handlers()``, "365                 "create a CryptContext instance and "366                 "use ``context.schemes(resolve=True)``.",367                 DeprecationWarning, stacklevel=2)368        return self._context.schemes(resolve=True, unconfigured=True)369 370    def schemes(self, resolve=False):371        """return list of schemes defined in policy.372 373        .. deprecated:: 1.6374            applications should use :meth:`CryptContext.schemes` instead.375        """376        if self._stub_policy:377            warn(_preamble + # pragma: no cover -- deprecated & unused378                 "Instead of ``context.policy.schemes()``, "379                 "use ``context.schemes()``.",380                 DeprecationWarning, stacklevel=2)381        else:382            warn(_preamble +383                 "Instead of ``CryptPolicy().schemes()``, "384                 "create a CryptContext instance and "385                 "use ``context.schemes()``.",386                 DeprecationWarning, stacklevel=2)387        return list(self._context.schemes(resolve=resolve, unconfigured=True))388 389    def get_handler(self, name=None, category=None, required=False):390        """return handler as specified by name, or default handler.391 392        .. deprecated:: 1.6393            applications should use :meth:`CryptContext.handler` instead,394            though note that the ``required`` keyword has been removed,395            and the new method will always act as if ``required=True``.396        """397        if self._stub_policy:398            warn(_preamble +399                 "Instead of ``context.policy.get_handler()``, "400                 "use ``context.handler()``.",401                 DeprecationWarning, stacklevel=2)402        else:403            warn(_preamble +404                 "Instead of ``CryptPolicy().get_handler()``, "405                 "create a CryptContext instance and "406                 "use ``context.handler()``.",407                 DeprecationWarning, stacklevel=2)408        # CryptContext.handler() doesn't support required=False,409        # so wrapping it in try/except410        try:411            return self._context.handler(name, category, unconfigured=True)412        except KeyError:413            if required:414                raise415            else:416                return None417 418    def get_min_verify_time(self, category=None):419        """get min_verify_time setting for policy.420 421        .. deprecated:: 1.6422            min_verify_time option will be removed entirely in passlib 1.8423 424        .. versionchanged:: 1.7425            this method now always returns the value automatically426            calculated by :meth:`CryptContext.min_verify_time`,427            any value specified by policy is ignored.428        """429        warn("get_min_verify_time() and min_verify_time option is deprecated and ignored, "430             "and will be removed in Passlib 1.8", DeprecationWarning,431             stacklevel=2)432        return 0433 434    def get_options(self, name, category=None):435        """return dictionary of options specific to a given handler.436 437        .. deprecated:: 1.6438            this method has no direct replacement in the 1.6 api, as there439            is not a clearly defined use-case. however, examining the output of440            :meth:`CryptContext.to_dict` should serve as the closest alternative.441        """442        # XXX: might make a public replacement, but need more study of the use cases.443        if self._stub_policy:444            warn(_preamble + # pragma: no cover -- deprecated & unused445                 "``context.policy.get_options()`` will no longer be available.",446                 DeprecationWarning, stacklevel=2)447        else:448            warn(_preamble +449                 "``CryptPolicy().get_options()`` will no longer be available.",450                 DeprecationWarning, stacklevel=2)451        if hasattr(name, "name"):452            name = name.name453        return self._context._config._get_record_options_with_flag(name, category)[0]454 455    def handler_is_deprecated(self, name, category=None):456        """check if handler has been deprecated by policy.457 458        .. deprecated:: 1.6459            this method has no direct replacement in the 1.6 api, as there460            is not a clearly defined use-case. however, examining the output of461            :meth:`CryptContext.to_dict` should serve as the closest alternative.462        """463        # XXX: might make a public replacement, but need more study of the use cases.464        if self._stub_policy:465            warn(_preamble +466                 "``context.policy.handler_is_deprecated()`` will no longer be available.",467                 DeprecationWarning, stacklevel=2)468        else:469            warn(_preamble +470                 "``CryptPolicy().handler_is_deprecated()`` will no longer be available.",471                 DeprecationWarning, stacklevel=2)472        if hasattr(name, "name"):473            name = name.name474        return self._context.handler(name, category).deprecated475 476    #===================================================================477    # serialization478    #===================================================================479 480    def iter_config(self, ini=False, resolve=False):481        """iterate over key/value pairs representing the policy object.482 483        .. deprecated:: 1.6484            applications should use :meth:`CryptContext.to_dict` instead.485        """486        if self._stub_policy:487            warn(_preamble + # pragma: no cover -- deprecated & unused488                 "Instead of ``context.policy.iter_config()``, "489                 "use ``context.to_dict().items()``.",490                 DeprecationWarning, stacklevel=2)491        else:492            warn(_preamble +493                 "Instead of ``CryptPolicy().iter_config()``, "494                 "create a CryptContext instance and "495                 "use ``context.to_dict().items()``.",496                 DeprecationWarning, stacklevel=2)497        # hacked code that renders keys & values in manner that approximates498        # old behavior. context.to_dict() is much cleaner.499        context = self._context500        if ini:501            def render_key(key):502                return context._render_config_key(key).replace("__", ".")503            def render_value(value):504                if isinstance(value, (list,tuple)):505                    value = ", ".join(value)506                return value507            resolve = False508        else:509            render_key = context._render_config_key510            render_value = lambda value: value511        return (512            (render_key(key), render_value(value))513            for key, value in context._config.iter_config(resolve)514        )515 516    def to_dict(self, resolve=False):517        """export policy object as dictionary of options.518 519        .. deprecated:: 1.6520            applications should use :meth:`CryptContext.to_dict` instead.521        """522        if self._stub_policy:523            warn(_preamble +524                 "Instead of ``context.policy.to_dict()``, "525                 "use ``context.to_dict()``.",526                 DeprecationWarning, stacklevel=2)527        else:528            warn(_preamble +529                 "Instead of ``CryptPolicy().to_dict()``, "530                 "create a CryptContext instance and "531                 "use ``context.to_dict()``.",532                 DeprecationWarning, stacklevel=2)533        return self._context.to_dict(resolve)534 535    def to_file(self, stream, section="passlib"): # pragma: no cover -- deprecated & unused536        """export policy to file.537 538        .. deprecated:: 1.6539            applications should use :meth:`CryptContext.to_string` instead,540            and then write the output to a file as desired.541        """542        if self._stub_policy:543            warn(_preamble +544                 "Instead of ``context.policy.to_file(stream)``, "545                 "use ``stream.write(context.to_string())``.",546                 DeprecationWarning, stacklevel=2)547        else:548            warn(_preamble +549                 "Instead of ``CryptPolicy().to_file(stream)``, "550                 "create a CryptContext instance and "551                 "use ``stream.write(context.to_string())``.",552                 DeprecationWarning, stacklevel=2)553        out = self._context.to_string(section=section)554        if PY2:555            out = out.encode("utf-8")556        stream.write(out)557 558    def to_string(self, section="passlib", encoding=None):559        """export policy to file.560 561        .. deprecated:: 1.6562            applications should use :meth:`CryptContext.to_string` instead.563        """564        if self._stub_policy:565            warn(_preamble + # pragma: no cover -- deprecated & unused566                 "Instead of ``context.policy.to_string()``, "567                 "use ``context.to_string()``.",568                 DeprecationWarning, stacklevel=2)569        else:570            warn(_preamble +571                 "Instead of ``CryptPolicy().to_string()``, "572                 "create a CryptContext instance and "573                 "use ``context.to_string()``.",574                 DeprecationWarning, stacklevel=2)575        out = self._context.to_string(section=section)576        if encoding:577            out = out.encode(encoding)578        return out579 580    #===================================================================581    # eoc582    #===================================================================583 584#=============================================================================585# _CryptConfig helper class586#=============================================================================587class _CryptConfig(object):588    """parses, validates, and stores CryptContext config589 590    this is a helper used internally by CryptContext to handle591    parsing, validation, and serialization of its config options.592    split out from the main class, but not made public since593    that just complicates interface too much (c.f. CryptPolicy)594 595    :arg source: config as dict mapping ``(cat,scheme,option) -> value``596    """597    #===================================================================598    # instance attrs599    #===================================================================600 601    # triple-nested dict which maps scheme -> category -> key -> value,602    # storing all hash-specific options603    _scheme_options = None604 605    # double-nested dict which maps key -> category -> value606    # storing all CryptContext options607    _context_options = None608 609    # tuple of handler objects610    handlers = None611 612    # tuple of scheme objects in same order as handlers613    schemes = None614 615    # tuple of categories in alphabetical order (not including None)616    categories = None617 618    # set of all context keywords used by active schemes619    context_kwds = None620 621    # dict mapping category -> default scheme622    _default_schemes = None623 624    # dict mapping (scheme, category) -> custom handler625    _records = None626 627    # dict mapping category -> list of custom handler instances for that category,628    # in order of schemes(). populated on demand by _get_record_list()629    _record_lists = None630 631    #===================================================================632    # constructor633    #===================================================================634    def __init__(self, source):635        self._init_scheme_list(source.get((None,None,"schemes")))636        self._init_options(source)637        self._init_default_schemes()638        self._init_records()639 640    def _init_scheme_list(self, data):641        """initialize .handlers and .schemes attributes"""642        handlers  = []643        schemes = []644        if isinstance(data, native_string_types):645            data = splitcomma(data)646        for elem in data or ():647            # resolve elem -> handler & scheme648            if hasattr(elem, "name"):649                handler = elem650                scheme = handler.name651                _validate_handler_name(scheme)652            elif isinstance(elem, native_string_types):653                handler = get_crypt_handler(elem)654                scheme = handler.name655            else:656                raise TypeError("scheme must be name or CryptHandler, "657                                "not %r" % type(elem))658 659            # check scheme name isn't already in use660            if scheme in schemes:661                raise KeyError("multiple handlers with same name: %r" %662                               (scheme,))663 664            # add to handler list665            handlers.append(handler)666            schemes.append(scheme)667 668        self.handlers = tuple(handlers)669        self.schemes = tuple(schemes)670 671    #===================================================================672    # lowlevel options673    #===================================================================674 675    #---------------------------------------------------------------676    # init lowlevel option storage677    #---------------------------------------------------------------678    def _init_options(self, source):679        """load config dict into internal representation,680        and init .categories attr681        """682        # prepare dicts & locals683        norm_scheme_option = self._norm_scheme_option684        norm_context_option = self._norm_context_option685        self._scheme_options = scheme_options = {}686        self._context_options = context_options = {}687        categories = set()688 689        # load source config into internal storage690        for (cat, scheme, key), value in iteritems(source):691            categories.add(cat)692            explicit_scheme = scheme693            if not cat and not scheme and key in _global_settings:694                # going forward, not using "<cat>__all__<key>" format. instead...695                # whitelisting set of keys which should be passed to (all) schemes,696                # rather than passed to the CryptContext itself697                scheme = "all"698            if scheme:699                # normalize scheme option700                key, value = norm_scheme_option(key, value)701 702                # e.g. things like "min_rounds" should never be set cross-scheme703                # this will be fatal under 2.0.704                if scheme == "all" and key not in _global_settings:705                    warn("The '%s' option should be configured per-algorithm, and not set "706                         "globally in the context; This will be an error in Passlib 2.0" %707                         (key,), PasslibConfigWarning)708 709                # this scheme is going away in 2.0;710                # but most keys deserve an extra warning since it impacts security.711                if explicit_scheme == "all":712                    warn("The 'all' scheme is deprecated as of Passlib 1.7, "713                         "and will be removed in Passlib 2.0; Please configure "714                         "options on a per-algorithm basis.", DeprecationWarning)715 716                # store in scheme_options717                # map structure: scheme_options[scheme][category][key] = value718                try:719                    category_map = scheme_options[scheme]720                except KeyError:721                    scheme_options[scheme] = {cat: {key: value}}722                else:723                    try:724                        option_map = category_map[cat]725                    except KeyError:726                        category_map[cat] = {key: value}727                    else:728                        option_map[key] = value729            else:730                # normalize context option731                if cat and key == "schemes":732                    raise KeyError("'schemes' context option is not allowed "733                                   "per category")734                key, value = norm_context_option(cat, key, value)735                if key == "min_verify_time": # ignored in 1.7, to be removed in 1.8736                    continue737 738                # store in context_options739                # map structure: context_options[key][category] = value740                try:741                    category_map = context_options[key]742                except KeyError:743                    context_options[key] = {cat: value}744                else:745                    category_map[cat] = value746 747        # store list of configured categories748        categories.discard(None)749        self.categories = tuple(sorted(categories))750 751    def _norm_scheme_option(self, key, value):752        # check for invalid options753        if key in _forbidden_scheme_options:754            raise KeyError("%r option not allowed in CryptContext "755                           "configuration" % (key,))756        # coerce strings for certain fields (e.g. min_rounds uses ints)757        if isinstance(value, native_string_types):758            func = _coerce_scheme_options.get(key)759            if func:760                value = func(value)761        return key, value762 763    def _norm_context_option(self, cat, key, value):764        schemes = self.schemes765        if key == "default":766            if hasattr(value, "name"):767                value = value.name768            elif not isinstance(value, native_string_types):769                raise ExpectedTypeError(value, "str", "default")770            if schemes and value not in schemes:771                raise KeyError("default scheme not found in policy")772        elif key == "deprecated":773            if isinstance(value, native_string_types):774                value = splitcomma(value)775            elif not isinstance(value, (list,tuple)):776                raise ExpectedTypeError(value, "str or seq", "deprecated")777            if 'auto' in value:778                # XXX: have any statements been made about when this is default?779                #      should do it in 1.8 at latest.780                if len(value) > 1:781                    raise ValueError("cannot list other schemes if "782                                     "``deprecated=['auto']`` is used")783            elif schemes:784                # make sure list of deprecated schemes is subset of configured schemes785                for scheme in value:786                    if not isinstance(scheme, native_string_types):787                        raise ExpectedTypeError(value, "str", "deprecated element")788                    if scheme not in schemes:789                        raise KeyError("deprecated scheme not found "790                                   "in policy: %r" % (scheme,))791        elif key == "min_verify_time":792            warn("'min_verify_time' was deprecated in Passlib 1.6, is "793                 "ignored in 1.7, and will be removed in 1.8",794                 DeprecationWarning)795        elif key == "harden_verify":796            warn("'harden_verify' is deprecated & ignored as of Passlib 1.7.1, "797                 " and will be removed in 1.8",798                 DeprecationWarning)799        elif key != "schemes":800            raise KeyError("unknown CryptContext keyword: %r" % (key,))801        return key, value802 803    #---------------------------------------------------------------804    # reading context options805    #---------------------------------------------------------------806    def get_context_optionmap(self, key, _default={}):807        """return dict mapping category->value for specific context option.808 809        .. warning:: treat return value as readonly!810        """811        return self._context_options.get(key, _default)812 813    def get_context_option_with_flag(self, category, key):814        """return value of specific option, handling category inheritance.815        also returns flag indicating whether value is category-specific.816        """817        try:818            category_map = self._context_options[key]819        except KeyError:820            return None, False821        value = category_map.get(None)822        if category:823            try:824                alt = category_map[category]825            except KeyError:826                pass827            else:828                if value is None or alt != value:829                    return alt, True830        return value, False831 832    #---------------------------------------------------------------833    # reading scheme options834    #---------------------------------------------------------------835    def _get_scheme_optionmap(self, scheme, category, default={}):836        """return all options for (scheme,category) combination837 838        .. warning:: treat return value as readonly!839        """840        try:841            return self._scheme_options[scheme][category]842        except KeyError:843            return default844 845    def get_base_handler(self, scheme):846        return self.handlers[self.schemes.index(scheme)]847 848    @staticmethod849    def expand_settings(handler):850        setting_kwds = handler.setting_kwds851        if 'rounds' in handler.setting_kwds:852            # XXX: historically this extras won't be listed in setting_kwds853            setting_kwds += uh.HasRounds.using_rounds_kwds854        return setting_kwds855 856    # NOTE: this is only used by _get_record_options_with_flag()...857    def get_scheme_options_with_flag(self, scheme, category):858        """return composite dict of all options set for scheme.859        includes options inherited from 'all' and from default category.860        result can be modified.861        returns (kwds, has_cat_specific_options)862        """863        # start out with copy of global options864        get_optionmap = self._get_scheme_optionmap865        kwds = get_optionmap("all", None).copy()866        has_cat_options = False867 868        # add in category-specific global options869        if category:870            defkwds = kwds.copy() # <-- used to detect category-specific options871            kwds.update(get_optionmap("all", category))872 873        # filter out global settings not supported by handler874        allowed_settings = self.expand_settings(self.get_base_handler(scheme))875        for key in set(kwds).difference(allowed_settings):876            kwds.pop(key)877        if category:878            for key in set(defkwds).difference(allowed_settings):879                defkwds.pop(key)880 881        # add in default options for scheme882        other = get_optionmap(scheme, None)883        kwds.update(other)884 885        # load category-specific options for scheme886        if category:887            defkwds.update(other)888            kwds.update(get_optionmap(scheme, category))889 890            # compare default category options to see if there's anything891            # category-specific892            if kwds != defkwds:893                has_cat_options = True894 895        return kwds, has_cat_options896 897    #===================================================================898    # deprecated & default schemes899    #===================================================================900    def _init_default_schemes(self):901        """initialize maps containing default scheme for each category.902 903        have to do this after _init_options(), since the default scheme904        is affected by the list of deprecated schemes.905        """906        # init maps & locals907        get_optionmap = self.get_context_optionmap908        default_map = self._default_schemes = get_optionmap("default").copy()909        dep_map = get_optionmap("deprecated")910        schemes = self.schemes911        if not schemes:912            return913 914        # figure out default scheme915        deps = dep_map.get(None) or ()916        default = default_map.get(None)917        if not default:918            for scheme in schemes:919                if scheme not in deps:920                    default_map[None] = scheme921                    break922            else:923                raise ValueError("must have at least one non-deprecated scheme")924        elif default in deps:925            raise ValueError("default scheme cannot be deprecated")926 927        # figure out per-category default schemes,928        for cat in self.categories:929            cdeps = dep_map.get(cat, deps)930            cdefault = default_map.get(cat, default)931            if not cdefault:932                for scheme in schemes:933                    if scheme not in cdeps:934                        default_map[cat] = scheme935                        break936                else:937                    raise ValueError("must have at least one non-deprecated "938                                     "scheme for %r category" % cat)939            elif cdefault in cdeps:940                raise ValueError("default scheme for %r category "941                                 "cannot be deprecated" % cat)942 943    def default_scheme(self, category):944        """return default scheme for specific category"""945        defaults = self._default_schemes946        try:947            return defaults[category]948        except KeyError:949            pass950        if not self.schemes:951            raise KeyError("no hash schemes configured for this "952                           "CryptContext instance")953        return defaults[None]954 955    def is_deprecated_with_flag(self, scheme, category):956        """is scheme deprecated under particular category?"""957        depmap = self.get_context_optionmap("deprecated")958        def test(cat):959            source = depmap.get(cat, depmap.get(None))960            if source is None:961                return None962            elif 'auto' in source:963                return scheme != self.default_scheme(cat)964            else:965                return scheme in source966        value = test(None) or False967        if category:968            alt = test(category)969            if alt is not None and value != alt:970                return alt, True971        return value, False972 973    #===================================================================974    # CryptRecord objects975    #===================================================================976    def _init_records(self):977        # NOTE: this step handles final validation of settings,978        #       checking for violations against handler's internal invariants.979        #       this is why we create all the records now,980        #       so CryptContext throws error immediately rather than later.981        self._record_lists = {}982        records = self._records = {}983        all_context_kwds = self.context_kwds = set()984        get_options = self._get_record_options_with_flag985        categories = (None,) + self.categories986        for handler in self.handlers:987            scheme = handler.name988            all_context_kwds.update(handler.context_kwds)989            for cat in categories:990                kwds, has_cat_options = get_options(scheme, cat)991                if cat is None or has_cat_options:992                    records[scheme, cat] = self._create_record(handler, cat, **kwds)993                # NOTE: if handler has no category-specific opts, get_record()994                # will automatically use the default category's record.995        # NOTE: default records for specific category stored under the996        # key (None,category); these are populated on-demand by get_record().997 998    @staticmethod999    def _create_record(handler, category=None, deprecated=False, **settings):1000        # create custom handler if needed.1001        try:1002            # XXX: relaxed=True is mostly here to retain backwards-compat behavior.1003            #      could make this optional flag in future.1004            subcls = handler.using(relaxed=True, **settings)1005        except TypeError as err:1006            m = re.match(r".* unexpected keyword argument '(.*)'$", str(err))1007            if m and m.group(1) in settings:1008                # translate into KeyError, for backwards compat.1009                # XXX: push this down to GenericHandler.using() implementation?1010                key = m.group(1)1011                raise KeyError("keyword not supported by %s handler: %r" %1012                               (handler.name, key))1013            raise1014 1015        # using private attrs to store some extra metadata in custom handler1016        assert subcls is not handler, "expected unique variant of handler"1017        ##subcls._Context__category = category1018        subcls._Context__orig_handler = handler1019        subcls.deprecated = deprecated  # attr reserved for this purpose1020        return subcls1021 1022    def _get_record_options_with_flag(self, scheme, category):1023        """return composite dict of options for given scheme + category.1024 1025        this is currently a private method, though some variant1026        of its output may eventually be made public.1027 1028        given a scheme & category, it returns two things:1029        a set of all the keyword options to pass to :meth:`_create_record`,1030        and a bool flag indicating whether any of these options1031        were specific to the named category. if this flag is false,1032        the options are identical to the options for the default category.1033 1034        the options dict includes all the scheme-specific settings,1035        as well as optional *deprecated* keyword.1036        """1037        # get scheme options1038        kwds, has_cat_options = self.get_scheme_options_with_flag(scheme, category)1039 1040        # throw in deprecated flag1041        value, not_inherited = self.is_deprecated_with_flag(scheme, category)1042        if value:1043            kwds['deprecated'] = True1044        if not_inherited:1045            has_cat_options = True1046 1047        return kwds, has_cat_options1048 1049    def get_record(self, scheme, category):1050        """return record for specific scheme & category (cached)"""1051        # NOTE: this is part of the critical path shared by1052        #       all of CryptContext's PasswordHash methods,1053        #       hence all the caching and error checking.1054 1055        # quick lookup in cache1056        try:1057            return self._records[scheme, category]1058        except KeyError:1059            pass1060 1061        # type check1062        if category is not None and not isinstance(category, native_string_types):1063            if PY2 and isinstance(category, unicode):1064                # for compatibility with unicode-centric py2 apps1065                return self.get_record(scheme, category.encode("utf-8"))1066            raise ExpectedTypeError(category, "str or None", "category")1067        if scheme is not None and not isinstance(scheme, native_string_types):1068            raise ExpectedTypeError(scheme, "str or None", "scheme")1069 1070        # if scheme=None,1071        # use record for category's default scheme, and cache result.1072        if not scheme:1073            default = self.default_scheme(category)1074            assert default1075            record = self._records[None, category] = self.get_record(default,1076                                                                      category)1077            return record1078 1079        # if no record for (scheme, category),1080        # use record for (scheme, None), and cache result.1081        if category:1082            try:1083                cache = self._records1084                record = cache[scheme, category] = cache[scheme, None]1085                return record1086            except KeyError:1087                pass1088 1089        # scheme not found in configuration for default category1090        raise KeyError("crypt algorithm not found in policy: %r" % (scheme,))1091 1092    def _get_record_list(self, category=None):1093        """return list of records for category (cached)1094 1095        this is an internal helper used only by identify_record()1096        """1097        # type check of category - handled by _get_record()1098        # quick lookup in cache1099        try:1100            return self._record_lists[category]1101        except KeyError:1102            pass1103        # cache miss - build list from scratch1104        value = self._record_lists[category] = [1105            self.get_record(scheme, category)1106            for scheme in self.schemes1107            ]1108        return value1109 1110    def identify_record(self, hash, category, required=True):1111        """internal helper to identify appropriate custom handler for hash"""1112        # NOTE: this is part of the critical path shared by1113        #       all of CryptContext's PasswordHash methods,1114        #       hence all the caching and error checking.1115        # FIXME: if multiple hashes could match (e.g. lmhash vs nthash)1116        #        this will only return first match. might want to do something1117        #        about this in future, but for now only hashes with1118        #        unique identifiers will work properly in a CryptContext.1119        # XXX: if all handlers have a unique prefix (e.g. all are MCF / LDAP),1120        #      could use dict-lookup to speed up this search.1121        if not isinstance(hash, unicode_or_bytes_types):1122            raise ExpectedStringError(hash, "hash")1123        # type check of category - handled by _get_record_list()1124        for record in self._get_record_list(category):1125            if record.identify(hash):1126                return record1127        if not required:1128            return None1129        elif not self.schemes:1130            raise KeyError("no crypt algorithms supported")1131        else:1132            raise exc.UnknownHashError("hash could not be identified")1133 1134    @memoized_property1135    def disabled_record(self):1136        for record in self._get_record_list(None):1137            if record.is_disabled:1138                return record1139        raise RuntimeError("no disabled hasher present "1140                           "(perhaps add 'unix_disabled' to list of schemes?)")1141 1142    #===================================================================1143    # serialization1144    #===================================================================1145    def iter_config(self, resolve=False):1146        """regenerate original config.1147 1148        this is an iterator which yields ``(cat,scheme,option),value`` items,1149        in the order they generally appear inside an INI file.1150        if interpreted as a dictionary, it should match the original1151        keywords passed to the CryptContext (aside from any canonization).1152 1153        it's mainly used as the internal backend for most of the public1154        serialization methods.1155        """1156        # grab various bits of data1157        scheme_options = self._scheme_options1158        context_options = self._context_options1159        scheme_keys = sorted(scheme_options)1160        context_keys = sorted(context_options)1161 1162        # write loaded schemes (may differ from 'schemes' local var)1163        if 'schemes' in context_keys:1164            context_keys.remove("schemes")1165        value = self.handlers if resolve else self.schemes1166        if value:1167            yield (None, None, "schemes"), list(value)1168 1169        # then run through config for each user category1170        for cat in (None,) + self.categories:1171 1172            # write context options1173            for key in context_keys:1174                try:1175                    value = context_options[key][cat]1176                except KeyError:1177                    pass1178                else:1179                    if isinstance(value, list):1180                        value = list(value)1181                    yield (cat, None, key), value1182 1183            # write per-scheme options for all schemes.1184            for scheme in scheme_keys:1185                try:1186                    kwds = scheme_options[scheme][cat]1187                except KeyError:1188                    pass1189                else:1190                    for key in sorted(kwds):1191                        yield (cat, scheme, key), kwds[key]1192 1193    #===================================================================1194    # eoc1195    #===================================================================1196 1197#=============================================================================1198# main CryptContext class1199#=============================================================================1200class CryptContext(object):

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

codekingpro/portable-devtools · Team Ai