codekingpro/portable-devtools
114k
1"""passlib.ifc - abstract interfaces used by Passlib"""2#=============================================================================3# imports4#=============================================================================5# core6import logging; log = logging.getLogger(__name__)7import sys8# site9# pkg10from passlib.utils.decor import deprecated_method11# local12__all__ = [13 "PasswordHash",14]15 16#=============================================================================17# 2/3 compatibility helpers18#=============================================================================19def recreate_with_metaclass(meta):20 """class decorator that re-creates class using metaclass"""21 def builder(cls):22 if meta is type(cls):23 return cls24 return meta(cls.__name__, cls.__bases__, cls.__dict__.copy())25 return builder26 27#=============================================================================28# PasswordHash interface29#=============================================================================30from abc import ABCMeta, abstractmethod, abstractproperty31 32# TODO: make this actually use abstractproperty(),33# now that we dropped py25, 'abc' is always available.34 35# XXX: rename to PasswordHasher?36 37@recreate_with_metaclass(ABCMeta)38class PasswordHash(object):39 """This class describes an abstract interface which all password hashes40 in Passlib adhere to. Under Python 2.6 and up, this is an actual41 Abstract Base Class built using the :mod:`!abc` module.42 43 See the Passlib docs for full documentation.44 """45 #===================================================================46 # class attributes47 #===================================================================48 49 #---------------------------------------------------------------50 # general information51 #---------------------------------------------------------------52 ##name53 ##setting_kwds54 ##context_kwds55 56 #: flag which indicates this hasher matches a "disabled" hash57 #: (e.g. unix_disabled, or django_disabled); and doesn't actually58 #: depend on the provided password.59 is_disabled = False60 61 #: Should be None, or a positive integer indicating hash62 #: doesn't support secrets larger than this value.63 #: Whether hash throws error or silently truncates secret64 #: depends on .truncate_error and .truncate_verify_reject flags below.65 #: NOTE: calls may treat as boolean, since value will never be 0.66 #: .. versionadded:: 1.767 #: .. TODO: passlib 1.8: deprecate/rename this attr to "max_secret_size"?68 truncate_size = None69 70 # NOTE: these next two default to the optimistic "ideal",71 # most hashes in passlib have to default to False72 # for backward compat and/or expected behavior with existing hashes.73 74 #: If True, .hash() should throw a :exc:`~passlib.exc.PasswordSizeError` for75 #: any secrets larger than .truncate_size. Many hashers default to False76 #: for historical / compatibility purposes, indicating they will silently77 #: truncate instead. All such hashers SHOULD support changing78 #: the policy via ``.using(truncate_error=True)``.79 #: .. versionadded:: 1.780 #: .. TODO: passlib 1.8: deprecate/rename this attr to "truncate_hash_error"?81 truncate_error = True82 83 #: If True, .verify() should reject secrets larger than max_password_size.84 #: Many hashers default to False for historical / compatibility purposes,85 #: indicating they will match on the truncated portion instead.86 #: .. versionadded:: 1.7.187 truncate_verify_reject = True88 89 #---------------------------------------------------------------90 # salt information -- if 'salt' in setting_kwds91 #---------------------------------------------------------------92 ##min_salt_size93 ##max_salt_size94 ##default_salt_size95 ##salt_chars96 ##default_salt_chars97 98 #---------------------------------------------------------------99 # rounds information -- if 'rounds' in setting_kwds100 #---------------------------------------------------------------101 ##min_rounds102 ##max_rounds103 ##default_rounds104 ##rounds_cost105 106 #---------------------------------------------------------------107 # encoding info -- if 'encoding' in context_kwds108 #---------------------------------------------------------------109 ##default_encoding110 111 #===================================================================112 # primary methods113 #===================================================================114 @classmethod115 @abstractmethod116 def hash(cls, secret, # *117 **setting_and_context_kwds): # pragma: no cover -- abstract method118 r"""119 Hash secret, returning result.120 Should handle generating salt, etc, and should return string121 containing identifier, salt & other configuration, as well as digest.122 123 :param \\*\\*settings_kwds:124 125 Pass in settings to customize configuration of resulting hash.126 127 .. deprecated:: 1.7128 129 Starting with Passlib 1.7, callers should no longer pass settings keywords130 (e.g. ``rounds`` or ``salt`` directly to :meth:`!hash`); should use131 ``.using(**settings).hash(secret)`` construction instead.132 133 Support will be removed in Passlib 2.0.134 135 :param \\*\\*context_kwds:136 137 Specific algorithms may require context-specific information (such as the user login).138 """139 # FIXME: need stub for classes that define .encrypt() instead ...140 # this should call .encrypt(), and check for recursion back to here.141 raise NotImplementedError("must be implemented by subclass")142 143 @deprecated_method(deprecated="1.7", removed="2.0", replacement=".hash()")144 @classmethod145 def encrypt(cls, *args, **kwds):146 """147 Legacy alias for :meth:`hash`.148 149 .. deprecated:: 1.7150 This method was renamed to :meth:`!hash` in version 1.7.151 This alias will be removed in version 2.0, and should only152 be used for compatibility with Passlib 1.3 - 1.6.153 """154 return cls.hash(*args, **kwds)155 156 # XXX: could provide default implementation which hands value to157 # hash(), and then does constant-time comparision on the result158 # (after making both are same string type)159 @classmethod160 @abstractmethod161 def verify(cls, secret, hash, **context_kwds): # pragma: no cover -- abstract method162 """verify secret against hash, returns True/False"""163 raise NotImplementedError("must be implemented by subclass")164 165 #===================================================================166 # configuration167 #===================================================================168 @classmethod169 @abstractmethod170 def using(cls, relaxed=False, **kwds):171 """172 Return another hasher object (typically a subclass of the current one),173 which integrates the configuration options specified by ``kwds``.174 This should *always* return a new object, even if no configuration options are changed.175 176 .. todo::177 178 document which options are accepted.179 180 :returns:181 typically returns a subclass for most hasher implementations.182 183 .. todo::184 185 add this method to main documentation.186 """187 raise NotImplementedError("must be implemented by subclass")188 189 #===================================================================190 # migration191 #===================================================================192 @classmethod193 def needs_update(cls, hash, secret=None):194 """195 check if hash's configuration is outside desired bounds,196 or contains some other internal option which requires197 updating the password hash.198 199 :param hash:200 hash string to examine201 202 :param secret:203 optional secret known to have verified against the provided hash.204 (this is used by some hashes to detect legacy algorithm mistakes).205 206 :return:207 whether secret needs re-hashing.208 209 .. versionadded:: 1.7210 """211 # by default, always report that we don't need update212 return False213 214 #===================================================================215 # additional methods216 #===================================================================217 @classmethod218 @abstractmethod219 def identify(cls, hash): # pragma: no cover -- abstract method220 """check if hash belongs to this scheme, returns True/False"""221 raise NotImplementedError("must be implemented by subclass")222 223 @deprecated_method(deprecated="1.7", removed="2.0")224 @classmethod225 def genconfig(cls, **setting_kwds): # pragma: no cover -- abstract method226 """227 compile settings into a configuration string for genhash()228 229 .. deprecated:: 1.7230 231 As of 1.7, this method is deprecated, and slated for complete removal in Passlib 2.0.232 233 For all known real-world uses, hashing a constant string234 should provide equivalent functionality.235 236 This deprecation may be reversed if a use-case presents itself in the mean time.237 """238 # NOTE: this fallback runs full hash alg, w/ whatever cost param is passed along.239 # implementations (esp ones w/ variable cost) will want to subclass this240 # with a constant-time implementation that just renders a config string.241 if cls.context_kwds:242 raise NotImplementedError("must be implemented by subclass")243 return cls.using(**setting_kwds).hash("")244 245 @deprecated_method(deprecated="1.7", removed="2.0")246 @classmethod247 def genhash(cls, secret, config, **context):248 """249 generated hash for secret, using settings from config/hash string250 251 .. deprecated:: 1.7252 253 As of 1.7, this method is deprecated, and slated for complete removal in Passlib 2.0.254 255 This deprecation may be reversed if a use-case presents itself in the mean time.256 """257 # XXX: if hashes reliably offered a .parse() method, could make a fallback for this.258 raise NotImplementedError("must be implemented by subclass")259 260 #===================================================================261 # undocumented methods / attributes262 #===================================================================263 # the following entry points are used internally by passlib,264 # and aren't documented as part of the exposed interface.265 # they are subject to change between releases,266 # but are documented here so there's a list of them *somewhere*.267 268 #---------------------------------------------------------------269 # extra metdata270 #---------------------------------------------------------------271 272 #: this attribute shouldn't be used by hashers themselves,273 #: it's reserved for the CryptContext to track which hashers are deprecated.274 #: Note the context will only set this on objects it owns (and generated by .using()),275 #: and WONT set it on global objects.276 #: [added in 1.7]277 #: TODO: document this, or at least the use of testing for278 #: 'CryptContext().handler().deprecated'279 deprecated = False280 281 #: optionally present if hasher corresponds to format built into Django.282 #: this attribute (if not None) should be the Django 'algorithm' name.283 #: also indicates to passlib.ext.django that (when installed in django),284 #: django's native hasher should be used in preference to this one.285 ## django_name286 287 #---------------------------------------------------------------288 # checksum information - defined for many hashes289 #---------------------------------------------------------------290 ## checksum_chars291 ## checksum_size292 293 #---------------------------------------------------------------294 # experimental methods295 #---------------------------------------------------------------296 297 ##@classmethod298 ##def normhash(cls, hash):299 ## """helper to clean up non-canonic instances of hash.300 ## currently only provided by bcrypt() to fix an historical passlib issue.301 ## """302 303 # experimental helper to parse hash into components.304 ##@classmethod305 ##def parsehash(cls, hash, checksum=True, sanitize=False):306 ## """helper to parse hash into components, returns dict"""307 308 # experiment helper to estimate bitsize of different hashes,309 # implement for GenericHandler, but may be currently be off for some hashes.310 # want to expand this into a way to programmatically compare311 # "strengths" of different hashes and hash algorithms.312 # still needs to have some factor for estimate relative cost per round,313 # ala in the style of the scrypt whitepaper.314 ##@classmethod315 ##def bitsize(cls, **kwds):316 ## """returns dict mapping component -> bits contributed.317 ## components currently include checksum, salt, rounds.318 ## """319 320 #===================================================================321 # eoc322 #===================================================================323 324class DisabledHash(PasswordHash):325 """326 extended disabled-hash methods; only need be present if .disabled = True327 """328 329 is_disabled = True330 331 @classmethod332 def disable(cls, hash=None):333 """334 return string representing a 'disabled' hash;335 optionally including previously enabled hash336 (this is up to the individual scheme).337 """338 # default behavior: ignore original hash, return standalone marker339 return cls.hash("")340 341 @classmethod342 def enable(cls, hash):343 """344 given a disabled-hash string,345 extract previously-enabled hash if one is present,346 otherwise raises ValueError347 """348 # default behavior: no way to restore original hash349 raise ValueError("cannot restore original hash")350 351#=============================================================================352# eof353#=============================================================================354 