You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
calculate-utils-3-console-gui/libs_crutch/contrib/passlib/ifc.py

354 lines
14 KiB

"""passlib.ifc - abstract interfaces used by Passlib"""
#=============================================================================
# imports
#=============================================================================
# core
import logging; log = logging.getLogger(__name__)
import sys
# site
# pkg
from passlib.utils.decor import deprecated_method
# local
__all__ = [
"PasswordHash",
]
#=============================================================================
# 2/3 compatibility helpers
#=============================================================================
def recreate_with_metaclass(meta):
"""class decorator that re-creates class using metaclass"""
def builder(cls):
if meta is type(cls):
return cls
return meta(cls.__name__, cls.__bases__, cls.__dict__.copy())
return builder
#=============================================================================
# PasswordHash interface
#=============================================================================
from abc import ABCMeta, abstractmethod, abstractproperty
# TODO: make this actually use abstractproperty(),
# now that we dropped py25, 'abc' is always available.
# XXX: rename to PasswordHasher?
@recreate_with_metaclass(ABCMeta)
class PasswordHash(object):
"""This class describes an abstract interface which all password hashes
in Passlib adhere to. Under Python 2.6 and up, this is an actual
Abstract Base Class built using the :mod:`!abc` module.
See the Passlib docs for full documentation.
"""
#===================================================================
# class attributes
#===================================================================
#---------------------------------------------------------------
# general information
#---------------------------------------------------------------
##name
##setting_kwds
##context_kwds
#: flag which indicates this hasher matches a "disabled" hash
#: (e.g. unix_disabled, or django_disabled); and doesn't actually
#: depend on the provided password.
is_disabled = False
#: Should be None, or a positive integer indicating hash
#: doesn't support secrets larger than this value.
#: Whether hash throws error or silently truncates secret
#: depends on .truncate_error and .truncate_verify_reject flags below.
#: NOTE: calls may treat as boolean, since value will never be 0.
#: .. versionadded:: 1.7
#: .. TODO: passlib 1.8: deprecate/rename this attr to "max_secret_size"?
truncate_size = None
# NOTE: these next two default to the optimistic "ideal",
# most hashes in passlib have to default to False
# for backward compat and/or expected behavior with existing hashes.
#: If True, .hash() should throw a :exc:`~passlib.exc.PasswordSizeError` for
#: any secrets larger than .truncate_size. Many hashers default to False
#: for historical / compatibility purposes, indicating they will silently
#: truncate instead. All such hashers SHOULD support changing
#: the policy via ``.using(truncate_error=True)``.
#: .. versionadded:: 1.7
#: .. TODO: passlib 1.8: deprecate/rename this attr to "truncate_hash_error"?
truncate_error = True
#: If True, .verify() should reject secrets larger than max_password_size.
#: Many hashers default to False for historical / compatibility purposes,
#: indicating they will match on the truncated portion instead.
#: .. versionadded:: 1.7.1
truncate_verify_reject = True
#---------------------------------------------------------------
# salt information -- if 'salt' in setting_kwds
#---------------------------------------------------------------
##min_salt_size
##max_salt_size
##default_salt_size
##salt_chars
##default_salt_chars
#---------------------------------------------------------------
# rounds information -- if 'rounds' in setting_kwds
#---------------------------------------------------------------
##min_rounds
##max_rounds
##default_rounds
##rounds_cost
#---------------------------------------------------------------
# encoding info -- if 'encoding' in context_kwds
#---------------------------------------------------------------
##default_encoding
#===================================================================
# primary methods
#===================================================================
@classmethod
@abstractmethod
def hash(cls, secret, # *
**setting_and_context_kwds): # pragma: no cover -- abstract method
r"""
Hash secret, returning result.
Should handle generating salt, etc, and should return string
containing identifier, salt & other configuration, as well as digest.
:param \*\*settings_kwds:
Pass in settings to customize configuration of resulting hash.
.. deprecated:: 1.7
Starting with Passlib 1.7, callers should no longer pass settings keywords
(e.g. ``rounds`` or ``salt`` directly to :meth:`!hash`); should use
``.using(**settings).hash(secret)`` construction instead.
Support will be removed in Passlib 2.0.
:param \*\*context_kwds:
Specific algorithms may require context-specific information (such as the user login).
"""
# FIXME: need stub for classes that define .encrypt() instead ...
# this should call .encrypt(), and check for recursion back to here.
raise NotImplementedError("must be implemented by subclass")
@deprecated_method(deprecated="1.7", removed="2.0", replacement=".hash()")
@classmethod
def encrypt(cls, *args, **kwds):
"""
Legacy alias for :meth:`hash`.
.. deprecated:: 1.7
This method was renamed to :meth:`!hash` in version 1.7.
This alias will be removed in version 2.0, and should only
be used for compatibility with Passlib 1.3 - 1.6.
"""
return cls.hash(*args, **kwds)
# XXX: could provide default implementation which hands value to
# hash(), and then does constant-time comparision on the result
# (after making both are same string type)
@classmethod
@abstractmethod
def verify(cls, secret, hash, **context_kwds): # pragma: no cover -- abstract method
"""verify secret against hash, returns True/False"""
raise NotImplementedError("must be implemented by subclass")
#===================================================================
# configuration
#===================================================================
@classmethod
@abstractmethod
def using(cls, relaxed=False, **kwds):
"""
Return another hasher object (typically a subclass of the current one),
which integrates the configuration options specified by ``kwds``.
This should *always* return a new object, even if no configuration options are changed.
.. todo::
document which options are accepted.
:returns:
typically returns a subclass for most hasher implementations.
.. todo::
add this method to main documentation.
"""
raise NotImplementedError("must be implemented by subclass")
#===================================================================
# migration
#===================================================================
@classmethod
def needs_update(cls, hash, secret=None):
"""
check if hash's configuration is outside desired bounds,
or contains some other internal option which requires
updating the password hash.
:param hash:
hash string to examine
:param secret:
optional secret known to have verified against the provided hash.
(this is used by some hashes to detect legacy algorithm mistakes).
:return:
whether secret needs re-hashing.
.. versionadded:: 1.7
"""
# by default, always report that we don't need update
return False
#===================================================================
# additional methods
#===================================================================
@classmethod
@abstractmethod
def identify(cls, hash): # pragma: no cover -- abstract method
"""check if hash belongs to this scheme, returns True/False"""
raise NotImplementedError("must be implemented by subclass")
@deprecated_method(deprecated="1.7", removed="2.0")
@classmethod
def genconfig(cls, **setting_kwds): # pragma: no cover -- abstract method
"""
compile settings into a configuration string for genhash()
.. deprecated:: 1.7
As of 1.7, this method is deprecated, and slated for complete removal in Passlib 2.0.
For all known real-world uses, hashing a constant string
should provide equivalent functionality.
This deprecation may be reversed if a use-case presents itself in the mean time.
"""
# NOTE: this fallback runs full hash alg, w/ whatever cost param is passed along.
# implementations (esp ones w/ variable cost) will want to subclass this
# with a constant-time implementation that just renders a config string.
if cls.context_kwds:
raise NotImplementedError("must be implemented by subclass")
return cls.using(**setting_kwds).hash("")
@deprecated_method(deprecated="1.7", removed="2.0")
@classmethod
def genhash(cls, secret, config, **context):
"""
generated hash for secret, using settings from config/hash string
.. deprecated:: 1.7
As of 1.7, this method is deprecated, and slated for complete removal in Passlib 2.0.
This deprecation may be reversed if a use-case presents itself in the mean time.
"""
# XXX: if hashes reliably offered a .parse() method, could make a fallback for this.
raise NotImplementedError("must be implemented by subclass")
#===================================================================
# undocumented methods / attributes
#===================================================================
# the following entry points are used internally by passlib,
# and aren't documented as part of the exposed interface.
# they are subject to change between releases,
# but are documented here so there's a list of them *somewhere*.
#---------------------------------------------------------------
# extra metdata
#---------------------------------------------------------------
#: this attribute shouldn't be used by hashers themselves,
#: it's reserved for the CryptContext to track which hashers are deprecated.
#: Note the context will only set this on objects it owns (and generated by .using()),
#: and WONT set it on global objects.
#: [added in 1.7]
#: TODO: document this, or at least the use of testing for
#: 'CryptContext().handler().deprecated'
deprecated = False
#: optionally present if hasher corresponds to format built into Django.
#: this attribute (if not None) should be the Django 'algorithm' name.
#: also indicates to passlib.ext.django that (when installed in django),
#: django's native hasher should be used in preference to this one.
## django_name
#---------------------------------------------------------------
# checksum information - defined for many hashes
#---------------------------------------------------------------
## checksum_chars
## checksum_size
#---------------------------------------------------------------
# experimental methods
#---------------------------------------------------------------
##@classmethod
##def normhash(cls, hash):
## """helper to clean up non-canonic instances of hash.
## currently only provided by bcrypt() to fix an historical passlib issue.
## """
# experimental helper to parse hash into components.
##@classmethod
##def parsehash(cls, hash, checksum=True, sanitize=False):
## """helper to parse hash into components, returns dict"""
# experiment helper to estimate bitsize of different hashes,
# implement for GenericHandler, but may be currently be off for some hashes.
# want to expand this into a way to programmatically compare
# "strengths" of different hashes and hash algorithms.
# still needs to have some factor for estimate relative cost per round,
# ala in the style of the scrypt whitepaper.
##@classmethod
##def bitsize(cls, **kwds):
## """returns dict mapping component -> bits contributed.
## components currently include checksum, salt, rounds.
## """
#===================================================================
# eoc
#===================================================================
class DisabledHash(PasswordHash):
"""
extended disabled-hash methods; only need be present if .disabled = True
"""
is_disabled = True
@classmethod
def disable(cls, hash=None):
"""
return string representing a 'disabled' hash;
optionally including previously enabled hash
(this is up to the individual scheme).
"""
# default behavior: ignore original hash, return standalone marker
return cls.hash("")
@classmethod
def enable(cls, hash):
"""
given a disabled-hash string,
extract previously-enabled hash if one is present,
otherwise raises ValueError
"""
# default behavior: no way to restore original hash
raise ValueError("cannot restore original hash")
#=============================================================================
# eof
#=============================================================================