Загрузить файлы в «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