Загрузить файлы в «venv/Lib/site-packages/passlib»
This commit is contained in:
68
venv/Lib/site-packages/passlib/hash.py
Normal file
68
venv/Lib/site-packages/passlib/hash.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
passlib.hash - proxy object mapping hash scheme names -> handlers
|
||||
|
||||
==================
|
||||
***** NOTICE *****
|
||||
==================
|
||||
|
||||
This module does not actually contain any hashes. This file
|
||||
is a stub that replaces itself with a proxy object.
|
||||
|
||||
This proxy object (passlib.registry._PasslibRegistryProxy)
|
||||
handles lazy-loading hashes as they are requested.
|
||||
|
||||
The actual implementation of the various hashes is store elsewhere,
|
||||
mainly in the submodules of the ``passlib.handlers`` subpackage.
|
||||
"""
|
||||
|
||||
#=============================================================================
|
||||
# import proxy object and replace this module
|
||||
#=============================================================================
|
||||
|
||||
# XXX: if any platform has problem w/ lazy modules, could support 'non-lazy'
|
||||
# version which just imports all schemes known to list_crypt_handlers()
|
||||
|
||||
from passlib.registry import _proxy
|
||||
import sys
|
||||
sys.modules[__name__] = _proxy
|
||||
|
||||
#=============================================================================
|
||||
# HACK: the following bit of code is unreachable, but it's presence seems to
|
||||
# help make autocomplete work for certain IDEs such as PyCharm.
|
||||
# this list is automatically regenerated using $SOURCE/admin/regen.py
|
||||
#=============================================================================
|
||||
|
||||
#----------------------------------------------------
|
||||
# begin autocomplete hack (autogenerated 2016-11-10)
|
||||
#----------------------------------------------------
|
||||
if False:
|
||||
from passlib.handlers.argon2 import argon2
|
||||
from passlib.handlers.bcrypt import bcrypt, bcrypt_sha256
|
||||
from passlib.handlers.cisco import cisco_asa, cisco_pix, cisco_type7
|
||||
from passlib.handlers.des_crypt import bigcrypt, bsdi_crypt, crypt16, des_crypt
|
||||
from passlib.handlers.digests import hex_md4, hex_md5, hex_sha1, hex_sha256, hex_sha512, htdigest
|
||||
from passlib.handlers.django import django_bcrypt, django_bcrypt_sha256, django_des_crypt, django_disabled, django_pbkdf2_sha1, django_pbkdf2_sha256, django_salted_md5, django_salted_sha1
|
||||
from passlib.handlers.fshp import fshp
|
||||
from passlib.handlers.ldap_digests import ldap_bcrypt, ldap_bsdi_crypt, ldap_des_crypt, ldap_md5, ldap_md5_crypt, ldap_plaintext, ldap_salted_md5, ldap_salted_sha1, ldap_salted_sha256, ldap_salted_sha512, ldap_sha1, ldap_sha1_crypt, ldap_sha256_crypt, ldap_sha512_crypt
|
||||
from passlib.handlers.md5_crypt import apr_md5_crypt, md5_crypt
|
||||
from passlib.handlers.misc import plaintext, unix_disabled, unix_fallback
|
||||
from passlib.handlers.mssql import mssql2000, mssql2005
|
||||
from passlib.handlers.mysql import mysql323, mysql41
|
||||
from passlib.handlers.oracle import oracle10, oracle11
|
||||
from passlib.handlers.pbkdf2 import atlassian_pbkdf2_sha1, cta_pbkdf2_sha1, dlitz_pbkdf2_sha1, grub_pbkdf2_sha512, ldap_pbkdf2_sha1, ldap_pbkdf2_sha256, ldap_pbkdf2_sha512, pbkdf2_sha1, pbkdf2_sha256, pbkdf2_sha512
|
||||
from passlib.handlers.phpass import phpass
|
||||
from passlib.handlers.postgres import postgres_md5
|
||||
from passlib.handlers.roundup import ldap_hex_md5, ldap_hex_sha1, roundup_plaintext
|
||||
from passlib.handlers.scram import scram
|
||||
from passlib.handlers.scrypt import scrypt
|
||||
from passlib.handlers.sha1_crypt import sha1_crypt
|
||||
from passlib.handlers.sha2_crypt import sha256_crypt, sha512_crypt
|
||||
from passlib.handlers.sun_md5_crypt import sun_md5_crypt
|
||||
from passlib.handlers.windows import bsd_nthash, lmhash, msdcc, msdcc2, nthash
|
||||
#----------------------------------------------------
|
||||
# end autocomplete hack
|
||||
#----------------------------------------------------
|
||||
|
||||
#=============================================================================
|
||||
# eoc
|
||||
#=============================================================================
|
||||
106
venv/Lib/site-packages/passlib/hosts.py
Normal file
106
venv/Lib/site-packages/passlib/hosts.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""passlib.hosts"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from warnings import warn
|
||||
# pkg
|
||||
from passlib.context import LazyCryptContext
|
||||
from passlib.exc import PasslibRuntimeWarning
|
||||
from passlib import registry
|
||||
from passlib.utils import has_crypt, unix_crypt_schemes
|
||||
# local
|
||||
__all__ = [
|
||||
"linux_context", "linux2_context",
|
||||
"openbsd_context",
|
||||
"netbsd_context",
|
||||
"freebsd_context",
|
||||
"host_context",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# linux support
|
||||
#=============================================================================
|
||||
|
||||
# known platform names - linux2
|
||||
|
||||
linux_context = linux2_context = LazyCryptContext(
|
||||
schemes = [ "sha512_crypt", "sha256_crypt", "md5_crypt",
|
||||
"des_crypt", "unix_disabled" ],
|
||||
deprecated = [ "des_crypt" ],
|
||||
)
|
||||
|
||||
#=============================================================================
|
||||
# bsd support
|
||||
#=============================================================================
|
||||
|
||||
# known platform names -
|
||||
# freebsd2
|
||||
# freebsd3
|
||||
# freebsd4
|
||||
# freebsd5
|
||||
# freebsd6
|
||||
# freebsd7
|
||||
#
|
||||
# netbsd1
|
||||
|
||||
# referencing source via -http://fxr.googlebit.com
|
||||
# freebsd 6,7,8 - des, md5, bcrypt, bsd_nthash
|
||||
# netbsd - des, ext, md5, bcrypt, sha1
|
||||
# openbsd - des, ext, md5, bcrypt
|
||||
|
||||
freebsd_context = LazyCryptContext(["bcrypt", "md5_crypt", "bsd_nthash",
|
||||
"des_crypt", "unix_disabled"])
|
||||
|
||||
openbsd_context = LazyCryptContext(["bcrypt", "md5_crypt", "bsdi_crypt",
|
||||
"des_crypt", "unix_disabled"])
|
||||
|
||||
netbsd_context = LazyCryptContext(["bcrypt", "sha1_crypt", "md5_crypt",
|
||||
"bsdi_crypt", "des_crypt", "unix_disabled"])
|
||||
|
||||
# XXX: include darwin in this list? it's got a BSD crypt variant,
|
||||
# but that's not what it uses for user passwords.
|
||||
|
||||
#=============================================================================
|
||||
# current host
|
||||
#=============================================================================
|
||||
if registry.os_crypt_present:
|
||||
# NOTE: this is basically mimicing the output of os crypt(),
|
||||
# except that it uses passlib's (usually stronger) defaults settings,
|
||||
# and can be inspected and used much more flexibly.
|
||||
|
||||
def _iter_os_crypt_schemes():
|
||||
"""helper which iterates over supported os_crypt schemes"""
|
||||
out = registry.get_supported_os_crypt_schemes()
|
||||
if out:
|
||||
# only offer disabled handler if there's another scheme in front,
|
||||
# as this can't actually hash any passwords
|
||||
out += ("unix_disabled",)
|
||||
return out
|
||||
|
||||
host_context = LazyCryptContext(_iter_os_crypt_schemes())
|
||||
|
||||
#=============================================================================
|
||||
# other platforms
|
||||
#=============================================================================
|
||||
|
||||
# known platform strings -
|
||||
# aix3
|
||||
# aix4
|
||||
# atheos
|
||||
# beos5
|
||||
# darwin
|
||||
# generic
|
||||
# hp-ux11
|
||||
# irix5
|
||||
# irix6
|
||||
# mac
|
||||
# next3
|
||||
# os2emx
|
||||
# riscos
|
||||
# sunos5
|
||||
# unixware7
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
353
venv/Lib/site-packages/passlib/ifc.py
Normal file
353
venv/Lib/site-packages/passlib/ifc.py
Normal file
@@ -0,0 +1,353 @@
|
||||
"""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
|
||||
#=============================================================================
|
||||
809
venv/Lib/site-packages/passlib/pwd.py
Normal file
809
venv/Lib/site-packages/passlib/pwd.py
Normal file
@@ -0,0 +1,809 @@
|
||||
"""passlib.pwd -- password generation helpers"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
# core
|
||||
import codecs
|
||||
from collections import defaultdict
|
||||
try:
|
||||
from collections.abc import MutableMapping
|
||||
except ImportError:
|
||||
# py2 compat
|
||||
from collections import MutableMapping
|
||||
from math import ceil, log as logf
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
import pkg_resources
|
||||
import os
|
||||
# site
|
||||
# pkg
|
||||
from passlib import exc
|
||||
from passlib.utils.compat import PY2, irange, itervalues, int_types
|
||||
from passlib.utils import rng, getrandstr, to_unicode
|
||||
from passlib.utils.decor import memoized_property
|
||||
# local
|
||||
__all__ = [
|
||||
"genword", "default_charsets",
|
||||
"genphrase", "default_wordsets",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# constants
|
||||
#=============================================================================
|
||||
|
||||
# XXX: rename / publically document this map?
|
||||
entropy_aliases = dict(
|
||||
# barest protection from throttled online attack
|
||||
unsafe=12,
|
||||
|
||||
# some protection from unthrottled online attack
|
||||
weak=24,
|
||||
|
||||
# some protection from offline attacks
|
||||
fair=36,
|
||||
|
||||
# reasonable protection from offline attacks
|
||||
strong=48,
|
||||
|
||||
# very good protection from offline attacks
|
||||
secure=60,
|
||||
)
|
||||
|
||||
#=============================================================================
|
||||
# internal helpers
|
||||
#=============================================================================
|
||||
|
||||
def _superclasses(obj, cls):
|
||||
"""return remaining classes in object's MRO after cls"""
|
||||
mro = type(obj).__mro__
|
||||
return mro[mro.index(cls)+1:]
|
||||
|
||||
|
||||
def _self_info_rate(source):
|
||||
"""
|
||||
returns 'rate of self-information' --
|
||||
i.e. average (per-symbol) entropy of the sequence **source**,
|
||||
where probability of a given symbol occurring is calculated based on
|
||||
the number of occurrences within the sequence itself.
|
||||
|
||||
if all elements of the source are unique, this should equal ``log(len(source), 2)``.
|
||||
|
||||
:arg source:
|
||||
iterable containing 0+ symbols
|
||||
(e.g. list of strings or ints, string of characters, etc).
|
||||
|
||||
:returns:
|
||||
float bits of entropy
|
||||
"""
|
||||
try:
|
||||
size = len(source)
|
||||
except TypeError:
|
||||
# if len() doesn't work, calculate size by summing counts later
|
||||
size = None
|
||||
counts = defaultdict(int)
|
||||
for char in source:
|
||||
counts[char] += 1
|
||||
if size is None:
|
||||
values = counts.values()
|
||||
size = sum(values)
|
||||
else:
|
||||
values = itervalues(counts)
|
||||
if not size:
|
||||
return 0
|
||||
# NOTE: the following performs ``- sum(value / size * logf(value / size, 2) for value in values)``,
|
||||
# it just does so with as much pulled out of the sum() loop as possible...
|
||||
return logf(size, 2) - sum(value * logf(value, 2) for value in values) / size
|
||||
|
||||
|
||||
# def _total_self_info(source):
|
||||
# """
|
||||
# return total self-entropy of a sequence
|
||||
# (the average entropy per symbol * size of sequence)
|
||||
# """
|
||||
# return _self_info_rate(source) * len(source)
|
||||
|
||||
|
||||
def _open_asset_path(path, encoding=None):
|
||||
"""
|
||||
:param asset_path:
|
||||
string containing absolute path to file,
|
||||
or package-relative path using format
|
||||
``"python.module:relative/file/path"``.
|
||||
|
||||
:returns:
|
||||
filehandle opened in 'rb' mode
|
||||
(unless encoding explicitly specified)
|
||||
"""
|
||||
if encoding:
|
||||
return codecs.getreader(encoding)(_open_asset_path(path))
|
||||
if os.path.isabs(path):
|
||||
return open(path, "rb")
|
||||
package, sep, subpath = path.partition(":")
|
||||
if not sep:
|
||||
raise ValueError("asset path must be absolute file path "
|
||||
"or use 'pkg.name:sub/path' format: %r" % (path,))
|
||||
return pkg_resources.resource_stream(package, subpath)
|
||||
|
||||
|
||||
#: type aliases
|
||||
_sequence_types = (list, tuple)
|
||||
_set_types = (set, frozenset)
|
||||
|
||||
#: set of elements that ensure_unique() has validated already.
|
||||
_ensure_unique_cache = set()
|
||||
|
||||
|
||||
def _ensure_unique(source, param="source"):
|
||||
"""
|
||||
helper for generators --
|
||||
Throws ValueError if source elements aren't unique.
|
||||
Error message will display (abbreviated) repr of the duplicates in a string/list
|
||||
"""
|
||||
# check cache to speed things up for frozensets / tuples / strings
|
||||
cache = _ensure_unique_cache
|
||||
hashable = True
|
||||
try:
|
||||
if source in cache:
|
||||
return True
|
||||
except TypeError:
|
||||
hashable = False
|
||||
|
||||
# check if it has dup elements
|
||||
if isinstance(source, _set_types) or len(set(source)) == len(source):
|
||||
if hashable:
|
||||
try:
|
||||
cache.add(source)
|
||||
except TypeError:
|
||||
# XXX: under pypy, "list() in set()" above doesn't throw TypeError,
|
||||
# but trying to add unhashable it to a set *does*.
|
||||
pass
|
||||
return True
|
||||
|
||||
# build list of duplicate values
|
||||
seen = set()
|
||||
dups = set()
|
||||
for elem in source:
|
||||
(dups if elem in seen else seen).add(elem)
|
||||
dups = sorted(dups)
|
||||
trunc = 8
|
||||
if len(dups) > trunc:
|
||||
trunc = 5
|
||||
dup_repr = ", ".join(repr(str(word)) for word in dups[:trunc])
|
||||
if len(dups) > trunc:
|
||||
dup_repr += ", ... plus %d others" % (len(dups) - trunc)
|
||||
|
||||
# throw error
|
||||
raise ValueError("`%s` cannot contain duplicate elements: %s" %
|
||||
(param, dup_repr))
|
||||
|
||||
#=============================================================================
|
||||
# base generator class
|
||||
#=============================================================================
|
||||
class SequenceGenerator(object):
|
||||
"""
|
||||
Base class used by word & phrase generators.
|
||||
|
||||
These objects take a series of options, corresponding
|
||||
to those of the :func:`generate` function.
|
||||
They act as callables which can be used to generate a password
|
||||
or a list of 1+ passwords. They also expose some read-only
|
||||
informational attributes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
:param entropy:
|
||||
Optionally specify the amount of entropy the resulting passwords
|
||||
should contain (as measured with respect to the generator itself).
|
||||
This will be used to auto-calculate the required password size.
|
||||
|
||||
:param length:
|
||||
Optionally specify the length of password to generate,
|
||||
measured as count of whatever symbols the subclass uses (characters or words).
|
||||
Note if ``entropy`` requires a larger minimum length,
|
||||
that will be used instead.
|
||||
|
||||
:param rng:
|
||||
Optionally provide a custom RNG source to use.
|
||||
Should be an instance of :class:`random.Random`,
|
||||
defaults to :class:`random.SystemRandom`.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
.. autoattribute:: length
|
||||
.. autoattribute:: symbol_count
|
||||
.. autoattribute:: entropy_per_symbol
|
||||
.. autoattribute:: entropy
|
||||
|
||||
Subclassing
|
||||
-----------
|
||||
Subclasses must implement the ``.__next__()`` method,
|
||||
and set ``.symbol_count`` before calling base ``__init__`` method.
|
||||
"""
|
||||
#=============================================================================
|
||||
# instance attrs
|
||||
#=============================================================================
|
||||
|
||||
#: requested size of final password
|
||||
length = None
|
||||
|
||||
#: requested entropy of final password
|
||||
requested_entropy = "strong"
|
||||
|
||||
#: random number source to use
|
||||
rng = rng
|
||||
|
||||
#: number of potential symbols (must be filled in by subclass)
|
||||
symbol_count = None
|
||||
|
||||
#=============================================================================
|
||||
# init
|
||||
#=============================================================================
|
||||
def __init__(self, entropy=None, length=None, rng=None, **kwds):
|
||||
|
||||
# make sure subclass set things up correctly
|
||||
assert self.symbol_count is not None, "subclass must set .symbol_count"
|
||||
|
||||
# init length & requested entropy
|
||||
if entropy is not None or length is None:
|
||||
if entropy is None:
|
||||
entropy = self.requested_entropy
|
||||
entropy = entropy_aliases.get(entropy, entropy)
|
||||
if entropy <= 0:
|
||||
raise ValueError("`entropy` must be positive number")
|
||||
min_length = int(ceil(entropy / self.entropy_per_symbol))
|
||||
if length is None or length < min_length:
|
||||
length = min_length
|
||||
|
||||
self.requested_entropy = entropy
|
||||
|
||||
if length < 1:
|
||||
raise ValueError("`length` must be positive integer")
|
||||
self.length = length
|
||||
|
||||
# init other common options
|
||||
if rng is not None:
|
||||
self.rng = rng
|
||||
|
||||
# hand off to parent
|
||||
if kwds and _superclasses(self, SequenceGenerator) == (object,):
|
||||
raise TypeError("Unexpected keyword(s): %s" % ", ".join(kwds.keys()))
|
||||
super(SequenceGenerator, self).__init__(**kwds)
|
||||
|
||||
#=============================================================================
|
||||
# informational helpers
|
||||
#=============================================================================
|
||||
|
||||
@memoized_property
|
||||
def entropy_per_symbol(self):
|
||||
"""
|
||||
Average entropy per symbol (assuming all symbols have equal probability)
|
||||
"""
|
||||
return logf(self.symbol_count, 2)
|
||||
|
||||
@memoized_property
|
||||
def entropy(self):
|
||||
"""
|
||||
Effective entropy of generated passwords.
|
||||
|
||||
This value will always be a multiple of :attr:`entropy_per_symbol`.
|
||||
If entropy is specified in constructor, :attr:`length` will be chosen so
|
||||
so that this value is the smallest multiple >= :attr:`requested_entropy`.
|
||||
"""
|
||||
return self.length * self.entropy_per_symbol
|
||||
|
||||
#=============================================================================
|
||||
# generation
|
||||
#=============================================================================
|
||||
def __next__(self):
|
||||
"""main generation function, should create one password/phrase"""
|
||||
raise NotImplementedError("implement in subclass")
|
||||
|
||||
def __call__(self, returns=None):
|
||||
"""
|
||||
frontend used by genword() / genphrase() to create passwords
|
||||
"""
|
||||
if returns is None:
|
||||
return next(self)
|
||||
elif isinstance(returns, int_types):
|
||||
return [next(self) for _ in irange(returns)]
|
||||
elif returns is iter:
|
||||
return self
|
||||
else:
|
||||
raise exc.ExpectedTypeError(returns, "<None>, int, or <iter>", "returns")
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
if PY2:
|
||||
def next(self):
|
||||
return self.__next__()
|
||||
|
||||
#=============================================================================
|
||||
# eoc
|
||||
#=============================================================================
|
||||
|
||||
#=============================================================================
|
||||
# default charsets
|
||||
#=============================================================================
|
||||
|
||||
#: global dict of predefined characters sets
|
||||
default_charsets = dict(
|
||||
# ascii letters, digits, and some punctuation
|
||||
ascii_72='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*?/',
|
||||
|
||||
# ascii letters and digits
|
||||
ascii_62='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
||||
|
||||
# ascii_50, without visually similar '1IiLl', '0Oo', '5S', '8B'
|
||||
ascii_50='234679abcdefghjkmnpqrstuvwxyzACDEFGHJKMNPQRTUVWXYZ',
|
||||
|
||||
# lower case hexadecimal
|
||||
hex='0123456789abcdef',
|
||||
)
|
||||
|
||||
#=============================================================================
|
||||
# password generator
|
||||
#=============================================================================
|
||||
|
||||
class WordGenerator(SequenceGenerator):
|
||||
"""
|
||||
Class which generates passwords by randomly choosing from a string of unique characters.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
:param chars:
|
||||
custom character string to draw from.
|
||||
|
||||
:param charset:
|
||||
predefined charset to draw from.
|
||||
|
||||
:param \\*\\*kwds:
|
||||
all other keywords passed to the :class:`SequenceGenerator` parent class.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
.. autoattribute:: chars
|
||||
.. autoattribute:: charset
|
||||
.. autoattribute:: default_charsets
|
||||
"""
|
||||
#=============================================================================
|
||||
# instance attrs
|
||||
#=============================================================================
|
||||
|
||||
#: Predefined character set in use (set to None for instances using custom 'chars')
|
||||
charset = "ascii_62"
|
||||
|
||||
#: string of chars to draw from -- usually filled in from charset
|
||||
chars = None
|
||||
|
||||
#=============================================================================
|
||||
# init
|
||||
#=============================================================================
|
||||
def __init__(self, chars=None, charset=None, **kwds):
|
||||
|
||||
# init chars and charset
|
||||
if chars:
|
||||
if charset:
|
||||
raise TypeError("`chars` and `charset` are mutually exclusive")
|
||||
else:
|
||||
if not charset:
|
||||
charset = self.charset
|
||||
assert charset
|
||||
chars = default_charsets[charset]
|
||||
self.charset = charset
|
||||
chars = to_unicode(chars, param="chars")
|
||||
_ensure_unique(chars, param="chars")
|
||||
self.chars = chars
|
||||
|
||||
# hand off to parent
|
||||
super(WordGenerator, self).__init__(**kwds)
|
||||
# log.debug("WordGenerator(): entropy/char=%r", self.entropy_per_symbol)
|
||||
|
||||
#=============================================================================
|
||||
# informational helpers
|
||||
#=============================================================================
|
||||
|
||||
@memoized_property
|
||||
def symbol_count(self):
|
||||
return len(self.chars)
|
||||
|
||||
#=============================================================================
|
||||
# generation
|
||||
#=============================================================================
|
||||
|
||||
def __next__(self):
|
||||
# XXX: could do things like optionally ensure certain character groups
|
||||
# (e.g. letters & punctuation) are included
|
||||
return getrandstr(self.rng, self.chars, self.length)
|
||||
|
||||
#=============================================================================
|
||||
# eoc
|
||||
#=============================================================================
|
||||
|
||||
|
||||
def genword(entropy=None, length=None, returns=None, **kwds):
|
||||
"""Generate one or more random passwords.
|
||||
|
||||
This function uses :mod:`random.SystemRandom` to generate
|
||||
one or more passwords using various character sets.
|
||||
The complexity of the password can be specified
|
||||
by size, or by the desired amount of entropy.
|
||||
|
||||
Usage Example::
|
||||
|
||||
>>> # generate a random alphanumeric string with 48 bits of entropy (the default)
|
||||
>>> from passlib import pwd
|
||||
>>> pwd.genword()
|
||||
'DnBHvDjMK6'
|
||||
|
||||
>>> # generate a random hexadecimal string with 52 bits of entropy
|
||||
>>> pwd.genword(entropy=52, charset="hex")
|
||||
'310f1a7ac793f'
|
||||
|
||||
:param entropy:
|
||||
Strength of resulting password, measured in 'guessing entropy' bits.
|
||||
An appropriate **length** value will be calculated
|
||||
based on the requested entropy amount, and the size of the character set.
|
||||
|
||||
This can be a positive integer, or one of the following preset
|
||||
strings: ``"weak"`` (24), ``"fair"`` (36),
|
||||
``"strong"`` (48), and ``"secure"`` (56).
|
||||
|
||||
If neither this or **length** is specified, **entropy** will default
|
||||
to ``"strong"`` (48).
|
||||
|
||||
:param length:
|
||||
Size of resulting password, measured in characters.
|
||||
If omitted, the size is auto-calculated based on the **entropy** parameter.
|
||||
|
||||
If both **entropy** and **length** are specified,
|
||||
the stronger value will be used.
|
||||
|
||||
:param returns:
|
||||
Controls what this function returns:
|
||||
|
||||
* If ``None`` (the default), this function will generate a single password.
|
||||
* If an integer, this function will return a list containing that many passwords.
|
||||
* If the ``iter`` constant, will return an iterator that yields passwords.
|
||||
|
||||
:param chars:
|
||||
|
||||
Optionally specify custom string of characters to use when randomly
|
||||
generating a password. This option cannot be combined with **charset**.
|
||||
|
||||
:param charset:
|
||||
|
||||
The predefined character set to draw from (if not specified by **chars**).
|
||||
There are currently four presets available:
|
||||
|
||||
* ``"ascii_62"`` (the default) -- all digits and ascii upper & lowercase letters.
|
||||
Provides ~5.95 entropy per character.
|
||||
|
||||
* ``"ascii_50"`` -- subset which excludes visually similar characters
|
||||
(``1IiLl0Oo5S8B``). Provides ~5.64 entropy per character.
|
||||
|
||||
* ``"ascii_72"`` -- all digits and ascii upper & lowercase letters,
|
||||
as well as some punctuation. Provides ~6.17 entropy per character.
|
||||
|
||||
* ``"hex"`` -- Lower case hexadecimal. Providers 4 bits of entropy per character.
|
||||
|
||||
:returns:
|
||||
:class:`!unicode` string containing randomly generated password;
|
||||
or list of 1+ passwords if :samp:`returns={int}` is specified.
|
||||
"""
|
||||
gen = WordGenerator(length=length, entropy=entropy, **kwds)
|
||||
return gen(returns)
|
||||
|
||||
#=============================================================================
|
||||
# default wordsets
|
||||
#=============================================================================
|
||||
|
||||
def _load_wordset(asset_path):
|
||||
"""
|
||||
load wordset from compressed datafile within package data.
|
||||
file should be utf-8 encoded
|
||||
|
||||
:param asset_path:
|
||||
string containing absolute path to wordset file,
|
||||
or "python.module:relative/file/path".
|
||||
|
||||
:returns:
|
||||
tuple of words, as loaded from specified words file.
|
||||
"""
|
||||
# open resource file, convert to tuple of words (strip blank lines & ws)
|
||||
with _open_asset_path(asset_path, "utf-8") as fh:
|
||||
gen = (word.strip() for word in fh)
|
||||
words = tuple(word for word in gen if word)
|
||||
|
||||
# NOTE: works but not used
|
||||
# # detect if file uses "<int> <word>" format, and strip numeric prefix
|
||||
# def extract(row):
|
||||
# idx, word = row.replace("\t", " ").split(" ", 1)
|
||||
# if not idx.isdigit():
|
||||
# raise ValueError("row is not dice index + word")
|
||||
# return word
|
||||
# try:
|
||||
# extract(words[-1])
|
||||
# except ValueError:
|
||||
# pass
|
||||
# else:
|
||||
# words = tuple(extract(word) for word in words)
|
||||
|
||||
log.debug("loaded %d-element wordset from %r", len(words), asset_path)
|
||||
return words
|
||||
|
||||
|
||||
class WordsetDict(MutableMapping):
|
||||
"""
|
||||
Special mapping used to store dictionary of wordsets.
|
||||
Different from a regular dict in that some wordsets
|
||||
may be lazy-loaded from an asset path.
|
||||
"""
|
||||
|
||||
#: dict of key -> asset path
|
||||
paths = None
|
||||
|
||||
#: dict of key -> value
|
||||
_loaded = None
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
self.paths = {}
|
||||
self._loaded = {}
|
||||
super(WordsetDict, self).__init__(*args, **kwds)
|
||||
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
return self._loaded[key]
|
||||
except KeyError:
|
||||
pass
|
||||
path = self.paths[key]
|
||||
value = self._loaded[key] = _load_wordset(path)
|
||||
return value
|
||||
|
||||
def set_path(self, key, path):
|
||||
"""
|
||||
set asset path to lazy-load wordset from.
|
||||
"""
|
||||
self.paths[key] = path
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._loaded[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
if key in self:
|
||||
del self._loaded[key]
|
||||
self.paths.pop(key, None)
|
||||
else:
|
||||
del self.paths[key]
|
||||
|
||||
@property
|
||||
def _keyset(self):
|
||||
keys = set(self._loaded)
|
||||
keys.update(self.paths)
|
||||
return keys
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._keyset)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._keyset)
|
||||
|
||||
# NOTE: speeds things up, and prevents contains from lazy-loading
|
||||
def __contains__(self, key):
|
||||
return key in self._loaded or key in self.paths
|
||||
|
||||
|
||||
#: dict of predefined word sets.
|
||||
#: key is name of wordset, value should be sequence of words.
|
||||
default_wordsets = WordsetDict()
|
||||
|
||||
# register the wordsets built into passlib
|
||||
for name in "eff_long eff_short eff_prefixed bip39".split():
|
||||
default_wordsets.set_path(name, "passlib:_data/wordsets/%s.txt" % name)
|
||||
|
||||
#=============================================================================
|
||||
# passphrase generator
|
||||
#=============================================================================
|
||||
class PhraseGenerator(SequenceGenerator):
|
||||
"""class which generates passphrases by randomly choosing
|
||||
from a list of unique words.
|
||||
|
||||
:param wordset:
|
||||
wordset to draw from.
|
||||
:param preset:
|
||||
name of preset wordlist to use instead of ``wordset``.
|
||||
:param spaces:
|
||||
whether to insert spaces between words in output (defaults to ``True``).
|
||||
:param \\*\\*kwds:
|
||||
all other keywords passed to the :class:`SequenceGenerator` parent class.
|
||||
|
||||
.. autoattribute:: wordset
|
||||
"""
|
||||
#=============================================================================
|
||||
# instance attrs
|
||||
#=============================================================================
|
||||
|
||||
#: predefined wordset to use
|
||||
wordset = "eff_long"
|
||||
|
||||
#: list of words to draw from
|
||||
words = None
|
||||
|
||||
#: separator to use when joining words
|
||||
sep = " "
|
||||
|
||||
#=============================================================================
|
||||
# init
|
||||
#=============================================================================
|
||||
def __init__(self, wordset=None, words=None, sep=None, **kwds):
|
||||
|
||||
# load wordset
|
||||
if words is not None:
|
||||
if wordset is not None:
|
||||
raise TypeError("`words` and `wordset` are mutually exclusive")
|
||||
else:
|
||||
if wordset is None:
|
||||
wordset = self.wordset
|
||||
assert wordset
|
||||
words = default_wordsets[wordset]
|
||||
self.wordset = wordset
|
||||
|
||||
# init words
|
||||
if not isinstance(words, _sequence_types):
|
||||
words = tuple(words)
|
||||
_ensure_unique(words, param="words")
|
||||
self.words = words
|
||||
|
||||
# init separator
|
||||
if sep is None:
|
||||
sep = self.sep
|
||||
sep = to_unicode(sep, param="sep")
|
||||
self.sep = sep
|
||||
|
||||
# hand off to parent
|
||||
super(PhraseGenerator, self).__init__(**kwds)
|
||||
##log.debug("PhraseGenerator(): entropy/word=%r entropy/char=%r min_chars=%r",
|
||||
## self.entropy_per_symbol, self.entropy_per_char, self.min_chars)
|
||||
|
||||
#=============================================================================
|
||||
# informational helpers
|
||||
#=============================================================================
|
||||
|
||||
@memoized_property
|
||||
def symbol_count(self):
|
||||
return len(self.words)
|
||||
|
||||
#=============================================================================
|
||||
# generation
|
||||
#=============================================================================
|
||||
|
||||
def __next__(self):
|
||||
words = (self.rng.choice(self.words) for _ in irange(self.length))
|
||||
return self.sep.join(words)
|
||||
|
||||
#=============================================================================
|
||||
# eoc
|
||||
#=============================================================================
|
||||
|
||||
|
||||
def genphrase(entropy=None, length=None, returns=None, **kwds):
|
||||
"""Generate one or more random password / passphrases.
|
||||
|
||||
This function uses :mod:`random.SystemRandom` to generate
|
||||
one or more passwords; it can be configured to generate
|
||||
alphanumeric passwords, or full english phrases.
|
||||
The complexity of the password can be specified
|
||||
by size, or by the desired amount of entropy.
|
||||
|
||||
Usage Example::
|
||||
|
||||
>>> # generate random phrase with 48 bits of entropy
|
||||
>>> from passlib import pwd
|
||||
>>> pwd.genphrase()
|
||||
'gangly robbing salt shove'
|
||||
|
||||
>>> # generate a random phrase with 52 bits of entropy
|
||||
>>> # using a particular wordset
|
||||
>>> pwd.genword(entropy=52, wordset="bip39")
|
||||
'wheat dilemma reward rescue diary'
|
||||
|
||||
:param entropy:
|
||||
Strength of resulting password, measured in 'guessing entropy' bits.
|
||||
An appropriate **length** value will be calculated
|
||||
based on the requested entropy amount, and the size of the word set.
|
||||
|
||||
This can be a positive integer, or one of the following preset
|
||||
strings: ``"weak"`` (24), ``"fair"`` (36),
|
||||
``"strong"`` (48), and ``"secure"`` (56).
|
||||
|
||||
If neither this or **length** is specified, **entropy** will default
|
||||
to ``"strong"`` (48).
|
||||
|
||||
:param length:
|
||||
Length of resulting password, measured in words.
|
||||
If omitted, the size is auto-calculated based on the **entropy** parameter.
|
||||
|
||||
If both **entropy** and **length** are specified,
|
||||
the stronger value will be used.
|
||||
|
||||
:param returns:
|
||||
Controls what this function returns:
|
||||
|
||||
* If ``None`` (the default), this function will generate a single password.
|
||||
* If an integer, this function will return a list containing that many passwords.
|
||||
* If the ``iter`` builtin, will return an iterator that yields passwords.
|
||||
|
||||
:param words:
|
||||
|
||||
Optionally specifies a list/set of words to use when randomly generating a passphrase.
|
||||
This option cannot be combined with **wordset**.
|
||||
|
||||
:param wordset:
|
||||
|
||||
The predefined word set to draw from (if not specified by **words**).
|
||||
There are currently four presets available:
|
||||
|
||||
``"eff_long"`` (the default)
|
||||
|
||||
Wordset containing 7776 english words of ~7 letters.
|
||||
Constructed by the EFF, it offers ~12.9 bits of entropy per word.
|
||||
|
||||
This wordset (and the other ``"eff_"`` wordsets)
|
||||
were `created by the EFF <https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases>`_
|
||||
to aid in generating passwords. See their announcement page
|
||||
for more details about the design & properties of these wordsets.
|
||||
|
||||
``"eff_short"``
|
||||
|
||||
Wordset containing 1296 english words of ~4.5 letters.
|
||||
Constructed by the EFF, it offers ~10.3 bits of entropy per word.
|
||||
|
||||
``"eff_prefixed"``
|
||||
|
||||
Wordset containing 1296 english words of ~8 letters,
|
||||
selected so that they each have a unique 3-character prefix.
|
||||
Constructed by the EFF, it offers ~10.3 bits of entropy per word.
|
||||
|
||||
``"bip39"``
|
||||
|
||||
Wordset of 2048 english words of ~5 letters,
|
||||
selected so that they each have a unique 4-character prefix.
|
||||
Published as part of Bitcoin's `BIP 39 <https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt>`_,
|
||||
this wordset has exactly 11 bits of entropy per word.
|
||||
|
||||
This list offers words that are typically shorter than ``"eff_long"``
|
||||
(at the cost of slightly less entropy); and much shorter than
|
||||
``"eff_prefixed"`` (at the cost of a longer unique prefix).
|
||||
|
||||
:param sep:
|
||||
Optional separator to use when joining words.
|
||||
Defaults to ``" "`` (a space), but can be an empty string, a hyphen, etc.
|
||||
|
||||
:returns:
|
||||
:class:`!unicode` string containing randomly generated passphrase;
|
||||
or list of 1+ passphrases if :samp:`returns={int}` is specified.
|
||||
"""
|
||||
gen = PhraseGenerator(entropy=entropy, length=length, **kwds)
|
||||
return gen(returns)
|
||||
|
||||
#=============================================================================
|
||||
# strength measurement
|
||||
#
|
||||
# NOTE:
|
||||
# for a little while, had rough draft of password strength measurement alg here.
|
||||
# but not sure if there's value in yet another measurement algorithm,
|
||||
# that's not just duplicating the effort of libraries like zxcbn.
|
||||
# may revive it later, but for now, leaving some refs to others out there:
|
||||
# * NIST 800-63 has simple alg
|
||||
# * zxcvbn (https://tech.dropbox.com/2012/04/zxcvbn-realistic-password-strength-estimation/)
|
||||
# might also be good, and has approach similar to composite approach i was already thinking about,
|
||||
# but much more well thought out.
|
||||
# * passfault (https://github.com/c-a-m/passfault) looks thorough,
|
||||
# but may have licensing issues, plus porting to python looks like very big job :(
|
||||
# * give a look at running things through zlib - might be able to cheaply
|
||||
# catch extra redundancies.
|
||||
#=============================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
547
venv/Lib/site-packages/passlib/registry.py
Normal file
547
venv/Lib/site-packages/passlib/registry.py
Normal file
@@ -0,0 +1,547 @@
|
||||
"""passlib.registry - registry for password hash handlers"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import re
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
from warnings import warn
|
||||
# pkg
|
||||
from passlib import exc
|
||||
from passlib.exc import ExpectedTypeError, PasslibWarning
|
||||
from passlib.ifc import PasswordHash
|
||||
from passlib.utils import (
|
||||
is_crypt_handler, has_crypt as os_crypt_present,
|
||||
unix_crypt_schemes as os_crypt_schemes,
|
||||
)
|
||||
from passlib.utils.compat import unicode_or_str
|
||||
from passlib.utils.decor import memoize_single_value
|
||||
# local
|
||||
__all__ = [
|
||||
"register_crypt_handler_path",
|
||||
"register_crypt_handler",
|
||||
"get_crypt_handler",
|
||||
"list_crypt_handlers",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# proxy object used in place of 'passlib.hash' module
|
||||
#=============================================================================
|
||||
class _PasslibRegistryProxy(object):
|
||||
"""proxy module passlib.hash
|
||||
|
||||
this module is in fact an object which lazy-loads
|
||||
the requested password hash algorithm from wherever it has been stored.
|
||||
it acts as a thin wrapper around :func:`passlib.registry.get_crypt_handler`.
|
||||
"""
|
||||
__name__ = "passlib.hash"
|
||||
__package__ = None
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr.startswith("_"):
|
||||
raise AttributeError("missing attribute: %r" % (attr,))
|
||||
handler = get_crypt_handler(attr, None)
|
||||
if handler:
|
||||
return handler
|
||||
else:
|
||||
raise AttributeError("unknown password hash: %r" % (attr,))
|
||||
|
||||
def __setattr__(self, attr, value):
|
||||
if attr.startswith("_"):
|
||||
# writing to private attributes should behave normally.
|
||||
# (required so GAE can write to the __loader__ attribute).
|
||||
object.__setattr__(self, attr, value)
|
||||
else:
|
||||
# writing to public attributes should be treated
|
||||
# as attempting to register a handler.
|
||||
register_crypt_handler(value, _attr=attr)
|
||||
|
||||
def __repr__(self):
|
||||
return "<proxy module 'passlib.hash'>"
|
||||
|
||||
def __dir__(self):
|
||||
# this adds in lazy-loaded handler names,
|
||||
# otherwise this is the standard dir() implementation.
|
||||
attrs = set(dir(self.__class__))
|
||||
attrs.update(self.__dict__)
|
||||
attrs.update(_locations)
|
||||
return sorted(attrs)
|
||||
|
||||
# create single instance - available publically as 'passlib.hash'
|
||||
_proxy = _PasslibRegistryProxy()
|
||||
|
||||
#=============================================================================
|
||||
# internal registry state
|
||||
#=============================================================================
|
||||
|
||||
# singleton uses to detect omitted keywords
|
||||
_UNSET = object()
|
||||
|
||||
# dict mapping name -> loaded handlers (just uses proxy object's internal dict)
|
||||
_handlers = _proxy.__dict__
|
||||
|
||||
# dict mapping names -> import path for lazy loading.
|
||||
# * import path should be "module.path" or "module.path:attr"
|
||||
# * if attr omitted, "name" used as default.
|
||||
_locations = dict(
|
||||
# NOTE: this is a hardcoded list of the handlers built into passlib,
|
||||
# applications should call register_crypt_handler_path()
|
||||
apr_md5_crypt = "passlib.handlers.md5_crypt",
|
||||
argon2 = "passlib.handlers.argon2",
|
||||
atlassian_pbkdf2_sha1 = "passlib.handlers.pbkdf2",
|
||||
bcrypt = "passlib.handlers.bcrypt",
|
||||
bcrypt_sha256 = "passlib.handlers.bcrypt",
|
||||
bigcrypt = "passlib.handlers.des_crypt",
|
||||
bsd_nthash = "passlib.handlers.windows",
|
||||
bsdi_crypt = "passlib.handlers.des_crypt",
|
||||
cisco_pix = "passlib.handlers.cisco",
|
||||
cisco_asa = "passlib.handlers.cisco",
|
||||
cisco_type7 = "passlib.handlers.cisco",
|
||||
cta_pbkdf2_sha1 = "passlib.handlers.pbkdf2",
|
||||
crypt16 = "passlib.handlers.des_crypt",
|
||||
des_crypt = "passlib.handlers.des_crypt",
|
||||
django_argon2 = "passlib.handlers.django",
|
||||
django_bcrypt = "passlib.handlers.django",
|
||||
django_bcrypt_sha256 = "passlib.handlers.django",
|
||||
django_pbkdf2_sha256 = "passlib.handlers.django",
|
||||
django_pbkdf2_sha1 = "passlib.handlers.django",
|
||||
django_salted_sha1 = "passlib.handlers.django",
|
||||
django_salted_md5 = "passlib.handlers.django",
|
||||
django_des_crypt = "passlib.handlers.django",
|
||||
django_disabled = "passlib.handlers.django",
|
||||
dlitz_pbkdf2_sha1 = "passlib.handlers.pbkdf2",
|
||||
fshp = "passlib.handlers.fshp",
|
||||
grub_pbkdf2_sha512 = "passlib.handlers.pbkdf2",
|
||||
hex_md4 = "passlib.handlers.digests",
|
||||
hex_md5 = "passlib.handlers.digests",
|
||||
hex_sha1 = "passlib.handlers.digests",
|
||||
hex_sha256 = "passlib.handlers.digests",
|
||||
hex_sha512 = "passlib.handlers.digests",
|
||||
htdigest = "passlib.handlers.digests",
|
||||
ldap_plaintext = "passlib.handlers.ldap_digests",
|
||||
ldap_md5 = "passlib.handlers.ldap_digests",
|
||||
ldap_sha1 = "passlib.handlers.ldap_digests",
|
||||
ldap_hex_md5 = "passlib.handlers.roundup",
|
||||
ldap_hex_sha1 = "passlib.handlers.roundup",
|
||||
ldap_salted_md5 = "passlib.handlers.ldap_digests",
|
||||
ldap_salted_sha1 = "passlib.handlers.ldap_digests",
|
||||
ldap_salted_sha256 = "passlib.handlers.ldap_digests",
|
||||
ldap_salted_sha512 = "passlib.handlers.ldap_digests",
|
||||
ldap_des_crypt = "passlib.handlers.ldap_digests",
|
||||
ldap_bsdi_crypt = "passlib.handlers.ldap_digests",
|
||||
ldap_md5_crypt = "passlib.handlers.ldap_digests",
|
||||
ldap_bcrypt = "passlib.handlers.ldap_digests",
|
||||
ldap_sha1_crypt = "passlib.handlers.ldap_digests",
|
||||
ldap_sha256_crypt = "passlib.handlers.ldap_digests",
|
||||
ldap_sha512_crypt = "passlib.handlers.ldap_digests",
|
||||
ldap_pbkdf2_sha1 = "passlib.handlers.pbkdf2",
|
||||
ldap_pbkdf2_sha256 = "passlib.handlers.pbkdf2",
|
||||
ldap_pbkdf2_sha512 = "passlib.handlers.pbkdf2",
|
||||
lmhash = "passlib.handlers.windows",
|
||||
md5_crypt = "passlib.handlers.md5_crypt",
|
||||
msdcc = "passlib.handlers.windows",
|
||||
msdcc2 = "passlib.handlers.windows",
|
||||
mssql2000 = "passlib.handlers.mssql",
|
||||
mssql2005 = "passlib.handlers.mssql",
|
||||
mysql323 = "passlib.handlers.mysql",
|
||||
mysql41 = "passlib.handlers.mysql",
|
||||
nthash = "passlib.handlers.windows",
|
||||
oracle10 = "passlib.handlers.oracle",
|
||||
oracle11 = "passlib.handlers.oracle",
|
||||
pbkdf2_sha1 = "passlib.handlers.pbkdf2",
|
||||
pbkdf2_sha256 = "passlib.handlers.pbkdf2",
|
||||
pbkdf2_sha512 = "passlib.handlers.pbkdf2",
|
||||
phpass = "passlib.handlers.phpass",
|
||||
plaintext = "passlib.handlers.misc",
|
||||
postgres_md5 = "passlib.handlers.postgres",
|
||||
roundup_plaintext = "passlib.handlers.roundup",
|
||||
scram = "passlib.handlers.scram",
|
||||
scrypt = "passlib.handlers.scrypt",
|
||||
sha1_crypt = "passlib.handlers.sha1_crypt",
|
||||
sha256_crypt = "passlib.handlers.sha2_crypt",
|
||||
sha512_crypt = "passlib.handlers.sha2_crypt",
|
||||
sun_md5_crypt = "passlib.handlers.sun_md5_crypt",
|
||||
unix_disabled = "passlib.handlers.misc",
|
||||
unix_fallback = "passlib.handlers.misc",
|
||||
)
|
||||
|
||||
# master regexp for detecting valid handler names
|
||||
_name_re = re.compile("^[a-z][a-z0-9_]+[a-z0-9]$")
|
||||
|
||||
# names which aren't allowed for various reasons
|
||||
# (mainly keyword conflicts in CryptContext)
|
||||
_forbidden_names = frozenset(["onload", "policy", "context", "all",
|
||||
"default", "none", "auto"])
|
||||
|
||||
#=============================================================================
|
||||
# registry frontend functions
|
||||
#=============================================================================
|
||||
def _validate_handler_name(name):
|
||||
"""helper to validate handler name
|
||||
|
||||
:raises ValueError:
|
||||
* if empty name
|
||||
* if name not lower case
|
||||
* if name contains double underscores
|
||||
* if name is reserved (e.g. ``context``, ``all``).
|
||||
"""
|
||||
if not name:
|
||||
raise ValueError("handler name cannot be empty: %r" % (name,))
|
||||
if name.lower() != name:
|
||||
raise ValueError("name must be lower-case: %r" % (name,))
|
||||
if not _name_re.match(name):
|
||||
raise ValueError("invalid name (must be 3+ characters, "
|
||||
" begin with a-z, and contain only underscore, a-z, "
|
||||
"0-9): %r" % (name,))
|
||||
if '__' in name:
|
||||
raise ValueError("name may not contain double-underscores: %r" %
|
||||
(name,))
|
||||
if name in _forbidden_names:
|
||||
raise ValueError("that name is not allowed: %r" % (name,))
|
||||
return True
|
||||
|
||||
def register_crypt_handler_path(name, path):
|
||||
"""register location to lazy-load handler when requested.
|
||||
|
||||
custom hashes may be registered via :func:`register_crypt_handler`,
|
||||
or they may be registered by this function,
|
||||
which will delay actually importing and loading the handler
|
||||
until a call to :func:`get_crypt_handler` is made for the specified name.
|
||||
|
||||
:arg name: name of handler
|
||||
:arg path: module import path
|
||||
|
||||
the specified module path should contain a password hash handler
|
||||
called :samp:`{name}`, or the path may contain a colon,
|
||||
specifying the module and module attribute to use.
|
||||
for example, the following would cause ``get_handler("myhash")`` to look
|
||||
for a class named ``myhash`` within the ``myapp.helpers`` module::
|
||||
|
||||
>>> from passlib.registry import registry_crypt_handler_path
|
||||
>>> registry_crypt_handler_path("myhash", "myapp.helpers")
|
||||
|
||||
...while this form would cause ``get_handler("myhash")`` to look
|
||||
for a class name ``MyHash`` within the ``myapp.helpers`` module::
|
||||
|
||||
>>> from passlib.registry import registry_crypt_handler_path
|
||||
>>> registry_crypt_handler_path("myhash", "myapp.helpers:MyHash")
|
||||
"""
|
||||
# validate name
|
||||
_validate_handler_name(name)
|
||||
|
||||
# validate path
|
||||
if path.startswith("."):
|
||||
raise ValueError("path cannot start with '.'")
|
||||
if ':' in path:
|
||||
if path.count(':') > 1:
|
||||
raise ValueError("path cannot have more than one ':'")
|
||||
if path.find('.', path.index(':')) > -1:
|
||||
raise ValueError("path cannot have '.' to right of ':'")
|
||||
|
||||
# store location
|
||||
_locations[name] = path
|
||||
log.debug("registered path to %r handler: %r", name, path)
|
||||
|
||||
def register_crypt_handler(handler, force=False, _attr=None):
|
||||
"""register password hash handler.
|
||||
|
||||
this method immediately registers a handler with the internal passlib registry,
|
||||
so that it will be returned by :func:`get_crypt_handler` when requested.
|
||||
|
||||
:arg handler: the password hash handler to register
|
||||
:param force: force override of existing handler (defaults to False)
|
||||
:param _attr:
|
||||
[internal kwd] if specified, ensures ``handler.name``
|
||||
matches this value, or raises :exc:`ValueError`.
|
||||
|
||||
:raises TypeError:
|
||||
if the specified object does not appear to be a valid handler.
|
||||
|
||||
:raises ValueError:
|
||||
if the specified object's name (or other required attributes)
|
||||
contain invalid values.
|
||||
|
||||
:raises KeyError:
|
||||
if a (different) handler was already registered with
|
||||
the same name, and ``force=True`` was not specified.
|
||||
"""
|
||||
# validate handler
|
||||
if not is_crypt_handler(handler):
|
||||
raise ExpectedTypeError(handler, "password hash handler", "handler")
|
||||
if not handler:
|
||||
raise AssertionError("``bool(handler)`` must be True")
|
||||
|
||||
# validate name
|
||||
name = handler.name
|
||||
_validate_handler_name(name)
|
||||
if _attr and _attr != name:
|
||||
raise ValueError("handlers must be stored only under their own name (%r != %r)" %
|
||||
(_attr, name))
|
||||
|
||||
# check for existing handler
|
||||
other = _handlers.get(name)
|
||||
if other:
|
||||
if other is handler:
|
||||
log.debug("same %r handler already registered: %r", name, handler)
|
||||
return
|
||||
elif force:
|
||||
log.warning("overriding previously registered %r handler: %r",
|
||||
name, other)
|
||||
else:
|
||||
raise KeyError("another %r handler has already been registered: %r" %
|
||||
(name, other))
|
||||
|
||||
# register handler
|
||||
_handlers[name] = handler
|
||||
log.debug("registered %r handler: %r", name, handler)
|
||||
|
||||
def get_crypt_handler(name, default=_UNSET):
|
||||
"""return handler for specified password hash scheme.
|
||||
|
||||
this method looks up a handler for the specified scheme.
|
||||
if the handler is not already loaded,
|
||||
it checks if the location is known, and loads it first.
|
||||
|
||||
:arg name: name of handler to return
|
||||
:param default: optional default value to return if no handler with specified name is found.
|
||||
|
||||
:raises KeyError: if no handler matching that name is found, and no default specified, a KeyError will be raised.
|
||||
|
||||
:returns: handler attached to name, or default value (if specified).
|
||||
"""
|
||||
# catch invalid names before we check _handlers,
|
||||
# since it's a module dict, and exposes things like __package__, etc.
|
||||
if name.startswith("_"):
|
||||
if default is _UNSET:
|
||||
raise KeyError("invalid handler name: %r" % (name,))
|
||||
else:
|
||||
return default
|
||||
|
||||
# check if handler is already loaded
|
||||
try:
|
||||
return _handlers[name]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# normalize name (and if changed, check dict again)
|
||||
assert isinstance(name, unicode_or_str), "name must be string instance"
|
||||
alt = name.replace("-","_").lower()
|
||||
if alt != name:
|
||||
warn("handler names should be lower-case, and use underscores instead "
|
||||
"of hyphens: %r => %r" % (name, alt), PasslibWarning,
|
||||
stacklevel=2)
|
||||
name = alt
|
||||
|
||||
# try to load using new name
|
||||
try:
|
||||
return _handlers[name]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# check if lazy load mapping has been specified for this driver
|
||||
path = _locations.get(name)
|
||||
if path:
|
||||
if ':' in path:
|
||||
modname, modattr = path.split(":")
|
||||
else:
|
||||
modname, modattr = path, name
|
||||
##log.debug("loading %r handler from path: '%s:%s'", name, modname, modattr)
|
||||
|
||||
# try to load the module - any import errors indicate runtime config, usually
|
||||
# either missing package, or bad path provided to register_crypt_handler_path()
|
||||
mod = __import__(modname, fromlist=[modattr], level=0)
|
||||
|
||||
# first check if importing module triggered register_crypt_handler(),
|
||||
# (this is discouraged due to its magical implicitness)
|
||||
handler = _handlers.get(name)
|
||||
if handler:
|
||||
# XXX: issue deprecation warning here?
|
||||
assert is_crypt_handler(handler), "unexpected object: name=%r object=%r" % (name, handler)
|
||||
return handler
|
||||
|
||||
# then get real handler & register it
|
||||
handler = getattr(mod, modattr)
|
||||
register_crypt_handler(handler, _attr=name)
|
||||
return handler
|
||||
|
||||
# fail!
|
||||
if default is _UNSET:
|
||||
raise KeyError("no crypt handler found for algorithm: %r" % (name,))
|
||||
else:
|
||||
return default
|
||||
|
||||
def list_crypt_handlers(loaded_only=False):
|
||||
"""return sorted list of all known crypt handler names.
|
||||
|
||||
:param loaded_only: if ``True``, only returns names of handlers which have actually been loaded.
|
||||
|
||||
:returns: list of names of all known handlers
|
||||
"""
|
||||
names = set(_handlers)
|
||||
if not loaded_only:
|
||||
names.update(_locations)
|
||||
# strip private attrs out of namespace and sort.
|
||||
# TODO: make _handlers a separate list, so we don't have module namespace mixed in.
|
||||
return sorted(name for name in names if not name.startswith("_"))
|
||||
|
||||
# NOTE: these two functions mainly exist just for the unittests...
|
||||
|
||||
def _has_crypt_handler(name, loaded_only=False):
|
||||
"""check if handler name is known.
|
||||
|
||||
this is only useful for two cases:
|
||||
|
||||
* quickly checking if handler has already been loaded
|
||||
* checking if handler exists, without actually loading it
|
||||
|
||||
:arg name: name of handler
|
||||
:param loaded_only: if ``True``, returns False if handler exists but hasn't been loaded
|
||||
"""
|
||||
return (name in _handlers) or (not loaded_only and name in _locations)
|
||||
|
||||
def _unload_handler_name(name, locations=True):
|
||||
"""unloads a handler from the registry.
|
||||
|
||||
.. warning::
|
||||
|
||||
this is an internal function,
|
||||
used only by the unittests.
|
||||
|
||||
if loaded handler is found with specified name, it's removed.
|
||||
if path to lazy load handler is found, it's removed.
|
||||
|
||||
missing names are a noop.
|
||||
|
||||
:arg name: name of handler to unload
|
||||
:param locations: if False, won't purge registered handler locations (default True)
|
||||
"""
|
||||
if name in _handlers:
|
||||
del _handlers[name]
|
||||
if locations and name in _locations:
|
||||
del _locations[name]
|
||||
|
||||
#=============================================================================
|
||||
# inspection helpers
|
||||
#=============================================================================
|
||||
|
||||
#------------------------------------------------------------------
|
||||
# general
|
||||
#------------------------------------------------------------------
|
||||
|
||||
# TODO: needs UTs
|
||||
def _resolve(hasher, param="value"):
|
||||
"""
|
||||
internal helper to resolve argument to hasher object
|
||||
"""
|
||||
if is_crypt_handler(hasher):
|
||||
return hasher
|
||||
elif isinstance(hasher, unicode_or_str):
|
||||
return get_crypt_handler(hasher)
|
||||
else:
|
||||
raise exc.ExpectedTypeError(hasher, unicode_or_str, param)
|
||||
|
||||
|
||||
#: backend aliases
|
||||
ANY = "any"
|
||||
BUILTIN = "builtin"
|
||||
OS_CRYPT = "os_crypt"
|
||||
|
||||
# TODO: needs UTs
|
||||
def has_backend(hasher, backend=ANY, safe=False):
|
||||
"""
|
||||
Test if specified backend is available for hasher.
|
||||
|
||||
:param hasher:
|
||||
Hasher name or object.
|
||||
|
||||
:param backend:
|
||||
Name of backend, or ``"any"`` if any backend will do.
|
||||
For hashers without multiple backends, will pretend
|
||||
they have a single backend named ``"builtin"``.
|
||||
|
||||
:param safe:
|
||||
By default, throws error if backend is unknown.
|
||||
If ``safe=True``, will just return false value.
|
||||
|
||||
:raises ValueError:
|
||||
* if hasher name is unknown.
|
||||
* if backend is unknown to hasher, and safe=False.
|
||||
|
||||
:return:
|
||||
True if backend available, False if not available,
|
||||
and None if unknown + safe=True.
|
||||
"""
|
||||
hasher = _resolve(hasher)
|
||||
|
||||
if backend == ANY:
|
||||
if not hasattr(hasher, "get_backend"):
|
||||
# single backend, assume it's loaded
|
||||
return True
|
||||
|
||||
# multiple backends, check at least one is loadable
|
||||
try:
|
||||
hasher.get_backend()
|
||||
return True
|
||||
except exc.MissingBackendError:
|
||||
return False
|
||||
|
||||
# test for specific backend
|
||||
if hasattr(hasher, "has_backend"):
|
||||
# multiple backends
|
||||
if safe and backend not in hasher.backends:
|
||||
return None
|
||||
return hasher.has_backend(backend)
|
||||
|
||||
# single builtin backend
|
||||
if backend == BUILTIN:
|
||||
return True
|
||||
elif safe:
|
||||
return None
|
||||
else:
|
||||
raise exc.UnknownBackendError(hasher, backend)
|
||||
|
||||
#------------------------------------------------------------------
|
||||
# os crypt
|
||||
#------------------------------------------------------------------
|
||||
|
||||
# TODO: move unix_crypt_schemes list to here.
|
||||
# os_crypt_schemes -- alias for unix_crypt_schemes above
|
||||
|
||||
|
||||
# TODO: needs UTs
|
||||
@memoize_single_value
|
||||
def get_supported_os_crypt_schemes():
|
||||
"""
|
||||
return tuple of schemes which :func:`crypt.crypt` natively supports.
|
||||
"""
|
||||
if not os_crypt_present:
|
||||
return ()
|
||||
cache = tuple(name for name in os_crypt_schemes
|
||||
if get_crypt_handler(name).has_backend(OS_CRYPT))
|
||||
if not cache: # pragma: no cover -- sanity check
|
||||
# no idea what OS this could happen on...
|
||||
import platform
|
||||
warn("crypt.crypt() function is present, but doesn't support any "
|
||||
"formats known to passlib! (system=%r release=%r)" %
|
||||
(platform.system(), platform.release()),
|
||||
exc.PasslibRuntimeWarning)
|
||||
return cache
|
||||
|
||||
|
||||
# TODO: needs UTs
|
||||
def has_os_crypt_support(hasher):
|
||||
"""
|
||||
check if hash is supported by native :func:`crypt.crypt` function.
|
||||
if :func:`crypt.crypt` is not present, will always return False.
|
||||
|
||||
:param hasher:
|
||||
name or hasher object.
|
||||
|
||||
:returns bool:
|
||||
True if hash format is supported by OS, else False.
|
||||
"""
|
||||
return os_crypt_present and has_backend(hasher, OS_CRYPT, safe=True)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
Reference in New Issue
Block a user